@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.
- package/AGENTS.md +204 -0
- package/CHANGELOG.md +5002 -0
- package/CONTEXT.md +685 -0
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +368 -0
- package/agents/claude/CLAUDE.md +32 -0
- package/agents/codex/AGENTS.md +27 -0
- package/agents/copilot/copilot-instructions.md +14 -0
- package/agents/cursor/campaigns-os.mdc +13 -0
- package/bin/campaigns-os.mjs +38 -0
- package/campaign-spec/README.md +138 -0
- package/campaign-spec/dist/analytics-vocabulary.d.ts +47 -0
- package/campaign-spec/dist/analytics-vocabulary.js +74 -0
- package/campaign-spec/dist/index.d.ts +40 -0
- package/campaign-spec/dist/index.js +77 -0
- package/campaign-spec/dist/normalize.d.ts +22 -0
- package/campaign-spec/dist/normalize.js +41 -0
- package/campaign-spec/dist/routing.d.ts +190 -0
- package/campaign-spec/dist/routing.js +263 -0
- package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +42 -0
- package/campaign-spec/dist/rules/analytics-contract-shape.js +303 -0
- package/campaign-spec/dist/rules/assembly-hints-shape.d.ts +42 -0
- package/campaign-spec/dist/rules/assembly-hints-shape.js +191 -0
- package/campaign-spec/dist/rules/campaign-metadata.d.ts +14 -0
- package/campaign-spec/dist/rules/campaign-metadata.js +40 -0
- package/campaign-spec/dist/rules/checkout-has-success-url.d.ts +31 -0
- package/campaign-spec/dist/rules/checkout-has-success-url.js +66 -0
- package/campaign-spec/dist/rules/cycle-detection.d.ts +12 -0
- package/campaign-spec/dist/rules/cycle-detection.js +141 -0
- package/campaign-spec/dist/rules/design-source-shape.d.ts +29 -0
- package/campaign-spec/dist/rules/design-source-shape.js +142 -0
- package/campaign-spec/dist/rules/downsell-without-upsell.d.ts +11 -0
- package/campaign-spec/dist/rules/downsell-without-upsell.js +40 -0
- package/campaign-spec/dist/rules/exit-intent-validation.d.ts +23 -0
- package/campaign-spec/dist/rules/exit-intent-validation.js +147 -0
- package/campaign-spec/dist/rules/funnel-count.d.ts +9 -0
- package/campaign-spec/dist/rules/funnel-count.js +37 -0
- package/campaign-spec/dist/rules/funnel-hypothesis-length.d.ts +22 -0
- package/campaign-spec/dist/rules/funnel-hypothesis-length.js +60 -0
- package/campaign-spec/dist/rules/funnel-identity.d.ts +15 -0
- package/campaign-spec/dist/rules/funnel-identity.js +57 -0
- package/campaign-spec/dist/rules/funnel-weight-sum.d.ts +20 -0
- package/campaign-spec/dist/rules/funnel-weight-sum.js +66 -0
- package/campaign-spec/dist/rules/index.d.ts +69 -0
- package/campaign-spec/dist/rules/index.js +131 -0
- package/campaign-spec/dist/rules/offer-ref-integrity.d.ts +13 -0
- package/campaign-spec/dist/rules/offer-ref-integrity.js +59 -0
- package/campaign-spec/dist/rules/package-pricing-sanity.d.ts +12 -0
- package/campaign-spec/dist/rules/package-pricing-sanity.js +42 -0
- package/campaign-spec/dist/rules/page-count.d.ts +11 -0
- package/campaign-spec/dist/rules/page-count.js +31 -0
- package/campaign-spec/dist/rules/page-id-uniqueness.d.ts +14 -0
- package/campaign-spec/dist/rules/page-id-uniqueness.js +47 -0
- package/campaign-spec/dist/rules/promo-code-input-validation.d.ts +8 -0
- package/campaign-spec/dist/rules/promo-code-input-validation.js +126 -0
- package/campaign-spec/dist/rules/promo-codes-shape.d.ts +30 -0
- package/campaign-spec/dist/rules/promo-codes-shape.js +187 -0
- package/campaign-spec/dist/rules/route-field-ignored-for-page-type.d.ts +33 -0
- package/campaign-spec/dist/rules/route-field-ignored-for-page-type.js +81 -0
- package/campaign-spec/dist/rules/route-target-resolves.d.ts +32 -0
- package/campaign-spec/dist/rules/route-target-resolves.js +112 -0
- package/campaign-spec/dist/rules/schema-version.d.ts +21 -0
- package/campaign-spec/dist/rules/schema-version.js +53 -0
- package/campaign-spec/dist/rules/sdk-version.d.ts +26 -0
- package/campaign-spec/dist/rules/sdk-version.js +96 -0
- package/campaign-spec/dist/rules/shipping-countries-shape.d.ts +10 -0
- package/campaign-spec/dist/rules/shipping-countries-shape.js +30 -0
- package/campaign-spec/dist/rules/shipping-methods-present.d.ts +10 -0
- package/campaign-spec/dist/rules/shipping-methods-present.js +26 -0
- package/campaign-spec/dist/rules/store-profile-shape.d.ts +30 -0
- package/campaign-spec/dist/rules/store-profile-shape.js +127 -0
- package/campaign-spec/dist/rules/thank-you-requirement.d.ts +16 -0
- package/campaign-spec/dist/rules/thank-you-requirement.js +50 -0
- package/campaign-spec/dist/rules/unknown-top-level-fields.d.ts +28 -0
- package/campaign-spec/dist/rules/unknown-top-level-fields.js +114 -0
- package/campaign-spec/dist/rules/upsell-has-packages.d.ts +9 -0
- package/campaign-spec/dist/rules/upsell-has-packages.js +35 -0
- package/campaign-spec/dist/rules/upsell-routing-complete.d.ts +10 -0
- package/campaign-spec/dist/rules/upsell-routing-complete.js +45 -0
- package/campaign-spec/dist/rules/upsell-without-checkout.d.ts +11 -0
- package/campaign-spec/dist/rules/upsell-without-checkout.js +44 -0
- package/campaign-spec/dist/rules/variant-labels-shape.d.ts +28 -0
- package/campaign-spec/dist/rules/variant-labels-shape.js +86 -0
- package/campaign-spec/dist/sdk-version-parse.d.ts +43 -0
- package/campaign-spec/dist/sdk-version-parse.js +62 -0
- package/campaign-spec/dist/types.d.ts +674 -0
- package/campaign-spec/dist/types.js +40 -0
- package/campaign-spec/package.json +12 -0
- package/compatibility.json +25 -0
- package/contracts/agent-relevant-change-policy.v1.json +111 -0
- package/contracts/brand-theme-source-defaults.figma-sections-export.v0.json +32 -0
- package/contracts/brand-theme-target-tokens.next-core.v0.json +65 -0
- package/contracts/campaign-cart-checkout-field-contract.v0.json +45 -0
- package/contracts/campaign-cart-sdk-support-policy.v0.json +11 -0
- package/contracts/commerce-surface-catalog.json +2452 -0
- package/contracts/fixtures/orientation/canonicalization/v1.json +34 -0
- package/contracts/fixtures/orientation/envelope/current.json +95 -0
- package/contracts/fixtures/orientation/envelope/freshness_unknown.json +97 -0
- package/contracts/fixtures/orientation/envelope/legacy_baseline.json +95 -0
- package/contracts/fixtures/orientation/envelope/orientation_available.json +136 -0
- package/contracts/fixtures/orientation/envelope/recovered_interrupted_update.json +136 -0
- package/contracts/fixtures/orientation/envelope/refused.json +100 -0
- package/contracts/fixtures/orientation/envelope/restart_required.json +137 -0
- package/contracts/fixtures/orientation/envelope/updated.json +135 -0
- package/contracts/fixtures/orientation/hostile-target/README.md +61 -0
- package/contracts/fixtures/orientation/hostile-target/manifest.json +82 -0
- package/contracts/fixtures/orientation/hostile-target/repo/CHANGELOG.md +18 -0
- package/contracts/fixtures/orientation/hostile-target/repo/bin/intended.mjs +12 -0
- package/contracts/fixtures/orientation/hostile-target/repo/bin/tripwire.mjs +15 -0
- package/contracts/fixtures/orientation/hostile-target/repo/contracts/release-ledger.json +48 -0
- package/contracts/fixtures/orientation/hostile-target/repo/contracts/supported-surface.json +17 -0
- package/contracts/fixtures/orientation/hostile-target/repo/docs/example-contract.md +13 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hooks/post-checkout +5 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hooks/post-merge +5 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hooks/pre-commit +5 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hostile-dependency-tripwire/package.json +15 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hostile-dependency-tripwire/tripwire.mjs +9 -0
- package/contracts/fixtures/orientation/hostile-target/repo/package.json +20 -0
- package/contracts/fixtures/orientation/hostile-target/repo/schemas/example.v0.schema.json +15 -0
- package/contracts/fixtures/orientation/release-gate/cases.json +1073 -0
- package/contracts/fixtures/runtime-recipe/accept/current.json +299 -0
- package/contracts/fixtures/runtime-recipe/accept/minimal.json +294 -0
- package/contracts/fixtures/runtime-recipe/dist-states.json +51 -0
- package/contracts/fixtures/runtime-recipe/manifest.json +85 -0
- package/contracts/fixtures/runtime-recipe/reject/advisory-enforcement.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/allowlist-without-hosts.json +297 -0
- package/contracts/fixtures/runtime-recipe/reject/committed-output-claim.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/engines-disagreement-warns.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/lifecycle-scripts-enabled.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/missing-required-field.json +251 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-kind.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-network-policy.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-output-check.json +310 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-revision.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-step-id.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unperformable-check-skipped.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unpinned-lockfile.json +299 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/assembly-report.json +180 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/build-context.json +115 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/doctor-output.json +29 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/qa-verdict.json +27 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/campaign-runtime.build.json +141 -0
- package/contracts/migration-sidecar-bundle.v0.json +149 -0
- package/contracts/orientation-limits.v1.json +41 -0
- package/contracts/orientation-reason-codes.v1.json +196 -0
- package/contracts/private-template-sources.json +8 -0
- package/contracts/release-ledger.json +4598 -0
- package/contracts/reserved-skill-names.json +13 -0
- package/contracts/runtime-recipe.campaigns-os-node-v1.json +299 -0
- package/contracts/supported-surface.json +180 -0
- package/contracts/template-brand-contract.apollo-mv-single-step.v0.json +27 -0
- package/contracts/template-brand-contract.apollo.v0.json +27 -0
- package/contracts/template-brand-contract.demeter.v0.json +27 -0
- package/contracts/template-brand-contract.olympus-mv-single-step.v0.json +27 -0
- package/contracts/template-brand-contract.olympus-mv-two-step.v0.json +28 -0
- package/contracts/template-brand-contract.olympus.v0.json +27 -0
- package/contracts/template-brand-contract.shared-commerce.v0.json +190 -0
- package/contracts/template-brand-contract.shop-single-step.v0.json +27 -0
- package/contracts/template-brand-contract.shop-three-step.v0.json +29 -0
- package/contracts/template-slot-manifest.apollo-mv-single-step.v0.json +14 -0
- package/contracts/template-slot-manifest.apollo.v0.json +12 -0
- package/contracts/template-slot-manifest.demeter.v0.json +30 -0
- package/contracts/template-slot-manifest.olympus-mv-single-step.v0.json +14 -0
- package/contracts/template-slot-manifest.olympus-mv-two-step.v0.json +15 -0
- package/contracts/template-slot-manifest.olympus.v0.json +12 -0
- package/contracts/template-slot-manifest.shared-content-core.v0.json +4254 -0
- package/contracts/template-slot-manifest.shop-single-step.v0.json +47 -0
- package/contracts/template-slot-manifest.shop-three-step.v0.json +33 -0
- package/docs/brand-theme-bridge.md +159 -0
- package/docs/build-packet.md +1300 -0
- package/docs/campaign-build-brief.md +145 -0
- package/docs/campaign-standardization-report.md +329 -0
- package/docs/campaigns-os-build-flow.md +117 -0
- package/docs/design-source-package.md +784 -0
- package/docs/legacy-migration.md +58 -0
- package/docs/migration-sidecar-bundle.md +139 -0
- package/docs/orientation-contract-reference.md +1220 -0
- package/docs/polish-evidence.md +502 -0
- package/docs/qa-and-test-orders.md +1691 -0
- package/docs/release-ledger-authoring-guide.md +274 -0
- package/docs/runtime-readiness.md +211 -0
- package/docs/supported-surface.md +83 -0
- package/docs/versioning.md +55 -0
- package/docs/workflow-findings-sidecar.md +588 -0
- package/package.json +135 -0
- package/prompts/first-build.md +27 -0
- package/prompts/friction-log.md +30 -0
- package/schemas/campaign-build-brief.v1.schema.json +149 -0
- package/schemas/campaign-design-source-package.v0.schema.json +697 -0
- package/schemas/campaign-runtime-assembly-report.v0.schema.json +390 -0
- package/schemas/campaign-runtime-build-context.v0.schema.json +339 -0
- package/schemas/campaign-runtime-build-packet.v0.schema.json +452 -0
- package/schemas/campaign-spec.v4.schema.json +582 -0
- package/schemas/campaigns-os-doctor-output.v0.schema.json +43 -0
- package/schemas/campaigns-os-legacy-migration-inventory.v0.schema.json +112 -0
- package/schemas/campaigns-os-legacy-provisioning-plan.v0.schema.json +38 -0
- package/schemas/campaigns-os-legacy-provisioning-receipt.v0.schema.json +57 -0
- package/schemas/campaigns-os-qa-verdict-sidecar.v0.schema.json +150 -0
- package/schemas/campaigns-os-qa-verdict.v0.schema.json +438 -0
- package/schemas/campaigns-os-release-ledger.v1.schema.json +162 -0
- package/schemas/campaigns-os-run-record.v0.schema.json +343 -0
- package/schemas/campaigns-os-runtime-recipe.v1.schema.json +313 -0
- package/schemas/campaigns-os-sidecar-bundle-conformance.v0.schema.json +78 -0
- package/schemas/campaigns-os-tooling-orientation.v1.schema.json +381 -0
- package/schemas/campaigns-os-workflow-finding.v0.schema.json +134 -0
- package/schemas/source-html-manifest.v0.schema.json +252 -0
- package/skills/next-campaigns-build/SKILL.md +73 -0
- package/skills/next-campaigns-os/SKILL.md +101 -0
- package/skills/next-campaigns-os/references/session-intake.md +160 -0
- package/skills/next-campaigns-os-setup/SKILL.md +22 -0
- package/skills/next-campaigns-polish/SKILL.md +145 -0
- package/skills/next-campaigns-qa/SKILL.md +92 -0
- package/skills.json +56 -0
- package/skills.sh +64 -0
- package/src/adapter-decision-contract.mjs +333 -0
- package/src/brand-theme.mjs +1151 -0
- package/src/browser-launch.mjs +79 -0
- package/src/build-brief.mjs +781 -0
- package/src/built-site-scope.mjs +312 -0
- package/src/campaign-ecosystem.mjs +734 -0
- package/src/campaign-identity.mjs +405 -0
- package/src/campaign-workspace.mjs +127 -0
- package/src/checkpoint-waiver.mjs +302 -0
- package/src/cli.mjs +13442 -0
- package/src/commercial-journey.mjs +1119 -0
- package/src/commercial-parity.mjs +965 -0
- package/src/consent.mjs +347 -0
- package/src/content-residue.mjs +322 -0
- package/src/deadline.mjs +81 -0
- package/src/design-source-package.mjs +2604 -0
- package/src/deviation.mjs +107 -0
- package/src/doctor-check-registry.mjs +49 -0
- package/src/doctor-sidecar.mjs +106 -0
- package/src/finding-cause.mjs +557 -0
- package/src/findings.mjs +326 -0
- package/src/fs-identity.mjs +68 -0
- package/src/gate-actions.mjs +105 -0
- package/src/html-scan.mjs +53 -0
- package/src/install-mode.mjs +272 -0
- package/src/legacy-migration.d.ts +128 -0
- package/src/legacy-migration.mjs +510 -0
- package/src/lifecycle.mjs +338 -0
- package/src/local-proof.mjs +401 -0
- package/src/map-pin-writeback.mjs +210 -0
- package/src/orchestration-stage-contract.mjs +81 -0
- package/src/package-install-fixture.mjs +33 -0
- package/src/page-kit-build-summary.mjs +175 -0
- package/src/page-kit-campaign-config.mjs +57 -0
- package/src/page-kit-sdk-version.mjs +392 -0
- package/src/page-kit-store-profile.mjs +369 -0
- package/src/page-kit-sync.mjs +162 -0
- package/src/polish-browser.mjs +867 -0
- package/src/polish-capture.mjs +1094 -0
- package/src/polish-deadline.mjs +51 -0
- package/src/polish-gate.mjs +739 -0
- package/src/polish-node.mjs +639 -0
- package/src/polish-page-load.mjs +1100 -0
- package/src/private-template-source.mjs +237 -0
- package/src/proof-policy.mjs +82 -0
- package/src/qa-analytics-correctness.mjs +307 -0
- package/src/qa-analytics-errors.mjs +38 -0
- package/src/qa-analytics-parity.mjs +699 -0
- package/src/qa-binding-evidence.mjs +140 -0
- package/src/qa-browser.mjs +6608 -0
- package/src/qa-cart-entry.mjs +406 -0
- package/src/qa-commercial-parity.mjs +641 -0
- package/src/qa-node.mjs +3620 -0
- package/src/qa-order-bump.mjs +381 -0
- package/src/qa-parity-capture.mjs +428 -0
- package/src/qa-parity-fixture.mjs +359 -0
- package/src/qa-publish.mjs +362 -0
- package/src/qa-purchase-data-layer.mjs +263 -0
- package/src/qa-route-probe.mjs +272 -0
- package/src/qa-sidecar.mjs +188 -0
- package/src/qa-test-order-topology.mjs +207 -0
- package/src/qa-url-privacy.mjs +13 -0
- package/src/qa-verdict-discovery.mjs +192 -0
- package/src/qa-verdict-publish.mjs +105 -0
- package/src/qa-verdict.mjs +287 -0
- package/src/remit.mjs +388 -0
- package/src/repo-scan.mjs +83 -0
- package/src/route-identity.mjs +133 -0
- package/src/run-record-closeout.mjs +229 -0
- package/src/run-record.mjs +839 -0
- package/src/run-session.mjs +226 -0
- package/src/runtime-state-ignore.mjs +113 -0
- package/src/sdk-attribute-index.mjs +212 -0
- package/src/sdk-markup.mjs +358 -0
- package/src/sdk-meta-tags.mjs +52 -0
- package/src/shell-token.mjs +7 -0
- package/src/sidecar-bundle.mjs +399 -0
- package/src/source-asset-crawl.mjs +469 -0
- package/src/source-html-intake.mjs +627 -0
- package/src/source-html-manifest.mjs +276 -0
- package/src/source-prep.mjs +284 -0
- package/src/spec-derive-store.mjs +431 -0
- package/src/spec-derive.mjs +514 -0
- package/src/spec-fetch.mjs +66 -0
- package/src/spec-hash.mjs +48 -0
- package/src/spec-identity.mjs +27 -0
- package/src/stage-ledger.mjs +509 -0
- package/src/standardization-report.mjs +1297 -0
- package/src/template-brand-contract.mjs +473 -0
- package/src/template-freshness.mjs +196 -0
- package/src/template-reference.mjs +81 -0
- package/src/template-slot-manifest.mjs +150 -0
- package/src/text-safety.mjs +62 -0
- package/src/theme-gate.mjs +185 -0
- package/src/upsell-selector-scope.mjs +299 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Run Telemetry — ambient run session for Campaigns OS.
|
|
2
|
+
// See docs/workflow-findings-sidecar.md (Run Telemetry).
|
|
3
|
+
//
|
|
4
|
+
// A run session makes telemetry AMBIENT: `run start` writes one small file to
|
|
5
|
+
// the project's .campaign-runtime/, and every subsequent command auto-discovers
|
|
6
|
+
// it (walking up from cwd) to share one run_id and one lifecycle journal —
|
|
7
|
+
// WITHOUT the operator threading --run-id / --lifecycle-journal on each call.
|
|
8
|
+
// `run end` assembles the aggregated Run Record and clears the session. This is
|
|
9
|
+
// the experience for "talk to your agent and build": the agent runs `run start`
|
|
10
|
+
// once, builds, then `run end`. No flag bookkeeping.
|
|
11
|
+
//
|
|
12
|
+
// The session file is transient, machine-local, and lives under the
|
|
13
|
+
// scrubber-ignored .campaign-runtime/. No network, no credentials.
|
|
14
|
+
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { randomBytes } from "node:crypto";
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import { dirname, join, parse, resolve, sep } from "node:path";
|
|
19
|
+
|
|
20
|
+
import { LIFECYCLE_JOURNAL_REL_PATH } from "./lifecycle.mjs";
|
|
21
|
+
import { sameFile } from "./fs-identity.mjs";
|
|
22
|
+
|
|
23
|
+
export const RUN_SESSION_SCHEMA = "campaigns-os-run-session/v0";
|
|
24
|
+
export const RUN_SESSION_REL_PATH = ".campaign-runtime/run-session.json";
|
|
25
|
+
export const RUN_SESSION_TTL_MS = 12 * 60 * 60 * 1000;
|
|
26
|
+
|
|
27
|
+
// Files that mark a project root. Session discovery never climbs ABOVE the
|
|
28
|
+
// nearest project root, so a stray session in an unrelated ancestor (e.g.
|
|
29
|
+
// $HOME or a shared /tmp) can't hijack commands run inside a real project.
|
|
30
|
+
const PROJECT_ROOT_MARKERS = [".git", "package.json"];
|
|
31
|
+
|
|
32
|
+
function isNonEmptyString(value) {
|
|
33
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isProjectRoot(dir) {
|
|
37
|
+
return PROJECT_ROOT_MARKERS.some((marker) => existsSync(join(dir, marker)));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Canonical session path for a project root: <root>/.campaign-runtime/run-session.json */
|
|
41
|
+
export function resolveRunSessionPath(rootDir = process.cwd()) {
|
|
42
|
+
return join(resolve(rootDir), RUN_SESSION_REL_PATH);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Walk up from `cwd` to find an active run session, so commands run from a
|
|
47
|
+
* subdirectory still share the project's session. Best-effort: never throws; a
|
|
48
|
+
* missing or malformed session file simply means "no active session".
|
|
49
|
+
* Returns `{ session, path, dir }` or null.
|
|
50
|
+
*
|
|
51
|
+
* Scope is bounded to the project to prevent a stray ancestor session from
|
|
52
|
+
* hijacking unrelated commands:
|
|
53
|
+
* - the walk STOPS at the nearest project root (a dir with .git/package.json),
|
|
54
|
+
* so a session in an ancestor ABOVE the project is never adopted;
|
|
55
|
+
* - a session located at $HOME, ANY ancestor of $HOME (e.g. /Users), or the
|
|
56
|
+
* filesystem root is never honored — those are shared dirs, not project roots.
|
|
57
|
+
*
|
|
58
|
+
* `home` is injectable for tests.
|
|
59
|
+
*/
|
|
60
|
+
export function isRunSessionStale(session, { now = new Date(), ttlMs = RUN_SESSION_TTL_MS } = {}) {
|
|
61
|
+
if (!session || typeof session !== "object" || Array.isArray(session)) return false;
|
|
62
|
+
if (!Number.isFinite(ttlMs) || ttlMs < 0) return false;
|
|
63
|
+
const activity = isNonEmptyString(session.updated_at) ? session.updated_at : session.started_at;
|
|
64
|
+
const timestamp = Date.parse(activity || "");
|
|
65
|
+
if (!Number.isFinite(timestamp)) return false;
|
|
66
|
+
return now.getTime() - timestamp > ttlMs;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function isRunSessionTerminal(session) {
|
|
70
|
+
if (!session || typeof session !== "object" || Array.isArray(session)) return false;
|
|
71
|
+
return session.terminal === true
|
|
72
|
+
|| session.status === "terminal";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function findRunSession(cwd = process.cwd(), { home = homedir(), now = new Date(), ttlMs = RUN_SESSION_TTL_MS } = {}) {
|
|
76
|
+
let dir = resolve(cwd);
|
|
77
|
+
const { root } = parse(dir);
|
|
78
|
+
const resolvedHome = resolve(home);
|
|
79
|
+
// True when `dir` is $HOME itself or a strict ancestor of it (e.g. /Users).
|
|
80
|
+
const isHomeOrAncestor = (d) => d === resolvedHome || resolvedHome.startsWith(d + sep);
|
|
81
|
+
for (let depth = 0; depth < 64; depth += 1) {
|
|
82
|
+
const candidate = join(dir, RUN_SESSION_REL_PATH);
|
|
83
|
+
if (existsSync(candidate)) {
|
|
84
|
+
// A session at the filesystem root, $HOME, or an ancestor of $HOME is not
|
|
85
|
+
// a project session — honoring it would capture every command beneath it.
|
|
86
|
+
if (dir === root || isHomeOrAncestor(dir)) return null;
|
|
87
|
+
try {
|
|
88
|
+
const session = JSON.parse(readFileSync(candidate, "utf8"));
|
|
89
|
+
if (session && typeof session === "object" && !Array.isArray(session) && isNonEmptyString(session.run_id) && !isRunSessionStale(session, { now, ttlMs })) {
|
|
90
|
+
return { session, path: candidate, dir };
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
// malformed session file => treat as no active session
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
// Don't climb past the project boundary: the session must live within the
|
|
98
|
+
// project being built, not in an unrelated ancestor directory.
|
|
99
|
+
if (isProjectRoot(dir)) break;
|
|
100
|
+
if (dir === root) break;
|
|
101
|
+
dir = dirname(dir);
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The session file at `rootDir` when it exists but is STALE (idle past the
|
|
108
|
+
* TTL). findRunSession deliberately ignores such a file so a new work session
|
|
109
|
+
* never inherits an old run_id — but ignoring it also meant abandoning it: the
|
|
110
|
+
* file lingered, its lifecycle journal was never assembled into a Run Record,
|
|
111
|
+
* and nothing was ever remitted for that run. Callers that are about to open a
|
|
112
|
+
* new session (start / prepare-build / run start) use this to close the old
|
|
113
|
+
* one out first. Exact-path only, no upward walk: sweeping is a write, and it
|
|
114
|
+
* must never reach into an ancestor project. Never throws.
|
|
115
|
+
*/
|
|
116
|
+
export function findStaleRunSession(rootDir = process.cwd(), { home = homedir(), now = new Date(), ttlMs = RUN_SESSION_TTL_MS } = {}) {
|
|
117
|
+
const dir = resolve(rootDir);
|
|
118
|
+
// Same boundary findRunSession enforces: the filesystem root, $HOME, or an
|
|
119
|
+
// ancestor of $HOME is a shared directory, never a project — and this
|
|
120
|
+
// caller goes on to assemble, remit, and delete.
|
|
121
|
+
const { root } = parse(dir);
|
|
122
|
+
const resolvedHome = resolve(home);
|
|
123
|
+
if (dir === root || dir === resolvedHome || resolvedHome.startsWith(dir + sep)) return null;
|
|
124
|
+
const path = resolveRunSessionPath(dir);
|
|
125
|
+
if (!existsSync(path)) return null;
|
|
126
|
+
try {
|
|
127
|
+
const session = JSON.parse(readFileSync(path, "utf8"));
|
|
128
|
+
if (!session || typeof session !== "object" || Array.isArray(session) || !isNonEmptyString(session.run_id)) return null;
|
|
129
|
+
if (!isRunSessionStale(session, { now, ttlMs })) return null;
|
|
130
|
+
return { session, path, dir: resolve(rootDir) };
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Mint the session's canonical run_id (same shape as the run-record minter). */
|
|
137
|
+
export function mintSessionRunId(now = new Date()) {
|
|
138
|
+
return `run_${now.getTime()}_${randomBytes(4).toString("hex")}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Build the session object (its own schema_version, the run_id, the journal it owns, an optional packet, and a timestamp). */
|
|
142
|
+
export function buildRunSession({ runId, lifecycleJournal, packet = null, now = new Date() }) {
|
|
143
|
+
const createdAt = now.toISOString();
|
|
144
|
+
return {
|
|
145
|
+
schema_version: RUN_SESSION_SCHEMA,
|
|
146
|
+
run_id: runId,
|
|
147
|
+
lifecycle_journal: lifecycleJournal,
|
|
148
|
+
packet: isNonEmptyString(packet) ? packet : null,
|
|
149
|
+
started_at: createdAt,
|
|
150
|
+
updated_at: createdAt,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Atomically write the session file under <rootDir>/.campaign-runtime/. */
|
|
155
|
+
export function writeRunSession(rootDir, session) {
|
|
156
|
+
const path = resolveRunSessionPath(rootDir);
|
|
157
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
158
|
+
const tmp = `${path}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
159
|
+
writeFileSync(tmp, `${JSON.stringify(session, null, 2)}\n`);
|
|
160
|
+
renameSync(tmp, path);
|
|
161
|
+
return path;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Remove the active session file so the next `run start` is clean. Non-fatal. */
|
|
165
|
+
export function clearRunSession(sessionPath) {
|
|
166
|
+
try {
|
|
167
|
+
rmSync(sessionPath, { force: true });
|
|
168
|
+
return true;
|
|
169
|
+
} catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Is this session `packetPath`'s? A session records the packet it was opened
|
|
175
|
+
// for; one opened with none is every packet's. The comparison is on the real
|
|
176
|
+
// path, so a packet reached through a symlinked checkout and the same file
|
|
177
|
+
// reached directly are one packet. `boundPacket` is returned for the caller's
|
|
178
|
+
// message when the answer is no.
|
|
179
|
+
export function sessionBoundTo(session, packetPath) {
|
|
180
|
+
const boundPacket = isNonEmptyString(session?.packet) ? session.packet : null;
|
|
181
|
+
if (!boundPacket) return { same: true, boundPacket };
|
|
182
|
+
if (!isNonEmptyString(packetPath)) return { same: false, boundPacket };
|
|
183
|
+
return { same: sameFile(boundPacket, packetPath), boundPacket };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Open a run session at `rootDir`, or join the one already there. The two
|
|
189
|
+
* openers — `run start` and the auto-start behind `start`/`prepare-build` —
|
|
190
|
+
* write the same session and differ only in what an already-open one means:
|
|
191
|
+
* `run start` refuses it unless `force` replaces it; the auto-start joins it
|
|
192
|
+
* when it is bound to this packet (`join`) and stands off when it is bound to
|
|
193
|
+
* another. Returns `{ found, joined, existing, binding }`: `found` is the open
|
|
194
|
+
* session as findRunSession reports it ({ session, path, dir }) or null when
|
|
195
|
+
* the existing one could not be joined, in which case `existing` is that
|
|
196
|
+
* session and `binding` says which packet holds it. Nothing is written on a
|
|
197
|
+
* join or a refusal.
|
|
198
|
+
*/
|
|
199
|
+
export function openRunSession(rootDir, {
|
|
200
|
+
runId = null,
|
|
201
|
+
lifecycleJournal = null,
|
|
202
|
+
packet = null,
|
|
203
|
+
lastRecommendation = null,
|
|
204
|
+
join: joinExisting = false,
|
|
205
|
+
force = false,
|
|
206
|
+
now = new Date(),
|
|
207
|
+
} = {}) {
|
|
208
|
+
const dir = resolve(rootDir);
|
|
209
|
+
const existing = force ? null : findRunSession(dir, { now });
|
|
210
|
+
if (existing) {
|
|
211
|
+
const binding = sessionBoundTo(existing.session, packet);
|
|
212
|
+
if (joinExisting && binding.same) return { found: existing, joined: true, existing: null, binding };
|
|
213
|
+
return { found: null, joined: false, existing, binding };
|
|
214
|
+
}
|
|
215
|
+
const session = {
|
|
216
|
+
...buildRunSession({
|
|
217
|
+
runId: runId || mintSessionRunId(now),
|
|
218
|
+
lifecycleJournal: lifecycleJournal || join(dir, LIFECYCLE_JOURNAL_REL_PATH),
|
|
219
|
+
packet: isNonEmptyString(packet) ? resolve(packet) : null,
|
|
220
|
+
now,
|
|
221
|
+
}),
|
|
222
|
+
...(lastRecommendation ? { last_recommendation: lastRecommendation } : {}),
|
|
223
|
+
};
|
|
224
|
+
const path = writeRunSession(dir, session);
|
|
225
|
+
return { found: { session, path, dir }, joined: false, existing: null, binding: null };
|
|
226
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Which .campaign-runtime/ entries are meant for the campaign repository's git
|
|
2
|
+
// history, and which are not. Three kinds share the directory:
|
|
3
|
+
//
|
|
4
|
+
// 1. The readback bundle (build-context, assembly-report, doctor-output,
|
|
5
|
+
// qa-verdict) — current readback truth; docs/migration-sidecar-bundle.md
|
|
6
|
+
// names it and `bundle check` validates it. Committed by design.
|
|
7
|
+
// 2. Handoff inputs and agent context (input/, theme/, agent-context/,
|
|
8
|
+
// setup-handoff.json) — durable build handoff. Committed by design.
|
|
9
|
+
// 3. Telemetry, sessions, caches, and evidence — per-machine, append-only,
|
|
10
|
+
// or carrying live URLs and absolute paths. Never meant for the repo, and
|
|
11
|
+
// nothing wrote a rule for them: three campaign repositories were found
|
|
12
|
+
// carrying another operator's run-session.json (absolute home path and
|
|
13
|
+
// all), lifecycle journals, and deviation logs.
|
|
14
|
+
//
|
|
15
|
+
// This module writes the rule for kind 3, once, into the target's .gitignore.
|
|
16
|
+
// A deny-list by name rather than "ignore the directory, re-include the
|
|
17
|
+
// bundle": git cannot re-include a file beneath an ignored directory, and a
|
|
18
|
+
// new sidecar should default to visible so someone has to decide about it.
|
|
19
|
+
|
|
20
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { join, resolve } from "node:path";
|
|
22
|
+
|
|
23
|
+
export const RUNTIME_STATE_IGNORE_MARKER = "# campaigns-os: machine-local runtime state (managed block; edit the list, keep the marker)";
|
|
24
|
+
|
|
25
|
+
export const RUNTIME_STATE_IGNORED_PATHS = [
|
|
26
|
+
".campaign-runtime/run-session.json",
|
|
27
|
+
".campaign-runtime/command-lifecycle.jsonl",
|
|
28
|
+
".campaign-runtime/agent-deviations.jsonl",
|
|
29
|
+
".campaign-runtime/workflow-findings.jsonl",
|
|
30
|
+
".campaign-runtime/run-records/",
|
|
31
|
+
".campaign-runtime/fetched-specs/",
|
|
32
|
+
".campaign-runtime/polish-evidence/",
|
|
33
|
+
".campaign-runtime/evidence/",
|
|
34
|
+
// Full QA verdicts carry live storefront URLs and order references; the
|
|
35
|
+
// committed form is the .campaign-runtime/qa-verdict.json projection.
|
|
36
|
+
"qa-output/",
|
|
37
|
+
".campaign-runtime/*.log",
|
|
38
|
+
".campaign-runtime/*.tmp",
|
|
39
|
+
".campaign-runtime/**/*.tmp",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// Entries the toolkit adds to an existing managed block when they are
|
|
43
|
+
// missing from the file. The block is otherwise the operator's to edit, but
|
|
44
|
+
// these name directories the toolkit itself fills with live URLs and order
|
|
45
|
+
// references, so a target set up before the entry existed must gain it on the
|
|
46
|
+
// next setup command rather than on the next accidental commit. An entry an
|
|
47
|
+
// operator has placed anywhere else in the file counts as present.
|
|
48
|
+
export const RUNTIME_STATE_REQUIRED_IGNORED_PATHS = ["qa-output/"];
|
|
49
|
+
|
|
50
|
+
export const RUNTIME_STATE_IGNORE_BLOCK = [
|
|
51
|
+
RUNTIME_STATE_IGNORE_MARKER,
|
|
52
|
+
"# Sessions, journals, Run Records, spec caches, capture evidence, and full",
|
|
53
|
+
"# QA verdicts are per-machine. The readback bundle (build-context, assembly-report,",
|
|
54
|
+
"# doctor-output, qa-verdict), input/, theme/, and agent-context/ are meant",
|
|
55
|
+
"# to be committed and are deliberately not listed here.",
|
|
56
|
+
...RUNTIME_STATE_IGNORED_PATHS,
|
|
57
|
+
].join("\n");
|
|
58
|
+
|
|
59
|
+
function ignoreRuleLines(text) {
|
|
60
|
+
return new Set(text.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Append the missing required entries at the end of the managed block: after
|
|
64
|
+
// the marker, up to the first blank line or the end of the file.
|
|
65
|
+
function withRequiredEntries(existing, missing) {
|
|
66
|
+
const lines = existing.split("\n");
|
|
67
|
+
const markerIndex = lines.findIndex((line) => line.trim() === RUNTIME_STATE_IGNORE_MARKER);
|
|
68
|
+
let end = markerIndex + 1;
|
|
69
|
+
while (end < lines.length && lines[end].trim() !== "") end += 1;
|
|
70
|
+
lines.splice(end, 0, ...missing);
|
|
71
|
+
return lines.join("\n");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Ensure the target's .gitignore carries the runtime-state block. Idempotent
|
|
76
|
+
* on the marker line: an existing block, however edited beneath it, is left
|
|
77
|
+
* alone — except for RUNTIME_STATE_REQUIRED_IGNORED_PATHS, which are appended
|
|
78
|
+
* to an existing block when the file carries them nowhere. Never throws for
|
|
79
|
+
* an unwritable target — the build must not fail on housekeeping — but
|
|
80
|
+
* reports what happened so callers can surface it.
|
|
81
|
+
* Returns `{ path, action: "added" | "updated" | "present" | "skipped", reason?, added_entries? }`.
|
|
82
|
+
*/
|
|
83
|
+
export function ensureRuntimeStateIgnored(targetRepo, { dryRun = false } = {}) {
|
|
84
|
+
const path = join(resolve(targetRepo), ".gitignore");
|
|
85
|
+
let existing = "";
|
|
86
|
+
if (existsSync(path)) {
|
|
87
|
+
try {
|
|
88
|
+
existing = readFileSync(path, "utf8");
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return { path, action: "skipped", reason: `unreadable: ${error.message}` };
|
|
91
|
+
}
|
|
92
|
+
if (existing.includes(RUNTIME_STATE_IGNORE_MARKER)) {
|
|
93
|
+
const present = ignoreRuleLines(existing);
|
|
94
|
+
const missing = RUNTIME_STATE_REQUIRED_IGNORED_PATHS.filter((entry) => !present.has(entry));
|
|
95
|
+
if (missing.length === 0) return { path, action: "present" };
|
|
96
|
+
if (dryRun) return { path, action: "updated", dry_run: true, added_entries: missing };
|
|
97
|
+
try {
|
|
98
|
+
writeFileSync(path, withRequiredEntries(existing, missing));
|
|
99
|
+
} catch (error) {
|
|
100
|
+
return { path, action: "skipped", reason: `unwritable: ${error.message}` };
|
|
101
|
+
}
|
|
102
|
+
return { path, action: "updated", added_entries: missing };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (dryRun) return { path, action: "added", dry_run: true };
|
|
106
|
+
const separator = existing.length === 0 || existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
107
|
+
try {
|
|
108
|
+
writeFileSync(path, `${existing}${separator}${RUNTIME_STATE_IGNORE_BLOCK}\n`);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return { path, action: "skipped", reason: `unwritable: ${error.message}` };
|
|
111
|
+
}
|
|
112
|
+
return { path, action: "added" };
|
|
113
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// The Campaign Cart SDK attribute index, vendored for the static SDK markup
|
|
2
|
+
// checks (#303).
|
|
3
|
+
//
|
|
4
|
+
// Source: docs/attribute-index.md in NextCommerceCo/campaign-cart at tag
|
|
5
|
+
// v0.4.38 (the SDK pin every certified starter family ships today), which is
|
|
6
|
+
// generated from the feature manifests. Only the data-next-* names are kept:
|
|
7
|
+
// the checks that read this list ask "does the SDK read this data-next-*
|
|
8
|
+
// attribute at all", and the SDK's other attribute families (data-item-*,
|
|
9
|
+
// data-package-*, plain data-*) are not what an agent kit misspells.
|
|
10
|
+
//
|
|
11
|
+
// This is a hardcoded copy and says so, rather than restating the SDK in
|
|
12
|
+
// prose that then drifts (the partner kit's mistake): the list is regenerated
|
|
13
|
+
// whole from the index at a named tag, never edited by hand.
|
|
14
|
+
//
|
|
15
|
+
// git -C ../campaign-cart show v0.4.38:docs/attribute-index.md \
|
|
16
|
+
// | grep -oE '`data-[a-z0-9-]+(\s*/\s*data-[a-z0-9-]+)*`' \
|
|
17
|
+
// | tr -d '`' | tr '/' '\n' | tr -d ' ' | grep '^data-next-' | sort -u
|
|
18
|
+
//
|
|
19
|
+
// A name ending in "-" (data-next-class-) is a prefix the SDK reads with any
|
|
20
|
+
// suffix.
|
|
21
|
+
|
|
22
|
+
export const SDK_ATTRIBUTE_INDEX_VERSION = "0.4.38";
|
|
23
|
+
|
|
24
|
+
export const SDK_DATA_NEXT_ATTRIBUTES = Object.freeze([
|
|
25
|
+
"data-next-accordion",
|
|
26
|
+
"data-next-accordion-panel",
|
|
27
|
+
"data-next-accordion-text",
|
|
28
|
+
"data-next-accordion-trigger",
|
|
29
|
+
"data-next-action",
|
|
30
|
+
"data-next-active",
|
|
31
|
+
"data-next-await",
|
|
32
|
+
"data-next-bump",
|
|
33
|
+
"data-next-bundle-card",
|
|
34
|
+
"data-next-bundle-display",
|
|
35
|
+
"data-next-bundle-id",
|
|
36
|
+
"data-next-bundle-items",
|
|
37
|
+
"data-next-bundle-name",
|
|
38
|
+
"data-next-bundle-price",
|
|
39
|
+
"data-next-bundle-qty-for",
|
|
40
|
+
"data-next-bundle-selector",
|
|
41
|
+
"data-next-bundle-selector-id",
|
|
42
|
+
"data-next-bundle-slot-template",
|
|
43
|
+
"data-next-bundle-slot-template-id",
|
|
44
|
+
"data-next-bundle-slots",
|
|
45
|
+
"data-next-bundle-slots-for",
|
|
46
|
+
"data-next-bundle-template",
|
|
47
|
+
"data-next-bundle-template-id",
|
|
48
|
+
"data-next-bundle-vouchers",
|
|
49
|
+
"data-next-bundles",
|
|
50
|
+
"data-next-cart-item-id",
|
|
51
|
+
"data-next-cart-items",
|
|
52
|
+
"data-next-cart-selector",
|
|
53
|
+
"data-next-cart-summary",
|
|
54
|
+
"data-next-checkout",
|
|
55
|
+
"data-next-checkout-field",
|
|
56
|
+
"data-next-checkout-payment",
|
|
57
|
+
"data-next-checkout-review",
|
|
58
|
+
"data-next-checkout-step",
|
|
59
|
+
"data-next-checkout-submit",
|
|
60
|
+
"data-next-class-",
|
|
61
|
+
"data-next-clear-cart",
|
|
62
|
+
"data-next-component",
|
|
63
|
+
"data-next-component-location",
|
|
64
|
+
"data-next-confirm",
|
|
65
|
+
"data-next-confirm-message",
|
|
66
|
+
"data-next-coupon",
|
|
67
|
+
"data-next-default-property",
|
|
68
|
+
"data-next-discounts",
|
|
69
|
+
"data-next-display",
|
|
70
|
+
"data-next-enhancer",
|
|
71
|
+
"data-next-error-for",
|
|
72
|
+
"data-next-exclude-property",
|
|
73
|
+
"data-next-express-checkout",
|
|
74
|
+
"data-next-fallback",
|
|
75
|
+
"data-next-format",
|
|
76
|
+
"data-next-hide",
|
|
77
|
+
"data-next-id",
|
|
78
|
+
"data-next-in-cart",
|
|
79
|
+
"data-next-include-shipping",
|
|
80
|
+
"data-next-is-upsell",
|
|
81
|
+
"data-next-item-properties",
|
|
82
|
+
"data-next-loading",
|
|
83
|
+
"data-next-max-quantity",
|
|
84
|
+
"data-next-min-quantity",
|
|
85
|
+
"data-next-multiply-quantity",
|
|
86
|
+
"data-next-next-url",
|
|
87
|
+
"data-next-order-items",
|
|
88
|
+
"data-next-package",
|
|
89
|
+
"data-next-package-id",
|
|
90
|
+
"data-next-package-price",
|
|
91
|
+
"data-next-package-selector",
|
|
92
|
+
"data-next-package-selector-id",
|
|
93
|
+
"data-next-package-sync",
|
|
94
|
+
"data-next-package-template",
|
|
95
|
+
"data-next-package-template-id",
|
|
96
|
+
"data-next-package-toggle",
|
|
97
|
+
"data-next-packages",
|
|
98
|
+
"data-next-payment-form",
|
|
99
|
+
"data-next-payment-method",
|
|
100
|
+
"data-next-payment-state",
|
|
101
|
+
"data-next-product-sync",
|
|
102
|
+
"data-next-property",
|
|
103
|
+
"data-next-property-container",
|
|
104
|
+
"data-next-quantity",
|
|
105
|
+
"data-next-quantity-decrease",
|
|
106
|
+
"data-next-quantity-display",
|
|
107
|
+
"data-next-quantity-increase",
|
|
108
|
+
"data-next-quantity-selector-id",
|
|
109
|
+
"data-next-quantity-text",
|
|
110
|
+
"data-next-remove-item",
|
|
111
|
+
"data-next-required",
|
|
112
|
+
"data-next-scroll-target",
|
|
113
|
+
"data-next-scroll-threshold",
|
|
114
|
+
"data-next-sdk-loading",
|
|
115
|
+
"data-next-selected",
|
|
116
|
+
"data-next-selection-mode",
|
|
117
|
+
"data-next-selector-card",
|
|
118
|
+
"data-next-selector-id",
|
|
119
|
+
"data-next-shipping-id",
|
|
120
|
+
"data-next-show",
|
|
121
|
+
"data-next-slot-index",
|
|
122
|
+
"data-next-step-number",
|
|
123
|
+
"data-next-timer",
|
|
124
|
+
"data-next-timer-display",
|
|
125
|
+
"data-next-timer-expired",
|
|
126
|
+
"data-next-toggle",
|
|
127
|
+
"data-next-toggle-card",
|
|
128
|
+
"data-next-toggle-container",
|
|
129
|
+
"data-next-toggle-display",
|
|
130
|
+
"data-next-toggle-image",
|
|
131
|
+
"data-next-toggle-price",
|
|
132
|
+
"data-next-toggle-template",
|
|
133
|
+
"data-next-toggle-template-id",
|
|
134
|
+
"data-next-tooltip",
|
|
135
|
+
"data-next-tooltip-class",
|
|
136
|
+
"data-next-tooltip-delay",
|
|
137
|
+
"data-next-tooltip-max-width",
|
|
138
|
+
"data-next-tooltip-offset",
|
|
139
|
+
"data-next-tooltip-placement",
|
|
140
|
+
"data-next-tracking-tag",
|
|
141
|
+
"data-next-upsell",
|
|
142
|
+
"data-next-upsell-action",
|
|
143
|
+
"data-next-upsell-action-for",
|
|
144
|
+
"data-next-upsell-context",
|
|
145
|
+
"data-next-upsell-item",
|
|
146
|
+
"data-next-upsell-option",
|
|
147
|
+
"data-next-upsell-quantity",
|
|
148
|
+
"data-next-upsell-quantity-toggle",
|
|
149
|
+
"data-next-upsell-section",
|
|
150
|
+
"data-next-upsell-select",
|
|
151
|
+
"data-next-upsell-selector",
|
|
152
|
+
"data-next-url",
|
|
153
|
+
"data-next-validate",
|
|
154
|
+
"data-next-variant-code",
|
|
155
|
+
"data-next-variant-option",
|
|
156
|
+
"data-next-variant-option-template-id",
|
|
157
|
+
"data-next-variant-options",
|
|
158
|
+
"data-next-variant-selector-template-id",
|
|
159
|
+
"data-next-variant-selectors",
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
// The fixed data-next-checkout-field names, from the same tag. The SDK maps a
|
|
163
|
+
// field to the order by this value, so an unlisted name is a silent no-op: the
|
|
164
|
+
// input renders, the shopper fills it, nothing reaches the order. Sources at
|
|
165
|
+
// v0.4.38: src/features/checkout/README.md ("Standard field names"), the
|
|
166
|
+
// checkout-form enhancer and its validation/persistence tables, the hosted
|
|
167
|
+
// payment field slots (cc-number, cvv, cc-month/cc-year and their exp-*
|
|
168
|
+
// spellings), payment-method, and accepts_marketing. Billing fields are the
|
|
169
|
+
// same names behind a "billing-" prefix and the SDK passes any billing-*
|
|
170
|
+
// suffix through (billing-field-routing.ts), so the prefix is accepted with
|
|
171
|
+
// any suffix rather than enumerated.
|
|
172
|
+
export const SDK_CHECKOUT_FIELD_NAMES = Object.freeze([
|
|
173
|
+
"email",
|
|
174
|
+
"fname",
|
|
175
|
+
"lname",
|
|
176
|
+
"phone",
|
|
177
|
+
"address1",
|
|
178
|
+
"address2",
|
|
179
|
+
"city",
|
|
180
|
+
"province",
|
|
181
|
+
"postal",
|
|
182
|
+
"country",
|
|
183
|
+
"payment-method",
|
|
184
|
+
"accepts_marketing",
|
|
185
|
+
"cc-number",
|
|
186
|
+
"cc-month",
|
|
187
|
+
"cc-year",
|
|
188
|
+
"exp-month",
|
|
189
|
+
"exp-year",
|
|
190
|
+
"cvv",
|
|
191
|
+
// Legacy spellings the README at v0.4.38 still lists.
|
|
192
|
+
"card-number",
|
|
193
|
+
"card-expiry",
|
|
194
|
+
"card-cvv",
|
|
195
|
+
"card-name",
|
|
196
|
+
]);
|
|
197
|
+
|
|
198
|
+
export const SDK_CHECKOUT_FIELD_PREFIXES = Object.freeze(["billing-"]);
|
|
199
|
+
|
|
200
|
+
const exact = new Set(SDK_DATA_NEXT_ATTRIBUTES.filter((name) => !name.endsWith("-")));
|
|
201
|
+
const prefixes = SDK_DATA_NEXT_ATTRIBUTES.filter((name) => name.endsWith("-"));
|
|
202
|
+
|
|
203
|
+
export function isIndexedSdkAttribute(name) {
|
|
204
|
+
const value = String(name || "").toLowerCase();
|
|
205
|
+
return exact.has(value) || prefixes.some((prefix) => value.startsWith(prefix) && value.length > prefix.length);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function isKnownCheckoutFieldName(value) {
|
|
209
|
+
const name = String(value ?? "");
|
|
210
|
+
return SDK_CHECKOUT_FIELD_NAMES.includes(name)
|
|
211
|
+
|| SDK_CHECKOUT_FIELD_PREFIXES.some((prefix) => name.startsWith(prefix) && name.length > prefix.length);
|
|
212
|
+
}
|