@nuwel-dev/setup 1.0.1 → 1.0.3
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/README.md +49 -6
- package/bin/setup.js +150 -36
- package/lib/wizard.js +256 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,13 +10,50 @@ npx @nuwel-dev/setup
|
|
|
10
10
|
> この橋自身は公開 npm 側に在るため scope registry を明示して実行する:
|
|
11
11
|
> `npx --@nuwel-dev:registry=https://registry.npmjs.org @nuwel-dev/setup`
|
|
12
12
|
|
|
13
|
+
## 動作環境(**始める前に**)
|
|
14
|
+
|
|
15
|
+
| | 要件 | 満たさない時 |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| OS | **macOS / Linux**(Windows は WSL2 の Linux 側で) | トークンを尋ねる**前**に停止します |
|
|
18
|
+
| Node | **22.18 以上 / 24.11 以上 / 26 以上**(23・25 は不可) | 同上(`nvm` / `Volta` で切り替えてから再実行) |
|
|
19
|
+
|
|
20
|
+
どちらもウィザードの起動直後に検査します。**発行済みのトークンが宙に浮かないよう、
|
|
21
|
+
秘密を尋ねる前に落とす**設計です(旧版は `.npmrc` を書き終えた最後の 1 手で英語のエラーになりました)。
|
|
22
|
+
|
|
13
23
|
## これは何をするか
|
|
14
24
|
|
|
15
|
-
1.
|
|
16
|
-
2.
|
|
17
|
-
3.
|
|
18
|
-
4.
|
|
19
|
-
5. `
|
|
25
|
+
1. 前提(OS / Node 版・`~/.npmrc` に安全に置けるか)を検査する — 満たさなければここで停止
|
|
26
|
+
2. GitHub のトークン作成ページ URL を表示する(`read:packages` にチェック済みの URL)
|
|
27
|
+
3. 貼り付けられたトークンを受け取る(**画面には表示しない**)
|
|
28
|
+
4. **先に接続確認する**(`~/.npmrc` はまだ触りません)。401 / 403 / 404 を**平語**で説明します(「招待をまだ Accept していない」等)
|
|
29
|
+
5. **通った時だけ** `~/.npmrc` にスコープ行と authToken を書く(権限 **600**・他の行は壊さない・原子的に置き換え)
|
|
30
|
+
6. `@nuwel-dev/figma-kit` の **latest を解決してメジャー(1)を検査し、その正確な版**で `init` へ委譲する(`^1` のようなレンジは prerelease〈1.4.0-alpha.x〉に一致しないため使いません)
|
|
31
|
+
|
|
32
|
+
> **失敗しても `~/.npmrc` は 1 バイトも変わりません。** 順序が 4 → 5 なのはそのためです。
|
|
33
|
+
> 誤入力・期限切れ・権限不足のトークンを 1 回貼っただけで、それまで動いていた**別のトークン**
|
|
34
|
+
> (`//npm.pkg.github.com/:_authToken`)が消えては困るからです。書き込みは新しいファイルを作って
|
|
35
|
+
> `rename` で置き換えるので、「途中まで書けた壊れた `.npmrc`」も残りません。
|
|
36
|
+
|
|
37
|
+
### 接続確認の経路(企業 proxy 下でも止まらないように)
|
|
38
|
+
|
|
39
|
+
第一手は **`npm` そのもの**(`npm view @nuwel-dev/figma-kit version --registry=https://npm.pkg.github.com`)です。
|
|
40
|
+
キットを実際に落とすのも `npm` なので、`~/.npmrc` の `proxy` / `https-proxy` / `cafile` / `strict-ssl` が
|
|
41
|
+
そのまま効きます。トークンは **`npm` の引数には渡さず環境変数だけ**で渡します(引数は同じマシンの
|
|
42
|
+
他のユーザーから `ps` で読めるため)。問い合わせ先はコマンドラインで固定するので、`~/.npmrc` に
|
|
43
|
+
古い `@nuwel-dev:registry=` が残っていても、そちらへ問い合わせて「接続できました」と誤表示することは
|
|
44
|
+
ありません。
|
|
45
|
+
|
|
46
|
+
`npm` が無い・時間内に終わらない・HTTP として判定できない場合だけ、直接 HTTPS で確かめます(その旨を表示)。
|
|
47
|
+
**どちらか一方でも通れば合格**にします — proxy 下は `npm` だけが通り、配布物が prerelease だけの時は
|
|
48
|
+
直接接続だけが通るので、片方の失敗で買い手を止めないためです。
|
|
49
|
+
|
|
50
|
+
### 残っているリスク(正直に)
|
|
51
|
+
|
|
52
|
+
版は固定しますが、**受け取った tarball が発行元の物かの検証はローカルでは行っていません**
|
|
53
|
+
(`npm` が lockfile の integrity で tarball のハッシュは検証しますが、それは「レジストリが返した物と
|
|
54
|
+
lockfile が一致する」ことであって「発行元が出した物である」ことではありません)。
|
|
55
|
+
発行元の同一性は、`figma-kit diagnose` の「配布物同一性」が出す **pkg-merkle** を、
|
|
56
|
+
リリース告知に載る値と照合して確認してください。
|
|
20
57
|
|
|
21
58
|
## なぜ公開 npm にあるのか
|
|
22
59
|
|
|
@@ -31,12 +68,18 @@ publish は provenance 付き(公開レジストリでのみ効く)。
|
|
|
31
68
|
- 書き込み先は `~/.npmrc` だけ。案件ディレクトリの `.npmrc` には**スコープ行しか書かない**(`figma-kit init` が書く・`.gitignore` 済み)
|
|
32
69
|
- stdout・ログ・エラーメッセージにトークンを出す経路を持たない(表示は `redact()` のマスクのみ)
|
|
33
70
|
- 例外は種別(`e.code`)だけを表示する(例外メッセージにトークンが載る事故を構造で防ぐ)
|
|
71
|
+
- 子プロセス(接続確認の `npm`)へは**環境変数だけ**で渡す。コマンドライン引数には載せません
|
|
72
|
+
- **接続確認に失敗したら `~/.npmrc` は一切変更しません**(成功した時だけ、新しいファイルを作って `rename` で原子的に置き換えます)
|
|
73
|
+
|
|
74
|
+
**マスク表示の仕様**: 登録の成功行に `abcd…yz(40 文字)` の形で **先頭 4 文字・末尾 2 文字・全体の文字数**だけを
|
|
75
|
+
表示します(それ以外は 1 文字も出しません)。これは「貼り付けが途中で切れていないか」「別の値を貼っていないか」を
|
|
76
|
+
買い手が自分で確かめられるようにするためで、意図的な仕様です。全部を隠すと、失敗時に何が起きたか誰も分かりません。
|
|
34
77
|
|
|
35
78
|
## 非対話フラグ(テスト・CI 用)
|
|
36
79
|
|
|
37
80
|
| flag | 意味 |
|
|
38
81
|
|---|---|
|
|
39
|
-
| `--no-verify` |
|
|
82
|
+
| `--no-verify` | 接続確認を行わない(**確認せずに書く**唯一の経路。ネットワークが無い環境での明示指定) |
|
|
40
83
|
| `--no-init` | `figma-kit init` へ委譲しない |
|
|
41
84
|
| `--home=<dir>` | `~/.npmrc` の代わりに `<dir>/.npmrc` を書く |
|
|
42
85
|
| `--dir=<dir>` | init を実行する案件ディレクトリ(既定は cwd) |
|
package/bin/setup.js
CHANGED
|
@@ -8,10 +8,16 @@
|
|
|
8
8
|
* なぜ公開 npm に置くのか: キット本体 `@nuwel-dev/figma-kit` は GitHub Packages(private)にあり、
|
|
9
9
|
* **トークンを設定しないと落とせない**。その設定手順自体を private 側に置くと鶏と卵になる。
|
|
10
10
|
* ここは秘密を 1 バイトも含まず(依存もゼロ)、やることは 4 つだけ:
|
|
11
|
-
* ① トークン作成ページの URL を出す → ② 貼ってもらう → ③
|
|
12
|
-
* ④
|
|
11
|
+
* ① トークン作成ページの URL を出す → ② 貼ってもらう → ③ **接続確認**(401/404 を平語で説明)→
|
|
12
|
+
* ④ **通った時だけ** ~/.npmrc へ 0600 で原子的に書く → ⑤ `npx @nuwel-dev/figma-kit init` へ委譲
|
|
13
|
+
*
|
|
14
|
+
* **③ が ④ より先なのは仕様**(2026-08-26・codex R2 Q7 block): 旧版は貼られた直後に `~/.npmrc` の
|
|
15
|
+
* スコープ行と authToken 行を置換し、その後で接続確認していた。誤入力 1 回で、それまで動いていた
|
|
16
|
+
* 別トークンが不可逆に消える(=買い手の既存の私有パッケージ取得を購入初日に壊す)。
|
|
17
|
+
* 接続確認は**買い手が実際に取得に使う npm CLI 経由**を第一手にする(企業 proxy・社内 CA を通す)。
|
|
13
18
|
*
|
|
14
19
|
* **トークンは stdout・ログ・エラー文へ 1 度も出さない**(表示は lib/wizard.js の redact のみ)。
|
|
20
|
+
* npm へ渡す時も **argv には載せず env だけ**(argv は同一ホストの他ユーザーから `ps` で読める)。
|
|
15
21
|
*
|
|
16
22
|
* flags(テスト・非対話用):
|
|
17
23
|
* --no-verify 接続確認を行わない(ネットワークが無い環境)
|
|
@@ -57,8 +63,9 @@ function prompt(question) {
|
|
|
57
63
|
});
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
/** GitHub Packages
|
|
61
|
-
|
|
66
|
+
/** GitHub Packages に直接 GET して権限を確かめる(応答本文は読み捨て・トークンはヘッダのみ)。
|
|
67
|
+
* **npm 経路で判定が付かなかった時だけ**使うフォールバック(npm の proxy / 証明書設定を通らないため) */
|
|
68
|
+
function probeDirect(token) {
|
|
62
69
|
return new Promise((resolve) => {
|
|
63
70
|
// eslint-disable-next-line global-require
|
|
64
71
|
const https = require('https');
|
|
@@ -72,20 +79,39 @@ function probe(token) {
|
|
|
72
79
|
});
|
|
73
80
|
}
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
/**
|
|
83
|
+
* npm CLI 経由の接続確認(**買い手が実際に取得に使う経路そのもの**=proxy・cafile・strict-ssl が効く)。
|
|
84
|
+
* トークンは argv に 1 度も載せず env だけで渡す(`W.npmProbeEnv` が唯一の口)。stdin は閉じる
|
|
85
|
+
* (ここまでに読み終えた stdin を npm に食わせない)。
|
|
86
|
+
*/
|
|
87
|
+
function probeNpm(token) {
|
|
88
|
+
const r = spawnSync('npm', W.npmProbeArgs(), {
|
|
89
|
+
encoding: 'utf8', env: W.npmProbeEnv(process.env, token),
|
|
90
|
+
stdio: ['ignore', 'pipe', 'pipe'], timeout: 30000,
|
|
91
|
+
});
|
|
92
|
+
return W.classifyNpmProbe({
|
|
93
|
+
status: r.status,
|
|
94
|
+
output: `${r.stdout || ''}${r.stderr || ''}`,
|
|
95
|
+
spawnError: r.error ? (r.error.code || 'ESPAWN') : null,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* `~/.npmrc` に**安全に書けるか**を検査し、既存の中身を返す。**1 バイトも書かない**ので
|
|
101
|
+
* 「トークンを尋ねる前の前提確認」と「書く直前の再検査(TOCTOU)」の両方から呼べる。
|
|
102
|
+
*
|
|
103
|
+
* 秘密を書く時の順序(codex R1〜R3 block の封鎖):
|
|
104
|
+
* ① 親ディレクトリが自分の所有で他者書込み不可(差し替え主体を排除)。O_NOFOLLOW / getuid が無い OS は**書かない**
|
|
105
|
+
* ② 既存 .npmrc は O_NOFOLLOW で開き **fd に対して** fstat(通常ファイル・自分の所有・nlink 1)してから内容を読む
|
|
106
|
+
*/
|
|
107
|
+
function inspectNpmrc() {
|
|
81
108
|
const dir = path.dirname(NPMRC);
|
|
82
109
|
fs.mkdirSync(dir, { recursive: true });
|
|
83
110
|
if (typeof process.getuid !== 'function' || !fs.constants.O_NOFOLLOW) throw Object.assign(new Error('unsafe-platform'), { code: 'EPLATFORM' });
|
|
84
111
|
const uid = process.getuid();
|
|
85
112
|
const dst = fs.lstatSync(dir);
|
|
86
113
|
if (!dst.isDirectory() || dst.uid !== uid || (dst.mode & 0o022)) throw Object.assign(new Error('parent-dir'), { code: 'EPARENT' });
|
|
87
|
-
const { O_RDONLY,
|
|
88
|
-
// ② 既存内容(あれば)
|
|
114
|
+
const { O_RDONLY, O_NOFOLLOW } = fs.constants;
|
|
89
115
|
let existing = '';
|
|
90
116
|
let rfd = null;
|
|
91
117
|
try { rfd = fs.openSync(NPMRC, O_RDONLY | O_NOFOLLOW); }
|
|
@@ -101,7 +127,19 @@ function writeNpmrc(token) {
|
|
|
101
127
|
existing = fs.readFileSync(rfd, 'utf8');
|
|
102
128
|
} finally { fs.closeSync(rfd); }
|
|
103
129
|
}
|
|
104
|
-
|
|
130
|
+
return existing;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* `~/.npmrc` を置き換える。**`W.mayWriteNpmrc` が ok を返した後にだけ呼ぶ**(codex R2 Q7 block)。
|
|
135
|
+
* ③ **既存 inode には書かない**(他者が先に read fd を握っていれば chmod 後も読める・codex R3):
|
|
136
|
+
* 同ディレクトリに O_EXCL|0600 で新 inode を作り → 書く → fsync → rename で置き換える(atomic)。
|
|
137
|
+
* rename まで到達しなければ既存の `.npmrc` は無傷=「途中まで書けた壊れた .npmrc」を作らない。
|
|
138
|
+
*/
|
|
139
|
+
function writeNpmrc(token) {
|
|
140
|
+
const existing = inspectNpmrc(); // 書く直前にもう一度(前提確認からここまでに差し替えられていないか)
|
|
141
|
+
const dir = path.dirname(NPMRC);
|
|
142
|
+
const { O_WRONLY, O_CREAT, O_EXCL, O_NOFOLLOW } = fs.constants;
|
|
105
143
|
const tmp = path.join(dir, `.npmrc.${process.pid}.${Date.now()}.tmp`);
|
|
106
144
|
const wfd = fs.openSync(tmp, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0o600);
|
|
107
145
|
try {
|
|
@@ -115,10 +153,37 @@ function writeNpmrc(token) {
|
|
|
115
153
|
try { fs.renameSync(tmp, NPMRC); } catch (e) { try { fs.unlinkSync(tmp); } catch (_) { /* noop */ } throw e; }
|
|
116
154
|
}
|
|
117
155
|
|
|
156
|
+
/** `.npmrc` 系の例外を買い手向けの 1 行にする。**例外の message は使わない**(トークンが載る経路を作らない) */
|
|
157
|
+
function npmrcErrorMessage(e) {
|
|
158
|
+
const code = e && e.code;
|
|
159
|
+
if (code === 'ESYMLINK') return `${NPMRC} がシンボリックリンクです。トークンをリンク先へ書くのは危険なので中止しました。リンクを外してから再実行してください`;
|
|
160
|
+
if (code === 'ENOTFILE') return `${NPMRC} が通常ファイルではありません。確認してから再実行してください`;
|
|
161
|
+
if (code === 'EOWNER') return `${NPMRC} があなたの所有でないか、ハードリンクされています。確認してから再実行してください`;
|
|
162
|
+
if (code === 'EPARENT') return `${path.dirname(NPMRC)} があなたの所有でないか、他のユーザーが書き込めます(安全にトークンを置けません)。権限を直してから再実行してください`;
|
|
163
|
+
if (code === 'EPLATFORM') return 'この OS では安全にトークンを保存できません(macOS / Linux で実行してください)';
|
|
164
|
+
return `${NPMRC} に書けませんでした(${code || 'エラー'})。ホームディレクトリの権限を確認してください`;
|
|
165
|
+
}
|
|
166
|
+
|
|
118
167
|
(async () => {
|
|
119
168
|
say('');
|
|
120
169
|
say('=== figma-kit セットアップ ===');
|
|
121
170
|
say('');
|
|
171
|
+
// **秘密を尋ねる前**に前提を検査する(L6#1 / L1#15)。旧版は PAT を貼らせ切ってから
|
|
172
|
+
// EBADENGINE(Node 版)・EPLATFORM(Windows)で落ちており、発行済みトークンが宙に浮いた。
|
|
173
|
+
{
|
|
174
|
+
// eslint-disable-next-line global-require
|
|
175
|
+
const engines = (require('../package.json').engines || {}).node || '';
|
|
176
|
+
const nv = W.nodeSupported(process.versions.node, engines);
|
|
177
|
+
if (!nv.ok) { say(`❌ ${nv.message}`); process.exit(1); }
|
|
178
|
+
const pv = W.platformSupported({ getuid: process.getuid, hasNoFollow: !!fs.constants.O_NOFOLLOW });
|
|
179
|
+
if (!pv.ok) { say(`❌ ${pv.message}`); process.exit(1); }
|
|
180
|
+
// `~/.npmrc` に**安全に置けるか**もここで見る(1 バイトも書かない検査)。旧版は書き込み時に初めて
|
|
181
|
+
// symlink / 所有者を判定していたため、買い手は PAT を発行して貼った**後**に「中止しました」と言われ、
|
|
182
|
+
// 発行済みトークンが宙に浮いた(L1#15 と同型)。書く直前の再検査は writeNpmrc 側に残す(TOCTOU)。
|
|
183
|
+
try { inspectNpmrc(); } catch (e) { say(`❌ ${npmrcErrorMessage(e)}`); process.exit(1); }
|
|
184
|
+
say(`· 前提の確認 OK(Node v${process.versions.node} / ${process.platform})`);
|
|
185
|
+
say('');
|
|
186
|
+
}
|
|
122
187
|
say('キット本体は GitHub の限定配布(あなたのアカウント=1 席)です。');
|
|
123
188
|
say('取得用のトークンを 1 回だけ登録します。所要 2 分。');
|
|
124
189
|
say('');
|
|
@@ -132,29 +197,31 @@ function writeNpmrc(token) {
|
|
|
132
197
|
if (!shape.ok) { say(`\n❌ ${shape.message}`); process.exit(1); }
|
|
133
198
|
const token = shape.token;
|
|
134
199
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
200
|
+
// ③ **確かめてから書く**(2026-08-26・codex R2 Q7 block の封鎖)。
|
|
201
|
+
// 旧版はここで先に `~/.npmrc` を置換し、その後で接続確認していた。誤入力・期限切れ・権限不足の
|
|
202
|
+
// トークン 1 つで、それまで動いていた別トークンとスコープ行が不可逆に消え、買い手の既存の私有
|
|
203
|
+
// パッケージ取得が購入初日に壊れていた。**probe が 200 を返すまで `~/.npmrc` は 1 バイトも触らない**。
|
|
204
|
+
const verified = !flag('--no-verify');
|
|
205
|
+
let status = null;
|
|
206
|
+
if (verified) {
|
|
207
|
+
say(`③ 接続を確認しています(${NPMRC} はまだ変更しません)…`);
|
|
208
|
+
// 第一手は npm CLI=買い手が実際に取得に使う経路(proxy・cafile・strict-ssl が効く)
|
|
209
|
+
const n = probeNpm(token);
|
|
210
|
+
status = n.status;
|
|
211
|
+
if (status !== 200) {
|
|
212
|
+
// npm が 200 を返さなかった=「本当に落とせない」か「npm 経路が使えない」かの区別が付かないので、
|
|
213
|
+
// **必ず**直接接続でも確かめて合成する(W.resolveProbe: どちらかが 200 なら合格)
|
|
214
|
+
if (n.note) say(` · ${n.note}。直接接続でも確かめます(npm の proxy 設定は通りません)`);
|
|
215
|
+
status = W.resolveProbe(n.status, await probeDirect(token));
|
|
216
|
+
}
|
|
151
217
|
const r = W.explainStatus(status);
|
|
152
218
|
if (!r.ok) {
|
|
153
219
|
say(`\n❌ ${r.message}`);
|
|
154
220
|
if (r.next) say(` → ${r.next}`);
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
say(`
|
|
221
|
+
// **これが Q7 の要**: 書く前に止めたので、買い手の既存設定は無傷。そう明示しないと
|
|
222
|
+
// 「もう壊れたのでは」と疑わせるし、実際に壊していた旧版との違いが伝わらない
|
|
223
|
+
say(` ※ ${NPMRC} は 1 バイトも変更していません(これまでの設定はそのまま残っています)`);
|
|
224
|
+
say(' (直したら次で再実行してください — scope registry の明示は 1・2 回目のどちらでも効きます)');
|
|
158
225
|
say(` npx --@nuwel-dev:registry=${W.NPMJS} @nuwel-dev/setup`);
|
|
159
226
|
process.exit(1);
|
|
160
227
|
}
|
|
@@ -163,19 +230,66 @@ function writeNpmrc(token) {
|
|
|
163
230
|
say('· 接続確認は省略しました(--no-verify)');
|
|
164
231
|
}
|
|
165
232
|
|
|
166
|
-
|
|
233
|
+
// 書く前に必ず通る関門。上の `explainStatus` で既に弾いているので通常は素通りする**backstop** で、
|
|
234
|
+
// 判定基準はわざと独立させてある(explainStatus は説明のための合否・こちらは status===200 ちょうど)。
|
|
235
|
+
// 片方だけを将来緩めても、もう片方が書き込みを止める。不明な形は fail-closed で拒否する。
|
|
236
|
+
const gate = W.mayWriteNpmrc({ verified, status });
|
|
237
|
+
if (!gate.ok) { say(`\n❌ 接続確認に通らなかったので中止しました(${gate.reason})。${NPMRC} は変更していません`); process.exit(1); }
|
|
238
|
+
|
|
239
|
+
try { writeNpmrc(token); } catch (e) {
|
|
240
|
+
// エラー文にトークンが載る経路を作らない(メッセージは自前で組む)
|
|
241
|
+
say(`\n❌ ${npmrcErrorMessage(e)}`);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
say('');
|
|
245
|
+
say(`✅ ${NPMRC} に登録しました(権限 600・${W.redact(token)})`);
|
|
246
|
+
// **今書いた設定が、このウィザード自身の 2 回目を壊す**(codex R1 major の恒久案内)。
|
|
247
|
+
// `@nuwel-dev:registry=<GitHub Packages>` を ~/.npmrc に置いた以上、素の
|
|
248
|
+
// `npx @nuwel-dev/setup` は公開 npm ではなく GitHub Packages を見に行って 404 になる。
|
|
249
|
+
// 失敗してから案内するのでは遅いので、**成功した今**、次回の打ち方を渡しておく。
|
|
250
|
+
say(` (このウィザードをもう一度実行する時は: npx --@nuwel-dev:registry=${W.NPMJS} @nuwel-dev/setup)`);
|
|
251
|
+
|
|
252
|
+
if (flag('--no-init')) { say('\n· ここまで(--no-init)。次は `npx @nuwel-dev/figma-kit@latest init` を実行してください'); return; }
|
|
167
253
|
|
|
168
254
|
const dir = path.resolve(val('--dir') || process.cwd());
|
|
169
255
|
say('');
|
|
170
256
|
say(`④ 案件を配線します: ${dir}`);
|
|
171
|
-
|
|
257
|
+
say(' (依存の取得と chromium で数分かかります。前提が合わない環境なら開始 1 秒で止まります)');
|
|
258
|
+
// **版を固定する**(codex/L3#6): 素の `npx --yes @nuwel-dev/figma-kit` はレジストリが返した物を
|
|
259
|
+
// 無確認で install+実行する。旧実装は `@^1` でメジャーを固定していたが、npm の semver 規則で
|
|
260
|
+
// `^1` は prerelease(1.4.0-alpha.x)に一致せず、安定版 1.x が無い間は必ず「一致する版が無い」で
|
|
261
|
+
// 落ちていた(2026-08-26 敵対レビュー block)。今は `latest` を解決 → メジャー検査 → **正確な版**で
|
|
262
|
+
// `npm exec` する(W.resolveInitVersion)。`npm exec` を使うのは、`--package=<名前>@<版>` で**取得する物**と
|
|
263
|
+
// **実行する名前**を別々に明示できるため(npx の位置引数だと版付き指定が name と混ざる)。
|
|
264
|
+
const view = spawnSync('npm', ['view', `${W.SCOPE}/figma-kit@latest`, 'version', '--json'], {
|
|
265
|
+
encoding: 'utf8', env: process.env, stdio: ['ignore', 'pipe', 'pipe'],
|
|
266
|
+
});
|
|
267
|
+
const resolved = W.resolveInitVersion(view.status === 0 ? view.stdout : '');
|
|
268
|
+
if (!resolved.ok) {
|
|
269
|
+
say(`\n❌ figma-kit の版を解決できません: ${resolved.why}`);
|
|
270
|
+
if (view.status !== 0) say(` npm view の失敗: ${W.explainNpmViewFailure({ status: view.status, stderr: view.stderr, spawnError: view.error && view.error.code })}`);
|
|
271
|
+
say(' トークンの権限(read:packages)と招待の Accept を確認して、同じコマンドで再実行してください');
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
say(` figma-kit ${resolved.version}(latest)を使います`);
|
|
275
|
+
const r = spawnSync('npm', ['exec', '--yes', `--package=${W.SCOPE}/figma-kit@${resolved.version}`, '--', 'figma-kit', 'init', dir], {
|
|
276
|
+
stdio: 'inherit', env: process.env,
|
|
277
|
+
});
|
|
172
278
|
if (r.status !== 0) {
|
|
173
|
-
say('\n❌ init
|
|
279
|
+
say('\n❌ init が失敗しました。上の出力の ❌ 行を見てください(直したら同じコマンドで再開できます)');
|
|
280
|
+
say(` やり直し: npx --yes ${W.SCOPE}/figma-kit@${resolved.version} init ${dir}`);
|
|
174
281
|
process.exit(r.status === null ? 1 : r.status);
|
|
175
282
|
}
|
|
176
283
|
say('');
|
|
177
284
|
say('=== 完了 ===');
|
|
178
|
-
|
|
285
|
+
// **実在する導線だけを案内する**(L1#3)。`/implement` 等は init が案件の
|
|
286
|
+
// `.claude/commands/` へ配線したファイルで、Claude Code がそこから発見する。
|
|
287
|
+
say('① 設置の確認(必須がすべて緑になれば完了):');
|
|
288
|
+
say(` cd ${dir} && npx figma-kit diagnose`);
|
|
289
|
+
say('② Claude Code を開いて、最初に打つ 1 行:');
|
|
290
|
+
say(' /orchestrate … 全体を状態機械で進める');
|
|
291
|
+
say(' /implement <セクション名> … 1 セクションだけ実装する');
|
|
292
|
+
say(' (使えるコマンドの一覧は .claude/commands/ ・手順の説明は .claude/USAGE.md)');
|
|
179
293
|
})().catch((e) => {
|
|
180
294
|
// 例外の message にトークンが混ざる可能性を排除するため、種別だけを出す
|
|
181
295
|
say(`\n❌ セットアップが異常終了しました(${(e && e.code) || 'エラー'})`);
|
package/lib/wizard.js
CHANGED
|
@@ -56,6 +56,129 @@ function mergeNpmrc(existing, token) {
|
|
|
56
56
|
return `${lines.join('\n')}\n`;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* 接続確認を **npm CLI 経由**で行うための引数(`npm view @nuwel-dev/figma-kit version`)。
|
|
61
|
+
*
|
|
62
|
+
* なぜ生 https ではなく npm なのか(2026-08-26・codex R2 Q7): 買い手が実際にキットを落とすのは
|
|
63
|
+
* `npm`(`npm exec … figma-kit init`)で、npm は proxy・cafile・strict-ssl・NODE_EXTRA_CA_CERTS を
|
|
64
|
+
* **自分の設定から**読む。生 `https.request` はそのどれも通らないので、企業 proxy 下では
|
|
65
|
+
* 「npm なら落とせるのに、接続確認だけが落ちる」=**買い手を誤って止める**。
|
|
66
|
+
*
|
|
67
|
+
* **トークンは argv に載せない**(argv は同一ホストの他ユーザーから `ps` で読める)。渡し口は
|
|
68
|
+
* `npmProbeEnv` の env 1 つだけ。`--prefer-online` は「前回の 200 がキャッシュに残っていて、
|
|
69
|
+
* 期限切れトークンが通ってしまう」経路を閉じるため(再検証を強制する)。
|
|
70
|
+
*
|
|
71
|
+
* **`--@nuwel-dev:registry=` を必ず併記する**(2026-08-26 実測で判明した hollow green の封鎖):
|
|
72
|
+
* scope 付きパッケージの取得先を決めるのは `--registry` ではなく `@scope:registry` の方で、
|
|
73
|
+
* 買い手の `~/.npmrc` に古い/誤った `@nuwel-dev:registry=` が残っていると `--registry` だけでは
|
|
74
|
+
* **そちらへ問い合わせに行く**。相手が 200 を返せば「接続できました」と出てしまい、
|
|
75
|
+
* 実際には GitHub Packages を 1 度も見ていない(=空回りの緑)。cli の config は npmrc より強いので、
|
|
76
|
+
* 両方を argv に置いて取得先を確定させる。
|
|
77
|
+
* @returns {string[]}
|
|
78
|
+
*/
|
|
79
|
+
function npmProbeArgs() {
|
|
80
|
+
return [
|
|
81
|
+
'view', `${SCOPE}/figma-kit`, 'version',
|
|
82
|
+
`--${SCOPE}:registry=${REGISTRY}`,
|
|
83
|
+
`--registry=${REGISTRY}`,
|
|
84
|
+
'--prefer-online', '--loglevel=error',
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 上の `npm` に**トークンだけ**を足した env を作る(`process.env` は書き換えない)。
|
|
90
|
+
*
|
|
91
|
+
* npm は `npm_config_` 接頭の env を config として読み、**`//` で始まるキーは正規化しない**(nerf dart は
|
|
92
|
+
* そのまま)ので、`npm_config_//npm.pkg.github.com/:_authToken` が `.npmrc` の同名行と同じ意味になる。
|
|
93
|
+
* env は cli の次に強く、買い手の `~/.npmrc` に残っている**古いトークン行より優先される**(=古い値で
|
|
94
|
+
* 401 になって新しい正しいトークンを弾く事故が起きない)。一方 proxy・cafile 等は `~/.npmrc` 側が
|
|
95
|
+
* そのまま効く(userconfig を差し替えていないため)=この関数が proxy 対応の肝。
|
|
96
|
+
*
|
|
97
|
+
* `logs-max=0` は npm のデバッグログファイルを 1 つも作らせないため(npm は秘匿値を redact するが、
|
|
98
|
+
* **作らせない**方が確実)。
|
|
99
|
+
* @param {NodeJS.ProcessEnv} base process.env
|
|
100
|
+
* @param {string} token
|
|
101
|
+
* @returns {NodeJS.ProcessEnv}
|
|
102
|
+
*/
|
|
103
|
+
function npmProbeEnv(base, token) {
|
|
104
|
+
const env = { ...(base || {}) };
|
|
105
|
+
env[`npm_config_${REGISTRY_KEY}`] = String(token);
|
|
106
|
+
env.npm_config_logs_max = '0';
|
|
107
|
+
env.npm_config_update_notifier = 'false';
|
|
108
|
+
env.npm_config_fund = 'false';
|
|
109
|
+
env.npm_config_audit = 'false';
|
|
110
|
+
return env;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* `npm view` の終了状態と出力から HTTP ステータス相当を導く。
|
|
115
|
+
*
|
|
116
|
+
* **接頭辞やコードを列挙しない**(列挙は必ずその外側を穴にする): npm は失敗を必ず `code E<数字>` の
|
|
117
|
+
* 形で出す(E401 / E403 / E404 / E500 …)ので、3 桁の数字が付いた物だけを「HTTP の判定が付いた」と見なし、
|
|
118
|
+
* それ以外(ENOTFOUND / ECONNREFUSED / ERR_TLS_* / npm 不在 / timeout)はすべて **conclusive=false**=
|
|
119
|
+
* 「判定できなかった」に倒して生 https のフォールバックへ回す。
|
|
120
|
+
* @param {{status:number|null, output:string, spawnError:string|null}} r
|
|
121
|
+
* @returns {{status:number|null, conclusive:boolean, reason:string, note:string}}
|
|
122
|
+
*/
|
|
123
|
+
function classifyNpmProbe(input) {
|
|
124
|
+
// 引数欠落は「判定できなかった」=フォールバックへ倒す(例外で落として probe ごと消さない)
|
|
125
|
+
const r = input && typeof input === 'object' ? input : {};
|
|
126
|
+
const spawnError = r.spawnError ? String(r.spawnError) : null;
|
|
127
|
+
if (spawnError === 'ENOENT') return { status: null, conclusive: false, reason: 'npm-not-found', note: 'npm が見つかりませんでした' };
|
|
128
|
+
if (spawnError === 'ETIMEDOUT') return { status: null, conclusive: false, reason: 'npm-timeout', note: 'npm が時間内に終わりませんでした' };
|
|
129
|
+
if (spawnError) return { status: null, conclusive: false, reason: `npm-spawn-${spawnError}`, note: `npm を起動できませんでした(${spawnError})` };
|
|
130
|
+
if (r.status === 0) return { status: 200, conclusive: true, reason: 'npm-ok', note: '' };
|
|
131
|
+
const m = /(?:^|\s)code\s+E(\d{3})(?:\s|$)/m.exec(String(r.output || ''));
|
|
132
|
+
if (m) return { status: Number(m[1]), conclusive: true, reason: `npm-E${m[1]}`, note: `npm が HTTP ${m[1]} を受け取りました` };
|
|
133
|
+
if (r.status === null) return { status: null, conclusive: false, reason: 'npm-killed', note: 'npm が最後まで実行されませんでした' };
|
|
134
|
+
return { status: null, conclusive: false, reason: 'npm-unclassified', note: 'npm の応答を HTTP として判定できませんでした' };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* npm 経路と直結経路の結果を合成する。
|
|
139
|
+
*
|
|
140
|
+
* **どちらか一方でも 200 なら合格**にするのが要。片方だけが通る環境が両向きに実在する:
|
|
141
|
+
* ① 企業 proxy 下 — npm は通り、生 https は通らない
|
|
142
|
+
* ② 未公開/prerelease だけの状態 — `npm view … version` は E404 でも packument の GET は 200
|
|
143
|
+
* どちらでも買い手は本当に取得できるので、**止めてはいけない**。合格でない時は「HTTP の判定が付いた方」を
|
|
144
|
+
* 採る(「ネットワークに出られません」より「401 でトークンが違います」の方が買い手が直せる)。
|
|
145
|
+
* @param {number|null} npmStatus
|
|
146
|
+
* @param {number|null} httpStatus
|
|
147
|
+
* @returns {number|null}
|
|
148
|
+
*/
|
|
149
|
+
function resolveProbe(npmStatus, httpStatus) {
|
|
150
|
+
if (npmStatus === 200 || httpStatus === 200) return 200;
|
|
151
|
+
if (typeof npmStatus === 'number') return npmStatus;
|
|
152
|
+
if (typeof httpStatus === 'number') return httpStatus;
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* **`~/.npmrc` を書いてよいか**を接続確認の結果だけから決める、唯一の口(2026-08-26・codex R2 Q7 block)。
|
|
158
|
+
*
|
|
159
|
+
* 旧版はトークンを受け取った直後に `~/.npmrc` を置換し、**その後で**接続確認していた。誤入力・期限切れ・
|
|
160
|
+
* 権限不足のトークン 1 つで、それまで動いていた別トークン(`//npm.pkg.github.com/:_authToken`)と
|
|
161
|
+
* スコープ行が不可逆に消え、買い手の既存の私有パッケージ取得が購入初日に壊れた。
|
|
162
|
+
*
|
|
163
|
+
* 判定を bin 側の `if` に散らすと同じ事故がいつでも戻せるので、**書く前に必ず通る関門**をここに置く。
|
|
164
|
+
* bin 側は `explainStatus(status).ok` が偽なら先に exit するので、通常この関門は素通りする(=backstop)。
|
|
165
|
+
* 判定基準は独立している: `explainStatus` は買い手への説明が目的で「合否」を返し、こちらは
|
|
166
|
+
* **`status === 200` ちょうど**でしか書かせない。片方だけを緩めても、もう片方が書き込みを止める。
|
|
167
|
+
*
|
|
168
|
+
* **不明な形は書かせない**(fail-closed): `--no-verify` は `verified === false` の明示だけを指し、
|
|
169
|
+
* 引数欠落・`verified` が真偽でない・呼び出し形の変更はすべて「判定が無い」=拒否に倒す。
|
|
170
|
+
* 「唯一の書き込み関門」が既定で通す作りだと、将来の呼び出し追加で静かに穴になる(codex/change-review)。
|
|
171
|
+
* @param {{verified:boolean, status:number|null}} a verified=false は `--no-verify`(明示的に確認しない指定)
|
|
172
|
+
* @returns {{ok:boolean, reason:string}}
|
|
173
|
+
*/
|
|
174
|
+
function mayWriteNpmrc(a) {
|
|
175
|
+
const o = a && typeof a === 'object' ? a : null;
|
|
176
|
+
if (!o || typeof o.verified !== 'boolean') return { ok: false, reason: 'no-verdict' };
|
|
177
|
+
if (o.verified === false) return { ok: true, reason: 'skip-verify' };
|
|
178
|
+
if (o.status === 200) return { ok: true, reason: 'probe-200' };
|
|
179
|
+
return { ok: false, reason: `probe-${o.status === null || o.status === undefined ? 'unreachable' : o.status}` };
|
|
180
|
+
}
|
|
181
|
+
|
|
59
182
|
/**
|
|
60
183
|
* 接続確認の HTTP ステータスを**平語**にする。
|
|
61
184
|
* 買い手が最も踏むのは 401(トークンが違う/期限切れ)と 404(招待を受けていない・
|
|
@@ -94,4 +217,136 @@ function explainStatus(status) {
|
|
|
94
217
|
return { ok: false, message: `想定外の応答でした(HTTP ${status})`, next: 'この行をそのまま販売元へ連絡してください' };
|
|
95
218
|
}
|
|
96
219
|
|
|
97
|
-
|
|
220
|
+
/**
|
|
221
|
+
* 実行中の Node がキットの床(この package.json の `engines.node`)を満たすか。
|
|
222
|
+
*
|
|
223
|
+
* なぜ入口で見るのか(2026-08-26・L6#1): 旧版は `engines: ">=20"` を宣言していて、Node 20/21/23/25 の
|
|
224
|
+
* 買い手が**成功して進み**、PAT を発行して貼り、`.npmrc` を書き終えた**最後の 1 手**(figma-kit init)で
|
|
225
|
+
* 初めて英語の EBADENGINE を踏んでいた。発行済みのトークンが宙に浮く最悪の落ち方なので、
|
|
226
|
+
* **秘密を求める前**に日本語で止める。
|
|
227
|
+
*
|
|
228
|
+
* 判定は `^a.b.c || >=d.e.f` の形(キットの engines が実際に使う記法)だけを解釈する。
|
|
229
|
+
* **解釈できないトークンは「満たす」に倒す** — レンジは自分の package.json 由来で
|
|
230
|
+
* 外部入力ではなく、ここで買い手を誤って弾く方が害が大きい(記法の妥当性は
|
|
231
|
+
* `tools/__tests__/setup-wizard.test.js` が実レンジで固定する)。
|
|
232
|
+
* @param {string} version process.versions.node("22.18.0")
|
|
233
|
+
* @param {string} range engines.node
|
|
234
|
+
* @returns {{ok:boolean, message:string}}
|
|
235
|
+
*/
|
|
236
|
+
function nodeSupported(version, range) {
|
|
237
|
+
const r = String(range == null ? '' : range).trim();
|
|
238
|
+
const m = /^v?(\d+)\.(\d+)/.exec(String(version == null ? '' : version));
|
|
239
|
+
// **前提が読めない時に先へ進めない**(codex R1 major)。ここはこの直後に PAT(秘密)を尋ねる門で、
|
|
240
|
+
// レンジはこのパッケージ自身の package.json 由来=読めないなら配布物が壊れている。
|
|
241
|
+
// 「読めないので通す」は、対応外の Node に PAT を発行させてから落とす旧挙動に戻ることを意味する。
|
|
242
|
+
if (!r || !m) {
|
|
243
|
+
return {
|
|
244
|
+
ok: false,
|
|
245
|
+
message: `対応 Node 版を判定できませんでした(宣言: ${r || '(なし)'} / 実行中: ${version})。`
|
|
246
|
+
+ '\n 配布物が壊れている可能性があります。販売元へこの行をそのまま連絡してください。',
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const major = Number(m[1]);
|
|
250
|
+
const minor = Number(m[2]);
|
|
251
|
+
const unknown = [];
|
|
252
|
+
const satisfiesAlt = (alt) => alt.trim().split(/\s+/).filter(Boolean).every((tok) => {
|
|
253
|
+
const t = /^(>=|<=|>|<|\^|~|=)?v?(\d+)(?:\.(\d+))?/.exec(tok.replace(/([<>]=?|\^|~|=)\s+/, '$1'));
|
|
254
|
+
if (!t) { unknown.push(tok); return false; } // 未知の記法は**満たさない**側へ(読めない前提で秘密を尋ねない)
|
|
255
|
+
const op = t[1] || '=';
|
|
256
|
+
const maj = Number(t[2]);
|
|
257
|
+
const min = t[3] === undefined ? 0 : Number(t[3]);
|
|
258
|
+
switch (op) {
|
|
259
|
+
case '>=': return major > maj || (major === maj && minor >= min);
|
|
260
|
+
case '>': return major > maj || (major === maj && minor > min);
|
|
261
|
+
case '<=': return major < maj || (major === maj && minor <= min);
|
|
262
|
+
case '<': return major < maj || (major === maj && minor < min);
|
|
263
|
+
case '^': case '~': case '=': return major === maj && minor >= min;
|
|
264
|
+
default: return true;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
if (r.split('||').some(satisfiesAlt)) return { ok: true, message: '' };
|
|
268
|
+
if (unknown.length) {
|
|
269
|
+
return {
|
|
270
|
+
ok: false,
|
|
271
|
+
message: `対応 Node 版の宣言を解釈できませんでした(読めなかった: ${unknown.slice(0, 3).join(', ')} / 宣言: ${r})。`
|
|
272
|
+
+ '\n 配布物が壊れている可能性があります。販売元へこの行をそのまま連絡してください。',
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
ok: false,
|
|
277
|
+
message: `Node v${version} はこのキットの対応外です(対応: ${r})。`
|
|
278
|
+
+ '\n nvm / Volta で対応版に切り替えてから、もう一度 `npx @nuwel-dev/setup` を実行してください。'
|
|
279
|
+
+ '\n (トークンはまだ作らないでください — 対応版に切り替えてからで大丈夫です)',
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 秘密を安全に保存できる OS か。**トークンを尋ねる前**に判定する(L1#15)。
|
|
285
|
+
* 旧版は writeNpmrc の中で判定していたため、Windows の買い手は「所要 2 分」を読み、PAT を発行して
|
|
286
|
+
* 貼り付けた**後**で「この OS では安全にトークンを保存できません」と言われ、発行済み PAT が宙に浮いた。
|
|
287
|
+
* @returns {{ok:boolean, message:string}}
|
|
288
|
+
*/
|
|
289
|
+
function platformSupported({ getuid, hasNoFollow }) {
|
|
290
|
+
if (typeof getuid !== 'function' || !hasNoFollow) {
|
|
291
|
+
return {
|
|
292
|
+
ok: false,
|
|
293
|
+
message: 'この OS では安全にトークンを保存できません(macOS / Linux、または WSL2 の Linux 側で実行してください)。'
|
|
294
|
+
+ '\n (トークンはまだ作らないでください — 対応 OS で実行すれば 1 回で終わります)',
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
return { ok: true, message: '' };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* init に委譲する figma-kit の版を **`latest` dist-tag から解決し、メジャーを検査して正確な版に固定する**。
|
|
302
|
+
*
|
|
303
|
+
* 2026-08-26(敵対レビュー block・prerelease-caret-resolution): 旧実装は `--package=@nuwel-dev/figma-kit@^1`
|
|
304
|
+
* だったが、npm の semver 規則で `^1` は prerelease(1.4.0-alpha.x)に一致しない。レジストリに安定版 1.x が
|
|
305
|
+
* 1 つも無い間、買い手導線は「一致する版が無い」で必ず落ちていた。レンジで prerelease を拾うには
|
|
306
|
+
* includePrerelease が要り npm exec では指定できないため、`npm view <pkg>@latest version` で
|
|
307
|
+
* **公開側が latest に載せた版**を取り、(a) メジャーが想定と違えば fail-loud(「別メジャーの別物が
|
|
308
|
+
* 初日に降ってくる」経路は閉じたまま)(b) 通れば **その正確な版**で `npm exec` する(レンジより強い固定)。
|
|
309
|
+
*
|
|
310
|
+
* @param {string} stdout `npm view <pkg>@latest version --json` の stdout
|
|
311
|
+
* @param {{major?: number}} [opts]
|
|
312
|
+
* @returns {{ok: true, version: string} | {ok: false, why: string}}
|
|
313
|
+
*/
|
|
314
|
+
function resolveInitVersion(stdout, opts = {}) {
|
|
315
|
+
const major = Number.isInteger(opts.major) ? opts.major : 1;
|
|
316
|
+
let v = null;
|
|
317
|
+
try { v = JSON.parse(String(stdout || '').trim()); } catch (_) { v = null; }
|
|
318
|
+
if (Array.isArray(v)) v = v[v.length - 1]; // 複数版が返る形(レンジ指定)でも最後を採る
|
|
319
|
+
// 正準 semver のみ(数値識別子の先頭 0 は不可 — 敵対レビュー minor)
|
|
320
|
+
if (typeof v !== 'string' || !/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[0-9A-Za-z.-]+)?$/.test(v)) {
|
|
321
|
+
return { ok: false, why: `latest の版を取得できません(npm view の出力: ${String(stdout || '').trim().slice(0, 80) || '(空)'})` };
|
|
322
|
+
}
|
|
323
|
+
const got = Number(v.split('.')[0]);
|
|
324
|
+
if (got !== major) return { ok: false, why: `latest の版 ${v} はメジャー ${major} ではありません(別メジャーの物は初日に入れない)` };
|
|
325
|
+
return { ok: true, version: v };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* `npm view` 失敗の**理由**を買い手向けに要約する(pure・敵対レビュー block「stderr の最終行は常に
|
|
330
|
+
* 『A complete log of this run can be found in: <path>』でログパスしか出ない」への対処)。
|
|
331
|
+
* HTTP 相当は classifyNpmProbe と同じ `code E<数字>` の拾い方で位置に依存せず取り、ネットワーク系は
|
|
332
|
+
* コード名で名指しする。生の行はそのまま出さない(トークン値が混ざる余地を作らない)。
|
|
333
|
+
*
|
|
334
|
+
* @param {{status:number|null, stderr?:string, spawnError?:string|null}} r
|
|
335
|
+
* @returns {string}
|
|
336
|
+
*/
|
|
337
|
+
function explainNpmViewFailure(r) {
|
|
338
|
+
const c = classifyNpmProbe({ status: r && r.status, output: (r && r.stderr) || '', spawnError: (r && r.spawnError) || null });
|
|
339
|
+
if (c.status && c.status !== 200) {
|
|
340
|
+
const ex = explainStatus(c.status);
|
|
341
|
+
return `${ex.message}${ex.next ? `。${ex.next}` : ''}`;
|
|
342
|
+
}
|
|
343
|
+
const net = /\b(ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET|EAI_AGAIN|CERT_[A-Z_]+|UNABLE_TO_[A-Z_]+)\b/.exec(String((r && r.stderr) || ''));
|
|
344
|
+
if (net) return `レジストリへ接続できませんでした(${net[1]})。ネットワーク・プロキシ・VPN を確認して再実行してください`;
|
|
345
|
+
return `${c.note || 'npm の応答を判定できませんでした'}(exit ${r && r.status === null ? 'なし' : (r && r.status)})`;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
module.exports = {
|
|
349
|
+
SCOPE, REGISTRY, NPMJS, REGISTRY_KEY, PAT_PAGE, PROBE_PATH,
|
|
350
|
+
redact, validateTokenShape, mergeNpmrc, explainStatus, nodeSupported, platformSupported,
|
|
351
|
+
npmProbeArgs, npmProbeEnv, classifyNpmProbe, resolveProbe, mayWriteNpmrc, resolveInitVersion, explainNpmViewFailure,
|
|
352
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuwel-dev/setup",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "figma-kit のセットアップウィザード(公開 npm・秘密ゼロ)。GitHub Packages のトークンを ~/.npmrc に置き、@nuwel-dev/figma-kit の init へ橋渡しする。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"nuwel-setup": "bin/setup.js"
|
|
19
19
|
},
|
|
20
20
|
"engines": {
|
|
21
|
-
"node": ">=
|
|
21
|
+
"node": "^22.18.0 || ^24.11.0 || >=26.0.0"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"bin/",
|