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