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,541 @@
1
+ /**
2
+ * CLI Unit Tests
3
+ *
4
+ * These tests directly test the CLI logic without spawning processes,
5
+ * avoiding the race conditions that plagued the previous integration tests.
6
+ *
7
+ * Strategy:
8
+ * - Mock external dependencies (fs, runModule, etc.)
9
+ * - Test command logic directly
10
+ * - Use isolated temp directories
11
+ * - Verify correct function calls and outputs
12
+ */
13
+
14
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
15
+ import { writeFileSync, existsSync, rmSync, mkdirSync } from 'fs'
16
+ import { join } from 'path'
17
+ import { tmpdir } from 'os'
18
+
19
+ // We'll test by mocking and importing the functions that would be called
20
+ // Note: The CLI uses commander which makes it hard to test directly
21
+ // So we'll test the underlying logic functions instead
22
+
23
+ import { loadConfig, validateConfig } from '../../src/utils/config'
24
+ import { IdempotencyManager } from '../../src/utils/idempotency'
25
+ import { VersionManager } from '../../src/utils/versioning'
26
+ import { PipecraftConfig } from '../../src/types'
27
+
28
+ describe('CLI Logic Tests', () => {
29
+ let testDir: string
30
+ let originalCwd: string
31
+
32
+ beforeEach(() => {
33
+ // Create unique temp directory for this test
34
+ testDir = join(tmpdir(), `pipecraft-cli-test-${Date.now()}-${Math.random().toString(36).slice(2)}`)
35
+ mkdirSync(testDir, { recursive: true })
36
+ originalCwd = process.cwd()
37
+ process.chdir(testDir)
38
+ })
39
+
40
+ afterEach(() => {
41
+ // Restore original directory and cleanup
42
+ try {
43
+ // Only chdir if the original directory still exists
44
+ if (existsSync(originalCwd)) {
45
+ process.chdir(originalCwd)
46
+ } else {
47
+ // Fallback to a safe directory if original doesn't exist
48
+ process.chdir(__dirname)
49
+ }
50
+ } catch (error) {
51
+ // If chdir fails, try to go to the test directory parent
52
+ process.chdir(__dirname)
53
+ }
54
+
55
+ if (existsSync(testDir)) {
56
+ rmSync(testDir, { recursive: true, force: true })
57
+ }
58
+ })
59
+
60
+ describe('Config Loading and Validation', () => {
61
+ it('should load valid config file', () => {
62
+ const config: PipecraftConfig = {
63
+ ciProvider: 'github',
64
+ mergeStrategy: 'fast-forward',
65
+ requireConventionalCommits: true,
66
+ initialBranch: 'develop',
67
+ finalBranch: 'main',
68
+ branchFlow: ['develop', 'staging', 'main'],
69
+ semver: {
70
+ bumpRules: {
71
+ feat: 'minor',
72
+ fix: 'patch',
73
+ breaking: 'major'
74
+ }
75
+ },
76
+ actions: {
77
+ onDevelopMerge: ['runTests', 'fastForwardToStaging'],
78
+ onStagingMerge: ['runTests', 'calculateVersion', 'createOrFastForwardToMain']
79
+ },
80
+ domains: {
81
+ api: {
82
+ paths: ['apps/api/**'],
83
+ description: 'API application'
84
+ }
85
+ }
86
+ }
87
+
88
+ const configPath = join(testDir, '.pipecraftrc.json')
89
+ writeFileSync(configPath, JSON.stringify(config, null, 2))
90
+
91
+ const loadedConfig = loadConfig(configPath)
92
+ expect(loadedConfig).toBeDefined()
93
+ expect(loadedConfig.ciProvider).toBe('github')
94
+ expect(loadedConfig.domains).toHaveProperty('api')
95
+ })
96
+
97
+ it('should throw error when config file not found', () => {
98
+ expect(() => loadConfig(join(testDir, 'nonexistent.json'))).toThrow()
99
+ })
100
+
101
+ it('should validate correct config', () => {
102
+ const config: PipecraftConfig = {
103
+ ciProvider: 'github',
104
+ mergeStrategy: 'fast-forward',
105
+ requireConventionalCommits: true,
106
+ initialBranch: 'develop',
107
+ finalBranch: 'main',
108
+ branchFlow: ['develop', 'main'],
109
+ semver: {
110
+ bumpRules: {
111
+ feat: 'minor',
112
+ fix: 'patch',
113
+ breaking: 'major'
114
+ }
115
+ },
116
+ actions: {
117
+ onDevelopMerge: ['runTests']
118
+ },
119
+ domains: {
120
+ api: {
121
+ paths: ['apps/api/**'],
122
+ description: 'API'
123
+ }
124
+ }
125
+ }
126
+
127
+ expect(() => validateConfig(config)).not.toThrow()
128
+ })
129
+
130
+ it('should fail validation for invalid config', () => {
131
+ const invalidConfig = {
132
+ ciProvider: 'invalid-provider', // Invalid
133
+ mergeStrategy: 'fast-forward',
134
+ requireConventionalCommits: true,
135
+ initialBranch: 'develop',
136
+ finalBranch: 'main'
137
+ } as any
138
+
139
+ expect(() => validateConfig(invalidConfig)).toThrow()
140
+ })
141
+
142
+ it('should fail validation for missing required fields', () => {
143
+ const incompleteConfig = {
144
+ ciProvider: 'github'
145
+ // Missing other required fields
146
+ } as any
147
+
148
+ expect(() => validateConfig(incompleteConfig)).toThrow()
149
+ })
150
+ })
151
+
152
+ describe('Generate Command Logic', () => {
153
+ let config: PipecraftConfig
154
+
155
+ beforeEach(() => {
156
+ config = {
157
+ ciProvider: 'github',
158
+ mergeStrategy: 'fast-forward',
159
+ requireConventionalCommits: true,
160
+ initialBranch: 'develop',
161
+ finalBranch: 'main',
162
+ branchFlow: ['develop', 'main'],
163
+ semver: {
164
+ bumpRules: {
165
+ feat: 'minor',
166
+ fix: 'patch',
167
+ breaking: 'major'
168
+ }
169
+ },
170
+ actions: {
171
+ onDevelopMerge: ['runTests']
172
+ },
173
+ domains: {
174
+ api: {
175
+ paths: ['apps/api/**'],
176
+ description: 'API'
177
+ }
178
+ },
179
+ rebuild: {
180
+ enabled: true,
181
+ skipIfUnchanged: true,
182
+ forceRegenerate: false,
183
+ watchMode: false,
184
+ hashAlgorithm: 'sha256',
185
+ cacheFile: join(testDir, '.pipecraft-cache.json'),
186
+ ignorePatterns: []
187
+ }
188
+ }
189
+
190
+ const configPath = join(testDir, '.pipecraftrc.json')
191
+ writeFileSync(configPath, JSON.stringify(config, null, 2))
192
+ })
193
+
194
+ it('should detect when regeneration is needed (no cache)', async () => {
195
+ const idempotencyManager = new IdempotencyManager(config)
196
+ const hasChanges = await idempotencyManager.hasChanges()
197
+
198
+ expect(hasChanges).toBe(true)
199
+ })
200
+
201
+ it('should detect no changes when cache exists and unchanged', async () => {
202
+ const idempotencyManager = new IdempotencyManager(config)
203
+
204
+ // Create initial cache
205
+ await idempotencyManager.updateCache()
206
+
207
+ // Check again - should be no changes
208
+ const hasChanges = await idempotencyManager.hasChanges()
209
+
210
+ expect(hasChanges).toBe(false)
211
+ })
212
+
213
+ it('should force regeneration when force flag is true', async () => {
214
+ const configWithForce = {
215
+ ...config,
216
+ rebuild: {
217
+ ...config.rebuild!,
218
+ forceRegenerate: true
219
+ }
220
+ }
221
+
222
+ const idempotencyManager = new IdempotencyManager(configWithForce)
223
+
224
+ // Even with cache, should return true due to force
225
+ await idempotencyManager.updateCache()
226
+ const hasChanges = await idempotencyManager.hasChanges()
227
+
228
+ expect(hasChanges).toBe(true)
229
+ })
230
+
231
+ it('should skip regeneration when rebuild is disabled', async () => {
232
+ const configWithoutRebuild = {
233
+ ...config,
234
+ rebuild: {
235
+ ...config.rebuild!,
236
+ enabled: false
237
+ }
238
+ }
239
+
240
+ const idempotencyManager = new IdempotencyManager(configWithoutRebuild)
241
+ const hasChanges = await idempotencyManager.hasChanges()
242
+
243
+ // Should always return true when rebuild is disabled
244
+ expect(hasChanges).toBe(true)
245
+ })
246
+
247
+ it('should update cache after generation', async () => {
248
+ const idempotencyManager = new IdempotencyManager(config)
249
+ const cacheFile = join(testDir, '.pipecraft-cache.json')
250
+
251
+ // Cache shouldn't exist initially
252
+ expect(existsSync(cacheFile)).toBe(false)
253
+
254
+ // Update cache
255
+ await idempotencyManager.updateCache()
256
+
257
+ // Cache should now exist
258
+ expect(existsSync(cacheFile)).toBe(true)
259
+
260
+ // Cache should contain valid data
261
+ const cache = idempotencyManager.loadCache()
262
+ expect(cache).toBeDefined()
263
+ expect(cache?.configHash).toBeDefined()
264
+ expect(cache?.lastGenerated).toBeDefined()
265
+ })
266
+ })
267
+
268
+ describe('Version Command Logic', () => {
269
+ let config: PipecraftConfig
270
+
271
+ beforeEach(() => {
272
+ config = {
273
+ ciProvider: 'github',
274
+ mergeStrategy: 'fast-forward',
275
+ requireConventionalCommits: true,
276
+ initialBranch: 'develop',
277
+ finalBranch: 'main',
278
+ branchFlow: ['develop', 'main'],
279
+ semver: {
280
+ bumpRules: {
281
+ feat: 'minor',
282
+ fix: 'patch',
283
+ breaking: 'major'
284
+ }
285
+ },
286
+ actions: {
287
+ onDevelopMerge: ['runTests']
288
+ },
289
+ domains: {
290
+ api: {
291
+ paths: ['apps/api/**'],
292
+ description: 'API'
293
+ }
294
+ },
295
+ versioning: {
296
+ enabled: true,
297
+ releaseItConfig: '.release-it.cjs',
298
+ conventionalCommits: true,
299
+ autoTag: true,
300
+ autoPush: true,
301
+ changelog: true,
302
+ bumpRules: {
303
+ feat: 'minor',
304
+ fix: 'patch',
305
+ breaking: 'major'
306
+ }
307
+ }
308
+ }
309
+
310
+ // Create package.json for version reading
311
+ writeFileSync(join(testDir, 'package.json'), JSON.stringify({
312
+ name: 'test-project',
313
+ version: '1.0.0'
314
+ }, null, 2))
315
+ })
316
+
317
+ it('should create version manager with config', () => {
318
+ const versionManager = new VersionManager(config)
319
+ expect(versionManager).toBeDefined()
320
+ })
321
+
322
+ it('should calculate next version based on commits', () => {
323
+ const versionManager = new VersionManager(config)
324
+ const nextVersion = versionManager.calculateNextVersion()
325
+
326
+ expect(nextVersion).toBeDefined()
327
+ expect(nextVersion.version).toBeDefined()
328
+ expect(nextVersion.type).toBeDefined()
329
+ })
330
+
331
+ it('should validate conventional commits', () => {
332
+ const versionManager = new VersionManager(config)
333
+ const isValid = versionManager.validateConventionalCommits()
334
+
335
+ // Should return boolean
336
+ expect(typeof isValid).toBe('boolean')
337
+ })
338
+ })
339
+
340
+ describe('Verify Command Logic', () => {
341
+ it('should detect when config file exists', () => {
342
+ const config = {
343
+ ciProvider: 'github',
344
+ mergeStrategy: 'fast-forward',
345
+ requireConventionalCommits: true,
346
+ initialBranch: 'develop',
347
+ finalBranch: 'main',
348
+ branchFlow: ['develop', 'main'],
349
+ domains: {
350
+ api: { paths: ['apps/api/**'], description: 'API' }
351
+ }
352
+ } as PipecraftConfig
353
+
354
+ writeFileSync(join(testDir, '.pipecraftrc.json'), JSON.stringify(config, null, 2))
355
+
356
+ expect(existsSync(join(testDir, '.pipecraftrc.json'))).toBe(true)
357
+ })
358
+
359
+ it('should detect when workflow files exist', () => {
360
+ const workflowDir = join(testDir, '.github', 'workflows')
361
+ mkdirSync(workflowDir, { recursive: true })
362
+
363
+ const pipelineFile = join(workflowDir, 'pipeline.yml')
364
+ writeFileSync(pipelineFile, 'name: Pipeline\n')
365
+
366
+ expect(existsSync(pipelineFile)).toBe(true)
367
+ })
368
+
369
+ it('should handle missing workflow directory', () => {
370
+ const workflowDir = join(testDir, '.github', 'workflows')
371
+
372
+ expect(existsSync(workflowDir)).toBe(false)
373
+ })
374
+ })
375
+
376
+ describe('Error Handling', () => {
377
+ it('should handle malformed config file', () => {
378
+ const configPath = join(testDir, '.pipecraftrc.json')
379
+ writeFileSync(configPath, '{ invalid json }')
380
+
381
+ expect(() => loadConfig(configPath)).toThrow()
382
+ })
383
+
384
+ it('should handle config with missing domains', () => {
385
+ const config = {
386
+ ciProvider: 'github',
387
+ mergeStrategy: 'fast-forward',
388
+ requireConventionalCommits: true,
389
+ initialBranch: 'develop',
390
+ finalBranch: 'main',
391
+ branchFlow: ['develop', 'main']
392
+ // Missing domains
393
+ } as any
394
+
395
+ expect(() => validateConfig(config)).toThrow()
396
+ })
397
+
398
+ it('should handle empty branch flow', () => {
399
+ const config = {
400
+ ciProvider: 'github',
401
+ mergeStrategy: 'fast-forward',
402
+ requireConventionalCommits: true,
403
+ initialBranch: 'develop',
404
+ finalBranch: 'main',
405
+ branchFlow: [], // Empty
406
+ domains: {
407
+ api: { paths: ['apps/api/**'], description: 'API' }
408
+ }
409
+ } as any
410
+
411
+ expect(() => validateConfig(config)).toThrow()
412
+ })
413
+ })
414
+
415
+ describe('Configuration Edge Cases', () => {
416
+ it('should handle custom branch names', () => {
417
+ const config: PipecraftConfig = {
418
+ ciProvider: 'github',
419
+ mergeStrategy: 'fast-forward',
420
+ requireConventionalCommits: true,
421
+ initialBranch: 'alpha',
422
+ finalBranch: 'production',
423
+ branchFlow: ['alpha', 'beta', 'gamma', 'production'],
424
+ semver: {
425
+ bumpRules: {
426
+ feat: 'minor',
427
+ fix: 'patch',
428
+ breaking: 'major'
429
+ }
430
+ },
431
+ actions: {
432
+ onDevelopMerge: ['runTests']
433
+ },
434
+ domains: {
435
+ backend: {
436
+ paths: ['services/backend/**'],
437
+ description: 'Backend services'
438
+ }
439
+ }
440
+ }
441
+
442
+ writeFileSync(join(testDir, '.pipecraftrc.json'), JSON.stringify(config, null, 2))
443
+
444
+ const loadedConfig = loadConfig(join(testDir, '.pipecraftrc.json'))
445
+ expect(loadedConfig.initialBranch).toBe('alpha')
446
+ expect(loadedConfig.finalBranch).toBe('production')
447
+ expect(loadedConfig.branchFlow).toHaveLength(4)
448
+ })
449
+
450
+ it('should handle many domains', () => {
451
+ const config: PipecraftConfig = {
452
+ ciProvider: 'github',
453
+ mergeStrategy: 'fast-forward',
454
+ requireConventionalCommits: true,
455
+ initialBranch: 'develop',
456
+ finalBranch: 'main',
457
+ branchFlow: ['develop', 'main'],
458
+ semver: {
459
+ bumpRules: {
460
+ feat: 'minor',
461
+ fix: 'patch',
462
+ breaking: 'major'
463
+ }
464
+ },
465
+ actions: {
466
+ onDevelopMerge: ['runTests']
467
+ },
468
+ domains: {
469
+ 'api': { paths: ['apps/api/**'], description: 'API' },
470
+ 'web': { paths: ['apps/web/**'], description: 'Web' },
471
+ 'mobile': { paths: ['apps/mobile/**'], description: 'Mobile' },
472
+ 'admin': { paths: ['apps/admin/**'], description: 'Admin' },
473
+ 'analytics': { paths: ['apps/analytics/**'], description: 'Analytics' }
474
+ }
475
+ }
476
+
477
+ writeFileSync(join(testDir, '.pipecraftrc.json'), JSON.stringify(config, null, 2))
478
+
479
+ const loadedConfig = loadConfig(join(testDir, '.pipecraftrc.json'))
480
+ expect(Object.keys(loadedConfig.domains)).toHaveLength(5)
481
+ })
482
+
483
+ it('should handle gitlab CI provider', () => {
484
+ const config: PipecraftConfig = {
485
+ ciProvider: 'gitlab',
486
+ mergeStrategy: 'fast-forward',
487
+ requireConventionalCommits: true,
488
+ initialBranch: 'develop',
489
+ finalBranch: 'main',
490
+ branchFlow: ['develop', 'main'],
491
+ semver: {
492
+ bumpRules: {
493
+ feat: 'minor',
494
+ fix: 'patch',
495
+ breaking: 'major'
496
+ }
497
+ },
498
+ actions: {
499
+ onDevelopMerge: ['runTests']
500
+ },
501
+ domains: {
502
+ api: { paths: ['apps/api/**'], description: 'API' }
503
+ }
504
+ }
505
+
506
+ writeFileSync(join(testDir, '.pipecraftrc.json'), JSON.stringify(config, null, 2))
507
+
508
+ const loadedConfig = loadConfig(join(testDir, '.pipecraftrc.json'))
509
+ expect(loadedConfig.ciProvider).toBe('gitlab')
510
+ })
511
+
512
+ it('should handle merge strategy', () => {
513
+ const config: PipecraftConfig = {
514
+ ciProvider: 'github',
515
+ mergeStrategy: 'merge',
516
+ requireConventionalCommits: true,
517
+ initialBranch: 'develop',
518
+ finalBranch: 'main',
519
+ branchFlow: ['develop', 'main'],
520
+ semver: {
521
+ bumpRules: {
522
+ feat: 'minor',
523
+ fix: 'patch',
524
+ breaking: 'major'
525
+ }
526
+ },
527
+ actions: {
528
+ onDevelopMerge: ['runTests']
529
+ },
530
+ domains: {
531
+ api: { paths: ['apps/api/**'], description: 'API' }
532
+ }
533
+ }
534
+
535
+ writeFileSync(join(testDir, '.pipecraftrc.json'), JSON.stringify(config, null, 2))
536
+
537
+ const loadedConfig = loadConfig(join(testDir, '.pipecraftrc.json'))
538
+ expect(loadedConfig.mergeStrategy).toBe('merge')
539
+ })
540
+ })
541
+ })