pipecraft 0.29.3 → 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 +310 -83
  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 +142 -17
  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 +374 -102
  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('')
293
+ }
294
+
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('')
114
314
  }
115
315
 
116
- // Include Nx configuration if detected
117
- if (ctx.nx) {
118
- config.nx = ctx.nx
316
+ // Remove trailing empty line
317
+ while (lines[lines.length - 1] === '') {
318
+ lines.pop()
119
319
  }
120
320
 
121
- return JSON.stringify(config, null, 2)
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',
@@ -235,6 +504,15 @@ export const generate = async (ctx: PinionContext) => {
235
504
  choices: ['npm', 'yarn', 'pnpm'],
236
505
  default: detectedPackageManager
237
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
+ },
238
516
  {
239
517
  type: 'list',
240
518
  name: 'domainSelection',
@@ -261,13 +539,20 @@ export const generate = async (ctx: PinionContext) => {
261
539
  if (!input.trim()) {
262
540
  return 'Please enter at least one domain'
263
541
  }
264
- const domains = input.split(',').map(d => d.trim()).filter(d => d)
542
+ const domains = input
543
+ .split(',')
544
+ .map(d => d.trim())
545
+ .filter(d => d)
265
546
  if (domains.length === 0) {
266
547
  return 'Please enter valid domain names'
267
548
  }
268
549
  return true
269
550
  },
270
- filter: (input: string) => input.split(',').map(d => d.trim()).filter(d => d)
551
+ filter: (input: string) =>
552
+ input
553
+ .split(',')
554
+ .map(d => d.trim())
555
+ .filter(d => d)
271
556
  }
272
557
  ])
273
558
  selectedDomains = customDomainsAnswer.customDomains
@@ -278,7 +563,10 @@ export const generate = async (ctx: PinionContext) => {
278
563
  'frontend-backend': ['frontend', 'backend'],
279
564
  'apps-libs': ['apps', 'libs']
280
565
  }
281
- selectedDomains = domainMappings[answers.domainSelection as keyof typeof domainMappings] || ['api', 'web']
566
+ selectedDomains = domainMappings[answers.domainSelection as keyof typeof domainMappings] || [
567
+ 'api',
568
+ 'web'
569
+ ]
282
570
  }
283
571
 
284
572
  // Generate domain configuration
@@ -292,62 +580,37 @@ export const generate = async (ctx: PinionContext) => {
292
580
 
293
581
  // Add cicd domain for CI/CD changes
294
582
  domainConfig.cicd = {
295
- paths: ['.github/workflows/**'],
583
+ paths: ['.github/**'],
296
584
  description: 'CI/CD configuration changes'
297
585
  }
298
586
 
299
587
  // Show warning for custom domains about path editing
300
588
  if (answers.domainSelection === 'custom') {
301
589
  console.log('\n⚠️ Custom domains selected!')
302
- console.log(' You will need to edit the paths in .pipecraftrc.json after generation')
590
+ console.log(' You will need to edit the paths in .pipecraftrc after generation')
303
591
  console.log(' to match your actual project structure.\n')
304
592
  }
305
593
 
306
594
  // Merge answers with context and defaults
307
595
  const mergedCtx = { ...ctx, ...defaultConfig, ...answers } as any
308
596
 
309
- // Detect Nx workspace
310
- const nxJsonPath = `${cwd}/nx.json`
311
- let nxConfig = undefined
312
-
313
- if (existsSync(nxJsonPath)) {
314
- try {
315
- const nxJsonContent = readFileSync(nxJsonPath, 'utf8')
316
- const nxJson = JSON.parse(nxJsonContent)
317
-
318
- // Extract tasks from targetDefaults
319
- const tasks = nxJson.targetDefaults ? Object.keys(nxJson.targetDefaults) : []
320
-
321
- // Sort tasks in a logical order (quality → test → build → e2e)
322
- const taskOrder = ['lint', 'typecheck', 'test', 'unit-test', 'build', 'integration-test', 'e2e', 'e2e-ci']
323
- const sortedTasks = tasks.sort((a, b) => {
324
- const aIdx = taskOrder.indexOf(a)
325
- const bIdx = taskOrder.indexOf(b)
326
- if (aIdx === -1 && bIdx === -1) return 0
327
- if (aIdx === -1) return 1
328
- if (bIdx === -1) return -1
329
- return aIdx - bIdx
330
- })
331
-
332
- console.log('✅ Nx workspace detected - enabling Nx integration')
333
- 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`)
334
604
 
335
- nxConfig = {
336
- enabled: true,
337
- tasks: sortedTasks,
338
- baseRef: 'origin/main',
339
- enableCache: true
340
- }
341
- } catch (error) {
342
- console.warn('⚠️ Found nx.json but could not parse it:', error)
343
- console.log(' Using default Nx configuration')
344
- nxConfig = {
345
- enabled: true,
346
- tasks: ['lint', 'test', 'build', 'integration-test'],
347
- baseRef: 'origin/main',
348
- enableCache: true
349
- }
605
+ nxConfig = {
606
+ enabled: true,
607
+ tasks: detectedNxTasks,
608
+ baseRef: 'origin/main',
609
+ enableCache: true
350
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')
351
614
  }
352
615
 
353
616
  const configData: any = {
@@ -370,5 +633,14 @@ export const generate = async (ctx: PinionContext) => {
370
633
  configData.nx = nxConfig
371
634
  }
372
635
 
373
- return writeJSON(() => configData, toFile('.pipecraftrc.json'))(mergedCtx)
374
- }
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
+ }