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,511 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'vitest'
|
|
2
|
+
import { readFileSync, existsSync } from 'fs'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
import { parseDocument } from 'yaml'
|
|
5
|
+
import { createPathBasedPipeline } from '../../src/templates/workflows/pipeline-path-based.yml.tpl'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extract job names in order from a YAML file
|
|
9
|
+
*/
|
|
10
|
+
function getJobOrder(filePath: string): string[] {
|
|
11
|
+
if (!existsSync(filePath)) {
|
|
12
|
+
console.error(`File not found: ${filePath}`)
|
|
13
|
+
return []
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const content = readFileSync(filePath, 'utf8')
|
|
18
|
+
const doc = parseDocument(content)
|
|
19
|
+
|
|
20
|
+
if (!doc.contents || !(doc.contents as any).get('jobs')) {
|
|
21
|
+
return []
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const jobsNode = (doc.contents as any).get('jobs')
|
|
25
|
+
if (!jobsNode.items) {
|
|
26
|
+
return []
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return jobsNode.items.map(item => item.key.value)
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error(`Error parsing ${filePath}:`, error)
|
|
32
|
+
return []
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if workflow_dispatch inputs exist in YAML content
|
|
38
|
+
*/
|
|
39
|
+
function hasWorkflowDispatchInputs(content: string): boolean {
|
|
40
|
+
return content.includes('workflow_dispatch:') &&
|
|
41
|
+
content.includes('inputs:') &&
|
|
42
|
+
content.includes('version:') &&
|
|
43
|
+
content.includes('baseRef:')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check if workflow_call inputs exist in YAML content
|
|
48
|
+
*/
|
|
49
|
+
function hasWorkflowCallInputs(content: string): boolean {
|
|
50
|
+
return content.includes('workflow_call:') &&
|
|
51
|
+
content.includes('inputs:') &&
|
|
52
|
+
content.includes('version:') &&
|
|
53
|
+
content.includes('baseRef:')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('Pipeline Path-Based Template', () => {
|
|
57
|
+
const testFixtures = {
|
|
58
|
+
original: join(process.cwd(), 'tests/fixtures/pipeline-user-modified.yml'),
|
|
59
|
+
config: join(process.cwd(), 'tests/fixtures/test-config.json')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe('Job Order Preservation', () => {
|
|
63
|
+
it('should preserve job order when Pipecraft jobs exist', () => {
|
|
64
|
+
const originalContent = readFileSync(testFixtures.original, 'utf8')
|
|
65
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
66
|
+
|
|
67
|
+
const ctx = {
|
|
68
|
+
existingPipelineContent: originalContent,
|
|
69
|
+
...config
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const result = createPathBasedPipeline(ctx)
|
|
73
|
+
const generatedDoc = parseDocument(result.yamlContent)
|
|
74
|
+
const generatedJobsNode = (generatedDoc.contents as any).get('jobs')
|
|
75
|
+
const generatedOrder = generatedJobsNode.items.map(item => item.key.value)
|
|
76
|
+
|
|
77
|
+
const originalOrder = getJobOrder(testFixtures.original)
|
|
78
|
+
|
|
79
|
+
// Should have same number of jobs
|
|
80
|
+
expect(generatedOrder.length).toBe(originalOrder.length)
|
|
81
|
+
|
|
82
|
+
// Should have same job order
|
|
83
|
+
expect(generatedOrder).toEqual(originalOrder)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should maintain Pipecraft jobs in their original positions', () => {
|
|
87
|
+
const originalContent = readFileSync(testFixtures.original, 'utf8')
|
|
88
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
89
|
+
|
|
90
|
+
const ctx = {
|
|
91
|
+
existingPipelineContent: originalContent,
|
|
92
|
+
...config
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const result = createPathBasedPipeline(ctx)
|
|
96
|
+
const generatedDoc = parseDocument(result.yamlContent)
|
|
97
|
+
const generatedJobsNode = (generatedDoc.contents as any).get('jobs')
|
|
98
|
+
const generatedOrder = generatedJobsNode.items.map(item => item.key.value)
|
|
99
|
+
|
|
100
|
+
const originalOrder = getJobOrder(testFixtures.original)
|
|
101
|
+
|
|
102
|
+
// Find positions of Pipecraft jobs
|
|
103
|
+
const pipecraftJobs = ['changes', 'version', 'tag', 'createpr', 'branch']
|
|
104
|
+
|
|
105
|
+
pipecraftJobs.forEach(job => {
|
|
106
|
+
const originalPos = originalOrder.indexOf(job)
|
|
107
|
+
const generatedPos = generatedOrder.indexOf(job)
|
|
108
|
+
|
|
109
|
+
if (originalPos !== -1) {
|
|
110
|
+
expect(generatedPos).toBe(originalPos)
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('should preserve user jobs in their original positions', () => {
|
|
116
|
+
const originalContent = readFileSync(testFixtures.original, 'utf8')
|
|
117
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
118
|
+
|
|
119
|
+
const ctx = {
|
|
120
|
+
existingPipelineContent: originalContent,
|
|
121
|
+
...config
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const result = createPathBasedPipeline(ctx)
|
|
125
|
+
const generatedDoc = parseDocument(result.yamlContent)
|
|
126
|
+
const generatedJobsNode = (generatedDoc.contents as any).get('jobs')
|
|
127
|
+
const generatedOrder = generatedJobsNode.items.map(item => item.key.value)
|
|
128
|
+
|
|
129
|
+
const originalOrder = getJobOrder(testFixtures.original)
|
|
130
|
+
|
|
131
|
+
// Find user jobs (non-Pipecraft jobs)
|
|
132
|
+
const pipecraftJobs = new Set(['changes', 'version', 'tag', 'createpr', 'branch'])
|
|
133
|
+
const userJobs = originalOrder.filter(job => !pipecraftJobs.has(job))
|
|
134
|
+
|
|
135
|
+
userJobs.forEach(userJob => {
|
|
136
|
+
const originalPos = originalOrder.indexOf(userJob)
|
|
137
|
+
const generatedPos = generatedOrder.indexOf(userJob)
|
|
138
|
+
expect(generatedPos).toBe(originalPos)
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
describe('Workflow Inputs', () => {
|
|
144
|
+
it('should add workflow_dispatch inputs', () => {
|
|
145
|
+
const originalContent = readFileSync(testFixtures.original, 'utf8')
|
|
146
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
147
|
+
|
|
148
|
+
const ctx = {
|
|
149
|
+
existingPipelineContent: originalContent,
|
|
150
|
+
...config
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result = createPathBasedPipeline(ctx)
|
|
154
|
+
|
|
155
|
+
expect(hasWorkflowDispatchInputs(result.yamlContent)).toBe(true)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('should add workflow_call inputs', () => {
|
|
159
|
+
const originalContent = readFileSync(testFixtures.original, 'utf8')
|
|
160
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
161
|
+
|
|
162
|
+
const ctx = {
|
|
163
|
+
existingPipelineContent: originalContent,
|
|
164
|
+
...config
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const result = createPathBasedPipeline(ctx)
|
|
168
|
+
|
|
169
|
+
expect(hasWorkflowCallInputs(result.yamlContent)).toBe(true)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('should add workflow inputs without affecting job order', () => {
|
|
173
|
+
const originalContent = readFileSync(testFixtures.original, 'utf8')
|
|
174
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
175
|
+
|
|
176
|
+
const ctx = {
|
|
177
|
+
existingPipelineContent: originalContent,
|
|
178
|
+
...config
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const result = createPathBasedPipeline(ctx)
|
|
182
|
+
|
|
183
|
+
// Should have workflow inputs
|
|
184
|
+
expect(hasWorkflowDispatchInputs(result.yamlContent)).toBe(true)
|
|
185
|
+
expect(hasWorkflowCallInputs(result.yamlContent)).toBe(true)
|
|
186
|
+
|
|
187
|
+
// But job order should still be preserved
|
|
188
|
+
const generatedDoc = parseDocument(result.yamlContent)
|
|
189
|
+
const generatedJobsNode = (generatedDoc.contents as any).get('jobs')
|
|
190
|
+
const generatedOrder = generatedJobsNode.items.map(item => item.key.value)
|
|
191
|
+
|
|
192
|
+
const originalOrder = getJobOrder(testFixtures.original)
|
|
193
|
+
expect(generatedOrder).toEqual(originalOrder)
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
describe('Branch Configuration', () => {
|
|
198
|
+
it('should merge template branches with user branches', () => {
|
|
199
|
+
const originalContent = readFileSync(testFixtures.original, 'utf8')
|
|
200
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
201
|
+
|
|
202
|
+
const ctx = {
|
|
203
|
+
existingPipelineContent: originalContent,
|
|
204
|
+
...config
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const result = createPathBasedPipeline(ctx)
|
|
208
|
+
const generatedDoc = parseDocument(result.yamlContent)
|
|
209
|
+
const pullRequestNode = (generatedDoc.contents as any).get('on').get('pull_request')
|
|
210
|
+
const branches = pullRequestNode.get('branches')
|
|
211
|
+
|
|
212
|
+
// Should include template branches
|
|
213
|
+
expect(branches.items.map(item => item.value)).toContain('alpha')
|
|
214
|
+
expect(branches.items.map(item => item.value)).toContain('beta')
|
|
215
|
+
expect(branches.items.map(item => item.value)).toContain('gamma')
|
|
216
|
+
expect(branches.items.map(item => item.value)).toContain('delta')
|
|
217
|
+
expect(branches.items.map(item => item.value)).toContain('epsilon')
|
|
218
|
+
|
|
219
|
+
// Should preserve user branches
|
|
220
|
+
expect(branches.items.map(item => item.value)).toContain('develop')
|
|
221
|
+
expect(branches.items.map(item => item.value)).toContain('feature')
|
|
222
|
+
})
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
describe('Pipecraft Job Content', () => {
|
|
226
|
+
it('should update Pipecraft job content with latest template', () => {
|
|
227
|
+
const originalContent = readFileSync(testFixtures.original, 'utf8')
|
|
228
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
229
|
+
|
|
230
|
+
const ctx = {
|
|
231
|
+
existingPipelineContent: originalContent,
|
|
232
|
+
...config
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const result = createPathBasedPipeline(ctx)
|
|
236
|
+
|
|
237
|
+
// Check that Pipecraft jobs have the expected structure
|
|
238
|
+
expect(result.yamlContent).toContain('runs-on: ubuntu-latest')
|
|
239
|
+
expect(result.yamlContent).toContain('./.github/actions/detect-changes')
|
|
240
|
+
expect(result.yamlContent).toContain('./.github/actions/calculate-version')
|
|
241
|
+
expect(result.yamlContent).toContain('./.github/actions/create-tag')
|
|
242
|
+
expect(result.yamlContent).toContain('./.github/actions/create-pr')
|
|
243
|
+
expect(result.yamlContent).toContain('./.github/actions/manage-branch')
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it('should use correct branch flow in job conditions', () => {
|
|
247
|
+
const originalContent = readFileSync(testFixtures.original, 'utf8')
|
|
248
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
249
|
+
|
|
250
|
+
const ctx = {
|
|
251
|
+
existingPipelineContent: originalContent,
|
|
252
|
+
...config
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const result = createPathBasedPipeline(ctx)
|
|
256
|
+
|
|
257
|
+
// Should use the correct initial branch from config
|
|
258
|
+
expect(result.yamlContent).toContain("github.ref_name == 'alpha'")
|
|
259
|
+
expect(result.yamlContent).toContain("github.ref_name != 'epsilon'")
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
describe('Comment Preservation', () => {
|
|
264
|
+
it('should preserve user comments', () => {
|
|
265
|
+
const originalContent = readFileSync(testFixtures.original, 'utf8')
|
|
266
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
267
|
+
|
|
268
|
+
const ctx = {
|
|
269
|
+
existingPipelineContent: originalContent,
|
|
270
|
+
...config
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const result = createPathBasedPipeline(ctx)
|
|
274
|
+
|
|
275
|
+
// Should preserve user comments
|
|
276
|
+
expect(result.yamlContent).toContain('# USER COMMENT 1')
|
|
277
|
+
expect(result.yamlContent).toContain('# USER COMMENT 9')
|
|
278
|
+
expect(result.yamlContent).toContain('# USER COMMENT 10')
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
describe('New Pipeline Generation', () => {
|
|
283
|
+
it('should create new pipeline when no existing content', () => {
|
|
284
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
285
|
+
|
|
286
|
+
const ctx = {
|
|
287
|
+
...config
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const result = createPathBasedPipeline(ctx)
|
|
291
|
+
|
|
292
|
+
expect(result.mergeStatus).toBe('overwritten')
|
|
293
|
+
expect(result.yamlContent).toContain('name: "Pipeline"')
|
|
294
|
+
expect(result.yamlContent).toContain('on:')
|
|
295
|
+
expect(result.yamlContent).toContain('jobs:')
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
it('should build jobs in correct order for new pipeline', () => {
|
|
299
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
300
|
+
|
|
301
|
+
const ctx = {
|
|
302
|
+
...config
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const result = createPathBasedPipeline(ctx)
|
|
306
|
+
const generatedDoc = parseDocument(result.yamlContent)
|
|
307
|
+
const generatedJobsNode = (generatedDoc.contents as any).get('jobs')
|
|
308
|
+
const generatedOrder = generatedJobsNode.items.map(item => item.key.value)
|
|
309
|
+
|
|
310
|
+
// Should have Pipecraft jobs in correct order
|
|
311
|
+
const expectedOrder = ['changes', 'version', 'tag', 'createpr', 'branch']
|
|
312
|
+
const pipecraftJobs = generatedOrder.filter(job =>
|
|
313
|
+
['changes', 'version', 'tag', 'createpr', 'branch'].includes(job)
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
expect(pipecraftJobs).toEqual(expectedOrder)
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
describe('Edge Cases', () => {
|
|
321
|
+
it('should handle pipeline with no existing Pipecraft jobs', () => {
|
|
322
|
+
// Create a pipeline with only user jobs
|
|
323
|
+
const userOnlyPipeline = `name: "User Pipeline"
|
|
324
|
+
on:
|
|
325
|
+
pull_request:
|
|
326
|
+
branches: [main]
|
|
327
|
+
jobs:
|
|
328
|
+
user-job-1:
|
|
329
|
+
runs-on: ubuntu-latest
|
|
330
|
+
steps:
|
|
331
|
+
- run: echo "User job"
|
|
332
|
+
user-job-2:
|
|
333
|
+
runs-on: ubuntu-latest
|
|
334
|
+
steps:
|
|
335
|
+
- run: echo "Another user job"`
|
|
336
|
+
|
|
337
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
338
|
+
|
|
339
|
+
const ctx = {
|
|
340
|
+
existingPipelineContent: userOnlyPipeline,
|
|
341
|
+
...config
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const result = createPathBasedPipeline(ctx)
|
|
345
|
+
const generatedDoc = parseDocument(result.yamlContent)
|
|
346
|
+
const generatedJobsNode = (generatedDoc.contents as any).get('jobs')
|
|
347
|
+
const generatedOrder = generatedJobsNode.items.map(item => item.key.value)
|
|
348
|
+
|
|
349
|
+
// Should preserve user jobs and add Pipecraft jobs
|
|
350
|
+
expect(generatedOrder).toContain('user-job-1')
|
|
351
|
+
expect(generatedOrder).toContain('user-job-2')
|
|
352
|
+
expect(generatedOrder).toContain('changes')
|
|
353
|
+
expect(generatedOrder).toContain('version')
|
|
354
|
+
expect(generatedOrder).toContain('tag')
|
|
355
|
+
expect(generatedOrder).toContain('createpr')
|
|
356
|
+
expect(generatedOrder).toContain('branch')
|
|
357
|
+
|
|
358
|
+
// When no existing Pipecraft jobs, Pipecraft jobs are added first, then user jobs
|
|
359
|
+
// This is the expected behavior based on the template logic
|
|
360
|
+
const userJobPositions = [
|
|
361
|
+
generatedOrder.indexOf('user-job-1'),
|
|
362
|
+
generatedOrder.indexOf('user-job-2')
|
|
363
|
+
]
|
|
364
|
+
const pipecraftJobPositions = [
|
|
365
|
+
generatedOrder.indexOf('changes'),
|
|
366
|
+
generatedOrder.indexOf('version')
|
|
367
|
+
]
|
|
368
|
+
|
|
369
|
+
// Pipecraft jobs should come first, then user jobs (when no existing Pipecraft jobs)
|
|
370
|
+
expect(Math.min(...pipecraftJobPositions)).toBeLessThan(Math.max(...userJobPositions))
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
it('should handle empty pipeline gracefully', () => {
|
|
374
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
375
|
+
|
|
376
|
+
const ctx = {
|
|
377
|
+
...config
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const result = createPathBasedPipeline(ctx)
|
|
381
|
+
|
|
382
|
+
expect(result.yamlContent).toBeDefined()
|
|
383
|
+
expect(result.yamlContent.length).toBeGreaterThan(0)
|
|
384
|
+
expect(result.mergeStatus).toBe('overwritten')
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
it('should handle malformed YAML gracefully', () => {
|
|
388
|
+
const malformedPipeline = `name: "Malformed"
|
|
389
|
+
on:
|
|
390
|
+
pull_request:
|
|
391
|
+
jobs:
|
|
392
|
+
invalid-job:
|
|
393
|
+
runs-on: ubuntu-latest
|
|
394
|
+
steps:
|
|
395
|
+
- name: Test
|
|
396
|
+
run: echo "test"
|
|
397
|
+
# Missing closing quote
|
|
398
|
+
another-job:
|
|
399
|
+
runs-on: ubuntu-latest
|
|
400
|
+
steps:
|
|
401
|
+
- name: Test
|
|
402
|
+
run: echo "test"`
|
|
403
|
+
|
|
404
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
405
|
+
|
|
406
|
+
const ctx = {
|
|
407
|
+
existingPipelineContent: malformedPipeline,
|
|
408
|
+
...config
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Should not throw an error
|
|
412
|
+
expect(() => createPathBasedPipeline(ctx)).not.toThrow()
|
|
413
|
+
})
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
describe('Configuration Handling', () => {
|
|
417
|
+
it('should use custom branch flow from config', () => {
|
|
418
|
+
const customConfig = {
|
|
419
|
+
branchFlow: ['custom1', 'custom2', 'custom3'],
|
|
420
|
+
initialBranch: 'custom1',
|
|
421
|
+
finalBranch: 'custom3'
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const ctx = {
|
|
425
|
+
...customConfig
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const result = createPathBasedPipeline(ctx)
|
|
429
|
+
|
|
430
|
+
// Should use custom branch names
|
|
431
|
+
expect(result.yamlContent).toContain("github.ref_name == 'custom1'")
|
|
432
|
+
expect(result.yamlContent).toContain("github.ref_name != 'custom3'")
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
it('should fall back to default branch flow when not provided', () => {
|
|
436
|
+
const ctx = {}
|
|
437
|
+
|
|
438
|
+
const result = createPathBasedPipeline(ctx)
|
|
439
|
+
|
|
440
|
+
// Should use default branch flow
|
|
441
|
+
expect(result.yamlContent).toContain("github.ref_name == 'develop'")
|
|
442
|
+
expect(result.yamlContent).toContain("github.ref_name != 'main'")
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
it('should handle missing config gracefully', () => {
|
|
446
|
+
const ctx = {
|
|
447
|
+
existingPipelineContent: readFileSync(testFixtures.original, 'utf8')
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const result = createPathBasedPipeline(ctx)
|
|
451
|
+
|
|
452
|
+
expect(result.yamlContent).toBeDefined()
|
|
453
|
+
expect(result.mergeStatus).toBe('merged')
|
|
454
|
+
})
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
describe('Job Content Validation', () => {
|
|
458
|
+
it('should have correct job dependencies', () => {
|
|
459
|
+
const originalContent = readFileSync(testFixtures.original, 'utf8')
|
|
460
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
461
|
+
|
|
462
|
+
const ctx = {
|
|
463
|
+
existingPipelineContent: originalContent,
|
|
464
|
+
...config
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const result = createPathBasedPipeline(ctx)
|
|
468
|
+
|
|
469
|
+
// Check job dependencies
|
|
470
|
+
expect(result.yamlContent).toContain('needs: changes') // version job
|
|
471
|
+
expect(result.yamlContent).toContain('needs: version') // tag job
|
|
472
|
+
expect(result.yamlContent).toContain('needs: [ changes, version ]') // createpr job
|
|
473
|
+
expect(result.yamlContent).toContain('needs: createpr') // branch job
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
it('should have correct job conditions', () => {
|
|
477
|
+
const originalContent = readFileSync(testFixtures.original, 'utf8')
|
|
478
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
479
|
+
|
|
480
|
+
const ctx = {
|
|
481
|
+
existingPipelineContent: originalContent,
|
|
482
|
+
...config
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const result = createPathBasedPipeline(ctx)
|
|
486
|
+
|
|
487
|
+
// Check job conditions
|
|
488
|
+
expect(result.yamlContent).toContain("if: github.ref_name == 'alpha'") // version and tag
|
|
489
|
+
expect(result.yamlContent).toContain("if: github.ref_name != 'epsilon'") // createpr
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
it('should use correct action paths', () => {
|
|
493
|
+
const originalContent = readFileSync(testFixtures.original, 'utf8')
|
|
494
|
+
const config = JSON.parse(readFileSync(testFixtures.config, 'utf8'))
|
|
495
|
+
|
|
496
|
+
const ctx = {
|
|
497
|
+
existingPipelineContent: originalContent,
|
|
498
|
+
...config
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const result = createPathBasedPipeline(ctx)
|
|
502
|
+
|
|
503
|
+
// Check action paths
|
|
504
|
+
expect(result.yamlContent).toContain('./.github/actions/detect-changes')
|
|
505
|
+
expect(result.yamlContent).toContain('./.github/actions/calculate-version')
|
|
506
|
+
expect(result.yamlContent).toContain('./.github/actions/create-tag')
|
|
507
|
+
expect(result.yamlContent).toContain('./.github/actions/create-pr')
|
|
508
|
+
expect(result.yamlContent).toContain('./.github/actions/manage-branch')
|
|
509
|
+
})
|
|
510
|
+
})
|
|
511
|
+
})
|