@einja/dev-cli 0.1.40 → 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 +2 -2
- 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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
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
|
+
};
|
|
15
|
+
|
|
16
|
+
function getPlatform(): "macos" | "linux" | "windows" | "unknown" {
|
|
17
|
+
switch (process.platform) {
|
|
18
|
+
case "darwin":
|
|
19
|
+
return "macos";
|
|
20
|
+
case "linux":
|
|
21
|
+
return "linux";
|
|
22
|
+
case "win32":
|
|
23
|
+
return "windows";
|
|
24
|
+
default:
|
|
25
|
+
return "unknown";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function commandExists(cmd: string): boolean {
|
|
30
|
+
try {
|
|
31
|
+
// Use 'command -v' for POSIX compatibility, 'where' for Windows
|
|
32
|
+
const checkCmd =
|
|
33
|
+
process.platform === "win32" ? `where ${cmd}` : `command -v ${cmd}`;
|
|
34
|
+
execSync(checkCmd, { stdio: "ignore", shell: true });
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* git worktreeのメインリポジトリのパスを取得
|
|
43
|
+
* worktreeでない場合はnullを返す
|
|
44
|
+
*/
|
|
45
|
+
function getMainWorktreePath(): string | null {
|
|
46
|
+
try {
|
|
47
|
+
const result = execSync("git worktree list --porcelain", {
|
|
48
|
+
encoding: "utf-8",
|
|
49
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
50
|
+
});
|
|
51
|
+
const lines = result.trim().split("\n");
|
|
52
|
+
// 最初の "worktree /path/to/main" がメインリポジトリ
|
|
53
|
+
for (const line of lines) {
|
|
54
|
+
if (line.startsWith("worktree ")) {
|
|
55
|
+
const mainPath = line.substring("worktree ".length);
|
|
56
|
+
// 現在のディレクトリと異なる場合のみ返す(worktree環境の場合)
|
|
57
|
+
if (mainPath !== process.cwd()) {
|
|
58
|
+
return mainPath;
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* worktreeの親から.env.keysをコピー
|
|
71
|
+
* 成功した場合はtrue、失敗またはworktreeでない場合はfalseを返す
|
|
72
|
+
*/
|
|
73
|
+
function copyEnvKeysFromMainWorktree(targetPath: string): boolean {
|
|
74
|
+
const mainPath = getMainWorktreePath();
|
|
75
|
+
if (!mainPath) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const sourceEnvKeysPath = path.join(mainPath, ".env.keys");
|
|
80
|
+
if (!fs.existsSync(sourceEnvKeysPath)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
fs.copyFileSync(sourceEnvKeysPath, targetPath);
|
|
86
|
+
return true;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getShellConfig(): { rcFile: string; hookCmd: string } | null {
|
|
93
|
+
const shell = process.env.SHELL || "";
|
|
94
|
+
const shellName = path.basename(shell);
|
|
95
|
+
const home = os.homedir();
|
|
96
|
+
|
|
97
|
+
switch (shellName) {
|
|
98
|
+
case "zsh":
|
|
99
|
+
return {
|
|
100
|
+
rcFile: path.join(home, ".zshrc"),
|
|
101
|
+
hookCmd: 'eval "$(direnv hook zsh)"',
|
|
102
|
+
};
|
|
103
|
+
case "bash":
|
|
104
|
+
return {
|
|
105
|
+
rcFile: path.join(home, ".bashrc"),
|
|
106
|
+
hookCmd: 'eval "$(direnv hook bash)"',
|
|
107
|
+
};
|
|
108
|
+
case "fish":
|
|
109
|
+
return {
|
|
110
|
+
rcFile: path.join(home, ".config", "fish", "config.fish"),
|
|
111
|
+
hookCmd: "direnv hook fish | source",
|
|
112
|
+
};
|
|
113
|
+
default:
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function ensureFileExists(filePath: string): void {
|
|
119
|
+
const dir = path.dirname(filePath);
|
|
120
|
+
if (!fs.existsSync(dir)) {
|
|
121
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
122
|
+
}
|
|
123
|
+
if (!fs.existsSync(filePath)) {
|
|
124
|
+
fs.writeFileSync(filePath, "");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function appendToRcFile(rcFile: string, content: string): void {
|
|
129
|
+
ensureFileExists(rcFile);
|
|
130
|
+
fs.appendFileSync(rcFile, content);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function log(prefix: string, message: string): void {
|
|
134
|
+
console.log(`${prefix} ${message}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function succeed(message: string): void {
|
|
138
|
+
log(colors.green("✓"), message);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function warn(message: string): void {
|
|
142
|
+
log(colors.yellow("⚠"), message);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function fail(message: string): void {
|
|
146
|
+
log(colors.red("✗"), message);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function step(num: number, message: string): void {
|
|
150
|
+
console.log(`\n${colors.blue(`Step ${num}:`)} ${message}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function setupVolta(): Promise<void> {
|
|
154
|
+
const platform = getPlatform();
|
|
155
|
+
const home = os.homedir();
|
|
156
|
+
|
|
157
|
+
// 1. Voltaインストール確認
|
|
158
|
+
step(1, "Voltaの確認...");
|
|
159
|
+
|
|
160
|
+
const hasVolta = commandExists("volta");
|
|
161
|
+
|
|
162
|
+
if (!hasVolta) {
|
|
163
|
+
if (platform !== "macos") {
|
|
164
|
+
warn("Voltaがインストールされていません");
|
|
165
|
+
console.log(colors.yellow(" 手動でインストールしてください:"));
|
|
166
|
+
console.log(colors.gray(" curl https://get.volta.sh | bash"));
|
|
167
|
+
console.log(
|
|
168
|
+
colors.gray(" インストール後、再度このスクリプトを実行してください\n"),
|
|
169
|
+
);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(" Voltaをインストール中...");
|
|
174
|
+
try {
|
|
175
|
+
execSync("curl -fsSL https://get.volta.sh | bash", {
|
|
176
|
+
stdio: "inherit",
|
|
177
|
+
shell: "/bin/bash",
|
|
178
|
+
});
|
|
179
|
+
succeed("Voltaをインストールしました");
|
|
180
|
+
} catch {
|
|
181
|
+
fail("Voltaのインストールに失敗しました");
|
|
182
|
+
console.log(
|
|
183
|
+
colors.yellow(
|
|
184
|
+
" 手動でインストールしてください: curl https://get.volta.sh | bash",
|
|
185
|
+
),
|
|
186
|
+
);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
succeed("Voltaは既にインストールされています");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 2. シェル設定確認(VOLTA_FEATURE_PNPM)
|
|
194
|
+
step(2, "Voltaシェル設定の確認...");
|
|
195
|
+
|
|
196
|
+
const shellConfig = getShellConfig();
|
|
197
|
+
if (shellConfig) {
|
|
198
|
+
const { rcFile } = shellConfig;
|
|
199
|
+
const rcContent = fs.existsSync(rcFile)
|
|
200
|
+
? fs.readFileSync(rcFile, "utf-8")
|
|
201
|
+
: "";
|
|
202
|
+
|
|
203
|
+
if (!rcContent.includes("VOLTA_FEATURE_PNPM")) {
|
|
204
|
+
const voltaConfig = `
|
|
205
|
+
# Volta - pnpm support
|
|
206
|
+
export VOLTA_FEATURE_PNPM=1
|
|
207
|
+
`;
|
|
208
|
+
appendToRcFile(rcFile, voltaConfig);
|
|
209
|
+
succeed(`${rcFile} にVOLTA_FEATURE_PNPMを追加しました`);
|
|
210
|
+
} else {
|
|
211
|
+
succeed("Voltaシェル設定は既に存在します");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 3. Node.js/pnpmインストール
|
|
216
|
+
step(3, "Node.js/pnpmのインストール...");
|
|
217
|
+
|
|
218
|
+
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
219
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
220
|
+
const voltaConfig = packageJson.volta as
|
|
221
|
+
| { node?: string; pnpm?: string }
|
|
222
|
+
| undefined;
|
|
223
|
+
|
|
224
|
+
if (voltaConfig) {
|
|
225
|
+
const voltaPath = path.join(home, ".volta", "bin", "volta");
|
|
226
|
+
const voltaCmd = fs.existsSync(voltaPath) ? voltaPath : "volta";
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
if (voltaConfig.node) {
|
|
230
|
+
execSync(`${voltaCmd} install node@${voltaConfig.node}`, {
|
|
231
|
+
stdio: "inherit",
|
|
232
|
+
env: { ...process.env, VOLTA_FEATURE_PNPM: "1" },
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (voltaConfig.pnpm) {
|
|
236
|
+
execSync(`${voltaCmd} install pnpm@${voltaConfig.pnpm}`, {
|
|
237
|
+
stdio: "inherit",
|
|
238
|
+
env: { ...process.env, VOLTA_FEATURE_PNPM: "1" },
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
succeed(
|
|
242
|
+
`Node.js ${voltaConfig.node}, pnpm ${voltaConfig.pnpm} をインストールしました`,
|
|
243
|
+
);
|
|
244
|
+
} catch {
|
|
245
|
+
warn("Node.js/pnpmのインストールに失敗しました(シェル再起動後に再実行してください)");
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
warn("package.jsonにvoltaフィールドがありません");
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function promptPassword(message: string): Promise<string> {
|
|
253
|
+
const stdin = process.stdin;
|
|
254
|
+
|
|
255
|
+
// Non-TTY environment (e.g., piped input, CI) - skip interactive prompt
|
|
256
|
+
if (!stdin.isTTY) {
|
|
257
|
+
return "";
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Dynamic import for readline (ESM)
|
|
261
|
+
const readline = await import("node:readline");
|
|
262
|
+
const rl = readline.createInterface({
|
|
263
|
+
input: stdin,
|
|
264
|
+
output: process.stdout,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
return new Promise((resolve) => {
|
|
268
|
+
// Hide input for password
|
|
269
|
+
process.stdout.write(`${message} `);
|
|
270
|
+
let input = "";
|
|
271
|
+
|
|
272
|
+
const wasRaw = stdin.isRaw;
|
|
273
|
+
stdin.setRawMode(true);
|
|
274
|
+
stdin.resume();
|
|
275
|
+
stdin.setEncoding("utf8");
|
|
276
|
+
|
|
277
|
+
const onData = (char: string) => {
|
|
278
|
+
switch (char) {
|
|
279
|
+
case "\n":
|
|
280
|
+
case "\r":
|
|
281
|
+
case "\u0004": // Ctrl+D
|
|
282
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
283
|
+
stdin.pause();
|
|
284
|
+
stdin.removeListener("data", onData);
|
|
285
|
+
console.log();
|
|
286
|
+
rl.close();
|
|
287
|
+
resolve(input);
|
|
288
|
+
break;
|
|
289
|
+
case "\u0003": // Ctrl+C
|
|
290
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
291
|
+
stdin.pause();
|
|
292
|
+
stdin.removeListener("data", onData);
|
|
293
|
+
rl.close();
|
|
294
|
+
process.exit(1);
|
|
295
|
+
break;
|
|
296
|
+
case "\u007F": // Backspace
|
|
297
|
+
if (input.length > 0) {
|
|
298
|
+
input = input.slice(0, -1);
|
|
299
|
+
process.stdout.write("\b \b");
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
default:
|
|
303
|
+
input += char;
|
|
304
|
+
process.stdout.write("*");
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
stdin.on("data", onData);
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function main(): Promise<void> {
|
|
314
|
+
const cwd = process.cwd();
|
|
315
|
+
const platform = getPlatform();
|
|
316
|
+
|
|
317
|
+
console.log(colors.blue("\n🚀 開発環境セットアップを開始します...\n"));
|
|
318
|
+
|
|
319
|
+
// Step 1-3: Voltaセットアップ
|
|
320
|
+
await setupVolta();
|
|
321
|
+
|
|
322
|
+
// 4. direnvインストール確認・実行
|
|
323
|
+
step(4, "direnvの確認...");
|
|
324
|
+
|
|
325
|
+
const hasDirenv = commandExists("direnv");
|
|
326
|
+
|
|
327
|
+
if (!hasDirenv) {
|
|
328
|
+
if (platform === "macos") {
|
|
329
|
+
console.log(" direnvをインストール中...");
|
|
330
|
+
const result = spawnSync("brew", ["install", "direnv"], {
|
|
331
|
+
stdio: "inherit",
|
|
332
|
+
});
|
|
333
|
+
if (result.error || result.status !== 0) {
|
|
334
|
+
fail("direnvのインストールに失敗しました");
|
|
335
|
+
console.log(
|
|
336
|
+
colors.yellow(" 手動でインストールしてください: brew install direnv"),
|
|
337
|
+
);
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
succeed("direnvをインストールしました");
|
|
341
|
+
} else {
|
|
342
|
+
warn("direnvがインストールされていません");
|
|
343
|
+
console.log(colors.yellow(" 手動でインストールしてください:"));
|
|
344
|
+
console.log(colors.gray(" Linux: sudo apt install direnv"));
|
|
345
|
+
console.log(
|
|
346
|
+
colors.gray(" 詳細: https://direnv.net/docs/installation.html"),
|
|
347
|
+
);
|
|
348
|
+
console.log(
|
|
349
|
+
colors.gray(" インストール後、再度このスクリプトを実行してください\n"),
|
|
350
|
+
);
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
succeed("direnvは既にインストールされています");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// 5. シェル設定
|
|
358
|
+
step(5, "シェル設定の確認...");
|
|
359
|
+
|
|
360
|
+
const shellConfig = getShellConfig();
|
|
361
|
+
|
|
362
|
+
if (shellConfig) {
|
|
363
|
+
const { rcFile, hookCmd } = shellConfig;
|
|
364
|
+
const rcContent = fs.existsSync(rcFile)
|
|
365
|
+
? fs.readFileSync(rcFile, "utf-8")
|
|
366
|
+
: "";
|
|
367
|
+
|
|
368
|
+
if (!rcContent.includes("direnv hook")) {
|
|
369
|
+
appendToRcFile(rcFile, `\n# direnv\n${hookCmd}\n`);
|
|
370
|
+
succeed(`${rcFile} に設定を追加しました`);
|
|
371
|
+
} else {
|
|
372
|
+
succeed("シェル設定は既に存在します");
|
|
373
|
+
}
|
|
374
|
+
} else {
|
|
375
|
+
warn("未対応のシェルです。手動でdirenvフックを設定してください");
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// 6. dotenvxインストール
|
|
379
|
+
step(6, "dotenvxの確認...");
|
|
380
|
+
|
|
381
|
+
const hasDotenvx = commandExists("dotenvx");
|
|
382
|
+
|
|
383
|
+
if (!hasDotenvx) {
|
|
384
|
+
if (platform === "macos" || platform === "linux") {
|
|
385
|
+
console.log(" dotenvxをインストール中...");
|
|
386
|
+
try {
|
|
387
|
+
// 公式インストールスクリプトを使用
|
|
388
|
+
execSync("curl -sfS https://dotenvx.sh/install.sh | sh", {
|
|
389
|
+
stdio: "inherit",
|
|
390
|
+
shell: "/bin/bash",
|
|
391
|
+
});
|
|
392
|
+
succeed("dotenvxをインストールしました");
|
|
393
|
+
} catch {
|
|
394
|
+
// フォールバック: npm経由でインストール
|
|
395
|
+
try {
|
|
396
|
+
execSync("npm install -g @dotenvx/dotenvx", { stdio: "inherit" });
|
|
397
|
+
succeed("dotenvxをnpm経由でインストールしました");
|
|
398
|
+
} catch {
|
|
399
|
+
warn("dotenvxのインストールに失敗しました");
|
|
400
|
+
console.log(colors.yellow(" 手動でインストールしてください:"));
|
|
401
|
+
console.log(
|
|
402
|
+
colors.gray(" curl -sfS https://dotenvx.sh/install.sh | sh"),
|
|
403
|
+
);
|
|
404
|
+
console.log(
|
|
405
|
+
colors.gray(" または: npm install -g @dotenvx/dotenvx"),
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
warn("dotenvxがインストールされていません");
|
|
411
|
+
console.log(colors.yellow(" 手動でインストールしてください:"));
|
|
412
|
+
console.log(
|
|
413
|
+
colors.gray(" curl -sfS https://dotenvx.sh/install.sh | sh"),
|
|
414
|
+
);
|
|
415
|
+
console.log(colors.gray(" または: npm install -g @dotenvx/dotenvx"));
|
|
416
|
+
}
|
|
417
|
+
} else {
|
|
418
|
+
succeed("dotenvxは既にインストールされています");
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// 7. .env.local復号 → .env作成
|
|
422
|
+
step(7, ".envファイルの作成(.env.localから復号)...");
|
|
423
|
+
|
|
424
|
+
const envPath = path.join(cwd, ".env");
|
|
425
|
+
const envLocalPath = path.join(cwd, ".env.local");
|
|
426
|
+
const envKeysPath = path.join(cwd, ".env.keys");
|
|
427
|
+
const envExamplePath = path.join(cwd, ".env.example");
|
|
428
|
+
|
|
429
|
+
if (!fs.existsSync(envPath)) {
|
|
430
|
+
// .env.local(暗号化済み)から復号して.envを作成
|
|
431
|
+
if (fs.existsSync(envLocalPath) && fs.existsSync(envKeysPath)) {
|
|
432
|
+
try {
|
|
433
|
+
// .env.keysから秘密鍵を読み込む
|
|
434
|
+
const keysContent = fs.readFileSync(envKeysPath, "utf-8");
|
|
435
|
+
const localKeyMatch = keysContent.match(
|
|
436
|
+
/DOTENV_PRIVATE_KEY_LOCAL=["']?([^"'\n]+)["']?/,
|
|
437
|
+
);
|
|
438
|
+
if (!localKeyMatch) {
|
|
439
|
+
throw new Error("DOTENV_PRIVATE_KEY_LOCAL が .env.keys に見つかりません");
|
|
440
|
+
}
|
|
441
|
+
const privateKey = localKeyMatch[1];
|
|
442
|
+
|
|
443
|
+
// dotenvxで復号
|
|
444
|
+
execSync("npx dotenvx decrypt -f .env.local --stdout > .env", {
|
|
445
|
+
shell: true,
|
|
446
|
+
cwd,
|
|
447
|
+
stdio: "pipe",
|
|
448
|
+
env: { ...process.env, DOTENV_PRIVATE_KEY_LOCAL: privateKey },
|
|
449
|
+
});
|
|
450
|
+
succeed(".env.local を復号して .env を作成しました");
|
|
451
|
+
} catch (error) {
|
|
452
|
+
fail(".env.local の復号に失敗しました");
|
|
453
|
+
console.log(colors.yellow(" 秘密鍵が正しいか確認してください"));
|
|
454
|
+
console.log(
|
|
455
|
+
colors.gray(" .env.keys はチームから共有を受けてください"),
|
|
456
|
+
);
|
|
457
|
+
// フォールバック: .env.exampleからコピー
|
|
458
|
+
if (fs.existsSync(envExamplePath)) {
|
|
459
|
+
fs.copyFileSync(envExamplePath, envPath);
|
|
460
|
+
warn("フォールバック: .env.example から .env を作成しました");
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
} else if (!fs.existsSync(envKeysPath)) {
|
|
464
|
+
// .env.keysがない場合、まずworktreeの親からコピーを試みる
|
|
465
|
+
if (copyEnvKeysFromMainWorktree(envKeysPath)) {
|
|
466
|
+
succeed("worktreeのメインリポジトリから .env.keys をコピーしました");
|
|
467
|
+
// コピー成功後、復号を再試行
|
|
468
|
+
try {
|
|
469
|
+
const keysContent = fs.readFileSync(envKeysPath, "utf-8");
|
|
470
|
+
const localKeyMatch = keysContent.match(
|
|
471
|
+
/DOTENV_PRIVATE_KEY_LOCAL=["']?([^"'\n]+)["']?/,
|
|
472
|
+
);
|
|
473
|
+
if (localKeyMatch) {
|
|
474
|
+
const privateKey = localKeyMatch[1];
|
|
475
|
+
execSync("npx dotenvx decrypt -f .env.local --stdout > .env", {
|
|
476
|
+
shell: true,
|
|
477
|
+
cwd,
|
|
478
|
+
stdio: "pipe",
|
|
479
|
+
env: { ...process.env, DOTENV_PRIVATE_KEY_LOCAL: privateKey },
|
|
480
|
+
});
|
|
481
|
+
succeed(".env.local を復号して .env を作成しました");
|
|
482
|
+
} else {
|
|
483
|
+
throw new Error("DOTENV_PRIVATE_KEY_LOCAL が見つかりません");
|
|
484
|
+
}
|
|
485
|
+
} catch {
|
|
486
|
+
warn(".env.keys のコピー後、復号に失敗しました");
|
|
487
|
+
if (fs.existsSync(envExamplePath)) {
|
|
488
|
+
fs.copyFileSync(envExamplePath, envPath);
|
|
489
|
+
warn("フォールバック: .env.example から .env を作成しました");
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
// worktreeからのコピーも失敗した場合
|
|
494
|
+
warn(".env.keys が見つかりません(秘密鍵ファイル)");
|
|
495
|
+
console.log(
|
|
496
|
+
colors.yellow(" チームから .env.keys を共有してもらってください"),
|
|
497
|
+
);
|
|
498
|
+
console.log(colors.gray(" または 1Password 等で共有されています"));
|
|
499
|
+
// フォールバック: .env.exampleからコピー
|
|
500
|
+
if (fs.existsSync(envExamplePath)) {
|
|
501
|
+
fs.copyFileSync(envExamplePath, envPath);
|
|
502
|
+
warn("フォールバック: .env.example から .env を作成しました");
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
} else if (!fs.existsSync(envLocalPath)) {
|
|
506
|
+
// .env.localがない場合(古いリポジトリ)
|
|
507
|
+
if (fs.existsSync(envExamplePath)) {
|
|
508
|
+
fs.copyFileSync(envExamplePath, envPath);
|
|
509
|
+
succeed(".env.example から .env を作成しました(レガシーモード)");
|
|
510
|
+
} else {
|
|
511
|
+
fail(".env.example が見つかりません");
|
|
512
|
+
process.exit(1);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
} else {
|
|
516
|
+
succeed(".env は既に存在します");
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// 8. .env.personal作成 & GITHUB_TOKEN設定(対話的)
|
|
520
|
+
step(8, "個人用トークン設定(.env.personal)...");
|
|
521
|
+
|
|
522
|
+
const envPersonalPath = path.join(cwd, ".env.personal");
|
|
523
|
+
const envPersonalExamplePath = path.join(cwd, ".env.personal.example");
|
|
524
|
+
|
|
525
|
+
// .env.personalがなければテンプレートからコピー
|
|
526
|
+
if (!fs.existsSync(envPersonalPath)) {
|
|
527
|
+
if (fs.existsSync(envPersonalExamplePath)) {
|
|
528
|
+
fs.copyFileSync(envPersonalExamplePath, envPersonalPath);
|
|
529
|
+
succeed(".env.personal.example から .env.personal を作成しました");
|
|
530
|
+
} else {
|
|
531
|
+
// テンプレートがない場合は最小限の内容で作成
|
|
532
|
+
fs.writeFileSync(
|
|
533
|
+
envPersonalPath,
|
|
534
|
+
"# 個人用トークン\nGITHUB_TOKEN=\n",
|
|
535
|
+
);
|
|
536
|
+
succeed(".env.personal を新規作成しました");
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// GITHUB_TOKENの確認
|
|
541
|
+
const envPersonalContent = fs.readFileSync(envPersonalPath, "utf-8");
|
|
542
|
+
const hasGithubToken =
|
|
543
|
+
envPersonalContent.includes("GITHUB_TOKEN=") &&
|
|
544
|
+
!envPersonalContent.match(/GITHUB_TOKEN=\s*$/m) &&
|
|
545
|
+
!envPersonalContent.match(/GITHUB_TOKEN=\s*\n/);
|
|
546
|
+
|
|
547
|
+
if (!hasGithubToken) {
|
|
548
|
+
console.log(colors.yellow("\n ⚠️ GITHUB_TOKENが設定されていません"));
|
|
549
|
+
console.log(colors.gray(" GitHub Personal Access Token が必要です"));
|
|
550
|
+
console.log(
|
|
551
|
+
colors.gray(" 取得方法: https://github.com/settings/tokens/new"),
|
|
552
|
+
);
|
|
553
|
+
console.log(colors.gray(" 必要なスコープ: repo, read:org\n"));
|
|
554
|
+
|
|
555
|
+
const token = await promptPassword(
|
|
556
|
+
" GITHUB_TOKENを入力してください(スキップはEnter):",
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
const trimmedToken = token.trim();
|
|
560
|
+
if (trimmedToken) {
|
|
561
|
+
let updatedContent: string;
|
|
562
|
+
if (envPersonalContent.includes("GITHUB_TOKEN=")) {
|
|
563
|
+
// Replace existing line
|
|
564
|
+
updatedContent = envPersonalContent.replace(
|
|
565
|
+
/GITHUB_TOKEN=.*/,
|
|
566
|
+
`GITHUB_TOKEN=${trimmedToken}`,
|
|
567
|
+
);
|
|
568
|
+
} else {
|
|
569
|
+
// Append new line
|
|
570
|
+
updatedContent = envPersonalContent.endsWith("\n")
|
|
571
|
+
? `${envPersonalContent}GITHUB_TOKEN=${trimmedToken}\n`
|
|
572
|
+
: `${envPersonalContent}\nGITHUB_TOKEN=${trimmedToken}\n`;
|
|
573
|
+
}
|
|
574
|
+
fs.writeFileSync(envPersonalPath, updatedContent);
|
|
575
|
+
succeed("GITHUB_TOKENを .env.personal に設定しました");
|
|
576
|
+
} else {
|
|
577
|
+
console.log(
|
|
578
|
+
colors.yellow(
|
|
579
|
+
" スキップしました。後で .env.personal を編集してください",
|
|
580
|
+
),
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
} else {
|
|
584
|
+
succeed("GITHUB_TOKENは既に設定されています");
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// 9. direnv有効化
|
|
588
|
+
step(9, "direnvの有効化...");
|
|
589
|
+
try {
|
|
590
|
+
execSync("direnv allow", { cwd, stdio: "ignore" });
|
|
591
|
+
succeed("direnvを有効化しました");
|
|
592
|
+
} catch {
|
|
593
|
+
warn("direnv allowに失敗(シェル再起動後に再実行してください)");
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// 10. データベース起動
|
|
597
|
+
step(10, "データベース起動...");
|
|
598
|
+
const hasDocker = commandExists("docker");
|
|
599
|
+
|
|
600
|
+
if (hasDocker) {
|
|
601
|
+
try {
|
|
602
|
+
execSync("docker-compose up -d postgres", { cwd, stdio: "inherit" });
|
|
603
|
+
succeed("PostgreSQLを起動しました");
|
|
604
|
+
|
|
605
|
+
// 起動を待つ
|
|
606
|
+
console.log(colors.gray(" データベースの起動を待機中..."));
|
|
607
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
608
|
+
|
|
609
|
+
// 11. Prismaセットアップ
|
|
610
|
+
step(11, "データベース初期化...");
|
|
611
|
+
execSync("pnpm db:generate", { cwd, stdio: "inherit" });
|
|
612
|
+
execSync("pnpm db:push", { cwd, stdio: "inherit" });
|
|
613
|
+
succeed("データベースを初期化しました");
|
|
614
|
+
} catch {
|
|
615
|
+
warn("データベースの起動または初期化に失敗しました");
|
|
616
|
+
console.log(colors.gray(" 手動で実行してください:"));
|
|
617
|
+
console.log(colors.gray(" docker-compose up -d postgres"));
|
|
618
|
+
console.log(colors.gray(" pnpm db:generate && pnpm db:push"));
|
|
619
|
+
}
|
|
620
|
+
} else {
|
|
621
|
+
warn("Dockerがインストールされていません");
|
|
622
|
+
console.log(colors.gray(" Dockerをインストール後、以下を実行してください:"));
|
|
623
|
+
console.log(colors.gray(" docker-compose up -d postgres"));
|
|
624
|
+
console.log(colors.gray(" pnpm db:generate && pnpm db:push"));
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// 完了
|
|
628
|
+
console.log(colors.green("\n=========================================="));
|
|
629
|
+
console.log(colors.green("✅ 環境セットアップが完了しました!"));
|
|
630
|
+
console.log(colors.green("==========================================\n"));
|
|
631
|
+
|
|
632
|
+
console.log("開発を始めるには:");
|
|
633
|
+
console.log(colors.cyan(" pnpm dev"));
|
|
634
|
+
console.log("");
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
main().catch((error: unknown) => {
|
|
638
|
+
console.error(colors.red("エラーが発生しました:"), error);
|
|
639
|
+
process.exit(1);
|
|
640
|
+
});
|