@pagefront/lint-commerce 0.8.0 → 0.10.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.
Files changed (55) hide show
  1. package/README.md +64 -6
  2. package/dist/catalogue.js +12 -3
  3. package/dist/index.d.ts +7 -4
  4. package/dist/index.js +6 -3
  5. package/dist/legacy-schemas.d.ts +9 -0
  6. package/dist/legacy-schemas.js +2446 -0
  7. package/dist/organization-catalogue.js +6 -3
  8. package/dist/organization-schema.d.ts +2 -1
  9. package/dist/organization-schema.js +9 -7
  10. package/dist/product-schema.d.ts +2 -2
  11. package/dist/product-schema.js +54 -22
  12. package/dist/releases.d.ts +36 -0
  13. package/dist/releases.js +41 -0
  14. package/dist/rules/configurator-pricing.d.ts +21 -2
  15. package/dist/rules/configurator-pricing.js +41 -5
  16. package/dist/rules/org-e-002.d.ts +7 -0
  17. package/dist/rules/org-e-002.js +13 -0
  18. package/dist/rules/org-e-003.d.ts +7 -0
  19. package/dist/rules/org-e-003.js +13 -0
  20. package/dist/rules/pdp-e-002.d.ts +3 -9
  21. package/dist/rules/pdp-e-002.js +6 -26
  22. package/dist/rules/pdp-e-008.js +1 -1
  23. package/dist/rules/pdp-e-009.d.ts +1 -1
  24. package/dist/rules/pdp-e-009.js +3 -3
  25. package/dist/rules/pdp-e-013.d.ts +9 -0
  26. package/dist/rules/pdp-e-013.js +26 -0
  27. package/dist/rules/pdp-e-014.d.ts +11 -0
  28. package/dist/rules/pdp-e-014.js +34 -0
  29. package/dist/rules/pdp-e-015.d.ts +2 -0
  30. package/dist/rules/pdp-e-015.js +91 -0
  31. package/dist/rules/pdp-e-016.d.ts +7 -0
  32. package/dist/rules/pdp-e-016.js +13 -0
  33. package/dist/rules/pdp-w-032.d.ts +3 -2
  34. package/dist/rules/pdp-w-032.js +20 -12
  35. package/dist/rules/pdp-w-036.d.ts +2 -0
  36. package/dist/rules/pdp-w-036.js +31 -0
  37. package/dist/rules/pdp-w-037.d.ts +2 -0
  38. package/dist/rules/pdp-w-037.js +37 -0
  39. package/dist/rules/pdp-w-038.d.ts +2 -0
  40. package/dist/rules/pdp-w-038.js +30 -0
  41. package/dist/rules/pdp-w-039.d.ts +10 -0
  42. package/dist/rules/pdp-w-039.js +29 -0
  43. package/dist/rules/release-schema.d.ts +16 -0
  44. package/dist/rules/release-schema.js +77 -0
  45. package/dist/schema-validators.d.ts +2 -0
  46. package/dist/schema-validators.js +10 -4
  47. package/dist/sheet-type.d.ts +17 -0
  48. package/dist/sheet-type.js +25 -0
  49. package/dist/validate-and-lint.d.ts +9 -5
  50. package/dist/validate-and-lint.js +17 -9
  51. package/dist/validate-organization.d.ts +4 -4
  52. package/dist/validate-organization.js +8 -5
  53. package/dist/version.d.ts +1 -1
  54. package/dist/version.js +1 -1
  55. package/package.json +2 -2
@@ -1,4 +1,7 @@
1
+ import { KNOWN_RELEASES } from "./releases.js";
1
2
  import { orgE001 } from "./rules/org-e-001.js";
3
+ import { orgE002 } from "./rules/org-e-002.js";
4
+ import { orgE003 } from "./rules/org-e-003.js";
2
5
  import { orgW001 } from "./rules/org-w-001.js";
3
6
  import { orgW002 } from "./rules/org-w-002.js";
4
7
  import { orgW003 } from "./rules/org-w-003.js";
@@ -16,7 +19,7 @@ import { orgW006 } from "./rules/org-w-006.js";
16
19
  */
17
20
  export const organizationCatalogue = {
18
21
  vertical: "commerce",
19
- formatVersion: "0.9",
20
- catalogueVersion: "0.11.0",
21
- rules: [orgE001, orgW001, orgW002, orgW003, orgW004, orgW005, orgW006],
22
+ releases: KNOWN_RELEASES,
23
+ catalogueVersion: "0.15.0",
24
+ rules: [orgE001, orgE002, orgE003, orgW001, orgW002, orgW003, orgW004, orgW005, orgW006],
22
25
  };
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * The Pagefront Commerce Organization Sheet JSON Schema
3
- * (commerce/spec/organization.json, format 0.9), bundled verbatim so
3
+ * (commerce/spec/organization.json, the current sheet-spec version on
4
+ * the release line), bundled verbatim so
4
5
  * the published package can run Layer 1 validation without the
5
6
  * monorepo checkout. Generated from the spec file; the schema drift
6
7
  * test (test/schema.test.mjs) fails when the two diverge. Regenerate
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * The Pagefront Commerce Organization Sheet JSON Schema
3
- * (commerce/spec/organization.json, format 0.9), bundled verbatim so
3
+ * (commerce/spec/organization.json, the current sheet-spec version on
4
+ * the release line), bundled verbatim so
4
5
  * the published package can run Layer 1 validation without the
5
6
  * monorepo checkout. Generated from the spec file; the schema drift
6
7
  * test (test/schema.test.mjs) fails when the two diverge. Regenerate
@@ -8,12 +9,12 @@
8
9
  */
9
10
  export const ORGANIZATION_SCHEMA = {
10
11
  "$schema": "https://json-schema.org/draft/2020-12/schema",
11
- "$id": "https://themachineweb.org/pagefront/spec/commerce/organization/v0.9.json",
12
- "title": "Pagefront Commerce Organization Sheet v0.9",
13
- "description": "Formal schema for the Pagefront Commerce Organization Sheet — the sheet describing the entity behind products (brand owner, manufacturer, seller), introduced with commerce format 0.9 alongside the reference-capable legalEntity and the seller-sheet return/shipping model. The data block roots on Schema.org Organization (recognized subtypes permitted; @type is not constrained to a single value). pagefront:commerceRole is the required role discriminator, an array of one or more values from the closed enum brand_owner / manufacturer / seller. pagefront:fulfillmentTerms carries the store-wide shipping defaults as an array of Schema.org-native OfferShippingDetails objects; only the attachment shell is validated, the payload is Schema.org content. pagefront:socialAccount carries the organization's official social accounts (platform / url / handle; publisher-stated only), distinct from sameAs, which carries identity authority records only. pagefront:authorizedResellers carries the brand's authorization claim over its distribution — a pagefront:AuthorizedResellers object with an optional exhaustive flag and a reseller array whose entries name an Organization either as a reference stub (@id) or inline (name plus url and/or address), with optional Schema.org-native areaServed; valid only where commerceRole includes brand_owner (linter ORG-W-006). There is no GTIN-equivalent identifier requirement: identity is @id + name. Validates the envelope and pagefront-namespaced extensions strictly; Schema.org-typed blocks permit additional unspecified properties.",
12
+ "$id": "https://themachineweb.org/pagefront/spec/commerce/organization/v0.3.0.json",
13
+ "title": "Pagefront Commerce Organization Sheet 0.3.0",
14
+ "description": "Sheet spec version 0.3.0, a member of commerce release 0.3.0: the envelope carries release in place of format_version, and $schema names this sheet-spec version; a sheet carrying format_version belongs to the legacy draft line and validates against the legacy schema at .../organization/v0.9.json, which is otherwise identical. Formal schema for the Pagefront Commerce Organization Sheet — the sheet describing the entity behind products (brand owner, manufacturer, seller), introduced with commerce format 0.9 alongside the reference-capable legalEntity and the seller-sheet return/shipping model. The data block roots on Schema.org Organization (recognized subtypes permitted; @type is not constrained to a single value). pagefront:commerceRole is the required role discriminator, an array of one or more values from the closed enum brand_owner / manufacturer / seller. pagefront:fulfillmentTerms carries the store-wide shipping defaults as an array of Schema.org-native OfferShippingDetails objects; only the attachment shell is validated, the payload is Schema.org content. pagefront:socialAccount carries the organization's official social accounts (platform / url / handle; publisher-stated only), distinct from sameAs, which carries identity authority records only. pagefront:authorizedResellers carries the brand's authorization claim over its distribution — a pagefront:AuthorizedResellers object with an optional exhaustive flag and a reseller array whose entries name an Organization either as a reference stub (@id) or inline (name plus url and/or address), with optional Schema.org-native areaServed; valid only where commerceRole includes brand_owner (linter ORG-W-006). There is no GTIN-equivalent identifier requirement: identity is @id + name. Validates the envelope and pagefront-namespaced extensions strictly; Schema.org-typed blocks permit additional unspecified properties.",
14
15
  "type": "object",
15
16
  "required": [
16
- "format_version",
17
+ "release",
17
18
  "schema_org_version",
18
19
  "canonical_url",
19
20
  "sheet_url",
@@ -25,8 +26,9 @@ export const ORGANIZATION_SCHEMA = {
25
26
  "type": "string",
26
27
  "format": "uri"
27
28
  },
28
- "format_version": {
29
- "const": "0.9"
29
+ "release": {
30
+ "type": "string",
31
+ "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$"
30
32
  },
31
33
  "schema_org_version": {
32
34
  "type": "string",
@@ -1,6 +1,6 @@
1
1
  /**
2
- * The Pagefront product JSON Schema (commerce/spec/product.json,
3
- * format 0.9), bundled verbatim so the published package can run
2
+ * The Pagefront Product Sheet JSON Schema (commerce/spec/product.json,
3
+ * the current sheet-spec version on the release line), bundled verbatim so the published package can run
4
4
  * Layer 1 validation without the monorepo checkout. Generated from
5
5
  * the spec file; the schema drift test (test/schema.test.mjs) fails
6
6
  * when the two diverge. Regenerate on any schema change, in the same
@@ -1,6 +1,6 @@
1
1
  /**
2
- * The Pagefront product JSON Schema (commerce/spec/product.json,
3
- * format 0.9), bundled verbatim so the published package can run
2
+ * The Pagefront Product Sheet JSON Schema (commerce/spec/product.json,
3
+ * the current sheet-spec version on the release line), bundled verbatim so the published package can run
4
4
  * Layer 1 validation without the monorepo checkout. Generated from
5
5
  * the spec file; the schema drift test (test/schema.test.mjs) fails
6
6
  * when the two diverge. Regenerate on any schema change, in the same
@@ -8,12 +8,12 @@
8
8
  */
9
9
  export const PRODUCT_SCHEMA = {
10
10
  "$schema": "https://json-schema.org/draft/2020-12/schema",
11
- "$id": "https://themachineweb.org/pagefront/spec/commerce/product/v0.9.json",
12
- "title": "Pagefront Product Sheet v0.9",
13
- "description": "Formal schema for the Pagefront product sheet. Revision of 2026-10-01 (breaking, within v0.9): configurator pricing moves from pagefront:configurator to the offer. The configurator loses basePrice, basePriceCurrency and priceFormula, its options lose priceModifier and currency, and it gains an optional @id and an exclusions[] array; configurator dimensions gain affectsPrice; Offer and AggregateOffer accept pagefront:configuratorPricing, which names the configurator it prices and carries baseConfiguration, basePrice, modifiers, optional adjustments[] and a pricing completeness flag. v0.9 sheets that carried prices inside the configurator no longer validate. v0.9 was otherwise an additive release on top of v0.8: the legalEntity entry's organization now also validates in reference-stub form (@type + @id, no name) so entries can reference a Commerce Organization Sheet; the inline form and all other constraints are unchanged, and v0.8 sheets remain valid once their format_version is updated. v0.8 is an additive release on top of v0.7: it adds a pagefront:featureGroup[] array for grouped, ordered technical specifications (groups of Schema.org PropertyValue entries, with an optional pagefront:presentationValue display variant per entry), a pagefront:productHighlights[] array of ordered publisher-authored selling-point strings, and a pagefront:legalEntity[] array for role-typed legal entities behind a product (closed role enum: brand_owner, licensee, authorized_representative, importer, manufacturer — EU GPSR responsible persons and similar). gtin and gtin13 now accept a string or an array of strings (regional packaging, re-releases); category now accepts the display string, a Schema.org CategoryCode object, or a mixed array of both. The pagefront:Document role enum gains repairability_index, recall_notice, and declaration_of_conformity; the pagefront:RelatedProduct relationship enum gains service. v0.8 also adds two GPSR-driven safety fields: a pagefront:safetyWarning[] array for typed, language-scoped safety warnings (text required; optional pictogram URL and open-vocabulary hazardCategory) and a pagefront:safetyNotice[] array for structured recall/safety notices (closed noticeType enum: recall, warning; closed remedy enum: repair, replacement, refund). v0.7 sheets remain valid under v0.8 once their format_version is updated. Validates the envelope and pagefront-namespaced extensions strictly; Schema.org-typed blocks permit additional unspecified properties.",
11
+ "$id": "https://themachineweb.org/pagefront/spec/commerce/product/v0.9.0.json",
12
+ "title": "Pagefront Product Sheet 0.9.0",
13
+ "description": "Formal schema for the Pagefront Product Sheet, sheet spec version 0.9.0, a member of commerce release 0.3.0. The envelope carries release in place of format_version: release names the commerce release the sheet is written against, and $schema names this sheet-spec version. A sheet carrying format_version belongs to the legacy draft line and validates against the legacy schema at .../product/v0.9.json, which is otherwise identical. History, in legacy format versions: revision of 2026-10-01 (breaking, within v0.9): configurator pricing moves from pagefront:configurator to the offer. The configurator loses basePrice, basePriceCurrency and priceFormula, its options lose priceModifier and currency, and it gains an optional @id and an exclusions[] array; configurator dimensions gain affectsPrice, inputType (select or text, for free-text dimensions) and maxLength; Offer and AggregateOffer accept pagefront:configuratorPricing, which names the configurator it prices and carries baseConfiguration, basePrice, modifiers, optional adjustments[] and a pricing completeness flag; every amount field (Offer price, AggregateOffer lowPrice and highPrice, variant price hints, promotion discountAmount, and the pricing block's basePrice, modifiers and adjustment amounts) references a shared definition, a JSON number or a plain decimal string: nonNegativeAmount for prices, bounds, base prices, price hints and discount amounts, and the signed amount for modifiers and adjustment amounts. v0.9 sheets that carried prices inside the configurator no longer validate. v0.9 was otherwise an additive release on top of v0.8: the legalEntity entry's organization now also validates in reference-stub form (@type + @id, no name) so entries can reference a Commerce Organization Sheet; the inline form and all other constraints are unchanged, and v0.8 sheets remain valid once their format_version is updated. v0.8 is an additive release on top of v0.7: it adds a pagefront:featureGroup[] array for grouped, ordered technical specifications (groups of Schema.org PropertyValue entries, with an optional pagefront:presentationValue display variant per entry), a pagefront:productHighlights[] array of ordered publisher-authored selling-point strings, and a pagefront:legalEntity[] array for role-typed legal entities behind a product (closed role enum: brand_owner, licensee, authorized_representative, importer, manufacturer — EU GPSR responsible persons and similar). gtin and gtin13 now accept a string or an array of strings (regional packaging, re-releases); category now accepts the display string, a Schema.org CategoryCode object, or a mixed array of both. The pagefront:Document role enum gains repairability_index, recall_notice, and declaration_of_conformity; the pagefront:RelatedProduct relationship enum gains service. v0.8 also adds two GPSR-driven safety fields: a pagefront:safetyWarning[] array for typed, language-scoped safety warnings (text required; optional pictogram URL and open-vocabulary hazardCategory) and a pagefront:safetyNotice[] array for structured recall/safety notices (closed noticeType enum: recall, warning; closed remedy enum: repair, replacement, refund). v0.7 sheets remain valid under v0.8 once their format_version is updated. Validates the envelope and pagefront-namespaced extensions strictly; Schema.org-typed blocks permit additional unspecified properties.",
14
14
  "type": "object",
15
15
  "required": [
16
- "format_version",
16
+ "release",
17
17
  "schema_org_version",
18
18
  "canonical_url",
19
19
  "sheet_url",
@@ -25,8 +25,9 @@ export const PRODUCT_SCHEMA = {
25
25
  "type": "string",
26
26
  "format": "uri"
27
27
  },
28
- "format_version": {
29
- "const": "0.9"
28
+ "release": {
29
+ "type": "string",
30
+ "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$"
30
31
  },
31
32
  "schema_org_version": {
32
33
  "type": "string",
@@ -433,6 +434,31 @@ export const PRODUCT_SCHEMA = {
433
434
  },
434
435
  "additionalProperties": true
435
436
  },
437
+ "amount": {
438
+ "description": "A currency amount: a JSON number, or a decimal string with a dot as decimal point, no thousands separators and no currency symbol. A Sheet uses one of the two carriers for all its amounts.",
439
+ "oneOf": [
440
+ {
441
+ "type": "number"
442
+ },
443
+ {
444
+ "type": "string",
445
+ "pattern": "^-?\\d+(\\.\\d+)?$"
446
+ }
447
+ ]
448
+ },
449
+ "nonNegativeAmount": {
450
+ "description": "An amount that is zero or positive: the same two carriers as amount, with no leading minus. Used for prices, price bounds, base prices, price hints and discount amounts; only modifiers and adjustment amounts are signed.",
451
+ "oneOf": [
452
+ {
453
+ "type": "number",
454
+ "minimum": 0
455
+ },
456
+ {
457
+ "type": "string",
458
+ "pattern": "^\\d+(\\.\\d+)?$"
459
+ }
460
+ ]
461
+ },
436
462
  "variantEntry": {
437
463
  "type": "object",
438
464
  "required": [
@@ -482,10 +508,7 @@ export const PRODUCT_SCHEMA = {
482
508
  "type": "string"
483
509
  },
484
510
  "price": {
485
- "type": [
486
- "string",
487
- "number"
488
- ]
511
+ "$ref": "#/$defs/nonNegativeAmount"
489
512
  },
490
513
  "image": {
491
514
  "type": "string",
@@ -1181,7 +1204,7 @@ export const PRODUCT_SCHEMA = {
1181
1204
  "type": "object"
1182
1205
  },
1183
1206
  "price": {
1184
- "type": "string"
1207
+ "$ref": "#/$defs/nonNegativeAmount"
1185
1208
  },
1186
1209
  "priceCurrency": {
1187
1210
  "type": "string",
@@ -1301,10 +1324,10 @@ export const PRODUCT_SCHEMA = {
1301
1324
  "type": "string"
1302
1325
  },
1303
1326
  "lowPrice": {
1304
- "type": "number"
1327
+ "$ref": "#/$defs/nonNegativeAmount"
1305
1328
  },
1306
1329
  "highPrice": {
1307
- "type": "number"
1330
+ "$ref": "#/$defs/nonNegativeAmount"
1308
1331
  },
1309
1332
  "priceCurrency": {
1310
1333
  "type": "string",
@@ -1734,6 +1757,18 @@ export const PRODUCT_SCHEMA = {
1734
1757
  "type": "boolean",
1735
1758
  "default": true
1736
1759
  },
1760
+ "inputType": {
1761
+ "type": "string",
1762
+ "enum": [
1763
+ "select",
1764
+ "text"
1765
+ ],
1766
+ "default": "select"
1767
+ },
1768
+ "maxLength": {
1769
+ "type": "integer",
1770
+ "minimum": 1
1771
+ },
1737
1772
  "system": {
1738
1773
  "type": "string"
1739
1774
  },
@@ -1792,16 +1827,14 @@ export const PRODUCT_SCHEMA = {
1792
1827
  "$ref": "#/$defs/configurationSelection"
1793
1828
  },
1794
1829
  "basePrice": {
1795
- "type": "string",
1796
- "pattern": "^[0-9]+(\\.[0-9]+)?$"
1830
+ "$ref": "#/$defs/nonNegativeAmount"
1797
1831
  },
1798
1832
  "modifiers": {
1799
1833
  "type": "object",
1800
1834
  "additionalProperties": {
1801
1835
  "type": "object",
1802
1836
  "additionalProperties": {
1803
- "type": "string",
1804
- "pattern": "^-?[0-9]+(\\.[0-9]+)?$"
1837
+ "$ref": "#/$defs/amount"
1805
1838
  }
1806
1839
  }
1807
1840
  },
@@ -1820,8 +1853,7 @@ export const PRODUCT_SCHEMA = {
1820
1853
  "minProperties": 1
1821
1854
  },
1822
1855
  "priceModifier": {
1823
- "type": "string",
1824
- "pattern": "^-?[0-9]+(\\.[0-9]+)?$"
1856
+ "$ref": "#/$defs/amount"
1825
1857
  }
1826
1858
  },
1827
1859
  "additionalProperties": false
@@ -1955,7 +1987,7 @@ export const PRODUCT_SCHEMA = {
1955
1987
  "maximum": 100
1956
1988
  },
1957
1989
  "discountAmount": {
1958
- "type": "string"
1990
+ "$ref": "#/$defs/nonNegativeAmount"
1959
1991
  },
1960
1992
  "discountCurrency": {
1961
1993
  "type": "string",
@@ -0,0 +1,36 @@
1
+ import type { SheetType } from "./sheet-type.js";
2
+ /**
3
+ * The commerce release manifest (commerce/spec/releases.json), bundled
4
+ * verbatim: each release and the member versions it bundles. Generated
5
+ * from the spec file; the drift test (test/releases.test.mjs) fails
6
+ * when the two diverge. Regenerate when a release is cut.
7
+ */
8
+ export declare const RELEASE_MANIFEST: {
9
+ readonly $comment: "The commerce release manifest: each release and the member versions it bundles. Consumed by the linter; the human-readable form is releases.md.";
10
+ readonly vertical: "commerce";
11
+ readonly schema_url_template: "https://themachineweb.org/pagefront/spec/commerce/{sheet}/v{version}.json";
12
+ readonly releases: readonly [{
13
+ readonly release: "0.3.0";
14
+ readonly date: "2026-10-01";
15
+ readonly vocabulary: "0.3.0";
16
+ readonly sheets: {
17
+ readonly product: "0.9.0";
18
+ readonly organization: "0.3.0";
19
+ };
20
+ }];
21
+ readonly legacy: {
22
+ readonly envelope_field: "format_version";
23
+ readonly format_versions: readonly ["0.9"];
24
+ readonly schemas: {
25
+ readonly product: "https://themachineweb.org/pagefront/spec/commerce/product/v0.9.json";
26
+ readonly organization: "https://themachineweb.org/pagefront/spec/commerce/organization/v0.9.json";
27
+ };
28
+ };
29
+ };
30
+ /** Every release the manifest lists, in manifest order. */
31
+ export declare const KNOWN_RELEASES: readonly string[];
32
+ /**
33
+ * The sheet-spec version a release bundles for a sheet type, or
34
+ * undefined when the manifest does not list the release.
35
+ */
36
+ export declare function sheetSpecVersionFor(release: string, sheetType: SheetType): string | undefined;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The commerce release manifest (commerce/spec/releases.json), bundled
3
+ * verbatim: each release and the member versions it bundles. Generated
4
+ * from the spec file; the drift test (test/releases.test.mjs) fails
5
+ * when the two diverge. Regenerate when a release is cut.
6
+ */
7
+ export const RELEASE_MANIFEST = {
8
+ "$comment": "The commerce release manifest: each release and the member versions it bundles. Consumed by the linter; the human-readable form is releases.md.",
9
+ "vertical": "commerce",
10
+ "schema_url_template": "https://themachineweb.org/pagefront/spec/commerce/{sheet}/v{version}.json",
11
+ "releases": [
12
+ {
13
+ "release": "0.3.0",
14
+ "date": "2026-10-01",
15
+ "vocabulary": "0.3.0",
16
+ "sheets": {
17
+ "product": "0.9.0",
18
+ "organization": "0.3.0"
19
+ }
20
+ }
21
+ ],
22
+ "legacy": {
23
+ "envelope_field": "format_version",
24
+ "format_versions": [
25
+ "0.9"
26
+ ],
27
+ "schemas": {
28
+ "product": "https://themachineweb.org/pagefront/spec/commerce/product/v0.9.json",
29
+ "organization": "https://themachineweb.org/pagefront/spec/commerce/organization/v0.9.json"
30
+ }
31
+ }
32
+ };
33
+ /** Every release the manifest lists, in manifest order. */
34
+ export const KNOWN_RELEASES = RELEASE_MANIFEST.releases.map((r) => r.release);
35
+ /**
36
+ * The sheet-spec version a release bundles for a sheet type, or
37
+ * undefined when the manifest does not list the release.
38
+ */
39
+ export function sheetSpecVersionFor(release, sheetType) {
40
+ return RELEASE_MANIFEST.releases.find((r) => r.release === release)?.sheets[sheetType];
41
+ }
@@ -38,9 +38,23 @@ export interface Dimension {
38
38
  name: string;
39
39
  /** `affectsPrice` is not `false`. */
40
40
  priced: boolean;
41
- /** Option keys in document order; null when the dimension enumerates none. */
41
+ /** `inputType` is `text` (Definitions: *text dimension*). */
42
+ text: boolean;
43
+ /** The declared sizing `system`, when the dimension is a sizing dimension. */
44
+ system: string | undefined;
45
+ /**
46
+ * Option keys in document order; null when the dimension enumerates
47
+ * none. Always null for a text dimension, which takes free input.
48
+ */
42
49
  optionKeys: string[] | null;
43
50
  }
51
+ /**
52
+ * `system` → the `pagefront:SizeAdjustment.sizeSystem` naming the same
53
+ * system (Definitions: *corresponding size system*).
54
+ */
55
+ export declare const CORRESPONDING_SIZE_SYSTEM: Record<string, string>;
56
+ /** The one modifier key of a text dimension: any non-empty input. */
57
+ export declare const TEXT_KEY = "*";
44
58
  export interface Configurator {
45
59
  id: string | undefined;
46
60
  dimensions: Dimension[];
@@ -86,6 +100,9 @@ export declare function baseConfigurationProblems(configurator: Configurator, bl
86
100
  export type MissingModifierProblem = {
87
101
  kind: "no-options";
88
102
  dimension: string;
103
+ } | {
104
+ kind: "no-text-modifier";
105
+ dimension: string;
89
106
  } | {
90
107
  kind: "missing";
91
108
  dimension: string;
@@ -108,7 +125,9 @@ export interface Band {
108
125
  * enumerated together. Every other priced dimension contributes its
109
126
  * smallest modifier to the minimum and its largest to the maximum,
110
127
  * independently of the rest, which yields the same band as the full
111
- * enumeration.
128
+ * enumeration. A priced text dimension is empty or non-empty: its `*`
129
+ * modifier lowers the minimum when negative and raises the maximum
130
+ * when positive.
112
131
  */
113
132
  export declare function computeBand(configurator: Configurator, block: Obj): Band | null;
114
133
  export {};
@@ -83,6 +83,18 @@ export function optionKey(value) {
83
83
  return JSON.stringify(value);
84
84
  return undefined;
85
85
  }
86
+ /**
87
+ * `system` → the `pagefront:SizeAdjustment.sizeSystem` naming the same
88
+ * system (Definitions: *corresponding size system*).
89
+ */
90
+ export const CORRESPONDING_SIZE_SYSTEM = {
91
+ UK: "WearableSizeSystemUK",
92
+ EU: "WearableSizeSystemEU",
93
+ US: "WearableSizeSystemUS",
94
+ JP: "WearableSizeSystemJP",
95
+ };
96
+ /** The one modifier key of a text dimension: any non-empty input. */
97
+ export const TEXT_KEY = "*";
86
98
  function selection(value) {
87
99
  const obj = asObject(value);
88
100
  if (obj === null)
@@ -107,8 +119,9 @@ export function readConfigurator(sheet) {
107
119
  const d = asObject(entry);
108
120
  if (d === null || typeof d["name"] !== "string")
109
121
  continue;
122
+ const text = d["inputType"] === "text";
110
123
  let optionKeys = null;
111
- if (Array.isArray(d["options"]) && d["options"].length > 0) {
124
+ if (!text && Array.isArray(d["options"]) && d["options"].length > 0) {
112
125
  optionKeys = [];
113
126
  for (const option of d["options"]) {
114
127
  const key = optionKey(asObject(option)?.["value"]);
@@ -116,7 +129,13 @@ export function readConfigurator(sheet) {
116
129
  optionKeys.push(key);
117
130
  }
118
131
  }
119
- const dimension = { name: d["name"], priced: d["affectsPrice"] !== false, optionKeys };
132
+ const dimension = {
133
+ name: d["name"],
134
+ priced: d["affectsPrice"] !== false,
135
+ text,
136
+ system: typeof d["system"] === "string" ? d["system"] : undefined,
137
+ optionKeys,
138
+ };
120
139
  dimensions.push(dimension);
121
140
  if (!byName.has(dimension.name))
122
141
  byName.set(dimension.name, dimension);
@@ -213,11 +232,17 @@ export function missingModifierProblems(configurator, block) {
213
232
  for (const d of configurator.dimensions) {
214
233
  if (!d.priced)
215
234
  continue;
235
+ const entry = asObject(own(modifiers, d.name));
236
+ if (d.text) {
237
+ if (own(entry, TEXT_KEY) === undefined) {
238
+ problems.push({ kind: "no-text-modifier", dimension: d.name });
239
+ }
240
+ continue;
241
+ }
216
242
  if (d.optionKeys === null) {
217
243
  problems.push({ kind: "no-options", dimension: d.name });
218
244
  continue;
219
245
  }
220
- const entry = asObject(own(modifiers, d.name));
221
246
  for (const option of d.optionKeys) {
222
247
  if (own(entry, option) === undefined) {
223
248
  problems.push({ kind: "missing", dimension: d.name, option });
@@ -244,7 +269,9 @@ function matches(pairs, configuration) {
244
269
  * enumerated together. Every other priced dimension contributes its
245
270
  * smallest modifier to the minimum and its largest to the maximum,
246
271
  * independently of the rest, which yields the same band as the full
247
- * enumeration.
272
+ * enumeration. A priced text dimension is empty or non-empty: its `*`
273
+ * modifier lowers the minimum when negative and raises the maximum
274
+ * when positive.
248
275
  */
249
276
  export function computeBand(configurator, block) {
250
277
  const base = parseDecimal(block["basePrice"]);
@@ -279,8 +306,17 @@ export function computeBand(configurator, block) {
279
306
  continue;
280
307
  seen.add(d.name);
281
308
  if (d.optionKeys === null) {
282
- if (d.priced)
309
+ if (!d.priced)
310
+ continue;
311
+ if (!d.text)
312
+ return null;
313
+ const amount = modifierOf(d, TEXT_KEY);
314
+ if (amount === undefined)
283
315
  return null;
316
+ if (amount.units < 0n)
317
+ min = addDecimal(min, amount);
318
+ else
319
+ max = addDecimal(max, amount);
284
320
  continue;
285
321
  }
286
322
  if (coupledNames.has(d.name)) {
@@ -0,0 +1,7 @@
1
+ import type { Rule } from "@pagefront/lint-core";
2
+ /**
3
+ * ORG-E-002 — Sheet carries both format_version and release.
4
+ * The organization-sheet counterpart of PDP-E-016. Transcribed from
5
+ * commerce/spec/rules.md (normative).
6
+ */
7
+ export declare const orgE002: Rule;
@@ -0,0 +1,13 @@
1
+ import { BOTH_VERSION_FIELDS, bothVersionFields } from "./release-schema.js";
2
+ /**
3
+ * ORG-E-002 — Sheet carries both format_version and release.
4
+ * The organization-sheet counterpart of PDP-E-016. Transcribed from
5
+ * commerce/spec/rules.md (normative).
6
+ */
7
+ export const orgE002 = {
8
+ id: "ORG-E-002",
9
+ ...BOTH_VERSION_FIELDS,
10
+ check: bothVersionFields,
11
+ introduced: "release 0.3.0",
12
+ specReference: "organization.md#the-envelope",
13
+ };
@@ -0,0 +1,7 @@
1
+ import type { Rule } from "@pagefront/lint-core";
2
+ /**
3
+ * ORG-E-003 — Release / schema mismatch.
4
+ * The organization-sheet counterpart of PDP-E-002. Transcribed from
5
+ * commerce/spec/rules.md (normative).
6
+ */
7
+ export declare const orgE003: Rule;
@@ -0,0 +1,13 @@
1
+ import { RELEASE_SCHEMA_MISMATCH, releaseSchemaMismatch } from "./release-schema.js";
2
+ /**
3
+ * ORG-E-003 — Release / schema mismatch.
4
+ * The organization-sheet counterpart of PDP-E-002. Transcribed from
5
+ * commerce/spec/rules.md (normative).
6
+ */
7
+ export const orgE003 = {
8
+ id: "ORG-E-003",
9
+ ...RELEASE_SCHEMA_MISMATCH,
10
+ check: releaseSchemaMismatch("organization"),
11
+ introduced: "release 0.3.0",
12
+ specReference: "organization.md#the-envelope",
13
+ };
@@ -1,13 +1,7 @@
1
1
  import type { Rule } from "@pagefront/lint-core";
2
2
  /**
3
- * PDP-E-002 — Format version / schema mismatch.
4
- * Transcribed from commerce/spec/rules.md (normative).
5
- *
6
- * `$schema` is read via the rule context, per the target line. The
7
- * version segment is the trailing `v<version>.json` of the `$schema`
8
- * URL. Two quiet cases, both documented readings: `$schema` absent
9
- * (optional at Layer 1 — nothing to compare), and a `$schema` with no
10
- * parseable version segment (it declares no version, so no mismatch
11
- * can be asserted; the message requires `{schema_version}`).
3
+ * PDP-E-002 — Release / schema mismatch (until release 0.3.0: "Format
4
+ * version / schema mismatch"). Transcribed from commerce/spec/rules.md
5
+ * (normative); the check is shared with ORG-E-003 (release-schema.ts).
12
6
  */
13
7
  export declare const pdpE002: Rule;
@@ -1,33 +1,13 @@
1
+ import { RELEASE_SCHEMA_MISMATCH, releaseSchemaMismatch } from "./release-schema.js";
1
2
  /**
2
- * PDP-E-002 — Format version / schema mismatch.
3
- * Transcribed from commerce/spec/rules.md (normative).
4
- *
5
- * `$schema` is read via the rule context, per the target line. The
6
- * version segment is the trailing `v<version>.json` of the `$schema`
7
- * URL. Two quiet cases, both documented readings: `$schema` absent
8
- * (optional at Layer 1 — nothing to compare), and a `$schema` with no
9
- * parseable version segment (it declares no version, so no mismatch
10
- * can be asserted; the message requires `{schema_version}`).
3
+ * PDP-E-002 — Release / schema mismatch (until release 0.3.0: "Format
4
+ * version / schema mismatch"). Transcribed from commerce/spec/rules.md
5
+ * (normative); the check is shared with ORG-E-003 (release-schema.ts).
11
6
  */
12
7
  export const pdpE002 = {
13
8
  id: "PDP-E-002",
14
- title: "Format version / schema mismatch",
15
- severity: "error",
16
- target: "$.format_version",
17
- check: (match, ctx) => {
18
- const formatVersion = String(match.value);
19
- const schema = ctx.sheet.$schema;
20
- if (typeof schema !== "string")
21
- return null;
22
- const segment = /v(\d+(?:\.\d+)*)\.json$/.exec(schema);
23
- if (segment === null)
24
- return null;
25
- return segment[1] === formatVersion
26
- ? null
27
- : { values: { format_version: formatVersion, schema_version: segment[1] } };
28
- },
29
- messageTemplate: "`format_version` is `{format_version}` but `$schema` URL declares version `{schema_version}`.",
30
- remediation: "Align both fields to the same version. Whichever version was intended, update the other field to match.",
9
+ ...RELEASE_SCHEMA_MISMATCH,
10
+ check: releaseSchemaMismatch("product"),
31
11
  introduced: "v0.6",
32
12
  specReference: "product.md#the-envelope",
33
13
  };
@@ -35,7 +35,7 @@ export const pdpE008 = {
35
35
  return hits.length > 0 ? hits : null;
36
36
  },
37
37
  messageTemplate: "Modifier for base option `{option}` of `{dimension}` at `{path}` is `{modifier}`; the base option's modifier is `0`.",
38
- remediation: "Set the base option's modifier to `\"0\"` and express the other options of the dimension as differences from it. If the modifier is right, `basePrice` is the total of another configuration: correct `baseConfiguration` or `basePrice`.",
38
+ remediation: "Set the base option's modifier to zero and express the other options of the dimension as differences from it. If the modifier is right, `basePrice` is the total of another configuration: correct `baseConfiguration` or `basePrice`.",
39
39
  introduced: "v0.9 (2026-10-01 revision)",
40
40
  specReference: "product.md#configurator-pricing-and-price-bands",
41
41
  };
@@ -7,6 +7,6 @@ import type { Rule } from "@pagefront/lint-core";
7
7
  * A `modifiers` key naming no dimension, or a dimension declared
8
8
  * `affectsPrice: false`, fires once at that key; under a priced
9
9
  * dimension, each option key that is not one of its options fires at
10
- * its own path.
10
+ * its own path. Under a priced text dimension, `*` is the one valid key.
11
11
  */
12
12
  export declare const pdpE009: Rule;
@@ -1,4 +1,4 @@
1
- import { asObject, resolveConfigurator, seg } from "./configurator-pricing.js";
1
+ import { asObject, resolveConfigurator, seg, TEXT_KEY } from "./configurator-pricing.js";
2
2
  /**
3
3
  * PDP-E-009 — Modifier for an unknown dimension or option.
4
4
  * Transcribed from commerce/spec/rules.md (normative).
@@ -7,7 +7,7 @@ import { asObject, resolveConfigurator, seg } from "./configurator-pricing.js";
7
7
  * A `modifiers` key naming no dimension, or a dimension declared
8
8
  * `affectsPrice: false`, fires once at that key; under a priced
9
9
  * dimension, each option key that is not one of its options fires at
10
- * its own path.
10
+ * its own path. Under a priced text dimension, `*` is the one valid key.
11
11
  */
12
12
  export const pdpE009 = {
13
13
  id: "PDP-E-009",
@@ -41,7 +41,7 @@ export const pdpE009 = {
41
41
  continue;
42
42
  }
43
43
  for (const option of Object.keys(asObject(entry) ?? {})) {
44
- if (d.optionKeys !== null && d.optionKeys.includes(option))
44
+ if (d.text ? option === TEXT_KEY : d.optionKeys !== null && d.optionKeys.includes(option))
45
45
  continue;
46
46
  hits.push({
47
47
  path: `${path}${seg(option)}`,
@@ -0,0 +1,9 @@
1
+ import type { Rule } from "@pagefront/lint-core";
2
+ /**
3
+ * PDP-E-013 — Text dimension with options.
4
+ * Transcribed from commerce/spec/rules.md (normative).
5
+ *
6
+ * Fires on a dimension whose `inputType` is `text` and whose `options`
7
+ * is an array with at least one entry.
8
+ */
9
+ export declare const pdpE013: Rule;