@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,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-Repo バリデーション関数のテスト
|
|
3
|
+
* Task 1.2: バリデーション関数の実装
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import {
|
|
8
|
+
validateProjectName,
|
|
9
|
+
validateJiraKey,
|
|
10
|
+
validateRepositoryUrl,
|
|
11
|
+
} from '../multi-repo-validator';
|
|
12
|
+
|
|
13
|
+
describe('validateProjectName', () => {
|
|
14
|
+
describe('正常ケース', () => {
|
|
15
|
+
it('有効なプロジェクト名を受け入れる', () => {
|
|
16
|
+
const validNames = [
|
|
17
|
+
'my-project',
|
|
18
|
+
'プロジェクト名',
|
|
19
|
+
'project_v2',
|
|
20
|
+
'My Project 123',
|
|
21
|
+
'a',
|
|
22
|
+
'a'.repeat(100),
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
validNames.forEach((name) => {
|
|
26
|
+
const result = validateProjectName(name);
|
|
27
|
+
expect(result.isValid).toBe(true);
|
|
28
|
+
expect(result.errors).toHaveLength(0);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('パストラバーサル対策', () => {
|
|
34
|
+
it('スラッシュ(/)を含む場合はエラー', () => {
|
|
35
|
+
const result = validateProjectName('project/name');
|
|
36
|
+
expect(result.isValid).toBe(false);
|
|
37
|
+
expect(result.errors).toContain(
|
|
38
|
+
'Project name must not contain path traversal characters (/, \\)',
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('バックスラッシュ(\\)を含む場合はエラー', () => {
|
|
43
|
+
const result = validateProjectName('project\\name');
|
|
44
|
+
expect(result.isValid).toBe(false);
|
|
45
|
+
expect(result.errors).toContain(
|
|
46
|
+
'Project name must not contain path traversal characters (/, \\)',
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('../を含む場合はエラー', () => {
|
|
51
|
+
const result = validateProjectName('../etc/passwd');
|
|
52
|
+
expect(result.isValid).toBe(false);
|
|
53
|
+
expect(result.errors).toContain(
|
|
54
|
+
'Project name must not contain path traversal characters (/, \\)',
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('/を含む場合はエラー', () => {
|
|
59
|
+
const result = validateProjectName('/etc/passwd');
|
|
60
|
+
expect(result.isValid).toBe(false);
|
|
61
|
+
expect(result.errors).toContain(
|
|
62
|
+
'Project name must not contain path traversal characters (/, \\)',
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('相対パス対策', () => {
|
|
68
|
+
it('ドット(.)単独の場合はエラー', () => {
|
|
69
|
+
const result = validateProjectName('.');
|
|
70
|
+
expect(result.isValid).toBe(false);
|
|
71
|
+
expect(result.errors).toContain(
|
|
72
|
+
'Project name must not be relative path components (., ..)',
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('ダブルドット(..)単独の場合はエラー', () => {
|
|
77
|
+
const result = validateProjectName('..');
|
|
78
|
+
expect(result.isValid).toBe(false);
|
|
79
|
+
expect(result.errors).toContain(
|
|
80
|
+
'Project name must not be relative path components (., ..)',
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('制御文字対策', () => {
|
|
86
|
+
it('ヌル文字(\\x00)を含む場合はエラー', () => {
|
|
87
|
+
const result = validateProjectName('project\x00name');
|
|
88
|
+
expect(result.isValid).toBe(false);
|
|
89
|
+
expect(result.errors).toContain(
|
|
90
|
+
'Project name must not contain control characters',
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('タブ文字(\\t)を含む場合はエラー', () => {
|
|
95
|
+
const result = validateProjectName('project\tname');
|
|
96
|
+
expect(result.isValid).toBe(false);
|
|
97
|
+
expect(result.errors).toContain(
|
|
98
|
+
'Project name must not contain control characters',
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('改行文字(\\n)を含む場合はエラー', () => {
|
|
103
|
+
const result = validateProjectName('project\nname');
|
|
104
|
+
expect(result.isValid).toBe(false);
|
|
105
|
+
expect(result.errors).toContain(
|
|
106
|
+
'Project name must not contain control characters',
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('改行文字(\\r)を含む場合はエラー', () => {
|
|
111
|
+
const result = validateProjectName('project\rname');
|
|
112
|
+
expect(result.isValid).toBe(false);
|
|
113
|
+
expect(result.errors).toContain(
|
|
114
|
+
'Project name must not contain control characters',
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('エスケープ文字(\\x1B)を含む場合はエラー', () => {
|
|
119
|
+
const result = validateProjectName('project\x1Bname');
|
|
120
|
+
expect(result.isValid).toBe(false);
|
|
121
|
+
expect(result.errors).toContain(
|
|
122
|
+
'Project name must not contain control characters',
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('削除文字(\\x7F)を含む場合はエラー', () => {
|
|
127
|
+
const result = validateProjectName('project\x7Fname');
|
|
128
|
+
expect(result.isValid).toBe(false);
|
|
129
|
+
expect(result.errors).toContain(
|
|
130
|
+
'Project name must not contain control characters',
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('長さチェック', () => {
|
|
136
|
+
it('101文字以上の場合はエラー', () => {
|
|
137
|
+
const longName = 'a'.repeat(101);
|
|
138
|
+
const result = validateProjectName(longName);
|
|
139
|
+
expect(result.isValid).toBe(false);
|
|
140
|
+
expect(result.errors).toContain(
|
|
141
|
+
'Project name must be between 1 and 100 characters',
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('空文字列の場合はエラー', () => {
|
|
146
|
+
const result = validateProjectName('');
|
|
147
|
+
expect(result.isValid).toBe(false);
|
|
148
|
+
expect(result.errors).toContain(
|
|
149
|
+
'Project name must be between 1 and 100 characters',
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('100文字の場合は正常', () => {
|
|
154
|
+
const name = 'a'.repeat(100);
|
|
155
|
+
const result = validateProjectName(name);
|
|
156
|
+
expect(result.isValid).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('1文字の場合は正常', () => {
|
|
160
|
+
const result = validateProjectName('a');
|
|
161
|
+
expect(result.isValid).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('validateJiraKey', () => {
|
|
167
|
+
describe('正常ケース', () => {
|
|
168
|
+
it('2-10文字の大文字英字を受け入れる', () => {
|
|
169
|
+
const validKeys = ['AB', 'PROJ', 'TICKET', 'ABCDEFGHIJ'];
|
|
170
|
+
|
|
171
|
+
validKeys.forEach((key) => {
|
|
172
|
+
const result = validateJiraKey(key);
|
|
173
|
+
expect(result.isValid).toBe(true);
|
|
174
|
+
expect(result.errors).toHaveLength(0);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('異常ケース', () => {
|
|
180
|
+
it('小文字を含む場合はエラー', () => {
|
|
181
|
+
const result = validateJiraKey('abc');
|
|
182
|
+
expect(result.isValid).toBe(false);
|
|
183
|
+
expect(result.errors).toContain(
|
|
184
|
+
'JIRA key must be 2-10 uppercase letters',
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('1文字の場合はエラー', () => {
|
|
189
|
+
const result = validateJiraKey('A');
|
|
190
|
+
expect(result.isValid).toBe(false);
|
|
191
|
+
expect(result.errors).toContain(
|
|
192
|
+
'JIRA key must be 2-10 uppercase letters',
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('11文字以上の場合はエラー', () => {
|
|
197
|
+
const result = validateJiraKey('ABCDEFGHIJK');
|
|
198
|
+
expect(result.isValid).toBe(false);
|
|
199
|
+
expect(result.errors).toContain(
|
|
200
|
+
'JIRA key must be 2-10 uppercase letters',
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('数字を含む場合はエラー', () => {
|
|
205
|
+
const result = validateJiraKey('ABC123');
|
|
206
|
+
expect(result.isValid).toBe(false);
|
|
207
|
+
expect(result.errors).toContain(
|
|
208
|
+
'JIRA key must be 2-10 uppercase letters',
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('空文字列の場合はエラー', () => {
|
|
213
|
+
const result = validateJiraKey('');
|
|
214
|
+
expect(result.isValid).toBe(false);
|
|
215
|
+
expect(result.errors).toContain(
|
|
216
|
+
'JIRA key must be 2-10 uppercase letters',
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('ハイフンを含む場合はエラー', () => {
|
|
221
|
+
const result = validateJiraKey('ABC-DEF');
|
|
222
|
+
expect(result.isValid).toBe(false);
|
|
223
|
+
expect(result.errors).toContain(
|
|
224
|
+
'JIRA key must be 2-10 uppercase letters',
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('スペースを含む場合はエラー', () => {
|
|
229
|
+
const result = validateJiraKey('ABC DEF');
|
|
230
|
+
expect(result.isValid).toBe(false);
|
|
231
|
+
expect(result.errors).toContain(
|
|
232
|
+
'JIRA key must be 2-10 uppercase letters',
|
|
233
|
+
);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe('validateRepositoryUrl', () => {
|
|
239
|
+
describe('正常ケース', () => {
|
|
240
|
+
it('有効なGitHub HTTPS URLを受け入れる', () => {
|
|
241
|
+
const validUrls = [
|
|
242
|
+
'https://github.com/owner/repo',
|
|
243
|
+
'https://github.com/my-org/my-repo',
|
|
244
|
+
'https://github.com/user123/project-name',
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
validUrls.forEach((url) => {
|
|
248
|
+
const result = validateRepositoryUrl(url);
|
|
249
|
+
expect(result.isValid).toBe(true);
|
|
250
|
+
expect(result.errors).toHaveLength(0);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe('異常ケース', () => {
|
|
256
|
+
it('GitLab URLの場合はエラー', () => {
|
|
257
|
+
const result = validateRepositoryUrl(
|
|
258
|
+
'https://gitlab.com/owner/repo',
|
|
259
|
+
);
|
|
260
|
+
expect(result.isValid).toBe(false);
|
|
261
|
+
expect(result.errors).toContain(
|
|
262
|
+
'Repository URL must be in GitHub format: https://github.com/{owner}/{repo}',
|
|
263
|
+
);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('HTTP URLの場合はエラー', () => {
|
|
267
|
+
const result = validateRepositoryUrl('http://github.com/owner/repo');
|
|
268
|
+
expect(result.isValid).toBe(false);
|
|
269
|
+
expect(result.errors).toContain(
|
|
270
|
+
'Repository URL must use HTTPS protocol',
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('SSH URLの場合はエラー', () => {
|
|
275
|
+
const result = validateRepositoryUrl('git@github.com:owner/repo.git');
|
|
276
|
+
expect(result.isValid).toBe(false);
|
|
277
|
+
expect(result.errors).toContain(
|
|
278
|
+
'Repository URL must be in GitHub format: https://github.com/{owner}/{repo}',
|
|
279
|
+
);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('不完全なURLの場合はエラー', () => {
|
|
283
|
+
const result = validateRepositoryUrl('https://github.com/owner');
|
|
284
|
+
expect(result.isValid).toBe(false);
|
|
285
|
+
expect(result.errors).toContain(
|
|
286
|
+
'Repository URL must be in GitHub format: https://github.com/{owner}/{repo}',
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('空文字列の場合はエラー', () => {
|
|
291
|
+
const result = validateRepositoryUrl('');
|
|
292
|
+
expect(result.isValid).toBe(false);
|
|
293
|
+
expect(result.errors).toContain('Repository URL is empty');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('無効なURL形式の場合はエラー', () => {
|
|
297
|
+
const result = validateRepositoryUrl('not-a-url');
|
|
298
|
+
expect(result.isValid).toBe(false);
|
|
299
|
+
expect(result.errors).toContain('Repository URL format is invalid');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('.git拡張子を含む場合はエラー', () => {
|
|
303
|
+
const result = validateRepositoryUrl(
|
|
304
|
+
'https://github.com/owner/repo.git',
|
|
305
|
+
);
|
|
306
|
+
expect(result.isValid).toBe(false);
|
|
307
|
+
expect(result.errors).toContain(
|
|
308
|
+
'Repository URL must not include .git extension',
|
|
309
|
+
);
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe('プレースホルダー検出', () => {
|
|
314
|
+
it('プレースホルダーURLの場合はエラー', () => {
|
|
315
|
+
const placeholders = [
|
|
316
|
+
'https://github.com/your-org/your-repo',
|
|
317
|
+
'https://github.com/owner/repo-name',
|
|
318
|
+
];
|
|
319
|
+
|
|
320
|
+
placeholders.forEach((url) => {
|
|
321
|
+
const result = validateRepositoryUrl(url);
|
|
322
|
+
if (
|
|
323
|
+
url.includes('your-org') ||
|
|
324
|
+
url.includes('your-repo') ||
|
|
325
|
+
url.includes('repo-name')
|
|
326
|
+
) {
|
|
327
|
+
expect(result.isValid).toBe(false);
|
|
328
|
+
expect(result.errors).toContain(
|
|
329
|
+
'Repository URL contains placeholder values',
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
});
|
|
@@ -3,12 +3,19 @@
|
|
|
3
3
|
* デフォルト設定 + プロジェクト固有設定をマージ
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { readFileSync, existsSync, statSync } from 'fs';
|
|
7
|
-
import { resolve, relative, isAbsolute } from 'path';
|
|
6
|
+
import { readFileSync, writeFileSync, existsSync, statSync, renameSync, unlinkSync, mkdirSync } from 'fs';
|
|
7
|
+
import { resolve, relative, isAbsolute, dirname } from 'path';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { homedir } from 'os';
|
|
10
10
|
import { config, parse as dotenvParse } from 'dotenv';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
AppConfigSchema,
|
|
13
|
+
MultiRepoProjectSchema,
|
|
14
|
+
RepositorySchema,
|
|
15
|
+
type AppConfig,
|
|
16
|
+
type MultiRepoProject,
|
|
17
|
+
type Repository,
|
|
18
|
+
} from '../config/config-schema.js';
|
|
12
19
|
|
|
13
20
|
// 環境変数読み込み
|
|
14
21
|
config();
|
|
@@ -612,3 +619,190 @@ export function getConfigPath(projectRoot: string = process.cwd()): string {
|
|
|
612
619
|
return resolveConfigPath(projectRoot);
|
|
613
620
|
}
|
|
614
621
|
|
|
622
|
+
/**
|
|
623
|
+
* Multi-Repo管理関数
|
|
624
|
+
*/
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* 設定ファイルをアトミックに保存
|
|
628
|
+
* 一時ファイルに書き込んでからrenameすることでアトミック性を保証
|
|
629
|
+
*/
|
|
630
|
+
function saveConfig(
|
|
631
|
+
config: Partial<AppConfig>,
|
|
632
|
+
projectRoot: string = process.cwd(),
|
|
633
|
+
): void {
|
|
634
|
+
const configPath = resolveConfigPath(projectRoot);
|
|
635
|
+
const tempPath = `${configPath}.tmp`;
|
|
636
|
+
|
|
637
|
+
try {
|
|
638
|
+
// .michiディレクトリが存在しない場合は作成
|
|
639
|
+
const configDir = dirname(configPath);
|
|
640
|
+
if (!existsSync(configDir)) {
|
|
641
|
+
mkdirSync(configDir, { recursive: true });
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// 一時ファイルに書き込み
|
|
645
|
+
writeFileSync(tempPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
646
|
+
|
|
647
|
+
// アトミックにリネーム
|
|
648
|
+
renameSync(tempPath, configPath);
|
|
649
|
+
|
|
650
|
+
// キャッシュをクリア
|
|
651
|
+
clearConfigCache();
|
|
652
|
+
} catch (error) {
|
|
653
|
+
// エラー時は一時ファイルを削除
|
|
654
|
+
if (existsSync(tempPath)) {
|
|
655
|
+
try {
|
|
656
|
+
unlinkSync(tempPath);
|
|
657
|
+
} catch {
|
|
658
|
+
// 削除失敗は無視
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
throw error;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* プロジェクト名でMulti-Repoプロジェクトを検索
|
|
667
|
+
*/
|
|
668
|
+
export function findProject(
|
|
669
|
+
projectName: string,
|
|
670
|
+
projectRoot: string = process.cwd(),
|
|
671
|
+
): MultiRepoProject | null {
|
|
672
|
+
const configPath = resolveConfigPath(projectRoot);
|
|
673
|
+
|
|
674
|
+
if (!existsSync(configPath)) {
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
try {
|
|
679
|
+
const config = loadProjectConfig(projectRoot);
|
|
680
|
+
if (!config?.multiRepoProjects) {
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const project = config.multiRepoProjects.find(
|
|
685
|
+
(p) => p.name === projectName,
|
|
686
|
+
);
|
|
687
|
+
return project || null;
|
|
688
|
+
} catch {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Multi-Repoプロジェクトを追加
|
|
695
|
+
*/
|
|
696
|
+
export function addMultiRepoProject(
|
|
697
|
+
project: MultiRepoProject,
|
|
698
|
+
projectRoot: string = process.cwd(),
|
|
699
|
+
): { success: boolean; project?: MultiRepoProject; error?: string } {
|
|
700
|
+
try {
|
|
701
|
+
// Zodスキーマでバリデーション
|
|
702
|
+
const validatedProject = MultiRepoProjectSchema.parse(project);
|
|
703
|
+
|
|
704
|
+
// 既存の設定を読み込み
|
|
705
|
+
const existingConfig = loadProjectConfig(projectRoot) || {};
|
|
706
|
+
const multiRepoProjects = existingConfig.multiRepoProjects || [];
|
|
707
|
+
|
|
708
|
+
// 重複チェック
|
|
709
|
+
const existingProject = multiRepoProjects.find(
|
|
710
|
+
(p) => p.name === validatedProject.name,
|
|
711
|
+
);
|
|
712
|
+
if (existingProject) {
|
|
713
|
+
return {
|
|
714
|
+
success: false,
|
|
715
|
+
error: `Project "${validatedProject.name}" already exists`,
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// プロジェクトを追加
|
|
720
|
+
const updatedConfig = {
|
|
721
|
+
...existingConfig,
|
|
722
|
+
multiRepoProjects: [...multiRepoProjects, validatedProject],
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
// 設定を保存
|
|
726
|
+
saveConfig(updatedConfig, projectRoot);
|
|
727
|
+
|
|
728
|
+
return {
|
|
729
|
+
success: true,
|
|
730
|
+
project: validatedProject,
|
|
731
|
+
};
|
|
732
|
+
} catch (error) {
|
|
733
|
+
return {
|
|
734
|
+
success: false,
|
|
735
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Multi-Repoプロジェクトにリポジトリを追加
|
|
742
|
+
*/
|
|
743
|
+
export function addRepositoryToProject(
|
|
744
|
+
projectName: string,
|
|
745
|
+
repository: Repository,
|
|
746
|
+
projectRoot: string = process.cwd(),
|
|
747
|
+
): { success: boolean; repository?: Repository; error?: string } {
|
|
748
|
+
try {
|
|
749
|
+
// Zodスキーマでバリデーション
|
|
750
|
+
const validatedRepo = RepositorySchema.parse(repository);
|
|
751
|
+
|
|
752
|
+
// 既存の設定を読み込み
|
|
753
|
+
const existingConfig = loadProjectConfig(projectRoot) || {};
|
|
754
|
+
const multiRepoProjects = existingConfig.multiRepoProjects || [];
|
|
755
|
+
|
|
756
|
+
// プロジェクトを検索
|
|
757
|
+
const projectIndex = multiRepoProjects.findIndex(
|
|
758
|
+
(p) => p.name === projectName,
|
|
759
|
+
);
|
|
760
|
+
if (projectIndex === -1) {
|
|
761
|
+
return {
|
|
762
|
+
success: false,
|
|
763
|
+
error: `Project "${projectName}" not found`,
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const project = multiRepoProjects[projectIndex];
|
|
768
|
+
|
|
769
|
+
// 重複チェック
|
|
770
|
+
const existingRepo = project.repositories.find(
|
|
771
|
+
(r) => r.name === validatedRepo.name,
|
|
772
|
+
);
|
|
773
|
+
if (existingRepo) {
|
|
774
|
+
return {
|
|
775
|
+
success: false,
|
|
776
|
+
error: `Repository "${validatedRepo.name}" already exists in project "${projectName}"`,
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// リポジトリを追加
|
|
781
|
+
const updatedProject = {
|
|
782
|
+
...project,
|
|
783
|
+
repositories: [...project.repositories, validatedRepo],
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
// プロジェクト配列を更新
|
|
787
|
+
const updatedProjects = [...multiRepoProjects];
|
|
788
|
+
updatedProjects[projectIndex] = updatedProject;
|
|
789
|
+
|
|
790
|
+
// 設定を保存
|
|
791
|
+
const updatedConfig = {
|
|
792
|
+
...existingConfig,
|
|
793
|
+
multiRepoProjects: updatedProjects,
|
|
794
|
+
};
|
|
795
|
+
saveConfig(updatedConfig, projectRoot);
|
|
796
|
+
|
|
797
|
+
return {
|
|
798
|
+
success: true,
|
|
799
|
+
repository: validatedRepo,
|
|
800
|
+
};
|
|
801
|
+
} catch (error) {
|
|
802
|
+
return {
|
|
803
|
+
success: false,
|
|
804
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* multi-repo-validator.ts
|
|
3
|
+
* Multi-Repo機能のバリデーションユーティリティ
|
|
4
|
+
*
|
|
5
|
+
* プロジェクト名、JIRAキー、リポジトリURLのバリデーションとセキュリティチェックを行います。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* バリデーション結果
|
|
10
|
+
*/
|
|
11
|
+
export interface ValidationResult {
|
|
12
|
+
isValid: boolean;
|
|
13
|
+
errors: string[];
|
|
14
|
+
warnings: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* プロジェクト名のバリデーション
|
|
19
|
+
* セキュリティ対策: パストラバーサル、相対パス、制御文字をチェック
|
|
20
|
+
*/
|
|
21
|
+
export function validateProjectName(name: string): ValidationResult {
|
|
22
|
+
const errors: string[] = [];
|
|
23
|
+
const warnings: string[] = [];
|
|
24
|
+
|
|
25
|
+
// 長さチェック: 1-100文字
|
|
26
|
+
if (name.length < 1 || name.length > 100) {
|
|
27
|
+
errors.push('Project name must be between 1 and 100 characters');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// パストラバーサル対策: '/', '\' 禁止
|
|
31
|
+
if (name.includes('/') || name.includes('\\')) {
|
|
32
|
+
errors.push(
|
|
33
|
+
'Project name must not contain path traversal characters (/, \\)',
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 相対パス対策: '.', '..' 禁止
|
|
38
|
+
if (name === '.' || name === '..') {
|
|
39
|
+
errors.push(
|
|
40
|
+
'Project name must not be relative path components (., ..)',
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 制御文字対策: \x00-\x1F, \x7F 禁止
|
|
45
|
+
// eslint-disable-next-line no-control-regex
|
|
46
|
+
const controlCharRegex = /[\x00-\x1F\x7F]/;
|
|
47
|
+
if (controlCharRegex.test(name)) {
|
|
48
|
+
errors.push('Project name must not contain control characters');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
isValid: errors.length === 0,
|
|
53
|
+
errors,
|
|
54
|
+
warnings,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* JIRAキーのバリデーション
|
|
60
|
+
* 2-10文字の大文字英字のみ許可
|
|
61
|
+
*/
|
|
62
|
+
export function validateJiraKey(key: string): ValidationResult {
|
|
63
|
+
const errors: string[] = [];
|
|
64
|
+
const warnings: string[] = [];
|
|
65
|
+
|
|
66
|
+
// 正規表現: 2-10文字の大文字英字
|
|
67
|
+
const jiraKeyRegex = /^[A-Z]{2,10}$/;
|
|
68
|
+
|
|
69
|
+
if (!jiraKeyRegex.test(key)) {
|
|
70
|
+
errors.push('JIRA key must be 2-10 uppercase letters');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
isValid: errors.length === 0,
|
|
75
|
+
errors,
|
|
76
|
+
warnings,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* リポジトリURLのバリデーション
|
|
82
|
+
* GitHub HTTPS URL形式のみ許可
|
|
83
|
+
*/
|
|
84
|
+
export function validateRepositoryUrl(url: string): ValidationResult {
|
|
85
|
+
const errors: string[] = [];
|
|
86
|
+
const warnings: string[] = [];
|
|
87
|
+
|
|
88
|
+
// 空文字列チェック
|
|
89
|
+
if (!url || url.trim() === '') {
|
|
90
|
+
errors.push('Repository URL is empty');
|
|
91
|
+
return { isValid: false, errors, warnings };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// SSH URL検出(git@github.com:形式)
|
|
95
|
+
if (url.startsWith('git@')) {
|
|
96
|
+
errors.push(
|
|
97
|
+
'Repository URL must be in GitHub format: https://github.com/{owner}/{repo}',
|
|
98
|
+
);
|
|
99
|
+
return { isValid: false, errors, warnings };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// URL形式チェック
|
|
103
|
+
try {
|
|
104
|
+
const parsedUrl = new URL(url);
|
|
105
|
+
|
|
106
|
+
// HTTPSプロトコルチェック
|
|
107
|
+
if (parsedUrl.protocol !== 'https:') {
|
|
108
|
+
errors.push('Repository URL must use HTTPS protocol');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// GitHub URL形式チェック: https://github.com/{owner}/{repo}
|
|
112
|
+
const githubPattern = /^https:\/\/github\.com\/[^/]+\/[^/]+$/;
|
|
113
|
+
if (!githubPattern.test(url)) {
|
|
114
|
+
errors.push(
|
|
115
|
+
'Repository URL must be in GitHub format: https://github.com/{owner}/{repo}',
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// .git拡張子チェック
|
|
120
|
+
if (url.endsWith('.git')) {
|
|
121
|
+
errors.push('Repository URL must not include .git extension');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// プレースホルダー検出
|
|
125
|
+
if (
|
|
126
|
+
url.includes('your-org') ||
|
|
127
|
+
url.includes('your-repo') ||
|
|
128
|
+
url.includes('repo-name')
|
|
129
|
+
) {
|
|
130
|
+
errors.push('Repository URL contains placeholder values');
|
|
131
|
+
}
|
|
132
|
+
} catch (_error) {
|
|
133
|
+
errors.push('Repository URL format is invalid');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
isValid: errors.length === 0,
|
|
138
|
+
errors,
|
|
139
|
+
warnings,
|
|
140
|
+
};
|
|
141
|
+
}
|