walover-line-harness-gui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +152 -0
- package/package.json +27 -0
- package/src/server/browser.js +22 -0
- package/src/server/cli-version.js +136 -0
- package/src/server/cli.js +51 -0
- package/src/server/completion.js +177 -0
- package/src/server/guide.js +181 -0
- package/src/server/index.js +334 -0
- package/src/server/leftover.js +111 -0
- package/src/server/prompt-contract.js +373 -0
- package/src/server/prompt-detector.js +181 -0
- package/src/server/pty-relay.py +130 -0
- package/src/server/pty.js +343 -0
- package/src/server/redact.js +54 -0
- package/src/server/runner.js +607 -0
- package/src/server/state.js +178 -0
- package/src/server/static.js +46 -0
- package/src/server/terminal.js +210 -0
- package/src/web/index.html +859 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 WALOVER LLC (WALOVER合同会社)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# walover-line-harness-gui
|
|
2
|
+
|
|
3
|
+
LINE Harness のセットアップを、**ターミナルを開かずにブラウザから**行うためのローカルツールです。
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx walover-line-harness-gui
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
サーバーが起動してブラウザが開きます。あとは画面の指示に従うだけです。
|
|
10
|
+
|
|
11
|
+
> **🚧 開発中です。** サーバーの土台と、本家 CLI を擬似端末で動かす部分ができています。
|
|
12
|
+
> 画面(フォーム・出力表示)はこれから作ります。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## これは何か
|
|
17
|
+
|
|
18
|
+
本家 [Shudesu/line-harness-oss](https://github.com/Shudesu/line-harness-oss) の
|
|
19
|
+
`npx create-line-harness` を**そのまま呼び出すためのラッパー**です。
|
|
20
|
+
|
|
21
|
+
| | |
|
|
22
|
+
|---|---|
|
|
23
|
+
| **やること** | フォームで受け取った値を、本家 CLI に中継する |
|
|
24
|
+
| **やらないこと** | 本家 CLI のロジック(DB マイグレーション・デプロイ処理など)の再実装 |
|
|
25
|
+
|
|
26
|
+
### なぜ必要か
|
|
27
|
+
|
|
28
|
+
本家 CLI は対話に実 TTY を要求します。そのため **Claude 経由では実行できません**
|
|
29
|
+
(Claude Code CLI・Claude Desktop の Code タブとも標準入出力が TTY ではなく、
|
|
30
|
+
最初のプロンプトで `uv_tty_init EINVAL` で落ちます)。
|
|
31
|
+
|
|
32
|
+
このツールはサーバーとして起動するため TTY が不要で、**Claude から起動できます。**
|
|
33
|
+
擬似端末(PTY)はこのツールが握り、本家 CLI との対話を仲介します。
|
|
34
|
+
|
|
35
|
+
### 応答の決め方(プロンプト契約)
|
|
36
|
+
|
|
37
|
+
本家のプロンプトには**順番ではなく文言で**答えます。
|
|
38
|
+
一致しなければ何も送らず、その場で止めて人に委ねます。
|
|
39
|
+
|
|
40
|
+
順番でマッチさせると、本家にプロンプトが1つ増えただけで値が1つずつズレます。
|
|
41
|
+
最悪の場合チャネルシークレットがプロジェクト名の欄に入り、画面にエコーされ、
|
|
42
|
+
Worker 名として Cloudflare に残ります。機能不全ではなく情報漏洩です。
|
|
43
|
+
|
|
44
|
+
契約は [`src/server/prompt-contract.js`](src/server/prompt-contract.js) にあり、
|
|
45
|
+
本家の文言と合っているかは次で確かめられます。
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm run check:contract # 契約が対象にしている版と照合
|
|
49
|
+
npm run check:contract latest # 最新版と照合(ズレの検知)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### バージョンの固定
|
|
53
|
+
|
|
54
|
+
契約は本家のバージョンに紐づくので、**動作確認済みの版を固定して呼びます**。
|
|
55
|
+
起動時に npm の最新版と比べ、差があれば警告だけ出します(自動では追従しません)。
|
|
56
|
+
|
|
57
|
+
追従するときは `npm run check:contract latest` で契約を照合し、
|
|
58
|
+
`CONTRACT_CLI_VERSION` を上げてください。
|
|
59
|
+
|
|
60
|
+
> npm パッケージ(`create-line-harness`)とアプリ本体(`line-oss-crm`)はバージョンが別で、
|
|
61
|
+
> プロンプトは npm パッケージ側にあります。npm 側を固定すれば対話の契約は安定し、
|
|
62
|
+
> アプリ側は実行時取得なので機能追従は保たれます。
|
|
63
|
+
|
|
64
|
+
### 入力フォーム
|
|
65
|
+
|
|
66
|
+
画面の入力欄は、**契約から組み立てています**。項目・文言・検証規則を画面側で持たないので、
|
|
67
|
+
本家との対応がズレたときに片側だけ古くなることがありません。
|
|
68
|
+
|
|
69
|
+
検証規則は**本家の `validate` を写したもの**です。本家より厳しくすると正しい値を弾いて
|
|
70
|
+
参加者が進めなくなるため、意図的に同じ厳しさに揃えてあります。文言も本家と同じなので、
|
|
71
|
+
画面で弾いても CLI で弾いても同じことを言います。
|
|
72
|
+
|
|
73
|
+
**Messaging API チャネルと LINE Login チャネルの取り違え**は実測で最大の混乱要因でした
|
|
74
|
+
(本家の文言が `Channel ID(数字)` と `チャネル ID(数字)` でほとんど同じ)。
|
|
75
|
+
画面では取得元ごとに区分を分け、ラベル自体にどちらのチャネルかを入れ、
|
|
76
|
+
**両方に同じ ID が入っていたら止めます**。本家はこれを見ていないため、
|
|
77
|
+
気づかないまま最後まで進んでしまいます。
|
|
78
|
+
|
|
79
|
+
### 手順書を入力欄の隣に置く
|
|
80
|
+
|
|
81
|
+
実測では、手作業13件のうち**このツールで隠せるのは2件だけ**でした。残りは LINE と
|
|
82
|
+
Cloudflare のブラウザ作業で、ターミナルとは関係ありません。
|
|
83
|
+
つまり value は「ターミナルを隠すこと」より、**「手順書と入力欄が同じ画面に並ぶこと」**にあります。
|
|
84
|
+
|
|
85
|
+
各欄の隣に、短い手順・間違えやすい点・作業画面へのリンク・実際のスクリーンショットを置いています。
|
|
86
|
+
**画像と詳しい手順は[配布キット側](https://saniman.github.io/walover-line-harness-kit/)のものを参照**し、
|
|
87
|
+
こちらには複製しません(複製していないことをテストで固定しています)。
|
|
88
|
+
|
|
89
|
+
リンク切れは `npm run check:links` で検出できます。
|
|
90
|
+
|
|
91
|
+
### 実行中の表示
|
|
92
|
+
|
|
93
|
+
本家の出力は `\r` による描き直し(スピナー)が大量に混ざるため、素朴に追記すると読めません。
|
|
94
|
+
**このツールは端末の再描画をサーバー側で解決してから流します**([`src/server/terminal.js`](src/server/terminal.js))。
|
|
95
|
+
|
|
96
|
+
そのため、ブラウザに送るのは**伏字を通した「いま画面に見えている行」だけ**です。
|
|
97
|
+
生のバイト列を流さないので、本家がエコーしたシークレットがブラウザに届く経路がありません。
|
|
98
|
+
端末エミュレータをブラウザ側に置いていないのは、この理由によります。
|
|
99
|
+
|
|
100
|
+
ログはどこにも書きません(`src/server` に書き込み系 API が現れないことをテストで固定しています)。
|
|
101
|
+
|
|
102
|
+
### 失敗したときに残るファイル
|
|
103
|
+
|
|
104
|
+
本家は**失敗・中断したときだけ** `~/.line-harness/.line-harness-setup.json` に途中経過を残します。
|
|
105
|
+
このファイルには**チャネルシークレットとアクセストークンが平文で**入り、権限は既定の 0644 です。
|
|
106
|
+
|
|
107
|
+
失敗した場面は、参加者がサポートにファイルやスクショを送りたくなる場面とちょうど重なります。
|
|
108
|
+
そのため、残っていることを検知して**「誰にも送らないでください」**を先に伝えます。
|
|
109
|
+
|
|
110
|
+
- 中身は読みません(`stat` だけで判断します)
|
|
111
|
+
- 消しません(再開に必要な進捗も同じファイルに入っています)
|
|
112
|
+
- やり直して完走すれば本家が自動で消します
|
|
113
|
+
|
|
114
|
+
## 動かすのに要るもの
|
|
115
|
+
|
|
116
|
+
| | |
|
|
117
|
+
|---|---|
|
|
118
|
+
| OS | macOS / Windows / Linux |
|
|
119
|
+
| Node.js | 20 以上。**Apple Silicon では arm64 ネイティブ**(x86_64 だと本家の依存インストールが `SIGILL` で落ちる) |
|
|
120
|
+
| git | 本家 CLI が使う |
|
|
121
|
+
| python3 | **予備の経路を使うときだけ**(macOS / Linux)。`xcode-select --install` で入る |
|
|
122
|
+
|
|
123
|
+
擬似端末は `node-pty` を主に使い、Unix では Python 標準ライブラリの `pty` を予備に持ちます。
|
|
124
|
+
経緯と実装上の注意は [`docs/pty-strategy.md`](docs/pty-strategy.md) にあります。
|
|
125
|
+
|
|
126
|
+
## シークレットの扱い
|
|
127
|
+
|
|
128
|
+
- **参加者のパソコンの中だけで動きます。** WALOVER のサーバーを経由しません
|
|
129
|
+
- チャネルシークレット・アクセストークンは**メモリ上のみ**で扱います
|
|
130
|
+
- **ディスク・ログ・データベースのいずれにも保存しません**
|
|
131
|
+
- サーバーは `127.0.0.1` にのみ bind します。外部から接続できません
|
|
132
|
+
- プロセスを終了すると破棄されます
|
|
133
|
+
|
|
134
|
+
## 開発
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npm test # 依存なし。擬似端末の通しテストを含む
|
|
138
|
+
npm run dev # ローカルで起動
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
テストは本家を実際には走らせません(走らせると本物の Cloudflare リソースが作られます)。
|
|
142
|
+
本家と同じ `@clack/prompts` が擬似端末に吐いた**実際のバイト列**を
|
|
143
|
+
[`test/fixtures/`](test/fixtures/) に記録してあり、それで照合しています。
|
|
144
|
+
|
|
145
|
+
## 関連
|
|
146
|
+
|
|
147
|
+
- 手順書(配布キット): https://saniman.github.io/walover-line-harness-kit/
|
|
148
|
+
- 本家: https://github.com/Shudesu/line-harness-oss
|
|
149
|
+
|
|
150
|
+
## ライセンス
|
|
151
|
+
|
|
152
|
+
MIT © 2026 WALOVER LLC(WALOVER合同会社)
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "walover-line-harness-gui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "LINE Harness のセットアップを、ターミナルを開かずにブラウザから行うためのローカルツール",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"walover-line-harness-gui": "src/server/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"src"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"dev": "node src/server/cli.js",
|
|
18
|
+
"start": "node src/server/cli.js",
|
|
19
|
+
"test": "node --test",
|
|
20
|
+
"check:contract": "node scripts/check-contract.js",
|
|
21
|
+
"check:links": "node scripts/check-guide-links.js",
|
|
22
|
+
"prepublishOnly": "npm test"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"node-pty": "1.1.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 既定のブラウザで URL を開く。依存パッケージを増やさないため自前で持つ。
|
|
5
|
+
* 開けなくても致命的ではない(URL を案内すればよい)ので、失敗は握って false を返す。
|
|
6
|
+
*/
|
|
7
|
+
export function openBrowser(url) {
|
|
8
|
+
const table = {
|
|
9
|
+
darwin: ["open", [url]],
|
|
10
|
+
win32: ["cmd", ["/c", "start", "", url]],
|
|
11
|
+
};
|
|
12
|
+
const [cmd, args] = table[process.platform] ?? ["xdg-open", [url]];
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
16
|
+
child.on("error", () => {});
|
|
17
|
+
child.unref();
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本家 CLI のバージョンの固定と、ズレの警告。
|
|
3
|
+
*
|
|
4
|
+
* プロンプト契約({@link ./prompt-contract.js})は本家のバージョンに紐づいている。
|
|
5
|
+
* 本家が文言を変えれば契約は当たらなくなり、参加者は「未知のプロンプト」で止まる。
|
|
6
|
+
* そのため**動作確認済みの版を固定して呼ぶ**。
|
|
7
|
+
*
|
|
8
|
+
* ただし固定したまま黙っていると、本家の修正に永久に追いつかない。
|
|
9
|
+
* 起動時に npm の `latest` と比べ、差があれば**警告だけ**出す。
|
|
10
|
+
* 自動では追従しない。追従には契約の再照合(`npm run check:contract`)が要る。
|
|
11
|
+
*
|
|
12
|
+
* > npm パッケージ(`create-line-harness`)とアプリ本体(`line-oss-crm`)は
|
|
13
|
+
* > バージョンが別で、プロンプトは npm パッケージ側にある。
|
|
14
|
+
* > npm 側を固定すれば対話の契約は安定し、アプリ側は実行時取得なので機能追従は保たれる。
|
|
15
|
+
*
|
|
16
|
+
* ネットワークは無くても動く。取れなければ「分からない」として先に進む。
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { CONTRACT_CLI_VERSION } from "./prompt-contract.js";
|
|
20
|
+
|
|
21
|
+
export const PACKAGE = "create-line-harness";
|
|
22
|
+
const REGISTRY = "https://registry.npmjs.org";
|
|
23
|
+
const TIMEOUT_MS = 3000;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* `1.2.3` を比べる。プレリリース識別子は見ない(本家は使っていない)。
|
|
27
|
+
* @returns {-1|0|1}
|
|
28
|
+
*/
|
|
29
|
+
export function compareVersions(a, b) {
|
|
30
|
+
const parse = (v) => String(v).split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
31
|
+
const left = parse(a);
|
|
32
|
+
const right = parse(b);
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < Math.max(left.length, right.length); i += 1) {
|
|
35
|
+
const l = left[i] ?? 0;
|
|
36
|
+
const r = right[i] ?? 0;
|
|
37
|
+
if (l < r) return -1;
|
|
38
|
+
if (l > r) return 1;
|
|
39
|
+
}
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* npm から版を1つ引く。
|
|
45
|
+
* @param {string} [spec] `latest` またはバージョン
|
|
46
|
+
* @param {{ fetchImpl?: typeof fetch, timeoutMs?: number }} [options]
|
|
47
|
+
* @returns {Promise<{ version: string, tarball: string }>}
|
|
48
|
+
*/
|
|
49
|
+
export async function resolveVersion(spec = "latest", options = {}) {
|
|
50
|
+
const { fetchImpl = fetch, timeoutMs = TIMEOUT_MS } = options;
|
|
51
|
+
const res = await fetchImpl(`${REGISTRY}/${PACKAGE}/${spec}`, {
|
|
52
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
53
|
+
headers: { Accept: "application/json" },
|
|
54
|
+
});
|
|
55
|
+
if (!res.ok) throw new Error(`registry から取得できません: ${res.status}`);
|
|
56
|
+
|
|
57
|
+
const meta = await res.json();
|
|
58
|
+
if (typeof meta?.version !== "string") throw new Error("registry の応答が想定と違います");
|
|
59
|
+
return { version: meta.version, tarball: meta?.dist?.tarball ?? "" };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @typedef {object} VersionStatus
|
|
64
|
+
* @property {string} pinned 固定している版
|
|
65
|
+
* @property {string|null} latest npm の最新版。取れなければ null
|
|
66
|
+
* @property {"pinned-is-latest"|"newer-available"|"pinned-is-newer"|"unknown"} status
|
|
67
|
+
* @property {string|null} warning 画面に出す文言
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 固定版と npm の最新版を比べる。**ここで失敗しても起動は止めない。**
|
|
72
|
+
* @param {{ fetchImpl?: typeof fetch, timeoutMs?: number }} [options]
|
|
73
|
+
* @returns {Promise<VersionStatus>}
|
|
74
|
+
*/
|
|
75
|
+
export async function checkCliVersion(options = {}) {
|
|
76
|
+
const pinned = CONTRACT_CLI_VERSION;
|
|
77
|
+
|
|
78
|
+
let latest = null;
|
|
79
|
+
try {
|
|
80
|
+
latest = (await resolveVersion("latest", options)).version;
|
|
81
|
+
} catch {
|
|
82
|
+
// ネットワークが無い・遅い・落ちている。警告が出せないだけで、セットアップは進められる
|
|
83
|
+
return {
|
|
84
|
+
pinned,
|
|
85
|
+
latest: null,
|
|
86
|
+
status: "unknown",
|
|
87
|
+
warning: null,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const order = compareVersions(pinned, latest);
|
|
92
|
+
|
|
93
|
+
if (order === 0) {
|
|
94
|
+
return { pinned, latest, status: "pinned-is-latest", warning: null };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (order < 0) {
|
|
98
|
+
return {
|
|
99
|
+
pinned,
|
|
100
|
+
latest,
|
|
101
|
+
status: "newer-available",
|
|
102
|
+
warning: [
|
|
103
|
+
`本家 CLI の新しい版 v${latest} が出ています(このツールが確認済みなのは v${pinned})。`,
|
|
104
|
+
"動作未確認のため、追従には検証が必要です。このままでも v" + pinned + " でセットアップできます。",
|
|
105
|
+
].join("\n"),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 固定版のほうが新しい。npm 側が取り下げられた場合などに起きる
|
|
110
|
+
return {
|
|
111
|
+
pinned,
|
|
112
|
+
latest,
|
|
113
|
+
status: "pinned-is-newer",
|
|
114
|
+
warning: [
|
|
115
|
+
`固定している v${pinned} が npm の最新 v${latest} より新しくなっています。`,
|
|
116
|
+
"npm 側で取り下げられた可能性があります。動作未確認のため、確認が必要です。",
|
|
117
|
+
].join("\n"),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** @type {Promise<VersionStatus>|null} */
|
|
122
|
+
let cached = null;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 起動中に一度だけ問い合わせる。npm を叩き続けない。
|
|
126
|
+
* @param {{ fetchImpl?: typeof fetch, timeoutMs?: number }} [options]
|
|
127
|
+
*/
|
|
128
|
+
export function getVersionStatus(options) {
|
|
129
|
+
cached ??= checkCliVersion(options);
|
|
130
|
+
return cached;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** テスト用 */
|
|
134
|
+
export function resetVersionCache() {
|
|
135
|
+
cached = null;
|
|
136
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { start, shutdown } from "./index.js";
|
|
3
|
+
import { openBrowser } from "./browser.js";
|
|
4
|
+
import { ptyBackend } from "./pty.js";
|
|
5
|
+
import { getVersionStatus } from "./cli-version.js";
|
|
6
|
+
|
|
7
|
+
const { server, url } = await start();
|
|
8
|
+
|
|
9
|
+
console.log("");
|
|
10
|
+
console.log(" LINE Harness セットアップ画面を起動しました");
|
|
11
|
+
console.log("");
|
|
12
|
+
console.log(` ${url}`);
|
|
13
|
+
console.log("");
|
|
14
|
+
console.log(" ブラウザが開かない場合は、上の URL をコピーして開いてください。");
|
|
15
|
+
console.log(" 終了するには Ctrl+C を押してください。");
|
|
16
|
+
|
|
17
|
+
if (ptyBackend() === null) {
|
|
18
|
+
// 本家 CLI は実際の端末(PTY)を要求する。無いと起動すらできないので先に伝える
|
|
19
|
+
console.log("");
|
|
20
|
+
console.log(" ⚠ 擬似端末を用意できません。このままではセットアップを開始できません。");
|
|
21
|
+
if (process.platform === "win32") {
|
|
22
|
+
console.log(" いったん終了して、もう一度お試しください。");
|
|
23
|
+
} else {
|
|
24
|
+
console.log(" macOS の場合: xcode-select --install");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
console.log("");
|
|
28
|
+
|
|
29
|
+
openBrowser(url);
|
|
30
|
+
|
|
31
|
+
// 本家 CLI の版のズレ。ネットワーク待ちで起動を遅らせないので、後から出す。
|
|
32
|
+
// 取れなくても何も言わない(セットアップは固定版で進められる)
|
|
33
|
+
getVersionStatus()
|
|
34
|
+
.then((version) => {
|
|
35
|
+
if (!version.warning) return;
|
|
36
|
+
console.log(" ⚠ " + version.warning.split("\n").join("\n "));
|
|
37
|
+
console.log("");
|
|
38
|
+
})
|
|
39
|
+
.catch(() => {});
|
|
40
|
+
|
|
41
|
+
let closing = false;
|
|
42
|
+
const stop = async () => {
|
|
43
|
+
if (closing) return;
|
|
44
|
+
closing = true;
|
|
45
|
+
console.log("\n 終了します。入力された情報はメモリから破棄されました。");
|
|
46
|
+
await shutdown(server);
|
|
47
|
+
process.exit(0);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
process.on("SIGINT", stop);
|
|
51
|
+
process.on("SIGTERM", stop);
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 完了画面(本家の `p.note(..., "セットアップ完了!")`)から、
|
|
3
|
+
* 参加者が**あとで使う値**を取り出す。
|
|
4
|
+
*
|
|
5
|
+
* ## なぜ必要か
|
|
6
|
+
*
|
|
7
|
+
* 本家は仕上げの手作業6件を端末に出して終わる。そこには貼り付けが要る URL が
|
|
8
|
+
* 5つと、**再表示できない API Key** が含まれる。端末の枠の中から手で写すのは
|
|
9
|
+
* 事故のもとなので、画面側で1つずつコピーできるようにする。
|
|
10
|
+
*
|
|
11
|
+
* ## 取り出すのは4つだけ
|
|
12
|
+
*
|
|
13
|
+
* 本家の出力を読むと、実体は次の4つで、他はすべてこれらの派生だった。
|
|
14
|
+
*
|
|
15
|
+
* | | |
|
|
16
|
+
* |---|---|
|
|
17
|
+
* | `workerUrl` | `/webhook` `/auth/callback` `/auth/line?ref=setup` `?liffId=` の元 |
|
|
18
|
+
* | `adminUrl` | 管理画面 |
|
|
19
|
+
* | `apiKey` | **再表示できない** |
|
|
20
|
+
* | `liffId` | エンドポイント URL の組み立てに使う |
|
|
21
|
+
*
|
|
22
|
+
* ## 読めなければ何もしない
|
|
23
|
+
*
|
|
24
|
+
* プロンプト契約と同じ考え方で、**本家の文言に完全一致したときだけ**取り出す。
|
|
25
|
+
* 一致しなければ null を返し、画面は端末表示のままにする。
|
|
26
|
+
* 間違った値をコピーさせるより、いつもの画面を見てもらうほうがよい。
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** 本家 0.2.11 の完了画面の見出し。ここが変わったら取り出しをやめる */
|
|
30
|
+
export const MARKERS = {
|
|
31
|
+
webhook: "② Webhook URL を設定してください:",
|
|
32
|
+
liffEndpoint: "④ LIFF エンドポイント URL を更新してください:",
|
|
33
|
+
admin: "⑥ 管理画面:",
|
|
34
|
+
apiKey: "API Key:",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** clack の note は本文を `│ … │` で囲む。枠を外して中身だけにする */
|
|
38
|
+
function unbox(line) {
|
|
39
|
+
return line.replace(/^\s*[│|]\s?/, "").replace(/\s*[│|]\s*$/, "").trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 見出しの**次の行**にある値を取る。
|
|
44
|
+
* @param {string[]} body
|
|
45
|
+
* @param {string} marker
|
|
46
|
+
*/
|
|
47
|
+
function valueAfter(body, marker) {
|
|
48
|
+
const at = body.findIndex((l) => l === marker);
|
|
49
|
+
if (at === -1) return null;
|
|
50
|
+
const value = body[at + 1];
|
|
51
|
+
return value ? value.trim() : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @typedef {object} Completion
|
|
56
|
+
* @property {string} workerUrl
|
|
57
|
+
* @property {string} adminUrl
|
|
58
|
+
* @property {string} apiKey
|
|
59
|
+
* @property {string|null} liffId
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {string[]} lines {@link Terminal#snapshot} の結果(**伏字を通す前**)
|
|
64
|
+
* @param {{ liffId?: string }} [fallback] 読み取れなかったときに使う値(フォームの入力など)
|
|
65
|
+
* @returns {Completion|null}
|
|
66
|
+
*/
|
|
67
|
+
export function parseCompletion(lines, fallback = {}) {
|
|
68
|
+
const body = lines.map(unbox);
|
|
69
|
+
|
|
70
|
+
// ② の次の行は `<workerUrl>/webhook`
|
|
71
|
+
const webhook = valueAfter(body, MARKERS.webhook);
|
|
72
|
+
const adminUrl = valueAfter(body, MARKERS.admin);
|
|
73
|
+
const apiKey = valueAfter(body, MARKERS.apiKey);
|
|
74
|
+
if (!webhook || !adminUrl || !apiKey) return null;
|
|
75
|
+
|
|
76
|
+
if (!webhook.endsWith("/webhook")) return null;
|
|
77
|
+
const workerUrl = webhook.slice(0, -"/webhook".length);
|
|
78
|
+
|
|
79
|
+
// 形が想定と違うなら、取り違えるくらいなら出さない
|
|
80
|
+
if (!/^https:\/\/\S+$/.test(workerUrl)) return null;
|
|
81
|
+
if (!/^https:\/\/\S+$/.test(adminUrl)) return null;
|
|
82
|
+
if (!/^\S+$/.test(apiKey)) return null;
|
|
83
|
+
|
|
84
|
+
// ④ の次の行は `<workerUrl>?liffId=<liffId>`
|
|
85
|
+
const endpoint = valueAfter(body, MARKERS.liffEndpoint);
|
|
86
|
+
const parsed = endpoint?.startsWith(`${workerUrl}?liffId=`)
|
|
87
|
+
? endpoint.slice(`${workerUrl}?liffId=`.length)
|
|
88
|
+
: null;
|
|
89
|
+
|
|
90
|
+
// 読めなければフォームに入っていた値を使う。
|
|
91
|
+
// どちらも無いなら null のままにして、**途中で切れた URL を渡さない**
|
|
92
|
+
const liffId = parsed || fallback.liffId || null;
|
|
93
|
+
|
|
94
|
+
return { workerUrl, adminUrl, apiKey, liffId };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 画面に出す形。**本家が指示する仕上げの手作業6件**を、貼り付ける値ごとに分ける。
|
|
99
|
+
*
|
|
100
|
+
* 文言は本家の完了画面に合わせてあるが、端末の枠から写す必要が無いように
|
|
101
|
+
* 「どこに貼るか」を先に書いている。
|
|
102
|
+
*
|
|
103
|
+
* @param {Completion} c
|
|
104
|
+
*/
|
|
105
|
+
export function completionSteps(c) {
|
|
106
|
+
return [
|
|
107
|
+
{
|
|
108
|
+
id: "reply-settings",
|
|
109
|
+
title: "LINE の応答設定を変える",
|
|
110
|
+
where: "LINE Official Account Manager → 設定 → 応答設定",
|
|
111
|
+
values: [],
|
|
112
|
+
checklist: [
|
|
113
|
+
"チャット: オン",
|
|
114
|
+
"あいさつメッセージ: オフ",
|
|
115
|
+
"Webhook: オン",
|
|
116
|
+
"応答メッセージ: オフ",
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "webhook",
|
|
121
|
+
title: "Webhook URL を設定する",
|
|
122
|
+
where: "LINE Official Account Manager → 設定 → Messaging API",
|
|
123
|
+
values: [{ label: "Webhook URL", value: `${c.workerUrl}/webhook` }],
|
|
124
|
+
checklist: ["貼り付けたあと「Webhookの利用」を ON にする"],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: "login-channel",
|
|
128
|
+
title: "LINE Login チャネルを設定する",
|
|
129
|
+
where: "LINE Developers Console → LINE Login チャネル",
|
|
130
|
+
values: [{ label: "Callback URL", value: `${c.workerUrl}/auth/callback` }],
|
|
131
|
+
checklist: [
|
|
132
|
+
"「リンクされたLINE公式アカウント」で公式アカウントを選ぶ",
|
|
133
|
+
"「友だち追加オプション」を On (aggressive) にする",
|
|
134
|
+
"「LINEログイン設定」タブ →「ウェブアプリでLINEログインを利用する」を ON",
|
|
135
|
+
"「Callback URL」に上の値を貼り付ける",
|
|
136
|
+
],
|
|
137
|
+
warn: "Callback URL が無いと、PC から QR を踏んだときに Invalid redirect_uri で無言で失敗します。スマホからは LIFF 経由なので気づけません。",
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: "liff-endpoint",
|
|
141
|
+
title: "LIFF エンドポイント URL を更新する",
|
|
142
|
+
where: "LINE Developers Console → LINE Login チャネル → LIFF",
|
|
143
|
+
// LIFF ID が分からないときは値を出さない。
|
|
144
|
+
// `?liffId=` が途中で切れた URL を貼らせると、後から原因を追いにくい壊れ方をする
|
|
145
|
+
values: c.liffId
|
|
146
|
+
? [{ label: "エンドポイント URL", value: `${c.workerUrl}?liffId=${c.liffId}` }]
|
|
147
|
+
: [],
|
|
148
|
+
checklist: c.liffId
|
|
149
|
+
? ["作成時に入れた https://example.com から、上の値に変更する"]
|
|
150
|
+
: [
|
|
151
|
+
"作成時に入れた https://example.com から、次の形に変更する",
|
|
152
|
+
`${c.workerUrl}?liffId=<あなたの LIFF ID>`,
|
|
153
|
+
],
|
|
154
|
+
warn: "?liffId= が付いていないと動きません。",
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: "invite-url",
|
|
158
|
+
title: "友だち追加 URL を配る",
|
|
159
|
+
where: "参加者・お客様に共有する",
|
|
160
|
+
values: [{ label: "友だち追加 URL", value: `${c.workerUrl}/auth/line?ref=setup` }],
|
|
161
|
+
checklist: ["QR で直接追加してもらうのではなく、この URL 経由で追加してもらう"],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: "admin",
|
|
165
|
+
title: "管理画面を開く",
|
|
166
|
+
where: "ブラウザで開く",
|
|
167
|
+
// ここだけは「貼る」ものではなく「開く」もの
|
|
168
|
+
values: [{ label: "管理画面", value: c.adminUrl, action: "open" }],
|
|
169
|
+
checklist: [
|
|
170
|
+
"開くと API Key を聞かれます。下の欄からコピーして貼り付けてください",
|
|
171
|
+
],
|
|
172
|
+
// URL に鍵を載せる案は採らない。画面共有中のアドレスバー・履歴・
|
|
173
|
+
// アクセスログ・ブックマークに残り、伏字にした意味が無くなる。
|
|
174
|
+
// 本家も API キーを httpOnly Cookie へ移す方向に動いている(本家 #102)
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
}
|