@nextcommerce/campaigns-os 1.33.0

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/AGENTS.md +204 -0
  2. package/CHANGELOG.md +5002 -0
  3. package/CONTEXT.md +685 -0
  4. package/LICENSE +202 -0
  5. package/NOTICE +4 -0
  6. package/README.md +368 -0
  7. package/agents/claude/CLAUDE.md +32 -0
  8. package/agents/codex/AGENTS.md +27 -0
  9. package/agents/copilot/copilot-instructions.md +14 -0
  10. package/agents/cursor/campaigns-os.mdc +13 -0
  11. package/bin/campaigns-os.mjs +38 -0
  12. package/campaign-spec/README.md +138 -0
  13. package/campaign-spec/dist/analytics-vocabulary.d.ts +47 -0
  14. package/campaign-spec/dist/analytics-vocabulary.js +74 -0
  15. package/campaign-spec/dist/index.d.ts +40 -0
  16. package/campaign-spec/dist/index.js +77 -0
  17. package/campaign-spec/dist/normalize.d.ts +22 -0
  18. package/campaign-spec/dist/normalize.js +41 -0
  19. package/campaign-spec/dist/routing.d.ts +190 -0
  20. package/campaign-spec/dist/routing.js +263 -0
  21. package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +42 -0
  22. package/campaign-spec/dist/rules/analytics-contract-shape.js +303 -0
  23. package/campaign-spec/dist/rules/assembly-hints-shape.d.ts +42 -0
  24. package/campaign-spec/dist/rules/assembly-hints-shape.js +191 -0
  25. package/campaign-spec/dist/rules/campaign-metadata.d.ts +14 -0
  26. package/campaign-spec/dist/rules/campaign-metadata.js +40 -0
  27. package/campaign-spec/dist/rules/checkout-has-success-url.d.ts +31 -0
  28. package/campaign-spec/dist/rules/checkout-has-success-url.js +66 -0
  29. package/campaign-spec/dist/rules/cycle-detection.d.ts +12 -0
  30. package/campaign-spec/dist/rules/cycle-detection.js +141 -0
  31. package/campaign-spec/dist/rules/design-source-shape.d.ts +29 -0
  32. package/campaign-spec/dist/rules/design-source-shape.js +142 -0
  33. package/campaign-spec/dist/rules/downsell-without-upsell.d.ts +11 -0
  34. package/campaign-spec/dist/rules/downsell-without-upsell.js +40 -0
  35. package/campaign-spec/dist/rules/exit-intent-validation.d.ts +23 -0
  36. package/campaign-spec/dist/rules/exit-intent-validation.js +147 -0
  37. package/campaign-spec/dist/rules/funnel-count.d.ts +9 -0
  38. package/campaign-spec/dist/rules/funnel-count.js +37 -0
  39. package/campaign-spec/dist/rules/funnel-hypothesis-length.d.ts +22 -0
  40. package/campaign-spec/dist/rules/funnel-hypothesis-length.js +60 -0
  41. package/campaign-spec/dist/rules/funnel-identity.d.ts +15 -0
  42. package/campaign-spec/dist/rules/funnel-identity.js +57 -0
  43. package/campaign-spec/dist/rules/funnel-weight-sum.d.ts +20 -0
  44. package/campaign-spec/dist/rules/funnel-weight-sum.js +66 -0
  45. package/campaign-spec/dist/rules/index.d.ts +69 -0
  46. package/campaign-spec/dist/rules/index.js +131 -0
  47. package/campaign-spec/dist/rules/offer-ref-integrity.d.ts +13 -0
  48. package/campaign-spec/dist/rules/offer-ref-integrity.js +59 -0
  49. package/campaign-spec/dist/rules/package-pricing-sanity.d.ts +12 -0
  50. package/campaign-spec/dist/rules/package-pricing-sanity.js +42 -0
  51. package/campaign-spec/dist/rules/page-count.d.ts +11 -0
  52. package/campaign-spec/dist/rules/page-count.js +31 -0
  53. package/campaign-spec/dist/rules/page-id-uniqueness.d.ts +14 -0
  54. package/campaign-spec/dist/rules/page-id-uniqueness.js +47 -0
  55. package/campaign-spec/dist/rules/promo-code-input-validation.d.ts +8 -0
  56. package/campaign-spec/dist/rules/promo-code-input-validation.js +126 -0
  57. package/campaign-spec/dist/rules/promo-codes-shape.d.ts +30 -0
  58. package/campaign-spec/dist/rules/promo-codes-shape.js +187 -0
  59. package/campaign-spec/dist/rules/route-field-ignored-for-page-type.d.ts +33 -0
  60. package/campaign-spec/dist/rules/route-field-ignored-for-page-type.js +81 -0
  61. package/campaign-spec/dist/rules/route-target-resolves.d.ts +32 -0
  62. package/campaign-spec/dist/rules/route-target-resolves.js +112 -0
  63. package/campaign-spec/dist/rules/schema-version.d.ts +21 -0
  64. package/campaign-spec/dist/rules/schema-version.js +53 -0
  65. package/campaign-spec/dist/rules/sdk-version.d.ts +26 -0
  66. package/campaign-spec/dist/rules/sdk-version.js +96 -0
  67. package/campaign-spec/dist/rules/shipping-countries-shape.d.ts +10 -0
  68. package/campaign-spec/dist/rules/shipping-countries-shape.js +30 -0
  69. package/campaign-spec/dist/rules/shipping-methods-present.d.ts +10 -0
  70. package/campaign-spec/dist/rules/shipping-methods-present.js +26 -0
  71. package/campaign-spec/dist/rules/store-profile-shape.d.ts +30 -0
  72. package/campaign-spec/dist/rules/store-profile-shape.js +127 -0
  73. package/campaign-spec/dist/rules/thank-you-requirement.d.ts +16 -0
  74. package/campaign-spec/dist/rules/thank-you-requirement.js +50 -0
  75. package/campaign-spec/dist/rules/unknown-top-level-fields.d.ts +28 -0
  76. package/campaign-spec/dist/rules/unknown-top-level-fields.js +114 -0
  77. package/campaign-spec/dist/rules/upsell-has-packages.d.ts +9 -0
  78. package/campaign-spec/dist/rules/upsell-has-packages.js +35 -0
  79. package/campaign-spec/dist/rules/upsell-routing-complete.d.ts +10 -0
  80. package/campaign-spec/dist/rules/upsell-routing-complete.js +45 -0
  81. package/campaign-spec/dist/rules/upsell-without-checkout.d.ts +11 -0
  82. package/campaign-spec/dist/rules/upsell-without-checkout.js +44 -0
  83. package/campaign-spec/dist/rules/variant-labels-shape.d.ts +28 -0
  84. package/campaign-spec/dist/rules/variant-labels-shape.js +86 -0
  85. package/campaign-spec/dist/sdk-version-parse.d.ts +43 -0
  86. package/campaign-spec/dist/sdk-version-parse.js +62 -0
  87. package/campaign-spec/dist/types.d.ts +674 -0
  88. package/campaign-spec/dist/types.js +40 -0
  89. package/campaign-spec/package.json +12 -0
  90. package/compatibility.json +25 -0
  91. package/contracts/agent-relevant-change-policy.v1.json +111 -0
  92. package/contracts/brand-theme-source-defaults.figma-sections-export.v0.json +32 -0
  93. package/contracts/brand-theme-target-tokens.next-core.v0.json +65 -0
  94. package/contracts/campaign-cart-checkout-field-contract.v0.json +45 -0
  95. package/contracts/campaign-cart-sdk-support-policy.v0.json +11 -0
  96. package/contracts/commerce-surface-catalog.json +2452 -0
  97. package/contracts/fixtures/orientation/canonicalization/v1.json +34 -0
  98. package/contracts/fixtures/orientation/envelope/current.json +95 -0
  99. package/contracts/fixtures/orientation/envelope/freshness_unknown.json +97 -0
  100. package/contracts/fixtures/orientation/envelope/legacy_baseline.json +95 -0
  101. package/contracts/fixtures/orientation/envelope/orientation_available.json +136 -0
  102. package/contracts/fixtures/orientation/envelope/recovered_interrupted_update.json +136 -0
  103. package/contracts/fixtures/orientation/envelope/refused.json +100 -0
  104. package/contracts/fixtures/orientation/envelope/restart_required.json +137 -0
  105. package/contracts/fixtures/orientation/envelope/updated.json +135 -0
  106. package/contracts/fixtures/orientation/hostile-target/README.md +61 -0
  107. package/contracts/fixtures/orientation/hostile-target/manifest.json +82 -0
  108. package/contracts/fixtures/orientation/hostile-target/repo/CHANGELOG.md +18 -0
  109. package/contracts/fixtures/orientation/hostile-target/repo/bin/intended.mjs +12 -0
  110. package/contracts/fixtures/orientation/hostile-target/repo/bin/tripwire.mjs +15 -0
  111. package/contracts/fixtures/orientation/hostile-target/repo/contracts/release-ledger.json +48 -0
  112. package/contracts/fixtures/orientation/hostile-target/repo/contracts/supported-surface.json +17 -0
  113. package/contracts/fixtures/orientation/hostile-target/repo/docs/example-contract.md +13 -0
  114. package/contracts/fixtures/orientation/hostile-target/repo/hooks/post-checkout +5 -0
  115. package/contracts/fixtures/orientation/hostile-target/repo/hooks/post-merge +5 -0
  116. package/contracts/fixtures/orientation/hostile-target/repo/hooks/pre-commit +5 -0
  117. package/contracts/fixtures/orientation/hostile-target/repo/hostile-dependency-tripwire/package.json +15 -0
  118. package/contracts/fixtures/orientation/hostile-target/repo/hostile-dependency-tripwire/tripwire.mjs +9 -0
  119. package/contracts/fixtures/orientation/hostile-target/repo/package.json +20 -0
  120. package/contracts/fixtures/orientation/hostile-target/repo/schemas/example.v0.schema.json +15 -0
  121. package/contracts/fixtures/orientation/release-gate/cases.json +1073 -0
  122. package/contracts/fixtures/runtime-recipe/accept/current.json +299 -0
  123. package/contracts/fixtures/runtime-recipe/accept/minimal.json +294 -0
  124. package/contracts/fixtures/runtime-recipe/dist-states.json +51 -0
  125. package/contracts/fixtures/runtime-recipe/manifest.json +85 -0
  126. package/contracts/fixtures/runtime-recipe/reject/advisory-enforcement.json +299 -0
  127. package/contracts/fixtures/runtime-recipe/reject/allowlist-without-hosts.json +297 -0
  128. package/contracts/fixtures/runtime-recipe/reject/committed-output-claim.json +299 -0
  129. package/contracts/fixtures/runtime-recipe/reject/engines-disagreement-warns.json +299 -0
  130. package/contracts/fixtures/runtime-recipe/reject/lifecycle-scripts-enabled.json +299 -0
  131. package/contracts/fixtures/runtime-recipe/reject/missing-required-field.json +251 -0
  132. package/contracts/fixtures/runtime-recipe/reject/unknown-kind.json +299 -0
  133. package/contracts/fixtures/runtime-recipe/reject/unknown-network-policy.json +299 -0
  134. package/contracts/fixtures/runtime-recipe/reject/unknown-output-check.json +310 -0
  135. package/contracts/fixtures/runtime-recipe/reject/unknown-revision.json +299 -0
  136. package/contracts/fixtures/runtime-recipe/reject/unknown-step-id.json +299 -0
  137. package/contracts/fixtures/runtime-recipe/reject/unperformable-check-skipped.json +299 -0
  138. package/contracts/fixtures/runtime-recipe/reject/unpinned-lockfile.json +299 -0
  139. package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/assembly-report.json +180 -0
  140. package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/build-context.json +115 -0
  141. package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/doctor-output.json +29 -0
  142. package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/qa-verdict.json +27 -0
  143. package/contracts/fixtures/sidecar-bundle/production-shaped/campaign-runtime.build.json +141 -0
  144. package/contracts/migration-sidecar-bundle.v0.json +149 -0
  145. package/contracts/orientation-limits.v1.json +41 -0
  146. package/contracts/orientation-reason-codes.v1.json +196 -0
  147. package/contracts/private-template-sources.json +8 -0
  148. package/contracts/release-ledger.json +4598 -0
  149. package/contracts/reserved-skill-names.json +13 -0
  150. package/contracts/runtime-recipe.campaigns-os-node-v1.json +299 -0
  151. package/contracts/supported-surface.json +180 -0
  152. package/contracts/template-brand-contract.apollo-mv-single-step.v0.json +27 -0
  153. package/contracts/template-brand-contract.apollo.v0.json +27 -0
  154. package/contracts/template-brand-contract.demeter.v0.json +27 -0
  155. package/contracts/template-brand-contract.olympus-mv-single-step.v0.json +27 -0
  156. package/contracts/template-brand-contract.olympus-mv-two-step.v0.json +28 -0
  157. package/contracts/template-brand-contract.olympus.v0.json +27 -0
  158. package/contracts/template-brand-contract.shared-commerce.v0.json +190 -0
  159. package/contracts/template-brand-contract.shop-single-step.v0.json +27 -0
  160. package/contracts/template-brand-contract.shop-three-step.v0.json +29 -0
  161. package/contracts/template-slot-manifest.apollo-mv-single-step.v0.json +14 -0
  162. package/contracts/template-slot-manifest.apollo.v0.json +12 -0
  163. package/contracts/template-slot-manifest.demeter.v0.json +30 -0
  164. package/contracts/template-slot-manifest.olympus-mv-single-step.v0.json +14 -0
  165. package/contracts/template-slot-manifest.olympus-mv-two-step.v0.json +15 -0
  166. package/contracts/template-slot-manifest.olympus.v0.json +12 -0
  167. package/contracts/template-slot-manifest.shared-content-core.v0.json +4254 -0
  168. package/contracts/template-slot-manifest.shop-single-step.v0.json +47 -0
  169. package/contracts/template-slot-manifest.shop-three-step.v0.json +33 -0
  170. package/docs/brand-theme-bridge.md +159 -0
  171. package/docs/build-packet.md +1300 -0
  172. package/docs/campaign-build-brief.md +145 -0
  173. package/docs/campaign-standardization-report.md +329 -0
  174. package/docs/campaigns-os-build-flow.md +117 -0
  175. package/docs/design-source-package.md +784 -0
  176. package/docs/legacy-migration.md +58 -0
  177. package/docs/migration-sidecar-bundle.md +139 -0
  178. package/docs/orientation-contract-reference.md +1220 -0
  179. package/docs/polish-evidence.md +502 -0
  180. package/docs/qa-and-test-orders.md +1691 -0
  181. package/docs/release-ledger-authoring-guide.md +274 -0
  182. package/docs/runtime-readiness.md +211 -0
  183. package/docs/supported-surface.md +83 -0
  184. package/docs/versioning.md +55 -0
  185. package/docs/workflow-findings-sidecar.md +588 -0
  186. package/package.json +135 -0
  187. package/prompts/first-build.md +27 -0
  188. package/prompts/friction-log.md +30 -0
  189. package/schemas/campaign-build-brief.v1.schema.json +149 -0
  190. package/schemas/campaign-design-source-package.v0.schema.json +697 -0
  191. package/schemas/campaign-runtime-assembly-report.v0.schema.json +390 -0
  192. package/schemas/campaign-runtime-build-context.v0.schema.json +339 -0
  193. package/schemas/campaign-runtime-build-packet.v0.schema.json +452 -0
  194. package/schemas/campaign-spec.v4.schema.json +582 -0
  195. package/schemas/campaigns-os-doctor-output.v0.schema.json +43 -0
  196. package/schemas/campaigns-os-legacy-migration-inventory.v0.schema.json +112 -0
  197. package/schemas/campaigns-os-legacy-provisioning-plan.v0.schema.json +38 -0
  198. package/schemas/campaigns-os-legacy-provisioning-receipt.v0.schema.json +57 -0
  199. package/schemas/campaigns-os-qa-verdict-sidecar.v0.schema.json +150 -0
  200. package/schemas/campaigns-os-qa-verdict.v0.schema.json +438 -0
  201. package/schemas/campaigns-os-release-ledger.v1.schema.json +162 -0
  202. package/schemas/campaigns-os-run-record.v0.schema.json +343 -0
  203. package/schemas/campaigns-os-runtime-recipe.v1.schema.json +313 -0
  204. package/schemas/campaigns-os-sidecar-bundle-conformance.v0.schema.json +78 -0
  205. package/schemas/campaigns-os-tooling-orientation.v1.schema.json +381 -0
  206. package/schemas/campaigns-os-workflow-finding.v0.schema.json +134 -0
  207. package/schemas/source-html-manifest.v0.schema.json +252 -0
  208. package/skills/next-campaigns-build/SKILL.md +73 -0
  209. package/skills/next-campaigns-os/SKILL.md +101 -0
  210. package/skills/next-campaigns-os/references/session-intake.md +160 -0
  211. package/skills/next-campaigns-os-setup/SKILL.md +22 -0
  212. package/skills/next-campaigns-polish/SKILL.md +145 -0
  213. package/skills/next-campaigns-qa/SKILL.md +92 -0
  214. package/skills.json +56 -0
  215. package/skills.sh +64 -0
  216. package/src/adapter-decision-contract.mjs +333 -0
  217. package/src/brand-theme.mjs +1151 -0
  218. package/src/browser-launch.mjs +79 -0
  219. package/src/build-brief.mjs +781 -0
  220. package/src/built-site-scope.mjs +312 -0
  221. package/src/campaign-ecosystem.mjs +734 -0
  222. package/src/campaign-identity.mjs +405 -0
  223. package/src/campaign-workspace.mjs +127 -0
  224. package/src/checkpoint-waiver.mjs +302 -0
  225. package/src/cli.mjs +13442 -0
  226. package/src/commercial-journey.mjs +1119 -0
  227. package/src/commercial-parity.mjs +965 -0
  228. package/src/consent.mjs +347 -0
  229. package/src/content-residue.mjs +322 -0
  230. package/src/deadline.mjs +81 -0
  231. package/src/design-source-package.mjs +2604 -0
  232. package/src/deviation.mjs +107 -0
  233. package/src/doctor-check-registry.mjs +49 -0
  234. package/src/doctor-sidecar.mjs +106 -0
  235. package/src/finding-cause.mjs +557 -0
  236. package/src/findings.mjs +326 -0
  237. package/src/fs-identity.mjs +68 -0
  238. package/src/gate-actions.mjs +105 -0
  239. package/src/html-scan.mjs +53 -0
  240. package/src/install-mode.mjs +272 -0
  241. package/src/legacy-migration.d.ts +128 -0
  242. package/src/legacy-migration.mjs +510 -0
  243. package/src/lifecycle.mjs +338 -0
  244. package/src/local-proof.mjs +401 -0
  245. package/src/map-pin-writeback.mjs +210 -0
  246. package/src/orchestration-stage-contract.mjs +81 -0
  247. package/src/package-install-fixture.mjs +33 -0
  248. package/src/page-kit-build-summary.mjs +175 -0
  249. package/src/page-kit-campaign-config.mjs +57 -0
  250. package/src/page-kit-sdk-version.mjs +392 -0
  251. package/src/page-kit-store-profile.mjs +369 -0
  252. package/src/page-kit-sync.mjs +162 -0
  253. package/src/polish-browser.mjs +867 -0
  254. package/src/polish-capture.mjs +1094 -0
  255. package/src/polish-deadline.mjs +51 -0
  256. package/src/polish-gate.mjs +739 -0
  257. package/src/polish-node.mjs +639 -0
  258. package/src/polish-page-load.mjs +1100 -0
  259. package/src/private-template-source.mjs +237 -0
  260. package/src/proof-policy.mjs +82 -0
  261. package/src/qa-analytics-correctness.mjs +307 -0
  262. package/src/qa-analytics-errors.mjs +38 -0
  263. package/src/qa-analytics-parity.mjs +699 -0
  264. package/src/qa-binding-evidence.mjs +140 -0
  265. package/src/qa-browser.mjs +6608 -0
  266. package/src/qa-cart-entry.mjs +406 -0
  267. package/src/qa-commercial-parity.mjs +641 -0
  268. package/src/qa-node.mjs +3620 -0
  269. package/src/qa-order-bump.mjs +381 -0
  270. package/src/qa-parity-capture.mjs +428 -0
  271. package/src/qa-parity-fixture.mjs +359 -0
  272. package/src/qa-publish.mjs +362 -0
  273. package/src/qa-purchase-data-layer.mjs +263 -0
  274. package/src/qa-route-probe.mjs +272 -0
  275. package/src/qa-sidecar.mjs +188 -0
  276. package/src/qa-test-order-topology.mjs +207 -0
  277. package/src/qa-url-privacy.mjs +13 -0
  278. package/src/qa-verdict-discovery.mjs +192 -0
  279. package/src/qa-verdict-publish.mjs +105 -0
  280. package/src/qa-verdict.mjs +287 -0
  281. package/src/remit.mjs +388 -0
  282. package/src/repo-scan.mjs +83 -0
  283. package/src/route-identity.mjs +133 -0
  284. package/src/run-record-closeout.mjs +229 -0
  285. package/src/run-record.mjs +839 -0
  286. package/src/run-session.mjs +226 -0
  287. package/src/runtime-state-ignore.mjs +113 -0
  288. package/src/sdk-attribute-index.mjs +212 -0
  289. package/src/sdk-markup.mjs +358 -0
  290. package/src/sdk-meta-tags.mjs +52 -0
  291. package/src/shell-token.mjs +7 -0
  292. package/src/sidecar-bundle.mjs +399 -0
  293. package/src/source-asset-crawl.mjs +469 -0
  294. package/src/source-html-intake.mjs +627 -0
  295. package/src/source-html-manifest.mjs +276 -0
  296. package/src/source-prep.mjs +284 -0
  297. package/src/spec-derive-store.mjs +431 -0
  298. package/src/spec-derive.mjs +514 -0
  299. package/src/spec-fetch.mjs +66 -0
  300. package/src/spec-hash.mjs +48 -0
  301. package/src/spec-identity.mjs +27 -0
  302. package/src/stage-ledger.mjs +509 -0
  303. package/src/standardization-report.mjs +1297 -0
  304. package/src/template-brand-contract.mjs +473 -0
  305. package/src/template-freshness.mjs +196 -0
  306. package/src/template-reference.mjs +81 -0
  307. package/src/template-slot-manifest.mjs +150 -0
  308. package/src/text-safety.mjs +62 -0
  309. package/src/theme-gate.mjs +185 -0
  310. package/src/upsell-selector-scope.mjs +299 -0
@@ -0,0 +1,272 @@
1
+ // Install-mode detection shared by `tooling status`, the printed `next`
2
+ // commands, and the browser-missing hints: where is this package running
3
+ // from, which commit is it pinned at, and how should a command be spelled so
4
+ // it runs THIS copy where the operator is.
5
+ import { accessSync, constants as fsConstants, existsSync, readFileSync, realpathSync } from "node:fs";
6
+ import { execFileSync } from "node:child_process";
7
+ import { basename, delimiter, dirname, join, relative, resolve, sep } from "node:path";
8
+
9
+ const PACKAGE_INSTALL_MODE_LABELS = Object.freeze({
10
+ checkout: "git checkout",
11
+ npx_cache: "package install (npx cache)",
12
+ node_modules: "package install (node_modules)",
13
+ package_directory: "package install",
14
+ });
15
+
16
+ const PUBLIC_GIT_SOURCE = "github:NextCommerceCo/campaigns-os";
17
+
18
+ function realpathOrSelf(path) {
19
+ try {
20
+ return realpathSync(path);
21
+ } catch (error) {
22
+ if (error?.code === "ENOENT" || error?.code === "ENOTDIR") return path;
23
+ throw error;
24
+ }
25
+ }
26
+
27
+ // Is `root` the top level of its own git worktree? A package directory can
28
+ // sit INSIDE someone else's repository (a consumer's node_modules, a home
29
+ // directory under dotfiles control), and `git -C root` would happily answer
30
+ // for that outer repository. Only a root that IS the worktree top level is a
31
+ // checkout of this toolkit.
32
+ function isOwnGitCheckout(root) {
33
+ const inside = runCommand("git", ["-C", root, "rev-parse", "--is-inside-work-tree"]);
34
+ if (!inside.ok || inside.stdout !== "true") return false;
35
+ const top = runCommand("git", ["-C", root, "rev-parse", "--show-toplevel"]);
36
+ if (!top.ok || !top.stdout) return false;
37
+ return realpathOrSelf(top.stdout) === realpathOrSelf(root);
38
+ }
39
+
40
+ // Locate the nearest enclosing `node_modules` directory, so the install root
41
+ // beside it can be read for the resolved package pin.
42
+ function enclosingNodeModules(root) {
43
+ let cursor = root;
44
+ for (;;) {
45
+ const parent = dirname(cursor);
46
+ if (parent === cursor) return null;
47
+ if (basename(parent) === "node_modules") return parent;
48
+ cursor = parent;
49
+ }
50
+ }
51
+
52
+ function pinFromResolved(resolved, version) {
53
+ if (typeof resolved !== "string") return null;
54
+ const match = resolved.match(/#([0-9a-f]{7,40})$/i);
55
+ if (!match) return null;
56
+ const commit = match[1].toLowerCase();
57
+ const source = resolved.match(/github\.com[/:]([^/]+)\/([^/#]+?)(?:\.git)?(?:#|$)/i);
58
+ return {
59
+ version: version || null,
60
+ commit,
61
+ resolved,
62
+ spec: source ? `github:${source[1]}/${source[2]}#${commit.slice(0, 12)}` : null,
63
+ };
64
+ }
65
+
66
+ // Derive "which commit is this package?" for a non-checkout install. npm
67
+ // records the resolved git URL, sha included, in the install root's hidden
68
+ // lockfile (`node_modules/.package-lock.json`) and in `package-lock.json`;
69
+ // `npm pack` additionally stamps `gitHead` into the packed package.json.
70
+ export function derivePackagePin(root, pkg = {}) {
71
+ if (typeof pkg.gitHead === "string" && /^[0-9a-f]{7,40}$/i.test(pkg.gitHead)) {
72
+ return { version: pkg.version || null, commit: pkg.gitHead.toLowerCase(), resolved: null, spec: `${PUBLIC_GIT_SOURCE}#${pkg.gitHead.slice(0, 12)}` };
73
+ }
74
+ // A nested dependency (project/node_modules/consumer/node_modules/<pkg>) is
75
+ // recorded in the PROJECT's lockfile under its full relative path, so walk
76
+ // every enclosing install root outward, recomputing the key per root.
77
+ let nodeModules = enclosingNodeModules(root);
78
+ while (nodeModules) {
79
+ const installRoot = dirname(nodeModules);
80
+ const key = relative(installRoot, root).split(sep).join("/");
81
+ for (const lockPath of [join(nodeModules, ".package-lock.json"), join(installRoot, "package-lock.json")]) {
82
+ if (!existsSync(lockPath)) continue;
83
+ let lock;
84
+ try {
85
+ lock = JSON.parse(readFileSync(lockPath, "utf8"));
86
+ } catch (error) {
87
+ if (error instanceof SyntaxError) continue;
88
+ throw error;
89
+ }
90
+ const entry = lock?.packages?.[key];
91
+ const pin = pinFromResolved(entry?.resolved, entry?.version || pkg.version);
92
+ if (pin) return pin;
93
+ }
94
+ nodeModules = enclosingNodeModules(installRoot);
95
+ }
96
+ return null;
97
+ }
98
+
99
+ export function localInstallStatus(root, pkg = {}) {
100
+ if (isOwnGitCheckout(root)) {
101
+ return {
102
+ mode: "checkout",
103
+ mode_label: PACKAGE_INSTALL_MODE_LABELS.checkout,
104
+ location: root,
105
+ pinned: null,
106
+ summary: `Install mode: git checkout at ${root}.`,
107
+ };
108
+ }
109
+ // npm's exec cache lays packages out as <cache>/_npx/<hex hash>/node_modules/<pkg>;
110
+ // a project that merely has `_npx` somewhere in its path is not that.
111
+ const segments = root.split(sep);
112
+ const npxIndex = segments.indexOf("_npx");
113
+ const inNpxCache = npxIndex >= 0 && /^[0-9a-f]{8,}$/i.test(segments[npxIndex + 1] || "") && segments[npxIndex + 2] === "node_modules";
114
+ const mode = inNpxCache
115
+ ? "npx_cache"
116
+ : enclosingNodeModules(root)
117
+ ? "node_modules"
118
+ : "package_directory";
119
+ const pinned = derivePackagePin(root, pkg);
120
+ const pinText = pinned
121
+ ? `pinned at ${pinned.version || "unknown version"} @ ${pinned.commit.slice(0, 12)}`
122
+ : `version ${pkg.version || "unknown"}, pinned commit not derivable`;
123
+ return {
124
+ mode,
125
+ mode_label: PACKAGE_INSTALL_MODE_LABELS[mode],
126
+ location: root,
127
+ pinned,
128
+ summary: `Install mode: ${PACKAGE_INSTALL_MODE_LABELS[mode]}, ${pinText}.`,
129
+ };
130
+ }
131
+
132
+
133
+ // The file a PATH executable ultimately runs: a symlink (node_modules/.bin on
134
+ // POSIX) is followed; an npm cmd-shim is read for the script it execs. Falls
135
+ // back to the executable itself when neither applies.
136
+ export function executableTargetPath(executable) {
137
+ const real = realpathOrSelf(executable);
138
+ if (real !== executable) return real;
139
+ // Not a symlink: an npm cmd-shim (Windows, `%~dp0`-relative), a pnpm/yarn
140
+ // shell wrapper, or the script itself. Read a small text wrapper for the
141
+ // script it execs; anything else is the executable.
142
+ let text;
143
+ try {
144
+ text = readFileSync(executable, "utf8").slice(0, 4096);
145
+ } catch (error) {
146
+ if (error?.code === "ENOENT" || error?.code === "EISDIR") return real;
147
+ throw error;
148
+ }
149
+ // Only the script argument of a node invocation counts, and only on a real
150
+ // command line: comment lines (`#`, `REM`, `::`) are dropped first so a
151
+ // `.mjs` or even a `node ./x.mjs` mentioned in a prologue cannot win, and
152
+ // the invocation must open its logical line (optionally after `exec`, an
153
+ // `@`, or `&&`/`;`/`|`). The path is a quoted or bare token that starts with
154
+ // a wrapper-relative or absolute prefix and ends at the quote/whitespace
155
+ // after `.mjs`.
156
+ const code = text.split(/\r?\n/).filter((line) => !/^\s*(?:#|@?rem\b|::)/i.test(line)).join("\n");
157
+ const prefix = String.raw`(?:%~dp0|\$basedir|\$\{basedir\}|\.|\.\.|\/|[A-Za-z]:)`;
158
+ const match = code.match(new RegExp(String.raw`(?:^|[;&|])\s*@?(?:exec\s+)?(?:"[^"\n]*node(?:\.exe)?"|node(?:\.exe)?)\s+(?:"(${prefix}[^"\n]*?\.mjs)"|'(${prefix}[^'\n]*?\.mjs)'|(${prefix}[^\s"'\n]*?\.mjs))(?=[\s"']|$)`, "m"));
159
+ if (!match) return real;
160
+ const scriptToken = match[1] ?? match[2] ?? match[3];
161
+ const script = scriptToken.replace(/^(%~dp0|\$basedir|\$\{basedir\})[\\/]?/, "");
162
+ return realpathOrSelf(resolve(dirname(executable), script.replace(/\\/g, "/")));
163
+ }
164
+
165
+ function findExecutableOnPath(name) {
166
+ const pathEnv = process.env.PATH || "";
167
+ const extensions = process.platform === "win32"
168
+ ? (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean)
169
+ : [""];
170
+ for (const dir of pathEnv.split(delimiter).filter(Boolean)) {
171
+ for (const ext of extensions) {
172
+ const candidate = join(dir, process.platform === "win32" && !name.toLowerCase().endsWith(ext.toLowerCase()) ? `${name}${ext}` : name);
173
+ if (isExecutableFile(candidate)) return candidate;
174
+ }
175
+ }
176
+ return null;
177
+ }
178
+
179
+ function isExecutableFile(candidate) {
180
+ if (!existsSync(candidate)) return false;
181
+ if (process.platform === "win32") return true;
182
+ try {
183
+ accessSync(candidate, fsConstants.X_OK);
184
+ return true;
185
+ } catch {
186
+ return false;
187
+ }
188
+ }
189
+
190
+ function runCommand(command, args) {
191
+ try {
192
+ return {
193
+ ok: true,
194
+ stdout: execFileSync(command, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim(),
195
+ error: null,
196
+ };
197
+ } catch (error) {
198
+ return {
199
+ ok: false,
200
+ stdout: typeof error.stdout === "string" ? error.stdout.trim() : "",
201
+ error: error.message || String(error),
202
+ };
203
+ }
204
+ }
205
+
206
+ // How a command should be spelled so it runs THIS install: the checkout
207
+ // script from a checkout; `npx --yes <spec>` from an npx cache (nothing is on
208
+ // PATH); `npx campaigns-os` from a consumer install (the toolkit pinned as a
209
+ // devDependency of the campaign folder) — always, because `npx` itself puts
210
+ // node_modules/.bin on PATH for the duration of the command, so a PATH match
211
+ // seen here says nothing about the operator's shell, and a bare command they
212
+ // paste there would not resolve; the bare binary from a plain package
213
+ // directory. The PATH comparison is still reported so a shadowing install is
214
+ // visible.
215
+ export function resolveInvocation(root, pkg = {}, install = localInstallStatus(root, pkg)) {
216
+ const binRel = pkg.bin && typeof pkg.bin === "object"
217
+ ? pkg.bin["campaigns-os"]
218
+ : typeof pkg.bin === "string"
219
+ ? pkg.bin
220
+ : null;
221
+ const localBin = binRel ? resolve(root, binRel) : resolve(root, "bin/campaigns-os.mjs");
222
+ const globalPath = findExecutableOnPath("campaigns-os");
223
+ const globalTarget = globalPath ? executableTargetPath(globalPath) : null;
224
+ const matches = Boolean(globalTarget && realpathOrSelf(globalTarget) === realpathOrSelf(localBin));
225
+ const nodeModules = install.mode === "checkout" ? null : enclosingNodeModules(root);
226
+ let prefix;
227
+ if (install.mode === "checkout") prefix = "npm run campaigns-os --";
228
+ else if (install.mode === "npx_cache") prefix = install.pinned?.spec ? `npx --yes ${install.pinned.spec}` : "campaigns-os";
229
+ else if (install.mode === "node_modules") prefix = "npx campaigns-os";
230
+ else prefix = "campaigns-os";
231
+ return {
232
+ install,
233
+ prefix,
234
+ // What printed commands are spelled with. A checkout keeps the canonical
235
+ // bare form: its printed output is a tested contract, and the README
236
+ // states the `npm run campaigns-os --` translation once.
237
+ print_prefix: install.mode === "checkout" ? "campaigns-os" : prefix,
238
+ local_bin: localBin,
239
+ local_bin_exists: existsSync(localBin),
240
+ bin_dir: nodeModules ? join(nodeModules, ".bin") : null,
241
+ global_binary: globalPath
242
+ ? { status: matches ? "found" : "found_other_install", path: globalPath, resolves_to: globalTarget, matches_local_bin: matches }
243
+ : { status: "not_found", path: null, resolves_to: null, matches_local_bin: false },
244
+ };
245
+ }
246
+
247
+ const prefixCache = new Map();
248
+
249
+ // The PRINT prefix for the package at `root`, computed once per process (it
250
+ // costs two git subprocesses and a PATH scan).
251
+ export function invocationPrefixFor(root, pkg = null) {
252
+ if (prefixCache.has(root)) return prefixCache.get(root);
253
+ let manifest = pkg;
254
+ if (!manifest) {
255
+ try {
256
+ manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
257
+ } catch (error) {
258
+ if (error?.code !== "ENOENT" && !(error instanceof SyntaxError)) throw error;
259
+ manifest = {};
260
+ }
261
+ }
262
+ const prefix = resolveInvocation(root, manifest).print_prefix;
263
+ prefixCache.set(root, prefix);
264
+ return prefix;
265
+ }
266
+
267
+ // Rewrites every command spelled with the canonical bare `campaigns-os <verb>`
268
+ // into the given prefix. Internal bookkeeping (deviation tracking, gate
269
+ // registries, tests) keeps the canonical spelling; only what is printed or
270
+ // emitted for an operator or agent to copy is rewritten. Skill names such as
271
+ // next-campaigns-os-setup, file names (campaigns-os.mjs), and already-prefixed
272
+ // forms (`npx campaigns-os`, `npm run campaigns-os --`) are left alone.
@@ -0,0 +1,128 @@
1
+ export type Sha256Digest = `sha256:${string}`;
2
+ export type LegacyMigrationAction = "campaign.create" | "package.create" | "shipping_method.create" | "offer.create";
3
+ export type LegacyMigrationReceiptState = "applying" | "provisioned" | "awaiting_manual_configuration" | "failed" | "reconciliation_required";
4
+
5
+ type LegacyOfferConditionV0 =
6
+ | { type: "any"; value?: null; all_packages: false; package_keys: string[] }
7
+ | { type: "count"; value: number; all_packages: false; package_keys: string[] };
8
+
9
+ interface LegacyOfferIntentBaseV0 {
10
+ intent_key: string;
11
+ name: string;
12
+ condition: LegacyOfferConditionV0;
13
+ benefit: { type: "package_percentage" | "shipping_percentage" | "order_percentage"; value: string; price_rounding?: "0.00" | "0.95" | "0.97" | "0.99" | null };
14
+ }
15
+
16
+ export type LegacyOfferIntentV0 = LegacyOfferIntentBaseV0 & (
17
+ | { offer_type: "offer"; code?: never }
18
+ | { offer_type: "voucher"; code: string }
19
+ );
20
+
21
+ type LegacyOfferCreateConditionV0 =
22
+ | { type: "any"; all_packages: false; package_ids: number[] }
23
+ | { type: "count"; value: number; all_packages: false; package_ids: number[] };
24
+
25
+ interface LegacyOfferCreateRequestBaseV0 {
26
+ name: string;
27
+ condition: LegacyOfferCreateConditionV0;
28
+ benefit: { type: LegacyOfferIntentV0["benefit"]["type"]; value: string; price_rounding?: NonNullable<LegacyOfferIntentV0["benefit"]["price_rounding"]> };
29
+ }
30
+
31
+ export type LegacyOfferCreateRequestV0 = LegacyOfferCreateRequestBaseV0 & (
32
+ | { offer_type: "offer"; code?: never }
33
+ | { offer_type: "voucher"; code: string }
34
+ );
35
+
36
+ export interface LegacyOfferReadbackV0 {
37
+ name?: string; offer_type?: string; code?: string;
38
+ condition?: { type?: string; value?: number | null; all_packages?: boolean; packages?: Array<{ id?: number }> };
39
+ benefit?: { type?: string; value?: string | number; price_rounding?: string | null };
40
+ }
41
+
42
+ export interface LegacyPackageCreateRequestV0 { product_id?: number; product_variant_ids: number[]; price: string }
43
+ export interface LegacyPackageReadbackV0 { product_id?: number; product_variant_id?: number; prices?: Array<{ currency?: string; price?: string | number }> }
44
+
45
+ export interface LegacyMigrationInventoryV0 {
46
+ schema_version: "campaigns-os-legacy-migration-inventory/v0";
47
+ migration_id: string;
48
+ source: { campaign_id: number; sdk_version: string };
49
+ target: {
50
+ campaign: {
51
+ name: string; currency: string; language: string; payment_gateway_group_id: number;
52
+ additional_currencies?: string[]; available_payment_methods?: string[];
53
+ available_express_payment_methods?: string[]; available_shipping_countries?: string[];
54
+ statement_descriptor?: string;
55
+ };
56
+ packages: Array<{
57
+ package_key: string; name: string; product_id: number; product_variant_id: number; price: string;
58
+ price_recurring?: string; interval?: string; interval_count?: number;
59
+ }>;
60
+ shipping_methods: Array<{ shipping_key: string; shipping_method: string; price: string }>;
61
+ offer_intents: LegacyOfferIntentV0[];
62
+ authorized_domains: string[];
63
+ };
64
+ }
65
+
66
+ export interface LegacyProvisioningOperationV0 {
67
+ operation_id: string; action: Exclude<LegacyMigrationAction, "offer.create">;
68
+ resource_key: string; depends_on?: string; request: Record<string, unknown>;
69
+ }
70
+
71
+ export interface LegacyProvisioningPlanV0 {
72
+ schema_version: "campaigns-os-legacy-provisioning-plan/v0";
73
+ store: string; migration_id: string; inventory_hash: Sha256Digest; preview_hash: Sha256Digest;
74
+ source: LegacyMigrationInventoryV0["source"];
75
+ operations: LegacyProvisioningOperationV0[];
76
+ offer_intents: LegacyOfferIntentV0[];
77
+ authorized_domains: string[];
78
+ }
79
+
80
+ interface LegacyProvisioningWriteBaseV0 {
81
+ operation_id: string; resource_key: string;
82
+ readback?: Record<string, unknown>; audit_key?: string;
83
+ }
84
+
85
+ type LegacyUpstreamIdsV0 = {
86
+ campaign_id: number; package_id?: number | null; shipping_method_id?: number | null; offer_id?: number | null;
87
+ };
88
+
89
+ export type LegacyProvisioningWriteV0 =
90
+ | (LegacyProvisioningWriteBaseV0 & { action: "campaign.create"; intent_key?: string; upstream_ids: LegacyUpstreamIdsV0 })
91
+ | (LegacyProvisioningWriteBaseV0 & { action: "package.create"; intent_key?: string; upstream_ids: LegacyUpstreamIdsV0 & { package_id: number } })
92
+ | (LegacyProvisioningWriteBaseV0 & { action: "shipping_method.create"; intent_key?: string; upstream_ids: LegacyUpstreamIdsV0 & { shipping_method_id: number } })
93
+ | (LegacyProvisioningWriteBaseV0 & { action: "offer.create"; intent_key: string; upstream_ids: LegacyUpstreamIdsV0 & { offer_id: number } });
94
+
95
+ interface LegacyProvisioningReceiptBaseV0 {
96
+ schema_version: "campaigns-os-legacy-provisioning-receipt/v0";
97
+ store: string; migration_id: string; inventory_hash: Sha256Digest; preview_hash: Sha256Digest;
98
+ created_at: string; updated_at: string; writes: LegacyProvisioningWriteV0[];
99
+ failure?: Record<string, unknown> | null;
100
+ }
101
+
102
+ export type LegacyProvisioningReceiptV0 = LegacyProvisioningReceiptBaseV0 & (
103
+ | { state: "provisioned" | "awaiting_manual_configuration"; applied_at: string; campaign_id: number }
104
+ | { state: "applying" | "failed" | "reconciliation_required"; applied_at?: string; campaign_id: number | null }
105
+ );
106
+
107
+ export interface LegacyReadbackCheck { field: string; expected: unknown; actual: unknown; status: "match" | "mismatch" | "unprojectable"; detail?: string }
108
+ export interface LegacyReadbackComparison { ok: boolean; checks: LegacyReadbackCheck[] }
109
+ export interface LegacyReceiptProjection { campaign_id: number; package_ids: Record<string, number>; shipping_method_ids: Record<string, number>; offer_ids: Record<string, number>; offer_intent_keys: string[]; plan_hash: Sha256Digest; receipt_hash: Sha256Digest; applied_at: string }
110
+
111
+ export class LegacyMigrationValidationError extends Error { issues: string[]; constructor(issues: string[]) }
112
+ export const LEGACY_MIGRATION_INVENTORY_VERSION: LegacyMigrationInventoryV0["schema_version"];
113
+ export const LEGACY_PROVISIONING_PLAN_VERSION: LegacyProvisioningPlanV0["schema_version"];
114
+ export const LEGACY_PROVISIONING_RECEIPT_VERSION: LegacyProvisioningReceiptV0["schema_version"];
115
+ export function stableCanonicalStringify(value: unknown): string;
116
+ export function hashLegacyMigrationArtifact(value: unknown): Promise<Sha256Digest>;
117
+ export function validateLegacyOfferIntent(intent: unknown, options?: { packageKeys?: Set<string> }): string[];
118
+ export function validateLegacyMigrationInventory(input: unknown): string[];
119
+ export function normalizeLegacyMigrationInventory(input: unknown): LegacyMigrationInventoryV0;
120
+ export function parseLegacyMigrationInventory(input: unknown): LegacyMigrationInventoryV0;
121
+ export function buildLegacyOfferRequest(intent: LegacyOfferIntentV0, packageIdByKey: Record<string, number>): LegacyOfferCreateRequestV0;
122
+ export function compareLegacyOfferReadback(request: LegacyOfferCreateRequestV0, readback: LegacyOfferReadbackV0): LegacyReadbackComparison;
123
+ export function compareLegacyPackageReadback(request: LegacyPackageCreateRequestV0, readback: LegacyPackageReadbackV0, options?: { currency?: string }): LegacyReadbackComparison;
124
+ export function validateLegacyProvisioningPlan(input: unknown): string[];
125
+ export function parseLegacyProvisioningPlan(input: unknown): LegacyProvisioningPlanV0;
126
+ export function validateLegacyProvisioningReceipt(input: unknown): string[];
127
+ export function parseLegacyProvisioningReceipt(input: unknown): LegacyProvisioningReceiptV0;
128
+ export function projectLegacyProvisioningReceipt(receipt: LegacyProvisioningReceiptV0): Promise<LegacyReceiptProjection>;