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,510 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
+ import { writeFileSync, existsSync, rmSync, readFileSync, mkdirSync } from 'fs'
3
+ import { join } from 'path'
4
+ import { TEST_DIR, FIXTURES_DIR } from '../setup'
5
+ import { parse } from 'yaml'
6
+
7
+ // Import the internal function directly for testing
8
+ import { createPathBasedPipeline } from '../../src/templates/workflows/pipeline-path-based.yml.tpl'
9
+
10
+ describe('Path-Based Template Generation Tests', () => {
11
+ const testConfigPath = join(TEST_DIR, 'custom-pipecraftrc.json')
12
+ const testPipelinePath = join(TEST_DIR, 'custom-pipeline.yml')
13
+ const outputPath = join(TEST_DIR, '.github/workflows/pipeline.yml')
14
+
15
+ beforeEach(() => {
16
+ // Clean up test files
17
+ const filesToClean = [testConfigPath, testPipelinePath, outputPath, '.pipecraft-cache.json']
18
+ filesToClean.forEach(file => {
19
+ const fullPath = join(TEST_DIR, file)
20
+ if (existsSync(fullPath)) {
21
+ rmSync(fullPath, { recursive: true, force: true })
22
+ }
23
+ })
24
+
25
+ // Create .github/workflows directory
26
+ mkdirSync(join(TEST_DIR, '.github/workflows'), { recursive: true })
27
+ })
28
+
29
+ afterEach(() => {
30
+ // Clean up after each test
31
+ const filesToClean = [testConfigPath, testPipelinePath, outputPath, '.pipecraft-cache.json']
32
+ filesToClean.forEach(file => {
33
+ const fullPath = join(TEST_DIR, file)
34
+ if (existsSync(fullPath)) {
35
+ rmSync(fullPath, { recursive: true, force: true })
36
+ }
37
+ })
38
+ })
39
+
40
+ describe('Custom Config and Pipeline Files', () => {
41
+ it('should generate pipeline with custom config file containing unexpected variables', async () => {
42
+ // Create custom config with unexpected variables (alpha, beta, gamma, delta, epsilon)
43
+ const customConfig = {
44
+ ciProvider: 'github',
45
+ mergeStrategy: 'fast-forward',
46
+ requireConventionalCommits: true,
47
+ initialBranch: 'alpha',
48
+ finalBranch: 'epsilon',
49
+ branchFlow: ['alpha', 'beta', 'gamma', 'delta', 'epsilon'],
50
+ semver: {
51
+ bumpRules: {
52
+ feat: 'minor',
53
+ fix: 'patch',
54
+ breaking: 'major'
55
+ }
56
+ },
57
+ actions: {
58
+ onDevelopMerge: ['runTests', 'fastForwardToStaging'],
59
+ onStagingMerge: ['runTests', 'calculateVersion', 'createOrFastForwardToMain']
60
+ },
61
+ domains: {
62
+ 'fake-domain1': {
63
+ paths: ['apps/fake-domain1/**'],
64
+ description: 'fake-domain1 application changes'
65
+ },
66
+ 'fake-domain2': {
67
+ paths: ['apps/fake-domain2/**'],
68
+ description: 'fake-domain2 application changes'
69
+ },
70
+ 'fake-domain3': {
71
+ paths: ['libs/fake-domain3/**'],
72
+ description: 'fake-domain3 application changes'
73
+ },
74
+ 'fake-domain4': {
75
+ paths: ['.github/workflows/fake-domain4/**'],
76
+ description: 'fake-domain4 application changes'
77
+ }
78
+ }
79
+ }
80
+
81
+ writeFileSync(testConfigPath, JSON.stringify(customConfig, null, 2))
82
+
83
+ // Create context with custom config
84
+ const ctx = {
85
+ ...customConfig,
86
+ cwd: TEST_DIR,
87
+ force: true
88
+ }
89
+
90
+ // Generate pipeline
91
+ const result = createPathBasedPipeline(ctx)
92
+
93
+ expect(result.yamlContent).toBeDefined()
94
+ expect(result.mergeStatus).toBe('overwritten')
95
+
96
+ // Parse generated YAML to verify structure
97
+ const parsedYaml = parse(result.yamlContent)
98
+ expect(parsedYaml).toBeDefined()
99
+ expect(parsedYaml.name).toBe('Pipeline')
100
+ expect(parsedYaml.on).toBeDefined()
101
+ expect(parsedYaml.jobs).toBeDefined()
102
+
103
+ // Verify workflow inputs are set correctly
104
+ expect(parsedYaml.on.workflow_call?.inputs?.version).toBeDefined()
105
+ expect(parsedYaml.on.workflow_call?.inputs?.baseRef).toBeDefined()
106
+
107
+ // Verify branch configuration uses custom branch flow
108
+ expect(parsedYaml.on.pull_request?.branches).toContain('alpha')
109
+ expect(parsedYaml.on.pull_request?.branches).toContain('beta')
110
+ expect(parsedYaml.on.pull_request?.branches).toContain('gamma')
111
+ expect(parsedYaml.on.pull_request?.branches).toContain('delta')
112
+ expect(parsedYaml.on.pull_request?.branches).toContain('epsilon')
113
+
114
+ // Verify core jobs exist
115
+ expect(parsedYaml.jobs.changes).toBeDefined()
116
+ expect(parsedYaml.jobs.version).toBeDefined()
117
+ expect(parsedYaml.jobs.tag).toBeDefined()
118
+ expect(parsedYaml.jobs.createpr).toBeDefined()
119
+ expect(parsedYaml.jobs.branch).toBeDefined()
120
+
121
+ // Verify version job uses custom initial branch
122
+ expect(parsedYaml.jobs.version.if).toContain('alpha')
123
+ expect(parsedYaml.jobs.tag.if).toContain('alpha')
124
+
125
+ // Verify createpr job excludes custom final branch
126
+ expect(parsedYaml.jobs.createpr.if).toContain('epsilon')
127
+ })
128
+
129
+ it('should merge with existing pipeline file containing user customizations', async () => {
130
+ // Create custom config
131
+ const customConfig = {
132
+ ciProvider: 'github',
133
+ mergeStrategy: 'fast-forward',
134
+ requireConventionalCommits: true,
135
+ initialBranch: 'alpha',
136
+ finalBranch: 'epsilon',
137
+ branchFlow: ['alpha', 'beta', 'gamma', 'delta', 'epsilon'],
138
+ semver: {
139
+ bumpRules: {
140
+ feat: 'minor',
141
+ fix: 'patch',
142
+ breaking: 'major'
143
+ }
144
+ },
145
+ actions: {
146
+ onDevelopMerge: ['runTests', 'fastForwardToStaging'],
147
+ onStagingMerge: ['runTests', 'calculateVersion', 'createOrFastForwardToMain']
148
+ },
149
+ domains: {
150
+ 'fake-domain1': {
151
+ paths: ['apps/fake-domain1/**'],
152
+ description: 'fake-domain1 application changes'
153
+ }
154
+ }
155
+ }
156
+
157
+ // Create existing pipeline with user customizations (similar to pipeline-user-modified.yml)
158
+ const existingPipeline = {
159
+ name: "USER NAME",
160
+ on: {
161
+ pull_request: {
162
+ paths: ['**/*.yml', '**/*.yaml', '**/*.json', '**/*.ts', '**/*.js', '**/*.tsx'],
163
+ branches: ['develop', 'feature']
164
+ },
165
+ workflow_call: {
166
+ inputs: {
167
+ fakevar1: {
168
+ description: 'The fake version to deploy',
169
+ required: false,
170
+ type: 'string'
171
+ },
172
+ version: {
173
+ description: 'The version to deploy',
174
+ required: false,
175
+ type: 'string'
176
+ },
177
+ fakevar2: {
178
+ description: 'The fake version to deploy',
179
+ required: false,
180
+ type: 'string'
181
+ }
182
+ },
183
+ outputs: {
184
+ fakevar3: {
185
+ value: 'fakevalue3'
186
+ },
187
+ fakevar4: {
188
+ value: 'fakevalue4'
189
+ }
190
+ }
191
+ }
192
+ },
193
+ jobs: {
194
+ 'fake-job-1': {
195
+ runs_on: 'ubuntu-latest',
196
+ steps: [
197
+ {
198
+ name: 'Fake Job',
199
+ run: 'echo "Running fake job"'
200
+ }
201
+ ]
202
+ },
203
+ changes: {
204
+ runs_on: 'ubuntu-latest',
205
+ steps: [
206
+ {
207
+ uses: './.github/actions/detect-changes',
208
+ with: {
209
+ baseRef: '${{ inputs.baseRef || \'main\' }}'
210
+ }
211
+ }
212
+ ]
213
+ },
214
+ 'fake-job-2': {
215
+ runs_on: 'ubuntu-latest',
216
+ steps: [
217
+ {
218
+ name: 'Fake Job',
219
+ run: 'echo "Running fake job"'
220
+ }
221
+ ]
222
+ }
223
+ }
224
+ }
225
+
226
+ // Create context with existing pipeline
227
+ const ctx = {
228
+ ...customConfig,
229
+ existingPipeline,
230
+ cwd: TEST_DIR,
231
+ force: true
232
+ }
233
+
234
+ // Generate pipeline
235
+ const result = createPathBasedPipeline(ctx)
236
+
237
+ expect(result.yamlContent).toBeDefined()
238
+ expect(result.mergeStatus).toBe('merged')
239
+
240
+ // Parse generated YAML to verify merging
241
+ const parsedYaml = parse(result.yamlContent)
242
+ expect(parsedYaml).toBeDefined()
243
+
244
+ // Verify user customizations are preserved
245
+ expect(parsedYaml.name).toBe('USER NAME')
246
+ expect(parsedYaml.on.pull_request.paths).toContain('**/*.yml')
247
+ expect(parsedYaml.on.pull_request.branches).toContain('develop')
248
+ expect(parsedYaml.on.pull_request.branches).toContain('feature')
249
+
250
+ // Verify user inputs are preserved and required inputs are added
251
+ expect(parsedYaml.on.workflow_call.inputs.fakevar1).toBeDefined()
252
+ expect(parsedYaml.on.workflow_call.inputs.version).toBeDefined()
253
+ expect(parsedYaml.on.workflow_call.inputs.fakevar2).toBeDefined()
254
+ expect(parsedYaml.on.workflow_call.inputs.baseRef).toBeDefined()
255
+
256
+ // Verify user outputs are preserved
257
+ expect(parsedYaml.on.workflow_call.outputs.fakevar3).toBeDefined()
258
+ expect(parsedYaml.on.workflow_call.outputs.fakevar4).toBeDefined()
259
+
260
+ // Verify user jobs are preserved
261
+ expect(parsedYaml.jobs['fake-job-1']).toBeDefined()
262
+ expect(parsedYaml.jobs['fake-job-2']).toBeDefined()
263
+
264
+ // Verify core Pipecraft jobs are updated/overwritten
265
+ expect(parsedYaml.jobs.changes).toBeDefined()
266
+ expect(parsedYaml.jobs.version).toBeDefined()
267
+ expect(parsedYaml.jobs.tag).toBeDefined()
268
+ expect(parsedYaml.jobs.createpr).toBeDefined()
269
+ expect(parsedYaml.jobs.branch).toBeDefined()
270
+
271
+ // Verify branch configuration is merged (user branches + template branches)
272
+ expect(parsedYaml.on.pull_request.branches).toContain('develop')
273
+ expect(parsedYaml.on.pull_request.branches).toContain('feature')
274
+ expect(parsedYaml.on.pull_request.branches).toContain('alpha')
275
+ expect(parsedYaml.on.pull_request.branches).toContain('beta')
276
+ expect(parsedYaml.on.pull_request.branches).toContain('gamma')
277
+ expect(parsedYaml.on.pull_request.branches).toContain('delta')
278
+ expect(parsedYaml.on.pull_request.branches).toContain('epsilon')
279
+ })
280
+
281
+ it('should handle missing existing pipeline gracefully', async () => {
282
+ const customConfig = {
283
+ ciProvider: 'github',
284
+ mergeStrategy: 'fast-forward',
285
+ requireConventionalCommits: true,
286
+ initialBranch: 'alpha',
287
+ finalBranch: 'epsilon',
288
+ branchFlow: ['alpha', 'beta', 'gamma', 'delta', 'epsilon'],
289
+ semver: {
290
+ bumpRules: {
291
+ feat: 'minor',
292
+ fix: 'patch',
293
+ breaking: 'major'
294
+ }
295
+ },
296
+ actions: {
297
+ onDevelopMerge: ['runTests', 'fastForwardToStaging'],
298
+ onStagingMerge: ['runTests', 'calculateVersion', 'createOrFastForwardToMain']
299
+ },
300
+ domains: {
301
+ 'fake-domain1': {
302
+ paths: ['apps/fake-domain1/**'],
303
+ description: 'fake-domain1 application changes'
304
+ }
305
+ }
306
+ }
307
+
308
+ // Create context without existing pipeline
309
+ const ctx = {
310
+ ...customConfig,
311
+ cwd: TEST_DIR,
312
+ force: true
313
+ }
314
+
315
+ // Generate pipeline
316
+ const result = createPathBasedPipeline(ctx)
317
+
318
+ expect(result.yamlContent).toBeDefined()
319
+ expect(result.mergeStatus).toBe('overwritten')
320
+
321
+ // Parse generated YAML to verify structure
322
+ const parsedYaml = parse(result.yamlContent)
323
+ expect(parsedYaml).toBeDefined()
324
+ expect(parsedYaml.name).toBe('Pipeline')
325
+ expect(parsedYaml.jobs).toBeDefined()
326
+ })
327
+ })
328
+
329
+ describe('Path Operations Validation', () => {
330
+ it('should correctly apply set operations for workflow inputs', async () => {
331
+ const ctx = {
332
+ ciProvider: 'github',
333
+ mergeStrategy: 'fast-forward',
334
+ requireConventionalCommits: true,
335
+ initialBranch: 'alpha',
336
+ finalBranch: 'epsilon',
337
+ branchFlow: ['alpha', 'beta', 'gamma', 'delta', 'epsilon'],
338
+ semver: { bumpRules: { feat: 'minor', fix: 'patch', breaking: 'major' } },
339
+ actions: { onDevelopMerge: ['runTests'], onStagingMerge: ['runTests'] },
340
+ domains: { 'fake-domain1': { paths: ['apps/fake-domain1/**'], description: 'fake-domain1' } },
341
+ cwd: TEST_DIR,
342
+ force: true
343
+ }
344
+
345
+ const result = createPathBasedPipeline(ctx)
346
+ const parsedYaml = parse(result.yamlContent)
347
+
348
+ // Verify workflow_call inputs are set
349
+ expect(parsedYaml.on.workflow_call?.inputs?.version).toEqual({
350
+ description: 'The version to deploy',
351
+ required: false,
352
+ type: 'string'
353
+ })
354
+ expect(parsedYaml.on.workflow_call?.inputs?.baseRef).toEqual({
355
+ description: 'The base reference for comparison',
356
+ required: false,
357
+ type: 'string'
358
+ })
359
+ })
360
+
361
+ it('should correctly apply merge operations for branch configuration', async () => {
362
+ const existingPipeline = {
363
+ name: "Test Pipeline",
364
+ on: {
365
+ pull_request: {
366
+ branches: ['develop', 'feature', 'custom-branch']
367
+ }
368
+ },
369
+ jobs: {}
370
+ }
371
+
372
+ const ctx = {
373
+ ciProvider: 'github',
374
+ mergeStrategy: 'fast-forward',
375
+ requireConventionalCommits: true,
376
+ initialBranch: 'alpha',
377
+ finalBranch: 'epsilon',
378
+ branchFlow: ['alpha', 'beta', 'gamma', 'delta', 'epsilon'],
379
+ semver: { bumpRules: { feat: 'minor', fix: 'patch', breaking: 'major' } },
380
+ actions: { onDevelopMerge: ['runTests'], onStagingMerge: ['runTests'] },
381
+ domains: { 'fake-domain1': { paths: ['apps/fake-domain1/**'], description: 'fake-domain1' } },
382
+ existingPipeline,
383
+ cwd: TEST_DIR,
384
+ force: true
385
+ }
386
+
387
+ const result = createPathBasedPipeline(ctx)
388
+ const parsedYaml = parse(result.yamlContent)
389
+
390
+ // Verify branches are merged (user + template)
391
+ const branches = parsedYaml.on.pull_request.branches
392
+ expect(branches).toContain('develop')
393
+ expect(branches).toContain('feature')
394
+ expect(branches).toContain('custom-branch')
395
+ expect(branches).toContain('alpha')
396
+ expect(branches).toContain('beta')
397
+ expect(branches).toContain('gamma')
398
+ expect(branches).toContain('delta')
399
+ expect(branches).toContain('epsilon')
400
+ })
401
+
402
+ it('should correctly apply overwrite operations for core Pipecraft jobs', async () => {
403
+ const existingPipeline = {
404
+ name: "Test Pipeline",
405
+ on: { pull_request: { branches: ['develop'] } },
406
+ jobs: {
407
+ changes: {
408
+ runs_on: 'ubuntu-latest',
409
+ steps: [
410
+ {
411
+ name: 'Old Changes Job',
412
+ run: 'echo "This should be overwritten"'
413
+ }
414
+ ]
415
+ },
416
+ version: {
417
+ runs_on: 'ubuntu-latest',
418
+ steps: [
419
+ {
420
+ name: 'Old Version Job',
421
+ run: 'echo "This should be overwritten"'
422
+ }
423
+ ]
424
+ }
425
+ }
426
+ }
427
+
428
+ const ctx = {
429
+ ciProvider: 'github',
430
+ mergeStrategy: 'fast-forward',
431
+ requireConventionalCommits: true,
432
+ initialBranch: 'alpha',
433
+ finalBranch: 'epsilon',
434
+ branchFlow: ['alpha', 'beta', 'gamma', 'delta', 'epsilon'],
435
+ semver: { bumpRules: { feat: 'minor', fix: 'patch', breaking: 'major' } },
436
+ actions: { onDevelopMerge: ['runTests'], onStagingMerge: ['runTests'] },
437
+ domains: { 'fake-domain1': { paths: ['apps/fake-domain1/**'], description: 'fake-domain1' } },
438
+ existingPipeline,
439
+ cwd: TEST_DIR,
440
+ force: true
441
+ }
442
+
443
+ const result = createPathBasedPipeline(ctx)
444
+ const parsedYaml = parse(result.yamlContent)
445
+
446
+ // Verify core jobs are overwritten with template versions
447
+ expect(parsedYaml.jobs.changes.steps[0].uses).toBe('./.github/actions/detect-changes')
448
+ expect(parsedYaml.jobs.version.steps[0].uses).toBe('./.github/actions/calculate-version')
449
+ expect(parsedYaml.jobs.changes.steps[0].name).toBeUndefined() // No custom name
450
+ expect(parsedYaml.jobs.version.steps[0].name).toBeUndefined() // No custom name
451
+ })
452
+
453
+ it('should correctly apply preserve operations for user-managed sections', async () => {
454
+ const existingPipeline = {
455
+ name: "Test Pipeline",
456
+ on: { pull_request: { branches: ['develop'] } },
457
+ jobs: {
458
+ 'testing-section': {
459
+ 'test-api': {
460
+ needs: 'changes',
461
+ runs_on: 'ubuntu-latest',
462
+ steps: [
463
+ {
464
+ name: 'Test API',
465
+ run: 'echo "Running API tests"'
466
+ }
467
+ ]
468
+ }
469
+ },
470
+ 'deployment-section': {
471
+ 'deploy-api': {
472
+ needs: 'version',
473
+ runs_on: 'ubuntu-latest',
474
+ steps: [
475
+ {
476
+ name: 'Deploy API',
477
+ run: 'echo "Deploying API"'
478
+ }
479
+ ]
480
+ }
481
+ }
482
+ }
483
+ }
484
+
485
+ const ctx = {
486
+ ciProvider: 'github',
487
+ mergeStrategy: 'fast-forward',
488
+ requireConventionalCommits: true,
489
+ initialBranch: 'alpha',
490
+ finalBranch: 'epsilon',
491
+ branchFlow: ['alpha', 'beta', 'gamma', 'delta', 'epsilon'],
492
+ semver: { bumpRules: { feat: 'minor', fix: 'patch', breaking: 'major' } },
493
+ actions: { onDevelopMerge: ['runTests'], onStagingMerge: ['runTests'] },
494
+ domains: { 'fake-domain1': { paths: ['apps/fake-domain1/**'], description: 'fake-domain1' } },
495
+ existingPipeline,
496
+ cwd: TEST_DIR,
497
+ force: true
498
+ }
499
+
500
+ const result = createPathBasedPipeline(ctx)
501
+ const parsedYaml = parse(result.yamlContent)
502
+
503
+ // Verify user-managed sections are preserved
504
+ expect(parsedYaml.jobs['testing-section']['test-api']).toBeDefined()
505
+ expect(parsedYaml.jobs['testing-section']['test-api'].steps[0].name).toBe('Test API')
506
+ expect(parsedYaml.jobs['deployment-section']['deploy-api']).toBeDefined()
507
+ expect(parsedYaml.jobs['deployment-section']['deploy-api'].steps[0].name).toBe('Deploy API')
508
+ })
509
+ })
510
+ })