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.
- package/.claude/settings.local.json +35 -0
- package/.github/actions/calculate-version/action.yml +106 -0
- package/.github/actions/create-pr/action.yml +122 -0
- package/.github/actions/create-release/action.yml +74 -0
- package/.github/actions/create-tag/action.yml +94 -0
- package/.github/actions/detect-changes/action.yml +62 -0
- package/.github/actions/manage-branch/action.yml +113 -0
- package/.github/actions/promote-branch/action.yml +352 -0
- package/.github/example/workflows/job.analyze.code.yml +26 -0
- package/.github/example/workflows/job.analyze.docker.yml +32 -0
- package/.github/example/workflows/job.app.api.deploy.yml +127 -0
- package/.github/example/workflows/job.app.api.test.yml +102 -0
- package/.github/example/workflows/job.app.docs.deploy.yml +40 -0
- package/.github/example/workflows/job.app.docs.test.yml +50 -0
- package/.github/example/workflows/job.app.web.deploy.yml +96 -0
- package/.github/example/workflows/job.app.web.test.yml +49 -0
- package/.github/example/workflows/job.changes.yml +82 -0
- package/.github/example/workflows/job.create-pr.yml +96 -0
- package/.github/example/workflows/job.env-check.yml +43 -0
- package/.github/example/workflows/job.fast-forward.yml +103 -0
- package/.github/example/workflows/job.lint.yml +64 -0
- package/.github/example/workflows/job.pr-name.yml +70 -0
- package/.github/example/workflows/job.pr-squash-name.yml +27 -0
- package/.github/example/workflows/job.tag.yml +38 -0
- package/.github/example/workflows/job.version.yml +99 -0
- package/.github/example/workflows/lib.plugin-base.publish.yml +59 -0
- package/.github/example/workflows/pipe.yml +178 -0
- package/.github/example/workflows/scripts/fastforward.sh +51 -0
- package/.github/example/workflows/scripts/pipe-cleaner.sh +70 -0
- package/.github/scripts/fastforward.sh +51 -0
- package/.github/scripts/pipe-cleaner.sh +70 -0
- package/.github/workflows/pipeline.yml +220 -0
- package/.github/workflows/pr-title-check.yml +70 -0
- package/.github/workflows/publish.yml +39 -0
- package/.pipecraftrc.json +78 -0
- package/.release-it.cjs +71 -0
- package/PIPELINE_TESTING_PLAN.md +499 -0
- package/README.md +1101 -0
- package/TRUNK_FLOW_PLAN.md +401 -0
- package/assets/logo_banner.png +0 -0
- package/assets/logo_banner.webp +0 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +364 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/generators/init.tpl.d.ts +3 -0
- package/dist/generators/init.tpl.d.ts.map +1 -0
- package/dist/generators/init.tpl.js +117 -0
- package/dist/generators/init.tpl.js.map +1 -0
- package/dist/generators/workflows.tpl.d.ts +7 -0
- package/dist/generators/workflows.tpl.d.ts.map +1 -0
- package/dist/generators/workflows.tpl.js +73 -0
- package/dist/generators/workflows.tpl.js.map +1 -0
- package/dist/templates/actions/calculate-version.yml.tpl.d.ts +3 -0
- package/dist/templates/actions/calculate-version.yml.tpl.d.ts.map +1 -0
- package/dist/templates/actions/calculate-version.yml.tpl.js +122 -0
- package/dist/templates/actions/calculate-version.yml.tpl.js.map +1 -0
- package/dist/templates/actions/create-pr.yml.tpl.d.ts +3 -0
- package/dist/templates/actions/create-pr.yml.tpl.d.ts.map +1 -0
- package/dist/templates/actions/create-pr.yml.tpl.js +145 -0
- package/dist/templates/actions/create-pr.yml.tpl.js.map +1 -0
- package/dist/templates/actions/create-tag.yml.tpl.d.ts +3 -0
- package/dist/templates/actions/create-tag.yml.tpl.d.ts.map +1 -0
- package/dist/templates/actions/create-tag.yml.tpl.js +111 -0
- package/dist/templates/actions/create-tag.yml.tpl.js.map +1 -0
- package/dist/templates/actions/detect-changes.yml.tpl.d.ts +3 -0
- package/dist/templates/actions/detect-changes.yml.tpl.d.ts.map +1 -0
- package/dist/templates/actions/detect-changes.yml.tpl.js +79 -0
- package/dist/templates/actions/detect-changes.yml.tpl.js.map +1 -0
- package/dist/templates/actions/manage-branch.yml.tpl.d.ts +3 -0
- package/dist/templates/actions/manage-branch.yml.tpl.d.ts.map +1 -0
- package/dist/templates/actions/manage-branch.yml.tpl.js +130 -0
- package/dist/templates/actions/manage-branch.yml.tpl.js.map +1 -0
- package/dist/templates/actions/promote-branch.yml.tpl.d.ts +3 -0
- package/dist/templates/actions/promote-branch.yml.tpl.d.ts.map +1 -0
- package/dist/templates/actions/promote-branch.yml.tpl.js +362 -0
- package/dist/templates/actions/promote-branch.yml.tpl.js.map +1 -0
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.d.ts +16 -0
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.d.ts.map +1 -0
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.js +652 -0
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.js.map +1 -0
- package/dist/types/index.d.ts +63 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/ast-path-operations.d.ts +287 -0
- package/dist/utils/ast-path-operations.d.ts.map +1 -0
- package/dist/utils/ast-path-operations.js +419 -0
- package/dist/utils/ast-path-operations.js.map +1 -0
- package/dist/utils/config.d.ts +3 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +49 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/github-setup.d.ts +74 -0
- package/dist/utils/github-setup.d.ts.map +1 -0
- package/dist/utils/github-setup.js +418 -0
- package/dist/utils/github-setup.js.map +1 -0
- package/dist/utils/idempotency.d.ts +47 -0
- package/dist/utils/idempotency.d.ts.map +1 -0
- package/dist/utils/idempotency.js +176 -0
- package/dist/utils/idempotency.js.map +1 -0
- package/dist/utils/preflight.d.ts +55 -0
- package/dist/utils/preflight.d.ts.map +1 -0
- package/dist/utils/preflight.js +261 -0
- package/dist/utils/preflight.js.map +1 -0
- package/dist/utils/versioning.d.ts +41 -0
- package/dist/utils/versioning.d.ts.map +1 -0
- package/dist/utils/versioning.js +219 -0
- package/dist/utils/versioning.js.map +1 -0
- package/docs/USER_JOURNEY_ERRORS.md +352 -0
- package/eslint.config.js +77 -0
- package/examples/basic-config.json +41 -0
- package/examples/monorepo-config.json +49 -0
- package/examples/usage.md +289 -0
- package/package.json +58 -0
- package/scripts/debug-workflows.sh +339 -0
- package/scripts/parse-pipeline.js +29 -0
- package/scripts/test-job-order.cjs +140 -0
- package/scripts/validate-pipeline.cjs +379 -0
- package/scripts/verify-job-order.sh +30 -0
- package/src/cli/index.ts +414 -0
- package/src/generators/init.tpl.ts +126 -0
- package/src/generators/workflows.tpl.ts +80 -0
- package/src/templates/actions/calculate-version.yml.tpl.ts +124 -0
- package/src/templates/actions/create-pr.yml.tpl.ts +147 -0
- package/src/templates/actions/create-tag.yml.tpl.ts +112 -0
- package/src/templates/actions/detect-changes.yml.tpl.ts +87 -0
- package/src/templates/actions/manage-branch.yml.tpl.ts +132 -0
- package/src/templates/actions/promote-branch.yml.tpl.ts +364 -0
- package/src/templates/workflows/pipeline-path-based.yml.tpl.ts +706 -0
- package/src/types/index.ts +64 -0
- package/src/utils/README-ast-path-operations.md +390 -0
- package/src/utils/ast-path-operations.ts +581 -0
- package/src/utils/config.ts +64 -0
- package/src/utils/github-setup.ts +558 -0
- package/src/utils/idempotency.ts +215 -0
- package/src/utils/preflight.ts +306 -0
- package/src/utils/versioning.ts +244 -0
- package/tests/README.md +229 -0
- package/tests/TEST_STRUCTURE.md +256 -0
- package/tests/act/run-act-tests.sh +345 -0
- package/tests/debugging/debug-utils.ts +538 -0
- package/tests/debugging/debug-workflow.test.ts +339 -0
- package/tests/debugging/debug-workflows.sh +339 -0
- package/tests/debugging/iterative-debug.ts +652 -0
- package/tests/debugging/run-debug-tests.sh +431 -0
- package/tests/fixtures/basic-config.json +51 -0
- package/tests/fixtures/invalid-config.json +9 -0
- package/tests/fixtures/pipeline-generated.yml +235 -0
- package/tests/fixtures/pipeline-preserve-comments.yml +245 -0
- package/tests/fixtures/pipeline-user-modified.yml +245 -0
- package/tests/fixtures/test-config.json +58 -0
- package/tests/github-live/README.md +250 -0
- package/tests/github-local/JOB_WORKFLOW_TESTS.md +296 -0
- package/tests/github-local/README.md +240 -0
- package/tests/github-local/run-all-tests.sh +422 -0
- package/tests/github-local/test-job-workflows.sh +631 -0
- package/tests/github-local/test-pipeline-workflow.sh +440 -0
- package/tests/integration/generators.test.ts +578 -0
- package/tests/integration/path-based-template.test.ts +510 -0
- package/tests/integration/simple-path-based.test.ts +415 -0
- package/tests/setup.ts +56 -0
- package/tests/unit/ast-path-operations-extended.test.ts +302 -0
- package/tests/unit/cli.test.ts +541 -0
- package/tests/unit/config-extended.test.ts +412 -0
- package/tests/unit/config.test.ts +152 -0
- package/tests/unit/github-setup.test.ts +189 -0
- package/tests/unit/idempotency-isolated.test.ts +297 -0
- package/tests/unit/job-order.test.ts +157 -0
- package/tests/unit/pipeline-path-based.test.ts +511 -0
- package/tests/unit/validate-pipeline.test.ts +384 -0
- package/tests/unit/versioning-extended.test.ts +279 -0
- package/tests/unit/versioning.test.ts +241 -0
- package/tsconfig.debug.json +17 -0
- package/tsconfig.json +28 -0
- 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.
|