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,499 @@
1
+ # Pipeline Testing Plan
2
+
3
+ ## Overview
4
+
5
+ This document outlines a comprehensive testing strategy to catch CI/CD pipeline errors locally before they reach GitHub Actions. The goal is to validate generated workflows and actions to prevent common issues like:
6
+
7
+ - Missing dependencies (unused imports)
8
+ - Incorrect action syntax (secrets in composite actions)
9
+ - Missing git configuration
10
+ - Token passing issues
11
+ - Version format validation
12
+ - Build failures
13
+
14
+ ## Current Pain Points
15
+
16
+ From our recent debugging session, we identified these issues that should be caught by tests:
17
+
18
+ 1. **Unused imports causing build failures** (`inquirer` import)
19
+ 2. **Composite actions trying to access `secrets` directly** (need inputs instead)
20
+ 3. **Missing git config** (user.name/email) in actions that create commits/tags
21
+ 4. **Version format mismatches** (v0.1.0 vs 0.1.0)
22
+ 5. **Lockfile compatibility** (frozen-lockfile flag issues)
23
+ 6. **Missing checkout steps** before using local actions
24
+ 7. **Token passing** to composite actions
25
+
26
+ ## Testing Strategy
27
+
28
+ ### 1. Local Action Testing with `act`
29
+
30
+ **Tool**: [nektos/act](https://github.com/nektos/act)
31
+
32
+ `act` allows running GitHub Actions locally using Docker.
33
+
34
+ #### Setup
35
+ ```bash
36
+ # Install act
37
+ brew install act
38
+
39
+ # Create .actrc for configuration
40
+ echo "-P ubuntu-latest=catthehacker/ubuntu:act-latest" > .actrc
41
+ ```
42
+
43
+ #### Test Structure
44
+ ```
45
+ tests/
46
+ ├── act/
47
+ │ ├── run-act-tests.sh # Main test runner
48
+ │ ├── test-changes-detection.sh # Test changes action
49
+ │ ├── test-version-calculation.sh # Test version action
50
+ │ ├── test-tag-creation.sh # Test tag action
51
+ │ └── test-pr-creation.sh # Test PR action
52
+ ```
53
+
54
+ #### Example Test Script
55
+ ```bash
56
+ #!/bin/bash
57
+ # tests/act/test-tag-creation.sh
58
+
59
+ # Test tag creation action locally
60
+ act -W .github/workflows/pipeline.yml \
61
+ --job tag \
62
+ --secret GITHUB_TOKEN=$GITHUB_TOKEN \
63
+ --input version=v1.0.0 \
64
+ --dryrun
65
+
66
+ # Verify outputs
67
+ if [ $? -eq 0 ]; then
68
+ echo "✅ Tag creation action syntax valid"
69
+ else
70
+ echo "❌ Tag creation action failed"
71
+ exit 1
72
+ fi
73
+ ```
74
+
75
+ ### 2. Static Analysis of Generated Workflows
76
+
77
+ #### YAML Linting
78
+ ```bash
79
+ # Add to package.json scripts
80
+ "lint:yaml": "yamllint .github/**/*.yml"
81
+ ```
82
+
83
+ #### Action Schema Validation
84
+ ```bash
85
+ # Validate action.yml files against GitHub's schema
86
+ npm install -g action-validator
87
+ action-validator .github/actions/*/action.yml
88
+ ```
89
+
90
+ #### Custom Validation Script
91
+ Create `scripts/validate-pipeline.js`:
92
+
93
+ ```javascript
94
+ const fs = require('fs');
95
+ const yaml = require('yaml');
96
+ const path = require('path');
97
+
98
+ function validateWorkflow(workflowPath) {
99
+ const content = fs.readFileSync(workflowPath, 'utf8');
100
+ const workflow = yaml.parse(content);
101
+
102
+ const errors = [];
103
+
104
+ // Check for local actions without checkout
105
+ for (const [jobName, job] of Object.entries(workflow.jobs || {})) {
106
+ const steps = job.steps || [];
107
+ let hasCheckout = false;
108
+
109
+ for (const step of steps) {
110
+ if (step.uses === 'actions/checkout@v4') {
111
+ hasCheckout = true;
112
+ }
113
+
114
+ // Check for local action usage
115
+ if (step.uses && step.uses.startsWith('./')) {
116
+ if (!hasCheckout) {
117
+ errors.push(`Job '${jobName}' uses local action '${step.uses}' without checking out code first`);
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ return errors;
124
+ }
125
+
126
+ function validateCompositeAction(actionPath) {
127
+ const content = fs.readFileSync(actionPath, 'utf8');
128
+ const action = yaml.parse(content);
129
+
130
+ const errors = [];
131
+
132
+ // Check for secrets usage in composite actions
133
+ const actionContent = fs.readFileSync(actionPath, 'utf8');
134
+ if (actionContent.includes('secrets.')) {
135
+ errors.push(`Composite action ${actionPath} tries to access secrets directly. Use inputs instead.`);
136
+ }
137
+
138
+ // Check for git operations without config
139
+ if (action.runs && action.runs.using === 'composite') {
140
+ const steps = action.runs.steps || [];
141
+ let hasGitConfig = false;
142
+ let hasGitOperations = false;
143
+
144
+ for (const step of steps) {
145
+ const run = step.run || '';
146
+
147
+ if (run.includes('git config')) {
148
+ hasGitConfig = true;
149
+ }
150
+
151
+ if (run.includes('git tag') || run.includes('git commit') || run.includes('git push')) {
152
+ hasGitOperations = true;
153
+ }
154
+ }
155
+
156
+ if (hasGitOperations && !hasGitConfig) {
157
+ errors.push(`Action ${actionPath} performs git operations without setting git config`);
158
+ }
159
+ }
160
+
161
+ return errors;
162
+ }
163
+
164
+ // Run validations
165
+ const workflowErrors = validateWorkflow('.github/workflows/pipeline.yml');
166
+ const actionFiles = fs.readdirSync('.github/actions')
167
+ .map(dir => path.join('.github/actions', dir, 'action.yml'))
168
+ .filter(p => fs.existsSync(p));
169
+
170
+ const actionErrors = actionFiles.flatMap(validateCompositeAction);
171
+
172
+ const allErrors = [...workflowErrors, ...actionErrors];
173
+
174
+ if (allErrors.length > 0) {
175
+ console.error('❌ Pipeline validation failed:');
176
+ allErrors.forEach(err => console.error(` - ${err}`));
177
+ process.exit(1);
178
+ } else {
179
+ console.log('✅ Pipeline validation passed');
180
+ }
181
+ ```
182
+
183
+ ### 3. Build Verification Tests
184
+
185
+ Add to test suite:
186
+
187
+ ```typescript
188
+ // tests/unit/build-verification.test.ts
189
+
190
+ import { describe, it, expect } from 'vitest';
191
+ import { execSync } from 'child_process';
192
+ import { readFileSync } from 'fs';
193
+
194
+ describe('Build Verification', () => {
195
+ it('should build without errors', () => {
196
+ expect(() => {
197
+ execSync('pnpm build', { encoding: 'utf8' });
198
+ }).not.toThrow();
199
+ });
200
+
201
+ it('should not have unused imports', () => {
202
+ // Use ts-unused-exports to find unused imports
203
+ const result = execSync('npx ts-unused-exports tsconfig.json', {
204
+ encoding: 'utf8',
205
+ stdio: 'pipe'
206
+ });
207
+
208
+ const unusedImports = result.split('\n').filter(line => line.includes('unused'));
209
+
210
+ if (unusedImports.length > 0) {
211
+ console.warn('Unused imports found:', unusedImports);
212
+ }
213
+
214
+ // This is a warning, not a failure, but we log it
215
+ });
216
+ });
217
+ ```
218
+
219
+ ### 4. Integration Tests with Test Workflows
220
+
221
+ Create minimal test workflows that can be run with `act`:
222
+
223
+ ```yaml
224
+ # .github/workflows/test-actions.yml
225
+ name: Test Actions
226
+
227
+ on:
228
+ workflow_dispatch:
229
+
230
+ jobs:
231
+ test-detect-changes:
232
+ runs-on: ubuntu-latest
233
+ steps:
234
+ - uses: actions/checkout@v4
235
+ with:
236
+ fetch-depth: 0
237
+
238
+ - uses: ./.github/actions/detect-changes
239
+ id: changes
240
+ with:
241
+ baseRef: main
242
+
243
+ - name: Verify outputs
244
+ run: |
245
+ echo "Changes detected: ${{ steps.changes.outputs.core }}"
246
+ if [ -z "${{ steps.changes.outputs.core }}" ]; then
247
+ echo "❌ No output from detect-changes"
248
+ exit 1
249
+ fi
250
+
251
+ test-calculate-version:
252
+ runs-on: ubuntu-latest
253
+ steps:
254
+ - uses: actions/checkout@v4
255
+ with:
256
+ fetch-depth: 0
257
+
258
+ - uses: ./.github/actions/calculate-version
259
+ id: version
260
+ with:
261
+ baseRef: main
262
+
263
+ - name: Verify version format
264
+ run: |
265
+ VERSION="${{ steps.version.outputs.nextVersion }}"
266
+ if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
267
+ echo "❌ Invalid version format: $VERSION"
268
+ exit 1
269
+ fi
270
+ echo "✅ Valid version: $VERSION"
271
+ ```
272
+
273
+ ### 5. Pre-commit Hooks
274
+
275
+ Add validation to pre-commit hooks:
276
+
277
+ ```bash
278
+ # .husky/pre-commit (or use lefthook/pre-commit)
279
+
280
+ #!/bin/bash
281
+
282
+ echo "🔍 Validating generated workflows..."
283
+
284
+ # Run validation script
285
+ node scripts/validate-pipeline.js
286
+
287
+ if [ $? -ne 0 ]; then
288
+ echo "❌ Pipeline validation failed. Please fix errors before committing."
289
+ exit 1
290
+ fi
291
+
292
+ # Lint YAML files
293
+ pnpm lint:yaml
294
+
295
+ # Run build to catch TypeScript errors
296
+ pnpm build
297
+
298
+ echo "✅ Pre-commit validation passed"
299
+ ```
300
+
301
+ ### 6. Automated Testing Matrix
302
+
303
+ #### Test Categories
304
+
305
+ | Category | Tool | Tests | Frequency |
306
+ |----------|------|-------|-----------|
307
+ | **Syntax Validation** | yamllint, action-validator | YAML syntax, schema validation | Pre-commit |
308
+ | **Build Verification** | TypeScript, pnpm | No unused imports, successful build | Pre-commit, CI |
309
+ | **Action Logic** | act, vitest | Action inputs/outputs, error handling | Pre-push, CI |
310
+ | **Integration** | act with test workflows | End-to-end workflow execution | Manual, CI |
311
+ | **Custom Validation** | Custom scripts | Git config, token passing, checkout order | Pre-commit, CI |
312
+
313
+ ## Implementation Checklist
314
+
315
+ ### Phase 1: Basic Validation (1-2 hours)
316
+ - [ ] Add yamllint to project
317
+ - [ ] Create `scripts/validate-pipeline.js`
318
+ - [ ] Add validation npm script
319
+ - [ ] Test with current pipeline
320
+
321
+ ### Phase 2: Act Integration (2-3 hours)
322
+ - [ ] Install and configure act
323
+ - [ ] Create test workflow (`.github/workflows/test-actions.yml`)
324
+ - [ ] Write basic act test scripts
325
+ - [ ] Document how to run act tests locally
326
+
327
+ ### Phase 3: Pre-commit Integration (1 hour)
328
+ - [ ] Set up husky or lefthook
329
+ - [ ] Add validation to pre-commit hook
330
+ - [ ] Add build check to pre-commit hook
331
+ - [ ] Test pre-commit hook
332
+
333
+ ### Phase 4: CI Integration (1-2 hours)
334
+ - [ ] Add validation job to pipeline
335
+ - [ ] Run act tests in CI (if feasible)
336
+ - [ ] Add build verification tests to test suite
337
+ - [ ] Configure failure notifications
338
+
339
+ ### Phase 5: Documentation (1 hour)
340
+ - [ ] Document how to run tests locally
341
+ - [ ] Add troubleshooting guide
342
+ - [ ] Create video/gif demos
343
+ - [ ] Update CONTRIBUTING.md
344
+
345
+ ## Testing Workflow
346
+
347
+ ### For Developers
348
+
349
+ ```bash
350
+ # Before committing changes to workflow/actions:
351
+
352
+ # 1. Validate pipeline syntax
353
+ npm run validate:pipeline
354
+
355
+ # 2. Test specific action locally with act
356
+ ./tests/act/test-tag-creation.sh
357
+
358
+ # 3. Run full build
359
+ pnpm build
360
+
361
+ # 4. Commit (pre-commit hooks will run automatically)
362
+ git commit -m "fix: update workflow"
363
+ ```
364
+
365
+ ### For CI
366
+
367
+ ```yaml
368
+ # Add to pipeline.yml or separate validation workflow
369
+ validate:
370
+ runs-on: ubuntu-latest
371
+ steps:
372
+ - uses: actions/checkout@v4
373
+
374
+ - name: Validate Pipeline
375
+ run: node scripts/validate-pipeline.js
376
+
377
+ - name: Lint YAML
378
+ run: pnpm lint:yaml
379
+
380
+ - name: Build Verification
381
+ run: pnpm build
382
+ ```
383
+
384
+ ## Specific Validations to Implement
385
+
386
+ ### 1. Git Config Validation
387
+ ```javascript
388
+ function checkGitConfig(actionPath) {
389
+ const content = fs.readFileSync(actionPath, 'utf8');
390
+ const gitOperations = ['git commit', 'git tag', 'git push'];
391
+ const hasGitOps = gitOperations.some(op => content.includes(op));
392
+ const hasGitConfig = content.includes('git config user.name');
393
+
394
+ if (hasGitOps && !hasGitConfig) {
395
+ return `❌ ${actionPath}: Git operations without git config`;
396
+ }
397
+ return null;
398
+ }
399
+ ```
400
+
401
+ ### 2. Token Passing Validation
402
+ ```javascript
403
+ function checkTokenUsage(actionPath) {
404
+ const content = fs.readFileSync(actionPath, 'utf8');
405
+ const action = yaml.parse(content);
406
+
407
+ // Check if composite action tries to use secrets
408
+ if (action.runs?.using === 'composite' && content.includes('secrets.')) {
409
+ return `❌ ${actionPath}: Composite action accessing secrets directly`;
410
+ }
411
+
412
+ // Check if action needs token but doesn't have input
413
+ if (content.includes('gh ') || content.includes('hub ')) {
414
+ const hasTokenInput = action.inputs?.token !== undefined;
415
+ if (!hasTokenInput) {
416
+ return `⚠️ ${actionPath}: Uses gh CLI but no token input defined`;
417
+ }
418
+ }
419
+
420
+ return null;
421
+ }
422
+ ```
423
+
424
+ ### 3. Version Format Validation
425
+ ```javascript
426
+ function checkVersionFormat(actionPath) {
427
+ const content = fs.readFileSync(actionPath, 'utf8');
428
+
429
+ // Check if action validates version format
430
+ if (content.includes('${{ inputs.version }}')) {
431
+ const hasValidation = content.includes('VERSION="${VERSION#v}"') ||
432
+ content.includes('strip v prefix');
433
+ if (!hasValidation) {
434
+ return `⚠️ ${actionPath}: Uses version input without stripping 'v' prefix`;
435
+ }
436
+ }
437
+
438
+ return null;
439
+ }
440
+ ```
441
+
442
+ ### 4. Checkout Before Local Action
443
+ ```javascript
444
+ function checkCheckoutBeforeLocalAction(workflow) {
445
+ const jobs = workflow.jobs || {};
446
+ const errors = [];
447
+
448
+ for (const [jobName, job] of Object.entries(jobs)) {
449
+ const steps = job.steps || [];
450
+ let hasCheckout = false;
451
+
452
+ for (let i = 0; i < steps.length; i++) {
453
+ const step = steps[i];
454
+
455
+ if (step.uses === 'actions/checkout@v4') {
456
+ hasCheckout = true;
457
+ }
458
+
459
+ if (step.uses && step.uses.startsWith('./') && !hasCheckout) {
460
+ errors.push(
461
+ `❌ Job '${jobName}' step ${i + 1}: Local action '${step.uses}' ` +
462
+ `used before checkout`
463
+ );
464
+ }
465
+ }
466
+ }
467
+
468
+ return errors;
469
+ }
470
+ ```
471
+
472
+ ## Benefits
473
+
474
+ 1. **Faster feedback loop**: Catch errors in seconds instead of waiting for CI
475
+ 2. **Reduced CI costs**: Fewer failed pipeline runs
476
+ 3. **Better developer experience**: Clear error messages locally
477
+ 4. **Confidence**: Know changes will work before pushing
478
+ 5. **Documentation**: Tests serve as examples of correct usage
479
+
480
+ ## Future Enhancements
481
+
482
+ 1. **Visual workflow validation**: Use mermaid diagrams to visualize workflow
483
+ 2. **Automated PR comments**: Bot comments on PRs with validation results
484
+ 3. **Performance testing**: Track workflow execution time
485
+ 4. **Drift detection**: Alert when workflows differ from templates
486
+ 5. **Security scanning**: Check for hardcoded secrets/tokens
487
+
488
+ ## Resources
489
+
490
+ - [nektos/act Documentation](https://github.com/nektos/act)
491
+ - [GitHub Actions Toolkit](https://github.com/actions/toolkit)
492
+ - [yamllint](https://github.com/adrienverge/yamllint)
493
+ - [action-validator](https://github.com/mpalmer/action-validator)
494
+
495
+ ## Conclusion
496
+
497
+ By implementing this testing strategy, we can catch most workflow/action errors locally before they reach GitHub Actions, saving time and improving the development experience. The key is to start with basic validation and gradually add more sophisticated tests as patterns emerge.
498
+
499
+ Start with Phase 1 (basic validation) and incrementally add more testing capabilities as needed.