pipecraft 0.0.0-releaseit

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.
Files changed (176) hide show
  1. package/.claude/settings.local.json +35 -0
  2. package/.github/actions/calculate-version/action.yml +106 -0
  3. package/.github/actions/create-pr/action.yml +122 -0
  4. package/.github/actions/create-release/action.yml +74 -0
  5. package/.github/actions/create-tag/action.yml +94 -0
  6. package/.github/actions/detect-changes/action.yml +62 -0
  7. package/.github/actions/manage-branch/action.yml +113 -0
  8. package/.github/actions/promote-branch/action.yml +352 -0
  9. package/.github/example/workflows/job.analyze.code.yml +26 -0
  10. package/.github/example/workflows/job.analyze.docker.yml +32 -0
  11. package/.github/example/workflows/job.app.api.deploy.yml +127 -0
  12. package/.github/example/workflows/job.app.api.test.yml +102 -0
  13. package/.github/example/workflows/job.app.docs.deploy.yml +40 -0
  14. package/.github/example/workflows/job.app.docs.test.yml +50 -0
  15. package/.github/example/workflows/job.app.web.deploy.yml +96 -0
  16. package/.github/example/workflows/job.app.web.test.yml +49 -0
  17. package/.github/example/workflows/job.changes.yml +82 -0
  18. package/.github/example/workflows/job.create-pr.yml +96 -0
  19. package/.github/example/workflows/job.env-check.yml +43 -0
  20. package/.github/example/workflows/job.fast-forward.yml +103 -0
  21. package/.github/example/workflows/job.lint.yml +64 -0
  22. package/.github/example/workflows/job.pr-name.yml +70 -0
  23. package/.github/example/workflows/job.pr-squash-name.yml +27 -0
  24. package/.github/example/workflows/job.tag.yml +38 -0
  25. package/.github/example/workflows/job.version.yml +99 -0
  26. package/.github/example/workflows/lib.plugin-base.publish.yml +59 -0
  27. package/.github/example/workflows/pipe.yml +178 -0
  28. package/.github/example/workflows/scripts/fastforward.sh +51 -0
  29. package/.github/example/workflows/scripts/pipe-cleaner.sh +70 -0
  30. package/.github/scripts/fastforward.sh +51 -0
  31. package/.github/scripts/pipe-cleaner.sh +70 -0
  32. package/.github/workflows/pipeline.yml +220 -0
  33. package/.github/workflows/pr-title-check.yml +70 -0
  34. package/.github/workflows/publish.yml +39 -0
  35. package/.pipecraftrc.json +78 -0
  36. package/.release-it.cjs +71 -0
  37. package/PIPELINE_TESTING_PLAN.md +499 -0
  38. package/README.md +1101 -0
  39. package/TRUNK_FLOW_PLAN.md +401 -0
  40. package/assets/logo_banner.png +0 -0
  41. package/assets/logo_banner.webp +0 -0
  42. package/dist/cli/index.d.ts +3 -0
  43. package/dist/cli/index.d.ts.map +1 -0
  44. package/dist/cli/index.js +364 -0
  45. package/dist/cli/index.js.map +1 -0
  46. package/dist/generators/init.tpl.d.ts +3 -0
  47. package/dist/generators/init.tpl.d.ts.map +1 -0
  48. package/dist/generators/init.tpl.js +117 -0
  49. package/dist/generators/init.tpl.js.map +1 -0
  50. package/dist/generators/workflows.tpl.d.ts +7 -0
  51. package/dist/generators/workflows.tpl.d.ts.map +1 -0
  52. package/dist/generators/workflows.tpl.js +73 -0
  53. package/dist/generators/workflows.tpl.js.map +1 -0
  54. package/dist/templates/actions/calculate-version.yml.tpl.d.ts +3 -0
  55. package/dist/templates/actions/calculate-version.yml.tpl.d.ts.map +1 -0
  56. package/dist/templates/actions/calculate-version.yml.tpl.js +122 -0
  57. package/dist/templates/actions/calculate-version.yml.tpl.js.map +1 -0
  58. package/dist/templates/actions/create-pr.yml.tpl.d.ts +3 -0
  59. package/dist/templates/actions/create-pr.yml.tpl.d.ts.map +1 -0
  60. package/dist/templates/actions/create-pr.yml.tpl.js +145 -0
  61. package/dist/templates/actions/create-pr.yml.tpl.js.map +1 -0
  62. package/dist/templates/actions/create-tag.yml.tpl.d.ts +3 -0
  63. package/dist/templates/actions/create-tag.yml.tpl.d.ts.map +1 -0
  64. package/dist/templates/actions/create-tag.yml.tpl.js +111 -0
  65. package/dist/templates/actions/create-tag.yml.tpl.js.map +1 -0
  66. package/dist/templates/actions/detect-changes.yml.tpl.d.ts +3 -0
  67. package/dist/templates/actions/detect-changes.yml.tpl.d.ts.map +1 -0
  68. package/dist/templates/actions/detect-changes.yml.tpl.js +79 -0
  69. package/dist/templates/actions/detect-changes.yml.tpl.js.map +1 -0
  70. package/dist/templates/actions/manage-branch.yml.tpl.d.ts +3 -0
  71. package/dist/templates/actions/manage-branch.yml.tpl.d.ts.map +1 -0
  72. package/dist/templates/actions/manage-branch.yml.tpl.js +130 -0
  73. package/dist/templates/actions/manage-branch.yml.tpl.js.map +1 -0
  74. package/dist/templates/actions/promote-branch.yml.tpl.d.ts +3 -0
  75. package/dist/templates/actions/promote-branch.yml.tpl.d.ts.map +1 -0
  76. package/dist/templates/actions/promote-branch.yml.tpl.js +362 -0
  77. package/dist/templates/actions/promote-branch.yml.tpl.js.map +1 -0
  78. package/dist/templates/workflows/pipeline-path-based.yml.tpl.d.ts +16 -0
  79. package/dist/templates/workflows/pipeline-path-based.yml.tpl.d.ts.map +1 -0
  80. package/dist/templates/workflows/pipeline-path-based.yml.tpl.js +652 -0
  81. package/dist/templates/workflows/pipeline-path-based.yml.tpl.js.map +1 -0
  82. package/dist/types/index.d.ts +63 -0
  83. package/dist/types/index.d.ts.map +1 -0
  84. package/dist/types/index.js +2 -0
  85. package/dist/types/index.js.map +1 -0
  86. package/dist/utils/ast-path-operations.d.ts +287 -0
  87. package/dist/utils/ast-path-operations.d.ts.map +1 -0
  88. package/dist/utils/ast-path-operations.js +419 -0
  89. package/dist/utils/ast-path-operations.js.map +1 -0
  90. package/dist/utils/config.d.ts +3 -0
  91. package/dist/utils/config.d.ts.map +1 -0
  92. package/dist/utils/config.js +49 -0
  93. package/dist/utils/config.js.map +1 -0
  94. package/dist/utils/github-setup.d.ts +74 -0
  95. package/dist/utils/github-setup.d.ts.map +1 -0
  96. package/dist/utils/github-setup.js +418 -0
  97. package/dist/utils/github-setup.js.map +1 -0
  98. package/dist/utils/idempotency.d.ts +47 -0
  99. package/dist/utils/idempotency.d.ts.map +1 -0
  100. package/dist/utils/idempotency.js +176 -0
  101. package/dist/utils/idempotency.js.map +1 -0
  102. package/dist/utils/preflight.d.ts +55 -0
  103. package/dist/utils/preflight.d.ts.map +1 -0
  104. package/dist/utils/preflight.js +261 -0
  105. package/dist/utils/preflight.js.map +1 -0
  106. package/dist/utils/versioning.d.ts +41 -0
  107. package/dist/utils/versioning.d.ts.map +1 -0
  108. package/dist/utils/versioning.js +219 -0
  109. package/dist/utils/versioning.js.map +1 -0
  110. package/docs/USER_JOURNEY_ERRORS.md +352 -0
  111. package/eslint.config.js +77 -0
  112. package/examples/basic-config.json +41 -0
  113. package/examples/monorepo-config.json +49 -0
  114. package/examples/usage.md +289 -0
  115. package/package.json +58 -0
  116. package/scripts/debug-workflows.sh +339 -0
  117. package/scripts/parse-pipeline.js +29 -0
  118. package/scripts/test-job-order.cjs +140 -0
  119. package/scripts/validate-pipeline.cjs +379 -0
  120. package/scripts/verify-job-order.sh +30 -0
  121. package/src/cli/index.ts +414 -0
  122. package/src/generators/init.tpl.ts +126 -0
  123. package/src/generators/workflows.tpl.ts +80 -0
  124. package/src/templates/actions/calculate-version.yml.tpl.ts +124 -0
  125. package/src/templates/actions/create-pr.yml.tpl.ts +147 -0
  126. package/src/templates/actions/create-tag.yml.tpl.ts +112 -0
  127. package/src/templates/actions/detect-changes.yml.tpl.ts +87 -0
  128. package/src/templates/actions/manage-branch.yml.tpl.ts +132 -0
  129. package/src/templates/actions/promote-branch.yml.tpl.ts +364 -0
  130. package/src/templates/workflows/pipeline-path-based.yml.tpl.ts +706 -0
  131. package/src/types/index.ts +64 -0
  132. package/src/utils/README-ast-path-operations.md +390 -0
  133. package/src/utils/ast-path-operations.ts +581 -0
  134. package/src/utils/config.ts +64 -0
  135. package/src/utils/github-setup.ts +558 -0
  136. package/src/utils/idempotency.ts +215 -0
  137. package/src/utils/preflight.ts +306 -0
  138. package/src/utils/versioning.ts +244 -0
  139. package/tests/README.md +229 -0
  140. package/tests/TEST_STRUCTURE.md +256 -0
  141. package/tests/act/run-act-tests.sh +345 -0
  142. package/tests/debugging/debug-utils.ts +538 -0
  143. package/tests/debugging/debug-workflow.test.ts +339 -0
  144. package/tests/debugging/debug-workflows.sh +339 -0
  145. package/tests/debugging/iterative-debug.ts +652 -0
  146. package/tests/debugging/run-debug-tests.sh +431 -0
  147. package/tests/fixtures/basic-config.json +51 -0
  148. package/tests/fixtures/invalid-config.json +9 -0
  149. package/tests/fixtures/pipeline-generated.yml +235 -0
  150. package/tests/fixtures/pipeline-preserve-comments.yml +245 -0
  151. package/tests/fixtures/pipeline-user-modified.yml +245 -0
  152. package/tests/fixtures/test-config.json +58 -0
  153. package/tests/github-live/README.md +250 -0
  154. package/tests/github-local/JOB_WORKFLOW_TESTS.md +296 -0
  155. package/tests/github-local/README.md +240 -0
  156. package/tests/github-local/run-all-tests.sh +422 -0
  157. package/tests/github-local/test-job-workflows.sh +631 -0
  158. package/tests/github-local/test-pipeline-workflow.sh +440 -0
  159. package/tests/integration/generators.test.ts +578 -0
  160. package/tests/integration/path-based-template.test.ts +510 -0
  161. package/tests/integration/simple-path-based.test.ts +415 -0
  162. package/tests/setup.ts +56 -0
  163. package/tests/unit/ast-path-operations-extended.test.ts +302 -0
  164. package/tests/unit/cli.test.ts +541 -0
  165. package/tests/unit/config-extended.test.ts +412 -0
  166. package/tests/unit/config.test.ts +152 -0
  167. package/tests/unit/github-setup.test.ts +189 -0
  168. package/tests/unit/idempotency-isolated.test.ts +297 -0
  169. package/tests/unit/job-order.test.ts +157 -0
  170. package/tests/unit/pipeline-path-based.test.ts +511 -0
  171. package/tests/unit/validate-pipeline.test.ts +384 -0
  172. package/tests/unit/versioning-extended.test.ts +279 -0
  173. package/tests/unit/versioning.test.ts +241 -0
  174. package/tsconfig.debug.json +17 -0
  175. package/tsconfig.json +28 -0
  176. package/vitest.config.ts +51 -0
@@ -0,0 +1,339 @@
1
+ /**
2
+ * GitHub Actions Workflow Debugging Test Suite
3
+ *
4
+ * This test suite validates the debugging utilities and ensures
5
+ * they can properly analyze workflow failures.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
9
+ import {
10
+ GitHubWorkflowDebugger,
11
+ WorkflowAnalyzer,
12
+ LogAnalyzer,
13
+ DebugReportGenerator,
14
+ WorkflowDebugOrchestrator,
15
+ type WorkflowRun,
16
+ type Job,
17
+ type DebugAnalysis,
18
+ type LogAnalysis
19
+ } from './debug-utils';
20
+
21
+ // Mock data for testing
22
+ const mockWorkflowRun: WorkflowRun = {
23
+ id: 12345,
24
+ status: 'completed',
25
+ conclusion: 'failure',
26
+ created_at: '2024-01-15T10:00:00Z',
27
+ html_url: 'https://github.com/owner/repo/actions/runs/12345',
28
+ workflow_id: 67890,
29
+ head_branch: 'main',
30
+ head_sha: 'abc123def456'
31
+ };
32
+
33
+ const mockJobs: Job[] = [
34
+ {
35
+ id: 1,
36
+ name: 'test-api',
37
+ status: 'completed',
38
+ conclusion: 'failure',
39
+ html_url: 'https://github.com/owner/repo/actions/runs/12345/jobs/1',
40
+ started_at: '2024-01-15T10:00:00Z',
41
+ completed_at: '2024-01-15T10:05:00Z',
42
+ steps: [
43
+ {
44
+ name: 'Setup Node.js',
45
+ status: 'completed',
46
+ conclusion: 'success',
47
+ number: 1,
48
+ started_at: '2024-01-15T10:00:00Z',
49
+ completed_at: '2024-01-15T10:01:00Z'
50
+ },
51
+ {
52
+ name: 'Install dependencies',
53
+ status: 'completed',
54
+ conclusion: 'success',
55
+ number: 2,
56
+ started_at: '2024-01-15T10:01:00Z',
57
+ completed_at: '2024-01-15T10:02:00Z'
58
+ },
59
+ {
60
+ name: 'Run tests',
61
+ status: 'completed',
62
+ conclusion: 'failure',
63
+ number: 3,
64
+ started_at: '2024-01-15T10:02:00Z',
65
+ completed_at: '2024-01-15T10:05:00Z'
66
+ }
67
+ ]
68
+ },
69
+ {
70
+ id: 2,
71
+ name: 'build-web',
72
+ status: 'completed',
73
+ conclusion: 'failure',
74
+ html_url: 'https://github.com/owner/repo/actions/runs/12345/jobs/2',
75
+ started_at: '2024-01-15T10:00:00Z',
76
+ completed_at: '2024-01-15T10:03:00Z',
77
+ steps: [
78
+ {
79
+ name: 'Build application',
80
+ status: 'completed',
81
+ conclusion: 'failure',
82
+ number: 1,
83
+ started_at: '2024-01-15T10:00:00Z',
84
+ completed_at: '2024-01-15T10:03:00Z'
85
+ }
86
+ ]
87
+ },
88
+ {
89
+ id: 3,
90
+ name: 'lint-code',
91
+ status: 'completed',
92
+ conclusion: 'success',
93
+ html_url: 'https://github.com/owner/repo/actions/runs/12345/jobs/3',
94
+ started_at: '2024-01-15T10:00:00Z',
95
+ completed_at: '2024-01-15T10:01:00Z',
96
+ steps: [
97
+ {
98
+ name: 'Run ESLint',
99
+ status: 'completed',
100
+ conclusion: 'success',
101
+ number: 1,
102
+ started_at: '2024-01-15T10:00:00Z',
103
+ completed_at: '2024-01-15T10:01:00Z'
104
+ }
105
+ ]
106
+ }
107
+ ];
108
+
109
+ const mockLogs = `
110
+ 2024-01-15T10:02:00.000Z [INFO] Starting test suite
111
+ 2024-01-15T10:02:30.000Z [ERROR] Test failed: API endpoint not responding
112
+ 2024-01-15T10:03:00.000Z [WARN] Connection timeout after 30 seconds
113
+ 2024-01-15T10:03:30.000Z [ERROR] Fatal error: Database connection refused
114
+ 2024-01-15T10:04:00.000Z [ERROR] Test suite failed with 5 errors
115
+ 2024-01-15T10:05:00.000Z [INFO] Test execution completed
116
+ `;
117
+
118
+ describe('WorkflowAnalyzer', () => {
119
+ it('should analyze workflow run and identify failed jobs', () => {
120
+ const analysis = WorkflowAnalyzer.analyzeWorkflowRun(mockWorkflowRun, mockJobs);
121
+
122
+ expect(analysis.runId).toBe(12345);
123
+ expect(analysis.status).toBe('completed');
124
+ expect(analysis.conclusion).toBe('failure');
125
+ expect(analysis.failedJobs).toHaveLength(2);
126
+ expect(analysis.totalJobs).toBe(3);
127
+ expect(analysis.successRate).toBeCloseTo(33.33, 1);
128
+ });
129
+
130
+ it('should extract common failure patterns', () => {
131
+ const analysis = WorkflowAnalyzer.analyzeWorkflowRun(mockWorkflowRun, mockJobs);
132
+
133
+ expect(analysis.commonFailurePatterns).toContain('Test Failures');
134
+ expect(analysis.commonFailurePatterns).toContain('Build Failures');
135
+ });
136
+
137
+ it('should generate relevant recommendations', () => {
138
+ const analysis = WorkflowAnalyzer.analyzeWorkflowRun(mockWorkflowRun, mockJobs);
139
+
140
+ expect(analysis.recommendations).toContain('Review test cases and ensure they are up to date');
141
+ expect(analysis.recommendations).toContain('Verify build dependencies and versions');
142
+ });
143
+ });
144
+
145
+ describe('LogAnalyzer', () => {
146
+ it('should analyze logs and count errors and warnings', () => {
147
+ const analysis = LogAnalyzer.analyzeLogs(mockLogs, 'test-api');
148
+
149
+ expect(analysis.jobName).toBe('test-api');
150
+ expect(analysis.errorCount).toBeGreaterThan(0);
151
+ expect(analysis.warningCount).toBeGreaterThan(0);
152
+ expect(analysis.criticalErrors.length).toBeGreaterThan(0);
153
+ });
154
+
155
+ it('should extract common error patterns', () => {
156
+ const analysis = LogAnalyzer.analyzeLogs(mockLogs, 'test-api');
157
+
158
+ expect(analysis.commonErrors.length).toBeGreaterThan(0);
159
+ expect(analysis.executionTime).toBeGreaterThan(0);
160
+ });
161
+
162
+ it('should identify critical errors', () => {
163
+ const analysis = LogAnalyzer.analyzeLogs(mockLogs, 'test-api');
164
+
165
+ expect(analysis.criticalErrors.length).toBeGreaterThan(0);
166
+ expect(analysis.criticalErrors.some(error => error.includes('Database connection refused'))).toBe(true);
167
+ });
168
+ });
169
+
170
+ describe('DebugReportGenerator', () => {
171
+ let mockAnalysis: DebugAnalysis;
172
+ let mockLogAnalyses: LogAnalysis[];
173
+
174
+ beforeEach(() => {
175
+ mockAnalysis = {
176
+ runId: 12345,
177
+ status: 'completed',
178
+ conclusion: 'failure',
179
+ failedJobs: mockJobs.filter(job => job.conclusion === 'failure'),
180
+ totalJobs: 3,
181
+ successRate: 33.33,
182
+ commonFailurePatterns: ['Test Failures', 'Build Failures'],
183
+ recommendations: [
184
+ 'Review test cases and ensure they are up to date',
185
+ 'Verify build dependencies and versions'
186
+ ]
187
+ };
188
+
189
+ mockLogAnalyses = [
190
+ {
191
+ jobId: 1,
192
+ jobName: 'test-api',
193
+ errorCount: 3,
194
+ warningCount: 1,
195
+ criticalErrors: ['Fatal error: Database connection refused'],
196
+ commonErrors: ['API endpoint not responding', 'Database connection refused'],
197
+ executionTime: 180
198
+ }
199
+ ];
200
+ });
201
+
202
+ it('should generate a comprehensive debug report', async () => {
203
+ const reportPath = await DebugReportGenerator.generateReport(
204
+ mockAnalysis,
205
+ mockLogAnalyses,
206
+ './test-output'
207
+ );
208
+
209
+ expect(reportPath).toContain('debug-report.md');
210
+ });
211
+
212
+ it('should include all analysis data in the report', async () => {
213
+ const reportPath = await DebugReportGenerator.generateReport(
214
+ mockAnalysis,
215
+ mockLogAnalyses,
216
+ './test-output'
217
+ );
218
+
219
+ // The report should be generated successfully
220
+ expect(reportPath).toBeDefined();
221
+ });
222
+ });
223
+
224
+ describe('GitHubWorkflowDebugger', () => {
225
+ let workflowDebugger: GitHubWorkflowDebugger;
226
+
227
+ beforeEach(() => {
228
+ workflowDebugger = new GitHubWorkflowDebugger('fake-token', 'owner', 'repo');
229
+ });
230
+
231
+ it('should initialize with correct parameters', () => {
232
+ expect(workflowDebugger).toBeDefined();
233
+ });
234
+
235
+ it('should handle API request errors gracefully', async () => {
236
+ // Mock fetch to throw an error
237
+ global.fetch = vi.fn().mockRejectedValue(new Error('API request failed'));
238
+
239
+ await expect(workflowDebugger.getWorkflowRuns('test-workflow')).rejects.toThrow('API request failed');
240
+ });
241
+ });
242
+
243
+ describe('WorkflowDebugOrchestrator', () => {
244
+ let orchestrator: WorkflowDebugOrchestrator;
245
+
246
+ beforeEach(() => {
247
+ orchestrator = new WorkflowDebugOrchestrator('fake-token', 'owner', 'repo', './test-output');
248
+ });
249
+
250
+ it('should initialize with correct parameters', () => {
251
+ expect(orchestrator).toBeDefined();
252
+ });
253
+
254
+ it('should handle missing workflow runs', async () => {
255
+ // Mock the workflowDebugger to return empty runs
256
+ const mockWorkflowDebugger = {
257
+ getWorkflowRuns: vi.fn().mockResolvedValue([])
258
+ };
259
+
260
+ // Replace the workflowDebugger instance
261
+ (orchestrator as any).debugger = mockWorkflowDebugger;
262
+
263
+ await expect(orchestrator.debugWorkflow('test-workflow')).rejects.toThrow('No workflow runs found');
264
+ });
265
+ });
266
+
267
+ describe('Integration Tests', () => {
268
+ it('should handle end-to-end debugging workflow', async () => {
269
+ // This test would require actual GitHub API access
270
+ // For now, we'll test the structure and error handling
271
+
272
+ const orchestrator = new WorkflowDebugOrchestrator('fake-token', 'owner', 'repo');
273
+
274
+ // Mock successful API responses
275
+ const mockRuns = [mockWorkflowRun];
276
+ const mockJobsData = mockJobs;
277
+
278
+ // This would be the actual integration test
279
+ // await orchestrator.debugWorkflow('test-workflow');
280
+
281
+ expect(orchestrator).toBeDefined();
282
+ });
283
+ });
284
+
285
+ describe('Error Handling', () => {
286
+ it('should handle network errors gracefully', async () => {
287
+ const workflowDebugger = new GitHubWorkflowDebugger('fake-token', 'owner', 'repo');
288
+
289
+ // Mock network error
290
+ global.fetch = vi.fn().mockRejectedValue(new Error('Network error'));
291
+
292
+ await expect(workflowDebugger.getWorkflowRuns('test-workflow')).rejects.toThrow('Network error');
293
+ });
294
+
295
+ it('should handle invalid JSON responses', async () => {
296
+ const workflowDebugger = new GitHubWorkflowDebugger('fake-token', 'owner', 'repo');
297
+
298
+ // Mock invalid JSON response
299
+ global.fetch = vi.fn().mockResolvedValue({
300
+ ok: true,
301
+ json: () => Promise.reject(new Error('Invalid JSON'))
302
+ });
303
+
304
+ await expect(workflowDebugger.getWorkflowRuns('test-workflow')).rejects.toThrow('Invalid JSON');
305
+ });
306
+ });
307
+
308
+ describe('Performance Tests', () => {
309
+ it('should handle large log files efficiently', () => {
310
+ const largeLogs = 'INFO: Starting\n'.repeat(10000) + 'ERROR: Failed\n'.repeat(100);
311
+
312
+ const startTime = Date.now();
313
+ const analysis = LogAnalyzer.analyzeLogs(largeLogs, 'test-job');
314
+ const endTime = Date.now();
315
+
316
+ expect(analysis.errorCount).toBe(100);
317
+ expect(endTime - startTime).toBeLessThan(1000); // Should complete in under 1 second
318
+ });
319
+
320
+ it('should handle many jobs efficiently', () => {
321
+ const manyJobs: Job[] = Array.from({ length: 100 }, (_, i) => ({
322
+ id: i,
323
+ name: `job-${i}`,
324
+ status: 'completed',
325
+ conclusion: i % 10 === 0 ? 'failure' : 'success',
326
+ html_url: `https://example.com/job/${i}`,
327
+ started_at: '2024-01-15T10:00:00Z',
328
+ completed_at: '2024-01-15T10:05:00Z',
329
+ steps: []
330
+ }));
331
+
332
+ const startTime = Date.now();
333
+ const analysis = WorkflowAnalyzer.analyzeWorkflowRun(mockWorkflowRun, manyJobs);
334
+ const endTime = Date.now();
335
+
336
+ expect(analysis.totalJobs).toBe(100);
337
+ expect(endTime - startTime).toBeLessThan(500); // Should complete in under 500ms
338
+ });
339
+ });
@@ -0,0 +1,339 @@
1
+ #!/bin/bash
2
+
3
+ # GitHub Actions Workflow Debugging Script
4
+ # This script fetches workflow status, job details, and logs for debugging
5
+
6
+ set -euo pipefail
7
+
8
+ # Configuration
9
+ REPO_OWNER="${GITHUB_REPOSITORY_OWNER:-$(git config --get remote.origin.url | sed 's/.*github.com[:/]\([^/]*\)\/\([^/]*\)\.git.*/\1/')}"
10
+ REPO_NAME="${GITHUB_REPOSITORY##*/:-$(git config --get remote.origin.url | sed 's/.*github.com[:/]\([^/]*\)\/\([^/]*\)\.git.*/\2/')}"
11
+ GITHUB_TOKEN="${GITHUB_TOKEN:-}"
12
+ API_BASE="https://api.github.com"
13
+
14
+ # Colors for output
15
+ RED='\033[0;31m'
16
+ GREEN='\033[0;32m'
17
+ YELLOW='\033[1;33m'
18
+ BLUE='\033[0;34m'
19
+ PURPLE='\033[0;35m'
20
+ CYAN='\033[0;36m'
21
+ NC='\033[0m' # No Color
22
+
23
+ # Default values
24
+ WORKFLOW_NAME=""
25
+ BRANCH=""
26
+ LIMIT=10
27
+ OUTPUT_DIR="./debug-output"
28
+ VERBOSE=false
29
+ FETCH_LOGS=true
30
+
31
+ # Function to print colored output
32
+ print_status() {
33
+ local status=$1
34
+ local message=$2
35
+
36
+ case $status in
37
+ "SUCCESS") echo -e "${GREEN}✓${NC} $message" ;;
38
+ "FAILURE") echo -e "${RED}✗${NC} $message" ;;
39
+ "CANCELLED") echo -e "${YELLOW}⚠${NC} $message" ;;
40
+ "IN_PROGRESS") echo -e "${BLUE}⟳${NC} $message" ;;
41
+ "QUEUED") echo -e "${PURPLE}⏳${NC} $message" ;;
42
+ "SKIPPED") echo -e "${CYAN}⊘${NC} $message" ;;
43
+ *) echo -e "${YELLOW}?${NC} $message" ;;
44
+ esac
45
+ }
46
+
47
+ # Function to make GitHub API calls
48
+ github_api() {
49
+ local endpoint=$1
50
+ local method=${2:-GET}
51
+
52
+ if [[ -z "$GITHUB_TOKEN" ]]; then
53
+ echo -e "${RED}Error: GITHUB_TOKEN environment variable is required${NC}" >&2
54
+ exit 1
55
+ fi
56
+
57
+ curl -s -H "Authorization: token $GITHUB_TOKEN" \
58
+ -H "Accept: application/vnd.github.v3+json" \
59
+ -X "$method" \
60
+ "$API_BASE/repos/$REPO_OWNER/$REPO_NAME$endpoint"
61
+ }
62
+
63
+ # Function to get workflow runs
64
+ get_workflow_runs() {
65
+ local workflow_name=$1
66
+ local branch=$2
67
+ local limit=$3
68
+
69
+ local endpoint="/actions/workflows/$workflow_name/runs"
70
+ local params="?per_page=$limit"
71
+
72
+ if [[ -n "$branch" ]]; then
73
+ params="${params}&branch=$branch"
74
+ fi
75
+
76
+ github_api "$endpoint$params"
77
+ }
78
+
79
+ # Function to get workflow run details
80
+ get_workflow_run() {
81
+ local run_id=$1
82
+ github_api "/actions/runs/$run_id"
83
+ }
84
+
85
+ # Function to get jobs for a workflow run
86
+ get_workflow_jobs() {
87
+ local run_id=$1
88
+ github_api "/actions/runs/$run_id/jobs"
89
+ }
90
+
91
+ # Function to get job logs
92
+ get_job_logs() {
93
+ local run_id=$1
94
+ local job_id=$2
95
+ local output_file=$3
96
+
97
+ local endpoint="/actions/runs/$run_id/jobs/$job_id/logs"
98
+
99
+ if [[ -z "$GITHUB_TOKEN" ]]; then
100
+ echo -e "${RED}Error: GITHUB_TOKEN required for log download${NC}" >&2
101
+ return 1
102
+ fi
103
+
104
+ curl -s -H "Authorization: token $GITHUB_TOKEN" \
105
+ -H "Accept: application/vnd.github.v3+json" \
106
+ "$API_BASE/repos/$REPO_OWNER/$REPO_NAME$endpoint" > "$output_file"
107
+ }
108
+
109
+ # Function to analyze workflow failures
110
+ analyze_failures() {
111
+ local run_id=$1
112
+ local output_dir=$2
113
+
114
+ echo -e "${BLUE}Analyzing workflow failures for run $run_id...${NC}"
115
+
116
+ # Get workflow run details
117
+ local run_details=$(get_workflow_run "$run_id")
118
+ local run_status=$(echo "$run_details" | jq -r '.conclusion // .status')
119
+ local run_url=$(echo "$run_details" | jq -r '.html_url')
120
+
121
+ echo -e "${CYAN}Workflow Status:${NC} $run_status"
122
+ echo -e "${CYAN}Workflow URL:${NC} $run_url"
123
+
124
+ # Get jobs
125
+ local jobs=$(get_workflow_jobs "$run_id")
126
+ local failed_jobs=$(echo "$jobs" | jq -r '.jobs[] | select(.conclusion == "failure" or .conclusion == "cancelled") | .name')
127
+
128
+ if [[ -z "$failed_jobs" ]]; then
129
+ echo -e "${GREEN}No failed jobs found${NC}"
130
+ return 0
131
+ fi
132
+
133
+ echo -e "${RED}Failed Jobs:${NC}"
134
+ echo "$failed_jobs" | while read -r job_name; do
135
+ echo -e " ${RED}•${NC} $job_name"
136
+ done
137
+
138
+ # Get detailed job information
139
+ echo "$jobs" | jq -r '.jobs[] | select(.conclusion == "failure" or .conclusion == "cancelled") | .id' | while read -r job_id; do
140
+ local job_info=$(echo "$jobs" | jq -r ".jobs[] | select(.id == $job_id)")
141
+ local job_name=$(echo "$job_info" | jq -r '.name')
142
+ local job_conclusion=$(echo "$job_info" | jq -r '.conclusion')
143
+ local job_url=$(echo "$job_info" | jq -r '.html_url')
144
+
145
+ echo -e "\n${YELLOW}Job: $job_name${NC}"
146
+ echo -e "${YELLOW}Status:${NC} $job_conclusion"
147
+ echo -e "${YELLOW}URL:${NC} $job_url"
148
+
149
+ # Get job steps
150
+ local steps=$(echo "$job_info" | jq -r '.steps[] | select(.conclusion == "failure") | "\(.name): \(.conclusion)"')
151
+ if [[ -n "$steps" ]]; then
152
+ echo -e "${YELLOW}Failed Steps:${NC}"
153
+ echo "$steps" | while read -r step; do
154
+ echo -e " ${RED}•${NC} $step"
155
+ done
156
+ fi
157
+
158
+ # Download logs if requested
159
+ if [[ "$FETCH_LOGS" == "true" ]]; then
160
+ local log_file="$output_dir/job-${job_id}-logs.txt"
161
+ echo -e "${BLUE}Downloading logs for job $job_name...${NC}"
162
+ if get_job_logs "$run_id" "$job_id" "$log_file"; then
163
+ echo -e "${GREEN}Logs saved to: $log_file${NC}"
164
+ else
165
+ echo -e "${RED}Failed to download logs for job $job_name${NC}"
166
+ fi
167
+ fi
168
+ done
169
+ }
170
+
171
+ # Function to create summary report
172
+ create_summary_report() {
173
+ local output_dir=$1
174
+ local report_file="$output_dir/debug-summary.md"
175
+
176
+ echo -e "${BLUE}Creating summary report...${NC}"
177
+
178
+ cat > "$report_file" << EOF
179
+ # GitHub Actions Debug Summary
180
+
181
+ Generated: $(date)
182
+ Repository: $REPO_OWNER/$REPO_NAME
183
+ Workflow: $WORKFLOW_NAME
184
+ Branch: ${BRANCH:-"all"}
185
+
186
+ ## Recent Workflow Runs
187
+
188
+ EOF
189
+
190
+ # Get recent runs
191
+ local runs=$(get_workflow_runs "$WORKFLOW_NAME" "$BRANCH" "$LIMIT")
192
+
193
+ echo "$runs" | jq -r '.workflow_runs[] | "\(.id)|\(.status)|\(.conclusion)|\(.created_at)|\(.html_url)"' | while IFS='|' read -r run_id status conclusion created_at url; do
194
+ echo "### Run $run_id" >> "$report_file"
195
+ echo "- **Status**: $status" >> "$report_file"
196
+ echo "- **Conclusion**: $conclusion" >> "$report_file"
197
+ echo "- **Created**: $created_at" >> "$report_file"
198
+ echo "- **URL**: $url" >> "$report_file"
199
+ echo "" >> "$report_file"
200
+ done
201
+
202
+ echo -e "${GREEN}Summary report created: $report_file${NC}"
203
+ }
204
+
205
+ # Function to show usage
206
+ show_usage() {
207
+ cat << EOF
208
+ Usage: $0 [OPTIONS]
209
+
210
+ GitHub Actions Workflow Debugging Script
211
+
212
+ OPTIONS:
213
+ -w, --workflow NAME Workflow name to debug (required)
214
+ -b, --branch BRANCH Branch to filter runs (optional)
215
+ -l, --limit NUMBER Number of recent runs to fetch (default: 10)
216
+ -o, --output DIR Output directory for logs and reports (default: ./debug-output)
217
+ -t, --token TOKEN GitHub token (or set GITHUB_TOKEN env var)
218
+ -v, --verbose Verbose output
219
+ --no-logs Don't download job logs
220
+ -h, --help Show this help message
221
+
222
+ EXAMPLES:
223
+ $0 -w "My Custom Pipeline" -b main -l 5
224
+ $0 -w "CI" --no-logs -o ./debug-logs
225
+ GITHUB_TOKEN=your_token $0 -w "Deploy" -v
226
+
227
+ ENVIRONMENT VARIABLES:
228
+ GITHUB_TOKEN GitHub personal access token
229
+ GITHUB_REPOSITORY_OWNER Repository owner (auto-detected from git)
230
+ GITHUB_REPOSITORY Repository name (auto-detected from git)
231
+
232
+ EOF
233
+ }
234
+
235
+ # Parse command line arguments
236
+ while [[ $# -gt 0 ]]; do
237
+ case $1 in
238
+ -w|--workflow)
239
+ WORKFLOW_NAME="$2"
240
+ shift 2
241
+ ;;
242
+ -b|--branch)
243
+ BRANCH="$2"
244
+ shift 2
245
+ ;;
246
+ -l|--limit)
247
+ LIMIT="$2"
248
+ shift 2
249
+ ;;
250
+ -o|--output)
251
+ OUTPUT_DIR="$2"
252
+ shift 2
253
+ ;;
254
+ -t|--token)
255
+ GITHUB_TOKEN="$2"
256
+ shift 2
257
+ ;;
258
+ -v|--verbose)
259
+ VERBOSE=true
260
+ shift
261
+ ;;
262
+ --no-logs)
263
+ FETCH_LOGS=false
264
+ shift
265
+ ;;
266
+ -h|--help)
267
+ show_usage
268
+ exit 0
269
+ ;;
270
+ *)
271
+ echo -e "${RED}Unknown option: $1${NC}" >&2
272
+ show_usage
273
+ exit 1
274
+ ;;
275
+ esac
276
+ done
277
+
278
+ # Validate required parameters
279
+ if [[ -z "$WORKFLOW_NAME" ]]; then
280
+ echo -e "${RED}Error: Workflow name is required${NC}" >&2
281
+ show_usage
282
+ exit 1
283
+ fi
284
+
285
+ if [[ -z "$GITHUB_TOKEN" ]]; then
286
+ echo -e "${RED}Error: GitHub token is required${NC}" >&2
287
+ echo -e "${YELLOW}Set GITHUB_TOKEN environment variable or use -t option${NC}"
288
+ exit 1
289
+ fi
290
+
291
+ # Check if jq is installed
292
+ if ! command -v jq &> /dev/null; then
293
+ echo -e "${RED}Error: jq is required but not installed${NC}" >&2
294
+ echo -e "${YELLOW}Install jq: brew install jq (macOS) or apt-get install jq (Ubuntu)${NC}"
295
+ exit 1
296
+ fi
297
+
298
+ # Create output directory
299
+ mkdir -p "$OUTPUT_DIR"
300
+
301
+ echo -e "${BLUE}GitHub Actions Workflow Debugger${NC}"
302
+ echo -e "${CYAN}Repository:${NC} $REPO_OWNER/$REPO_NAME"
303
+ echo -e "${CYAN}Workflow:${NC} $WORKFLOW_NAME"
304
+ echo -e "${CYAN}Branch:${NC} ${BRANCH:-"all"}"
305
+ echo -e "${CYAN}Output:${NC} $OUTPUT_DIR"
306
+ echo ""
307
+
308
+ # Get recent workflow runs
309
+ echo -e "${BLUE}Fetching recent workflow runs...${NC}"
310
+ runs=$(get_workflow_runs "$WORKFLOW_NAME" "$BRANCH" "$LIMIT")
311
+
312
+ if [[ "$runs" == "null" || $(echo "$runs" | jq '.workflow_runs | length') -eq 0 ]]; then
313
+ echo -e "${YELLOW}No workflow runs found${NC}"
314
+ exit 0
315
+ fi
316
+
317
+ # Display recent runs
318
+ echo -e "${CYAN}Recent Workflow Runs:${NC}"
319
+ echo "$runs" | jq -r '.workflow_runs[] | "\(.id)|\(.status)|\(.conclusion)|\(.created_at)|\(.html_url)"' | while IFS='|' read -r run_id status conclusion created_at url; do
320
+ print_status "$conclusion" "Run $run_id ($status) - $created_at"
321
+ if [[ "$VERBOSE" == "true" ]]; then
322
+ echo " URL: $url"
323
+ fi
324
+ done
325
+
326
+ # Analyze failures
327
+ echo ""
328
+ echo -e "${BLUE}Analyzing failures...${NC}"
329
+ echo "$runs" | jq -r '.workflow_runs[] | select(.conclusion == "failure" or .conclusion == "cancelled") | .id' | while read -r run_id; do
330
+ analyze_failures "$run_id" "$OUTPUT_DIR"
331
+ done
332
+
333
+ # Create summary report
334
+ create_summary_report "$OUTPUT_DIR"
335
+
336
+ echo ""
337
+ echo -e "${GREEN}Debug analysis complete!${NC}"
338
+ echo -e "${CYAN}Output directory:${NC} $OUTPUT_DIR"
339
+ echo -e "${CYAN}Summary report:${NC} $OUTPUT_DIR/debug-summary.md"