@pagefront/lint-commerce 0.8.0 → 0.9.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.
@@ -4,8 +4,9 @@ import { asObject, isComplete, missingModifierProblems, resolveConfigurator, } f
4
4
  * Transcribed from commerce/spec/rules.md (normative).
5
5
  *
6
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.
7
+ * `complete`. One finding per priced option with no modifier, one per
8
+ * priced text dimension with no `*` modifier, and one per priced select
9
+ * dimension that enumerates no options, all at the block.
9
10
  */
10
11
  export const pdpW032 = {
11
12
  id: "PDP-W-032",
@@ -19,24 +20,31 @@ export const pdpW032 = {
19
20
  const configurator = resolveConfigurator(block, ctx.sheet);
20
21
  if (configurator === null)
21
22
  return null;
22
- const hits = missingModifierProblems(configurator, block).map((p) => p.kind === "no-options"
23
+ const hits = missingModifierProblems(configurator, block).map((p) => p.kind === "no-text-modifier"
23
24
  ? {
24
25
  values: {
25
26
  dimension: p.dimension,
26
- problem: `cannot state modifiers for \`${p.dimension}\`, a priced dimension that enumerates no options`,
27
+ problem: `states no \`*\` modifier for text dimension \`${p.dimension}\``,
27
28
  },
28
29
  }
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
- });
30
+ : p.kind === "no-options"
31
+ ? {
32
+ values: {
33
+ dimension: p.dimension,
34
+ problem: `cannot state modifiers for \`${p.dimension}\`, a priced dimension that enumerates no options`,
35
+ },
36
+ }
37
+ : {
38
+ values: {
39
+ dimension: p.dimension,
40
+ option: p.option,
41
+ problem: `states no modifier for option \`${p.option}\` of \`${p.dimension}\``,
42
+ },
43
+ });
36
44
  return hits.length > 0 ? hits : null;
37
45
  },
38
46
  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\"`.",
47
+ remediation: "State the modifier, zero included; for a text dimension, state it under the key `\"*\"`. If the dimension never changes the price, declare `affectsPrice: false` on it. If the price is not known, declare `pricing: \"partial\"`.",
40
48
  introduced: "v0.9 (2026-10-01 revision)",
41
49
  specReference: "product.md#configurator-pricing-and-price-bands",
42
50
  };
@@ -0,0 +1,2 @@
1
+ import { type Rule } from "@pagefront/lint-core";
2
+ export declare const pdpW036: Rule;
@@ -0,0 +1,31 @@
1
+ import { valueAtPath } from "@pagefront/lint-core";
2
+ import { asObject } from "./configurator-pricing.js";
3
+ /**
4
+ * PDP-W-036 — Option value looks like a display label.
5
+ * Transcribed from commerce/spec/rules.md (normative).
6
+ *
7
+ * Heuristic on string option values: whitespace, an uppercase letter,
8
+ * or a decimal number followed by letters to the end (a unit suffix).
9
+ * Options of a sizing dimension (one declaring `system`) are exempt;
10
+ * PDP-W-037 checks those.
11
+ */
12
+ const UNIT_SUFFIX = /^-?\d+(\.\d+)?\p{L}+$/u;
13
+ export const pdpW036 = {
14
+ id: "PDP-W-036",
15
+ title: "Option value looks like a display label",
16
+ severity: "warning",
17
+ target: "$.data['pagefront:configurator'].dimensions[*].options[*]",
18
+ check: (match, ctx) => {
19
+ const value = asObject(match.value)?.["value"];
20
+ if (typeof value !== "string")
21
+ return null;
22
+ const dimension = asObject(valueAtPath(ctx.sheet, match.path.replace(/\['options'\]\[\d+\]$/, "")));
23
+ if (typeof dimension?.["system"] === "string")
24
+ return null;
25
+ return /\s/.test(value) || /\p{Lu}/u.test(value) || UNIT_SUFFIX.test(value) ? {} : null;
26
+ },
27
+ messageTemplate: "Option value should be a stable identifier; put the label in description.",
28
+ remediation: "Replace the value with a lowercase identifier without spaces (`yellow_gold`, `0.5`) and move the label to `description`. Put a unit in the dimension's `note`. Update the pricing block's keys, `baseConfiguration`, `when` and `exclusions` to the new value.",
29
+ introduced: "v0.9 (2026-10-01 revision)",
30
+ specReference: "product.md#configurator-pricing-and-price-bands",
31
+ };
@@ -0,0 +1,2 @@
1
+ import { type Rule } from "@pagefront/lint-core";
2
+ export declare const pdpW037: Rule;
@@ -0,0 +1,37 @@
1
+ import { valueAtPath } from "@pagefront/lint-core";
2
+ import { asObject } from "./configurator-pricing.js";
3
+ /**
4
+ * PDP-W-037 — Option in a sizing dimension is not a size.
5
+ * Transcribed from commerce/spec/rules.md (normative).
6
+ *
7
+ * Heuristic on string option values of a dimension whose `system` is
8
+ * `UK`, `EU`, `US` or `JP`: the value is written as that system writes
9
+ * a size. Other systems are out of scope.
10
+ */
11
+ const NUMERIC_SIZE = /^\d+(\.\d+|½)?$/;
12
+ const SIZE_IN_SYSTEM = {
13
+ UK: /^[A-Z]\s*½?$/,
14
+ EU: NUMERIC_SIZE,
15
+ US: NUMERIC_SIZE,
16
+ JP: NUMERIC_SIZE,
17
+ };
18
+ export const pdpW037 = {
19
+ id: "PDP-W-037",
20
+ title: "Option in a sizing dimension is not a size",
21
+ severity: "warning",
22
+ target: "$.data['pagefront:configurator'].dimensions[*].options[*]",
23
+ check: (match, ctx) => {
24
+ const value = asObject(match.value)?.["value"];
25
+ if (typeof value !== "string")
26
+ return null;
27
+ const dimension = asObject(valueAtPath(ctx.sheet, match.path.replace(/\['options'\]\[\d+\]$/, "")));
28
+ const system = dimension?.["system"];
29
+ if (typeof system !== "string" || !Object.hasOwn(SIZE_IN_SYSTEM, system))
30
+ return null;
31
+ return SIZE_IN_SYSTEM[system].test(value) ? null : {};
32
+ },
33
+ messageTemplate: "Option does not look like a size in the declared system.",
34
+ remediation: "Remove the entry if it is a service, and describe the service in the dimension's `note` or in `pagefront:madeToOrderTerms.aftermarketServices`. If it is a size, write it as the system writes it, without the system's name.",
35
+ introduced: "v0.9 (2026-10-01 revision)",
36
+ specReference: "product.md#configurator-pricing-and-price-bands",
37
+ };
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "@pagefront/lint-core";
2
+ export declare const pdpW038: Rule;
@@ -0,0 +1,30 @@
1
+ import { asObject } from "./configurator-pricing.js";
2
+ /**
3
+ * PDP-W-038 — Configurator note reads as page copy.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Heuristic on English Sheets (`inLanguage` `en` or `en-…`): the
7
+ * dimension's `note` contains `you`, `your`, `select`, `order` or
8
+ * `contact` as a whole word, delimited by non-letters.
9
+ */
10
+ const PAGE_COPY_WORD = /(?<!\p{L})(you|your|select|order|contact)(?!\p{L})/iu;
11
+ export const pdpW038 = {
12
+ id: "PDP-W-038",
13
+ title: "Configurator note reads as page copy",
14
+ severity: "warning",
15
+ target: "$.data['pagefront:configurator'].dimensions[*]",
16
+ check: (match, ctx) => {
17
+ const inLanguage = asObject(ctx.sheet.data)?.["inLanguage"];
18
+ if (typeof inLanguage !== "string" || !/^en(-|$)/i.test(inLanguage))
19
+ return null;
20
+ const note = asObject(match.value)?.["note"];
21
+ if (typeof note !== "string")
22
+ return null;
23
+ const found = PAGE_COPY_WORD.exec(note);
24
+ return found === null ? null : { values: { word: found[1].toLowerCase() } };
25
+ },
26
+ messageTemplate: "Note reads as page copy; state the fact.",
27
+ remediation: "Restate the note as a fact in the third person: what the merchant offers or recommends, with no \"you\" and no instruction.",
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-039 — Size advice carried as prose only.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Fires when `pagefront:sizeRecommendation` carries a non-empty
7
+ * `description` and no `sizeAdjustment`, and the configurator has a
8
+ * sizing dimension. `{system}` is the first sizing dimension's system.
9
+ */
10
+ export declare const pdpW039: Rule;
@@ -0,0 +1,29 @@
1
+ import { asObject, readConfigurator } from "./configurator-pricing.js";
2
+ /**
3
+ * PDP-W-039 — Size advice carried as prose only.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Fires when `pagefront:sizeRecommendation` carries a non-empty
7
+ * `description` and no `sizeAdjustment`, and the configurator has a
8
+ * sizing dimension. `{system}` is the first sizing dimension's system.
9
+ */
10
+ export const pdpW039 = {
11
+ id: "PDP-W-039",
12
+ title: "Size advice carried as prose only",
13
+ severity: "warning",
14
+ target: "$.data['pagefront:sizeRecommendation']",
15
+ check: (match, ctx) => {
16
+ const recommendation = asObject(match.value);
17
+ if (recommendation === null || recommendation["sizeAdjustment"] !== undefined)
18
+ return null;
19
+ const description = recommendation["description"];
20
+ if (typeof description !== "string" || description.trim() === "")
21
+ return null;
22
+ const sizing = (readConfigurator(ctx.sheet)?.dimensions ?? []).find((d) => d.system !== undefined);
23
+ return sizing?.system === undefined ? null : { values: { system: sizing.system } };
24
+ },
25
+ messageTemplate: "Size advice at `{path}` could not be stated in the Sheet's sizing system (`{system}`); it is carried as prose only.",
26
+ remediation: "If the merchant states the advice in the sizing dimension's system, add a `sizeAdjustment` in that system. If it does not, no action: do not convert between systems.",
27
+ introduced: "v0.9 (2026-10-01 revision)",
28
+ specReference: "product.md#size-guidance-pagefrontsizechart-and-pagefrontsizerecommendation",
29
+ };