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
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * @module templates/release-it.cjs.tpl
14
14
  */
15
- import { toFile, renderTemplate } from '@featherscloud/pinion';
15
+ import { renderTemplate, toFile } from '@featherscloud/pinion';
16
16
  import { logger } from '../utils/logger.js';
17
17
  /**
18
18
  * Generate release-it configuration content
@@ -26,32 +26,30 @@ import { logger } from '../utils/logger.js';
26
26
  * @returns {string} JavaScript configuration content
27
27
  */
28
28
  const releaseItTemplate = (ctx) => {
29
- const bumpRules = ctx.semver?.bumpRules || {
30
- feat: 'minor',
29
+ // Base default prefixes for conventional commits
30
+ const baseDefaults = {
31
+ test: 'ignore',
32
+ build: 'ignore',
33
+ ci: 'patch',
34
+ docs: 'patch',
35
+ chore: 'minor',
36
+ style: 'patch',
31
37
  fix: 'patch',
32
- breaking: 'major'
38
+ perf: 'patch',
39
+ refactor: 'patch',
40
+ feat: 'minor',
41
+ major: 'major'
33
42
  };
34
- // Convert bump rules to release-it format
35
- const releaseItTypes = Object.entries(bumpRules).map(([type, level]) => {
36
- const releaseItLevel = level === 'major' ? 'major' :
37
- level === 'minor' ? 'minor' :
38
- level === 'patch' ? 'patch' : 'ignore';
39
- return ` ${type}: '${releaseItLevel}'`;
40
- }).join(',\n');
43
+ // Get user-defined bump rules from config
44
+ const userBumpRules = ctx.config?.semver?.bumpRules || ctx.semver?.bumpRules || {};
45
+ // Merge user rules with defaults (user rules take precedence)
46
+ const mergedRules = { ...baseDefaults, ...userBumpRules };
47
+ // Convert to formatted string for the config file
48
+ const rulesEntries = Object.entries(mergedRules)
49
+ .map(([type, level]) => ` ${type}: '${level}'`)
50
+ .join(',\n');
41
51
  return `const DEFAULT_PREFIXES = {
42
- test: 'ignore',
43
- build: 'ignore',
44
-
45
- ci: 'patch',
46
- docs: 'patch',
47
- chore: 'minor',
48
- style: 'patch',
49
- fix: 'patch',
50
- perf: 'patch',
51
- refactor: 'patch',
52
-
53
- feat: 'minor',
54
- major: 'major',
52
+ ${rulesEntries}
55
53
  }
56
54
 
57
55
  module.exports = {
@@ -137,12 +135,12 @@ module.exports = {
137
135
  * ```
138
136
  */
139
137
  export const generate = (ctx) => Promise.resolve(ctx)
140
- .then((ctx) => {
138
+ .then(ctx => {
141
139
  logger.verbose('📝 Generating release-it configuration...');
142
140
  return ctx;
143
141
  })
144
142
  .then(renderTemplate((ctx) => releaseItTemplate(ctx), toFile('.release-it.cjs')))
145
- .then((ctx) => {
143
+ .then(ctx => {
146
144
  logger.verbose('✅ Generated .release-it.cjs');
147
145
  return ctx;
148
146
  });
@@ -1 +1 @@
1
- {"version":3,"file":"release-it.cjs.tpl.js","sourceRoot":"","sources":["../../src/templates/release-it.cjs.tpl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAiB,MAAM,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAC7E,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAE3C;;;;;;;;;;GAUG;AACH,MAAM,iBAAiB,GAAG,CAAC,GAAQ,EAAE,EAAE;IACrC,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,IAAI;QACzC,IAAI,EAAE,OAAO;QACb,GAAG,EAAE,OAAO;QACZ,QAAQ,EAAE,OAAO;KAClB,CAAA;IAED,0CAA0C;IAC1C,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE;QACrE,MAAM,cAAc,GAAG,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YAC9B,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;gBAC7B,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAA;QAC5D,OAAO,iBAAiB,IAAI,MAAM,cAAc,GAAG,CAAA;IACrD,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAEd,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAwEL,CAAA;AACJ,CAAC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,GAAkB,EAAE,EAAE,CAC7C,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;KACjB,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;IACZ,MAAM,CAAC,OAAO,CAAC,2CAA2C,CAAC,CAAA;IAC3D,OAAO,GAAG,CAAA;AACZ,CAAC,CAAC;KACD,IAAI,CAAC,cAAc,CAClB,CAAC,GAAQ,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,EACpC,MAAM,CAAC,iBAAiB,CAAC,CAC1B,CAAC;KACD,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;IACZ,MAAM,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAA;IAC7C,OAAO,GAAG,CAAA;AACZ,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"release-it.cjs.tpl.js","sourceRoot":"","sources":["../../src/templates/release-it.cjs.tpl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAsB,cAAc,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAClF,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAE3C;;;;;;;;;;GAUG;AACH,MAAM,iBAAiB,GAAG,CAAC,GAAQ,EAAE,EAAE;IACrC,iDAAiD;IACjD,MAAM,YAAY,GAA2B;QAC3C,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,QAAQ;QACf,EAAE,EAAE,OAAO;QACX,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,OAAO;QACd,KAAK,EAAE,OAAO;QACd,GAAG,EAAE,OAAO;QACZ,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,OAAO;KACf,CAAA;IAED,0CAA0C;IAC1C,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,IAAI,GAAG,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,CAAA;IAElF,8DAA8D;IAC9D,MAAM,WAAW,GAAG,EAAE,GAAG,YAAY,EAAE,GAAG,aAAa,EAAE,CAAA;IAEzD,kDAAkD;IAClD,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC;SAC7C,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,MAAM,KAAK,GAAG,CAAC;SAC/C,IAAI,CAAC,KAAK,CAAC,CAAA;IAEd,OAAO;EACP,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA2DV,CAAA;AACJ,CAAC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,GAAkB,EAAE,EAAE,CAC7C,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;KACjB,IAAI,CAAC,GAAG,CAAC,EAAE;IACV,MAAM,CAAC,OAAO,CAAC,2CAA2C,CAAC,CAAA;IAC3D,OAAO,GAAG,CAAA;AACZ,CAAC,CAAC;KACD,IAAI,CAAC,cAAc,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC;KACrF,IAAI,CAAC,GAAG,CAAC,EAAE;IACV,MAAM,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAA;IAC7C,OAAO,GAAG,CAAA;AACZ,CAAC,CAAC,CAAA"}
@@ -26,7 +26,7 @@
26
26
  * })
27
27
  * ```
28
28
  */
29
- import { PinionContext } from '@featherscloud/pinion';
29
+ import { type PinionContext } from '@featherscloud/pinion';
30
30
  /**
31
31
  * Generates the enforce-pr-target.yml workflow file.
32
32
  *
@@ -1 +1 @@
1
- {"version":3,"file":"enforce-pr-target.yml.tpl.d.ts","sourceRoot":"","sources":["../../../src/templates/workflows/enforce-pr-target.yml.tpl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,aAAa,EAA0B,MAAM,uBAAuB,CAAA;AAE7E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,aAAa,iBAmCrC,CAAA"}
1
+ {"version":3,"file":"enforce-pr-target.yml.tpl.d.ts","sourceRoot":"","sources":["../../../src/templates/workflows/enforce-pr-target.yml.tpl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,KAAK,aAAa,EAA0B,MAAM,uBAAuB,CAAA;AAElF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,aAAa,iBAiCxC,CAAA"}
@@ -26,7 +26,7 @@
26
26
  * })
27
27
  * ```
28
28
  */
29
- import { toFile, renderTemplate } from '@featherscloud/pinion';
29
+ import { renderTemplate, toFile } from '@featherscloud/pinion';
30
30
  /**
31
31
  * Generates the enforce-pr-target.yml workflow file.
32
32
  *
@@ -49,8 +49,7 @@ import { toFile, renderTemplate } from '@featherscloud/pinion';
49
49
  * // Creates: .github/workflows/enforce-pr-target.yml
50
50
  * ```
51
51
  */
52
- export const generate = (ctx) => Promise.resolve(ctx)
53
- .then(renderTemplate((ctx) => {
52
+ export const generate = (ctx) => Promise.resolve(ctx).then(renderTemplate((ctx) => {
54
53
  const { initialBranch = 'develop', finalBranch = 'main' } = ctx;
55
54
  return `name: Enforce PR Target Branch
56
55
 
@@ -1 +1 @@
1
- {"version":3,"file":"enforce-pr-target.yml.tpl.js","sourceRoot":"","sources":["../../../src/templates/workflows/enforce-pr-target.yml.tpl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAiB,MAAM,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE7E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,GAAkB,EAAE,EAAE,CAC7C,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;KACjB,IAAI,CAAC,cAAc,CAClB,CAAC,GAAQ,EAAE,EAAE;IACX,MAAM,EAAE,aAAa,GAAG,SAAS,EAAE,WAAW,GAAG,MAAM,EAAE,GAAG,GAAG,CAAA;IAE/D,OAAO;;;;;;;;;;;kCAWmB,WAAW;;sDAES,aAAa,kBAAkB,WAAW;6DACnC,aAAa;;;;;8CAK5B,WAAW,SAAS,aAAa;;;;kCAI7C,aAAa;;0CAEL,aAAa;CACtD,CAAA;AACK,CAAC,EACD,MAAM,CAAC,yCAAyC,CAAC,CAClD,CAAC,CAAA"}
1
+ {"version":3,"file":"enforce-pr-target.yml.tpl.js","sourceRoot":"","sources":["../../../src/templates/workflows/enforce-pr-target.yml.tpl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAsB,cAAc,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAElF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,GAAkB,EAAE,EAAE,CAC7C,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CACvB,cAAc,CAAC,CAAC,GAAQ,EAAE,EAAE;IAC1B,MAAM,EAAE,aAAa,GAAG,SAAS,EAAE,WAAW,GAAG,MAAM,EAAE,GAAG,GAAG,CAAA;IAE/D,OAAO;;;;;;;;;;;kCAWqB,WAAW;;sDAES,aAAa,kBAAkB,WAAW;6DACnC,aAAa;;;;;8CAK5B,WAAW,SAAS,aAAa;;;;kCAI7C,aAAa;;0CAEL,aAAa;CACtD,CAAA;AACG,CAAC,EAAE,MAAM,CAAC,yCAAyC,CAAC,CAAC,CACtD,CAAA"}
@@ -7,8 +7,8 @@
7
7
  *
8
8
  * This is Option 1 (Sequential Strategy) - simple, fast, and leverages Nx's intelligence.
9
9
  */
10
- import { PinionContext } from '@featherscloud/pinion';
11
- import { PipecraftConfig } from '../../types/index.js';
10
+ import { type PinionContext } from '@featherscloud/pinion';
11
+ import type { PipecraftConfig } from '../../types/index.js';
12
12
  interface NxPipelineContext extends PinionContext {
13
13
  config: PipecraftConfig;
14
14
  branchFlow: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline-nx.yml.tpl.d.ts","sourceRoot":"","sources":["../../../src/templates/workflows/pipeline-nx.yml.tpl.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,aAAa,EAA0B,MAAM,uBAAuB,CAAA;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAiBtD,UAAU,iBAAkB,SAAQ,aAAa;IAC/C,MAAM,EAAE,eAAe,CAAA;IACvB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAqFD;;GAEG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,iBAAiB,iBAwH2F,CAAA"}
1
+ {"version":3,"file":"pipeline-nx.yml.tpl.d.ts","sourceRoot":"","sources":["../../../src/templates/workflows/pipeline-nx.yml.tpl.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,KAAK,aAAa,EAA0B,MAAM,uBAAuB,CAAA;AAGlF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAoB3D,UAAU,iBAAkB,SAAQ,aAAa;IAC/C,MAAM,EAAE,eAAe,CAAA;IACvB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAgHD;;GAEG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,iBAAiB,iBA2U1C,CAAA"}
@@ -8,86 +8,102 @@
8
8
  * This is Option 1 (Sequential Strategy) - simple, fast, and leverages Nx's intelligence.
9
9
  */
10
10
  import { renderTemplate, toFile } from '@featherscloud/pinion';
11
- import { parseDocument, stringify } from 'yaml';
12
11
  import fs from 'fs';
13
- import { logger } from '../../utils/logger.js';
12
+ import { parseDocument, Scalar, stringify } from 'yaml';
14
13
  import { applyPathOperations, createValueFromString } from '../../utils/ast-path-operations.js';
15
- import { createHeaderOperations, createChangesJobOperation, createDomainTestJobOperations, createDomainDeployJobOperations, createDomainRemoteTestJobOperations, createVersionJobOperation, createTagPromoteReleaseOperations, getDomainJobNames } from './shared/index.js';
14
+ import { logger } from '../../utils/logger.js';
16
15
  import { formatIfConditions } from '../yaml-format-utils.js';
16
+ import { createChangesJobOperation, createGateJobOperation, createHeaderOperations, createTagPromoteReleaseOperations, createVersionJobOperation, getDomainJobNames } from './shared/index.js';
17
+ /**
18
+ * Extract user-customized section between markers from YAML content
19
+ * Returns content WITHOUT the markers themselves
20
+ * Uses unique delimiters as YAML comments (indentation-independent)
21
+ */
22
+ function extractUserSection(yamlContent) {
23
+ // Match markers as YAML comments: any leading whitespace + one or more # + optional whitespace + marker
24
+ // Example: " ### <--START CUSTOM JOBS-->"
25
+ const startMarkerRegex = /^.*#+\s*<--START CUSTOM JOBS-->\s*$/m;
26
+ const endMarkerRegex = /^.*#+\s*<--END CUSTOM JOBS-->\s*$/m;
27
+ const startMatch = yamlContent.match(startMarkerRegex);
28
+ const endMatch = yamlContent.match(endMarkerRegex);
29
+ if (!startMatch || !endMatch) {
30
+ return null;
31
+ }
32
+ const startIndex = startMatch.index + startMatch[0].length;
33
+ const endIndex = endMatch.index;
34
+ // Extract content between markers (NOT including the markers themselves)
35
+ let extracted = yamlContent.substring(startIndex, endIndex);
36
+ // Normalize whitespace: remove leading/trailing newlines
37
+ // These will be added back consistently during insertion
38
+ extracted = extracted.replace(/^\n+/, '');
39
+ extracted = extracted.replace(/\n+$/, '');
40
+ return extracted;
41
+ }
17
42
  /**
18
- * Create the nx-ci job operation (Nx-specific)
43
+ * Create the test-nx job operation (Nx-specific)
19
44
  */
20
- function createNxCiJobOperation(ctx) {
45
+ function createTestNxJobOperation(ctx) {
21
46
  const { config } = ctx;
22
47
  const nxConfig = config.nx;
23
48
  const packageManager = config.packageManager || 'npm';
24
49
  const enableCache = nxConfig.enableCache !== false;
25
- const taskSteps = nxConfig.tasks
26
- .map(task => ` - name: Run Nx Affected - ${task}
27
- run: npx nx affected --target=${task} --base=\${{ inputs.baseRef || '${nxConfig.baseRef || 'origin/main'}' }}`)
28
- .join('\n');
50
+ const targets = nxConfig.tasks.join(',');
51
+ // Build the job steps
29
52
  const cacheStep = enableCache
30
53
  ? `
31
54
  - name: Cache Nx
32
55
  uses: actions/cache@v4
33
56
  with:
34
57
  path: .nx/cache
35
- # Cache key includes run_number to allow saving updated cache on each run
36
- # Nx cache accumulates computation results, so we want to save after every run
37
- # restore-keys ensure we start from the most recent cache on this branch
38
- key: nx-\${{ runner.os }}-\${{ hashFiles('package-lock.json') }}-\${{ github.ref_name }}-\${{ github.run_number }}
58
+ key: \${{ runner.os }}-nx-\${{ hashFiles('**/nx.json', '**/.nxignore', '**/package-lock.json', '**/pnpm-lock.yaml', '**/yarn.lock') }}
39
59
  restore-keys: |
40
- nx-\${{ runner.os }}-\${{ hashFiles('package-lock.json') }}-\${{ github.ref_name }}-
41
- nx-\${{ runner.os }}-\${{ hashFiles('package-lock.json') }}-
42
- nx-\${{ runner.os }}-
43
- `
60
+ \${{ runner.os }}-nx-`
44
61
  : '';
62
+ // Build individual task steps
63
+ const taskSteps = nxConfig.tasks
64
+ .map(target => `
65
+ - name: Run nx affected --target=${target}
66
+ run: |
67
+ npx nx affected --target=${target} --base=\${{ inputs.baseRef || '${nxConfig.baseRef || 'origin/main'}' }} || echo "No affected projects"`)
68
+ .join('');
45
69
  return {
46
- path: 'jobs.nx-ci',
47
- operation: 'overwrite',
70
+ path: 'jobs.test-nx',
71
+ operation: 'preserve', // User can customize this job!
48
72
  commentBefore: `
49
73
  =============================================================================
50
- NX CI - All Tasks Sequential (⚠️ Managed by Pipecraft - do not modify)
74
+ TEST-NX ( Customize these with your test logic)
51
75
  =============================================================================
52
- This job runs all Nx tasks sequentially using \`nx affected\`.
53
- Nx handles dependency detection, caching, and parallel execution internally.
76
+ This job runs all Nx tasks sequentially using \`nx affected\`.
77
+ Nx handles dependency detection, caching, and parallel execution internally.
54
78
  `,
55
79
  value: createValueFromString(`
56
- needs: changes
80
+ needs: [ changes ]
57
81
  runs-on: ubuntu-latest
58
82
  steps:
59
- - name: Checkout Code
60
- uses: actions/checkout@v4
83
+ - uses: actions/checkout@v4
61
84
  with:
62
85
  ref: \${{ inputs.commitSha || github.sha }}
63
- fetch-depth: 0
64
-
86
+ fetch-depth: \${{ env.FETCH_DEPTH_AFFECTED }}
65
87
  - name: Setup Node.js
66
88
  uses: actions/setup-node@v4
67
89
  with:
68
- node-version: '24'
69
-
70
- - name: Install Dependencies
71
- run: |
72
- case "${packageManager}" in
73
- pnpm)
74
- corepack enable
75
- pnpm install --frozen-lockfile || pnpm install
76
- ;;
77
- yarn)
78
- yarn install --frozen-lockfile || yarn install
79
- ;;
80
- npm)
81
- npm ci || npm install
82
- ;;
83
- *)
84
- npm install
85
- ;;
86
- esac
87
- env:
88
- packageManager: ${packageManager}
89
- ${cacheStep}
90
- ${taskSteps}
90
+ node-version: \${{ env.NODE_VERSION }}
91
+ ${packageManager === 'pnpm'
92
+ ? `
93
+ - name: Setup pnpm
94
+ uses: pnpm/action-setup@v4
95
+ with:
96
+ version: \${{ env.PNPM_VERSION }}
97
+ - name: Install dependencies
98
+ run: pnpm install --frozen-lockfile`
99
+ : packageManager === 'yarn'
100
+ ? `
101
+ - name: Install dependencies
102
+ run: yarn install --frozen-lockfile`
103
+ : `
104
+ - name: Install dependencies
105
+ run: npm ci`}
106
+ ${cacheStep}${taskSteps}
91
107
  `)
92
108
  };
93
109
  }
@@ -102,7 +118,7 @@ export const generate = (ctx) => Promise.resolve(ctx)
102
118
  const nxConfig = config.nx;
103
119
  // Get domain job names for dependency management
104
120
  const { testJobs, deployJobs, remoteTestJobs } = getDomainJobNames(domains);
105
- // Build operations array
121
+ // Build operations array - only managed jobs
106
122
  const operations = [
107
123
  // Header (name, run-name, on triggers)
108
124
  ...createHeaderOperations({ branchFlow }),
@@ -110,60 +126,202 @@ export const generate = (ctx) => Promise.resolve(ctx)
110
126
  createChangesJobOperation({
111
127
  domains,
112
128
  useNx: true,
113
- baseRef: nxConfig.baseRef || 'origin/main'
129
+ baseRef: nxConfig.baseRef || 'origin/main',
130
+ config
114
131
  }),
115
- // Nx CI job (Nx-specific!)
116
- createNxCiJobOperation(ctx),
117
- // Domain test jobs (preserved for user customization)
118
- ...createDomainTestJobOperations({ domains }),
119
- // Version calculation
132
+ // Nx test job (runs all Nx tasks)
133
+ createTestNxJobOperation(ctx),
134
+ // Version calculation (depends on test-nx and changes)
120
135
  createVersionJobOperation({
121
- testJobNames: testJobs,
122
- nxEnabled: true,
123
- baseRef: nxConfig.baseRef || 'origin/main'
136
+ testJobNames: [], // No domain test job dependencies
137
+ nxEnabled: true, // Depends on test-nx job
138
+ baseRef: nxConfig.baseRef || 'origin/main',
139
+ config
140
+ }),
141
+ // Gate job (runs after test-nx, gates downstream jobs)
142
+ createGateJobOperation({
143
+ testJobNames: ['test-nx'], // Gate on the Nx test job
144
+ deployJobNames: [] // Could add deploy jobs here if needed
124
145
  }),
125
- // Domain deploy jobs (preserved for user customization)
126
- ...createDomainDeployJobOperations({ domains }),
127
- // Domain remote test jobs (preserved for user customization)
128
- ...createDomainRemoteTestJobOperations({ domains }),
129
146
  // Tag, promote, release
130
147
  ...createTagPromoteReleaseOperations({
131
148
  branchFlow,
132
- deployJobNames: deployJobs,
133
- remoteTestJobNames: remoteTestJobs
149
+ deployJobNames: [], // No deployment dependencies in new model
150
+ remoteTestJobNames: [],
151
+ testJobNames: ['test-nx'], // For Nx, the single test-nx job is the gate
152
+ autoMerge: typeof config.autoMerge === 'object' ? config.autoMerge : {},
153
+ config
134
154
  })
135
155
  ];
136
156
  // Check if file exists
137
- if (!fs.existsSync(filePath)) {
138
- logger.verbose('📝 Creating new Nx pipeline');
157
+ const fileExists = fs.existsSync(filePath);
158
+ // Extract user-customized section and custom jobs from existing file if it exists
159
+ let userSection = null;
160
+ const customJobsFromExisting = [];
161
+ if (fileExists) {
162
+ const existingContent = fs.readFileSync(filePath, 'utf8');
163
+ userSection = extractUserSection(existingContent);
164
+ if (userSection) {
165
+ logger.verbose('📋 Found user-customized section between markers');
166
+ }
167
+ // Also extract custom jobs (for force mode preservation)
168
+ const existingDoc = parseDocument(existingContent);
169
+ const existingJobs = existingDoc.contents && existingDoc.contents.get
170
+ ? existingDoc.contents.get('jobs')
171
+ : null;
172
+ const managedJobs = new Set([
173
+ 'changes',
174
+ 'version',
175
+ 'gate',
176
+ 'tag',
177
+ 'promote',
178
+ 'release',
179
+ 'test-nx'
180
+ ]);
181
+ if (existingJobs && existingJobs.items) {
182
+ for (const pair of existingJobs.items) {
183
+ const keyStr = pair.key instanceof Scalar ? pair.key.value : pair.key;
184
+ if (!managedJobs.has(keyStr)) {
185
+ customJobsFromExisting.push(pair);
186
+ }
187
+ }
188
+ }
189
+ if (customJobsFromExisting.length > 0) {
190
+ logger.verbose(`📋 Found ${customJobsFromExisting.length} custom job(s) to preserve`);
191
+ }
192
+ }
193
+ // In force mode or new file, create fresh document to ensure correct structure
194
+ if (!fileExists || ctx.pinion?.force) {
195
+ const logMessage = !fileExists
196
+ ? '📝 Creating new Nx pipeline'
197
+ : '🔄 Force mode: Rebuilding Nx pipeline from scratch';
198
+ logger.verbose(logMessage);
139
199
  // Create new document with empty YAML map
140
200
  const doc = parseDocument('{}');
201
+ // Ensure the root map uses block style (not flow/compact)
202
+ if (doc.contents && doc.contents.flow !== undefined) {
203
+ ;
204
+ doc.contents.flow = false;
205
+ }
206
+ // Add header comment block to document
207
+ const headerComment = `=============================================================================
208
+ PIPECRAFT MANAGED WORKFLOW
209
+ =============================================================================
210
+
211
+ ✅ YOU CAN CUSTOMIZE:
212
+ - Custom jobs between the '# <--START CUSTOM JOBS-->' and '# <--END CUSTOM JOBS-->' comment markers
213
+ - Workflow name
214
+
215
+ ⚠️ PIPECRAFT MANAGES (do not modify):
216
+ - Workflow triggers, job dependencies, and conditionals
217
+ - Changes detection, version calculation, and tag creation
218
+ - CreatePR, branch management, promote, and release jobs
219
+
220
+ 📌 VERSION PROMOTION BEHAVIOR:
221
+ - Only commits that trigger a version bump will promote to staging/main
222
+ - Non-versioned commits (test, build, etc.) remain on develop
223
+ - This keeps staging/main aligned with tagged releases
224
+
225
+ Running 'pipecraft generate' updates managed sections while preserving
226
+ your customizations in test/deploy/smoke-test etc jobs.
227
+
228
+ 📖 Learn more: https://pipecraft.thecraftlab.dev
229
+ =============================================================================`;
230
+ doc.commentBefore = headerComment;
141
231
  if (doc.contents) {
142
232
  applyPathOperations(doc.contents, operations, doc);
143
233
  }
144
- const yamlContent = stringify(doc, { lineWidth: 0, minContentWidth: 0 });
234
+ let yamlContent = stringify(doc, {
235
+ lineWidth: 0, // 0 means no line width limit for scalars
236
+ indent: 2,
237
+ defaultStringType: 'PLAIN',
238
+ defaultKeyType: 'PLAIN',
239
+ minContentWidth: 0
240
+ });
241
+ // Insert user section and custom jobs after version job if they exist
242
+ const hasCustomContent = userSection || customJobsFromExisting.length > 0;
243
+ if (hasCustomContent) {
244
+ // Find the insertion point (after version job outputs)
245
+ const versionOutputsPattern = /^ {2}version:\s*\n(?:.*\n)*? {4}outputs:\s*\n\s*version:.*$/m;
246
+ const match = yamlContent.match(versionOutputsPattern);
247
+ if (match) {
248
+ const insertionIndex = match.index + match[0].length;
249
+ let contentToInsert = '';
250
+ // Add user section if exists
251
+ if (userSection) {
252
+ contentToInsert += `# <--START CUSTOM JOBS-->\n\n${userSection}\n\n # <--END CUSTOM JOBS-->`;
253
+ }
254
+ // Add custom jobs if they exist (and weren't in user section)
255
+ if (customJobsFromExisting.length > 0 && !userSection) {
256
+ const customJobsYaml = customJobsFromExisting
257
+ .map(pair => {
258
+ const keyStr = pair.key instanceof Scalar ? pair.key.value : pair.key;
259
+ const valueYaml = stringify(pair.value, { indent: 2 });
260
+ return ` ${keyStr}:\n${valueYaml
261
+ .split('\n')
262
+ .map((line) => (line ? ' ' + line : line))
263
+ .join('\n')}`;
264
+ })
265
+ .join('\n\n');
266
+ contentToInsert = `# <--START CUSTOM JOBS-->\n\n${customJobsYaml}\n\n # <--END CUSTOM JOBS-->`;
267
+ }
268
+ if (contentToInsert) {
269
+ yamlContent =
270
+ yamlContent.slice(0, insertionIndex) +
271
+ '\n\n ' +
272
+ contentToInsert +
273
+ '\n' +
274
+ yamlContent.slice(insertionIndex);
275
+ logger.verbose('📋 Inserted user-customized section after version job');
276
+ }
277
+ }
278
+ else {
279
+ logger.verbose('⚠️ Could not find version job outputs, appending user section at end');
280
+ const contentToInsert = userSection ||
281
+ (customJobsFromExisting.length > 0
282
+ ? customJobsFromExisting
283
+ .map(pair => {
284
+ const keyStr = pair.key instanceof Scalar ? pair.key.value : pair.key;
285
+ const valueYaml = stringify(pair.value, { indent: 2 });
286
+ return ` ${keyStr}:\n${valueYaml
287
+ .split('\n')
288
+ .map((line) => (line ? ' ' + line : line))
289
+ .join('\n')}`;
290
+ })
291
+ .join('\n\n')
292
+ : '');
293
+ const userSectionWithMarkers = `# <--START CUSTOM JOBS-->\n\n${contentToInsert}\n\n # <--END CUSTOM JOBS-->`;
294
+ yamlContent = yamlContent + '\n\n ' + userSectionWithMarkers;
295
+ }
296
+ }
297
+ else if (!fileExists) {
298
+ // For new files, add placeholder markers
299
+ const versionOutputsPattern = /^( {2}version:\s*\n(?:.*\n)*? {4}outputs:\s*\n\s*version:.*)$/m;
300
+ yamlContent = yamlContent.replace(versionOutputsPattern, '$1\n\n # <--START CUSTOM JOBS-->\n\n # <--END CUSTOM JOBS-->\n');
301
+ logger.verbose('📝 Added placeholder user section markers');
302
+ }
145
303
  const formattedContent = formatIfConditions(yamlContent);
146
- return { ...ctx, yamlContent: formattedContent, mergeStatus: 'created' };
304
+ const status = hasCustomContent ? 'merged' : fileExists ? 'rebuilt' : 'created';
305
+ return { ...ctx, yamlContent: formattedContent, mergeStatus: status };
147
306
  }
148
- // Parse existing file
307
+ // Parse existing file for merge mode (no force flag)
149
308
  const existingContent = fs.readFileSync(filePath, 'utf8');
150
309
  const doc = parseDocument(existingContent);
151
310
  // Get managed jobs (always overwritten)
152
- const managedJobs = new Set(['changes', 'nx-ci', 'version', 'tag', 'promote', 'release']);
153
- // Extract user jobs from existing (preserves comments!)
154
- const userJobs = new Map();
311
+ const managedJobs = new Set(['changes', 'version', 'tag', 'promote', 'release', 'test-nx']);
312
+ // test-nx is preserved (user can customize), but still managed if it doesn't exist
313
+ // Extract custom jobs (not managed and not in user section between markers)
155
314
  const existingJobs = doc.contents && doc.contents.get ? doc.contents.get('jobs') : null;
315
+ const customJobs = [];
156
316
  if (existingJobs && existingJobs.items) {
157
- for (const item of existingJobs.items) {
158
- const jobName = item.key?.toString();
159
- if (jobName && !managedJobs.has(jobName) && !testJobs.includes(jobName) && !deployJobs.includes(jobName) && !remoteTestJobs.includes(jobName)) {
160
- // This is a custom user job - preserve it completely
161
- userJobs.set(jobName, item);
317
+ // Preserve jobs that are not managed
318
+ for (const pair of existingJobs.items) {
319
+ const keyStr = pair.key instanceof Scalar ? pair.key.value : pair.key;
320
+ if (!managedJobs.has(keyStr)) {
321
+ customJobs.push(pair);
162
322
  }
163
323
  }
164
- }
165
- // Clear all jobs before applying operations to prevent duplicates
166
- if (existingJobs && existingJobs.items) {
324
+ // Clear all jobs before applying operations to prevent duplicates
167
325
  ;
168
326
  existingJobs.items = [];
169
327
  }
@@ -171,24 +329,73 @@ export const generate = (ctx) => Promise.resolve(ctx)
171
329
  if (doc.contents) {
172
330
  applyPathOperations(doc.contents, operations, doc);
173
331
  }
174
- // Add back user jobs (preserves their comments!)
175
- if (userJobs.size > 0) {
176
- logger.verbose(`📋 Preserving ${userJobs.size} user jobs: ${Array.from(userJobs.keys()).join(', ')}`);
177
- const jobsNode = doc.contents.get('jobs');
178
- if (jobsNode && jobsNode.items) {
179
- for (const [_, item] of userJobs) {
180
- jobsNode.items.push(item);
181
- }
332
+ // Reinsert custom jobs (preserved from existing pipeline)
333
+ if (existingJobs && customJobs.length > 0) {
334
+ for (const customPair of customJobs) {
335
+ existingJobs.add(customPair);
336
+ }
337
+ logger.verbose(`📋 Preserved ${customJobs.length} custom job(s) from existing pipeline`);
338
+ }
339
+ let yamlContent = stringify(doc, {
340
+ lineWidth: 0,
341
+ indent: 2,
342
+ defaultStringType: 'PLAIN',
343
+ defaultKeyType: 'PLAIN',
344
+ minContentWidth: 0
345
+ });
346
+ // Insert user section after version job if it exists
347
+ if (userSection) {
348
+ const versionOutputsPattern = /^ {2}version:\s*\n(?:.*\n)*? {4}outputs:\s*\n\s*version:.*$/m;
349
+ const match = yamlContent.match(versionOutputsPattern);
350
+ if (match) {
351
+ const insertionIndex = match.index + match[0].length;
352
+ const userSectionWithMarkers = `# <--START CUSTOM JOBS-->\n\n${userSection}\n\n # <--END CUSTOM JOBS-->`;
353
+ yamlContent =
354
+ yamlContent.slice(0, insertionIndex) +
355
+ '\n\n ' +
356
+ userSectionWithMarkers +
357
+ '\n' +
358
+ yamlContent.slice(insertionIndex);
359
+ logger.verbose('📋 Inserted user-customized section after version job');
360
+ }
361
+ }
362
+ // Also insert custom jobs that weren't in the user section
363
+ if (customJobs.length > 0 && !userSection) {
364
+ const versionOutputsPattern = /^ {2}version:\s*\n(?:.*\n)*? {4}outputs:\s*\n\s*version:.*$/m;
365
+ const match = yamlContent.match(versionOutputsPattern);
366
+ if (match) {
367
+ const insertionIndex = match.index + match[0].length;
368
+ const customJobsYaml = customJobs
369
+ .map(pair => {
370
+ const keyStr = pair.key instanceof Scalar ? pair.key.value : pair.key;
371
+ const valueYaml = stringify(pair.value, { indent: 2 });
372
+ return ` ${keyStr}:\n${valueYaml
373
+ .split('\n')
374
+ .map((line) => (line ? ' ' + line : line))
375
+ .join('\n')}`;
376
+ })
377
+ .join('\n\n');
378
+ yamlContent =
379
+ yamlContent.slice(0, insertionIndex) +
380
+ '\n\n # <--START CUSTOM JOBS-->\n\n' +
381
+ customJobsYaml +
382
+ '\n\n # <--END CUSTOM JOBS-->\n' +
383
+ yamlContent.slice(insertionIndex);
182
384
  }
183
385
  }
184
- const status = userJobs.size > 0 ? 'merged' : 'updated';
185
- const yamlContent = stringify(doc, { lineWidth: 0, minContentWidth: 0 });
386
+ const status = userSection || customJobs.length > 0 ? 'merged' : 'updated';
186
387
  const formattedContent = formatIfConditions(yamlContent);
187
388
  return { ...ctx, yamlContent: formattedContent, mergeStatus: status };
188
389
  })
189
390
  .then(ctx => {
190
391
  const outputPath = ctx.outputPipelinePath || '.github/workflows/pipeline.yml';
191
- const status = ctx.mergeStatus === 'merged' ? '🔄 Merged with existing' : ctx.mergeStatus === 'updated' ? '🔄 Updated existing' : '📝 Created new';
392
+ const status = ctx.mergeStatus === 'merged'
393
+ ? '🔄 Merged with existing'
394
+ : ctx.mergeStatus === 'updated'
395
+ ? '🔄 Updated existing'
396
+ : ctx.mergeStatus === 'rebuilt'
397
+ ? '🔄 Rebuilt from scratch'
398
+ : '📝 Created new';
192
399
  logger.verbose(`${status} ${outputPath}`);
193
400
  return ctx;
194
401
  })