@sk8metal/michi-cli 0.8.1 → 0.8.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 +33 -0
- package/dist/scripts/confluence-sync.js +2 -2
- package/dist/scripts/confluence-sync.js.map +1 -1
- package/dist/scripts/jira-sync.js +2 -2
- package/dist/scripts/jira-sync.js.map +1 -1
- package/dist/scripts/multi-project-estimate.js +2 -2
- package/dist/scripts/multi-project-estimate.js.map +1 -1
- package/dist/scripts/pr-automation.js +2 -2
- package/dist/scripts/pr-automation.js.map +1 -1
- package/dist/scripts/pre-flight-check.js +2 -2
- package/dist/scripts/pre-flight-check.js.map +1 -1
- package/dist/scripts/resource-dashboard.js +2 -2
- package/dist/scripts/resource-dashboard.js.map +1 -1
- package/dist/scripts/spec-impl-workflow.js +2 -2
- package/dist/scripts/spec-impl-workflow.js.map +1 -1
- package/dist/scripts/template/multi-repo-renderer.d.ts +1 -1
- package/dist/scripts/template/multi-repo-renderer.d.ts.map +1 -1
- package/dist/scripts/template/multi-repo-renderer.js +8 -3
- package/dist/scripts/template/multi-repo-renderer.js.map +1 -1
- package/dist/scripts/test-workflow-stages.js +2 -2
- package/dist/scripts/test-workflow-stages.js.map +1 -1
- package/dist/scripts/utils/config-loader.d.ts.map +1 -1
- package/dist/scripts/utils/config-loader.js +3 -2
- package/dist/scripts/utils/config-loader.js.map +1 -1
- package/dist/scripts/utils/env-loader.d.ts +11 -0
- package/dist/scripts/utils/env-loader.d.ts.map +1 -0
- package/dist/scripts/utils/env-loader.js +23 -0
- package/dist/scripts/utils/env-loader.js.map +1 -0
- package/dist/scripts/workflow-orchestrator.js +2 -2
- package/dist/scripts/workflow-orchestrator.js.map +1 -1
- package/dist/src/cli.js +3 -3
- package/dist/src/cli.js.map +1 -1
- package/docs/michi-development/design/config-unification.md +38 -4094
- package/docs/michi-development/design/design-config-current-state.md +330 -0
- package/docs/michi-development/design/design-config-implementation.md +628 -0
- package/docs/michi-development/design/design-config-migration.md +952 -0
- package/docs/michi-development/design/design-config-security.md +771 -0
- package/docs/michi-development/design/design-config-solution.md +583 -0
- package/docs/michi-development/design/design-config-testing.md +892 -0
- package/docs/michi-development/testing/manual-verification-flow.md +6 -1377
- package/docs/michi-development/testing/manual-verification-other-tools.md +1277 -0
- package/docs/michi-development/testing/manual-verification-troubleshooting.md +122 -0
- package/docs/user-guide/getting-started/setup.md +14 -32
- package/docs/user-guide/guides/multi-repo-guide.md +367 -44
- package/docs/user-guide/reference/config.md +1 -1
- package/docs/user-guide/reference/security-test-payloads.md +50 -0
- package/docs/user-guide/release/ci-setup-java.md +114 -0
- package/docs/user-guide/release/ci-setup-nodejs.md +94 -0
- package/docs/user-guide/release/ci-setup-php.md +102 -0
- package/docs/user-guide/release/ci-setup-troubleshooting.md +94 -0
- package/docs/user-guide/release/ci-setup.md +17 -370
- package/docs/user-guide/templates/test-specs/e2e-test-spec-template.md +9 -3
- package/docs/user-guide/templates/test-specs/security-test-spec-template.md +4 -43
- package/package.json +2 -3
- package/scripts/confluence-sync.ts +2 -2
- package/scripts/jira-sync.ts +2 -2
- package/scripts/multi-project-estimate.ts +2 -2
- package/scripts/pr-automation.ts +2 -2
- package/scripts/pre-flight-check.ts +2 -2
- package/scripts/resource-dashboard.ts +2 -2
- package/scripts/spec-impl-workflow.ts +2 -2
- package/scripts/template/__tests__/multi-repo-renderer.test.ts +15 -10
- package/scripts/template/multi-repo-renderer.ts +9 -3
- package/scripts/test-workflow-stages.ts +2 -2
- package/scripts/utils/__tests__/env-loader.test.ts +145 -0
- package/scripts/utils/config-loader.ts +3 -2
- package/scripts/utils/env-loader.ts +25 -0
- package/scripts/workflow-orchestrator.ts +2 -2
- package/docs/design-issue-55.md +0 -240
- package/docs/design-issue-56.md +0 -181
- package/docs/user-guide/guides/multi-repo-migration-guide.md +0 -516
|
@@ -13,6 +13,13 @@ import {
|
|
|
13
13
|
type MultiRepoTemplateContext,
|
|
14
14
|
} from '../multi-repo-renderer.js';
|
|
15
15
|
import * as fs from 'fs';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
17
|
+
import { dirname, resolve } from 'path';
|
|
18
|
+
|
|
19
|
+
// Calculate MICHI_PACKAGE_ROOT for tests
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
22
|
+
const MICHI_PACKAGE_ROOT = resolve(__dirname, '..', '..', '..');
|
|
16
23
|
|
|
17
24
|
vi.mock('fs');
|
|
18
25
|
|
|
@@ -63,10 +70,11 @@ describe('loadMultiRepoTemplate', () => {
|
|
|
63
70
|
const mockContent = '# {{PROJECT_NAME}} - Requirements';
|
|
64
71
|
vi.spyOn(fs, 'readFileSync').mockReturnValue(mockContent);
|
|
65
72
|
|
|
66
|
-
const content = loadMultiRepoTemplate('overview/requirements'
|
|
73
|
+
const content = loadMultiRepoTemplate('overview/requirements');
|
|
67
74
|
|
|
75
|
+
const expectedPath = resolve(MICHI_PACKAGE_ROOT, 'templates', 'multi-repo', 'overview', 'requirements.md');
|
|
68
76
|
expect(fs.readFileSync).toHaveBeenCalledWith(
|
|
69
|
-
|
|
77
|
+
expectedPath,
|
|
70
78
|
'utf-8'
|
|
71
79
|
);
|
|
72
80
|
expect(content).toBe(mockContent);
|
|
@@ -77,7 +85,7 @@ describe('loadMultiRepoTemplate', () => {
|
|
|
77
85
|
throw new Error('ENOENT: no such file or directory');
|
|
78
86
|
});
|
|
79
87
|
|
|
80
|
-
expect(() => loadMultiRepoTemplate('invalid/template'
|
|
88
|
+
expect(() => loadMultiRepoTemplate('invalid/template')).toThrow(
|
|
81
89
|
'Multi-Repo template not found: invalid/template.md'
|
|
82
90
|
);
|
|
83
91
|
});
|
|
@@ -151,8 +159,7 @@ describe('loadAndRenderMultiRepoTemplate', () => {
|
|
|
151
159
|
|
|
152
160
|
const rendered = loadAndRenderMultiRepoTemplate(
|
|
153
161
|
'overview/requirements',
|
|
154
|
-
context
|
|
155
|
-
'/test/root'
|
|
162
|
+
context
|
|
156
163
|
);
|
|
157
164
|
|
|
158
165
|
expect(rendered).toBe('# test-project Requirements\n\n**JIRA**: TEST');
|
|
@@ -184,8 +191,7 @@ describe('renderMultiRepoTemplates', () => {
|
|
|
184
191
|
|
|
185
192
|
const rendered = renderMultiRepoTemplates(
|
|
186
193
|
['overview/requirements', 'overview/architecture'],
|
|
187
|
-
context
|
|
188
|
-
'/test/root'
|
|
194
|
+
context
|
|
189
195
|
);
|
|
190
196
|
|
|
191
197
|
expect(rendered['overview/requirements']).toBe('# test-project Requirements');
|
|
@@ -200,7 +206,7 @@ describe('renderMultiRepoTemplates', () => {
|
|
|
200
206
|
CREATED_AT: '2025-12-14T10:00:00Z',
|
|
201
207
|
};
|
|
202
208
|
|
|
203
|
-
const rendered = renderMultiRepoTemplates([], context
|
|
209
|
+
const rendered = renderMultiRepoTemplates([], context);
|
|
204
210
|
|
|
205
211
|
expect(rendered).toEqual({});
|
|
206
212
|
});
|
|
@@ -248,8 +254,7 @@ describe('統合テスト', () => {
|
|
|
248
254
|
|
|
249
255
|
const rendered = loadAndRenderMultiRepoTemplate(
|
|
250
256
|
'overview/requirements',
|
|
251
|
-
context
|
|
252
|
-
'/test/root'
|
|
257
|
+
context
|
|
253
258
|
);
|
|
254
259
|
|
|
255
260
|
expect(rendered).toContain('# my-awesome-project - 要件定義書');
|
|
@@ -5,9 +5,15 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { readFileSync } from 'fs';
|
|
8
|
-
import { resolve, relative, isAbsolute } from 'path';
|
|
8
|
+
import { resolve, relative, isAbsolute, dirname } from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
9
10
|
import { renderTemplate, type TemplateContext } from './renderer.js';
|
|
10
11
|
|
|
12
|
+
// Resolve Michi package root directory
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
const MICHI_PACKAGE_ROOT = resolve(__dirname, '..', '..');
|
|
16
|
+
|
|
11
17
|
export interface MultiRepoTemplateContext {
|
|
12
18
|
PROJECT_NAME: string;
|
|
13
19
|
JIRA_KEY: string;
|
|
@@ -51,7 +57,7 @@ export const createMultiRepoTemplateContext = (
|
|
|
51
57
|
*/
|
|
52
58
|
export const loadMultiRepoTemplate = (
|
|
53
59
|
templateName: string,
|
|
54
|
-
|
|
60
|
+
_projectRoot: string = process.cwd()
|
|
55
61
|
): string => {
|
|
56
62
|
// Security Layer 1: Validate template name
|
|
57
63
|
// Reject path traversal characters (../, ..\, absolute paths)
|
|
@@ -63,7 +69,7 @@ export const loadMultiRepoTemplate = (
|
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
// Security Layer 2: Resolve absolute paths
|
|
66
|
-
const templateDir = resolve(
|
|
72
|
+
const templateDir = resolve(MICHI_PACKAGE_ROOT, 'templates', 'multi-repo');
|
|
67
73
|
const templatePath = resolve(templateDir, `${templateName}.md`);
|
|
68
74
|
|
|
69
75
|
// Security Layer 3: Verify path containment
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* testとreleaseステージのみを実行
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { loadEnv } from './utils/env-loader.js';
|
|
7
7
|
import { WorkflowOrchestrator, WorkflowConfig } from './workflow-orchestrator.js';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
loadEnv();
|
|
10
10
|
|
|
11
11
|
async function main() {
|
|
12
12
|
const args = process.argv.slice(2);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* env-loader.ts のユニットテスト
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import { writeFileSync, existsSync, mkdirSync, rmSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { config } from 'dotenv';
|
|
9
|
+
|
|
10
|
+
describe('env-loader', () => {
|
|
11
|
+
const testDir = join(process.cwd(), 'test-temp-env-loader');
|
|
12
|
+
const testGlobalDir = join(testDir, '.michi');
|
|
13
|
+
const testGlobalEnvPath = join(testGlobalDir, '.env');
|
|
14
|
+
const testLocalEnvPath = join(testDir, '.env');
|
|
15
|
+
|
|
16
|
+
// 元の環境変数を保存
|
|
17
|
+
const originalEnv = { ...process.env };
|
|
18
|
+
const originalHome = process.env.HOME;
|
|
19
|
+
const originalCwd = process.cwd();
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
// テスト用ディレクトリ作成
|
|
23
|
+
if (!existsSync(testDir)) {
|
|
24
|
+
mkdirSync(testDir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
if (!existsSync(testGlobalDir)) {
|
|
27
|
+
mkdirSync(testGlobalDir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// カレントディレクトリをテストディレクトリに変更
|
|
31
|
+
process.chdir(testDir);
|
|
32
|
+
|
|
33
|
+
// 環境変数をクリア
|
|
34
|
+
for (const key in process.env) {
|
|
35
|
+
if (key.startsWith('TEST_')) {
|
|
36
|
+
delete process.env[key];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
// カレントディレクトリを戻す
|
|
43
|
+
process.chdir(originalCwd);
|
|
44
|
+
|
|
45
|
+
// クリーンアップ
|
|
46
|
+
if (existsSync(testDir)) {
|
|
47
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 環境変数を復元
|
|
51
|
+
process.env = { ...originalEnv };
|
|
52
|
+
if (originalHome) {
|
|
53
|
+
process.env.HOME = originalHome;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should load global .env if exists', () => {
|
|
58
|
+
// グローバル .env を作成
|
|
59
|
+
const globalContent = `TEST_GLOBAL_VAR=global_value
|
|
60
|
+
TEST_SHARED_VAR=global_shared`;
|
|
61
|
+
writeFileSync(testGlobalEnvPath, globalContent, 'utf-8');
|
|
62
|
+
|
|
63
|
+
// HOMEを一時的に変更
|
|
64
|
+
process.env.HOME = testDir;
|
|
65
|
+
|
|
66
|
+
// dotenvで読み込みをシミュレート(env-loader.tsと同じロジック)
|
|
67
|
+
if (existsSync(testGlobalEnvPath)) {
|
|
68
|
+
config({ path: testGlobalEnvPath });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
expect(process.env.TEST_GLOBAL_VAR).toBe('global_value');
|
|
72
|
+
expect(process.env.TEST_SHARED_VAR).toBe('global_shared');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should load local .env if exists', () => {
|
|
76
|
+
// ローカル .env を作成
|
|
77
|
+
const localContent = `TEST_LOCAL_VAR=local_value
|
|
78
|
+
TEST_SHARED_VAR=local_shared`;
|
|
79
|
+
writeFileSync(testLocalEnvPath, localContent, 'utf-8');
|
|
80
|
+
|
|
81
|
+
// dotenvでローカル.envを読み込み
|
|
82
|
+
config();
|
|
83
|
+
|
|
84
|
+
expect(process.env.TEST_LOCAL_VAR).toBe('local_value');
|
|
85
|
+
expect(process.env.TEST_SHARED_VAR).toBe('local_shared');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should override global with local', () => {
|
|
89
|
+
// グローバル .env を作成
|
|
90
|
+
const globalContent = `TEST_GLOBAL_VAR=global_value
|
|
91
|
+
TEST_SHARED_VAR=global_shared`;
|
|
92
|
+
writeFileSync(testGlobalEnvPath, globalContent, 'utf-8');
|
|
93
|
+
|
|
94
|
+
// ローカル .env を作成
|
|
95
|
+
const localContent = `TEST_LOCAL_VAR=local_value
|
|
96
|
+
TEST_SHARED_VAR=local_shared`;
|
|
97
|
+
writeFileSync(testLocalEnvPath, localContent, 'utf-8');
|
|
98
|
+
|
|
99
|
+
// HOMEを一時的に変更
|
|
100
|
+
process.env.HOME = testDir;
|
|
101
|
+
|
|
102
|
+
// env-loader.tsと同じロジックで読み込み
|
|
103
|
+
if (existsSync(testGlobalEnvPath)) {
|
|
104
|
+
config({ path: testGlobalEnvPath });
|
|
105
|
+
}
|
|
106
|
+
config({ override: true }); // ローカル .env(グローバルを上書き)
|
|
107
|
+
|
|
108
|
+
// ローカルがグローバルを上書きする
|
|
109
|
+
expect(process.env.TEST_GLOBAL_VAR).toBe('global_value');
|
|
110
|
+
expect(process.env.TEST_LOCAL_VAR).toBe('local_value');
|
|
111
|
+
expect(process.env.TEST_SHARED_VAR).toBe('local_shared'); // ローカルで上書き
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should handle missing files gracefully', () => {
|
|
115
|
+
// 両方のファイルを作成しない
|
|
116
|
+
process.env.HOME = join(testDir, 'nonexistent');
|
|
117
|
+
|
|
118
|
+
// エラーを投げないことを確認
|
|
119
|
+
expect(() => {
|
|
120
|
+
if (existsSync(join(process.env.HOME!, '.michi', '.env'))) {
|
|
121
|
+
config({ path: join(process.env.HOME!, '.michi', '.env') });
|
|
122
|
+
}
|
|
123
|
+
config();
|
|
124
|
+
}).not.toThrow();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should not fail if global directory does not exist', () => {
|
|
128
|
+
// ローカル .env のみ作成
|
|
129
|
+
const localContent = `TEST_LOCAL_VAR=local_value`;
|
|
130
|
+
writeFileSync(testLocalEnvPath, localContent, 'utf-8');
|
|
131
|
+
|
|
132
|
+
// .michiディレクトリが存在しないHOMEを設定
|
|
133
|
+
process.env.HOME = join(testDir, 'no-michi-dir');
|
|
134
|
+
|
|
135
|
+
expect(() => {
|
|
136
|
+
const globalPath = join(process.env.HOME!, '.michi', '.env');
|
|
137
|
+
if (existsSync(globalPath)) {
|
|
138
|
+
config({ path: globalPath });
|
|
139
|
+
}
|
|
140
|
+
config();
|
|
141
|
+
}).not.toThrow();
|
|
142
|
+
|
|
143
|
+
expect(process.env.TEST_LOCAL_VAR).toBe('local_value');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -7,7 +7,8 @@ import { readFileSync, writeFileSync, existsSync, statSync, renameSync, unlinkSy
|
|
|
7
7
|
import { resolve, relative, isAbsolute, dirname } from 'path';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { homedir } from 'os';
|
|
10
|
-
import {
|
|
10
|
+
import { parse as dotenvParse } from 'dotenv';
|
|
11
|
+
import { loadEnv } from './env-loader.js';
|
|
11
12
|
import {
|
|
12
13
|
AppConfigSchema,
|
|
13
14
|
MultiRepoProjectSchema,
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
} from '../config/config-schema.js';
|
|
19
20
|
|
|
20
21
|
// 環境変数読み込み
|
|
21
|
-
|
|
22
|
+
loadEnv();
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* グローバル設定ファイルのパス定数
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 環境変数読み込みユーティリティ
|
|
3
|
+
* グローバル設定(~/.michi/.env)とローカル設定(.env)を読み込む
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { config } from 'dotenv';
|
|
7
|
+
import { existsSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 環境変数を読み込む
|
|
13
|
+
* グローバル設定(~/.michi/.env)→ローカル設定(.env)の順で読み込み
|
|
14
|
+
* ローカル設定がグローバル設定を上書きする
|
|
15
|
+
*/
|
|
16
|
+
export function loadEnv(): void {
|
|
17
|
+
// 1. グローバル設定 ~/.michi/.env を読み込む
|
|
18
|
+
const globalEnvPath = join(homedir(), '.michi', '.env');
|
|
19
|
+
if (existsSync(globalEnvPath)) {
|
|
20
|
+
config({ path: globalEnvPath });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 2. ローカル設定 .env を読み込む(グローバル設定を上書き)
|
|
24
|
+
config({ override: true });
|
|
25
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* AI開発フロー全体を統合実行
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { loadEnv } from './utils/env-loader.js';
|
|
7
7
|
import { loadProjectMeta } from './utils/project-meta.js';
|
|
8
8
|
import { syncToConfluence, getConfluenceConfig } from './confluence-sync.js';
|
|
9
9
|
import { syncTasksToJIRA } from './jira-sync.js';
|
|
@@ -12,7 +12,7 @@ import { executeTests, generateTestReport } from './utils/test-runner.js';
|
|
|
12
12
|
import { createReleaseNotes } from './utils/release-notes-generator.js';
|
|
13
13
|
import { pollForApproval, waitForManualApproval } from './utils/confluence-approval.js';
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
loadEnv();
|
|
16
16
|
|
|
17
17
|
export interface WorkflowConfig {
|
|
18
18
|
feature: string;
|
package/docs/design-issue-55.md
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
# Issue #55: バリデーションエラーハンドリング修正 - 詳細設計
|
|
2
|
-
|
|
3
|
-
## 問題の根本原因
|
|
4
|
-
|
|
5
|
-
### 現在の実装(src/commands/setup-existing.ts)
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// buildConfig関数内(Line 167-172)
|
|
9
|
-
try {
|
|
10
|
-
projectName = validateProjectName(projectName);
|
|
11
|
-
} catch (error) {
|
|
12
|
-
throw new Error(`プロジェクト名が不正です: ${error instanceof Error ? error.message : error}`);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// buildConfig関数内(Line 180-185)
|
|
16
|
-
try {
|
|
17
|
-
jiraKey = validateJiraKey(jiraKey);
|
|
18
|
-
} catch (error) {
|
|
19
|
-
throw new Error(`JIRAキーが不正です: ${error instanceof Error ? error.message : error}`);
|
|
20
|
-
}
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### テストの期待値
|
|
24
|
-
|
|
25
|
-
```typescript
|
|
26
|
-
// validation.test.ts(Line 214)
|
|
27
|
-
.rejects.toThrow(/プロジェクト名が空です/);
|
|
28
|
-
|
|
29
|
-
// validation.test.ts(Line 225)
|
|
30
|
-
.rejects.toThrow(/パス区切り文字/);
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### 問題
|
|
34
|
-
|
|
35
|
-
**実際のエラーメッセージ**: `プロジェクト名が不正です: プロジェクト名が空です`
|
|
36
|
-
**期待されるメッセージ**: `プロジェクト名が空です`
|
|
37
|
-
|
|
38
|
-
→ **ラッピングによってマッチしない**
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## 修正方針
|
|
43
|
-
|
|
44
|
-
### Option 1: エラーをラッピングせずそのままスロー(推奨)
|
|
45
|
-
|
|
46
|
-
**メリット**:
|
|
47
|
-
- テストの期待値と一致する
|
|
48
|
-
- エラーメッセージがシンプル
|
|
49
|
-
- エラーの原因が直接的に伝わる
|
|
50
|
-
|
|
51
|
-
**実装**:
|
|
52
|
-
```typescript
|
|
53
|
-
// buildConfig関数内(Line 167-172)
|
|
54
|
-
try {
|
|
55
|
-
projectName = validateProjectName(projectName);
|
|
56
|
-
} catch (error) {
|
|
57
|
-
// ラッピングせず、そのままスロー
|
|
58
|
-
throw error;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// buildConfig関数内(Line 180-185)
|
|
62
|
-
try {
|
|
63
|
-
jiraKey = validateJiraKey(jiraKey);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
// ラッピングせず、そのままスロー
|
|
66
|
-
throw error;
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Option 2: テストの期待値を修正(非推奨)
|
|
71
|
-
|
|
72
|
-
**デメリット**:
|
|
73
|
-
- エラーメッセージが冗長になる
|
|
74
|
-
- ラッピングの必要性が不明瞭
|
|
75
|
-
|
|
76
|
-
**実装**:
|
|
77
|
-
```typescript
|
|
78
|
-
// テストを修正
|
|
79
|
-
.rejects.toThrow(/プロジェクト名が不正です.*プロジェクト名が空です/);
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## 採用する修正方針
|
|
85
|
-
|
|
86
|
-
**Option 1を採用**
|
|
87
|
-
|
|
88
|
-
理由:
|
|
89
|
-
1. エラーメッセージがシンプルで分かりやすい
|
|
90
|
-
2. `validateProjectName`や`validateJiraKey`が既に適切なエラーメッセージを返している
|
|
91
|
-
3. 二重ラッピングの必要性がない
|
|
92
|
-
4. テストの期待値が自然
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## 修正対象ファイル
|
|
97
|
-
|
|
98
|
-
### 1. src/commands/setup-existing.ts
|
|
99
|
-
|
|
100
|
-
**修正箇所**: `buildConfig`関数内のエラーハンドリング
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
// Line 167-172: プロジェクト名バリデーション
|
|
104
|
-
try {
|
|
105
|
-
projectName = validateProjectName(projectName);
|
|
106
|
-
} catch (error) {
|
|
107
|
-
throw error; // 修正: ラッピングを削除
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Line 180-185: JIRAキーバリデーション
|
|
111
|
-
try {
|
|
112
|
-
jiraKey = validateJiraKey(jiraKey);
|
|
113
|
-
} catch (error) {
|
|
114
|
-
throw error; // 修正: ラッピングを削除
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### 2. src/__tests__/integration/setup/validation.test.ts
|
|
119
|
-
|
|
120
|
-
**修正箇所**: スキップされた5つのテストの`.skip`を削除
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
// Line 145
|
|
124
|
-
it('should reject unsupported language', async () => { ... });
|
|
125
|
-
|
|
126
|
-
// Line 207
|
|
127
|
-
it('should reject empty project name', async () => { ... });
|
|
128
|
-
|
|
129
|
-
// Line 218
|
|
130
|
-
it('should reject project name with path traversal characters', async () => { ... });
|
|
131
|
-
|
|
132
|
-
// Line 229
|
|
133
|
-
it('should reject project name with backslash', async () => { ... });
|
|
134
|
-
|
|
135
|
-
// Line 240
|
|
136
|
-
it('should reject project name with control characters', async () => { ... });
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
---
|
|
140
|
-
|
|
141
|
-
## 影響範囲
|
|
142
|
-
|
|
143
|
-
### 変更による影響
|
|
144
|
-
|
|
145
|
-
1. **エラーメッセージの変更**:
|
|
146
|
-
- 修正前: `プロジェクト名が不正です: <詳細メッセージ>`
|
|
147
|
-
- 修正後: `<詳細メッセージ>`(例: `プロジェクト名が空です`)
|
|
148
|
-
|
|
149
|
-
2. **ユーザー体験**:
|
|
150
|
-
- より直接的でシンプルなエラーメッセージ
|
|
151
|
-
- エラーの原因が明確になる
|
|
152
|
-
|
|
153
|
-
3. **他のテストへの影響**:
|
|
154
|
-
- 既存の成功テスト(プロジェクト名とJIRAキーの正常系)には影響なし
|
|
155
|
-
- エラーテストのうち、以下は既に成功している(ラッピングされていないため):
|
|
156
|
-
- Line 250: `should reject project name that is too long`
|
|
157
|
-
- Line 328-366: JIRAキーバリデーションのエラーテスト
|
|
158
|
-
|
|
159
|
-
### 回帰テストの必要性
|
|
160
|
-
|
|
161
|
-
以下のテストが引き続き成功することを確認:
|
|
162
|
-
- [ ] Line 250: `should reject project name that is too long`
|
|
163
|
-
- [ ] Line 328: `should reject JIRA key with 1 character`
|
|
164
|
-
- [ ] Line 338: `should reject JIRA key with 11 characters`
|
|
165
|
-
- [ ] Line 348: `should reject JIRA key with numbers`
|
|
166
|
-
- [ ] Line 358: `should reject JIRA key with special characters`
|
|
167
|
-
|
|
168
|
-
---
|
|
169
|
-
|
|
170
|
-
## テスト実行計画
|
|
171
|
-
|
|
172
|
-
### Step 1: 修正前のテスト実行(失敗確認)
|
|
173
|
-
|
|
174
|
-
```bash
|
|
175
|
-
cd /Users/arigatatsuya/Work/git/michi
|
|
176
|
-
npm test -- validation.test.ts
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
**期待結果**: 5つのテストがスキップされていることを確認
|
|
180
|
-
|
|
181
|
-
### Step 2: 実装修正
|
|
182
|
-
|
|
183
|
-
1. `src/commands/setup-existing.ts`のエラーハンドリングを修正
|
|
184
|
-
2. まだ`.skip`は残す
|
|
185
|
-
|
|
186
|
-
### Step 3: テスト実行(成功確認)
|
|
187
|
-
|
|
188
|
-
```bash
|
|
189
|
-
npm test -- validation.test.ts
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
**期待結果**: 修正後も既存の成功テストが全て成功
|
|
193
|
-
|
|
194
|
-
### Step 4: `.skip`削除
|
|
195
|
-
|
|
196
|
-
`src/__tests__/integration/setup/validation.test.ts`の5つのテストから`.skip`を削除
|
|
197
|
-
|
|
198
|
-
### Step 5: 最終テスト実行
|
|
199
|
-
|
|
200
|
-
```bash
|
|
201
|
-
npm test -- validation.test.ts
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
**期待結果**: 全テスト(5つの新規有効化テストを含む)が成功
|
|
205
|
-
|
|
206
|
-
### Step 6: 全テストスイート実行
|
|
207
|
-
|
|
208
|
-
```bash
|
|
209
|
-
npm test
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
**期待結果**: 全プロジェクトのテストが成功
|
|
213
|
-
|
|
214
|
-
---
|
|
215
|
-
|
|
216
|
-
## チェックリスト
|
|
217
|
-
|
|
218
|
-
### 実装
|
|
219
|
-
- [ ] `src/commands/setup-existing.ts` Line 167-172 修正
|
|
220
|
-
- [ ] `src/commands/setup-existing.ts` Line 180-185 修正
|
|
221
|
-
|
|
222
|
-
### テスト
|
|
223
|
-
- [ ] `validation.test.ts` Line 145 `.skip`削除
|
|
224
|
-
- [ ] `validation.test.ts` Line 207 `.skip`削除
|
|
225
|
-
- [ ] `validation.test.ts` Line 218 `.skip`削除
|
|
226
|
-
- [ ] `validation.test.ts` Line 229 `.skip`削除
|
|
227
|
-
- [ ] `validation.test.ts` Line 240 `.skip`削除
|
|
228
|
-
|
|
229
|
-
### 検証
|
|
230
|
-
- [ ] 修正前テスト実行(失敗/スキップ確認)
|
|
231
|
-
- [ ] 実装修正
|
|
232
|
-
- [ ] テスト実行(既存テスト成功確認)
|
|
233
|
-
- [ ] `.skip`削除
|
|
234
|
-
- [ ] テスト実行(新規テスト成功確認)
|
|
235
|
-
- [ ] 全テストスイート実行(全テスト成功確認)
|
|
236
|
-
|
|
237
|
-
---
|
|
238
|
-
|
|
239
|
-
最終更新: 2025-11-17 (月)
|
|
240
|
-
|