Node.js で メールをいっぱい作る

「システム化するほどではないけど、手っ取り早く メールの下書きをたくさん作りたいな〜、 Node で。」って時の Tips です。一応 Mac 限定だけど、 Linux でもできる場合もあるかもです。

mailto:user@example.com スキームを open コマンドで開きます。
const mails = [
  {
    to: 'user1@example.com',
    sub: 'タイトル',
    body: 'これは本文です。'
  },
  {
    to: 'user2@example.com',
    sub: 'タイトル',
    body: 'これも本文です。'
  }
]

const { promisify } = require('util')
const { exec } = require('child_process')
const execPromise = promisify(exec)

Promise.all(
  mails.map(({ to, sub, body }) =>
    execPromise(`open "mailto:${to}?Subject=${sub}&Body=${body}"`)
  )
)
  .then(() => console.log('OK!'))
  .catch(() => console.error('Error..'))

これで、デフォルトのメーラーでメールの下書きがどどっと開くはず!

AWS CLI で Amazon SQS の機能を試す

AWS CLI から Amazon SQS のキューを作ったりメッセージを送ったり受信したりしてみます。シンプルなサービスなので、CLI からの操作も簡単ですね。

以下ではメッセージを受信して何かの仕事をするサービスをワーカー、メッセージを送信して何かの仕事を依頼するサービスをクライアントと表記して例示します。

AWS CLI 環境のセットアップ

AWS CLI をインストールし、Amazon SQS へのアクセス権のあるクレデンシャルを用意する必要があります。

キューの作成

メッセージ受信待機時間を最大の20秒に設定してキューを作成します。これはあとでロングポーリングを試すためです。

$ aws sqs create-queue --queue-name my-test-queue --attributes ReceiveMessageWaitTimeSeconds=20
{
    "QueueUrl": "https://ap-northeast-1.queue.amazonaws.com/000000000000/my-test-queue"
}

作成したキューを含むキューの一覧を確認する

$ aws sqs list-queues
{
    "QueueUrls": [
        "https://ap-northeast-1.queue.amazonaws.com/000000000000/my-test-queue"
    ]
}

できているようです。URL を取得しておきます。

$ QUEUE_URL=$(aws sqs get-queue-url --queue-name my-test-queue |  npx jqf --raw-string-output 'x => x.QueueUrl')

メッセージを受信する

メッセージが空の状態で受信してみます。ワーカーがメッセージの受信を待っている状況を想定しています。

$ aws sqs receive-message --queue-url $QUEUE_URL

20秒待って、何も表示せずに終わります。メッセージの受信を待っている状態なので、期待通りの挙動です。

メッセージを送信する

作成したキューにメッセージを送信します。クライアントが仕事を依頼する状況を想定しています。

$ aws sqs send-message --queue-url $QUEUE_URL --message-body '{"hello":"SQS"}'
{
    "MD5OfMessageBody": "23759ae80d00f2b3e9c5eb026b74fdd8",
    "MessageId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

メッセージを受信する

作成したメッセージを受信してみます。ワーカーが実際に仕事の依頼を受け付ける状況を想定しています。

$ aws sqs receive-message --queue-url $QUEUE_URL
{
    "Messages": [
        {
            "MessageId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "ReceiptHandle": "<base 64 value>",
            "MD5OfBody": "23759ae80d00f2b3e9c5eb026b74fdd8",
            "Body": "{\"hello\":\"SQS\"}"
        }
    ]
}

送信ずみのメッセージが受信できました。コンソールで確認すると、メッセージのステータスが「利用可能」から「処理中」になっています。

メッセージを削除する

メッセージの受信の際に得られた ReceptHandle の値を送信すると削除できます。ワーカーが仕事を終えた状況を想定しています。

$ aws sqs delete-message --receipt-handle "<base 64 value>" --queue-url $QUEUE_URL

ロングポーリング中にメッセージを受信する

それでは 2つのターミナルを開いて、メッセージの送信と受付をシミュレートしてみます。メッセージを通してクライアントがワーカーに仕事を依頼する一般的なシチュエーションを想定しています。

それぞれのターミナルで、QUEUE_URL 正しい URL の値が代入されていることを確認してください。

$ QUEUE_URL=$(aws sqs get-queue-url --queue-name my-test-queue |  npx jqf --raw-string-output 'x => x.QueueUrl')
$ echo $QUEUE_URL
https://ap-northeast-1.queue.amazonaws.com/000000000000/my-test-queue

以下の2つのコマンドをそれぞれのターミナルに順番に入力します。

# ターミナルA
$ aws sqs receive-message --queue-url $QUEUE_URL

# 数秒待つ。20秒待つとポーリングが終わってしまうので注意

# ターミナルB
$ aws sqs send-message --queue-url $QUEUE_URL --message-body '{"hello":"SQS"}'

正しく入力できていれば、ターミナルB にコマンドを入力した瞬間にターミナルA でメッセージが受信されるはずです。

Animated GIF - Find & Share on GIPHY

キューを削除する

最後に CLI からキューを削除します。

$ aws sqs delete-queue --queue-url $QUEUE_URL

AWS の IAM ユーザーに請求ダッシュボードへのアクセスを許可する

ルートユーザーではなく、特定の IAM ユーザーに、請求ダッシュボードへのアクセスを許可する方法です。以下の手順の一部はルートユーザーで操作する必要があります。

請求情報への IAM ユーザーアクセスのアクティベート

IAM ユーザーに billing ポリシーを付与する前にこの作業を行う必要があります。まずはアカウントメニューから “My Account” を選択して遷移します。

ページの中から IAM User and Role Access to Billing Information (IAM ユーザーとロールの請求情報へのアクセス) を探して..

Activate IAM Access (IAM アクセスを有効化する)にチェックを入れて更新します。

IAM ユーザーに billing のポリシーを付与する

後は IAM のメニューから billing のポリシーをアタッチするだけです。許可を与えたい IAM ユーザーの詳細を開いて、

既存のポリシーの中から billing ポリシーを検索してアタッチします。

アクセスできました。

CLI からもアクセスしてみます。AWS CLI の API リファレンスはここ。以前はアクセスできませんでしたが、

$ aws ce get-cost-and-usage \
  --granularity MONTHLY \
  --time-period Start=2018-01-01,End=2019-01-01 \
  --metrics BlendedCost

An error occurred (AccessDeniedException) when calling the GetCostAndUsage operation: User: arn:aws:iam::xxxxxxxxxxxx:user/your-cli-user is not authorized to perform: ce:GetCostAndUsage on resource: arn:aws:ce:us-east-1:xxxxxxxxxxxx:/GetCostAndUsage

アクセスできるようになりました。

$ aws ce get-cost-and-usage \
  --granularity MONTHLY \
  --time-period Start=2018-01-01,End=2019-01-01 \
  --metrics BlendedCost
{
    "ResultsByTime": [
        {
            "TimePeriod": {
                "Start": "2018-01-01",
                "End": "2018-02-01"
            },
            "Total": {
                "BlendedCost": {
                    "Amount": "100.0000000000",
                    "Unit": "USD"
                }
            },
            "Groups": [],
            "Estimated": false
        },
        {
            "TimePeriod": {
                "Start": "2018-02-01",
                "End": "2018-03-01"
            },
            "Total": {
                "BlendedCost": {
                    "Amount": "100.0000000000",
                    "Unit": "USD"
                }
            },
            "Groups": [],
            "Estimated": false
        },
        {
            "TimePeriod": {
                "Start": "2018-03-01",
                "End": "2018-04-01"
            },
            "Total": {
                "BlendedCost": {
                    "Amount": "100.0000000000",
                    "Unit": "USD"
                }
            },
            "Groups": [],
            "Estimated": false
        },
        {
            "TimePeriod": {
                "Start": "2018-04-01",
                "End": "2018-05-01"
            },
            "Total": {
                "BlendedCost": {
                    "Amount": "100.0000000000",
                    "Unit": "USD"
                }
            },
            "Groups": [],
            "Estimated": false
        },
        {
            "TimePeriod": {
                "Start": "2018-05-01",
                "End": "2018-06-01"
            },
            "Total": {
                "BlendedCost": {
                    "Amount": "100.0000000000",
                    "Unit": "USD"
                }
            },
            "Groups": [],
            "Estimated": false
        },
        {
            "TimePeriod": {
                "Start": "2018-06-01",
                "End": "2018-07-01"
            },
            "Total": {
                "BlendedCost": {
                    "Amount": "100.0000000000",
                    "Unit": "USD"
                }
            },
            "Groups": [],
            "Estimated": false
        },
        {
            "TimePeriod": {
                "Start": "2018-07-01",
                "End": "2018-08-01"
            },
            "Total": {
                "BlendedCost": {
                    "Amount": "100.0000000000",
                    "Unit": "USD"
                }
            },
            "Groups": [],
            "Estimated": false
        },
        {
            "TimePeriod": {
                "Start": "2018-08-01",
                "End": "2018-09-01"
            },
            "Total": {
                "BlendedCost": {
                    "Amount": "100.0000000000",
                    "Unit": "USD"
                }
            },
            "Groups": [],
            "Estimated": false
        },
        {
            "TimePeriod": {
                "Start": "2018-09-01",
                "End": "2018-10-01"
            },
            "Total": {
                "BlendedCost": {
                    "Amount": "100.0000000000",
                    "Unit": "USD"
                }
            },
            "Groups": [],
            "Estimated": false
        },
        {
            "TimePeriod": {
                "Start": "2018-10-01",
                "End": "2018-11-01"
            },
            "Total": {
                "BlendedCost": {
                    "Amount": "100.0000000000",
                    "Unit": "USD"
                }
            },
            "Groups": [],
            "Estimated": false
        },
        {
            "TimePeriod": {
                "Start": "2018-11-01",
                "End": "2018-12-01"
            },
            "Total": {
                "BlendedCost": {
                    "Amount": "100.0000000000",
                    "Unit": "USD"
                }
            },
            "Groups": [],
            "Estimated": false
        },
        {
            "TimePeriod": {
                "Start": "2018-12-01",
                "End": "2019-01-01"
            },
            "Total": {
                "BlendedCost": {
                    "Amount": "100.0000000000",
                    "Unit": "USD"
                }
            },
            "Groups": [],
            "Estimated": false
        }
    ]
}

やったね!

Microsoft Azure Web Apps で利用可能なランタイムを列挙する

$ az webapp list-runtimes --linux
[
  "RUBY|2.3",
  "NODE|lts",
  "NODE|4.4",
  "NODE|4.5",
  "NODE|4.8",
  "NODE|6.2",
  "NODE|6.6",
  "NODE|6.9",
  "NODE|6.10",
  "NODE|6.11",
  "NODE|8.0",
  "NODE|8.1",
  "NODE|8.2",
  "NODE|8.8",
  "NODE|8.9",
  "NODE|8.11",
  "NODE|8.12",
  "NODE|9.4",
  "NODE|10.1",
  "NODE|10.10",
  "PHP|5.6",
  "PHP|7.0",
  "PHP|7.2",
  "DOTNETCORE|1.0",
  "DOTNETCORE|1.1",
  "DOTNETCORE|2.0",
  "DOTNETCORE|2.1",
  "TOMCAT|8.5-jre8",
  "TOMCAT|9.0-jre8",
  "JAVA|8-jre8",
  "WILDFLY|14-jre8",
  "PYTHON|3.7",
  "PYTHON|3.6",
  "PYTHON|2.7"
]

上記のコマンドで、 CLI から利用可能ならんタイムを列挙することができます。Node 10 があるのが嬉しいですね。

Microsoft Azure の仮想マシンを CLI で操作する

Azure を使ってみたメモ。

ユーザー登録

https://azure.microsoft.com/ja-jp/free/
以下のものが必要です。最初に ¥20,500 の体験枠がもらえます。アカウントをアップグレードしないと請求されることはないとのこと。

  • クレジットカード
  • 電話番号

Azure CLI のインストールとログイン

Azure CLI をドキュメントのとおりにインストールします。Mac ならこんな感じ。

$ brew update && brew install azure-cli

Azure CLI でログインします。

$ az login
Note, we have launched a browser for you to login. For old experience with device code, use "az login --use-device-code"
You have logged in. Now let us find all the subscriptions to which you have access...

OAuth を使ってブラウザ経由でログインできます。

ログインに成功すると、ログイン情報が吐かれてログインできたことが分かります。

$ az login
Note, we have launched a browser for you to login. For old experience with device code, use "az login --use-device-code"
You have logged in. Now let us find all the subscriptions to which you have access...
[
  {
    "cloudName": "AzureCloud",
    "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "isDefault": true,
    "name": "無料試用版",
    "state": "Enabled",
    "tenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "user": {
      "name": "user@example.com",
      "type": "user"
    }
  }
]

ロケーション

Azure ではロケーションと呼ばれる地域を指定してリソースを作成するようです。まずロケーションの一覧を取得してみます。緯度経度も表示されてなんか嬉しいですね。

$ az account list-locations
[
  ...
  {
    "displayName": "Japan West",
    "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/locations/japanwest",
    "latitude": "34.6939",
    "longitude": "135.5022",
    "name": "japanwest",
    "subscriptionId": null
  },
  {
    "displayName": "Japan East",
    "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/locations/japaneast",
    "latitude": "35.68",
    "longitude": "139.77",
    "name": "japaneast",
    "subscriptionId": null
  },
  ...
]

全部の地域のリストはこちら。

$ az account list-locations | npx jqf 'locations =>
     locations.reduce((prev, { name, displayName }) => ({ ...prev, [name]: displayName }), {})
> '
{
  "eastasia": "East Asia",
  "southeastasia": "Southeast Asia",
  "centralus": "Central US",
  "eastus": "East US",
  "eastus2": "East US 2",
  "westus": "West US",
  "northcentralus": "North Central US",
  "southcentralus": "South Central US",
  "northeurope": "North Europe",
  "westeurope": "West Europe",
  "japanwest": "Japan West",
  "japaneast": "Japan East",
  "brazilsouth": "Brazil South",
  "australiaeast": "Australia East",
  "australiasoutheast": "Australia Southeast",
  "southindia": "South India",
  "centralindia": "Central India",
  "westindia": "West India",
  "canadacentral": "Canada Central",
  "canadaeast": "Canada East",
  "uksouth": "UK South",
  "ukwest": "UK West",
  "westcentralus": "West Central US",
  "westus2": "West US 2",
  "koreacentral": "Korea Central",
  "koreasouth": "Korea South",
  "francecentral": "France Central",
  "francesouth": "France South",
  "australiacentral": "Australia Central",
  "australiacentral2": "Australia Central 2"
}

リソースグループ

ロケーションを指定してリソースグループを作成します。これから作成する仮想マシンなどは、このリソースグループに属するようです。

$ az group create --name azure-tutorial --location japanwest
{
  "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/azure-tutorial",
  "location": "japanwest",
  "managedBy": null,
  "name": "azure-tutorial",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": null
}

作ったらリストするコマンドで取得できるか確認してみます。

$ az group list
[
  {
    "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/azure-tutorial",
    "location": "japanwest",
    "managedBy": null,
    "name": "azure-tutorial",
    "properties": {
      "provisioningState": "Succeeded"
    },
    "tags": null
  }
]

ついでに削除のコマンドも確認してみます。削除には数十秒かかるようです。

$ az group delete --name azure-tutorial --yes
# やや時間がかかる。 - Running.. と表示される
$ az group list
[]

仮想マシンを作成する

リソースグループなどを指定して仮想マシンを作成します。まずは指定可能な仮想マシンのイメージのリストを確認してみます。

$ az vm image list
You are viewing an offline list of images, use --all to retrieve an up-to-date list
[
  ...
  {
    "offer": "UbuntuServer",
    "publisher": "Canonical",
    "sku": "16.04-LTS",
    "urn": "Canonical:UbuntuServer:16.04-LTS:latest",
    "urnAlias": "UbuntuLTS",
    "version": "latest"
  },
  ...
]

ちなみにローカルで取得できる仮想マシンのリストはこんな感じ。 –all オプションで全部のイメージの情報を取得するのはリクエスト時間が長すぎて諦めました。

$ az vm image list | npx jqf 'x => x.map(y => y.offer + " " +  y.sku)'
You are viewing an offline list of images, use --all to retrieve an up-to-date list
[
  "CentOS 7.5",
  "CoreOS Stable",
  "Debian 8",
  "openSUSE-Leap 42.3",
  "RHEL 7-RAW",
  "SLES 12-SP2",
  "UbuntuServer 16.04-LTS",
  "WindowsServer 2019-Datacenter",
  "WindowsServer 2016-Datacenter",
  "WindowsServer 2012-R2-Datacenter",
  "WindowsServer 2012-Datacenter",
  "WindowsServer 2008-R2-SP1"
]

仮想マシンを作成します。SSH 公開鍵についてはここにドキュメントがあります。–generate-ssh-keys オプションを指定すると、 ~/.ssh/id_rsa がない場合は鍵ペアを作るようです。

$ az vm create --resource-group azure-tutorial \
    --name azure-turorial-vm-1 \
    --image UbuntuLTS \
    --generate-ssh-keys \
    --output json \
    --verbose
SSH key files '/Users/kamataryo/.ssh/id_rsa' and '/Users/kamataryo/.ssh/id_rsa.pub' have been generated under ~/.ssh to allow SSH access to the VM. If using machines without permanent storage, back up your keys to a safe location.
Accepted: vm_deploy_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (Microsoft.Resources/deployments)
Succeeded: azure-turorial-vm-1PublicIP (Microsoft.Network/publicIPAddresses)
Succeeded: azure-turorial-vm-1VMNic (Microsoft.Network/networkInterfaces)
Succeeded: azure-turorial-vm-1NSG (Microsoft.Network/networkSecurityGroups)
Accepted: azure-turorial-vm-1 (Microsoft.Compute/virtualMachines)
Accepted: azure-turorial-vm-1_OsDisk_1_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (Microsoft.Compute/disks)
Accepted: azure-turorial-vm-1 (Microsoft.Compute/virtualMachines)
{
  "fqdns": "",
  "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/azure-tutorial/providers/Microsoft.Compute/virtualMachines/azure-turorial-vm-1",
  "location": "japanwest",
  "macAddress": "xx-xx-xx-xx-xx-xx",
  "powerState": "VM running",
  "privateIpAddress": "10.0.0.4",
  "publicIpAddress": "xx.xx.xx.xx",
  "resourceGroup": "azure-tutorial",
  "zones": ""
}
Suppress exception No module named 'azure.cli.telemetry'

ちなみに SSH キーの形式は RSA しかサポートされていないそうです。

$ az vm create --resource-group azure-tutorial \
    --name azure-turorial-vm-1 \
    --image UbuntuLTS \
    --ssh-key-value "$(cat ~/.ssh/id_ecdsa.pub)" \
    --output json \
    --verbose
Succeeded: azure-turorial-vm-1NSG (Microsoft.Network/networkSecurityGroups)
Accepted: vm_deploy_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (Microsoft.Resources/deployments)
Succeeded: azure-turorial-vm-1PublicIP (Microsoft.Network/publicIPAddresses)
Succeeded: azure-turorial-vm-1VMNic (Microsoft.Network/networkInterfaces)
Failed: vm_deploy_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (Microsoft.Resources/deployments)
Failed: azure-turorial-vm-1 (Microsoft.Compute/virtualMachines)
Deployment failed. Correlation ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. {
  "error": {
    "code": "InvalidParameter",
    "message": "The value of parameter linuxConfiguration.ssh.publicKeys.keyData is invalid.",
    "target": "linuxConfiguration.ssh.publicKeys.keyData"
  }
}
Suppress exception No module named 'azure.cli.telemetry'

publicIpAddress の値を使って SSH してみます。

$ ssh xx.xx.xx.xx
Welcome to Ubuntu 16.04.5 LTS (GNU/Linux 4.15.0-1036-azure x86_64)

kamataryo@azure-turorial-vm-1:~$

できました!ユーザーも自分の名前で勝手に作成してくれるようですね。SSH が楽なのでいい感じです!

仮想マシンを削除する

仮想マシンをリストコマンドで確認してから、削除します。

$ az vm list | npx jqf 'x => x.map(y => y.name + ": " + y.id)'
[
  "azure-turorial-vm-1: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/AZURE-TUTORIAL/providers/Microsoft.Compute/virtualMachines/azure-turorial-vm-1"
]
$ az vm delete --resource-group azure-tutorial --name azure-turorial-vm-1 --yes

$ az vm list
[]

まとめ

一度もコンソールを表示させずに、仮想マシンを立ち上げてログインし、削除することができました。やったね!

高雄でワーケーションしてきた話

12月の中頃に台湾の高雄で避寒&ワーケーションしてきました。3泊滞在しました。

コスト

現地に着いてからほとんど移動せず、観光もしないでゆっくりしていたので、ほとんどお金がかからなかったです。合計で40,000円弱くらいかな。

項目概要金額
電車米原 – 関西国際空港、往復、特急はるか使用¥7,780
航空券ピーチ航空、シンプルピーチ¥20,880
宿泊ホテル1泊、ドミトリー2泊¥4,000くらい
コワーキングスペースDAKUO100元
Uber空港 -> ホテル220元くらい
SIM5日分300元
その他の現地の生活費など12000円くらい

パッキング

機内持ち込みの手荷物だけに絞ったのでスイスイでした。持ち物はこんなです。

  • パスポート
  • SIMピン、常備薬
  • 家の鍵とか財布
  • ラップトップ一式
  • スマートフォン
  • モバイルバッテリー
  • 着替え一揃え
  • カバン

高雄で移動する

高雄は地下鉄が便利で、かつ安くて、20元〜30元(72〜108円)で乗ることができます。空港にも駅があるので、Uber を使う必要はなかったです。最初に止まった塩埕埔にあるホテルは、なんかディストピアな感じ..

塩埕埔の近くは朝食のお店が多かったです。虱目魚粥が美味しかった!サバヒー(虱目魚)を食べるのは今回の旅行の目的の一つだったのです。骨が全くなくて、味も薄味で食べやすかったです。

高雄のコワーキングスペース

高雄にもコワーキングスペースがありました。
DAKUO 創業夢想前哨站 http://www.dakuo.co/
スタッフが中を案内してくれて、色々説明してくれました。政府のテコが入っていて、ゲーム制作会社などが多く入居しているそうです。ドロップインは1日100元 (360円くらい)でかなり安めです。
帰りがけに創業セミナー的なものが催されているのを見かけました。

美麗島駅周辺と夜市

ホテルが今ひとつだったので、ドミトリータイプのホテルに移動しました。美麗島駅というステンドガラスの駅舎で有名な地下鉄駅のそばにあるユースホステル的なホテルで、6階に位置しているので眺めもよく、作業したりするのにも快適なホテルでした。

旅聚居青年旅舍
http://www.tripgg.com.tw/

六合観光夜市という有名な夜市が美麗島駅の近くにあって、ホテルからすぐ歩いて行けます。

まとめ

こんな感じの食い倒れ旅行でしたけど、夜はチャットで打ち合わせをしたり、転職活動したり、割とコードも書いて、なかなか充実した4日間でしたよ。

西表島でワーケーションしてきた話

11月に沖縄の西表島でワーケーションしてきました。LCC (格安航空会社) ってちゃんと価格を検討して使ったことがなかったので、どんなもんかと値段を調べてみたところ、思ったより安くて、割とカジュアルに沖縄とか行っていいんじゃない?と思ったのがきっかけです。

今回使ったのはピーチ航空です。航空券以外も含めたコストはこんな感じ。4泊で合計50,000円くらい。

項目概要値段
電車移動米原 – 関西国際空港、往復、特急はるか使用¥7,780
航空券関西国際空港 – 石垣空港、往復¥22,030
機内預け荷物追加手数往復¥3,000 くらい
バス代石垣空港 – 離島ターミナル、往復¥1,000 くらい
船代石垣 – 大原(西表島)、往復
¥3,440
タクシー代大原 – 南風見田の浜、往復¥4,000 くらい
キャンプ場代南風見田の浜キャンプ場、2泊¥1,000
コワーキングスペース利用料竹富町 Painushima Share ドロップイン利用、2日分¥200
ホテル宿泊ゲストハウスちゅらくくる石垣島、2泊¥7500 くらい
食費何回か自炊

航空券は安かった

ピーチ航空の航空券は、機内預け荷物の数とかでグレードがあるんですね。今回は銛突きセットとかキャンプセットをフル装備で持って行ったので、機内預け荷物が2個になっちゃいました。手荷物だけにして、平日に行って、次の週の平日に帰ってくる、みたいなプランを組めれば 15,000円くらいまで切り詰められるはず!

ただ、関空出発が 朝 7:30 なのがなかなか辛くて、日程が合わなかったので空港の待合室で1泊しました。

石垣島のゲストハウスで前泊

今回はキャンプの準備があったので、余裕を持って前泊してみましたが、準備はすぐ済んだので、要らなかったかな?
ゲストハウスは綺麗でした。カプセルホテル型のドミトリーなんですが、2階建てになってない部屋があって、運よくそこが割り当てられました。個室で立ち上がれるって素晴らしい。

南風見田の浜キャンプ場で生活基盤を作る

かつてはヒッピーの集積地だった南風見田の浜キャンプ場。季節柄か、利用者は自分だけでした。ここは沢水しかないので、飲み水は一旦沸かさないといけないです。山には獣もいるので、汚染されているかもしれません。昔行った時はそのまま飲んでた気がするな..

食べ物をカラスとか獣に取られたりするので、しっかりパッキングしたりする必要があります。ハブとかサソリモドキに気をつけましょう。あと、沢の水の水シャワーです。

電気ありません。電波も入りにくいです。

どこで働くか

キャンプ場は文明がないので、コワーキングスペース(!)に移動しましょう。

竹富町(西表島の地方自治体)がテコ入れしているコワーキングスペースが大原にあります。竹富町外の人はドロップイン1日100円で利用できます。

キャンプ場から6km くらいで、私はキャンプ場のレンタル自転車で移動しました。道中では、カンムリワシとか、牛とか、ヤギ牧場とか、色々見れます。

ご飯とか

キャンプ場は炊事場があるので、ガスコンロやクッキングギアがなくても最悪なんとかなります。コワーキングスペースすぐそばの玉盛スーパーで色々買い込んで、キャンプ場で楽しく飯盒炊爨ができますよ。ちなみに玉盛スーパーではQUIC PAYとかiDが使えたので会計が楽です。
キャンプで使うガス缶は石垣港の釣具屋さん(中村つりぐ店)で買うと便利です。帰りがてら、使い切った缶ゴミを引き取ってくれます。銛とかエビ網とか、面白いものもたくさん売ってます。

夜はいろんな生き物がギョギョギョみたいに鳴いてるのでそれを楽しんだりしました。ヤエヤマオオコウモリも結構飛んでいた。

その他(ご飯や観光とか)

ワーケーションなので、現地のご飯を食べたり、遊んだりしてリフレッシュしてきました。

西表島いいよ!

WP-CLI で全ての投稿のコメントを閉じる

全ての post_id を取得して、comment_status を closed にするだけ。カンタン!


$ wp post list --field=ID | xargs -I {} wp post update {} --comment_status=closed

ついでにスパムコメントも全部消しちゃおう。
NOTE: このコマンドは全てのコメントを消すので、消したくないコメントがある人は実行しないでね。


$ wp comment list --field=comment_ID | xargs -I {} wp comment delete {} --force

自己紹介モジュールと npm 名前空間汚染について

先日香川県高松市で開催されたフロントエンドの勉強会で、小ネタのLTをしてきました。

$ npx kamataryo などとコマンドを打つと、自己紹介が標準出力に吐かれるシンプルなプログラムについてです。

このエントリでは Hello モジュールと呼びます。

このプログラム自体はほとんど意味はありませんが、 Node パッケージを初めて作って publish するような教育目的に適していると思い、話題提供をしました。

Node パッケージのスター作者 Sindre Sorhus 氏が端緒となって、国内外でいろんな人が Hello モジュールを作成しています。

 

自己紹介モジュールの存在で npm の名前空間が汚染されてしまうことの是非について少し考えたのでエントリとしてまとめます。

まず、グローバルな npm の名前空間に kamataryo が存在することですが、 ユーザー名の kamataryo は一般的な名詞ではないですし、今後 kamataryo という名前のパッケージが登場する見込みもないと思いますので、そこまでマナーが悪くはないかと思います。ただ、もっとシンプルなユーザー名を使っている人は注意した方が良さそうですね。特に既存の製品と同一のユーザー名の方がいるならば、係争の原因になりかねません。そもそもユーザー名としても難がありそう。

scoped package にすると、より治安が良くなりそうです。その場合は、 @kamataryo/aboutme とかでしょうか。ただし冗長な感は否めません。

ご自身のユーザー名でブランディングに成功している人は見せ方にこだわりを発揮されるでしょう。

特にこれと言って結論や主張はありませんが、この Hello モジュールの流行によって、ユーザー名の名前空間と npm の名前空間が衝突しつつあるのを観測している状況なので、そんなことを書いておきます。

Flow の Union Type をキャストする

Flow の suppress_comment を使って、 Union Type の強制キャストっぽいことをしてみます。
Redux の Reducer を定義する時にを例にとっています。
action から payload の値を抽出するのに3行を費やしてしまいますが、 Prettier などで破壊されないのでいい感じ。


? .flowconfig
[options]
suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe

// reducer.js
type Action =
  SubAction1 |
  SubAction2 |
  SubAction3

const reducer = (state: State, action: Action): State => {
  const { type } = action
  switch (type) {
     case: SUB_ACTION1:
       const { payload } = // SubAction1 として型チェックされて嬉しい
         // $FlowFixMe
         (action: SubAction1) // ここのキャストにはエラーが含まれるが suppress_comment により無視される
       return update1(state, payload)
     
     case: SUB_ACTION2:
       const { payload } =
         // $FlowFixMe
         (action: SubAction2)
       return update2(state, payload)

     case: SUB_ACTION3:
       const { payload } =
         // $FlowFixMe
         (action: SubAction3)
       return update3(state, payload)

     default:
       return state
  }
}