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,72 +1,70 @@
1
1
  /**
2
2
  * Workflows Template Generator
3
- *
3
+ *
4
4
  * Main orchestrator for generating complete GitHub Actions CI/CD pipeline.
5
5
  * This generator creates all necessary workflow files including:
6
6
  * - Main pipeline workflow (path-based change detection)
7
7
  * - Reusable composite actions (tag creation, versioning, branch management, etc.)
8
- *
8
+ *
9
9
  * The generator supports both initial generation and incremental updates, preserving
10
10
  * user modifications to existing workflows through intelligent merging.
11
- *
11
+ *
12
12
  * @module generators/workflows.tpl
13
- *
13
+ *
14
14
  * @example
15
15
  * ```typescript
16
16
  * import { generate } from './generators/workflows.tpl.js'
17
- *
17
+ *
18
18
  * // Initial generation - creates all workflows
19
19
  * await generate({
20
20
  * cwd: '/path/to/project',
21
21
  * config: pipecraftConfig
22
22
  * })
23
- *
23
+ *
24
24
  * // Incremental update - merges with existing pipeline
25
25
  * await generate({
26
26
  * cwd: '/path/to/project',
27
27
  * pipelinePath: '.github/workflows/pipeline.yml',
28
28
  * config: pipecraftConfig
29
29
  * })
30
- *
30
+ *
31
31
  * // Creates:
32
32
  * // .github/workflows/pipeline.yml - Main pipeline
33
- * // .github/actions/detect-changes/... - Change detection action
34
- * // .github/actions/calculate-version/... - Version calculation action
35
- * // .github/actions/create-tag/... - Tag creation action
36
- * // .github/actions/create-pr/... - PR creation action
37
- * // .github/actions/manage-branch/... - Branch management action
38
- * // .github/actions/promote-branch/... - Branch promotion action
39
- * // .github/actions/create-release/... - Release creation action
33
+ * // actions/detect-changes/... - Change detection action
34
+ * // actions/calculate-version/... - Version calculation action
35
+ * // actions/create-tag/... - Tag creation action
36
+ * // actions/create-pr/... - PR creation action
37
+ * // actions/manage-branch/... - Branch management action
38
+ * // actions/promote-branch/... - Branch promotion action
39
+ * // actions/create-release/... - Release creation action
40
40
  * // .release-it.cjs - Release-it configuration
41
41
  * ```
42
42
  */
43
43
 
44
- import { PinionContext, toFile, renderTemplate, loadJSON, when } from '@featherscloud/pinion'
45
- import { IdempotencyManager } from '../utils/idempotency.js'
46
- import { PipecraftConfig } from '../types/index.js'
47
- import { readFileSync, existsSync } from 'fs'
44
+ import { loadJSON, type PinionContext, renderTemplate, toFile, when } from '@featherscloud/pinion'
45
+ import { existsSync, readFileSync } from 'fs'
48
46
  import { parse } from 'yaml'
49
- import { logger } from '../utils/logger.js'
50
-
47
+ import { generate as generateVersionWorkflow } from '../templates/actions/calculate-version.yml.tpl.js'
48
+ import { generate as generateCreatePRWorkflow } from '../templates/actions/create-pr.yml.tpl.js'
49
+ import { generate as generateReleaseWorkflow } from '../templates/actions/create-release.yml.tpl.js'
51
50
  // Import individual workflow templates
52
51
  import { generate as generateTagWorkflow } from '../templates/actions/create-tag.yml.tpl.js'
53
52
  import { generate as generateChangesWorkflow } from '../templates/actions/detect-changes.yml.tpl.js'
54
- import { generate as generateChangesNxWorkflow } from '../templates/actions/detect-changes-nx.yml.tpl.js'
55
- import { generate as generateVersionWorkflow } from '../templates/actions/calculate-version.yml.tpl.js'
56
- import { generate as generateCreatePRWorkflow } from '../templates/actions/create-pr.yml.tpl.js'
57
53
  import { generate as generateBranchWorkflow } from '../templates/actions/manage-branch.yml.tpl.js'
58
54
  import { generate as generatePromoteBranchWorkflow } from '../templates/actions/promote-branch.yml.tpl.js'
59
- import { generate as generateReleaseWorkflow } from '../templates/actions/create-release.yml.tpl.js'
55
+ import { generate as generateRunNxAffectedWorkflow } from '../templates/actions/run-nx-affected.yml.tpl.js'
56
+ import { generate as generateReleaseItConfig } from '../templates/release-it.cjs.tpl.js'
57
+ import { generate as generateEnforcePRTarget } from '../templates/workflows/enforce-pr-target.yml.tpl.js'
60
58
  import { generate as generatePathBasedPipeline } from '../templates/workflows/pipeline.yml.tpl.js'
61
59
  import { generate as generateNxPipeline } from '../templates/workflows/pipeline-nx.yml.tpl.js'
62
- import { generate as generateEnforcePRTarget } from '../templates/workflows/enforce-pr-target.yml.tpl.js'
63
60
  import { generate as generatePRTitleCheck } from '../templates/workflows/pr-title-check.yml.tpl.js'
64
- import { generate as generateReleaseItConfig } from '../templates/release-it.cjs.tpl.js'
61
+ import { PipecraftConfig } from '../types/index.js'
62
+ import { logger } from '../utils/logger.js'
65
63
 
66
64
  /**
67
65
  * Default fallback configuration when no config file exists.
68
66
  * Mirrors `defaultConfig` from init.tpl.ts to ensure consistent behavior.
69
- *
67
+ *
70
68
  * @const
71
69
  * @see {@link module:generators/init.tpl~defaultConfig}
72
70
  */
@@ -94,22 +92,22 @@ const defaultConfig = {
94
92
 
95
93
  /**
96
94
  * Workflows generator main entry point.
97
- *
95
+ *
98
96
  * Orchestrates the complete workflow generation process:
99
97
  * 1. Loads existing pipeline (if specified) for merging
100
98
  * 2. Merges configuration with defaults
101
99
  * 3. Generates all composite actions in parallel
102
100
  * 4. Generates the main pipeline workflow
103
- *
101
+ *
104
102
  * @param {PinionContext & { pipelinePath?: string, outputPipelinePath?: string, config?: any }} ctx - Extended Pinion context
105
103
  * @param {string} [ctx.pipelinePath] - Path to existing pipeline for incremental updates
106
104
  * @param {string} [ctx.outputPipelinePath] - Custom output path for pipeline (defaults to .github/workflows/pipeline.yml)
107
105
  * @param {PipecraftConfig} [ctx.config] - PipeCraft configuration (overrides defaults)
108
106
  * @returns {Promise<PinionContext>} Updated context after workflow generation
109
- *
107
+ *
110
108
  * @throws {Error} If workflow files cannot be written
111
109
  * @throws {Error} If existing pipeline cannot be parsed
112
- *
110
+ *
113
111
  * @example
114
112
  * ```typescript
115
113
  * // Initial generation with config
@@ -121,7 +119,7 @@ const defaultConfig = {
121
119
  * domains: { api: { paths: ['src/api/**'] } }
122
120
  * }
123
121
  * })
124
- *
122
+ *
125
123
  * // Update existing pipeline
126
124
  * await generate({
127
125
  * cwd: '/path/to/project',
@@ -129,18 +127,20 @@ const defaultConfig = {
129
127
  * config: updatedConfig
130
128
  * })
131
129
  * ```
132
- *
130
+ *
133
131
  * @note The generator creates 9 files:
134
132
  * - 1 main workflow (pipeline.yml)
135
- * - 7 composite actions (in .github/actions/)
133
+ * - 7 composite actions (in actions/)
136
134
  * - 1 release-it configuration (.release-it.cjs)
137
- *
135
+ *
138
136
  * All actions are generated in parallel for performance, followed by
139
137
  * the main pipeline which may reference the actions.
140
138
  */
141
- export const generate = (ctx: PinionContext & { pipelinePath?: string, outputPipelinePath?: string, config?: any }) =>
139
+ export const generate = (
140
+ ctx: PinionContext & { pipelinePath?: string; outputPipelinePath?: string; config?: any }
141
+ ) =>
142
142
  Promise.resolve(ctx)
143
- .then((ctx) => {
143
+ .then(ctx => {
144
144
  logger.info('🔧 Generating GitHub Actions...')
145
145
 
146
146
  // Load existing pipeline if provided
@@ -156,11 +156,20 @@ export const generate = (ctx: PinionContext & { pipelinePath?: string, outputPip
156
156
  }
157
157
  }
158
158
 
159
- return { ...ctx, ...defaultConfig, ...ctx.config, ...ctx, existingPipeline, existingPipelineContent, outputPipelinePath: ctx.outputPipelinePath }
159
+ return {
160
+ ...ctx,
161
+ ...defaultConfig,
162
+ ...ctx.config,
163
+ ...ctx,
164
+ existingPipeline,
165
+ existingPipelineContent,
166
+ outputPipelinePath: ctx.outputPipelinePath
167
+ }
160
168
  })
161
- .then((ctx) => {
169
+ .then(ctx => {
162
170
  // Generate individual GitHub Actions and release-it config
163
171
  const generators = [
172
+ // Unified detect-changes action works for all stacks
164
173
  generateChangesWorkflow(ctx),
165
174
  generateTagWorkflow(ctx),
166
175
  generateVersionWorkflow(ctx),
@@ -173,12 +182,12 @@ export const generate = (ctx: PinionContext & { pipelinePath?: string, outputPip
173
182
 
174
183
  // Add Nx-specific action if Nx is enabled
175
184
  if (ctx.config?.nx?.enabled) {
176
- generators.push(generateChangesNxWorkflow(ctx))
185
+ generators.push(generateRunNxAffectedWorkflow(ctx))
177
186
  }
178
187
 
179
188
  return Promise.all(generators).then(() => ctx)
180
189
  })
181
- .then((ctx) => {
190
+ .then(ctx => {
182
191
  // Generate the main pipeline (Nx or path-based)
183
192
  if (ctx.config?.nx?.enabled) {
184
193
  logger.info('🔧 Generating Nx-optimized pipeline...')
@@ -187,15 +196,15 @@ export const generate = (ctx: PinionContext & { pipelinePath?: string, outputPip
187
196
  return generatePathBasedPipeline(ctx)
188
197
  }
189
198
  })
190
- .then((ctx) => {
199
+ .then(ctx => {
191
200
  // Generate the enforce PR target workflow
192
201
  return generateEnforcePRTarget(ctx)
193
202
  })
194
- .then((ctx) => {
203
+ .then(ctx => {
195
204
  // Generate the PR title check workflow
196
205
  return generatePRTitleCheck(ctx)
197
206
  })
198
- .then((ctx) => {
207
+ .then(ctx => {
199
208
  logger.success('✅ Generated workflows in: .github/workflows')
200
209
  return ctx
201
- })
210
+ })
@@ -1,32 +0,0 @@
1
- /**
2
- * Detect Changes Nx Action Template
3
- *
4
- * Generates a GitHub composite action that detects which domains have changed
5
- * using Nx dependency graph analysis with path-based fallback.
6
- *
7
- * ## Purpose
8
- *
9
- * For Nx monorepos, this action uses `nx show projects --affected` to leverage
10
- * the dependency graph for intelligent change detection. It maps affected Nx
11
- * projects to configured domains and falls back to path-based detection when
12
- * Nx is unavailable.
13
- *
14
- * ## Generated Action Location
15
- *
16
- * `.github/actions/detect-changes-nx/action.yml`
17
- *
18
- * @module templates/actions/detect-changes-nx.yml.tpl
19
- */
20
- import { PinionContext } from '@featherscloud/pinion';
21
- /**
22
- * Generator entry point for detect-changes-nx composite action.
23
- *
24
- * Generates the `.github/actions/detect-changes-nx/action.yml` file with Nx-aware
25
- * change detection logic. This action is used by Nx-enabled pipelines.
26
- *
27
- * @param {PinionContext} ctx - Pinion generator context
28
- * @param {Record<string, DomainConfig>} ctx.domains - Domain configurations
29
- * @returns {Promise<PinionContext>} Updated context after file generation
30
- */
31
- export declare const generate: (ctx: PinionContext) => Promise<any>;
32
- //# sourceMappingURL=detect-changes-nx.yml.tpl.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"detect-changes-nx.yml.tpl.d.ts","sourceRoot":"","sources":["../../../src/templates/actions/detect-changes-nx.yml.tpl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,aAAa,EAA0B,MAAM,uBAAuB,CAAA;AAmN7E;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,aAAa,iBAU+D,CAAA"}
@@ -1,227 +0,0 @@
1
- /**
2
- * Detect Changes Nx Action Template
3
- *
4
- * Generates a GitHub composite action that detects which domains have changed
5
- * using Nx dependency graph analysis with path-based fallback.
6
- *
7
- * ## Purpose
8
- *
9
- * For Nx monorepos, this action uses `nx show projects --affected` to leverage
10
- * the dependency graph for intelligent change detection. It maps affected Nx
11
- * projects to configured domains and falls back to path-based detection when
12
- * Nx is unavailable.
13
- *
14
- * ## Generated Action Location
15
- *
16
- * `.github/actions/detect-changes-nx/action.yml`
17
- *
18
- * @module templates/actions/detect-changes-nx.yml.tpl
19
- */
20
- import { toFile, renderTemplate } from '@featherscloud/pinion';
21
- import fs from 'fs';
22
- import { logger } from '../../utils/logger.js';
23
- /**
24
- * Generates the detect-changes-nx composite action YAML content.
25
- *
26
- * Creates a GitHub Actions composite action that:
27
- * - Checks for Nx availability in the repository
28
- * - Uses `nx show projects --affected` for dependency graph analysis
29
- * - Maps affected projects to domains
30
- * - Falls back to path-based detection if Nx isn't available
31
- * - Outputs Nx-specific information (affected projects, Nx availability)
32
- *
33
- * @param {any} ctx - Context containing domains configuration
34
- * @param {Record<string, DomainConfig>} ctx.domains - Domain configurations with path patterns
35
- * @returns {string} YAML content for the composite action
36
- */
37
- const changesNxActionTemplate = (ctx) => {
38
- const domainOutputs = Object.entries(ctx.domains).map((entry) => {
39
- const [domainName, domainConfig] = entry;
40
- return ` ${domainName}:
41
- description: 'Whether ${domainName} domain has changes'
42
- value: \${{ steps.merge.outputs.${domainName} }}`;
43
- }).join('\n');
44
- const domainFilters = Object.entries(ctx.domains).map((entry) => {
45
- const [domainName, domainConfig] = entry;
46
- return ` ${domainName}:\n - '${domainConfig.paths.join("'\\n - '")}'`;
47
- }).join('\n');
48
- // Generate Nx pattern matching logic for each domain
49
- const domainNames = Object.keys(ctx.domains);
50
- const nxPatternMatching = domainNames.map(domainName => {
51
- return ` # Check ${domainName} domain patterns
52
- if echo "$project" | grep -qE "(${domainName}|$(echo "${domainName}" | sed 's/-/[-_]/g'))"; then
53
- ${domainName.toUpperCase()}_AFFECTED=true
54
- echo " ✅ $project matches ${domainName} domain"
55
- fi`;
56
- }).join('\n');
57
- const nxFlagInit = domainNames.map(domainName => ` ${domainName.toUpperCase()}_AFFECTED=false`).join('\n');
58
- const nxOutputs = domainNames.map(domainName => ` echo "${domainName}=$${domainName.toUpperCase()}_AFFECTED" >> $GITHUB_OUTPUT`).join('\n');
59
- const nxFallbackOutputs = domainNames.map(domainName => ` echo "${domainName}=false" >> $GITHUB_OUTPUT`).join('\n');
60
- const pathFilterOutputs = domainNames.map(domainName => ` echo "${domainName}=\${{ contains(steps.filter.outputs.changes, '${domainName}') }}" >> $GITHUB_OUTPUT`).join('\n');
61
- const nxMergeOutputs = domainNames.map(domainName => ` echo "${domainName}=\${{ steps.nx-filter.outputs.${domainName} }}" >> $GITHUB_OUTPUT`).join('\n');
62
- const resultEchoes = domainNames.map(domainName => ` echo " ${domainName}: \${{ steps.merge.outputs.${domainName} }}"`).join('\n');
63
- // Generate the composite action YAML
64
- return `name: 'Detect Changes with Nx Support'
65
- description: 'Enhanced change detection using Nx dependency graph with path-based fallback'
66
- author: 'Pipecraft'
67
-
68
- inputs:
69
- baseRef:
70
- description: 'Base reference to compare against'
71
- required: false
72
- default: 'main'
73
- useNx:
74
- description: 'Whether to use Nx dependency graph for change detection'
75
- required: false
76
- default: 'true'
77
-
78
- outputs:
79
- ${domainOutputs}
80
- nxAvailable:
81
- description: 'Whether Nx is available in the repository'
82
- value: \${{ steps.nx-check.outputs.available }}
83
- affectedProjects:
84
- description: 'Comma-separated list of affected Nx projects'
85
- value: \${{ steps.nx-filter.outputs.affectedProjects }}
86
-
87
- runs:
88
- using: 'composite'
89
- steps:
90
- - name: Checkout Code
91
- uses: actions/checkout@v4
92
- with:
93
- fetch-depth: 0
94
-
95
- - name: Set Base Branch
96
- id: set-base
97
- shell: bash
98
- run: |
99
- base_branch=\${{ inputs.baseRef || 'main' }}
100
- echo "base_branch=$base_branch" >> $GITHUB_OUTPUT
101
- echo "base_branch=$base_branch" >> $GITHUB_ENV
102
-
103
- - name: Check for Nx
104
- id: nx-check
105
- shell: bash
106
- run: |
107
- if [ -f "nx.json" ] || ([ -f "package.json" ] && grep -q '"nx"' package.json); then
108
- echo "available=true" >> $GITHUB_OUTPUT
109
- echo "🔍 Nx detected in repository"
110
- else
111
- echo "available=false" >> $GITHUB_OUTPUT
112
- echo "⚠️ Nx not detected, falling back to path-based detection"
113
- fi
114
-
115
- - name: Setup Node.js
116
- if: steps.nx-check.outputs.available == 'true' && inputs.useNx == 'true'
117
- uses: actions/setup-node@v4
118
- with:
119
- node-version: '20'
120
-
121
- - name: Install Dependencies
122
- if: steps.nx-check.outputs.available == 'true' && inputs.useNx == 'true'
123
- shell: bash
124
- run: |
125
- echo "📦 Installing dependencies for Nx..."
126
- if [ -f "pnpm-lock.yaml" ]; then
127
- corepack enable
128
- pnpm install --frozen-lockfile || pnpm install
129
- elif [ -f "yarn.lock" ]; then
130
- yarn install --frozen-lockfile || yarn install
131
- elif [ -f "package-lock.json" ]; then
132
- npm ci || npm install
133
- else
134
- npm install
135
- fi
136
-
137
- - name: Detect Changes with Nx (if available)
138
- id: nx-filter
139
- if: steps.nx-check.outputs.available == 'true' && inputs.useNx == 'true'
140
- shell: bash
141
- run: |
142
- echo "🚀 Using Nx dependency graph for change detection"
143
-
144
- # Get affected projects using Nx
145
- if command -v npx >/dev/null 2>&1; then
146
- # Get list of affected projects (newline-separated)
147
- AFFECTED_PROJECTS_RAW=$(npx nx show projects --affected --base=\${{ steps.set-base.outputs.base_branch }} 2>/dev/null || echo "")
148
-
149
- # Convert newlines to commas for storage
150
- AFFECTED_PROJECTS=$(echo "$AFFECTED_PROJECTS_RAW" | tr '\\n' ',' | sed 's/,$//')
151
- echo "affectedProjects=$AFFECTED_PROJECTS" >> $GITHUB_OUTPUT
152
-
153
- if [ -n "$AFFECTED_PROJECTS" ]; then
154
- echo "📦 Affected Nx projects: $AFFECTED_PROJECTS"
155
-
156
- # Initialize domain flags
157
- ${nxFlagInit}
158
-
159
- # Check each affected project against domain patterns (comma-separated)
160
- IFS=',' read -ra PROJECTS <<< "$AFFECTED_PROJECTS"
161
- for project in "\${PROJECTS[@]}"; do
162
- project=$(echo "$project" | xargs) # trim whitespace
163
- ${nxPatternMatching}
164
- done
165
-
166
- # Set outputs
167
- ${nxOutputs}
168
-
169
- else
170
- echo "No affected projects detected"
171
- ${nxFallbackOutputs}
172
- fi
173
- else
174
- echo "⚠️ npx not available, falling back to path-based detection"
175
- ${nxFallbackOutputs}
176
- echo "affectedProjects=" >> $GITHUB_OUTPUT
177
- fi
178
-
179
- - name: Detect Changes with Paths Filter (fallback)
180
- uses: dorny/paths-filter@v3
181
- id: filter
182
- if: steps.nx-check.outputs.available != 'true' || inputs.useNx != 'true'
183
- with:
184
- base: \${{ steps.set-base.outputs.base_branch }}
185
- filters: |
186
- ${domainFilters}
187
-
188
- - name: Merge filter outputs
189
- id: merge
190
- shell: bash
191
- run: |
192
- # Use Nx results if available, otherwise use path filter results
193
- if [ "\${{ steps.nx-check.outputs.available }}" == "true" ] && [ "\${{ inputs.useNx }}" == "true" ]; then
194
- ${nxMergeOutputs}
195
- echo "🔍 Using Nx dependency analysis results"
196
- echo "📦 Affected projects: \${{ steps.nx-filter.outputs.affectedProjects }}"
197
- else
198
- ${pathFilterOutputs}
199
- echo "📁 Using path-based change detection"
200
- fi
201
-
202
- echo "📋 Change Detection Results:"
203
- ${resultEchoes}
204
- echo " nx-available: \${{ steps.nx-check.outputs.available }}"
205
- `;
206
- };
207
- /**
208
- * Generator entry point for detect-changes-nx composite action.
209
- *
210
- * Generates the `.github/actions/detect-changes-nx/action.yml` file with Nx-aware
211
- * change detection logic. This action is used by Nx-enabled pipelines.
212
- *
213
- * @param {PinionContext} ctx - Pinion generator context
214
- * @param {Record<string, DomainConfig>} ctx.domains - Domain configurations
215
- * @returns {Promise<PinionContext>} Updated context after file generation
216
- */
217
- export const generate = (ctx) => Promise.resolve(ctx)
218
- .then((ctx) => {
219
- // Check if file exists to determine merge status
220
- const filePath = '.github/actions/detect-changes-nx/action.yml';
221
- const exists = fs.existsSync(filePath);
222
- const status = exists ? '🔄 Merged with existing' : '📝 Created new';
223
- logger.verbose(`${status} ${filePath}`);
224
- return ctx;
225
- })
226
- .then(renderTemplate(changesNxActionTemplate, toFile('.github/actions/detect-changes-nx/action.yml')));
227
- //# sourceMappingURL=detect-changes-nx.yml.tpl.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"detect-changes-nx.yml.tpl.js","sourceRoot":"","sources":["../../../src/templates/actions/detect-changes-nx.yml.tpl.ts"],"names":[],"mappings":"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"}
@@ -1,187 +0,0 @@
1
- /**
2
- * Idempotency and Caching Utilities
3
- *
4
- * This module implements intelligent caching to avoid unnecessary workflow regeneration.
5
- * It tracks file hashes and modification times for:
6
- * - Configuration files (.pipecraftrc.json)
7
- * - Template source files (src/templates/)
8
- * - Generator code (src/generators/)
9
- *
10
- * Workflows are only regenerated when relevant source files have changed,
11
- * significantly improving performance during iterative development.
12
- *
13
- * The cache is stored in `.pipecraft-cache.json` and contains hashes
14
- * of all tracked files along with metadata.
15
- *
16
- * @module utils/idempotency
17
- */
18
- import { PipecraftConfig } from '../types/index.js';
19
- /**
20
- * Metadata and hash information for a tracked file.
21
- */
22
- export interface FileHash {
23
- /** Relative path to the file */
24
- path: string;
25
- /** Cryptographic hash of file content and metadata */
26
- hash: string;
27
- /** Last modification time (Unix timestamp in milliseconds) */
28
- mtime: number;
29
- /** File size in bytes */
30
- size: number;
31
- }
32
- /**
33
- * Complete rebuild cache structure stored in .pipecraft-cache.json.
34
- *
35
- * This cache enables idempotent workflow generation by tracking when
36
- * configuration or template files change.
37
- */
38
- export interface RebuildCache {
39
- /** Map of file paths to their hash metadata */
40
- files: Record<string, FileHash>;
41
- /** Hash of the configuration file (.pipecraftrc.json) */
42
- configHash: string;
43
- /** Timestamp when workflows were last generated */
44
- lastGenerated: number;
45
- /** Cache format version for future migration compatibility */
46
- version: string;
47
- }
48
- /**
49
- * Manager for idempotent workflow generation through intelligent caching.
50
- *
51
- * This class tracks changes to configuration and template files to determine
52
- * when workflows need to be regenerated. It creates cryptographic hashes of
53
- * files and directories to detect changes efficiently.
54
- *
55
- * @example
56
- * ```typescript
57
- * const manager = new IdempotencyManager(config)
58
- *
59
- * if (await manager.hasChanges()) {
60
- * // Generate workflows
61
- * await generateWorkflows()
62
- * // Update cache after successful generation
63
- * await manager.updateCache()
64
- * } else {
65
- * console.log('No changes detected, skipping regeneration')
66
- * }
67
- * ```
68
- */
69
- export declare class IdempotencyManager {
70
- private cacheFile;
71
- private config;
72
- /**
73
- * Create a new IdempotencyManager instance.
74
- *
75
- * @param config - PipeCraft configuration object
76
- * @param cacheFile - Path to cache file (default: .pipecraft-cache.json)
77
- */
78
- constructor(config: PipecraftConfig, cacheFile?: string);
79
- /**
80
- * Calculate cryptographic hash for a file or directory.
81
- *
82
- * For files: Hashes content + mtime + size to detect any changes
83
- * For directories: Recursively hashes all files within
84
- *
85
- * Returns empty string if the path doesn't exist.
86
- *
87
- * @param filePath - Path to file or directory to hash
88
- * @param algorithm - Hashing algorithm (default: sha256)
89
- * @returns Hex-encoded hash string
90
- *
91
- * @example
92
- * ```typescript
93
- * const configHash = await manager.calculateHash('.pipecraftrc.json')
94
- * const templateHash = await manager.calculateHash('src/templates')
95
- * ```
96
- */
97
- calculateHash(filePath: string, algorithm?: string): Promise<string>;
98
- /**
99
- * Hash directory contents recursively.
100
- *
101
- * Traverses all files in the directory tree and combines their hashes
102
- * along with metadata into a single directory hash. This allows detecting
103
- * when any file within the directory structure has changed.
104
- *
105
- * @param dirPath - Path to directory to hash
106
- * @param algorithm - Hashing algorithm
107
- * @returns Hex-encoded hash string representing the entire directory
108
- * @private
109
- */
110
- private hashDirectory;
111
- /**
112
- * Load rebuild cache from filesystem.
113
- *
114
- * Reads and parses the cache file containing previous generation state.
115
- * Returns null if cache doesn't exist or can't be parsed.
116
- *
117
- * @returns Parsed cache object or null if unavailable
118
- */
119
- loadCache(): RebuildCache | null;
120
- /**
121
- * Save rebuild cache to filesystem.
122
- *
123
- * Writes the cache object to disk as formatted JSON.
124
- * Failures are logged as warnings but don't throw errors.
125
- *
126
- * @param cache - Cache object to persist
127
- */
128
- saveCache(cache: RebuildCache): void;
129
- /**
130
- * Check if any tracked files have changed since last generation.
131
- *
132
- * Compares current file hashes against cached values to detect changes in:
133
- * - Configuration file (.pipecraftrc.json)
134
- * - Template source files (src/templates/)
135
- * - Generator code (src/generators/)
136
- *
137
- * Returns true if:
138
- * - Rebuild is disabled (always regenerate)
139
- * - Force regenerate flag is set
140
- * - No cache exists
141
- * - Any tracked file has changed
142
- *
143
- * @returns true if workflows should be regenerated
144
- *
145
- * @example
146
- * ```typescript
147
- * if (await manager.hasChanges()) {
148
- * console.log('Changes detected, regenerating workflows...')
149
- * await generateWorkflows()
150
- * await manager.updateCache()
151
- * }
152
- * ```
153
- */
154
- hasChanges(): Promise<boolean>;
155
- /**
156
- * Update cache with current file states after successful generation.
157
- *
158
- * Should be called after workflows are successfully generated to save
159
- * the current state of all tracked files. This allows future runs to
160
- * skip regeneration if nothing has changed.
161
- *
162
- * @example
163
- * ```typescript
164
- * await generateWorkflows()
165
- * await manager.updateCache() // Save current state
166
- * ```
167
- */
168
- updateCache(): Promise<void>;
169
- /**
170
- * Check if a specific file should be regenerated.
171
- *
172
- * Similar to hasChanges() but checks a single file instead of the
173
- * entire project. Useful for selective regeneration.
174
- *
175
- * @param filePath - Path to file to check
176
- * @returns true if file should be regenerated
177
- *
178
- * @example
179
- * ```typescript
180
- * if (await manager.shouldRegenerateFile('.github/workflows/pipeline.yml')) {
181
- * await generatePipeline()
182
- * }
183
- * ```
184
- */
185
- shouldRegenerateFile(filePath: string): Promise<boolean>;
186
- }
187
- //# sourceMappingURL=idempotency.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../../src/utils/idempotency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAEnD;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAA;IAEZ,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAA;IAEZ,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAA;IAEb,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAE/B,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAA;IAElB,mDAAmD;IACnD,aAAa,EAAE,MAAM,CAAA;IAErB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,MAAM,CAAiB;IAE/B;;;;;OAKG;gBACS,MAAM,EAAE,eAAe,EAAE,SAAS,SAA0B;IAKxE;;;;;;;;;;;;;;;;;OAiBG;IACG,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,GAAE,MAAiB,GAAG,OAAO,CAAC,MAAM,CAAC;IAqBpF;;;;;;;;;;;OAWG;YACW,aAAa;IA4B3B;;;;;;;OAOG;IACH,SAAS,IAAI,YAAY,GAAG,IAAI;IAchC;;;;;;;OAOG;IACH,SAAS,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAQpC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IA2CpC;;;;;;;;;;;;OAYG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAiClC;;;;;;;;;;;;;;;OAeG;IACG,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAuB/D"}