@sk8metal/michi-cli 0.5.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +136 -1
- package/README.md +2 -1
- package/dist/scripts/config/config-schema.d.ts +35 -0
- package/dist/scripts/config/config-schema.d.ts.map +1 -1
- package/dist/scripts/config/config-schema.js +56 -0
- package/dist/scripts/config/config-schema.js.map +1 -1
- package/dist/scripts/confluence-sync.d.ts.map +1 -1
- package/dist/scripts/confluence-sync.js +15 -2
- package/dist/scripts/confluence-sync.js.map +1 -1
- package/dist/scripts/github-actions-client.d.ts +79 -0
- package/dist/scripts/github-actions-client.d.ts.map +1 -0
- package/dist/scripts/github-actions-client.js +182 -0
- package/dist/scripts/github-actions-client.js.map +1 -0
- package/dist/scripts/health-check-service.d.ts +46 -0
- package/dist/scripts/health-check-service.d.ts.map +1 -0
- package/dist/scripts/health-check-service.js +114 -0
- package/dist/scripts/health-check-service.js.map +1 -0
- package/dist/scripts/markdown-to-confluence.d.ts.map +1 -1
- package/dist/scripts/markdown-to-confluence.js +25 -3
- package/dist/scripts/markdown-to-confluence.js.map +1 -1
- package/dist/scripts/mermaid-converter.d.ts +24 -0
- package/dist/scripts/mermaid-converter.d.ts.map +1 -0
- package/dist/scripts/mermaid-converter.js +49 -0
- package/dist/scripts/mermaid-converter.js.map +1 -0
- package/dist/scripts/template/multi-repo-renderer.d.ts +67 -0
- package/dist/scripts/template/multi-repo-renderer.d.ts.map +1 -0
- package/dist/scripts/template/multi-repo-renderer.js +123 -0
- package/dist/scripts/template/multi-repo-renderer.js.map +1 -0
- package/dist/scripts/template/renderer.d.ts +4 -0
- package/dist/scripts/template/renderer.d.ts.map +1 -1
- package/dist/scripts/template/renderer.js.map +1 -1
- package/dist/scripts/test-execution-generator.d.ts.map +1 -1
- package/dist/scripts/test-execution-generator.js +94 -11
- package/dist/scripts/test-execution-generator.js.map +1 -1
- package/dist/scripts/test-script-runner.d.ts +33 -0
- package/dist/scripts/test-script-runner.d.ts.map +1 -0
- package/dist/scripts/test-script-runner.js +77 -0
- package/dist/scripts/test-script-runner.js.map +1 -0
- package/dist/scripts/utils/config-loader.d.ts +21 -1
- package/dist/scripts/utils/config-loader.d.ts.map +1 -1
- package/dist/scripts/utils/config-loader.js +149 -3
- package/dist/scripts/utils/config-loader.js.map +1 -1
- package/dist/scripts/utils/multi-repo-validator.d.ts +30 -0
- package/dist/scripts/utils/multi-repo-validator.d.ts.map +1 -0
- package/dist/scripts/utils/multi-repo-validator.js +105 -0
- package/dist/scripts/utils/multi-repo-validator.js.map +1 -0
- package/dist/scripts/utils/spec-archiver.d.ts +38 -0
- package/dist/scripts/utils/spec-archiver.d.ts.map +1 -0
- package/dist/scripts/utils/spec-archiver.js +210 -0
- package/dist/scripts/utils/spec-archiver.js.map +1 -0
- package/dist/scripts/utils/spec-updater.d.ts +4 -0
- package/dist/scripts/utils/spec-updater.d.ts.map +1 -1
- package/dist/scripts/utils/spec-updater.js.map +1 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +262 -14
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/multi-repo-add-repo.d.ts +26 -0
- package/dist/src/commands/multi-repo-add-repo.d.ts.map +1 -0
- package/dist/src/commands/multi-repo-add-repo.js +56 -0
- package/dist/src/commands/multi-repo-add-repo.js.map +1 -0
- package/dist/src/commands/multi-repo-ci-status.d.ts +46 -0
- package/dist/src/commands/multi-repo-ci-status.d.ts.map +1 -0
- package/dist/src/commands/multi-repo-ci-status.js +285 -0
- package/dist/src/commands/multi-repo-ci-status.js.map +1 -0
- package/dist/src/commands/multi-repo-confluence-sync.d.ts +45 -0
- package/dist/src/commands/multi-repo-confluence-sync.d.ts.map +1 -0
- package/dist/src/commands/multi-repo-confluence-sync.js +135 -0
- package/dist/src/commands/multi-repo-confluence-sync.js.map +1 -0
- package/dist/src/commands/multi-repo-init.d.ts +26 -0
- package/dist/src/commands/multi-repo-init.d.ts.map +1 -0
- package/dist/src/commands/multi-repo-init.js +101 -0
- package/dist/src/commands/multi-repo-init.js.map +1 -0
- package/dist/src/commands/multi-repo-list.d.ts +28 -0
- package/dist/src/commands/multi-repo-list.d.ts.map +1 -0
- package/dist/src/commands/multi-repo-list.js +38 -0
- package/dist/src/commands/multi-repo-list.js.map +1 -0
- package/dist/src/commands/multi-repo-test.d.ts +56 -0
- package/dist/src/commands/multi-repo-test.d.ts.map +1 -0
- package/dist/src/commands/multi-repo-test.js +70 -0
- package/dist/src/commands/multi-repo-test.js.map +1 -0
- package/dist/src/commands/setup-existing.d.ts.map +1 -1
- package/dist/src/commands/setup-existing.js +30 -8
- package/dist/src/commands/setup-existing.js.map +1 -1
- package/dist/src/commands/spec-archive.d.ts +17 -0
- package/dist/src/commands/spec-archive.d.ts.map +1 -0
- package/dist/src/commands/spec-archive.js +40 -0
- package/dist/src/commands/spec-archive.js.map +1 -0
- package/dist/src/commands/spec-list.d.ts +15 -0
- package/dist/src/commands/spec-list.d.ts.map +1 -0
- package/dist/src/commands/spec-list.js +55 -0
- package/dist/src/commands/spec-list.js.map +1 -0
- package/docs/user-guide/guides/multi-repo-guide.md +591 -0
- package/docs/user-guide/guides/multi-repo-migration-guide.md +516 -0
- package/docs/user-guide/reference/multi-repo-api.md +771 -0
- package/docs/user-guide/reference/quick-reference.md +22 -37
- package/package.json +1 -4
- package/scripts/__tests__/config-loader-multi-repo.test.ts +342 -0
- package/scripts/__tests__/github-actions-client.test.ts +543 -0
- package/scripts/__tests__/health-check-service.test.ts +142 -0
- package/scripts/__tests__/markdown-to-confluence.test.ts +262 -0
- package/scripts/__tests__/mermaid-converter.test.ts +236 -0
- package/scripts/__tests__/multi-repo-config-schema.test.ts +335 -0
- package/scripts/__tests__/multi-repo-validator.test.ts +524 -0
- package/scripts/__tests__/spec-archiver.test.ts +512 -0
- package/scripts/__tests__/test-script-runner.test.ts +217 -0
- package/scripts/config/config-schema.ts +64 -0
- package/scripts/confluence-sync.ts +16 -2
- package/scripts/github-actions-client.ts +258 -0
- package/scripts/health-check-service.ts +171 -0
- package/scripts/markdown-to-confluence.ts +37 -6
- package/scripts/mermaid-converter.ts +56 -0
- package/scripts/template/__tests__/multi-repo-renderer.test.ts +261 -0
- package/scripts/template/multi-repo-renderer.ts +172 -0
- package/scripts/template/renderer.ts +5 -0
- package/scripts/test-execution-generator.ts +104 -11
- package/scripts/test-script-runner.ts +130 -0
- package/scripts/utils/__tests__/config-validator.test.ts +104 -6
- package/scripts/utils/__tests__/multi-repo-validator.test.ts +335 -0
- package/scripts/utils/config-loader.ts +197 -3
- package/scripts/utils/multi-repo-validator.ts +141 -0
- package/scripts/utils/spec-archiver.ts +260 -0
- package/scripts/utils/spec-updater.ts +4 -0
- package/templates/claude/agents/pr-size-monitor/AGENT.md +330 -0
- package/templates/claude/commands/michi/spec-impl.md +208 -35
- package/templates/claude-agent/agents/doc-reviewer.md +176 -0
- package/templates/claude-agent/rules/doc-review-rules.md +98 -0
- package/templates/claude-agent/rules/doc-review.md +91 -0
- package/templates/multi-repo/docs/ci-status.md +51 -0
- package/templates/multi-repo/docs/release-notes.md +99 -0
- package/templates/multi-repo/overview/architecture.md +102 -0
- package/templates/multi-repo/overview/requirements.md +68 -0
- package/templates/multi-repo/overview/sequence.md +79 -0
- package/templates/multi-repo/steering/multi-repo.md +74 -0
- package/templates/multi-repo/tests/strategy.md +89 -0
- package/dist/scripts/__tests__/create-project.test.d.ts +0 -2
- package/dist/scripts/__tests__/create-project.test.d.ts.map +0 -1
- package/dist/scripts/__tests__/create-project.test.js +0 -243
- package/dist/scripts/__tests__/create-project.test.js.map +0 -1
- package/dist/scripts/__tests__/jira-transitions.test.d.ts +0 -5
- package/dist/scripts/__tests__/jira-transitions.test.d.ts.map +0 -1
- package/dist/scripts/__tests__/jira-transitions.test.js +0 -172
- package/dist/scripts/__tests__/jira-transitions.test.js.map +0 -1
- package/dist/scripts/__tests__/multi-project-estimate.test.d.ts +0 -2
- package/dist/scripts/__tests__/multi-project-estimate.test.d.ts.map +0 -1
- package/dist/scripts/__tests__/multi-project-estimate.test.js +0 -118
- package/dist/scripts/__tests__/multi-project-estimate.test.js.map +0 -1
- package/dist/scripts/__tests__/setup-existing-project.test.d.ts +0 -2
- package/dist/scripts/__tests__/setup-existing-project.test.d.ts.map +0 -1
- package/dist/scripts/__tests__/setup-existing-project.test.js +0 -208
- package/dist/scripts/__tests__/setup-existing-project.test.js.map +0 -1
- package/dist/scripts/__tests__/setup-interactive.test.d.ts +0 -2
- package/dist/scripts/__tests__/setup-interactive.test.d.ts.map +0 -1
- package/dist/scripts/__tests__/setup-interactive.test.js +0 -166
- package/dist/scripts/__tests__/setup-interactive.test.js.map +0 -1
- package/dist/scripts/__tests__/spec-impl-workflow.test.d.ts +0 -5
- package/dist/scripts/__tests__/spec-impl-workflow.test.d.ts.map +0 -1
- package/dist/scripts/__tests__/spec-impl-workflow.test.js +0 -323
- package/dist/scripts/__tests__/spec-impl-workflow.test.js.map +0 -1
- package/dist/scripts/__tests__/spec-loader.test.d.ts +0 -5
- package/dist/scripts/__tests__/spec-loader.test.d.ts.map +0 -1
- package/dist/scripts/__tests__/spec-loader.test.js +0 -153
- package/dist/scripts/__tests__/spec-loader.test.js.map +0 -1
- package/dist/scripts/__tests__/validate-phase.test.d.ts +0 -5
- package/dist/scripts/__tests__/validate-phase.test.d.ts.map +0 -1
- package/dist/scripts/__tests__/validate-phase.test.js +0 -249
- package/dist/scripts/__tests__/validate-phase.test.js.map +0 -1
- package/dist/scripts/constants/__tests__/environments.test.d.ts +0 -2
- package/dist/scripts/constants/__tests__/environments.test.d.ts.map +0 -1
- package/dist/scripts/constants/__tests__/environments.test.js +0 -125
- package/dist/scripts/constants/__tests__/environments.test.js.map +0 -1
- package/dist/scripts/constants/__tests__/languages.test.d.ts +0 -2
- package/dist/scripts/constants/__tests__/languages.test.d.ts.map +0 -1
- package/dist/scripts/constants/__tests__/languages.test.js +0 -82
- package/dist/scripts/constants/__tests__/languages.test.js.map +0 -1
- package/dist/scripts/create-project.d.ts +0 -16
- package/dist/scripts/create-project.d.ts.map +0 -1
- package/dist/scripts/create-project.js +0 -334
- package/dist/scripts/create-project.js.map +0 -1
- package/dist/scripts/list-projects.d.ts +0 -7
- package/dist/scripts/list-projects.d.ts.map +0 -1
- package/dist/scripts/list-projects.js +0 -88
- package/dist/scripts/list-projects.js.map +0 -1
- package/dist/scripts/template/__tests__/renderer.test.d.ts +0 -2
- package/dist/scripts/template/__tests__/renderer.test.d.ts.map +0 -1
- package/dist/scripts/template/__tests__/renderer.test.js +0 -165
- package/dist/scripts/template/__tests__/renderer.test.js.map +0 -1
- package/dist/scripts/utils/__tests__/aidlc-parser.test.d.ts +0 -5
- package/dist/scripts/utils/__tests__/aidlc-parser.test.d.ts.map +0 -1
- package/dist/scripts/utils/__tests__/aidlc-parser.test.js +0 -315
- package/dist/scripts/utils/__tests__/aidlc-parser.test.js.map +0 -1
- package/dist/scripts/utils/__tests__/business-days.test.d.ts +0 -5
- package/dist/scripts/utils/__tests__/business-days.test.d.ts.map +0 -1
- package/dist/scripts/utils/__tests__/business-days.test.js +0 -171
- package/dist/scripts/utils/__tests__/business-days.test.js.map +0 -1
- package/dist/scripts/utils/__tests__/config-loader.test.d.ts +0 -5
- package/dist/scripts/utils/__tests__/config-loader.test.d.ts.map +0 -1
- package/dist/scripts/utils/__tests__/config-loader.test.js +0 -314
- package/dist/scripts/utils/__tests__/config-loader.test.js.map +0 -1
- package/dist/scripts/utils/__tests__/config-validator.test.d.ts +0 -5
- package/dist/scripts/utils/__tests__/config-validator.test.d.ts.map +0 -1
- package/dist/scripts/utils/__tests__/config-validator.test.js +0 -396
- package/dist/scripts/utils/__tests__/config-validator.test.js.map +0 -1
- package/dist/scripts/utils/__tests__/env-config.test.d.ts +0 -5
- package/dist/scripts/utils/__tests__/env-config.test.d.ts.map +0 -1
- package/dist/scripts/utils/__tests__/env-config.test.js +0 -216
- package/dist/scripts/utils/__tests__/env-config.test.js.map +0 -1
- package/dist/scripts/utils/__tests__/feature-name-validator.test.d.ts +0 -5
- package/dist/scripts/utils/__tests__/feature-name-validator.test.d.ts.map +0 -1
- package/dist/scripts/utils/__tests__/feature-name-validator.test.js +0 -106
- package/dist/scripts/utils/__tests__/feature-name-validator.test.js.map +0 -1
- package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.d.ts +0 -5
- package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.d.ts.map +0 -1
- package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.js +0 -202
- package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.js.map +0 -1
- package/dist/scripts/utils/__tests__/project-meta.test.d.ts +0 -6
- package/dist/scripts/utils/__tests__/project-meta.test.d.ts.map +0 -1
- package/dist/scripts/utils/__tests__/project-meta.test.js +0 -154
- package/dist/scripts/utils/__tests__/project-meta.test.js.map +0 -1
- package/dist/scripts/utils/__tests__/security-validator.test.d.ts +0 -6
- package/dist/scripts/utils/__tests__/security-validator.test.d.ts.map +0 -1
- package/dist/scripts/utils/__tests__/security-validator.test.js +0 -219
- package/dist/scripts/utils/__tests__/security-validator.test.js.map +0 -1
- package/dist/scripts/utils/__tests__/spec-updater.test.d.ts +0 -5
- package/dist/scripts/utils/__tests__/spec-updater.test.d.ts.map +0 -1
- package/dist/scripts/utils/__tests__/spec-updater.test.js +0 -158
- package/dist/scripts/utils/__tests__/spec-updater.test.js.map +0 -1
- package/dist/scripts/utils/__tests__/tasks-converter.test.d.ts +0 -5
- package/dist/scripts/utils/__tests__/tasks-converter.test.d.ts.map +0 -1
- package/dist/scripts/utils/__tests__/tasks-converter.test.js +0 -500
- package/dist/scripts/utils/__tests__/tasks-converter.test.js.map +0 -1
- package/dist/scripts/utils/__tests__/tasks-format-validator.test.d.ts +0 -5
- package/dist/scripts/utils/__tests__/tasks-format-validator.test.d.ts.map +0 -1
- package/dist/scripts/utils/__tests__/tasks-format-validator.test.js +0 -314
- package/dist/scripts/utils/__tests__/tasks-format-validator.test.js.map +0 -1
- package/dist/scripts/utils/__tests__/test-runner.test.d.ts +0 -5
- package/dist/scripts/utils/__tests__/test-runner.test.d.ts.map +0 -1
- package/dist/scripts/utils/__tests__/test-runner.test.js +0 -64
- package/dist/scripts/utils/__tests__/test-runner.test.js.map +0 -1
- package/dist/src/__tests__/cli.test.d.ts +0 -5
- package/dist/src/__tests__/cli.test.d.ts.map +0 -1
- package/dist/src/__tests__/cli.test.js +0 -58
- package/dist/src/__tests__/cli.test.js.map +0 -1
- package/dist/src/__tests__/integration/internationalization.test.d.ts +0 -8
- package/dist/src/__tests__/integration/internationalization.test.d.ts.map +0 -1
- package/dist/src/__tests__/integration/internationalization.test.js +0 -333
- package/dist/src/__tests__/integration/internationalization.test.js.map +0 -1
- package/dist/src/__tests__/integration/setup/claude-agent.test.d.ts +0 -5
- package/dist/src/__tests__/integration/setup/claude-agent.test.d.ts.map +0 -1
- package/dist/src/__tests__/integration/setup/claude-agent.test.js +0 -122
- package/dist/src/__tests__/integration/setup/claude-agent.test.js.map +0 -1
- package/dist/src/__tests__/integration/setup/claude.test.d.ts +0 -5
- package/dist/src/__tests__/integration/setup/claude.test.d.ts.map +0 -1
- package/dist/src/__tests__/integration/setup/claude.test.js +0 -193
- package/dist/src/__tests__/integration/setup/claude.test.js.map +0 -1
- package/dist/src/__tests__/integration/setup/cursor.test.d.ts +0 -5
- package/dist/src/__tests__/integration/setup/cursor.test.d.ts.map +0 -1
- package/dist/src/__tests__/integration/setup/cursor.test.js +0 -166
- package/dist/src/__tests__/integration/setup/cursor.test.js.map +0 -1
- package/dist/src/__tests__/integration/setup/helpers/fs-assertions.d.ts +0 -32
- package/dist/src/__tests__/integration/setup/helpers/fs-assertions.d.ts.map +0 -1
- package/dist/src/__tests__/integration/setup/helpers/fs-assertions.js +0 -72
- package/dist/src/__tests__/integration/setup/helpers/fs-assertions.js.map +0 -1
- package/dist/src/__tests__/integration/setup/helpers/test-project.d.ts +0 -38
- package/dist/src/__tests__/integration/setup/helpers/test-project.d.ts.map +0 -1
- package/dist/src/__tests__/integration/setup/helpers/test-project.js +0 -83
- package/dist/src/__tests__/integration/setup/helpers/test-project.js.map +0 -1
- package/dist/src/__tests__/integration/setup/init.test.d.ts +0 -5
- package/dist/src/__tests__/integration/setup/init.test.d.ts.map +0 -1
- package/dist/src/__tests__/integration/setup/init.test.js +0 -352
- package/dist/src/__tests__/integration/setup/init.test.js.map +0 -1
- package/dist/src/__tests__/integration/setup/validation.test.d.ts +0 -5
- package/dist/src/__tests__/integration/setup/validation.test.d.ts.map +0 -1
- package/dist/src/__tests__/integration/setup/validation.test.js +0 -301
- package/dist/src/__tests__/integration/setup/validation.test.js.map +0 -1
- package/dist/src/commands/__tests__/init.test.d.ts +0 -5
- package/dist/src/commands/__tests__/init.test.d.ts.map +0 -1
- package/dist/src/commands/__tests__/init.test.js +0 -255
- package/dist/src/commands/__tests__/init.test.js.map +0 -1
- package/dist/src/commands/__tests__/migrate.test.d.ts +0 -5
- package/dist/src/commands/__tests__/migrate.test.d.ts.map +0 -1
- package/dist/src/commands/__tests__/migrate.test.js +0 -216
- package/dist/src/commands/__tests__/migrate.test.js.map +0 -1
- package/scripts/create-project.ts +0 -386
- package/scripts/list-projects.ts +0 -112
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-Repo template renderer
|
|
3
|
+
*
|
|
4
|
+
* Provides template rendering functionality for Multi-Repo projects
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from 'fs';
|
|
8
|
+
import { resolve, relative, isAbsolute } from 'path';
|
|
9
|
+
import { renderTemplate, type TemplateContext } from './renderer.js';
|
|
10
|
+
|
|
11
|
+
export interface MultiRepoTemplateContext {
|
|
12
|
+
PROJECT_NAME: string;
|
|
13
|
+
JIRA_KEY: string;
|
|
14
|
+
CONFLUENCE_SPACE: string;
|
|
15
|
+
CREATED_AT: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create Multi-Repo template context
|
|
20
|
+
*
|
|
21
|
+
* @param projectName - Project name
|
|
22
|
+
* @param jiraKey - JIRA project key
|
|
23
|
+
* @param confluenceSpace - Confluence space key
|
|
24
|
+
* @param createdAt - Created timestamp (ISO 8601 format)
|
|
25
|
+
* @returns Multi-Repo template context
|
|
26
|
+
*/
|
|
27
|
+
export const createMultiRepoTemplateContext = (
|
|
28
|
+
projectName: string,
|
|
29
|
+
jiraKey: string,
|
|
30
|
+
confluenceSpace: string,
|
|
31
|
+
createdAt?: string
|
|
32
|
+
): MultiRepoTemplateContext => ({
|
|
33
|
+
PROJECT_NAME: projectName,
|
|
34
|
+
JIRA_KEY: jiraKey,
|
|
35
|
+
CONFLUENCE_SPACE: confluenceSpace,
|
|
36
|
+
CREATED_AT: createdAt || new Date().toISOString(),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Load Multi-Repo template file
|
|
41
|
+
*
|
|
42
|
+
* Security: Path traversal prevention with three-layer validation:
|
|
43
|
+
* 1. Validate template name (no path separators)
|
|
44
|
+
* 2. Resolve absolute paths
|
|
45
|
+
* 3. Verify path containment
|
|
46
|
+
*
|
|
47
|
+
* @param templateName - Template name (e.g., "overview/requirements", "steering/multi-repo")
|
|
48
|
+
* @param projectRoot - Project root directory
|
|
49
|
+
* @returns Template content
|
|
50
|
+
* @throws {Error} If template file not found or path traversal detected
|
|
51
|
+
*/
|
|
52
|
+
export const loadMultiRepoTemplate = (
|
|
53
|
+
templateName: string,
|
|
54
|
+
projectRoot: string = process.cwd()
|
|
55
|
+
): string => {
|
|
56
|
+
// Security Layer 1: Validate template name
|
|
57
|
+
// Reject path traversal characters (../, ..\, absolute paths)
|
|
58
|
+
if (templateName.includes('..') || templateName.includes('\\')) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`Invalid template name: ${templateName}\n` +
|
|
61
|
+
'Template name must not contain path traversal characters (\\, ..)'
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Security Layer 2: Resolve absolute paths
|
|
66
|
+
const templateDir = resolve(projectRoot, 'templates', 'multi-repo');
|
|
67
|
+
const templatePath = resolve(templateDir, `${templateName}.md`);
|
|
68
|
+
|
|
69
|
+
// Security Layer 3: Verify path containment
|
|
70
|
+
const relativePath = relative(templateDir, templatePath);
|
|
71
|
+
if (relativePath.startsWith('..') || isAbsolute(relativePath)) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`Invalid template path: ${templateName}\n` +
|
|
74
|
+
`Template path is outside template directory: ${templateDir}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
return readFileSync(templatePath, 'utf-8');
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Multi-Repo template not found: ${templateName}.md\n` +
|
|
84
|
+
`Path: ${templatePath}\n` +
|
|
85
|
+
`Error: ${errorMessage}`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Render Multi-Repo template with placeholder replacement
|
|
92
|
+
*
|
|
93
|
+
* @param template - Template string
|
|
94
|
+
* @param context - Multi-Repo template context
|
|
95
|
+
* @returns Rendered template string
|
|
96
|
+
*/
|
|
97
|
+
export const renderMultiRepoTemplate = (
|
|
98
|
+
template: string,
|
|
99
|
+
context: MultiRepoTemplateContext
|
|
100
|
+
): string => {
|
|
101
|
+
// Convert MultiRepoTemplateContext to TemplateContext
|
|
102
|
+
const templateContext: TemplateContext = {
|
|
103
|
+
LANG_CODE: 'ja', // Default language
|
|
104
|
+
DEV_GUIDELINES: '', // Not used for Multi-Repo templates
|
|
105
|
+
KIRO_DIR: '.kiro',
|
|
106
|
+
AGENT_DIR: '.claude',
|
|
107
|
+
PROJECT_NAME: context.PROJECT_NAME,
|
|
108
|
+
JIRA_KEY: context.JIRA_KEY,
|
|
109
|
+
CONFLUENCE_SPACE: context.CONFLUENCE_SPACE,
|
|
110
|
+
CREATED_AT: context.CREATED_AT,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return renderTemplate(template, templateContext);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Load and render Multi-Repo template
|
|
118
|
+
*
|
|
119
|
+
* @param templateName - Template name
|
|
120
|
+
* @param context - Multi-Repo template context
|
|
121
|
+
* @param projectRoot - Project root directory
|
|
122
|
+
* @returns Rendered template string
|
|
123
|
+
*/
|
|
124
|
+
export const loadAndRenderMultiRepoTemplate = (
|
|
125
|
+
templateName: string,
|
|
126
|
+
context: MultiRepoTemplateContext,
|
|
127
|
+
projectRoot: string = process.cwd()
|
|
128
|
+
): string => {
|
|
129
|
+
const template = loadMultiRepoTemplate(templateName, projectRoot);
|
|
130
|
+
return renderMultiRepoTemplate(template, context);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Batch render multiple Multi-Repo templates
|
|
135
|
+
*
|
|
136
|
+
* @param templateNames - Array of template names
|
|
137
|
+
* @param context - Multi-Repo template context
|
|
138
|
+
* @param projectRoot - Project root directory
|
|
139
|
+
* @returns Map of template names to rendered strings
|
|
140
|
+
*/
|
|
141
|
+
export const renderMultiRepoTemplates = (
|
|
142
|
+
templateNames: string[],
|
|
143
|
+
context: MultiRepoTemplateContext,
|
|
144
|
+
projectRoot: string = process.cwd()
|
|
145
|
+
): Record<string, string> => {
|
|
146
|
+
const rendered: Record<string, string> = {};
|
|
147
|
+
|
|
148
|
+
for (const templateName of templateNames) {
|
|
149
|
+
rendered[templateName] = loadAndRenderMultiRepoTemplate(
|
|
150
|
+
templateName,
|
|
151
|
+
context,
|
|
152
|
+
projectRoot
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return rendered;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* List of all Multi-Repo template names
|
|
161
|
+
*/
|
|
162
|
+
export const MULTI_REPO_TEMPLATES = [
|
|
163
|
+
'overview/requirements',
|
|
164
|
+
'overview/architecture',
|
|
165
|
+
'overview/sequence',
|
|
166
|
+
'steering/multi-repo',
|
|
167
|
+
'tests/strategy',
|
|
168
|
+
'docs/ci-status',
|
|
169
|
+
'docs/release-notes',
|
|
170
|
+
] as const;
|
|
171
|
+
|
|
172
|
+
export type MultiRepoTemplateName = typeof MULTI_REPO_TEMPLATES[number];
|
|
@@ -14,6 +14,11 @@ export interface TemplateContext {
|
|
|
14
14
|
PROJECT_ID?: string;
|
|
15
15
|
FEATURE_NAME?: string;
|
|
16
16
|
TIMESTAMP?: string;
|
|
17
|
+
// Multi-Repo specific placeholders
|
|
18
|
+
PROJECT_NAME?: string;
|
|
19
|
+
JIRA_KEY?: string;
|
|
20
|
+
CONFLUENCE_SPACE?: string;
|
|
21
|
+
CREATED_AT?: string;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
/**
|
|
@@ -6,6 +6,73 @@
|
|
|
6
6
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from 'fs';
|
|
7
7
|
import { join } from 'path';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* セキュリティ: URLバリデーション
|
|
11
|
+
* HTTP/HTTPS URLのみ許可、特殊文字やコマンドインジェクションを防ぐ
|
|
12
|
+
*/
|
|
13
|
+
function validateUrl(url: string): void {
|
|
14
|
+
// 基本的なURL形式チェック
|
|
15
|
+
try {
|
|
16
|
+
const parsed = new URL(url);
|
|
17
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
18
|
+
throw new Error(`Invalid protocol: ${parsed.protocol}. Only http: and https: are allowed.`);
|
|
19
|
+
}
|
|
20
|
+
} catch (_error) {
|
|
21
|
+
throw new Error(`Invalid URL format: ${url}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// コマンドインジェクション対策: 危険な文字を検出
|
|
25
|
+
const dangerousChars = /[;`$()&|<>]/;
|
|
26
|
+
if (dangerousChars.test(url)) {
|
|
27
|
+
throw new Error(`URL contains dangerous characters: ${url}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* セキュリティ: Bash用文字列エスケープ
|
|
33
|
+
* シェルスクリプト内で安全に使用できる形式にエスケープ
|
|
34
|
+
*/
|
|
35
|
+
function escapeBash(str: string): string {
|
|
36
|
+
// シングルクォートで囲み、シングルクォート自体をエスケープ
|
|
37
|
+
return `'${str.replace(/'/g, '\'\\\'\'')}'`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* セキュリティ: Python用文字列エスケープ
|
|
42
|
+
* Pythonコード内で安全に使用できる形式にエスケープ
|
|
43
|
+
*/
|
|
44
|
+
function escapePython(str: string): string {
|
|
45
|
+
return str
|
|
46
|
+
.replace(/\\/g, '\\\\') // バックスラッシュ
|
|
47
|
+
.replace(/"/g, '\\"') // ダブルクォート
|
|
48
|
+
.replace(/\n/g, '\\n') // 改行
|
|
49
|
+
.replace(/\r/g, '\\r') // キャリッジリターン
|
|
50
|
+
.replace(/\t/g, '\\t'); // タブ
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* セキュリティ: Python識別子バリデーション
|
|
55
|
+
* Pythonのクラス名として有効な形式かチェック
|
|
56
|
+
*/
|
|
57
|
+
function validatePythonIdentifier(name: string): void {
|
|
58
|
+
// Python識別子のルール: 英字またはアンダースコアで始まり、英数字とアンダースコアのみ
|
|
59
|
+
const pythonIdentifierPattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
60
|
+
if (!pythonIdentifierPattern.test(name)) {
|
|
61
|
+
throw new Error(`Invalid Python identifier: ${name}. Must start with letter or underscore, and contain only letters, numbers, and underscores.`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Pythonの予約語チェック
|
|
65
|
+
const pythonKeywords = [
|
|
66
|
+
'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await',
|
|
67
|
+
'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except',
|
|
68
|
+
'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda',
|
|
69
|
+
'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield'
|
|
70
|
+
];
|
|
71
|
+
if (pythonKeywords.includes(name)) {
|
|
72
|
+
throw new Error(`Invalid Python identifier: ${name} is a reserved keyword.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
9
76
|
/**
|
|
10
77
|
* テスト実行ファイルの生成オプション
|
|
11
78
|
*/
|
|
@@ -151,28 +218,47 @@ function generateLocustFile(
|
|
|
151
218
|
endpoint: { endpoint: string; method: string; baseUrl: string },
|
|
152
219
|
perfReqs: { targetRps: string; targetResponseTime: string }
|
|
153
220
|
): string {
|
|
221
|
+
// セキュリティ: URLバリデーション
|
|
222
|
+
validateUrl(endpoint.baseUrl);
|
|
223
|
+
|
|
224
|
+
// セキュリティ: Pythonエスケープ
|
|
225
|
+
const escapedFeature = escapePython(feature);
|
|
226
|
+
const escapedEndpoint = escapePython(endpoint.endpoint);
|
|
227
|
+
const escapedMethod = escapePython(endpoint.method);
|
|
228
|
+
const escapedBaseUrl = escapePython(endpoint.baseUrl);
|
|
229
|
+
const escapedTargetRps = escapePython(perfReqs.targetRps);
|
|
230
|
+
const escapedTargetResponseTime = escapePython(perfReqs.targetResponseTime);
|
|
231
|
+
|
|
232
|
+
// セキュリティ: Pythonクラス名の生成と検証
|
|
233
|
+
let className = feature.replace(/[^a-zA-Z0-9]/g, '_');
|
|
234
|
+
// クラス名は大文字で始まる必要がある(Python慣例)
|
|
235
|
+
if (!/^[A-Z]/.test(className)) {
|
|
236
|
+
className = 'Test' + className.charAt(0).toUpperCase() + className.slice(1);
|
|
237
|
+
}
|
|
238
|
+
// Python識別子として有効かチェック
|
|
239
|
+
validatePythonIdentifier(className);
|
|
240
|
+
|
|
154
241
|
const methodLower = endpoint.method.toLowerCase();
|
|
155
242
|
const hasBody = ['post', 'put', 'patch'].includes(methodLower);
|
|
156
|
-
const className = feature.replace(/[^a-zA-Z0-9]/g, '');
|
|
157
243
|
|
|
158
244
|
let taskCode = '';
|
|
159
245
|
if (hasBody) {
|
|
160
246
|
taskCode = ` self.client.${methodLower}(
|
|
161
|
-
"${
|
|
247
|
+
"${escapedEndpoint}",
|
|
162
248
|
json={},
|
|
163
249
|
headers={"Content-Type": "application/json"}
|
|
164
250
|
)`;
|
|
165
251
|
} else {
|
|
166
|
-
taskCode = ` self.client.${methodLower}("${
|
|
252
|
+
taskCode = ` self.client.${methodLower}("${escapedEndpoint}")`;
|
|
167
253
|
}
|
|
168
254
|
|
|
169
255
|
return `"""
|
|
170
|
-
${
|
|
171
|
-
自動生成: michi phase:run ${
|
|
256
|
+
${escapedFeature} 負荷テスト
|
|
257
|
+
自動生成: michi phase:run ${escapedFeature} phase-b
|
|
172
258
|
|
|
173
259
|
目標:
|
|
174
|
-
- RPS: ${
|
|
175
|
-
- 応答時間: ${
|
|
260
|
+
- RPS: ${escapedTargetRps}
|
|
261
|
+
- 応答時間: ${escapedTargetResponseTime}ms以内
|
|
176
262
|
"""
|
|
177
263
|
|
|
178
264
|
from locust import HttpUser, task, between
|
|
@@ -182,11 +268,11 @@ class ${className}User(HttpUser):
|
|
|
182
268
|
"""テスト対象ユーザーシミュレーション"""
|
|
183
269
|
|
|
184
270
|
wait_time = between(1, 3)
|
|
185
|
-
host = "${
|
|
271
|
+
host = "${escapedBaseUrl}"
|
|
186
272
|
|
|
187
273
|
@task
|
|
188
274
|
def test_endpoint(self):
|
|
189
|
-
"""${
|
|
275
|
+
"""${escapedEndpoint}への${escapedMethod}リクエスト"""
|
|
190
276
|
${taskCode}
|
|
191
277
|
|
|
192
278
|
|
|
@@ -369,14 +455,21 @@ function generateZapScript(
|
|
|
369
455
|
feature: string,
|
|
370
456
|
endpoint: { baseUrl: string }
|
|
371
457
|
): string {
|
|
458
|
+
// セキュリティ: URLバリデーション(コマンドインジェクション対策)
|
|
459
|
+
validateUrl(endpoint.baseUrl);
|
|
460
|
+
|
|
461
|
+
// セキュリティ: Bashエスケープ(追加の防御層)
|
|
462
|
+
const escapedUrl = escapeBash(endpoint.baseUrl);
|
|
463
|
+
const escapedFeature = escapeBash(feature);
|
|
464
|
+
|
|
372
465
|
return `#!/bin/bash
|
|
373
466
|
# OWASP ZAPセキュリティスキャン実行スクリプト
|
|
374
|
-
# 自動生成: michi phase:run ${
|
|
467
|
+
# 自動生成: michi phase:run ${escapedFeature} phase-b
|
|
375
468
|
|
|
376
469
|
set -e
|
|
377
470
|
|
|
378
471
|
# 変数定義
|
|
379
|
-
TARGET_URL
|
|
472
|
+
TARGET_URL=${escapedUrl}
|
|
380
473
|
CONFIG_FILE="zap-config.yaml"
|
|
381
474
|
REPORT_DIR="./reports"
|
|
382
475
|
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* テストスクリプトランナー
|
|
3
|
+
* Multi-Repoプロジェクトのテストスクリプトを実行
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* テストタイプ
|
|
11
|
+
*/
|
|
12
|
+
export type TestType = 'e2e' | 'integration' | 'performance';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* テスト実行結果
|
|
16
|
+
*/
|
|
17
|
+
export interface TestExecutionResult {
|
|
18
|
+
success: boolean;
|
|
19
|
+
exitCode: number;
|
|
20
|
+
executionTime: number; // 秒単位
|
|
21
|
+
outputPath: string;
|
|
22
|
+
error?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* execSync実行時のエラー型
|
|
27
|
+
*/
|
|
28
|
+
interface ExecError extends Error {
|
|
29
|
+
status?: number;
|
|
30
|
+
stderr?: Buffer | string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* テストスクリプトランナー
|
|
35
|
+
*/
|
|
36
|
+
export class TestScriptRunner {
|
|
37
|
+
/**
|
|
38
|
+
* テストスクリプトを実行
|
|
39
|
+
*
|
|
40
|
+
* @param projectName プロジェクト名
|
|
41
|
+
* @param testType テストタイプ (e2e | integration | performance)
|
|
42
|
+
* @param projectRoot プロジェクトルートディレクトリ(デフォルト: process.cwd())
|
|
43
|
+
* @returns テスト実行結果
|
|
44
|
+
*/
|
|
45
|
+
async runTestScript(
|
|
46
|
+
projectName: string,
|
|
47
|
+
testType: TestType,
|
|
48
|
+
projectRoot: string = process.cwd()
|
|
49
|
+
): Promise<TestExecutionResult> {
|
|
50
|
+
// 1. テストスクリプトパスを生成
|
|
51
|
+
const scriptPath = join(
|
|
52
|
+
projectRoot,
|
|
53
|
+
'docs',
|
|
54
|
+
'michi',
|
|
55
|
+
projectName,
|
|
56
|
+
'tests',
|
|
57
|
+
'scripts',
|
|
58
|
+
`run-${testType}.sh`
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// 2. テスト結果の出力先パスを生成(スクリプト側で使用)
|
|
62
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('T')[0];
|
|
63
|
+
const outputPath = join(
|
|
64
|
+
projectRoot,
|
|
65
|
+
'docs',
|
|
66
|
+
'michi',
|
|
67
|
+
projectName,
|
|
68
|
+
'tests',
|
|
69
|
+
'results',
|
|
70
|
+
`${testType}-${timestamp}.log`
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// 3. 実行前のログ出力
|
|
74
|
+
console.log('🚀 テストスクリプトを実行中...');
|
|
75
|
+
console.log(` テストタイプ: ${testType}`);
|
|
76
|
+
console.log(` スクリプトパス: ${scriptPath}`);
|
|
77
|
+
console.log(` 実行開始時刻: ${new Date().toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' })}`);
|
|
78
|
+
|
|
79
|
+
// 4. テストスクリプトを実行
|
|
80
|
+
const startTime = Date.now();
|
|
81
|
+
let exitCode = 0;
|
|
82
|
+
let error: string | undefined;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
execSync(scriptPath, {
|
|
86
|
+
stdio: 'inherit', // リアルタイム出力
|
|
87
|
+
encoding: 'utf-8',
|
|
88
|
+
});
|
|
89
|
+
} catch (err) {
|
|
90
|
+
// スクリプト実行エラー
|
|
91
|
+
const execError = err as ExecError;
|
|
92
|
+
exitCode = execError.status !== undefined ? execError.status : 1;
|
|
93
|
+
error = execError.message;
|
|
94
|
+
|
|
95
|
+
// stderrがある場合は追加
|
|
96
|
+
if (execError.stderr) {
|
|
97
|
+
const stderrStr = execError.stderr.toString();
|
|
98
|
+
error = stderrStr || error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const endTime = Date.now();
|
|
103
|
+
const executionTime = (endTime - startTime) / 1000; // ミリ秒から秒に変換
|
|
104
|
+
|
|
105
|
+
// 5. 実行後のログ出力
|
|
106
|
+
console.log('\n📊 テスト実行結果:');
|
|
107
|
+
console.log(` 終了コード: ${exitCode}`);
|
|
108
|
+
console.log(` 実行時間: ${executionTime.toFixed(2)}秒`);
|
|
109
|
+
console.log(` テスト結果ファイル: ${outputPath}`);
|
|
110
|
+
|
|
111
|
+
const success = exitCode === 0;
|
|
112
|
+
|
|
113
|
+
if (success) {
|
|
114
|
+
console.log('✅ テスト実行が成功しました');
|
|
115
|
+
} else {
|
|
116
|
+
console.log('❌ テスト実行が失敗しました');
|
|
117
|
+
if (error) {
|
|
118
|
+
console.log(` エラー: ${error}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
success,
|
|
124
|
+
exitCode,
|
|
125
|
+
executionTime,
|
|
126
|
+
outputPath,
|
|
127
|
+
error,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -147,6 +147,22 @@ describe('config-validator', () => {
|
|
|
147
147
|
});
|
|
148
148
|
|
|
149
149
|
describe('validateForConfluenceSync', () => {
|
|
150
|
+
let savedConfluenceSpace: string | undefined;
|
|
151
|
+
|
|
152
|
+
beforeEach(() => {
|
|
153
|
+
// 環境変数を保存
|
|
154
|
+
savedConfluenceSpace = process.env.CONFLUENCE_PRD_SPACE;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
afterEach(() => {
|
|
158
|
+
// 環境変数を復元
|
|
159
|
+
if (savedConfluenceSpace !== undefined) {
|
|
160
|
+
process.env.CONFLUENCE_PRD_SPACE = savedConfluenceSpace;
|
|
161
|
+
} else {
|
|
162
|
+
delete process.env.CONFLUENCE_PRD_SPACE;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
150
166
|
it('spaces設定がない場合は警告を返す', () => {
|
|
151
167
|
// 環境変数をクリア
|
|
152
168
|
delete process.env.CONFLUENCE_PRD_SPACE;
|
|
@@ -236,6 +252,7 @@ describe('config-validator', () => {
|
|
|
236
252
|
});
|
|
237
253
|
|
|
238
254
|
it('環境変数CONFLUENCE_PRD_SPACEがある場合は情報メッセージ', () => {
|
|
255
|
+
// 環境変数を明示的に設定
|
|
239
256
|
process.env.CONFLUENCE_PRD_SPACE = 'Michi';
|
|
240
257
|
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
241
258
|
writeFileSync(
|
|
@@ -259,23 +276,48 @@ describe('config-validator', () => {
|
|
|
259
276
|
});
|
|
260
277
|
|
|
261
278
|
describe('validateForJiraSync', () => {
|
|
279
|
+
let savedStoryEnv: string | undefined;
|
|
280
|
+
let savedSubtaskEnv: string | undefined;
|
|
281
|
+
|
|
262
282
|
beforeEach(() => {
|
|
263
|
-
//
|
|
283
|
+
// 環境変数を保存して削除
|
|
284
|
+
savedStoryEnv = process.env.JIRA_ISSUE_TYPE_STORY;
|
|
285
|
+
savedSubtaskEnv = process.env.JIRA_ISSUE_TYPE_SUBTASK;
|
|
264
286
|
delete process.env.JIRA_ISSUE_TYPE_STORY;
|
|
265
287
|
delete process.env.JIRA_ISSUE_TYPE_SUBTASK;
|
|
266
288
|
});
|
|
267
289
|
|
|
268
|
-
|
|
290
|
+
afterEach(() => {
|
|
291
|
+
// 環境変数を復元
|
|
292
|
+
if (savedStoryEnv !== undefined) {
|
|
293
|
+
process.env.JIRA_ISSUE_TYPE_STORY = savedStoryEnv;
|
|
294
|
+
} else {
|
|
295
|
+
delete process.env.JIRA_ISSUE_TYPE_STORY;
|
|
296
|
+
}
|
|
297
|
+
if (savedSubtaskEnv !== undefined) {
|
|
298
|
+
process.env.JIRA_ISSUE_TYPE_SUBTASK = savedSubtaskEnv;
|
|
299
|
+
} else {
|
|
300
|
+
delete process.env.JIRA_ISSUE_TYPE_SUBTASK;
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('issueTypes.story設定がない場合はエラー(デフォルト設定なし)', () => {
|
|
269
305
|
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
306
|
+
// issueTypes.storyを明示的にnullに設定してデフォルト値を無効化
|
|
270
307
|
writeFileSync(
|
|
271
308
|
configPath,
|
|
272
309
|
JSON.stringify({
|
|
273
|
-
jira: {
|
|
310
|
+
jira: {
|
|
311
|
+
issueTypes: {
|
|
312
|
+
story: null,
|
|
313
|
+
},
|
|
314
|
+
},
|
|
274
315
|
}),
|
|
275
316
|
);
|
|
276
317
|
|
|
277
318
|
const result = validateForJiraSync(testProjectRoot);
|
|
278
319
|
|
|
320
|
+
// story=nullの場合、環境変数もないのでエラーになる
|
|
279
321
|
expect(result.valid).toBe(false);
|
|
280
322
|
expect(result.errors.length).toBeGreaterThan(0);
|
|
281
323
|
expect(result.errors[0]).toContain('issueTypes.story');
|
|
@@ -301,6 +343,7 @@ describe('config-validator', () => {
|
|
|
301
343
|
});
|
|
302
344
|
|
|
303
345
|
it('環境変数JIRA_ISSUE_TYPE_STORYがある場合は情報メッセージ', () => {
|
|
346
|
+
// 環境変数を明示的に設定
|
|
304
347
|
process.env.JIRA_ISSUE_TYPE_STORY = '10036';
|
|
305
348
|
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
306
349
|
writeFileSync(
|
|
@@ -322,7 +365,7 @@ describe('config-validator', () => {
|
|
|
322
365
|
}
|
|
323
366
|
});
|
|
324
367
|
|
|
325
|
-
it('issueTypes.subtask
|
|
368
|
+
it('issueTypes.subtask設定がない場合は警告(デフォルト設定なし)', () => {
|
|
326
369
|
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
327
370
|
writeFileSync(
|
|
328
371
|
configPath,
|
|
@@ -330,6 +373,7 @@ describe('config-validator', () => {
|
|
|
330
373
|
jira: {
|
|
331
374
|
issueTypes: {
|
|
332
375
|
story: '10036',
|
|
376
|
+
subtask: null, // 明示的にnullに設定してデフォルト値を無効化
|
|
333
377
|
},
|
|
334
378
|
},
|
|
335
379
|
}),
|
|
@@ -337,6 +381,7 @@ describe('config-validator', () => {
|
|
|
337
381
|
|
|
338
382
|
const result = validateForJiraSync(testProjectRoot);
|
|
339
383
|
|
|
384
|
+
// subtask=nullの場合、環境変数もないので警告になる
|
|
340
385
|
expect(result.valid).toBe(true);
|
|
341
386
|
expect(result.warnings.length).toBeGreaterThan(0);
|
|
342
387
|
expect(result.warnings[0]).toContain('subtask');
|
|
@@ -365,8 +410,23 @@ describe('config-validator', () => {
|
|
|
365
410
|
});
|
|
366
411
|
|
|
367
412
|
describe('validateForJiraSyncAsync', () => {
|
|
413
|
+
let savedEnv: {
|
|
414
|
+
JIRA_ISSUE_TYPE_STORY?: string;
|
|
415
|
+
JIRA_ISSUE_TYPE_SUBTASK?: string;
|
|
416
|
+
ATLASSIAN_URL?: string;
|
|
417
|
+
ATLASSIAN_EMAIL?: string;
|
|
418
|
+
ATLASSIAN_API_TOKEN?: string;
|
|
419
|
+
};
|
|
420
|
+
|
|
368
421
|
beforeEach(() => {
|
|
369
|
-
//
|
|
422
|
+
// 環境変数を保存して削除
|
|
423
|
+
savedEnv = {
|
|
424
|
+
JIRA_ISSUE_TYPE_STORY: process.env.JIRA_ISSUE_TYPE_STORY,
|
|
425
|
+
JIRA_ISSUE_TYPE_SUBTASK: process.env.JIRA_ISSUE_TYPE_SUBTASK,
|
|
426
|
+
ATLASSIAN_URL: process.env.ATLASSIAN_URL,
|
|
427
|
+
ATLASSIAN_EMAIL: process.env.ATLASSIAN_EMAIL,
|
|
428
|
+
ATLASSIAN_API_TOKEN: process.env.ATLASSIAN_API_TOKEN,
|
|
429
|
+
};
|
|
370
430
|
delete process.env.JIRA_ISSUE_TYPE_STORY;
|
|
371
431
|
delete process.env.JIRA_ISSUE_TYPE_SUBTASK;
|
|
372
432
|
delete process.env.ATLASSIAN_URL;
|
|
@@ -374,6 +434,35 @@ describe('config-validator', () => {
|
|
|
374
434
|
delete process.env.ATLASSIAN_API_TOKEN;
|
|
375
435
|
});
|
|
376
436
|
|
|
437
|
+
afterEach(() => {
|
|
438
|
+
// 環境変数を復元
|
|
439
|
+
if (savedEnv.JIRA_ISSUE_TYPE_STORY !== undefined) {
|
|
440
|
+
process.env.JIRA_ISSUE_TYPE_STORY = savedEnv.JIRA_ISSUE_TYPE_STORY;
|
|
441
|
+
} else {
|
|
442
|
+
delete process.env.JIRA_ISSUE_TYPE_STORY;
|
|
443
|
+
}
|
|
444
|
+
if (savedEnv.JIRA_ISSUE_TYPE_SUBTASK !== undefined) {
|
|
445
|
+
process.env.JIRA_ISSUE_TYPE_SUBTASK = savedEnv.JIRA_ISSUE_TYPE_SUBTASK;
|
|
446
|
+
} else {
|
|
447
|
+
delete process.env.JIRA_ISSUE_TYPE_SUBTASK;
|
|
448
|
+
}
|
|
449
|
+
if (savedEnv.ATLASSIAN_URL !== undefined) {
|
|
450
|
+
process.env.ATLASSIAN_URL = savedEnv.ATLASSIAN_URL;
|
|
451
|
+
} else {
|
|
452
|
+
delete process.env.ATLASSIAN_URL;
|
|
453
|
+
}
|
|
454
|
+
if (savedEnv.ATLASSIAN_EMAIL !== undefined) {
|
|
455
|
+
process.env.ATLASSIAN_EMAIL = savedEnv.ATLASSIAN_EMAIL;
|
|
456
|
+
} else {
|
|
457
|
+
delete process.env.ATLASSIAN_EMAIL;
|
|
458
|
+
}
|
|
459
|
+
if (savedEnv.ATLASSIAN_API_TOKEN !== undefined) {
|
|
460
|
+
process.env.ATLASSIAN_API_TOKEN = savedEnv.ATLASSIAN_API_TOKEN;
|
|
461
|
+
} else {
|
|
462
|
+
delete process.env.ATLASSIAN_API_TOKEN;
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
377
466
|
it('認証情報が未設定の場合は同期版と同じ結果を返す', async () => {
|
|
378
467
|
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
379
468
|
writeFileSync(
|
|
@@ -406,6 +495,7 @@ describe('config-validator', () => {
|
|
|
406
495
|
});
|
|
407
496
|
|
|
408
497
|
it('認証情報が設定されていて、Issue Type IDが存在する場合は成功', async () => {
|
|
498
|
+
// 環境変数を明示的に設定
|
|
409
499
|
process.env.ATLASSIAN_URL = 'https://test.atlassian.net';
|
|
410
500
|
process.env.ATLASSIAN_EMAIL = 'test@example.com';
|
|
411
501
|
process.env.ATLASSIAN_API_TOKEN = 'test-token';
|
|
@@ -451,16 +541,23 @@ describe('config-validator', () => {
|
|
|
451
541
|
});
|
|
452
542
|
|
|
453
543
|
it('認証情報が設定されていて、Issue Type IDが存在しない場合はエラー', async () => {
|
|
544
|
+
// 環境変数を明示的に設定(既存の環境変数を上書き)
|
|
454
545
|
process.env.ATLASSIAN_URL = 'https://test.atlassian.net';
|
|
455
546
|
process.env.ATLASSIAN_EMAIL = 'test@example.com';
|
|
456
547
|
process.env.ATLASSIAN_API_TOKEN = 'test-token';
|
|
457
548
|
process.env.JIRA_ISSUE_TYPE_STORY = '99999'; // 存在しないID
|
|
458
549
|
|
|
459
550
|
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
551
|
+
// issueTypes.storyを環境変数から取得するように空にする
|
|
552
|
+
// (デフォルト値を使わないように明示的にnullに設定)
|
|
460
553
|
writeFileSync(
|
|
461
554
|
configPath,
|
|
462
555
|
JSON.stringify({
|
|
463
|
-
jira: {
|
|
556
|
+
jira: {
|
|
557
|
+
issueTypes: {
|
|
558
|
+
story: null, // 環境変数から取得
|
|
559
|
+
},
|
|
560
|
+
},
|
|
464
561
|
}),
|
|
465
562
|
);
|
|
466
563
|
|
|
@@ -501,6 +598,7 @@ describe('config-validator', () => {
|
|
|
501
598
|
});
|
|
502
599
|
|
|
503
600
|
it('JIRA API取得に失敗した場合は警告を追加するがエラーにはしない', async () => {
|
|
601
|
+
// 環境変数を明示的に設定
|
|
504
602
|
process.env.ATLASSIAN_URL = 'https://test.atlassian.net';
|
|
505
603
|
process.env.ATLASSIAN_EMAIL = 'test@example.com';
|
|
506
604
|
process.env.ATLASSIAN_API_TOKEN = 'test-token';
|