@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,479 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 環境変数設定ウィザード
|
|
3
|
+
*
|
|
4
|
+
* 対話的に環境変数を設定・更新するCLIツール
|
|
5
|
+
* 使用方法: pnpm env
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execSync } from "node:child_process";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import * as p from "@clack/prompts";
|
|
12
|
+
import {
|
|
13
|
+
type EnvironmentConfig,
|
|
14
|
+
ENVIRONMENTS,
|
|
15
|
+
parseEnvFile,
|
|
16
|
+
getPrivateKey,
|
|
17
|
+
ENV_KEYS_PATH,
|
|
18
|
+
} from "./lib/env-common.js";
|
|
19
|
+
|
|
20
|
+
const cwd = process.cwd();
|
|
21
|
+
|
|
22
|
+
// ファイルパス
|
|
23
|
+
const ENV_PATH = path.join(cwd, ".env");
|
|
24
|
+
const ENV_LOCAL_PATH = path.join(cwd, ".env.local");
|
|
25
|
+
const ENV_PERSONAL_PATH = path.join(cwd, ".env.personal");
|
|
26
|
+
const ENV_PERSONAL_EXAMPLE_PATH = path.join(cwd, ".env.personal.example");
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 環境変数ファイルに値を設定
|
|
30
|
+
*/
|
|
31
|
+
function setEnvValue(filePath: string, key: string, value: string): void {
|
|
32
|
+
let content = "";
|
|
33
|
+
if (fs.existsSync(filePath)) {
|
|
34
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const regex = new RegExp(`^${key}=.*$`, "m");
|
|
38
|
+
if (regex.test(content)) {
|
|
39
|
+
content = content.replace(regex, `${key}=${value}`);
|
|
40
|
+
} else {
|
|
41
|
+
content = content.trim() + `\n${key}=${value}\n`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fs.writeFileSync(filePath, content);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 現在の環境変数の状態を表示
|
|
49
|
+
*/
|
|
50
|
+
function showStatus(): void {
|
|
51
|
+
p.note(
|
|
52
|
+
[
|
|
53
|
+
"📁 ファイル状態:",
|
|
54
|
+
` .env : ${fs.existsSync(ENV_PATH) ? "✅ 存在" : "❌ なし"}`,
|
|
55
|
+
` .env.local : ${fs.existsSync(ENV_LOCAL_PATH) ? "✅ 存在" : "❌ なし"}`,
|
|
56
|
+
` .env.personal : ${fs.existsSync(ENV_PERSONAL_PATH) ? "✅ 存在" : "❌ なし"}`,
|
|
57
|
+
` .env.keys : ${fs.existsSync(ENV_KEYS_PATH) ? "✅ 存在" : "❌ なし"}`,
|
|
58
|
+
"",
|
|
59
|
+
"🔑 主要な環境変数:",
|
|
60
|
+
].join("\n"),
|
|
61
|
+
"環境変数の状態"
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const envPersonal = parseEnvFile(ENV_PERSONAL_PATH);
|
|
65
|
+
const env = parseEnvFile(ENV_PATH);
|
|
66
|
+
|
|
67
|
+
const checkVars = [
|
|
68
|
+
{ key: "GITHUB_TOKEN", file: ".env.personal" },
|
|
69
|
+
{ key: "DATABASE_URL", file: ".env" },
|
|
70
|
+
{ key: "AUTH_SECRET", file: ".env" },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
for (const { key, file } of checkVars) {
|
|
74
|
+
const value = file === ".env.personal" ? envPersonal[key] : env[key];
|
|
75
|
+
const status = value ? "✅ 設定済み" : "❌ 未設定";
|
|
76
|
+
const masked = value ? `${value.slice(0, 8)}...` : "-";
|
|
77
|
+
console.log(` ${key}: ${status} (${masked})`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 個人トークンを設定
|
|
83
|
+
*/
|
|
84
|
+
async function setupPersonalTokens(): Promise<void> {
|
|
85
|
+
// .env.personal がなければ作成
|
|
86
|
+
if (!fs.existsSync(ENV_PERSONAL_PATH)) {
|
|
87
|
+
if (fs.existsSync(ENV_PERSONAL_EXAMPLE_PATH)) {
|
|
88
|
+
fs.copyFileSync(ENV_PERSONAL_EXAMPLE_PATH, ENV_PERSONAL_PATH);
|
|
89
|
+
p.log.success(".env.personal を作成しました");
|
|
90
|
+
} else {
|
|
91
|
+
fs.writeFileSync(ENV_PERSONAL_PATH, "# 個人用トークン\n");
|
|
92
|
+
p.log.success(".env.personal を新規作成しました");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const current = parseEnvFile(ENV_PERSONAL_PATH);
|
|
97
|
+
|
|
98
|
+
// GITHUB_TOKEN
|
|
99
|
+
const currentGithubToken = current.GITHUB_TOKEN;
|
|
100
|
+
const githubTokenStatus = currentGithubToken
|
|
101
|
+
? `現在: ${currentGithubToken.slice(0, 8)}...`
|
|
102
|
+
: "未設定";
|
|
103
|
+
|
|
104
|
+
const updateGithubToken = await p.confirm({
|
|
105
|
+
message: `GITHUB_TOKEN を設定しますか? (${githubTokenStatus})`,
|
|
106
|
+
initialValue: !currentGithubToken,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (p.isCancel(updateGithubToken)) {
|
|
110
|
+
p.cancel("キャンセルしました");
|
|
111
|
+
process.exit(0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (updateGithubToken) {
|
|
115
|
+
p.log.info("GitHub Personal Access Token を入力してください");
|
|
116
|
+
p.log.info("取得方法: https://github.com/settings/tokens/new");
|
|
117
|
+
p.log.info("必要なスコープ: repo, read:org");
|
|
118
|
+
|
|
119
|
+
const token = await p.password({
|
|
120
|
+
message: "GITHUB_TOKEN:",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (p.isCancel(token)) {
|
|
124
|
+
p.cancel("キャンセルしました");
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (token) {
|
|
129
|
+
setEnvValue(ENV_PERSONAL_PATH, "GITHUB_TOKEN", token);
|
|
130
|
+
p.log.success("GITHUB_TOKEN を設定しました");
|
|
131
|
+
|
|
132
|
+
// トークンの検証を試みる
|
|
133
|
+
try {
|
|
134
|
+
const result = execSync(
|
|
135
|
+
`curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token ${token}" https://api.github.com/user`,
|
|
136
|
+
{ encoding: "utf-8" }
|
|
137
|
+
).trim();
|
|
138
|
+
if (result === "200") {
|
|
139
|
+
p.log.success("✅ トークンの検証に成功しました");
|
|
140
|
+
} else {
|
|
141
|
+
p.log.warn(`⚠️ トークンの検証に失敗しました (HTTP ${result})`);
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
p.log.warn("⚠️ トークンの検証をスキップしました");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 他のトークン(オプション)
|
|
150
|
+
const addMore = await p.confirm({
|
|
151
|
+
message: "他のトークンも設定しますか? (OPENAI_API_KEY, ANTHROPIC_API_KEY等)",
|
|
152
|
+
initialValue: false,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (p.isCancel(addMore)) {
|
|
156
|
+
p.cancel("キャンセルしました");
|
|
157
|
+
process.exit(0);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (addMore) {
|
|
161
|
+
const keyName = await p.text({
|
|
162
|
+
message: "環境変数名を入力:",
|
|
163
|
+
placeholder: "OPENAI_API_KEY",
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
if (p.isCancel(keyName) || !keyName) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const keyValue = await p.password({
|
|
171
|
+
message: `${keyName}:`,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
if (p.isCancel(keyValue)) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (keyValue) {
|
|
179
|
+
setEnvValue(ENV_PERSONAL_PATH, keyName, keyValue);
|
|
180
|
+
p.log.success(`${keyName} を設定しました`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
p.note(
|
|
185
|
+
"direnv allow を実行するか、新しいターミナルを開くと反映されます",
|
|
186
|
+
"💡 次のステップ"
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 環境設定を変更(汎用)
|
|
193
|
+
*/
|
|
194
|
+
async function updateEnvironmentSettings(env: EnvironmentConfig): Promise<void> {
|
|
195
|
+
const envFilePath = path.join(cwd, env.file);
|
|
196
|
+
|
|
197
|
+
if (!fs.existsSync(envFilePath)) {
|
|
198
|
+
p.log.error(`${env.file} が見つかりません`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!fs.existsSync(ENV_KEYS_PATH)) {
|
|
203
|
+
p.log.error(".env.keys が見つかりません(秘密鍵が必要です)");
|
|
204
|
+
p.log.info("チームから .env.keys を共有してもらってください");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const privateKey = getPrivateKey(env.privateKeyEnv);
|
|
209
|
+
if (!privateKey) {
|
|
210
|
+
p.log.error(`.env.keys に ${env.privateKeyEnv} が見つかりません`);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 本番環境の場合は追加確認
|
|
215
|
+
if (env.name === "production") {
|
|
216
|
+
p.log.warn("⚠️ 本番環境の設定を変更しようとしています");
|
|
217
|
+
const confirmProd = await p.confirm({
|
|
218
|
+
message: "本当に本番環境の設定を変更しますか?",
|
|
219
|
+
initialValue: false,
|
|
220
|
+
});
|
|
221
|
+
if (p.isCancel(confirmProd) || !confirmProd) {
|
|
222
|
+
p.cancel("キャンセルしました");
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
p.note(
|
|
228
|
+
[
|
|
229
|
+
`${env.description}(${env.file})を変更します。`,
|
|
230
|
+
"",
|
|
231
|
+
"手順:",
|
|
232
|
+
`1. ${env.file} を復号`,
|
|
233
|
+
"2. エディタで編集",
|
|
234
|
+
"3. 再暗号化",
|
|
235
|
+
"4. git commit & push",
|
|
236
|
+
].join("\n"),
|
|
237
|
+
`📝 ${env.description}の変更`
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const proceed = await p.confirm({
|
|
241
|
+
message: "続行しますか?",
|
|
242
|
+
initialValue: true,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (p.isCancel(proceed) || !proceed) {
|
|
246
|
+
p.cancel("キャンセルしました");
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const spinner = p.spinner();
|
|
251
|
+
const tmpPath = path.join(cwd, `${env.file}.tmp`);
|
|
252
|
+
const backupPath = path.join(cwd, `${env.file}.bak`);
|
|
253
|
+
|
|
254
|
+
// dotenvx実行時の環境変数
|
|
255
|
+
const dotenvxEnv = { ...process.env, [env.privateKeyEnv]: privateKey };
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
// 1. 復号
|
|
259
|
+
spinner.start(`${env.file} を復号中...`);
|
|
260
|
+
const decrypted = execSync(`dotenvx decrypt -f ${env.file} --stdout`, {
|
|
261
|
+
cwd,
|
|
262
|
+
encoding: "utf-8",
|
|
263
|
+
env: dotenvxEnv,
|
|
264
|
+
});
|
|
265
|
+
fs.writeFileSync(tmpPath, decrypted);
|
|
266
|
+
spinner.stop(`${env.file} を復号しました`);
|
|
267
|
+
|
|
268
|
+
// 2. エディタで開く
|
|
269
|
+
const editor = process.env.EDITOR || "vi";
|
|
270
|
+
|
|
271
|
+
// vi/vimの場合はファイル先頭に使い方ヘルプをコメントとして追加
|
|
272
|
+
const vimHelpMarker = "# === ↓↓↓ ここから下を編集(この行より上は保存時に自動削除)↓↓↓ ===";
|
|
273
|
+
if (editor === "vi" || editor === "vim") {
|
|
274
|
+
const vimHelp = `# ┌─────────────────────────────────────────────────┐
|
|
275
|
+
# │ vi/vim の基本操作 │
|
|
276
|
+
# ├─────────────────────────────────────────────────┤
|
|
277
|
+
# │ 【編集モードに入る】 │
|
|
278
|
+
# │ i ... カーソル位置から入力開始 │
|
|
279
|
+
# │ a ... カーソルの次の位置から入力開始 │
|
|
280
|
+
# │ o ... 次の行に新しい行を挿入して入力開始 │
|
|
281
|
+
# │ │
|
|
282
|
+
# │ 【編集モードから出る】 │
|
|
283
|
+
# │ Esc ... ノーマルモードに戻る │
|
|
284
|
+
# │ │
|
|
285
|
+
# │ 【保存・終了】(Escを押してから) │
|
|
286
|
+
# │ :wq ... 保存して終了 │
|
|
287
|
+
# │ :w ... 保存のみ │
|
|
288
|
+
# │ :q! ... 保存せず強制終了 │
|
|
289
|
+
# │ │
|
|
290
|
+
# │ 【カーソル移動】 │
|
|
291
|
+
# │ h j k l または 矢印キー │
|
|
292
|
+
# └─────────────────────────────────────────────────┘
|
|
293
|
+
${vimHelpMarker}
|
|
294
|
+
|
|
295
|
+
`;
|
|
296
|
+
const currentContent = fs.readFileSync(tmpPath, "utf-8");
|
|
297
|
+
fs.writeFileSync(tmpPath, vimHelp + currentContent);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
p.log.info(`${editor} で ${env.file}.tmp を開きます...`);
|
|
301
|
+
|
|
302
|
+
execSync(`${editor} ${env.file}.tmp`, {
|
|
303
|
+
cwd,
|
|
304
|
+
stdio: "inherit",
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// 3. 編集確認
|
|
308
|
+
const confirmSave = await p.confirm({
|
|
309
|
+
message: "変更を保存しますか?",
|
|
310
|
+
initialValue: true,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
if (p.isCancel(confirmSave) || !confirmSave) {
|
|
314
|
+
fs.unlinkSync(tmpPath);
|
|
315
|
+
p.log.info("変更をキャンセルしました");
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// 4. バックアップ作成 → リネーム → 再暗号化
|
|
320
|
+
spinner.start("再暗号化中...");
|
|
321
|
+
|
|
322
|
+
// vi/vimヘルプコメントを削除(ファイル先頭から)
|
|
323
|
+
if (editor === "vi" || editor === "vim") {
|
|
324
|
+
let content = fs.readFileSync(tmpPath, "utf-8");
|
|
325
|
+
const markerIndex = content.indexOf(vimHelpMarker);
|
|
326
|
+
if (markerIndex !== -1) {
|
|
327
|
+
// マーカー行の次の改行以降を残す
|
|
328
|
+
const afterMarker = markerIndex + vimHelpMarker.length;
|
|
329
|
+
content = content.substring(afterMarker).replace(/^\n+/, "");
|
|
330
|
+
}
|
|
331
|
+
fs.writeFileSync(tmpPath, content);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// 元のファイルをバックアップ
|
|
335
|
+
fs.copyFileSync(envFilePath, backupPath);
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
// テンポラリファイルを元のファイルにリネーム
|
|
339
|
+
fs.unlinkSync(envFilePath);
|
|
340
|
+
fs.renameSync(tmpPath, envFilePath);
|
|
341
|
+
|
|
342
|
+
// 暗号化
|
|
343
|
+
execSync(`dotenvx encrypt -f ${env.file}`, {
|
|
344
|
+
cwd,
|
|
345
|
+
stdio: "pipe",
|
|
346
|
+
env: dotenvxEnv,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// 成功したらバックアップを削除
|
|
350
|
+
fs.unlinkSync(backupPath);
|
|
351
|
+
spinner.stop("再暗号化が完了しました");
|
|
352
|
+
} catch (encryptError) {
|
|
353
|
+
// 暗号化失敗時はバックアップから復元
|
|
354
|
+
spinner.stop("暗号化に失敗しました");
|
|
355
|
+
if (fs.existsSync(backupPath)) {
|
|
356
|
+
fs.copyFileSync(backupPath, envFilePath);
|
|
357
|
+
fs.unlinkSync(backupPath);
|
|
358
|
+
p.log.info("元のファイルを復元しました");
|
|
359
|
+
}
|
|
360
|
+
throw encryptError;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
p.note(
|
|
364
|
+
[
|
|
365
|
+
`git add ${env.file}`,
|
|
366
|
+
`git commit -m "chore: ${env.description}設定を更新"`,
|
|
367
|
+
"git push",
|
|
368
|
+
"",
|
|
369
|
+
env.name === "local"
|
|
370
|
+
? "チームメンバーは git pull 後に pnpm dev:setup で反映"
|
|
371
|
+
: "変更はデプロイ時に反映されます",
|
|
372
|
+
].join("\n"),
|
|
373
|
+
"💡 次のステップ"
|
|
374
|
+
);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
spinner.stop("エラーが発生しました");
|
|
377
|
+
// テンポラリファイルを削除
|
|
378
|
+
if (fs.existsSync(tmpPath)) {
|
|
379
|
+
fs.unlinkSync(tmpPath);
|
|
380
|
+
}
|
|
381
|
+
// バックアップも削除
|
|
382
|
+
if (fs.existsSync(backupPath)) {
|
|
383
|
+
fs.unlinkSync(backupPath);
|
|
384
|
+
}
|
|
385
|
+
throw error;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* 環境選択メニューを表示
|
|
391
|
+
*/
|
|
392
|
+
async function selectEnvironment(): Promise<void> {
|
|
393
|
+
// 利用可能な環境をチェック
|
|
394
|
+
const availableEnvs = ENVIRONMENTS.filter((env) => {
|
|
395
|
+
const envFilePath = path.join(cwd, env.file);
|
|
396
|
+
const hasFile = fs.existsSync(envFilePath);
|
|
397
|
+
const hasKey = getPrivateKey(env.privateKeyEnv) !== null;
|
|
398
|
+
return hasFile && hasKey;
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
if (availableEnvs.length === 0) {
|
|
402
|
+
p.log.error("編集可能な環境がありません");
|
|
403
|
+
p.log.info("環境ファイルと秘密鍵が必要です");
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const envOptions = availableEnvs.map((env) => ({
|
|
408
|
+
value: env.name,
|
|
409
|
+
label: env.description,
|
|
410
|
+
hint: env.file,
|
|
411
|
+
}));
|
|
412
|
+
|
|
413
|
+
const selectedEnv = await p.select({
|
|
414
|
+
message: "編集する環境を選択してください",
|
|
415
|
+
options: envOptions,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
if (p.isCancel(selectedEnv)) {
|
|
419
|
+
p.cancel("キャンセルしました");
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const env = ENVIRONMENTS.find((e) => e.name === selectedEnv);
|
|
424
|
+
if (env) {
|
|
425
|
+
await updateEnvironmentSettings(env);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* メイン処理
|
|
431
|
+
*/
|
|
432
|
+
async function main(): Promise<void> {
|
|
433
|
+
p.intro("🔧 環境変数設定ウィザード");
|
|
434
|
+
|
|
435
|
+
const action = await p.select({
|
|
436
|
+
message: "何をしますか?",
|
|
437
|
+
options: [
|
|
438
|
+
{
|
|
439
|
+
value: "personal",
|
|
440
|
+
label: "個人トークンを設定",
|
|
441
|
+
hint: "GITHUB_TOKEN, API_KEY等",
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
value: "environment",
|
|
445
|
+
label: "環境設定を変更",
|
|
446
|
+
hint: "local, staging, production, ci 等",
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
value: "status",
|
|
450
|
+
label: "現在の状態を確認",
|
|
451
|
+
hint: "環境変数の設定状況を表示",
|
|
452
|
+
},
|
|
453
|
+
],
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
if (p.isCancel(action)) {
|
|
457
|
+
p.cancel("キャンセルしました");
|
|
458
|
+
process.exit(0);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
switch (action) {
|
|
462
|
+
case "personal":
|
|
463
|
+
await setupPersonalTokens();
|
|
464
|
+
break;
|
|
465
|
+
case "environment":
|
|
466
|
+
await selectEnvironment();
|
|
467
|
+
break;
|
|
468
|
+
case "status":
|
|
469
|
+
showStatus();
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
p.outro("✅ 完了");
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
main().catch((error: unknown) => {
|
|
477
|
+
p.log.error(`エラーが発生しました: ${error}`);
|
|
478
|
+
process.exit(1);
|
|
479
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# init.sh - 初回セットアップ(Volta/Node/pnpm導入)
|
|
4
|
+
#
|
|
5
|
+
# 使い方:
|
|
6
|
+
# ./scripts/init.sh
|
|
7
|
+
#
|
|
8
|
+
# ※ 初回のみ実行。2回目以降は不要です。
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
set -e
|
|
12
|
+
|
|
13
|
+
# 色定義
|
|
14
|
+
BLUE='\033[34m'
|
|
15
|
+
GREEN='\033[32m'
|
|
16
|
+
YELLOW='\033[33m'
|
|
17
|
+
GRAY='\033[90m'
|
|
18
|
+
NC='\033[0m'
|
|
19
|
+
|
|
20
|
+
log_success() { echo -e "${GREEN}✓${NC} $1"; }
|
|
21
|
+
log_warn() { echo -e "${YELLOW}⚠${NC} $1"; }
|
|
22
|
+
log_step() { echo -e "\n${BLUE}Step $1:${NC} $2"; }
|
|
23
|
+
|
|
24
|
+
# スクリプトのディレクトリに移動
|
|
25
|
+
cd "$(dirname "$0")/.."
|
|
26
|
+
|
|
27
|
+
echo -e "${BLUE}"
|
|
28
|
+
echo "=========================================="
|
|
29
|
+
echo " 初回セットアップ"
|
|
30
|
+
echo "=========================================="
|
|
31
|
+
echo -e "${NC}"
|
|
32
|
+
|
|
33
|
+
# Step 1: Voltaの確認とインストール
|
|
34
|
+
log_step 1 "Voltaのインストール..."
|
|
35
|
+
|
|
36
|
+
if ! command -v volta &> /dev/null; then
|
|
37
|
+
curl -fsSL https://get.volta.sh | bash -s -- --skip-setup
|
|
38
|
+
export VOLTA_HOME="$HOME/.volta"
|
|
39
|
+
export PATH="$VOLTA_HOME/bin:$PATH"
|
|
40
|
+
log_success "Voltaをインストールしました"
|
|
41
|
+
else
|
|
42
|
+
log_success "Voltaは既にインストール済み"
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Volta PATHを設定
|
|
46
|
+
export VOLTA_HOME="$HOME/.volta"
|
|
47
|
+
export PATH="$VOLTA_HOME/bin:$PATH"
|
|
48
|
+
export VOLTA_FEATURE_PNPM=1
|
|
49
|
+
|
|
50
|
+
# Step 2: シェル設定
|
|
51
|
+
log_step 2 "シェル設定..."
|
|
52
|
+
|
|
53
|
+
SHELL_NAME=$(basename "$SHELL")
|
|
54
|
+
case "$SHELL_NAME" in
|
|
55
|
+
zsh) RC_FILE="$HOME/.zshrc" ;;
|
|
56
|
+
bash) RC_FILE="$HOME/.bashrc" ;;
|
|
57
|
+
*) RC_FILE="" ;;
|
|
58
|
+
esac
|
|
59
|
+
|
|
60
|
+
if [ -n "$RC_FILE" ] && ! grep -q "VOLTA_FEATURE_PNPM" "$RC_FILE" 2>/dev/null; then
|
|
61
|
+
echo -e "\n# Volta - pnpm support\nexport VOLTA_FEATURE_PNPM=1" >> "$RC_FILE"
|
|
62
|
+
log_success "シェル設定を追加しました"
|
|
63
|
+
else
|
|
64
|
+
log_success "シェル設定は既に完了"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Step 3: Node.js/pnpmインストール
|
|
68
|
+
log_step 3 "Node.js/pnpmのインストール..."
|
|
69
|
+
|
|
70
|
+
NODE_VERSION=$(grep -o '"node": *"[^"]*"' package.json | grep -o '[0-9.]*')
|
|
71
|
+
PNPM_VERSION=$(grep -o '"pnpm": *"[^"]*"' package.json | grep -o '[0-9.]*')
|
|
72
|
+
|
|
73
|
+
volta install node@"$NODE_VERSION"
|
|
74
|
+
volta install pnpm@"$PNPM_VERSION"
|
|
75
|
+
log_success "Node.js $NODE_VERSION, pnpm $PNPM_VERSION をインストールしました"
|
|
76
|
+
|
|
77
|
+
# Step 4: 依存関係インストール
|
|
78
|
+
log_step 4 "依存関係のインストール..."
|
|
79
|
+
|
|
80
|
+
pnpm install
|
|
81
|
+
log_success "依存関係をインストールしました"
|
|
82
|
+
|
|
83
|
+
# 完了
|
|
84
|
+
echo ""
|
|
85
|
+
echo -e "${GREEN}=========================================="
|
|
86
|
+
echo -e "✅ 初回セットアップ完了!"
|
|
87
|
+
echo -e "==========================================${NC}"
|
|
88
|
+
echo ""
|
|
89
|
+
echo "次のステップ:"
|
|
90
|
+
echo -e " 1. ターミナルを再起動: ${BLUE}exec \$SHELL${NC}"
|
|
91
|
+
echo -e " 2. 環境セットアップ: ${BLUE}pnpm dev:setup${NC}"
|
|
92
|
+
echo ""
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 環境変数管理の共通処理
|
|
3
|
+
*
|
|
4
|
+
* scripts/env.ts と scripts/env-rotate-secrets.ts で共有される
|
|
5
|
+
* 共通のユーティリティ関数と型定義を提供します。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
|
|
13
|
+
/** 環境変数ファイルのパス */
|
|
14
|
+
export const ENV_KEYS_PATH = path.join(cwd, ".env.keys");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 環境設定の定義
|
|
18
|
+
*/
|
|
19
|
+
export interface EnvironmentConfig {
|
|
20
|
+
name: string;
|
|
21
|
+
file: string;
|
|
22
|
+
privateKeyEnv: string;
|
|
23
|
+
description: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* サポートされる環境の定義
|
|
28
|
+
*/
|
|
29
|
+
export const ENVIRONMENTS: EnvironmentConfig[] = [
|
|
30
|
+
{
|
|
31
|
+
name: "local",
|
|
32
|
+
file: ".env.local",
|
|
33
|
+
privateKeyEnv: "DOTENV_PRIVATE_KEY_LOCAL",
|
|
34
|
+
description: "ローカル開発環境",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "develop",
|
|
38
|
+
file: ".env.develop",
|
|
39
|
+
privateKeyEnv: "DOTENV_PRIVATE_KEY_DEVELOP",
|
|
40
|
+
description: "開発環境",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "preview",
|
|
44
|
+
file: ".env.preview",
|
|
45
|
+
privateKeyEnv: "DOTENV_PRIVATE_KEY_PREVIEW",
|
|
46
|
+
description: "プレビュー環境",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "production",
|
|
50
|
+
file: ".env.production",
|
|
51
|
+
privateKeyEnv: "DOTENV_PRIVATE_KEY_PRODUCTION",
|
|
52
|
+
description: "本番環境",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "ci",
|
|
56
|
+
file: ".env.ci",
|
|
57
|
+
privateKeyEnv: "DOTENV_PRIVATE_KEY_CI",
|
|
58
|
+
description: "CI環境",
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 環境変数ファイルを読み込んでパース
|
|
64
|
+
*
|
|
65
|
+
* @param filePath - パースする環境変数ファイルのパス
|
|
66
|
+
* @returns 環境変数のキーバリューペア
|
|
67
|
+
*/
|
|
68
|
+
export function parseEnvFile(filePath: string): Record<string, string> {
|
|
69
|
+
if (!fs.existsSync(filePath)) {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
73
|
+
const result: Record<string, string> = {};
|
|
74
|
+
|
|
75
|
+
for (const line of content.split("\n")) {
|
|
76
|
+
const trimmed = line.trim();
|
|
77
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
78
|
+
|
|
79
|
+
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
80
|
+
if (match) {
|
|
81
|
+
const key = match[1].trim();
|
|
82
|
+
let value = match[2].trim();
|
|
83
|
+
// クォートを除去
|
|
84
|
+
if (
|
|
85
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
86
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
87
|
+
) {
|
|
88
|
+
value = value.slice(1, -1);
|
|
89
|
+
}
|
|
90
|
+
result[key] = value;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* .env.keysから指定された環境の秘密鍵を取得
|
|
98
|
+
*
|
|
99
|
+
* @param privateKeyEnv - 秘密鍵の環境変数名(例: DOTENV_PRIVATE_KEY_LOCAL)
|
|
100
|
+
* @returns 秘密鍵の値。見つからない場合はnull
|
|
101
|
+
*/
|
|
102
|
+
export function getPrivateKey(privateKeyEnv: string): string | null {
|
|
103
|
+
if (!fs.existsSync(ENV_KEYS_PATH)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const keys = parseEnvFile(ENV_KEYS_PATH);
|
|
107
|
+
return keys[privateKeyEnv] || null;
|
|
108
|
+
}
|