よしたく blog

ほぼ週刊で記事を書いています

VS Code上でリモートファイルを操作するSSH FSを使ってみた

VS Code上でRaspberry PiやJetsonなどのリモート環境にあるファイルを操作するときに使える「SSH FS」を使ってみた。 設定ファイルの簡単な編集ならばSSHでログインしvimを使って編集していたが、ガッツリとスクリプトファイルを書くタイミングになったときに、普段使っているVS Codeを使いたかったのでプラグインを探した。

github.com

marketplace.visualstudio.com

インストール

VS Codeプラグイン機能なので、VS CodeのExtensionsで検索してインストールボタンを押すだけだった。 Extensionsに「SSH FS」と検索すると一番上に出てくる。今回の記事でのバージョンは1.16.3だった。

接続先設定

SSH FS」のインストールが完了すると、エクスプローラーの最下部に「SSH FILE SYSTEMS」が出現している。

f:id:yoshitaku_jp:20190816122448p:plain

「表示」→「コマンドパレット」をクリックし、「SSH FS: Create a SSH FS configuration」を実行する。

f:id:yoshitaku_jp:20190816122653p:plain

Create new configuration

Create new configuration画面で接続先の名前を設定する。

各種設定

次の4つが設定されていれば、最初に例にしたRaspberry PiやJetsonはすぐに接続できる。ここでは特別解説することはしない。 - Host - Port - Username - Password

その他の設定

上記以外に現時点でよく使う設定の説明を載せておく。

  • Label
  • エクスプローラーの最下部の「SSH FILE SYSTEMS」などの表示名を変更できる。指定しない場合はnameに設定した値が表示される。
  • Group
  • エクスプローラーの最下部の「SSH FILE SYSTEMS」などの表示名をグループでまとめることが出来る。「社内デバイス」「クラウド」といった分け方や、「raspberry pi」「jetson」といったデバイスごとの分け方が考えられる。
  • Root
  • 接続したときにどこのフォルダを開くかを設定する。自分は「/home/pi」を指定することが多い。

まとめ

インストールから設定、接続まで5分かからず完了した。すぐに使えて、設定も難しくないのが好感が持てた。Visual Studio Code Remote - SSHというMicrosoftが作っているものもあるが、まだ0.45.5でプレビューとなっているので今回は見送った。正式リリースとなったら、こちらも使ってみたい。

じぶん Release Notes 0.29.3

f:id:yoshitaku_jp:20190809171314p:plain 7月1日〜30日のあいだの出来事がリリースされました。

技術・開発関連

技術面での取り組みは以下の通りです。

  • Nginxでリバースプロキシを導入しました
  • Node-REDでMakerFaireTokyo出展物の作成をしました

イベント

参加したイベントは以下の通りです。

読んだ

  • インフラ/ネットワークエンジニアのためのネットワーク技術&設計入門
  • 現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法

買ったもの

主要な買い物は下記の通りです。 - 今月は特にありません。

ブログ

リリースノートを除き、次のエントリを書きました。

PV数

7月は3879でした

f:id:yoshitaku_jp:20190809171003p:plain

仕事

  • 部署が変わり出張が多くなりました
  • Azureを多く触ることが多くなりました
  • お客様と接する機会が多くなりました

KPT

K

  • 青森・秋田・山形へ行った
  • ソファを買った
  • 業務を楽しくこなせている
  • 社内イベントに参加した
  • Kubernetesについての理解が深まった
  • 断片的だった知識が業務でいかせた
  • 本を読む時間が取れた

P

  • 業務のキャッチアップが大変でブログが疎かになった
  • 社内イベントの準備で休日出勤を2度した
  • 自分リリースノートを出すのが遅くなった

T

  • ブログを書く際のスタートとゴールを明確にし、TDD方式で書いてみる
  • データ指向アプリケーションデザインを読み終える
  • 次回のリリースは2019/09/01です

Maker Faire Tokyo 2019に自動積荷システム「Salaie」を出展してきた!

毎年見に行っていたMaker Faire Tokyoですが、今年は出展してきました!

makezine.jp

何を出展したの?

「Salaie」 と名付けた、自動積荷システムを社内の有志と作りました。

「Salaie」 は、ベルトコンベアで流れてきた荷物をカメラで撮影します。 次に、撮影した画像を推論し、アーム側へ箱の分類情報を渡します。 最後に箱の推論情報を受け取ったアームが指定の場所へ荷物を運びます。 これで一つの処理が完了し、それを続けていきます。

どのようなシステム構成なの?

f:id:yoshitaku_jp:20190804112003p:plain
saraieのシステム構成図

「Salaie」 のシステム構成ですが、Raspberry Pi上のKubernetes(k3s)がMQTTブローカーの役割と全体の制御をおこなうNode-REDを実行しています。 Kubernetes(k3s)を使用している理由ですが、正直家でRaspberry Piを使用を想定するとクラスタリングをする必要はありませんが、今回は業務用を想定したため「冗長性」「負荷分散」「Infrastructure as Code」の点からKubernetes(k3s)で実装しました。

また、学習済みのモデルはGoogle Colaboratoryを使って作成しました。その学習済みモデルをJetson TX2に配置し、撮影した画像を推論しています。 また、学習済みモデルはFlaskでローカルサーバーをたて、GETリクエストから推論を実行できるようにしておき、デバッグもおこないやすくしておきました。

その他のデバイスは、Arudino YUNを使い制御しています。

トラブルはあったの?

準備段階

準備段階では、学習済みモデルをFlaskで読み込む処理が正常に実施できませんでした。 Tensorflowの特定のバージョンで起こるバグのせいでFlask起動時に学習済みモデルを正常に読み込めず苦労しました。 GitHubのissueに載っていた回避スクリプトを実行したら動きました。 (該当のissueが見当たらないため見つけたら記載) 色んな場所で相談させてくださった方ありがとうございます。

本番当日

本番では、会場の光度と練習していた社内の光度に大きな違いがあり、デモがうまく動きませんでした。 モバイルのライトを緊急で購入し、光センサーの周りの光度をあげて対応しました。

他には、各デバイスが持ってきたルーターに繋がらないところもトラブルとなりました。

IoTにおけるデモ・リハーサルの大切さ

社内で電源を抜き再びつけるところからリハーサルを3回ほど実施したにもかかわらず、想定外のトラブルに合いました。 この3回リハーサルの間にもトラブルがあり、それらを解消できたので当日のトラブルを必要最小限に抑えられたのかなとも思います。素振り大切。 例えば、Kubernetesがコンテナイメージを取得する際、持参予定のルーターが外部へ接続されていないために、コンテナイメージ取得待ちになっていたことがありました。

まとめ

IT企業ではありますが、プログラミングをガリガリ書かない企業であるため、どれだけ簡単に実装できるかにこだわりました。その反面、今回の展示のためではなく、その先の業務用を意識した構成にし最後まで完成させられたところは良かったと思います。

また、会場でのトラブルを見学に来てくれた方に説明すると、「展示会場ではネットワークと光系のトラブルが多い」と教えていただけたり、出展しなければわからないことも多く知れたところが自分にとって多くプラスでした。

興味はあるけど普段の業務では触れない技術も触れて、知的好奇心を大きくくすぐられる準備期間とMaker Faire Tokyo 2019になりました。

見学してくださった方、質問してくださった方、実装に困っていたときに相談に乗ってくださった方、最後まで一緒にやり遂げてくれたメンバーの方、ありがとうございました。

RaspberryPiにHadoopをインストールする

クラウド環境を使うことでHadoopを始めとする分散環境を用意することは簡単だが、内部でどのような処理がおこなわれているか疑問に思った。 今回はHadoopをRaspberryPiにセットアップし、スタンドアローン環境が動作するところまで確認する。

環境

RaspberryPi 3B+

OS:Ubuntu 18.04

セットアップ

まずはupdateをおこなう。

sudo apt-get update
sudo apt-get upgrade

Javaのインストール

Hadoopを動かすためにはJavaが必要なため、openjdkをインストールする。

sudo apt install openjdk-11-jre-headless

Hadoopのセットアップ

Hadoopwgetコマンドでダウンロードする。 2019/07/28時点で最新版は3.1.2である。

wget http://ftp.yz.yamagata-u.ac.jp/pub/network/apache/hadoop/common/hadoop-3.1.2/hadoop-3.1.2.tar.gz
tar xzvf hadoop-3.1.2.tar.gz
mv hadoop-3.1.2 hadoop

hadoop-env.shを編集する。JAVA_HOMEの項目にインストールしたJavaディレクトリを指定する。 vim hadoop-env.sh

JAVA_HOME=/usr/lib/jvm/java-11-openjdk-arm64

確認

hadoopを実行するとUsageが表示される。 Usageが表示されないときはhadoop-env.shのJAVA_HOMEが正しく指定されていないので確認する。

hadoop/bin/hadoop

Usage: hadoop [OPTIONS] SUBCOMMAND [SUBCOMMAND OPTIONS]

(省略)

    Daemon Commands:

kms           run KMS, the Key Management Server

SUBCOMMAND may print help when invoked w/o parameters or with -h.

テスト実行

inputディレクトリを作成し、hadoop-mapreduce-examplesのwordcountを実行する。 結果が帰ってくれば成功。

mkdir hadoop/input
bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.2.jar wordcount input output

まとめ

今回はHadoopをRaspberryPiにセットアップし、スタンドアローン環境が動作するところまで確認した。 クラウドを利用しブラウザでクリックするだけで終わる処理を体験し、今回のスタンドアローンのセットアップ以外にも分散環境のセットアップが入ることを考えると便利な時代になっていると感じる。

Flaskをさわってみた

Maker Faireへの出展物にWeb API化したいものがあり、軽量なFlaskを採用した。 以前Djangoを使用して個人的なプロダクトを作ったことがあったので、比較対象としてFlaskの名前は聞いたことがあったが触るのは初めてだった。 Djangoに比べると軽量に使えるものであり、用途が限定されているものであったらすぐにFlaskで作成し見せられる状態まで持っていくことが可能であると感じた。

自分が触った機能のメモを残しておく。

インストール

Pythonnにはおなじみのpipコマンドでインストールが可能である。

pip install flask

Hello world

公式からのサンプルコードとして載っているものを借りる。 今回は次のコードをserver.pyとして、python server.pyで起動する。 ブラウザからhttp://localhost:5000にアクセスすると、Hello World!が返却される。

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return "Hello World!"

if __name__ == '__main__':
    app.run()

パラメーターの受け取り

パラメータの受け取りは、from flask import requestを使う必要がある。 自分はpip install requestsで提供されているものと勘違いして躓いてしまった。 request.args.get('search_word')http://localhost:5000/?search_word=hatenaとアクセスされたhatenaが受け取れる。

from flask import Flask
from flask import request

app = Flask(__name__)

@app.route('/')
def hello_world():
    search_word = request.args.get('search_word')
    return search_word

if __name__ == '__main__':
    app.run()

外部からのアクセスに対応する

Flaskはデフォルトで起動すると、localhostからのアクセスしか対応していない。 本番で出展するときはFlaskを動作させているサーバ内部だけで処理が完結するものではあるのだが、テストを容易にするため外部からのアクセスも可能としたい。 このときの対応も非常に簡単で設定ファイルをいじることもなく、app.run()の引数をapp.run(host='0.0.0.0')とするだけでよかった。

from flask import Flask
from flask import request

app = Flask(__name__)

@app.route('/')
def hello_world():
    search_word = request.args.get('search_word')
    return search_word

if __name__ == '__main__':
    app.run(host='0.0.0.0')

これだけの作業でWeb APIが作られることを魅力的に感じた。 リッチなフレームワークを使うと様々なことを実現できることも理解しているが、大量の設定ファイルやコマンドを実行することになり、使用する前段階での理解する時間が大量に必要だった。その結果、本当にやりたい作業までなかなかたどり着けないことがあった。 Flaskを使うと理解する時間の大幅な短縮が見込めるので、まずはどんどん自分のやりたいことを実現し、そのうえで別のフレームワークが必要と感じたら移行を検討していくことが大切であると感じた。

Azure Bastionをさわってみた

現在パブリックプレビューとして公開されている、 「Azure Bastion」 を触ってみた。 「Azure Bastion」 はブラウザ上のAzure Portalから、仮想ネットワーク内の仮想マシンにプライベートIPだけで、リモートデスクトップSSHでアクセスができる。 つまり、仮想マシンにパブリックIPを設定する必要はない。 Bastionという単語は要塞という意味があり、IT業界的には「Bastion Server(踏み台サーバ)」と略されることがある。 読み方は「バッション」

azure.microsoft.com

使用した際のイメージ図は次のようになる。

f:id:yoshitaku_jp:20190712143331p:plain
https://docs.microsoft.com/en-us/azure/bastion/bastion-nsg

必要なもの

  • Virtual Machine
    • 「Azure Bastion」 が利用できるリージョンとVirtual Machineも同じ場所に配置するのが望ましい。
      • West US
      • East US
      • South Central US
      • West Europe
      • West Europe
      • Australia East
      • Japan East
  • Virtual Network
  • Bastion

「Azure Bastion」 は現在パブリックプレビューなので、通常のAzure Portalからでは利用ができない。 次のURLからアクセスする必要がある。アクセスをするとヘッダーがオレンジ色の画面に遷移する。

Azure portal - Bastion Preview

仮想ネットワークの作成

仮想ネットワーク上にBastionをデプロイするためのサブネットを作成する。作成する際の注意は、サブネットの名前を 「AzureBastionSubnet」、アドレス範囲(CIDRブロック)を 「/27」 より大きいものにしなければならない。この2つを指定された値以外にすると「Azure Bastion」 を使うことができない。画像はサブネットを作成したあとのものである。Virtual Machineは 10.0.0.0/24にデプロイしてある。

f:id:yoshitaku_jp:20190712143226p:plain

Bastionの作成

Bastionを作成するが特に難しいことはなく、「サブスクリプション」「リソースグループ」「インスタンス」「仮想ネットワーク」を指定する。 「仮想ネットワーク」は先程の「AzureBastionSubnet」を指定する。

f:id:yoshitaku_jp:20190712144605p:plain

Bastionの作成時のエラー

仮想ネットワークの作成で次のようにと書いた。

作成する際の注意は、サブネットの名前を 「AzureBastionSubnet」、アドレス範囲(CIDRブロック)を 「/27」 より大きいものにしなければならない。

実際に指定の値以外でサブネットを作成したときのエラーメッセージを確認しておく。

To associate a virtual network with a Bastion, 
it must contain a subnet with name AzureBastionSubnet with prefix of at least /27
仮想ネットワークをBastionに関連付けるには、AzureBastionSubnetという名前のサブネットと少なくとも/ 27のプレフィックスを持つサブネットを含める必要があります。

f:id:yoshitaku_jp:20190712150102p:plain

「Azure Bastion」 からログイン

Connectをクリックすると、右側からパネルが出現する。 通常のAzure PortalでConnectをクリックしても出てこない「BASTION」が存在するので、Virtual Machineのユーザ名とパスワードを入力しログインする。 別のタブが開くので、ポップアップブロックに引っかかる可能性がある。動いていないときはポップアップブロックを確認すること。

f:id:yoshitaku_jp:20190712151743p:plain

Jupyter Notebookを外部からアクセスして使う方法

Jupyter Notebookを起動しているサーバーに外部からアクセスして利用する方法をTipsとしてまとめておく。

個人で小さく利用している場合はローカルマシンでJupyter Notebookを使うことで足りるが、企業単位であったり重い処理を実行する場合はクラウドGPUを搭載したマシンを用意し使うことがある。クラウド上で起動しているJupyter Notebookへアクセスし、利用する方法をまとめておく。

設定ファイルの作成

jupyter notebook -- generate-configを実行することでデフォルト設定の設定ファイルを作成できる。 ~/.jupyter/jupyter_notebook_config.pyが作成先になる。

設定ファイル内のc.NotebookApp.ipを変更することで、外部からのアクセスが可能となる。 *だとすべての場所からのアクセスが可能となるため、セキュリティには注意が必要となる。 ポート番号を変更することも可能。

#c.NotebookApp.ip = 'localhost'
#c.NotebookApp.port = 8888
c.NotebookApp.ip = '*'
c.NotebookApp.port = 9999

Jupyter Notebook起動時にブラウザを起動させたくない場合はc.NotebookApp.open_browserの設定を変更する。

c.NotebookApp.open_browser = True
c.NotebookApp.open_browser = False

じぶん Release Notes 0.29.2

f:id:yoshitaku_jp:20190702075126p:plain 6月1日〜30日のあいだの出来事がリリースされました。

技術・開発関連

技術面での取り組みは以下の通りです。

  • write-blog-every-weekのサイトにVuetifyを導入しました。
    • 途中なので今後も継続
  • バケットリストのサイトを作成しました。
    • 途中なので今後も継続

イベント

参加したイベントは以下の通りです。

  • せんべろの旅in池袋
  • フットサルチーム合宿in河口湖

読んだ

買ったもの

主要な買い物は下記の通りです。

  • 今月は特にありません。

ブログ

リリースノートを除き、次のエントリを書きました。

PV数

6月は3,987でした

f:id:yoshitaku_jp:20190702074200p:plain

仕事

  • 部署が変わることもあり、サポート業務が多めでした。
  • 来月からはインプットが多くなりそう…!

KPT

K

  • 週1本映画を見た
  • 運動をして痩せた
  • 漫画を大人買いした

P

  • Podcastを聞く時間が取れなかった
  • 本を読む時間が取れなかった

T

  • 新しい部署に慣れる
  • 次回のリリースは2019/07/01です

RaspberryPiとM5StackをMQTTでつなぐ

RaspberryPiをMQTTブローカーにして、M5Stackから送られるメッセージを受信する。

RaspberryPiの設定

まずはMQTTブローカーをインストールする。 MQTTブローカーのオープンソースである、「mosquitto」を使う。

sudo apt-get install mosquitto

MQTTブローカーの起動や停止はserviceから実行できる。

service mosquitto start
service mosquitto stop

今回、RaspberryPiではMQTTブローカーとしての役割とM5Stackからのメッセージ受信もおこなうのでmosquitto-clientsをインストールする。mosquitto-clientsをインストールすることで、メッセージの送信と受信ができる。MTQQブローカーとしての役割だけだったらインストールする必要はない!

sudo apt-get install mosquitto-clients

M5Stackへソースを書き込む

今回はこちらのサイトのソースを編集して使わせてもらった。 Arduino IDEからM5Stackへ書き込む。

#include <WiFi.h>
#include <PubSubClient.h>
#include <M5Stack.h>
 
// Wi-FiのSSID
char *ssid = "YOUR-SSID";
// Wi-Fiのパスワード
char *password = "YOUR-PASSWORD";
// MQTTの接続先のIP
const char *endpoint = "YOUR-MQTT-IP";
// MQTTのポート
const int port = 1883;
// デバイスID
char *deviceID = "M5Stack";  // デバイスIDは機器ごとにユニークにします
// メッセージを知らせるトピック
char *pubTopic = "/pub/M5Stack";
// メッセージを待つトピック
char *subTopic = "/sub/M5Stack";
 
////////////////////////////////////////////////////////////////////////////////
   
WiFiClient httpsClient;
PubSubClient mqttClient(httpsClient);
   
void setup() {
    Serial.begin(115200);
     
    // Initialize the M5Stack object
    M5.begin();
 
    // START
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(10, 10);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setTextSize(3);
    M5.Lcd.printf("START");
     
    // Start WiFi
    Serial.println("Connecting to ");
    Serial.print(ssid);
    WiFi.begin(ssid, password);
   
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
 
    // WiFi Connected
    Serial.println("\nWiFi Connected.");
    M5.Lcd.setCursor(10, 40);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setTextSize(3);
    M5.Lcd.printf("WiFi Connected.");
     
    mqttClient.setServer(endpoint, port);
   
    connectMQTT();
}
   
void connectMQTT() {
    while (!mqttClient.connected()) {
        if (mqttClient.connect(deviceID)) {
            Serial.println("Connected.");
            int qos = 0;
            mqttClient.subscribe(subTopic, qos);
            Serial.println("Subscribed.");
        } else {
            Serial.print("Failed. Error state=");
            Serial.print(mqttClient.state());
            // Wait 5 seconds before retrying
            delay(5000);
        }
    }
}
   
long messageSentAt = 0;
int count = 0;
char pubMessage[128];
int led,red,green,blue;

  
void mqttLoop() {
    if (!mqttClient.connected()) {
        connectMQTT();
    }
    mqttClient.loop();
}
 
void loop() {
 
  // 常にチェックして切断されたら復帰できるように
  mqttLoop();
 
  // 5秒ごとにメッセージを飛ばす
  long now = millis();
  if (now - messageSentAt > 5000) {
      messageSentAt = now;
      sprintf(pubMessage, "{\"count\": %d}", count++);
      Serial.print("Publishing message to topic ");
      Serial.println(pubTopic);
      Serial.println(pubMessage);
      mqttClient.publish(pubTopic, pubMessage);
      Serial.println("Published.");
  }
}

実行

M5Stack

M5Stackへ無事に書き込みが完了し起動をすると、M5StackはMQTTブローカーへ向けてメッセージを送信し続ける。 5秒ごとに数字が1上がり、その値をメッセージとして/pub/M5Stackへ送信している。

/pub/M5Stackはメッセージがおいてある場所を指しており、「トピック」と呼ばれる。 送信者は指定したトピックにメッセージを送信する。 トピックはディレクトリのように「/(スラッシュ)」で区切って利用する。

/pub/M5Stack
/pub/RaspberryPi
/pub/Arduino
/pub/IoT

/message/room1
/message/room2
/message/room3

RaspberryPi

RaspberryPiはmosquitto_subコマンドを実行し、MQTTブローカーへ問い合わせる。 - 実行

mosquitto_sub -d -t /pub/M5Stack
  • 結果
{"count": 1}
{"count": 2}
{"count": 3}
{"count": 4}
...
{"count": n}

textlintでブログの文章を整える

f:id:yoshitaku_jp:20190620121524p:plain

今までこのブログの文章は、はてなブログ上のエディタで書いていた。 最近はnoteでも文章を書くことがあり、複数アプリで置き場所が1つにならないのが煩わしくなり文章の管理をGitHub上に持ってきた。 普段使っているVS Codeで文章が書けることになったので、このタイミングで「textlint」を導入した。 文章上のルールや専門用語のチェックをおこない正しい文章を書いていきたい。

textlintの準備

npmを使い「textlint」をインストールする。

npm install --save-dev textlint

1から文章のルールを作成することもできるが、今回はすでにルールプリセットを公開してくださっている方のものを利用させてもらう。 https://github.com/textlint-ja/textlint-rule-preset-ja-technical-writing

npm install --save-dev textlint-rule-preset-ja-technical-writing

また、技術用語のチェック用辞書もインストールする。こちらはルールプリセットを公開してくださっている方が作っているもので、WEB+DB PRESS用語統一ルールをベースにされている。

npm install --save-dev textlint-rule-spellcheck-tech-word

設定ファイルの準備

ここまで次のもののインストールが完了したら、設定ファイルを作成する。

  • textlint
  • textlint-rule-preset-ja-technical-writing
  • textlint-rule-spellcheck-tech-word

「textlint」をおこないたいフォルダーでtextlint initを実行し.textlintrcを生成する。 自分はarticlesフォルダをルートとし、配下にhatenablogフォルダ・noteフォルダを作成している。 今回はarticleフォルダでtextlint initを実行した。

- article
  - .textlintrc ←ココに作られる
  - hatenablog
    - *.md
  - note
    - *.md

.textlintrc の中に次の記述をする。設定したルールが適応される。今回は事前にインストールしたものを設定した。

{
  "rules": {
    "preset-ja-technical-writing": true,
    "spellcheck-tech-word": true,
  }
}

これで 「textlint」 自体の設定は完了した。コマンドライン./node_modules/.bin/textlint ./*"を実行すると 「textlint」 された結果がコンソールに表示される。

vscode-textlintをインストールする

自分はVS Codeを利用しているので、VS Code拡張機能を利用する。 拡張機能から vscode-textlint」 を検索しインストールする。 特に難しい部分ではないので割愛する。

画面上での動き

VS Codeを起動する際に注意するところがあるので先に載せておく。 vscode-textlint」 を利用する場合、.textlintrcが存在するフォルダをVS Codeで開かないといけない。 今回の説明では、articleフォルダに配置したのでarticleフォルダを開く。

- article
  - .textlintrc ←ココに作られる
  - hatenablog
    - *.md
  - note
    - *.md

下記の例だと、workspace配下でVS Codeを開くと.textlintrcがないため、vscode-textlint」 が正しく動作しない。

- workspace
  - hoge
  - fuga
  - article
    - .textlintrc
    - hatenablog
      - *.md
    - note
      - *.md

使えるようになると、次のように警告を出してくれる。

f:id:yoshitaku_jp:20190620120940p:plain

これは正式な表現がJavaScript」なのに対して、Javascript」になっていると警告されている。