@pagefront/lint-commerce 0.12.1 → 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 +72 -13
- package/dist/catalogue.js +8 -2
- package/dist/collection-catalogue.d.ts +15 -0
- package/dist/collection-catalogue.js +31 -0
- package/dist/generated/release-manifest.d.ts +9 -0
- package/dist/generated/release-manifest.js +10 -0
- package/dist/generated/schema-ids.js +3 -0
- package/dist/generated/schemas.js +2744 -23
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/lint-sheet.d.ts +4 -3
- package/dist/lint-sheet.js +9 -6
- package/dist/organization-catalogue.d.ts +4 -4
- package/dist/organization-catalogue.js +8 -6
- package/dist/overrides.d.ts +36 -0
- package/dist/overrides.js +5 -0
- package/dist/releases.d.ts +21 -6
- package/dist/releases.js +30 -8
- package/dist/rules/checkout-template.d.ts +69 -0
- package/dist/rules/checkout-template.js +165 -0
- package/dist/rules/col-e-001.d.ts +11 -0
- package/dist/rules/col-e-001.js +31 -0
- package/dist/rules/col-e-002.d.ts +2 -0
- package/dist/rules/col-e-002.js +37 -0
- package/dist/rules/col-e-003.d.ts +11 -0
- package/dist/rules/col-e-003.js +18 -0
- package/dist/rules/col-e-004.d.ts +12 -0
- package/dist/rules/col-e-004.js +19 -0
- package/dist/rules/col-e-005.d.ts +16 -0
- package/dist/rules/col-e-005.js +64 -0
- package/dist/rules/col-shared.d.ts +25 -0
- package/dist/rules/col-shared.js +43 -0
- package/dist/rules/col-w-001.d.ts +11 -0
- package/dist/rules/col-w-001.js +21 -0
- package/dist/rules/col-w-002.d.ts +12 -0
- package/dist/rules/col-w-002.js +31 -0
- package/dist/rules/col-w-004.d.ts +12 -0
- package/dist/rules/col-w-004.js +37 -0
- package/dist/rules/col-w-005.d.ts +12 -0
- package/dist/rules/col-w-005.js +41 -0
- package/dist/rules/col-w-006.d.ts +11 -0
- package/dist/rules/col-w-006.js +37 -0
- package/dist/rules/col-w-007.d.ts +13 -0
- package/dist/rules/col-w-007.js +32 -0
- package/dist/rules/org-w-007.d.ts +15 -0
- package/dist/rules/org-w-007.js +47 -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-005.d.ts +3 -1
- package/dist/rules/pdp-e-005.js +7 -5
- package/dist/rules/pdp-e-019.d.ts +2 -0
- package/dist/rules/pdp-e-019.js +50 -0
- package/dist/rules/pdp-e-020.d.ts +13 -0
- package/dist/rules/pdp-e-020.js +31 -0
- package/dist/rules/pdp-e-021.d.ts +11 -0
- package/dist/rules/pdp-e-021.js +32 -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-021.js +1 -1
- package/dist/rules/pdp-w-031.d.ts +4 -4
- package/dist/rules/pdp-w-031.js +9 -7
- package/dist/rules/pdp-w-040.d.ts +12 -0
- package/dist/rules/pdp-w-040.js +37 -0
- package/dist/rules/pdp-w-041.d.ts +15 -0
- package/dist/rules/pdp-w-041.js +53 -0
- package/dist/rules/release-schema.d.ts +3 -2
- package/dist/rules/release-schema.js +11 -11
- package/dist/rules/unavailable-offer.d.ts +2 -0
- package/dist/rules/unavailable-offer.js +13 -0
- package/dist/schema-registry.d.ts +11 -0
- package/dist/schema-registry.js +24 -9
- package/dist/schema-validators.js +14 -5
- package/dist/sheet-type.d.ts +17 -12
- package/dist/sheet-type.js +11 -0
- package/dist/test-hooks.d.ts +15 -0
- package/dist/test-hooks.js +28 -0
- package/dist/validate-and-lint.d.ts +9 -5
- package/dist/validate-and-lint.js +33 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { asObject, own } from "./configurator-pricing.js";
|
|
2
|
+
import { checkoutTemplateProblem } from "./checkout-template.js";
|
|
3
|
+
/**
|
|
4
|
+
* PDP-E-019 — Invalid checkout template.
|
|
5
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
6
|
+
*
|
|
7
|
+
* An Offer's `checkoutPageURLTemplate` must be a string, a valid RFC 6570
|
|
8
|
+
* template, name no variable other than `quantity`, the one variable the
|
|
9
|
+
* Product Sheet spec defines ("Where to buy"), and expand with `quantity`
|
|
10
|
+
* set to 1 to an absolute https URL (an empty template fails). One
|
|
11
|
+
* finding per Offer, the first of those checks in that order. Applies
|
|
12
|
+
* from release 0.5.0, whose Product Sheet spec 0.11.0 introduces the
|
|
13
|
+
* requirement.
|
|
14
|
+
*/
|
|
15
|
+
function listVariables(variables) {
|
|
16
|
+
const quoted = variables.map((v) => `\`${v}\``);
|
|
17
|
+
if (quoted.length === 1)
|
|
18
|
+
return `the variable ${quoted[0]}`;
|
|
19
|
+
return `the variables ${quoted.slice(0, -1).join(", ")} and ${quoted[quoted.length - 1]}`;
|
|
20
|
+
}
|
|
21
|
+
export const pdpE019 = {
|
|
22
|
+
id: "PDP-E-019",
|
|
23
|
+
title: "Invalid checkout template",
|
|
24
|
+
severity: "error",
|
|
25
|
+
target: "$.data.offers[*]",
|
|
26
|
+
check: (match) => {
|
|
27
|
+
const offer = asObject(match.value);
|
|
28
|
+
if (offer === null || !Object.prototype.hasOwnProperty.call(offer, "checkoutPageURLTemplate"))
|
|
29
|
+
return null;
|
|
30
|
+
const problem = checkoutTemplateProblem(own(offer, "checkoutPageURLTemplate"));
|
|
31
|
+
if (problem === null)
|
|
32
|
+
return null;
|
|
33
|
+
const text = problem.kind === "not-string"
|
|
34
|
+
? "is not a string"
|
|
35
|
+
: problem.kind === "invalid"
|
|
36
|
+
? `is not a valid RFC 6570 template: ${problem.detail}`
|
|
37
|
+
: problem.kind === "undefined-variables"
|
|
38
|
+
? `names ${listVariables(problem.variables)}, which Pagefront does not define; the one defined variable is \`quantity\``
|
|
39
|
+
: problem.expansion === ""
|
|
40
|
+
? "is empty"
|
|
41
|
+
: `expands with \`quantity\` set to 1 to \`${problem.expansion}\`, which is not an absolute https URL`;
|
|
42
|
+
return { path: `${match.path}['checkoutPageURLTemplate']`, values: { problem: text } };
|
|
43
|
+
},
|
|
44
|
+
messageTemplate: "Checkout template at `{path}` {problem}.",
|
|
45
|
+
remediation: "Use only `quantity` and write the template so that it expands to an absolute `https` URL, or remove the template.",
|
|
46
|
+
introduced: "release 0.5.0",
|
|
47
|
+
// The requirement is introduced by release 0.5.0 (Product Sheet spec 0.11.0).
|
|
48
|
+
appliesFrom: "0.5.0",
|
|
49
|
+
specReference: "product.md#where-to-buy",
|
|
50
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Rule } from "@pagefront/lint-core";
|
|
2
|
+
/**
|
|
3
|
+
* PDP-E-020 — Checkout template without a region.
|
|
4
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
5
|
+
*
|
|
6
|
+
* An Offer that carries `checkoutPageURLTemplate` declares the market the
|
|
7
|
+
* checkout serves with `areaServed` or `eligibleRegion` ("Where to buy").
|
|
8
|
+
* A region property that is empty or whitespace-only counts as absent.
|
|
9
|
+
* The template's own validity is PDP-E-019's concern. Applies from
|
|
10
|
+
* release 0.5.0, whose Product Sheet spec 0.11.0 introduces the
|
|
11
|
+
* requirement.
|
|
12
|
+
*/
|
|
13
|
+
export declare const pdpE020: Rule;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { asObject } from "./configurator-pricing.js";
|
|
2
|
+
import { declaresRegion } from "./checkout-template.js";
|
|
3
|
+
/**
|
|
4
|
+
* PDP-E-020 — Checkout template without a region.
|
|
5
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
6
|
+
*
|
|
7
|
+
* An Offer that carries `checkoutPageURLTemplate` declares the market the
|
|
8
|
+
* checkout serves with `areaServed` or `eligibleRegion` ("Where to buy").
|
|
9
|
+
* A region property that is empty or whitespace-only counts as absent.
|
|
10
|
+
* The template's own validity is PDP-E-019's concern. Applies from
|
|
11
|
+
* release 0.5.0, whose Product Sheet spec 0.11.0 introduces the
|
|
12
|
+
* requirement.
|
|
13
|
+
*/
|
|
14
|
+
export const pdpE020 = {
|
|
15
|
+
id: "PDP-E-020",
|
|
16
|
+
title: "Checkout template without a region",
|
|
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
|
+
return declaresRegion(offer) ? null : { path: match.path, values: {} };
|
|
24
|
+
},
|
|
25
|
+
messageTemplate: "Offer at `{path}` carries `checkoutPageURLTemplate` and neither `areaServed` nor `eligibleRegion`.",
|
|
26
|
+
remediation: "Declare the region the checkout serves, or remove the template.",
|
|
27
|
+
introduced: "release 0.5.0",
|
|
28
|
+
// The requirement is introduced by release 0.5.0 (Product Sheet spec 0.11.0).
|
|
29
|
+
appliesFrom: "0.5.0",
|
|
30
|
+
specReference: "product.md#where-to-buy",
|
|
31
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Rule } from "@pagefront/lint-core";
|
|
2
|
+
/**
|
|
3
|
+
* PDP-E-021 — Offer `url` is not an absolute URL.
|
|
4
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
5
|
+
*
|
|
6
|
+
* An Offer's `url` is the page on which the shopper buys it ("Where to
|
|
7
|
+
* buy"); when present it must be a full absolute URL with the scheme
|
|
8
|
+
* http or https. Silent when the Offer carries no `url` (PDP-W-040's
|
|
9
|
+
* concern on a sheet with several offers). Applies from release 0.5.0.
|
|
10
|
+
*/
|
|
11
|
+
export declare const pdpE021: Rule;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { asObject, own } from "./configurator-pricing.js";
|
|
2
|
+
import { isAbsoluteHttpUrl } from "./checkout-template.js";
|
|
3
|
+
/**
|
|
4
|
+
* PDP-E-021 — Offer `url` is not an absolute URL.
|
|
5
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
6
|
+
*
|
|
7
|
+
* An Offer's `url` is the page on which the shopper buys it ("Where to
|
|
8
|
+
* buy"); when present it must be a full absolute URL with the scheme
|
|
9
|
+
* http or https. Silent when the Offer carries no `url` (PDP-W-040's
|
|
10
|
+
* concern on a sheet with several offers). Applies from release 0.5.0.
|
|
11
|
+
*/
|
|
12
|
+
export const pdpE021 = {
|
|
13
|
+
id: "PDP-E-021",
|
|
14
|
+
title: "Offer url is not an absolute URL",
|
|
15
|
+
severity: "error",
|
|
16
|
+
target: "$.data.offers[*]",
|
|
17
|
+
check: (match) => {
|
|
18
|
+
const offer = asObject(match.value);
|
|
19
|
+
if (offer === null || !Object.prototype.hasOwnProperty.call(offer, "url"))
|
|
20
|
+
return null;
|
|
21
|
+
const url = own(offer, "url");
|
|
22
|
+
if (isAbsoluteHttpUrl(url))
|
|
23
|
+
return null;
|
|
24
|
+
return { path: `${match.path}['url']`, values: { url: JSON.stringify(url) } };
|
|
25
|
+
},
|
|
26
|
+
messageTemplate: "Offer `url` at `{path}` is `{url}`, which is not an absolute `http` or `https` URL.",
|
|
27
|
+
remediation: "Give the offer the absolute URL of the page where it is bought.",
|
|
28
|
+
introduced: "release 0.5.0",
|
|
29
|
+
// The requirement is introduced by release 0.5.0 (Product Sheet spec 0.11.0).
|
|
30
|
+
appliesFrom: "0.5.0",
|
|
31
|
+
specReference: "product.md#where-to-buy",
|
|
32
|
+
};
|
|
@@ -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 &&
|
package/dist/rules/pdp-w-021.js
CHANGED
|
@@ -46,7 +46,7 @@ export const pdpW021 = {
|
|
|
46
46
|
}
|
|
47
47
|
return hits;
|
|
48
48
|
},
|
|
49
|
-
messageTemplate: "Variant link `{name}` at `{path}` appears to reference a separate Sheet by `@id` but omits the recommended `sheet_url`; consumers must reach the target Sheet via `url` + `
|
|
49
|
+
messageTemplate: "Variant link `{name}` at `{path}` appears to reference a separate Sheet by `@id` but omits the recommended `sheet_url`; consumers must reach the target Sheet via `url` + `alternate`.",
|
|
50
50
|
remediation: "Add `sheet_url` with the target's Sheet URL — the variant's Sheet URL on a parent `hasVariant` entry, or the parent's Sheet URL on a variant's `isVariantOf`. If you do not emit a separate Sheet for the target (the family is modeled inline in one ProductGroup Sheet), no cross-Sheet link exists and `sheet_url` does not apply.",
|
|
51
51
|
introduced: "v0.7",
|
|
52
52
|
appliesFrom: "0.3.0",
|
|
@@ -3,10 +3,10 @@ import type { Rule } from "@pagefront/lint-core";
|
|
|
3
3
|
* PDP-W-031 — Seller reference not locatable.
|
|
4
4
|
* Transcribed from commerce/spec/rules.md (normative).
|
|
5
5
|
*
|
|
6
|
-
* Fires when an offer's `seller` carries `@id` but
|
|
7
|
-
* `pagefront:sheetUrl`, and the offer carries neither
|
|
6
|
+
* Fires when an offer's `seller` carries `@id` but neither
|
|
7
|
+
* `pagefront:sheetUrl` nor `url`, and the offer carries neither
|
|
8
8
|
* `hasMerchantReturnPolicy` nor `shippingDetails`: the terms it would
|
|
9
|
-
* inherit sit on a seller sheet no consumer can
|
|
10
|
-
* sits at the `seller` path.
|
|
9
|
+
* inherit sit on a seller sheet no consumer can reach, by the hint or
|
|
10
|
+
* through the seller's page. The finding sits at the `seller` path.
|
|
11
11
|
*/
|
|
12
12
|
export declare const pdpW031: Rule;
|
package/dist/rules/pdp-w-031.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* PDP-W-031 — Seller reference not locatable.
|
|
3
3
|
* Transcribed from commerce/spec/rules.md (normative).
|
|
4
4
|
*
|
|
5
|
-
* Fires when an offer's `seller` carries `@id` but
|
|
6
|
-
* `pagefront:sheetUrl`, and the offer carries neither
|
|
5
|
+
* Fires when an offer's `seller` carries `@id` but neither
|
|
6
|
+
* `pagefront:sheetUrl` nor `url`, and the offer carries neither
|
|
7
7
|
* `hasMerchantReturnPolicy` nor `shippingDetails`: the terms it would
|
|
8
|
-
* inherit sit on a seller sheet no consumer can
|
|
9
|
-
* sits at the `seller` path.
|
|
8
|
+
* inherit sit on a seller sheet no consumer can reach, by the hint or
|
|
9
|
+
* through the seller's page. The finding sits at the `seller` path.
|
|
10
10
|
*/
|
|
11
11
|
export const pdpW031 = {
|
|
12
12
|
id: "PDP-W-031",
|
|
@@ -22,14 +22,16 @@ export const pdpW031 = {
|
|
|
22
22
|
if (typeof seller !== "object" || seller === null || Array.isArray(seller))
|
|
23
23
|
return null;
|
|
24
24
|
const s = seller;
|
|
25
|
-
if (s["@id"] === undefined
|
|
25
|
+
if (s["@id"] === undefined)
|
|
26
|
+
return null;
|
|
27
|
+
if (s["pagefront:sheetUrl"] !== undefined || s["url"] !== undefined)
|
|
26
28
|
return null;
|
|
27
29
|
if (o["hasMerchantReturnPolicy"] !== undefined || o["shippingDetails"] !== undefined)
|
|
28
30
|
return null;
|
|
29
31
|
return { path: `${match.path}['seller']` };
|
|
30
32
|
},
|
|
31
|
-
messageTemplate: "Seller at `{path}` has an `@id` but
|
|
32
|
-
remediation: "Add `pagefront:sheetUrl` to the seller reference, or state the terms on the Offer.",
|
|
33
|
+
messageTemplate: "Seller at `{path}` has an `@id` but neither `pagefront:sheetUrl` nor `url`, and the Offer states no return or shipping terms of its own.",
|
|
34
|
+
remediation: "Add `url` or `pagefront:sheetUrl` to the seller reference, or state the terms on the Offer.",
|
|
33
35
|
introduced: "v0.9",
|
|
34
36
|
appliesFrom: "0.3.0",
|
|
35
37
|
appliesToLegacy: true,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Rule } from "@pagefront/lint-core";
|
|
2
|
+
/**
|
|
3
|
+
* PDP-W-040 — Offer without `url` among several offers.
|
|
4
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
5
|
+
*
|
|
6
|
+
* An Offer without `url` is bought on the product's page; that default
|
|
7
|
+
* serves a single offer. When `offers` is an array with more than one
|
|
8
|
+
* entry, each entry should state its own `url` ("Where to buy"). One
|
|
9
|
+
* finding per entry without it; silent on a single offer. Applies from
|
|
10
|
+
* release 0.5.0.
|
|
11
|
+
*/
|
|
12
|
+
export declare const pdpW040: Rule;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { asObject, own } from "./configurator-pricing.js";
|
|
2
|
+
/**
|
|
3
|
+
* PDP-W-040 — Offer without `url` among several offers.
|
|
4
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
5
|
+
*
|
|
6
|
+
* An Offer without `url` is bought on the product's page; that default
|
|
7
|
+
* serves a single offer. When `offers` is an array with more than one
|
|
8
|
+
* entry, each entry should state its own `url` ("Where to buy"). One
|
|
9
|
+
* finding per entry without it; silent on a single offer. Applies from
|
|
10
|
+
* release 0.5.0.
|
|
11
|
+
*/
|
|
12
|
+
export const pdpW040 = {
|
|
13
|
+
id: "PDP-W-040",
|
|
14
|
+
title: "Offer without url among several offers",
|
|
15
|
+
severity: "warning",
|
|
16
|
+
target: "$.data",
|
|
17
|
+
check: (match) => {
|
|
18
|
+
const data = asObject(match.value);
|
|
19
|
+
const offers = own(data, "offers");
|
|
20
|
+
if (!Array.isArray(offers) || offers.length < 2)
|
|
21
|
+
return null;
|
|
22
|
+
const hits = [];
|
|
23
|
+
offers.forEach((entry, index) => {
|
|
24
|
+
const offer = asObject(entry);
|
|
25
|
+
if (offer !== null && !Object.prototype.hasOwnProperty.call(offer, "url")) {
|
|
26
|
+
hits.push({ path: `${match.path}['offers'][${index}]`, values: {} });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
return hits.length > 0 ? hits : null;
|
|
30
|
+
},
|
|
31
|
+
messageTemplate: "Offer at `{path}` has no `url`, and the sheet carries several offers.",
|
|
32
|
+
remediation: "Give each offer the URL of the page where it is bought.",
|
|
33
|
+
introduced: "release 0.5.0",
|
|
34
|
+
// "Where to buy" is introduced by release 0.5.0 (Product Sheet spec 0.11.0).
|
|
35
|
+
appliesFrom: "0.5.0",
|
|
36
|
+
specReference: "product.md#where-to-buy",
|
|
37
|
+
};
|
|
@@ -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
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CheckFn, Rule } from "@pagefront/lint-core";
|
|
2
|
-
import type
|
|
2
|
+
import { type SheetType } from "../sheet-type.js";
|
|
3
3
|
/**
|
|
4
4
|
* Release / schema mismatch. The rule reads `$schema` and is quiet
|
|
5
5
|
* whenever it is absent, on both lines: whether `$schema` is required is
|
|
@@ -8,7 +8,8 @@ import type { SheetType } from "../sheet-type.js";
|
|
|
8
8
|
* present `$schema` must be the URL of the sheet-spec schema the release
|
|
9
9
|
* manifest maps the declared `release` and the sheet type to; a release
|
|
10
10
|
* the manifest does not list has no mapping (the unknown-release rule
|
|
11
|
-
* reports it)
|
|
11
|
+
* reports it), and neither has a listed release that bundles no schema
|
|
12
|
+
* for the sheet type (Layer 1 reports that). On the legacy line the `$schema` version segment must
|
|
12
13
|
* equal `format_version`, as before. Quiet when the sheet carries both
|
|
13
14
|
* fields (the both-fields rule reports that).
|
|
14
15
|
*/
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { knownReleases, schemaUrlFor, sheetSpecVersionFor } from "../releases.js";
|
|
2
|
+
import { SHEET_LABEL } from "../sheet-type.js";
|
|
2
3
|
/**
|
|
3
|
-
* Shared checks for the envelope's version fields, used by the product
|
|
4
|
-
* and
|
|
5
|
-
* ORG-E-002
|
|
4
|
+
* Shared checks for the envelope's version fields, used by the product,
|
|
5
|
+
* organization and collection catalogues (PDP-E-002 / ORG-E-003 /
|
|
6
|
+
* COL-E-003, PDP-E-016 / ORG-E-002, PDP-E-018 / ORG-E-004 / COL-E-004).
|
|
7
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
6
8
|
*/
|
|
7
9
|
/** The trailing `v<version>.json` of a `$schema` URL. */
|
|
8
10
|
const SCHEMA_VERSION = /v(\d+(?:\.\d+)*)\.json$/;
|
|
9
|
-
const SHEET_LABEL = {
|
|
10
|
-
product: "Product Sheet",
|
|
11
|
-
organization: "Organization Sheet",
|
|
12
|
-
};
|
|
13
11
|
/**
|
|
14
12
|
* Release / schema mismatch. The rule reads `$schema` and is quiet
|
|
15
13
|
* whenever it is absent, on both lines: whether `$schema` is required is
|
|
@@ -18,7 +16,8 @@ const SHEET_LABEL = {
|
|
|
18
16
|
* present `$schema` must be the URL of the sheet-spec schema the release
|
|
19
17
|
* manifest maps the declared `release` and the sheet type to; a release
|
|
20
18
|
* the manifest does not list has no mapping (the unknown-release rule
|
|
21
|
-
* reports it)
|
|
19
|
+
* reports it), and neither has a listed release that bundles no schema
|
|
20
|
+
* for the sheet type (Layer 1 reports that). On the legacy line the `$schema` version segment must
|
|
22
21
|
* equal `format_version`, as before. Quiet when the sheet carries both
|
|
23
22
|
* fields (the both-fields rule reports that).
|
|
24
23
|
*/
|
|
@@ -104,9 +103,10 @@ export const BOTH_VERSION_FIELDS = {
|
|
|
104
103
|
*/
|
|
105
104
|
export const unknownRelease = (_match, ctx) => {
|
|
106
105
|
const { release } = ctx.sheet;
|
|
107
|
-
|
|
106
|
+
const known = knownReleases();
|
|
107
|
+
if (typeof release !== "string" || known.includes(release))
|
|
108
108
|
return null;
|
|
109
|
-
return { path: "$['release']", values: { release, known:
|
|
109
|
+
return { path: "$['release']", values: { release, known: known.join(", ") } };
|
|
110
110
|
};
|
|
111
111
|
export const UNKNOWN_RELEASE = {
|
|
112
112
|
title: "Unknown release",
|
|
@@ -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
|
+
}
|
|
@@ -18,6 +18,17 @@ export type SchemaResolution = {
|
|
|
18
18
|
kind: "unknown-release";
|
|
19
19
|
declared: string;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* The release bundles no schema for the sheet's type: the type joined
|
|
23
|
+
* the line after that release (`release` is the release tried, the
|
|
24
|
+
* declared one or the current one), or the sheet is on the legacy
|
|
25
|
+
* draft line and the type was never on it (`release` is null).
|
|
26
|
+
*/
|
|
27
|
+
| {
|
|
28
|
+
kind: "unbundled-type";
|
|
29
|
+
sheetType: SheetType;
|
|
30
|
+
release: string | null;
|
|
31
|
+
}
|
|
21
32
|
/** A legacy sheet's `$schema` names no schema this package bundles. */
|
|
22
33
|
| {
|
|
23
34
|
kind: "unknown-schema";
|
package/dist/schema-registry.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SCHEMA_IDS } from "./generated/schema-ids.js";
|
|
2
|
-
import {
|
|
2
|
+
import { overrides } from "./overrides.js";
|
|
3
|
+
import { currentRelease, knownReleases, legacySchemaUrl, schemaUrlFor, sheetSpecVersionFor, } from "./releases.js";
|
|
3
4
|
import { detectSheetLine, detectSheetType } from "./sheet-type.js";
|
|
4
5
|
/**
|
|
5
6
|
* The identifiers the legacy schemas carried before 2026-09-08
|
|
@@ -29,24 +30,38 @@ export function resolveSchema(sheet, sheetType) {
|
|
|
29
30
|
if (sheet.release !== undefined) {
|
|
30
31
|
// A non-string release is validated against the current schema,
|
|
31
32
|
// whose `release` pattern rejects it.
|
|
32
|
-
if (typeof sheet.release !== "string")
|
|
33
|
-
return
|
|
34
|
-
|
|
33
|
+
if (typeof sheet.release !== "string")
|
|
34
|
+
return currentSchema(type);
|
|
35
|
+
if (!knownReleases().includes(sheet.release))
|
|
36
|
+
return { kind: "unknown-release", declared: sheet.release };
|
|
35
37
|
const version = sheetSpecVersionFor(sheet.release, type);
|
|
36
38
|
if (version === undefined)
|
|
37
|
-
return { kind: "
|
|
39
|
+
return { kind: "unbundled-type", sheetType: type, release: sheet.release };
|
|
38
40
|
return { kind: "schema", id: schemaUrlFor(type, version) };
|
|
39
41
|
}
|
|
40
42
|
if (typeof declared === "string") {
|
|
41
|
-
if (SCHEMA_IDS.includes(declared))
|
|
43
|
+
if (SCHEMA_IDS.includes(declared) || overrides().validators?.[declared] !== undefined) {
|
|
42
44
|
return { kind: "schema", id: declared };
|
|
45
|
+
}
|
|
43
46
|
const former = FORMER_LEGACY_ID.exec(declared);
|
|
44
47
|
if (former !== null)
|
|
45
48
|
return { kind: "schema", id: legacySchemaUrl(former[1]) };
|
|
46
49
|
if (detectSheetLine(sheet) === "legacy")
|
|
47
50
|
return { kind: "unknown-schema", declared };
|
|
48
51
|
}
|
|
49
|
-
if (detectSheetLine(sheet) === "legacy")
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
if (detectSheetLine(sheet) === "legacy") {
|
|
53
|
+
const legacy = legacySchemaUrl(type);
|
|
54
|
+
return legacy === undefined
|
|
55
|
+
? { kind: "unbundled-type", sheetType: type, release: null }
|
|
56
|
+
: { kind: "schema", id: legacy };
|
|
57
|
+
}
|
|
58
|
+
return currentSchema(type);
|
|
59
|
+
}
|
|
60
|
+
/** The current release's schema for a type, or unbundled-type when the current release has none. */
|
|
61
|
+
function currentSchema(type) {
|
|
62
|
+
const release = currentRelease();
|
|
63
|
+
const version = sheetSpecVersionFor(release, type);
|
|
64
|
+
if (version === undefined)
|
|
65
|
+
return { kind: "unbundled-type", sheetType: type, release };
|
|
66
|
+
return { kind: "schema", id: schemaUrlFor(type, version) };
|
|
52
67
|
}
|