@pagefront/lint-commerce 0.6.0 → 0.8.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 (39) hide show
  1. package/README.md +42 -2
  2. package/dist/catalogue.js +15 -2
  3. package/dist/organization-catalogue.js +1 -1
  4. package/dist/product-schema.js +143 -19
  5. package/dist/rules/configurator-pricing.d.ts +114 -0
  6. package/dist/rules/configurator-pricing.js +349 -0
  7. package/dist/rules/pdp-e-001.js +12 -3
  8. package/dist/rules/pdp-e-005.d.ts +12 -0
  9. package/dist/rules/pdp-e-005.js +40 -0
  10. package/dist/rules/pdp-e-006.d.ts +11 -0
  11. package/dist/rules/pdp-e-006.js +36 -0
  12. package/dist/rules/pdp-e-007.d.ts +12 -0
  13. package/dist/rules/pdp-e-007.js +53 -0
  14. package/dist/rules/pdp-e-008.d.ts +10 -0
  15. package/dist/rules/pdp-e-008.js +41 -0
  16. package/dist/rules/pdp-e-009.d.ts +12 -0
  17. package/dist/rules/pdp-e-009.js +62 -0
  18. package/dist/rules/pdp-e-010.d.ts +10 -0
  19. package/dist/rules/pdp-e-010.js +37 -0
  20. package/dist/rules/pdp-e-011.d.ts +11 -0
  21. package/dist/rules/pdp-e-011.js +53 -0
  22. package/dist/rules/pdp-e-012.d.ts +2 -0
  23. package/dist/rules/pdp-e-012.js +32 -0
  24. package/dist/rules/pdp-w-031.d.ts +12 -0
  25. package/dist/rules/pdp-w-031.js +35 -0
  26. package/dist/rules/pdp-w-032.d.ts +10 -0
  27. package/dist/rules/pdp-w-032.js +42 -0
  28. package/dist/rules/pdp-w-033.d.ts +10 -0
  29. package/dist/rules/pdp-w-033.js +30 -0
  30. package/dist/rules/pdp-w-034.d.ts +10 -0
  31. package/dist/rules/pdp-w-034.js +28 -0
  32. package/dist/rules/pdp-w-035.d.ts +11 -0
  33. package/dist/rules/pdp-w-035.js +41 -0
  34. package/dist/rules/sheet-url-host.d.ts +11 -0
  35. package/dist/rules/sheet-url-host.js +22 -0
  36. package/dist/schema-validators.js +8 -2
  37. package/dist/version.d.ts +1 -1
  38. package/dist/version.js +1 -1
  39. package/package.json +1 -1
@@ -0,0 +1,62 @@
1
+ import { asObject, resolveConfigurator, seg } from "./configurator-pricing.js";
2
+ /**
3
+ * PDP-E-009 — Modifier for an unknown dimension or option.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Applies to a pricing block whose `configurator` reference resolves.
7
+ * A `modifiers` key naming no dimension, or a dimension declared
8
+ * `affectsPrice: false`, fires once at that key; under a priced
9
+ * dimension, each option key that is not one of its options fires at
10
+ * its own path.
11
+ */
12
+ export const pdpE009 = {
13
+ id: "PDP-E-009",
14
+ title: "Modifier for an unknown dimension or option",
15
+ severity: "error",
16
+ target: "$.data.offers[*]['pagefront:configuratorPricing']",
17
+ check: (match, ctx) => {
18
+ const block = asObject(match.value);
19
+ const modifiers = asObject(block?.["modifiers"]);
20
+ if (block === null || modifiers === null)
21
+ return null;
22
+ const configurator = resolveConfigurator(block, ctx.sheet);
23
+ if (configurator === null)
24
+ return null;
25
+ const hits = [];
26
+ for (const [dimension, entry] of Object.entries(modifiers)) {
27
+ const path = `${match.path}['modifiers']${seg(dimension)}`;
28
+ const d = configurator.byName.get(dimension);
29
+ if (d === undefined) {
30
+ hits.push({
31
+ path,
32
+ values: { dimension, problem: `names \`${dimension}\`, which is not a dimension of the configurator` },
33
+ });
34
+ continue;
35
+ }
36
+ if (!d.priced) {
37
+ hits.push({
38
+ path,
39
+ values: { dimension, problem: `names \`${dimension}\`, which is declared \`affectsPrice: false\`` },
40
+ });
41
+ continue;
42
+ }
43
+ for (const option of Object.keys(asObject(entry) ?? {})) {
44
+ if (d.optionKeys !== null && d.optionKeys.includes(option))
45
+ continue;
46
+ hits.push({
47
+ path: `${path}${seg(option)}`,
48
+ values: {
49
+ dimension,
50
+ option,
51
+ problem: `names \`${option}\`, which is not an option of \`${dimension}\``,
52
+ },
53
+ });
54
+ }
55
+ }
56
+ return hits.length > 0 ? hits : null;
57
+ },
58
+ messageTemplate: "Modifier at `{path}` {problem}.",
59
+ remediation: "Spell dimension names and option values as the configurator spells them. Remove modifiers for dimensions that do not change the price, or remove `affectsPrice: false` from a dimension that does.",
60
+ introduced: "v0.9 (2026-10-01 revision)",
61
+ specReference: "product.md#configurator-pricing-and-price-bands",
62
+ };
@@ -0,0 +1,10 @@
1
+ import type { Rule } from "@pagefront/lint-core";
2
+ /**
3
+ * PDP-E-010 — Offer price disagrees with base price.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * An `Offer` carrying a pricing block has `price` equal to the block's
7
+ * `basePrice` as a number, whatever the block's `pricing` value. The
8
+ * finding sits at the Offer's `price`.
9
+ */
10
+ export declare const pdpE010: Rule;
@@ -0,0 +1,37 @@
1
+ import { asObject, compareDecimal, parseDecimal, pricingBlock } from "./configurator-pricing.js";
2
+ /**
3
+ * PDP-E-010 — Offer price disagrees with base price.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * An `Offer` carrying a pricing block has `price` equal to the block's
7
+ * `basePrice` as a number, whatever the block's `pricing` value. The
8
+ * finding sits at the Offer's `price`.
9
+ */
10
+ export const pdpE010 = {
11
+ id: "PDP-E-010",
12
+ title: "Offer price disagrees with base price",
13
+ severity: "error",
14
+ target: "$.data.offers[*]",
15
+ check: (match) => {
16
+ const offer = asObject(match.value);
17
+ if (offer === null || offer["@type"] !== "Offer")
18
+ return null;
19
+ const block = pricingBlock(offer);
20
+ if (block === null)
21
+ return null;
22
+ const price = parseDecimal(offer["price"]);
23
+ const basePrice = parseDecimal(block["basePrice"]);
24
+ if (price === undefined || basePrice === undefined)
25
+ return null;
26
+ if (compareDecimal(price, basePrice) === 0)
27
+ return null;
28
+ return {
29
+ path: `${match.path}['price']`,
30
+ values: { price: String(offer["price"]), basePrice: String(block["basePrice"]) },
31
+ };
32
+ },
33
+ messageTemplate: "Offer `price` at `{path}` is `{price}` but its pricing block's `basePrice` is `{basePrice}`.",
34
+ remediation: "Make the two amounts equal. If `price` is the amount the page shows on load, set `baseConfiguration` to the configuration the page preselects and `basePrice` to its total.",
35
+ introduced: "v0.9 (2026-10-01 revision)",
36
+ specReference: "product.md#configurator-pricing-and-price-bands",
37
+ };
@@ -0,0 +1,11 @@
1
+ import type { Rule } from "@pagefront/lint-core";
2
+ /**
3
+ * PDP-E-011 — AggregateOffer band disagrees with computed band.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Applies to an `AggregateOffer` whose pricing block is complete. Silent
7
+ * when the band cannot be computed: the reference does not resolve, the
8
+ * block fires PDP-E-007 or PDP-W-032, or every configuration is
9
+ * excluded. One finding per disagreeing bound, at the bound.
10
+ */
11
+ export declare const pdpE011: Rule;
@@ -0,0 +1,53 @@
1
+ import { asObject, baseConfigurationProblems, compareDecimal, computeBand, formatDecimal, isComplete, missingModifierProblems, parseDecimal, pricingBlock, resolveConfigurator, } from "./configurator-pricing.js";
2
+ /**
3
+ * PDP-E-011 — AggregateOffer band disagrees with computed band.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Applies to an `AggregateOffer` whose pricing block is complete. Silent
7
+ * when the band cannot be computed: the reference does not resolve, the
8
+ * block fires PDP-E-007 or PDP-W-032, or every configuration is
9
+ * excluded. One finding per disagreeing bound, at the bound.
10
+ */
11
+ export const pdpE011 = {
12
+ id: "PDP-E-011",
13
+ title: "AggregateOffer band disagrees with computed band",
14
+ severity: "error",
15
+ target: "$.data.offers[*]",
16
+ check: (match, ctx) => {
17
+ const offer = asObject(match.value);
18
+ if (offer === null || offer["@type"] !== "AggregateOffer")
19
+ return null;
20
+ const block = pricingBlock(offer);
21
+ if (block === null || !isComplete(block))
22
+ return null;
23
+ const configurator = resolveConfigurator(block, ctx.sheet);
24
+ if (configurator === null)
25
+ return null;
26
+ if (baseConfigurationProblems(configurator, block).length > 0)
27
+ return null;
28
+ if (missingModifierProblems(configurator, block).length > 0)
29
+ return null;
30
+ const band = computeBand(configurator, block);
31
+ if (band === null)
32
+ return null;
33
+ const hits = [];
34
+ const bounds = [
35
+ { bound: "lowPrice", extreme: "cheapest", computed: band.min },
36
+ { bound: "highPrice", extreme: "dearest", computed: band.max },
37
+ ];
38
+ for (const { bound, extreme, computed } of bounds) {
39
+ const stated = parseDecimal(offer[bound]);
40
+ if (stated === undefined || compareDecimal(stated, computed) === 0)
41
+ continue;
42
+ hits.push({
43
+ path: `${match.path}['${bound}']`,
44
+ values: { bound, extreme, stated: String(offer[bound]), computed: formatDecimal(computed) },
45
+ });
46
+ }
47
+ return hits.length > 0 ? hits : null;
48
+ },
49
+ messageTemplate: "`{bound}` at `{path}` is `{stated}` but the {extreme} non-excluded configuration computed from the pricing block totals `{computed}`.",
50
+ remediation: "Correct the bound, or correct the price list: a missing adjustment, a missing exclusion and a wrong modifier each move the computed band. If the price list is known to have gaps, declare `pricing: \"partial\"`.",
51
+ introduced: "v0.9 (2026-10-01 revision)",
52
+ specReference: "product.md#configurator-pricing-and-price-bands",
53
+ };
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "@pagefront/lint-core";
2
+ export declare const pdpE012: Rule;
@@ -0,0 +1,32 @@
1
+ import { pricedConfiguratorId, pricingBlock } from "./configurator-pricing.js";
2
+ /**
3
+ * PDP-E-012 — Configurator priced by more than one offer.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Fires on each `offers` entry whose pricing block names a configurator
7
+ * that an earlier entry already prices (exact string match on
8
+ * `configurator.@id`); `{firstPath}` is the first such entry.
9
+ */
10
+ function pricedId(offer) {
11
+ const block = pricingBlock(offer);
12
+ return block === null ? undefined : pricedConfiguratorId(block);
13
+ }
14
+ export const pdpE012 = {
15
+ id: "PDP-E-012",
16
+ title: "Configurator priced by more than one offer",
17
+ severity: "error",
18
+ target: "$.data.offers[*]",
19
+ check: (match, ctx) => {
20
+ const value = pricedId(match.value);
21
+ if (value === undefined)
22
+ return null;
23
+ const first = ctx.allMatches.find((m) => pricedId(m.value) === value);
24
+ if (first === undefined || first.path === match.path)
25
+ return null;
26
+ return { values: { value, firstPath: first.path } };
27
+ },
28
+ messageTemplate: "Offer at `{path}` prices configurator `{value}`, which the offer at `{firstPath}` already prices.",
29
+ remediation: "Keep one pricing block per configurator. Publish each further market's price list in that market's own Sheet.",
30
+ introduced: "v0.9 (2026-10-01 revision)",
31
+ specReference: "product.md#configurator-pricing-and-price-bands",
32
+ };
@@ -0,0 +1,12 @@
1
+ import type { Rule } from "@pagefront/lint-core";
2
+ /**
3
+ * PDP-W-031 — Seller reference not locatable.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Fires when an offer's `seller` carries `@id` but no
7
+ * `pagefront:sheetUrl`, and the offer carries neither
8
+ * `hasMerchantReturnPolicy` nor `shippingDetails`: the terms it would
9
+ * inherit sit on a seller sheet no consumer can locate. The finding
10
+ * sits at the `seller` path.
11
+ */
12
+ export declare const pdpW031: Rule;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * PDP-W-031 — Seller reference not locatable.
3
+ * Transcribed from commerce/spec/rules.md (normative).
4
+ *
5
+ * Fires when an offer's `seller` carries `@id` but no
6
+ * `pagefront:sheetUrl`, and the offer carries neither
7
+ * `hasMerchantReturnPolicy` nor `shippingDetails`: the terms it would
8
+ * inherit sit on a seller sheet no consumer can locate. The finding
9
+ * sits at the `seller` path.
10
+ */
11
+ export const pdpW031 = {
12
+ id: "PDP-W-031",
13
+ title: "Seller reference not locatable",
14
+ severity: "warning",
15
+ target: "$.data.offers[*]",
16
+ check: (match) => {
17
+ const offer = match.value;
18
+ if (typeof offer !== "object" || offer === null)
19
+ return null;
20
+ const o = offer;
21
+ const seller = o["seller"];
22
+ if (typeof seller !== "object" || seller === null || Array.isArray(seller))
23
+ return null;
24
+ const s = seller;
25
+ if (s["@id"] === undefined || s["pagefront:sheetUrl"] !== undefined)
26
+ return null;
27
+ if (o["hasMerchantReturnPolicy"] !== undefined || o["shippingDetails"] !== undefined)
28
+ return null;
29
+ return { path: `${match.path}['seller']` };
30
+ },
31
+ messageTemplate: "Seller at `{path}` has an `@id` but no `pagefront:sheetUrl`, and the Offer states no return or shipping terms of its own.",
32
+ remediation: "Add `pagefront:sheetUrl` to the seller reference, or state the terms on the Offer.",
33
+ introduced: "v0.9",
34
+ specReference: "organization.md#linking-contract-identity-only",
35
+ };
@@ -0,0 +1,10 @@
1
+ import type { Rule } from "@pagefront/lint-core";
2
+ /**
3
+ * PDP-W-032 — Priced option without a modifier.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Applies to a resolving pricing block whose `pricing` is absent or
7
+ * `complete`. One finding per priced option with no modifier, and one
8
+ * per priced dimension that enumerates no options, all at the block.
9
+ */
10
+ export declare const pdpW032: Rule;
@@ -0,0 +1,42 @@
1
+ import { asObject, isComplete, missingModifierProblems, resolveConfigurator, } from "./configurator-pricing.js";
2
+ /**
3
+ * PDP-W-032 — Priced option without a modifier.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Applies to a resolving pricing block whose `pricing` is absent or
7
+ * `complete`. One finding per priced option with no modifier, and one
8
+ * per priced dimension that enumerates no options, all at the block.
9
+ */
10
+ export const pdpW032 = {
11
+ id: "PDP-W-032",
12
+ title: "Priced option without a modifier",
13
+ severity: "warning",
14
+ target: "$.data.offers[*]['pagefront:configuratorPricing']",
15
+ check: (match, ctx) => {
16
+ const block = asObject(match.value);
17
+ if (block === null || !isComplete(block))
18
+ return null;
19
+ const configurator = resolveConfigurator(block, ctx.sheet);
20
+ if (configurator === null)
21
+ return null;
22
+ const hits = missingModifierProblems(configurator, block).map((p) => p.kind === "no-options"
23
+ ? {
24
+ values: {
25
+ dimension: p.dimension,
26
+ problem: `cannot state modifiers for \`${p.dimension}\`, a priced dimension that enumerates no options`,
27
+ },
28
+ }
29
+ : {
30
+ values: {
31
+ dimension: p.dimension,
32
+ option: p.option,
33
+ problem: `states no modifier for option \`${p.option}\` of \`${p.dimension}\``,
34
+ },
35
+ });
36
+ return hits.length > 0 ? hits : null;
37
+ },
38
+ messageTemplate: "Pricing block at `{path}` {problem}, and `pricing` is not `partial`.",
39
+ remediation: "State the modifier, `\"0\"` included. If the dimension never changes the price, declare `affectsPrice: false` on it. If the price is not known, declare `pricing: \"partial\"`.",
40
+ introduced: "v0.9 (2026-10-01 revision)",
41
+ specReference: "product.md#configurator-pricing-and-price-bands",
42
+ };
@@ -0,0 +1,10 @@
1
+ import type { Rule } from "@pagefront/lint-core";
2
+ /**
3
+ * PDP-W-033 — Priced configurator with no offer pricing it.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Fires when the configurator has a priced dimension, no `offers` entry
7
+ * carries a pricing block, and no `offers` entry states a price. The
8
+ * last case is PDP-W-034's; the two never fire on the same Sheet.
9
+ */
10
+ export declare const pdpW033: Rule;
@@ -0,0 +1,30 @@
1
+ import { entryStatesPrice, hasPricedDimension, offerEntries, pricingBlock, readConfigurator, } from "./configurator-pricing.js";
2
+ /**
3
+ * PDP-W-033 — Priced configurator with no offer pricing it.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Fires when the configurator has a priced dimension, no `offers` entry
7
+ * carries a pricing block, and no `offers` entry states a price. The
8
+ * last case is PDP-W-034's; the two never fire on the same Sheet.
9
+ */
10
+ export const pdpW033 = {
11
+ id: "PDP-W-033",
12
+ title: "Priced configurator with no offer pricing it",
13
+ severity: "warning",
14
+ target: "$.data['pagefront:configurator']",
15
+ check: (_match, ctx) => {
16
+ const configurator = readConfigurator(ctx.sheet);
17
+ if (configurator === null || !hasPricedDimension(configurator))
18
+ return null;
19
+ const offers = offerEntries(ctx.sheet);
20
+ if (offers.some((o) => pricingBlock(o) !== null))
21
+ return null;
22
+ if (offers.some(entryStatesPrice))
23
+ return null;
24
+ return {};
25
+ },
26
+ messageTemplate: "Configurator at `{path}` has priced dimensions but no offer prices it.",
27
+ remediation: "Add an offer carrying `pagefront:configuratorPricing` for this configurator. If no option changes the price, declare `affectsPrice: false` on each dimension.",
28
+ introduced: "v0.9 (2026-10-01 revision)",
29
+ specReference: "product.md#configurator-pricing-and-price-bands",
30
+ };
@@ -0,0 +1,10 @@
1
+ import type { Rule } from "@pagefront/lint-core";
2
+ /**
3
+ * PDP-W-034 — Offer beside a configurator with no pricing block.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Fires once per Sheet, at the first `offers` entry that states a
7
+ * price, when the Sheet carries a configurator with a priced dimension
8
+ * and no `offers` entry carries a pricing block.
9
+ */
10
+ export declare const pdpW034: Rule;
@@ -0,0 +1,28 @@
1
+ import { entryStatesPrice, hasPricedDimension, pricingBlock, readConfigurator, } from "./configurator-pricing.js";
2
+ /**
3
+ * PDP-W-034 — Offer beside a configurator with no pricing block.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Fires once per Sheet, at the first `offers` entry that states a
7
+ * price, when the Sheet carries a configurator with a priced dimension
8
+ * and no `offers` entry carries a pricing block.
9
+ */
10
+ export const pdpW034 = {
11
+ id: "PDP-W-034",
12
+ title: "Offer beside a configurator with no pricing block",
13
+ severity: "warning",
14
+ target: "$.data.offers[*]",
15
+ check: (match, ctx) => {
16
+ const configurator = readConfigurator(ctx.sheet);
17
+ if (configurator === null || !hasPricedDimension(configurator))
18
+ return null;
19
+ if (ctx.allMatches.some((m) => pricingBlock(m.value) !== null))
20
+ return null;
21
+ const first = ctx.allMatches.find((m) => entryStatesPrice(m.value));
22
+ return first !== undefined && first.path === match.path ? {} : null;
23
+ },
24
+ messageTemplate: "Offer price is not linked to the configurator; state which configuration it prices.",
25
+ remediation: "Add `pagefront:configuratorPricing` to the offer: give the configurator an `@id`, reference it, and name the configuration the price is the total of. When only that one total is known, declare `pricing: \"partial\"`.",
26
+ introduced: "v0.9 (2026-10-01 revision)",
27
+ specReference: "product.md#configurator-pricing-and-price-bands",
28
+ };
@@ -0,0 +1,11 @@
1
+ import type { Rule } from "@pagefront/lint-core";
2
+ /**
3
+ * PDP-W-035 — `basePrice` may be a component price.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Heuristic, one finding per pricing block: (a) `basePrice` equals the
7
+ * largest modifier of one dimension's `modifiers` entry, or (b)
8
+ * `basePrice` is below every non-zero modifier of one entry that has at
9
+ * least one.
10
+ */
11
+ export declare const pdpW035: Rule;
@@ -0,0 +1,41 @@
1
+ import { asObject, compareDecimal, isZero, parseDecimal, } from "./configurator-pricing.js";
2
+ /**
3
+ * PDP-W-035 — `basePrice` may be a component price.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Heuristic, one finding per pricing block: (a) `basePrice` equals the
7
+ * largest modifier of one dimension's `modifiers` entry, or (b)
8
+ * `basePrice` is below every non-zero modifier of one entry that has at
9
+ * least one.
10
+ */
11
+ export const pdpW035 = {
12
+ id: "PDP-W-035",
13
+ title: "`basePrice` may be a component price",
14
+ severity: "warning",
15
+ target: "$.data.offers[*]['pagefront:configuratorPricing']",
16
+ check: (match) => {
17
+ const block = asObject(match.value);
18
+ const modifiers = asObject(block?.["modifiers"]);
19
+ const basePrice = parseDecimal(block?.["basePrice"]);
20
+ if (modifiers === null || basePrice === undefined)
21
+ return null;
22
+ for (const entry of Object.values(modifiers)) {
23
+ const amounts = Object.values(asObject(entry) ?? {})
24
+ .map(parseDecimal)
25
+ .filter((d) => d !== undefined);
26
+ if (amounts.length === 0)
27
+ continue;
28
+ const largest = amounts.reduce((a, b) => (compareDecimal(b, a) > 0 ? b : a));
29
+ if (compareDecimal(basePrice, largest) === 0)
30
+ return {};
31
+ const nonZero = amounts.filter((d) => !isZero(d));
32
+ if (nonZero.length > 0 && nonZero.every((d) => compareDecimal(basePrice, d) < 0))
33
+ return {};
34
+ }
35
+ return null;
36
+ },
37
+ messageTemplate: "basePrice may be a component price, not a configured total.",
38
+ remediation: "Confirm that `basePrice` is what a buyer pays for the whole of `baseConfiguration`. If it is the price of one part, replace it with the configured total and recompute the modifiers as differences from that total.",
39
+ introduced: "v0.9 (2026-10-01 revision)",
40
+ specReference: "product.md#configurator-pricing-and-price-bands",
41
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Shared helper for the organization-reference rules (PDP-E-005,
3
+ * PDP-E-006, PDP-W-031): the *host* of a value per the catalogue's
4
+ * Definitions section — the host a WHATWG URL parser yields (lowercased,
5
+ * default port dropped) for a full absolute URL, or `null` when the
6
+ * value is not a full absolute URL, does not parse, or parses with an
7
+ * empty host (`urn:…`).
8
+ */
9
+ export declare function hostOf(value: unknown): string | null;
10
+ /** The rendered form of a host placeholder: the host, or `(none)`. */
11
+ export declare const renderHost: (host: string | null) => string;
@@ -0,0 +1,22 @@
1
+ import { isFullAbsoluteUrl } from "@pagefront/lint-core";
2
+ /**
3
+ * Shared helper for the organization-reference rules (PDP-E-005,
4
+ * PDP-E-006, PDP-W-031): the *host* of a value per the catalogue's
5
+ * Definitions section — the host a WHATWG URL parser yields (lowercased,
6
+ * default port dropped) for a full absolute URL, or `null` when the
7
+ * value is not a full absolute URL, does not parse, or parses with an
8
+ * empty host (`urn:…`).
9
+ */
10
+ export function hostOf(value) {
11
+ if (!isFullAbsoluteUrl(value))
12
+ return null;
13
+ try {
14
+ const host = new URL(value).host;
15
+ return host === "" ? null : host;
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ /** The rendered form of a host placeholder: the host, or `(none)`. */
22
+ export const renderHost = (host) => host ?? "(none)";