@nuwel-dev/setup 1.0.2 → 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 CHANGED
@@ -27,7 +27,7 @@ npx @nuwel-dev/setup
27
27
  3. 貼り付けられたトークンを受け取る(**画面には表示しない**)
28
28
  4. **先に接続確認する**(`~/.npmrc` はまだ触りません)。401 / 403 / 404 を**平語**で説明します(「招待をまだ Accept していない」等)
29
29
  5. **通った時だけ** `~/.npmrc` にスコープ行と authToken を書く(権限 **600**・他の行は壊さない・原子的に置き換え)
30
- 6. `@nuwel-dev/figma-kit@^1 init` へ委譲する(**メジャー版を固定**して実行します)
30
+ 6. `@nuwel-dev/figma-kit` の **latest を解決してメジャー(1)を検査し、その正確な版**で `init` へ委譲する(`^1` のようなレンジは prerelease〈1.4.0-alpha.x〉に一致しないため使いません)
31
31
 
32
32
  > **失敗しても `~/.npmrc` は 1 バイトも変わりません。** 順序が 4 → 5 なのはそのためです。
33
33
  > 誤入力・期限切れ・権限不足のトークンを 1 回貼っただけで、それまで動いていた**別のトークン**
package/bin/setup.js CHANGED
@@ -249,23 +249,35 @@ function npmrcErrorMessage(e) {
249
249
  // 失敗してから案内するのでは遅いので、**成功した今**、次回の打ち方を渡しておく。
250
250
  say(` (このウィザードをもう一度実行する時は: npx --@nuwel-dev:registry=${W.NPMJS} @nuwel-dev/setup)`);
251
251
 
252
- if (flag('--no-init')) { say('\n· ここまで(--no-init)。次は `npx @nuwel-dev/figma-kit@^1 init` を実行してください'); return; }
252
+ if (flag('--no-init')) { say('\n· ここまで(--no-init)。次は `npx @nuwel-dev/figma-kit@latest init` を実行してください'); return; }
253
253
 
254
254
  const dir = path.resolve(val('--dir') || process.cwd());
255
255
  say('');
256
256
  say(`④ 案件を配線します: ${dir}`);
257
257
  say(' (依存の取得と chromium で数分かかります。前提が合わない環境なら開始 1 秒で止まります)');
258
258
  // **版を固定する**(codex/L3#6): 素の `npx --yes @nuwel-dev/figma-kit` はレジストリが返した物を
259
- // 無確認で install+実行する。メジャーを固定すれば、少なくとも「別メジャーの別物が初日に降ってくる」
260
- // 経路は閉じる(integrity 検証まではローカルでは行えない README の残存リスクに明記)。
261
- // `npm exec` を使うのは、`--package=<名前>@<レンジ>` で**取得する物**と**実行する名前**を
262
- // 別々に明示できるため(npx の位置引数だと版付き指定が name と混ざる)。
263
- const r = spawnSync('npm', ['exec', '--yes', `--package=${W.SCOPE}/figma-kit@^1`, '--', 'figma-kit', 'init', dir], {
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], {
264
276
  stdio: 'inherit', env: process.env,
265
277
  });
266
278
  if (r.status !== 0) {
267
279
  say('\n❌ init が失敗しました。上の出力の ❌ 行を見てください(直したら同じコマンドで再開できます)');
268
- say(` やり直し: npx --yes ${W.SCOPE}/figma-kit@^1 init ${dir}`);
280
+ say(` やり直し: npx --yes ${W.SCOPE}/figma-kit@${resolved.version} init ${dir}`);
269
281
  process.exit(r.status === null ? 1 : r.status);
270
282
  }
271
283
  say('');
package/lib/wizard.js CHANGED
@@ -297,8 +297,56 @@ function platformSupported({ getuid, hasNoFollow }) {
297
297
  return { ok: true, message: '' };
298
298
  }
299
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
+
300
348
  module.exports = {
301
349
  SCOPE, REGISTRY, NPMJS, REGISTRY_KEY, PAT_PAGE, PROBE_PATH,
302
350
  redact, validateTokenShape, mergeNpmrc, explainStatus, nodeSupported, platformSupported,
303
- npmProbeArgs, npmProbeEnv, classifyNpmProbe, resolveProbe, mayWriteNpmrc,
351
+ npmProbeArgs, npmProbeEnv, classifyNpmProbe, resolveProbe, mayWriteNpmrc, resolveInitVersion, explainNpmViewFailure,
304
352
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuwel-dev/setup",
3
- "version": "1.0.2",
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,