@orkestrel/scaffold 0.0.16 → 0.0.18

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.
@@ -1177,7 +1177,7 @@ export declare function coreViteConfig(): string;
1177
1177
  readonly content: string;
1178
1178
  readonly freshness: Freshness;
1179
1179
  readonly note?: string;
1180
- /** SHA-256 of the observed local mirror, or `absent`; omitted outside target-aware pulls. */
1180
+ /** SHA-256 of the observed local mirror, or `absent`; omitted outside target-aware synchronization. */
1181
1181
  readonly baseline?: string;
1182
1182
  }
1183
1183
 
@@ -2275,7 +2275,7 @@ export declare function coreViteConfig(): string;
2275
2275
  export declare function rootViteConfig(src: readonly Environment[], facts?: ViteFacts): string;
2276
2276
 
2277
2277
  /** The devDependency range generated packages pin `@orkestrel/scaffold` at. */
2278
- export declare const SCAFFOLD_RANGE = "^0.0.16";
2278
+ export declare const SCAFFOLD_RANGE = "^0.0.18";
2279
2279
 
2280
2280
  /**
2281
2281
  * Carries a `ScaffoldErrorCode` + optional `context` (AGENTS §12).
@@ -2537,7 +2537,7 @@ export declare function coreViteConfig(): string;
2537
2537
  export declare const SYNC_BASELINE_PATTERN: RegExp;
2538
2538
 
2539
2539
  /**
2540
- * The whole outcome of a `Sync.pull`.
2540
+ * The whole outcome of a `Sync.pull` or `Sync.mirror` synchronization.
2541
2541
  *
2542
2542
  * @remarks
2543
2543
  * `clean` is `true` iff no drift AND no failures; `failed` is the count of
@@ -1177,7 +1177,7 @@ export declare function coreViteConfig(): string;
1177
1177
  readonly content: string;
1178
1178
  readonly freshness: Freshness;
1179
1179
  readonly note?: string;
1180
- /** SHA-256 of the observed local mirror, or `absent`; omitted outside target-aware pulls. */
1180
+ /** SHA-256 of the observed local mirror, or `absent`; omitted outside target-aware synchronization. */
1181
1181
  readonly baseline?: string;
1182
1182
  }
1183
1183
 
@@ -2275,7 +2275,7 @@ export declare function coreViteConfig(): string;
2275
2275
  export declare function rootViteConfig(src: readonly Environment[], facts?: ViteFacts): string;
2276
2276
 
2277
2277
  /** The devDependency range generated packages pin `@orkestrel/scaffold` at. */
2278
- export declare const SCAFFOLD_RANGE = "^0.0.16";
2278
+ export declare const SCAFFOLD_RANGE = "^0.0.18";
2279
2279
 
2280
2280
  /**
2281
2281
  * Carries a `ScaffoldErrorCode` + optional `context` (AGENTS §12).
@@ -2537,7 +2537,7 @@ export declare function coreViteConfig(): string;
2537
2537
  export declare const SYNC_BASELINE_PATTERN: RegExp;
2538
2538
 
2539
2539
  /**
2540
- * The whole outcome of a `Sync.pull`.
2540
+ * The whole outcome of a `Sync.pull` or `Sync.mirror` synchronization.
2541
2541
  *
2542
2542
  * @remarks
2543
2543
  * `clean` is `true` iff no drift AND no failures; `failed` is the count of
@@ -226,7 +226,7 @@ var DEFAULT_VERSION = "0.0.1";
226
226
  /** The `engines.node` range the `blueprint` builder fills. */
227
227
  var DEFAULT_ENGINES = `>=${MINIMUM_NODE_VERSION}`;
228
228
  /** The devDependency range generated packages pin `@orkestrel/scaffold` at. */
229
- var SCAFFOLD_RANGE = "^0.0.16";
229
+ var SCAFFOLD_RANGE = "^0.0.18";
230
230
  /** Tooling versions shared by scaffold and every generated workspace. */
231
231
  var BASE_DEV_DEPENDENCIES = Object.freeze({
232
232
  "@microsoft/api-extractor": "^7.58.12",
@@ -4466,9 +4466,7 @@ describe('createApplication', () => {
4466
4466
  'x'.repeat(204),
4467
4467
  \`\${' '.repeat(MAX_APPLICATION_NAME_INPUT_LENGTH)}x\`,
4468
4468
  ]) {
4469
- expect(() => Reflect.apply(createApplication, undefined, [value])).toThrowError(
4470
- ApplicationError,
4471
- )
4469
+ expect(() => Reflect.apply(createApplication, undefined, [value])).toThrow(ApplicationError)
4472
4470
  }
4473
4471
  const error = new ApplicationError('CONFIG', 'invalid')
4474
4472
  expect(isApplicationError(error)).toBe(true)
@@ -4543,7 +4541,7 @@ describe('createBrowserApplication', () => {
4543
4541
  { name: 'x'.repeat(204) },
4544
4542
  { name: \`\${' '.repeat(MAX_BROWSER_APPLICATION_NAME_INPUT_LENGTH)}x\` },
4545
4543
  ]) {
4546
- expect(() => Reflect.apply(createBrowserApplication, undefined, [value])).toThrowError(
4544
+ expect(() => Reflect.apply(createBrowserApplication, undefined, [value])).toThrow(
4547
4545
  BrowserApplicationError,
4548
4546
  )
4549
4547
  }
@@ -4568,14 +4566,14 @@ describe('createBrowserApplication', () => {
4568
4566
  const revocable = Proxy.revocable({}, {})
4569
4567
  revocable.revoke()
4570
4568
 
4571
- expect(() => parseBrowserApplicationOptions(accessor)).toThrowError(BrowserApplicationError)
4569
+ expect(() => parseBrowserApplicationOptions(accessor)).toThrow(BrowserApplicationError)
4572
4570
  expect(reads).toBe(0)
4573
- expect(() => Reflect.apply(createBrowserApplication, undefined, [hostile])).toThrowError(
4571
+ expect(() => Reflect.apply(createBrowserApplication, undefined, [hostile])).toThrow(
4572
+ BrowserApplicationError,
4573
+ )
4574
+ expect(() => Reflect.apply(createBrowserApplication, undefined, [revocable.proxy])).toThrow(
4574
4575
  BrowserApplicationError,
4575
4576
  )
4576
- expect(() =>
4577
- Reflect.apply(createBrowserApplication, undefined, [revocable.proxy]),
4578
- ).toThrowError(BrowserApplicationError)
4579
4577
  expect(isBrowserApplicationError(new BrowserApplicationError('CONFIG', 'invalid'))).toBe(true)
4580
4578
  expect(isBrowserApplicationError(new Error('plain'))).toBe(false)
4581
4579
  expect(isBrowserApplicationError(revocable.proxy)).toBe(false)
@@ -4678,20 +4676,20 @@ describe('ApplicationServer', () => {
4678
4676
  })
4679
4677
 
4680
4678
  it('validates direct options before allocating a listener', () => {
4681
- expect(() => createApplicationServer({ host: ' ' })).toThrowError(
4679
+ expect(() => createApplicationServer({ host: ' ' })).toThrow(
4682
4680
  expect.objectContaining({ code: 'CONFIG' }),
4683
4681
  )
4684
- expect(() => createApplicationServer({ port: Number.NaN })).toThrowError(
4682
+ expect(() => createApplicationServer({ port: Number.NaN })).toThrow(
4685
4683
  expect.objectContaining({ code: 'CONFIG' }),
4686
4684
  )
4687
- expect(() => createApplicationServer({ port: -1 })).toThrowError(
4685
+ expect(() => createApplicationServer({ port: -1 })).toThrow(
4688
4686
  expect.objectContaining({ code: 'CONFIG' }),
4689
4687
  )
4690
- expect(() => createApplicationServer({ timeout: 0 })).toThrowError(
4688
+ expect(() => createApplicationServer({ timeout: 0 })).toThrow(
4691
4689
  expect.objectContaining({ code: 'CONFIG' }),
4692
4690
  )
4693
4691
  for (const value of [null, 42, [], { host: 42 }, { port: [42] }]) {
4694
- expect(() => Reflect.apply(createApplicationServer, undefined, [value])).toThrowError(
4692
+ expect(() => Reflect.apply(createApplicationServer, undefined, [value])).toThrow(
4695
4693
  expect.objectContaining({ code: 'CONFIG' }),
4696
4694
  )
4697
4695
  }
@@ -4711,14 +4709,10 @@ describe('ApplicationServer', () => {
4711
4709
  expect(server.port).toBe(0)
4712
4710
 
4713
4711
  process.env.APP_PORT = '1e3'
4714
- expect(() => createApplicationServer()).toThrowError(
4715
- expect.objectContaining({ code: 'CONFIG' }),
4716
- )
4712
+ expect(() => createApplicationServer()).toThrow(expect.objectContaining({ code: 'CONFIG' }))
4717
4713
  process.env.APP_PORT = '0'
4718
4714
  process.env.APP_START_TIMEOUT = '0'
4719
- expect(() => createApplicationServer()).toThrowError(
4720
- expect.objectContaining({ code: 'CONFIG' }),
4721
- )
4715
+ expect(() => createApplicationServer()).toThrow(expect.objectContaining({ code: 'CONFIG' }))
4722
4716
  } finally {
4723
4717
  if (previousHost === undefined) delete process.env.APP_HOST
4724
4718
  else process.env.APP_HOST = previousHost
@@ -5087,15 +5081,13 @@ describe('application environment parsers', () => {
5087
5081
  '999.999.999.999',
5088
5082
  \`\${'a'.repeat(64)}.example\`,
5089
5083
  ])('rejects hostile APP_HOST value %s before listener allocation', (value) => {
5090
- expect(() => parseApplicationHost(value)).toThrowError(
5091
- expect.objectContaining({ code: 'CONFIG' }),
5092
- )
5084
+ expect(() => parseApplicationHost(value)).toThrow(expect.objectContaining({ code: 'CONFIG' }))
5093
5085
  })
5094
5086
 
5095
5087
  it.each([null, 42, [], { port: [42] }, { timeout: 0 }])(
5096
5088
  'rejects hostile option container or leaf value %#',
5097
5089
  (value) => {
5098
- expect(() => parseApplicationServerOptions(value)).toThrowError(
5090
+ expect(() => parseApplicationServerOptions(value)).toThrow(
5099
5091
  expect.objectContaining({ code: 'CONFIG' }),
5100
5092
  )
5101
5093
  },
@@ -5118,7 +5110,7 @@ describe('application environment parsers', () => {
5118
5110
  ])(
5119
5111
  'rejects inherited, unknown, symbolic, accessor, instance, and hostile proxy options %#',
5120
5112
  (value) => {
5121
- expect(() => parseApplicationServerOptions(value)).toThrowError(
5113
+ expect(() => parseApplicationServerOptions(value)).toThrow(
5122
5114
  expect.objectContaining({ code: 'CONFIG' }),
5123
5115
  )
5124
5116
  },
@@ -5629,7 +5621,7 @@ function packageManifest(spec) {
5629
5621
  }
5630
5622
  if (spec.bin) scripts["test:src:bin"] = "vitest run --config vite.config.ts --no-cache --reporter=dot --project src:bin";
5631
5623
  if (spec.integration) scripts["test:integration"] = "vitest run --config vite.config.ts --no-cache --reporter=dot --project integration";
5632
- if (spec.bin && spec.integration) scripts["test:equivalence"] = "node -e \"const c=require('node:child_process'),n=process.platform==='win32'?'npm.cmd':'npm',r=c.spawnSync(n,['run','test:integration'],{stdio:'inherit',env:{...process.env,SCAFFOLD_BOUNDARY_EQUIVALENCE:'1'}});process.exit(r.status??1)\"";
5624
+ if (spec.bin && spec.integration) scripts["test:equivalence"] = "node -e \"const c=require('node:child_process'),p=process.env.npm_execpath;if(p===undefined)process.exit(1);const r=c.spawnSync(process.execPath,[p,'run','test:integration'],{stdio:'inherit',env:{...process.env,SCAFFOLD_BOUNDARY_EQUIVALENCE:'1'}});process.exit(r.status??1)\"";
5633
5625
  if (spec.service) scripts["test:service"] = "vitest run --config vite.config.ts --no-cache --reporter=dot --project service";
5634
5626
  if (spec.app.length > 0) {
5635
5627
  scripts["test:app"] = "vitest run --config vite.config.ts --no-cache --reporter=dot " + spec.app.map((environment) => `--project ${APP_MATRIX[environment].project}`).join(" ");
@@ -5723,6 +5715,7 @@ function rootTsconfig(src, app = []) {
5723
5715
  target: "ESNext",
5724
5716
  module: "ESNext",
5725
5717
  moduleResolution: "bundler",
5718
+ allowImportingTsExtensions: true,
5726
5719
  lib: [
5727
5720
  "ESNext",
5728
5721
  "DOM",
@@ -5872,7 +5865,7 @@ ${renderedProjects}
5872
5865
  ${fitsPrintWidth(inlineRegistrations) ? inlineRegistrations : ` [
5873
5866
  ${renderedRegistrations.map((registration) => ` ${registration},`).join("\n")}
5874
5867
  ],`}
5875
- chromiumPath !== undefined,
5868
+ browserOptions !== undefined,
5876
5869
  process.argv,
5877
5870
  ),`;
5878
5871
  }
@@ -5896,6 +5889,8 @@ ${renderedRegistrations.map((registration) => ` ${registration},`).join("\n")}
5896
5889
  */
5897
5890
  function viteHeader(machinery) {
5898
5891
  const { browser: needsBrowser, vue: needsVue, output: needsOutput } = machinery;
5892
+ const playwrightTypeImports = needsBrowser ? `import type { PlaywrightProviderOptions } from '@vitest/browser-playwright'
5893
+ ` : "";
5899
5894
  const playwrightImports = needsBrowser ? `import { playwright } from '@vitest/browser-playwright'
5900
5895
  import { chromium } from 'playwright'
5901
5896
  ` : "";
@@ -6206,7 +6201,9 @@ import { parse as parseVue } from 'vue/compiler-sfc'
6206
6201
  ? undefined
6207
6202
  : packageRootOf(packageName, physicalSource)
6208
6203
  if (packageRoot === undefined || !containedPath(packageRoot, physicalSource)) {
6209
- this.error('Resolved dependencies must remain inside their physical package root')
6204
+ return this.error(
6205
+ 'Resolved dependencies must remain inside their physical package root',
6206
+ )
6210
6207
  }
6211
6208
  trustedPackageRoots.add(packageRoot)
6212
6209
  }
@@ -6214,7 +6211,7 @@ import { parse as parseVue } from 'vue/compiler-sfc'
6214
6211
  }
6215
6212
  const resolvedSource = workspacePath(physicalSource)
6216
6213
  if (resolvedSource === undefined) {
6217
- this.error('Environment modules cannot import files outside the workspace')
6214
+ return this.error('Environment modules cannot import files outside the workspace')
6218
6215
  }
6219
6216
  const assetError = environmentPathError(owner, resolvedSource)
6220
6217
  if (assetError !== undefined) this.error(assetError)
@@ -7243,7 +7240,7 @@ ${needsVue ? ` configureServer(server) {
7243
7240
  }
7244
7241
  }
7245
7242
  `;
7246
- return `${viteTypeImports}
7243
+ return `${playwrightTypeImports}${viteTypeImports}
7247
7244
  ${needsBrowser ? `import { isCSSRequest, parseSync, preprocessCSS, transformWithOxc, Visitor } from 'vite'
7248
7245
  ` : `import { parseSync, transformWithOxc, Visitor } from 'vite'
7249
7246
  `}import { defineConfig, mergeConfig } from 'vitest/config'
@@ -7251,7 +7248,7 @@ import tsconfig from './tsconfig.json' with { type: 'json' }
7251
7248
  import { fileURLToPath, URL } from 'node:url'
7252
7249
  import { isBuiltin } from 'node:module'
7253
7250
  import {
7254
- closeSync,
7251
+ ${needsBrowser ? " accessSync,\n" : ""} closeSync,
7255
7252
  constants as FS_CONSTANTS,
7256
7253
  existsSync,
7257
7254
  fstatSync,
@@ -7260,7 +7257,15 @@ import {
7260
7257
  ${needsBrowser ? " readdirSync,\n" : ""} readSync,
7261
7258
  realpathSync,
7262
7259
  ${needsBrowser ? " statSync,\n" : ""}} from 'node:fs'
7263
- import { ${needsBrowser ? "basename, " : ""}dirname, isAbsolute, relative, resolve as resolvePath, sep } from 'node:path'
7260
+ ${needsBrowser ? `import {
7261
+ basename,
7262
+ join,
7263
+ dirname,
7264
+ isAbsolute,
7265
+ relative,
7266
+ resolve as resolvePath,
7267
+ sep,
7268
+ } from 'node:path'` : `import { dirname, isAbsolute, relative, resolve as resolvePath, sep } from 'node:path'`}
7264
7269
  ${playwrightImports}${vueImports}${needsBrowser ? `\n/** Chromium executable layouts inside a \`chromium-<revision>\` browsers-directory entry, per platform. */
7265
7270
  ${EXPORT_KEYWORD} ${CONST_KEYWORD} CHROMIUM_LAYOUTS = Object.freeze([
7266
7271
  'chrome-linux/chrome',
@@ -7271,15 +7276,64 @@ ${EXPORT_KEYWORD} ${CONST_KEYWORD} CHROMIUM_LAYOUTS = Object.freeze([
7271
7276
  'chrome-mac-arm64/Chromium.app/Contents/MacOS/Chromium',
7272
7277
  ])
7273
7278
 
7279
+ /** Stable Playwright Chromium channels and their standard executable layouts. */
7280
+ ${EXPORT_KEYWORD} ${CONST_KEYWORD} SYSTEM_BROWSER_CHANNELS = Object.freeze([
7281
+ Object.freeze({
7282
+ channel: 'chrome',
7283
+ layouts: Object.freeze({
7284
+ linux: '/opt/google/chrome/chrome',
7285
+ darwin: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
7286
+ win32: Object.freeze(['Google', 'Chrome', 'Application', 'chrome.exe']),
7287
+ }),
7288
+ }),
7289
+ Object.freeze({
7290
+ channel: 'msedge',
7291
+ layouts: Object.freeze({
7292
+ linux: '/opt/microsoft/msedge/msedge',
7293
+ darwin: '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
7294
+ win32: Object.freeze(['Microsoft', 'Edge', 'Application', 'msedge.exe']),
7295
+ }),
7296
+ }),
7297
+ ])
7298
+
7299
+ /**
7300
+ * Determine whether a path identifies an executable regular file.
7301
+ *
7302
+ * @param path - The filesystem path to inspect.
7303
+ * @returns Whether the path is a regular file with execute access.
7304
+ *
7305
+ * @example
7306
+ * \`\`\`ts
7307
+ * isBrowserExecutable('/opt/google/chrome/chrome')
7308
+ * \`\`\`
7309
+ */
7310
+ ${EXPORT_KEYWORD} function isBrowserExecutable(path: string): boolean {
7311
+ try {
7312
+ if (!statSync(path).isFile()) return false
7313
+ accessSync(path, FS_CONSTANTS.X_OK)
7314
+ return true
7315
+ } catch {
7316
+ return false
7317
+ }
7318
+ }
7319
+
7274
7320
  /**
7275
- * Resolve a launchable Chromium executable: the pinned revision when installed,
7321
+ * Resolve a launchable Playwright-managed Chromium executable: the pinned revision when installed,
7276
7322
  * otherwise a \`chromium\` / \`chromium.exe\` alias or any other \`chromium-*\`
7277
7323
  * revision under the same Playwright browsers directory. A pinned-revision miss
7278
7324
  * is not Chromium absence — managed containers ship one usable build (often
7279
7325
  * behind a revision-agnostic alias) for many Playwright versions.
7326
+ *
7327
+ * @param pinned - The executable path for Playwright's pinned Chromium revision.
7328
+ * @returns The managed executable path, or \`undefined\` when none is executable.
7329
+ *
7330
+ * @example
7331
+ * \`\`\`ts
7332
+ * resolveManagedBrowser(chromium.executablePath())
7333
+ * \`\`\`
7280
7334
  */
7281
- ${EXPORT_KEYWORD} function resolveChromium(pinned: string): string | undefined {
7282
- if (existsSync(pinned)) return pinned
7335
+ ${EXPORT_KEYWORD} function resolveManagedBrowser(pinned: string): string | undefined {
7336
+ if (isBrowserExecutable(pinned)) return pinned
7283
7337
  let revisionRoot = dirname(pinned)
7284
7338
  for (;;) {
7285
7339
  if (/^chromium-\\d+$/.test(basename(revisionRoot))) break
@@ -7290,7 +7344,7 @@ ${EXPORT_KEYWORD} function resolveChromium(pinned: string): string | undefined {
7290
7344
  const browsersRoot = dirname(revisionRoot)
7291
7345
  for (const alias of ['chromium', 'chromium.exe']) {
7292
7346
  const candidate = resolvePath(browsersRoot, alias)
7293
- if (existsSync(candidate) && statSync(candidate).isFile()) return candidate
7347
+ if (isBrowserExecutable(candidate)) return candidate
7294
7348
  }
7295
7349
  let entries: readonly string[]
7296
7350
  try {
@@ -7304,18 +7358,89 @@ ${EXPORT_KEYWORD} function resolveChromium(pinned: string): string | undefined {
7304
7358
  for (const revision of revisions) {
7305
7359
  for (const layout of CHROMIUM_LAYOUTS) {
7306
7360
  const candidate = resolvePath(browsersRoot, revision, layout)
7307
- if (existsSync(candidate)) return candidate
7361
+ if (isBrowserExecutable(candidate)) return candidate
7308
7362
  }
7309
7363
  }
7310
7364
  return undefined
7311
7365
  }
7312
7366
 
7313
- ${CONST_KEYWORD} chromiumPinned = chromium.executablePath()
7314
- ${CONST_KEYWORD} chromiumPath = resolveChromium(chromiumPinned)
7315
- ${CONST_KEYWORD} chromiumOptions =
7316
- chromiumPath === undefined || chromiumPath === chromiumPinned
7317
- ? {}
7318
- : { launchOptions: { executablePath: chromiumPath } }
7367
+ /**
7368
+ * Resolve the first installed stable system Chromium channel.
7369
+ *
7370
+ * @param platform - The Node platform whose standard layouts should be probed.
7371
+ * @param environment - The process environment supplying Windows installation roots.
7372
+ * @returns \`chrome\`, then \`msedge\`, or \`undefined\` when neither is executable.
7373
+ *
7374
+ * @example
7375
+ * \`\`\`ts
7376
+ * resolveSystemBrowser(process.platform, process.env)
7377
+ * \`\`\`
7378
+ */
7379
+ ${EXPORT_KEYWORD} function resolveSystemBrowser(
7380
+ platform: NodeJS.Platform,
7381
+ environment: NodeJS.ProcessEnv,
7382
+ ): string | undefined {
7383
+ if (platform !== 'linux' && platform !== 'darwin' && platform !== 'win32') return undefined
7384
+ const roots = new Set<string>()
7385
+ if (platform === 'win32') {
7386
+ for (const root of [
7387
+ environment.LOCALAPPDATA,
7388
+ environment.PROGRAMFILES,
7389
+ environment['PROGRAMFILES(X86)'],
7390
+ ]) {
7391
+ if (root !== undefined && root.length > 0) roots.add(root)
7392
+ }
7393
+ const homeDrive = environment.HOMEDRIVE
7394
+ if (homeDrive !== undefined && homeDrive.length > 0) {
7395
+ roots.add(join(homeDrive, 'Program Files'))
7396
+ roots.add(join(homeDrive, 'Program Files (x86)'))
7397
+ }
7398
+ }
7399
+ for (const browser of SYSTEM_BROWSER_CHANNELS) {
7400
+ if (platform === 'win32') {
7401
+ for (const root of roots) {
7402
+ if (isBrowserExecutable(join(root, ...browser.layouts.win32))) return browser.channel
7403
+ }
7404
+ continue
7405
+ }
7406
+ if (isBrowserExecutable(browser.layouts[platform])) return browser.channel
7407
+ }
7408
+ return undefined
7409
+ }
7410
+
7411
+ /**
7412
+ * Resolve launch options for a managed Chromium or stable system browser.
7413
+ *
7414
+ * @param pinned - The executable path for Playwright's pinned Chromium revision.
7415
+ * @param platform - The Node platform whose standard system layouts should be probed.
7416
+ * @param environment - The process environment supplying Windows installation roots.
7417
+ * @returns Provider options for managed Chromium, Chrome, or Edge, or \`undefined\`.
7418
+ *
7419
+ * @remarks
7420
+ * An installed pinned revision returns an empty object so Playwright retains
7421
+ * its default launch semantics. A different managed executable is selected by
7422
+ * path; a system browser is selected by its stable Playwright channel.
7423
+ *
7424
+ * @example
7425
+ * \`\`\`ts
7426
+ * resolveBrowser(chromium.executablePath(), process.platform, process.env)
7427
+ * \`\`\`
7428
+ */
7429
+ ${EXPORT_KEYWORD} function resolveBrowser(
7430
+ pinned: string,
7431
+ platform: NodeJS.Platform,
7432
+ environment: NodeJS.ProcessEnv,
7433
+ ): PlaywrightProviderOptions | undefined {
7434
+ const managed = resolveManagedBrowser(pinned)
7435
+ if (managed !== undefined) {
7436
+ return managed === pinned ? {} : { launchOptions: { executablePath: managed } }
7437
+ }
7438
+ const channel = resolveSystemBrowser(platform, environment)
7439
+ return channel === undefined ? undefined : { launchOptions: { channel } }
7440
+ }
7441
+
7442
+ ${CONST_KEYWORD} browserPinned = chromium.executablePath()
7443
+ ${CONST_KEYWORD} browserOptions = resolveBrowser(browserPinned, process.platform, process.env)
7319
7444
  ` : ""}
7320
7445
  ${EXPORT_KEYWORD} function resolveWorkspacePath(relativePath: string): string {
7321
7446
  return fileURLToPath(new URL(relativePath, import.meta.url))
@@ -7364,7 +7489,9 @@ ${needsBrowser ? `${EXPORT_KEYWORD} function gateBrowserProjects(
7364
7489
  projects.push(registration.project())
7365
7490
  }
7366
7491
  if (gated.length === 0) return { projects }
7367
- console.warn(\`browser projects skipped: Chromium absent (\${gated.join(', ')})\`)
7492
+ console.warn(
7493
+ \`browser projects skipped: no Playwright Chromium, Chrome, or Edge found (\${gated.join(', ')})\`,
7494
+ )
7368
7495
  const filters: string[] = []
7369
7496
  let readable = true
7370
7497
  for (let index = 0; index < argv.length; index += 1) {
@@ -7655,7 +7782,7 @@ ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
7655
7782
  }),
7656
7783
  browser: {
7657
7784
  enabled: true,
7658
- provider: playwright(chromiumOptions),
7785
+ provider: playwright(browserOptions),
7659
7786
  instances: [{ browser: 'chromium', headless: true }],
7660
7787
  },
7661
7788
  fileParallelism: false,
@@ -7689,6 +7816,7 @@ ${EXPORT_KEYWORD} const srcServer = (config?: UserConfig): UserConfig =>
7689
7816
  outDir: 'dist/src/server',
7690
7817
  target: 'node22',
7691
7818
  rolldownOptions: {
7819
+ platform: 'node',
7692
7820
  external: (id: string) => id.startsWith('node:') || id.startsWith('@orkestrel/'),
7693
7821
  },
7694
7822
  },
@@ -7787,7 +7915,7 @@ ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
7787
7915
  }),
7788
7916
  browser: {
7789
7917
  enabled: true,
7790
- provider: playwright(chromiumOptions),
7918
+ provider: playwright(browserOptions),
7791
7919
  instances: [{ browser: 'chromium', headless: true }],
7792
7920
  },
7793
7921
  fileParallelism: false,
@@ -7812,6 +7940,7 @@ ${EXPORT_KEYWORD} const srcServer = (config?: UserConfig): UserConfig =>
7812
7940
  outDir: 'dist/src/server',
7813
7941
  target: 'node22',
7814
7942
  rolldownOptions: {
7943
+ platform: 'node',
7815
7944
  external: (id: string) =>
7816
7945
  id === '@src/core' || id.startsWith('node:') || id.startsWith('@orkestrel/'),
7817
7946
  output: [
@@ -7952,7 +8081,7 @@ ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
7952
8081
  }),
7953
8082
  browser: {
7954
8083
  enabled: true,
7955
- provider: playwright(chromiumOptions),
8084
+ provider: playwright(browserOptions),
7956
8085
  instances: [{ browser: 'chromium', headless: true }],
7957
8086
  },
7958
8087
  fileParallelism: false,
@@ -7996,6 +8125,7 @@ ${EXPORT_KEYWORD} const srcServer = (config?: UserConfig): UserConfig =>
7996
8125
  outDir: 'dist/src/server',
7997
8126
  target: 'node22',
7998
8127
  rolldownOptions: {
8128
+ platform: 'node',
7999
8129
  external: (id: string) =>
8000
8130
  ${coreExternal}id.startsWith('node:') || id.startsWith('@orkestrel/'),${coreOutput}
8001
8131
  },
@@ -8081,7 +8211,7 @@ ${EXPORT_KEYWORD} function appBrowser(...config: readonly never[]): UserConfig {
8081
8211
  },
8082
8212
  browser: {
8083
8213
  enabled: true,
8084
- provider: playwright(chromiumOptions),
8214
+ provider: playwright(browserOptions),
8085
8215
  instances: [{ browser: 'chromium', headless: true }],
8086
8216
  },
8087
8217
  fileParallelism: false,
@@ -8174,7 +8304,7 @@ import {
8174
8304
  outputBoundary,
8175
8305
  srcCore,
8176
8306
  resolveWorkspacePath,
8177
- } from '../../vite.config'
8307
+ } from '../../vite.config.ts'
8178
8308
 
8179
8309
  export default defineConfig(
8180
8310
  srcCore({
@@ -8259,7 +8389,7 @@ function srcViteConfig(environment) {
8259
8389
  const anchor = environment === "browser" ? "srcBrowser" : "srcServer";
8260
8390
  return `import { defineConfig } from 'vite'
8261
8391
  import dts from 'vite-plugin-dts'
8262
- import { ${anchor}, resolveWorkspacePath } from '../../vite.config'
8392
+ import { ${anchor}, resolveWorkspacePath } from '../../vite.config.ts'
8263
8393
 
8264
8394
  // Types are bundled inline by vite-plugin-dts (see configs/src/vite.core.config.ts
8265
8395
  // for the same pattern).
@@ -8312,7 +8442,7 @@ function binTsconfig() {
8312
8442
  */
8313
8443
  function binViteConfig() {
8314
8444
  return `import { defineConfig } from 'vite'
8315
- import { srcBin } from '../../vite.config'
8445
+ import { srcBin } from '../../vite.config.ts'
8316
8446
 
8317
8447
  // The \`scaffold\` executable build — a single ESM lib file, no declarations (an
8318
8448
  // executable ships no types), with the \`#!/usr/bin/env node\` shebang re-emitted via
@@ -8379,7 +8509,7 @@ function appTsconfig(environment, hasCore) {
8379
8509
  function appViteConfig(environment) {
8380
8510
  const anchor = environment === "browser" ? "appBrowser" : "appServer";
8381
8511
  return `import { defineConfig } from 'vite'
8382
- import { ${anchor} } from '../../vite.config'
8512
+ import { ${anchor} } from '../../vite.config.ts'
8383
8513
 
8384
8514
  export default defineConfig(${anchor}())
8385
8515
  `;
@@ -8648,8 +8778,7 @@ function paritySpecifiers(spec) {
8648
8778
  function testArtifacts(spec, pascal) {
8649
8779
  const hasBrowser = spec.src.includes("browser") || spec.app.includes("browser");
8650
8780
  const hasVue = spec.app.includes("browser");
8651
- const browserPolicySpecifier = hasBrowser ? ", accessSync, constants as FS_CONSTANTS, statSync" : "";
8652
- const browserPolicyImport = hasBrowser ? "\nimport { chromium } from 'playwright'\nimport { resolveChromium } from '../vite.config.js'" : "";
8781
+ const browserPolicyImport = hasBrowser ? "\nimport { chromium } from 'playwright'\nimport { isBrowserExecutable, resolveBrowser, SYSTEM_BROWSER_CHANNELS } from '../vite.config.js'" : "";
8653
8782
  const vuePolicyImport = hasVue ? "\nimport { parse as parseVue } from 'vue/compiler-sfc'" : "";
8654
8783
  const workspacePolicyAssertion = hasVue ? `expect(
8655
8784
  inspectCodingWorkspace(process.cwd(), (path, content) => {
@@ -8662,15 +8791,20 @@ function testArtifacts(spec, pascal) {
8662
8791
  ).toEqual([])` : "expect(inspectCodingWorkspace(process.cwd())).toEqual([])";
8663
8792
  const browserPolicyTest = hasBrowser ? `
8664
8793
 
8665
- it('resolves Chromium only to a real executable file', () => {
8666
- const chromiumPath = resolveChromium(chromium.executablePath())
8667
- if (chromiumPath === undefined) return
8668
-
8669
- expect(statSync(chromiumPath).isFile()).toBe(true)
8670
- expect(() => accessSync(chromiumPath, FS_CONSTANTS.X_OK)).not.toThrow()
8794
+ it('resolves only a real managed executable or stable system browser channel', () => {
8795
+ const options = resolveBrowser(chromium.executablePath(), process.platform, process.env)
8796
+ let valid = options === undefined
8797
+ if (options !== undefined) {
8798
+ const channel = options.launchOptions?.channel
8799
+ valid =
8800
+ channel === undefined
8801
+ ? isBrowserExecutable(options.launchOptions?.executablePath ?? chromium.executablePath())
8802
+ : SYSTEM_BROWSER_CHANNELS.some((browser) => browser.channel === channel)
8803
+ }
8804
+ expect(valid).toBe(true)
8671
8805
  })` : "";
8672
8806
  const artifacts = [fillArtifact("tests/setup.ts", "tests", "setup", {}), fillArtifact("tests/policy.test.ts", "tests", "policyTest", {
8673
- browserPolicySpecifier,
8807
+ browserPolicySpecifier: "",
8674
8808
  browserPolicyImport,
8675
8809
  browserPolicyTest,
8676
8810
  vuePolicyImport,