@motion-proto/live-tokens 0.76.0 → 0.78.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 (50) hide show
  1. package/.claude/skills/live-tokens-check-compliance/SKILL.md +2 -0
  2. package/.claude/skills/live-tokens-create-component/SKILL.md +3 -3
  3. package/.claude/skills/live-tokens-create-component/references/contract-tests.md +99 -3
  4. package/.claude/skills/live-tokens-create-page/SKILL.md +5 -4
  5. package/.claude/skills/live-tokens-fix-findings/SKILL.md +7 -0
  6. package/CHANGELOG.md +90 -0
  7. package/bin/check-component.mjs +2 -0
  8. package/bin/check-page.mjs +131 -9
  9. package/bin/cli.mjs +25 -3
  10. package/bin/contractRunner.mjs +349 -30
  11. package/bin/lib/pageRoutes.mjs +309 -0
  12. package/package.json +5 -3
  13. package/src/editor/overlay/ColumnsOverlay.svelte +1 -1
  14. package/src/editor/overlay/LiveEditorOverlay.svelte +1 -0
  15. package/src/editor/overlay/LiveTokensRouter.svelte +1 -0
  16. package/src/editor/skill-atlas/skillSources.generated.ts +5 -5
  17. package/src/editor/skill-atlas/trees/check-compliance.ts +18 -18
  18. package/src/editor/skill-atlas/trees/create-component.ts +8 -3
  19. package/src/editor/skill-atlas/trees/create-page.ts +26 -26
  20. package/src/editor/skill-atlas/trees/fix-findings.ts +46 -11
  21. package/src/testing-js/{chunk-LXR3MN6N.js → chunk-4JQX6WWL.js} +1 -1
  22. package/src/testing-js/chunk-4JQX6WWL.js.map +1 -0
  23. package/src/testing-js/{chunk-AO7EZHYV.js → chunk-GNIUPIU2.js} +3 -3
  24. package/src/testing-js/{chunk-ZMSX6CXR.js → chunk-NB3NZRBM.js} +13 -2
  25. package/src/testing-js/chunk-NB3NZRBM.js.map +1 -0
  26. package/src/testing-js/{chunk-L73N4NSO.js → chunk-WIZ6W7UT.js} +2 -2
  27. package/src/testing-js/component-alias.contract.js +2 -2
  28. package/src/testing-js/component-editor.contract.js +21 -5
  29. package/src/testing-js/component-editor.contract.js.map +1 -1
  30. package/src/testing-js/component-render.contract.js +4 -4
  31. package/src/testing-js/index.d.ts +9 -3
  32. package/src/testing-js/index.js +20 -12
  33. package/src/testing-js/index.js.map +1 -1
  34. package/src/testing-js/page-compliance.contract.js +829 -0
  35. package/src/testing-js/page-compliance.contract.js.map +1 -0
  36. package/src/testing-js/{vitest-BE6uGF31.d.ts → vitest-C-wNWcoA.d.ts} +21 -1
  37. package/src/testing-js/vitest.d.ts +1 -1
  38. package/src/testing-js/vitest.js +1 -1
  39. package/template/README.md +17 -5
  40. package/template/live-tokens.testing.ts +6 -0
  41. package/template/package.json +5 -2
  42. package/template/src/main.ts +1 -0
  43. package/template/src/pages/Home.svelte +13 -0
  44. package/template/src/registerComponents.ts +4 -0
  45. package/template/tests/contracts.ts +3 -0
  46. package/template/tsconfig.json +1 -1
  47. package/src/testing-js/chunk-LXR3MN6N.js.map +0 -1
  48. package/src/testing-js/chunk-ZMSX6CXR.js.map +0 -1
  49. /package/src/testing-js/{chunk-AO7EZHYV.js.map → chunk-GNIUPIU2.js.map} +0 -0
  50. /package/src/testing-js/{chunk-L73N4NSO.js.map → chunk-WIZ6W7UT.js.map} +0 -0
@@ -1,16 +1,32 @@
1
1
  import {
2
2
  ContractHarness
3
- } from "./chunk-AO7EZHYV.js";
4
- import "./chunk-L73N4NSO.js";
3
+ } from "./chunk-GNIUPIU2.js";
4
+ import "./chunk-WIZ6W7UT.js";
5
5
  import {
6
+ ContractViolation,
7
+ allContracts,
6
8
  isInapplicable,
7
9
  selectedContracts
8
- } from "./chunk-LXR3MN6N.js";
9
- import "./chunk-ZMSX6CXR.js";
10
+ } from "./chunk-4JQX6WWL.js";
11
+ import "./chunk-NB3NZRBM.js";
10
12
 
11
13
  // src/testing/component-editor.contract.ts
12
14
  import { test } from "@playwright/test";
13
- for (const contract of await selectedContracts()) {
15
+ var selected = await selectedContracts();
16
+ var requested = process.env.LIVE_TOKENS_COMPONENT;
17
+ if (requested && selected.length === 0) {
18
+ const declared = (await allContracts()).map((contract) => contract.id).sort();
19
+ test.describe.serial(requested, () => {
20
+ test(`${requested} has a component contract`, () => {
21
+ throw new ContractViolation(
22
+ "contract-missing",
23
+ requested,
24
+ `no component contract is declared for it. Export one from the module \`contractsModule\` names in live-tokens.testing.ts. Declared: ${declared.join(", ")}`
25
+ );
26
+ });
27
+ });
28
+ }
29
+ for (const contract of selected) {
14
30
  test.describe.serial(contract.id, () => {
15
31
  test(`${contract.id} is listed in its registry group`, async ({ page }) => {
16
32
  const harness = await ContractHarness.attach(page, contract);
@@ -1 +1 @@
1
- {"version":3,"sources":["../testing/component-editor.contract.ts"],"sourcesContent":["import { test } from '@playwright/test';\nimport { isInapplicable } from './componentContract';\nimport { selectedContracts } from './contracts';\nimport { ContractHarness } from './support/contractHarness';\n\nfor (const contract of await selectedContracts()) {\n // Serial, so a component that is not registered reports that and stops rather\n // than reporting six preview failures caused by the missing entry.\n test.describe.serial(contract.id, () => {\n test(`${contract.id} is listed in its registry group`, async ({ page }) => {\n const harness = await ContractHarness.attach(page, contract);\n await harness.assertListed();\n });\n\n test(`${contract.id} declares every part and every shipped alias`, async ({ page }) => {\n const harness = await ContractHarness.open(page, contract);\n await harness.assertInventory();\n });\n\n test(`${contract.id} resolves every alias it paints with`, async ({ page }) => {\n const harness = await ContractHarness.open(page, contract);\n await harness.assertAliasesResolve();\n });\n\n test(`${contract.id} previews the state being edited`, async ({ page }) => {\n const harness = await ContractHarness.open(page, contract);\n if (isInapplicable(contract.states)) {\n test.info().annotations.push({ type: 'inapplicable', description: contract.states.reason });\n }\n await harness.assertStates();\n });\n\n test(`${contract.id} answers the pointer and the keyboard`, async ({ page }) => {\n const harness = await ContractHarness.open(page, contract);\n if (isInapplicable(contract.interaction)) {\n test.info().annotations.push({ type: 'inapplicable', description: contract.interaction.reason });\n }\n await harness.assertInteraction();\n });\n\n test(`${contract.id} persists an edit and resets to the saved config`, async ({ page }) => {\n test.setTimeout(180_000);\n const harness = await ContractHarness.open(page, contract);\n await harness.assertPersistence();\n });\n\n test(`${contract.id} takes the theme's values and gives them back`, async ({ page }) => {\n const harness = await ContractHarness.open(page, contract);\n await harness.assertThemeProjection(contract.theme);\n });\n\n test(`${contract.id} draws every painted part in Sketch mode`, async ({ page }) => {\n const harness = await ContractHarness.open(page, contract);\n const sketch = contract.sketch;\n if (isInapplicable(sketch)) {\n test.info().annotations.push({ type: 'inapplicable', description: sketch.reason });\n await harness.assertNoSketchPaint('pencil');\n return;\n }\n await harness.assertSketchPaint(sketch);\n });\n });\n}\n"],"mappings":";;;;;;;;;;;AAAA,SAAS,YAAY;AAKrB,WAAW,YAAY,MAAM,kBAAkB,GAAG;AAGhD,OAAK,SAAS,OAAO,SAAS,IAAI,MAAM;AACtC,SAAK,GAAG,SAAS,EAAE,oCAAoC,OAAO,EAAE,KAAK,MAAM;AACzE,YAAM,UAAU,MAAM,gBAAgB,OAAO,MAAM,QAAQ;AAC3D,YAAM,QAAQ,aAAa;AAAA,IAC7B,CAAC;AAED,SAAK,GAAG,SAAS,EAAE,gDAAgD,OAAO,EAAE,KAAK,MAAM;AACrF,YAAM,UAAU,MAAM,gBAAgB,KAAK,MAAM,QAAQ;AACzD,YAAM,QAAQ,gBAAgB;AAAA,IAChC,CAAC;AAED,SAAK,GAAG,SAAS,EAAE,wCAAwC,OAAO,EAAE,KAAK,MAAM;AAC7E,YAAM,UAAU,MAAM,gBAAgB,KAAK,MAAM,QAAQ;AACzD,YAAM,QAAQ,qBAAqB;AAAA,IACrC,CAAC;AAED,SAAK,GAAG,SAAS,EAAE,oCAAoC,OAAO,EAAE,KAAK,MAAM;AACzE,YAAM,UAAU,MAAM,gBAAgB,KAAK,MAAM,QAAQ;AACzD,UAAI,eAAe,SAAS,MAAM,GAAG;AACnC,aAAK,KAAK,EAAE,YAAY,KAAK,EAAE,MAAM,gBAAgB,aAAa,SAAS,OAAO,OAAO,CAAC;AAAA,MAC5F;AACA,YAAM,QAAQ,aAAa;AAAA,IAC7B,CAAC;AAED,SAAK,GAAG,SAAS,EAAE,yCAAyC,OAAO,EAAE,KAAK,MAAM;AAC9E,YAAM,UAAU,MAAM,gBAAgB,KAAK,MAAM,QAAQ;AACzD,UAAI,eAAe,SAAS,WAAW,GAAG;AACxC,aAAK,KAAK,EAAE,YAAY,KAAK,EAAE,MAAM,gBAAgB,aAAa,SAAS,YAAY,OAAO,CAAC;AAAA,MACjG;AACA,YAAM,QAAQ,kBAAkB;AAAA,IAClC,CAAC;AAED,SAAK,GAAG,SAAS,EAAE,oDAAoD,OAAO,EAAE,KAAK,MAAM;AACzF,WAAK,WAAW,IAAO;AACvB,YAAM,UAAU,MAAM,gBAAgB,KAAK,MAAM,QAAQ;AACzD,YAAM,QAAQ,kBAAkB;AAAA,IAClC,CAAC;AAED,SAAK,GAAG,SAAS,EAAE,iDAAiD,OAAO,EAAE,KAAK,MAAM;AACtF,YAAM,UAAU,MAAM,gBAAgB,KAAK,MAAM,QAAQ;AACzD,YAAM,QAAQ,sBAAsB,SAAS,KAAK;AAAA,IACpD,CAAC;AAED,SAAK,GAAG,SAAS,EAAE,4CAA4C,OAAO,EAAE,KAAK,MAAM;AACjF,YAAM,UAAU,MAAM,gBAAgB,KAAK,MAAM,QAAQ;AACzD,YAAM,SAAS,SAAS;AACxB,UAAI,eAAe,MAAM,GAAG;AAC1B,aAAK,KAAK,EAAE,YAAY,KAAK,EAAE,MAAM,gBAAgB,aAAa,OAAO,OAAO,CAAC;AACjF,cAAM,QAAQ,oBAAoB,QAAQ;AAC1C;AAAA,MACF;AACA,YAAM,QAAQ,kBAAkB,MAAM;AAAA,IACxC,CAAC;AAAA,EACH,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../testing/component-editor.contract.ts"],"sourcesContent":["import { test } from '@playwright/test';\nimport { ContractViolation, isInapplicable } from './componentContract';\nimport { allContracts, selectedContracts } from './contracts';\nimport { ContractHarness } from './support/contractHarness';\n\nconst selected = await selectedContracts();\nconst requested = process.env.LIVE_TOKENS_COMPONENT;\n\n// A failing test rather than a throw from the config: the JSON report then\n// carries the rule, and the runner maps it like any other violation instead\n// of reading a stderr tail.\nif (requested && selected.length === 0) {\n const declared = (await allContracts()).map((contract) => contract.id).sort();\n test.describe.serial(requested, () => {\n test(`${requested} has a component contract`, () => {\n throw new ContractViolation(\n 'contract-missing',\n requested,\n 'no component contract is declared for it. Export one from the module '\n + `\\`contractsModule\\` names in live-tokens.testing.ts. Declared: ${declared.join(', ')}`,\n );\n });\n });\n}\n\nfor (const contract of selected) {\n // Serial, so a component that is not registered reports that and stops rather\n // than reporting six preview failures caused by the missing entry.\n test.describe.serial(contract.id, () => {\n test(`${contract.id} is listed in its registry group`, async ({ page }) => {\n const harness = await ContractHarness.attach(page, contract);\n await harness.assertListed();\n });\n\n test(`${contract.id} declares every part and every shipped alias`, async ({ page }) => {\n const harness = await ContractHarness.open(page, contract);\n await harness.assertInventory();\n });\n\n test(`${contract.id} resolves every alias it paints with`, async ({ page }) => {\n const harness = await ContractHarness.open(page, contract);\n await harness.assertAliasesResolve();\n });\n\n test(`${contract.id} previews the state being edited`, async ({ page }) => {\n const harness = await ContractHarness.open(page, contract);\n if (isInapplicable(contract.states)) {\n test.info().annotations.push({ type: 'inapplicable', description: contract.states.reason });\n }\n await harness.assertStates();\n });\n\n test(`${contract.id} answers the pointer and the keyboard`, async ({ page }) => {\n const harness = await ContractHarness.open(page, contract);\n if (isInapplicable(contract.interaction)) {\n test.info().annotations.push({ type: 'inapplicable', description: contract.interaction.reason });\n }\n await harness.assertInteraction();\n });\n\n test(`${contract.id} persists an edit and resets to the saved config`, async ({ page }) => {\n test.setTimeout(180_000);\n const harness = await ContractHarness.open(page, contract);\n await harness.assertPersistence();\n });\n\n test(`${contract.id} takes the theme's values and gives them back`, async ({ page }) => {\n const harness = await ContractHarness.open(page, contract);\n await harness.assertThemeProjection(contract.theme);\n });\n\n test(`${contract.id} draws every painted part in Sketch mode`, async ({ page }) => {\n const harness = await ContractHarness.open(page, contract);\n const sketch = contract.sketch;\n if (isInapplicable(sketch)) {\n test.info().annotations.push({ type: 'inapplicable', description: sketch.reason });\n await harness.assertNoSketchPaint('pencil');\n return;\n }\n await harness.assertSketchPaint(sketch);\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,YAAY;AAKrB,IAAM,WAAW,MAAM,kBAAkB;AACzC,IAAM,YAAY,QAAQ,IAAI;AAK9B,IAAI,aAAa,SAAS,WAAW,GAAG;AACtC,QAAM,YAAY,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,SAAS,EAAE,EAAE,KAAK;AAC5E,OAAK,SAAS,OAAO,WAAW,MAAM;AACpC,SAAK,GAAG,SAAS,6BAA6B,MAAM;AAClD,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA,uIACoE,SAAS,KAAK,IAAI,CAAC;AAAA,MACzF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAEA,WAAW,YAAY,UAAU;AAG/B,OAAK,SAAS,OAAO,SAAS,IAAI,MAAM;AACtC,SAAK,GAAG,SAAS,EAAE,oCAAoC,OAAO,EAAE,KAAK,MAAM;AACzE,YAAM,UAAU,MAAM,gBAAgB,OAAO,MAAM,QAAQ;AAC3D,YAAM,QAAQ,aAAa;AAAA,IAC7B,CAAC;AAED,SAAK,GAAG,SAAS,EAAE,gDAAgD,OAAO,EAAE,KAAK,MAAM;AACrF,YAAM,UAAU,MAAM,gBAAgB,KAAK,MAAM,QAAQ;AACzD,YAAM,QAAQ,gBAAgB;AAAA,IAChC,CAAC;AAED,SAAK,GAAG,SAAS,EAAE,wCAAwC,OAAO,EAAE,KAAK,MAAM;AAC7E,YAAM,UAAU,MAAM,gBAAgB,KAAK,MAAM,QAAQ;AACzD,YAAM,QAAQ,qBAAqB;AAAA,IACrC,CAAC;AAED,SAAK,GAAG,SAAS,EAAE,oCAAoC,OAAO,EAAE,KAAK,MAAM;AACzE,YAAM,UAAU,MAAM,gBAAgB,KAAK,MAAM,QAAQ;AACzD,UAAI,eAAe,SAAS,MAAM,GAAG;AACnC,aAAK,KAAK,EAAE,YAAY,KAAK,EAAE,MAAM,gBAAgB,aAAa,SAAS,OAAO,OAAO,CAAC;AAAA,MAC5F;AACA,YAAM,QAAQ,aAAa;AAAA,IAC7B,CAAC;AAED,SAAK,GAAG,SAAS,EAAE,yCAAyC,OAAO,EAAE,KAAK,MAAM;AAC9E,YAAM,UAAU,MAAM,gBAAgB,KAAK,MAAM,QAAQ;AACzD,UAAI,eAAe,SAAS,WAAW,GAAG;AACxC,aAAK,KAAK,EAAE,YAAY,KAAK,EAAE,MAAM,gBAAgB,aAAa,SAAS,YAAY,OAAO,CAAC;AAAA,MACjG;AACA,YAAM,QAAQ,kBAAkB;AAAA,IAClC,CAAC;AAED,SAAK,GAAG,SAAS,EAAE,oDAAoD,OAAO,EAAE,KAAK,MAAM;AACzF,WAAK,WAAW,IAAO;AACvB,YAAM,UAAU,MAAM,gBAAgB,KAAK,MAAM,QAAQ;AACzD,YAAM,QAAQ,kBAAkB;AAAA,IAClC,CAAC;AAED,SAAK,GAAG,SAAS,EAAE,iDAAiD,OAAO,EAAE,KAAK,MAAM;AACtF,YAAM,UAAU,MAAM,gBAAgB,KAAK,MAAM,QAAQ;AACzD,YAAM,QAAQ,sBAAsB,SAAS,KAAK;AAAA,IACpD,CAAC;AAED,SAAK,GAAG,SAAS,EAAE,4CAA4C,OAAO,EAAE,KAAK,MAAM;AACjF,YAAM,UAAU,MAAM,gBAAgB,KAAK,MAAM,QAAQ;AACzD,YAAM,SAAS,SAAS;AACxB,UAAI,eAAe,MAAM,GAAG;AAC1B,aAAK,KAAK,EAAE,YAAY,KAAK,EAAE,MAAM,gBAAgB,aAAa,OAAO,OAAO,CAAC;AACjF,cAAM,QAAQ,oBAAoB,QAAQ;AAC1C;AAAA,MACF;AACA,YAAM,QAAQ,kBAAkB,MAAM;AAAA,IACxC,CAAC;AAAA,EACH,CAAC;AACH;","names":[]}
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  ContractHarness
3
- } from "./chunk-AO7EZHYV.js";
3
+ } from "./chunk-GNIUPIU2.js";
4
4
  import {
5
5
  openComponentsEditor
6
- } from "./chunk-L73N4NSO.js";
6
+ } from "./chunk-WIZ6W7UT.js";
7
7
  import {
8
8
  selectedContracts
9
- } from "./chunk-LXR3MN6N.js";
10
- import "./chunk-ZMSX6CXR.js";
9
+ } from "./chunk-4JQX6WWL.js";
10
+ import "./chunk-NB3NZRBM.js";
11
11
 
12
12
  // src/testing/component-render.contract.ts
13
13
  import fs from "fs";
@@ -1,6 +1,6 @@
1
1
  import { PlaywrightTestProject, PlaywrightTestConfig } from '@playwright/test';
2
- import { L as LiveTokensTestingConfig } from './vitest-BE6uGF31.js';
3
- export { C as COMPONENTS_PATH_ENV, a as COMPONENT_ENV, R as ResolvedTestingConfig, T as TESTING_CONFIG_FILE, V as VitestConfigOptions, c as createVitestConfig, d as defineTestingConfig, b as devServerCommand, r as resolveTestingConfig } from './vitest-BE6uGF31.js';
2
+ import { L as LiveTokensTestingConfig } from './vitest-C-wNWcoA.js';
3
+ export { C as COMPONENTS_PATH_ENV, a as COMPONENT_ENV, D as DEFAULT_PAGE_VIEWPORTS, P as PAGES_ENV, b as PAGE_VIEWPORTS_ENV, c as PageViewport, R as ResolvedTestingConfig, T as TESTING_CONFIG_FILE, V as VitestConfigOptions, d as createVitestConfig, e as defineTestingConfig, f as devServerCommand, r as resolveTestingConfig } from './vitest-C-wNWcoA.js';
4
4
  import 'vite';
5
5
  import 'vitest/config';
6
6
 
@@ -11,6 +11,12 @@ interface PlaywrightConfigOptions extends LiveTokensTestingConfig {
11
11
  extraProjects?: PlaywrightTestProject[];
12
12
  /** Where projects that declare no `testDir` of their own look. */
13
13
  testDir?: string;
14
+ /** Bounds the whole run. Default fifteen minutes, the same bound
15
+ * `check-component --tests` and `check-page --tests` hold their spawned
16
+ * child to (`LIVE_TOKENS_TESTS_TIMEOUT` in `bin/contractRunner.mjs`), so a
17
+ * hung run stops here too, for a caller that invokes this factory
18
+ * directly rather than through the CLI. */
19
+ globalTimeout?: number;
14
20
  }
15
21
  /**
16
22
  * The contract project, its dev server, and the data isolation the two share.
@@ -54,7 +60,7 @@ declare const PORT_ENV = "LIVE_TOKENS_TEST_PORT";
54
60
  */
55
61
  declare function resolvePort(preferred?: number): number;
56
62
 
57
- type ContractRule = 'contract-listed' | 'contract-render' | 'contract-alias' | 'contract-persist' | 'contract-theme' | 'contract-preview' | 'contract-sketch';
63
+ type ContractRule = 'contract-listed' | 'contract-render' | 'contract-alias' | 'contract-persist' | 'contract-theme' | 'contract-preview' | 'contract-sketch' | 'contract-missing';
58
64
  /**
59
65
  * A failed obligation, carrying the rule id the CLI reports it under. A defect
60
66
  * fixture names the rule it expects, and `check-component --tests` maps a
@@ -7,18 +7,21 @@ import {
7
7
  requiresInteraction,
8
8
  selectedContracts,
9
9
  shippedContracts
10
- } from "./chunk-LXR3MN6N.js";
10
+ } from "./chunk-4JQX6WWL.js";
11
11
  import {
12
12
  createVitestConfig
13
13
  } from "./chunk-FAFOAWYL.js";
14
14
  import {
15
15
  COMPONENTS_PATH_ENV,
16
16
  COMPONENT_ENV,
17
+ DEFAULT_PAGE_VIEWPORTS,
18
+ PAGES_ENV,
19
+ PAGE_VIEWPORTS_ENV,
17
20
  TESTING_CONFIG_FILE,
18
21
  defineTestingConfig,
19
22
  devServerCommand,
20
23
  resolveTestingConfig
21
- } from "./chunk-ZMSX6CXR.js";
24
+ } from "./chunk-NB3NZRBM.js";
22
25
 
23
26
  // src/testing/playwright.ts
24
27
  import path2 from "path";
@@ -109,23 +112,15 @@ function resolvePort(preferred) {
109
112
  // src/testing/playwright.ts
110
113
  var HOST = "127.0.0.1";
111
114
  var CONTRACT_TEST_DIR = path2.dirname(fileURLToPath(import.meta.url));
115
+ var DEFAULT_GLOBAL_TIMEOUT_MS = 15 * 6e4;
112
116
  async function createPlaywrightConfig(options = {}) {
113
117
  const settings = resolveTestingConfig(options, options.root);
114
118
  const { dataDir } = isolateDataDir(settings.dataDir);
115
119
  const port = resolvePort(settings.port);
116
120
  const baseURL = `http://${HOST}:${port}`;
117
121
  process.env[COMPONENTS_PATH_ENV] = settings.componentsPath;
122
+ process.env[PAGE_VIEWPORTS_ENV] = JSON.stringify(settings.pageViewports);
118
123
  if (settings.contractsModule) process.env[CONTRACTS_MODULE_ENV] = settings.contractsModule;
119
- const requested = process.env[COMPONENT_ENV];
120
- if (requested) {
121
- const matches = await selectedContracts();
122
- if (matches.length === 0) {
123
- const all = await allContracts();
124
- throw new Error(
125
- `${COMPONENT_ENV}=${requested} names a component with no contract. Declared: ${all.map((contract) => contract.id).sort().join(", ")}`
126
- );
127
- }
128
- }
129
124
  return defineConfig({
130
125
  testDir: options.testDir ?? CONTRACT_TEST_DIR,
131
126
  // The component suites carry their serial and parallel intent per file:
@@ -138,6 +133,7 @@ async function createPlaywrightConfig(options = {}) {
138
133
  // command line.
139
134
  workers: 1,
140
135
  timeout: 3e4,
136
+ globalTimeout: options.globalTimeout ?? DEFAULT_GLOBAL_TIMEOUT_MS,
141
137
  expect: { timeout: 5e3 },
142
138
  // A shared-server suite times out under CI load in ways it never does
143
139
  // locally, and without a retry budget one wobble aborts a tagged release.
@@ -174,6 +170,15 @@ async function createPlaywrightConfig(options = {}) {
174
170
  // band can cover a control at some scroll positions. Until that is
175
171
  // fixed the suite runs taller. See docs/contract-test-defects.md.
176
172
  use: { ...devices["Desktop Chrome"], viewport: { width: 1280, height: 900 } }
173
+ },
174
+ {
175
+ name: "page",
176
+ testDir: CONTRACT_TEST_DIR,
177
+ testMatch: "**/page-*.contract.{ts,js}",
178
+ // The suite sizes the window itself, once per viewport in
179
+ // `pageViewports`, so the sizes stay one list rather than a project
180
+ // per viewport whose name the reporter would have to decode.
181
+ use: { ...devices["Desktop Chrome"] }
177
182
  }
178
183
  ],
179
184
  webServer: {
@@ -202,6 +207,9 @@ export {
202
207
  CONTRACTS_MODULE_ENV,
203
208
  ContractViolation,
204
209
  DATA_DIR_ENV,
210
+ DEFAULT_PAGE_VIEWPORTS,
211
+ PAGES_ENV,
212
+ PAGE_VIEWPORTS_ENV,
205
213
  PORT_ENV,
206
214
  TESTING_CONFIG_FILE,
207
215
  TEST_DATA_DIR_ENV,
@@ -1 +1 @@
1
- {"version":3,"sources":["../testing/playwright.ts","../testing/isolation.ts","../testing/port.ts"],"sourcesContent":["import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n defineConfig,\n devices,\n type PlaywrightTestConfig,\n type PlaywrightTestProject,\n} from '@playwright/test';\nimport {\n COMPONENT_ENV,\n COMPONENTS_PATH_ENV,\n devServerCommand,\n resolveTestingConfig,\n type LiveTokensTestingConfig,\n} from './config';\nimport { allContracts, CONTRACTS_MODULE_ENV, selectedContracts } from './contracts';\nimport { DATA_DIR_ENV, TEST_DATA_DIR_ENV, isolateDataDir } from './isolation';\nimport { resolvePort } from './port';\n\nconst HOST = '127.0.0.1';\n\n/** The shipped suites, wherever the package is installed. */\nconst CONTRACT_TEST_DIR = path.dirname(fileURLToPath(import.meta.url));\n\nexport interface PlaywrightConfigOptions extends LiveTokensTestingConfig {\n /** Project root the settings resolve against. Default `process.cwd()`. */\n root?: string;\n /** Projects that run beside the contract project. */\n extraProjects?: PlaywrightTestProject[];\n /** Where projects that declare no `testDir` of their own look. */\n testDir?: string;\n}\n\n/**\n * The contract project, its dev server, and the data isolation the two share.\n *\n * Isolation runs here rather than in a global setup because the config is the\n * first thing every process in the run evaluates: the environment it leaves\n * behind reaches the workers and the dev server, and nothing in the run can\n * observe the state before it.\n */\nexport async function createPlaywrightConfig(\n options: PlaywrightConfigOptions = {},\n): Promise<PlaywrightTestConfig> {\n const settings = resolveTestingConfig(options, options.root);\n const { dataDir } = isolateDataDir(settings.dataDir);\n const port = resolvePort(settings.port);\n const baseURL = `http://${HOST}:${port}`;\n\n process.env[COMPONENTS_PATH_ENV] = settings.componentsPath;\n if (settings.contractsModule) process.env[CONTRACTS_MODULE_ENV] = settings.contractsModule;\n\n const requested = process.env[COMPONENT_ENV];\n if (requested) {\n const matches = await selectedContracts();\n if (matches.length === 0) {\n const all = await allContracts();\n throw new Error(\n `${COMPONENT_ENV}=${requested} names a component with no contract. `\n + `Declared: ${all.map((contract) => contract.id).sort().join(', ')}`,\n );\n }\n }\n\n return defineConfig({\n testDir: options.testDir ?? CONTRACT_TEST_DIR,\n // The component suites carry their serial and parallel intent per file:\n // `component-editor.contract.ts` runs each component's save and reset\n // cycles in order against one data tree. Turning this on would interleave\n // them.\n fullyParallel: false,\n // One worker is the safe default: the specs share a dev server and a data\n // directory. A run that can afford more passes a worker count on the\n // command line.\n workers: 1,\n timeout: 30_000,\n expect: { timeout: 5_000 },\n // A shared-server suite times out under CI load in ways it never does\n // locally, and without a retry budget one wobble aborts a tagged release.\n retries: process.env.CI ? 2 : 0,\n // Absolute and rooted at the consumer project, not the config file's own\n // directory: `check-component --tests`'s generated config lives in a\n // temporary directory that gets removed on completion, and a relative\n // `outputDir` would put every trace and screenshot in there too.\n outputDir: path.join(settings.root, 'test-results/playwright'),\n reporter: process.env.CI ? [['line'], ['html', { open: 'never' }]] : 'list',\n use: {\n baseURL,\n // Unset, both of these are unbounded: a locator that never resolves waits\n // out the whole test timeout and reports nothing about where it stopped.\n actionTimeout: 10_000,\n navigationTimeout: 30_000,\n trace: 'on-first-retry',\n screenshot: 'only-on-failure',\n // `retain-on-failure` still records every passing test and throws the\n // file away, which costs ~28% CPU. Match the trace policy instead.\n video: 'on-first-retry',\n },\n projects: [\n ...(options.extraProjects ?? []),\n {\n name: 'contract',\n testDir: CONTRACT_TEST_DIR,\n // `.ts` in this repo, `.js` once tsup compiles the shipped build into\n // `src/testing-js`; matching both here needs no build-time swap.\n testMatch: '**/component-*.contract.{ts,js}',\n // `.tabs-preview` now caps its sticky band at 50vh, which is enough for\n // image, panel, card and sidenavigation to pass alone at 1280x720. A\n // full parallel run still fails panel's gradient radio there, so the\n // band can cover a control at some scroll positions. Until that is\n // fixed the suite runs taller. See docs/contract-test-defects.md.\n use: { ...devices['Desktop Chrome'], viewport: { width: 1280, height: 900 } },\n },\n ],\n webServer: {\n command: devServerCommand(settings.devCommand, HOST, port),\n // Otherwise defaults to the config file's own directory. A generated\n // config that lives outside the consumer root (`check-component\n // --tests`'s temporary config) would run `npm run dev` from wherever\n // that happens to be instead.\n cwd: settings.root,\n url: baseURL,\n // A server this run did not start carries none of these variables, so it\n // would write the project's own data tree.\n reuseExistingServer: false,\n timeout: 120_000,\n env: {\n ...process.env,\n [TEST_DATA_DIR_ENV]: dataDir,\n [DATA_DIR_ENV]: dataDir,\n },\n },\n });\n}\n","import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\n/**\n * The plugin reads this name ahead of its own options and\n * `live-tokens.config.json`, so setting it redirects every server-side write —\n * the data tree, `tokens.generated.css`, and `fonts.css` — into the copy.\n */\nexport const TEST_DATA_DIR_ENV = 'LIVE_TOKENS_TEST_DATA_DIR';\n\n/** What the suites and the harness read component configs from in the runner's\n * own process. It names the same directory and is a separate variable because\n * it answers a different question: where a reader looks, not where the server\n * is confined. */\nexport const DATA_DIR_ENV = 'LIVE_TOKENS_DATA_DIR';\n\n/**\n * A developer's current session rather than the document under test. Each one\n * is gitignored or dev-written, so leaving it in the copy would make the run\n * pass or fail on whichever theme the maintainer has open.\n *\n * _working.json an unsaved buffer, which reads as a dirty document and\n * can raise a destructive confirmation dialog\n * _active.json the theme the editor has open. One carrying a sketchstyle\n * boots the suite with the sketch layer on, which paints\n * every fill onto a pseudo-element and leaves the real\n * background transparent\n * _production.json the theme baked into tokens.generated.css\n *\n * A missing pointer resolves to \"default\" at runtime, so removing them is the\n * whole reset.\n */\nconst SESSION_FILES = new Set(['_working.json', '_active.json', '_production.json']);\n\nexport interface IsolatedData {\n dataDir: string;\n /** True when this process made the copy and owns removing it. */\n created: boolean;\n}\n\nlet removed = false;\n\nfunction registerCleanup(dataDir: string): void {\n const remove = () => {\n if (removed) return;\n removed = true;\n fs.rmSync(dataDir, { recursive: true, force: true });\n };\n process.once('exit', remove);\n // Playwright owns the exit path on a signal: it stops the workers and kills\n // the dev server's process group, then exits, which reaches `exit` above.\n // These two only make sure the copy is gone before that teardown starts.\n process.once('SIGINT', remove);\n process.once('SIGTERM', remove);\n}\n\n/**\n * Copy `sourceDataDir` into a unique temporary directory and point both the\n * server and the runner at it. Idempotent across processes: Playwright loads\n * the config again in every worker, and each inherits the parent's environment,\n * so only the first call copies anything.\n */\nexport function isolateDataDir(sourceDataDir: string): IsolatedData {\n const existing = process.env[TEST_DATA_DIR_ENV];\n if (existing) {\n const dataDir = path.resolve(existing);\n if (!fs.existsSync(dataDir)) {\n throw new Error(`${TEST_DATA_DIR_ENV} names ${dataDir}, which does not exist`);\n }\n process.env[DATA_DIR_ENV] = dataDir;\n return { dataDir, created: false };\n }\n\n const source = path.resolve(sourceDataDir);\n if (!fs.existsSync(source)) {\n throw new Error(\n `No data directory at ${source}. Set \\`dataDir\\` in live-tokens.testing.ts `\n + 'when the project keeps its live-tokens data somewhere else.',\n );\n }\n\n // realpath: macOS resolves os.tmpdir() through a symlink, and the plugin\n // compares resolved paths when it decides whether a write is in scope.\n const dataDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'live-tokens-contract-')));\n fs.cpSync(source, dataDir, { recursive: true });\n for (const entry of fs.readdirSync(dataDir, { recursive: true }) as string[]) {\n if (SESSION_FILES.has(path.basename(entry))) {\n fs.rmSync(path.join(dataDir, entry), { force: true });\n }\n }\n\n process.env[TEST_DATA_DIR_ENV] = dataDir;\n process.env[DATA_DIR_ENV] = dataDir;\n registerCleanup(dataDir);\n return { dataDir, created: true };\n}\n","import { execFileSync } from 'node:child_process';\n\nexport const PORT_ENV = 'LIVE_TOKENS_TEST_PORT';\n\nconst PREFERRED_PORT = 4173;\n\n/** Bind the preferred port, fall back to whatever the OS hands out, print the\n * answer. A Playwright config is evaluated synchronously, so the check runs in\n * a child rather than on a listener callback. */\nconst PROBE = `\nconst net = require('net');\nconst listen = (port) => new Promise((resolve, reject) => {\n const server = net.createServer();\n server.once('error', reject);\n server.listen(port, '127.0.0.1', () => {\n const { port: bound } = server.address();\n server.close(() => resolve(bound));\n });\n});\n// process.stdout.write, not console.log: the parent's FORCE_COLOR makes\n// console.log wrap a number in ANSI escapes.\nlisten(Number(process.argv[1])).catch(() => listen(0)).then((port) => process.stdout.write(String(port)));\n`;\n\n/**\n * The port the dev server and `baseURL` share. Cached in the environment so\n * every Playwright worker, which loads the config again in its own process,\n * reads the port the main process settled on.\n */\nexport function resolvePort(preferred?: number): number {\n const cached = process.env[PORT_ENV];\n if (cached) return Number(cached);\n\n const port = preferred\n ?? Number(execFileSync(process.execPath, ['-e', PROBE, String(PREFERRED_PORT)], {\n encoding: 'utf-8',\n }).trim());\n if (!Number.isInteger(port) || port <= 0) {\n throw new Error(`Could not settle on a dev-server port (read ${port})`);\n }\n process.env[PORT_ENV] = String(port);\n return port;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAOA,WAAU;AACjB,SAAS,qBAAqB;AAC9B;AAAA,EACE;AAAA,EACA;AAAA,OAGK;;;ACPP,OAAO,QAAQ;AACf,OAAO,QAAQ;AACf,OAAO,UAAU;AAOV,IAAM,oBAAoB;AAM1B,IAAM,eAAe;AAkB5B,IAAM,gBAAgB,oBAAI,IAAI,CAAC,iBAAiB,gBAAgB,kBAAkB,CAAC;AAQnF,IAAI,UAAU;AAEd,SAAS,gBAAgB,SAAuB;AAC9C,QAAM,SAAS,MAAM;AACnB,QAAI,QAAS;AACb,cAAU;AACV,OAAG,OAAO,SAAS,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACrD;AACA,UAAQ,KAAK,QAAQ,MAAM;AAI3B,UAAQ,KAAK,UAAU,MAAM;AAC7B,UAAQ,KAAK,WAAW,MAAM;AAChC;AAQO,SAAS,eAAe,eAAqC;AAClE,QAAM,WAAW,QAAQ,IAAI,iBAAiB;AAC9C,MAAI,UAAU;AACZ,UAAMC,WAAU,KAAK,QAAQ,QAAQ;AACrC,QAAI,CAAC,GAAG,WAAWA,QAAO,GAAG;AAC3B,YAAM,IAAI,MAAM,GAAG,iBAAiB,UAAUA,QAAO,wBAAwB;AAAA,IAC/E;AACA,YAAQ,IAAI,YAAY,IAAIA;AAC5B,WAAO,EAAE,SAAAA,UAAS,SAAS,MAAM;AAAA,EACnC;AAEA,QAAM,SAAS,KAAK,QAAQ,aAAa;AACzC,MAAI,CAAC,GAAG,WAAW,MAAM,GAAG;AAC1B,UAAM,IAAI;AAAA,MACR,wBAAwB,MAAM;AAAA,IAEhC;AAAA,EACF;AAIA,QAAM,UAAU,GAAG,aAAa,GAAG,YAAY,KAAK,KAAK,GAAG,OAAO,GAAG,uBAAuB,CAAC,CAAC;AAC/F,KAAG,OAAO,QAAQ,SAAS,EAAE,WAAW,KAAK,CAAC;AAC9C,aAAW,SAAS,GAAG,YAAY,SAAS,EAAE,WAAW,KAAK,CAAC,GAAe;AAC5E,QAAI,cAAc,IAAI,KAAK,SAAS,KAAK,CAAC,GAAG;AAC3C,SAAG,OAAO,KAAK,KAAK,SAAS,KAAK,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,IACtD;AAAA,EACF;AAEA,UAAQ,IAAI,iBAAiB,IAAI;AACjC,UAAQ,IAAI,YAAY,IAAI;AAC5B,kBAAgB,OAAO;AACvB,SAAO,EAAE,SAAS,SAAS,KAAK;AAClC;;;AChGA,SAAS,oBAAoB;AAEtB,IAAM,WAAW;AAExB,IAAM,iBAAiB;AAKvB,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBP,SAAS,YAAY,WAA4B;AACtD,QAAM,SAAS,QAAQ,IAAI,QAAQ;AACnC,MAAI,OAAQ,QAAO,OAAO,MAAM;AAEhC,QAAM,OAAO,aACR,OAAO,aAAa,QAAQ,UAAU,CAAC,MAAM,OAAO,OAAO,cAAc,CAAC,GAAG;AAAA,IAC9E,UAAU;AAAA,EACZ,CAAC,EAAE,KAAK,CAAC;AACX,MAAI,CAAC,OAAO,UAAU,IAAI,KAAK,QAAQ,GAAG;AACxC,UAAM,IAAI,MAAM,+CAA+C,IAAI,GAAG;AAAA,EACxE;AACA,UAAQ,IAAI,QAAQ,IAAI,OAAO,IAAI;AACnC,SAAO;AACT;;;AFvBA,IAAM,OAAO;AAGb,IAAM,oBAAoBC,MAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAmBrE,eAAsB,uBACpB,UAAmC,CAAC,GACL;AAC/B,QAAM,WAAW,qBAAqB,SAAS,QAAQ,IAAI;AAC3D,QAAM,EAAE,QAAQ,IAAI,eAAe,SAAS,OAAO;AACnD,QAAM,OAAO,YAAY,SAAS,IAAI;AACtC,QAAM,UAAU,UAAU,IAAI,IAAI,IAAI;AAEtC,UAAQ,IAAI,mBAAmB,IAAI,SAAS;AAC5C,MAAI,SAAS,gBAAiB,SAAQ,IAAI,oBAAoB,IAAI,SAAS;AAE3E,QAAM,YAAY,QAAQ,IAAI,aAAa;AAC3C,MAAI,WAAW;AACb,UAAM,UAAU,MAAM,kBAAkB;AACxC,QAAI,QAAQ,WAAW,GAAG;AACxB,YAAM,MAAM,MAAM,aAAa;AAC/B,YAAM,IAAI;AAAA,QACR,GAAG,aAAa,IAAI,SAAS,kDACd,IAAI,IAAI,CAAC,aAAa,SAAS,EAAE,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;AAAA,MACrE;AAAA,IACF;AAAA,EACF;AAEA,SAAO,aAAa;AAAA,IAClB,SAAS,QAAQ,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,IAK5B,eAAe;AAAA;AAAA;AAAA;AAAA,IAIf,SAAS;AAAA,IACT,SAAS;AAAA,IACT,QAAQ,EAAE,SAAS,IAAM;AAAA;AAAA;AAAA,IAGzB,SAAS,QAAQ,IAAI,KAAK,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,IAK9B,WAAWA,MAAK,KAAK,SAAS,MAAM,yBAAyB;AAAA,IAC7D,UAAU,QAAQ,IAAI,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,QAAQ,EAAE,MAAM,QAAQ,CAAC,CAAC,IAAI;AAAA,IACrE,KAAK;AAAA,MACH;AAAA;AAAA;AAAA,MAGA,eAAe;AAAA,MACf,mBAAmB;AAAA,MACnB,OAAO;AAAA,MACP,YAAY;AAAA;AAAA;AAAA,MAGZ,OAAO;AAAA,IACT;AAAA,IACA,UAAU;AAAA,MACR,GAAI,QAAQ,iBAAiB,CAAC;AAAA,MAC9B;AAAA,QACE,MAAM;AAAA,QACN,SAAS;AAAA;AAAA;AAAA,QAGT,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMX,KAAK,EAAE,GAAG,QAAQ,gBAAgB,GAAG,UAAU,EAAE,OAAO,MAAM,QAAQ,IAAI,EAAE;AAAA,MAC9E;AAAA,IACF;AAAA,IACA,WAAW;AAAA,MACT,SAAS,iBAAiB,SAAS,YAAY,MAAM,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,MAKzD,KAAK,SAAS;AAAA,MACd,KAAK;AAAA;AAAA;AAAA,MAGL,qBAAqB;AAAA,MACrB,SAAS;AAAA,MACT,KAAK;AAAA,QACH,GAAG,QAAQ;AAAA,QACX,CAAC,iBAAiB,GAAG;AAAA,QACrB,CAAC,YAAY,GAAG;AAAA,MAClB;AAAA,IACF;AAAA,EACF,CAAC;AACH;","names":["path","dataDir","path"]}
1
+ {"version":3,"sources":["../testing/playwright.ts","../testing/isolation.ts","../testing/port.ts"],"sourcesContent":["import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n defineConfig,\n devices,\n type PlaywrightTestConfig,\n type PlaywrightTestProject,\n} from '@playwright/test';\nimport {\n COMPONENT_ENV,\n COMPONENTS_PATH_ENV,\n PAGE_VIEWPORTS_ENV,\n devServerCommand,\n resolveTestingConfig,\n type LiveTokensTestingConfig,\n} from './config';\nimport { CONTRACTS_MODULE_ENV } from './contracts';\nimport { DATA_DIR_ENV, TEST_DATA_DIR_ENV, isolateDataDir } from './isolation';\nimport { resolvePort } from './port';\n\nconst HOST = '127.0.0.1';\n\n/** The shipped suites, wherever the package is installed. */\nconst CONTRACT_TEST_DIR = path.dirname(fileURLToPath(import.meta.url));\n\nexport interface PlaywrightConfigOptions extends LiveTokensTestingConfig {\n /** Project root the settings resolve against. Default `process.cwd()`. */\n root?: string;\n /** Projects that run beside the contract project. */\n extraProjects?: PlaywrightTestProject[];\n /** Where projects that declare no `testDir` of their own look. */\n testDir?: string;\n /** Bounds the whole run. Default fifteen minutes, the same bound\n * `check-component --tests` and `check-page --tests` hold their spawned\n * child to (`LIVE_TOKENS_TESTS_TIMEOUT` in `bin/contractRunner.mjs`), so a\n * hung run stops here too, for a caller that invokes this factory\n * directly rather than through the CLI. */\n globalTimeout?: number;\n}\n\n/** Mirrors `bin/contractRunner.mjs`'s own `DEFAULT_TESTS_TIMEOUT_MS`,\n * duplicated because that file is plain JS and cannot import this module\n * before `build:testing` compiles it (see `bin/engineLoadsLazily.test.ts`). */\nconst DEFAULT_GLOBAL_TIMEOUT_MS = 15 * 60_000;\n\n/**\n * The contract project, its dev server, and the data isolation the two share.\n *\n * Isolation runs here rather than in a global setup because the config is the\n * first thing every process in the run evaluates: the environment it leaves\n * behind reaches the workers and the dev server, and nothing in the run can\n * observe the state before it.\n */\nexport async function createPlaywrightConfig(\n options: PlaywrightConfigOptions = {},\n): Promise<PlaywrightTestConfig> {\n const settings = resolveTestingConfig(options, options.root);\n const { dataDir } = isolateDataDir(settings.dataDir);\n const port = resolvePort(settings.port);\n const baseURL = `http://${HOST}:${port}`;\n\n process.env[COMPONENTS_PATH_ENV] = settings.componentsPath;\n process.env[PAGE_VIEWPORTS_ENV] = JSON.stringify(settings.pageViewports);\n if (settings.contractsModule) process.env[CONTRACTS_MODULE_ENV] = settings.contractsModule;\n\n return defineConfig({\n testDir: options.testDir ?? CONTRACT_TEST_DIR,\n // The component suites carry their serial and parallel intent per file:\n // `component-editor.contract.ts` runs each component's save and reset\n // cycles in order against one data tree. Turning this on would interleave\n // them.\n fullyParallel: false,\n // One worker is the safe default: the specs share a dev server and a data\n // directory. A run that can afford more passes a worker count on the\n // command line.\n workers: 1,\n timeout: 30_000,\n globalTimeout: options.globalTimeout ?? DEFAULT_GLOBAL_TIMEOUT_MS,\n expect: { timeout: 5_000 },\n // A shared-server suite times out under CI load in ways it never does\n // locally, and without a retry budget one wobble aborts a tagged release.\n retries: process.env.CI ? 2 : 0,\n // Absolute and rooted at the consumer project, not the config file's own\n // directory: `check-component --tests`'s generated config lives in a\n // temporary directory that gets removed on completion, and a relative\n // `outputDir` would put every trace and screenshot in there too.\n outputDir: path.join(settings.root, 'test-results/playwright'),\n reporter: process.env.CI ? [['line'], ['html', { open: 'never' }]] : 'list',\n use: {\n baseURL,\n // Unset, both of these are unbounded: a locator that never resolves waits\n // out the whole test timeout and reports nothing about where it stopped.\n actionTimeout: 10_000,\n navigationTimeout: 30_000,\n trace: 'on-first-retry',\n screenshot: 'only-on-failure',\n // `retain-on-failure` still records every passing test and throws the\n // file away, which costs ~28% CPU. Match the trace policy instead.\n video: 'on-first-retry',\n },\n projects: [\n ...(options.extraProjects ?? []),\n {\n name: 'contract',\n testDir: CONTRACT_TEST_DIR,\n // `.ts` in this repo, `.js` once tsup compiles the shipped build into\n // `src/testing-js`; matching both here needs no build-time swap.\n testMatch: '**/component-*.contract.{ts,js}',\n // `.tabs-preview` now caps its sticky band at 50vh, which is enough for\n // image, panel, card and sidenavigation to pass alone at 1280x720. A\n // full parallel run still fails panel's gradient radio there, so the\n // band can cover a control at some scroll positions. Until that is\n // fixed the suite runs taller. See docs/contract-test-defects.md.\n use: { ...devices['Desktop Chrome'], viewport: { width: 1280, height: 900 } },\n },\n {\n name: 'page',\n testDir: CONTRACT_TEST_DIR,\n testMatch: '**/page-*.contract.{ts,js}',\n // The suite sizes the window itself, once per viewport in\n // `pageViewports`, so the sizes stay one list rather than a project\n // per viewport whose name the reporter would have to decode.\n use: { ...devices['Desktop Chrome'] },\n },\n ],\n webServer: {\n command: devServerCommand(settings.devCommand, HOST, port),\n // Otherwise defaults to the config file's own directory. A generated\n // config that lives outside the consumer root (`check-component\n // --tests`'s temporary config) would run `npm run dev` from wherever\n // that happens to be instead.\n cwd: settings.root,\n url: baseURL,\n // A server this run did not start carries none of these variables, so it\n // would write the project's own data tree.\n reuseExistingServer: false,\n timeout: 120_000,\n env: {\n ...process.env,\n [TEST_DATA_DIR_ENV]: dataDir,\n [DATA_DIR_ENV]: dataDir,\n },\n },\n });\n}\n","import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\n/**\n * The plugin reads this name ahead of its own options and\n * `live-tokens.config.json`, so setting it redirects every server-side write —\n * the data tree, `tokens.generated.css`, and `fonts.css` — into the copy.\n */\nexport const TEST_DATA_DIR_ENV = 'LIVE_TOKENS_TEST_DATA_DIR';\n\n/** What the suites and the harness read component configs from in the runner's\n * own process. It names the same directory and is a separate variable because\n * it answers a different question: where a reader looks, not where the server\n * is confined. */\nexport const DATA_DIR_ENV = 'LIVE_TOKENS_DATA_DIR';\n\n/**\n * A developer's current session rather than the document under test. Each one\n * is gitignored or dev-written, so leaving it in the copy would make the run\n * pass or fail on whichever theme the maintainer has open.\n *\n * _working.json an unsaved buffer, which reads as a dirty document and\n * can raise a destructive confirmation dialog\n * _active.json the theme the editor has open. One carrying a sketchstyle\n * boots the suite with the sketch layer on, which paints\n * every fill onto a pseudo-element and leaves the real\n * background transparent\n * _production.json the theme baked into tokens.generated.css\n *\n * A missing pointer resolves to \"default\" at runtime, so removing them is the\n * whole reset.\n */\nconst SESSION_FILES = new Set(['_working.json', '_active.json', '_production.json']);\n\nexport interface IsolatedData {\n dataDir: string;\n /** True when this process made the copy and owns removing it. */\n created: boolean;\n}\n\nlet removed = false;\n\nfunction registerCleanup(dataDir: string): void {\n const remove = () => {\n if (removed) return;\n removed = true;\n fs.rmSync(dataDir, { recursive: true, force: true });\n };\n process.once('exit', remove);\n // Playwright owns the exit path on a signal: it stops the workers and kills\n // the dev server's process group, then exits, which reaches `exit` above.\n // These two only make sure the copy is gone before that teardown starts.\n process.once('SIGINT', remove);\n process.once('SIGTERM', remove);\n}\n\n/**\n * Copy `sourceDataDir` into a unique temporary directory and point both the\n * server and the runner at it. Idempotent across processes: Playwright loads\n * the config again in every worker, and each inherits the parent's environment,\n * so only the first call copies anything.\n */\nexport function isolateDataDir(sourceDataDir: string): IsolatedData {\n const existing = process.env[TEST_DATA_DIR_ENV];\n if (existing) {\n const dataDir = path.resolve(existing);\n if (!fs.existsSync(dataDir)) {\n throw new Error(`${TEST_DATA_DIR_ENV} names ${dataDir}, which does not exist`);\n }\n process.env[DATA_DIR_ENV] = dataDir;\n return { dataDir, created: false };\n }\n\n const source = path.resolve(sourceDataDir);\n if (!fs.existsSync(source)) {\n throw new Error(\n `No data directory at ${source}. Set \\`dataDir\\` in live-tokens.testing.ts `\n + 'when the project keeps its live-tokens data somewhere else.',\n );\n }\n\n // realpath: macOS resolves os.tmpdir() through a symlink, and the plugin\n // compares resolved paths when it decides whether a write is in scope.\n const dataDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'live-tokens-contract-')));\n fs.cpSync(source, dataDir, { recursive: true });\n for (const entry of fs.readdirSync(dataDir, { recursive: true }) as string[]) {\n if (SESSION_FILES.has(path.basename(entry))) {\n fs.rmSync(path.join(dataDir, entry), { force: true });\n }\n }\n\n process.env[TEST_DATA_DIR_ENV] = dataDir;\n process.env[DATA_DIR_ENV] = dataDir;\n registerCleanup(dataDir);\n return { dataDir, created: true };\n}\n","import { execFileSync } from 'node:child_process';\n\nexport const PORT_ENV = 'LIVE_TOKENS_TEST_PORT';\n\nconst PREFERRED_PORT = 4173;\n\n/** Bind the preferred port, fall back to whatever the OS hands out, print the\n * answer. A Playwright config is evaluated synchronously, so the check runs in\n * a child rather than on a listener callback. */\nconst PROBE = `\nconst net = require('net');\nconst listen = (port) => new Promise((resolve, reject) => {\n const server = net.createServer();\n server.once('error', reject);\n server.listen(port, '127.0.0.1', () => {\n const { port: bound } = server.address();\n server.close(() => resolve(bound));\n });\n});\n// process.stdout.write, not console.log: the parent's FORCE_COLOR makes\n// console.log wrap a number in ANSI escapes.\nlisten(Number(process.argv[1])).catch(() => listen(0)).then((port) => process.stdout.write(String(port)));\n`;\n\n/**\n * The port the dev server and `baseURL` share. Cached in the environment so\n * every Playwright worker, which loads the config again in its own process,\n * reads the port the main process settled on.\n */\nexport function resolvePort(preferred?: number): number {\n const cached = process.env[PORT_ENV];\n if (cached) return Number(cached);\n\n const port = preferred\n ?? Number(execFileSync(process.execPath, ['-e', PROBE, String(PREFERRED_PORT)], {\n encoding: 'utf-8',\n }).trim());\n if (!Number.isInteger(port) || port <= 0) {\n throw new Error(`Could not settle on a dev-server port (read ${port})`);\n }\n process.env[PORT_ENV] = String(port);\n return port;\n}\n"],"mappings":"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","names":["path","dataDir","path"]}