@einja/dev-cli 0.1.39 → 0.1.41
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 +89 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +71 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +187 -13
- package/dist/commands/sync.js.map +1 -1
- package/dist/lib/dependency-checker.d.ts.map +1 -1
- package/dist/lib/merger.d.ts +12 -0
- package/dist/lib/merger.d.ts.map +1 -1
- package/dist/lib/merger.js +28 -0
- package/dist/lib/merger.js.map +1 -1
- package/dist/lib/preset-update/cli-repo-detector.d.ts.map +1 -1
- package/dist/lib/preset-update/file-copier.d.ts.map +1 -1
- package/dist/lib/preset-update/preset-finder.d.ts.map +1 -1
- package/dist/lib/preset.d.ts.map +1 -1
- package/dist/lib/sync/category-validator.d.ts +1 -1
- package/dist/lib/sync/category-validator.d.ts.map +1 -1
- package/dist/lib/sync/category-validator.js +2 -1
- package/dist/lib/sync/category-validator.js.map +1 -1
- package/dist/lib/sync/category-validator.test.js +3 -1
- package/dist/lib/sync/category-validator.test.js.map +1 -1
- package/dist/lib/sync/conflict-reporter.d.ts.map +1 -1
- package/dist/lib/sync/diff-engine.d.ts.map +1 -1
- package/dist/lib/sync/file-filter.d.ts.map +1 -1
- package/dist/lib/sync/file-filter.js +1 -0
- package/dist/lib/sync/file-filter.js.map +1 -1
- package/dist/lib/sync/integration.test.js +255 -69
- package/dist/lib/sync/integration.test.js.map +1 -1
- package/dist/lib/sync/json-processor.d.ts +4 -4
- package/dist/lib/sync/json-processor.d.ts.map +1 -1
- package/dist/lib/sync/json-processor.js +11 -11
- package/dist/lib/sync/json-processor.js.map +1 -1
- package/dist/lib/sync/marker-processor.d.ts +60 -8
- package/dist/lib/sync/marker-processor.d.ts.map +1 -1
- package/dist/lib/sync/marker-processor.js +117 -26
- package/dist/lib/sync/marker-processor.js.map +1 -1
- package/dist/lib/sync/marker-processor.test.js +261 -40
- package/dist/lib/sync/marker-processor.test.js.map +1 -1
- package/dist/lib/sync/metadata-manager.d.ts +4 -0
- package/dist/lib/sync/metadata-manager.d.ts.map +1 -1
- package/dist/lib/sync/metadata-manager.js +15 -0
- package/dist/lib/sync/metadata-manager.js.map +1 -1
- package/dist/lib/sync/metadata-manager.test.js +68 -0
- package/dist/lib/sync/metadata-manager.test.js.map +1 -1
- package/dist/lib/sync/orphan-cleaner.d.ts +29 -0
- package/dist/lib/sync/orphan-cleaner.d.ts.map +1 -0
- package/dist/lib/sync/orphan-cleaner.js +80 -0
- package/dist/lib/sync/orphan-cleaner.js.map +1 -0
- package/dist/lib/sync/orphan-cleaner.test.d.ts +2 -0
- package/dist/lib/sync/orphan-cleaner.test.d.ts.map +1 -0
- package/dist/lib/sync/orphan-cleaner.test.js +169 -0
- package/dist/lib/sync/orphan-cleaner.test.js.map +1 -0
- package/dist/lib/sync/project-private-synchronizer.d.ts +52 -0
- package/dist/lib/sync/project-private-synchronizer.d.ts.map +1 -0
- package/dist/lib/sync/project-private-synchronizer.js +106 -0
- package/dist/lib/sync/project-private-synchronizer.js.map +1 -0
- package/dist/lib/sync/project-private-synchronizer.test.d.ts +2 -0
- package/dist/lib/sync/project-private-synchronizer.test.d.ts.map +1 -0
- package/dist/lib/sync/project-private-synchronizer.test.js +348 -0
- package/dist/lib/sync/project-private-synchronizer.test.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/sync.d.ts +36 -6
- package/dist/types/sync.d.ts.map +1 -1
- package/dist/types/sync.js +2 -2
- package/dist/types/sync.js.map +1 -1
- package/package.json +5 -4
- package/presets/default/.claude/agents/einja/Explore.md +140 -0
- package/presets/default/.claude/agents/einja/backend-architect.md +4 -0
- package/presets/default/.claude/agents/einja/codex-agent.md +4 -0
- package/presets/default/.claude/agents/einja/design-engineer.md +4 -0
- package/presets/default/.claude/agents/einja/docs/docs-updater.md +4 -0
- package/presets/default/.claude/agents/einja/frontend-architect.md +4 -0
- package/presets/default/.claude/agents/einja/frontend-coder.md +4 -0
- package/presets/default/.claude/agents/einja/git/conflict-resolver.md +4 -0
- package/presets/default/.claude/agents/einja/specs/spec-design-generator.md +4 -1
- package/presets/default/.claude/agents/einja/specs/spec-qa-generator.md +4 -0
- package/presets/default/.claude/agents/einja/specs/spec-requirements-generator.md +4 -1
- package/presets/default/.claude/agents/einja/specs/spec-tasks-generator.md +6 -2
- package/presets/default/.claude/agents/einja/specs/spec-tasks-validator.md +4 -0
- package/presets/default/.claude/agents/einja/task/task-executer.md +57 -115
- package/presets/default/.claude/agents/einja/task/task-modification-analyzer.md +4 -0
- package/presets/default/.claude/agents/einja/task/task-qa.md +4 -0
- package/presets/default/.claude/agents/einja/task/task-reviewer.md +4 -0
- package/presets/default/.claude/commands/einja/einja-sync.md +5 -1
- package/presets/default/.claude/commands/einja/frontend-implement.md +3 -1
- package/presets/default/.claude/commands/einja/issue-exec.md +403 -0
- package/presets/default/.claude/commands/einja/spec-create.md +15 -1
- package/presets/default/.claude/commands/einja/start-dev.md +4 -0
- package/presets/default/.claude/commands/einja/sync-cursor-commands.md +4 -0
- package/presets/default/.claude/commands/einja/task-exec.md +106 -14
- package/presets/default/.claude/commands/einja/update-docs-by-task-specs.md +4 -0
- package/presets/default/.claude/hooks/einja/plan-mode-skill-loader.sh +23 -0
- package/presets/default/.claude/settings.json +15 -1
- package/presets/default/.claude/skills/einja-conflict-resolver/SKILL.md +4 -0
- package/presets/default/.claude/skills/einja-general-context-loader/SKILL.md +4 -0
- package/presets/default/.claude/skills/einja-output-format/SKILL.md +4 -0
- package/presets/default/.claude/skills/einja-project-overview/SKILL.md +7 -3
- package/presets/default/.claude/skills/einja-skill-creator/SKILL.md +266 -274
- package/presets/default/.claude/skills/einja-skill-creator/agents/analyzer.md +274 -0
- package/presets/default/.claude/skills/einja-skill-creator/agents/comparator.md +202 -0
- package/presets/default/.claude/skills/einja-skill-creator/agents/grader.md +195 -0
- package/presets/default/.claude/skills/einja-skill-creator/assets/eval_review.html +146 -0
- package/presets/default/.claude/skills/einja-skill-creator/eval-viewer/generate_review.py +471 -0
- package/presets/default/.claude/skills/einja-skill-creator/eval-viewer/viewer.html +1325 -0
- package/presets/default/.claude/skills/einja-skill-creator/references/schemas.md +430 -0
- package/presets/default/.claude/skills/einja-skill-creator/scripts/aggregate_benchmark.py +154 -0
- package/presets/default/.claude/skills/einja-skill-creator/scripts/generate_report.py +265 -0
- package/presets/default/.claude/skills/einja-skill-creator/scripts/improve_description.py +252 -0
- package/presets/default/.claude/skills/einja-skill-creator/scripts/init_skill.py +13 -19
- package/presets/default/.claude/skills/einja-skill-creator/scripts/package_skill.py +36 -7
- package/presets/default/.claude/skills/einja-skill-creator/scripts/run_eval.py +310 -0
- package/presets/default/.claude/skills/einja-skill-creator/scripts/run_loop.py +295 -0
- package/presets/default/.claude/skills/einja-skill-creator/scripts/utils.py +48 -0
- package/presets/default/.claude/skills/einja-spec-context-loader/SKILL.md +4 -0
- package/presets/default/.claude/skills/einja-task-commit/SKILL.md +4 -0
- package/presets/default/.claude/skills/einja-task-qa/SKILL.md +4 -0
- package/presets/default/.envrc +5 -0
- package/presets/default/.mcp.json +2 -12
- package/presets/default/CLAUDE.md.template +26 -4
- package/presets/default/docs/einja/example/specs/issues/issue999-example-task/tasks.md +1 -1
- package/presets/default/docs/einja/instructions/deployment-setup.md +3 -8
- package/presets/default/docs/einja/instructions/environment-setup.md +3 -8
- package/presets/default/docs/einja/instructions/issue-exec-workflow.md +276 -0
- package/presets/default/docs/einja/instructions/local-server-environment-and-worktree.md +70 -8
- package/presets/default/docs/einja/instructions/neon-cli-reference.md +3 -8
- package/presets/default/docs/einja/instructions/task-execute.md +23 -28
- package/presets/default/docs/einja/instructions/vercel-cli-reference.md +17 -10
- package/presets/default/docs/einja/steering/README.md +11 -11
- package/presets/default/docs/einja/steering/acceptance-criteria-and-qa-guide.md +3 -8
- package/presets/default/docs/einja/steering/architecture.md +3 -8
- package/presets/default/docs/einja/steering/branch-strategy.md +63 -70
- package/presets/default/docs/einja/steering/commit-rules.md +3 -8
- package/presets/default/docs/einja/steering/db-schema-design.md +3 -8
- package/presets/default/docs/einja/steering/development/api-development.md +3 -8
- package/presets/default/docs/einja/steering/development/backend-architecture.md +3 -8
- package/presets/default/docs/einja/steering/development/coding-standards.md +723 -0
- package/presets/default/docs/einja/steering/development/component-design.md +502 -0
- package/presets/default/docs/einja/steering/development/database-guidelines.md +54 -5
- package/presets/default/docs/einja/steering/development/frontend-development.md +3 -8
- package/presets/default/docs/einja/steering/development/playwright-guidelines.md +59 -0
- package/presets/default/docs/einja/steering/development/review-guidelines.md +3 -8
- package/presets/default/docs/einja/steering/development/testing-strategy.md +3 -8
- package/presets/default/docs/einja/steering/development-workflow.md +71 -124
- package/presets/default/docs/einja/steering/infrastructure/deployment.md +49 -55
- package/presets/default/docs/einja/steering/infrastructure/environment-variables.md +4 -8
- package/presets/default/docs/einja/steering/product.md +3 -8
- package/presets/default/docs/einja/steering/task-management.md +14 -98
- package/presets/default/scripts/ensure-serena.sh +75 -0
- package/presets/default/scripts/env-rotate-secrets.ts +336 -0
- package/presets/default/scripts/env-show.ts +130 -0
- package/presets/default/scripts/env.ts +479 -0
- package/presets/default/scripts/init.sh +92 -0
- package/presets/default/scripts/lib/env-common.ts +108 -0
- package/presets/default/scripts/lib/worktree-config.ts +64 -0
- package/presets/default/scripts/setup-dev.ts +640 -0
- package/presets/default/scripts/stop-serena.sh +25 -0
- package/presets/default/scripts/worktree/dev.ts +872 -0
- package/dist/lib/sync/seed-synchronizer.d.ts +0 -27
- package/dist/lib/sync/seed-synchronizer.d.ts.map +0 -1
- package/dist/lib/sync/seed-synchronizer.js +0 -72
- package/dist/lib/sync/seed-synchronizer.js.map +0 -1
- package/dist/lib/sync/seed-synchronizer.test.d.ts +0 -2
- package/dist/lib/sync/seed-synchronizer.test.d.ts.map +0 -1
- package/dist/lib/sync/seed-synchronizer.test.js +0 -147
- package/dist/lib/sync/seed-synchronizer.test.js.map +0 -1
- package/presets/default/.claude/skills/einja-api-development/SKILL.md +0 -14
- package/presets/default/.claude/skills/einja-backend-architecture/SKILL.md +0 -18
- package/presets/default/.claude/skills/einja-coding-standards/SKILL.md +0 -132
- package/presets/default/.claude/skills/einja-coding-standards/references/import-conventions.md +0 -69
- package/presets/default/.claude/skills/einja-coding-standards/references/naming-conventions.md +0 -107
- package/presets/default/.claude/skills/einja-coding-standards/references/prohibited-patterns.md +0 -169
- package/presets/default/.claude/skills/einja-coding-standards/references/typescript-rules.md +0 -247
- package/presets/default/.claude/skills/einja-component-design/SKILL.md +0 -109
- package/presets/default/.claude/skills/einja-component-design/references/directory-structure.md +0 -117
- package/presets/default/.claude/skills/einja-component-design/references/props-patterns.md +0 -159
- package/presets/default/.claude/skills/einja-component-design/references/styling-guide.md +0 -122
- package/presets/default/.claude/skills/einja-frontend-development/SKILL.md +0 -14
- package/presets/default/docs/einja/instructions/task-vibe-kanban-loop.md +0 -565
|
@@ -356,7 +356,7 @@ TDDは**3タスク分割(X.Y.1 テスト / X.Y.2 実装 / X.Y.3 リファク
|
|
|
356
356
|
|
|
357
357
|
### 依存関係の記述形式(重要)
|
|
358
358
|
|
|
359
|
-
**🔴 `
|
|
359
|
+
**🔴 `/einja:issue-exec` が認識できる形式のみ使用すること**
|
|
360
360
|
|
|
361
361
|
| 記述形式 | 意味 | 例 |
|
|
362
362
|
|---------|------|-----|
|
|
@@ -377,7 +377,7 @@ TDDは**3タスク分割(X.Y.1 テスト / X.Y.2 実装 / X.Y.3 リファク
|
|
|
377
377
|
### 1. タスクグループを選定・実行
|
|
378
378
|
- `/einja:task-exec #{issue_number} {タスクグループ番号}`コマンドを実行
|
|
379
379
|
- タスクグループ番号は必須引数(例: `1.1`, `2.3`)
|
|
380
|
-
- **注意**:
|
|
380
|
+
- **注意**: 単一タスクグループを品質重視で確実に完了させたい場合に使用。`/einja:issue-exec` の Worker 内部でも呼ばれる
|
|
381
381
|
- executer → reviewer → qa の3段階で実行
|
|
382
382
|
|
|
383
383
|
### 2. タスクを順次実装し、コミット
|
|
@@ -396,85 +396,6 @@ TDDは**3タスク分割(X.Y.1 テスト / X.Y.2 実装 / X.Y.3 リファク
|
|
|
396
396
|
- または、ユーザーが明示的に指示した場合のみ自動更新
|
|
397
397
|
- すべてのタスクグループが完了したら、IssueをClose
|
|
398
398
|
|
|
399
|
-
## Vibe-Kanbanタスク作成時の必須情報
|
|
400
|
-
|
|
401
|
-
### タスクタイトル形式
|
|
402
|
-
```
|
|
403
|
-
[タスクグループ番号] タスクグループ名
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
**例**: `[1.1] Server Core構築とDB設定`
|
|
407
|
-
|
|
408
|
-
### タスク説明形式
|
|
409
|
-
|
|
410
|
-
```markdown
|
|
411
|
-
## GitHub Issue
|
|
412
|
-
#{issue_number}
|
|
413
|
-
|
|
414
|
-
## タスクグループ番号
|
|
415
|
-
<タスクグループ番号>
|
|
416
|
-
|
|
417
|
-
## 概要
|
|
418
|
-
<タスクグループの概要>
|
|
419
|
-
|
|
420
|
-
## タスク
|
|
421
|
-
<GitHub Issueから抽出したタスク一覧>
|
|
422
|
-
|
|
423
|
-
## 依存関係
|
|
424
|
-
<依存するタスクグループ番号>
|
|
425
|
-
|
|
426
|
-
## 完了条件
|
|
427
|
-
<受け入れ基準を含む完了条件>
|
|
428
|
-
|
|
429
|
-
## 対応設計
|
|
430
|
-
<design.mdの参照箇所>
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
**例**:
|
|
434
|
-
```markdown
|
|
435
|
-
## GitHub Issue
|
|
436
|
-
#17
|
|
437
|
-
|
|
438
|
-
## タスクグループ番号
|
|
439
|
-
1.1
|
|
440
|
-
|
|
441
|
-
## 概要
|
|
442
|
-
packages/server-coreの初期化からDB接続設定まで、Server Coreの基盤構築を完了
|
|
443
|
-
|
|
444
|
-
## タスク
|
|
445
|
-
- 1.1.1 packages/server-core初期化とtsconfig設定
|
|
446
|
-
- 1.1.2 DB接続設定とマイグレーション
|
|
447
|
-
|
|
448
|
-
## 依存関係
|
|
449
|
-
なし
|
|
450
|
-
|
|
451
|
-
## 完了条件
|
|
452
|
-
server-coreパッケージが動作し、DBに接続できること(AC1.1を満たす)
|
|
453
|
-
|
|
454
|
-
## 対応設計
|
|
455
|
-
design.md「Server Core構築」セクション
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
### 親Issue/サブIssue階層(task:loop使用時)
|
|
459
|
-
|
|
460
|
-
`pnpm task:loop` 使用時は、Vibe-Kanban上でPhase→親Issue、タスクグループ→サブIssueの階層構造で管理されます。
|
|
461
|
-
|
|
462
|
-
**親Issueタイトル形式**:
|
|
463
|
-
```
|
|
464
|
-
[Issue{N} Phase{M}] {Phase名}
|
|
465
|
-
```
|
|
466
|
-
例: `[Issue17 Phase1] 基盤構築`
|
|
467
|
-
|
|
468
|
-
**サブIssueタイトル形式**:
|
|
469
|
-
```
|
|
470
|
-
[Issue{N} {X.Y}] {タスクグループ名}
|
|
471
|
-
```
|
|
472
|
-
例: `[Issue17 1.1] Server Core構築とDB設定`
|
|
473
|
-
|
|
474
|
-
**親子関係の設定**: サブIssue作成後、REST API(PATCH `/api/remote/issues/{id}`)で `parent_issue_id` を設定します。
|
|
475
|
-
|
|
476
|
-
**注意**: `/einja:task-exec` による単発実行時はこの階層構造は使用されません。`pnpm task:loop` 使用時のみ適用されます。
|
|
477
|
-
|
|
478
399
|
## コマンドリファレンス
|
|
479
400
|
|
|
480
401
|
### タスク管理関連コマンド
|
|
@@ -493,18 +414,18 @@ design.md「Server Core構築」セクション
|
|
|
493
414
|
- executer → reviewer → qa の3段階で実行
|
|
494
415
|
- QA合格後は追加指示待ち状態に入る
|
|
495
416
|
- GitHub Issue更新はユーザーの明示的指示時のみ
|
|
496
|
-
- **位置づけ**: `
|
|
417
|
+
- **位置づけ**: 単一タスクグループを品質重視で実行するコマンド。`/einja:issue-exec` の Worker 内部でも使用される
|
|
497
418
|
|
|
498
|
-
|
|
419
|
+
**Issue全体の並列実行**:
|
|
499
420
|
```bash
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
421
|
+
/einja:issue-exec #<issue番号>
|
|
422
|
+
/einja:issue-exec #<issue番号> --merge-mode auto # 全自動モード
|
|
423
|
+
/einja:issue-exec #<issue番号> --max-phase <番号> # 指定Phaseまで実行
|
|
424
|
+
/einja:issue-exec #<issue番号> --base <ブランチ> # ベースブランチ指定
|
|
503
425
|
```
|
|
504
|
-
-
|
|
505
|
-
-
|
|
506
|
-
-
|
|
507
|
-
- **前提**: `npx @einja/cli init` 実行済み、Claude Code インストール済み
|
|
426
|
+
- Manager → Director → Worker の3階層で並列実行
|
|
427
|
+
- tmux セッションで全プロセスを監視可能
|
|
428
|
+
- マージモードで自動化レベルを制御
|
|
508
429
|
|
|
509
430
|
**仕様書からドキュメント更新**:
|
|
510
431
|
```bash
|
|
@@ -513,11 +434,6 @@ pnpm task:loop <issue番号> --branch <ブランチ> # ベースブランチ指
|
|
|
513
434
|
- タスク仕様書の内容をfeature仕様書とsteering仕様書に反映
|
|
514
435
|
<!-- @einja:managed:end -->
|
|
515
436
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
<!-- @einja:
|
|
519
|
-
## プロジェクト固有の設定
|
|
520
|
-
|
|
521
|
-
<!-- このセクションはプロジェクト固有の内容を追記する場所です -->
|
|
522
|
-
<!-- einja syncで上書きされません -->
|
|
523
|
-
<!-- @einja:seed:end -->
|
|
437
|
+
<!-- @einja:project-private:start id="task-management-project" -->
|
|
438
|
+
<!-- プロジェクト固有の情報を記入 -->
|
|
439
|
+
<!-- @einja:project-private:end -->
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Serena MCP サーバーの冪等起動
|
|
3
|
+
# .envrc から source して使用
|
|
4
|
+
|
|
5
|
+
# メインワークツリーをベースにする(worktree 間で共有)
|
|
6
|
+
_SERENA_BASE="${1:-$(pwd)}"
|
|
7
|
+
_SERENA_PORT_FILE="$_SERENA_BASE/.serena-port"
|
|
8
|
+
_SERENA_DEFAULT_PORT="${SERENA_PORT:-9850}"
|
|
9
|
+
|
|
10
|
+
# --- 既存インスタンスチェック(PIDベース) ---
|
|
11
|
+
if [ -f "$_SERENA_PORT_FILE" ]; then
|
|
12
|
+
read -r _saved_port _saved_pid < "$_SERENA_PORT_FILE"
|
|
13
|
+
if [ -n "$_saved_pid" ] && kill -0 "$_saved_pid" 2>/dev/null; then
|
|
14
|
+
# PIDが生存 → 自プロジェクトのSerena
|
|
15
|
+
export SERENA_PORT="$_saved_port"
|
|
16
|
+
return 0 2>/dev/null || true
|
|
17
|
+
fi
|
|
18
|
+
# PID死亡 → クリーンアップ
|
|
19
|
+
rm -f "$_SERENA_PORT_FILE"
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# --- uvx 確認 ---
|
|
23
|
+
if ! command -v uvx &> /dev/null; then
|
|
24
|
+
echo "[ensure-serena] Warning: uvx not found. Serena will not auto-start." >&2
|
|
25
|
+
echo "[ensure-serena] Install uv first: curl -LsSf https://astral.sh/uv/install.sh | sh" >&2
|
|
26
|
+
return 0 2>/dev/null || true
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# --- 空きポート検出 ---
|
|
30
|
+
_port="$_SERENA_DEFAULT_PORT"
|
|
31
|
+
_port_found=false
|
|
32
|
+
for _i in $(seq 1 10); do
|
|
33
|
+
if ! lsof -i ":$_port" -sTCP:LISTEN > /dev/null 2>&1; then
|
|
34
|
+
_port_found=true
|
|
35
|
+
break
|
|
36
|
+
fi
|
|
37
|
+
_port=$((_port + 1))
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
if [ "$_port_found" = false ]; then
|
|
41
|
+
echo "[ensure-serena] Error: No available port found (tried $_SERENA_DEFAULT_PORT-$_port)" >&2
|
|
42
|
+
return 0 2>/dev/null || true
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# --- バックグラウンド起動 ---
|
|
46
|
+
echo "[ensure-serena] Starting Serena on port $_port..."
|
|
47
|
+
uvx --from git+https://github.com/oraios/serena \
|
|
48
|
+
serena start-mcp-server \
|
|
49
|
+
--transport streamable-http \
|
|
50
|
+
--host 127.0.0.1 \
|
|
51
|
+
--port "$_port" \
|
|
52
|
+
--context claude-code \
|
|
53
|
+
--project "$_SERENA_BASE" \
|
|
54
|
+
> /dev/null 2>&1 &
|
|
55
|
+
_serena_pid=$!
|
|
56
|
+
disown
|
|
57
|
+
|
|
58
|
+
# --- 起動待機(PID生存 + ポートLISTEN、最大30秒) ---
|
|
59
|
+
for _i in $(seq 1 60); do
|
|
60
|
+
if ! kill -0 "$_serena_pid" 2>/dev/null; then
|
|
61
|
+
echo "[ensure-serena] Warning: Serena process exited unexpectedly" >&2
|
|
62
|
+
return 0 2>/dev/null || true
|
|
63
|
+
fi
|
|
64
|
+
if lsof -p "$_serena_pid" -i ":$_port" -sTCP:LISTEN > /dev/null 2>&1; then
|
|
65
|
+
echo "$_port $_serena_pid" > "$_SERENA_PORT_FILE"
|
|
66
|
+
export SERENA_PORT="$_port"
|
|
67
|
+
echo "[ensure-serena] Serena ready on port $_port (PID: $_serena_pid)"
|
|
68
|
+
return 0 2>/dev/null || true
|
|
69
|
+
fi
|
|
70
|
+
sleep 0.5
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
# タイムアウト(起動失敗してもdirenvはブロックしない)
|
|
74
|
+
echo "[ensure-serena] Warning: Serena failed to start within 30s" >&2
|
|
75
|
+
return 0 2>/dev/null || true
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 秘密鍵ローテーションスクリプト
|
|
3
|
+
*
|
|
4
|
+
* AUTH_SECRETとDOTENV_PRIVATE_KEY_*のローテーションを可能にする
|
|
5
|
+
* 使用方法: pnpm env:rotate-secrets
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execSync } from "node:child_process";
|
|
9
|
+
import crypto from "node:crypto";
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import * as p from "@clack/prompts";
|
|
13
|
+
import {
|
|
14
|
+
type EnvironmentConfig,
|
|
15
|
+
ENVIRONMENTS,
|
|
16
|
+
parseEnvFile,
|
|
17
|
+
getPrivateKey,
|
|
18
|
+
ENV_KEYS_PATH,
|
|
19
|
+
} from "./lib/env-common.js";
|
|
20
|
+
|
|
21
|
+
const cwd = process.cwd();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* ローテーションタイプ
|
|
25
|
+
*/
|
|
26
|
+
type RotationType = "auth" | "dotenv" | "both";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* AUTH_SECRETを生成
|
|
30
|
+
*
|
|
31
|
+
* @returns 生成されたAUTH_SECRET(64文字のランダム16進数文字列)
|
|
32
|
+
*/
|
|
33
|
+
function generateAuthSecret(): string {
|
|
34
|
+
return crypto.randomBytes(32).toString("hex");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* ローテーションする秘密鍵を選択
|
|
39
|
+
*
|
|
40
|
+
* @returns 選択されたローテーションタイプ
|
|
41
|
+
*/
|
|
42
|
+
async function selectRotationType(): Promise<RotationType> {
|
|
43
|
+
const rotationType = await p.select({
|
|
44
|
+
message: "ローテーションする秘密鍵を選択してください",
|
|
45
|
+
options: [
|
|
46
|
+
{
|
|
47
|
+
value: "auth" as const,
|
|
48
|
+
label: "AUTH_SECRET",
|
|
49
|
+
hint: "NextAuth署名鍵",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
value: "dotenv" as const,
|
|
53
|
+
label: "DOTENV_PRIVATE_KEY",
|
|
54
|
+
hint: "dotenvx暗号化鍵",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
value: "both" as const,
|
|
58
|
+
label: "両方",
|
|
59
|
+
hint: "AUTH_SECRET と DOTENV_PRIVATE_KEY",
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (p.isCancel(rotationType)) {
|
|
65
|
+
p.cancel("キャンセルしました");
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return rotationType;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 対象環境を選択(複数選択可)
|
|
74
|
+
*
|
|
75
|
+
* @returns 選択された環境の配列
|
|
76
|
+
*/
|
|
77
|
+
async function selectTargetEnvironments(): Promise<EnvironmentConfig[]> {
|
|
78
|
+
// 利用可能な環境をチェック
|
|
79
|
+
const availableEnvs = ENVIRONMENTS.filter((env) => {
|
|
80
|
+
const envFilePath = path.join(cwd, env.file);
|
|
81
|
+
const hasFile = fs.existsSync(envFilePath);
|
|
82
|
+
const hasKey = getPrivateKey(env.privateKeyEnv) !== null;
|
|
83
|
+
return hasFile && hasKey;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (availableEnvs.length === 0) {
|
|
87
|
+
p.log.error("ローテーション可能な環境がありません");
|
|
88
|
+
p.log.info("環境ファイルと秘密鍵が必要です");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const envOptions = availableEnvs.map((env) => ({
|
|
93
|
+
value: env.name,
|
|
94
|
+
label: env.description,
|
|
95
|
+
hint: env.file,
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
const selectedEnvs = await p.multiselect({
|
|
99
|
+
message: "対象環境を選択してください(スペースで選択、Enterで確定)",
|
|
100
|
+
options: envOptions,
|
|
101
|
+
required: true,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (p.isCancel(selectedEnvs)) {
|
|
105
|
+
p.cancel("キャンセルしました");
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const selected = ENVIRONMENTS.filter((env) =>
|
|
110
|
+
(selectedEnvs as string[]).includes(env.name),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// 本番環境が含まれる場合は追加確認
|
|
114
|
+
const includesProduction = selected.some((env) => env.name === "production");
|
|
115
|
+
if (includesProduction) {
|
|
116
|
+
p.log.warn("⚠️ 本番環境の秘密鍵を変更します");
|
|
117
|
+
const confirmProd = await p.confirm({
|
|
118
|
+
message: "本当に本番環境の秘密鍵を変更しますか?",
|
|
119
|
+
initialValue: false,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (p.isCancel(confirmProd) || !confirmProd) {
|
|
123
|
+
p.cancel("キャンセルしました");
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return selected;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* AUTH_SECRETをローテーション
|
|
133
|
+
*
|
|
134
|
+
* @param env - 対象環境
|
|
135
|
+
*/
|
|
136
|
+
async function rotateAuthSecret(env: EnvironmentConfig): Promise<void> {
|
|
137
|
+
const envFilePath = path.join(cwd, env.file);
|
|
138
|
+
const privateKey = getPrivateKey(env.privateKeyEnv);
|
|
139
|
+
|
|
140
|
+
if (!privateKey) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`.env.keys に ${env.privateKeyEnv} が見つかりません`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// dotenvx実行時の環境変数
|
|
147
|
+
const dotenvxEnv = { ...process.env, [env.privateKeyEnv]: privateKey };
|
|
148
|
+
|
|
149
|
+
// 復号
|
|
150
|
+
const decrypted = execSync(`dotenvx decrypt -f ${env.file} --stdout`, {
|
|
151
|
+
cwd,
|
|
152
|
+
encoding: "utf-8",
|
|
153
|
+
env: dotenvxEnv,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// 新しいAUTH_SECRETを生成
|
|
157
|
+
const newAuthSecret = generateAuthSecret();
|
|
158
|
+
|
|
159
|
+
// AUTH_SECRET行を置換
|
|
160
|
+
const updatedContent = decrypted.replace(
|
|
161
|
+
/^AUTH_SECRET=.*$/m,
|
|
162
|
+
`AUTH_SECRET=${newAuthSecret}`,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// テンポラリファイルに書き込み
|
|
166
|
+
const tmpPath = path.join(cwd, `${env.file}.tmp`);
|
|
167
|
+
fs.writeFileSync(tmpPath, updatedContent);
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
// 元のファイルを削除してリネーム
|
|
171
|
+
fs.unlinkSync(envFilePath);
|
|
172
|
+
fs.renameSync(tmpPath, envFilePath);
|
|
173
|
+
|
|
174
|
+
// 再暗号化
|
|
175
|
+
execSync(`dotenvx encrypt -f ${env.file}`, {
|
|
176
|
+
cwd,
|
|
177
|
+
stdio: "pipe",
|
|
178
|
+
env: dotenvxEnv,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
p.log.success(`✅ AUTH_SECRET をローテーションしました (${env.name})`);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
// テンポラリファイルを削除
|
|
184
|
+
if (fs.existsSync(tmpPath)) {
|
|
185
|
+
fs.unlinkSync(tmpPath);
|
|
186
|
+
}
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* DOTENV_PRIVATE_KEYをローテーション
|
|
193
|
+
*
|
|
194
|
+
* @param env - 対象環境
|
|
195
|
+
*/
|
|
196
|
+
async function rotateDotenvKey(env: EnvironmentConfig): Promise<void> {
|
|
197
|
+
const envFilePath = path.join(cwd, env.file);
|
|
198
|
+
const privateKey = getPrivateKey(env.privateKeyEnv);
|
|
199
|
+
|
|
200
|
+
if (!privateKey) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`.env.keys に ${env.privateKeyEnv} が見つかりません`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// dotenvx実行時の環境変数
|
|
207
|
+
const dotenvxEnv = { ...process.env, [env.privateKeyEnv]: privateKey };
|
|
208
|
+
|
|
209
|
+
// dotenvx rotate を実行(新しいキーペアが自動生成され、.env.keysが自動更新される)
|
|
210
|
+
execSync(`dotenvx rotate -f ${env.file}`, {
|
|
211
|
+
cwd,
|
|
212
|
+
stdio: "pipe",
|
|
213
|
+
env: dotenvxEnv,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
p.log.success(`✅ DOTENV_PRIVATE_KEY をローテーションしました (${env.name})`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* エラー時復元付きでローテーションを実行
|
|
221
|
+
*
|
|
222
|
+
* @param env - 対象環境
|
|
223
|
+
* @param type - ローテーションタイプ
|
|
224
|
+
*/
|
|
225
|
+
async function rotateWithRecovery(
|
|
226
|
+
env: EnvironmentConfig,
|
|
227
|
+
type: RotationType,
|
|
228
|
+
): Promise<void> {
|
|
229
|
+
const envFilePath = path.join(cwd, env.file);
|
|
230
|
+
const backupPath = path.join(cwd, `${env.file}.bak`);
|
|
231
|
+
const keysBackupPath = path.join(cwd, ".env.keys.bak");
|
|
232
|
+
|
|
233
|
+
// バックアップ作成
|
|
234
|
+
fs.copyFileSync(envFilePath, backupPath);
|
|
235
|
+
fs.copyFileSync(ENV_KEYS_PATH, keysBackupPath);
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
if (type === "auth" || type === "both") {
|
|
239
|
+
await rotateAuthSecret(env);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (type === "dotenv" || type === "both") {
|
|
243
|
+
await rotateDotenvKey(env);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// 成功したらバックアップを削除
|
|
247
|
+
fs.unlinkSync(backupPath);
|
|
248
|
+
if (type === "dotenv" || type === "both") {
|
|
249
|
+
fs.unlinkSync(keysBackupPath);
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
// エラー発生時はバックアップから復元
|
|
253
|
+
p.log.error(`❌ エラーが発生しました: ${error}`);
|
|
254
|
+
if (fs.existsSync(backupPath)) {
|
|
255
|
+
fs.copyFileSync(backupPath, envFilePath);
|
|
256
|
+
fs.unlinkSync(backupPath);
|
|
257
|
+
}
|
|
258
|
+
if (fs.existsSync(keysBackupPath)) {
|
|
259
|
+
fs.copyFileSync(keysBackupPath, ENV_KEYS_PATH);
|
|
260
|
+
fs.unlinkSync(keysBackupPath);
|
|
261
|
+
}
|
|
262
|
+
p.log.info("元のファイルを復元しました");
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 次のステップを表示
|
|
269
|
+
*
|
|
270
|
+
* @param envs - ローテーションした環境の配列
|
|
271
|
+
*/
|
|
272
|
+
function showNextSteps(envs: EnvironmentConfig[]): void {
|
|
273
|
+
const envFiles = envs.map((env) => env.file).join(" ");
|
|
274
|
+
|
|
275
|
+
p.note(
|
|
276
|
+
[
|
|
277
|
+
`git diff ${envFiles} .env.keys`,
|
|
278
|
+
`git add ${envFiles} .env.keys`,
|
|
279
|
+
'git commit -m "chore: 秘密鍵をローテーション"',
|
|
280
|
+
"",
|
|
281
|
+
".env.keys をチームと共有(1Password等)",
|
|
282
|
+
].join("\n"),
|
|
283
|
+
"💡 次のステップ",
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* メイン処理
|
|
289
|
+
*/
|
|
290
|
+
async function main(): Promise<void> {
|
|
291
|
+
p.intro("🔐 秘密鍵ローテーション");
|
|
292
|
+
|
|
293
|
+
// ローテーションタイプを選択
|
|
294
|
+
const rotationType = await selectRotationType();
|
|
295
|
+
|
|
296
|
+
// 対象環境を選択
|
|
297
|
+
const targetEnvs = await selectTargetEnvironments();
|
|
298
|
+
|
|
299
|
+
// 確認
|
|
300
|
+
const proceed = await p.confirm({
|
|
301
|
+
message: "ローテーションを実行しますか?",
|
|
302
|
+
initialValue: true,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
if (p.isCancel(proceed) || !proceed) {
|
|
306
|
+
p.cancel("キャンセルしました");
|
|
307
|
+
process.exit(0);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const spinner = p.spinner();
|
|
311
|
+
|
|
312
|
+
// バックアップを作成中
|
|
313
|
+
spinner.start("バックアップを作成中...");
|
|
314
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
315
|
+
spinner.stop("バックアップを作成しました");
|
|
316
|
+
|
|
317
|
+
// 各環境でローテーション実行
|
|
318
|
+
for (const env of targetEnvs) {
|
|
319
|
+
try {
|
|
320
|
+
await rotateWithRecovery(env, rotationType);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
p.log.error(`${env.name} のローテーションに失敗しました`);
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 次のステップを表示
|
|
328
|
+
showNextSteps(targetEnvs);
|
|
329
|
+
|
|
330
|
+
p.outro("✅ 完了");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
main().catch((error: unknown) => {
|
|
334
|
+
p.log.error(`エラーが発生しました: ${error}`);
|
|
335
|
+
process.exit(1);
|
|
336
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 環境変数表示スクリプト(非対話式)
|
|
3
|
+
*
|
|
4
|
+
* 指定した環境の.envファイルを復号して表示
|
|
5
|
+
* 使用方法:
|
|
6
|
+
* pnpm env:show → .env.local
|
|
7
|
+
* pnpm env:show staging → .env.staging
|
|
8
|
+
* pnpm env:show production → .env.production
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execSync } from "node:child_process";
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
|
|
15
|
+
const cwd = process.cwd();
|
|
16
|
+
const ENV_KEYS_PATH = path.join(cwd, ".env.keys");
|
|
17
|
+
|
|
18
|
+
interface EnvironmentConfig {
|
|
19
|
+
name: string;
|
|
20
|
+
file: string;
|
|
21
|
+
privateKeyEnv: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ENVIRONMENTS: EnvironmentConfig[] = [
|
|
25
|
+
{ name: "local", file: ".env.local", privateKeyEnv: "DOTENV_PRIVATE_KEY_LOCAL" },
|
|
26
|
+
{ name: "develop", file: ".env.develop", privateKeyEnv: "DOTENV_PRIVATE_KEY_DEVELOP" },
|
|
27
|
+
{ name: "staging", file: ".env.staging", privateKeyEnv: "DOTENV_PRIVATE_KEY_STAGING" },
|
|
28
|
+
{ name: "preview", file: ".env.preview", privateKeyEnv: "DOTENV_PRIVATE_KEY_PREVIEW" },
|
|
29
|
+
{ name: "production", file: ".env.production", privateKeyEnv: "DOTENV_PRIVATE_KEY_PRODUCTION" },
|
|
30
|
+
{ name: "ci", file: ".env.ci", privateKeyEnv: "DOTENV_PRIVATE_KEY_CI" },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
function parseEnvFile(filePath: string): Record<string, string> {
|
|
34
|
+
if (!fs.existsSync(filePath)) {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
38
|
+
const result: Record<string, string> = {};
|
|
39
|
+
|
|
40
|
+
for (const line of content.split("\n")) {
|
|
41
|
+
const trimmed = line.trim();
|
|
42
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
43
|
+
|
|
44
|
+
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
45
|
+
if (match) {
|
|
46
|
+
const key = match[1].trim();
|
|
47
|
+
let value = match[2].trim();
|
|
48
|
+
if (
|
|
49
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
50
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
51
|
+
) {
|
|
52
|
+
value = value.slice(1, -1);
|
|
53
|
+
}
|
|
54
|
+
result[key] = value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getPrivateKey(privateKeyEnv: string): string | null {
|
|
61
|
+
if (!fs.existsSync(ENV_KEYS_PATH)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const keys = parseEnvFile(ENV_KEYS_PATH);
|
|
65
|
+
return keys[privateKeyEnv] || null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function showUsage(): void {
|
|
69
|
+
console.log("使用方法: pnpm env:show [環境名]");
|
|
70
|
+
console.log("");
|
|
71
|
+
console.log("環境名:");
|
|
72
|
+
for (const env of ENVIRONMENTS) {
|
|
73
|
+
console.log(` ${env.name.padEnd(12)} → ${env.file}`);
|
|
74
|
+
}
|
|
75
|
+
console.log("");
|
|
76
|
+
console.log("例:");
|
|
77
|
+
console.log(" pnpm env:show # .env.local を表示");
|
|
78
|
+
console.log(" pnpm env:show staging # .env.staging を表示");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function main(): void {
|
|
82
|
+
const arg = process.argv[2];
|
|
83
|
+
|
|
84
|
+
// ヘルプ
|
|
85
|
+
if (arg === "-h" || arg === "--help") {
|
|
86
|
+
showUsage();
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 環境を特定
|
|
91
|
+
const envName = arg || "local";
|
|
92
|
+
const env = ENVIRONMENTS.find((e) => e.name === envName);
|
|
93
|
+
|
|
94
|
+
if (!env) {
|
|
95
|
+
console.error(`❌ 不明な環境: ${envName}`);
|
|
96
|
+
console.error("");
|
|
97
|
+
showUsage();
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const envFilePath = path.join(cwd, env.file);
|
|
102
|
+
|
|
103
|
+
if (!fs.existsSync(envFilePath)) {
|
|
104
|
+
console.error(`❌ ${env.file} が見つかりません`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const privateKey = getPrivateKey(env.privateKeyEnv);
|
|
109
|
+
if (!privateKey) {
|
|
110
|
+
console.error(`❌ .env.keys に ${env.privateKeyEnv} が見つかりません`);
|
|
111
|
+
console.error(" チームから .env.keys を共有してもらってください");
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// dotenvx で復号
|
|
116
|
+
try {
|
|
117
|
+
const decrypted = execSync(`dotenvx decrypt -f ${env.file} --stdout`, {
|
|
118
|
+
cwd,
|
|
119
|
+
encoding: "utf-8",
|
|
120
|
+
env: { ...process.env, [env.privateKeyEnv]: privateKey },
|
|
121
|
+
});
|
|
122
|
+
console.log(decrypted);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error("❌ 復号に失敗しました");
|
|
125
|
+
console.error(error);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
main();
|