@pagefront/lint-commerce 0.6.0 → 0.7.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 +14 -2
- package/dist/catalogue.js +5 -2
- package/dist/organization-catalogue.js +1 -1
- package/dist/product-schema.js +18 -2
- package/dist/rules/pdp-e-005.d.ts +12 -0
- package/dist/rules/pdp-e-005.js +40 -0
- package/dist/rules/pdp-e-006.d.ts +11 -0
- package/dist/rules/pdp-e-006.js +36 -0
- package/dist/rules/pdp-w-031.d.ts +12 -0
- package/dist/rules/pdp-w-031.js +35 -0
- package/dist/rules/sheet-url-host.d.ts +11 -0
- package/dist/rules/sheet-url-host.js +22 -0
- package/dist/schema-validators.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,8 +26,8 @@ lint with their own catalogue, dispatched by sheet type.
|
|
|
26
26
|
|
|
27
27
|
## Catalogue
|
|
28
28
|
|
|
29
|
-
Catalogue 0.
|
|
30
|
-
`PDP-E-
|
|
29
|
+
Catalogue 0.10.0, targeting format 0.9: 38 product rules (`PDP-E-001` to
|
|
30
|
+
`PDP-E-006`, `PDP-W-001` to `PDP-W-031`, `PDP-I-001`) and 7 organization
|
|
31
31
|
rules (`ORG-E-001`, `ORG-W-001` to `ORG-W-006`), each defined in
|
|
32
32
|
[`rules.md`](https://github.com/themachineweb/pagefront/blob/main/commerce/spec/rules.md)
|
|
33
33
|
with its target, check, message and remediation. Every report carries the
|
|
@@ -53,6 +53,18 @@ section:
|
|
|
53
53
|
- `PDP-W-030` — a `warranty` on an Offer, or a `pagefront:manufacturerWarranty`
|
|
54
54
|
on the Product, carries neither `durationOfWarranty` nor `description`
|
|
55
55
|
|
|
56
|
+
Added in 0.7.0 (catalogue 0.10.0), from the organization spec's
|
|
57
|
+
["Linking contract"](https://themachineweb.org/pagefront/spec/commerce/organization#linking-contract-identity-only)
|
|
58
|
+
section:
|
|
59
|
+
|
|
60
|
+
- `PDP-E-005` — `pagefront:sheetUrl` on an Offer's `seller` is on a host
|
|
61
|
+
other than the product sheet's
|
|
62
|
+
- `PDP-E-006` — `pagefront:sheetUrl` on `brand` is on a host other than
|
|
63
|
+
that of the brand's `@id`
|
|
64
|
+
- `PDP-W-031` — an Offer's `seller` has an `@id` but no
|
|
65
|
+
`pagefront:sheetUrl`, and the Offer states no return or shipping terms
|
|
66
|
+
of its own
|
|
67
|
+
|
|
56
68
|
## API
|
|
57
69
|
|
|
58
70
|
The public surface — everything importable from the package root, and
|
package/dist/catalogue.js
CHANGED
|
@@ -2,6 +2,8 @@ import { pdpE001 } from "./rules/pdp-e-001.js";
|
|
|
2
2
|
import { pdpE002 } from "./rules/pdp-e-002.js";
|
|
3
3
|
import { pdpE003 } from "./rules/pdp-e-003.js";
|
|
4
4
|
import { pdpE004 } from "./rules/pdp-e-004.js";
|
|
5
|
+
import { pdpE005 } from "./rules/pdp-e-005.js";
|
|
6
|
+
import { pdpE006 } from "./rules/pdp-e-006.js";
|
|
5
7
|
import { pdpW001 } from "./rules/pdp-w-001.js";
|
|
6
8
|
import { pdpW002 } from "./rules/pdp-w-002.js";
|
|
7
9
|
import { pdpW003 } from "./rules/pdp-w-003.js";
|
|
@@ -32,6 +34,7 @@ import { pdpW027 } from "./rules/pdp-w-027.js";
|
|
|
32
34
|
import { pdpW028 } from "./rules/pdp-w-028.js";
|
|
33
35
|
import { pdpW029 } from "./rules/pdp-w-029.js";
|
|
34
36
|
import { pdpW030 } from "./rules/pdp-w-030.js";
|
|
37
|
+
import { pdpW031 } from "./rules/pdp-w-031.js";
|
|
35
38
|
import { pdpI001 } from "./rules/pdp-i-001.js";
|
|
36
39
|
/**
|
|
37
40
|
* The commerce product-sheet rule catalogue — the executable form of
|
|
@@ -43,6 +46,6 @@ import { pdpI001 } from "./rules/pdp-i-001.js";
|
|
|
43
46
|
export const commerceCatalogue = {
|
|
44
47
|
vertical: "commerce",
|
|
45
48
|
formatVersion: "0.9",
|
|
46
|
-
catalogueVersion: "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],
|
|
49
|
+
catalogueVersion: "0.10.0",
|
|
50
|
+
rules: [pdpE001, pdpE002, pdpE003, pdpE004, pdpE005, pdpE006, 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, pdpI001],
|
|
48
51
|
};
|
|
@@ -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.
|
|
20
|
+
catalogueVersion: "0.10.0",
|
|
21
21
|
rules: [orgE001, orgW001, orgW002, orgW003, orgW004, orgW005, orgW006],
|
|
22
22
|
};
|
package/dist/product-schema.js
CHANGED
|
@@ -135,7 +135,13 @@ export const PRODUCT_SCHEMA = {
|
|
|
135
135
|
}
|
|
136
136
|
},
|
|
137
137
|
"brand": {
|
|
138
|
-
"type": "object"
|
|
138
|
+
"type": "object",
|
|
139
|
+
"properties": {
|
|
140
|
+
"pagefront:sheetUrl": {
|
|
141
|
+
"type": "string",
|
|
142
|
+
"format": "uri"
|
|
143
|
+
}
|
|
144
|
+
}
|
|
139
145
|
},
|
|
140
146
|
"description": {
|
|
141
147
|
"type": "string"
|
|
@@ -1053,6 +1059,10 @@ export const PRODUCT_SCHEMA = {
|
|
|
1053
1059
|
"email": {
|
|
1054
1060
|
"type": "string",
|
|
1055
1061
|
"format": "email"
|
|
1062
|
+
},
|
|
1063
|
+
"pagefront:sheetUrl": {
|
|
1064
|
+
"type": "string",
|
|
1065
|
+
"format": "uri"
|
|
1056
1066
|
}
|
|
1057
1067
|
},
|
|
1058
1068
|
"additionalProperties": true
|
|
@@ -1199,7 +1209,13 @@ export const PRODUCT_SCHEMA = {
|
|
|
1199
1209
|
"pattern": "^https?://schema\\.org/(NewCondition|UsedCondition|RefurbishedCondition|DamagedCondition)$"
|
|
1200
1210
|
},
|
|
1201
1211
|
"seller": {
|
|
1202
|
-
"type": "object"
|
|
1212
|
+
"type": "object",
|
|
1213
|
+
"properties": {
|
|
1214
|
+
"pagefront:sheetUrl": {
|
|
1215
|
+
"type": "string",
|
|
1216
|
+
"format": "uri"
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1203
1219
|
},
|
|
1204
1220
|
"shippingDetails": {
|
|
1205
1221
|
"oneOf": [
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Rule } from "@pagefront/lint-core";
|
|
2
|
+
/**
|
|
3
|
+
* PDP-E-005 — Seller sheet URL off the product sheet's host.
|
|
4
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
5
|
+
*
|
|
6
|
+
* Fires when an offer's `seller` carries `pagefront:sheetUrl` whose host
|
|
7
|
+
* differs from the host of the sheet's own `sheet_url`, or has no host.
|
|
8
|
+
* The seller's `@id` plays no part: the linking contract lets it be
|
|
9
|
+
* minted on another domain the organization controls. The finding sits
|
|
10
|
+
* at the `pagefront:sheetUrl` path.
|
|
11
|
+
*/
|
|
12
|
+
export declare const pdpE005: Rule;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { hostOf, renderHost } from "./sheet-url-host.js";
|
|
2
|
+
/**
|
|
3
|
+
* PDP-E-005 — Seller sheet URL off the product sheet's host.
|
|
4
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
5
|
+
*
|
|
6
|
+
* Fires when an offer's `seller` carries `pagefront:sheetUrl` whose host
|
|
7
|
+
* differs from the host of the sheet's own `sheet_url`, or has no host.
|
|
8
|
+
* The seller's `@id` plays no part: the linking contract lets it be
|
|
9
|
+
* minted on another domain the organization controls. The finding sits
|
|
10
|
+
* at the `pagefront:sheetUrl` path.
|
|
11
|
+
*/
|
|
12
|
+
export const pdpE005 = {
|
|
13
|
+
id: "PDP-E-005",
|
|
14
|
+
title: "Seller sheet URL off the product sheet's host",
|
|
15
|
+
severity: "error",
|
|
16
|
+
target: "$.data.offers[*]",
|
|
17
|
+
check: (match, ctx) => {
|
|
18
|
+
const offer = match.value;
|
|
19
|
+
if (typeof offer !== "object" || offer === null)
|
|
20
|
+
return null;
|
|
21
|
+
const seller = offer["seller"];
|
|
22
|
+
if (typeof seller !== "object" || seller === null || Array.isArray(seller))
|
|
23
|
+
return null;
|
|
24
|
+
const s = seller;
|
|
25
|
+
if (s["pagefront:sheetUrl"] === undefined)
|
|
26
|
+
return null;
|
|
27
|
+
const urlHost = hostOf(s["pagefront:sheetUrl"]);
|
|
28
|
+
const sheetHost = hostOf(ctx.sheet.sheet_url);
|
|
29
|
+
if (urlHost !== null && urlHost === sheetHost)
|
|
30
|
+
return null;
|
|
31
|
+
return {
|
|
32
|
+
path: `${match.path}['seller']['pagefront:sheetUrl']`,
|
|
33
|
+
values: { sheetUrlHost: renderHost(urlHost), sheetHost: renderHost(sheetHost) },
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
messageTemplate: "Seller sheet URL at `{path}` is on host `{sheetUrlHost}`; the product sheet is on `{sheetHost}`.",
|
|
37
|
+
remediation: "Publish the seller's organization sheet on the storefront host.",
|
|
38
|
+
introduced: "v0.9",
|
|
39
|
+
specReference: "organization.md#linking-contract-identity-only",
|
|
40
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Rule } from "@pagefront/lint-core";
|
|
2
|
+
/**
|
|
3
|
+
* PDP-E-006 — Brand sheet URL off the brand's host.
|
|
4
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
5
|
+
*
|
|
6
|
+
* Fires when the data block's `brand` carries `pagefront:sheetUrl` whose
|
|
7
|
+
* host differs from the host of the brand's `@id`. A brand with no `@id`,
|
|
8
|
+
* or an `@id` without a host, fires too: there is no host for the URL to
|
|
9
|
+
* match. The finding sits at the `pagefront:sheetUrl` path.
|
|
10
|
+
*/
|
|
11
|
+
export declare const pdpE006: Rule;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { hostOf, renderHost } from "./sheet-url-host.js";
|
|
2
|
+
/**
|
|
3
|
+
* PDP-E-006 — Brand sheet URL off the brand's host.
|
|
4
|
+
* Transcribed from commerce/spec/rules.md (normative).
|
|
5
|
+
*
|
|
6
|
+
* Fires when the data block's `brand` carries `pagefront:sheetUrl` whose
|
|
7
|
+
* host differs from the host of the brand's `@id`. A brand with no `@id`,
|
|
8
|
+
* or an `@id` without a host, fires too: there is no host for the URL to
|
|
9
|
+
* match. The finding sits at the `pagefront:sheetUrl` path.
|
|
10
|
+
*/
|
|
11
|
+
export const pdpE006 = {
|
|
12
|
+
id: "PDP-E-006",
|
|
13
|
+
title: "Brand sheet URL off the brand's host",
|
|
14
|
+
severity: "error",
|
|
15
|
+
target: "$.data.brand",
|
|
16
|
+
check: (match) => {
|
|
17
|
+
const brand = match.value;
|
|
18
|
+
if (typeof brand !== "object" || brand === null || Array.isArray(brand))
|
|
19
|
+
return null;
|
|
20
|
+
const b = brand;
|
|
21
|
+
if (b["pagefront:sheetUrl"] === undefined)
|
|
22
|
+
return null;
|
|
23
|
+
const urlHost = hostOf(b["pagefront:sheetUrl"]);
|
|
24
|
+
const idHost = hostOf(b["@id"]);
|
|
25
|
+
if (urlHost !== null && urlHost === idHost)
|
|
26
|
+
return null;
|
|
27
|
+
return {
|
|
28
|
+
path: `${match.path}['pagefront:sheetUrl']`,
|
|
29
|
+
values: { sheetUrlHost: renderHost(urlHost), idHost: renderHost(idHost) },
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
messageTemplate: "Brand sheet URL at `{path}` is on host `{sheetUrlHost}`; the brand's `@id` is on `{idHost}`.",
|
|
33
|
+
remediation: "Point `pagefront:sheetUrl` at the sheet the brand publishes on its own host, or remove it.",
|
|
34
|
+
introduced: "v0.9",
|
|
35
|
+
specReference: "organization.md#linking-contract-identity-only",
|
|
36
|
+
};
|
|
@@ -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,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)";
|