pipecraft 0.29.2 → 0.36.2

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 (136) hide show
  1. package/README.md +98 -34
  2. package/dist/cli/index.d.ts +3 -3
  3. package/dist/cli/index.js +66 -24
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/generators/init.tpl.d.ts +4 -4
  6. package/dist/generators/init.tpl.d.ts.map +1 -1
  7. package/dist/generators/init.tpl.js +369 -79
  8. package/dist/generators/init.tpl.js.map +1 -1
  9. package/dist/generators/workflows.tpl.d.ts +9 -9
  10. package/dist/generators/workflows.tpl.d.ts.map +1 -1
  11. package/dist/generators/workflows.tpl.js +33 -24
  12. package/dist/generators/workflows.tpl.js.map +1 -1
  13. package/dist/templates/actions/calculate-version.yml.tpl.d.ts +7 -4
  14. package/dist/templates/actions/calculate-version.yml.tpl.d.ts.map +1 -1
  15. package/dist/templates/actions/calculate-version.yml.tpl.js +16 -9
  16. package/dist/templates/actions/calculate-version.yml.tpl.js.map +1 -1
  17. package/dist/templates/actions/create-pr.yml.tpl.d.ts +5 -2
  18. package/dist/templates/actions/create-pr.yml.tpl.d.ts.map +1 -1
  19. package/dist/templates/actions/create-pr.yml.tpl.js +9 -6
  20. package/dist/templates/actions/create-pr.yml.tpl.js.map +1 -1
  21. package/dist/templates/actions/create-release.yml.tpl.d.ts +5 -2
  22. package/dist/templates/actions/create-release.yml.tpl.d.ts.map +1 -1
  23. package/dist/templates/actions/create-release.yml.tpl.js +9 -6
  24. package/dist/templates/actions/create-release.yml.tpl.js.map +1 -1
  25. package/dist/templates/actions/create-tag.yml.tpl.d.ts +5 -2
  26. package/dist/templates/actions/create-tag.yml.tpl.d.ts.map +1 -1
  27. package/dist/templates/actions/create-tag.yml.tpl.js +9 -6
  28. package/dist/templates/actions/create-tag.yml.tpl.js.map +1 -1
  29. package/dist/templates/actions/detect-changes.yml.tpl.d.ts +24 -52
  30. package/dist/templates/actions/detect-changes.yml.tpl.d.ts.map +1 -1
  31. package/dist/templates/actions/detect-changes.yml.tpl.js +281 -108
  32. package/dist/templates/actions/detect-changes.yml.tpl.js.map +1 -1
  33. package/dist/templates/actions/manage-branch.yml.tpl.d.ts +5 -2
  34. package/dist/templates/actions/manage-branch.yml.tpl.d.ts.map +1 -1
  35. package/dist/templates/actions/manage-branch.yml.tpl.js +9 -6
  36. package/dist/templates/actions/manage-branch.yml.tpl.js.map +1 -1
  37. package/dist/templates/actions/promote-branch.yml.tpl.d.ts +5 -2
  38. package/dist/templates/actions/promote-branch.yml.tpl.d.ts.map +1 -1
  39. package/dist/templates/actions/promote-branch.yml.tpl.js +27 -76
  40. package/dist/templates/actions/promote-branch.yml.tpl.js.map +1 -1
  41. package/dist/templates/actions/run-nx-affected.yml.tpl.d.ts +33 -0
  42. package/dist/templates/actions/run-nx-affected.yml.tpl.d.ts.map +1 -0
  43. package/dist/templates/actions/run-nx-affected.yml.tpl.js +346 -0
  44. package/dist/templates/actions/run-nx-affected.yml.tpl.js.map +1 -0
  45. package/dist/templates/release-it.cjs.tpl.d.ts +1 -1
  46. package/dist/templates/release-it.cjs.tpl.d.ts.map +1 -1
  47. package/dist/templates/release-it.cjs.tpl.js +24 -26
  48. package/dist/templates/release-it.cjs.tpl.js.map +1 -1
  49. package/dist/templates/workflows/enforce-pr-target.yml.tpl.d.ts +1 -1
  50. package/dist/templates/workflows/enforce-pr-target.yml.tpl.d.ts.map +1 -1
  51. package/dist/templates/workflows/enforce-pr-target.yml.tpl.js +2 -3
  52. package/dist/templates/workflows/enforce-pr-target.yml.tpl.js.map +1 -1
  53. package/dist/templates/workflows/pipeline-nx.yml.tpl.d.ts +2 -2
  54. package/dist/templates/workflows/pipeline-nx.yml.tpl.d.ts.map +1 -1
  55. package/dist/templates/workflows/pipeline-nx.yml.tpl.js +300 -93
  56. package/dist/templates/workflows/pipeline-nx.yml.tpl.js.map +1 -1
  57. package/dist/templates/workflows/pipeline.yml.tpl.d.ts +2 -2
  58. package/dist/templates/workflows/pipeline.yml.tpl.d.ts.map +1 -1
  59. package/dist/templates/workflows/pipeline.yml.tpl.js +347 -74
  60. package/dist/templates/workflows/pipeline.yml.tpl.js.map +1 -1
  61. package/dist/templates/workflows/pr-title-check.yml.tpl.d.ts +1 -1
  62. package/dist/templates/workflows/pr-title-check.yml.tpl.d.ts.map +1 -1
  63. package/dist/templates/workflows/pr-title-check.yml.tpl.js +101 -22
  64. package/dist/templates/workflows/pr-title-check.yml.tpl.js.map +1 -1
  65. package/dist/templates/workflows/shared/index.d.ts +3 -2
  66. package/dist/templates/workflows/shared/index.d.ts.map +1 -1
  67. package/dist/templates/workflows/shared/index.js +3 -2
  68. package/dist/templates/workflows/shared/index.js.map +1 -1
  69. package/dist/templates/workflows/shared/operations-changes.d.ts +13 -2
  70. package/dist/templates/workflows/shared/operations-changes.d.ts.map +1 -1
  71. package/dist/templates/workflows/shared/operations-changes.js +47 -16
  72. package/dist/templates/workflows/shared/operations-changes.js.map +1 -1
  73. package/dist/templates/workflows/shared/operations-domain-jobs.d.ts +18 -1
  74. package/dist/templates/workflows/shared/operations-domain-jobs.d.ts.map +1 -1
  75. package/dist/templates/workflows/shared/operations-domain-jobs.js +143 -19
  76. package/dist/templates/workflows/shared/operations-domain-jobs.js.map +1 -1
  77. package/dist/templates/workflows/shared/operations-gate.d.ts +32 -0
  78. package/dist/templates/workflows/shared/operations-gate.d.ts.map +1 -0
  79. package/dist/templates/workflows/shared/operations-gate.js +77 -0
  80. package/dist/templates/workflows/shared/operations-gate.js.map +1 -0
  81. package/dist/templates/workflows/shared/operations-header.d.ts +1 -1
  82. package/dist/templates/workflows/shared/operations-header.d.ts.map +1 -1
  83. package/dist/templates/workflows/shared/operations-header.js +46 -6
  84. package/dist/templates/workflows/shared/operations-header.js.map +1 -1
  85. package/dist/templates/workflows/shared/operations-tag-promote.d.ts +5 -1
  86. package/dist/templates/workflows/shared/operations-tag-promote.d.ts.map +1 -1
  87. package/dist/templates/workflows/shared/operations-tag-promote.js +138 -31
  88. package/dist/templates/workflows/shared/operations-tag-promote.js.map +1 -1
  89. package/dist/templates/workflows/shared/operations-version.d.ts +3 -1
  90. package/dist/templates/workflows/shared/operations-version.d.ts.map +1 -1
  91. package/dist/templates/workflows/shared/operations-version.js +18 -10
  92. package/dist/templates/workflows/shared/operations-version.js.map +1 -1
  93. package/dist/templates/yaml-format-utils.d.ts.map +1 -1
  94. package/dist/templates/yaml-format-utils.js.map +1 -1
  95. package/dist/types/index.d.ts +57 -4
  96. package/dist/types/index.d.ts.map +1 -1
  97. package/dist/utils/action-reference.d.ts +73 -0
  98. package/dist/utils/action-reference.d.ts.map +1 -0
  99. package/dist/utils/action-reference.js +120 -0
  100. package/dist/utils/action-reference.js.map +1 -0
  101. package/dist/utils/ast-path-operations.d.ts +3 -3
  102. package/dist/utils/ast-path-operations.d.ts.map +1 -1
  103. package/dist/utils/ast-path-operations.js +58 -17
  104. package/dist/utils/ast-path-operations.js.map +1 -1
  105. package/dist/utils/config.d.ts +11 -5
  106. package/dist/utils/config.d.ts.map +1 -1
  107. package/dist/utils/config.js +33 -8
  108. package/dist/utils/config.js.map +1 -1
  109. package/dist/utils/github-setup.d.ts +4 -4
  110. package/dist/utils/github-setup.d.ts.map +1 -1
  111. package/dist/utils/github-setup.js +57 -36
  112. package/dist/utils/github-setup.js.map +1 -1
  113. package/dist/utils/logger.d.ts.map +1 -1
  114. package/dist/utils/logger.js.map +1 -1
  115. package/dist/utils/messaging.d.ts.map +1 -1
  116. package/dist/utils/messaging.js +7 -3
  117. package/dist/utils/messaging.js.map +1 -1
  118. package/dist/utils/preflight.d.ts +2 -2
  119. package/dist/utils/preflight.d.ts.map +1 -1
  120. package/dist/utils/preflight.js +43 -41
  121. package/dist/utils/preflight.js.map +1 -1
  122. package/dist/utils/versioning.d.ts +1 -1
  123. package/dist/utils/versioning.d.ts.map +1 -1
  124. package/dist/utils/versioning.js +10 -10
  125. package/dist/utils/versioning.js.map +1 -1
  126. package/package.json +11 -4
  127. package/src/generators/init.tpl.ts +434 -96
  128. package/src/generators/workflows.tpl.ts +54 -45
  129. package/dist/templates/actions/detect-changes-nx.yml.tpl.d.ts +0 -32
  130. package/dist/templates/actions/detect-changes-nx.yml.tpl.d.ts.map +0 -1
  131. package/dist/templates/actions/detect-changes-nx.yml.tpl.js +0 -227
  132. package/dist/templates/actions/detect-changes-nx.yml.tpl.js.map +0 -1
  133. package/dist/utils/idempotency.d.ts +0 -187
  134. package/dist/utils/idempotency.d.ts.map +0 -1
  135. package/dist/utils/idempotency.js +0 -304
  136. package/dist/utils/idempotency.js.map +0 -1
@@ -1,43 +1,40 @@
1
1
  /**
2
2
  * Init Template Generator
3
- *
4
- * Generates the initial PipeCraft configuration file (.pipecraftrc.json) with default settings.
3
+ *
4
+ * Generates the initial PipeCraft configuration file (.pipecraftrc) with default settings.
5
5
  * This generator is invoked by the `pipecraft init` command and prompts the user for
6
6
  * basic project configuration preferences.
7
- *
7
+ *
8
8
  * @module generators/init.tpl
9
- *
9
+ *
10
10
  * @example
11
11
  * ```typescript
12
12
  * import { generate } from './generators/init.tpl.js'
13
- *
13
+ *
14
14
  * // Called by CLI with Pinion context
15
15
  * await generate(pinionContext)
16
- *
17
- * // Creates .pipecraftrc.json with:
16
+ *
17
+ * // Creates .pipecraftrc with:
18
18
  * // - CI provider (GitHub/GitLab)
19
19
  * // - Merge strategy (fast-forward/merge)
20
20
  * // - Branch flow configuration
21
21
  * // - Domain-based change detection
22
22
  * // - Semantic versioning rules
23
23
  * ```
24
- *
24
+ *
25
25
  * @note Current Implementation: The generator currently uses hardcoded defaults
26
26
  * from `defaultConfig` regardless of user prompt responses. This is intentional
27
27
  * for the initial release to ensure consistent behavior. Future versions will
28
28
  * respect user input and allow customization.
29
29
  */
30
30
 
31
- import { PinionContext, toFile, renderTemplate, writeJSON } from '@featherscloud/pinion'
32
- import { existsSync, readFileSync } from 'fs'
31
+ import type { PinionContext } from '@featherscloud/pinion'
32
+ import { existsSync, readFileSync, writeFileSync } from 'fs'
33
33
  import inquirer from 'inquirer'
34
- import { IdempotencyManager } from '../utils/idempotency.js'
35
- import { VersionManager } from '../utils/versioning.js'
36
- import { PipecraftConfig } from '../types/index.js'
37
34
 
38
35
  /**
39
36
  * Default PipeCraft configuration for new projects.
40
- *
37
+ *
41
38
  * Provides a complete, working trunk-based development workflow with:
42
39
  * - GitHub Actions CI/CD
43
40
  * - Fast-forward merge strategy
@@ -45,7 +42,7 @@ import { PipecraftConfig } from '../types/index.js'
45
42
  * - Conventional commits enforcement
46
43
  * - Semantic versioning with standard bump rules
47
44
  * - Domain-based change detection for monorepos
48
- *
45
+ *
49
46
  * @const
50
47
  */
51
48
  const defaultConfig = {
@@ -61,8 +58,22 @@ const defaultConfig = {
61
58
  },
62
59
  semver: {
63
60
  bumpRules: {
64
- feat: 'minor',
61
+ test: 'ignore',
62
+ build: 'ignore',
63
+
64
+ ci: 'patch',
65
+ docs: 'patch',
66
+ style: 'patch',
65
67
  fix: 'patch',
68
+ perf: 'patch',
69
+ refactor: 'patch',
70
+ chore: 'patch',
71
+ patch: 'patch',
72
+
73
+ feat: 'minor',
74
+ minor: 'minor',
75
+
76
+ major: 'major',
66
77
  breaking: 'major'
67
78
  }
68
79
  },
@@ -87,54 +98,245 @@ const defaultConfig = {
87
98
  }
88
99
 
89
100
  /**
90
- * Generates a JSON string representation of the PipeCraft configuration.
91
- *
92
- * Serializes the configuration object with proper formatting (2-space indentation)
93
- * for writing to .pipecraftrc.json file.
94
- *
95
- * @param {PipecraftConfig} ctx - The configuration context to serialize
96
- * @returns {string} JSON string representation of the configuration
97
- *
98
- * @note The function extracts specific fields from the context to ensure
99
- * only valid configuration properties are included in the output file.
101
+ * Generates a YAML configuration file with comprehensive comments.
102
+ *
103
+ * Creates a .pipecraftrc file with:
104
+ * - Descriptive section headers
105
+ * - Inline comments for each configuration option
106
+ * - Examples and valid values
107
+ * - Proper YAML formatting
108
+ *
109
+ * @param {any} config - The configuration object to serialize
110
+ * @returns {string} YAML string with comments
100
111
  */
101
- const configTemplate = (ctx: PipecraftConfig) => {
102
- const config: any = {
103
- ciProvider: ctx.ciProvider,
104
- mergeStrategy: ctx.mergeStrategy,
105
- requireConventionalCommits: ctx.requireConventionalCommits,
106
- initialBranch: ctx.initialBranch,
107
- finalBranch: ctx.finalBranch,
108
- branchFlow: ctx.branchFlow,
109
- autoMerge: ctx.autoMerge,
110
- semver: {
111
- bumpRules: ctx.semver.bumpRules
112
+ const generateYamlConfig = (config: any): string => {
113
+ const lines: string[] = []
114
+
115
+ // Header
116
+ lines.push('# PipeCraft Configuration')
117
+ lines.push('# Trunk-based development workflow automation')
118
+ lines.push('# https://pipecraft.thecraftlab.dev')
119
+ lines.push('')
120
+
121
+ // CI Provider
122
+ lines.push('# CI/CD Platform')
123
+ lines.push(`ciProvider: ${config.ciProvider}`)
124
+ lines.push('')
125
+
126
+ // Merge Strategy
127
+ lines.push('# Merge strategy for automatic promotions')
128
+ lines.push(
129
+ "# Options: 'fast-forward' (clean linear history, recommended) | 'merge' (creates merge commits)"
130
+ )
131
+ lines.push(`mergeStrategy: ${config.mergeStrategy}`)
132
+ lines.push('')
133
+
134
+ // Conventional Commits
135
+ lines.push('# Enforce conventional commit messages (feat:, fix:, etc.)')
136
+ lines.push(`requireConventionalCommits: ${config.requireConventionalCommits}`)
137
+ lines.push('')
138
+
139
+ // Branch Configuration
140
+ lines.push('# Branch flow configuration')
141
+ lines.push(`initialBranch: ${config.initialBranch} # Where features land`)
142
+ lines.push(`finalBranch: ${config.finalBranch} # Production branch`)
143
+ lines.push('')
144
+
145
+ // Branch Flow
146
+ lines.push(`# Promotion flow: ${config.branchFlow.join(' → ')}`)
147
+ lines.push('branchFlow:')
148
+ config.branchFlow.forEach((branch: string, index: number) => {
149
+ const comments = [
150
+ '# Feature integration and initial testing',
151
+ '# Pre-production validation',
152
+ '# Production releases'
153
+ ]
154
+ lines.push(` - ${branch} ${comments[index] || ''}`)
155
+ })
156
+ lines.push('')
157
+
158
+ // Auto Merge
159
+ lines.push('# Automatic promotion after successful tests')
160
+ lines.push('autoMerge:')
161
+ const autoMergeEntries = Object.entries(config.autoMerge)
162
+ autoMergeEntries.forEach(([key, value]) => {
163
+ const flowIndex = config.branchFlow.indexOf(key)
164
+ const fromBranch = flowIndex > 0 ? config.branchFlow[flowIndex - 1] : ''
165
+ const comment = fromBranch ? ` # Auto-promote ${fromBranch} → ${key}` : ''
166
+ lines.push(` ${key}: ${value}${comment}`)
167
+ })
168
+ lines.push('')
169
+
170
+ // Package Manager (if present)
171
+ if (config.packageManager) {
172
+ lines.push('# Package manager')
173
+ lines.push(`packageManager: ${config.packageManager}`)
174
+ lines.push('')
175
+ }
176
+
177
+ // Semantic Versioning
178
+ lines.push('# Semantic versioning rules')
179
+ lines.push('# Prefixes are used to determine the type of change and the version bump level')
180
+ lines.push('semver:')
181
+ lines.push(' bumpRules:')
182
+
183
+ const bumpRules = config.semver.bumpRules
184
+ const ruleGroups = [
185
+ { title: '# Ignored types (no version bump)', keys: ['test', 'build'] },
186
+ {
187
+ title: '# Patch-level changes (0.0.x)',
188
+ keys: ['ci', 'docs', 'style', 'fix', 'perf', 'refactor', 'chore', 'patch']
112
189
  },
113
- domains: ctx.domains
190
+ { title: '# Minor-level changes (0.x.0)', keys: ['feat', 'minor'] },
191
+ { title: '# Major-level changes (x.0.0)', keys: ['major', 'breaking'] }
192
+ ]
193
+
194
+ ruleGroups.forEach((group, groupIndex) => {
195
+ if (groupIndex > 0) lines.push('')
196
+ lines.push(` ${group.title}`)
197
+ group.keys.forEach(key => {
198
+ if (bumpRules[key]) {
199
+ lines.push(` ${key}: '${bumpRules[key]}'`)
200
+ }
201
+ })
202
+ })
203
+ lines.push('')
204
+
205
+ // Domains
206
+ lines.push('# Domain definitions - what parts of your codebase trigger which jobs')
207
+ lines.push(
208
+ '# Will create placeholder jobs for each domain if no jobs are defined in the pipeline.yml'
209
+ )
210
+ lines.push('domains:')
211
+
212
+ Object.entries(config.domains).forEach(([domainName, domainConfig]: [string, any]) => {
213
+ lines.push(` ${domainName}:`)
214
+
215
+ // Description (always write as field)
216
+ if (domainConfig.description) {
217
+ lines.push(` description: '${domainConfig.description}'`)
218
+ }
219
+
220
+ // Paths
221
+ if (domainConfig.paths) {
222
+ lines.push(' paths:')
223
+ domainConfig.paths.forEach((path: string) => {
224
+ lines.push(` - ${path}`)
225
+ })
226
+ }
227
+
228
+ // Prefixes (if present)
229
+ if (domainConfig.prefixes) {
230
+ lines.push(` prefixes: [${domainConfig.prefixes.map((p: string) => `'${p}'`).join(', ')}]`)
231
+ }
232
+
233
+ lines.push('')
234
+ })
235
+
236
+ // Nx Configuration (if present)
237
+ if (config.nx) {
238
+ lines.push('# Nx integration for monorepo optimization')
239
+ lines.push('nx:')
240
+ lines.push(` enabled: ${config.nx.enabled}`)
241
+ if (config.nx.tasks !== undefined) {
242
+ if (config.nx.tasks.length > 0) {
243
+ lines.push(' tasks:')
244
+ config.nx.tasks.forEach((task: string) => {
245
+ lines.push(` - ${task}`)
246
+ })
247
+ } else {
248
+ lines.push(' tasks: []')
249
+ }
250
+ }
251
+ if (config.nx.baseRef) {
252
+ lines.push(` baseRef: ${config.nx.baseRef}`)
253
+ }
254
+ if (config.nx.enableCache !== undefined) {
255
+ lines.push(` enableCache: ${config.nx.enableCache}`)
256
+ }
257
+ lines.push('')
258
+ }
259
+
260
+ // Versioning (if present)
261
+ if (config.versioning) {
262
+ lines.push('# Version calculation and release automation')
263
+ lines.push('versioning:')
264
+ lines.push(` enabled: ${config.versioning.enabled}`)
265
+ if (config.versioning.releaseItConfig) {
266
+ lines.push(` releaseItConfig: ${config.versioning.releaseItConfig}`)
267
+ }
268
+ if (config.versioning.conventionalCommits !== undefined) {
269
+ lines.push(
270
+ ` conventionalCommits: ${config.versioning.conventionalCommits} # Use conventional commits for version bumps`
271
+ )
272
+ }
273
+ if (config.versioning.autoTag !== undefined) {
274
+ lines.push(
275
+ ` autoTag: ${config.versioning.autoTag} # Automatically create git tags`
276
+ )
277
+ }
278
+ if (config.versioning.autoPush !== undefined) {
279
+ lines.push(
280
+ ` autoPush: ${config.versioning.autoPush} # Manual control over git push`
281
+ )
282
+ }
283
+ if (config.versioning.changelog !== undefined) {
284
+ lines.push(` changelog: ${config.versioning.changelog} # Generate CHANGELOG.md`)
285
+ }
286
+ if (config.versioning.bumpRules) {
287
+ lines.push(' bumpRules:')
288
+ Object.entries(config.versioning.bumpRules).forEach(([key, value]) => {
289
+ lines.push(` ${key}: ${value}`)
290
+ })
291
+ }
292
+ lines.push('')
114
293
  }
115
294
 
116
- // Include Nx configuration if detected
117
- if (ctx.nx) {
118
- config.nx = ctx.nx
295
+ // Rebuild configuration (if present)
296
+ if (config.rebuild) {
297
+ lines.push('# Workflow rebuild optimization')
298
+ lines.push('rebuild:')
299
+ lines.push(` enabled: ${config.rebuild.enabled} # Enable smart rebuild detection`)
300
+ if (config.rebuild.skipIfUnchanged !== undefined) {
301
+ lines.push(
302
+ ` skipIfUnchanged: ${config.rebuild.skipIfUnchanged} # Skip regeneration if config hasn't changed`
303
+ )
304
+ }
305
+ if (config.rebuild.forceRegenerate !== undefined) {
306
+ lines.push(
307
+ ` forceRegenerate: ${config.rebuild.forceRegenerate} # Force regenerate even if unchanged`
308
+ )
309
+ }
310
+ if (config.rebuild.cacheFile) {
311
+ lines.push(` cacheFile: ${config.rebuild.cacheFile}`)
312
+ }
313
+ lines.push('')
119
314
  }
120
315
 
121
- return JSON.stringify(config, null, 2)
316
+ // Remove trailing empty line
317
+ while (lines[lines.length - 1] === '') {
318
+ lines.pop()
319
+ }
320
+
321
+ lines.push('') // Single trailing newline
322
+
323
+ return lines.join('\n')
122
324
  }
123
325
 
124
326
  /**
125
327
  * Init generator main entry point.
126
- *
328
+ *
127
329
  * Orchestrates the initialization process by:
128
330
  * 1. Prompting user for project preferences (currently unused - see note)
129
331
  * 2. Merging user input with default configuration
130
332
  * 3. Generating and writing .pipecraftrc.json file
131
- *
333
+ *
132
334
  * @param {PinionContext} ctx - Pinion generator context from CLI
133
335
  * @returns {Promise<PinionContext>} Updated context after file generation
134
- *
336
+ *
135
337
  * @throws {Error} If configuration file cannot be written
136
338
  * @throws {Error} If user input validation fails
137
- *
339
+ *
138
340
  * @example
139
341
  * ```typescript
140
342
  * // Called by Pinion framework when user runs `pipecraft init`
@@ -143,16 +345,16 @@ const configTemplate = (ctx: PipecraftConfig) => {
143
345
  * argv: ['init'],
144
346
  * pinion: { ... }
145
347
  * })
146
- *
348
+ *
147
349
  * // Results in: /path/to/project/.pipecraftrc.json
148
350
  * ```
149
- *
351
+ *
150
352
  * @note Current Behavior: Despite prompting for user input, the generator
151
353
  * currently overwrites all responses with `defaultConfig` values (line 167).
152
354
  * This ensures consistency for the initial release. Future versions will
153
355
  * respect user choices and allow customization of branch names, merge strategies,
154
356
  * and domain configurations.
155
- *
357
+ *
156
358
  * Prompts Presented (currently unused):
157
359
  * - Project name
158
360
  * - CI provider (GitHub/GitLab)
@@ -165,6 +367,34 @@ const configTemplate = (ctx: PipecraftConfig) => {
165
367
  export const generate = async (ctx: PinionContext) => {
166
368
  const cwd = ctx.cwd || process.cwd()
167
369
 
370
+ // Check if .pipecraftrc already exists (YAML format)
371
+ const configPath = `${cwd}/.pipecraftrc`
372
+ const legacyConfigPath = `${cwd}/.pipecraftrc.json`
373
+
374
+ if (existsSync(configPath) || existsSync(legacyConfigPath)) {
375
+ const existingPath = existsSync(configPath) ? '.pipecraftrc' : '.pipecraftrc.json'
376
+ const overwriteAnswer = await inquirer.prompt([
377
+ {
378
+ type: 'confirm',
379
+ name: 'overwrite',
380
+ message: `⚠️ ${existingPath} already exists. Overwrite it?`,
381
+ default: false
382
+ }
383
+ ])
384
+
385
+ if (!overwriteAnswer.overwrite) {
386
+ console.log('\n❌ Init cancelled. Existing configuration preserved.\n')
387
+ return
388
+ }
389
+
390
+ console.log('\n✅ Proceeding with overwrite...\n')
391
+
392
+ // Set force flag to true so Pinion will overwrite the file
393
+ if (ctx.pinion) {
394
+ ctx.pinion.force = true
395
+ }
396
+ }
397
+
168
398
  // Detect package manager before prompting
169
399
  let detectedPackageManager: 'npm' | 'yarn' | 'pnpm' = 'npm'
170
400
  if (existsSync(`${cwd}/pnpm-lock.yaml`)) {
@@ -175,14 +405,53 @@ export const generate = async (ctx: PinionContext) => {
175
405
  detectedPackageManager = 'npm'
176
406
  }
177
407
 
408
+ // Detect Nx workspace before prompting
409
+ const nxJsonPath = `${cwd}/nx.json`
410
+ let detectedNxTasks: string[] = []
411
+ let nxDetected = false
412
+
413
+ if (existsSync(nxJsonPath)) {
414
+ try {
415
+ const nxJsonContent = readFileSync(nxJsonPath, 'utf8')
416
+ const nxJson = JSON.parse(nxJsonContent)
417
+
418
+ // Extract tasks from targetDefaults
419
+ const tasks = nxJson.targetDefaults ? Object.keys(nxJson.targetDefaults) : []
420
+
421
+ // Sort tasks in a logical order (quality → test → build → e2e)
422
+ const taskOrder = [
423
+ 'lint',
424
+ 'typecheck',
425
+ 'test',
426
+ 'unit-test',
427
+ 'build',
428
+ 'integration-test',
429
+ 'e2e',
430
+ 'e2e-ci'
431
+ ]
432
+ detectedNxTasks = tasks.sort((a, b) => {
433
+ const aIdx = taskOrder.indexOf(a)
434
+ const bIdx = taskOrder.indexOf(b)
435
+ if (aIdx === -1 && bIdx === -1) return 0
436
+ if (aIdx === -1) return 1
437
+ if (bIdx === -1) return -1
438
+ return aIdx - bIdx
439
+ })
440
+
441
+ nxDetected = true
442
+ console.log('\n✅ Nx workspace detected!')
443
+ console.log(` Found tasks: ${detectedNxTasks.join(', ')}`)
444
+ console.log(' Pipecraft can optimize your CI pipeline using Nx affected commands.\n')
445
+ } catch (error) {
446
+ console.warn('\n⚠️ Found nx.json but could not parse it:', error)
447
+ console.log(' Nx integration will use default tasks.\n')
448
+ nxDetected = true
449
+ detectedNxTasks = ['lint', 'test', 'build', 'integration-test']
450
+ }
451
+ }
452
+
178
453
  // Run inquirer prompts
179
454
  const answers = await inquirer.prompt([
180
- {
181
- type: 'input',
182
- name: 'projectName',
183
- message: 'What is your project name?',
184
- default: 'my-project'
185
- },
186
455
  {
187
456
  type: 'list',
188
457
  name: 'ciProvider',
@@ -234,54 +503,114 @@ export const generate = async (ctx: PinionContext) => {
234
503
  message: `Which package manager do you use? (detected: ${detectedPackageManager})`,
235
504
  choices: ['npm', 'yarn', 'pnpm'],
236
505
  default: detectedPackageManager
506
+ },
507
+ {
508
+ type: 'confirm',
509
+ name: 'enableNx',
510
+ message: nxDetected
511
+ ? `Enable Nx integration? (detected ${detectedNxTasks.length} tasks)`
512
+ : 'Enable Nx integration?',
513
+ default: nxDetected,
514
+ when: () => nxDetected // Only show if Nx is detected
515
+ },
516
+ {
517
+ type: 'list',
518
+ name: 'domainSelection',
519
+ message: 'What domains exist in your codebase?',
520
+ choices: [
521
+ { name: 'API + Web (common monorepo)', value: 'api-web' },
522
+ { name: 'Frontend + Backend', value: 'frontend-backend' },
523
+ { name: 'Apps + Libs (Nx-style)', value: 'apps-libs' },
524
+ { name: 'Custom domains', value: 'custom' }
525
+ ],
526
+ default: 'api-web'
237
527
  }
238
528
  ])
239
529
 
240
- // Merge answers with context and defaults
241
- const mergedCtx = { ...ctx, ...defaultConfig, ...answers } as any
530
+ // Handle custom domain selection
531
+ let selectedDomains = []
532
+ if (answers.domainSelection === 'custom') {
533
+ const customDomainsAnswer = await inquirer.prompt([
534
+ {
535
+ type: 'input',
536
+ name: 'customDomains',
537
+ message: 'Enter your domains (comma-separated)',
538
+ validate: (input: string) => {
539
+ if (!input.trim()) {
540
+ return 'Please enter at least one domain'
541
+ }
542
+ const domains = input
543
+ .split(',')
544
+ .map(d => d.trim())
545
+ .filter(d => d)
546
+ if (domains.length === 0) {
547
+ return 'Please enter valid domain names'
548
+ }
549
+ return true
550
+ },
551
+ filter: (input: string) =>
552
+ input
553
+ .split(',')
554
+ .map(d => d.trim())
555
+ .filter(d => d)
556
+ }
557
+ ])
558
+ selectedDomains = customDomainsAnswer.customDomains
559
+ } else {
560
+ // Map predefined selections to domain names
561
+ const domainMappings = {
562
+ 'api-web': ['api', 'web'],
563
+ 'frontend-backend': ['frontend', 'backend'],
564
+ 'apps-libs': ['apps', 'libs']
565
+ }
566
+ selectedDomains = domainMappings[answers.domainSelection as keyof typeof domainMappings] || [
567
+ 'api',
568
+ 'web'
569
+ ]
570
+ }
242
571
 
243
- // Detect Nx workspace
244
- const nxJsonPath = `${cwd}/nx.json`
245
- let nxConfig = undefined
572
+ // Generate domain configuration
573
+ const domainConfig: Record<string, any> = {}
574
+ selectedDomains.forEach((domain: string) => {
575
+ domainConfig[domain] = {
576
+ paths: [`${domain}/**`], // Default path pattern
577
+ description: `${domain} application changes`
578
+ }
579
+ })
246
580
 
247
- if (existsSync(nxJsonPath)) {
248
- try {
249
- const nxJsonContent = readFileSync(nxJsonPath, 'utf8')
250
- const nxJson = JSON.parse(nxJsonContent)
581
+ // Add cicd domain for CI/CD changes
582
+ domainConfig.cicd = {
583
+ paths: ['.github/**'],
584
+ description: 'CI/CD configuration changes'
585
+ }
251
586
 
252
- // Extract tasks from targetDefaults
253
- const tasks = nxJson.targetDefaults ? Object.keys(nxJson.targetDefaults) : []
587
+ // Show warning for custom domains about path editing
588
+ if (answers.domainSelection === 'custom') {
589
+ console.log('\n⚠️ Custom domains selected!')
590
+ console.log(' You will need to edit the paths in .pipecraftrc after generation')
591
+ console.log(' to match your actual project structure.\n')
592
+ }
254
593
 
255
- // Sort tasks in a logical order (quality → test → build → e2e)
256
- const taskOrder = ['lint', 'typecheck', 'test', 'unit-test', 'build', 'integration-test', 'e2e', 'e2e-ci']
257
- const sortedTasks = tasks.sort((a, b) => {
258
- const aIdx = taskOrder.indexOf(a)
259
- const bIdx = taskOrder.indexOf(b)
260
- if (aIdx === -1 && bIdx === -1) return 0
261
- if (aIdx === -1) return 1
262
- if (bIdx === -1) return -1
263
- return aIdx - bIdx
264
- })
594
+ // Merge answers with context and defaults
595
+ const mergedCtx = { ...ctx, ...defaultConfig, ...answers } as any
265
596
 
266
- console.log('✅ Nx workspace detected - enabling Nx integration')
267
- console.log(` Detected tasks: ${sortedTasks.join(', ')}`)
597
+ // Configure Nx based on detection and user confirmation
598
+ let nxConfig:
599
+ | { enabled: boolean; tasks: string[]; baseRef: string; enableCache: boolean }
600
+ | undefined
601
+ if (nxDetected && answers.enableNx) {
602
+ console.log('\n✅ Nx integration enabled!')
603
+ console.log(` Tasks to run: ${detectedNxTasks.join(', ')}\n`)
268
604
 
269
- nxConfig = {
270
- enabled: true,
271
- tasks: sortedTasks,
272
- baseRef: 'origin/main',
273
- enableCache: true
274
- }
275
- } catch (error) {
276
- console.warn('⚠️ Found nx.json but could not parse it:', error)
277
- console.log(' Using default Nx configuration')
278
- nxConfig = {
279
- enabled: true,
280
- tasks: ['lint', 'test', 'build', 'integration-test'],
281
- baseRef: 'origin/main',
282
- enableCache: true
283
- }
605
+ nxConfig = {
606
+ enabled: true,
607
+ tasks: detectedNxTasks,
608
+ baseRef: 'origin/main',
609
+ enableCache: true
284
610
  }
611
+ } else if (nxDetected && !answers.enableNx) {
612
+ console.log('\n⚠️ Nx detected but integration disabled.')
613
+ console.log(' You can enable it later by editing .pipecraftrc\n')
285
614
  }
286
615
 
287
616
  const configData: any = {
@@ -296,7 +625,7 @@ export const generate = async (ctx: PinionContext) => {
296
625
  semver: {
297
626
  bumpRules: mergedCtx.semver.bumpRules
298
627
  },
299
- domains: mergedCtx.domains
628
+ domains: domainConfig // Use user-selected domains instead of defaults
300
629
  }
301
630
 
302
631
  // Add Nx config if detected
@@ -304,5 +633,14 @@ export const generate = async (ctx: PinionContext) => {
304
633
  configData.nx = nxConfig
305
634
  }
306
635
 
307
- return writeJSON(() => configData, toFile('.pipecraftrc.json'))(mergedCtx)
308
- }
636
+ // Generate YAML content with comments
637
+ const yamlContent = generateYamlConfig(configData)
638
+
639
+ // Write the .pipecraftrc file
640
+ const outputPath = `${cwd}/.pipecraftrc`
641
+ writeFileSync(outputPath, yamlContent, 'utf-8')
642
+
643
+ console.log(`\n✅ Created ${outputPath}\n`)
644
+
645
+ return mergedCtx
646
+ }