@sk8metal/michi-cli 0.0.7 → 0.0.9
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 +55 -0
- package/README.md +205 -11
- package/dist/scripts/__tests__/create-project.test.d.ts +2 -0
- package/dist/scripts/__tests__/create-project.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/create-project.test.js +247 -0
- package/dist/scripts/__tests__/create-project.test.js.map +1 -0
- package/dist/scripts/__tests__/multi-project-estimate.test.d.ts +2 -0
- package/dist/scripts/__tests__/multi-project-estimate.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/multi-project-estimate.test.js +119 -0
- package/dist/scripts/__tests__/multi-project-estimate.test.js.map +1 -0
- package/dist/scripts/__tests__/setup-existing-project.test.d.ts +2 -0
- package/dist/scripts/__tests__/setup-existing-project.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/setup-existing-project.test.js +129 -0
- package/dist/scripts/__tests__/setup-existing-project.test.js.map +1 -0
- package/dist/scripts/__tests__/setup-interactive.test.d.ts +2 -0
- package/dist/scripts/__tests__/setup-interactive.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/setup-interactive.test.js +160 -0
- package/dist/scripts/__tests__/setup-interactive.test.js.map +1 -0
- package/dist/scripts/config/default-config.json +57 -0
- package/dist/scripts/confluence-sync.d.ts +4 -0
- package/dist/scripts/confluence-sync.d.ts.map +1 -1
- package/dist/scripts/confluence-sync.js +12 -23
- package/dist/scripts/confluence-sync.js.map +1 -1
- package/dist/scripts/constants/__tests__/environments.test.d.ts +2 -0
- package/dist/scripts/constants/__tests__/environments.test.d.ts.map +1 -0
- package/dist/scripts/constants/__tests__/environments.test.js +91 -0
- package/dist/scripts/constants/__tests__/environments.test.js.map +1 -0
- package/dist/scripts/constants/__tests__/languages.test.d.ts +2 -0
- package/dist/scripts/constants/__tests__/languages.test.d.ts.map +1 -0
- package/dist/scripts/constants/__tests__/languages.test.js +82 -0
- package/dist/scripts/constants/__tests__/languages.test.js.map +1 -0
- package/dist/scripts/constants/environments.d.ts +33 -0
- package/dist/scripts/constants/environments.d.ts.map +1 -0
- package/dist/scripts/constants/environments.js +49 -0
- package/dist/scripts/constants/environments.js.map +1 -0
- package/dist/scripts/constants/languages.d.ts +23 -0
- package/dist/scripts/constants/languages.d.ts.map +1 -0
- package/dist/scripts/constants/languages.js +53 -0
- package/dist/scripts/constants/languages.js.map +1 -0
- package/dist/scripts/create-project.d.ts +4 -0
- package/dist/scripts/create-project.d.ts.map +1 -1
- package/dist/scripts/create-project.js +227 -137
- package/dist/scripts/create-project.js.map +1 -1
- package/dist/scripts/jira-sync.d.ts.map +1 -1
- package/dist/scripts/jira-sync.js +15 -0
- package/dist/scripts/jira-sync.js.map +1 -1
- package/dist/scripts/list-projects.d.ts.map +1 -1
- package/dist/scripts/list-projects.js +42 -15
- package/dist/scripts/list-projects.js.map +1 -1
- package/dist/scripts/multi-project-estimate.d.ts.map +1 -1
- package/dist/scripts/multi-project-estimate.js +56 -21
- package/dist/scripts/multi-project-estimate.js.map +1 -1
- package/dist/scripts/resource-dashboard.d.ts.map +1 -1
- package/dist/scripts/resource-dashboard.js +74 -17
- package/dist/scripts/resource-dashboard.js.map +1 -1
- package/dist/scripts/setup-existing-project.d.ts +3 -1
- package/dist/scripts/setup-existing-project.d.ts.map +1 -1
- package/dist/scripts/setup-existing-project.js +306 -217
- package/dist/scripts/setup-existing-project.js.map +1 -1
- package/dist/scripts/setup-interactive.d.ts +10 -0
- package/dist/scripts/setup-interactive.d.ts.map +1 -0
- package/dist/scripts/setup-interactive.js +413 -0
- package/dist/scripts/setup-interactive.js.map +1 -0
- package/dist/scripts/template/__tests__/renderer.test.d.ts +2 -0
- package/dist/scripts/template/__tests__/renderer.test.d.ts.map +1 -0
- package/dist/scripts/template/__tests__/renderer.test.js +165 -0
- package/dist/scripts/template/__tests__/renderer.test.js.map +1 -0
- package/dist/scripts/template/renderer.d.ts +70 -0
- package/dist/scripts/template/renderer.d.ts.map +1 -0
- package/dist/scripts/template/renderer.js +99 -0
- package/dist/scripts/template/renderer.js.map +1 -0
- package/dist/scripts/utils/__tests__/config-validator.test.js +5 -0
- package/dist/scripts/utils/__tests__/config-validator.test.js.map +1 -1
- package/dist/scripts/utils/__tests__/spec-updater.test.d.ts +5 -0
- package/dist/scripts/utils/__tests__/spec-updater.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/spec-updater.test.js +158 -0
- package/dist/scripts/utils/__tests__/spec-updater.test.js.map +1 -0
- package/dist/scripts/utils/confluence-hierarchy.d.ts +2 -1
- package/dist/scripts/utils/confluence-hierarchy.d.ts.map +1 -1
- package/dist/scripts/utils/confluence-hierarchy.js +5 -0
- package/dist/scripts/utils/confluence-hierarchy.js.map +1 -1
- package/dist/scripts/utils/project-finder.d.ts +30 -0
- package/dist/scripts/utils/project-finder.d.ts.map +1 -0
- package/dist/scripts/utils/project-finder.js +147 -0
- package/dist/scripts/utils/project-finder.js.map +1 -0
- package/dist/scripts/utils/spec-updater.d.ts +72 -0
- package/dist/scripts/utils/spec-updater.d.ts.map +1 -0
- package/dist/scripts/utils/spec-updater.js +141 -0
- package/dist/scripts/utils/spec-updater.js.map +1 -0
- package/dist/scripts/utils/template-finder.d.ts +37 -0
- package/dist/scripts/utils/template-finder.d.ts.map +1 -0
- package/dist/scripts/utils/template-finder.js +63 -0
- package/dist/scripts/utils/template-finder.js.map +1 -0
- package/dist/src/__tests__/integration/setup/claude-agent.test.d.ts +5 -0
- package/dist/src/__tests__/integration/setup/claude-agent.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/claude-agent.test.js +125 -0
- package/dist/src/__tests__/integration/setup/claude-agent.test.js.map +1 -0
- package/dist/src/__tests__/integration/setup/claude.test.d.ts +5 -0
- package/dist/src/__tests__/integration/setup/claude.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/claude.test.js +111 -0
- package/dist/src/__tests__/integration/setup/claude.test.js.map +1 -0
- package/dist/src/__tests__/integration/setup/cursor.test.d.ts +5 -0
- package/dist/src/__tests__/integration/setup/cursor.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/cursor.test.js +162 -0
- package/dist/src/__tests__/integration/setup/cursor.test.js.map +1 -0
- package/dist/src/__tests__/integration/setup/helpers/fs-assertions.d.ts +32 -0
- package/dist/src/__tests__/integration/setup/helpers/fs-assertions.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/helpers/fs-assertions.js +72 -0
- package/dist/src/__tests__/integration/setup/helpers/fs-assertions.js.map +1 -0
- package/dist/src/__tests__/integration/setup/helpers/test-project.d.ts +38 -0
- package/dist/src/__tests__/integration/setup/helpers/test-project.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/helpers/test-project.js +83 -0
- package/dist/src/__tests__/integration/setup/helpers/test-project.js.map +1 -0
- package/dist/src/__tests__/integration/setup/validation.test.d.ts +5 -0
- package/dist/src/__tests__/integration/setup/validation.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/validation.test.js +318 -0
- package/dist/src/__tests__/integration/setup/validation.test.js.map +1 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +20 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/setup-existing.d.ts +22 -0
- package/dist/src/commands/setup-existing.d.ts.map +1 -0
- package/dist/src/commands/setup-existing.js +408 -0
- package/dist/src/commands/setup-existing.js.map +1 -0
- package/dist/vitest.config.d.ts.map +1 -1
- package/dist/vitest.config.js +9 -6
- package/dist/vitest.config.js.map +1 -1
- package/docs/README.md +2 -2
- package/docs/contributing/development.md +37 -0
- package/docs/getting-started/{new-project-setup.md → new-repository-setup.md} +165 -38
- package/docs/getting-started/quick-start.md +57 -6
- package/docs/getting-started/setup.md +551 -180
- package/docs/guides/customization.md +4 -4
- package/docs/guides/multi-project.md +12 -9
- package/docs/reference/quick-reference.md +27 -18
- package/docs/testing/integration-tests.md +297 -0
- package/docs/testing-strategy.md +87 -0
- package/package.json +23 -6
- package/scripts/__tests__/create-project.test.ts +292 -0
- package/scripts/__tests__/multi-project-estimate.test.ts +145 -0
- package/scripts/__tests__/setup-existing-project.test.ts +147 -0
- package/scripts/__tests__/setup-interactive.test.ts +199 -0
- package/scripts/confluence-sync.ts +17 -29
- package/scripts/constants/__tests__/environments.test.ts +110 -0
- package/scripts/constants/__tests__/languages.test.ts +100 -0
- package/scripts/constants/environments.ts +61 -0
- package/scripts/constants/languages.ts +70 -0
- package/scripts/copy-static-assets.js +50 -0
- package/scripts/create-project.ts +251 -158
- package/scripts/jira-sync.ts +16 -1
- package/scripts/list-projects.ts +51 -24
- package/scripts/multi-project-estimate.ts +58 -22
- package/scripts/resource-dashboard.ts +91 -26
- package/scripts/setup-existing-project.ts +350 -230
- package/scripts/setup-existing.sh +159 -25
- package/scripts/setup-interactive.ts +565 -0
- package/scripts/template/__tests__/renderer.test.ts +207 -0
- package/scripts/template/renderer.ts +133 -0
- package/scripts/utils/__tests__/config-validator.test.ts +6 -0
- package/scripts/utils/__tests__/spec-updater.test.ts +220 -0
- package/scripts/utils/confluence-hierarchy.ts +7 -1
- package/scripts/utils/project-finder.ts +184 -0
- package/scripts/utils/spec-updater.ts +212 -0
- package/scripts/utils/template-finder.ts +75 -0
- package/templates/claude/commands/michi/confluence-sync.md +38 -0
- package/templates/claude/commands/michi/project-switch.md +36 -0
- package/templates/claude/rules/atlassian-integration.md +35 -0
- package/templates/claude/rules/michi-core.md +54 -0
- package/templates/claude-agent/README.md +25 -0
- package/templates/cursor/commands/michi/confluence-sync.md +76 -0
- package/templates/cursor/commands/michi/project-switch.md +69 -0
- package/templates/cursor/rules/atlassian-mcp.mdc +188 -0
- package/templates/cursor/rules/github-ssot.mdc +151 -0
- package/templates/cursor/rules/multi-project.mdc +81 -0
- package/scripts/setup-env.sh +0 -52
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { basename } from 'path';
|
|
3
|
+
|
|
4
|
+
// validateProjectId関数のロジックを再現
|
|
5
|
+
function validateProjectId(projectId: string): boolean {
|
|
6
|
+
// 空文字、空白のみを拒否
|
|
7
|
+
if (!projectId.trim() || /^\s+$/.test(projectId)) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
// パストラバーサル文字を拒否
|
|
11
|
+
if (projectId.includes('..') || projectId.includes('/') || projectId.includes('\\')) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
// 許可する文字のみ(英数字、ハイフン、アンダースコア)
|
|
15
|
+
return /^[A-Za-z0-9_-]+$/.test(projectId);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('setup-interactive.ts 修正内容のテスト', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
vi.clearAllMocks();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('validateProjectId の呼び出し', () => {
|
|
24
|
+
it('getProjectMetadata 内で validateProjectId が呼び出される', () => {
|
|
25
|
+
const projectIdDefault = 'test-project';
|
|
26
|
+
const projectId = projectIdDefault;
|
|
27
|
+
|
|
28
|
+
// バリデーション(getProjectMetadata内のロジックを再現)
|
|
29
|
+
if (!validateProjectId(projectId)) {
|
|
30
|
+
throw new Error('無効なプロジェクトIDです。英数字、ハイフン、アンダースコアのみ使用できます。');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 有効なプロジェクトIDはエラーを投げない
|
|
34
|
+
expect(projectId).toBe('test-project');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('無効なプロジェクトID(パストラバーサル)は拒否される', () => {
|
|
38
|
+
const invalidIds = ['../test', 'test/../', 'test\\..', '../../'];
|
|
39
|
+
|
|
40
|
+
invalidIds.forEach(id => {
|
|
41
|
+
expect(validateProjectId(id)).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('無効なプロジェクトID(特殊文字)は拒否される', () => {
|
|
46
|
+
const invalidIds = ['test@project', 'test#project', 'test$project', 'test project'];
|
|
47
|
+
|
|
48
|
+
invalidIds.forEach(id => {
|
|
49
|
+
expect(validateProjectId(id)).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('有効なプロジェクトIDは受け入れられる', () => {
|
|
54
|
+
const validIds = ['test-project', 'test_project', 'test123', 'Test-Project_123'];
|
|
55
|
+
|
|
56
|
+
validIds.forEach(id => {
|
|
57
|
+
expect(validateProjectId(id)).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('main 関数の戻り値', () => {
|
|
63
|
+
it('main 関数は Promise<number> を返す', async () => {
|
|
64
|
+
// main関数のシグネチャを再現
|
|
65
|
+
async function main(): Promise<number> {
|
|
66
|
+
// 正常終了
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const result = await main();
|
|
71
|
+
expect(typeof result).toBe('number');
|
|
72
|
+
expect(result).toBe(0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('エラー時は 1 を返す', async () => {
|
|
76
|
+
// main関数のエラーハンドリングを再現
|
|
77
|
+
async function main(): Promise<number> {
|
|
78
|
+
try {
|
|
79
|
+
throw new Error('テストエラー');
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const result = await main();
|
|
86
|
+
expect(result).toBe(1);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('早期終了時は 0 を返す', async () => {
|
|
90
|
+
// 早期終了のロジックを再現
|
|
91
|
+
async function main(): Promise<number> {
|
|
92
|
+
// 設定する項目がない場合
|
|
93
|
+
const configureProject = false;
|
|
94
|
+
const configureEnv = false;
|
|
95
|
+
|
|
96
|
+
if (!configureProject && !configureEnv) {
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const result = await main();
|
|
104
|
+
expect(result).toBe(0);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('process.exit の削除', () => {
|
|
109
|
+
it('process.exit の代わりに return を使用する', () => {
|
|
110
|
+
// 修正前: process.exit(0)
|
|
111
|
+
// 修正後: return 0
|
|
112
|
+
|
|
113
|
+
function oldStyle(): void {
|
|
114
|
+
// process.exit(0); // 削除
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function newStyle(): number {
|
|
118
|
+
return 0; // 新しいスタイル
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const result = newStyle();
|
|
122
|
+
expect(result).toBe(0);
|
|
123
|
+
expect(typeof result).toBe('number');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('エラー時も process.exit の代わりに return を使用する', () => {
|
|
127
|
+
// 修正前: process.exit(1)
|
|
128
|
+
// 修正後: return 1
|
|
129
|
+
|
|
130
|
+
function oldStyle(): void {
|
|
131
|
+
// process.exit(1); // 削除
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function newStyle(): number {
|
|
135
|
+
return 1; // 新しいスタイル
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const result = newStyle();
|
|
139
|
+
expect(result).toBe(1);
|
|
140
|
+
expect(typeof result).toBe('number');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('CLIエントリーポイントでの process.exit 呼び出し', () => {
|
|
145
|
+
it('main() の戻り値を受け取って process.exit を呼び出す', async () => {
|
|
146
|
+
// CLIエントリーポイントのロジックを再現
|
|
147
|
+
let exitCode: number | null = null;
|
|
148
|
+
|
|
149
|
+
const mockProcessExit = (code: number) => {
|
|
150
|
+
exitCode = code;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
async function main(): Promise<number> {
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// CLIエントリーポイント
|
|
158
|
+
main()
|
|
159
|
+
.then(code => {
|
|
160
|
+
mockProcessExit(code);
|
|
161
|
+
})
|
|
162
|
+
.catch(() => {
|
|
163
|
+
mockProcessExit(1);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// 非同期処理を待つ
|
|
167
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
168
|
+
|
|
169
|
+
expect(exitCode).toBe(0);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('エラー時は 1 で終了する', async () => {
|
|
173
|
+
let exitCode: number | null = null;
|
|
174
|
+
|
|
175
|
+
const mockProcessExit = (code: number) => {
|
|
176
|
+
exitCode = code;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
async function main(): Promise<number> {
|
|
180
|
+
throw new Error('テストエラー');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// CLIエントリーポイント
|
|
184
|
+
main()
|
|
185
|
+
.then(code => {
|
|
186
|
+
mockProcessExit(code);
|
|
187
|
+
})
|
|
188
|
+
.catch(() => {
|
|
189
|
+
mockProcessExit(1);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// 非同期処理を待つ
|
|
193
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
194
|
+
|
|
195
|
+
expect(exitCode).toBe(1);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
* GitHub の Markdown ファイルを Confluence に同期
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { readFileSync
|
|
7
|
-
import { resolve
|
|
6
|
+
import { readFileSync } from 'fs';
|
|
7
|
+
import { resolve } from 'path';
|
|
8
8
|
import axios from 'axios';
|
|
9
9
|
import { config } from 'dotenv';
|
|
10
|
-
import { loadProjectMeta
|
|
11
|
-
import { convertMarkdownToConfluence, createConfluencePage } from './markdown-to-confluence.js';
|
|
10
|
+
import { loadProjectMeta } from './utils/project-meta.js';
|
|
12
11
|
import { validateFeatureNameOrThrow } from './utils/feature-name-validator.js';
|
|
13
12
|
import { getConfig, getConfigPath } from './utils/config-loader.js';
|
|
14
13
|
import { createPagesByGranularity } from './utils/confluence-hierarchy.js';
|
|
15
14
|
import { validateForConfluenceSync } from './utils/config-validator.js';
|
|
15
|
+
import { updateSpecJsonAfterConfluenceSync, loadSpecJson } from './utils/spec-updater.js';
|
|
16
16
|
|
|
17
17
|
// 環境変数読み込み
|
|
18
18
|
config();
|
|
@@ -42,7 +42,7 @@ interface ConfluenceConfig {
|
|
|
42
42
|
/**
|
|
43
43
|
* Confluence設定を環境変数から取得
|
|
44
44
|
*/
|
|
45
|
-
function getConfluenceConfig(): ConfluenceConfig {
|
|
45
|
+
export function getConfluenceConfig(): ConfluenceConfig {
|
|
46
46
|
const url = process.env.ATLASSIAN_URL;
|
|
47
47
|
const email = process.env.ATLASSIAN_EMAIL;
|
|
48
48
|
const apiToken = process.env.ATLASSIAN_API_TOKEN;
|
|
@@ -312,27 +312,6 @@ class ConfluenceClient {
|
|
|
312
312
|
}
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
-
/**
|
|
316
|
-
* spec.jsonを読み込む
|
|
317
|
-
* @param featureName 機能名
|
|
318
|
-
* @param projectRoot プロジェクトルート(デフォルト: process.cwd())
|
|
319
|
-
* @returns spec.jsonの内容、存在しない場合はnull
|
|
320
|
-
*/
|
|
321
|
-
function loadSpecJson(featureName: string, projectRoot: string = process.cwd()): any | null {
|
|
322
|
-
const specPath = resolve(projectRoot, `.kiro/specs/${featureName}/spec.json`);
|
|
323
|
-
|
|
324
|
-
if (!existsSync(specPath)) {
|
|
325
|
-
return null;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
try {
|
|
329
|
-
const content = readFileSync(specPath, 'utf-8');
|
|
330
|
-
return JSON.parse(content);
|
|
331
|
-
} catch (error) {
|
|
332
|
-
console.warn(`⚠️ Failed to load spec.json from ${specPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
333
|
-
return null;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
315
|
|
|
337
316
|
/**
|
|
338
317
|
* Markdownファイルを Confluence に同期
|
|
@@ -408,7 +387,7 @@ async function syncToConfluence(
|
|
|
408
387
|
let spaceKey: string;
|
|
409
388
|
let spaceKeySource: string;
|
|
410
389
|
|
|
411
|
-
if (specJson
|
|
390
|
+
if (specJson.confluence?.spaceKey) {
|
|
412
391
|
spaceKey = specJson.confluence.spaceKey;
|
|
413
392
|
spaceKeySource = 'spec.json';
|
|
414
393
|
} else if (confluenceConfig.spaces?.[docType]) {
|
|
@@ -444,14 +423,23 @@ async function syncToConfluence(
|
|
|
444
423
|
|
|
445
424
|
const firstPageUrl = result.pages[0].url;
|
|
446
425
|
console.log(`✅ Sync completed: ${result.pages.length} page(s) created/updated`);
|
|
447
|
-
|
|
426
|
+
|
|
448
427
|
if (result.pages.length > 1) {
|
|
449
428
|
console.log('📄 Created pages:');
|
|
450
429
|
result.pages.forEach((page, index) => {
|
|
451
430
|
console.log(` ${index + 1}. ${page.title} - ${page.url}`);
|
|
452
431
|
});
|
|
453
432
|
}
|
|
454
|
-
|
|
433
|
+
|
|
434
|
+
// spec.json を更新
|
|
435
|
+
const firstPage = result.pages[0];
|
|
436
|
+
updateSpecJsonAfterConfluenceSync(featureName, docType, {
|
|
437
|
+
pageId: firstPage.id,
|
|
438
|
+
url: firstPage.url,
|
|
439
|
+
title: firstPage.title,
|
|
440
|
+
spaceKey: spaceKey
|
|
441
|
+
});
|
|
442
|
+
|
|
455
443
|
return firstPageUrl;
|
|
456
444
|
}
|
|
457
445
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
ENV_CONFIG,
|
|
4
|
+
getEnvironmentConfig,
|
|
5
|
+
isSupportedEnvironment,
|
|
6
|
+
getSupportedEnvironments,
|
|
7
|
+
type Environment,
|
|
8
|
+
type EnvironmentConfig
|
|
9
|
+
} from '../environments.js';
|
|
10
|
+
|
|
11
|
+
describe('environments', () => {
|
|
12
|
+
describe('ENV_CONFIG', () => {
|
|
13
|
+
it('should have entries for all environments', () => {
|
|
14
|
+
expect(ENV_CONFIG.claude).toBeDefined();
|
|
15
|
+
expect(ENV_CONFIG['claude-agent']).toBeDefined();
|
|
16
|
+
expect(ENV_CONFIG.cursor).toBeDefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should have correct structure for claude', () => {
|
|
20
|
+
const config = ENV_CONFIG.claude;
|
|
21
|
+
expect(config.rulesDir).toBe('.claude/rules');
|
|
22
|
+
expect(config.commandsDir).toBe('.claude/commands/kiro');
|
|
23
|
+
expect(config.templateSource).toBe('claude');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should have correct structure for claude-agent', () => {
|
|
27
|
+
const config = ENV_CONFIG['claude-agent'];
|
|
28
|
+
expect(config.rulesDir).toBe('.claude/rules');
|
|
29
|
+
expect(config.commandsDir).toBe('.claude/commands/kiro');
|
|
30
|
+
expect(config.templateSource).toBe('claude-agent');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should have correct structure for cursor', () => {
|
|
34
|
+
const config = ENV_CONFIG.cursor;
|
|
35
|
+
expect(config.rulesDir).toBe('.cursor/rules');
|
|
36
|
+
expect(config.commandsDir).toBe('.cursor/commands/kiro');
|
|
37
|
+
expect(config.templateSource).toBe('cursor');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should have all required properties for each environment', () => {
|
|
41
|
+
for (const env of Object.keys(ENV_CONFIG) as Environment[]) {
|
|
42
|
+
const config = ENV_CONFIG[env];
|
|
43
|
+
expect(config).toHaveProperty('rulesDir');
|
|
44
|
+
expect(config).toHaveProperty('commandsDir');
|
|
45
|
+
expect(config).toHaveProperty('templateSource');
|
|
46
|
+
expect(typeof config.rulesDir).toBe('string');
|
|
47
|
+
expect(typeof config.commandsDir).toBe('string');
|
|
48
|
+
expect(typeof config.templateSource).toBe('string');
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('getEnvironmentConfig', () => {
|
|
54
|
+
it('should return config for valid environment', () => {
|
|
55
|
+
const config = getEnvironmentConfig('claude');
|
|
56
|
+
expect(config).toBe(ENV_CONFIG.claude);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return config for all supported environments', () => {
|
|
60
|
+
const environments: Environment[] = ['claude', 'claude-agent', 'cursor'];
|
|
61
|
+
for (const env of environments) {
|
|
62
|
+
const config = getEnvironmentConfig(env);
|
|
63
|
+
expect(config).toBeDefined();
|
|
64
|
+
expect(config).toBe(ENV_CONFIG[env]);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('isSupportedEnvironment', () => {
|
|
70
|
+
it('should return true for supported environments', () => {
|
|
71
|
+
expect(isSupportedEnvironment('claude')).toBe(true);
|
|
72
|
+
expect(isSupportedEnvironment('claude-agent')).toBe(true);
|
|
73
|
+
expect(isSupportedEnvironment('cursor')).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should return false for unsupported environments', () => {
|
|
77
|
+
expect(isSupportedEnvironment('invalid')).toBe(false);
|
|
78
|
+
expect(isSupportedEnvironment('vscode')).toBe(false);
|
|
79
|
+
expect(isSupportedEnvironment('')).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should work as type guard', () => {
|
|
83
|
+
const env: string = 'cursor';
|
|
84
|
+
if (isSupportedEnvironment(env)) {
|
|
85
|
+
// TypeScript should recognize env as Environment here
|
|
86
|
+
const config: EnvironmentConfig = ENV_CONFIG[env];
|
|
87
|
+
expect(config).toBeDefined();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('getSupportedEnvironments', () => {
|
|
93
|
+
it('should return all supported environments', () => {
|
|
94
|
+
const environments = getSupportedEnvironments();
|
|
95
|
+
expect(environments).toHaveLength(3);
|
|
96
|
+
expect(environments).toContain('claude');
|
|
97
|
+
expect(environments).toContain('claude-agent');
|
|
98
|
+
expect(environments).toContain('cursor');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should return array with correct type', () => {
|
|
102
|
+
const environments = getSupportedEnvironments();
|
|
103
|
+
expect(Array.isArray(environments)).toBe(true);
|
|
104
|
+
environments.forEach(env => {
|
|
105
|
+
expect(isSupportedEnvironment(env)).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
supportedLanguages,
|
|
4
|
+
DEV_GUIDELINES_MAP,
|
|
5
|
+
getDevGuidelines,
|
|
6
|
+
isSupportedLanguage,
|
|
7
|
+
type SupportedLanguage
|
|
8
|
+
} from '../languages.js';
|
|
9
|
+
|
|
10
|
+
describe('languages', () => {
|
|
11
|
+
describe('supportedLanguages', () => {
|
|
12
|
+
it('should contain 12 languages', () => {
|
|
13
|
+
expect(supportedLanguages).toHaveLength(12);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should include expected languages', () => {
|
|
17
|
+
expect(supportedLanguages).toContain('ja');
|
|
18
|
+
expect(supportedLanguages).toContain('en');
|
|
19
|
+
expect(supportedLanguages).toContain('zh-TW');
|
|
20
|
+
expect(supportedLanguages).toContain('zh');
|
|
21
|
+
expect(supportedLanguages).toContain('es');
|
|
22
|
+
expect(supportedLanguages).toContain('pt');
|
|
23
|
+
expect(supportedLanguages).toContain('de');
|
|
24
|
+
expect(supportedLanguages).toContain('fr');
|
|
25
|
+
expect(supportedLanguages).toContain('ru');
|
|
26
|
+
expect(supportedLanguages).toContain('it');
|
|
27
|
+
expect(supportedLanguages).toContain('ko');
|
|
28
|
+
expect(supportedLanguages).toContain('ar');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('DEV_GUIDELINES_MAP', () => {
|
|
33
|
+
it('should have entries for all supported languages', () => {
|
|
34
|
+
for (const lang of supportedLanguages) {
|
|
35
|
+
expect(DEV_GUIDELINES_MAP[lang]).toBeDefined();
|
|
36
|
+
expect(DEV_GUIDELINES_MAP[lang]).not.toBe('');
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should have English guideline', () => {
|
|
41
|
+
expect(DEV_GUIDELINES_MAP.en).toBe('- Think in English, generate responses in English');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should have Japanese guideline with bilingual instruction', () => {
|
|
45
|
+
expect(DEV_GUIDELINES_MAP.ja).toContain('Think in English');
|
|
46
|
+
expect(DEV_GUIDELINES_MAP.ja).toContain('日本語');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should contain bilingual instructions', () => {
|
|
50
|
+
// Most languages should mention "Think in English" in some form
|
|
51
|
+
const languagesWithEnglishInstruction = ['ja', 'en', 'es', 'pt', 'de', 'fr', 'ru', 'it', 'ko', 'ar'];
|
|
52
|
+
for (const lang of languagesWithEnglishInstruction) {
|
|
53
|
+
expect(DEV_GUIDELINES_MAP[lang as SupportedLanguage]).toContain('Think in English');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Chinese languages have their own format
|
|
57
|
+
expect(DEV_GUIDELINES_MAP['zh-TW']).toContain('以英文思考');
|
|
58
|
+
expect(DEV_GUIDELINES_MAP.zh).toContain('以英文思考');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('getDevGuidelines', () => {
|
|
63
|
+
it('should return guidelines for valid language', () => {
|
|
64
|
+
const guidelines = getDevGuidelines('ja');
|
|
65
|
+
expect(guidelines).toBe(DEV_GUIDELINES_MAP.ja);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should return guidelines for all supported languages', () => {
|
|
69
|
+
for (const lang of supportedLanguages) {
|
|
70
|
+
const guidelines = getDevGuidelines(lang);
|
|
71
|
+
expect(guidelines).toBeDefined();
|
|
72
|
+
expect(guidelines).toBe(DEV_GUIDELINES_MAP[lang]);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('isSupportedLanguage', () => {
|
|
78
|
+
it('should return true for supported languages', () => {
|
|
79
|
+
expect(isSupportedLanguage('ja')).toBe(true);
|
|
80
|
+
expect(isSupportedLanguage('en')).toBe(true);
|
|
81
|
+
expect(isSupportedLanguage('zh-TW')).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should return false for unsupported languages', () => {
|
|
85
|
+
expect(isSupportedLanguage('xx')).toBe(false);
|
|
86
|
+
expect(isSupportedLanguage('invalid')).toBe(false);
|
|
87
|
+
expect(isSupportedLanguage('')).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should work as type guard', () => {
|
|
91
|
+
const lang: string = 'ja';
|
|
92
|
+
if (isSupportedLanguage(lang)) {
|
|
93
|
+
// TypeScript should recognize lang as SupportedLanguage here
|
|
94
|
+
const guidelines: string = DEV_GUIDELINES_MAP[lang];
|
|
95
|
+
expect(guidelines).toBeDefined();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment configuration mapping for cc-sdd environments
|
|
3
|
+
*
|
|
4
|
+
* Issue #37: 環境別コピー実装
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface EnvironmentConfig {
|
|
8
|
+
rulesDir: string;
|
|
9
|
+
commandsDir: string;
|
|
10
|
+
templateSource: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type Environment = 'claude' | 'claude-agent' | 'cursor';
|
|
14
|
+
|
|
15
|
+
export const ENV_CONFIG: Record<Environment, EnvironmentConfig> = {
|
|
16
|
+
'claude': {
|
|
17
|
+
rulesDir: '.claude/rules',
|
|
18
|
+
commandsDir: '.claude/commands/kiro',
|
|
19
|
+
templateSource: 'claude'
|
|
20
|
+
},
|
|
21
|
+
'claude-agent': {
|
|
22
|
+
rulesDir: '.claude/rules',
|
|
23
|
+
commandsDir: '.claude/commands/kiro',
|
|
24
|
+
templateSource: 'claude-agent'
|
|
25
|
+
},
|
|
26
|
+
'cursor': {
|
|
27
|
+
rulesDir: '.cursor/rules',
|
|
28
|
+
commandsDir: '.cursor/commands/kiro',
|
|
29
|
+
templateSource: 'cursor'
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get environment configuration
|
|
35
|
+
*
|
|
36
|
+
* @param env - Environment name
|
|
37
|
+
* @returns Environment configuration
|
|
38
|
+
*/
|
|
39
|
+
export const getEnvironmentConfig = (env: Environment): EnvironmentConfig => {
|
|
40
|
+
return ENV_CONFIG[env];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Validate if an environment is supported
|
|
45
|
+
*
|
|
46
|
+
* @param env - Environment name to validate
|
|
47
|
+
* @returns True if supported, false otherwise
|
|
48
|
+
*/
|
|
49
|
+
export const isSupportedEnvironment = (env: string): env is Environment => {
|
|
50
|
+
return env in ENV_CONFIG;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get all supported environments
|
|
55
|
+
*
|
|
56
|
+
* @returns Array of supported environment names
|
|
57
|
+
*/
|
|
58
|
+
export const getSupportedEnvironments = (): Environment[] => {
|
|
59
|
+
return Object.keys(ENV_CONFIG) as Environment[];
|
|
60
|
+
};
|
|
61
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported languages and development guidelines map
|
|
3
|
+
*
|
|
4
|
+
* Issue #37: 環境別コピー実装
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const supportedLanguages = [
|
|
8
|
+
'ja', 'en', 'zh-TW', 'zh', 'es', 'pt',
|
|
9
|
+
'de', 'fr', 'ru', 'it', 'ko', 'ar'
|
|
10
|
+
] as const;
|
|
11
|
+
|
|
12
|
+
export type SupportedLanguage = typeof supportedLanguages[number];
|
|
13
|
+
|
|
14
|
+
export const DEV_GUIDELINES_MAP: Record<SupportedLanguage, string> = {
|
|
15
|
+
en: '- Think in English, generate responses in English',
|
|
16
|
+
|
|
17
|
+
ja: '- Think in English, but generate responses in Japanese ' +
|
|
18
|
+
'(思考は英語、回答の生成は日本語で行うように)',
|
|
19
|
+
|
|
20
|
+
'zh-TW': '- 以英文思考,但以繁體中文生成回應' +
|
|
21
|
+
'(Think in English, generate in Traditional Chinese)',
|
|
22
|
+
|
|
23
|
+
zh: '- 以英文思考,但以简体中文生成回复' +
|
|
24
|
+
'(Think in English, generate in Simplified Chinese)',
|
|
25
|
+
|
|
26
|
+
es: '- Think in English, generate responses in Spanish ' +
|
|
27
|
+
'(Piensa en inglés, genera respuestas en español)',
|
|
28
|
+
|
|
29
|
+
pt: '- Think in English, generate responses in Portuguese ' +
|
|
30
|
+
'(Pense em inglês, gere respostas em português)',
|
|
31
|
+
|
|
32
|
+
de: '- Think in English, generate responses in German ' +
|
|
33
|
+
'(Denke auf Englisch, formuliere Antworten auf Deutsch)',
|
|
34
|
+
|
|
35
|
+
fr: '- Think in English, generate responses in French ' +
|
|
36
|
+
'(Pensez en anglais, générez des réponses en français)',
|
|
37
|
+
|
|
38
|
+
ru: '- Think in English, generate responses in Russian ' +
|
|
39
|
+
'(Думай по-английски, отвечай по-русски)',
|
|
40
|
+
|
|
41
|
+
it: '- Think in English, generate responses in Italian ' +
|
|
42
|
+
'(Pensa in inglese, genera risposte in italiano)',
|
|
43
|
+
|
|
44
|
+
ko: '- Think in English, generate responses in Korean ' +
|
|
45
|
+
'(영어로 사고하고, 한국어로 응답을 생성하세요)',
|
|
46
|
+
|
|
47
|
+
ar: '- Think in English, generate responses in Arabic ' +
|
|
48
|
+
'(فكر بالإنجليزية وأجب بالعربية)',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get development guidelines for a specific language
|
|
53
|
+
*
|
|
54
|
+
* @param lang - Language code
|
|
55
|
+
* @returns Development guidelines string
|
|
56
|
+
*/
|
|
57
|
+
export const getDevGuidelines = (lang: SupportedLanguage): string => {
|
|
58
|
+
return DEV_GUIDELINES_MAP[lang];
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Validate if a language code is supported
|
|
63
|
+
*
|
|
64
|
+
* @param lang - Language code to validate
|
|
65
|
+
* @returns True if supported, false otherwise
|
|
66
|
+
*/
|
|
67
|
+
export const isSupportedLanguage = (lang: string): lang is SupportedLanguage => {
|
|
68
|
+
return (supportedLanguages as readonly string[]).includes(lang);
|
|
69
|
+
};
|
|
70
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ビルド時に静的アセット(JSON等)をdistディレクトリにコピー
|
|
3
|
+
* tscはTypeScriptのトランスパイルのみで、JSONファイルは自動コピーされないため
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { copyFileSync, mkdirSync, existsSync } from 'fs';
|
|
7
|
+
import { dirname, resolve } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const projectRoot = resolve(__dirname, '..');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* ファイルをコピーし、ディレクトリが存在しない場合は作成
|
|
15
|
+
*/
|
|
16
|
+
function copyFile(src, dest) {
|
|
17
|
+
const srcPath = resolve(projectRoot, src);
|
|
18
|
+
const destPath = resolve(projectRoot, dest);
|
|
19
|
+
|
|
20
|
+
if (!existsSync(srcPath)) {
|
|
21
|
+
console.error(`❌ Source file not found: ${srcPath}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// コピー先のディレクトリを作成
|
|
26
|
+
const destDir = dirname(destPath);
|
|
27
|
+
if (!existsSync(destDir)) {
|
|
28
|
+
mkdirSync(destDir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
copyFileSync(srcPath, destPath);
|
|
33
|
+
console.log(`✅ Copied: ${src} → ${dest}`);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error(`❌ Failed to copy ${src}:`, error.message);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// コピー対象のファイル一覧
|
|
41
|
+
const filesToCopy = [
|
|
42
|
+
{
|
|
43
|
+
src: 'scripts/config/default-config.json',
|
|
44
|
+
dest: 'dist/scripts/config/default-config.json'
|
|
45
|
+
}
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
console.log('📦 Copying static assets...');
|
|
49
|
+
filesToCopy.forEach(({ src, dest }) => copyFile(src, dest));
|
|
50
|
+
console.log('✅ All static assets copied successfully.');
|