@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,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UpsellHasPackages — upsell and downsell pages must declare at least one
|
|
3
|
+
* package, since the runtime renders a bundle/offer selector for these page
|
|
4
|
+
* types and an empty packages array has nothing to show.
|
|
5
|
+
*
|
|
6
|
+
* Message text inherited verbatim from the pre-#110 validator at migration time.
|
|
7
|
+
*/
|
|
8
|
+
function needsPackages(page) {
|
|
9
|
+
return page.type === 'upsell' || page.type === 'downsell';
|
|
10
|
+
}
|
|
11
|
+
export const UpsellHasPackages = {
|
|
12
|
+
id: 'UpsellHasPackages',
|
|
13
|
+
severity: 'error',
|
|
14
|
+
tags: ['fast', 'structure', 'spec-only'],
|
|
15
|
+
check(spec) {
|
|
16
|
+
const violations = [];
|
|
17
|
+
spec.funnels.forEach((funnel, funnelIdx) => {
|
|
18
|
+
const pages = funnel.pages ?? [];
|
|
19
|
+
pages.forEach((page, pageIdx) => {
|
|
20
|
+
if (!needsPackages(page))
|
|
21
|
+
return;
|
|
22
|
+
if (page.packages && page.packages.length > 0)
|
|
23
|
+
return;
|
|
24
|
+
violations.push({
|
|
25
|
+
ruleId: 'UpsellHasPackages',
|
|
26
|
+
severity: 'error',
|
|
27
|
+
message: `"${page.label}" has no packages assigned.`,
|
|
28
|
+
path: `/funnels/${funnelIdx}/pages/${pageIdx}/packages`,
|
|
29
|
+
data: { pageId: page.id, pageType: page.type },
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
return violations;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UpsellRoutingComplete — upsell and downsell pages must declare both
|
|
3
|
+
* on_accept and on_decline routes. A missing route means the funnel
|
|
4
|
+
* topology has a dead-end at the offer.
|
|
5
|
+
*
|
|
6
|
+
* One rule, two potential violations per offending page. Message text
|
|
7
|
+
* inherited verbatim from the pre-#110 validator at migration time.
|
|
8
|
+
*/
|
|
9
|
+
import type { Rule } from '../types.ts';
|
|
10
|
+
export declare const UpsellRoutingComplete: Rule;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UpsellRoutingComplete — upsell and downsell pages must declare both
|
|
3
|
+
* on_accept and on_decline routes. A missing route means the funnel
|
|
4
|
+
* topology has a dead-end at the offer.
|
|
5
|
+
*
|
|
6
|
+
* One rule, two potential violations per offending page. Message text
|
|
7
|
+
* inherited verbatim from the pre-#110 validator at migration time.
|
|
8
|
+
*/
|
|
9
|
+
function needsAcceptDecline(page) {
|
|
10
|
+
return page.type === 'upsell' || page.type === 'downsell';
|
|
11
|
+
}
|
|
12
|
+
export const UpsellRoutingComplete = {
|
|
13
|
+
id: 'UpsellRoutingComplete',
|
|
14
|
+
severity: 'error',
|
|
15
|
+
tags: ['fast', 'structure', 'spec-only'],
|
|
16
|
+
check(spec) {
|
|
17
|
+
const violations = [];
|
|
18
|
+
spec.funnels.forEach((funnel, funnelIdx) => {
|
|
19
|
+
const pages = funnel.pages ?? [];
|
|
20
|
+
pages.forEach((page, pageIdx) => {
|
|
21
|
+
if (!needsAcceptDecline(page))
|
|
22
|
+
return;
|
|
23
|
+
if (!page.on_accept) {
|
|
24
|
+
violations.push({
|
|
25
|
+
ruleId: 'UpsellRoutingComplete',
|
|
26
|
+
severity: 'error',
|
|
27
|
+
message: `"${page.label}" — Accept path not set.`,
|
|
28
|
+
path: `/funnels/${funnelIdx}/pages/${pageIdx}/on_accept`,
|
|
29
|
+
data: { pageId: page.id, missing: 'on_accept' },
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (!page.on_decline) {
|
|
33
|
+
violations.push({
|
|
34
|
+
ruleId: 'UpsellRoutingComplete',
|
|
35
|
+
severity: 'error',
|
|
36
|
+
message: `"${page.label}" — Decline path not set.`,
|
|
37
|
+
path: `/funnels/${funnelIdx}/pages/${pageIdx}/on_decline`,
|
|
38
|
+
data: { pageId: page.id, missing: 'on_decline' },
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
return violations;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UpsellWithoutCheckout — if the spec has upsell pages, it must also have a
|
|
3
|
+
* checkout page. Otherwise nothing routes into the upsell flow.
|
|
4
|
+
*
|
|
5
|
+
* Severity downgrades to warning when build_scope.mode === 'partial', because
|
|
6
|
+
* partial-upsell builds may legitimately ship a standalone upsell that an
|
|
7
|
+
* existing downstream campaign routes into. Message text and severity rule
|
|
8
|
+
* inherited verbatim from the pre-#110 validator at migration time.
|
|
9
|
+
*/
|
|
10
|
+
import type { Rule } from '../types.ts';
|
|
11
|
+
export declare const UpsellWithoutCheckout: Rule;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UpsellWithoutCheckout — if the spec has upsell pages, it must also have a
|
|
3
|
+
* checkout page. Otherwise nothing routes into the upsell flow.
|
|
4
|
+
*
|
|
5
|
+
* Severity downgrades to warning when build_scope.mode === 'partial', because
|
|
6
|
+
* partial-upsell builds may legitimately ship a standalone upsell that an
|
|
7
|
+
* existing downstream campaign routes into. Message text and severity rule
|
|
8
|
+
* inherited verbatim from the pre-#110 validator at migration time.
|
|
9
|
+
*/
|
|
10
|
+
export const UpsellWithoutCheckout = {
|
|
11
|
+
id: 'UpsellWithoutCheckout',
|
|
12
|
+
severity: 'error',
|
|
13
|
+
tags: ['fast', 'structure', 'spec-only'],
|
|
14
|
+
check(spec) {
|
|
15
|
+
let hasUpsell = false;
|
|
16
|
+
let hasCheckout = false;
|
|
17
|
+
for (const funnel of spec.funnels) {
|
|
18
|
+
for (const page of funnel.pages ?? []) {
|
|
19
|
+
if (page.type === 'upsell')
|
|
20
|
+
hasUpsell = true;
|
|
21
|
+
else if (page.type === 'checkout')
|
|
22
|
+
hasCheckout = true;
|
|
23
|
+
if (hasUpsell && hasCheckout)
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (!hasUpsell)
|
|
28
|
+
return [];
|
|
29
|
+
if (hasCheckout)
|
|
30
|
+
return [];
|
|
31
|
+
const isPartialScope = spec.build_scope?.mode === 'partial';
|
|
32
|
+
return [
|
|
33
|
+
{
|
|
34
|
+
ruleId: 'UpsellWithoutCheckout',
|
|
35
|
+
severity: isPartialScope ? 'warning' : 'error',
|
|
36
|
+
message: isPartialScope
|
|
37
|
+
? 'Upsells are present without an active Checkout page. Valid for partial upsell builds when an existing checkout routes into this page.'
|
|
38
|
+
: 'Upsells require an active Checkout page.',
|
|
39
|
+
path: '/funnels',
|
|
40
|
+
data: { isPartialScope },
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VariantLabelsShape — validates per-page `variant_labels` introduced in
|
|
3
|
+
* Slice 4e.
|
|
4
|
+
*
|
|
5
|
+
* Multi-variant upsell templates (olympus-mv-single-step tier-cards, etc.)
|
|
6
|
+
* render columns for product attributes like size, color, flavor. The
|
|
7
|
+
* starter HTML often assumes two columns; single-attribute products
|
|
8
|
+
* (size-only, color-only) ship with empty second columns.
|
|
9
|
+
*
|
|
10
|
+
* Author-time hint: declare `{primary: "Size"}` on the upsell page and
|
|
11
|
+
* the build drops the second column; declare `{primary: "Size",
|
|
12
|
+
* secondary: "Color"}` and both columns render with the spec-declared
|
|
13
|
+
* labels. CLI/operator overrides at build time still win.
|
|
14
|
+
*
|
|
15
|
+
* Validation surface:
|
|
16
|
+
* - set on non-upsell page (placement warning, mirrors the
|
|
17
|
+
* pattern-on-non-upsell check in AssemblyHintsShape)
|
|
18
|
+
* - bad outer shape (array, null, scalar)
|
|
19
|
+
* - missing/empty primary
|
|
20
|
+
* - non-string primary or secondary
|
|
21
|
+
*
|
|
22
|
+
* Pattern reference: AssemblyHintsShape.upsell_template_pattern is the
|
|
23
|
+
* canonical 4a per-page hint check this rule mirrors. Slice 4b
|
|
24
|
+
* (upsell_mv_tiers) ships without a dedicated shape rule today — its
|
|
25
|
+
* shape is enforced consumer-side in campaigns-os normalizedMvTiers().
|
|
26
|
+
*/
|
|
27
|
+
import type { Rule } from '../types.ts';
|
|
28
|
+
export declare const VariantLabelsShape: Rule;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VariantLabelsShape — validates per-page `variant_labels` introduced in
|
|
3
|
+
* Slice 4e.
|
|
4
|
+
*
|
|
5
|
+
* Multi-variant upsell templates (olympus-mv-single-step tier-cards, etc.)
|
|
6
|
+
* render columns for product attributes like size, color, flavor. The
|
|
7
|
+
* starter HTML often assumes two columns; single-attribute products
|
|
8
|
+
* (size-only, color-only) ship with empty second columns.
|
|
9
|
+
*
|
|
10
|
+
* Author-time hint: declare `{primary: "Size"}` on the upsell page and
|
|
11
|
+
* the build drops the second column; declare `{primary: "Size",
|
|
12
|
+
* secondary: "Color"}` and both columns render with the spec-declared
|
|
13
|
+
* labels. CLI/operator overrides at build time still win.
|
|
14
|
+
*
|
|
15
|
+
* Validation surface:
|
|
16
|
+
* - set on non-upsell page (placement warning, mirrors the
|
|
17
|
+
* pattern-on-non-upsell check in AssemblyHintsShape)
|
|
18
|
+
* - bad outer shape (array, null, scalar)
|
|
19
|
+
* - missing/empty primary
|
|
20
|
+
* - non-string primary or secondary
|
|
21
|
+
*
|
|
22
|
+
* Pattern reference: AssemblyHintsShape.upsell_template_pattern is the
|
|
23
|
+
* canonical 4a per-page hint check this rule mirrors. Slice 4b
|
|
24
|
+
* (upsell_mv_tiers) ships without a dedicated shape rule today — its
|
|
25
|
+
* shape is enforced consumer-side in campaigns-os normalizedMvTiers().
|
|
26
|
+
*/
|
|
27
|
+
function isNonEmptyString(value) {
|
|
28
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
29
|
+
}
|
|
30
|
+
export const VariantLabelsShape = {
|
|
31
|
+
id: 'VariantLabelsShape',
|
|
32
|
+
severity: 'warning',
|
|
33
|
+
tags: ['fast', 'spec-only'],
|
|
34
|
+
check(spec) {
|
|
35
|
+
const violations = [];
|
|
36
|
+
spec.funnels.forEach((funnel, funnelIdx) => {
|
|
37
|
+
const pages = funnel.pages ?? [];
|
|
38
|
+
pages.forEach((page, pageIdx) => {
|
|
39
|
+
const labels = page.variant_labels;
|
|
40
|
+
if (labels === undefined)
|
|
41
|
+
return;
|
|
42
|
+
const path = `/funnels/${funnelIdx}/pages/${pageIdx}/variant_labels`;
|
|
43
|
+
const pageLabel = page.label || page.id || '(unnamed page)';
|
|
44
|
+
if (labels === null || typeof labels !== 'object' || Array.isArray(labels)) {
|
|
45
|
+
violations.push({
|
|
46
|
+
ruleId: 'VariantLabelsShape',
|
|
47
|
+
severity: 'warning',
|
|
48
|
+
message: `"${pageLabel}" — variant_labels must be an object with a "primary" string field; got ${Array.isArray(labels) ? 'array' : typeof labels}.`,
|
|
49
|
+
path,
|
|
50
|
+
data: { pageId: page.id, check: 'labels-bad-shape' },
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const labelsObj = labels;
|
|
55
|
+
if (page.type !== 'upsell') {
|
|
56
|
+
violations.push({
|
|
57
|
+
ruleId: 'VariantLabelsShape',
|
|
58
|
+
severity: 'warning',
|
|
59
|
+
message: `"${pageLabel}" — variant_labels is set on a non-upsell page (type=${page.type}). The hint is meaningful only on upsell pages; remove it or move it to an upsell page.`,
|
|
60
|
+
path,
|
|
61
|
+
data: { pageId: page.id, pageType: page.type, check: 'labels-on-non-upsell' },
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (!isNonEmptyString(labelsObj.primary)) {
|
|
65
|
+
violations.push({
|
|
66
|
+
ruleId: 'VariantLabelsShape',
|
|
67
|
+
severity: 'warning',
|
|
68
|
+
message: `"${pageLabel}" — variant_labels.primary is required and must be a non-empty string (e.g. "Size").`,
|
|
69
|
+
path: `${path}/primary`,
|
|
70
|
+
data: { pageId: page.id, check: 'labels-primary-missing' },
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (labelsObj.secondary !== undefined && labelsObj.secondary !== null && !isNonEmptyString(labelsObj.secondary)) {
|
|
74
|
+
violations.push({
|
|
75
|
+
ruleId: 'VariantLabelsShape',
|
|
76
|
+
severity: 'warning',
|
|
77
|
+
message: `"${pageLabel}" — variant_labels.secondary must be a non-empty string when set (omit the field for single-attribute products); got ${JSON.stringify(labelsObj.secondary)}.`,
|
|
78
|
+
path: `${path}/secondary`,
|
|
79
|
+
data: { pageId: page.id, check: 'labels-secondary-bad-type', value: labelsObj.secondary },
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
return violations;
|
|
85
|
+
},
|
|
86
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sdk-version-parse — the ONE strict released-SDK-version parser.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from the Page Kit SDK-version checkpoint (src/page-kit-sdk-version.mjs)
|
|
5
|
+
* so authoring-time validation (the SdkVersion rule) and build-time gating
|
|
6
|
+
* (the checkpoint) reject exactly the same values. The acceptance predicate is
|
|
7
|
+
* the checkpoint's original one, unchanged: a plain string in canonical
|
|
8
|
+
* released `MAJOR.MINOR.PATCH` form — no leading zeros, no `v` prefix, no
|
|
9
|
+
* surrounding whitespace, no prerelease or build-metadata suffix, nothing but
|
|
10
|
+
* the three dot-separated numeric components.
|
|
11
|
+
*
|
|
12
|
+
* Dependency-free and browser-bundle-safe on purpose: the Map Builder bundles
|
|
13
|
+
* the campaign-spec module via esbuild, so nothing in here may import node
|
|
14
|
+
* builtins.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Canonical released semver: MAJOR.MINOR.PATCH, each component `0` or a
|
|
18
|
+
* non-zero-leading integer. Identical to the pattern the Page Kit checkpoint
|
|
19
|
+
* shipped with — this constant is now its single home.
|
|
20
|
+
*/
|
|
21
|
+
export declare const RELEASED_SDK_VERSION_PATTERN: RegExp;
|
|
22
|
+
/** Why a value was rejected. Diagnostic detail only — every reason is equally fatal. */
|
|
23
|
+
export type SdkVersionRejectionReason = 'non-string' | 'empty' | 'padded' | 'prerelease-or-build' | 'non-canonical';
|
|
24
|
+
export type SdkVersionParseResult = {
|
|
25
|
+
ok: true;
|
|
26
|
+
version: string;
|
|
27
|
+
} | {
|
|
28
|
+
ok: false;
|
|
29
|
+
reason: SdkVersionRejectionReason;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Parse a candidate SDK version strictly. Never normalizes: a value either IS
|
|
33
|
+
* the canonical released form or it is rejected — the reason is diagnostic
|
|
34
|
+
* classification for error messages, not a repair hint the parser acts on.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseSdkVersion(value: unknown): SdkVersionParseResult;
|
|
37
|
+
/**
|
|
38
|
+
* Boolean form of parseSdkVersion — drop-in for the checkpoint's original
|
|
39
|
+
* `isReleasedSemver` predicate.
|
|
40
|
+
*/
|
|
41
|
+
export declare function isReleasedSdkVersion(value: unknown): value is string;
|
|
42
|
+
/** Human-readable fragment for a rejection reason, for composing error messages. */
|
|
43
|
+
export declare function describeSdkVersionRejection(reason: SdkVersionRejectionReason): string;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sdk-version-parse — the ONE strict released-SDK-version parser.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from the Page Kit SDK-version checkpoint (src/page-kit-sdk-version.mjs)
|
|
5
|
+
* so authoring-time validation (the SdkVersion rule) and build-time gating
|
|
6
|
+
* (the checkpoint) reject exactly the same values. The acceptance predicate is
|
|
7
|
+
* the checkpoint's original one, unchanged: a plain string in canonical
|
|
8
|
+
* released `MAJOR.MINOR.PATCH` form — no leading zeros, no `v` prefix, no
|
|
9
|
+
* surrounding whitespace, no prerelease or build-metadata suffix, nothing but
|
|
10
|
+
* the three dot-separated numeric components.
|
|
11
|
+
*
|
|
12
|
+
* Dependency-free and browser-bundle-safe on purpose: the Map Builder bundles
|
|
13
|
+
* the campaign-spec module via esbuild, so nothing in here may import node
|
|
14
|
+
* builtins.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Canonical released semver: MAJOR.MINOR.PATCH, each component `0` or a
|
|
18
|
+
* non-zero-leading integer. Identical to the pattern the Page Kit checkpoint
|
|
19
|
+
* shipped with — this constant is now its single home.
|
|
20
|
+
*/
|
|
21
|
+
export const RELEASED_SDK_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
22
|
+
/**
|
|
23
|
+
* Parse a candidate SDK version strictly. Never normalizes: a value either IS
|
|
24
|
+
* the canonical released form or it is rejected — the reason is diagnostic
|
|
25
|
+
* classification for error messages, not a repair hint the parser acts on.
|
|
26
|
+
*/
|
|
27
|
+
export function parseSdkVersion(value) {
|
|
28
|
+
if (typeof value !== 'string')
|
|
29
|
+
return { ok: false, reason: 'non-string' };
|
|
30
|
+
const trimmed = value.trim();
|
|
31
|
+
if (trimmed === '')
|
|
32
|
+
return { ok: false, reason: 'empty' };
|
|
33
|
+
if (RELEASED_SDK_VERSION_PATTERN.test(value))
|
|
34
|
+
return { ok: true, version: value };
|
|
35
|
+
if (value !== trimmed && RELEASED_SDK_VERSION_PATTERN.test(trimmed)) {
|
|
36
|
+
return { ok: false, reason: 'padded' };
|
|
37
|
+
}
|
|
38
|
+
// Classify against the trimmed form so a padded prerelease (" 0.4.37-beta.1 ")
|
|
39
|
+
// reports the substantive rejection (the suffix) rather than 'non-canonical'.
|
|
40
|
+
if (/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)[-+]/.test(trimmed)) {
|
|
41
|
+
return { ok: false, reason: 'prerelease-or-build' };
|
|
42
|
+
}
|
|
43
|
+
return { ok: false, reason: 'non-canonical' };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Boolean form of parseSdkVersion — drop-in for the checkpoint's original
|
|
47
|
+
* `isReleasedSemver` predicate.
|
|
48
|
+
*/
|
|
49
|
+
export function isReleasedSdkVersion(value) {
|
|
50
|
+
return typeof value === 'string' && RELEASED_SDK_VERSION_PATTERN.test(value);
|
|
51
|
+
}
|
|
52
|
+
const REJECTION_DETAIL = {
|
|
53
|
+
'non-string': 'value is not a string',
|
|
54
|
+
empty: 'value is empty',
|
|
55
|
+
padded: 'value has surrounding whitespace',
|
|
56
|
+
'prerelease-or-build': 'prerelease/build suffixes are not released versions',
|
|
57
|
+
'non-canonical': 'value is not in canonical released form',
|
|
58
|
+
};
|
|
59
|
+
/** Human-readable fragment for a rejection reason, for composing error messages. */
|
|
60
|
+
export function describeSdkVersionRejection(reason) {
|
|
61
|
+
return REJECTION_DETAIL[reason];
|
|
62
|
+
}
|