sparkle-design-cli 2.5.0-beta.6 → 2.5.0-beta.7
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 +46 -0
- package/bin/sparkle-design.js +82 -1
- package/lib/auth.js +167 -1
- package/lib/migrate-registry.js +390 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,6 +79,9 @@ npx --yes sparkle-design-cli migrate
|
|
|
79
79
|
|
|
80
80
|
# Artifact Registry の npm registry 用トークンを ~/.npmrc に書く(beta)
|
|
81
81
|
npx --yes sparkle-design-cli@beta auth
|
|
82
|
+
|
|
83
|
+
# @goodpatch の取得元を GitHub Packages から Artifact Registry へ切り替える(beta。既定は dry-run)
|
|
84
|
+
npx --yes sparkle-design-cli@beta migrate-registry
|
|
82
85
|
```
|
|
83
86
|
|
|
84
87
|
### generate: 基本的な使用方法
|
|
@@ -238,6 +241,49 @@ pnpm install
|
|
|
238
241
|
- トークンは Artifact Registry(`<location>-npm.pkg.dev`)以外には書きません。`cloud-platform` スコープの Google トークンなので、ほかのホストに渡すと資格情報の漏えいになるためです
|
|
239
242
|
- トークンを画面に出さず、refresh token などの長期クレデンシャルもコピーしません(gcloud の管理下に置いたまま)
|
|
240
243
|
|
|
244
|
+
#### CI の中で使う(`--ci`)
|
|
245
|
+
|
|
246
|
+
GitHub Actions と Vercel のビルドでは、`--ci` を付けると gcloud なしで認証できます。ビルド環境が発行する OIDC トークンを、Workload Identity Federation(STS)で AR のアクセストークンに交換します。長期のトークンを secrets に置く必要はありません。
|
|
247
|
+
|
|
248
|
+
```yaml
|
|
249
|
+
# GitHub Actions
|
|
250
|
+
permissions:
|
|
251
|
+
contents: read
|
|
252
|
+
id-token: write
|
|
253
|
+
steps:
|
|
254
|
+
- uses: actions/checkout@v4 # プロジェクトの .npmrc から registry を読むので必要
|
|
255
|
+
- uses: actions/setup-node@v4 # registry-url は付けない
|
|
256
|
+
- run: npx --yes sparkle-design-cli@beta auth --ci # install の直前に置く
|
|
257
|
+
- run: pnpm install --frozen-lockfile
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
# Vercel の Install Command(Settings → Security で OIDC federation を有効にし、Issuer Mode を Team にする)
|
|
262
|
+
npx --yes sparkle-design-cli@beta auth --ci && pnpm install --frozen-lockfile
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
- Sparkle の registry では、使う WIF プロバイダを CLI が知っています。受け付けるのは goodpatch org の GitHub Actions と、Vercel の sparkle-design チームのビルドです。別の registry では `--provider` でプロバイダを指定します
|
|
266
|
+
- サービスアカウントは経由しません。AR の IAM に付けた principalSet で認可されます
|
|
267
|
+
- **GitHub Actions では、トークンは数分で失効します。** 交換したトークンの寿命は、元になる GitHub の OIDC トークンの残り時間(発行から約 5 分)までです。`auth --ci` は install の直前に置いてください。Vercel の OIDC トークンは約 60 分あります
|
|
268
|
+
|
|
269
|
+
### migrate-registry: GitHub Packages から Artifact Registry への切り替え(beta)
|
|
270
|
+
|
|
271
|
+
既存プロジェクトの `@goodpatch` の取得元を AR に切り替えます。既定は dry-run で、`--write` を付けたときだけ書き換えます。
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
npx --yes sparkle-design-cli@beta migrate-registry # 変更内容と、手で直す箇所を表示
|
|
275
|
+
npx --yes sparkle-design-cli@beta migrate-registry --write # .npmrc と lockfile を書き換える
|
|
276
|
+
npx --yes sparkle-design-cli@beta auth
|
|
277
|
+
pnpm install --frozen-lockfile
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
- **書き換えるもの:**
|
|
281
|
+
- プロジェクトの `.npmrc`: `@goodpatch:registry` を AR に向けます。使われなくなる GitHub Packages の認証行も消します
|
|
282
|
+
- lockfile: `pnpm-lock.yaml` は GitHub Packages の `tarball:` を外し、`package-lock.json` は `resolved` を AR の URL に置き換えます。lockfile には取得元の URL が記録されているので、`.npmrc` だけを変えても frozen install は GitHub Packages に取りに行き、401 で落ちます
|
|
283
|
+
- integrity は変わりません。AR には GitHub Packages と同じ tarball を置いています
|
|
284
|
+
- **書き換えないもの:** GitHub Actions のワークフロー、`vercel.json`、yarn の設定。GitHub Packages を前提にしている箇所の一覧と、置き換え後の形を表示します
|
|
285
|
+
- AR に無い `@goodpatch` パッケージを使っているプロジェクトでは止まります。スコープごと切り替えると、それらが取得できなくなるためです。影響を承知のうえで続けるときは `--force` を付けます
|
|
286
|
+
|
|
241
287
|
### setup: プロジェクトのフルセットアップ
|
|
242
288
|
|
|
243
289
|
`sparkle-design-cli setup` は、Sparkle Design の導入に必要な作業をまとめて行います:
|
package/bin/sparkle-design.js
CHANGED
|
@@ -9,6 +9,7 @@ import { loadAntiPatternPlugins } from '../lib/load-plugins.js';
|
|
|
9
9
|
import { runRules } from '../lib/rules-report.js';
|
|
10
10
|
import { runAuth } from '../lib/auth.js';
|
|
11
11
|
import { runMigrate } from '../lib/migrate.js';
|
|
12
|
+
import { runMigrateRegistry } from '../lib/migrate-registry.js';
|
|
12
13
|
|
|
13
14
|
const SUBCOMMANDS = new Set([
|
|
14
15
|
'generate',
|
|
@@ -17,6 +18,7 @@ const SUBCOMMANDS = new Set([
|
|
|
17
18
|
'migrate',
|
|
18
19
|
'setup',
|
|
19
20
|
'auth',
|
|
21
|
+
'migrate-registry',
|
|
20
22
|
'stop-hook',
|
|
21
23
|
'plugin-spec',
|
|
22
24
|
]);
|
|
@@ -237,7 +239,7 @@ function parseMigrateOptions(args) {
|
|
|
237
239
|
* en: Unknown flags fail fast, same as the other subcommands.
|
|
238
240
|
*/
|
|
239
241
|
function parseAuthOptions(args) {
|
|
240
|
-
const options = { registry: null, help: false };
|
|
242
|
+
const options = { registry: null, ci: false, provider: null, help: false };
|
|
241
243
|
|
|
242
244
|
for (let i = 0; i < args.length; i += 1) {
|
|
243
245
|
const arg = args[i];
|
|
@@ -250,12 +252,53 @@ function parseAuthOptions(args) {
|
|
|
250
252
|
i += 1;
|
|
251
253
|
continue;
|
|
252
254
|
}
|
|
255
|
+
if (arg === '--ci') {
|
|
256
|
+
options.ci = true;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (arg === '--provider') {
|
|
260
|
+
options.provider = requireOptionValue(args, i, '--provider');
|
|
261
|
+
i += 1;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
253
264
|
throw new Error(`Unknown option for auth: ${arg}`);
|
|
254
265
|
}
|
|
255
266
|
|
|
256
267
|
return options;
|
|
257
268
|
}
|
|
258
269
|
|
|
270
|
+
/**
|
|
271
|
+
* `migrate-registry` のオプション。既定は dry-run で、--write のときだけ書き換える。
|
|
272
|
+
* en: Dry-run by default; --write applies the changes.
|
|
273
|
+
*/
|
|
274
|
+
function parseMigrateRegistryOptions(args) {
|
|
275
|
+
const options = { registry: null, write: false, force: false, help: false };
|
|
276
|
+
|
|
277
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
278
|
+
const arg = args[i];
|
|
279
|
+
if (arg === '-h' || arg === '--help') {
|
|
280
|
+
options.help = true;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (arg === '--write') {
|
|
284
|
+
options.write = true;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (arg === '--force') {
|
|
288
|
+
options.force = true;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (arg === '--registry') {
|
|
292
|
+
options.registry = requireOptionValue(args, i, '--registry');
|
|
293
|
+
i += 1;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
throw new Error(`Unknown option for migrate-registry: ${arg}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return options;
|
|
300
|
+
}
|
|
301
|
+
|
|
259
302
|
function showHelp() {
|
|
260
303
|
console.log(`
|
|
261
304
|
Sparkle Design CLI
|
|
@@ -270,6 +313,9 @@ Commands:
|
|
|
270
313
|
migrate 旧セマンティックトークンを新トークンへ移行(既定は dry-run。--write で書き換え)
|
|
271
314
|
setup Sparkle Design プロジェクトをセットアップ(パッケージ導入 + 初期ファイル + AI ガード + generate)
|
|
272
315
|
auth Artifact Registry の npm registry 用アクセストークンを ~/.npmrc に書く(beta)
|
|
316
|
+
migrate-registry
|
|
317
|
+
@goodpatch の取得元を GitHub Packages から Artifact Registry へ切り替える
|
|
318
|
+
(.npmrc と lockfile。既定は dry-run。--write で書き換え)
|
|
273
319
|
stop-hook AI assistant の Stop hook 用 internal subcommand(severity=error の findings か、
|
|
274
320
|
実行に失敗して未検査のルールがあるときに exit 2 で 1 度だけ停止をブロック / 再発火は自動回避。
|
|
275
321
|
warning / info はブロックせず件数の要約を stderr に出す)
|
|
@@ -308,6 +354,11 @@ Plugin spec:
|
|
|
308
354
|
Auth (beta):
|
|
309
355
|
sparkle-design-cli auth # プロジェクトの .npmrc の @goodpatch:registry を対象に認証
|
|
310
356
|
sparkle-design-cli auth --registry https://asia-northeast1-npm.pkg.dev/<project>/<repository>/
|
|
357
|
+
sparkle-design-cli auth --ci # GitHub Actions / Vercel のビルド内で(OIDC で認証)
|
|
358
|
+
|
|
359
|
+
Migrate registry:
|
|
360
|
+
sparkle-design-cli migrate-registry # 変更内容を表示するだけ(dry-run)
|
|
361
|
+
sparkle-design-cli migrate-registry --write # .npmrc と lockfile を書き換える
|
|
311
362
|
|
|
312
363
|
Setup:
|
|
313
364
|
sparkle-design-cli setup # フルセットアップ(推奨・新規導入時)
|
|
@@ -414,6 +465,11 @@ Migrate の分類:
|
|
|
414
465
|
Auth options:
|
|
415
466
|
-h, --help このヘルプメッセージを表示
|
|
416
467
|
--registry <url> 対象の registry(default: プロジェクトの .npmrc の @goodpatch:registry)
|
|
468
|
+
--ci CI の中で使う。GitHub Actions(permissions: id-token: write)または
|
|
469
|
+
Vercel(OIDC federation を Team issuer で有効化)の OIDC トークンを
|
|
470
|
+
Workload Identity Federation で交換する。gcloud は不要
|
|
471
|
+
--provider <name> --ci で使う WIF プロバイダ(//iam.googleapis.com/projects/<番号>/...)。
|
|
472
|
+
Sparkle の registry では既定値があるので不要
|
|
417
473
|
|
|
418
474
|
Auth の動作:
|
|
419
475
|
1. Google の資格情報(ADC → gcloud auth print-access-token の順)からアクセストークンを取得
|
|
@@ -424,6 +480,21 @@ Auth の動作:
|
|
|
424
480
|
トークンは Artifact Registry(<location>-npm.pkg.dev)にだけ書き、画面には出しません。
|
|
425
481
|
refresh token などの長期クレデンシャルは gcloud の管理下に置いたままで、コピーしません。
|
|
426
482
|
|
|
483
|
+
Migrate registry options:
|
|
484
|
+
-h, --help このヘルプメッセージを表示
|
|
485
|
+
--write 実際に書き換える(指定しなければ dry-run)
|
|
486
|
+
--registry <url> 移行先(default: Sparkle の Artifact Registry)
|
|
487
|
+
--force Artifact Registry に無い @goodpatch パッケージがあっても続行する
|
|
488
|
+
|
|
489
|
+
Migrate registry の動作:
|
|
490
|
+
1. プロジェクトの .npmrc の @goodpatch:registry を Artifact Registry に向け、
|
|
491
|
+
使われなくなる GitHub Packages の認証行を消す
|
|
492
|
+
2. lockfile に記録された GitHub Packages の tarball URL を外す / 置き換える
|
|
493
|
+
(pnpm-lock.yaml は tarball を外し integrity を残す。package-lock.json は resolved を置き換える)
|
|
494
|
+
そのままだと .npmrc を変えても frozen install が GitHub Packages に取りに行き 401 になる
|
|
495
|
+
3. GitHub Actions のワークフロー・vercel.json・yarn の設定で GitHub Packages を前提に
|
|
496
|
+
している箇所を一覧にする(書き換えない。対処例を表示する)
|
|
497
|
+
|
|
427
498
|
Setup options:
|
|
428
499
|
-h, --help このヘルプメッセージを表示
|
|
429
500
|
--assistant <name> claude / codex / cursor / generic (default: generic)
|
|
@@ -570,6 +641,16 @@ async function main() {
|
|
|
570
641
|
return;
|
|
571
642
|
}
|
|
572
643
|
|
|
644
|
+
if (command === 'migrate-registry') {
|
|
645
|
+
const options = parseMigrateRegistryOptions(args.slice(1));
|
|
646
|
+
if (options.help) {
|
|
647
|
+
showHelp();
|
|
648
|
+
process.exit(0);
|
|
649
|
+
}
|
|
650
|
+
await runMigrateRegistry(options);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
573
654
|
if (command === 'stop-hook') {
|
|
574
655
|
// setup で各 agent の hook 設定ファイルから呼ばれる internal subcommand。
|
|
575
656
|
// 以降の引数はすべて lint 対象 path。setup が書き出す hook は 1 つだが、
|
package/lib/auth.js
CHANGED
|
@@ -22,6 +22,31 @@ import { URL, URLSearchParams } from 'url';
|
|
|
22
22
|
const SCOPE = '@goodpatch';
|
|
23
23
|
const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token';
|
|
24
24
|
const GOOGLE_TOKENINFO_URL = 'https://oauth2.googleapis.com/tokeninfo';
|
|
25
|
+
const STS_TOKEN_URL = 'https://sts.googleapis.com/v1/token';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Sparkle の本番 registry(goodpatch/sparkle-design-internal#261)。
|
|
29
|
+
* en: Production Artifact Registry for Sparkle packages.
|
|
30
|
+
*/
|
|
31
|
+
export const DEFAULT_REGISTRY =
|
|
32
|
+
'https://asia-northeast1-npm.pkg.dev/gp-sparkle-design/sparkle-npm/';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* `auth --ci` で使う Workload Identity Federation のプロバイダ。registry ごとに持ち、
|
|
36
|
+
* ここに無い registry では `--provider` の指定を必須にする(他の GCP プロジェクトの
|
|
37
|
+
* registry に、Sparkle 用のプロバイダで交換したトークンを書かないため)。
|
|
38
|
+
* - github: goodpatch org(repository_owner_id)の GitHub Actions だけを受け入れる
|
|
39
|
+
* - vercel: Vercel の sparkle-design チームのビルドだけを受け入れる
|
|
40
|
+
* en: WIF providers per registry. Unknown registries require --provider.
|
|
41
|
+
*/
|
|
42
|
+
export const KNOWN_WIF_PROVIDERS = {
|
|
43
|
+
[DEFAULT_REGISTRY]: {
|
|
44
|
+
github:
|
|
45
|
+
'//iam.googleapis.com/projects/339424476445/locations/global/workloadIdentityPools/github/providers/goodpatch',
|
|
46
|
+
vercel:
|
|
47
|
+
'//iam.googleapis.com/projects/339424476445/locations/global/workloadIdentityPools/vercel/providers/sparkle-design',
|
|
48
|
+
},
|
|
49
|
+
};
|
|
25
50
|
/** アクセストークンとして受け付ける文字。改行などで .npmrc に別の行を差し込ませない */
|
|
26
51
|
const TOKEN_PATTERN = /^[A-Za-z0-9._\-~+/=]+$/;
|
|
27
52
|
|
|
@@ -197,6 +222,142 @@ function tokenFromGcloud({ spawn }) {
|
|
|
197
222
|
return token ? { token, expiresIn: null, via: 'gcloud' } : null;
|
|
198
223
|
}
|
|
199
224
|
|
|
225
|
+
// ---- CI(auth --ci): OIDC トークン → STS ----
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* CI の実行環境を判別する。
|
|
229
|
+
* - GitHub Actions: OIDC トークンの発行 URL が渡されていること(`permissions: id-token: write`)
|
|
230
|
+
* - Vercel: ビルド時の `VERCEL_OIDC_TOKEN`(プロジェクト設定で OIDC を有効にしたとき)
|
|
231
|
+
* 判別できないときは、足りない設定を含めた理由を返す。
|
|
232
|
+
* en: Detect the CI environment, or explain what is missing.
|
|
233
|
+
*/
|
|
234
|
+
export function detectCiEnvironment(env) {
|
|
235
|
+
if (env.ACTIONS_ID_TOKEN_REQUEST_URL && env.ACTIONS_ID_TOKEN_REQUEST_TOKEN) {
|
|
236
|
+
return { kind: 'github' };
|
|
237
|
+
}
|
|
238
|
+
if (env.VERCEL_OIDC_TOKEN) {
|
|
239
|
+
return { kind: 'vercel' };
|
|
240
|
+
}
|
|
241
|
+
if (env.GITHUB_ACTIONS === 'true') {
|
|
242
|
+
return {
|
|
243
|
+
kind: null,
|
|
244
|
+
reason:
|
|
245
|
+
'GitHub Actions で OIDC トークンを取得できません。ジョブに次の権限を足してください:\n' +
|
|
246
|
+
' permissions:\n' +
|
|
247
|
+
' id-token: write\n' +
|
|
248
|
+
' contents: read',
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
if (env.VERCEL === '1') {
|
|
252
|
+
return {
|
|
253
|
+
kind: null,
|
|
254
|
+
reason:
|
|
255
|
+
'Vercel のビルドで VERCEL_OIDC_TOKEN がありません。プロジェクトの Settings → Security で\n' +
|
|
256
|
+
' Secure backend access with OIDC federation を有効にし、Issuer Mode を Team にしてください。',
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
kind: null,
|
|
261
|
+
reason:
|
|
262
|
+
'CI の実行環境(GitHub Actions / Vercel)を判別できません。\n' +
|
|
263
|
+
' ローカルでは --ci を付けずに sparkle-design-cli auth を実行してください。',
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** WIF プロバイダのリソース名(`//iam.googleapis.com/projects/<番号>/.../providers/<id>`)を検証する */
|
|
268
|
+
export function normalizeWifProvider(raw) {
|
|
269
|
+
const value = String(raw)
|
|
270
|
+
.trim()
|
|
271
|
+
.replace(/^https:/, '');
|
|
272
|
+
if (
|
|
273
|
+
!/^\/\/iam\.googleapis\.com\/projects\/\d+\/locations\/global\/workloadIdentityPools\/[a-z0-9-]+\/providers\/[a-z0-9-]+$/.test(
|
|
274
|
+
value
|
|
275
|
+
)
|
|
276
|
+
) {
|
|
277
|
+
throw new AuthError(
|
|
278
|
+
`WIF プロバイダの形式ではありません: ${raw}\n` +
|
|
279
|
+
' //iam.googleapis.com/projects/<プロジェクト番号>/locations/global/workloadIdentityPools/<プール>/providers/<プロバイダ> の形で指定してください。'
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
return value;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** registry と実行環境から、交換に使う WIF プロバイダを決める */
|
|
286
|
+
export function resolveWifProvider(registry, kind, override = null) {
|
|
287
|
+
if (override) return normalizeWifProvider(override);
|
|
288
|
+
const known = KNOWN_WIF_PROVIDERS[registry]?.[kind];
|
|
289
|
+
if (!known) {
|
|
290
|
+
throw new AuthError(
|
|
291
|
+
`この registry(${registry})の ${kind} 用 WIF プロバイダが分かりません。--provider で指定してください。`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
return known;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function readJson(res) {
|
|
298
|
+
try {
|
|
299
|
+
return await res.json();
|
|
300
|
+
} catch {
|
|
301
|
+
return {};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** GitHub Actions の OIDC トークン。audience は STS が既定で受け付けるプロバイダの https URL */
|
|
306
|
+
async function oidcTokenFromGithub({ env, fetchImpl }, provider) {
|
|
307
|
+
const url = new URL(env.ACTIONS_ID_TOKEN_REQUEST_URL);
|
|
308
|
+
url.searchParams.set('audience', `https:${provider}`);
|
|
309
|
+
const res = await fetchImpl(url.toString(), {
|
|
310
|
+
headers: { authorization: `bearer ${env.ACTIONS_ID_TOKEN_REQUEST_TOKEN}` },
|
|
311
|
+
});
|
|
312
|
+
const json = await readJson(res);
|
|
313
|
+
if (!res.ok || typeof json.value !== 'string') {
|
|
314
|
+
throw new AuthError(
|
|
315
|
+
`GitHub Actions の OIDC トークンを取得できませんでした(HTTP ${res.status})`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
return json.value;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* OIDC トークンを STS で GCP のアクセストークン(federated token)に交換する。
|
|
323
|
+
* サービスアカウントは経由せず、AR の IAM に付けた principalSet で認可される。
|
|
324
|
+
* en: Exchange the OIDC token at STS; no service account impersonation.
|
|
325
|
+
*/
|
|
326
|
+
async function exchangeWithSts({ fetchImpl }, provider, subjectToken) {
|
|
327
|
+
const res = await fetchImpl(STS_TOKEN_URL, {
|
|
328
|
+
method: 'POST',
|
|
329
|
+
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
330
|
+
body: new URLSearchParams({
|
|
331
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
|
|
332
|
+
audience: provider,
|
|
333
|
+
scope: 'https://www.googleapis.com/auth/cloud-platform',
|
|
334
|
+
requested_token_type: 'urn:ietf:params:oauth:token-type:access_token',
|
|
335
|
+
subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
|
|
336
|
+
subject_token: subjectToken,
|
|
337
|
+
}).toString(),
|
|
338
|
+
});
|
|
339
|
+
const json = await readJson(res);
|
|
340
|
+
if (!res.ok || typeof json.access_token !== 'string') {
|
|
341
|
+
// STS のエラー本文にトークンは含まれない(error / error_description のみ)
|
|
342
|
+
const detail = [json.error, json.error_description].filter(Boolean).join(': ');
|
|
343
|
+
throw new AuthError(
|
|
344
|
+
`STS でのトークン交換に失敗しました(HTTP ${res.status}${detail ? ` ${detail}` : ''})。\n` +
|
|
345
|
+
' WIF プロバイダの条件(リポジトリの owner / Vercel のチーム)を満たしているか確認してください。'
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
return { token: json.access_token, expiresIn: Number(json.expires_in) || null };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function tokenFromCi(d, registry, providerOverride) {
|
|
352
|
+
const ci = detectCiEnvironment(d.env);
|
|
353
|
+
if (!ci.kind) throw new AuthError(ci.reason);
|
|
354
|
+
const provider = resolveWifProvider(registry, ci.kind, providerOverride);
|
|
355
|
+
const subjectToken =
|
|
356
|
+
ci.kind === 'github' ? await oidcTokenFromGithub(d, provider) : d.env.VERCEL_OIDC_TOKEN;
|
|
357
|
+
const exchanged = await exchangeWithSts(d, provider, subjectToken);
|
|
358
|
+
return { ...exchanged, via: ci.kind === 'github' ? 'GitHub Actions の OIDC' : 'Vercel の OIDC' };
|
|
359
|
+
}
|
|
360
|
+
|
|
200
361
|
/** 失効までの秒数を tokeninfo で調べる(取れなくても認証自体は成功扱い) */
|
|
201
362
|
async function lookupExpiresIn(token, fetchImpl) {
|
|
202
363
|
try {
|
|
@@ -249,7 +410,12 @@ export async function runAuth(options = {}, deps = {}) {
|
|
|
249
410
|
}
|
|
250
411
|
const registry = normalizeRegistry(rawRegistry);
|
|
251
412
|
|
|
252
|
-
|
|
413
|
+
if (!options.ci && options.provider) {
|
|
414
|
+
throw new AuthError('--provider は --ci と組み合わせて使います。');
|
|
415
|
+
}
|
|
416
|
+
const acquired = options.ci
|
|
417
|
+
? await tokenFromCi(d, registry, options.provider)
|
|
418
|
+
: ((await tokenFromAdc(d)) ?? tokenFromGcloud(d));
|
|
253
419
|
if (!acquired) {
|
|
254
420
|
throw new AuthError(
|
|
255
421
|
'Google の認証情報が見つからないか、期限切れです。次を実行してから、もう一度 auth を実行してください:\n' +
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { URL } from 'url';
|
|
4
|
+
import { DEFAULT_REGISTRY, normalizeRegistry } from './auth.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `sparkle-design-cli migrate-registry` — `@goodpatch/sparkle-design-internal` の取得元を
|
|
8
|
+
* GitHub Packages から Google Artifact Registry(AR)へ切り替える
|
|
9
|
+
* (goodpatch/sparkle-design-internal#261)。
|
|
10
|
+
*
|
|
11
|
+
* 書き換えるのは「誰がやっても同じ結果になる」ものだけ:
|
|
12
|
+
* - プロジェクトの `.npmrc`(`@goodpatch:registry` を AR に向ける)
|
|
13
|
+
* - lockfile に記録された GitHub Packages の tarball URL
|
|
14
|
+
* (そのままだと `.npmrc` を変えても frozen install が GitHub Packages に取りに行き 401 になる)
|
|
15
|
+
*
|
|
16
|
+
* CI のワークフローや Vercel など、プロジェクトごとに形が違う設定は書き換えず、
|
|
17
|
+
* 該当箇所と対処を一覧にして返す(判断はスキル / 人が行う)。
|
|
18
|
+
* en: Rewrites only deterministic files (.npmrc, lockfiles); CI / Vercel settings are
|
|
19
|
+
* reported, not edited.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const SCOPE = '@goodpatch';
|
|
23
|
+
const GITHUB_REGISTRY_HOST = 'npm.pkg.github.com';
|
|
24
|
+
/** AR に移植済みで、スコープごと切り替えても引ける @goodpatch パッケージ */
|
|
25
|
+
export const MIGRATABLE_PACKAGES = new Set(['@goodpatch/sparkle-design-internal']);
|
|
26
|
+
|
|
27
|
+
export class MigrateRegistryError extends Error {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = 'MigrateRegistryError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function splitLines(text) {
|
|
35
|
+
const lines = text === '' ? [] : text.split(/\r?\n/);
|
|
36
|
+
if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();
|
|
37
|
+
return lines;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseNpmrcLine(line) {
|
|
41
|
+
const trimmed = line.trim();
|
|
42
|
+
if (trimmed === '' || trimmed.startsWith('#') || trimmed.startsWith(';')) return null;
|
|
43
|
+
const match = /^([^=]+?)\s*=\s*(.*)$/.exec(trimmed);
|
|
44
|
+
return match ? { key: match[1], value: match[2].trim() } : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isGithubRegistry(value) {
|
|
48
|
+
try {
|
|
49
|
+
return new URL(value).host === GITHUB_REGISTRY_HOST;
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* プロジェクトの `.npmrc` を書き換える。
|
|
57
|
+
* - `@goodpatch:registry=` を AR に置き換える(無ければ足す)
|
|
58
|
+
* - GitHub Packages の認証行(`//npm.pkg.github.com/:_authToken=`)は、他のスコープや既定 registry が
|
|
59
|
+
* GitHub Packages を使っていなければ消す(使っていれば残して警告する)
|
|
60
|
+
*/
|
|
61
|
+
export function rewriteNpmrc(text, registry) {
|
|
62
|
+
const lines = splitLines(text);
|
|
63
|
+
const changes = [];
|
|
64
|
+
const warnings = [];
|
|
65
|
+
|
|
66
|
+
const othersUseGithub = lines.some((line) => {
|
|
67
|
+
const parsed = parseNpmrcLine(line);
|
|
68
|
+
if (!parsed || parsed.key === `${SCOPE}:registry`) return false;
|
|
69
|
+
return (
|
|
70
|
+
(parsed.key === 'registry' || parsed.key.endsWith(':registry')) &&
|
|
71
|
+
isGithubRegistry(parsed.value)
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
let replaced = false;
|
|
76
|
+
const next = [];
|
|
77
|
+
for (const line of lines) {
|
|
78
|
+
const parsed = parseNpmrcLine(line);
|
|
79
|
+
if (parsed?.key === `${SCOPE}:registry`) {
|
|
80
|
+
if (!replaced) {
|
|
81
|
+
next.push(`${SCOPE}:registry=${registry}`);
|
|
82
|
+
if (parsed.value !== registry) changes.push(`${SCOPE}:registry を ${registry} に変更`);
|
|
83
|
+
replaced = true;
|
|
84
|
+
}
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (parsed && parsed.key.startsWith(`//${GITHUB_REGISTRY_HOST}/`)) {
|
|
88
|
+
if (othersUseGithub) {
|
|
89
|
+
warnings.push(
|
|
90
|
+
`GitHub Packages の認証行(${parsed.key})は、他のスコープが GitHub Packages を使っているため残しました`
|
|
91
|
+
);
|
|
92
|
+
next.push(line);
|
|
93
|
+
} else {
|
|
94
|
+
changes.push(`GitHub Packages の認証行(${parsed.key})を削除`);
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
next.push(line);
|
|
99
|
+
}
|
|
100
|
+
if (!replaced) {
|
|
101
|
+
next.push(`${SCOPE}:registry=${registry}`);
|
|
102
|
+
changes.push(`${SCOPE}:registry=${registry} を追加`);
|
|
103
|
+
}
|
|
104
|
+
return { text: `${next.join('\n')}\n`, changes, warnings };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* pnpm-lock.yaml の `resolution: {integrity: ..., tarball: https://npm.pkg.github.com/download/@goodpatch/...}`
|
|
109
|
+
* から tarball を外す。integrity は AR に移植した tarball と同じなので残せば検証は効き、
|
|
110
|
+
* 取得先は `.npmrc` の registry から決まる(新しく解決した版は pnpm が AR の URL を記録する)。
|
|
111
|
+
*/
|
|
112
|
+
export function rewritePnpmLock(text) {
|
|
113
|
+
let count = 0;
|
|
114
|
+
const next = text.replace(
|
|
115
|
+
/, tarball: https:\/\/npm\.pkg\.github\.com\/download\/@goodpatch\/[^}\s]+(?=})/g,
|
|
116
|
+
() => {
|
|
117
|
+
count += 1;
|
|
118
|
+
return '';
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
return { text: next, count };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* package-lock.json の `resolved` を AR の tarball URL に置き換える。npm は registry を
|
|
126
|
+
* 変えても lockfile の `resolved` に取りに行く(読み替えるのは registry.npmjs.org だけ)ため。
|
|
127
|
+
* JSON を組み直さず文字列で置き換えて、整形を保つ。
|
|
128
|
+
*/
|
|
129
|
+
export function rewritePackageLock(text, registry) {
|
|
130
|
+
let count = 0;
|
|
131
|
+
const next = text.replace(
|
|
132
|
+
/"https:\/\/npm\.pkg\.github\.com\/download\/@goodpatch\/([a-z0-9._-]+)\/([^/"]+)\/[0-9a-f]+"/g,
|
|
133
|
+
(_, name, version) => {
|
|
134
|
+
count += 1;
|
|
135
|
+
return `"${registry}@goodpatch/${name}/-/@goodpatch/${name}-${version}.tgz"`;
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
return { text: next, count };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* lockfile / package.json に出てくる @goodpatch パッケージ名。
|
|
143
|
+
* AR の tarball URL の末尾(`/-/@goodpatch/<name>-<version>.tgz`)はファイル名なので数えない
|
|
144
|
+
* (移行済みの lockfile で再実行したときに、別パッケージと誤認して止まらないように)。
|
|
145
|
+
*/
|
|
146
|
+
export function collectGoodpatchPackages(texts) {
|
|
147
|
+
const names = new Set();
|
|
148
|
+
for (const text of texts) {
|
|
149
|
+
for (const match of text.matchAll(/(?<!\/-\/)@goodpatch\/([a-z0-9._-]+)/g)) {
|
|
150
|
+
names.add(`@goodpatch/${match[1]}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return names;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* CI / デプロイ設定で GitHub Packages を前提にしている箇所を探す(書き換えない)。
|
|
158
|
+
* en: Find CI / deploy settings that assume GitHub Packages. Report only.
|
|
159
|
+
*/
|
|
160
|
+
export function scanCiSettings(root, readDir = fs.readdirSync, readFile = fs.readFileSync) {
|
|
161
|
+
const findings = [];
|
|
162
|
+
const workflowsDir = path.join(root, '.github', 'workflows');
|
|
163
|
+
let workflowFiles = [];
|
|
164
|
+
try {
|
|
165
|
+
workflowFiles = readDir(workflowsDir).filter((f) => /\.ya?ml$/.test(f));
|
|
166
|
+
} catch {
|
|
167
|
+
// ワークフローが無いプロジェクト
|
|
168
|
+
}
|
|
169
|
+
for (const file of workflowFiles.sort()) {
|
|
170
|
+
const rel = path.join('.github', 'workflows', file);
|
|
171
|
+
const lines = String(readFile(path.join(workflowsDir, file), 'utf8')).split(/\r?\n/);
|
|
172
|
+
lines.forEach((line, index) => {
|
|
173
|
+
if (/npm\.pkg\.github\.com|NODE_AUTH_TOKEN|packages:\s*read/.test(line)) {
|
|
174
|
+
findings.push({ kind: 'github-actions', file: rel, line: index + 1, text: line.trim() });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
for (const file of ['vercel.json']) {
|
|
179
|
+
try {
|
|
180
|
+
readFile(path.join(root, file), 'utf8');
|
|
181
|
+
findings.push({ kind: 'vercel', file, line: null, text: null });
|
|
182
|
+
} catch {
|
|
183
|
+
// 無ければ対象外
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
for (const file of ['.yarnrc.yml', 'yarn.lock']) {
|
|
187
|
+
try {
|
|
188
|
+
const text = String(readFile(path.join(root, file), 'utf8'));
|
|
189
|
+
if (text.includes(GITHUB_REGISTRY_HOST)) {
|
|
190
|
+
findings.push({ kind: 'yarn', file, line: null, text: null });
|
|
191
|
+
}
|
|
192
|
+
} catch {
|
|
193
|
+
// 無ければ対象外
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return findings;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function readIfExists(file) {
|
|
200
|
+
try {
|
|
201
|
+
return fs.readFileSync(file, 'utf8');
|
|
202
|
+
} catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 案内に出す `npx` の指定。auth / migrate-registry を持たない版(2.4.x 以前)は未知のコマンドを
|
|
209
|
+
* exit 0 で受け流すため、CI に書いた認証が黙って素通りし install が 401 になる。
|
|
210
|
+
* 実行中の CLI が prerelease なら同じチャネルを付けて、確実にこのコマンドを持つ版を指す。
|
|
211
|
+
* en: Older releases exit 0 on unknown commands, so point CI at the channel of the running CLI.
|
|
212
|
+
*/
|
|
213
|
+
export function cliSpecForVersion(version) {
|
|
214
|
+
const channel = /^\d+\.\d+\.\d+-(beta|rc)\.\d+$/.exec(String(version))?.[1];
|
|
215
|
+
if (channel === 'beta') return 'sparkle-design-cli@beta';
|
|
216
|
+
if (channel === 'rc') return 'sparkle-design-cli@next';
|
|
217
|
+
return 'sparkle-design-cli';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function ownVersion() {
|
|
221
|
+
try {
|
|
222
|
+
return JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
223
|
+
} catch {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export const defaultDeps = {
|
|
229
|
+
cwd: () => process.cwd(),
|
|
230
|
+
log: (...args) => console.log(...args),
|
|
231
|
+
version: ownVersion,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* `migrate-registry` 本体。既定は dry-run(`write: true` のときだけ書き込む)。
|
|
236
|
+
*/
|
|
237
|
+
export async function runMigrateRegistry(options = {}, deps = {}) {
|
|
238
|
+
const d = { ...defaultDeps, ...deps };
|
|
239
|
+
const root = path.resolve(d.cwd());
|
|
240
|
+
const registry = normalizeRegistry(options.registry ?? DEFAULT_REGISTRY);
|
|
241
|
+
const write = Boolean(options.write);
|
|
242
|
+
|
|
243
|
+
if (!fs.existsSync(path.join(root, 'package.json'))) {
|
|
244
|
+
throw new MigrateRegistryError(
|
|
245
|
+
`package.json が見つかりません: ${root}\n プロジェクトのルート(package.json と .npmrc がある場所)で実行してください。`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const files = {
|
|
250
|
+
npmrc: path.join(root, '.npmrc'),
|
|
251
|
+
pnpmLock: path.join(root, 'pnpm-lock.yaml'),
|
|
252
|
+
packageLock: path.join(root, 'package-lock.json'),
|
|
253
|
+
};
|
|
254
|
+
const current = {
|
|
255
|
+
npmrc: readIfExists(files.npmrc) ?? '',
|
|
256
|
+
pnpmLock: readIfExists(files.pnpmLock),
|
|
257
|
+
packageLock: readIfExists(files.packageLock),
|
|
258
|
+
packageJson: readIfExists(path.join(root, 'package.json')),
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// スコープごと AR に向けるので、AR に無い @goodpatch パッケージがあると引けなくなる
|
|
262
|
+
const others = [
|
|
263
|
+
...collectGoodpatchPackages(
|
|
264
|
+
[current.packageJson, current.pnpmLock, current.packageLock].filter(Boolean)
|
|
265
|
+
),
|
|
266
|
+
].filter((name) => !MIGRATABLE_PACKAGES.has(name));
|
|
267
|
+
if (others.length > 0 && !options.force) {
|
|
268
|
+
throw new MigrateRegistryError(
|
|
269
|
+
`Artifact Registry に無い @goodpatch パッケージを使っています: ${others.join(', ')}\n` +
|
|
270
|
+
` ${SCOPE} のスコープごと切り替えると、これらが取得できなくなります。\n` +
|
|
271
|
+
' AR への移植を Sparkle Design チームに相談するか、影響を承知のうえで --force を付けてください。'
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const planned = [];
|
|
276
|
+
const warnings = [];
|
|
277
|
+
|
|
278
|
+
const npmrc = rewriteNpmrc(current.npmrc, registry);
|
|
279
|
+
warnings.push(...npmrc.warnings);
|
|
280
|
+
if (npmrc.changes.length > 0) {
|
|
281
|
+
planned.push({ file: '.npmrc', path: files.npmrc, text: npmrc.text, notes: npmrc.changes });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (current.pnpmLock !== null) {
|
|
285
|
+
const result = rewritePnpmLock(current.pnpmLock);
|
|
286
|
+
if (result.count > 0) {
|
|
287
|
+
planned.push({
|
|
288
|
+
file: 'pnpm-lock.yaml',
|
|
289
|
+
path: files.pnpmLock,
|
|
290
|
+
text: result.text,
|
|
291
|
+
notes: [`GitHub Packages の tarball URL を ${result.count} 件外す(integrity は残す)`],
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
if (result.text.includes(GITHUB_REGISTRY_HOST)) {
|
|
295
|
+
warnings.push(
|
|
296
|
+
'pnpm-lock.yaml に GitHub Packages の URL が残っています。手で確認してください'
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (current.packageLock !== null) {
|
|
302
|
+
const result = rewritePackageLock(current.packageLock, registry);
|
|
303
|
+
if (result.count > 0) {
|
|
304
|
+
planned.push({
|
|
305
|
+
file: 'package-lock.json',
|
|
306
|
+
path: files.packageLock,
|
|
307
|
+
text: result.text,
|
|
308
|
+
notes: [`resolved を Artifact Registry の URL に ${result.count} 件置き換える`],
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
if (result.text.includes(GITHUB_REGISTRY_HOST)) {
|
|
312
|
+
warnings.push(
|
|
313
|
+
'package-lock.json に GitHub Packages の URL が残っています。手で確認してください'
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const findings = scanCiSettings(root);
|
|
319
|
+
|
|
320
|
+
if (write) {
|
|
321
|
+
for (const item of planned) fs.writeFileSync(item.path, item.text);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const cli = cliSpecForVersion(d.version());
|
|
325
|
+
report(d.log, { root, registry, write, planned, warnings, findings, cli });
|
|
326
|
+
return { registry, write, planned, warnings, findings };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function report(log, { registry, write, planned, warnings, findings, cli }) {
|
|
330
|
+
log(`${write ? '✅' : '🔍'} migrate-registry(${write ? '書き換え' : 'dry-run'})`);
|
|
331
|
+
log(` 移行先: ${registry}`);
|
|
332
|
+
log('');
|
|
333
|
+
if (planned.length === 0) {
|
|
334
|
+
log('書き換えるファイルはありません(移行済みか、対象がありません)。');
|
|
335
|
+
} else {
|
|
336
|
+
log(write ? '書き換えたファイル:' : '書き換えるファイル(--write で反映):');
|
|
337
|
+
for (const item of planned) {
|
|
338
|
+
log(` ${item.file}`);
|
|
339
|
+
for (const note of item.notes) log(` - ${note}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (warnings.length > 0) {
|
|
344
|
+
log('');
|
|
345
|
+
log('⚠️ 注意:');
|
|
346
|
+
for (const warning of warnings) log(` - ${warning}`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const actions = findings.filter((f) => f.kind === 'github-actions');
|
|
350
|
+
const vercel = findings.some((f) => f.kind === 'vercel');
|
|
351
|
+
const yarn = findings.filter((f) => f.kind === 'yarn');
|
|
352
|
+
|
|
353
|
+
if (actions.length > 0 || vercel || yarn.length > 0) {
|
|
354
|
+
log('');
|
|
355
|
+
log('手で直す必要がある設定(このコマンドは書き換えません):');
|
|
356
|
+
}
|
|
357
|
+
if (actions.length > 0) {
|
|
358
|
+
log('');
|
|
359
|
+
log(' [GitHub Actions] GitHub Packages を前提にしている箇所:');
|
|
360
|
+
for (const f of actions) log(` ${f.file}:${f.line} ${f.text}`);
|
|
361
|
+
log(' → 次の形に置き換えてください:');
|
|
362
|
+
log(' permissions:');
|
|
363
|
+
log(' id-token: write # packages: read は不要');
|
|
364
|
+
log(' contents: read');
|
|
365
|
+
log(' ...');
|
|
366
|
+
log(' - uses: actions/setup-node@v4 # registry-url は外す');
|
|
367
|
+
log(` - run: npx --yes ${cli} auth --ci # 数分で失効するので install の直前に置く`);
|
|
368
|
+
log(' - run: pnpm install --frozen-lockfile # NODE_AUTH_TOKEN は不要');
|
|
369
|
+
}
|
|
370
|
+
if (vercel) {
|
|
371
|
+
log('');
|
|
372
|
+
log(' [Vercel] vercel.json があります。Vercel でビルドしているなら:');
|
|
373
|
+
log(' 1. Settings → Security で OIDC federation を有効にし、Issuer Mode を Team にする');
|
|
374
|
+
log(' 2. Install Command を次にする:');
|
|
375
|
+
log(` npx --yes ${cli} auth --ci && pnpm install --frozen-lockfile`);
|
|
376
|
+
log(' 3. GitHub Packages 用の環境変数(NODE_AUTH_TOKEN など)を削除する');
|
|
377
|
+
}
|
|
378
|
+
for (const f of yarn) {
|
|
379
|
+
log('');
|
|
380
|
+
log(
|
|
381
|
+
` [yarn] ${f.file} に GitHub Packages の設定があります。yarn は自動書き換えの対象外です。`
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
log('');
|
|
386
|
+
log('次にやること:');
|
|
387
|
+
if (!write) log(' 0. 内容を確認して --write を付けて再実行する');
|
|
388
|
+
log(` 1. npx --yes ${cli} auth # ローカルの認証(約 60 分で失効)`);
|
|
389
|
+
log(' 2. pnpm install --frozen-lockfile # Artifact Registry から取得できることを確認');
|
|
390
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sparkle-design-cli",
|
|
3
|
-
"version": "2.5.0-beta.
|
|
3
|
+
"version": "2.5.0-beta.7",
|
|
4
4
|
"description": "Sparkle Design CLI — プロジェクトセットアップ、CSS・フォント生成、アンチパターン検査、AI エージェント(Claude Code / Cursor / Codex)向けのガードと hook 設定まで一括で行う sparkle-design 公式 CLI。",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org",
|