@pagefront/lint-commerce 0.5.0 → 0.6.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/README.md CHANGED
@@ -24,6 +24,35 @@ failure. Both commerce page types are handled: product sheets and
24
24
  Commerce Organization Sheets each validate against their own schema and
25
25
  lint with their own catalogue, dispatched by sheet type.
26
26
 
27
+ ## Catalogue
28
+
29
+ Catalogue 0.9.0, targeting format 0.9: 35 product rules (`PDP-E-001` to
30
+ `PDP-E-004`, `PDP-W-001` to `PDP-W-030`, `PDP-I-001`) and 7 organization
31
+ rules (`ORG-E-001`, `ORG-W-001` to `ORG-W-006`), each defined in
32
+ [`rules.md`](https://github.com/themachineweb/pagefront/blob/main/commerce/spec/rules.md)
33
+ with its target, check, message and remediation. Every report carries the
34
+ `catalogue_version` it was produced with.
35
+
36
+ Added in 0.5.0 (catalogue 0.8.0), from the product spec's
37
+ ["Tax inclusion"](https://themachineweb.org/pagefront/spec/commerce/product#tax-inclusion)
38
+ section:
39
+
40
+ - `PDP-W-028` — an Offer states a price but no `priceSpecification` entry
41
+ carries a boolean `valueAddedTaxIncluded`
42
+ - `PDP-W-029` — an Offer states a price but declares neither `areaServed`
43
+ nor `eligibleRegion`
44
+ - `PDP-E-004` — a `priceSpecification` entry mirroring the Offer's price
45
+ states a different amount in the same currency
46
+
47
+ The two warnings exempt offers carrying `pagefront:priceOnRequest`.
48
+
49
+ Added in 0.6.0 (catalogue 0.9.0), from the product spec's
50
+ ["Warranty"](https://themachineweb.org/pagefront/spec/commerce/product#warranty)
51
+ section:
52
+
53
+ - `PDP-W-030` — a `warranty` on an Offer, or a `pagefront:manufacturerWarranty`
54
+ on the Product, carries neither `durationOfWarranty` nor `description`
55
+
27
56
  ## API
28
57
 
29
58
  The public surface — everything importable from the package root, and
package/dist/catalogue.js CHANGED
@@ -31,6 +31,7 @@ import { pdpW026 } from "./rules/pdp-w-026.js";
31
31
  import { pdpW027 } from "./rules/pdp-w-027.js";
32
32
  import { pdpW028 } from "./rules/pdp-w-028.js";
33
33
  import { pdpW029 } from "./rules/pdp-w-029.js";
34
+ import { pdpW030 } from "./rules/pdp-w-030.js";
34
35
  import { pdpI001 } from "./rules/pdp-i-001.js";
35
36
  /**
36
37
  * The commerce product-sheet rule catalogue — the executable form of
@@ -42,6 +43,6 @@ import { pdpI001 } from "./rules/pdp-i-001.js";
42
43
  export const commerceCatalogue = {
43
44
  vertical: "commerce",
44
45
  formatVersion: "0.9",
45
- catalogueVersion: "0.8.0",
46
- rules: [pdpE001, pdpE002, pdpE003, pdpE004, pdpW001, pdpW002, pdpW003, pdpW004, pdpW005, pdpW006, pdpW007, pdpW008, pdpW009, pdpW010, pdpW011, pdpW012, pdpW013, pdpW014, pdpW015, pdpW016, pdpW017, pdpW018, pdpW019, pdpW020, pdpW021, pdpW022, pdpW023, pdpW024, pdpW025, pdpW026, pdpW027, pdpW028, pdpW029, pdpI001],
46
+ catalogueVersion: "0.9.0",
47
+ rules: [pdpE001, pdpE002, pdpE003, pdpE004, pdpW001, pdpW002, pdpW003, pdpW004, pdpW005, pdpW006, pdpW007, pdpW008, pdpW009, pdpW010, pdpW011, pdpW012, pdpW013, pdpW014, pdpW015, pdpW016, pdpW017, pdpW018, pdpW019, pdpW020, pdpW021, pdpW022, pdpW023, pdpW024, pdpW025, pdpW026, pdpW027, pdpW028, pdpW029, pdpW030, pdpI001],
47
48
  };
@@ -17,6 +17,6 @@ import { orgW006 } from "./rules/org-w-006.js";
17
17
  export const organizationCatalogue = {
18
18
  vertical: "commerce",
19
19
  formatVersion: "0.9",
20
- catalogueVersion: "0.8.0",
20
+ catalogueVersion: "0.9.0",
21
21
  rules: [orgE001, orgW001, orgW002, orgW003, orgW004, orgW005, orgW006],
22
22
  };
@@ -348,6 +348,9 @@ export const PRODUCT_SCHEMA = {
348
348
  "pagefront:madeToOrderTerms": {
349
349
  "$ref": "#/$defs/madeToOrderTerms"
350
350
  },
351
+ "pagefront:manufacturerWarranty": {
352
+ "type": "object"
353
+ },
351
354
  "pagefront:careInstructions": {
352
355
  "type": "array",
353
356
  "items": {
@@ -1214,6 +1217,9 @@ export const PRODUCT_SCHEMA = {
1214
1217
  "hasMerchantReturnPolicy": {
1215
1218
  "type": "object"
1216
1219
  },
1220
+ "warranty": {
1221
+ "type": "object"
1222
+ },
1217
1223
  "areaServed": {},
1218
1224
  "eligibleRegion": {
1219
1225
  "type": "array"
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "@pagefront/lint-core";
2
+ export declare const pdpW030: Rule;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * PDP-W-030 — Offer or product warranty with no content.
3
+ * Transcribed from commerce/spec/rules.md (normative).
4
+ *
5
+ * Present-but-empty rule (the PDP-W-022 / PDP-W-027 family): a
6
+ * `warranty` on an offer, or the data block's
7
+ * `pagefront:manufacturerWarranty`, that carries neither
8
+ * `durationOfWarranty` nor a non-empty `description`. The rule's
9
+ * conceptual target spans two positions; the JSONPath target matches
10
+ * the data block once and the check walks both from there, giving each
11
+ * finding the entry's own path.
12
+ */
13
+ const isEmptyWarranty = (entry) => {
14
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry))
15
+ return false;
16
+ const e = entry;
17
+ if (e["durationOfWarranty"] !== undefined && e["durationOfWarranty"] !== null)
18
+ return false;
19
+ const description = e["description"];
20
+ return !(typeof description === "string" && description.trim() !== "");
21
+ };
22
+ export const pdpW030 = {
23
+ id: "PDP-W-030",
24
+ title: "Offer or product warranty with no content",
25
+ severity: "warning",
26
+ target: "$.data",
27
+ check: (match) => {
28
+ const data = match.value;
29
+ if (typeof data !== "object" || data === null)
30
+ return null;
31
+ const d = data;
32
+ const hits = [];
33
+ if (isEmptyWarranty(d["pagefront:manufacturerWarranty"])) {
34
+ hits.push({ path: `${match.path}['pagefront:manufacturerWarranty']` });
35
+ }
36
+ const offers = d["offers"];
37
+ if (Array.isArray(offers)) {
38
+ offers.forEach((offer, index) => {
39
+ if (typeof offer !== "object" || offer === null)
40
+ return;
41
+ if (isEmptyWarranty(offer["warranty"])) {
42
+ hits.push({ path: `${match.path}['offers'][${index}]['warranty']` });
43
+ }
44
+ });
45
+ }
46
+ return hits.length > 0 ? hits : null;
47
+ },
48
+ messageTemplate: "Warranty at `{path}` states neither a duration nor a description.",
49
+ remediation: "State the warranty as written in `description`, and add `durationOfWarranty` where a finite length is stated. A lifetime warranty carries `description` (and optionally `name`) with no `durationOfWarranty`.",
50
+ introduced: "v0.9",
51
+ specReference: "product.md#warranty",
52
+ };
@@ -46,6 +46,7 @@ export const DATA_BLOCK_PROPERTIES = [
46
46
  "pagefront:gemstone",
47
47
  "pagefront:legalEntity",
48
48
  "pagefront:madeToOrderTerms",
49
+ "pagefront:manufacturerWarranty",
49
50
  "pagefront:manufacturingChain",
50
51
  "pagefront:materialComposition",
51
52
  "pagefront:productHighlights",