@pagefront/lint-commerce 0.13.0 → 0.14.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 +28 -5
- package/dist/catalogue.js +4 -2
- package/dist/collection-catalogue.js +3 -2
- package/dist/organization-catalogue.js +3 -2
- package/dist/rules/col-w-007.d.ts +13 -0
- package/dist/rules/col-w-007.js +32 -0
- package/dist/rules/org-w-008.d.ts +15 -0
- package/dist/rules/org-w-008.js +59 -0
- package/dist/rules/pdp-e-022.d.ts +13 -0
- package/dist/rules/pdp-e-022.js +33 -0
- package/dist/rules/pdp-w-016.d.ts +4 -0
- package/dist/rules/pdp-w-016.js +7 -0
- package/dist/rules/pdp-w-041.d.ts +15 -0
- package/dist/rules/pdp-w-041.js +53 -0
- package/dist/rules/unavailable-offer.d.ts +2 -0
- package/dist/rules/unavailable-offer.js +13 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,14 +27,14 @@ sheet type.
|
|
|
27
27
|
|
|
28
28
|
## Catalogue
|
|
29
29
|
|
|
30
|
-
Catalogue 0.
|
|
30
|
+
Catalogue 0.20.0, supporting commerce releases 0.5.0 (Product Sheet spec
|
|
31
31
|
0.11.0, Organization Sheet spec 0.5.0, Collection Sheet spec 0.1.0), 0.4.0
|
|
32
32
|
(Product Sheet spec 0.10.0, Organization Sheet spec 0.4.0) and 0.3.0, and the
|
|
33
|
-
legacy draft line (format 0.9):
|
|
34
|
-
`PDP-W-
|
|
35
|
-
`ORG-E-004`, `ORG-W-001` to `ORG-W-
|
|
33
|
+
legacy draft line (format 0.9): 64 product rules (`PDP-E-001` to `PDP-E-022`, `PDP-W-001` to
|
|
34
|
+
`PDP-W-041`, `PDP-I-001`), 12 organization rules (`ORG-E-001` to
|
|
35
|
+
`ORG-E-004`, `ORG-W-001` to `ORG-W-008`) and 11 collection rules
|
|
36
36
|
(`COL-E-001` to `COL-E-005`, `COL-W-001`, `COL-W-002`, `COL-W-004` to
|
|
37
|
-
`COL-W-
|
|
37
|
+
`COL-W-007`; `COL-W-003` is reserved), each defined in
|
|
38
38
|
[`rules.md`](https://github.com/themachineweb/pagefront/blob/main/commerce/spec/rules.md)
|
|
39
39
|
with its target, check, message and remediation. Every report carries the
|
|
40
40
|
`catalogue_version` it was produced with.
|
|
@@ -209,6 +209,29 @@ with the Commerce Collection Sheet spec
|
|
|
209
209
|
no such schema (at `/format_version`).
|
|
210
210
|
- New export: `collectionCatalogue`. `SheetType` gains `"collection"`.
|
|
211
211
|
|
|
212
|
+
Added in 0.14.0 (catalogue 0.20.0; catalogues 0.18.0 and 0.19.0 were never
|
|
213
|
+
published on their own). Requires `@pagefront/lint-core` 0.4.0, unchanged.
|
|
214
|
+
|
|
215
|
+
- `PDP-E-022` (applies from release 0.6.0) — "Unavailable offers
|
|
216
|
+
without a price" in the Product Sheet spec: a `checkoutPageURLTemplate` on
|
|
217
|
+
an Offer whose `availability` is `OutOfStock`, `SoldOut` or `Discontinued`.
|
|
218
|
+
From the same change, `PDP-W-016` is silent on such an Offer, which the
|
|
219
|
+
spec publishes without a price.
|
|
220
|
+
- `PDP-W-041` (applies from release 0.6.0) — a purchase URL off the
|
|
221
|
+
canonical host: an Offer's `url`, or its `checkoutPageURLTemplate`
|
|
222
|
+
expanded with `quantity` set to 1, on another host than the canonical
|
|
223
|
+
host. The linter cannot see the seller's `pagefront:checkoutHosts`.
|
|
224
|
+
- `ORG-W-008` (applies from release 0.6.0) — `pagefront:checkoutHosts`
|
|
225
|
+
without the `seller` role, and one finding per entry that is not a bare
|
|
226
|
+
host (not a string, empty, or carrying a scheme, a path or a port).
|
|
227
|
+
- `COL-W-007` (applies from release 0.5.0) — a parent entry in `isPartOf`
|
|
228
|
+
without `pagefront:sheetUrl`, or with a blank one: the counterpart of
|
|
229
|
+
`COL-W-002` for a sheet's parents. A category's parents are category
|
|
230
|
+
sheets, so the producer that emitted the sheet knows their addresses.
|
|
231
|
+
- The three rules gated to release 0.6.0 wait for that release: the bundled
|
|
232
|
+
release manifest has no 0.6.0 row yet, and a sheet declaring 0.6.0 fails
|
|
233
|
+
Layer 1 (`PDP-E-018`, `ORG-E-004`) until it lands.
|
|
234
|
+
|
|
212
235
|
Changed in 0.12.1 (catalogue 0.16.1):
|
|
213
236
|
|
|
214
237
|
- `PDP-E-017` adds a type hint. When a reference matches no option but a
|
package/dist/catalogue.js
CHANGED
|
@@ -20,6 +20,7 @@ import { pdpE018 } from "./rules/pdp-e-018.js";
|
|
|
20
20
|
import { pdpE019 } from "./rules/pdp-e-019.js";
|
|
21
21
|
import { pdpE020 } from "./rules/pdp-e-020.js";
|
|
22
22
|
import { pdpE021 } from "./rules/pdp-e-021.js";
|
|
23
|
+
import { pdpE022 } from "./rules/pdp-e-022.js";
|
|
23
24
|
import { pdpW001 } from "./rules/pdp-w-001.js";
|
|
24
25
|
import { pdpW002 } from "./rules/pdp-w-002.js";
|
|
25
26
|
import { pdpW003 } from "./rules/pdp-w-003.js";
|
|
@@ -60,6 +61,7 @@ import { pdpW037 } from "./rules/pdp-w-037.js";
|
|
|
60
61
|
import { pdpW038 } from "./rules/pdp-w-038.js";
|
|
61
62
|
import { pdpW039 } from "./rules/pdp-w-039.js";
|
|
62
63
|
import { pdpW040 } from "./rules/pdp-w-040.js";
|
|
64
|
+
import { pdpW041 } from "./rules/pdp-w-041.js";
|
|
63
65
|
import { pdpI001 } from "./rules/pdp-i-001.js";
|
|
64
66
|
/**
|
|
65
67
|
* The commerce product-sheet rule catalogue — the executable form of
|
|
@@ -71,6 +73,6 @@ import { pdpI001 } from "./rules/pdp-i-001.js";
|
|
|
71
73
|
export const commerceCatalogue = {
|
|
72
74
|
vertical: "commerce",
|
|
73
75
|
releases: KNOWN_RELEASES,
|
|
74
|
-
catalogueVersion: "0.
|
|
75
|
-
rules: [pdpE001, pdpE002, pdpE003, pdpE004, pdpE005, pdpE006, pdpE007, pdpE008, pdpE009, pdpE010, pdpE011, pdpE012, pdpE013, pdpE014, pdpE015, pdpE016, pdpE017, pdpE018, pdpE019, pdpE020, pdpE021, 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, pdpW031, pdpW032, pdpW033, pdpW034, pdpW035, pdpW036, pdpW037, pdpW038, pdpW039, pdpW040, pdpI001],
|
|
76
|
+
catalogueVersion: "0.20.0",
|
|
77
|
+
rules: [pdpE001, pdpE002, pdpE003, pdpE004, pdpE005, pdpE006, pdpE007, pdpE008, pdpE009, pdpE010, pdpE011, pdpE012, pdpE013, pdpE014, pdpE015, pdpE016, pdpE017, pdpE018, pdpE019, pdpE020, pdpE021, pdpE022, 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, pdpW031, pdpW032, pdpW033, pdpW034, pdpW035, pdpW036, pdpW037, pdpW038, pdpW039, pdpW040, pdpW041, pdpI001],
|
|
76
78
|
};
|
|
@@ -9,6 +9,7 @@ import { colW002 } from "./rules/col-w-002.js";
|
|
|
9
9
|
import { colW004 } from "./rules/col-w-004.js";
|
|
10
10
|
import { colW005 } from "./rules/col-w-005.js";
|
|
11
11
|
import { colW006 } from "./rules/col-w-006.js";
|
|
12
|
+
import { colW007 } from "./rules/col-w-007.js";
|
|
12
13
|
/**
|
|
13
14
|
* The Commerce Collection Sheet rule catalogue — the executable form
|
|
14
15
|
* of the "Commerce Collection Sheet rules" section of
|
|
@@ -25,6 +26,6 @@ import { colW006 } from "./rules/col-w-006.js";
|
|
|
25
26
|
export const collectionCatalogue = {
|
|
26
27
|
vertical: "commerce",
|
|
27
28
|
releases: KNOWN_RELEASES,
|
|
28
|
-
catalogueVersion: "0.
|
|
29
|
-
rules: [colE001, colE002, colE003, colE004, colE005, colW001, colW002, colW004, colW005, colW006],
|
|
29
|
+
catalogueVersion: "0.20.0",
|
|
30
|
+
rules: [colE001, colE002, colE003, colE004, colE005, colW001, colW002, colW004, colW005, colW006, colW007],
|
|
30
31
|
};
|
|
@@ -10,6 +10,7 @@ import { orgW004 } from "./rules/org-w-004.js";
|
|
|
10
10
|
import { orgW005 } from "./rules/org-w-005.js";
|
|
11
11
|
import { orgW006 } from "./rules/org-w-006.js";
|
|
12
12
|
import { orgW007 } from "./rules/org-w-007.js";
|
|
13
|
+
import { orgW008 } from "./rules/org-w-008.js";
|
|
13
14
|
/**
|
|
14
15
|
* The Commerce Organization Sheet rule catalogue — the executable form
|
|
15
16
|
* of the "Commerce Organization Sheet rules" section of
|
|
@@ -22,6 +23,6 @@ import { orgW007 } from "./rules/org-w-007.js";
|
|
|
22
23
|
export const organizationCatalogue = {
|
|
23
24
|
vertical: "commerce",
|
|
24
25
|
releases: KNOWN_RELEASES,
|
|
25
|
-
catalogueVersion: "0.
|
|
26
|
-
rules: [orgE001, orgE002, orgE003, orgE004, orgW001, orgW002, orgW003, orgW004, orgW005, orgW006, orgW007],
|
|
26
|
+
catalogueVersion: "0.20.0",
|
|
27
|
+
rules: [orgE001, orgE002, orgE003, orgE004, orgW001, orgW002, orgW003, orgW004, orgW005, orgW006, orgW007, orgW008],
|
|
27
28
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Rule } from "@pagefront/lint-core";
|
|
2
|
+
/**
|
|
3
|
+
* COL-W-007 — Parent entry without `pagefront:sheetUrl`.
|
|
4
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
5
|
+
*
|
|
6
|
+
* A category's parents are category sheets and a collection's parent
|
|
7
|
+
* is a collection sheet, so the producer that emits the sheet has
|
|
8
|
+
* emitted its parents and knows their addresses. The hint is an
|
|
9
|
+
* optimisation that MAY be stale; the rule asks for its presence and
|
|
10
|
+
* never checks that it resolves. One finding per entry, at the entry's
|
|
11
|
+
* path.
|
|
12
|
+
*/
|
|
13
|
+
export declare const colW007: Rule;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { entryName, memberEntries, nonBlank } from "./col-shared.js";
|
|
2
|
+
/**
|
|
3
|
+
* COL-W-007 — Parent entry without `pagefront:sheetUrl`.
|
|
4
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
5
|
+
*
|
|
6
|
+
* A category's parents are category sheets and a collection's parent
|
|
7
|
+
* is a collection sheet, so the producer that emits the sheet has
|
|
8
|
+
* emitted its parents and knows their addresses. The hint is an
|
|
9
|
+
* optimisation that MAY be stale; the rule asks for its presence and
|
|
10
|
+
* never checks that it resolves. One finding per entry, at the entry's
|
|
11
|
+
* path.
|
|
12
|
+
*/
|
|
13
|
+
export const colW007 = {
|
|
14
|
+
id: "COL-W-007",
|
|
15
|
+
title: "Parent entry without pagefront:sheetUrl",
|
|
16
|
+
severity: "warning",
|
|
17
|
+
target: "$.data.isPartOf",
|
|
18
|
+
check: (match) => {
|
|
19
|
+
const hits = [];
|
|
20
|
+
for (const { entry, path } of memberEntries(match.value, match.path)) {
|
|
21
|
+
if (nonBlank(entry["pagefront:sheetUrl"]))
|
|
22
|
+
continue;
|
|
23
|
+
hits.push({ path, values: { name: entryName(entry) } });
|
|
24
|
+
}
|
|
25
|
+
return hits;
|
|
26
|
+
},
|
|
27
|
+
messageTemplate: "Parent `{name}` at `{path}` carries no `pagefront:sheetUrl`; consumers must reach its sheet via `url` + `alternate`.",
|
|
28
|
+
remediation: "Emit the parent's sheet and add `pagefront:sheetUrl` with its address. A category's parents are category sheets, and a collection nests only in a collection that has a sheet.",
|
|
29
|
+
introduced: "catalogue 0.20.0",
|
|
30
|
+
appliesFrom: "0.5.0",
|
|
31
|
+
specReference: "collection.md#hierarchy",
|
|
32
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Rule } from "@pagefront/lint-core";
|
|
2
|
+
/**
|
|
3
|
+
* ORG-W-008 — Checkout hosts without seller role, or not a bare host.
|
|
4
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
5
|
+
*
|
|
6
|
+
* `pagefront:checkoutHosts` lists the hosts outside the canonical host
|
|
7
|
+
* on which the seller's offers are bought or checked out
|
|
8
|
+
* (organization.md, "Seller block"); it is admitted only where
|
|
9
|
+
* `commerceRole` includes `seller`, and each entry is a bare host name.
|
|
10
|
+
* One finding on the field when the role is absent, and one per entry
|
|
11
|
+
* that is not a bare host: a value that is not a string, is empty or
|
|
12
|
+
* whitespace-only, or carries a scheme, a path or a port. The entries
|
|
13
|
+
* are read as written; the rule compares them with nothing.
|
|
14
|
+
*/
|
|
15
|
+
export declare const orgW008: Rule;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { asRecord, commerceRoles } from "./org-shared.js";
|
|
2
|
+
/**
|
|
3
|
+
* ORG-W-008 — Checkout hosts without seller role, or not a bare host.
|
|
4
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
5
|
+
*
|
|
6
|
+
* `pagefront:checkoutHosts` lists the hosts outside the canonical host
|
|
7
|
+
* on which the seller's offers are bought or checked out
|
|
8
|
+
* (organization.md, "Seller block"); it is admitted only where
|
|
9
|
+
* `commerceRole` includes `seller`, and each entry is a bare host name.
|
|
10
|
+
* One finding on the field when the role is absent, and one per entry
|
|
11
|
+
* that is not a bare host: a value that is not a string, is empty or
|
|
12
|
+
* whitespace-only, or carries a scheme, a path or a port. The entries
|
|
13
|
+
* are read as written; the rule compares them with nothing.
|
|
14
|
+
*/
|
|
15
|
+
export const orgW008 = {
|
|
16
|
+
id: "ORG-W-008",
|
|
17
|
+
title: "Checkout hosts without seller role, or not a bare host",
|
|
18
|
+
severity: "warning",
|
|
19
|
+
target: "$.data",
|
|
20
|
+
check: (match) => {
|
|
21
|
+
const data = asRecord(match.value);
|
|
22
|
+
if (data === null || !Object.prototype.hasOwnProperty.call(data, "pagefront:checkoutHosts"))
|
|
23
|
+
return null;
|
|
24
|
+
const field = `${match.path}['pagefront:checkoutHosts']`;
|
|
25
|
+
const hits = [];
|
|
26
|
+
if (!commerceRoles(data).includes("seller")) {
|
|
27
|
+
hits.push({ path: field, values: { problem: "the sheet does not declare the `seller` role" } });
|
|
28
|
+
}
|
|
29
|
+
const hosts = data["pagefront:checkoutHosts"];
|
|
30
|
+
if (Array.isArray(hosts)) {
|
|
31
|
+
hosts.forEach((entry, i) => {
|
|
32
|
+
const problem = notBareHost(entry);
|
|
33
|
+
if (problem !== null)
|
|
34
|
+
hits.push({ path: `${field}[${i}]`, values: { problem } });
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return hits.length > 0 ? hits : null;
|
|
38
|
+
},
|
|
39
|
+
messageTemplate: "`pagefront:checkoutHosts` at `{path}`: {problem}.",
|
|
40
|
+
remediation: "List bare host names only (`checkout.merchant.example`), and only on the seller's own sheet, one whose `pagefront:commerceRole` includes `seller`; otherwise remove the field.",
|
|
41
|
+
introduced: "release 0.6.0",
|
|
42
|
+
appliesFrom: "0.6.0",
|
|
43
|
+
specReference: "organization.md#seller-block-seller",
|
|
44
|
+
};
|
|
45
|
+
/** Why `entry` is not a bare host, or `null` when it is one. */
|
|
46
|
+
function notBareHost(entry) {
|
|
47
|
+
if (typeof entry !== "string")
|
|
48
|
+
return `entry ${JSON.stringify(entry)} is not a string`;
|
|
49
|
+
if (entry.trim() === "")
|
|
50
|
+
return "an entry is empty or whitespace-only";
|
|
51
|
+
const shown = `entry \`${entry}\``;
|
|
52
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(entry))
|
|
53
|
+
return `${shown} carries a scheme`;
|
|
54
|
+
if (entry.includes("/"))
|
|
55
|
+
return `${shown} carries a path`;
|
|
56
|
+
if (entry.includes(":"))
|
|
57
|
+
return `${shown} carries a port`;
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Rule } from "@pagefront/lint-core";
|
|
2
|
+
/**
|
|
3
|
+
* PDP-E-022 — Checkout template on an unavailable offer.
|
|
4
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
5
|
+
*
|
|
6
|
+
* An Offer whose `availability` is OutOfStock, SoldOut or Discontinued
|
|
7
|
+
* cannot be bought now, so it MUST NOT carry `checkoutPageURLTemplate`
|
|
8
|
+
* ("Unavailable offers without a price"); `url` stays, since the page
|
|
9
|
+
* is where a shopper asks to be notified. Fires whether or not the
|
|
10
|
+
* offer states a price. Applies from release 0.6.0, whose Product Sheet
|
|
11
|
+
* spec 0.12.0 introduces the requirement.
|
|
12
|
+
*/
|
|
13
|
+
export declare const pdpE022: Rule;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { asObject } from "./configurator-pricing.js";
|
|
2
|
+
import { isUnavailable } from "./unavailable-offer.js";
|
|
3
|
+
/**
|
|
4
|
+
* PDP-E-022 — Checkout template on an unavailable offer.
|
|
5
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
6
|
+
*
|
|
7
|
+
* An Offer whose `availability` is OutOfStock, SoldOut or Discontinued
|
|
8
|
+
* cannot be bought now, so it MUST NOT carry `checkoutPageURLTemplate`
|
|
9
|
+
* ("Unavailable offers without a price"); `url` stays, since the page
|
|
10
|
+
* is where a shopper asks to be notified. Fires whether or not the
|
|
11
|
+
* offer states a price. Applies from release 0.6.0, whose Product Sheet
|
|
12
|
+
* spec 0.12.0 introduces the requirement.
|
|
13
|
+
*/
|
|
14
|
+
export const pdpE022 = {
|
|
15
|
+
id: "PDP-E-022",
|
|
16
|
+
title: "Checkout template on an unavailable offer",
|
|
17
|
+
severity: "error",
|
|
18
|
+
target: "$.data.offers[*]",
|
|
19
|
+
check: (match) => {
|
|
20
|
+
const offer = asObject(match.value);
|
|
21
|
+
if (offer === null || !Object.prototype.hasOwnProperty.call(offer, "checkoutPageURLTemplate"))
|
|
22
|
+
return null;
|
|
23
|
+
if (!isUnavailable(offer))
|
|
24
|
+
return null;
|
|
25
|
+
return { path: `${match.path}['checkoutPageURLTemplate']`, values: { availability: String(offer["availability"]) } };
|
|
26
|
+
},
|
|
27
|
+
messageTemplate: "Checkout template at `{path}` sits on an Offer whose `availability` is `{availability}`, which cannot be bought now.",
|
|
28
|
+
remediation: "Remove the template; keep `url`, the page where a shopper can ask to be notified.",
|
|
29
|
+
introduced: "release 0.6.0",
|
|
30
|
+
// The requirement is introduced by release 0.6.0 (Product Sheet spec 0.12.0).
|
|
31
|
+
appliesFrom: "0.6.0",
|
|
32
|
+
specReference: "product.md#tax-inclusion",
|
|
33
|
+
};
|
|
@@ -5,5 +5,9 @@ import type { Rule } from "@pagefront/lint-core";
|
|
|
5
5
|
*
|
|
6
6
|
* Bespoke cross-field check: `availability` and `priceCurrency` present,
|
|
7
7
|
* but no `price`, no `priceSpecification`, no `pagefront:priceOnRequest`.
|
|
8
|
+
* Silent when `availability` is OutOfStock, SoldOut or Discontinued: an
|
|
9
|
+
* offer the page shows as unavailable is published without a price
|
|
10
|
+
* ("Unavailable offers without a price"), so the missing price is no
|
|
11
|
+
* sign of an extraction failure.
|
|
8
12
|
*/
|
|
9
13
|
export declare const pdpW016: Rule;
|
package/dist/rules/pdp-w-016.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { isUnavailable } from "./unavailable-offer.js";
|
|
1
2
|
/**
|
|
2
3
|
* PDP-W-016 — Possibly-missed price.
|
|
3
4
|
* Transcribed from commerce/spec/rules.md (normative).
|
|
4
5
|
*
|
|
5
6
|
* Bespoke cross-field check: `availability` and `priceCurrency` present,
|
|
6
7
|
* but no `price`, no `priceSpecification`, no `pagefront:priceOnRequest`.
|
|
8
|
+
* Silent when `availability` is OutOfStock, SoldOut or Discontinued: an
|
|
9
|
+
* offer the page shows as unavailable is published without a price
|
|
10
|
+
* ("Unavailable offers without a price"), so the missing price is no
|
|
11
|
+
* sign of an extraction failure.
|
|
7
12
|
*/
|
|
8
13
|
export const pdpW016 = {
|
|
9
14
|
id: "PDP-W-016",
|
|
@@ -15,6 +20,8 @@ export const pdpW016 = {
|
|
|
15
20
|
if (typeof offer !== "object" || offer === null)
|
|
16
21
|
return null;
|
|
17
22
|
const o = offer;
|
|
23
|
+
if (isUnavailable(o))
|
|
24
|
+
return null;
|
|
18
25
|
return o["availability"] !== undefined &&
|
|
19
26
|
o["priceCurrency"] !== undefined &&
|
|
20
27
|
o["price"] === undefined &&
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Rule } from "@pagefront/lint-core";
|
|
2
|
+
/**
|
|
3
|
+
* PDP-W-041 — Purchase URL off the canonical host.
|
|
4
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
5
|
+
*
|
|
6
|
+
* A purchase URL is an Offer's `url`, or its `checkoutPageURLTemplate`
|
|
7
|
+
* expanded with `quantity` set to 1 ("Where to buy", "Purchase host").
|
|
8
|
+
* Fires once for each whose host differs from the canonical host, the
|
|
9
|
+
* host of the envelope `canonical_url`. A `url` with no host is
|
|
10
|
+
* PDP-E-021's concern and a template that does not expand to an
|
|
11
|
+
* absolute https URL is PDP-E-019's; both are skipped here. The seller
|
|
12
|
+
* sheet's `pagefront:checkoutHosts`, which can declare the host, is out
|
|
13
|
+
* of the linter's sight: it never fetches. Applies from release 0.6.0.
|
|
14
|
+
*/
|
|
15
|
+
export declare const pdpW041: Rule;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { asObject, own } from "./configurator-pricing.js";
|
|
2
|
+
import { checkoutTemplateProblem, expandTemplate } from "./checkout-template.js";
|
|
3
|
+
import { hostOf, renderHost } from "./sheet-url-host.js";
|
|
4
|
+
/**
|
|
5
|
+
* PDP-W-041 — Purchase URL off the canonical host.
|
|
6
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
7
|
+
*
|
|
8
|
+
* A purchase URL is an Offer's `url`, or its `checkoutPageURLTemplate`
|
|
9
|
+
* expanded with `quantity` set to 1 ("Where to buy", "Purchase host").
|
|
10
|
+
* Fires once for each whose host differs from the canonical host, the
|
|
11
|
+
* host of the envelope `canonical_url`. A `url` with no host is
|
|
12
|
+
* PDP-E-021's concern and a template that does not expand to an
|
|
13
|
+
* absolute https URL is PDP-E-019's; both are skipped here. The seller
|
|
14
|
+
* sheet's `pagefront:checkoutHosts`, which can declare the host, is out
|
|
15
|
+
* of the linter's sight: it never fetches. Applies from release 0.6.0.
|
|
16
|
+
*/
|
|
17
|
+
export const pdpW041 = {
|
|
18
|
+
id: "PDP-W-041",
|
|
19
|
+
title: "Purchase URL off the canonical host",
|
|
20
|
+
severity: "warning",
|
|
21
|
+
target: "$.data.offers[*]",
|
|
22
|
+
check: (match, ctx) => {
|
|
23
|
+
const offer = asObject(match.value);
|
|
24
|
+
if (offer === null)
|
|
25
|
+
return null;
|
|
26
|
+
const canonicalHost = hostOf(ctx.sheet.canonical_url);
|
|
27
|
+
const hits = [];
|
|
28
|
+
const urlHost = hostOf(own(offer, "url"));
|
|
29
|
+
if (urlHost !== null && urlHost !== canonicalHost) {
|
|
30
|
+
hits.push({
|
|
31
|
+
path: `${match.path}['url']`,
|
|
32
|
+
values: { purchaseHost: urlHost, host: renderHost(canonicalHost) },
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const template = own(offer, "checkoutPageURLTemplate");
|
|
36
|
+
if (typeof template === "string" && checkoutTemplateProblem(template) === null) {
|
|
37
|
+
const templateHost = hostOf(expandTemplate(template, { quantity: "1" }));
|
|
38
|
+
if (templateHost !== null && templateHost !== canonicalHost) {
|
|
39
|
+
hits.push({
|
|
40
|
+
path: `${match.path}['checkoutPageURLTemplate']`,
|
|
41
|
+
values: { purchaseHost: templateHost, host: renderHost(canonicalHost) },
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return hits.length > 0 ? hits : null;
|
|
46
|
+
},
|
|
47
|
+
messageTemplate: "Purchase URL at `{path}` is on host `{purchaseHost}`; the canonical page is on `{host}`.",
|
|
48
|
+
remediation: "Move the purchase to the canonical host, or declare the host in `pagefront:checkoutHosts` on the seller's organization sheet.",
|
|
49
|
+
introduced: "release 0.6.0",
|
|
50
|
+
// The purchase-host rule is introduced by release 0.6.0 (Product Sheet spec 0.12.0).
|
|
51
|
+
appliesFrom: "0.6.0",
|
|
52
|
+
specReference: "product.md#where-to-buy",
|
|
53
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for the rules that read an Offer's availability as
|
|
3
|
+
* "cannot be bought now" (product.md, "Unavailable offers without a
|
|
4
|
+
* price"): OutOfStock, SoldOut and Discontinued, in the http or https
|
|
5
|
+
* form the schema accepts. The other availability values describe an
|
|
6
|
+
* item that can still be bought.
|
|
7
|
+
*/
|
|
8
|
+
const UNAVAILABLE = /^https?:\/\/schema\.org\/(OutOfStock|SoldOut|Discontinued)$/;
|
|
9
|
+
/** True when the offer's `availability` is one of the three unavailable values. */
|
|
10
|
+
export function isUnavailable(offer) {
|
|
11
|
+
const availability = offer["availability"];
|
|
12
|
+
return typeof availability === "string" && UNAVAILABLE.test(availability);
|
|
13
|
+
}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/package.json
CHANGED