@sk8metal/michi-cli 0.3.0 → 0.4.0
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/CHANGELOG.md +43 -0
- package/dist/scripts/config-global.d.ts +10 -0
- package/dist/scripts/config-global.d.ts.map +1 -0
- package/dist/scripts/config-global.js +111 -0
- package/dist/scripts/config-global.js.map +1 -0
- package/dist/scripts/confluence-sync.d.ts +22 -4
- package/dist/scripts/confluence-sync.d.ts.map +1 -1
- package/dist/scripts/confluence-sync.js +22 -12
- package/dist/scripts/confluence-sync.js.map +1 -1
- package/dist/scripts/jira-sync.d.ts.map +1 -1
- package/dist/scripts/jira-sync.js +201 -167
- package/dist/scripts/jira-sync.js.map +1 -1
- package/dist/scripts/list-projects.js.map +1 -1
- package/dist/scripts/multi-project-estimate.js.map +1 -1
- package/dist/scripts/phase-runner.d.ts +1 -1
- package/dist/scripts/phase-runner.d.ts.map +1 -1
- package/dist/scripts/phase-runner.js +295 -522
- package/dist/scripts/phase-runner.js.map +1 -1
- package/dist/scripts/pre-flight-check.d.ts.map +1 -1
- package/dist/scripts/pre-flight-check.js +10 -6
- package/dist/scripts/pre-flight-check.js.map +1 -1
- package/dist/scripts/resource-dashboard.js.map +1 -1
- package/dist/scripts/spec-impl-workflow.js +1 -1
- package/dist/scripts/spec-impl-workflow.js.map +1 -1
- package/dist/scripts/template/renderer.d.ts +1 -1
- package/dist/scripts/template/renderer.d.ts.map +1 -1
- package/dist/scripts/test-interactive.d.ts.map +1 -1
- package/dist/scripts/test-interactive.js +0 -15
- package/dist/scripts/test-interactive.js.map +1 -1
- package/dist/scripts/test-new-features.js +6 -3
- package/dist/scripts/test-new-features.js.map +1 -1
- package/dist/scripts/test-spec-generator.d.ts.map +1 -1
- package/dist/scripts/test-spec-generator.js +1 -2
- package/dist/scripts/test-spec-generator.js.map +1 -1
- package/dist/scripts/utils/config-loader.d.ts +7 -2
- package/dist/scripts/utils/config-loader.d.ts.map +1 -1
- package/dist/scripts/utils/config-loader.js +79 -8
- package/dist/scripts/utils/config-loader.js.map +1 -1
- package/dist/scripts/utils/config-sections.d.ts +54 -0
- package/dist/scripts/utils/config-sections.d.ts.map +1 -0
- package/dist/scripts/utils/config-sections.js +178 -0
- package/dist/scripts/utils/config-sections.js.map +1 -0
- package/dist/scripts/utils/config-validator.d.ts +4 -0
- package/dist/scripts/utils/config-validator.d.ts.map +1 -1
- package/dist/scripts/utils/config-validator.js +57 -1
- package/dist/scripts/utils/config-validator.js.map +1 -1
- package/dist/scripts/utils/confluence-approval.d.ts.map +1 -1
- package/dist/scripts/utils/confluence-approval.js +5 -3
- package/dist/scripts/utils/confluence-approval.js.map +1 -1
- package/dist/scripts/utils/confluence-hierarchy.d.ts.map +1 -1
- package/dist/scripts/utils/confluence-hierarchy.js.map +1 -1
- package/dist/scripts/utils/interactive-helpers.d.ts +32 -0
- package/dist/scripts/utils/interactive-helpers.d.ts.map +1 -0
- package/dist/scripts/utils/interactive-helpers.js +92 -0
- package/dist/scripts/utils/interactive-helpers.js.map +1 -0
- package/dist/scripts/utils/jira-issue-type-fetcher.d.ts.map +1 -1
- package/dist/scripts/utils/jira-issue-type-fetcher.js +27 -18
- package/dist/scripts/utils/jira-issue-type-fetcher.js.map +1 -1
- package/dist/scripts/utils/release-notes-generator.d.ts.map +1 -1
- package/dist/scripts/utils/release-notes-generator.js +2 -1
- package/dist/scripts/utils/release-notes-generator.js.map +1 -1
- package/dist/scripts/utils/spec-updater.d.ts +19 -0
- package/dist/scripts/utils/spec-updater.d.ts.map +1 -1
- package/dist/scripts/utils/spec-updater.js.map +1 -1
- package/dist/scripts/utils/tasks-converter.d.ts.map +1 -1
- package/dist/scripts/utils/tasks-converter.js +2 -2
- package/dist/scripts/utils/tasks-converter.js.map +1 -1
- package/dist/scripts/utils/tasks-format-validator.d.ts.map +1 -1
- package/dist/scripts/utils/tasks-format-validator.js +0 -12
- package/dist/scripts/utils/tasks-format-validator.js.map +1 -1
- package/dist/scripts/utils/test-runner.d.ts.map +1 -1
- package/dist/scripts/utils/test-runner.js +3 -2
- package/dist/scripts/utils/test-runner.js.map +1 -1
- package/dist/scripts/validate-phase.d.ts +1 -1
- package/dist/scripts/validate-phase.d.ts.map +1 -1
- package/dist/scripts/validate-phase.js +12 -62
- package/dist/scripts/validate-phase.js.map +1 -1
- package/dist/scripts/workflow-orchestrator.d.ts.map +1 -1
- package/dist/scripts/workflow-orchestrator.js +11 -16
- package/dist/scripts/workflow-orchestrator.js.map +1 -1
- package/dist/src/__tests__/integration/setup/init.test.d.ts +5 -0
- package/dist/src/__tests__/integration/setup/init.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/init.test.js +352 -0
- package/dist/src/__tests__/integration/setup/init.test.js.map +1 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +28 -20
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/init.d.ts +28 -0
- package/dist/src/commands/init.d.ts.map +1 -0
- package/dist/src/commands/init.js +490 -0
- package/dist/src/commands/init.js.map +1 -0
- package/docs/user-guide/getting-started/setup.md +31 -3
- package/docs/user-guide/guides/customization.md +64 -11
- package/docs/user-guide/guides/workflow.md +35 -21
- package/docs/user-guide/reference/config.md +30 -5
- package/docs/user-guide/reference/quick-reference.md +68 -74
- package/docs/user-guide/testing/test-planning-flow.md +4 -0
- package/package.json +2 -4
- package/scripts/config-global.ts +160 -0
- package/scripts/confluence-sync.ts +91 -27
- package/scripts/jira-sync.ts +284 -218
- package/scripts/list-projects.ts +2 -2
- package/scripts/multi-project-estimate.ts +3 -3
- package/scripts/phase-runner.ts +391 -594
- package/scripts/pre-flight-check.ts +20 -9
- package/scripts/pre-publish-check.sh +3 -34
- package/scripts/resource-dashboard.ts +4 -4
- package/scripts/spec-impl-workflow.ts +1 -1
- package/scripts/template/renderer.ts +1 -1
- package/scripts/test-interactive.ts +0 -19
- package/scripts/test-new-features.ts +10 -7
- package/scripts/test-npm-package.sh +3 -34
- package/scripts/test-spec-generator.ts +3 -7
- package/scripts/utils/config-loader.ts +107 -26
- package/scripts/utils/config-sections.ts +316 -0
- package/scripts/utils/config-validator.ts +66 -1
- package/scripts/utils/confluence-approval.ts +8 -6
- package/scripts/utils/confluence-hierarchy.ts +27 -27
- package/scripts/utils/interactive-helpers.ts +135 -0
- package/scripts/utils/jira-issue-type-fetcher.ts +29 -21
- package/scripts/utils/release-notes-generator.ts +3 -2
- package/scripts/utils/spec-updater.ts +37 -15
- package/scripts/utils/tasks-converter.ts +4 -6
- package/scripts/utils/tasks-format-validator.ts +0 -13
- package/scripts/utils/test-runner.ts +4 -3
- package/scripts/validate-phase.ts +21 -80
- package/scripts/workflow-orchestrator.ts +16 -25
- package/templates/claude/commands/kiro/kiro-spec-impl.md +4 -0
- package/templates/claude/commands/kiro/kiro-spec-tasks.md +3 -1
- package/templates/claude/commands/michi/confluence-sync.md +8 -2
- package/templates/claude/commands/michi/design-review.md +4 -0
- package/templates/claude/commands/michi/e2e-plan.md +4 -0
- package/templates/claude/commands/michi/license-check.md +4 -0
- package/templates/claude/commands/michi/pr-resolve.md +4 -0
- package/templates/claude/commands/michi/project-switch.md +8 -2
- package/templates/claude/commands/michi/spec-design.md +78 -0
- package/templates/claude/commands/michi/spec-impl.md +716 -0
- package/templates/claude/commands/michi/test-planning.md +174 -0
- package/templates/claude/commands/michi/validate-design.md +58 -0
- package/templates/claude/commands/michi/version-audit.md +4 -0
- package/templates/michi/cc-sdd-overrides/README.md +8 -0
- package/templates/michi/cc-sdd-overrides/settings/rules/design-review-michi.md +53 -0
- package/dist/scripts/config-interactive.d.ts +0 -10
- package/dist/scripts/config-interactive.d.ts.map +0 -1
- package/dist/scripts/config-interactive.js +0 -372
- package/dist/scripts/config-interactive.js.map +0 -1
- package/dist/scripts/setup-existing-project.d.ts +0 -15
- package/dist/scripts/setup-existing-project.d.ts.map +0 -1
- package/dist/scripts/setup-existing-project.js +0 -455
- package/dist/scripts/setup-existing-project.js.map +0 -1
- package/dist/scripts/setup-interactive.d.ts +0 -10
- package/dist/scripts/setup-interactive.d.ts.map +0 -1
- package/dist/scripts/setup-interactive.js +0 -413
- package/dist/scripts/setup-interactive.js.map +0 -1
- package/scripts/config-interactive.ts +0 -550
- package/scripts/setup-existing-project.ts +0 -585
- package/scripts/setup-interactive.ts +0 -565
|
@@ -1,565 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 対話式設定ツール
|
|
3
|
-
* project.jsonと.envを対話的に作成・更新
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
7
|
-
import { join, basename } from 'path';
|
|
8
|
-
import * as readline from 'readline';
|
|
9
|
-
import { findCurrentProject, findAllProjects, selectProject, findRepositoryRoot } from './utils/project-finder.js';
|
|
10
|
-
import type { ProjectMetadata } from './utils/project-meta.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* プロジェクトIDのバリデーション
|
|
14
|
-
* パストラバーサル攻撃を防ぐため、安全な文字のみを許可
|
|
15
|
-
*/
|
|
16
|
-
function validateProjectId(projectId: string): boolean {
|
|
17
|
-
// 空文字、空白のみを拒否
|
|
18
|
-
if (!projectId.trim() || /^\s+$/.test(projectId)) {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
// パストラバーサル文字を拒否
|
|
22
|
-
if (projectId.includes('..') || projectId.includes('/') || projectId.includes('\\')) {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
// 許可する文字のみ(英数字、ハイフン、アンダースコア)
|
|
26
|
-
return /^[A-Za-z0-9_-]+$/.test(projectId);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* readlineインターフェースを作成
|
|
31
|
-
*/
|
|
32
|
-
function createInterface(): readline.Interface {
|
|
33
|
-
return readline.createInterface({
|
|
34
|
-
input: process.stdin,
|
|
35
|
-
output: process.stdout
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 質問を表示して回答を取得
|
|
41
|
-
*/
|
|
42
|
-
function question(rl: readline.Interface, query: string): Promise<string> {
|
|
43
|
-
return new Promise(resolve => {
|
|
44
|
-
rl.question(query, answer => {
|
|
45
|
-
resolve(answer.trim());
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Yes/No質問
|
|
52
|
-
*/
|
|
53
|
-
async function confirm(rl: readline.Interface, prompt: string, defaultValue: boolean = true): Promise<boolean> {
|
|
54
|
-
const defaultText = defaultValue ? '[Y/n]' : '[y/N]';
|
|
55
|
-
const answer = await question(rl, `${prompt} ${defaultText}: `);
|
|
56
|
-
|
|
57
|
-
if (!answer) {
|
|
58
|
-
return defaultValue;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* プロジェクトメタデータを対話的に取得
|
|
66
|
-
*/
|
|
67
|
-
async function getProjectMetadata(
|
|
68
|
-
rl: readline.Interface,
|
|
69
|
-
existingMeta: Partial<ProjectMetadata> | null,
|
|
70
|
-
projectPath: string
|
|
71
|
-
): Promise<ProjectMetadata> {
|
|
72
|
-
console.log('\n📦 プロジェクトメタデータ設定');
|
|
73
|
-
console.log('='.repeat(60));
|
|
74
|
-
|
|
75
|
-
// projectId
|
|
76
|
-
const projectIdDefault = existingMeta?.projectId || basename(projectPath);
|
|
77
|
-
const projectId = await question(
|
|
78
|
-
rl,
|
|
79
|
-
`プロジェクトID [${projectIdDefault}]: `
|
|
80
|
-
) || projectIdDefault;
|
|
81
|
-
|
|
82
|
-
// バリデーション
|
|
83
|
-
if (!validateProjectId(projectId)) {
|
|
84
|
-
throw new Error('無効なプロジェクトIDです。英数字、ハイフン、アンダースコアのみ使用できます。');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// projectName
|
|
88
|
-
const projectName = await question(
|
|
89
|
-
rl,
|
|
90
|
-
`プロジェクト名${existingMeta?.projectName ? ` [${existingMeta.projectName}]` : ''}: `
|
|
91
|
-
) || existingMeta?.projectName || '';
|
|
92
|
-
|
|
93
|
-
if (!projectName) {
|
|
94
|
-
throw new Error('プロジェクト名は必須です');
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// jiraProjectKey
|
|
98
|
-
const jiraProjectKey = await question(
|
|
99
|
-
rl,
|
|
100
|
-
`JIRAプロジェクトキー${existingMeta?.jiraProjectKey ? ` [${existingMeta.jiraProjectKey}]` : ''}: `
|
|
101
|
-
) || existingMeta?.jiraProjectKey || '';
|
|
102
|
-
|
|
103
|
-
if (!jiraProjectKey) {
|
|
104
|
-
throw new Error('JIRAプロジェクトキーは必須です');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// confluenceLabels
|
|
108
|
-
const existingLabels = existingMeta?.confluenceLabels || [];
|
|
109
|
-
const labelsInput = await question(
|
|
110
|
-
rl,
|
|
111
|
-
`Confluenceラベル(カンマ区切り)${existingLabels.length > 0 ? ` [${existingLabels.join(', ')}]` : ''}: `
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
const confluenceLabels = labelsInput
|
|
115
|
-
? labelsInput.split(',').map(s => s.trim()).filter(s => s.length > 0)
|
|
116
|
-
: existingLabels;
|
|
117
|
-
|
|
118
|
-
// プロジェクトラベルが含まれていない場合は自動追加
|
|
119
|
-
const projectLabel = `project:${projectId}`;
|
|
120
|
-
if (!confluenceLabels.includes(projectLabel)) {
|
|
121
|
-
confluenceLabels.unshift(projectLabel);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// status
|
|
125
|
-
const statusChoices = [
|
|
126
|
-
{ value: 'active', label: 'active(開発中)' },
|
|
127
|
-
{ value: 'inactive', label: 'inactive(休止中)' },
|
|
128
|
-
{ value: 'completed', label: 'completed(完了)' }
|
|
129
|
-
];
|
|
130
|
-
|
|
131
|
-
console.log('\nステータスを選択してください:');
|
|
132
|
-
statusChoices.forEach((choice, index) => {
|
|
133
|
-
const defaultMark = existingMeta?.status === choice.value ? ' (現在の値)' : '';
|
|
134
|
-
console.log(` ${index + 1}. ${choice.label}${defaultMark}`);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const statusAnswer = await question(rl, `選択 [${existingMeta?.status || 'active'}]: `);
|
|
138
|
-
const statusIndex = statusAnswer ? parseInt(statusAnswer, 10) - 1 : -1;
|
|
139
|
-
const status = (statusIndex >= 0 && statusIndex < statusChoices.length)
|
|
140
|
-
? statusChoices[statusIndex].value
|
|
141
|
-
: (existingMeta?.status || 'active');
|
|
142
|
-
|
|
143
|
-
// team
|
|
144
|
-
const existingTeam = existingMeta?.team || [];
|
|
145
|
-
const teamInput = await question(
|
|
146
|
-
rl,
|
|
147
|
-
`チームメンバー(カンマ区切り、@プレフィックス付き)${existingTeam.length > 0 ? ` [${existingTeam.join(', ')}]` : ''}: `
|
|
148
|
-
);
|
|
149
|
-
const team = teamInput
|
|
150
|
-
? teamInput.split(',').map(s => s.trim()).filter(s => s.length > 0)
|
|
151
|
-
: existingTeam;
|
|
152
|
-
|
|
153
|
-
// stakeholders
|
|
154
|
-
const existingStakeholders = existingMeta?.stakeholders || [];
|
|
155
|
-
const stakeholdersInput = await question(
|
|
156
|
-
rl,
|
|
157
|
-
`ステークホルダー(カンマ区切り、@プレフィックス付き)${existingStakeholders.length > 0 ? ` [${existingStakeholders.join(', ')}]` : ''}: `
|
|
158
|
-
);
|
|
159
|
-
const stakeholders = stakeholdersInput
|
|
160
|
-
? stakeholdersInput.split(',').map(s => s.trim()).filter(s => s.length > 0)
|
|
161
|
-
: existingStakeholders;
|
|
162
|
-
|
|
163
|
-
// repository
|
|
164
|
-
const repository = await question(
|
|
165
|
-
rl,
|
|
166
|
-
`リポジトリURL${existingMeta?.repository ? ` [${existingMeta.repository}]` : ''}: `
|
|
167
|
-
) || existingMeta?.repository || '';
|
|
168
|
-
|
|
169
|
-
// description
|
|
170
|
-
const description = await question(
|
|
171
|
-
rl,
|
|
172
|
-
`説明${existingMeta?.description ? ` [${existingMeta.description}]` : ''}: `
|
|
173
|
-
) || existingMeta?.description || '';
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
projectId,
|
|
177
|
-
projectName,
|
|
178
|
-
jiraProjectKey,
|
|
179
|
-
confluenceLabels,
|
|
180
|
-
status: status as ProjectMetadata['status'],
|
|
181
|
-
team,
|
|
182
|
-
stakeholders,
|
|
183
|
-
repository,
|
|
184
|
-
description: description || undefined
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* 環境変数を対話的に取得
|
|
190
|
-
*/
|
|
191
|
-
async function getEnvConfig(
|
|
192
|
-
rl: readline.Interface,
|
|
193
|
-
existingEnv: Record<string, string> | null
|
|
194
|
-
): Promise<Record<string, string>> {
|
|
195
|
-
console.log('\n🔐 環境変数設定');
|
|
196
|
-
console.log('='.repeat(60));
|
|
197
|
-
|
|
198
|
-
const env: Record<string, string> = {};
|
|
199
|
-
|
|
200
|
-
// Atlassian設定
|
|
201
|
-
console.log('\n📋 Atlassian設定');
|
|
202
|
-
env.ATLASSIAN_URL = await question(
|
|
203
|
-
rl,
|
|
204
|
-
`Atlassian URL${existingEnv?.ATLASSIAN_URL ? ` [${existingEnv.ATLASSIAN_URL}]` : ''}: `
|
|
205
|
-
) || existingEnv?.ATLASSIAN_URL || '';
|
|
206
|
-
|
|
207
|
-
env.ATLASSIAN_EMAIL = await question(
|
|
208
|
-
rl,
|
|
209
|
-
`Atlassian Email${existingEnv?.ATLASSIAN_EMAIL ? ` [${existingEnv.ATLASSIAN_EMAIL}]` : ''}: `
|
|
210
|
-
) || existingEnv?.ATLASSIAN_EMAIL || '';
|
|
211
|
-
|
|
212
|
-
env.ATLASSIAN_API_TOKEN = await question(
|
|
213
|
-
rl,
|
|
214
|
-
`Atlassian API Token${existingEnv?.ATLASSIAN_API_TOKEN ? ' [***]' : ''}: `
|
|
215
|
-
) || existingEnv?.ATLASSIAN_API_TOKEN || '';
|
|
216
|
-
|
|
217
|
-
// GitHub設定
|
|
218
|
-
console.log('\n🐙 GitHub設定');
|
|
219
|
-
env.GITHUB_ORG = await question(
|
|
220
|
-
rl,
|
|
221
|
-
`GitHub組織名${existingEnv?.GITHUB_ORG ? ` [${existingEnv.GITHUB_ORG}]` : ''}: `
|
|
222
|
-
) || existingEnv?.GITHUB_ORG || '';
|
|
223
|
-
|
|
224
|
-
env.GITHUB_TOKEN = await question(
|
|
225
|
-
rl,
|
|
226
|
-
`GitHub Token${existingEnv?.GITHUB_TOKEN ? ' [***]' : ''}: `
|
|
227
|
-
) || existingEnv?.GITHUB_TOKEN || '';
|
|
228
|
-
|
|
229
|
-
env.GITHUB_REPO = await question(
|
|
230
|
-
rl,
|
|
231
|
-
`GitHubリポジトリ(org/repo形式)${existingEnv?.GITHUB_REPO ? ` [${existingEnv.GITHUB_REPO}]` : ''}: `
|
|
232
|
-
) || existingEnv?.GITHUB_REPO || '';
|
|
233
|
-
|
|
234
|
-
// Confluence設定
|
|
235
|
-
console.log('\n📄 Confluence設定');
|
|
236
|
-
env.CONFLUENCE_PRD_SPACE = await question(
|
|
237
|
-
rl,
|
|
238
|
-
`PRDスペースキー${existingEnv?.CONFLUENCE_PRD_SPACE ? ` [${existingEnv.CONFLUENCE_PRD_SPACE}]` : ''}: `
|
|
239
|
-
) || existingEnv?.CONFLUENCE_PRD_SPACE || '';
|
|
240
|
-
|
|
241
|
-
env.CONFLUENCE_QA_SPACE = await question(
|
|
242
|
-
rl,
|
|
243
|
-
`QAスペースキー${existingEnv?.CONFLUENCE_QA_SPACE ? ` [${existingEnv.CONFLUENCE_QA_SPACE}]` : ''}: `
|
|
244
|
-
) || existingEnv?.CONFLUENCE_QA_SPACE || '';
|
|
245
|
-
|
|
246
|
-
env.CONFLUENCE_RELEASE_SPACE = await question(
|
|
247
|
-
rl,
|
|
248
|
-
`RELEASEスペースキー${existingEnv?.CONFLUENCE_RELEASE_SPACE ? ` [${existingEnv.CONFLUENCE_RELEASE_SPACE}]` : ''}: `
|
|
249
|
-
) || existingEnv?.CONFLUENCE_RELEASE_SPACE || '';
|
|
250
|
-
|
|
251
|
-
// JIRA設定
|
|
252
|
-
console.log('\n📋 JIRA設定');
|
|
253
|
-
env.JIRA_PROJECT_KEYS = await question(
|
|
254
|
-
rl,
|
|
255
|
-
`JIRAプロジェクトキー(カンマ区切り)${existingEnv?.JIRA_PROJECT_KEYS ? ` [${existingEnv.JIRA_PROJECT_KEYS}]` : ''}: `
|
|
256
|
-
) || existingEnv?.JIRA_PROJECT_KEYS || '';
|
|
257
|
-
|
|
258
|
-
env.JIRA_ISSUE_TYPE_STORY = await question(
|
|
259
|
-
rl,
|
|
260
|
-
`JIRA Story Issue Type ID${existingEnv?.JIRA_ISSUE_TYPE_STORY ? ` [${existingEnv.JIRA_ISSUE_TYPE_STORY}]` : ''}: `
|
|
261
|
-
) || existingEnv?.JIRA_ISSUE_TYPE_STORY || '';
|
|
262
|
-
|
|
263
|
-
env.JIRA_ISSUE_TYPE_SUBTASK = await question(
|
|
264
|
-
rl,
|
|
265
|
-
`JIRA Subtask Issue Type ID${existingEnv?.JIRA_ISSUE_TYPE_SUBTASK ? ` [${existingEnv.JIRA_ISSUE_TYPE_SUBTASK}]` : ''}: `
|
|
266
|
-
) || existingEnv?.JIRA_ISSUE_TYPE_SUBTASK || '';
|
|
267
|
-
|
|
268
|
-
// Slack設定(オプション)
|
|
269
|
-
const configureSlack = await confirm(rl, '\nSlack通知を設定しますか?', false);
|
|
270
|
-
if (configureSlack) {
|
|
271
|
-
env.SLACK_WEBHOOK_URL = await question(
|
|
272
|
-
rl,
|
|
273
|
-
`Slack Webhook URL${existingEnv?.SLACK_WEBHOOK_URL ? ' [***]' : ''}: `
|
|
274
|
-
) || existingEnv?.SLACK_WEBHOOK_URL || '';
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return env;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* .envファイルを読み込む
|
|
282
|
-
*/
|
|
283
|
-
function loadEnvFile(envPath: string): Record<string, string> {
|
|
284
|
-
const env: Record<string, string> = {};
|
|
285
|
-
|
|
286
|
-
if (!existsSync(envPath)) {
|
|
287
|
-
return env;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
try {
|
|
291
|
-
const content = readFileSync(envPath, 'utf-8');
|
|
292
|
-
const lines = content.split('\n');
|
|
293
|
-
|
|
294
|
-
for (const line of lines) {
|
|
295
|
-
const trimmed = line.trim();
|
|
296
|
-
// コメント行と空行をスキップ
|
|
297
|
-
if (!trimmed || trimmed.startsWith('#')) {
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
302
|
-
if (match) {
|
|
303
|
-
const key = match[1].trim();
|
|
304
|
-
const value = match[2].trim();
|
|
305
|
-
env[key] = value;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
} catch (error) {
|
|
309
|
-
// 読み込みエラーを警告として表示
|
|
310
|
-
console.warn(`⚠️ .envファイルの読み込みに失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return env;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* .envファイルを保存
|
|
318
|
-
*/
|
|
319
|
-
function saveEnvFile(envPath: string, env: Record<string, string>): void {
|
|
320
|
-
const lines: string[] = [];
|
|
321
|
-
|
|
322
|
-
// Atlassian設定
|
|
323
|
-
lines.push('# Atlassian設定');
|
|
324
|
-
lines.push(`ATLASSIAN_URL=${env.ATLASSIAN_URL || ''}`);
|
|
325
|
-
lines.push(`ATLASSIAN_EMAIL=${env.ATLASSIAN_EMAIL || ''}`);
|
|
326
|
-
lines.push(`ATLASSIAN_API_TOKEN=${env.ATLASSIAN_API_TOKEN || ''}`);
|
|
327
|
-
lines.push('');
|
|
328
|
-
|
|
329
|
-
// GitHub設定
|
|
330
|
-
lines.push('# GitHub設定');
|
|
331
|
-
lines.push(`GITHUB_ORG=${env.GITHUB_ORG || ''}`);
|
|
332
|
-
lines.push(`GITHUB_TOKEN=${env.GITHUB_TOKEN || ''}`);
|
|
333
|
-
lines.push(`GITHUB_REPO=${env.GITHUB_REPO || ''}`);
|
|
334
|
-
lines.push('');
|
|
335
|
-
|
|
336
|
-
// Confluence設定
|
|
337
|
-
lines.push('# Confluence設定');
|
|
338
|
-
lines.push(`CONFLUENCE_PRD_SPACE=${env.CONFLUENCE_PRD_SPACE || ''}`);
|
|
339
|
-
lines.push(`CONFLUENCE_QA_SPACE=${env.CONFLUENCE_QA_SPACE || ''}`);
|
|
340
|
-
lines.push(`CONFLUENCE_RELEASE_SPACE=${env.CONFLUENCE_RELEASE_SPACE || ''}`);
|
|
341
|
-
lines.push('');
|
|
342
|
-
|
|
343
|
-
// JIRA設定
|
|
344
|
-
lines.push('# JIRA設定');
|
|
345
|
-
lines.push(`JIRA_PROJECT_KEYS=${env.JIRA_PROJECT_KEYS || ''}`);
|
|
346
|
-
lines.push('');
|
|
347
|
-
lines.push('# JIRA Issue Type IDs(JIRAインスタンス固有の値 - 必須)');
|
|
348
|
-
lines.push(`JIRA_ISSUE_TYPE_STORY=${env.JIRA_ISSUE_TYPE_STORY || ''}`);
|
|
349
|
-
lines.push(`JIRA_ISSUE_TYPE_SUBTASK=${env.JIRA_ISSUE_TYPE_SUBTASK || ''}`);
|
|
350
|
-
lines.push('');
|
|
351
|
-
|
|
352
|
-
// Slack設定(オプション)
|
|
353
|
-
if (env.SLACK_WEBHOOK_URL) {
|
|
354
|
-
lines.push('# Slack通知(オプション)');
|
|
355
|
-
lines.push(`SLACK_WEBHOOK_URL=${env.SLACK_WEBHOOK_URL}`);
|
|
356
|
-
lines.push('');
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
writeFileSync(envPath, lines.join('\n'), 'utf-8');
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* メイン処理
|
|
364
|
-
*/
|
|
365
|
-
async function main(): Promise<number> {
|
|
366
|
-
const rl = createInterface();
|
|
367
|
-
|
|
368
|
-
try {
|
|
369
|
-
console.log('🎨 Michi プロジェクト設定ツール');
|
|
370
|
-
console.log('='.repeat(60));
|
|
371
|
-
console.log('このツールで project.json と .env を対話的に設定できます。\n');
|
|
372
|
-
|
|
373
|
-
// リポジトリルートを検出
|
|
374
|
-
const repoRoot = findRepositoryRoot();
|
|
375
|
-
const projectsDir = join(repoRoot, 'projects');
|
|
376
|
-
|
|
377
|
-
// プロジェクト検出
|
|
378
|
-
findCurrentProject();
|
|
379
|
-
const allProjects = findAllProjects();
|
|
380
|
-
|
|
381
|
-
let projectPath: string;
|
|
382
|
-
let existingMeta: Partial<ProjectMetadata> | null = null;
|
|
383
|
-
let isNewProject = false;
|
|
384
|
-
|
|
385
|
-
if (allProjects.length === 0) {
|
|
386
|
-
// プロジェクトが見つからない場合、新規プロジェクトを作成
|
|
387
|
-
// プロジェクトIDを取得(ディレクトリ名から推測、または対話的に入力)
|
|
388
|
-
const defaultProjectId = basename(repoRoot);
|
|
389
|
-
let projectId: string;
|
|
390
|
-
let isValid = false;
|
|
391
|
-
|
|
392
|
-
while (!isValid) {
|
|
393
|
-
const projectIdInput = await question(
|
|
394
|
-
rl,
|
|
395
|
-
`新規プロジェクトID [${defaultProjectId}]: `
|
|
396
|
-
);
|
|
397
|
-
projectId = projectIdInput.trim() || defaultProjectId;
|
|
398
|
-
|
|
399
|
-
if (validateProjectId(projectId)) {
|
|
400
|
-
isValid = true;
|
|
401
|
-
} else {
|
|
402
|
-
console.log('❌ 無効なプロジェクトIDです。英数字、ハイフン、アンダースコアのみ使用できます。');
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// projects/{project-id}/配下に作成
|
|
407
|
-
projectPath = join(projectsDir, projectId!);
|
|
408
|
-
isNewProject = true;
|
|
409
|
-
|
|
410
|
-
console.log(`\n📁 新規プロジェクトを作成します: ${projectPath}\n`);
|
|
411
|
-
} else if (allProjects.length === 1) {
|
|
412
|
-
// 1つのプロジェクトのみ
|
|
413
|
-
projectPath = allProjects[0].path;
|
|
414
|
-
console.log(`📦 プロジェクト: ${allProjects[0].projectName} (${allProjects[0].projectId})`);
|
|
415
|
-
console.log(`📁 パス: ${projectPath}\n`);
|
|
416
|
-
|
|
417
|
-
// 既存のproject.jsonを読み込む
|
|
418
|
-
const projectJsonPath = join(projectPath, '.kiro', 'project.json');
|
|
419
|
-
if (existsSync(projectJsonPath)) {
|
|
420
|
-
try {
|
|
421
|
-
const content = readFileSync(projectJsonPath, 'utf-8');
|
|
422
|
-
existingMeta = JSON.parse(content);
|
|
423
|
-
} catch {
|
|
424
|
-
// 読み込みエラーは無視
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
} else {
|
|
428
|
-
// 複数プロジェクトが見つかった場合、選択
|
|
429
|
-
const selected = await selectProject(allProjects, question.bind(null, rl));
|
|
430
|
-
if (!selected) {
|
|
431
|
-
console.log('プロジェクトが選択されませんでした。');
|
|
432
|
-
return 0;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
projectPath = selected.path;
|
|
436
|
-
console.log(`\n📦 選択されたプロジェクト: ${selected.projectName} (${selected.projectId})`);
|
|
437
|
-
console.log(`📁 パス: ${projectPath}\n`);
|
|
438
|
-
|
|
439
|
-
// 既存のproject.jsonを読み込む
|
|
440
|
-
const projectJsonPath = join(projectPath, '.kiro', 'project.json');
|
|
441
|
-
if (existsSync(projectJsonPath)) {
|
|
442
|
-
try {
|
|
443
|
-
const content = readFileSync(projectJsonPath, 'utf-8');
|
|
444
|
-
existingMeta = JSON.parse(content);
|
|
445
|
-
} catch {
|
|
446
|
-
// 読み込みエラーは無視
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// 設定する項目を選択
|
|
452
|
-
const configureProject = await confirm(rl, 'project.jsonを設定しますか?', true);
|
|
453
|
-
const configureEnv = await confirm(rl, '.envを設定しますか?', true);
|
|
454
|
-
|
|
455
|
-
if (!configureProject && !configureEnv) {
|
|
456
|
-
console.log('設定する項目がありません。');
|
|
457
|
-
return 0;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
let projectMeta: ProjectMetadata | null = null;
|
|
461
|
-
let envConfig: Record<string, string> | null = null;
|
|
462
|
-
|
|
463
|
-
// project.json設定
|
|
464
|
-
if (configureProject) {
|
|
465
|
-
projectMeta = await getProjectMetadata(rl, existingMeta, projectPath);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// .env設定
|
|
469
|
-
if (configureEnv) {
|
|
470
|
-
const envPath = join(projectPath, '.env');
|
|
471
|
-
const existingEnv = loadEnvFile(envPath);
|
|
472
|
-
envConfig = await getEnvConfig(rl, existingEnv);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// 確認
|
|
476
|
-
console.log('\n📋 設定内容の確認');
|
|
477
|
-
console.log('='.repeat(60));
|
|
478
|
-
|
|
479
|
-
if (projectMeta) {
|
|
480
|
-
console.log('\n📦 project.json:');
|
|
481
|
-
console.log(JSON.stringify(projectMeta, null, 2));
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
if (envConfig) {
|
|
485
|
-
console.log('\n🔐 .env:');
|
|
486
|
-
// 機密情報をマスク
|
|
487
|
-
const maskedEnv = { ...envConfig };
|
|
488
|
-
if (maskedEnv.ATLASSIAN_API_TOKEN) {
|
|
489
|
-
maskedEnv.ATLASSIAN_API_TOKEN = '***';
|
|
490
|
-
}
|
|
491
|
-
if (maskedEnv.GITHUB_TOKEN) {
|
|
492
|
-
maskedEnv.GITHUB_TOKEN = '***';
|
|
493
|
-
}
|
|
494
|
-
if (maskedEnv.SLACK_WEBHOOK_URL) {
|
|
495
|
-
maskedEnv.SLACK_WEBHOOK_URL = '***';
|
|
496
|
-
}
|
|
497
|
-
Object.entries(maskedEnv).forEach(([key, value]) => {
|
|
498
|
-
if (value) {
|
|
499
|
-
console.log(`${key}=${value}`);
|
|
500
|
-
}
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
console.log('');
|
|
505
|
-
const confirmSave = await confirm(rl, 'この設定を保存しますか?', true);
|
|
506
|
-
|
|
507
|
-
if (!confirmSave) {
|
|
508
|
-
console.log('保存をキャンセルしました。');
|
|
509
|
-
return 0;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// 保存
|
|
513
|
-
if (projectMeta) {
|
|
514
|
-
// 新規プロジェクトの場合はディレクトリを作成
|
|
515
|
-
if (isNewProject) {
|
|
516
|
-
if (!existsSync(projectsDir)) {
|
|
517
|
-
mkdirSync(projectsDir, { recursive: true });
|
|
518
|
-
}
|
|
519
|
-
if (!existsSync(projectPath)) {
|
|
520
|
-
mkdirSync(projectPath, { recursive: true });
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
const projectJsonPath = join(projectPath, '.kiro', 'project.json');
|
|
525
|
-
const projectJsonDir = join(projectPath, '.kiro');
|
|
526
|
-
|
|
527
|
-
if (!existsSync(projectJsonDir)) {
|
|
528
|
-
mkdirSync(projectJsonDir, { recursive: true });
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
writeFileSync(projectJsonPath, JSON.stringify(projectMeta, null, 2) + '\n', 'utf-8');
|
|
532
|
-
console.log(`\n✅ project.jsonを保存しました: ${projectJsonPath}`);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (envConfig) {
|
|
536
|
-
const envPath = join(projectPath, '.env');
|
|
537
|
-
saveEnvFile(envPath, envConfig);
|
|
538
|
-
console.log(`✅ .envを保存しました: ${envPath}`);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
console.log('\n🎉 設定が完了しました!');
|
|
542
|
-
return 0;
|
|
543
|
-
|
|
544
|
-
} catch (error) {
|
|
545
|
-
console.error('❌ エラーが発生しました:', error instanceof Error ? error.message : error);
|
|
546
|
-
return 1;
|
|
547
|
-
} finally {
|
|
548
|
-
rl.close();
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// CLI実行
|
|
553
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
554
|
-
main()
|
|
555
|
-
.then(exitCode => {
|
|
556
|
-
process.exit(exitCode);
|
|
557
|
-
})
|
|
558
|
-
.catch(error => {
|
|
559
|
-
console.error('❌ 予期しないエラー:', error);
|
|
560
|
-
process.exit(1);
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
export { main as setupInteractive };
|
|
565
|
-
|