create-einja-app 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +34 -1
  2. package/dist/cli.js +92 -80
  3. package/dist/cli.js.map +1 -1
  4. package/package.json +1 -1
  5. package/templates/default/.changeset/config.json +11 -0
  6. package/templates/default/.claude/hooks/einja/plan-mode-skill-loader.sh +27 -0
  7. package/templates/default/.claude/settings.json +29 -1
  8. package/templates/default/.claude/skills/cli-package-specs/SKILL.md +247 -0
  9. package/templates/default/.einja-sync.json +1 -1
  10. package/templates/default/.env.personal.example +6 -2
  11. package/templates/default/.envrc +5 -0
  12. package/templates/default/.github/release.yml +10 -0
  13. package/templates/default/.github/workflows/changeset-status.yml +60 -0
  14. package/templates/default/.github/workflows/deploy-pr-preview.yml +23 -24
  15. package/templates/default/.github/workflows/deploy-stable-branches.yml +336 -100
  16. package/templates/default/.mcp.json +2 -12
  17. package/templates/default/.serena/project.yml +7 -0
  18. package/templates/default/CLAUDE.md +61 -10
  19. package/templates/default/README.md +22 -10
  20. package/templates/default/apps/admin/package.json +1 -1
  21. package/templates/default/apps/admin/tsconfig.json +2 -1
  22. package/templates/default/apps/web/package.json +1 -1
  23. package/templates/default/apps/web/tsconfig.json +2 -1
  24. package/templates/default/docs/plans/.gitkeep +0 -0
  25. package/templates/default/docs/plans/agile-munching-knuth.md +161 -0
  26. package/templates/default/docs/plans/agile-riding-nova.md +158 -0
  27. package/templates/default/docs/plans/agile-wibbling-dusk.md +91 -0
  28. package/templates/default/docs/plans/ancient-greeting-flamingo-agent-a87e67c.md +221 -0
  29. package/templates/default/docs/plans/ancient-greeting-flamingo-agent-ab73a1c.md +107 -0
  30. package/templates/default/docs/plans/ancient-greeting-flamingo.md +120 -0
  31. package/templates/default/docs/plans/ancient-watching-otter.md +152 -0
  32. package/templates/default/docs/plans/bright-sauteeing-bumblebee.md +30 -0
  33. package/templates/default/docs/plans/bright-stargazing-dawn.md +87 -0
  34. package/templates/default/docs/plans/calm-stirring-bonbon.md +196 -0
  35. package/templates/default/docs/plans/calm-watching-widget.md +111 -0
  36. package/templates/default/docs/plans/cheerful-wiggling-ullman.md +164 -0
  37. package/templates/default/docs/plans/compiled-humming-cherny.md +94 -0
  38. package/templates/default/docs/plans/composed-doodling-mountain.md +362 -0
  39. package/templates/default/docs/plans/dapper-launching-lynx.md +81 -0
  40. package/templates/default/docs/plans/dazzling-foraging-cascade.md +32 -0
  41. package/templates/default/docs/plans/effervescent-munching-kite-agent-ac08baf.md +672 -0
  42. package/templates/default/docs/plans/effervescent-munching-kite-agent-aecc373.md +442 -0
  43. package/templates/default/docs/plans/effervescent-munching-kite.md +263 -0
  44. package/templates/default/docs/plans/enchanted-wiggling-ember-agent-a5befd57d0ca4c7c7.md +177 -0
  45. package/templates/default/docs/plans/enchanted-wiggling-ember.md +170 -0
  46. package/templates/default/docs/plans/federated-questing-kahan.md +47 -0
  47. package/templates/default/docs/plans/fix-orphan-cleaner-review.md +25 -0
  48. package/templates/default/docs/plans/fix-sync-template-variables.md +162 -0
  49. package/templates/default/docs/plans/flickering-pondering-hearth.md +26 -0
  50. package/templates/default/docs/plans/fluttering-snuggling-sprout.md +172 -0
  51. package/templates/default/docs/plans/generic-sleeping-snowglobe-agent-a41d8da.md +179 -0
  52. package/templates/default/docs/plans/generic-sleeping-snowglobe.md +108 -0
  53. package/templates/default/docs/plans/generic-snuggling-pudding.md +57 -0
  54. package/templates/default/docs/plans/glimmering-giggling-sedgewick.md +126 -0
  55. package/templates/default/docs/plans/glittery-swimming-bachman.md +78 -0
  56. package/templates/default/docs/plans/happy-watching-toast.md +56 -0
  57. package/templates/default/docs/plans/harmonic-strolling-nebula.md +210 -0
  58. package/templates/default/docs/plans/idempotent-wiggling-cherny.md +122 -0
  59. package/templates/default/docs/plans/import-alias-refactor.md +75 -0
  60. package/templates/default/docs/plans/lazy-percolating-sloth-agent-abda679.md +346 -0
  61. package/templates/default/docs/plans/lazy-percolating-sloth.md +151 -0
  62. package/templates/default/docs/plans/linked-greeting-llama-agent-a7a6e5b.md +345 -0
  63. package/templates/default/docs/plans/linked-greeting-llama.md +467 -0
  64. package/templates/default/docs/plans/lovely-bubbling-rose.md +80 -0
  65. package/templates/default/docs/plans/optimized-watching-sprout.md +149 -0
  66. package/templates/default/docs/plans/peaceful-beaming-toast-agent-a292da6.md +288 -0
  67. package/templates/default/docs/plans/peaceful-beaming-toast-agent-a819699.md +366 -0
  68. package/templates/default/docs/plans/peaceful-beaming-toast-agent-ac11de2.md +474 -0
  69. package/templates/default/docs/plans/peaceful-beaming-toast.md +345 -0
  70. package/templates/default/docs/plans/purrfect-spinning-hickey-agent-ae6194c.md +300 -0
  71. package/templates/default/docs/plans/purrfect-spinning-hickey-agent-ae6900e.md +444 -0
  72. package/templates/default/docs/plans/purrfect-spinning-hickey.md +361 -0
  73. package/templates/default/docs/plans/recursive-fluttering-mitten.md +176 -0
  74. package/templates/default/docs/plans/recursive-kindling-lemon-agent-a42199e.md +186 -0
  75. package/templates/default/docs/plans/recursive-kindling-lemon.md +36 -0
  76. package/templates/default/docs/plans/seed-migration-tests.md +47 -0
  77. package/templates/default/docs/plans/sprightly-leaping-manatee.md +224 -0
  78. package/templates/default/docs/plans/stateful-wishing-lerdorf.md +161 -0
  79. package/templates/default/docs/plans/streamed-purring-wreath.md +40 -0
  80. package/templates/default/docs/plans/synthetic-percolating-pearl.md +101 -0
  81. package/templates/default/docs/plans/todo-create-einja-app-ux-fix.md +16 -0
  82. package/templates/default/docs/plans/todo-direnv-hang-fix.md +12 -0
  83. package/templates/default/docs/plans/todo-fix-sync-template-variables.md +21 -0
  84. package/templates/default/docs/plans/todo-github-actions-release-workflow.md +34 -0
  85. package/templates/default/docs/plans/todo-issue-spec-rename.md +24 -0
  86. package/templates/default/docs/plans/todo-phase4-marker-update.md +39 -0
  87. package/templates/default/docs/plans/todo-skill-creator-sync.md +23 -0
  88. package/templates/default/docs/plans/todo-skill-creator-upgrade.md +18 -0
  89. package/templates/default/docs/plans/typed-snuggling-parnas-agent-a6f6391.md +476 -0
  90. package/templates/default/docs/plans/typed-snuggling-parnas-agent-adb678b.md +144 -0
  91. package/templates/default/docs/plans/typed-snuggling-parnas.md +84 -0
  92. package/templates/default/docs/plans/velvety-chasing-spark.md +28 -0
  93. package/templates/default/docs/plans/warm-hopping-lighthouse-agent-a30aa4f.md +534 -0
  94. package/templates/default/docs/plans/warm-hopping-lighthouse-agent-a57a278.md +508 -0
  95. package/templates/default/docs/plans/warm-hopping-lighthouse-agent-a90b809.md +421 -0
  96. package/templates/default/docs/plans/warm-hopping-lighthouse.md +199 -0
  97. package/templates/default/docs/plans/wondrous-strolling-crystal-agent-a0615fc.md +215 -0
  98. package/templates/default/docs/plans/wondrous-strolling-crystal.md +182 -0
  99. package/templates/default/docs/plans/zesty-roaming-steele.md +74 -0
  100. package/templates/default/docs/verification-test.md +2 -0
  101. package/templates/default/gitignore +9 -1
  102. package/templates/default/package.json +6 -2
  103. package/templates/default/packages/admin-ui/package.json +1 -1
  104. package/templates/default/packages/server-core/tsconfig.json +6 -1
  105. package/templates/default/pnpm-lock.yaml +823 -57
  106. package/templates/default/scripts/ensure-serena.sh +75 -0
  107. package/templates/default/scripts/env-rotate-secrets.ts +66 -6
  108. package/templates/default/scripts/init-github.ts +363 -0
  109. package/templates/default/scripts/init.sh +11 -5
  110. package/templates/default/scripts/lib/worktree-config.ts +64 -0
  111. package/templates/default/scripts/setup-dev.ts +16 -1
  112. package/templates/default/scripts/stop-serena.sh +25 -0
  113. package/templates/default/scripts/worktree/dev.ts +2 -2
  114. package/templates/default/.claude/skills/create-einja-app-release/SKILL.md +0 -186
  115. package/templates/default/.claude/skills/dev-cli-release/SKILL.md +0 -173
  116. package/templates/default/.cursor/commands/spec-create.md +0 -227
  117. package/templates/default/.cursor/commands/task-exec.md +0 -287
  118. package/templates/default/.cursor/commands/update-docs-by-task-specs.md +0 -448
  119. /package/templates/default/scripts/{cli-template-update.ts → _cli-template-update.ts} +0 -0
  120. /package/templates/default/scripts/{template-update.ts → _template-update.ts} +0 -0
@@ -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 ! nc -z 127.0.0.1 "$_port" > /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 nc -z 127.0.0.1 "$_port" > /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
@@ -285,9 +285,60 @@ function showNextSteps(envs: EnvironmentConfig[]): void {
285
285
  }
286
286
 
287
287
  /**
288
- * メイン処理
288
+ * CLIフラグをパース
289
289
  */
290
- async function main(): Promise<void> {
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
- main().catch((error: unknown) => {
334
- p.log.error(`エラーが発生しました: ${error}`);
335
- process.exit(1);
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
- pnpm install
81
- log_success "依存関係をインストールしました"
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. 環境セットアップ: ${BLUE}pnpm dev:setup${NC}"
96
+ echo -e " 2. 依存関係インストール: ${BLUE}pnpm install${NC}"
97
+ echo -e " 3. 環境セットアップ: ${BLUE}pnpm dev:setup${NC}"
92
98
  echo ""
@@ -0,0 +1,64 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export interface AppConfig {
5
+ id: string;
6
+ portRangeStart: number;
7
+ rangeSize: number;
8
+ }
9
+
10
+ export interface PostgresConfig {
11
+ port: number;
12
+ containerName: string;
13
+ }
14
+
15
+ export interface WorktreeConfig {
16
+ schemaVersion: number;
17
+ postgres: PostgresConfig;
18
+ apps: AppConfig[];
19
+ }
20
+
21
+ const defaultWorktreeConfig: WorktreeConfig = {
22
+ schemaVersion: 1,
23
+ postgres: { port: 25432, containerName: "einja-management-postgres" },
24
+ apps: [{ id: "web", portRangeStart: 3000, rangeSize: 1000 }],
25
+ };
26
+
27
+ function findProjectRoot(startDir: string = process.cwd()): string | null {
28
+ let currentDir = startDir;
29
+ while (currentDir !== path.dirname(currentDir)) {
30
+ if (fs.existsSync(path.join(currentDir, "package.json"))) {
31
+ return currentDir;
32
+ }
33
+ currentDir = path.dirname(currentDir);
34
+ }
35
+ return null;
36
+ }
37
+
38
+ export function loadWorktreeConfig(projectRoot?: string): WorktreeConfig {
39
+ const root = projectRoot ?? findProjectRoot();
40
+ if (!root) return defaultWorktreeConfig;
41
+
42
+ const configPath = path.join(root, "worktree.config.json");
43
+ if (!fs.existsSync(configPath)) return defaultWorktreeConfig;
44
+
45
+ try {
46
+ const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
47
+ return {
48
+ schemaVersion: raw.schemaVersion ?? 1,
49
+ postgres: {
50
+ port: typeof raw.postgres?.port === "number" ? raw.postgres.port : 25432,
51
+ containerName: typeof raw.postgres?.containerName === "string"
52
+ ? raw.postgres.containerName : "einja-management-postgres",
53
+ },
54
+ apps: Array.isArray(raw.apps)
55
+ ? raw.apps.filter((a: unknown) =>
56
+ typeof a === "object" && a !== null && "id" in a && "portRangeStart" in a
57
+ )
58
+ : defaultWorktreeConfig.apps,
59
+ };
60
+ } catch {
61
+ console.warn("worktree.config.json の読み込みに失敗。デフォルト設定を使用します。");
62
+ return defaultWorktreeConfig;
63
+ }
64
+ }
@@ -619,7 +619,18 @@ async function main(): Promise<void> {
619
619
  }
620
620
  } else {
621
621
  warn("Dockerがインストールされていません");
622
- console.log(colors.gray(" Dockerをインストール後、以下を実行してください:"));
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) => {
@@ -0,0 +1,25 @@
1
+ #!/bin/bash
2
+ PORT_FILE=".serena-port"
3
+ if [ -f "$PORT_FILE" ]; then
4
+ read -r PORT PID < "$PORT_FILE"
5
+ if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
6
+ kill "$PID"
7
+ # SIGTERM後、最大5秒待機
8
+ for _i in $(seq 1 10); do
9
+ if ! kill -0 "$PID" 2>/dev/null; then
10
+ echo "Serena stopped (PID: $PID, port: $PORT)"
11
+ rm -f "$PORT_FILE"
12
+ exit 0
13
+ fi
14
+ sleep 0.5
15
+ done
16
+ # 応答なし → 強制終了
17
+ kill -9 "$PID" 2>/dev/null
18
+ echo "Serena force-killed (PID: $PID, port: $PORT)"
19
+ else
20
+ echo "Serena process not running (PID: $PID)"
21
+ fi
22
+ rm -f "$PORT_FILE"
23
+ else
24
+ echo "Serena not running (.serena-port not found)"
25
+ fi
@@ -11,8 +11,8 @@ import crypto from "node:crypto";
11
11
  import fs from "node:fs";
12
12
  import path from "node:path";
13
13
  import readline from "node:readline";
14
- import type { AppConfig, WorktreeConfig } from "../../packages/config/src/worktree-config.js";
15
- import { loadWorktreeConfig } from "../../packages/config/src/worktree-config-loader.js";
14
+ import type { AppConfig, WorktreeConfig } from "../lib/worktree-config.js";
15
+ import { loadWorktreeConfig } from "../lib/worktree-config.js";
16
16
 
17
17
  /** 設定を保持するグローバル変数 */
18
18
  let config: WorktreeConfig;