primitive-admin 1.0.60 → 1.0.62

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 (310) hide show
  1. package/README.md +125 -69
  2. package/assets/skill/skills/primitive-platform/SKILL.md +336 -37
  3. package/dist/bin/primitive.js +29 -14
  4. package/dist/bin/primitive.js.map +1 -1
  5. package/dist/src/commands/admins.js +22 -8
  6. package/dist/src/commands/admins.js.map +1 -1
  7. package/dist/src/commands/analytics.js +96 -16
  8. package/dist/src/commands/analytics.js.map +1 -1
  9. package/dist/src/commands/apps.js +49 -188
  10. package/dist/src/commands/apps.js.map +1 -1
  11. package/dist/src/commands/blob-buckets.js +50 -155
  12. package/dist/src/commands/blob-buckets.js.map +1 -1
  13. package/dist/src/commands/collection-type-configs.js +14 -100
  14. package/dist/src/commands/collection-type-configs.js.map +1 -1
  15. package/dist/src/commands/collections.js +47 -8
  16. package/dist/src/commands/collections.js.map +1 -1
  17. package/dist/src/commands/config.d.ts +46 -0
  18. package/dist/src/commands/config.js +479 -0
  19. package/dist/src/commands/config.js.map +1 -0
  20. package/dist/src/commands/connections.d.ts +2 -0
  21. package/dist/src/commands/connections.js +100 -0
  22. package/dist/src/commands/connections.js.map +1 -0
  23. package/dist/src/commands/cron-triggers.js +84 -176
  24. package/dist/src/commands/cron-triggers.js.map +1 -1
  25. package/dist/src/commands/database-type-configs.d.ts +2 -0
  26. package/dist/src/commands/database-type-configs.js +171 -0
  27. package/dist/src/commands/database-type-configs.js.map +1 -0
  28. package/dist/src/commands/database-types.js +4 -4
  29. package/dist/src/commands/database-types.js.map +1 -1
  30. package/dist/src/commands/databases.d.ts +63 -0
  31. package/dist/src/commands/databases.js +649 -87
  32. package/dist/src/commands/databases.js.map +1 -1
  33. package/dist/src/commands/documents.js +869 -12
  34. package/dist/src/commands/documents.js.map +1 -1
  35. package/dist/src/commands/email-templates.js +18 -125
  36. package/dist/src/commands/email-templates.js.map +1 -1
  37. package/dist/src/commands/env.d.ts +12 -1
  38. package/dist/src/commands/env.js +98 -25
  39. package/dist/src/commands/env.js.map +1 -1
  40. package/dist/src/commands/feature-flags.d.ts +14 -0
  41. package/dist/src/commands/feature-flags.js +116 -0
  42. package/dist/src/commands/feature-flags.js.map +1 -0
  43. package/dist/src/commands/group-type-configs.js +14 -109
  44. package/dist/src/commands/group-type-configs.js.map +1 -1
  45. package/dist/src/commands/init.d.ts +9 -1
  46. package/dist/src/commands/init.js +1058 -279
  47. package/dist/src/commands/init.js.map +1 -1
  48. package/dist/src/commands/integrations.js +136 -421
  49. package/dist/src/commands/integrations.js.map +1 -1
  50. package/dist/src/commands/metadata-category-configs.d.ts +12 -0
  51. package/dist/src/commands/metadata-category-configs.js +112 -0
  52. package/dist/src/commands/metadata-category-configs.js.map +1 -0
  53. package/dist/src/commands/metadata.js +33 -141
  54. package/dist/src/commands/metadata.js.map +1 -1
  55. package/dist/src/commands/prompts.js +206 -563
  56. package/dist/src/commands/prompts.js.map +1 -1
  57. package/dist/src/commands/rule-sets.d.ts +1 -1
  58. package/dist/src/commands/rule-sets.js +16 -132
  59. package/dist/src/commands/rule-sets.js.map +1 -1
  60. package/dist/src/commands/scripts.js +67 -234
  61. package/dist/src/commands/scripts.js.map +1 -1
  62. package/dist/src/commands/sessions.d.ts +2 -0
  63. package/dist/src/commands/sessions.js +75 -0
  64. package/dist/src/commands/sessions.js.map +1 -0
  65. package/dist/src/commands/skill.js +2 -2
  66. package/dist/src/commands/skill.js.map +1 -1
  67. package/dist/src/commands/sync-app-settings.d.ts +113 -70
  68. package/dist/src/commands/sync-app-settings.js +193 -242
  69. package/dist/src/commands/sync-app-settings.js.map +1 -1
  70. package/dist/src/commands/sync.d.ts +1932 -49
  71. package/dist/src/commands/sync.js +9584 -1725
  72. package/dist/src/commands/sync.js.map +1 -1
  73. package/dist/src/commands/tokens.js +8 -2
  74. package/dist/src/commands/tokens.js.map +1 -1
  75. package/dist/src/commands/users.js +92 -1
  76. package/dist/src/commands/users.js.map +1 -1
  77. package/dist/src/commands/vars.js +44 -58
  78. package/dist/src/commands/vars.js.map +1 -1
  79. package/dist/src/commands/webhooks.js +318 -156
  80. package/dist/src/commands/webhooks.js.map +1 -1
  81. package/dist/src/commands/workflows.d.ts +49 -12
  82. package/dist/src/commands/workflows.js +652 -1043
  83. package/dist/src/commands/workflows.js.map +1 -1
  84. package/dist/src/lib/access-rule-display.d.ts +21 -0
  85. package/dist/src/lib/access-rule-display.js +34 -0
  86. package/dist/src/lib/access-rule-display.js.map +1 -0
  87. package/dist/src/lib/api-client.d.ts +630 -61
  88. package/dist/src/lib/api-client.js +676 -116
  89. package/dist/src/lib/api-client.js.map +1 -1
  90. package/dist/src/lib/app-settings-descriptor.d.ts +180 -25
  91. package/dist/src/lib/app-settings-descriptor.js +403 -82
  92. package/dist/src/lib/app-settings-descriptor.js.map +1 -1
  93. package/dist/src/lib/canonical-json.d.ts +12 -0
  94. package/dist/src/lib/canonical-json.js +35 -0
  95. package/dist/src/lib/canonical-json.js.map +1 -0
  96. package/dist/src/lib/cli-manifest.d.ts +8 -0
  97. package/dist/src/lib/cli-manifest.js +1 -0
  98. package/dist/src/lib/cli-manifest.js.map +1 -1
  99. package/dist/src/lib/codegen-shared/generatedFiles.d.ts +6 -2
  100. package/dist/src/lib/codegen-shared/generatedFiles.js +9 -2
  101. package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -1
  102. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +6 -6
  103. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +8 -8
  104. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -1
  105. package/dist/src/lib/config-object-descriptor.d.ts +127 -0
  106. package/dist/src/lib/config-object-descriptor.js +658 -0
  107. package/dist/src/lib/config-object-descriptor.js.map +1 -0
  108. package/dist/src/lib/config-payload.d.ts +85 -0
  109. package/dist/src/lib/config-payload.js +116 -0
  110. package/dist/src/lib/config-payload.js.map +1 -0
  111. package/dist/src/lib/config-surface.d.ts +130 -0
  112. package/dist/src/lib/config-surface.js +300 -0
  113. package/dist/src/lib/config-surface.js.map +1 -0
  114. package/dist/src/lib/credentials-store.d.ts +10 -0
  115. package/dist/src/lib/credentials-store.js +23 -0
  116. package/dist/src/lib/credentials-store.js.map +1 -1
  117. package/dist/src/lib/data-input.d.ts +23 -0
  118. package/dist/src/lib/data-input.js +50 -0
  119. package/dist/src/lib/data-input.js.map +1 -0
  120. package/dist/src/lib/db-codegen/dbTemplates.js +1 -1
  121. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
  122. package/dist/src/lib/db-codegen/dbTypeIR.d.ts +1 -1
  123. package/dist/src/lib/env-resolver-core.d.ts +147 -0
  124. package/dist/src/lib/env-resolver-core.js +265 -0
  125. package/dist/src/lib/env-resolver-core.js.map +1 -0
  126. package/dist/src/lib/env-resolver.d.ts +28 -6
  127. package/dist/src/lib/env-resolver.js +44 -32
  128. package/dist/src/lib/env-resolver.js.map +1 -1
  129. package/dist/src/lib/generated-allowlist.js +13 -0
  130. package/dist/src/lib/generated-allowlist.js.map +1 -1
  131. package/dist/src/lib/generated-config-surfaces.d.ts +682 -0
  132. package/dist/src/lib/generated-config-surfaces.js +4058 -0
  133. package/dist/src/lib/generated-config-surfaces.js.map +1 -0
  134. package/dist/src/lib/generated-template-lint.d.ts +212 -0
  135. package/dist/src/lib/generated-template-lint.js +624 -0
  136. package/dist/src/lib/generated-template-lint.js.map +1 -0
  137. package/dist/src/lib/init-adopt.d.ts +16 -0
  138. package/dist/src/lib/init-adopt.js +34 -0
  139. package/dist/src/lib/init-adopt.js.map +1 -0
  140. package/dist/src/lib/init-assets.d.ts +39 -0
  141. package/dist/src/lib/init-assets.js +97 -0
  142. package/dist/src/lib/init-assets.js.map +1 -0
  143. package/dist/src/lib/init-config.d.ts +44 -5
  144. package/dist/src/lib/init-config.js +80 -7
  145. package/dist/src/lib/init-config.js.map +1 -1
  146. package/dist/src/lib/init-ios-links.d.ts +50 -0
  147. package/dist/src/lib/init-ios-links.js +153 -0
  148. package/dist/src/lib/init-ios-links.js.map +1 -0
  149. package/dist/src/lib/init-plan.d.ts +80 -0
  150. package/dist/src/lib/init-plan.js +95 -0
  151. package/dist/src/lib/init-plan.js.map +1 -0
  152. package/dist/src/lib/init-production-env.d.ts +48 -0
  153. package/dist/src/lib/init-production-env.js +59 -0
  154. package/dist/src/lib/init-production-env.js.map +1 -0
  155. package/dist/src/lib/init-schema.d.ts +74 -0
  156. package/dist/src/lib/init-schema.js +358 -0
  157. package/dist/src/lib/init-schema.js.map +1 -0
  158. package/dist/src/lib/init-xcode.d.ts +33 -0
  159. package/dist/src/lib/init-xcode.js +114 -0
  160. package/dist/src/lib/init-xcode.js.map +1 -0
  161. package/dist/src/lib/integration-request-config.d.ts +30 -0
  162. package/dist/src/lib/integration-request-config.js +145 -0
  163. package/dist/src/lib/integration-request-config.js.map +1 -0
  164. package/dist/src/lib/local-state.d.ts +55 -0
  165. package/dist/src/lib/local-state.js +167 -0
  166. package/dist/src/lib/local-state.js.map +1 -0
  167. package/dist/src/lib/log-inspection.d.ts +568 -0
  168. package/dist/src/lib/log-inspection.js +639 -0
  169. package/dist/src/lib/log-inspection.js.map +1 -0
  170. package/dist/src/lib/migration-nag.d.ts +2 -2
  171. package/dist/src/lib/migration-nag.js +3 -3
  172. package/dist/src/lib/object-status-filter.d.ts +22 -0
  173. package/dist/src/lib/object-status-filter.js +45 -0
  174. package/dist/src/lib/object-status-filter.js.map +1 -0
  175. package/dist/src/lib/output.d.ts +24 -1
  176. package/dist/src/lib/output.js +42 -1
  177. package/dist/src/lib/output.js.map +1 -1
  178. package/dist/src/lib/package-manager.d.ts +140 -0
  179. package/dist/src/lib/package-manager.js +305 -0
  180. package/dist/src/lib/package-manager.js.map +1 -0
  181. package/dist/src/lib/paginate.d.ts +50 -0
  182. package/dist/src/lib/paginate.js +53 -0
  183. package/dist/src/lib/paginate.js.map +1 -1
  184. package/dist/src/lib/platform-owned.d.ts +29 -3
  185. package/dist/src/lib/platform-owned.js +42 -3
  186. package/dist/src/lib/platform-owned.js.map +1 -1
  187. package/dist/src/lib/project-config.js +13 -5
  188. package/dist/src/lib/project-config.js.map +1 -1
  189. package/dist/src/lib/query-operators.d.ts +1 -1
  190. package/dist/src/lib/query-operators.js +1 -1
  191. package/dist/src/lib/record-filter.d.ts +18 -0
  192. package/dist/src/lib/record-filter.js +55 -0
  193. package/dist/src/lib/record-filter.js.map +1 -0
  194. package/dist/src/lib/resolve-init-dev-port.d.ts +56 -0
  195. package/dist/src/lib/resolve-init-dev-port.js +55 -0
  196. package/dist/src/lib/resolve-init-dev-port.js.map +1 -0
  197. package/dist/src/lib/resolve-init-server.d.ts +64 -0
  198. package/dist/src/lib/resolve-init-server.js +77 -0
  199. package/dist/src/lib/resolve-init-server.js.map +1 -0
  200. package/dist/src/lib/resolve-platform.d.ts +43 -14
  201. package/dist/src/lib/resolve-platform.js +74 -12
  202. package/dist/src/lib/resolve-platform.js.map +1 -1
  203. package/dist/src/lib/run-status.d.ts +19 -0
  204. package/dist/src/lib/run-status.generated.d.ts +39 -0
  205. package/dist/src/lib/run-status.generated.js +66 -0
  206. package/dist/src/lib/run-status.generated.js.map +1 -0
  207. package/dist/src/lib/run-status.js +19 -0
  208. package/dist/src/lib/run-status.js.map +1 -0
  209. package/dist/src/lib/server-text-normalization.d.ts +51 -0
  210. package/dist/src/lib/server-text-normalization.js +90 -0
  211. package/dist/src/lib/server-text-normalization.js.map +1 -0
  212. package/dist/src/lib/server-url.d.ts +22 -0
  213. package/dist/src/lib/server-url.js +33 -0
  214. package/dist/src/lib/server-url.js.map +1 -0
  215. package/dist/src/lib/signing-secret-status.d.ts +81 -0
  216. package/dist/src/lib/signing-secret-status.js +116 -0
  217. package/dist/src/lib/signing-secret-status.js.map +1 -0
  218. package/dist/src/lib/skill-installer.d.ts +4 -2
  219. package/dist/src/lib/skill-installer.js +143 -12
  220. package/dist/src/lib/skill-installer.js.map +1 -1
  221. package/dist/src/lib/snapshots.d.ts +2 -2
  222. package/dist/src/lib/snapshots.js +2 -2
  223. package/dist/src/lib/swift-codegen/dbGenerator.d.ts +15 -2
  224. package/dist/src/lib/swift-codegen/dbGenerator.js +250 -18
  225. package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -1
  226. package/dist/src/lib/swift-codegen/generator.d.ts +11 -2
  227. package/dist/src/lib/swift-codegen/generator.js +181 -7
  228. package/dist/src/lib/swift-codegen/generator.js.map +1 -1
  229. package/dist/src/lib/swift-codegen/siblingSymbols.d.ts +7 -3
  230. package/dist/src/lib/swift-codegen/siblingSymbols.js +4 -1
  231. package/dist/src/lib/swift-codegen/siblingSymbols.js.map +1 -1
  232. package/dist/src/lib/sync-dir-selector.d.ts +21 -0
  233. package/dist/src/lib/sync-dir-selector.js +30 -0
  234. package/dist/src/lib/sync-dir-selector.js.map +1 -0
  235. package/dist/src/lib/sync-paths.d.ts +39 -0
  236. package/dist/src/lib/sync-paths.js +70 -2
  237. package/dist/src/lib/sync-paths.js.map +1 -1
  238. package/dist/src/lib/sync-resource-types.d.ts +138 -31
  239. package/dist/src/lib/sync-resource-types.js +241 -47
  240. package/dist/src/lib/sync-resource-types.js.map +1 -1
  241. package/dist/src/lib/sync-selectors.d.ts +95 -0
  242. package/dist/src/lib/sync-selectors.js +228 -0
  243. package/dist/src/lib/sync-selectors.js.map +1 -0
  244. package/dist/src/lib/template.d.ts +62 -4
  245. package/dist/src/lib/template.js +147 -39
  246. package/dist/src/lib/template.js.map +1 -1
  247. package/dist/src/lib/test-case-keys.d.ts +29 -0
  248. package/dist/src/lib/test-case-keys.js +55 -0
  249. package/dist/src/lib/test-case-keys.js.map +1 -0
  250. package/dist/src/lib/test-case-variables.d.ts +15 -0
  251. package/dist/src/lib/test-case-variables.js +29 -0
  252. package/dist/src/lib/test-case-variables.js.map +1 -0
  253. package/dist/src/lib/toml-database-config.d.ts +4 -4
  254. package/dist/src/lib/toml-database-config.js +27 -34
  255. package/dist/src/lib/toml-database-config.js.map +1 -1
  256. package/dist/src/lib/toml-metadata-config.d.ts +9 -5
  257. package/dist/src/lib/toml-metadata-config.js +40 -45
  258. package/dist/src/lib/toml-metadata-config.js.map +1 -1
  259. package/dist/src/lib/toml-native-form.d.ts +1 -1
  260. package/dist/src/lib/toml-native-form.js +1 -1
  261. package/dist/src/lib/toml-params-validator.d.ts +1 -1
  262. package/dist/src/lib/toml-params-validator.js +1 -1
  263. package/dist/src/lib/toml-scalar-edit.d.ts +43 -0
  264. package/dist/src/lib/toml-scalar-edit.js +283 -0
  265. package/dist/src/lib/toml-scalar-edit.js.map +1 -0
  266. package/dist/src/lib/user-selector.d.ts +24 -0
  267. package/dist/src/lib/user-selector.js +33 -0
  268. package/dist/src/lib/user-selector.js.map +1 -0
  269. package/dist/src/lib/version-check.d.ts +25 -0
  270. package/dist/src/lib/version-check.js +77 -8
  271. package/dist/src/lib/version-check.js.map +1 -1
  272. package/dist/src/lib/watch.d.ts +121 -0
  273. package/dist/src/lib/watch.js +169 -0
  274. package/dist/src/lib/watch.js.map +1 -0
  275. package/dist/src/lib/workflow-apply.d.ts +69 -45
  276. package/dist/src/lib/workflow-apply.js +99 -72
  277. package/dist/src/lib/workflow-apply.js.map +1 -1
  278. package/dist/src/lib/workflow-codegen/generator.js +4 -4
  279. package/dist/src/lib/workflow-codegen/generator.js.map +1 -1
  280. package/dist/src/lib/workflow-codegen/invokerIR.d.ts +11 -0
  281. package/dist/src/lib/workflow-codegen/invokerIR.js +5 -0
  282. package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -1
  283. package/dist/src/lib/workflow-config-apply.d.ts +70 -0
  284. package/dist/src/lib/workflow-config-apply.js +137 -0
  285. package/dist/src/lib/workflow-config-apply.js.map +1 -0
  286. package/dist/src/lib/workflow-config-sidecar.d.ts +63 -0
  287. package/dist/src/lib/workflow-config-sidecar.js +96 -0
  288. package/dist/src/lib/workflow-config-sidecar.js.map +1 -0
  289. package/dist/src/lib/workflow-defaults.d.ts +29 -0
  290. package/dist/src/lib/workflow-defaults.js +41 -0
  291. package/dist/src/lib/workflow-defaults.js.map +1 -0
  292. package/dist/src/lib/workflow-fragments.d.ts +23 -0
  293. package/dist/src/lib/workflow-fragments.js +229 -8
  294. package/dist/src/lib/workflow-fragments.js.map +1 -1
  295. package/dist/src/lib/workflow-include-preserve.d.ts +76 -0
  296. package/dist/src/lib/workflow-include-preserve.js +286 -0
  297. package/dist/src/lib/workflow-include-preserve.js.map +1 -0
  298. package/dist/src/lib/workflow-payload.d.ts +31 -20
  299. package/dist/src/lib/workflow-payload.js +100 -93
  300. package/dist/src/lib/workflow-payload.js.map +1 -1
  301. package/dist/src/lib/workflow-toml-validator.d.ts +80 -3
  302. package/dist/src/lib/workflow-toml-validator.js +231 -3
  303. package/dist/src/lib/workflow-toml-validator.js.map +1 -1
  304. package/dist/src/types/index.d.ts +78 -22
  305. package/dist/src/validators.d.ts +7 -7
  306. package/dist/src/validators.js +6 -6
  307. package/package.json +11 -6
  308. package/dist/src/commands/settings.d.ts +0 -15
  309. package/dist/src/commands/settings.js +0 -102
  310. package/dist/src/commands/settings.js.map +0 -1
@@ -14,6 +14,29 @@
14
14
  * - Unique step ids validated post-expansion; collisions name both
15
15
  * locations in the error message.
16
16
  *
17
+ * Parameterized includes (#1448):
18
+ * - Two include forms are supported. The bare form
19
+ * `include = ["name", ...]` (an array of fragment-name strings) is
20
+ * unchanged. The parameterized form uses `[[include]]` array-of-tables:
21
+ *
22
+ * [[include]]
23
+ * fragment = "lifecycle-email" # required
24
+ * runIf = "input.type == 'x'" # optional; ANDed onto each step
25
+ * with = { id = "...", subject = "..." } # params, exposed as {{ params.* }}
26
+ *
27
+ * - A file uses one form or the other — mixing string and table entries in a
28
+ * single `include` array is an error.
29
+ * - `{{ params.* }}` references inside the fragment's steps are substituted
30
+ * at EXPAND time from the include's `with` table. A whole-string
31
+ * `{{ params.x }}` splices the raw typed value (number/bool/table);
32
+ * an embedded `{{ params.x }}` inside a larger string stringifies it.
33
+ * A missing param is a hard error. Only the `params.` namespace is
34
+ * touched — every other `{{ ... }}` template passes through to the server
35
+ * verbatim, and `params.` never survives into the expanded output.
36
+ * - The include-level `runIf` (CEL) is ANDed onto every expanded step:
37
+ * both present → `(<includeRunIf>) && (<stepRunIf>)`; one present → that
38
+ * one; neither → no runIf.
39
+ *
17
40
  * Wiring: `parseTomlFile()` in `cli/src/commands/sync.ts` calls
18
41
  * `expandWorkflowTomlData()` after parsing. All 24 push parse sites in
19
42
  * sync.ts route through that single seam, so the expansion is uniform.
@@ -23,6 +46,22 @@
23
46
  import { existsSync, readFileSync } from "fs";
24
47
  import { dirname, join, basename } from "path";
25
48
  import { parseConfigToml } from "./config-toml.js";
49
+ /**
50
+ * True only for a genuine plain TOML table: a non-null, non-array object whose
51
+ * prototype is `Object.prototype` or `null`. This deliberately rejects the
52
+ * non-plain objects that a bare `typeof x === "object"` check would accept —
53
+ * notably smol-toml's `TomlDate` (a `Date` subclass) and any other boxed value
54
+ * — so a TOML date/time literal (`with = 1979-05-27T07:32:00Z`) can never slip
55
+ * through a guard that expects a parameter table. Used everywhere a `with` /
56
+ * params sub-table is required so the plain-table rule is applied consistently.
57
+ */
58
+ function isPlainTable(value) {
59
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
60
+ return false;
61
+ }
62
+ const proto = Object.getPrototypeOf(value);
63
+ return proto === Object.prototype || proto === null;
64
+ }
26
65
  /**
27
66
  * Read and expand a workflow TOML file from disk. Returns the parsed
28
67
  * TOML with all `include` fragments spliced in. If the file has no
@@ -58,16 +97,35 @@ export function expandWorkflowTomlData(parsed, workflowPath) {
58
97
  ? parsed.steps
59
98
  : [];
60
99
  const allOriginated = [];
61
- for (const fragmentName of includeList) {
62
- if (typeof fragmentName !== "string" || fragmentName.length === 0) {
63
- throw new Error(`Workflow '${workflowPath}' has an invalid include entry: ${JSON.stringify(fragmentName)}. Each entry must be a fragment name string.`);
100
+ // Classify each entry as a bare fragment-name string or a parameterized
101
+ // `[[include]]` table. A single file must use one form throughout. A
102
+ // `[[include]]` entry must be a genuine plain table a non-plain object
103
+ // (e.g. a TOML date literal parsed to a `TomlDate`) is neither a valid
104
+ // string entry nor a valid table and falls through to the per-entry error.
105
+ const isTable = (e) => isPlainTable(e);
106
+ const hasStringEntry = includeList.some((e) => typeof e === "string");
107
+ const hasTableEntry = includeList.some((e) => isTable(e));
108
+ if (hasStringEntry && hasTableEntry) {
109
+ throw new Error(`Workflow '${workflowPath}' mixes bare-string include entries and [[include]] tables in one 'include' array. Pick one form per file: either \`include = ["a", "b"]\` or repeated \`[[include]]\` tables.`);
110
+ }
111
+ includeList.forEach((entry, index) => {
112
+ if (typeof entry === "string") {
113
+ if (entry.length === 0) {
114
+ throw new Error(`Workflow '${workflowPath}' has an invalid include entry: ${JSON.stringify(entry)}. Each entry must be a fragment name string.`);
115
+ }
116
+ const fragmentPath = join(fragmentsDir, `${entry}.toml`);
117
+ const fragment = readFragmentSafely(fragmentPath, workflowPath, entry);
118
+ for (const step of fragment.steps) {
119
+ allOriginated.push({ step, origin: `fragment '${entry}'` });
120
+ }
121
+ return;
64
122
  }
65
- const fragmentPath = join(fragmentsDir, `${fragmentName}.toml`);
66
- const fragment = readFragmentSafely(fragmentPath, workflowPath, fragmentName);
67
- for (const step of fragment.steps) {
68
- allOriginated.push({ step, origin: `fragment '${fragmentName}'` });
123
+ if (isTable(entry)) {
124
+ expandParameterizedInclude(entry, index, fragmentsDir, workflowPath, allOriginated);
125
+ return;
69
126
  }
70
- }
127
+ throw new Error(`Workflow '${workflowPath}' has an invalid include entry: ${JSON.stringify(entry)}. Each entry must be a fragment name string or a [[include]] table.`);
128
+ });
71
129
  for (const step of workflowOwnSteps) {
72
130
  allOriginated.push({ step, origin: `workflow '${basename(workflowPath)}'` });
73
131
  }
@@ -103,6 +161,169 @@ function readFragmentSafely(fragmentPath, workflowPath, fragmentName) {
103
161
  : [];
104
162
  return { steps };
105
163
  }
164
+ // ── Parameterized include expansion (#1448) ─────────────────────────────────
165
+ const ALLOWED_INCLUDE_KEYS = new Set(["fragment", "runIf", "with"]);
166
+ // The dotted path after `params.`. Each segment is a TOML bare key, which may
167
+ // contain letters, digits, `_` and `-`; segments are joined by dots. Keeping
168
+ // the full bare-key charset means hyphenated keys like `subject-line` expand
169
+ // instead of leaking `{{ params.subject-line }}` to the server.
170
+ const PARAM_PATH = "[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*";
171
+ // A string that is EXACTLY one `{{ params.<path> }}` token (raw typed splice).
172
+ const WHOLE_PARAM_RE = new RegExp(`^\\{\\{\\s*params\\.(${PARAM_PATH})\\s*\\}\\}$`);
173
+ // Any embedded `{{ params.<path> }}` token (string interpolation).
174
+ const EMBEDDED_PARAM_RE = new RegExp(`\\{\\{\\s*params\\.(${PARAM_PATH})\\s*\\}\\}`, "g");
175
+ function expandParameterizedInclude(entry, index, fragmentsDir, workflowPath, allOriginated) {
176
+ const includeLabel = `[[include]] #${index + 1}`;
177
+ for (const key of Object.keys(entry)) {
178
+ if (!ALLOWED_INCLUDE_KEYS.has(key)) {
179
+ throw new Error(`Workflow '${workflowPath}' has an unknown key '${key}' in ${includeLabel}. Allowed keys are: fragment, runIf, with.`);
180
+ }
181
+ }
182
+ const fragmentName = entry.fragment;
183
+ if (typeof fragmentName !== "string" || fragmentName.length === 0) {
184
+ throw new Error(`Workflow '${workflowPath}' has a ${includeLabel} missing a valid 'fragment' name (got ${JSON.stringify(fragmentName)}).`);
185
+ }
186
+ const includeRunIf = entry.runIf;
187
+ if (includeRunIf !== undefined && typeof includeRunIf !== "string") {
188
+ throw new Error(`Workflow '${workflowPath}' has a non-string 'runIf' in ${includeLabel} (fragment '${fragmentName}'). runIf must be a CEL string.`);
189
+ }
190
+ const withParams = entry.with;
191
+ // `with` must be a genuine plain table. `isPlainTable` rejects arrays and any
192
+ // non-plain object — including a TOML date/time literal (`with = 1979-…Z`),
193
+ // which parses to a `TomlDate` and would otherwise be accepted as a param
194
+ // table by a bare `typeof === "object"` check.
195
+ if (withParams !== undefined && !isPlainTable(withParams)) {
196
+ throw new Error(`Workflow '${workflowPath}' has a non-table 'with' in ${includeLabel} (fragment '${fragmentName}'). 'with' must be a table of parameters.`);
197
+ }
198
+ const params = withParams ?? {};
199
+ const fragmentPath = join(fragmentsDir, `${fragmentName}.toml`);
200
+ const fragment = readFragmentSafely(fragmentPath, workflowPath, fragmentName);
201
+ const ctx = {
202
+ fragment: fragmentName,
203
+ includeLabel,
204
+ workflowPath,
205
+ };
206
+ for (const rawStep of fragment.steps) {
207
+ const step = substituteParamsDeep(rawStep, params, ctx);
208
+ applyIncludeRunIf(step, includeRunIf, ctx);
209
+ allOriginated.push({
210
+ step,
211
+ origin: `fragment '${fragmentName}' (${includeLabel})`,
212
+ });
213
+ }
214
+ }
215
+ /**
216
+ * Recursively substitute `{{ params.* }}` references throughout a fragment
217
+ * step. Returns a new value; the input is not mutated. Only the `params.`
218
+ * namespace is touched — every other `{{ ... }}` template is preserved
219
+ * verbatim for the server to render.
220
+ */
221
+ function substituteParamsDeep(node, params, ctx) {
222
+ if (typeof node === "string") {
223
+ return substituteParamsInString(node, params, ctx);
224
+ }
225
+ if (Array.isArray(node)) {
226
+ return node.map((el) => substituteParamsDeep(el, params, ctx));
227
+ }
228
+ if (node !== null && typeof node === "object") {
229
+ // Only recurse into plain tables. Non-plain objects (e.g. smol-toml's
230
+ // `TomlDate`, which extends `Date`) carry internal state that is not
231
+ // enumerable, so rebuilding them via `Object.entries` would collapse them
232
+ // to `{}`. TOML date/time literals can only appear as leaf param values —
233
+ // there is nothing to substitute inside them — so pass them through
234
+ // unchanged.
235
+ const proto = Object.getPrototypeOf(node);
236
+ if (proto !== Object.prototype && proto !== null) {
237
+ return node;
238
+ }
239
+ // Clone into a null-prototype object so a legal TOML key that collides with
240
+ // a prototype member — `__proto__`, `constructor`, `prototype` — is written
241
+ // as an ordinary own data property instead of tripping the `__proto__`
242
+ // setter (which would drop the field or reparent the clone). `Object.entries`
243
+ // reads only own enumerable keys, so no inherited members leak in either.
244
+ const out = Object.create(null);
245
+ for (const [k, v] of Object.entries(node)) {
246
+ out[k] = substituteParamsDeep(v, params, ctx);
247
+ }
248
+ return out;
249
+ }
250
+ return node;
251
+ }
252
+ function substituteParamsInString(value, params, ctx) {
253
+ // Whole-string `{{ params.x }}` → splice the raw typed value.
254
+ const whole = value.match(WHOLE_PARAM_RE);
255
+ if (whole) {
256
+ return resolveParamPath(whole[1], params, ctx);
257
+ }
258
+ // Embedded `{{ params.x }}` → stringify the resolved value in place.
259
+ return value.replace(EMBEDDED_PARAM_RE, (_match, path) => stringifyParamValue(resolveParamPath(path, params, ctx)));
260
+ }
261
+ /**
262
+ * Resolve a dotted `params.<path>` reference against the include's `with`
263
+ * table. A missing key (or missing sub-path) is a hard error naming the
264
+ * fragment, the param, and the include.
265
+ */
266
+ function resolveParamPath(path, params, ctx) {
267
+ const segments = path.split(".");
268
+ let cursor = params;
269
+ for (const segment of segments) {
270
+ // Resolve only OWN properties at each segment. Using `in` here would walk
271
+ // the prototype chain, so `{{ params.constructor }}`, `{{ params.toString }}`,
272
+ // `{{ params.__proto__ }}`, or `{{ params.hasOwnProperty }}` would resolve
273
+ // an inherited `Object.prototype` member instead of hard-erroring like any
274
+ // other missing param. `Object.hasOwn` restricts the lookup to real,
275
+ // author-declared keys.
276
+ if (cursor === null ||
277
+ typeof cursor !== "object" ||
278
+ Array.isArray(cursor) ||
279
+ !Object.hasOwn(cursor, segment)) {
280
+ throw new Error(`Fragment '${ctx.fragment}' references '{{ params.${path} }}' but no matching key exists in the ${ctx.includeLabel} 'with' table (workflow '${ctx.workflowPath}').`);
281
+ }
282
+ cursor = cursor[segment];
283
+ }
284
+ return cursor;
285
+ }
286
+ function stringifyParamValue(value) {
287
+ if (value === null || value === undefined)
288
+ return "";
289
+ if (typeof value === "object")
290
+ return JSON.stringify(value);
291
+ return String(value);
292
+ }
293
+ /**
294
+ * AND the include-level `runIf` onto a step's own `runIf`. Each operand is
295
+ * paren-wrapped because CEL's `&&` binds tighter than `||`. Mutates `step`.
296
+ */
297
+ function applyIncludeRunIf(step, includeRunIf, ctx) {
298
+ // A non-string step `runIf` (e.g. `runIf = 123`, or a param that spliced a
299
+ // non-string value in) must hard-error, consistent with the include-level
300
+ // non-string `runIf` rejection. Otherwise it would fall through the
301
+ // string-only guard below and be silently dropped, turning a guarded step
302
+ // into an unconditional one.
303
+ if ("runIf" in step &&
304
+ step.runIf !== undefined &&
305
+ typeof step.runIf !== "string") {
306
+ throw new Error(`Fragment '${ctx.fragment}' produced a non-string 'runIf' (got ${JSON.stringify(step.runIf)}) in ${ctx.includeLabel} (workflow '${ctx.workflowPath}'). runIf must be a CEL string.`);
307
+ }
308
+ const include = typeof includeRunIf === "string" && includeRunIf.length > 0
309
+ ? includeRunIf
310
+ : undefined;
311
+ const own = typeof step.runIf === "string" && step.runIf.length > 0
312
+ ? step.runIf
313
+ : undefined;
314
+ let combined;
315
+ if (include && own)
316
+ combined = `(${include}) && (${own})`;
317
+ else
318
+ combined = include ?? own;
319
+ if (combined === undefined) {
320
+ if ("runIf" in step)
321
+ delete step.runIf;
322
+ }
323
+ else {
324
+ step.runIf = combined;
325
+ }
326
+ }
106
327
  function assertUniqueStepIds(originated, workflowPath) {
107
328
  const seen = new Map();
108
329
  for (const { step, origin } of originated) {
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-fragments.js","sourceRoot":"","sources":["../../../src/lib/workflow-fragments.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workflow-fragments.js","sourceRoot":"","sources":["../../../src/lib/workflow-fragments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Keep `[[include]]` fragment structure alive across `config pull` (issue #2416).
3
+ *
4
+ * Fragments are a CLI-only authoring form: `expandWorkflowTomlData()` splices
5
+ * the referenced fragments into a flat `[[steps]]` list and the server only
6
+ * ever stores that expansion (see `workflow-fragments.ts`). `config pull`
7
+ * serializes the server's flat steps straight back to disk, so before this
8
+ * module a pull unconditionally overwrote a fragment-authored file with its
9
+ * own expansion — the `[[include]]` blocks were gone and had to be rebuilt by
10
+ * hand, even when `config diff` reported the file as synced.
11
+ *
12
+ * `reconcileWorkflowIncludes()` decides what pull should write for one
13
+ * workflow file. It only ever engages for a local file that actually carries
14
+ * an `include` key; everything else takes the unchanged "write what the server
15
+ * gave us" path.
16
+ *
17
+ * - **skip** — the local file expands to exactly what the server is running.
18
+ * There is nothing to apply, so leave the fragment source (and its
19
+ * comments and formatting) untouched. One thing still has to be applied to
20
+ * an otherwise-unchanged file: a retired inline `[[configs]]` list (#2733).
21
+ * Pull rewrites every other workflow file off it, and push rejects a file
22
+ * that still carries one, so a plain skip left the fragment source in a
23
+ * shape push refuses — pull → push of an untouched tree failed. Such a
24
+ * file takes the write-merged path with only the list removed.
25
+ * - **write-merged** — the server drifted, but its step list still *starts*
26
+ * with exactly the steps the local includes expand to. The drift is
27
+ * therefore expressible in fragment form: re-emit the file with the
28
+ * original `[[include]]` blocks and put the remaining server steps in the
29
+ * workflow's own `[[steps]]` list. Re-expanding that file reproduces the
30
+ * server's step list in order, so the next `config diff` reads as synced.
31
+ * - **write-expanded** — the fragment-derived prefix no longer matches what
32
+ * the server is running (a fragment's steps were edited or removed
33
+ * server-side), so no fragment-preserving file could reproduce it. Fall
34
+ * back to the historical behavior — write the fully expanded form — and
35
+ * say why, so the operator knows the fragment source was replaced rather
36
+ * than silently losing it.
37
+ *
38
+ * The order rule comes from the expander: it emits every fragment's steps
39
+ * first (in `include` order) and then the workflow's own `[[steps]]`. So a
40
+ * fragment-preserving file can only represent a server step list whose leading
41
+ * steps are exactly the fragment expansion. Server steps inserted *between*
42
+ * fragment steps are not representable and take the write-expanded path.
43
+ */
44
+ /**
45
+ * Hashes an already-parsed (and, for a local file, already-expanded) workflow
46
+ * TOML object to its canonical content hash. Injected by the caller so this
47
+ * module uses exactly the same normalization `config diff` does
48
+ * (`hashWorkflowTomlForDiff` in `commands/sync.ts`) without importing the
49
+ * command module back into `lib/`.
50
+ */
51
+ export type WorkflowTomlHasher = (parsed: any) => string;
52
+ export interface IncludeReconcileOutcome {
53
+ /**
54
+ * `write-expanded`: write the server's serialized TOML unchanged (the
55
+ * pre-#2416 behavior). `skip`: leave the local file alone. `write-merged`:
56
+ * write `content`, which preserves the local `[[include]]` blocks.
57
+ */
58
+ action: "write-expanded" | "skip" | "write-merged";
59
+ /** TOML to write. Only set when `action` is `write-merged`. */
60
+ content?: string;
61
+ /** Human-readable note for the pull log. Absent when there's nothing to say. */
62
+ message?: string;
63
+ }
64
+ export interface ReconcileWorkflowIncludesOptions {
65
+ /** Workflow key, used only for log messages. */
66
+ workflowKey: string;
67
+ /** Absolute path of the local workflow TOML (also resolves the fragments dir). */
68
+ localPath: string;
69
+ /** Current contents of that file, or `undefined` when it doesn't exist yet. */
70
+ localContent: string | undefined;
71
+ /** The TOML `config pull` would write, from `serializeWorkflow()`. */
72
+ remoteToml: string;
73
+ /** Canonical content hash for a parsed workflow TOML. */
74
+ hashParsedToml: WorkflowTomlHasher;
75
+ }
76
+ export declare function reconcileWorkflowIncludes(options: ReconcileWorkflowIncludesOptions): IncludeReconcileOutcome;
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Keep `[[include]]` fragment structure alive across `config pull` (issue #2416).
3
+ *
4
+ * Fragments are a CLI-only authoring form: `expandWorkflowTomlData()` splices
5
+ * the referenced fragments into a flat `[[steps]]` list and the server only
6
+ * ever stores that expansion (see `workflow-fragments.ts`). `config pull`
7
+ * serializes the server's flat steps straight back to disk, so before this
8
+ * module a pull unconditionally overwrote a fragment-authored file with its
9
+ * own expansion — the `[[include]]` blocks were gone and had to be rebuilt by
10
+ * hand, even when `config diff` reported the file as synced.
11
+ *
12
+ * `reconcileWorkflowIncludes()` decides what pull should write for one
13
+ * workflow file. It only ever engages for a local file that actually carries
14
+ * an `include` key; everything else takes the unchanged "write what the server
15
+ * gave us" path.
16
+ *
17
+ * - **skip** — the local file expands to exactly what the server is running.
18
+ * There is nothing to apply, so leave the fragment source (and its
19
+ * comments and formatting) untouched. One thing still has to be applied to
20
+ * an otherwise-unchanged file: a retired inline `[[configs]]` list (#2733).
21
+ * Pull rewrites every other workflow file off it, and push rejects a file
22
+ * that still carries one, so a plain skip left the fragment source in a
23
+ * shape push refuses — pull → push of an untouched tree failed. Such a
24
+ * file takes the write-merged path with only the list removed.
25
+ * - **write-merged** — the server drifted, but its step list still *starts*
26
+ * with exactly the steps the local includes expand to. The drift is
27
+ * therefore expressible in fragment form: re-emit the file with the
28
+ * original `[[include]]` blocks and put the remaining server steps in the
29
+ * workflow's own `[[steps]]` list. Re-expanding that file reproduces the
30
+ * server's step list in order, so the next `config diff` reads as synced.
31
+ * - **write-expanded** — the fragment-derived prefix no longer matches what
32
+ * the server is running (a fragment's steps were edited or removed
33
+ * server-side), so no fragment-preserving file could reproduce it. Fall
34
+ * back to the historical behavior — write the fully expanded form — and
35
+ * say why, so the operator knows the fragment source was replaced rather
36
+ * than silently losing it.
37
+ *
38
+ * The order rule comes from the expander: it emits every fragment's steps
39
+ * first (in `include` order) and then the workflow's own `[[steps]]`. So a
40
+ * fragment-preserving file can only represent a server step list whose leading
41
+ * steps are exactly the fragment expansion. Server steps inserted *between*
42
+ * fragment steps are not representable and take the write-expanded path.
43
+ */
44
+ import { parseConfigToml, stringifyConfigToml } from "./config-toml.js";
45
+ import { expandWorkflowTomlData } from "./workflow-fragments.js";
46
+ import { canonicalJsonStringify } from "./canonical-json.js";
47
+ export function reconcileWorkflowIncludes(options) {
48
+ const { workflowKey, localPath, localContent, remoteToml, hashParsedToml } = options;
49
+ const label = `workflows/${workflowKey}.toml`;
50
+ if (localContent === undefined) {
51
+ return { action: "write-expanded" };
52
+ }
53
+ let localParsed;
54
+ try {
55
+ localParsed = parseConfigToml(localContent);
56
+ }
57
+ catch {
58
+ // An unparseable local file carries no structure worth preserving — pull
59
+ // replaces it, exactly as it did before.
60
+ return { action: "write-expanded" };
61
+ }
62
+ if (localParsed === null ||
63
+ typeof localParsed !== "object" ||
64
+ !("include" in localParsed)) {
65
+ return { action: "write-expanded" };
66
+ }
67
+ let expandedLocal;
68
+ try {
69
+ expandedLocal = expandWorkflowTomlData(localParsed, localPath);
70
+ }
71
+ catch (err) {
72
+ return {
73
+ action: "write-expanded",
74
+ message: `${label}: could not expand its [[include]] fragments ` +
75
+ `(${err?.message ?? err}); wrote the fully expanded form from the server.`,
76
+ };
77
+ }
78
+ const remoteParsed = parseConfigToml(remoteToml);
79
+ // Nothing to apply: the fragment source already describes what's running —
80
+ // except for a retired inline `[[configs]]` list, which pull has to take out
81
+ // on its way past (#2733).
82
+ if (hashParsedToml(expandedLocal) === hashParsedToml(remoteParsed)) {
83
+ if (hasRetiredInlineConfigs(localParsed)) {
84
+ return {
85
+ action: "write-merged",
86
+ content: withoutRetiredInlineConfigs({
87
+ localContent,
88
+ localParsed,
89
+ localPath,
90
+ remoteHash: hashParsedToml(remoteParsed),
91
+ hashParsedToml,
92
+ }),
93
+ message: `${label}: removed the retired [[configs]] list (#2645) and kept ` +
94
+ `the local [[include]] fragment source. Named configurations are ` +
95
+ `authored one per file at workflows/${workflowKey}.configs/<name>.toml.`,
96
+ };
97
+ }
98
+ return {
99
+ action: "skip",
100
+ message: `${label}: unchanged — kept the local [[include]] fragment source.`,
101
+ };
102
+ }
103
+ const ownSteps = Array.isArray(localParsed.steps) ? localParsed.steps : [];
104
+ const expandedSteps = Array.isArray(expandedLocal.steps)
105
+ ? expandedLocal.steps
106
+ : [];
107
+ const remoteSteps = Array.isArray(remoteParsed.steps)
108
+ ? remoteParsed.steps
109
+ : [];
110
+ const fragmentCount = expandedSteps.length - ownSteps.length;
111
+ if (fragmentCount < 0) {
112
+ // Defensive: the expander always emits fragment steps plus own steps.
113
+ return { action: "write-expanded" };
114
+ }
115
+ const mismatch = findFragmentPrefixMismatch(expandedSteps.slice(0, fragmentCount), remoteSteps);
116
+ if (mismatch) {
117
+ return {
118
+ action: "write-expanded",
119
+ message: `${label}: the running workflow no longer starts with the steps its ` +
120
+ `[[include]] fragments expand to (${mismatch}), so the fragment ` +
121
+ `structure can't be preserved. Wrote the fully expanded form — ` +
122
+ `re-author the includes by hand if you want them back.`,
123
+ };
124
+ }
125
+ const remainingSteps = remoteSteps.slice(fragmentCount);
126
+ // Rebuild the file from the server's content with the local include blocks
127
+ // spliced back in. `workflow` first, then `include`, then the steps the
128
+ // fragments don't cover, then everything else the server serialized
129
+ // (`configs`, `[expr.cel]`, the declared-access manifest, ...).
130
+ const data = {};
131
+ if ("workflow" in remoteParsed) {
132
+ data.workflow = remoteParsed.workflow;
133
+ }
134
+ data.include = localParsed.include;
135
+ if (remainingSteps.length > 0) {
136
+ data.steps = remainingSteps;
137
+ }
138
+ for (const [key, value] of Object.entries(remoteParsed)) {
139
+ if (!(key in data) && key !== "steps") {
140
+ data[key] = value;
141
+ }
142
+ }
143
+ return {
144
+ action: "write-merged",
145
+ content: stringifyConfigToml(data),
146
+ message: `${label}: kept ${fragmentCount} step(s) from [[include]] fragments; ` +
147
+ `wrote ${remainingSteps.length} server step(s) the fragments don't cover.`,
148
+ };
149
+ }
150
+ /**
151
+ * Whether the local file carries the inline `[[configs]]` list #2645 retired.
152
+ * Matches the push validator's rule exactly (`validateRetiredInlineConfigs` in
153
+ * `workflow-toml-validator.ts`): a non-empty array at the document root. An
154
+ * empty one is not rejected by push, so pull has no reason to touch the file
155
+ * over it.
156
+ */
157
+ function hasRetiredInlineConfigs(localParsed) {
158
+ return Array.isArray(localParsed.configs) && localParsed.configs.length > 0;
159
+ }
160
+ /** A `[[configs]]` header line, the only form a retired list can be authored in as a table array. */
161
+ const RETIRED_CONFIGS_HEADER = /^\[\[\s*configs\s*\]\]\s*(#.*)?$/;
162
+ /** `[configs.x]` / `[[configs.x]]` — a subtable of the entry being removed. */
163
+ const RETIRED_CONFIGS_SUBTABLE = /^\[\[?\s*configs\s*\./;
164
+ /**
165
+ * The local file with its retired `[[configs]]` blocks taken out, preserving
166
+ * every other byte — comments, key order and formatting included. That matters
167
+ * here more than anywhere else in this module: the file is otherwise in sync,
168
+ * so re-emitting it would reformat an operator's hand-authored source for a
169
+ * change that only deletes a block nothing reads.
170
+ *
171
+ * Returns `undefined` when the list is not authored as `[[configs]]` blocks
172
+ * (an inline `configs = [ … ]` array, say), which the caller handles by
173
+ * re-emitting the parsed document instead.
174
+ */
175
+ function stripRetiredInlineConfigsText(content) {
176
+ const kept = [];
177
+ /**
178
+ * Blank and comment lines seen since the last line that definitely belongs to
179
+ * the block being removed. A comment sitting between the retired list and the
180
+ * next table almost always documents *that* table, so it is handed back when
181
+ * the table arrives instead of being deleted with the block; trivia followed
182
+ * by another config line documented the removed entry and is dropped.
183
+ */
184
+ let trivia = [];
185
+ let removing = false;
186
+ let removedAny = false;
187
+ /** Give the pending trivia back, from its first comment line onwards. */
188
+ const keepTrivia = () => {
189
+ const firstComment = trivia.findIndex((line) => line.trim().startsWith("#"));
190
+ // Blank-only trivia is just the separator the removed block left behind.
191
+ if (firstComment >= 0)
192
+ kept.push(...trivia.slice(firstComment));
193
+ trivia = [];
194
+ };
195
+ for (const line of content.split("\n")) {
196
+ const trimmed = line.trim();
197
+ if (RETIRED_CONFIGS_HEADER.test(trimmed)) {
198
+ trivia = [];
199
+ removing = true;
200
+ removedAny = true;
201
+ continue;
202
+ }
203
+ if (removing) {
204
+ // The block runs to the next top-level table header; a `configs.*`
205
+ // subtable belongs to the entry being removed and goes with it.
206
+ if (RETIRED_CONFIGS_SUBTABLE.test(trimmed)) {
207
+ trivia = [];
208
+ continue;
209
+ }
210
+ if (!trimmed.startsWith("[")) {
211
+ if (trimmed === "" || trimmed.startsWith("#"))
212
+ trivia.push(line);
213
+ else
214
+ trivia = [];
215
+ continue;
216
+ }
217
+ keepTrivia();
218
+ removing = false;
219
+ }
220
+ kept.push(line);
221
+ }
222
+ if (removing)
223
+ keepTrivia();
224
+ if (!removedAny)
225
+ return undefined;
226
+ const stripped = kept.join("\n");
227
+ // A block that ran to the end of the file takes the final newline with it.
228
+ return content.endsWith("\n") && !stripped.endsWith("\n")
229
+ ? `${stripped}\n`
230
+ : stripped;
231
+ }
232
+ /**
233
+ * What pull writes for an in-sync fragment source that still carries the
234
+ * retired list: the same file minus the `[[configs]]` blocks.
235
+ *
236
+ * The textual strip is verified before it is used — the result has to parse,
237
+ * carry no `configs` any more, and still expand to exactly the server's
238
+ * content hash. Anything else (an inline-array form, a shape the line scan
239
+ * misreads) falls back to re-emitting the parsed document without its
240
+ * `configs` key, which loses comments but is correct by construction.
241
+ */
242
+ function withoutRetiredInlineConfigs(options) {
243
+ const { localContent, localParsed, localPath, remoteHash, hashParsedToml } = options;
244
+ const stripped = stripRetiredInlineConfigsText(localContent);
245
+ if (stripped !== undefined) {
246
+ try {
247
+ const reparsed = parseConfigToml(stripped);
248
+ if (!hasRetiredInlineConfigs(reparsed) &&
249
+ hashParsedToml(expandWorkflowTomlData(reparsed, localPath)) ===
250
+ remoteHash) {
251
+ return stripped;
252
+ }
253
+ }
254
+ catch {
255
+ // Fall through to the re-emit below.
256
+ }
257
+ }
258
+ const { configs: _retired, ...rest } = localParsed;
259
+ return stringifyConfigToml(rest);
260
+ }
261
+ /**
262
+ * Returns a human-readable description of the first place the server's step
263
+ * list departs from the fragment-derived prefix, or `undefined` when the
264
+ * prefix matches exactly.
265
+ */
266
+ function findFragmentPrefixMismatch(fragmentSteps, remoteSteps) {
267
+ if (remoteSteps.length < fragmentSteps.length) {
268
+ return `the server has ${remoteSteps.length} step(s), fewer than the ${fragmentSteps.length} the fragments produce`;
269
+ }
270
+ for (let i = 0; i < fragmentSteps.length; i++) {
271
+ if (canonicalJsonStringify(fragmentSteps[i]) !==
272
+ canonicalJsonStringify(remoteSteps[i])) {
273
+ const expectedId = describeStepId(fragmentSteps[i]);
274
+ const actualId = describeStepId(remoteSteps[i]);
275
+ return expectedId === actualId
276
+ ? `step ${i + 1} '${expectedId}' differs`
277
+ : `step ${i + 1} is '${actualId}' where the fragments produce '${expectedId}'`;
278
+ }
279
+ }
280
+ return undefined;
281
+ }
282
+ function describeStepId(step) {
283
+ const id = step?.id;
284
+ return typeof id === "string" && id.length > 0 ? id : "<no id>";
285
+ }
286
+ //# sourceMappingURL=workflow-include-preserve.js.map