@sk8metal/michi-cli 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +60 -24
- package/dist/scripts/__tests__/validate-phase.test.d.ts +5 -0
- package/dist/scripts/__tests__/validate-phase.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/validate-phase.test.js +162 -0
- package/dist/scripts/__tests__/validate-phase.test.js.map +1 -0
- package/dist/scripts/utils/__tests__/config-validator.test.d.ts +5 -0
- package/dist/scripts/utils/__tests__/config-validator.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/config-validator.test.js +247 -0
- package/dist/scripts/utils/__tests__/config-validator.test.js.map +1 -0
- package/dist/scripts/utils/__tests__/feature-name-validator.test.d.ts +5 -0
- package/dist/scripts/utils/__tests__/feature-name-validator.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/feature-name-validator.test.js +106 -0
- package/dist/scripts/utils/__tests__/feature-name-validator.test.js.map +1 -0
- package/dist/scripts/utils/config-loader.js +1 -1
- package/dist/scripts/utils/config-loader.js.map +1 -1
- package/dist/scripts/utils/confluence-hierarchy.d.ts.map +1 -1
- package/dist/scripts/utils/confluence-hierarchy.js +2 -1
- package/dist/scripts/utils/confluence-hierarchy.js.map +1 -1
- package/dist/src/__tests__/cli.test.d.ts +5 -0
- package/dist/src/__tests__/cli.test.d.ts.map +1 -0
- package/dist/src/__tests__/cli.test.js +58 -0
- package/dist/src/__tests__/cli.test.js.map +1 -0
- package/dist/src/cli.js +0 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +29 -0
- package/dist/vitest.config.js.map +1 -0
- package/docs/setup.md +1 -1
- package/package.json +8 -4
- package/scripts/__tests__/README.md +101 -0
- package/scripts/__tests__/validate-phase.test.ts +185 -0
- package/scripts/config/config-schema.ts +130 -0
- package/scripts/config/default-config.json +57 -0
- package/scripts/config-interactive.ts +494 -0
- package/scripts/confluence-sync.ts +503 -0
- package/scripts/create-project.ts +293 -0
- package/scripts/jira-sync.ts +644 -0
- package/scripts/list-projects.ts +85 -0
- package/scripts/markdown-to-confluence.ts +161 -0
- package/scripts/multi-project-estimate.ts +255 -0
- package/scripts/phase-runner.ts +303 -0
- package/scripts/pr-automation.ts +67 -0
- package/scripts/pre-flight-check.ts +285 -0
- package/scripts/resource-dashboard.ts +124 -0
- package/scripts/setup-env.sh +52 -0
- package/scripts/setup-existing-project.ts +381 -0
- package/scripts/setup-existing.sh +145 -0
- package/scripts/utils/__tests__/config-validator.test.ts +302 -0
- package/scripts/utils/__tests__/feature-name-validator.test.ts +129 -0
- package/scripts/utils/config-loader.ts +326 -0
- package/scripts/utils/config-validator.ts +347 -0
- package/scripts/utils/confluence-hierarchy.ts +855 -0
- package/scripts/utils/feature-name-validator.ts +135 -0
- package/scripts/utils/project-meta.ts +69 -0
- package/scripts/validate-phase.ts +279 -0
- package/scripts/workflow-orchestrator.ts +178 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* フェーズランナー
|
|
3
|
+
* 各フェーズを実行し、Confluence/JIRA作成を確実に実行
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { syncToConfluence } from './confluence-sync.js';
|
|
9
|
+
import { syncTasksToJIRA } from './jira-sync.js';
|
|
10
|
+
import { validatePhase } from './validate-phase.js';
|
|
11
|
+
import { runPreFlightCheck } from './pre-flight-check.js';
|
|
12
|
+
import { validateFeatureNameOrThrow } from './utils/feature-name-validator.js';
|
|
13
|
+
|
|
14
|
+
type Phase = 'requirements' | 'design' | 'tasks';
|
|
15
|
+
|
|
16
|
+
interface PhaseRunResult {
|
|
17
|
+
phase: Phase;
|
|
18
|
+
success: boolean;
|
|
19
|
+
confluenceCreated: boolean;
|
|
20
|
+
jiraCreated: boolean;
|
|
21
|
+
validationPassed: boolean;
|
|
22
|
+
errors: string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 要件定義フェーズを実行
|
|
27
|
+
*/
|
|
28
|
+
async function runRequirementsPhase(feature: string): Promise<PhaseRunResult> {
|
|
29
|
+
console.log('\n📋 Phase: Requirements(要件定義)');
|
|
30
|
+
console.log('='.repeat(60));
|
|
31
|
+
|
|
32
|
+
const errors: string[] = [];
|
|
33
|
+
let confluenceCreated = false;
|
|
34
|
+
let confluenceUrl: string | null = null;
|
|
35
|
+
|
|
36
|
+
// Step 1: requirements.md存在確認
|
|
37
|
+
const requirementsPath = join(process.cwd(), '.kiro', 'specs', feature, 'requirements.md');
|
|
38
|
+
if (!existsSync(requirementsPath)) {
|
|
39
|
+
errors.push('requirements.mdが存在しません。先に/kiro:spec-requirements を実行してください');
|
|
40
|
+
return {
|
|
41
|
+
phase: 'requirements',
|
|
42
|
+
success: false,
|
|
43
|
+
confluenceCreated: false,
|
|
44
|
+
jiraCreated: false,
|
|
45
|
+
validationPassed: false,
|
|
46
|
+
errors
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log('✅ requirements.md 存在確認');
|
|
51
|
+
|
|
52
|
+
// Step 2: Confluenceページ作成
|
|
53
|
+
console.log('\n📤 Confluenceページ作成中...');
|
|
54
|
+
try {
|
|
55
|
+
confluenceUrl = await syncToConfluence(feature, 'requirements');
|
|
56
|
+
confluenceCreated = true;
|
|
57
|
+
console.log('✅ Confluenceページ作成成功');
|
|
58
|
+
} catch (error: any) {
|
|
59
|
+
errors.push(`Confluenceページ作成失敗: ${error.message}`);
|
|
60
|
+
console.error('❌ Confluenceページ作成失敗:', error.message);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Step 3: バリデーション
|
|
64
|
+
console.log('\n🔍 バリデーション実行中...');
|
|
65
|
+
const validation = validatePhase(feature, 'requirements');
|
|
66
|
+
errors.push(...validation.errors);
|
|
67
|
+
|
|
68
|
+
// Step 4: 結果サマリー
|
|
69
|
+
console.log('\n' + '='.repeat(60));
|
|
70
|
+
console.log('📊 要件定義フェーズ完了チェック:');
|
|
71
|
+
console.log(' ✅ requirements.md: 作成済み');
|
|
72
|
+
console.log(` ${confluenceCreated ? '✅' : '❌'} Confluenceページ: ${confluenceCreated ? '作成済み' : '未作成'}`);
|
|
73
|
+
console.log(` ${validation.valid ? '✅' : '❌'} バリデーション: ${validation.valid ? '成功' : '失敗'}`);
|
|
74
|
+
|
|
75
|
+
if (validation.valid && confluenceCreated) {
|
|
76
|
+
console.log('\n🎉 要件定義フェーズが完了しました!');
|
|
77
|
+
console.log('📢 PMや部長にConfluenceでレビューを依頼してください');
|
|
78
|
+
if (confluenceUrl) {
|
|
79
|
+
console.log(`📄 Confluence: ${confluenceUrl}`);
|
|
80
|
+
} else {
|
|
81
|
+
const baseUrl = process.env.ATLASSIAN_URL || 'https://your-site.atlassian.net';
|
|
82
|
+
console.log(`📄 Confluence: ${baseUrl}/wiki/spaces/(URLは上記のログを参照)`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
phase: 'requirements',
|
|
88
|
+
success: validation.valid && confluenceCreated,
|
|
89
|
+
confluenceCreated,
|
|
90
|
+
jiraCreated: false,
|
|
91
|
+
validationPassed: validation.valid,
|
|
92
|
+
errors
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 設計フェーズを実行
|
|
98
|
+
*/
|
|
99
|
+
async function runDesignPhase(feature: string): Promise<PhaseRunResult> {
|
|
100
|
+
console.log('\n🏗️ Phase: Design(設計)');
|
|
101
|
+
console.log('='.repeat(60));
|
|
102
|
+
|
|
103
|
+
const errors: string[] = [];
|
|
104
|
+
let confluenceCreated = false;
|
|
105
|
+
let confluenceUrl: string | null = null;
|
|
106
|
+
|
|
107
|
+
// Step 1: design.md存在確認
|
|
108
|
+
const designPath = join(process.cwd(), '.kiro', 'specs', feature, 'design.md');
|
|
109
|
+
if (!existsSync(designPath)) {
|
|
110
|
+
errors.push('design.mdが存在しません。先に/kiro:spec-design を実行してください');
|
|
111
|
+
return {
|
|
112
|
+
phase: 'design',
|
|
113
|
+
success: false,
|
|
114
|
+
confluenceCreated: false,
|
|
115
|
+
jiraCreated: false,
|
|
116
|
+
validationPassed: false,
|
|
117
|
+
errors
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log('✅ design.md 存在確認');
|
|
122
|
+
|
|
123
|
+
// Step 2: Confluenceページ作成
|
|
124
|
+
console.log('\n📤 Confluenceページ作成中...');
|
|
125
|
+
try {
|
|
126
|
+
confluenceUrl = await syncToConfluence(feature, 'design');
|
|
127
|
+
confluenceCreated = true;
|
|
128
|
+
console.log('✅ Confluenceページ作成成功');
|
|
129
|
+
} catch (error: any) {
|
|
130
|
+
errors.push(`Confluenceページ作成失敗: ${error.message}`);
|
|
131
|
+
console.error('❌ Confluenceページ作成失敗:', error.message);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Step 3: バリデーション
|
|
135
|
+
console.log('\n🔍 バリデーション実行中...');
|
|
136
|
+
const validation = validatePhase(feature, 'design');
|
|
137
|
+
errors.push(...validation.errors);
|
|
138
|
+
|
|
139
|
+
// Step 4: 結果サマリー
|
|
140
|
+
console.log('\n' + '='.repeat(60));
|
|
141
|
+
console.log('📊 設計フェーズ完了チェック:');
|
|
142
|
+
console.log(' ✅ design.md: 作成済み');
|
|
143
|
+
console.log(` ${confluenceCreated ? '✅' : '❌'} Confluenceページ: ${confluenceCreated ? '作成済み' : '未作成'}`);
|
|
144
|
+
console.log(` ${validation.valid ? '✅' : '❌'} バリデーション: ${validation.valid ? '成功' : '失敗'}`);
|
|
145
|
+
|
|
146
|
+
if (validation.valid && confluenceCreated) {
|
|
147
|
+
console.log('\n🎉 設計フェーズが完了しました!');
|
|
148
|
+
console.log('📢 PMや部長にConfluenceでレビューを依頼してください');
|
|
149
|
+
if (confluenceUrl) {
|
|
150
|
+
console.log(`📄 Confluence: ${confluenceUrl}`);
|
|
151
|
+
} else {
|
|
152
|
+
const baseUrl = process.env.ATLASSIAN_URL || 'https://your-site.atlassian.net';
|
|
153
|
+
console.log(`📄 Confluence: ${baseUrl}/wiki/spaces/(URLは上記のログを参照)`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
phase: 'design',
|
|
159
|
+
success: validation.valid && confluenceCreated,
|
|
160
|
+
confluenceCreated,
|
|
161
|
+
jiraCreated: false,
|
|
162
|
+
validationPassed: validation.valid,
|
|
163
|
+
errors
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* タスク分割フェーズを実行
|
|
169
|
+
*/
|
|
170
|
+
async function runTasksPhase(feature: string): Promise<PhaseRunResult> {
|
|
171
|
+
console.log('\n📝 Phase: Tasks(タスク分割)');
|
|
172
|
+
console.log('='.repeat(60));
|
|
173
|
+
|
|
174
|
+
const errors: string[] = [];
|
|
175
|
+
let jiraCreated = false;
|
|
176
|
+
|
|
177
|
+
// Step 0: プリフライトチェック
|
|
178
|
+
console.log('\n🔍 プリフライトチェック...');
|
|
179
|
+
const preFlightResult = await runPreFlightCheck('jira');
|
|
180
|
+
|
|
181
|
+
if (!preFlightResult.valid) {
|
|
182
|
+
console.log('\n❌ プリフライトチェック失敗:');
|
|
183
|
+
preFlightResult.errors.forEach(e => console.log(` ${e}`));
|
|
184
|
+
return {
|
|
185
|
+
phase: 'tasks',
|
|
186
|
+
success: false,
|
|
187
|
+
confluenceCreated: false,
|
|
188
|
+
jiraCreated: false,
|
|
189
|
+
validationPassed: false,
|
|
190
|
+
errors: preFlightResult.errors
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log('✅ プリフライトチェック成功');
|
|
195
|
+
|
|
196
|
+
// Step 1: tasks.md存在確認
|
|
197
|
+
const tasksPath = join(process.cwd(), '.kiro', 'specs', feature, 'tasks.md');
|
|
198
|
+
if (!existsSync(tasksPath)) {
|
|
199
|
+
errors.push('tasks.mdが存在しません。先に/kiro:spec-tasks を実行してください');
|
|
200
|
+
return {
|
|
201
|
+
phase: 'tasks',
|
|
202
|
+
success: false,
|
|
203
|
+
confluenceCreated: false,
|
|
204
|
+
jiraCreated: false,
|
|
205
|
+
validationPassed: false,
|
|
206
|
+
errors
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log('✅ tasks.md 存在確認');
|
|
211
|
+
|
|
212
|
+
// Step 2: JIRA Epic/Story作成
|
|
213
|
+
console.log('\n📤 JIRA Epic/Story作成中...');
|
|
214
|
+
try {
|
|
215
|
+
await syncTasksToJIRA(feature);
|
|
216
|
+
jiraCreated = true;
|
|
217
|
+
console.log('✅ JIRA Epic/Story作成成功');
|
|
218
|
+
} catch (error: any) {
|
|
219
|
+
errors.push(`JIRA作成失敗: ${error.message}`);
|
|
220
|
+
console.error('❌ JIRA作成失敗:', error.message);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Step 3: バリデーション
|
|
224
|
+
console.log('\n🔍 バリデーション実行中...');
|
|
225
|
+
const validation = validatePhase(feature, 'tasks');
|
|
226
|
+
errors.push(...validation.errors);
|
|
227
|
+
|
|
228
|
+
// Step 4: 結果サマリー
|
|
229
|
+
console.log('\n' + '='.repeat(60));
|
|
230
|
+
console.log('📊 タスク分割フェーズ完了チェック:');
|
|
231
|
+
console.log(' ✅ tasks.md: 作成済み');
|
|
232
|
+
console.log(` ${jiraCreated ? '✅' : '❌'} JIRA Epic/Story: ${jiraCreated ? '作成済み' : '未作成'}`);
|
|
233
|
+
console.log(` ${validation.valid ? '✅' : '❌'} バリデーション: ${validation.valid ? '成功' : '失敗'}`);
|
|
234
|
+
|
|
235
|
+
if (validation.valid && jiraCreated) {
|
|
236
|
+
console.log('\n🎉 タスク分割フェーズが完了しました!');
|
|
237
|
+
console.log('📢 開発チームに実装開始を通知してください');
|
|
238
|
+
console.log('🚀 次のステップ: /kiro:spec-impl <feature>');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
phase: 'tasks',
|
|
243
|
+
success: validation.valid && jiraCreated,
|
|
244
|
+
confluenceCreated: false,
|
|
245
|
+
jiraCreated,
|
|
246
|
+
validationPassed: validation.valid,
|
|
247
|
+
errors
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* フェーズを実行
|
|
253
|
+
*/
|
|
254
|
+
export async function runPhase(feature: string, phase: Phase): Promise<PhaseRunResult> {
|
|
255
|
+
// feature名のバリデーション(必須)
|
|
256
|
+
validateFeatureNameOrThrow(feature);
|
|
257
|
+
|
|
258
|
+
switch (phase) {
|
|
259
|
+
case 'requirements':
|
|
260
|
+
return await runRequirementsPhase(feature);
|
|
261
|
+
case 'design':
|
|
262
|
+
return await runDesignPhase(feature);
|
|
263
|
+
case 'tasks':
|
|
264
|
+
return await runTasksPhase(feature);
|
|
265
|
+
default:
|
|
266
|
+
throw new Error(`Unknown phase: ${phase}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// CLI実行
|
|
271
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
272
|
+
const args = process.argv.slice(2);
|
|
273
|
+
|
|
274
|
+
if (args.length < 2) {
|
|
275
|
+
console.error('Usage: npm run phase:run <feature> <phase>');
|
|
276
|
+
console.error('Example: npm run phase:run calculator-app requirements');
|
|
277
|
+
console.error('Phases: requirements, design, tasks');
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const [feature, phase] = args;
|
|
282
|
+
|
|
283
|
+
if (!['requirements', 'design', 'tasks'].includes(phase)) {
|
|
284
|
+
console.error('Invalid phase. Must be: requirements, design, or tasks');
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
runPhase(feature, phase as Phase)
|
|
289
|
+
.then((result) => {
|
|
290
|
+
if (result.success) {
|
|
291
|
+
console.log('\n✅ フェーズ完了');
|
|
292
|
+
process.exit(0);
|
|
293
|
+
} else {
|
|
294
|
+
console.log('\n❌ フェーズ未完了(エラーを確認してください)');
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
.catch((error) => {
|
|
299
|
+
console.error(`\n❌ フェーズ実行エラー: ${error.message}`);
|
|
300
|
+
process.exit(1);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub PR自動化スクリプト
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Octokit } from '@octokit/rest';
|
|
6
|
+
import { config } from 'dotenv';
|
|
7
|
+
import { loadProjectMeta } from './utils/project-meta.js';
|
|
8
|
+
|
|
9
|
+
config();
|
|
10
|
+
|
|
11
|
+
interface PROptions {
|
|
12
|
+
branch: string;
|
|
13
|
+
title: string;
|
|
14
|
+
body: string;
|
|
15
|
+
base?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function createPR(options: PROptions): Promise<void> {
|
|
19
|
+
const token = process.env.GITHUB_TOKEN;
|
|
20
|
+
const repo = process.env.GITHUB_REPO;
|
|
21
|
+
|
|
22
|
+
if (!token || !repo) {
|
|
23
|
+
throw new Error('Missing GitHub credentials');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const [owner, repoName] = repo.split('/');
|
|
27
|
+
const octokit = new Octokit({ auth: token });
|
|
28
|
+
|
|
29
|
+
const { branch, title, body, base = 'main' } = options;
|
|
30
|
+
|
|
31
|
+
console.log(`Creating PR: ${title}`);
|
|
32
|
+
|
|
33
|
+
const pr = await octokit.pulls.create({
|
|
34
|
+
owner,
|
|
35
|
+
repo: repoName,
|
|
36
|
+
title,
|
|
37
|
+
body,
|
|
38
|
+
head: branch,
|
|
39
|
+
base
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
console.log(`✅ PR created: ${pr.data.html_url}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// CLI実行
|
|
46
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
47
|
+
const args = process.argv.slice(2);
|
|
48
|
+
|
|
49
|
+
if (args.length === 0) {
|
|
50
|
+
console.error('Usage: npm run github:create-pr <branch> [title]');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const branch = args[0];
|
|
55
|
+
const title = args[1] || `feat: ${branch}`;
|
|
56
|
+
const body = 'Auto-generated PR';
|
|
57
|
+
|
|
58
|
+
createPR({ branch, title, body })
|
|
59
|
+
.then(() => process.exit(0))
|
|
60
|
+
.catch((error) => {
|
|
61
|
+
console.error('❌ PR creation failed:', error.message);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { createPR };
|
|
67
|
+
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* プリフライトチェック
|
|
3
|
+
* スクリプト実行前に必要な設定が揃っているか確認
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, readFileSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import axios from 'axios';
|
|
9
|
+
import { config } from 'dotenv';
|
|
10
|
+
|
|
11
|
+
config();
|
|
12
|
+
|
|
13
|
+
interface PreFlightResult {
|
|
14
|
+
valid: boolean;
|
|
15
|
+
errors: string[];
|
|
16
|
+
warnings: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* .env設定をチェック
|
|
21
|
+
*/
|
|
22
|
+
function checkEnvConfig(): { errors: string[], warnings: string[] } {
|
|
23
|
+
const errors: string[] = [];
|
|
24
|
+
const warnings: string[] = [];
|
|
25
|
+
|
|
26
|
+
// .envファイル存在チェック
|
|
27
|
+
if (!existsSync('.env')) {
|
|
28
|
+
errors.push('❌ .envファイルが存在しません');
|
|
29
|
+
errors.push(' → テンプレートからコピー: cp .env.example .env');
|
|
30
|
+
errors.push(' → 編集: vim .env(認証情報を設定)');
|
|
31
|
+
errors.push(' → API Token取得: https://id.atlassian.com/manage-profile/security/api-tokens');
|
|
32
|
+
return { errors, warnings };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 必須環境変数チェック
|
|
36
|
+
const required = [
|
|
37
|
+
'ATLASSIAN_URL',
|
|
38
|
+
'ATLASSIAN_EMAIL',
|
|
39
|
+
'ATLASSIAN_API_TOKEN'
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const missing = required.filter(key => !process.env[key]);
|
|
43
|
+
|
|
44
|
+
if (missing.length > 0) {
|
|
45
|
+
errors.push(`❌ .envに必須項目が設定されていません: ${missing.join(', ')}`);
|
|
46
|
+
errors.push(' → 編集: vim .env');
|
|
47
|
+
errors.push(' → API Token取得: https://id.atlassian.com/manage-profile/security/api-tokens');
|
|
48
|
+
errors.push('');
|
|
49
|
+
errors.push(' 必須項目:');
|
|
50
|
+
errors.push(' ATLASSIAN_URL=https://your-site.atlassian.net');
|
|
51
|
+
errors.push(' ATLASSIAN_EMAIL=your-email@example.com');
|
|
52
|
+
errors.push(' ATLASSIAN_API_TOKEN=your-api-token');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// オプション環境変数の警告
|
|
56
|
+
if (!process.env.CONFLUENCE_PRD_SPACE) {
|
|
57
|
+
warnings.push('⚠️ CONFLUENCE_PRD_SPACEが未設定(デフォルト: PRD)');
|
|
58
|
+
warnings.push(` → スペース一覧: ${process.env.ATLASSIAN_URL}/wiki/spaces`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { errors, warnings };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* project.jsonをチェック
|
|
66
|
+
*/
|
|
67
|
+
function checkProjectJson(): { errors: string[], warnings: string[] } {
|
|
68
|
+
const errors: string[] = [];
|
|
69
|
+
const warnings: string[] = [];
|
|
70
|
+
|
|
71
|
+
const projectJsonPath = join(process.cwd(), '.kiro', 'project.json');
|
|
72
|
+
|
|
73
|
+
if (!existsSync(projectJsonPath)) {
|
|
74
|
+
errors.push('❌ .kiro/project.json が存在しません');
|
|
75
|
+
errors.push(' → このディレクトリはMichiプロジェクトではありません');
|
|
76
|
+
errors.push(' → セットアップ: npm run setup-existing(michi-practice1の場合)');
|
|
77
|
+
errors.push(' → または、Michiプロジェクトのディレクトリに移動してください');
|
|
78
|
+
return { errors, warnings };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let projectMeta: any;
|
|
82
|
+
try {
|
|
83
|
+
projectMeta = JSON.parse(readFileSync(projectJsonPath, 'utf-8'));
|
|
84
|
+
} catch (error) {
|
|
85
|
+
errors.push('❌ project.json のパースに失敗しました');
|
|
86
|
+
return { errors, warnings };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 必須フィールドチェック
|
|
90
|
+
const required = ['projectId', 'projectName', 'jiraProjectKey'];
|
|
91
|
+
const missing = required.filter(key => !projectMeta[key]);
|
|
92
|
+
|
|
93
|
+
if (missing.length > 0) {
|
|
94
|
+
errors.push(`❌ project.jsonに必須項目がありません: ${missing.join(', ')}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { errors, warnings };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Confluenceスペース存在チェック(API呼び出し)
|
|
102
|
+
*/
|
|
103
|
+
async function checkConfluenceSpace(spaceKey: string): Promise<{ errors: string[], warnings: string[] }> {
|
|
104
|
+
const errors: string[] = [];
|
|
105
|
+
const warnings: string[] = [];
|
|
106
|
+
|
|
107
|
+
const url = process.env.ATLASSIAN_URL;
|
|
108
|
+
const email = process.env.ATLASSIAN_EMAIL;
|
|
109
|
+
const apiToken = process.env.ATLASSIAN_API_TOKEN;
|
|
110
|
+
|
|
111
|
+
if (!url || !email || !apiToken) {
|
|
112
|
+
errors.push('❌ .env設定が不完全なため、Confluenceスペースチェックをスキップ');
|
|
113
|
+
return { errors, warnings };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const auth = Buffer.from(`${email}:${apiToken}`).toString('base64');
|
|
118
|
+
const response = await axios.get(`${url}/wiki/rest/api/space/${spaceKey}`, {
|
|
119
|
+
headers: {
|
|
120
|
+
'Authorization': `Basic ${auth}`,
|
|
121
|
+
'Content-Type': 'application/json'
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (response.data) {
|
|
126
|
+
console.log(` ✅ Confluenceスペース確認: ${spaceKey} (${response.data.name})`);
|
|
127
|
+
}
|
|
128
|
+
} catch (error: any) {
|
|
129
|
+
if (error.response?.status === 404) {
|
|
130
|
+
errors.push(`❌ Confluenceスペースが存在しません: ${spaceKey}`);
|
|
131
|
+
errors.push(` → Confluenceで新しいスペースを作成: ${url}/wiki/spaces`);
|
|
132
|
+
errors.push(' → または、.envのCONFLUENCE_PRD_SPACEを修正してください');
|
|
133
|
+
} else if (error.response?.status === 401) {
|
|
134
|
+
errors.push('❌ Confluence認証エラー(.envの認証情報を確認)');
|
|
135
|
+
errors.push(` → API Token管理: ${url.replace('atlassian.net', 'atlassian.net/manage/profile/security/api-tokens')}`);
|
|
136
|
+
} else {
|
|
137
|
+
warnings.push(`⚠️ Confluenceスペースチェック失敗: ${error.message}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { errors, warnings };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* JIRAプロジェクト存在チェック(API呼び出し)
|
|
146
|
+
*/
|
|
147
|
+
async function checkJiraProject(projectKey: string): Promise<{ errors: string[], warnings: string[] }> {
|
|
148
|
+
const errors: string[] = [];
|
|
149
|
+
const warnings: string[] = [];
|
|
150
|
+
|
|
151
|
+
const url = process.env.ATLASSIAN_URL;
|
|
152
|
+
const email = process.env.ATLASSIAN_EMAIL;
|
|
153
|
+
const apiToken = process.env.ATLASSIAN_API_TOKEN;
|
|
154
|
+
|
|
155
|
+
if (!url || !email || !apiToken) {
|
|
156
|
+
errors.push('❌ .env設定が不完全なため、JIRAプロジェクトチェックをスキップ');
|
|
157
|
+
return { errors, warnings };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const auth = Buffer.from(`${email}:${apiToken}`).toString('base64');
|
|
162
|
+
const response = await axios.get(`${url}/rest/api/3/project/${projectKey}`, {
|
|
163
|
+
headers: {
|
|
164
|
+
'Authorization': `Basic ${auth}`,
|
|
165
|
+
'Content-Type': 'application/json'
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
if (response.data) {
|
|
170
|
+
console.log(` ✅ JIRAプロジェクト確認: ${projectKey} (${response.data.name})`);
|
|
171
|
+
}
|
|
172
|
+
} catch (error: any) {
|
|
173
|
+
if (error.response?.status === 404) {
|
|
174
|
+
errors.push(`❌ JIRAプロジェクトが存在しません: ${projectKey}`);
|
|
175
|
+
errors.push(` → JIRAプロジェクト作成: ${url}/jira/projects/create`);
|
|
176
|
+
errors.push(` → プロジェクト一覧: ${url}/jira/settings/projects`);
|
|
177
|
+
errors.push(' → または、.kiro/project.jsonのjiraProjectKeyを修正してください');
|
|
178
|
+
errors.push(` 現在の設定: "${projectKey}" → 実際に存在するキーに変更`);
|
|
179
|
+
} else if (error.response?.status === 401) {
|
|
180
|
+
errors.push('❌ JIRA認証エラー(.envの認証情報を確認)');
|
|
181
|
+
errors.push(` → API Token管理: https://id.atlassian.com/manage-profile/security/api-tokens`);
|
|
182
|
+
} else {
|
|
183
|
+
warnings.push(`⚠️ JIRAプロジェクトチェック失敗: ${error.message}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { errors, warnings };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* プリフライトチェック実行
|
|
192
|
+
*/
|
|
193
|
+
export async function runPreFlightCheck(phase: 'confluence' | 'jira' | 'all'): Promise<PreFlightResult> {
|
|
194
|
+
console.log('\n🔍 プリフライトチェック実行中...\n');
|
|
195
|
+
|
|
196
|
+
const allErrors: string[] = [];
|
|
197
|
+
const allWarnings: string[] = [];
|
|
198
|
+
|
|
199
|
+
// 1. .env設定チェック
|
|
200
|
+
console.log('📋 Step 1: .env設定チェック');
|
|
201
|
+
const envCheck = checkEnvConfig();
|
|
202
|
+
allErrors.push(...envCheck.errors);
|
|
203
|
+
allWarnings.push(...envCheck.warnings);
|
|
204
|
+
|
|
205
|
+
if (envCheck.errors.length === 0) {
|
|
206
|
+
console.log(' ✅ .env設定OK');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 2. project.jsonチェック
|
|
210
|
+
console.log('\n📋 Step 2: project.json設定チェック');
|
|
211
|
+
const projectCheck = checkProjectJson();
|
|
212
|
+
allErrors.push(...projectCheck.errors);
|
|
213
|
+
allWarnings.push(...projectCheck.warnings);
|
|
214
|
+
|
|
215
|
+
if (projectCheck.errors.length === 0) {
|
|
216
|
+
console.log(' ✅ project.json設定OK');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// .envまたはproject.jsonエラーがあれば、ここで中断
|
|
220
|
+
if (allErrors.length > 0) {
|
|
221
|
+
return { valid: false, errors: allErrors, warnings: allWarnings };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 3. Confluenceスペースチェック(API呼び出し)
|
|
225
|
+
if (phase === 'confluence' || phase === 'all') {
|
|
226
|
+
console.log('\n📋 Step 3: Confluenceスペース存在チェック');
|
|
227
|
+
const spaceKey = process.env.CONFLUENCE_PRD_SPACE || 'PRD';
|
|
228
|
+
const spaceCheck = await checkConfluenceSpace(spaceKey);
|
|
229
|
+
allErrors.push(...spaceCheck.errors);
|
|
230
|
+
allWarnings.push(...spaceCheck.warnings);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 4. JIRAプロジェクトチェック(API呼び出し)
|
|
234
|
+
if (phase === 'jira' || phase === 'all') {
|
|
235
|
+
console.log('\n📋 Step 4: JIRAプロジェクト存在チェック');
|
|
236
|
+
const projectJsonPath = join(process.cwd(), '.kiro', 'project.json');
|
|
237
|
+
const projectMeta = JSON.parse(readFileSync(projectJsonPath, 'utf-8'));
|
|
238
|
+
const jiraCheck = await checkJiraProject(projectMeta.jiraProjectKey);
|
|
239
|
+
allErrors.push(...jiraCheck.errors);
|
|
240
|
+
allWarnings.push(...jiraCheck.warnings);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
valid: allErrors.length === 0,
|
|
245
|
+
errors: allErrors,
|
|
246
|
+
warnings: allWarnings
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// CLI実行
|
|
251
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
252
|
+
const args = process.argv.slice(2);
|
|
253
|
+
const phase = (args[0] as 'confluence' | 'jira' | 'all') || 'all';
|
|
254
|
+
|
|
255
|
+
if (!['confluence', 'jira', 'all'].includes(phase)) {
|
|
256
|
+
console.error('Usage: npm run preflight [confluence|jira|all]');
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
runPreFlightCheck(phase)
|
|
261
|
+
.then((result) => {
|
|
262
|
+
console.log('\n' + '='.repeat(60));
|
|
263
|
+
|
|
264
|
+
if (result.warnings.length > 0) {
|
|
265
|
+
console.log('\n⚠️ 警告:');
|
|
266
|
+
result.warnings.forEach(w => console.log(` ${w}`));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (result.errors.length > 0) {
|
|
270
|
+
console.log('\n❌ エラー:');
|
|
271
|
+
result.errors.forEach(e => console.log(` ${e}`));
|
|
272
|
+
console.log('\n❌ プリフライトチェック失敗');
|
|
273
|
+
process.exit(1);
|
|
274
|
+
} else {
|
|
275
|
+
console.log('\n✅ プリフライトチェック成功');
|
|
276
|
+
console.log(' すべての設定が正しく構成されています');
|
|
277
|
+
process.exit(0);
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
.catch((error) => {
|
|
281
|
+
console.error(`\n❌ チェックエラー: ${error.message}`);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|