@sk8metal/michi-cli 0.0.4 → 0.0.5
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 +18 -0
- package/README.md +24 -25
- package/dist/scripts/config-interactive.d.ts +1 -1
- package/dist/scripts/config-interactive.d.ts.map +1 -1
- package/dist/scripts/config-interactive.js +4 -4
- package/dist/scripts/config-interactive.js.map +1 -1
- package/dist/scripts/confluence-sync.js +2 -2
- package/dist/scripts/confluence-sync.js.map +1 -1
- package/dist/scripts/create-project.d.ts +2 -2
- package/dist/scripts/create-project.js +2 -2
- package/dist/scripts/jira-sync.js +3 -3
- package/dist/scripts/jira-sync.js.map +1 -1
- package/dist/scripts/utils/__tests__/config-loader.test.d.ts +5 -0
- package/dist/scripts/utils/__tests__/config-loader.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/config-loader.test.js +201 -0
- package/dist/scripts/utils/__tests__/config-loader.test.js.map +1 -0
- package/dist/scripts/utils/__tests__/config-validator.test.js +29 -16
- package/dist/scripts/utils/__tests__/config-validator.test.js.map +1 -1
- package/dist/scripts/utils/config-loader.d.ts +4 -0
- package/dist/scripts/utils/config-loader.d.ts.map +1 -1
- package/dist/scripts/utils/config-loader.js +24 -2
- package/dist/scripts/utils/config-loader.js.map +1 -1
- package/dist/scripts/utils/config-validator.js +8 -9
- package/dist/scripts/utils/config-validator.js.map +1 -1
- package/dist/src/cli.js +2 -2
- package/dist/src/cli.js.map +1 -1
- package/docs/config-reference.md +76 -197
- package/docs/customization-guide.md +61 -9
- package/docs/multi-project.md +157 -25
- package/docs/new-project-setup.md +36 -36
- package/docs/quick-reference.md +23 -20
- package/docs/setup.md +5 -3
- package/env.example +3 -1
- package/package.json +1 -1
- package/scripts/config-interactive.ts +4 -3
- package/scripts/confluence-sync.ts +2 -2
- package/scripts/create-project.ts +4 -4
- package/scripts/jira-sync.ts +3 -3
- package/scripts/setup-existing.sh +1 -1
- package/scripts/utils/__tests__/config-loader.test.ts +254 -0
- package/scripts/utils/__tests__/config-validator.test.ts +32 -16
- package/scripts/utils/config-loader.ts +30 -2
- package/scripts/utils/config-validator.ts +8 -8
- package/docs/testing.md +0 -202
package/scripts/jira-sync.ts
CHANGED
|
@@ -23,7 +23,7 @@ import axios from 'axios';
|
|
|
23
23
|
import { config } from 'dotenv';
|
|
24
24
|
import { loadProjectMeta } from './utils/project-meta.js';
|
|
25
25
|
import { validateFeatureNameOrThrow } from './utils/feature-name-validator.js';
|
|
26
|
-
import { getConfig } from './utils/config-loader.js';
|
|
26
|
+
import { getConfig, getConfigPath } from './utils/config-loader.js';
|
|
27
27
|
import { validateForJiraSync } from './utils/config-validator.js';
|
|
28
28
|
|
|
29
29
|
config();
|
|
@@ -355,7 +355,7 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
|
|
|
355
355
|
if (validation.errors.length > 0) {
|
|
356
356
|
console.error('❌ Configuration errors:');
|
|
357
357
|
validation.errors.forEach(error => console.error(` ${error}`));
|
|
358
|
-
const configPath =
|
|
358
|
+
const configPath = getConfigPath();
|
|
359
359
|
console.error(`\n設定ファイル: ${configPath}`);
|
|
360
360
|
throw new Error('JIRA同期に必要な設定値が不足しています。上記のエラーを確認して設定を修正してください。');
|
|
361
361
|
}
|
|
@@ -370,7 +370,7 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
|
|
|
370
370
|
if (!storyIssueTypeId) {
|
|
371
371
|
throw new Error(
|
|
372
372
|
'JIRA Story issue type ID is not configured. ' +
|
|
373
|
-
'Please set JIRA_ISSUE_TYPE_STORY environment variable or configure it in .
|
|
373
|
+
'Please set JIRA_ISSUE_TYPE_STORY environment variable or configure it in .michi/config.json. ' +
|
|
374
374
|
'You can find the issue type ID in JIRA UI (Settings > Issues > Issue types) or via REST API: ' +
|
|
375
375
|
'GET https://your-domain.atlassian.net/rest/api/3/issuetype'
|
|
376
376
|
);
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config-loader.ts のユニットテスト
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
6
|
+
import { existsSync, writeFileSync, unlinkSync, mkdirSync, rmSync } from 'fs';
|
|
7
|
+
import { resolve, join } from 'path';
|
|
8
|
+
import { tmpdir } from 'os';
|
|
9
|
+
import {
|
|
10
|
+
loadConfig,
|
|
11
|
+
getConfig,
|
|
12
|
+
getConfigPath,
|
|
13
|
+
clearConfigCache
|
|
14
|
+
} from '../config-loader.js';
|
|
15
|
+
|
|
16
|
+
describe('config-loader', () => {
|
|
17
|
+
let testProjectRoot: string;
|
|
18
|
+
let originalEnv: NodeJS.ProcessEnv;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
// テスト用の一時ディレクトリを作成(ランダム要素を追加して衝突を防ぐ)
|
|
22
|
+
testProjectRoot = resolve(tmpdir(), `michi-test-${Date.now()}-${Math.random().toString(36).substring(7)}`);
|
|
23
|
+
|
|
24
|
+
// 既存のディレクトリがあれば削除
|
|
25
|
+
if (existsSync(testProjectRoot)) {
|
|
26
|
+
rmSync(testProjectRoot, { recursive: true, force: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
mkdirSync(testProjectRoot, { recursive: true });
|
|
30
|
+
mkdirSync(join(testProjectRoot, '.michi'), { recursive: true });
|
|
31
|
+
|
|
32
|
+
// 環境変数をバックアップ
|
|
33
|
+
originalEnv = { ...process.env };
|
|
34
|
+
|
|
35
|
+
// キャッシュをクリア(クリーンな状態から開始)
|
|
36
|
+
clearConfigCache();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
// 環境変数を復元
|
|
41
|
+
process.env = originalEnv;
|
|
42
|
+
|
|
43
|
+
// テスト用ディレクトリを削除
|
|
44
|
+
if (existsSync(testProjectRoot)) {
|
|
45
|
+
// ファイルを削除
|
|
46
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
47
|
+
if (existsSync(configPath)) {
|
|
48
|
+
unlinkSync(configPath);
|
|
49
|
+
}
|
|
50
|
+
// ディレクトリを削除
|
|
51
|
+
try {
|
|
52
|
+
rmSync(testProjectRoot, { recursive: true, force: true });
|
|
53
|
+
} catch (e) {
|
|
54
|
+
// 削除失敗は無視
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// キャッシュをクリア(ディレクトリ削除後に実行)
|
|
59
|
+
clearConfigCache();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('getConfigPath', () => {
|
|
63
|
+
it('.michi/config.jsonのパスを返す', () => {
|
|
64
|
+
clearConfigCache();
|
|
65
|
+
const configPath = getConfigPath(testProjectRoot);
|
|
66
|
+
expect(configPath).toBe(join(testProjectRoot, '.michi/config.json'));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('設定ファイルが存在しない場合でも.michi/config.jsonのパスを返す', () => {
|
|
70
|
+
clearConfigCache();
|
|
71
|
+
const configPath = getConfigPath(testProjectRoot);
|
|
72
|
+
expect(configPath).toBe(join(testProjectRoot, '.michi/config.json'));
|
|
73
|
+
expect(existsSync(configPath)).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('loadConfig', () => {
|
|
78
|
+
it('設定ファイルが存在しない場合はデフォルト設定を返す', () => {
|
|
79
|
+
clearConfigCache();
|
|
80
|
+
const config = loadConfig(testProjectRoot);
|
|
81
|
+
|
|
82
|
+
expect(config).toBeDefined();
|
|
83
|
+
expect(config.confluence).toBeDefined();
|
|
84
|
+
expect(config.confluence?.pageCreationGranularity).toBe('single');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('設定ファイルが存在する場合はマージされた設定を返す', () => {
|
|
88
|
+
clearConfigCache();
|
|
89
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
90
|
+
writeFileSync(configPath, JSON.stringify({
|
|
91
|
+
confluence: {
|
|
92
|
+
pageCreationGranularity: 'by-hierarchy',
|
|
93
|
+
spaces: {
|
|
94
|
+
requirements: 'TestSpace'
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
const config = loadConfig(testProjectRoot);
|
|
100
|
+
|
|
101
|
+
expect(config.confluence?.pageCreationGranularity).toBe('by-hierarchy');
|
|
102
|
+
expect(config.confluence?.spaces?.requirements).toBe('TestSpace');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('無効なJSONの場合はエラーをスロー', () => {
|
|
106
|
+
clearConfigCache(); // キャッシュをクリア
|
|
107
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
108
|
+
writeFileSync(configPath, '{ invalid json }');
|
|
109
|
+
|
|
110
|
+
expect(() => {
|
|
111
|
+
loadConfig(testProjectRoot);
|
|
112
|
+
}).toThrow(/Invalid JSON/);
|
|
113
|
+
|
|
114
|
+
// テスト後にファイルを削除して次のテストへの影響を防ぐ
|
|
115
|
+
if (existsSync(configPath)) {
|
|
116
|
+
unlinkSync(configPath);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('getConfig (キャッシュ付き)', () => {
|
|
122
|
+
// キャッシュテストでは、beforeEachでキャッシュをクリアしない
|
|
123
|
+
// 代わりに、各テストの最後にキャッシュをクリアする
|
|
124
|
+
|
|
125
|
+
it('設定ファイルが存在しない場合はデフォルト設定を返す', () => {
|
|
126
|
+
clearConfigCache(); // テスト開始時にクリア
|
|
127
|
+
const config = getConfig(testProjectRoot);
|
|
128
|
+
|
|
129
|
+
expect(config).toBeDefined();
|
|
130
|
+
expect(config.confluence).toBeDefined();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('同じ設定ファイルを2回読み込む場合はキャッシュが使用される', () => {
|
|
134
|
+
clearConfigCache(); // テスト開始時にクリア
|
|
135
|
+
|
|
136
|
+
// .kiro/が存在しないことを確認(legacy警告を防ぐ)
|
|
137
|
+
const legacyDir = join(testProjectRoot, '.kiro');
|
|
138
|
+
if (existsSync(legacyDir)) {
|
|
139
|
+
rmSync(legacyDir, { recursive: true, force: true });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
143
|
+
writeFileSync(configPath, JSON.stringify({
|
|
144
|
+
confluence: {
|
|
145
|
+
pageCreationGranularity: 'by-hierarchy'
|
|
146
|
+
}
|
|
147
|
+
}));
|
|
148
|
+
|
|
149
|
+
const config1 = getConfig(testProjectRoot);
|
|
150
|
+
const config2 = getConfig(testProjectRoot);
|
|
151
|
+
|
|
152
|
+
// 同じオブジェクト参照であることを確認(キャッシュが使用されている)
|
|
153
|
+
expect(config1).toBe(config2);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('設定ファイルを変更するとキャッシュが無効化される', async () => {
|
|
157
|
+
clearConfigCache(); // テスト開始時にクリア
|
|
158
|
+
|
|
159
|
+
// .michiディレクトリが存在することを確認(前のテストで削除されている可能性)
|
|
160
|
+
const michiDir = join(testProjectRoot, '.michi');
|
|
161
|
+
if (!existsSync(michiDir)) {
|
|
162
|
+
mkdirSync(michiDir, { recursive: true });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
166
|
+
writeFileSync(configPath, JSON.stringify({
|
|
167
|
+
confluence: {
|
|
168
|
+
pageCreationGranularity: 'single'
|
|
169
|
+
}
|
|
170
|
+
}));
|
|
171
|
+
|
|
172
|
+
const config1 = getConfig(testProjectRoot);
|
|
173
|
+
expect(config1.confluence?.pageCreationGranularity).toBe('single');
|
|
174
|
+
|
|
175
|
+
// ファイルシステムのmtime精度を考慮して少し待つ
|
|
176
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
177
|
+
|
|
178
|
+
// 設定ファイルを更新(ディレクトリが削除されている可能性があるため再確認)
|
|
179
|
+
if (!existsSync(michiDir)) {
|
|
180
|
+
mkdirSync(michiDir, { recursive: true });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
writeFileSync(configPath, JSON.stringify({
|
|
184
|
+
confluence: {
|
|
185
|
+
pageCreationGranularity: 'by-hierarchy'
|
|
186
|
+
}
|
|
187
|
+
}));
|
|
188
|
+
|
|
189
|
+
const config2 = getConfig(testProjectRoot);
|
|
190
|
+
expect(config2.confluence?.pageCreationGranularity).toBe('by-hierarchy');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('警告メッセージ', () => {
|
|
195
|
+
it('legacyパス(.kiro/config.json)が存在する場合は警告を表示', () => {
|
|
196
|
+
// .michi/config.jsonが存在しないことを確認(警告が表示される条件)
|
|
197
|
+
const michiConfigPath = join(testProjectRoot, '.michi/config.json');
|
|
198
|
+
const michiDir = join(testProjectRoot, '.michi');
|
|
199
|
+
|
|
200
|
+
// .michi/config.jsonとディレクトリを削除
|
|
201
|
+
if (existsSync(michiConfigPath)) {
|
|
202
|
+
unlinkSync(michiConfigPath);
|
|
203
|
+
}
|
|
204
|
+
if (existsSync(michiDir)) {
|
|
205
|
+
rmSync(michiDir, { recursive: true, force: true });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// legacyパスにファイルを作成
|
|
209
|
+
mkdirSync(join(testProjectRoot, '.kiro'), { recursive: true });
|
|
210
|
+
const legacyConfigPath = join(testProjectRoot, '.kiro/config.json');
|
|
211
|
+
writeFileSync(legacyConfigPath, JSON.stringify({
|
|
212
|
+
confluence: {
|
|
213
|
+
pageCreationGranularity: 'single'
|
|
214
|
+
}
|
|
215
|
+
}));
|
|
216
|
+
|
|
217
|
+
// 警告が表示されることを確認(console.warnをモック)
|
|
218
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
219
|
+
|
|
220
|
+
// loadConfigを呼ぶことでresolveConfigPathが実行され、警告が表示される
|
|
221
|
+
loadConfig(testProjectRoot);
|
|
222
|
+
|
|
223
|
+
expect(consoleWarnSpy).toHaveBeenCalled();
|
|
224
|
+
expect(consoleWarnSpy.mock.calls[0][0]).toContain('Deprecated');
|
|
225
|
+
expect(consoleWarnSpy.mock.calls[0][0]).toContain('.kiro/config.json');
|
|
226
|
+
|
|
227
|
+
consoleWarnSpy.mockRestore();
|
|
228
|
+
|
|
229
|
+
// 次のテストのために.michiディレクトリを再作成
|
|
230
|
+
mkdirSync(michiDir, { recursive: true });
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('legacyパスと新規パスの両方が存在する場合は警告を表示しない', () => {
|
|
234
|
+
// 両方のパスにファイルを作成
|
|
235
|
+
mkdirSync(join(testProjectRoot, '.kiro'), { recursive: true });
|
|
236
|
+
const legacyConfigPath = join(testProjectRoot, '.kiro/config.json');
|
|
237
|
+
const michiConfigPath = join(testProjectRoot, '.michi/config.json');
|
|
238
|
+
writeFileSync(legacyConfigPath, JSON.stringify({}));
|
|
239
|
+
writeFileSync(michiConfigPath, JSON.stringify({}));
|
|
240
|
+
|
|
241
|
+
// 警告が表示されないことを確認
|
|
242
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
243
|
+
|
|
244
|
+
// loadConfigを呼ぶことでresolveConfigPathが実行される
|
|
245
|
+
loadConfig(testProjectRoot);
|
|
246
|
+
|
|
247
|
+
// 警告は表示されない(新規パスが存在するため)
|
|
248
|
+
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
249
|
+
|
|
250
|
+
consoleWarnSpy.mockRestore();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
validateForJiraSync,
|
|
13
13
|
validateAndReport
|
|
14
14
|
} from '../config-validator.js';
|
|
15
|
+
import { clearConfigCache } from '../config-loader.js';
|
|
15
16
|
|
|
16
17
|
describe('config-validator', () => {
|
|
17
18
|
let testProjectRoot: string;
|
|
@@ -21,10 +22,13 @@ describe('config-validator', () => {
|
|
|
21
22
|
// テスト用の一時ディレクトリを作成
|
|
22
23
|
testProjectRoot = resolve(tmpdir(), `michi-test-${Date.now()}`);
|
|
23
24
|
mkdirSync(testProjectRoot, { recursive: true });
|
|
24
|
-
mkdirSync(join(testProjectRoot, '.
|
|
25
|
+
mkdirSync(join(testProjectRoot, '.michi'), { recursive: true });
|
|
25
26
|
|
|
26
27
|
// 環境変数をバックアップ
|
|
27
28
|
originalEnv = { ...process.env };
|
|
29
|
+
|
|
30
|
+
// キャッシュをクリア
|
|
31
|
+
clearConfigCache();
|
|
28
32
|
});
|
|
29
33
|
|
|
30
34
|
afterEach(() => {
|
|
@@ -34,7 +38,7 @@ describe('config-validator', () => {
|
|
|
34
38
|
// テスト用ディレクトリを削除
|
|
35
39
|
if (existsSync(testProjectRoot)) {
|
|
36
40
|
// ファイルを削除
|
|
37
|
-
const configPath = join(testProjectRoot, '.
|
|
41
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
38
42
|
if (existsSync(configPath)) {
|
|
39
43
|
unlinkSync(configPath);
|
|
40
44
|
}
|
|
@@ -49,6 +53,12 @@ describe('config-validator', () => {
|
|
|
49
53
|
|
|
50
54
|
describe('validateProjectConfig', () => {
|
|
51
55
|
it('設定ファイルが存在しない場合は情報メッセージを返す', () => {
|
|
56
|
+
// .michi/config.jsonが存在しないことを確認
|
|
57
|
+
const michiConfigPath = join(testProjectRoot, '.michi/config.json');
|
|
58
|
+
if (existsSync(michiConfigPath)) {
|
|
59
|
+
unlinkSync(michiConfigPath);
|
|
60
|
+
}
|
|
61
|
+
|
|
52
62
|
const result = validateProjectConfig(testProjectRoot);
|
|
53
63
|
|
|
54
64
|
expect(result.valid).toBe(true);
|
|
@@ -59,7 +69,13 @@ describe('config-validator', () => {
|
|
|
59
69
|
});
|
|
60
70
|
|
|
61
71
|
it('有効な設定ファイルの場合は成功', () => {
|
|
62
|
-
|
|
72
|
+
// .michiディレクトリが存在することを確認
|
|
73
|
+
const michiDir = join(testProjectRoot, '.michi');
|
|
74
|
+
if (!existsSync(michiDir)) {
|
|
75
|
+
mkdirSync(michiDir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
63
79
|
writeFileSync(configPath, JSON.stringify({
|
|
64
80
|
confluence: {
|
|
65
81
|
pageCreationGranularity: 'single',
|
|
@@ -76,7 +92,7 @@ describe('config-validator', () => {
|
|
|
76
92
|
});
|
|
77
93
|
|
|
78
94
|
it('無効なJSONの場合はエラーを返す', () => {
|
|
79
|
-
const configPath = join(testProjectRoot, '.
|
|
95
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
80
96
|
writeFileSync(configPath, '{ invalid json }');
|
|
81
97
|
|
|
82
98
|
const result = validateProjectConfig(testProjectRoot);
|
|
@@ -87,7 +103,7 @@ describe('config-validator', () => {
|
|
|
87
103
|
});
|
|
88
104
|
|
|
89
105
|
it('by-hierarchyモードでhierarchy設定がない場合はエラー', () => {
|
|
90
|
-
const configPath = join(testProjectRoot, '.
|
|
106
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
91
107
|
writeFileSync(configPath, JSON.stringify({
|
|
92
108
|
confluence: {
|
|
93
109
|
pageCreationGranularity: 'by-hierarchy'
|
|
@@ -102,7 +118,7 @@ describe('config-validator', () => {
|
|
|
102
118
|
});
|
|
103
119
|
|
|
104
120
|
it('selected-phasesモードでselectedPhases設定がない場合はエラー', () => {
|
|
105
|
-
const configPath = join(testProjectRoot, '.
|
|
121
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
106
122
|
writeFileSync(configPath, JSON.stringify({
|
|
107
123
|
jira: {
|
|
108
124
|
storyCreationGranularity: 'selected-phases'
|
|
@@ -122,7 +138,7 @@ describe('config-validator', () => {
|
|
|
122
138
|
// 環境変数をクリア
|
|
123
139
|
delete process.env.CONFLUENCE_PRD_SPACE;
|
|
124
140
|
|
|
125
|
-
const configPath = join(testProjectRoot, '.
|
|
141
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
126
142
|
writeFileSync(configPath, JSON.stringify({
|
|
127
143
|
confluence: {
|
|
128
144
|
pageCreationGranularity: 'single'
|
|
@@ -140,7 +156,7 @@ describe('config-validator', () => {
|
|
|
140
156
|
});
|
|
141
157
|
|
|
142
158
|
it('spaces設定がある場合は成功', () => {
|
|
143
|
-
const configPath = join(testProjectRoot, '.
|
|
159
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
144
160
|
writeFileSync(configPath, JSON.stringify({
|
|
145
161
|
confluence: {
|
|
146
162
|
spaces: {
|
|
@@ -158,7 +174,7 @@ describe('config-validator', () => {
|
|
|
158
174
|
it('by-hierarchyモードでhierarchy設定がない場合はエラー', () => {
|
|
159
175
|
// デフォルト設定を上書きするため、hierarchyキーを削除
|
|
160
176
|
// デフォルト設定にhierarchyがあるため、実際にはエラーにならない可能性がある
|
|
161
|
-
const configPath = join(testProjectRoot, '.
|
|
177
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
162
178
|
writeFileSync(configPath, JSON.stringify({
|
|
163
179
|
confluence: {
|
|
164
180
|
pageCreationGranularity: 'by-hierarchy',
|
|
@@ -177,7 +193,7 @@ describe('config-validator', () => {
|
|
|
177
193
|
});
|
|
178
194
|
|
|
179
195
|
it('manualモードでstructure設定がない場合はエラー', () => {
|
|
180
|
-
const configPath = join(testProjectRoot, '.
|
|
196
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
181
197
|
writeFileSync(configPath, JSON.stringify({
|
|
182
198
|
confluence: {
|
|
183
199
|
pageCreationGranularity: 'manual',
|
|
@@ -196,7 +212,7 @@ describe('config-validator', () => {
|
|
|
196
212
|
|
|
197
213
|
it('環境変数CONFLUENCE_PRD_SPACEがある場合は情報メッセージ', () => {
|
|
198
214
|
process.env.CONFLUENCE_PRD_SPACE = 'Michi';
|
|
199
|
-
const configPath = join(testProjectRoot, '.
|
|
215
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
200
216
|
writeFileSync(configPath, JSON.stringify({
|
|
201
217
|
confluence: {
|
|
202
218
|
pageCreationGranularity: 'single'
|
|
@@ -216,7 +232,7 @@ describe('config-validator', () => {
|
|
|
216
232
|
|
|
217
233
|
describe('validateForJiraSync', () => {
|
|
218
234
|
it('issueTypes.story設定がない場合はエラー', () => {
|
|
219
|
-
const configPath = join(testProjectRoot, '.
|
|
235
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
220
236
|
writeFileSync(configPath, JSON.stringify({
|
|
221
237
|
jira: {}
|
|
222
238
|
}));
|
|
@@ -229,7 +245,7 @@ describe('config-validator', () => {
|
|
|
229
245
|
});
|
|
230
246
|
|
|
231
247
|
it('issueTypes.story設定がある場合は成功', () => {
|
|
232
|
-
const configPath = join(testProjectRoot, '.
|
|
248
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
233
249
|
writeFileSync(configPath, JSON.stringify({
|
|
234
250
|
jira: {
|
|
235
251
|
issueTypes: {
|
|
@@ -246,7 +262,7 @@ describe('config-validator', () => {
|
|
|
246
262
|
|
|
247
263
|
it('環境変数JIRA_ISSUE_TYPE_STORYがある場合は情報メッセージ', () => {
|
|
248
264
|
process.env.JIRA_ISSUE_TYPE_STORY = '10036';
|
|
249
|
-
const configPath = join(testProjectRoot, '.
|
|
265
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
250
266
|
writeFileSync(configPath, JSON.stringify({
|
|
251
267
|
jira: {
|
|
252
268
|
createEpic: true
|
|
@@ -264,7 +280,7 @@ describe('config-validator', () => {
|
|
|
264
280
|
});
|
|
265
281
|
|
|
266
282
|
it('issueTypes.subtask設定がない場合は警告', () => {
|
|
267
|
-
const configPath = join(testProjectRoot, '.
|
|
283
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
268
284
|
writeFileSync(configPath, JSON.stringify({
|
|
269
285
|
jira: {
|
|
270
286
|
issueTypes: {
|
|
@@ -281,7 +297,7 @@ describe('config-validator', () => {
|
|
|
281
297
|
});
|
|
282
298
|
|
|
283
299
|
it('selected-phasesモードでselectedPhases設定がない場合はエラー', () => {
|
|
284
|
-
const configPath = join(testProjectRoot, '.
|
|
300
|
+
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
285
301
|
writeFileSync(configPath, JSON.stringify({
|
|
286
302
|
jira: {
|
|
287
303
|
storyCreationGranularity: 'selected-phases',
|
|
@@ -140,11 +140,32 @@ function validateConfigPath(configPath: string, projectRoot: string): boolean {
|
|
|
140
140
|
return !relativePath.startsWith('..') && !isAbsolute(relativePath);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/**
|
|
144
|
+
* 設定ファイルのパスを解決
|
|
145
|
+
* 新規パス: .michi/config.json
|
|
146
|
+
* legacyパス(.kiro/config.json)が存在する場合は警告のみ表示
|
|
147
|
+
*/
|
|
148
|
+
function resolveConfigPath(projectRoot: string): string {
|
|
149
|
+
const michiConfigPath = resolve(projectRoot, '.michi/config.json');
|
|
150
|
+
const legacyConfigPath = resolve(projectRoot, '.kiro/config.json');
|
|
151
|
+
|
|
152
|
+
// legacyパスが存在する場合は警告(移行推奨)
|
|
153
|
+
if (existsSync(legacyConfigPath) && !existsSync(michiConfigPath)) {
|
|
154
|
+
console.warn(
|
|
155
|
+
'⚠️ Deprecated: .kiro/config.json is deprecated.\n' +
|
|
156
|
+
' Please migrate to .michi/config.json\n' +
|
|
157
|
+
' The legacy path will not be supported in future versions.\n'
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return michiConfigPath;
|
|
162
|
+
}
|
|
163
|
+
|
|
143
164
|
/**
|
|
144
165
|
* プロジェクト固有設定を読み込む
|
|
145
166
|
*/
|
|
146
167
|
function loadProjectConfig(projectRoot: string = process.cwd()): Partial<AppConfig> | null {
|
|
147
|
-
const projectConfigPath =
|
|
168
|
+
const projectConfigPath = resolveConfigPath(projectRoot);
|
|
148
169
|
|
|
149
170
|
// パストラバーサル対策: パスを検証
|
|
150
171
|
if (!validateConfigPath(projectConfigPath, projectRoot)) {
|
|
@@ -247,7 +268,7 @@ let cachedConfigMtime: number | null = null;
|
|
|
247
268
|
let cachedDefaultConfigMtime: number | null = null;
|
|
248
269
|
|
|
249
270
|
export function getConfig(projectRoot: string = process.cwd()): AppConfig {
|
|
250
|
-
const projectConfigPath =
|
|
271
|
+
const projectConfigPath = resolveConfigPath(projectRoot);
|
|
251
272
|
const currentFileUrl = import.meta.url;
|
|
252
273
|
const currentFilePath = fileURLToPath(currentFileUrl);
|
|
253
274
|
const currentDir = resolve(currentFilePath, '..');
|
|
@@ -324,3 +345,10 @@ export function clearConfigCache(): void {
|
|
|
324
345
|
cachedDefaultConfigMtime = null;
|
|
325
346
|
}
|
|
326
347
|
|
|
348
|
+
/**
|
|
349
|
+
* 設定ファイルのパスを解決(外部から使用可能)
|
|
350
|
+
*/
|
|
351
|
+
export function getConfigPath(projectRoot: string = process.cwd()): string {
|
|
352
|
+
return resolveConfigPath(projectRoot);
|
|
353
|
+
}
|
|
354
|
+
|
|
@@ -7,7 +7,7 @@ import { resolve } from 'path';
|
|
|
7
7
|
import { AppConfigSchema } from '../config/config-schema.js';
|
|
8
8
|
import type { ZodIssue } from 'zod';
|
|
9
9
|
import type { AppConfig } from '../config/config-schema.js';
|
|
10
|
-
import { getConfig } from './config-loader.js';
|
|
10
|
+
import { getConfig, getConfigPath } from './config-loader.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* バリデーション結果
|
|
@@ -27,7 +27,7 @@ export function validateProjectConfig(projectRoot: string = process.cwd()): Vali
|
|
|
27
27
|
const warnings: string[] = [];
|
|
28
28
|
const info: string[] = [];
|
|
29
29
|
|
|
30
|
-
const configPath =
|
|
30
|
+
const configPath = getConfigPath(projectRoot);
|
|
31
31
|
|
|
32
32
|
if (!existsSync(configPath)) {
|
|
33
33
|
// 設定ファイルが存在しない場合は情報メッセージ(デフォルト設定を使用)
|
|
@@ -184,7 +184,7 @@ export function validateForConfluenceSync(
|
|
|
184
184
|
const info: string[] = [];
|
|
185
185
|
|
|
186
186
|
const config = getConfig(projectRoot);
|
|
187
|
-
const configPath =
|
|
187
|
+
const configPath = getConfigPath(projectRoot);
|
|
188
188
|
|
|
189
189
|
// Confluence設定のチェック
|
|
190
190
|
if (!config.confluence) {
|
|
@@ -198,7 +198,7 @@ export function validateForConfluenceSync(
|
|
|
198
198
|
warnings.push(
|
|
199
199
|
`confluence.spaces.${docType}が設定されていません。` +
|
|
200
200
|
'環境変数CONFLUENCE_PRD_SPACEも設定されていないため、デフォルト値(PRD)を使用します。' +
|
|
201
|
-
'\n 推奨: .
|
|
201
|
+
'\n 推奨: .michi/config.jsonに以下を追加してください:\n' +
|
|
202
202
|
' {\n' +
|
|
203
203
|
' "confluence": {\n' +
|
|
204
204
|
' "spaces": {\n' +
|
|
@@ -218,7 +218,7 @@ export function validateForConfluenceSync(
|
|
|
218
218
|
errors.push(
|
|
219
219
|
'confluence.hierarchyが設定されていません。' +
|
|
220
220
|
`pageCreationGranularityが"${confluence.pageCreationGranularity}"の場合、hierarchy設定が必須です。` +
|
|
221
|
-
'\n 解決方法: .
|
|
221
|
+
'\n 解決方法: .michi/config.jsonに以下を追加してください:\n' +
|
|
222
222
|
' {\n' +
|
|
223
223
|
' "confluence": {\n' +
|
|
224
224
|
' "hierarchy": {\n' +
|
|
@@ -263,7 +263,7 @@ export function validateForJiraSync(projectRoot: string = process.cwd()): Valida
|
|
|
263
263
|
const info: string[] = [];
|
|
264
264
|
|
|
265
265
|
const config = getConfig(projectRoot);
|
|
266
|
-
const configPath =
|
|
266
|
+
const configPath = getConfigPath(projectRoot);
|
|
267
267
|
|
|
268
268
|
// JIRA設定のチェック
|
|
269
269
|
if (!config.jira) {
|
|
@@ -279,7 +279,7 @@ export function validateForJiraSync(projectRoot: string = process.cwd()): Valida
|
|
|
279
279
|
'環境変数JIRA_ISSUE_TYPE_STORYも設定されていないため、JIRA同期を実行できません。' +
|
|
280
280
|
'\n 解決方法1: 環境変数を設定:\n' +
|
|
281
281
|
' export JIRA_ISSUE_TYPE_STORY=10036 # JIRAインスタンス固有のID\n' +
|
|
282
|
-
'\n 解決方法2: .
|
|
282
|
+
'\n 解決方法2: .michi/config.jsonに以下を追加:\n' +
|
|
283
283
|
' {\n' +
|
|
284
284
|
' "jira": {\n' +
|
|
285
285
|
' "issueTypes": {\n' +
|
|
@@ -299,7 +299,7 @@ export function validateForJiraSync(projectRoot: string = process.cwd()): Valida
|
|
|
299
299
|
errors.push(
|
|
300
300
|
'jira.issueTypes.storyが設定されていません。' +
|
|
301
301
|
'環境変数JIRA_ISSUE_TYPE_STORYも設定されていないため、JIRA同期を実行できません。' +
|
|
302
|
-
'\n 解決方法: .
|
|
302
|
+
'\n 解決方法: .michi/config.jsonのjira.issueTypes.storyに値を設定するか、' +
|
|
303
303
|
'環境変数JIRA_ISSUE_TYPE_STORYを設定してください。'
|
|
304
304
|
);
|
|
305
305
|
} else {
|