pipecraft 0.0.0-releaseit ā 0.26.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/README.md +126 -108
- package/dist/cli/index.d.ts +69 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +94 -22
- package/dist/cli/index.js.map +1 -1
- package/dist/generators/init.tpl.d.ts +70 -0
- package/dist/generators/init.tpl.d.ts.map +1 -1
- package/dist/generators/init.tpl.js +100 -8
- package/dist/generators/init.tpl.js.map +1 -1
- package/dist/generators/workflows.tpl.d.ts +86 -0
- package/dist/generators/workflows.tpl.d.ts.map +1 -1
- package/dist/generators/workflows.tpl.js +101 -9
- package/dist/generators/workflows.tpl.js.map +1 -1
- package/dist/templates/actions/calculate-version.yml.tpl.d.ts +47 -0
- package/dist/templates/actions/calculate-version.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/calculate-version.yml.tpl.js +72 -3
- package/dist/templates/actions/calculate-version.yml.tpl.js.map +1 -1
- package/dist/templates/actions/create-pr.yml.tpl.d.ts +14 -0
- package/dist/templates/actions/create-pr.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/create-pr.yml.tpl.js +22 -2
- package/dist/templates/actions/create-pr.yml.tpl.js.map +1 -1
- package/dist/templates/actions/create-release.yml.tpl.d.ts +18 -0
- package/dist/templates/actions/create-release.yml.tpl.d.ts.map +1 -0
- package/dist/templates/actions/create-release.yml.tpl.js +141 -0
- package/dist/templates/actions/create-release.yml.tpl.js.map +1 -0
- package/dist/templates/actions/create-tag.yml.tpl.d.ts +14 -0
- package/dist/templates/actions/create-tag.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/create-tag.yml.tpl.js +22 -2
- package/dist/templates/actions/create-tag.yml.tpl.js.map +1 -1
- package/dist/templates/actions/detect-changes.yml.tpl.d.ts +70 -0
- package/dist/templates/actions/detect-changes.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/detect-changes.yml.tpl.js +98 -2
- package/dist/templates/actions/detect-changes.yml.tpl.js.map +1 -1
- package/dist/templates/actions/manage-branch.yml.tpl.d.ts +14 -0
- package/dist/templates/actions/manage-branch.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/manage-branch.yml.tpl.js +22 -2
- package/dist/templates/actions/manage-branch.yml.tpl.js.map +1 -1
- package/dist/templates/actions/promote-branch.yml.tpl.d.ts +14 -0
- package/dist/templates/actions/promote-branch.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/promote-branch.yml.tpl.js +50 -7
- package/dist/templates/actions/promote-branch.yml.tpl.js.map +1 -1
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.d.ts +114 -1
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.d.ts.map +1 -1
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.js +479 -74
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.js.map +1 -1
- package/dist/types/index.d.ts +231 -8
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +9 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/ast-path-operations.d.ts.map +1 -1
- package/dist/utils/ast-path-operations.js +24 -11
- package/dist/utils/ast-path-operations.js.map +1 -1
- package/dist/utils/config.d.ts +63 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +71 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/github-setup.d.ts +323 -2
- package/dist/utils/github-setup.d.ts.map +1 -1
- package/dist/utils/github-setup.js +551 -23
- package/dist/utils/github-setup.js.map +1 -1
- package/dist/utils/idempotency.d.ts +147 -7
- package/dist/utils/idempotency.d.ts.map +1 -1
- package/dist/utils/idempotency.js +143 -15
- package/dist/utils/idempotency.js.map +1 -1
- package/dist/utils/logger.d.ts +134 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +153 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/preflight.d.ts +223 -12
- package/dist/utils/preflight.d.ts.map +1 -1
- package/dist/utils/preflight.js +216 -16
- package/dist/utils/preflight.js.map +1 -1
- package/dist/utils/versioning.d.ts +160 -8
- package/dist/utils/versioning.d.ts.map +1 -1
- package/dist/utils/versioning.js +179 -15
- package/dist/utils/versioning.js.map +1 -1
- package/package.json +26 -5
- package/.claude/settings.local.json +0 -35
- package/.github/actions/calculate-version/action.yml +0 -106
- package/.github/actions/create-pr/action.yml +0 -122
- package/.github/actions/create-release/action.yml +0 -74
- package/.github/actions/create-tag/action.yml +0 -94
- package/.github/actions/detect-changes/action.yml +0 -62
- package/.github/actions/manage-branch/action.yml +0 -113
- package/.github/actions/promote-branch/action.yml +0 -352
- package/.github/example/workflows/job.analyze.code.yml +0 -26
- package/.github/example/workflows/job.analyze.docker.yml +0 -32
- package/.github/example/workflows/job.app.api.deploy.yml +0 -127
- package/.github/example/workflows/job.app.api.test.yml +0 -102
- package/.github/example/workflows/job.app.docs.deploy.yml +0 -40
- package/.github/example/workflows/job.app.docs.test.yml +0 -50
- package/.github/example/workflows/job.app.web.deploy.yml +0 -96
- package/.github/example/workflows/job.app.web.test.yml +0 -49
- package/.github/example/workflows/job.changes.yml +0 -82
- package/.github/example/workflows/job.create-pr.yml +0 -96
- package/.github/example/workflows/job.env-check.yml +0 -43
- package/.github/example/workflows/job.fast-forward.yml +0 -103
- package/.github/example/workflows/job.lint.yml +0 -64
- package/.github/example/workflows/job.pr-name.yml +0 -70
- package/.github/example/workflows/job.pr-squash-name.yml +0 -27
- package/.github/example/workflows/job.tag.yml +0 -38
- package/.github/example/workflows/job.version.yml +0 -99
- package/.github/example/workflows/lib.plugin-base.publish.yml +0 -59
- package/.github/example/workflows/pipe.yml +0 -178
- package/.github/example/workflows/scripts/fastforward.sh +0 -51
- package/.github/example/workflows/scripts/pipe-cleaner.sh +0 -70
- package/.github/scripts/fastforward.sh +0 -51
- package/.github/scripts/pipe-cleaner.sh +0 -70
- package/.github/workflows/pipeline.yml +0 -220
- package/.github/workflows/pr-title-check.yml +0 -70
- package/.github/workflows/publish.yml +0 -39
- package/.pipecraftrc.json +0 -78
- package/.release-it.cjs +0 -71
- package/PIPELINE_TESTING_PLAN.md +0 -499
- package/TRUNK_FLOW_PLAN.md +0 -401
- package/assets/logo_banner.png +0 -0
- package/assets/logo_banner.webp +0 -0
- package/docs/USER_JOURNEY_ERRORS.md +0 -352
- package/eslint.config.js +0 -77
- package/examples/basic-config.json +0 -41
- package/examples/monorepo-config.json +0 -49
- package/examples/usage.md +0 -289
- package/scripts/debug-workflows.sh +0 -339
- package/scripts/parse-pipeline.js +0 -29
- package/scripts/test-job-order.cjs +0 -140
- package/scripts/validate-pipeline.cjs +0 -379
- package/scripts/verify-job-order.sh +0 -30
- package/src/cli/index.ts +0 -414
- package/src/generators/init.tpl.ts +0 -126
- package/src/generators/workflows.tpl.ts +0 -80
- package/src/templates/actions/calculate-version.yml.tpl.ts +0 -124
- package/src/templates/actions/create-pr.yml.tpl.ts +0 -147
- package/src/templates/actions/create-tag.yml.tpl.ts +0 -112
- package/src/templates/actions/detect-changes.yml.tpl.ts +0 -87
- package/src/templates/actions/manage-branch.yml.tpl.ts +0 -132
- package/src/templates/actions/promote-branch.yml.tpl.ts +0 -364
- package/src/templates/workflows/pipeline-path-based.yml.tpl.ts +0 -706
- package/src/types/index.ts +0 -64
- package/src/utils/README-ast-path-operations.md +0 -390
- package/src/utils/ast-path-operations.ts +0 -581
- package/src/utils/config.ts +0 -64
- package/src/utils/github-setup.ts +0 -558
- package/src/utils/idempotency.ts +0 -215
- package/src/utils/preflight.ts +0 -306
- package/src/utils/versioning.ts +0 -244
- package/tests/README.md +0 -229
- package/tests/TEST_STRUCTURE.md +0 -256
- package/tests/act/run-act-tests.sh +0 -345
- package/tests/debugging/debug-utils.ts +0 -538
- package/tests/debugging/debug-workflow.test.ts +0 -339
- package/tests/debugging/debug-workflows.sh +0 -339
- package/tests/debugging/iterative-debug.ts +0 -652
- package/tests/debugging/run-debug-tests.sh +0 -431
- package/tests/fixtures/basic-config.json +0 -51
- package/tests/fixtures/invalid-config.json +0 -9
- package/tests/fixtures/pipeline-generated.yml +0 -235
- package/tests/fixtures/pipeline-preserve-comments.yml +0 -245
- package/tests/fixtures/pipeline-user-modified.yml +0 -245
- package/tests/fixtures/test-config.json +0 -58
- package/tests/github-live/README.md +0 -250
- package/tests/github-local/JOB_WORKFLOW_TESTS.md +0 -296
- package/tests/github-local/README.md +0 -240
- package/tests/github-local/run-all-tests.sh +0 -422
- package/tests/github-local/test-job-workflows.sh +0 -631
- package/tests/github-local/test-pipeline-workflow.sh +0 -440
- package/tests/integration/generators.test.ts +0 -578
- package/tests/integration/path-based-template.test.ts +0 -510
- package/tests/integration/simple-path-based.test.ts +0 -415
- package/tests/setup.ts +0 -56
- package/tests/unit/ast-path-operations-extended.test.ts +0 -302
- package/tests/unit/cli.test.ts +0 -541
- package/tests/unit/config-extended.test.ts +0 -412
- package/tests/unit/config.test.ts +0 -152
- package/tests/unit/github-setup.test.ts +0 -189
- package/tests/unit/idempotency-isolated.test.ts +0 -297
- package/tests/unit/job-order.test.ts +0 -157
- package/tests/unit/pipeline-path-based.test.ts +0 -511
- package/tests/unit/validate-pipeline.test.ts +0 -384
- package/tests/unit/versioning-extended.test.ts +0 -279
- package/tests/unit/versioning.test.ts +0 -241
- package/tsconfig.debug.json +0 -17
- package/tsconfig.json +0 -28
- package/vitest.config.ts +0 -51
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Test script to compare job order between original and generated pipelines
|
|
5
|
-
* This ensures that Pipecraft jobs maintain their original positions
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const yaml = require('yaml');
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Extract job names in order from a YAML file
|
|
14
|
-
*/
|
|
15
|
-
function getJobOrder(filePath) {
|
|
16
|
-
if (!fs.existsSync(filePath)) {
|
|
17
|
-
console.error(`ā File not found: ${filePath}`);
|
|
18
|
-
return [];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
23
|
-
const doc = yaml.parseDocument(content);
|
|
24
|
-
|
|
25
|
-
if (!doc.contents || !doc.contents.get('jobs')) {
|
|
26
|
-
console.error(`ā No jobs section found in ${filePath}`);
|
|
27
|
-
return [];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const jobsNode = doc.contents.get('jobs');
|
|
31
|
-
if (!jobsNode.items) {
|
|
32
|
-
console.error(`ā No job items found in ${filePath}`);
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return jobsNode.items.map(item => item.key.value);
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.error(`ā Error parsing ${filePath}:`, error.message);
|
|
39
|
-
return [];
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Compare two job orders and report differences
|
|
45
|
-
*/
|
|
46
|
-
function compareJobOrders(original, generated, testName) {
|
|
47
|
-
console.log(`\nš Testing: ${testName}`);
|
|
48
|
-
console.log(`š Original order: [${original.join(', ')}]`);
|
|
49
|
-
console.log(`š Generated order: [${generated.join(', ')}]`);
|
|
50
|
-
|
|
51
|
-
if (original.length !== generated.length) {
|
|
52
|
-
console.log(`ā Length mismatch: original has ${original.length} jobs, generated has ${generated.length}`);
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let matches = 0;
|
|
57
|
-
let differences = [];
|
|
58
|
-
|
|
59
|
-
for (let i = 0; i < original.length; i++) {
|
|
60
|
-
if (original[i] === generated[i]) {
|
|
61
|
-
matches++;
|
|
62
|
-
} else {
|
|
63
|
-
differences.push({
|
|
64
|
-
position: i,
|
|
65
|
-
original: original[i],
|
|
66
|
-
generated: generated[i]
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (matches === original.length) {
|
|
72
|
-
console.log(`ā
Perfect match! All ${matches} jobs in correct order`);
|
|
73
|
-
return true;
|
|
74
|
-
} else {
|
|
75
|
-
console.log(`ā ${differences.length} position(s) differ:`);
|
|
76
|
-
differences.forEach(diff => {
|
|
77
|
-
console.log(` Position ${diff.position}: expected "${diff.original}", got "${diff.generated}"`);
|
|
78
|
-
});
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Main test function
|
|
85
|
-
*/
|
|
86
|
-
function runJobOrderTests() {
|
|
87
|
-
console.log('š§Ŗ Pipecraft Job Order Test Suite');
|
|
88
|
-
console.log('=====================================');
|
|
89
|
-
|
|
90
|
-
const testCases = [
|
|
91
|
-
{
|
|
92
|
-
name: 'User Modified Pipeline',
|
|
93
|
-
original: '.github/workflows/pipeline-user-modified.yml',
|
|
94
|
-
generated: '.github/workflows/pipeline-test-order-restored.yml'
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
name: 'Final Test Pipeline',
|
|
98
|
-
original: '.github/workflows/pipeline-user-modified.yml',
|
|
99
|
-
generated: '.github/workflows/pipeline-test-final.yml'
|
|
100
|
-
}
|
|
101
|
-
];
|
|
102
|
-
|
|
103
|
-
let allPassed = true;
|
|
104
|
-
|
|
105
|
-
for (const testCase of testCases) {
|
|
106
|
-
const originalOrder = getJobOrder(testCase.original);
|
|
107
|
-
const generatedOrder = getJobOrder(testCase.generated);
|
|
108
|
-
|
|
109
|
-
if (originalOrder.length === 0 || generatedOrder.length === 0) {
|
|
110
|
-
console.log(`ā Skipping ${testCase.name} - failed to parse files`);
|
|
111
|
-
allPassed = false;
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const passed = compareJobOrders(originalOrder, generatedOrder, testCase.name);
|
|
116
|
-
if (!passed) {
|
|
117
|
-
allPassed = false;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
console.log('\nš Test Results');
|
|
122
|
-
console.log('================');
|
|
123
|
-
if (allPassed) {
|
|
124
|
-
console.log('ā
All job order tests PASSED!');
|
|
125
|
-
console.log('š Pipecraft jobs are maintaining their original positions');
|
|
126
|
-
} else {
|
|
127
|
-
console.log('ā Some job order tests FAILED!');
|
|
128
|
-
console.log('š§ Job positions are not being preserved correctly');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return allPassed;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Run the tests if this script is executed directly
|
|
135
|
-
if (require.main === module) {
|
|
136
|
-
const success = runJobOrderTests();
|
|
137
|
-
process.exit(success ? 0 : 1);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
module.exports = { getJobOrder, compareJobOrders, runJobOrderTests };
|
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Pipeline Validation Script
|
|
5
|
-
*
|
|
6
|
-
* Validates GitHub Actions workflows and composite actions to catch common errors:
|
|
7
|
-
* - Missing git config in actions that perform git operations
|
|
8
|
-
* - Secrets usage in composite actions (should use inputs)
|
|
9
|
-
* - Local actions used without checkout
|
|
10
|
-
* - Missing token inputs for actions that need authentication
|
|
11
|
-
* - Version format issues
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const fs = require('fs');
|
|
15
|
-
const path = require('path');
|
|
16
|
-
const yaml = require('yaml');
|
|
17
|
-
|
|
18
|
-
// ANSI color codes for pretty output
|
|
19
|
-
const colors = {
|
|
20
|
-
reset: '\x1b[0m',
|
|
21
|
-
red: '\x1b[31m',
|
|
22
|
-
green: '\x1b[32m',
|
|
23
|
-
yellow: '\x1b[33m',
|
|
24
|
-
blue: '\x1b[34m',
|
|
25
|
-
cyan: '\x1b[36m',
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
function log(message, color = colors.reset) {
|
|
29
|
-
console.log(`${color}${message}${colors.reset}`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function error(message) {
|
|
33
|
-
log(`ā ${message}`, colors.red);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function warning(message) {
|
|
37
|
-
log(`ā ļø ${message}`, colors.yellow);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function success(message) {
|
|
41
|
-
log(`ā
${message}`, colors.green);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function info(message) {
|
|
45
|
-
log(`ā¹ļø ${message}`, colors.cyan);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Validate that git operations have git config set
|
|
50
|
-
*/
|
|
51
|
-
function validateGitConfig(actionPath) {
|
|
52
|
-
const content = fs.readFileSync(actionPath, 'utf8');
|
|
53
|
-
// Only check for write operations that require git config
|
|
54
|
-
// Exclude read-only operations like 'git tag --points-at'
|
|
55
|
-
const gitWriteOps = [
|
|
56
|
-
/git commit(?!\s+--points-at)/,
|
|
57
|
-
/git tag(?!\s+(--points-at|--list))/,
|
|
58
|
-
/git push/,
|
|
59
|
-
/git merge/
|
|
60
|
-
];
|
|
61
|
-
|
|
62
|
-
const hasGitWriteOps = gitWriteOps.some(op => op.test(content));
|
|
63
|
-
const hasGitConfig = content.includes('git config user.name') ||
|
|
64
|
-
content.includes('git config --global user.name');
|
|
65
|
-
|
|
66
|
-
if (hasGitWriteOps && !hasGitConfig) {
|
|
67
|
-
return `Git operations without git config (user.name/email)`;
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Validate that composite actions don't access secrets directly
|
|
74
|
-
*/
|
|
75
|
-
function validateTokenUsage(actionPath) {
|
|
76
|
-
const content = fs.readFileSync(actionPath, 'utf8');
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
const action = yaml.parse(content);
|
|
80
|
-
|
|
81
|
-
// Check if composite action tries to use secrets
|
|
82
|
-
if (action.runs?.using === 'composite' && content.includes('secrets.')) {
|
|
83
|
-
return `Composite action accessing secrets directly (use inputs instead)`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Check if action uses gh/hub CLI without token input
|
|
87
|
-
if (content.includes('gh ') || content.includes('hub ') || content.includes('GITHUB_TOKEN')) {
|
|
88
|
-
const hasTokenInput = action.inputs?.token !== undefined;
|
|
89
|
-
if (!hasTokenInput && content.includes('secrets.GITHUB_TOKEN')) {
|
|
90
|
-
return `Uses GITHUB_TOKEN via secrets instead of input parameter`;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
} catch (err) {
|
|
94
|
-
return `Failed to parse YAML: ${err.message}`;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Validate version format handling
|
|
102
|
-
*/
|
|
103
|
-
function validateVersionFormat(actionPath) {
|
|
104
|
-
const content = fs.readFileSync(actionPath, 'utf8');
|
|
105
|
-
|
|
106
|
-
// Check if action uses version input
|
|
107
|
-
if (content.includes('${{ inputs.version }}') || content.includes('${VERSION}')) {
|
|
108
|
-
// Check if it handles both v-prefixed and non-prefixed versions
|
|
109
|
-
const hasStripPrefix = content.includes('VERSION="${VERSION#v}"') ||
|
|
110
|
-
content.includes('strip v prefix') ||
|
|
111
|
-
content.includes('VERSION#v');
|
|
112
|
-
|
|
113
|
-
// Only warn if it looks like it's validating version format without stripping
|
|
114
|
-
const hasVersionValidation = content.includes('=~ ^[0-9]+') ||
|
|
115
|
-
content.includes('version format');
|
|
116
|
-
|
|
117
|
-
if (hasVersionValidation && !hasStripPrefix) {
|
|
118
|
-
return `Validates version format but doesn't strip 'v' prefix (may reject v1.0.0)`;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Validate error handling in scripts
|
|
127
|
-
*/
|
|
128
|
-
function validateErrorHandling(actionPath) {
|
|
129
|
-
const content = fs.readFileSync(actionPath, 'utf8');
|
|
130
|
-
const warnings = [];
|
|
131
|
-
|
|
132
|
-
// Check for suppressed error output (2>/dev/null) with critical commands
|
|
133
|
-
const criticalCommands = ['gh pr create', 'gh pr merge', 'gh release create', 'git push', 'git tag'];
|
|
134
|
-
|
|
135
|
-
for (const cmd of criticalCommands) {
|
|
136
|
-
// Match command followed by 2>/dev/null
|
|
137
|
-
const regex = new RegExp(`${cmd.replace(/\s+/g, '\\s+')}[^\\n]*2>/dev/null`, 'g');
|
|
138
|
-
if (regex.test(content)) {
|
|
139
|
-
warnings.push(`Suppresses errors for '${cmd}' (2>/dev/null hides useful error messages)`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return warnings.length > 0 ? warnings.join('; ') : null;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Validate GitHub CLI usage has GH_TOKEN set
|
|
148
|
-
* Note: For composite actions, GH_TOKEN should be set at the workflow job level,
|
|
149
|
-
* not in the action itself. This validator just warns if gh CLI is used.
|
|
150
|
-
*/
|
|
151
|
-
function validateGhToken(actionPath) {
|
|
152
|
-
const content = fs.readFileSync(actionPath, 'utf8');
|
|
153
|
-
|
|
154
|
-
// Check if action uses gh CLI commands
|
|
155
|
-
const ghCommands = ['gh pr', 'gh release', 'gh issue', 'gh workflow'];
|
|
156
|
-
const usesGhCli = ghCommands.some(cmd => content.includes(cmd));
|
|
157
|
-
|
|
158
|
-
if (!usesGhCli) {
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// For composite actions, this is just a warning reminder
|
|
163
|
-
// The GH_TOKEN should be set at the job level in the workflow
|
|
164
|
-
return null; // No error - workflow should handle this
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Validate workflow jobs
|
|
169
|
-
*/
|
|
170
|
-
function validateWorkflow(workflowPath) {
|
|
171
|
-
const errors = [];
|
|
172
|
-
const warnings = [];
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
const content = fs.readFileSync(workflowPath, 'utf8');
|
|
176
|
-
const workflow = yaml.parse(content);
|
|
177
|
-
|
|
178
|
-
// Check for local actions without checkout
|
|
179
|
-
for (const [jobName, job] of Object.entries(workflow.jobs || {})) {
|
|
180
|
-
const steps = job.steps || [];
|
|
181
|
-
let hasCheckout = false;
|
|
182
|
-
|
|
183
|
-
for (let i = 0; i < steps.length; i++) {
|
|
184
|
-
const step = steps[i];
|
|
185
|
-
|
|
186
|
-
if (step.uses === 'actions/checkout@v4' || step.uses?.startsWith('actions/checkout@')) {
|
|
187
|
-
hasCheckout = true;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Check for local action usage
|
|
191
|
-
if (step.uses && step.uses.startsWith('./')) {
|
|
192
|
-
if (!hasCheckout) {
|
|
193
|
-
// Check if the action itself has a checkout step
|
|
194
|
-
const actionPath = path.join('.github', 'actions', path.basename(step.uses), 'action.yml');
|
|
195
|
-
let actionHasCheckout = false;
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
if (fs.existsSync(actionPath)) {
|
|
199
|
-
const actionContent = fs.readFileSync(actionPath, 'utf8');
|
|
200
|
-
actionHasCheckout = actionContent.includes('actions/checkout');
|
|
201
|
-
}
|
|
202
|
-
} catch (e) {
|
|
203
|
-
// Ignore errors reading action file
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (!actionHasCheckout) {
|
|
207
|
-
errors.push(`Job '${jobName}' step ${i + 1}: Local action '${step.uses}' used before checkout`);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Check for pnpm cache before pnpm install
|
|
215
|
-
for (const [jobName, job] of Object.entries(workflow.jobs || {})) {
|
|
216
|
-
const steps = job.steps || [];
|
|
217
|
-
let setupNodeWithPnpmCache = false;
|
|
218
|
-
let pnpmInstalled = false;
|
|
219
|
-
|
|
220
|
-
for (const step of steps) {
|
|
221
|
-
if (step.uses?.includes('actions/setup-node') && step.with?.cache === 'pnpm') {
|
|
222
|
-
setupNodeWithPnpmCache = true;
|
|
223
|
-
if (!pnpmInstalled) {
|
|
224
|
-
warnings.push(`Job '${jobName}': setup-node with pnpm cache before pnpm is installed`);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (step.uses?.includes('pnpm/action-setup')) {
|
|
229
|
-
pnpmInstalled = true;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
} catch (err) {
|
|
235
|
-
errors.push(`Failed to parse workflow: ${err.message}`);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return { errors, warnings };
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Validate composite action
|
|
243
|
-
*/
|
|
244
|
-
function validateCompositeAction(actionPath) {
|
|
245
|
-
const errors = [];
|
|
246
|
-
const warnings = [];
|
|
247
|
-
const fileName = path.basename(path.dirname(actionPath));
|
|
248
|
-
|
|
249
|
-
// Git config validation
|
|
250
|
-
const gitConfigError = validateGitConfig(actionPath);
|
|
251
|
-
if (gitConfigError) {
|
|
252
|
-
errors.push(`${fileName}: ${gitConfigError}`);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Token usage validation
|
|
256
|
-
const tokenError = validateTokenUsage(actionPath);
|
|
257
|
-
if (tokenError) {
|
|
258
|
-
errors.push(`${fileName}: ${tokenError}`);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Version format validation
|
|
262
|
-
const versionWarning = validateVersionFormat(actionPath);
|
|
263
|
-
if (versionWarning) {
|
|
264
|
-
warnings.push(`${fileName}: ${versionWarning}`);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Error handling validation
|
|
268
|
-
const errorHandlingWarning = validateErrorHandling(actionPath);
|
|
269
|
-
if (errorHandlingWarning) {
|
|
270
|
-
warnings.push(`${fileName}: ${errorHandlingWarning}`);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// GH_TOKEN validation
|
|
274
|
-
const ghTokenError = validateGhToken(actionPath);
|
|
275
|
-
if (ghTokenError) {
|
|
276
|
-
errors.push(`${fileName}: ${ghTokenError}`);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
return { errors, warnings };
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Main validation function
|
|
284
|
-
*/
|
|
285
|
-
function main() {
|
|
286
|
-
info('š Validating PipeCraft Pipeline...\n');
|
|
287
|
-
|
|
288
|
-
let allErrors = [];
|
|
289
|
-
let allWarnings = [];
|
|
290
|
-
|
|
291
|
-
// Validate workflows
|
|
292
|
-
const workflowsDir = path.join(process.cwd(), '.github/workflows');
|
|
293
|
-
if (fs.existsSync(workflowsDir)) {
|
|
294
|
-
const workflowFiles = fs.readdirSync(workflowsDir)
|
|
295
|
-
.filter(f => f.endsWith('.yml') || f.endsWith('.yaml'))
|
|
296
|
-
.map(f => path.join(workflowsDir, f));
|
|
297
|
-
|
|
298
|
-
log(`š Validating ${workflowFiles.length} workflow(s)...`, colors.blue);
|
|
299
|
-
|
|
300
|
-
for (const workflowPath of workflowFiles) {
|
|
301
|
-
const fileName = path.basename(workflowPath);
|
|
302
|
-
const { errors, warnings } = validateWorkflow(workflowPath);
|
|
303
|
-
|
|
304
|
-
if (errors.length === 0 && warnings.length === 0) {
|
|
305
|
-
success(` ${fileName}`);
|
|
306
|
-
} else {
|
|
307
|
-
if (errors.length > 0) {
|
|
308
|
-
error(` ${fileName}`);
|
|
309
|
-
errors.forEach(err => error(` ${err}`));
|
|
310
|
-
}
|
|
311
|
-
if (warnings.length > 0) {
|
|
312
|
-
warning(` ${fileName}`);
|
|
313
|
-
warnings.forEach(warn => warning(` ${warn}`));
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
allErrors = allErrors.concat(errors);
|
|
318
|
-
allWarnings = allWarnings.concat(warnings);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
console.log();
|
|
323
|
-
|
|
324
|
-
// Validate composite actions
|
|
325
|
-
const actionsDir = path.join(process.cwd(), '.github/actions');
|
|
326
|
-
if (fs.existsSync(actionsDir)) {
|
|
327
|
-
const actionDirs = fs.readdirSync(actionsDir)
|
|
328
|
-
.map(dir => path.join(actionsDir, dir, 'action.yml'))
|
|
329
|
-
.filter(p => fs.existsSync(p));
|
|
330
|
-
|
|
331
|
-
log(`š¬ Validating ${actionDirs.length} action(s)...`, colors.blue);
|
|
332
|
-
|
|
333
|
-
for (const actionPath of actionDirs) {
|
|
334
|
-
const actionName = path.basename(path.dirname(actionPath));
|
|
335
|
-
const { errors, warnings } = validateCompositeAction(actionPath);
|
|
336
|
-
|
|
337
|
-
if (errors.length === 0 && warnings.length === 0) {
|
|
338
|
-
success(` ${actionName}`);
|
|
339
|
-
} else {
|
|
340
|
-
if (errors.length > 0) {
|
|
341
|
-
error(` ${actionName}`);
|
|
342
|
-
errors.forEach(err => error(` ${err}`));
|
|
343
|
-
}
|
|
344
|
-
if (warnings.length > 0) {
|
|
345
|
-
warning(` ${actionName}`);
|
|
346
|
-
warnings.forEach(warn => warning(` ${warn}`));
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
allErrors = allErrors.concat(errors);
|
|
351
|
-
allWarnings = allWarnings.concat(warnings);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
console.log();
|
|
356
|
-
|
|
357
|
-
// Summary
|
|
358
|
-
if (allErrors.length === 0 && allWarnings.length === 0) {
|
|
359
|
-
success('⨠Pipeline validation passed! No issues found.');
|
|
360
|
-
return 0;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
if (allErrors.length > 0) {
|
|
364
|
-
error(`\nā Found ${allErrors.length} error(s)`);
|
|
365
|
-
return 1;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
if (allWarnings.length > 0) {
|
|
369
|
-
warning(`\nā ļø Found ${allWarnings.length} warning(s)`);
|
|
370
|
-
warning('Warnings do not fail validation but should be reviewed.');
|
|
371
|
-
return 0;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return 0;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// Run validation
|
|
378
|
-
const exitCode = main();
|
|
379
|
-
process.exit(exitCode);
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# Quick job order verification script
|
|
4
|
-
# Usage: ./scripts/verify-job-order.sh <original-file> <generated-file>
|
|
5
|
-
|
|
6
|
-
set -e
|
|
7
|
-
|
|
8
|
-
ORIGINAL_FILE=${1:-".github/workflows/pipeline-user-modified.yml"}
|
|
9
|
-
GENERATED_FILE=${2:-".github/workflows/pipeline-test-order-restored.yml"}
|
|
10
|
-
|
|
11
|
-
echo "š Verifying job order preservation..."
|
|
12
|
-
echo "š Original: $ORIGINAL_FILE"
|
|
13
|
-
echo "š Generated: $GENERATED_FILE"
|
|
14
|
-
|
|
15
|
-
# Extract job names from both files (only from jobs section)
|
|
16
|
-
ORIGINAL_JOBS=$(sed -n '/^jobs:/,/^[^ ]/p' "$ORIGINAL_FILE" | grep "^ [a-zA-Z]" | sed 's/://' | tr '\n' ' ')
|
|
17
|
-
GENERATED_JOBS=$(sed -n '/^jobs:/,/^[^ ]/p' "$GENERATED_FILE" | grep "^ [a-zA-Z]" | sed 's/://' | tr '\n' ' ')
|
|
18
|
-
|
|
19
|
-
echo "š Original jobs: $ORIGINAL_JOBS"
|
|
20
|
-
echo "š Generated jobs: $GENERATED_JOBS"
|
|
21
|
-
|
|
22
|
-
# Compare the job orders
|
|
23
|
-
if [ "$ORIGINAL_JOBS" = "$GENERATED_JOBS" ]; then
|
|
24
|
-
echo "ā
Job order preserved correctly!"
|
|
25
|
-
exit 0
|
|
26
|
-
else
|
|
27
|
-
echo "ā Job order differs!"
|
|
28
|
-
echo "š§ Pipecraft jobs may have moved to incorrect positions"
|
|
29
|
-
exit 1
|
|
30
|
-
fi
|