@motion-proto/live-tokens 0.77.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 (37) hide show
  1. package/.claude/skills/live-tokens-check-compliance/SKILL.md +2 -0
  2. package/.claude/skills/live-tokens-create-page/SKILL.md +5 -4
  3. package/.claude/skills/live-tokens-fix-findings/SKILL.md +7 -0
  4. package/CHANGELOG.md +53 -0
  5. package/bin/check-page.mjs +131 -9
  6. package/bin/cli.mjs +25 -3
  7. package/bin/contractRunner.mjs +343 -29
  8. package/bin/lib/pageRoutes.mjs +309 -0
  9. package/package.json +5 -3
  10. package/src/editor/overlay/ColumnsOverlay.svelte +1 -1
  11. package/src/editor/overlay/LiveEditorOverlay.svelte +1 -0
  12. package/src/editor/overlay/LiveTokensRouter.svelte +1 -0
  13. package/src/editor/skill-atlas/skillSources.generated.ts +3 -3
  14. package/src/editor/skill-atlas/trees/check-compliance.ts +18 -18
  15. package/src/editor/skill-atlas/trees/create-page.ts +26 -26
  16. package/src/editor/skill-atlas/trees/fix-findings.ts +46 -11
  17. package/src/testing-js/{chunk-7TI7Z6Y6.js → chunk-GNIUPIU2.js} +2 -2
  18. package/src/testing-js/{chunk-ZMSX6CXR.js → chunk-NB3NZRBM.js} +13 -2
  19. package/src/testing-js/chunk-NB3NZRBM.js.map +1 -0
  20. package/src/testing-js/{chunk-L73N4NSO.js → chunk-WIZ6W7UT.js} +2 -2
  21. package/src/testing-js/component-alias.contract.js +2 -2
  22. package/src/testing-js/component-editor.contract.js +3 -3
  23. package/src/testing-js/component-render.contract.js +3 -3
  24. package/src/testing-js/index.d.ts +8 -2
  25. package/src/testing-js/index.js +19 -1
  26. package/src/testing-js/index.js.map +1 -1
  27. package/src/testing-js/page-compliance.contract.js +829 -0
  28. package/src/testing-js/page-compliance.contract.js.map +1 -0
  29. package/src/testing-js/{vitest-BE6uGF31.d.ts → vitest-C-wNWcoA.d.ts} +21 -1
  30. package/src/testing-js/vitest.d.ts +1 -1
  31. package/src/testing-js/vitest.js +1 -1
  32. package/template/README.md +16 -9
  33. package/template/package.json +1 -1
  34. package/template/src/pages/Home.svelte +13 -0
  35. package/src/testing-js/chunk-ZMSX6CXR.js.map +0 -1
  36. /package/src/testing-js/{chunk-7TI7Z6Y6.js.map → chunk-GNIUPIU2.js.map} +0 -0
  37. /package/src/testing-js/{chunk-L73N4NSO.js.map → chunk-WIZ6W7UT.js.map} +0 -0
@@ -2,7 +2,7 @@ import type { SkillTree } from '../types';
2
2
 
3
3
  export const fixFindings: SkillTree = {
4
4
  "id": "live-tokens-fix-findings",
5
- "digest": "sha256:2c39f9f00b03b5ad",
5
+ "digest": "sha256:e400a3de85a17e3c",
6
6
  "title": "fix-findings",
7
7
  "tagline": "Repair Deviations from the Design System",
8
8
  "nodes": [
@@ -160,7 +160,7 @@ export const fixFindings: SkillTree = {
160
160
  "kind": "chipset",
161
161
  "title": "The remaining rules",
162
162
  "desc": "Every other rule has its fix in the table.",
163
- "lines": [71, 90],
163
+ "lines": [71, 97],
164
164
  "anchor": "## The remaining rules",
165
165
  "anchorEnd": "| `fix: runtime` | Wire the component as the recipe in **liv",
166
166
  "chips": [
@@ -205,48 +205,83 @@ export const fixFindings: SkillTree = {
205
205
  "anchor": "| `danger-without-dialog` | Open a `Dialog` from the danger "
206
206
  },
207
207
  {
208
- "label": "hardcoded-columns",
208
+ "label": "native-control",
209
209
  "lines": [83, 83],
210
+ "anchor": "| `native-control` | Replace the native element with the shi"
211
+ },
212
+ {
213
+ "label": "property-override",
214
+ "lines": [84, 84],
215
+ "anchor": "| `property-override` | Delete the declaration from the page"
216
+ },
217
+ {
218
+ "label": "page-component-paint",
219
+ "lines": [85, 85],
220
+ "anchor": "| `page-component-paint` | The finding names the page file a"
221
+ },
222
+ {
223
+ "label": "page-text-style",
224
+ "lines": [86, 86],
225
+ "anchor": "| `page-text-style` | The finding names the page file and th"
226
+ },
227
+ {
228
+ "label": "page-contrast",
229
+ "lines": [87, 87],
230
+ "anchor": "| `page-contrast` | The finding names the page file and the "
231
+ },
232
+ {
233
+ "label": "page-grid",
234
+ "lines": [88, 88],
235
+ "anchor": "| `page-grid` | The finding names the page file and the line"
236
+ },
237
+ {
238
+ "label": "page-overflow",
239
+ "lines": [89, 89],
240
+ "anchor": "| `page-overflow` | The finding names the page file and the "
241
+ },
242
+ {
243
+ "label": "hardcoded-columns",
244
+ "lines": [90, 90],
210
245
  "anchor": "| `hardcoded-columns` | `repeat(var(--columns-count), 1fr)` "
211
246
  },
212
247
  {
213
248
  "label": "site-css-in-main",
214
- "lines": [84, 84],
249
+ "lines": [91, 91],
215
250
  "anchor": "| `site-css-in-main` | Delete the import from `main.ts`. Add"
216
251
  },
217
252
  {
218
253
  "label": "missing-source",
219
- "lines": [85, 85],
254
+ "lines": [92, 92],
220
255
  "anchor": "| `missing-source` | Add `source: 'src/...'` to the route en"
221
256
  },
222
257
  {
223
258
  "label": "reserved-route",
224
- "lines": [86, 86],
259
+ "lines": [93, 93],
225
260
  "anchor": "| `reserved-route` | Move the route out of `/live-tokens/*`."
226
261
  },
227
262
  {
228
263
  "label": "deep-import",
229
- "lines": [87, 87],
264
+ "lines": [94, 94],
230
265
  "anchor": "| `deep-import` | Import from `@motion-proto/live-tokens`, `"
231
266
  },
232
267
  {
233
268
  "label": "Component name rules",
234
- "lines": [88, 88],
269
+ "lines": [95, 95],
235
270
  "anchor": "| `fix: property-name` | Rename the token to the name a ship"
236
271
  },
237
272
  {
238
273
  "label": "Component token rules",
239
- "lines": [89, 89],
274
+ "lines": [96, 96],
240
275
  "anchor": "| `fix: property-token` | Make the `:global(:root)` default "
241
276
  },
242
277
  {
243
278
  "label": "Component editor rules",
244
- "lines": [92, 92],
279
+ "lines": [99, 99],
245
280
  "anchor": "| `fix: editor` | The editor names a token the runtime never"
246
281
  },
247
282
  {
248
283
  "label": "Component wiring rules",
249
- "lines": [90, 90],
284
+ "lines": [97, 97],
250
285
  "anchor": "| `fix: runtime` | Wire the component as the recipe in **liv"
251
286
  }
252
287
  ]
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  openComponentsEditor
3
- } from "./chunk-L73N4NSO.js";
3
+ } from "./chunk-WIZ6W7UT.js";
4
4
  import {
5
5
  ContractViolation,
6
6
  isInapplicable,
@@ -773,4 +773,4 @@ function probeValueFor(css) {
773
773
  export {
774
774
  ContractHarness
775
775
  };
776
- //# sourceMappingURL=chunk-7TI7Z6Y6.js.map
776
+ //# sourceMappingURL=chunk-GNIUPIU2.js.map
@@ -9,6 +9,12 @@ var DEFAULT_COMPONENTS_PATH = "/live-tokens/components";
9
9
  var TESTING_CONFIG_FILE = "live-tokens.testing.ts";
10
10
  var COMPONENTS_PATH_ENV = "LIVE_TOKENS_COMPONENTS_PATH";
11
11
  var COMPONENT_ENV = "LIVE_TOKENS_COMPONENT";
12
+ var PAGES_ENV = "LIVE_TOKENS_PAGES";
13
+ var PAGE_VIEWPORTS_ENV = "LIVE_TOKENS_PAGE_VIEWPORTS";
14
+ var DEFAULT_PAGE_VIEWPORTS = [
15
+ { width: 1280, height: 900 },
16
+ { width: 390, height: 844 }
17
+ ];
12
18
  var DEFAULT_DATA_DIR = "src/live-tokens/data";
13
19
  var DEFAULT_DEV_COMMAND = "npm run dev -- --host {host} --port {port}";
14
20
  function defineTestingConfig(config) {
@@ -34,7 +40,9 @@ function resolveTestingConfig(config = {}, root = process.cwd()) {
34
40
  componentsPath: config.componentsPath ?? DEFAULT_COMPONENTS_PATH,
35
41
  dataDir: config.dataDir ? path.resolve(projectRoot, config.dataDir) : configuredDataDir(projectRoot),
36
42
  registrySetup: config.registrySetup ? path.resolve(projectRoot, config.registrySetup) : void 0,
37
- contractsModule: config.contractsModule ? path.resolve(projectRoot, config.contractsModule) : void 0
43
+ contractsModule: config.contractsModule ? path.resolve(projectRoot, config.contractsModule) : void 0,
44
+ pageRoutes: config.pageRoutes ?? {},
45
+ pageViewports: config.pageViewports?.length ? config.pageViewports : DEFAULT_PAGE_VIEWPORTS
38
46
  };
39
47
  }
40
48
  function devServerCommand(command, host, port) {
@@ -46,8 +54,11 @@ export {
46
54
  TESTING_CONFIG_FILE,
47
55
  COMPONENTS_PATH_ENV,
48
56
  COMPONENT_ENV,
57
+ PAGES_ENV,
58
+ PAGE_VIEWPORTS_ENV,
59
+ DEFAULT_PAGE_VIEWPORTS,
49
60
  defineTestingConfig,
50
61
  resolveTestingConfig,
51
62
  devServerCommand
52
63
  };
53
- //# sourceMappingURL=chunk-ZMSX6CXR.js.map
64
+ //# sourceMappingURL=chunk-NB3NZRBM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../testing/config.ts","../editor/core/routing/ownedRoutes.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { DEFAULT_COMPONENTS_PATH } from '../editor/core/routing/ownedRoutes';\n\n/** The file a project puts its shared test settings in, at the project root. */\nexport const TESTING_CONFIG_FILE = 'live-tokens.testing.ts';\n\n/** The components route the run drives, published from the settings so the\n * suites need no argument. */\nexport const COMPONENTS_PATH_ENV = 'LIVE_TOKENS_COMPONENTS_PATH';\n\n/** Narrows a run to one component. */\nexport const COMPONENT_ENV = 'LIVE_TOKENS_COMPONENT';\n\n/** The page targets a run opens, as JSON: `[{ source, route }]`. */\nexport const PAGES_ENV = 'LIVE_TOKENS_PAGES';\n\n/** The viewports every page rule runs at, as JSON: `[{ width, height }]`. */\nexport const PAGE_VIEWPORTS_ENV = 'LIVE_TOKENS_PAGE_VIEWPORTS';\n\nexport interface PageViewport {\n width: number;\n height: number;\n}\n\n/** The contract project's own desktop size, and the phone the text styles\n * carry media overrides for. Nothing here is derived from a page. */\nexport const DEFAULT_PAGE_VIEWPORTS: PageViewport[] = [\n { width: 1280, height: 900 },\n { width: 390, height: 844 },\n];\n\n/** What `resolveDataDirs` falls back to, restated because that resolver is\n * plugin-side and the tarball ships the plugin built rather than as source.\n * `vite-plugin/testingConfig.test.ts` pins the two together. */\nconst DEFAULT_DATA_DIR = 'src/live-tokens/data';\n\nconst DEFAULT_DEV_COMMAND = 'npm run dev -- --host {host} --port {port}';\n\nexport interface LiveTokensTestingConfig {\n /** Vite config a generated Vitest config merges. Default `vite.config.ts`. */\n viteConfig?: string;\n /** Command that starts the dev server. `{host}` and `{port}` are substituted.\n * Default `npm run dev -- --host {host} --port {port}`. */\n devCommand?: string;\n /** Fixed port for the dev server. Omitted, the run takes a free one. */\n port?: number;\n /** The components route, when `editorRoutes` moved it off the owned default. */\n componentsPath?: string;\n /** The plugin's data directory. Default: `dataDir` from\n * `live-tokens.config.json`, else `src/live-tokens/data`. The run never uses\n * it directly; it is the tree the isolated copy is made from. */\n dataDir?: string;\n /** Module that registers the project's components without mounting the app.\n * The registry contract imports it before it selects entries. */\n registrySetup?: string;\n /** Module exporting the `ComponentContract[]` a custom component adds to the\n * shipped list (default export or a named `contracts` export). The suite\n * files are static Playwright entry points, so an env var naming this path\n * plus a dynamic import inside `selectedContracts()` is the only way a\n * custom contract reaches them. */\n contractsModule?: string;\n /** Page source path to the concrete URL that renders it, for a route the\n * `pages` object cannot express: one served by `resolve()`, or one whose\n * parameters only the project knows. Page paths are relative to the\n * project root. */\n pageRoutes?: Record<string, string>;\n /** Replaces the two sizes every page rule runs at. */\n pageViewports?: PageViewport[];\n}\n\nexport interface ResolvedTestingConfig {\n root: string;\n viteConfig: string;\n devCommand: string;\n port?: number;\n componentsPath: string;\n dataDir: string;\n registrySetup?: string;\n contractsModule?: string;\n pageRoutes: Record<string, string>;\n pageViewports: PageViewport[];\n}\n\n/** Types the settings file without importing the interface by hand. */\nexport function defineTestingConfig(config: LiveTokensTestingConfig): LiveTokensTestingConfig {\n return config;\n}\n\n/** `dataDir` as the plugin resolves it, from the same file the plugin reads. */\nfunction configuredDataDir(root: string): string {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(root, 'live-tokens.config.json'), 'utf-8'));\n if (parsed && typeof parsed === 'object' && typeof parsed.dataDir === 'string') {\n return path.resolve(root, parsed.dataDir);\n }\n } catch {\n // Missing or unparseable reads as absent, matching `readLiveTokensConfig`.\n }\n return path.resolve(root, DEFAULT_DATA_DIR);\n}\n\nexport function resolveTestingConfig(\n config: LiveTokensTestingConfig = {},\n root: string = process.cwd(),\n): ResolvedTestingConfig {\n const projectRoot = path.resolve(root);\n return {\n root: projectRoot,\n viteConfig: path.resolve(projectRoot, config.viteConfig ?? 'vite.config.ts'),\n devCommand: config.devCommand ?? DEFAULT_DEV_COMMAND,\n port: config.port,\n componentsPath: config.componentsPath ?? DEFAULT_COMPONENTS_PATH,\n dataDir: config.dataDir ? path.resolve(projectRoot, config.dataDir) : configuredDataDir(projectRoot),\n registrySetup: config.registrySetup\n ? path.resolve(projectRoot, config.registrySetup)\n : undefined,\n contractsModule: config.contractsModule\n ? path.resolve(projectRoot, config.contractsModule)\n : undefined,\n pageRoutes: config.pageRoutes ?? {},\n pageViewports: config.pageViewports?.length ? config.pageViewports : DEFAULT_PAGE_VIEWPORTS,\n };\n}\n\nexport function devServerCommand(command: string, host: string, port: number): string {\n return command.replaceAll('{host}', host).replaceAll('{port}', String(port));\n}\n","// The package's dev-only editor surfaces live under a reserved `/live-tokens/*`\n// namespace so they can never collide with a consumer's own routes — the\n// consumer owns the rest of the URL space. Keeping the defaults in one place\n// is the guard against the nav rail and the dispatcher drifting onto different\n// paths (the collision/shadow class these constants exist to prevent).\n//\n// A consumer relocates or disables each surface via the `editorRoutes` prop on\n// <LiveTokensRouter>; these are only the defaults.\nexport const DEFAULT_EDITOR_PATH = '/live-tokens/editor';\nexport const DEFAULT_COMPONENTS_PATH = '/live-tokens/components';\nexport const DEFAULT_COLORS_PATH = '/live-tokens/colors';\nexport const DEFAULT_DOCS_PATH = '/live-tokens/docs';\n"],"mappings":";AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;;;ACQV,IAAM,0BAA0B;;;ADJhC,IAAM,sBAAsB;AAI5B,IAAM,sBAAsB;AAG5B,IAAM,gBAAgB;AAGtB,IAAM,YAAY;AAGlB,IAAM,qBAAqB;AAS3B,IAAM,yBAAyC;AAAA,EACpD,EAAE,OAAO,MAAM,QAAQ,IAAI;AAAA,EAC3B,EAAE,OAAO,KAAK,QAAQ,IAAI;AAC5B;AAKA,IAAM,mBAAmB;AAEzB,IAAM,sBAAsB;AAgDrB,SAAS,oBAAoB,QAA0D;AAC5F,SAAO;AACT;AAGA,SAAS,kBAAkB,MAAsB;AAC/C,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG,aAAa,KAAK,KAAK,MAAM,yBAAyB,GAAG,OAAO,CAAC;AAC9F,QAAI,UAAU,OAAO,WAAW,YAAY,OAAO,OAAO,YAAY,UAAU;AAC9E,aAAO,KAAK,QAAQ,MAAM,OAAO,OAAO;AAAA,IAC1C;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO,KAAK,QAAQ,MAAM,gBAAgB;AAC5C;AAEO,SAAS,qBACd,SAAkC,CAAC,GACnC,OAAe,QAAQ,IAAI,GACJ;AACvB,QAAM,cAAc,KAAK,QAAQ,IAAI;AACrC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,YAAY,KAAK,QAAQ,aAAa,OAAO,cAAc,gBAAgB;AAAA,IAC3E,YAAY,OAAO,cAAc;AAAA,IACjC,MAAM,OAAO;AAAA,IACb,gBAAgB,OAAO,kBAAkB;AAAA,IACzC,SAAS,OAAO,UAAU,KAAK,QAAQ,aAAa,OAAO,OAAO,IAAI,kBAAkB,WAAW;AAAA,IACnG,eAAe,OAAO,gBAClB,KAAK,QAAQ,aAAa,OAAO,aAAa,IAC9C;AAAA,IACJ,iBAAiB,OAAO,kBACpB,KAAK,QAAQ,aAAa,OAAO,eAAe,IAChD;AAAA,IACJ,YAAY,OAAO,cAAc,CAAC;AAAA,IAClC,eAAe,OAAO,eAAe,SAAS,OAAO,gBAAgB;AAAA,EACvE;AACF;AAEO,SAAS,iBAAiB,SAAiB,MAAc,MAAsB;AACpF,SAAO,QAAQ,WAAW,UAAU,IAAI,EAAE,WAAW,UAAU,OAAO,IAAI,CAAC;AAC7E;","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  COMPONENTS_PATH_ENV,
3
3
  DEFAULT_COMPONENTS_PATH
4
- } from "./chunk-ZMSX6CXR.js";
4
+ } from "./chunk-NB3NZRBM.js";
5
5
 
6
6
  // src/testing/support/editor.ts
7
7
  function componentsPath() {
@@ -20,4 +20,4 @@ async function openComponentsEditor(page, path = componentsPath()) {
20
20
  export {
21
21
  openComponentsEditor
22
22
  };
23
- //# sourceMappingURL=chunk-L73N4NSO.js.map
23
+ //# sourceMappingURL=chunk-WIZ6W7UT.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  openComponentsEditor
3
- } from "./chunk-L73N4NSO.js";
4
- import "./chunk-ZMSX6CXR.js";
3
+ } from "./chunk-WIZ6W7UT.js";
4
+ import "./chunk-NB3NZRBM.js";
5
5
 
6
6
  // src/testing/component-alias.contract.ts
7
7
  import fs from "fs";
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  ContractHarness
3
- } from "./chunk-7TI7Z6Y6.js";
4
- import "./chunk-L73N4NSO.js";
3
+ } from "./chunk-GNIUPIU2.js";
4
+ import "./chunk-WIZ6W7UT.js";
5
5
  import {
6
6
  ContractViolation,
7
7
  allContracts,
8
8
  isInapplicable,
9
9
  selectedContracts
10
10
  } from "./chunk-4JQX6WWL.js";
11
- import "./chunk-ZMSX6CXR.js";
11
+ import "./chunk-NB3NZRBM.js";
12
12
 
13
13
  // src/testing/component-editor.contract.ts
14
14
  import { test } from "@playwright/test";
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  ContractHarness
3
- } from "./chunk-7TI7Z6Y6.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
9
  } from "./chunk-4JQX6WWL.js";
10
- import "./chunk-ZMSX6CXR.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.
@@ -14,11 +14,14 @@ import {
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,12 +112,14 @@ 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
124
  return defineConfig({
120
125
  testDir: options.testDir ?? CONTRACT_TEST_DIR,
@@ -128,6 +133,7 @@ async function createPlaywrightConfig(options = {}) {
128
133
  // command line.
129
134
  workers: 1,
130
135
  timeout: 3e4,
136
+ globalTimeout: options.globalTimeout ?? DEFAULT_GLOBAL_TIMEOUT_MS,
131
137
  expect: { timeout: 5e3 },
132
138
  // A shared-server suite times out under CI load in ways it never does
133
139
  // locally, and without a retry budget one wobble aborts a tagged release.
@@ -164,6 +170,15 @@ async function createPlaywrightConfig(options = {}) {
164
170
  // band can cover a control at some scroll positions. Until that is
165
171
  // fixed the suite runs taller. See docs/contract-test-defects.md.
166
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"] }
167
182
  }
168
183
  ],
169
184
  webServer: {
@@ -192,6 +207,9 @@ export {
192
207
  CONTRACTS_MODULE_ENV,
193
208
  ContractViolation,
194
209
  DATA_DIR_ENV,
210
+ DEFAULT_PAGE_VIEWPORTS,
211
+ PAGES_ENV,
212
+ PAGE_VIEWPORTS_ENV,
195
213
  PORT_ENV,
196
214
  TESTING_CONFIG_FILE,
197
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 { 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}\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 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":"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","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"]}