@sk8metal/michi-cli 0.4.0 → 0.7.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 +162 -1
- package/README.md +2 -1
- package/dist/scripts/config/config-schema.d.ts +87 -0
- package/dist/scripts/config/config-schema.d.ts.map +1 -1
- package/dist/scripts/config/config-schema.js +81 -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/pr-automation.d.ts.map +1 -1
- package/dist/scripts/pr-automation.js +11 -3
- package/dist/scripts/pr-automation.js.map +1 -1
- package/dist/scripts/spec-impl-workflow.d.ts.map +1 -1
- package/dist/scripts/spec-impl-workflow.js +22 -6
- package/dist/scripts/spec-impl-workflow.js.map +1 -1
- 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 +31 -5
- package/dist/scripts/utils/config-loader.d.ts.map +1 -1
- package/dist/scripts/utils/config-loader.js +363 -50
- package/dist/scripts/utils/config-loader.js.map +1 -1
- package/dist/scripts/utils/env-config.d.ts +1 -1
- package/dist/scripts/utils/env-config.d.ts.map +1 -1
- package/dist/scripts/utils/env-config.js +2 -14
- package/dist/scripts/utils/env-config.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/project-meta.d.ts +9 -0
- package/dist/scripts/utils/project-meta.d.ts.map +1 -1
- package/dist/scripts/utils/project-meta.js +22 -0
- package/dist/scripts/utils/project-meta.js.map +1 -1
- package/dist/scripts/utils/security-validator.d.ts +55 -0
- package/dist/scripts/utils/security-validator.d.ts.map +1 -0
- package/dist/scripts/utils/security-validator.js +232 -0
- package/dist/scripts/utils/security-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 +303 -17
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/config-validate.d.ts +9 -0
- package/dist/src/commands/config-validate.d.ts.map +1 -0
- package/dist/src/commands/config-validate.js +90 -0
- package/dist/src/commands/config-validate.js.map +1 -0
- package/dist/src/commands/init.d.ts +1 -0
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +29 -6
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/migrate.d.ts +25 -0
- package/dist/src/commands/migrate.d.ts.map +1 -0
- package/dist/src/commands/migrate.js +341 -0
- package/dist/src/commands/migrate.js.map +1 -0
- 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 +0 -1
- 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/dist/vitest.config.d.ts.map +1 -1
- package/dist/vitest.config.js +32 -8
- package/dist/vitest.config.js.map +1 -1
- package/docs/michi-development/design/config-unification.md +4789 -0
- package/docs/user-guide/getting-started/github-token-setup.md +2 -1
- package/docs/user-guide/getting-started/new-repository-setup.md +1 -1
- package/docs/user-guide/getting-started/quick-start.md +1 -1
- package/docs/user-guide/getting-started/setup.md +4 -11
- 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/hands-on/claude-agent-setup.md +2 -2
- package/docs/user-guide/hands-on/claude-setup.md +2 -2
- package/docs/user-guide/hands-on/cursor-setup.md +2 -2
- package/docs/user-guide/hands-on/workflow-walkthrough.md +4 -1
- package/docs/user-guide/reference/multi-repo-api.md +771 -0
- package/docs/user-guide/reference/quick-reference.md +22 -37
- package/env.example +1 -1
- package/package.json +2 -5
- 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__/spec-impl-workflow.test.ts +5 -2
- package/scripts/__tests__/test-script-runner.test.ts +217 -0
- package/scripts/config/config-schema.ts +104 -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/pr-automation.ts +15 -5
- package/scripts/spec-impl-workflow.ts +22 -6
- 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-loader.test.ts +149 -0
- package/scripts/utils/__tests__/config-validator.test.ts +106 -6
- package/scripts/utils/__tests__/env-config.test.ts +0 -2
- package/scripts/utils/__tests__/multi-repo-validator.test.ts +335 -0
- package/scripts/utils/__tests__/project-meta.test.ts +192 -0
- package/scripts/utils/__tests__/security-validator.test.ts +272 -0
- package/scripts/utils/config-loader.ts +429 -56
- package/scripts/utils/env-config.ts +2 -14
- package/scripts/utils/multi-repo-validator.ts +141 -0
- package/scripts/utils/project-meta.ts +27 -0
- package/scripts/utils/security-validator.ts +286 -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/kiro/kiro-spec-impl.md +1 -1
- package/templates/claude/commands/michi/spec-impl.md +208 -35
- package/templates/claude-agent/commands/kiro/kiro-spec-impl.md +1 -1
- package/templates/cursor/commands/kiro/kiro-spec-impl.md +1 -1
- 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 -321
- 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 -201
- 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 -394
- 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 -218
- 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__/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/scripts/create-project.ts +0 -386
- package/scripts/list-projects.ts +0 -112
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for TestScriptRunner
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import { TestScriptRunner } from '../test-script-runner.js';
|
|
7
|
+
import * as child_process from 'child_process';
|
|
8
|
+
|
|
9
|
+
vi.mock('child_process');
|
|
10
|
+
|
|
11
|
+
// カスタムエラー型の定義
|
|
12
|
+
interface ExecError extends Error {
|
|
13
|
+
status?: number;
|
|
14
|
+
code?: string;
|
|
15
|
+
stderr?: Buffer;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('TestScriptRunner', () => {
|
|
19
|
+
let runner: TestScriptRunner;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
vi.clearAllMocks();
|
|
23
|
+
runner = new TestScriptRunner();
|
|
24
|
+
vi.useFakeTimers();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
vi.restoreAllMocks();
|
|
29
|
+
vi.useRealTimers();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('正常ケース', () => {
|
|
33
|
+
it('テストスクリプト実行が成功(終了コード0)', async () => {
|
|
34
|
+
vi.spyOn(child_process, 'execSync').mockReturnValue(Buffer.from('Test passed'));
|
|
35
|
+
|
|
36
|
+
const startTime = Date.now();
|
|
37
|
+
vi.setSystemTime(startTime);
|
|
38
|
+
|
|
39
|
+
const result = await runner.runTestScript('my-project', 'e2e');
|
|
40
|
+
|
|
41
|
+
expect(result.success).toBe(true);
|
|
42
|
+
expect(result.exitCode).toBe(0);
|
|
43
|
+
expect(result.executionTime).toBeGreaterThanOrEqual(0);
|
|
44
|
+
expect(result.outputPath).toContain('docs/michi/my-project/tests/results/e2e-');
|
|
45
|
+
expect(result.error).toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('統合テスト実行が成功', async () => {
|
|
49
|
+
vi.spyOn(child_process, 'execSync').mockReturnValue(Buffer.from('Integration test passed'));
|
|
50
|
+
|
|
51
|
+
const result = await runner.runTestScript('my-project', 'integration');
|
|
52
|
+
|
|
53
|
+
expect(result.success).toBe(true);
|
|
54
|
+
expect(result.exitCode).toBe(0);
|
|
55
|
+
expect(result.outputPath).toContain('integration-');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('パフォーマンステスト実行が成功', async () => {
|
|
59
|
+
vi.spyOn(child_process, 'execSync').mockReturnValue(Buffer.from('Performance test passed'));
|
|
60
|
+
|
|
61
|
+
const result = await runner.runTestScript('my-project', 'performance');
|
|
62
|
+
|
|
63
|
+
expect(result.success).toBe(true);
|
|
64
|
+
expect(result.exitCode).toBe(0);
|
|
65
|
+
expect(result.outputPath).toContain('performance-');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('実行時間を正しく計測', async () => {
|
|
69
|
+
const startTime = Date.now();
|
|
70
|
+
vi.setSystemTime(startTime);
|
|
71
|
+
|
|
72
|
+
vi.spyOn(child_process, 'execSync').mockImplementation(() => {
|
|
73
|
+
// 5秒後に完了をシミュレート
|
|
74
|
+
vi.setSystemTime(startTime + 5000);
|
|
75
|
+
return Buffer.from('Test passed');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const result = await runner.runTestScript('my-project', 'e2e');
|
|
79
|
+
|
|
80
|
+
expect(result.executionTime).toBeCloseTo(5, 1);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('テスト失敗ケース', () => {
|
|
85
|
+
it('テストスクリプト実行が失敗(終了コード非0)', async () => {
|
|
86
|
+
const error: ExecError = new Error('Command failed');
|
|
87
|
+
error.status = 1;
|
|
88
|
+
error.stderr = Buffer.from('Test failed: assertion error');
|
|
89
|
+
|
|
90
|
+
vi.spyOn(child_process, 'execSync').mockImplementation(() => {
|
|
91
|
+
throw error;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const result = await runner.runTestScript('my-project', 'e2e');
|
|
95
|
+
|
|
96
|
+
expect(result.success).toBe(false);
|
|
97
|
+
expect(result.exitCode).toBe(1);
|
|
98
|
+
expect(result.error).toContain('Test failed');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('終了コード2のテスト失敗', async () => {
|
|
102
|
+
const error: ExecError = new Error('Command failed');
|
|
103
|
+
error.status = 2;
|
|
104
|
+
|
|
105
|
+
vi.spyOn(child_process, 'execSync').mockImplementation(() => {
|
|
106
|
+
throw error;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const result = await runner.runTestScript('my-project', 'integration');
|
|
110
|
+
|
|
111
|
+
expect(result.success).toBe(false);
|
|
112
|
+
expect(result.exitCode).toBe(2);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('エラーハンドリング', () => {
|
|
117
|
+
it('スクリプト実行タイムアウトの場合はエラー', async () => {
|
|
118
|
+
const error: ExecError = new Error('Timeout');
|
|
119
|
+
error.code = 'ETIMEDOUT';
|
|
120
|
+
|
|
121
|
+
vi.spyOn(child_process, 'execSync').mockImplementation(() => {
|
|
122
|
+
throw error;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const result = await runner.runTestScript('my-project', 'e2e');
|
|
126
|
+
|
|
127
|
+
expect(result.success).toBe(false);
|
|
128
|
+
expect(result.error).toContain('Timeout');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('パーミッションエラーの場合はエラー', async () => {
|
|
132
|
+
const error: ExecError = new Error('Permission denied');
|
|
133
|
+
error.code = 'EACCES';
|
|
134
|
+
|
|
135
|
+
vi.spyOn(child_process, 'execSync').mockImplementation(() => {
|
|
136
|
+
throw error;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const result = await runner.runTestScript('my-project', 'e2e');
|
|
140
|
+
|
|
141
|
+
expect(result.success).toBe(false);
|
|
142
|
+
expect(result.error).toContain('Permission denied');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('スクリプト未存在エラーの場合はエラー', async () => {
|
|
146
|
+
const error: ExecError = new Error('Script not found');
|
|
147
|
+
error.code = 'ENOENT';
|
|
148
|
+
|
|
149
|
+
vi.spyOn(child_process, 'execSync').mockImplementation(() => {
|
|
150
|
+
throw error;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const result = await runner.runTestScript('my-project', 'e2e');
|
|
154
|
+
|
|
155
|
+
expect(result.success).toBe(false);
|
|
156
|
+
expect(result.error).toContain('Script not found');
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('カスタムプロジェクトルート', () => {
|
|
161
|
+
it('カスタムプロジェクトルートを指定できる', async () => {
|
|
162
|
+
const customRoot = '/custom/path';
|
|
163
|
+
vi.spyOn(child_process, 'execSync').mockReturnValue(Buffer.from('Test passed'));
|
|
164
|
+
|
|
165
|
+
const result = await runner.runTestScript('my-project', 'e2e', customRoot);
|
|
166
|
+
|
|
167
|
+
expect(result.success).toBe(true);
|
|
168
|
+
expect(child_process.execSync).toHaveBeenCalledWith(
|
|
169
|
+
expect.stringContaining(`${customRoot}/docs/michi/my-project/tests/scripts/run-e2e.sh`),
|
|
170
|
+
expect.any(Object)
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('ログ出力', () => {
|
|
176
|
+
it('実行前にログを出力', async () => {
|
|
177
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
178
|
+
vi.spyOn(child_process, 'execSync').mockReturnValue(Buffer.from('Test passed'));
|
|
179
|
+
|
|
180
|
+
await runner.runTestScript('my-project', 'e2e');
|
|
181
|
+
|
|
182
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
183
|
+
expect.stringContaining('🚀 テストスクリプトを実行中')
|
|
184
|
+
);
|
|
185
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
186
|
+
expect.stringContaining('テストタイプ: e2e')
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('成功時に成功メッセージを表示', async () => {
|
|
191
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
192
|
+
vi.spyOn(child_process, 'execSync').mockReturnValue(Buffer.from('Test passed'));
|
|
193
|
+
|
|
194
|
+
await runner.runTestScript('my-project', 'e2e');
|
|
195
|
+
|
|
196
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
197
|
+
expect.stringContaining('✅ テスト実行が成功しました')
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('失敗時に失敗メッセージを表示', async () => {
|
|
202
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
203
|
+
const error: ExecError = new Error('Command failed');
|
|
204
|
+
error.status = 1;
|
|
205
|
+
|
|
206
|
+
vi.spyOn(child_process, 'execSync').mockImplementation(() => {
|
|
207
|
+
throw error;
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
await runner.runTestScript('my-project', 'e2e');
|
|
211
|
+
|
|
212
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
213
|
+
expect.stringContaining('❌ テスト実行が失敗しました')
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -146,6 +146,92 @@ export const ValidationConfigSchema = z.object({
|
|
|
146
146
|
weekendExclusion: z.boolean().default(true),
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Atlassian設定スキーマ
|
|
151
|
+
*/
|
|
152
|
+
export const AtlassianConfigSchema = z.object({
|
|
153
|
+
url: z.string().optional(),
|
|
154
|
+
email: z.string().optional(),
|
|
155
|
+
apiToken: z.string().optional(),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* プロジェクトメタデータスキーマ
|
|
160
|
+
*/
|
|
161
|
+
export const ProjectMetaSchema = z.object({
|
|
162
|
+
projectId: z.string().min(1),
|
|
163
|
+
projectName: z.string().min(1),
|
|
164
|
+
language: z.enum(['ja', 'en']).optional(),
|
|
165
|
+
jiraProjectKey: z.string().optional(),
|
|
166
|
+
confluenceLabels: z.array(z.string()).optional(),
|
|
167
|
+
status: z.string().optional(),
|
|
168
|
+
team: z.array(z.string()).optional(),
|
|
169
|
+
stakeholders: z.array(z.string()).optional(),
|
|
170
|
+
repository: z.string().optional(),
|
|
171
|
+
description: z.string().optional(),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Multi-Repo リポジトリスキーマ
|
|
176
|
+
*/
|
|
177
|
+
export const RepositorySchema = z.object({
|
|
178
|
+
name: z.string().min(1),
|
|
179
|
+
url: z
|
|
180
|
+
.string()
|
|
181
|
+
.url()
|
|
182
|
+
.regex(/^https:\/\/github\.com\/[^/]+\/[^/]+$/, {
|
|
183
|
+
message: 'GitHub URL must be in format: https://github.com/{owner}/{repo}',
|
|
184
|
+
}),
|
|
185
|
+
branch: z.string().default('main'),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Multi-Repo プロジェクトスキーマ
|
|
190
|
+
*/
|
|
191
|
+
export const MultiRepoProjectSchema = z.object({
|
|
192
|
+
name: z
|
|
193
|
+
.string()
|
|
194
|
+
.min(1, { message: 'Project name must be at least 1 character' })
|
|
195
|
+
.max(100, { message: 'Project name must be at most 100 characters' })
|
|
196
|
+
.refine(
|
|
197
|
+
(name) => {
|
|
198
|
+
// パストラバーサル対策: '/', '\' 禁止
|
|
199
|
+
if (name.includes('/') || name.includes('\\')) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
// 相対パス対策: '.', '..' 禁止
|
|
203
|
+
if (name === '.' || name === '..') {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
// 制御文字対策: \x00-\x1F, \x7F 禁止
|
|
207
|
+
// eslint-disable-next-line no-control-regex
|
|
208
|
+
const controlCharRegex = /[\x00-\x1F\x7F]/;
|
|
209
|
+
if (controlCharRegex.test(name)) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
message:
|
|
216
|
+
'Project name must not contain path traversal characters (/, \\), relative path components (., ..), or control characters',
|
|
217
|
+
},
|
|
218
|
+
),
|
|
219
|
+
jiraKey: z
|
|
220
|
+
.string()
|
|
221
|
+
.min(1)
|
|
222
|
+
.regex(/^[A-Z]{2,10}$/, {
|
|
223
|
+
message: 'JIRA key must be 2-10 uppercase letters',
|
|
224
|
+
}),
|
|
225
|
+
confluenceSpace: z.string().min(1, {
|
|
226
|
+
message: 'Confluence space must be a non-empty string',
|
|
227
|
+
}),
|
|
228
|
+
createdAt: z.string().datetime({
|
|
229
|
+
offset: true,
|
|
230
|
+
message: 'createdAt must be in ISO 8601 format',
|
|
231
|
+
}),
|
|
232
|
+
repositories: z.array(RepositorySchema).default([]),
|
|
233
|
+
});
|
|
234
|
+
|
|
149
235
|
/**
|
|
150
236
|
* 全体設定スキーマ
|
|
151
237
|
*/
|
|
@@ -154,6 +240,9 @@ export const AppConfigSchema = z.object({
|
|
|
154
240
|
jira: JiraConfigSchema.optional(),
|
|
155
241
|
workflow: WorkflowConfigSchema.optional(),
|
|
156
242
|
validation: ValidationConfigSchema.optional(),
|
|
243
|
+
atlassian: AtlassianConfigSchema.optional(),
|
|
244
|
+
project: ProjectMetaSchema.optional(),
|
|
245
|
+
multiRepoProjects: z.array(MultiRepoProjectSchema).default([]),
|
|
157
246
|
});
|
|
158
247
|
|
|
159
248
|
/**
|
|
@@ -177,4 +266,19 @@ export type JiraStatusMapping = z.infer<typeof JiraStatusMappingSchema>;
|
|
|
177
266
|
export type JiraConfig = z.infer<typeof JiraConfigSchema>;
|
|
178
267
|
export type WorkflowConfig = z.infer<typeof WorkflowConfigSchema>;
|
|
179
268
|
export type ValidationConfig = z.infer<typeof ValidationConfigSchema>;
|
|
269
|
+
export type AtlassianConfig = z.infer<typeof AtlassianConfigSchema>;
|
|
270
|
+
export type ProjectMeta = z.infer<typeof ProjectMetaSchema>;
|
|
271
|
+
export type Repository = z.infer<typeof RepositorySchema>;
|
|
272
|
+
export type MultiRepoProject = z.infer<typeof MultiRepoProjectSchema>;
|
|
180
273
|
export type AppConfig = z.infer<typeof AppConfigSchema>;
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* 設定の読み込み元
|
|
277
|
+
*/
|
|
278
|
+
export type ConfigSource =
|
|
279
|
+
| 'default' // default-config.json
|
|
280
|
+
| 'global-env' // ~/.michi/.env
|
|
281
|
+
| 'global-config' // ~/.michi/config.json
|
|
282
|
+
| 'project-meta' // .kiro/project.json
|
|
283
|
+
| 'project-config' // .michi/config.json
|
|
284
|
+
| 'project-env'; // .env
|
|
@@ -17,6 +17,20 @@ import { updateSpecJsonAfterConfluenceSync, loadSpecJson } from './utils/spec-up
|
|
|
17
17
|
// 環境変数読み込み
|
|
18
18
|
config();
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* セキュリティ: CQLクエリ文字列のエスケープ
|
|
22
|
+
* CQLインジェクション対策: バックスラッシュと引用符を適切にエスケープ
|
|
23
|
+
*
|
|
24
|
+
* @param str - エスケープする文字列
|
|
25
|
+
* @returns エスケープされた文字列
|
|
26
|
+
*/
|
|
27
|
+
function escapeCQL(str: string): string {
|
|
28
|
+
return str
|
|
29
|
+
.replace(/\\/g, '\\\\') // バックスラッシュを先にエスケープ(重要: 最初に実行)
|
|
30
|
+
.replace(/"/g, '\\"') // ダブルクォート
|
|
31
|
+
.replace(/'/g, '\\\''); // シングルクォート
|
|
32
|
+
}
|
|
33
|
+
|
|
20
34
|
/**
|
|
21
35
|
* Confluence APIページレスポンス
|
|
22
36
|
*/
|
|
@@ -133,8 +147,8 @@ class ConfluenceClient {
|
|
|
133
147
|
// 親ページIDが指定されている場合、CQLクエリを使用して親ページの子ページのみを検索
|
|
134
148
|
if (parentId) {
|
|
135
149
|
// CQLクエリ: スペース、タイトル、親ページIDで検索
|
|
136
|
-
//
|
|
137
|
-
const escapedTitle = title
|
|
150
|
+
// セキュリティ: CQLインジェクション対策 - タイトルを適切にエスケープ
|
|
151
|
+
const escapedTitle = escapeCQL(title);
|
|
138
152
|
// ancestorの代わりにparentを使用(Confluence CQLの正しい構文)
|
|
139
153
|
const cql = `space = ${spaceKey} AND title = "${escapedTitle}" AND parent = ${parentId}`;
|
|
140
154
|
console.log(` CQL Query: ${cql}`);
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Actions Client
|
|
3
|
+
* GitHub Actions APIへのアクセスを抽象化し、Workflow Runsの取得とレート制限対策を提供
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Octokit } from '@octokit/rest';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* GitHub Workflow Run情報
|
|
10
|
+
*/
|
|
11
|
+
export interface IGitHubWorkflowRun {
|
|
12
|
+
id: number;
|
|
13
|
+
name: string;
|
|
14
|
+
head_branch: string;
|
|
15
|
+
status: 'completed' | 'in_progress' | 'queued';
|
|
16
|
+
conclusion: 'success' | 'failure' | 'cancelled' | 'skipped' | null;
|
|
17
|
+
created_at: string;
|
|
18
|
+
updated_at: string;
|
|
19
|
+
html_url: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* リポジトリのCI結果
|
|
24
|
+
*/
|
|
25
|
+
export interface IRepositoryCIStatus {
|
|
26
|
+
name: string;
|
|
27
|
+
url: string;
|
|
28
|
+
branch: string;
|
|
29
|
+
status: 'success' | 'failure' | 'running' | 'unknown';
|
|
30
|
+
testStatus: 'passed' | 'failed' | 'skipped' | 'unknown';
|
|
31
|
+
coverage?: number;
|
|
32
|
+
lastExecutionTime: Date;
|
|
33
|
+
failureDetails?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* GitHub APIエラー
|
|
38
|
+
*/
|
|
39
|
+
export type GitHubAPIError =
|
|
40
|
+
| { type: 'RATE_LIMIT_EXCEEDED'; retryAfter: number }
|
|
41
|
+
| { type: 'NOT_FOUND'; message: string }
|
|
42
|
+
| { type: 'UNAUTHORIZED'; message: string }
|
|
43
|
+
| { type: 'SERVER_ERROR'; message: string; statusCode: number };
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Octokit APIから返されるエラーオブジェクトの型
|
|
47
|
+
*/
|
|
48
|
+
interface OctokitError extends Error {
|
|
49
|
+
status?: number;
|
|
50
|
+
response?: {
|
|
51
|
+
url?: string;
|
|
52
|
+
status?: number;
|
|
53
|
+
headers?: Record<string, string>;
|
|
54
|
+
data?: unknown;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Result型
|
|
60
|
+
*/
|
|
61
|
+
export type Result<T, E> =
|
|
62
|
+
| { success: true; data: T }
|
|
63
|
+
| { success: false; error: E };
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* GitHub Workflow Runを解析してCI結果に変換
|
|
67
|
+
* @param run GitHub Workflow Run情報
|
|
68
|
+
* @returns CI結果
|
|
69
|
+
*/
|
|
70
|
+
export function parseGitHubWorkflowRun(
|
|
71
|
+
run: IGitHubWorkflowRun
|
|
72
|
+
): Omit<IRepositoryCIStatus, 'name' | 'url' | 'branch'> {
|
|
73
|
+
// statusマッピング
|
|
74
|
+
let status: 'success' | 'failure' | 'running' | 'unknown';
|
|
75
|
+
let testStatus: 'passed' | 'failed' | 'skipped' | 'unknown';
|
|
76
|
+
|
|
77
|
+
if (run.status === 'completed') {
|
|
78
|
+
if (run.conclusion === 'success') {
|
|
79
|
+
status = 'success';
|
|
80
|
+
testStatus = 'passed';
|
|
81
|
+
} else if (run.conclusion === 'failure') {
|
|
82
|
+
status = 'failure';
|
|
83
|
+
testStatus = 'failed';
|
|
84
|
+
} else if (run.conclusion === 'cancelled') {
|
|
85
|
+
status = 'unknown';
|
|
86
|
+
testStatus = 'skipped';
|
|
87
|
+
} else if (run.conclusion === 'skipped') {
|
|
88
|
+
status = 'unknown';
|
|
89
|
+
testStatus = 'skipped';
|
|
90
|
+
} else {
|
|
91
|
+
status = 'unknown';
|
|
92
|
+
testStatus = 'unknown';
|
|
93
|
+
}
|
|
94
|
+
} else if (run.status === 'in_progress' || run.status === 'queued') {
|
|
95
|
+
status = 'running';
|
|
96
|
+
testStatus = 'unknown';
|
|
97
|
+
} else {
|
|
98
|
+
status = 'unknown';
|
|
99
|
+
testStatus = 'unknown';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 日時変換
|
|
103
|
+
const lastExecutionTime = new Date(run.updated_at);
|
|
104
|
+
|
|
105
|
+
// 失敗詳細
|
|
106
|
+
const failureDetails =
|
|
107
|
+
status === 'failure' ? run.html_url : undefined;
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
status,
|
|
111
|
+
testStatus,
|
|
112
|
+
lastExecutionTime,
|
|
113
|
+
failureDetails,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Exponential Backoffで再試行
|
|
119
|
+
* @param fn 実行する関数
|
|
120
|
+
* @param maxRetries 最大再試行回数
|
|
121
|
+
* @returns 関数の実行結果
|
|
122
|
+
*/
|
|
123
|
+
async function exponentialBackoff<T>(
|
|
124
|
+
fn: () => Promise<T>,
|
|
125
|
+
maxRetries: number = 3
|
|
126
|
+
): Promise<T> {
|
|
127
|
+
let lastError: OctokitError | undefined;
|
|
128
|
+
|
|
129
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
130
|
+
try {
|
|
131
|
+
return await fn();
|
|
132
|
+
} catch (error) {
|
|
133
|
+
const octokitError = error as OctokitError;
|
|
134
|
+
lastError = octokitError;
|
|
135
|
+
|
|
136
|
+
// レート制限エラーでない場合は即座にスロー
|
|
137
|
+
if (octokitError.status !== 403 || !octokitError.response?.headers) {
|
|
138
|
+
throw octokitError;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 最後の試行の場合はスロー
|
|
142
|
+
if (attempt === maxRetries - 1) {
|
|
143
|
+
throw octokitError;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Exponential Backoff: 1秒、2秒、4秒
|
|
147
|
+
const waitTime = Math.pow(2, attempt) * 1000;
|
|
148
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
throw lastError ?? new Error('Unexpected error in exponentialBackoff');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* GitHub Actions Client
|
|
157
|
+
*/
|
|
158
|
+
export class GitHubActionsClient {
|
|
159
|
+
private octokit: Octokit;
|
|
160
|
+
|
|
161
|
+
constructor() {
|
|
162
|
+
const token = process.env.GITHUB_TOKEN;
|
|
163
|
+
if (!token) {
|
|
164
|
+
throw new Error('GITHUB_TOKENが設定されていません');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this.octokit = new Octokit({
|
|
168
|
+
auth: token,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* リポジトリの最新Workflow Runを取得
|
|
174
|
+
* @param owner リポジトリオーナー
|
|
175
|
+
* @param repo リポジトリ名
|
|
176
|
+
* @param branch ブランチ名
|
|
177
|
+
* @returns Workflow Run情報(成功/失敗)
|
|
178
|
+
*/
|
|
179
|
+
async getLatestWorkflowRun(
|
|
180
|
+
owner: string,
|
|
181
|
+
repo: string,
|
|
182
|
+
branch: string
|
|
183
|
+
): Promise<Result<IGitHubWorkflowRun, GitHubAPIError>> {
|
|
184
|
+
try {
|
|
185
|
+
const response = await exponentialBackoff(async () => {
|
|
186
|
+
return await this.octokit.actions.listWorkflowRunsForRepo({
|
|
187
|
+
owner,
|
|
188
|
+
repo,
|
|
189
|
+
branch,
|
|
190
|
+
status: 'completed',
|
|
191
|
+
per_page: 1,
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (
|
|
196
|
+
!response.data.workflow_runs ||
|
|
197
|
+
response.data.workflow_runs.length === 0
|
|
198
|
+
) {
|
|
199
|
+
return {
|
|
200
|
+
success: false,
|
|
201
|
+
error: {
|
|
202
|
+
type: 'NOT_FOUND',
|
|
203
|
+
message: `リポジトリ ${owner}/${repo} のWorkflow Runが見つかりません`,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const run = response.data.workflow_runs[0] as IGitHubWorkflowRun;
|
|
209
|
+
return {
|
|
210
|
+
success: true,
|
|
211
|
+
data: run,
|
|
212
|
+
};
|
|
213
|
+
} catch (error) {
|
|
214
|
+
// エラーハンドリング
|
|
215
|
+
const octokitError = error as OctokitError;
|
|
216
|
+
if (octokitError.status === 404) {
|
|
217
|
+
return {
|
|
218
|
+
success: false,
|
|
219
|
+
error: {
|
|
220
|
+
type: 'NOT_FOUND',
|
|
221
|
+
message: octokitError.message || 'リポジトリが見つかりません',
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
} else if (octokitError.status === 403) {
|
|
225
|
+
// レート制限
|
|
226
|
+
const resetTime = octokitError.response?.headers?.['x-ratelimit-reset'];
|
|
227
|
+
const retryAfter = resetTime
|
|
228
|
+
? parseInt(resetTime, 10) - Math.floor(Date.now() / 1000)
|
|
229
|
+
: 3600;
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
success: false,
|
|
233
|
+
error: {
|
|
234
|
+
type: 'RATE_LIMIT_EXCEEDED',
|
|
235
|
+
retryAfter: Math.max(retryAfter, 0),
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
} else if (octokitError.status === 401) {
|
|
239
|
+
return {
|
|
240
|
+
success: false,
|
|
241
|
+
error: {
|
|
242
|
+
type: 'UNAUTHORIZED',
|
|
243
|
+
message: octokitError.message || '認証エラー',
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
} else {
|
|
247
|
+
return {
|
|
248
|
+
success: false,
|
|
249
|
+
error: {
|
|
250
|
+
type: 'SERVER_ERROR',
|
|
251
|
+
message: octokitError.message || 'サーバーエラー',
|
|
252
|
+
statusCode: octokitError.status || 500,
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|