@einja/dev-cli 0.1.41 → 0.1.44
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/dist/commands/task-loop/lib/github-client.test.js.map +1 -1
- package/dist/commands/task-loop/lib/vibe-kanban-rest-client.js +2 -2
- package/dist/commands/task-loop/lib/vibe-kanban-rest-client.js.map +1 -1
- package/dist/lib/preset-update/file-copier.js +3 -3
- package/dist/lib/preset-update/file-copier.js.map +1 -1
- package/dist/lib/sync/marker-processor.js.map +1 -1
- package/dist/lib/sync/metadata-manager.js +1 -1
- package/dist/lib/sync/metadata-manager.js.map +1 -1
- package/dist/lib/sync/metadata-manager.test.js +3 -2
- package/dist/lib/sync/metadata-manager.test.js.map +1 -1
- package/dist/lib/sync/project-private-synchronizer.d.ts.map +1 -1
- package/dist/lib/sync/project-private-synchronizer.js +5 -1
- package/dist/lib/sync/project-private-synchronizer.js.map +1 -1
- package/package.json +1 -1
- package/presets/default/.claude/agents/einja/backend-architect.md +17 -1
- package/presets/default/.claude/agents/einja/codex-agent.md +1 -1
- package/presets/default/.claude/agents/einja/design-engineer.md +1 -1
- package/presets/default/.claude/agents/einja/docs/docs-updater.md +3 -93
- package/presets/default/.claude/agents/einja/frontend-architect.md +17 -1
- package/presets/default/.claude/agents/einja/frontend-coder.md +1 -1
- package/presets/default/.claude/agents/einja/{specs/spec-design-generator.md → issue-specs/design-generator.md} +12 -7
- package/presets/default/.claude/agents/einja/{specs/spec-qa-generator.md → issue-specs/qa-generator.md} +6 -4
- package/presets/default/.claude/agents/einja/{specs/spec-requirements-generator.md → issue-specs/requirements-generator.md} +5 -5
- package/presets/default/.claude/agents/einja/{specs/spec-tasks-generator.md → issue-specs/tasks-generator.md} +13 -14
- package/presets/default/.claude/agents/einja/{specs/spec-tasks-validator.md → issue-specs/tasks-validator.md} +9 -9
- package/presets/default/.claude/agents/einja/issue-specs/ui-design-generator.md +114 -0
- package/presets/default/.claude/agents/einja/task/task-executer.md +9 -3
- package/presets/default/.claude/agents/einja/task/task-modification-analyzer.md +2 -2
- package/presets/default/.claude/agents/einja/task/task-qa.md +3 -3
- package/presets/default/.claude/agents/einja/task/task-reviewer.md +13 -1
- package/presets/default/.claude/commands/einja/einja-sync.md +119 -44
- package/presets/default/.claude/commands/einja/issue-exec.md +29 -19
- package/presets/default/.claude/commands/einja/sync-cursor-commands.md +6 -6
- package/presets/default/.claude/commands/einja/{update-docs-by-task-specs.md → update-docs-by-issue-specs.md} +58 -58
- package/presets/default/.claude/hooks/einja/plan-mode-skill-loader.sh +5 -1
- package/presets/default/.claude/settings.json +14 -4
- package/presets/default/.claude/skills/{einja-general-context-loader → _einja-general-context-loader}/SKILL.md +2 -2
- package/presets/default/.claude/skills/{einja-output-format → _einja-output-format}/SKILL.md +1 -1
- package/presets/default/.claude/skills/_einja-project-overview/SKILL.md +29 -0
- package/presets/default/.claude/skills/{einja-spec-context-loader → _einja-spec-context-loader}/SKILL.md +5 -5
- package/presets/default/.claude/skills/einja-coding-standards/references/testing-strategy.md +899 -0
- package/presets/default/.claude/skills/einja-conflict-resolver/SKILL.md +1 -1
- package/presets/default/.claude/skills/einja-create-pr/SKILL.md +138 -0
- package/presets/default/.claude/skills/einja-infra-maintenance/SKILL.md +779 -0
- package/presets/default/.claude/{commands/einja/spec-create.md → skills/einja-issue-spec-create/SKILL.md} +47 -24
- package/presets/default/.claude/skills/einja-issue-spec-generator/SKILL.md +105 -0
- package/presets/default/.claude/skills/einja-issue-spec-generator/references/format-rules.md +35 -0
- package/presets/default/.claude/skills/einja-issue-spec-validator/SKILL.md +130 -0
- package/presets/default/.claude/skills/einja-issue-spec-validator/references/validation-rules.md +52 -0
- package/presets/default/.claude/skills/einja-npm-release/SKILL.md +242 -0
- package/presets/default/.claude/skills/einja-skill-creator/SKILL.md +68 -12
- package/presets/default/.claude/skills/einja-skill-creator/scripts/aggregate_benchmark.py +368 -121
- package/presets/default/.claude/skills/einja-skill-creator/scripts/compare_runs.py +154 -0
- package/presets/default/.claude/skills/einja-skill-creator/scripts/generate_report.py +14 -7
- package/presets/default/.claude/skills/einja-skill-creator/scripts/improve_description.py +2 -7
- package/presets/default/.claude/skills/einja-skill-creator/scripts/run_loop.py +263 -183
- package/presets/default/.claude/skills/einja-skill-first/SKILL.md +265 -0
- package/presets/default/.claude/skills/einja-subagent-question-protocol/SKILL.md +98 -0
- package/presets/default/.claude/skills/einja-task-commit/SKILL.md +7 -7
- package/presets/default/.claude/{commands/einja/task-exec.md → skills/einja-task-exec/SKILL.md} +3 -78
- package/presets/default/.claude/skills/einja-task-qa/SKILL.md +4 -4
- package/presets/default/.claude/skills/einja-task-qa/references/troubleshooting.md +1 -1
- package/presets/default/.claude/skills/einja-task-qa/references/usage-patterns.md +2 -2
- package/presets/default/.claude/skills/einja-team-exec/SKILL.md +165 -0
- package/presets/default/CLAUDE.md.template +21 -6
- package/presets/default/docs/einja/instructions/deployment-setup.md +1 -1
- package/presets/default/docs/einja/instructions/issue-exec-workflow.md +11 -11
- package/presets/default/docs/einja/instructions/local-server-environment-and-worktree.md +1 -1
- package/presets/default/docs/einja/instructions/setup-flow.md +279 -0
- package/presets/default/docs/einja/instructions/task-execute.md +42 -42
- package/presets/default/docs/einja/steering/acceptance-criteria-and-qa-guide.md +1 -1
- package/presets/default/docs/einja/steering/branch-strategy.md +1 -1
- package/presets/default/docs/einja/steering/development-workflow.md +93 -25
- package/presets/default/docs/einja/steering/infrastructure/deployment.md +107 -0
- package/presets/default/docs/einja/steering/task-management.md +9 -13
- package/presets/default/scripts/ensure-serena.sh +2 -2
- package/presets/default/scripts/env-rotate-secrets.ts +66 -6
- package/presets/default/scripts/init-github.ts +363 -0
- package/presets/default/scripts/init.sh +11 -5
- package/presets/default/scripts/setup-dev.ts +16 -1
- package/presets/default/.claude/agents/einja/git/conflict-resolver.md +0 -152
- package/presets/default/.claude/hooks/einja/validate-git-commit.sh +0 -239
- package/presets/default/.claude/skills/einja-project-overview/SKILL.md +0 -39
|
@@ -87,12 +87,12 @@ GitHub Issueのチェックボックスでタスクグループのステータ
|
|
|
87
87
|
- [x] 1.1 タスクグループ名
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
**注意**: タスクグループの完了時、GitHub Issue
|
|
90
|
+
**注意**: タスクグループの完了時、GitHub Issueのチェックボックス更新はユーザーが明示的に指示した場合のみ行います。`einja-task-exec` Skillは自動でIssueを更新しません。
|
|
91
91
|
|
|
92
92
|
### Issueのライフサイクル
|
|
93
93
|
|
|
94
|
-
1. **Issue作成**:
|
|
95
|
-
2. **タスクグループ実行**:
|
|
94
|
+
1. **Issue作成**: `einja-issue-spec-create` Skillで仕様書作成時に自動生成
|
|
95
|
+
2. **タスクグループ実行**: `einja-task-exec` Skillで `#{issue_number} {タスクグループ番号}` を指定してタスクグループを実行
|
|
96
96
|
3. **タスクグループ完了**: 実装・テスト・レビュー完了後、チェックボックスをON(手動更新)
|
|
97
97
|
4. **Issue完了**: すべてのタスクグループが完了したらIssueをClose
|
|
98
98
|
|
|
@@ -375,7 +375,7 @@ TDDは**3タスク分割(X.Y.1 テスト / X.Y.2 実装 / X.Y.3 リファク
|
|
|
375
375
|
> **詳細なフロー(仕様書作成からレビュー・マージまで)は[開発ワークフロー](development-workflow.md)を参照してください。**
|
|
376
376
|
|
|
377
377
|
### 1. タスクグループを選定・実行
|
|
378
|
-
-
|
|
378
|
+
- `einja-task-exec` Skillで `#{issue_number} {タスクグループ番号}` を指定して実行
|
|
379
379
|
- タスクグループ番号は必須引数(例: `1.1`, `2.3`)
|
|
380
380
|
- **注意**: 単一タスクグループを品質重視で確実に完了させたい場合に使用。`/einja:issue-exec` の Worker 内部でも呼ばれる
|
|
381
381
|
- executer → reviewer → qa の3段階で実行
|
|
@@ -401,20 +401,16 @@ TDDは**3タスク分割(X.Y.1 テスト / X.Y.2 実装 / X.Y.3 リファク
|
|
|
401
401
|
### タスク管理関連コマンド
|
|
402
402
|
|
|
403
403
|
**仕様書作成とIssue生成**:
|
|
404
|
-
|
|
405
|
-
/einja:spec-create [タスク内容の説明]
|
|
406
|
-
```
|
|
404
|
+
`einja-issue-spec-create` Skillを使用し、タスク内容の説明を引数に指定します。
|
|
407
405
|
- requirements.md、design.mdを作成し、GitHub Issueを自動生成
|
|
408
406
|
|
|
409
407
|
**タスクグループ実行**:
|
|
410
|
-
|
|
411
|
-
/einja:task-exec #{issue_number} {タスクグループ番号}
|
|
412
|
-
```
|
|
408
|
+
`einja-task-exec` Skillを使用し、`#{issue_number} {タスクグループ番号}` を引数に指定します。
|
|
413
409
|
- Issue番号とタスクグループ番号は両方必須
|
|
414
410
|
- executer → reviewer → qa の3段階で実行
|
|
415
411
|
- QA合格後は追加指示待ち状態に入る
|
|
416
412
|
- GitHub Issue更新はユーザーの明示的指示時のみ
|
|
417
|
-
- **位置づけ**:
|
|
413
|
+
- **位置づけ**: 単一タスクグループを品質重視で実行するSkill。`/einja:issue-exec` の Worker 内部でも使用される
|
|
418
414
|
|
|
419
415
|
**Issue全体の並列実行**:
|
|
420
416
|
```bash
|
|
@@ -429,9 +425,9 @@ TDDは**3タスク分割(X.Y.1 テスト / X.Y.2 実装 / X.Y.3 リファク
|
|
|
429
425
|
|
|
430
426
|
**仕様書からドキュメント更新**:
|
|
431
427
|
```bash
|
|
432
|
-
/einja:update-docs-by-
|
|
428
|
+
/einja:update-docs-by-issue-specs [Issue仕様書ディレクトリパス]
|
|
433
429
|
```
|
|
434
|
-
-
|
|
430
|
+
- Issue仕様書の内容をfeature仕様書とsteering仕様書に反映
|
|
435
431
|
<!-- @einja:managed:end -->
|
|
436
432
|
|
|
437
433
|
<!-- @einja:project-private:start id="task-management-project" -->
|
|
@@ -30,7 +30,7 @@ fi
|
|
|
30
30
|
_port="$_SERENA_DEFAULT_PORT"
|
|
31
31
|
_port_found=false
|
|
32
32
|
for _i in $(seq 1 10); do
|
|
33
|
-
if !
|
|
33
|
+
if ! nc -z 127.0.0.1 "$_port" > /dev/null 2>&1; then
|
|
34
34
|
_port_found=true
|
|
35
35
|
break
|
|
36
36
|
fi
|
|
@@ -61,7 +61,7 @@ for _i in $(seq 1 60); do
|
|
|
61
61
|
echo "[ensure-serena] Warning: Serena process exited unexpectedly" >&2
|
|
62
62
|
return 0 2>/dev/null || true
|
|
63
63
|
fi
|
|
64
|
-
if
|
|
64
|
+
if nc -z 127.0.0.1 "$_port" > /dev/null 2>&1; then
|
|
65
65
|
echo "$_port $_serena_pid" > "$_SERENA_PORT_FILE"
|
|
66
66
|
export SERENA_PORT="$_port"
|
|
67
67
|
echo "[ensure-serena] Serena ready on port $_port (PID: $_serena_pid)"
|
|
@@ -285,9 +285,60 @@ function showNextSteps(envs: EnvironmentConfig[]): void {
|
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
/**
|
|
288
|
-
*
|
|
288
|
+
* CLIフラグをパース
|
|
289
289
|
*/
|
|
290
|
-
|
|
290
|
+
function parseArgs(): { all: boolean; nonInteractive: boolean } {
|
|
291
|
+
const args = process.argv.slice(2);
|
|
292
|
+
return {
|
|
293
|
+
all: args.includes("--all"),
|
|
294
|
+
nonInteractive: args.includes("--non-interactive"),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* 非対話モードで全環境のローテーションを実行
|
|
300
|
+
* create-einja-app のセットアップ時に使用
|
|
301
|
+
*
|
|
302
|
+
* @returns 成功した環境数
|
|
303
|
+
*/
|
|
304
|
+
async function runNonInteractive(): Promise<number> {
|
|
305
|
+
const rotationType: RotationType = "both";
|
|
306
|
+
let successCount = 0;
|
|
307
|
+
|
|
308
|
+
// 利用可能な環境をフィルタ
|
|
309
|
+
const availableEnvs = ENVIRONMENTS.filter((env) => {
|
|
310
|
+
const envFilePath = path.join(cwd, env.file);
|
|
311
|
+
const hasFile = fs.existsSync(envFilePath);
|
|
312
|
+
const hasKey = getPrivateKey(env.privateKeyEnv) !== null;
|
|
313
|
+
return hasFile && hasKey;
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
if (availableEnvs.length === 0) {
|
|
317
|
+
console.log("[env:rotate-secrets] ローテーション可能な環境がありません");
|
|
318
|
+
return 0;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
console.log(`[env:rotate-secrets] ${availableEnvs.length}環境の秘密鍵をローテーション中...`);
|
|
322
|
+
|
|
323
|
+
for (const env of availableEnvs) {
|
|
324
|
+
try {
|
|
325
|
+
await rotateWithRecovery(env, rotationType);
|
|
326
|
+
successCount++;
|
|
327
|
+
console.log(`[env:rotate-secrets] ✅ ${env.name}: 完了`);
|
|
328
|
+
} catch (error) {
|
|
329
|
+
console.error(`[env:rotate-secrets] ⚠ ${env.name}: 失敗 - ${error}`);
|
|
330
|
+
// 失敗時も続行(中断しない)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
console.log(`[env:rotate-secrets] ${successCount}/${availableEnvs.length}環境のローテーション完了`);
|
|
335
|
+
return successCount;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* 対話モードのメイン処理
|
|
340
|
+
*/
|
|
341
|
+
async function runInteractive(): Promise<void> {
|
|
291
342
|
p.intro("🔐 秘密鍵ローテーション");
|
|
292
343
|
|
|
293
344
|
// ローテーションタイプを選択
|
|
@@ -330,7 +381,16 @@ async function main(): Promise<void> {
|
|
|
330
381
|
p.outro("✅ 完了");
|
|
331
382
|
}
|
|
332
383
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
384
|
+
const flags = parseArgs();
|
|
385
|
+
|
|
386
|
+
if (flags.all && flags.nonInteractive) {
|
|
387
|
+
runNonInteractive().catch((error: unknown) => {
|
|
388
|
+
console.error(`[env:rotate-secrets] エラー: ${error}`);
|
|
389
|
+
// 非対話モードではprocess.exit(1)を呼ばない(呼び出し元で制御)
|
|
390
|
+
});
|
|
391
|
+
} else {
|
|
392
|
+
runInteractive().catch((error: unknown) => {
|
|
393
|
+
p.log.error(`エラーが発生しました: ${error}`);
|
|
394
|
+
process.exit(1);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
5
|
+
|
|
6
|
+
// ANSI color codes
|
|
7
|
+
const colors = {
|
|
8
|
+
blue: (text: string) => `\x1b[34m${text}\x1b[0m`,
|
|
9
|
+
green: (text: string) => `\x1b[32m${text}\x1b[0m`,
|
|
10
|
+
yellow: (text: string) => `\x1b[33m${text}\x1b[0m`,
|
|
11
|
+
gray: (text: string) => `\x1b[90m${text}\x1b[0m`,
|
|
12
|
+
red: (text: string) => `\x1b[31m${text}\x1b[0m`,
|
|
13
|
+
cyan: (text: string) => `\x1b[36m${text}\x1b[0m`,
|
|
14
|
+
bold: (text: string) => `\x1b[1m${text}\x1b[0m`,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function commandExists(cmd: string): boolean {
|
|
18
|
+
try {
|
|
19
|
+
const checkCmd =
|
|
20
|
+
process.platform === "win32" ? `where ${cmd}` : `command -v ${cmd}`;
|
|
21
|
+
execSync(checkCmd, { stdio: "ignore", shell: true });
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function succeed(message: string): void {
|
|
29
|
+
console.log(`${colors.green("✓")} ${message}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function warn(message: string): void {
|
|
33
|
+
console.log(`${colors.yellow("⚠")} ${message}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function fail(message: string): void {
|
|
37
|
+
console.log(`${colors.red("✗")} ${message}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function step(num: number, message: string): void {
|
|
41
|
+
console.log(`\n${colors.blue(`Step ${num}:`)} ${message}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* git remote URLからorg/repoを解析する
|
|
46
|
+
*/
|
|
47
|
+
function parseRemoteUrl(url: string): { org: string; repo: string } | null {
|
|
48
|
+
// SSH形式: git@github.com:org/repo.git
|
|
49
|
+
const sshMatch = url.match(/git@github\.com:([^/]+)\/([^/.]+)(\.git)?/);
|
|
50
|
+
if (sshMatch) {
|
|
51
|
+
return { org: sshMatch[1], repo: sshMatch[2] };
|
|
52
|
+
}
|
|
53
|
+
// HTTPS形式: https://github.com/org/repo.git
|
|
54
|
+
const httpsMatch = url.match(
|
|
55
|
+
/https:\/\/github\.com\/([^/]+)\/([^/.]+)(\.git)?/,
|
|
56
|
+
);
|
|
57
|
+
if (httpsMatch) {
|
|
58
|
+
return { org: httpsMatch[1], repo: httpsMatch[2] };
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* .env.keysファイルからDOTENV_PRIVATE_KEY_*のエントリを読み取る
|
|
65
|
+
*/
|
|
66
|
+
function parseEnvKeys(
|
|
67
|
+
filePath: string,
|
|
68
|
+
): Array<{ name: string; value: string }> {
|
|
69
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
70
|
+
const entries: Array<{ name: string; value: string }> = [];
|
|
71
|
+
for (const line of content.split("\n")) {
|
|
72
|
+
const match = line.match(
|
|
73
|
+
/^(DOTENV_PRIVATE_KEY_\w+)=["']?([^"'\n]+)["']?/,
|
|
74
|
+
);
|
|
75
|
+
if (match) {
|
|
76
|
+
entries.push({ name: match[1], value: match[2] });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return entries;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function main(): Promise<void> {
|
|
83
|
+
const cwd = process.cwd();
|
|
84
|
+
|
|
85
|
+
p.intro(colors.blue("GitHub リポジトリセットアップ"));
|
|
86
|
+
|
|
87
|
+
// Step 1: gh CLIの確認
|
|
88
|
+
step(1, "GitHub CLI (gh) の確認...");
|
|
89
|
+
|
|
90
|
+
if (!commandExists("gh")) {
|
|
91
|
+
fail("GitHub CLI (gh) がインストールされていません");
|
|
92
|
+
console.log(colors.yellow(" インストール方法:"));
|
|
93
|
+
console.log(colors.cyan(" brew install gh"));
|
|
94
|
+
console.log(
|
|
95
|
+
colors.gray(" 詳細: https://cli.github.com/manual/installation"),
|
|
96
|
+
);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
succeed("GitHub CLI (gh) がインストールされています");
|
|
100
|
+
|
|
101
|
+
// Step 2: gh認証の確認
|
|
102
|
+
step(2, "GitHub認証の確認...");
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
execSync("gh auth status", { stdio: "pipe" });
|
|
106
|
+
succeed("GitHub認証済みです");
|
|
107
|
+
} catch {
|
|
108
|
+
fail("GitHubに認証されていません");
|
|
109
|
+
console.log(colors.yellow(" 以下のコマンドでログインしてください:"));
|
|
110
|
+
console.log(colors.cyan(" gh auth login"));
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Step 3: リモートリポジトリの確認
|
|
115
|
+
step(3, "リモートリポジトリの確認...");
|
|
116
|
+
|
|
117
|
+
let org = "";
|
|
118
|
+
let repo = "";
|
|
119
|
+
let remoteUrl = "";
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
remoteUrl = execSync("git remote get-url origin", {
|
|
123
|
+
encoding: "utf-8",
|
|
124
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
125
|
+
}).trim();
|
|
126
|
+
} catch {
|
|
127
|
+
remoteUrl = "";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!remoteUrl) {
|
|
131
|
+
// Step 4: リモートがない場合、新規作成
|
|
132
|
+
step(4, "新規リポジトリの作成...");
|
|
133
|
+
|
|
134
|
+
const dirName = path.basename(cwd);
|
|
135
|
+
|
|
136
|
+
const orgInput = await p.text({
|
|
137
|
+
message: "GitHub組織名(またはユーザー名)を入力してください",
|
|
138
|
+
placeholder: "einja-inc",
|
|
139
|
+
initialValue: "einja-inc",
|
|
140
|
+
});
|
|
141
|
+
if (p.isCancel(orgInput)) {
|
|
142
|
+
p.cancel("キャンセルしました");
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
org = orgInput;
|
|
146
|
+
|
|
147
|
+
const repoInput = await p.text({
|
|
148
|
+
message: "リポジトリ名を入力してください",
|
|
149
|
+
placeholder: dirName,
|
|
150
|
+
defaultValue: dirName,
|
|
151
|
+
});
|
|
152
|
+
if (p.isCancel(repoInput)) {
|
|
153
|
+
p.cancel("キャンセルしました");
|
|
154
|
+
process.exit(0);
|
|
155
|
+
}
|
|
156
|
+
repo = repoInput;
|
|
157
|
+
|
|
158
|
+
const visibility = await p.select({
|
|
159
|
+
message: "リポジトリの公開設定を選択してください",
|
|
160
|
+
options: [
|
|
161
|
+
{ value: "private", label: "Private(非公開)", hint: "推奨" },
|
|
162
|
+
{ value: "public", label: "Public(公開)" },
|
|
163
|
+
],
|
|
164
|
+
initialValue: "private",
|
|
165
|
+
});
|
|
166
|
+
if (p.isCancel(visibility)) {
|
|
167
|
+
p.cancel("キャンセルしました");
|
|
168
|
+
process.exit(0);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const visibilityFlag =
|
|
172
|
+
visibility === "public" ? "--public" : "--private";
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
execSync(
|
|
176
|
+
`gh repo create ${org}/${repo} ${visibilityFlag} --source=. --remote=origin`,
|
|
177
|
+
{ stdio: "inherit", cwd },
|
|
178
|
+
);
|
|
179
|
+
succeed(`リポジトリ ${org}/${repo} を作成しました`);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
fail("リポジトリの作成に失敗しました");
|
|
182
|
+
console.log(colors.red(` ${error}`));
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
// リモートが存在する場合、org/repoを解析
|
|
187
|
+
const parsed = parseRemoteUrl(remoteUrl);
|
|
188
|
+
if (parsed) {
|
|
189
|
+
org = parsed.org;
|
|
190
|
+
repo = parsed.repo;
|
|
191
|
+
succeed(`リモートリポジトリ: ${org}/${repo}`);
|
|
192
|
+
} else {
|
|
193
|
+
warn(`リモートURLの解析に失敗しました: ${remoteUrl}`);
|
|
194
|
+
org = "";
|
|
195
|
+
repo = "";
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Step 5: mainブランチのプッシュ
|
|
200
|
+
step(5, "mainブランチのプッシュ...");
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const trackingBranch = execSync(
|
|
204
|
+
"git rev-parse --abbrev-ref --symbolic-full-name @{u}",
|
|
205
|
+
{
|
|
206
|
+
encoding: "utf-8",
|
|
207
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
208
|
+
},
|
|
209
|
+
).trim();
|
|
210
|
+
succeed(`リモート追跡ブランチ: ${trackingBranch}`);
|
|
211
|
+
} catch {
|
|
212
|
+
// 追跡ブランチがない場合はプッシュ
|
|
213
|
+
try {
|
|
214
|
+
execSync("git push -u origin main", { stdio: "inherit", cwd });
|
|
215
|
+
succeed("mainブランチをプッシュしました");
|
|
216
|
+
} catch (error) {
|
|
217
|
+
warn("mainブランチのプッシュに失敗しました");
|
|
218
|
+
console.log(colors.gray(` ${error}`));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Step 6: ブランチ保護ルールの設定
|
|
223
|
+
step(6, "ブランチ保護ルールの設定...");
|
|
224
|
+
|
|
225
|
+
if (org && repo) {
|
|
226
|
+
try {
|
|
227
|
+
const protectionPayload = JSON.stringify({
|
|
228
|
+
required_status_checks: {
|
|
229
|
+
strict: true,
|
|
230
|
+
contexts: [],
|
|
231
|
+
},
|
|
232
|
+
enforce_admins: false,
|
|
233
|
+
required_pull_request_reviews: {
|
|
234
|
+
dismiss_stale_reviews: true,
|
|
235
|
+
require_code_owner_reviews: false,
|
|
236
|
+
required_approving_review_count: 1,
|
|
237
|
+
},
|
|
238
|
+
restrictions: null,
|
|
239
|
+
allow_force_pushes: false,
|
|
240
|
+
allow_deletions: false,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
execSync(
|
|
244
|
+
`gh api repos/${org}/${repo}/branches/main/protection -X PUT --input - <<'EOF'\n${protectionPayload}\nEOF`,
|
|
245
|
+
{
|
|
246
|
+
stdio: "pipe",
|
|
247
|
+
cwd,
|
|
248
|
+
shell: true,
|
|
249
|
+
},
|
|
250
|
+
);
|
|
251
|
+
succeed("ブランチ保護ルールを設定しました");
|
|
252
|
+
console.log(colors.gray(" - PRレビュー必須(1名以上)"));
|
|
253
|
+
console.log(colors.gray(" - ステータスチェック必須"));
|
|
254
|
+
console.log(colors.gray(" - 古いレビューの自動却下"));
|
|
255
|
+
console.log(colors.gray(" - フォースプッシュ禁止"));
|
|
256
|
+
} catch (error) {
|
|
257
|
+
warn("ブランチ保護ルールの設定に失敗しました");
|
|
258
|
+
console.log(
|
|
259
|
+
colors.gray(
|
|
260
|
+
" リポジトリの権限を確認してください(Freeプランでは制限あり)",
|
|
261
|
+
),
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
warn("org/repoが不明のため、ブランチ保護ルールをスキップしました");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Step 7: GitHub Secretsの設定
|
|
269
|
+
step(7, "GitHub Secretsの設定(.env.keys)...");
|
|
270
|
+
|
|
271
|
+
const envKeysPath = path.join(cwd, ".env.keys");
|
|
272
|
+
|
|
273
|
+
if (fs.existsSync(envKeysPath)) {
|
|
274
|
+
const keys = parseEnvKeys(envKeysPath);
|
|
275
|
+
if (keys.length > 0) {
|
|
276
|
+
let successCount = 0;
|
|
277
|
+
let failCount = 0;
|
|
278
|
+
|
|
279
|
+
for (const { name, value } of keys) {
|
|
280
|
+
try {
|
|
281
|
+
const result = spawnSync("gh", ["secret", "set", name, "--body", value], {
|
|
282
|
+
stdio: "pipe",
|
|
283
|
+
cwd,
|
|
284
|
+
});
|
|
285
|
+
if (result.status !== 0) {
|
|
286
|
+
throw new Error(result.stderr?.toString() || "gh secret set failed");
|
|
287
|
+
}
|
|
288
|
+
successCount++;
|
|
289
|
+
} catch {
|
|
290
|
+
failCount++;
|
|
291
|
+
warn(` Secret ${name} の設定に失敗しました`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (successCount > 0) {
|
|
296
|
+
succeed(`${successCount}件のSecretを設定しました`);
|
|
297
|
+
}
|
|
298
|
+
if (failCount > 0) {
|
|
299
|
+
warn(`${failCount}件のSecretの設定に失敗しました`);
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
warn(
|
|
303
|
+
".env.keys にDOTENV_PRIVATE_KEY_*エントリが見つかりませんでした",
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
warn(".env.keys が見つかりません(スキップ)");
|
|
308
|
+
console.log(
|
|
309
|
+
colors.gray(
|
|
310
|
+
" 後で .env.keys を配置して再実行してください",
|
|
311
|
+
),
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Step 8: GitHub Environmentsの作成
|
|
316
|
+
step(8, "GitHub Environmentsの作成...");
|
|
317
|
+
|
|
318
|
+
if (org && repo) {
|
|
319
|
+
const environments = ["production", "preview"];
|
|
320
|
+
|
|
321
|
+
for (const envName of environments) {
|
|
322
|
+
try {
|
|
323
|
+
execSync(
|
|
324
|
+
`gh api repos/${org}/${repo}/environments/${envName} -X PUT --input - <<'EOF'\n{}\nEOF`,
|
|
325
|
+
{
|
|
326
|
+
stdio: "pipe",
|
|
327
|
+
cwd,
|
|
328
|
+
shell: true,
|
|
329
|
+
},
|
|
330
|
+
);
|
|
331
|
+
succeed(`Environment "${envName}" を作成しました`);
|
|
332
|
+
} catch {
|
|
333
|
+
warn(`Environment "${envName}" の作成に失敗しました`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
warn("org/repoが不明のため、Environmentsの作成をスキップしました");
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 完了サマリー
|
|
341
|
+
console.log(colors.green("\n=========================================="));
|
|
342
|
+
console.log(colors.green("✅ GitHubリポジトリのセットアップが完了しました!"));
|
|
343
|
+
console.log(colors.green("==========================================\n"));
|
|
344
|
+
|
|
345
|
+
if (org && repo) {
|
|
346
|
+
console.log(`リポジトリ: ${colors.cyan(`https://github.com/${org}/${repo}`)}`);
|
|
347
|
+
console.log("");
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
console.log("セットアップ内容:");
|
|
351
|
+
console.log(colors.gray(" - リモートリポジトリの設定"));
|
|
352
|
+
console.log(colors.gray(" - mainブランチの保護ルール"));
|
|
353
|
+
console.log(colors.gray(" - GitHub Secrets(.env.keys)"));
|
|
354
|
+
console.log(colors.gray(" - GitHub Environments(production, preview)"));
|
|
355
|
+
console.log("");
|
|
356
|
+
|
|
357
|
+
p.outro(colors.green("セットアップが完了しました"));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
main().catch((error: unknown) => {
|
|
361
|
+
console.error(colors.red("エラーが発生しました:"), error);
|
|
362
|
+
process.exit(1);
|
|
363
|
+
});
|
|
@@ -74,11 +74,16 @@ volta install node@"$NODE_VERSION"
|
|
|
74
74
|
volta install pnpm@"$PNPM_VERSION"
|
|
75
75
|
log_success "Node.js $NODE_VERSION, pnpm $PNPM_VERSION をインストールしました"
|
|
76
76
|
|
|
77
|
-
# Step 4:
|
|
78
|
-
log_step 4 "
|
|
77
|
+
# Step 4: direnv allow(direnvが利用可能な場合)
|
|
78
|
+
log_step 4 "direnv設定..."
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
if command -v direnv &> /dev/null; then
|
|
81
|
+
direnv allow
|
|
82
|
+
log_success "direnv allow を実行しました"
|
|
83
|
+
else
|
|
84
|
+
log_warn "direnvが見つかりません(スキップ)"
|
|
85
|
+
echo -e " ${GRAY}direnvインストール後に 'direnv allow' を実行してください${NC}"
|
|
86
|
+
fi
|
|
82
87
|
|
|
83
88
|
# 完了
|
|
84
89
|
echo ""
|
|
@@ -88,5 +93,6 @@ echo -e "==========================================${NC}"
|
|
|
88
93
|
echo ""
|
|
89
94
|
echo "次のステップ:"
|
|
90
95
|
echo -e " 1. ターミナルを再起動: ${BLUE}exec \$SHELL${NC}"
|
|
91
|
-
echo -e " 2.
|
|
96
|
+
echo -e " 2. 依存関係インストール: ${BLUE}pnpm install${NC}"
|
|
97
|
+
echo -e " 3. 環境セットアップ: ${BLUE}pnpm dev:setup${NC}"
|
|
92
98
|
echo ""
|
|
@@ -619,7 +619,18 @@ async function main(): Promise<void> {
|
|
|
619
619
|
}
|
|
620
620
|
} else {
|
|
621
621
|
warn("Dockerがインストールされていません");
|
|
622
|
-
|
|
622
|
+
if (platform === "macos") {
|
|
623
|
+
console.log(colors.yellow(" OrbStack(Docker互換の軽量ツール)のインストールを推奨します:"));
|
|
624
|
+
console.log(colors.cyan(" brew install orbstack"));
|
|
625
|
+
console.log(colors.gray(" または: https://orbstack.dev/"));
|
|
626
|
+
} else if (platform === "windows") {
|
|
627
|
+
console.log(colors.yellow(" Dockerをインストールしてください:"));
|
|
628
|
+
console.log(colors.gray(" https://docs.docker.com/desktop/install/windows-install/"));
|
|
629
|
+
} else {
|
|
630
|
+
console.log(colors.yellow(" Docker Engineをインストールしてください:"));
|
|
631
|
+
console.log(colors.gray(" https://docs.docker.com/engine/install/"));
|
|
632
|
+
}
|
|
633
|
+
console.log(colors.gray(" インストール後、以下を実行してください:"));
|
|
623
634
|
console.log(colors.gray(" docker-compose up -d postgres"));
|
|
624
635
|
console.log(colors.gray(" pnpm db:generate && pnpm db:push"));
|
|
625
636
|
}
|
|
@@ -632,6 +643,10 @@ async function main(): Promise<void> {
|
|
|
632
643
|
console.log("開発を始めるには:");
|
|
633
644
|
console.log(colors.cyan(" pnpm dev"));
|
|
634
645
|
console.log("");
|
|
646
|
+
|
|
647
|
+
console.log("GitHubリポジトリのセットアップ:");
|
|
648
|
+
console.log(colors.cyan(" pnpm init:github"));
|
|
649
|
+
console.log("");
|
|
635
650
|
}
|
|
636
651
|
|
|
637
652
|
main().catch((error: unknown) => {
|