@gfdelarue/testkit 0.5.0 → 0.6.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 (95) hide show
  1. package/README.md +136 -110
  2. package/dist/app/bugs/draft.js +2 -2
  3. package/dist/app/bugs/guide.js +1 -1
  4. package/dist/app/bugs/record.js +2 -2
  5. package/dist/app/campaigns/index.js +2 -2
  6. package/dist/app/destroy/index.js +1 -1
  7. package/dist/app/discover/index.js +2 -2
  8. package/dist/app/environments/static-values.js +1 -1
  9. package/dist/app/environments/supplied.js +1 -1
  10. package/dist/app/inspect/index.js +1 -1
  11. package/dist/app/lint/native.js +1 -1
  12. package/dist/app/lint/options.js +1 -1
  13. package/dist/app/run/engine-orchestration.js +1 -1
  14. package/dist/app/run/environment.js +1 -1
  15. package/dist/app/run/orchestrator.js +1 -1
  16. package/dist/app/run/request.js +1 -1
  17. package/dist/app/run/run-finalization.js +1 -1
  18. package/dist/app/run/task-batches.js +1 -1
  19. package/dist/app/run/worker-loop.js +1 -1
  20. package/dist/app/status/model.js +1 -1
  21. package/dist/app/status/runs.js +1 -1
  22. package/dist/app/typecheck/index.js +4 -4
  23. package/dist/cli/command-catalog.js +1 -1
  24. package/dist/cli/command-flags.js +1 -1
  25. package/dist/cli/commands/artifacts.js +2 -2
  26. package/dist/cli/commands/discover.js +1 -1
  27. package/dist/cli/commands/guard/unsafe-entrypoint.js +1 -1
  28. package/dist/cli/commands/run.js +2 -2
  29. package/dist/cli/commands/typecheck.js +1 -1
  30. package/dist/cli/commands/workspace.js +1 -1
  31. package/dist/config/authoring/database-fixture-discovery.js +1 -0
  32. package/dist/config/authoring/index.js +1 -1
  33. package/dist/config/loader/config-loader.js +1 -1
  34. package/dist/config/loader/decode-discovery.js +1 -1
  35. package/dist/config/loader/discover-tests.js +1 -1
  36. package/dist/config/loader/discovery.js +2 -2
  37. package/dist/config/loader/file-metadata.js +1 -1
  38. package/dist/config/loader/groups.js +2 -1
  39. package/dist/config/loader/load.js +1 -1
  40. package/dist/config/loader/managed-configs.js +1 -1
  41. package/dist/config/loader/owned-discovery.js +1 -1
  42. package/dist/config/loader/runtime-isolation.js +1 -1
  43. package/dist/config/loader/skip-config.js +1 -1
  44. package/dist/database/admin.js +2 -2
  45. package/dist/database/configured-steps.js +1 -1
  46. package/dist/domain/identifiers.js +1 -1
  47. package/dist/domain/retired-test-types.js +1 -1
  48. package/dist/domain/suite-selection.js +1 -1
  49. package/dist/domain/test-types.js +1 -1
  50. package/dist/engines/catalog.js +1 -1
  51. package/dist/engines/definitions.js +1 -1
  52. package/dist/engines/environment/index.js +1 -1
  53. package/dist/engines/installations/node-vitest.js +1 -1
  54. package/dist/engines/installations/playwright.js +1 -1
  55. package/dist/engines/managed-module-resolution.js +6 -2
  56. package/dist/engines/node-vitest/adapter/adapter.js +1 -1
  57. package/dist/engines/node-vitest/adapter/driver.js +2 -2
  58. package/dist/engines/node-vitest/adapter/evidence.js +2 -2
  59. package/dist/engines/node-vitest/adapter/workspace.js +8 -24
  60. package/dist/engines/node-vitest/inventory/index.js +2 -2
  61. package/dist/engines/node-vitest/inventory/list-files.js +3 -3
  62. package/dist/engines/node-vitest/runtime/data/index.js +1 -1
  63. package/dist/engines/node-vitest/runtime/data/report.js +1 -1
  64. package/dist/engines/node-vitest/runtime/data/scenario.js +2 -2
  65. package/dist/engines/node-vitest/runtime/runtime-input.js +1 -1
  66. package/dist/engines/playwright/adapter/adapter.js +1 -1
  67. package/dist/engines/playwright/adapter/config.js +2 -2
  68. package/dist/engines/playwright/runtime/managed-test.js +1 -0
  69. package/dist/environment/lifecycle.js +1 -1
  70. package/dist/environment/run-environment.js +1 -1
  71. package/dist/evidence/artifact-config.js +1 -1
  72. package/dist/evidence/maintenance.js +1 -0
  73. package/dist/evidence/read-run.js +1 -1
  74. package/dist/kernel/known-keys.js +1 -0
  75. package/dist/process/execa-bundle.json +1 -1
  76. package/dist/repo/fingerprint-policy.js +1 -1
  77. package/dist/steps/configured-step-testkit-modules.js +1 -1
  78. package/dist/toolchains/index.js +2 -2
  79. package/dist/toolchains/vitest/index.js +1 -1
  80. package/dist/types/config.d.ts +7 -86
  81. package/dist/types/environment.d.ts +6 -0
  82. package/dist/types/node-vitest-data.d.ts +2 -218
  83. package/dist/types/workspace.d.ts +27 -11
  84. package/docs/bug-records.md +3 -8
  85. package/docs/engines.md +63 -17
  86. package/docs/release-notes.md +2116 -0
  87. package/docs/test-types.md +19 -69
  88. package/package.json +2 -5
  89. package/testkit-config-schema.md +65 -128
  90. package/dist/app/lint/rules/data.js +0 -1
  91. package/dist/config/authoring/runtime.js +0 -1
  92. package/dist/engines/node-vitest/runtime/contract/index.js +0 -1
  93. package/dist/engines/node-vitest/runtime/data/http.js +0 -1
  94. package/dist/engines/node-vitest/runtime/data/runner.js +0 -1
  95. package/dist/types/node-vitest-contract.d.ts +0 -75
@@ -1 +1 @@
1
- import m from"node:crypto";import{isUnknownRecord as c}from"../../kernel/unknown-values.js";import o from"node:fs";import i from"node:path";import{fileURLToPath as d,pathToFileURL as p}from"node:url";import{buildWithEsbuildRetry as h}from"../../toolchains/esbuild.js";const u=i.resolve(i.dirname(d(import.meta.url)),"..","..",".."),k=i.join(u,"dist","index.js"),j=i.join(u,"dist","config","authoring","index.js"),w=new Set(["/environment","/node-vitest/data","/node-vitest/contract","/playwright"]),y=["testkit.config.ts","testkit.config.mts","testkit.config.mjs","testkit.config.js"];export function findConfigFile(t){for(const n of y){const e=i.join(t,n);if(o.existsSync(e))return e}return null}export async function loadTestkitConfig(t,n={}){const e=findConfigFile(t);if(!e)return{config:{},configFile:null};const r=i.join(t,".testkit","_config");o.mkdirSync(r,{recursive:!0});const s=b(e),a=i.join(r,`config-${s.slice(0,12)}.mjs`);await h({absWorkingDir:t,bundle:!0,entryPoints:[e],format:"esm",legalComments:"none",outfile:a,platform:"node",sourcemap:"inline",target:"es2020",plugins:[C()]});const l=[s,n.importCacheSalt||""].filter(Boolean).join("-"),g=await import(`${p(a).href}?v=${l}`),f=S(g);if(!c(f))throw new Error(`testkit config file must export an object: ${i.basename(e)}`);return{config:f,configFile:e}}function b(t){const n=o.readFileSync(t,"utf8");return m.createHash("sha256").update(t).update("\0").update(n).digest("hex")}function C(){return{name:"testkit-config-alias",setup(t){t.onResolve({filter:/^@gfdelarue\/testkit(?:\/.*)?$/},n=>({namespace:"file",path:E(n.path)}))}}}function E(t){const n=t.slice(18);if(!n)return k;if(n==="/config")return j;throw w.has(n)?new Error(`testkit.config.ts may not import engine authoring module "${t}". Use repository config helpers from @gfdelarue/testkit/config instead.`):new Error(`Unsupported @gfdelarue/testkit import "${t}" while loading testkit config`)}function S(t){if(c(t))return t.default||t.config||{}}
1
+ import g from"node:crypto";import{rejectUnknownKeys as p}from"../../kernel/known-keys.js";import{isUnknownRecord as c}from"../../kernel/unknown-values.js";import r from"node:fs";import i from"node:path";import{fileURLToPath as d,pathToFileURL as h}from"node:url";import{buildWithEsbuildRetry as k}from"../../toolchains/esbuild.js";import{migrationText as b}from"../../domain/retired-test-types.js";const u=i.resolve(i.dirname(d(import.meta.url)),"..","..",".."),w=i.join(u,"dist","index.js"),y=i.join(u,"dist","config","authoring","index.js"),j=new Set(["/environment","/node-vitest/data","/playwright"]),E=["testkit.config.ts","testkit.config.mts","testkit.config.mjs","testkit.config.js"],T=Object.keys({areas:!0,artifacts:!0,bugs:!0,discovery:!0,execution:!0,fingerprints:!0,groups:!0,lint:!0,redact:!0,services:!0,toolchains:!0,ui:!0});export function findConfigFile(t){for(const e of E){const o=i.join(t,e);if(r.existsSync(o))return o}return null}export async function loadTestkitConfig(t,e={}){const o=findConfigFile(t);if(!o)return{config:{},configFile:null};const s=i.join(t,".testkit","_config");r.mkdirSync(s,{recursive:!0});const a=C(o),f=i.join(s,`config-${a.slice(0,12)}.mjs`);await k({absWorkingDir:t,bundle:!0,entryPoints:[o],format:"esm",legalComments:"none",outfile:f,platform:"node",sourcemap:"inline",target:"es2020",plugins:[v()]});const l=[a,e.importCacheSalt||""].filter(Boolean).join("-"),m=await import(`${h(f).href}?v=${l}`),n=x(m);if(!c(n))throw new Error(`testkit config file must export an object: ${i.basename(o)}`);if(Object.hasOwn(n,"profiles"))throw new Error(`${i.basename(o)}: Testkit 0.6.0 removed HTTP profiles; log in from test code. ${b("Remove the data test type")}.`);return p(n,T,i.basename(o)),{config:n,configFile:o}}function C(t){const e=r.readFileSync(t,"utf8");return g.createHash("sha256").update(t).update("\0").update(e).digest("hex")}function v(){return{name:"testkit-config-alias",setup(t){t.onResolve({filter:/^@gfdelarue\/testkit(?:\/.*)?$/},e=>({namespace:"file",path:O(e.path)}))}}}function O(t){const e=t.slice(18);if(!e)return w;if(e==="/config")return y;throw j.has(e)?new Error(`testkit.config.ts may not import engine authoring module "${t}". Use repository config helpers from @gfdelarue/testkit/config instead.`):new Error(`Unsupported @gfdelarue/testkit import "${t}" while loading testkit config`)}function x(t){if(c(t))return t.default||t.config||{}}
@@ -1 +1 @@
1
- import s from"node:path";import{normalizeDiscoveryPath as n}from"./path-policy.js";export function normalizeDiscoveryConfig(r,{allowRoots:t=!0}={}){if(r==null)return{};if(typeof r!="object"||Array.isArray(r))throw new Error("testkit discovery config must be an object");const e={};return t&&Object.prototype.hasOwnProperty.call(r,"roots")&&(e.roots=i(Reflect.get(r,"roots"),"testkit discovery roots")),Object.prototype.hasOwnProperty.call(r,"exclude")&&(e.exclude=i(Reflect.get(r,"exclude"),"testkit discovery exclude")),e}function i(r,t){if(!Array.isArray(r))throw new Error(`${t} must be an array of relative paths`);const e=r.map(o=>{if(typeof o!="string"||o.trim().length===0)throw new Error(`${t} entries must be non-empty strings`);if(s.isAbsolute(o))throw new Error(`${t} entries must be relative paths`);return n(o)}).filter(Boolean);return[...new Set(e)]}
1
+ import s from"node:path";import{rejectUnknownKeys as n}from"../../kernel/known-keys.js";import{normalizeDiscoveryPath as c}from"./path-policy.js";const f=Object.keys({exclude:!0,roots:!0});export function normalizeDiscoveryConfig(t,{allowRoots:r=!0}={}){if(t==null)return{};if(typeof t!="object"||Array.isArray(t))throw new Error("testkit discovery config must be an object");n(t,f,"testkit discovery config");const e={};return r&&Object.prototype.hasOwnProperty.call(t,"roots")&&(e.roots=i(Reflect.get(t,"roots"),"testkit discovery roots")),Object.prototype.hasOwnProperty.call(t,"exclude")&&(e.exclude=i(Reflect.get(t,"exclude"),"testkit discovery exclude")),e}function i(t,r){if(!Array.isArray(t))throw new Error(`${r} must be an array of relative paths`);const e=t.map(o=>{if(typeof o!="string"||o.trim().length===0)throw new Error(`${r} entries must be non-empty strings`);if(s.isAbsolute(o))throw new Error(`${r} entries must be relative paths`);return c(o)}).filter(Boolean);return[...new Set(e)]}
@@ -1 +1 @@
1
- import m from"path";import{randomUUID as j}from"node:crypto";import{isUnknownRecord as D,readDynamicProperty as S}from"../../kernel/unknown-values.js";import{loadConfigContext as P,resolveProductDir as z}from"./load.js";import{hasDiscoveryErrors as B,buildDiscoveryErrorFromDiagnostics as V}from"./discovery.js";import{discoverOwnedTests as _}from"./owned-discovery.js";import{loadTestkitConfig as x}from"./config-loader.js";import{TEST_TYPE_ORDER as A,isRunType as L}from"../../domain/test-types.js";import"../../domain/identifiers.js";import{DEFAULT_RUNTIME_ISOLATION_POLICY as U,resolveRuntimeIsolationPolicy as q}from"./runtime-isolation.js";import{resolveExecutionProfile as Y}from"../../engines/catalog.js";import{readTestFileMetadata as G}from"./file-metadata.js";import{buildDiscoverySummary as K,emptyDiscoverySummary as H}from"./discovery-summary.js";import{matchesSelectedTypes as J,matchesSuiteSelectors as w,normalizeTypeValues as Q,parseSuiteSelectors as W,suiteKey as E}from"../../domain/suite-selection.js";import{errorMessage as b}from"../../kernel/error-serialization.js";const X=10,h=String;export async function discoverTests(e={}){return(await loadDiscoveryContext(e)).discovery}export async function loadDiscoveredConfigContext(e={}){const i=await P({...e,configImportCacheSalt:j()}),{discovery:o}=await loadDiscoveryContext({dir:i.productDir,diagnostics:"error"},i);return{context:i,discovery:o}}export async function loadDiscoveryContext(e={},i){const o=i?.productDir??z(process.cwd(),e.dir),n=se(e);if(i&&n.diagnosticsMode==="error"&&B(i.discovery.diagnostics))throw V(i.discovery.diagnostics);const r=i?{config:i.config,configFile:i.configFile,diagnostics:[]}:await Z(o,n.diagnosticsMode),a={schemaVersion:X,source:"testkit-discovery",product:{name:m.basename(o),directory:o},configFile:r.configFile?m.relative(o,r.configFile)||m.basename(r.configFile):null,filters:{group:n.groupFilter,types:n.typeValues,suiteSelectors:n.suiteSelectors.map(t=>t.raw),fileNames:n.fileNames,runnableOnly:n.runnableOnly,diagnostics:n.diagnosticsMode},groups:[],suites:[],files:[],diagnostics:[...r.diagnostics],summary:H()};if(!r.config)return{discovery:T(a),context:null,notice:null};const c=i?{groups:i.groups,notice:i.ownershipNotice,plainFiles:[],problems:[],project:i.discovery}:await _(o,r.config,{selection:e.vitest,strict:n.diagnosticsMode==="error"}),p=c.project,d={owned:c,fileMetadataByPath:new Map(i?.groups.flatMap(t=>[...t.testkit.fileMetadataByPath])??[])};a.diagnostics.push(...p.diagnostics),re(n.groupFilter,c.groups);let f=i??null;try{f=i??await P({dir:o,preparedDiscovery:d,configContext:{config:r.config,configFile:r.configFile},discoveryOptions:{strict:n.diagnosticsMode==="error"}})}catch(t){if(n.diagnosticsMode==="error")throw t;a.diagnostics.push({code:"config_invalid",severity:"error",message:b(t)})}const y=ie({productDir:o,rawDiscovery:p,declarations:c.groups,groups:f?.groups??[],fileMetadataByPath:d.fileMetadataByPath,filters:n});return{discovery:T({...a,...y}),context:f,notice:c.notice}}export function formatDisplayName(e){return h(e||"").split(/[-_/]+/g).filter(Boolean).map(i=>i.replace(/^\w/,o=>o.toUpperCase())).join(" ")}export function testTypeContract(e,i,o){return Y({engineId:i,profileId:o,testType:e}),e==="data"?{authoringApi:"@gfdelarue/testkit/node-vitest/data",summary:"Data suites use the node-vitest engine and record Testkit data evidence; they are not raw Vitest or service-namespace tests."}:e==="contract"?{authoringApi:"@gfdelarue/testkit/node-vitest/contract",summary:"Contract suites use the node-vitest engine for Node-backed service/runtime contracts without data-flow evidence requirements."}:i==="playwright"?{authoringApi:"@gfdelarue/testkit/playwright"}:{}}async function Z(e,i){try{const o=await x(e),{config:n,configFile:r}=ee(o);return{config:n,configFile:r,diagnostics:[]}}catch(o){if(i==="error")throw o;return{config:null,configFile:null,diagnostics:[{code:"config_invalid",severity:"error",message:b(o)}]}}}function ee(e){if(!D(e))throw new Error("testkit config loader must return an object");const i=S(e,"config"),o=S(e,"configFile");if(!D(i)){const n=typeof o=="string"?m.basename(o):"testkit config file";throw new Error(`testkit config file must export an object: ${n}`)}if(o!==null&&typeof o!="string")throw new Error("testkit config loader returned an invalid config path");return{config:i,configFile:o}}function ie({declarations:e,fileMetadataByPath:i,filters:o,groups:n,productDir:r,rawDiscovery:a}){const c=new Map(n.map(t=>[t.name,t])),p=new Map,d=new Map,f=[];for(const t of e)o.groupFilter&&t.name!==o.groupFilter||p.set(t.name,{name:t.name,roots:[...t.roots],uses:[...t.uses],source:t.source,suiteCount:0,fileCount:0,activeFileCount:0,skippedFileCount:0});for(const t of a.files){const s=t.testType;if(!J(s,o.typeValues)||!w(s,t.suiteName,o.suiteSelectors)||o.groupFilter&&t.serviceName!==o.groupFilter||o.fileNameSet.size>0&&!o.fileNameSet.has(t.filePath))continue;const u=i.get(t.filePath)??G(r,t.filePath);i.set(t.filePath,u);const l=c.get(t.serviceName),g=l?.suites.find(F=>F.testType===s&&F.name===t.suiteName);if(l&&(!g||g.engineId!==u.engineId||g.profileId!==u.profileId))throw new Error(`Normalized suite ${t.serviceName}/${E({name:t.suiteName,testType:s})} conflicts with file ${t.filePath}`);const I=l&&(u.skipReason||l.testkit.skip?.fileReasonByPath.get(t.filePath)||ce(l,s,t.suiteName))||null;if(o.runnableOnly&&I)continue;const O=l?[...new Set([...le(l,s,t.suiteName),...l.testkit.requirements.fileLocksByPath.get(t.filePath)||[],...u.locks])].sort():[],v=oe({groupName:t.serviceName,suiteName:t.suiteName,testType:s,filePath:t.filePath,engineId:u.engineId,profileId:u.profileId,skipReason:I,locks:O,runtimeIsolation:l&&g?q(l,g,t.filePath):U});f.push(v);const k=[t.serviceName,s??"",t.suiteName].join("|"),N=d.get(k);N?N.files.push(v):d.set(k,{groupName:t.serviceName,testType:s,suiteName:t.suiteName,files:[v]})}const y=[...d.values()].map(t=>te(t.groupName,t.testType,t.suiteName,t.files));for(const t of y){const s=p.get(t.group);if(s===void 0)throw new Error(`Discovered suite ${t.id} belongs to no declared group`);s.suiteCount+=1,s.fileCount+=t.fileCount,s.activeFileCount+=t.activeFileCount,s.skippedFileCount+=t.skippedFileCount}return{groups:[...p.values()].sort((t,s)=>t.name.localeCompare(s.name)),suites:y.sort(ue),files:f.sort(pe)}}function oe(e){const i=testTypeContract(e.testType,e.engineId,e.profileId);return{id:R(e.groupName,e.testType,e.engineId,e.profileId,M(e.filePath)),path:e.filePath,displayName:fileDisplayName(e.filePath),group:e.groupName,suiteName:e.suiteName,suiteLabel:formatDisplayName(e.suiteName),...e.testType===void 0?{}:{testType:e.testType},engineId:e.engineId,profileId:e.profileId,skipped:!!e.skipReason,skipReason:e.skipReason,locks:e.locks,runtimeIsolation:e.runtimeIsolation,contract:i}}function te(e,i,o,n){const r=ne(e,i,o,n),a=n.filter(c=>c.skipped).length;return{id:R(e,i,r.engineId,r.profileId,o),group:e,name:o,displayName:formatDisplayName(o),...i===void 0?{}:{testType:i},engineId:r.engineId,profileId:r.profileId,suiteLabel:formatDisplayName(o),fileCount:n.length,activeFileCount:n.length-a,skippedFileCount:a,locks:[...new Set(n.flatMap(c=>c.locks))].sort(),filePaths:n.map(c=>c.path),contract:r.contract}}function ne(e,i,o,n){const r=n[0];for(const a of n.slice(1))if(a.engineId!==r.engineId||a.profileId!==r.profileId)throw new Error(`Discovery suite ${e}/${E({name:o,testType:i})} mixes engine profiles ${r.engineId}/${r.profileId} and ${a.engineId}/${a.profileId}`);return{contract:r.contract,engineId:r.engineId,profileId:r.profileId}}function T(e){return{...e,diagnostics:e.diagnostics.sort(de),summary:K({diagnostics:e.diagnostics,files:e.files,groupCount:e.groups.length,suiteCount:e.suites.length})}}function re(e,i){if(!e||i.some(n=>n.name===e))return;const o=i.map(n=>n.name).sort((n,r)=>n.localeCompare(r)).join(", ");throw new Error(`Group "${e}" not found. Available: ${o}`)}function se(e){const i=ae(Q(C(e.type))),o=W(C(e.suite)),n=C(e.file).map(M);return{typeValues:i,suiteSelectors:o,fileNames:n,fileNameSet:new Set(n),groupFilter:e.group||null,runnableOnly:!!e.runnableOnly,diagnosticsMode:e.diagnostics==="error"?"error":"report"}}function ae(e){const i=[];for(const o of e){if(!L(o))throw new Error(`Discovery produced unsupported test type "${o}".`);i.push(o)}return i}function ce(e,i,o){for(const n of e.testkit.skip?.suites||[])if(w(i,o,[n.selector]))return n.reason;return null}function le(e,i,o){const n=new Set;for(const r of e.testkit.requirements.suites)if(w(i,o,[r.selector]))for(const a of r.locks)n.add(a);return[...n].sort()}function C(e){return Array.isArray(e)?e.filter(o=>o!=null).map(o=>h(o)):e==null||e===""?[]:[h(e)]}function M(e){return h(e).split(m.sep).join("/").replace(/^\.\/+/,"")}export function fileDisplayName(e){const i=new RegExp(`\\.(${A.join("|")})\\.testkit\\.ts$`),o=m.posix.basename(e).replace(i,"");return formatDisplayName(o)}function R(e,i,o,n,r){return[e,...i===void 0?[]:[i],o,n,r].join("|")}function ue(e,i){return e.group.localeCompare(i.group)||(e.testType??"").localeCompare(i.testType??"")||e.name.localeCompare(i.name)}function pe(e,i){return e.group.localeCompare(i.group)||(e.testType??"").localeCompare(i.testType??"")||e.suiteName.localeCompare(i.suiteName)||e.path.localeCompare(i.path)}function de(e,i){return $(e.severity)-$(i.severity)||e.code.localeCompare(i.code)||(e.path||"").localeCompare(i.path||"")}function $(e){return e==="error"?1:2}
1
+ import m from"path";import{randomUUID as j}from"node:crypto";import{isUnknownRecord as S,readDynamicProperty as D}from"../../kernel/unknown-values.js";import{loadConfigContext as P,resolveProductDir as x}from"./load.js";import{hasDiscoveryErrors as z,buildDiscoveryErrorFromDiagnostics as B}from"./discovery.js";import{discoverOwnedTests as _}from"./owned-discovery.js";import{loadTestkitConfig as V}from"./config-loader.js";import{TEST_TYPE_ORDER as L,isRunType as A}from"../../domain/test-types.js";import"../../domain/identifiers.js";import{DEFAULT_RUNTIME_ISOLATION_POLICY as U,resolveRuntimeIsolationPolicy as q}from"./runtime-isolation.js";import{resolveExecutionProfile as Y}from"../../engines/catalog.js";import{readTestFileMetadata as G}from"./file-metadata.js";import{buildDiscoverySummary as K,emptyDiscoverySummary as H}from"./discovery-summary.js";import{matchesSelectedTypes as J,matchesSuiteSelectors as C,normalizeTypeValues as Q,parseSuiteSelectors as W,suiteKey as E}from"../../domain/suite-selection.js";import{errorMessage as T}from"../../kernel/error-serialization.js";const X=10,h=String;export async function discoverTests(e={}){return(await loadDiscoveryContext(e)).discovery}export async function loadDiscoveredConfigContext(e={}){const{group:o,...i}=e,n=await P({...i,configImportCacheSalt:j()}),{discovery:r}=await loadDiscoveryContext({dir:n.productDir,diagnostics:"error",group:o},n);return{context:n,discovery:r}}export async function loadDiscoveryContext(e={},o){const i=o?.productDir??x(process.cwd(),e.dir),n=se(e);if(o&&n.diagnosticsMode==="error"&&z(o.discovery.diagnostics))throw B(o.discovery.diagnostics);const r=o?{config:o.config,configFile:o.configFile,diagnostics:[]}:await Z(i,n.diagnosticsMode),a={schemaVersion:X,source:"testkit-discovery",product:{name:m.basename(i),directory:i},configFile:r.configFile?m.relative(i,r.configFile)||m.basename(r.configFile):null,filters:{group:n.groupFilter,types:n.typeValues,suiteSelectors:n.suiteSelectors.map(t=>t.raw),fileNames:n.fileNames,runnableOnly:n.runnableOnly,diagnostics:n.diagnosticsMode},groups:[],suites:[],files:[],diagnostics:[...r.diagnostics],summary:H()};if(!r.config)return{discovery:b(a),context:null};const c=o?{groups:o.groups,migration:null,plainFiles:[],problems:[],project:o.discovery}:await _(i,r.config,{selection:e.vitest,strict:n.diagnosticsMode==="error"}),p=c.project,f={owned:c,fileMetadataByPath:new Map(o?.groups.flatMap(t=>[...t.testkit.fileMetadataByPath])??[])};a.diagnostics.push(...p.diagnostics),re(n.groupFilter,c.groups);let d=o??null;try{d=o??await P({dir:i,preparedDiscovery:f,configContext:{config:r.config,configFile:r.configFile},discoveryOptions:{strict:n.diagnosticsMode==="error"}})}catch(t){if(n.diagnosticsMode==="error")throw t;a.diagnostics.push({code:"config_invalid",severity:"error",message:T(t)})}const y=oe({productDir:i,rawDiscovery:p,declarations:c.groups,groups:d?.groups??[],fileMetadataByPath:f.fileMetadataByPath,filters:n});return{discovery:b({...a,...y}),context:d}}export function formatDisplayName(e){return h(e||"").split(/[-_/]+/g).filter(Boolean).map(o=>o.replace(/^\w/,i=>i.toUpperCase())).join(" ")}export function testTypeContract(e,o,i){return Y({engineId:o,profileId:i,testType:e}),o==="playwright"?{authoringApi:"@gfdelarue/testkit/playwright"}:{}}async function Z(e,o){try{const i=await V(e),{config:n,configFile:r}=ee(i);return{config:n,configFile:r,diagnostics:[]}}catch(i){if(o==="error")throw i;return{config:null,configFile:null,diagnostics:[{code:"config_invalid",severity:"error",message:T(i)}]}}}function ee(e){if(!S(e))throw new Error("testkit config loader must return an object");const o=D(e,"config"),i=D(e,"configFile");if(!S(o)){const n=typeof i=="string"?m.basename(i):"testkit config file";throw new Error(`testkit config file must export an object: ${n}`)}if(i!==null&&typeof i!="string")throw new Error("testkit config loader returned an invalid config path");return{config:o,configFile:i}}function oe({declarations:e,fileMetadataByPath:o,filters:i,groups:n,productDir:r,rawDiscovery:a}){const c=new Map(n.map(t=>[t.name,t])),p=new Map,f=new Map,d=[];for(const t of e)i.groupFilter&&t.name!==i.groupFilter||p.set(t.name,{name:t.name,roots:[...t.roots],uses:[...t.uses],source:t.source,suiteCount:0,fileCount:0,activeFileCount:0,skippedFileCount:0});for(const t of a.files){const s=t.testType;if(!J(s,i.typeValues)||!C(s,t.suiteName,i.suiteSelectors)||i.groupFilter&&t.serviceName!==i.groupFilter||i.fileNameSet.size>0&&!i.fileNameSet.has(t.filePath))continue;const u=o.get(t.filePath)??G(r,t.filePath);o.set(t.filePath,u);const l=c.get(t.serviceName),g=l?.suites.find(F=>F.testType===s&&F.name===t.suiteName);if(l&&(!g||g.engineId!==u.engineId||g.profileId!==u.profileId))throw new Error(`Normalized suite ${t.serviceName}/${E({name:t.suiteName,testType:s})} conflicts with file ${t.filePath}`);const w=l&&(u.skipReason||l.testkit.skip?.fileReasonByPath.get(t.filePath)||ce(l,s,t.suiteName))||null;if(i.runnableOnly&&w)continue;const O=l?[...new Set([...le(l,s,t.suiteName),...l.testkit.requirements.fileLocksByPath.get(t.filePath)||[],...u.locks])].sort():[],v=ie({groupName:t.serviceName,suiteName:t.suiteName,testType:s,filePath:t.filePath,engineId:u.engineId,profileId:u.profileId,skipReason:w,locks:O,runtimeIsolation:l&&g?q(l,g,t.filePath):U});d.push(v);const k=[t.serviceName,s??"",t.suiteName].join("|"),N=f.get(k);N?N.files.push(v):f.set(k,{groupName:t.serviceName,testType:s,suiteName:t.suiteName,files:[v]})}const y=[...f.values()].map(t=>te(t.groupName,t.testType,t.suiteName,t.files));for(const t of y){const s=p.get(t.group);if(s===void 0)throw new Error(`Discovered suite ${t.id} belongs to no declared group`);s.suiteCount+=1,s.fileCount+=t.fileCount,s.activeFileCount+=t.activeFileCount,s.skippedFileCount+=t.skippedFileCount}return{groups:[...p.values()].sort((t,s)=>t.name.localeCompare(s.name)),suites:y.sort(ue),files:d.sort(pe)}}function ie(e){const o=testTypeContract(e.testType,e.engineId,e.profileId);return{id:R(e.groupName,e.testType,e.engineId,e.profileId,M(e.filePath)),path:e.filePath,displayName:fileDisplayName(e.filePath),group:e.groupName,suiteName:e.suiteName,suiteLabel:formatDisplayName(e.suiteName),...e.testType===void 0?{}:{testType:e.testType},engineId:e.engineId,profileId:e.profileId,skipped:!!e.skipReason,skipReason:e.skipReason,locks:e.locks,runtimeIsolation:e.runtimeIsolation,contract:o}}function te(e,o,i,n){const r=ne(e,o,i,n),a=n.filter(c=>c.skipped).length;return{id:R(e,o,r.engineId,r.profileId,i),group:e,name:i,displayName:formatDisplayName(i),...o===void 0?{}:{testType:o},engineId:r.engineId,profileId:r.profileId,suiteLabel:formatDisplayName(i),fileCount:n.length,activeFileCount:n.length-a,skippedFileCount:a,locks:[...new Set(n.flatMap(c=>c.locks))].sort(),filePaths:n.map(c=>c.path),contract:r.contract}}function ne(e,o,i,n){const r=n[0];for(const a of n.slice(1))if(a.engineId!==r.engineId||a.profileId!==r.profileId)throw new Error(`Discovery suite ${e}/${E({name:i,testType:o})} mixes engine profiles ${r.engineId}/${r.profileId} and ${a.engineId}/${a.profileId}`);return{contract:r.contract,engineId:r.engineId,profileId:r.profileId}}function b(e){return{...e,diagnostics:e.diagnostics.sort(fe),summary:K({diagnostics:e.diagnostics,files:e.files,groupCount:e.groups.length,suiteCount:e.suites.length})}}function re(e,o){if(!e||o.some(n=>n.name===e))return;const i=o.map(n=>n.name).sort((n,r)=>n.localeCompare(r)).join(", ");throw new Error(`Group "${e}" not found. Available: ${i}`)}function se(e){const o=ae(Q(I(e.type))),i=W(I(e.suite)),n=I(e.file).map(M);return{typeValues:o,suiteSelectors:i,fileNames:n,fileNameSet:new Set(n),groupFilter:e.group||null,runnableOnly:!!e.runnableOnly,diagnosticsMode:e.diagnostics==="error"?"error":"report"}}function ae(e){const o=[];for(const i of e){if(!A(i))throw new Error(`Discovery produced unsupported test type "${i}".`);o.push(i)}return o}function ce(e,o,i){for(const n of e.testkit.skip?.suites||[])if(C(o,i,[n.selector]))return n.reason;return null}function le(e,o,i){const n=new Set;for(const r of e.testkit.requirements.suites)if(C(o,i,[r.selector]))for(const a of r.locks)n.add(a);return[...n].sort()}function I(e){return Array.isArray(e)?e.filter(i=>i!=null).map(i=>h(i)):e==null||e===""?[]:[h(e)]}function M(e){return h(e).split(m.sep).join("/").replace(/^\.\/+/,"")}export function fileDisplayName(e){const o=new RegExp(`\\.(${L.join("|")})\\.testkit\\.ts$`),i=m.posix.basename(e).replace(o,"");return formatDisplayName(i)}function R(e,o,i,n,r){return[e,...o===void 0?[]:[o],i,n,r].join("|")}function ue(e,o){return e.group.localeCompare(o.group)||(e.testType??"").localeCompare(o.testType??"")||e.name.localeCompare(o.name)}function pe(e,o){return e.group.localeCompare(o.group)||(e.testType??"").localeCompare(o.testType??"")||e.suiteName.localeCompare(o.suiteName)||e.path.localeCompare(o.path)}function fe(e,o){return $(e.severity)-$(o.severity)||e.code.localeCompare(o.code)||(e.path||"").localeCompare(o.path||"")}function $(e){return e==="error"?1:2}
@@ -1,2 +1,2 @@
1
- import k from"fs";import y from"path";import{recordWork as v}from"../../kernel/work-metrics.js";import{DEFAULT_DISCOVERY_EXCLUDES as F,normalizeDiscoveryPath as C,resolveDiscoveryRoots as D,shouldExcludeDiscoveryPath as E}from"./path-policy.js";const P=String,w="__testkit__",N=/\.(?:test|spec)\.[cm]?[jt]sx?$/u,$=[{suffix:".contract.testkit.ts",testType:"contract"},{suffix:".data.testkit.ts",testType:"data"},{suffix:".ui.testkit.ts",testType:"ui"}];export function scanTestFiles(o,e={}){const i=[],n=[],t=[],u=[...new Set([...F,...(e.exclude||[]).map(b)])],l=D(o,e.roots,".");for(;l.length>0;){const c=l.pop();if(c!==void 0){v("discovery.directoriesRead");for(const s of k.readdirSync(c,{withFileTypes:!0})){v("discovery.entriesVisited");const r=y.join(c,s.name);if(s.isSymbolicLink())continue;if(s.isDirectory()){const p=C(y.relative(o,r));if(E(p,u))continue;l.push(r);continue}if(!s.isFile())continue;const a=b(y.relative(o,r));if(!x(a)){a.endsWith(".testkit.ts")&&t.push(a);continue}a.split("/").includes(w)?i.push(a):n.push(a)}}}return{suiteFiles:i.sort((c,s)=>c.localeCompare(s)),legacyFiles:n.sort((c,s)=>c.localeCompare(s)),untypedFiles:t.sort((c,s)=>c.localeCompare(s))}}export function buildDiscoveryProject(o,e,i={}){const n=i.strict!==!1,t=I(o),u=new Map;for(const s of[...e].sort((r,a)=>r.filePath.localeCompare(a.filePath))){const r=s.filePath;if(s.kind==="ambiguous"){const g=[...s.owners].sort((S,T)=>S.localeCompare(T));t.push({code:"ambiguous_test",severity:"error",message:`Ambiguous test file: ${r} -> ${g.join(", ")}`,path:r,groupNames:g});continue}const a=x(r),p=`${s.owner}|${a?.testType??""}`,m=u.get(p)??new Map;u.set(p,m);const f=L(s.relativePath,a),d=f.join("/"),h=a===null?{}:{testType:a.testType},_=m.get(d)??{_suiteKey:d,_suitePath:f,serviceName:s.owner,suite:{files:[],name:"",...h},type:h};m.set(d,_),_.suite.files.push(r)}if(n&&hasDiscoveryErrors(t))throw buildDiscoveryErrorFromDiagnostics(t);const l=[],c={};for(const s of u.values()){const r=j([...s.values()]);for(const{_suiteKey:a,_suitePath:p,serviceName:m,suite:f,type:d}of s.values()){f.name=K(r,a),f.files.sort((h,_)=>h.localeCompare(_));for(const h of f.files)l.push({...d,filePath:h,serviceName:m,suiteName:f.name,suitePath:[...p]});(c[m]??=[]).push(f)}}for(const s of Object.values(c))s?.sort((r,a)=>(r.testType??"").localeCompare(a.testType??"")||r.name.localeCompare(a.name));return{suitesByService:c,files:l.sort((s,r)=>s.serviceName.localeCompare(r.serviceName)||(s.testType??"").localeCompare(r.testType??"")||s.suiteName.localeCompare(r.suiteName)||s.filePath.localeCompare(r.filePath)),diagnostics:t}}export function hasDiscoveryErrors(o=[]){return o.some(e=>e?.severity==="error")}export function buildDiscoveryErrorFromDiagnostics(o=[]){const e=["Filesystem discovery failed for one or more .testkit.ts files."],i=o.filter(t=>t.code==="legacy_path").map(t=>t.path),n=o.filter(t=>t.code==="ambiguous_test");if(i.length>0){e.push(""),e.push("Legacy test files outside __testkit__:");for(const t of i)e.push(`- ${t}`)}if(n.length>0){e.push(""),e.push("Ambiguous test files:");for(const t of n)e.push(`- ${t.path} -> ${t.groupNames.join(", ")}`)}return e.push(""),e.push('Expected test files to live under a "__testkit__" directory within a group root.'),new Error(e.join(`
2
- `))}function j(o){const e=new Map,i=new Map;for(const n of o){const t=n._suitePath;let u="";for(let l=1;l<=t.length;l+=1){u=t.slice(-l).join("-");const c=e.get(u);if(!c){e.set(u,n._suiteKey);break}if(c===n._suiteKey)break;u=""}u||(u=t.join("-")),i.set(n._suiteKey,u)}if(new Set(i.values()).size!==o.length){const n=new Set;for(const t of o){const u=t._suitePath.join("-");let l=u;for(let c=2;n.has(l);c+=1)l=`${u}-${P(c)}`;n.add(l),i.set(t._suiteKey,l)}}return i}function K(o,e){const i=o.get(e);if(i===void 0)throw new Error(`Discovery suite name was not resolved for "${e}"`);return i}function L(o,e){const i=o.split("/").slice(0,-1);if(e===null)return i.length>0?i:[y.posix.basename(o).replace(N,"")];const n=i.indexOf(w),t=n===-1?[]:[...i.slice(0,n),...i.slice(n+1,n+2)];return t.length>0?t:[y.posix.basename(o,e.suffix)]}function x(o){return $.find(e=>o.endsWith(e.suffix))||null}function b(o){const e=P(o).split(y.sep).join("/");return e==="."||e==="./"?".":e.replace(/^\.\/+/,"").replace(/\/+$/,"")||"."}function I(o){return o.map(e=>({code:"legacy_path",severity:"error",message:`Legacy test file outside __testkit__: ${e}`,path:e}))}
1
+ import T from"fs";import y from"path";import{recordWork as v}from"../../kernel/work-metrics.js";import{DEFAULT_DISCOVERY_EXCLUDES as k,normalizeDiscoveryPath as C,resolveDiscoveryRoots as D,shouldExcludeDiscoveryPath as E}from"./path-policy.js";const P=String,w="__testkit__";export const PLAIN_TEST_SUFFIX=/\.(?:test|spec)\.[cm]?[jt]sx?$/u;const N=[{suffix:".ui.testkit.ts",testType:"ui"}];export function scanTestFiles(o,e={}){const i=[],n=[],t=[],u=[...new Set([...k,...(e.exclude||[]).map(x)])],l=D(o,e.roots,".");for(;l.length>0;){const c=l.pop();if(c!==void 0){v("discovery.directoriesRead");for(const s of T.readdirSync(c,{withFileTypes:!0})){v("discovery.entriesVisited");const r=y.join(c,s.name);if(s.isSymbolicLink())continue;if(s.isDirectory()){const p=C(y.relative(o,r));if(E(p,u))continue;l.push(r);continue}if(!s.isFile())continue;const a=x(y.relative(o,r));if(!b(a)){a.endsWith(".testkit.ts")&&t.push(a);continue}a.split("/").includes(w)?i.push(a):n.push(a)}}}return{suiteFiles:i.sort((c,s)=>c.localeCompare(s)),legacyFiles:n.sort((c,s)=>c.localeCompare(s)),untypedFiles:t.sort((c,s)=>c.localeCompare(s))}}export function buildDiscoveryProject(o,e,i={}){const n=i.strict!==!1,t=L(o),u=new Map;for(const s of[...e].sort((r,a)=>r.filePath.localeCompare(a.filePath))){const r=s.filePath;if(s.kind==="ambiguous"){const g=[...s.owners].sort((S,F)=>S.localeCompare(F));t.push({code:"ambiguous_test",severity:"error",message:`Ambiguous test file: ${r} -> ${g.join(", ")}`,path:r,groupNames:g});continue}const a=b(r),p=`${s.owner}|${a?.testType??""}`,m=u.get(p)??new Map;u.set(p,m);const f=K(s.relativePath,a),d=f.join("/"),h=a===null?{}:{testType:a.testType},_=m.get(d)??{_suiteKey:d,_suitePath:f,serviceName:s.owner,suite:{files:[],name:"",...h},type:h};m.set(d,_),_.suite.files.push(r)}if(n&&hasDiscoveryErrors(t))throw buildDiscoveryErrorFromDiagnostics(t);const l=[],c={};for(const s of u.values()){const r=$([...s.values()]);for(const{_suiteKey:a,_suitePath:p,serviceName:m,suite:f,type:d}of s.values()){f.name=j(r,a),f.files.sort((h,_)=>h.localeCompare(_));for(const h of f.files)l.push({...d,filePath:h,serviceName:m,suiteName:f.name,suitePath:[...p]});(c[m]??=[]).push(f)}}for(const s of Object.values(c))s?.sort((r,a)=>(r.testType??"").localeCompare(a.testType??"")||r.name.localeCompare(a.name));return{suitesByService:c,files:l.sort((s,r)=>s.serviceName.localeCompare(r.serviceName)||(s.testType??"").localeCompare(r.testType??"")||s.suiteName.localeCompare(r.suiteName)||s.filePath.localeCompare(r.filePath)),diagnostics:t}}export function hasDiscoveryErrors(o=[]){return o.some(e=>e?.severity==="error")}export function buildDiscoveryErrorFromDiagnostics(o=[]){const e=["Filesystem discovery failed for one or more .testkit.ts files."],i=o.filter(t=>t.code==="legacy_path").map(t=>t.path),n=o.filter(t=>t.code==="ambiguous_test");if(i.length>0){e.push(""),e.push("Legacy test files outside __testkit__:");for(const t of i)e.push(`- ${t}`)}if(n.length>0){e.push(""),e.push("Ambiguous test files:");for(const t of n)e.push(`- ${t.path} -> ${t.groupNames.join(", ")}`)}return e.push(""),e.push('Expected test files to live under a "__testkit__" directory within a group root.'),new Error(e.join(`
2
+ `))}function $(o){const e=new Map,i=new Map;for(const n of o){const t=n._suitePath;let u="";for(let l=1;l<=t.length;l+=1){u=t.slice(-l).join("-");const c=e.get(u);if(!c){e.set(u,n._suiteKey);break}if(c===n._suiteKey)break;u=""}u||(u=t.join("-")),i.set(n._suiteKey,u)}if(new Set(i.values()).size!==o.length){const n=new Set;for(const t of o){const u=t._suitePath.join("-");let l=u;for(let c=2;n.has(l);c+=1)l=`${u}-${P(c)}`;n.add(l),i.set(t._suiteKey,l)}}return i}function j(o,e){const i=o.get(e);if(i===void 0)throw new Error(`Discovery suite name was not resolved for "${e}"`);return i}function K(o,e){const i=o.split("/").slice(0,-1);if(e===null)return i.length>0?i:[y.posix.basename(o).replace(PLAIN_TEST_SUFFIX,"")];const n=i.indexOf(w),t=n===-1?[]:[...i.slice(0,n),...i.slice(n+1,n+2)];return t.length>0?t:[y.posix.basename(o,e.suffix)]}function b(o){return N.find(e=>o.endsWith(e.suffix))||null}function x(o){const e=P(o).split(y.sep).join("/");return e==="."||e==="./"?".":e.replace(/^\.\/+/,"").replace(/\/+$/,"")||"."}function L(o){return o.map(e=>({code:"legacy_path",severity:"error",message:`Legacy test file outside __testkit__: ${e}`,path:e}))}
@@ -1 +1 @@
1
- import{isWorkObserved as $,recordWork as L}from"../../kernel/work-metrics.js";import{retiredEngineMessage as A}from"../../domain/retired-test-types.js";import{isPathWithin as h}from"../../process/files/paths.js";import g from"fs";import S from"path";import{createRequire as B}from"node:module";import{fileURLToPath as O}from"node:url";import{ENGINE_IDS as j,PUBLIC_TEST_TYPES as K,isEngineId as V}from"../../domain/identifiers.js";import{normalizeRuntimeIsolationPolicy as W}from"../../domain/runtime-isolation-contract.js";import{resolveEngineProfile as _,allowsEngineAuthoringImport as z,expectedEngineAuthoringModule as U,invalidEngineAuthoringImport as G,requireMatchingEngineImport as J}from"../../engines/catalog.js";const Y=B(import.meta.url),H=O(import.meta.resolve("typescript"));let Q;function a(){return Q??=Y(H)}const I="@gfdelarue/testkit";export function loadTestFileMetadataMap(e,t={},i=new Map){for(const r of Object.values(t||{}).flatMap(s=>s??[]))for(const s of r.files||[])i.has(s)||i.set(s,readTestFileMetadata(e,s));return i}export function readTestFileMetadata(e,t){const i=K.find(f=>t.endsWith(`.${f}.testkit.ts`));if(i===void 0)return{engineId:"node-vitest",locks:[],profileId:"node-vitest",runtimeIsolation:null,skipReason:null};const r=a(),s=ke(e,t);let n;if($()){const f=g.readFileSync(s);L("metadata.filesRead",1),L("metadata.bytesRead",f.byteLength),n=f.toString("utf8")}else n=g.readFileSync(s,"utf8");const o=r.createSourceFile(s,n,r.ScriptTarget.Latest,!0,r.ScriptKind.TS),l=Ee(o);if(l.length>0){const f=l.map(d=>r.flattenDiagnosticMessageText(d.messageText," ")).join("; ");throw c(t,`TypeScript syntax is invalid: ${f}`)}let p=null,u=null;for(const f of o.statements)if(!(!r.isVariableStatement(f)||!re(f))){for(const d of f.declarationList.declarations)if(!(!r.isIdentifier(d.name)||d.name.text!=="testkit")){if(p)throw c(t,'exported "testkit" metadata must be declared exactly once');if(!ne(f))throw c(t,'exported "testkit" metadata must be const');p=d,u=f}}if(!p||!u)throw c(t,'export const testkit = defineFile({ engine: "..." }) is required');oe(o,u,t);const m=X(p.initializer,t,i);return requireMatchingEngineAuthoringImports(o,t,m),m}function X(e,t,i){const r=a();if(!e)throw c(t,'exported "testkit" metadata has no value');if(!r.isCallExpression(e))throw c(t,"testkit metadata must use defineFile({ ... })");if(ie(e.expression)!=="defineFile")throw c(t,"testkit metadata must call defineFile");if(e.arguments.length!==1)throw c(t,"defineFile metadata must receive exactly one object literal");const s=e.arguments[0];if(!r.isObjectLiteralExpression(s))throw c(t,"defineFile metadata must receive an object literal");return Z(s,t,i)}function Z(e,t,i){const r=a(),s={locks:[],runtimeIsolation:null,skipReason:null};let n=null;const o=new Set;for(const u of e.properties){if(r.isSpreadAssignment(u))throw c(t,"testkit metadata may not contain spread properties");if(!r.isPropertyAssignment(u))throw c(t,"testkit metadata properties must use key: value assignments");const m=b(u.name,t,"testkit metadata");if(!se(m))throw c(t,`unknown metadata property "${m}"`);if(o.has(m))throw c(t,`duplicate metadata property "${m}"`);if(o.add(m),m==="engine"){n=k(u.initializer,t,'metadata property "engine"');continue}if(m==="locks"){s.locks=P(u.initializer,t);continue}if(m==="skip"){s.skipReason=ee(u.initializer,t);continue}s.runtimeIsolation=te(u.initializer,t)}if(!n)throw c(t,'a literal "engine" property is required');const l=A(n);if(l!==null)throw c(t,l);if(!V(n))throw c(t,`unsupported engine "${n}"; expected one of: ${j.join(", ")}`);let p;try{p=_(i,n)}catch(u){throw c(t,R(u),u)}return{engineId:n,locks:s.locks,profileId:p.id,runtimeIsolation:s.runtimeIsolation,skipReason:s.skipReason}}function P(e,t){const i=a();if(!i.isArrayLiteralExpression(e))throw c(t,'metadata property "locks" must be an array');const r=[];for(const s of e.elements){if(i.isSpreadElement(s)||i.isOmittedExpression(s))throw c(t,'metadata property "locks" must contain only string literals');r.push(D(s,t,'metadata property "locks" entry'))}return[...new Set(r)].sort()}function ee(e,t){const i=a(),r=x(e);if(r!==null)return F(r,t,'metadata property "skip"').trim();if(!i.isObjectLiteralExpression(e))throw c(t,'metadata property "skip" must be a string literal or { reason: string literal }');if(e.properties.length!==1)throw c(t,'metadata property "skip" object must contain exactly one reason property');const s=e.properties[0];if(!i.isPropertyAssignment(s))throw c(t,'metadata property "skip" reason must use a string-literal assignment');if(b(s.name,t,'metadata property "skip"')!=="reason")throw c(t,'metadata property "skip" object only accepts "reason"');return D(s.initializer,t,'metadata property "skip.reason"')}function te(e,t){const i=k(e,t,'metadata property "runtimeIsolation"');try{return W(i,"defineFile runtimeIsolation")}catch(r){throw c(t,R(r),r)}}function ie(e){return a().isIdentifier(e)?e.text:null}function b(e,t,i){const r=a();if(r.isIdentifier(e)||r.isStringLiteral(e))return e.text;throw c(t,`${i} property names must be plain identifiers or string literals`)}function x(e){const t=a();return t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e)?e.text:null}function k(e,t,i){const r=x(e);if(r===null)throw c(t,`${i} must be a string literal`);return F(r,t,i)}function D(e,t,i){return k(e,t,i).trim()}function F(e,t,i){if(e.trim().length===0)throw c(t,`${i} must not be empty`);return e}function re(e){const t=a();return(e.modifiers||[]).some(i=>i.kind===t.SyntaxKind.ExportKeyword)}function ne(e){const t=a();return(e.declarationList.flags&t.NodeFlags.Const)!==0}function se(e){return["engine","locks","runtimeIsolation","skip"].includes(e)}function oe(e,t,i){const r=a();let s=null,n=0;for(const o of e.statements){if(r.isImportDeclaration(o)){const l=o.importClause;if(!l||l.phaseModifier===r.SyntaxKind.TypeKeyword)continue;l.name?.text==="defineFile"&&(n+=1);const p=l.namedBindings;if(p&&r.isNamespaceImport(p)){p.name.text==="defineFile"&&(n+=1);continue}if(!p||!r.isNamedImports(p))continue;for(const u of p.elements){if(u.isTypeOnly||u.name.text!=="defineFile")continue;if(n+=1,(u.propertyName?.text??u.name.text)==="defineFile"&&r.isStringLiteral(o.moduleSpecifier)&&o.moduleSpecifier.text===I){if(s)throw c(i,`defineFile must have exactly one named value import from "${I}"`);s=o}}continue}n+=Ie(o)}if(!s)throw c(i,`defineFile must be a named value import from "${I}"`);if(n!==1)throw c(i,"the imported defineFile binding may not be shadowed or redeclared");if(s.getStart(e)>t.getStart(e))throw c(i,"the defineFile import must precede metadata")}export function requireMatchingEngineAuthoringImports(e,t,i){ae(e,t,i);const r=U(i.profileId);for(const s of collectModuleSpecifiers(e))if(J(t,i,s),!z(r,s))throw G(t,i,s,r)}function ae(e,t,i){const r=a(),s=q(e),n=o=>{if(ce(o,t,i),le(o,s,t,i),r.isCallExpression(o)&&T(o,s)){const l=o.arguments.at(0);if(!l||!r.isStringLiteralLike(l)){const p=o.expression.kind===r.SyntaxKind.ImportKeyword?"import()":"require()";throw new Error(`Invalid Testkit authoring in "${t}": ${i.engineId}/${i.profileId} may not use non-literal ${p}; module specifiers must be string literals so engine boundaries can be verified`)}}for(const l of C(o))n(l);r.forEachChild(o,n)};n(e)}function T(e,t){const i=a();return e.expression.kind===i.SyntaxKind.ImportKeyword||i.isIdentifier(e.expression)&&e.expression.text==="require"&&!E(e.expression,t)}function ce(e,t,i){const r=a();if(r.isImportDeclaration(e)){const n=e.importClause?.namedBindings;if(n&&r.isNamedImports(n)&&n.elements.find(l=>!l.isTypeOnly&&(l.propertyName?.text??l.name.text)==="createRequire"))throw y(t,i,"createRequire")}if(r.isPropertyAccessExpression(e)&&e.name.text==="createRequire"||r.isElementAccessExpression(e)&&x(e.argumentExpression)==="createRequire"||r.isBindingElement(e)&&(fe(e)==="createRequire"||r.isIdentifier(e.name)&&e.name.text==="createRequire"))throw y(t,i,"createRequire")}function le(e,t,i,r){const s=a();if(s.isPropertyAccessExpression(e)&&e.name.text==="require"&&v(e.expression,t)||s.isElementAccessExpression(e)&&x(e.argumentExpression)==="require"&&v(e.expression,t))throw y(i,r,e.getText());if(s.isIdentifier(e)&&e.text==="require"&&!E(e,t)&&!ue(e)&&!pe(e))throw y(i,r,"global require")}function y(e,t,i){return new Error(`Invalid Testkit authoring in "${e}": ${t.engineId}/${t.profileId} may not alias or indirectly access the module loader through ${i}; use literal import and require specifiers so engine boundaries can be verified`)}function v(e,t){return a().isIdentifier(e)&&(e.text==="globalThis"||e.text==="module")&&!E(e,t)}function ue(e){return a().isCallExpression(e.parent)&&e.parent.expression===e}function pe(e){const t=a(),i=e.parent;return me(e)||t.isPropertyAccessExpression(i)&&i.name===e||t.isPropertyAssignment(i)&&i.name===e||t.isImportSpecifier(i)&&i.propertyName===e||t.isExportSpecifier(i)&&i.parent.parent.moduleSpecifier!==void 0}function me(e){const t=a(),i=e.parent;return i.name===e&&!t.isShorthandPropertyAssignment(i)&&!t.isExportSpecifier(i)}function fe(e){const t=a(),i=e.propertyName;return i&&(t.isIdentifier(i)||t.isStringLiteralLike(i))?i.text:null}function q(e){const t=a(),i=new WeakMap,r=(n,o)=>{let l=i.get(n);l||(l=new Set,i.set(n,l)),N(l,o)},s=n=>{if(t.isImportDeclaration(n))de(n,e,r);else if(t.isImportEqualsDeclaration(n)&&!n.isTypeOnly)r(e,n.name);else if(t.isVariableDeclaration(n)){const o=n.parent,l=t.isVariableDeclarationList(o)&&(o.flags&t.NodeFlags.BlockScoped)===0?ge(n):w(n);r(l,n.name)}else t.isParameter(n)?r(ye(n),n.name):t.isFunctionDeclaration(n)&&n.name?r(w(n),n.name):t.isFunctionExpression(n)&&n.name?r(n,n.name):(t.isClassDeclaration(n)||t.isEnumDeclaration(n)||t.isModuleDeclaration(n))&&n.name&&t.isIdentifier(n.name)?r(w(n),n.name):t.isClassExpression(n)&&n.name?r(n,n.name):t.isCatchClause(n)&&n.variableDeclaration&&r(n,n.variableDeclaration.name);t.forEachChild(n,s)};return s(e),i}function de(e,t,i){const r=a(),s=e.importClause;if(!s||s.phaseModifier===r.SyntaxKind.TypeKeyword)return;s.name&&i(t,s.name);const n=s.namedBindings;if(n&&r.isNamespaceImport(n))i(t,n.name);else if(n&&r.isNamedImports(n))for(const o of n.elements)o.isTypeOnly||i(t,o.name)}function N(e,t){const i=a();if(i.isIdentifier(t)){e.add(t.text);return}for(const r of t.elements)i.isOmittedExpression(r)||N(e,r.name)}function E(e,t){const i=a();let r=e;for(;;){if(t.get(r)?.has(e.text))return!0;if(i.isSourceFile(r))return!1;r=r.parent}}function w(e){let t=e.parent;for(;;){if(xe(t))return t;t=t.parent}}function ge(e){const t=a();let i=e.parent;for(;;){if(t.isFunctionLike(i)||t.isSourceFile(i))return i;i=i.parent}}function ye(e){const t=a();let i=e.parent;for(;;){if(t.isFunctionLike(i)||t.isSourceFile(i))return i;i=i.parent}}function xe(e){const t=a();return t.isSourceFile(e)||t.isBlock(e)||t.isCaseBlock(e)||t.isCatchClause(e)||t.isForStatement(e)||t.isForInStatement(e)||t.isForOfStatement(e)||t.isFunctionLike(e)||t.isClassStaticBlockDeclaration(e)}export function collectModuleSpecifiers(e){const t=a(),i=new Set,r=q(e),s=n=>{const o=Se(n,r);o&&i.add(o);for(const l of C(n))s(l);t.forEachChild(n,s)};return s(e),i}function C(e){return e.jsDoc??[]}function Se(e,t){const i=a();if(i.isJSDocImportTag(e)&&i.isStringLiteralLike(e.moduleSpecifier)||(i.isImportDeclaration(e)||i.isExportDeclaration(e))&&e.moduleSpecifier&&i.isStringLiteralLike(e.moduleSpecifier))return e.moduleSpecifier.text;if(i.isImportEqualsDeclaration(e)&&i.isExternalModuleReference(e.moduleReference)&&i.isStringLiteralLike(e.moduleReference.expression))return e.moduleReference.expression.text;if(i.isImportTypeNode(e)){const s=e.argument;if(i.isLiteralTypeNode(s)&&i.isStringLiteralLike(s.literal))return s.literal.text}if(!i.isCallExpression(e)||!T(e,t))return null;const r=e.arguments[0];return i.isStringLiteralLike(r)?r.text:null}function Ie(e){const t=a();return t.isVariableStatement(e)?e.declarationList.declarations.reduce((i,r)=>i+M(r.name,"defineFile"),0):(t.isClassDeclaration(e)||t.isEnumDeclaration(e)||t.isFunctionDeclaration(e))&&e.name?.text==="defineFile"?1:0}function M(e,t){const i=a();return i.isIdentifier(e)?e.text===t?1:0:e.elements.reduce((r,s)=>i.isOmittedExpression(s)?r:r+M(s.name,t),0)}function ke(e,t){const i=S.resolve(e);if(S.isAbsolute(t))throw new Error(`Test file metadata path must be product-relative: ${t}`);const r=S.resolve(i,t);if(!h(i,r))throw new Error(`Test file metadata path escapes the product root: ${t}`);if(!g.existsSync(r))throw new Error(`Test file metadata source does not exist: ${t}`);const s=g.realpathSync(i),n=g.realpathSync(r);if(!h(s,n))throw new Error(`Test file metadata path resolves outside the product root: ${t}`);return n}function Ee(e){return e.parseDiagnostics??[]}function c(e,t,i){return new Error(`Invalid testkit metadata in "${e}": ${t}.`,{cause:i})}function R(e){return e instanceof Error?e.message.replace(/\.$/u,""):String(e)}
1
+ import{isWorkObserved as $,recordWork as L}from"../../kernel/work-metrics.js";import{isPathWithin as h}from"../../process/files/paths.js";import d from"fs";import S from"path";import{createRequire as A}from"node:module";import{fileURLToPath as B}from"node:url";import{ENGINE_IDS as O,PUBLIC_TEST_TYPES as j,isEngineId as K}from"../../domain/identifiers.js";import{normalizeRuntimeIsolationPolicy as V}from"../../domain/runtime-isolation-contract.js";import{resolveEngineProfile as W,allowsEngineAuthoringImport as _,expectedEngineAuthoringModule as z,invalidEngineAuthoringImport as U,requireMatchingEngineImport as G}from"../../engines/catalog.js";const J=A(import.meta.url),Y=B(import.meta.resolve("typescript"));let H;function a(){return H??=J(Y)}const I="@gfdelarue/testkit";export function loadTestFileMetadataMap(e,t={},i=new Map){for(const r of Object.values(t||{}).flatMap(s=>s??[]))for(const s of r.files||[])i.has(s)||i.set(s,readTestFileMetadata(e,s));return i}export function readTestFileMetadata(e,t){const i=j.find(m=>t.endsWith(`.${m}.testkit.ts`));if(i===void 0)return{engineId:"node-vitest",locks:[],profileId:"node-vitest",runtimeIsolation:null,skipReason:null};const r=a(),s=Ie(e,t);let n;if($()){const m=d.readFileSync(s);L("metadata.filesRead",1),L("metadata.bytesRead",m.byteLength),n=m.toString("utf8")}else n=d.readFileSync(s,"utf8");const o=r.createSourceFile(s,n,r.ScriptTarget.Latest,!0,r.ScriptKind.TS),l=ke(o);if(l.length>0){const m=l.map(f=>r.flattenDiagnosticMessageText(f.messageText," ")).join("; ");throw c(t,`TypeScript syntax is invalid: ${m}`)}let u=null,p=null;for(const m of o.statements)if(!(!r.isVariableStatement(m)||!ie(m))){for(const f of m.declarationList.declarations)if(!(!r.isIdentifier(f.name)||f.name.text!=="testkit")){if(u)throw c(t,'exported "testkit" metadata must be declared exactly once');if(!re(m))throw c(t,'exported "testkit" metadata must be const');u=f,p=m}}if(!u||!p)throw c(t,'export const testkit = defineFile({ engine: "..." }) is required');se(o,p,t);const x=Q(u.initializer,t,i);return requireMatchingEngineAuthoringImports(o,t,x),x}function Q(e,t,i){const r=a();if(!e)throw c(t,'exported "testkit" metadata has no value');if(!r.isCallExpression(e))throw c(t,"testkit metadata must use defineFile({ ... })");if(te(e.expression)!=="defineFile")throw c(t,"testkit metadata must call defineFile");if(e.arguments.length!==1)throw c(t,"defineFile metadata must receive exactly one object literal");const s=e.arguments[0];if(!r.isObjectLiteralExpression(s))throw c(t,"defineFile metadata must receive an object literal");return X(s,t,i)}function X(e,t,i){const r=a(),s={locks:[],runtimeIsolation:null,skipReason:null};let n=null;const o=new Set;for(const u of e.properties){if(r.isSpreadAssignment(u))throw c(t,"testkit metadata may not contain spread properties");if(!r.isPropertyAssignment(u))throw c(t,"testkit metadata properties must use key: value assignments");const p=b(u.name,t,"testkit metadata");if(!ne(p))throw c(t,`unknown metadata property "${p}"`);if(o.has(p))throw c(t,`duplicate metadata property "${p}"`);if(o.add(p),p==="engine"){n=k(u.initializer,t,'metadata property "engine"');continue}if(p==="locks"){s.locks=Z(u.initializer,t);continue}if(p==="skip"){s.skipReason=P(u.initializer,t);continue}s.runtimeIsolation=ee(u.initializer,t)}if(!n)throw c(t,'a literal "engine" property is required');if(!K(n))throw c(t,`unsupported engine "${n}"; expected one of: ${O.join(", ")}`);let l;try{l=W(i,n)}catch(u){throw c(t,M(u),u)}return{engineId:n,locks:s.locks,profileId:l.id,runtimeIsolation:s.runtimeIsolation,skipReason:s.skipReason}}function Z(e,t){const i=a();if(!i.isArrayLiteralExpression(e))throw c(t,'metadata property "locks" must be an array');const r=[];for(const s of e.elements){if(i.isSpreadElement(s)||i.isOmittedExpression(s))throw c(t,'metadata property "locks" must contain only string literals');r.push(D(s,t,'metadata property "locks" entry'))}return[...new Set(r)].sort()}function P(e,t){const i=a(),r=y(e);if(r!==null)return F(r,t,'metadata property "skip"').trim();if(!i.isObjectLiteralExpression(e))throw c(t,'metadata property "skip" must be a string literal or { reason: string literal }');if(e.properties.length!==1)throw c(t,'metadata property "skip" object must contain exactly one reason property');const s=e.properties[0];if(!i.isPropertyAssignment(s))throw c(t,'metadata property "skip" reason must use a string-literal assignment');if(b(s.name,t,'metadata property "skip"')!=="reason")throw c(t,'metadata property "skip" object only accepts "reason"');return D(s.initializer,t,'metadata property "skip.reason"')}function ee(e,t){const i=k(e,t,'metadata property "runtimeIsolation"');try{return V(i,"defineFile runtimeIsolation")}catch(r){throw c(t,M(r),r)}}function te(e){return a().isIdentifier(e)?e.text:null}function b(e,t,i){const r=a();if(r.isIdentifier(e)||r.isStringLiteral(e))return e.text;throw c(t,`${i} property names must be plain identifiers or string literals`)}function y(e){const t=a();return t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e)?e.text:null}function k(e,t,i){const r=y(e);if(r===null)throw c(t,`${i} must be a string literal`);return F(r,t,i)}function D(e,t,i){return k(e,t,i).trim()}function F(e,t,i){if(e.trim().length===0)throw c(t,`${i} must not be empty`);return e}function ie(e){const t=a();return(e.modifiers||[]).some(i=>i.kind===t.SyntaxKind.ExportKeyword)}function re(e){const t=a();return(e.declarationList.flags&t.NodeFlags.Const)!==0}function ne(e){return["engine","locks","runtimeIsolation","skip"].includes(e)}function se(e,t,i){const r=a();let s=null,n=0;for(const o of e.statements){if(r.isImportDeclaration(o)){const l=o.importClause;if(!l||l.phaseModifier===r.SyntaxKind.TypeKeyword)continue;l.name?.text==="defineFile"&&(n+=1);const u=l.namedBindings;if(u&&r.isNamespaceImport(u)){u.name.text==="defineFile"&&(n+=1);continue}if(!u||!r.isNamedImports(u))continue;for(const p of u.elements){if(p.isTypeOnly||p.name.text!=="defineFile")continue;if(n+=1,(p.propertyName?.text??p.name.text)==="defineFile"&&r.isStringLiteral(o.moduleSpecifier)&&o.moduleSpecifier.text===I){if(s)throw c(i,`defineFile must have exactly one named value import from "${I}"`);s=o}}continue}n+=Se(o)}if(!s)throw c(i,`defineFile must be a named value import from "${I}"`);if(n!==1)throw c(i,"the imported defineFile binding may not be shadowed or redeclared");if(s.getStart(e)>t.getStart(e))throw c(i,"the defineFile import must precede metadata")}export function requireMatchingEngineAuthoringImports(e,t,i){oe(e,t,i);const r=z(i.profileId);for(const s of collectModuleSpecifiers(e))if(G(t,i,s),!_(r,s))throw U(t,i,s,r)}function oe(e,t,i){const r=a(),s=q(e),n=o=>{if(ae(o,t,i),ce(o,s,t,i),r.isCallExpression(o)&&T(o,s)){const l=o.arguments.at(0);if(!l||!r.isStringLiteralLike(l)){const u=o.expression.kind===r.SyntaxKind.ImportKeyword?"import()":"require()";throw new Error(`Invalid Testkit authoring in "${t}": ${i.engineId}/${i.profileId} may not use non-literal ${u}; module specifiers must be string literals so engine boundaries can be verified`)}}for(const l of C(o))n(l);r.forEachChild(o,n)};n(e)}function T(e,t){const i=a();return e.expression.kind===i.SyntaxKind.ImportKeyword||i.isIdentifier(e.expression)&&e.expression.text==="require"&&!E(e.expression,t)}function ae(e,t,i){const r=a();if(r.isImportDeclaration(e)){const n=e.importClause?.namedBindings;if(n&&r.isNamedImports(n)&&n.elements.find(l=>!l.isTypeOnly&&(l.propertyName?.text??l.name.text)==="createRequire"))throw g(t,i,"createRequire")}if(r.isPropertyAccessExpression(e)&&e.name.text==="createRequire"||r.isElementAccessExpression(e)&&y(e.argumentExpression)==="createRequire"||r.isBindingElement(e)&&(me(e)==="createRequire"||r.isIdentifier(e.name)&&e.name.text==="createRequire"))throw g(t,i,"createRequire")}function ce(e,t,i,r){const s=a();if(s.isPropertyAccessExpression(e)&&e.name.text==="require"&&v(e.expression,t)||s.isElementAccessExpression(e)&&y(e.argumentExpression)==="require"&&v(e.expression,t))throw g(i,r,e.getText());if(s.isIdentifier(e)&&e.text==="require"&&!E(e,t)&&!le(e)&&!ue(e))throw g(i,r,"global require")}function g(e,t,i){return new Error(`Invalid Testkit authoring in "${e}": ${t.engineId}/${t.profileId} may not alias or indirectly access the module loader through ${i}; use literal import and require specifiers so engine boundaries can be verified`)}function v(e,t){return a().isIdentifier(e)&&(e.text==="globalThis"||e.text==="module")&&!E(e,t)}function le(e){return a().isCallExpression(e.parent)&&e.parent.expression===e}function ue(e){const t=a(),i=e.parent;return pe(e)||t.isPropertyAccessExpression(i)&&i.name===e||t.isPropertyAssignment(i)&&i.name===e||t.isImportSpecifier(i)&&i.propertyName===e||t.isExportSpecifier(i)&&i.parent.parent.moduleSpecifier!==void 0}function pe(e){const t=a(),i=e.parent;return i.name===e&&!t.isShorthandPropertyAssignment(i)&&!t.isExportSpecifier(i)}function me(e){const t=a(),i=e.propertyName;return i&&(t.isIdentifier(i)||t.isStringLiteralLike(i))?i.text:null}function q(e){const t=a(),i=new WeakMap,r=(n,o)=>{let l=i.get(n);l||(l=new Set,i.set(n,l)),N(l,o)},s=n=>{if(t.isImportDeclaration(n))fe(n,e,r);else if(t.isImportEqualsDeclaration(n)&&!n.isTypeOnly)r(e,n.name);else if(t.isVariableDeclaration(n)){const o=n.parent,l=t.isVariableDeclarationList(o)&&(o.flags&t.NodeFlags.BlockScoped)===0?de(n):w(n);r(l,n.name)}else t.isParameter(n)?r(ge(n),n.name):t.isFunctionDeclaration(n)&&n.name?r(w(n),n.name):t.isFunctionExpression(n)&&n.name?r(n,n.name):(t.isClassDeclaration(n)||t.isEnumDeclaration(n)||t.isModuleDeclaration(n))&&n.name&&t.isIdentifier(n.name)?r(w(n),n.name):t.isClassExpression(n)&&n.name?r(n,n.name):t.isCatchClause(n)&&n.variableDeclaration&&r(n,n.variableDeclaration.name);t.forEachChild(n,s)};return s(e),i}function fe(e,t,i){const r=a(),s=e.importClause;if(!s||s.phaseModifier===r.SyntaxKind.TypeKeyword)return;s.name&&i(t,s.name);const n=s.namedBindings;if(n&&r.isNamespaceImport(n))i(t,n.name);else if(n&&r.isNamedImports(n))for(const o of n.elements)o.isTypeOnly||i(t,o.name)}function N(e,t){const i=a();if(i.isIdentifier(t)){e.add(t.text);return}for(const r of t.elements)i.isOmittedExpression(r)||N(e,r.name)}function E(e,t){const i=a();let r=e;for(;;){if(t.get(r)?.has(e.text))return!0;if(i.isSourceFile(r))return!1;r=r.parent}}function w(e){let t=e.parent;for(;;){if(ye(t))return t;t=t.parent}}function de(e){const t=a();let i=e.parent;for(;;){if(t.isFunctionLike(i)||t.isSourceFile(i))return i;i=i.parent}}function ge(e){const t=a();let i=e.parent;for(;;){if(t.isFunctionLike(i)||t.isSourceFile(i))return i;i=i.parent}}function ye(e){const t=a();return t.isSourceFile(e)||t.isBlock(e)||t.isCaseBlock(e)||t.isCatchClause(e)||t.isForStatement(e)||t.isForInStatement(e)||t.isForOfStatement(e)||t.isFunctionLike(e)||t.isClassStaticBlockDeclaration(e)}export function collectModuleSpecifiers(e){const t=a(),i=new Set,r=q(e),s=n=>{const o=xe(n,r);o&&i.add(o);for(const l of C(n))s(l);t.forEachChild(n,s)};return s(e),i}function C(e){return e.jsDoc??[]}function xe(e,t){const i=a();if(i.isJSDocImportTag(e)&&i.isStringLiteralLike(e.moduleSpecifier)||(i.isImportDeclaration(e)||i.isExportDeclaration(e))&&e.moduleSpecifier&&i.isStringLiteralLike(e.moduleSpecifier))return e.moduleSpecifier.text;if(i.isImportEqualsDeclaration(e)&&i.isExternalModuleReference(e.moduleReference)&&i.isStringLiteralLike(e.moduleReference.expression))return e.moduleReference.expression.text;if(i.isImportTypeNode(e)){const s=e.argument;if(i.isLiteralTypeNode(s)&&i.isStringLiteralLike(s.literal))return s.literal.text}if(!i.isCallExpression(e)||!T(e,t))return null;const r=e.arguments[0];return i.isStringLiteralLike(r)?r.text:null}function Se(e){const t=a();return t.isVariableStatement(e)?e.declarationList.declarations.reduce((i,r)=>i+R(r.name,"defineFile"),0):(t.isClassDeclaration(e)||t.isEnumDeclaration(e)||t.isFunctionDeclaration(e))&&e.name?.text==="defineFile"?1:0}function R(e,t){const i=a();return i.isIdentifier(e)?e.text===t?1:0:e.elements.reduce((r,s)=>i.isOmittedExpression(s)?r:r+R(s.name,t),0)}function Ie(e,t){const i=S.resolve(e);if(S.isAbsolute(t))throw new Error(`Test file metadata path must be product-relative: ${t}`);const r=S.resolve(i,t);if(!h(i,r))throw new Error(`Test file metadata path escapes the product root: ${t}`);if(!d.existsSync(r))throw new Error(`Test file metadata source does not exist: ${t}`);const s=d.realpathSync(i),n=d.realpathSync(r);if(!h(s,n))throw new Error(`Test file metadata path resolves outside the product root: ${t}`);return n}function ke(e){return e.parseDiagnostics??[]}function c(e,t,i){return new Error(`Invalid testkit metadata in "${e}": ${t}.`,{cause:i})}function M(e){return e instanceof Error?e.message.replace(/\.$/u,""):String(e)}
@@ -1 +1,2 @@
1
- import l from"node:path";import{requireServiceName as S}from"../../domain/service-name.js";import{errorMessage as y}from"../../kernel/error-serialization.js";import{isUnknownRecord as v,readDynamicProperty as d}from"../../kernel/unknown-values.js";import{normalizeDiscoveryConfig as b}from"./decode-discovery.js";import{DEFAULT_DISCOVERY_EXCLUDES as C,shouldExcludeDiscoveryPath as w}from"./path-policy.js";const j=String;export const ROOT_GROUP="root",LEGACY_OWNERSHIP_NOTICE="testkit.config.ts has services but no groups, so each service owns the tests under its directory as a group of the same name. Legacy ownership will be removed; declare groups with roots and uses instead.";export function resolveGroups(e,r){const t=[],s=m(d(e,"discovery"),null,t),n=L(e,t),o=e&&Object.hasOwn(e,"groups")?D(d(e,"groups"),n,t):O(n,t);e&&Object.hasOwn(e,"groups")&&t.push(...z(n,o.groups));const u=(s.exclude??[]).map(f),p=[...C,...u],a=r.map(c=>P(c,o.rules,c.kind==="typed"?p:u)),i=new Set(o.groups.filter(c=>c.source==="services").map(c=>c.name));return{assignments:a,groups:[...o.groups,{name:ROOT_GROUP,roots:["."],source:"implicit",uses:[]}],notice:a.some(c=>c.status==="owned"&&i.has(c.group))?LEGACY_OWNERSHIP_NOTICE:null,problems:t}}export function readDiscoveryScan(e){return m(d(e,"discovery"),null,[])}export function repoDiscoveryProblem(e){const r=[];return m(d(e,"discovery"),null,r),r[0]??null}export function legacyServiceProblem(e,r){const t=[];return O({[e]:r},t),t[0]??null}function P(e,r,t){const s=I(e.path);if(w(s,t))return{path:s,status:"excluded"};const n=r.filter(i=>(e.kind==="typed"||!i.typedOnly)&&(i.prefix===null||x(s,i.prefix))),o=n.filter(i=>!w(s,i.exclude));if(n.length>0&&o.length===0)return{path:s,status:"excluded"};if(o.length===0)return{group:ROOT_GROUP,path:s,relativePath:s,status:"owned"};const u=Math.max(...o.map(i=>i.depth)),p=o.filter(i=>i.depth===u),a=[...new Set(p.map(i=>i.group))].sort((i,c)=>i.localeCompare(c));return a.length>1?{groups:a,path:s,status:"ambiguous"}:{group:a[0],path:s,relativePath:R(p[0],s),status:"owned"}}function D(e,r,t){const s={groups:[],rules:[]};if(!v(e))return t.push("testkit.config.ts groups must be an object"),s;for(const[n,o]of Object.entries(e)){const u=`groups.${n}`;if(n===ROOT_GROUP){t.push(`${u} is reserved for the implicit group that owns files no other group claims; rename it`);continue}try{S(n,`Group name "${n}"`)}catch(i){t.push(y(i));continue}if(!v(o)){t.push(`${u} must be an object with roots`);continue}const p=$(o.roots,`${u}.roots`,!0,t),a=$(o.exclude,`${u}.exclude`,!1,t);s.groups.push({name:n,roots:p,source:"groups",uses:A(o.uses,u,r,t)});for(const i of p)s.rules.push({depth:i==="."?0:i.split("/").length,exclude:a,group:n,legacyCwd:null,prefix:i==="."?null:i,typedOnly:!1})}return s}const E=["skip","requirements","runtimeIsolation"];function z(e,r){return Object.entries(e).flatMap(([t,s])=>E.filter(n=>d(s,n)!==void 0).map(n=>{const o=r.find(u=>u.uses.includes(t))?.name??t;return`services.${t}.${n} applies to tests, and services own no tests when groups is set; move it to groups.${o}.${n}`}))}function $(e,r,t,s){if(e===void 0&&!t)return[];if(!Array.isArray(e)||t&&e.length===0)return s.push(`${r} must be ${t?"a non-empty array":"an array"} of repo-relative paths`),[];const n=e,o=new Set;for(const u of n){const p=k(u);p===null?s.push(`${r} entry ${JSON.stringify(u)} must be a relative path inside the product directory`):o.add(p)}return[...o]}function A(e,r,t,s){if(e===void 0)return[];if(!_(e))return s.push(`${r}.uses must be an array of service names`),[];const n=[];for(const o of new Set(e))Object.hasOwn(t,o)?n.push(o):s.push(`${r}.uses names "${o}", which is not a service; declare services.${o} or remove it from ${r}.uses`);return n}function O(e,r){const t={groups:[],rules:[]};for(const[s,n]of Object.entries(e)){if(s===ROOT_GROUP){r.push("services.root is reserved for the implicit group that owns files no other group claims; rename the service");continue}const o=m(d(n,"discovery"),s,r),u=G(n),p=o.roots&&o.roots.length>0?o.roots:["."],a=(o.exclude??[]).map(c=>f(u==="."?c:l.posix.join(u,c))),i=p.map(c=>{const g=f(c),h=u==="."?g:g==="."?u:f(l.posix.join(u,g));return{depth:h==="."?0:h.split("/").filter(Boolean).length,exclude:a,group:s,legacyCwd:u==="."?null:u,prefix:h==="."?null:h,typedOnly:!0}});t.rules.push(...i),t.groups.push({name:s,roots:[...new Set(i.map(c=>c.prefix??"."))],source:"services",uses:[s]})}return t}function G(e){return f(j(d(d(e,"local"),"cwd")||"."))}function L(e,r){const t=d(e,"services");return t==null?{}:v(t)?t:(r.push("testkit.config.ts services must be an object"),{})}function m(e,r,t){try{return b(e,{allowRoots:!0})}catch(s){return t.push(r===null?y(s):`Service "${r}" has invalid discovery config: ${y(s)}`),{}}}function R(e,r){return e.prefix!==null&&x(r,e.prefix)?l.posix.relative(e.prefix,r):e.legacyCwd!==null&&x(r,e.legacyCwd)?l.posix.relative(e.legacyCwd,r):r}function _(e){return Array.isArray(e)&&e.every(r=>typeof r=="string")}function x(e,r){return e===r||e.startsWith(`${r}/`)}function k(e){if(typeof e!="string")return null;const r=e.trim(),t=r.replaceAll("\\","/");if(t===""||l.posix.isAbsolute(t)||l.win32.isAbsolute(r))return null;const s=l.posix.normalize(t).replace(/\/+$/u,"")||".";return s===".."||s.startsWith("../")?null:s}function I(e){return l.posix.normalize(e.split(l.sep).join("/")).replace(/\/+$/u,"")}function f(e){const r=e.split(l.sep).join("/");return r==="."||r==="./"?".":r.replace(/^\.\/+/u,"").replace(/\/+$/u,"")||"."}
1
+ import f from"node:path";import{migrationText as P}from"../../domain/retired-test-types.js";import{requireServiceName as b}from"../../domain/service-name.js";import{errorMessage as x}from"../../kernel/error-serialization.js";import{unknownKeysMessage as A}from"../../kernel/known-keys.js";import{isUnknownRecord as h,readDynamicProperty as a}from"../../kernel/unknown-values.js";import{DATABASE_FIXTURE_DISCOVERY as D}from"../authoring/database-fixture-discovery.js";import{normalizeDiscoveryConfig as k}from"./decode-discovery.js";import{DEFAULT_DISCOVERY_EXCLUDES as z,shouldExcludeDiscoveryPath as m}from"./path-policy.js";const E=String,R=Object.keys({exclude:!0,requirements:!0,roots:!0,runtimeIsolation:!0,skip:!0,uses:!0});export const ROOT_GROUP="root";export function resolveGroups(e,r){const t=[],s=g(a(e,"discovery"),t),i=T(e,t),u=(s.exclude??[]).map(d),n=[...z,...u],o=e!=null&&Object.hasOwn(e,"groups"),p=o?G(a(e,"groups"),i,t):{groups:[],rules:[]};return o&&t.push(...C(i,p.groups)),{assignments:r.map(l=>v(l,p.rules,l.kind==="typed"?n:u)),groups:[...p.groups,{name:ROOT_GROUP,roots:["."],source:"implicit",uses:[]}],migration:o?null:I(i,r,n),problems:t}}export function readDiscoveryScan(e){return g(a(e,"discovery"),[])}export function repoDiscoveryProblem(e){const r=[];return g(a(e,"discovery"),r),r[0]??null}function v(e,r,t){const s=F(e.path);if(m(s,t))return{path:s,status:"excluded"};const i=r.filter(c=>c.prefix===null||O(s,c.prefix)),u=i.filter(c=>!m(s,c.exclude));if(i.length>0&&u.length===0)return{path:s,status:"excluded"};if(u.length===0)return{group:ROOT_GROUP,path:s,relativePath:s,status:"owned"};const n=Math.max(...u.map(c=>c.depth)),o=u.filter(c=>c.depth===n),p=[...new Set(o.map(c=>c.group))].sort((c,j)=>c.localeCompare(j));if(p.length>1)return{groups:p,path:s,status:"ambiguous"};const l=o[0].prefix;return{group:p[0],path:s,relativePath:l===null?s:f.posix.relative(l,s),status:"owned"}}function G(e,r,t){const s={groups:[],rules:[]};if(!h(e))return t.push("testkit.config.ts groups must be an object"),s;for(const[i,u]of Object.entries(e)){const n=`groups.${i}`;if(i===ROOT_GROUP){t.push(`${n} is reserved for the implicit group that owns files no other group claims; rename it`);continue}try{b(i,`Group name "${i}"`)}catch(c){t.push(x(c));continue}if(!h(u)){t.push(`${n} must be an object with roots`);continue}const o=A(u,R,n);o!==null&&t.push(o);const p=w(u.roots,`${n}.roots`,!0,t),l=w(u.exclude,`${n}.exclude`,!1,t);s.groups.push({name:i,roots:p,source:"groups",uses:U(u.uses,n,r,t)});for(const c of p)s.rules.push({depth:c==="."?0:c.split("/").length,exclude:l,group:i,prefix:c==="."?null:c})}return s}const _=["skip","requirements","runtimeIsolation"];function C(e,r){return Object.entries(e).flatMap(([t,s])=>{const i=r.find(o=>o.uses.includes(t))?.name??t,u=_.filter(o=>a(s,o)!==void 0).map(o=>`services.${t}.${o} applies to tests, and services own no tests when groups is set; move it to groups.${i}.${o}`),n=a(s,"discovery");return n!==void 0&&!L(n)&&u.push(`services.${t}.discovery applies to tests, and services own no tests when groups is set; move its roots and exclude to groups.${i}.roots and groups.${i}.exclude, as paths from the product directory`),u})}function L(e){return h(e)&&JSON.stringify(e)===JSON.stringify(D)}function w(e,r,t,s){if(e===void 0&&!t)return[];if(!Array.isArray(e)||t&&e.length===0)return s.push(`${r} must be ${t?"a non-empty array":"an array"} of repo-relative paths`),[];const i=e,u=new Set;for(const n of i){const o=S(n);o===null?s.push(`${r} entry ${JSON.stringify(n)} must be a relative path inside the product directory`):u.add(o)}return[...u]}function U(e,r,t,s){if(e===void 0)return[];if(!J(e))return s.push(`${r}.uses must be an array of service names`),[];const i=[];for(const u of new Set(e))Object.hasOwn(t,u)?i.push(u):s.push(`${r}.uses names "${u}", which is not a service; declare services.${u} or remove it from ${r}.uses`);return i}function I(e,r,t){const s=Object.entries(e).flatMap(([n,o])=>M(n,o)),i=new Set;for(const n of r){if(n.kind!=="typed")continue;const o=v(n,s,t);if(o.status==="ambiguous")for(const p of o.groups)i.add(p);if(o.status==="owned"&&o.group!==ROOT_GROUP&&i.add(o.group),o.status==="excluded"&&!m(o.path,t))for(const p of s)(p.prefix===null||O(o.path,p.prefix))&&i.add(p.group)}return i.size===0?null:["testkit.config.ts has services and no groups. Testkit 0.6.0 removed legacy service ownership, so services no longer own the test files under their directories. Add these groups to keep each typed test file with the service that owned it:","","groups: {",...Object.keys(e).filter(n=>i.has(n)).map(n=>{const o=s.filter(c=>c.group===n),p=o[0].exclude.filter($),l=[`roots: ${y([...new Set(o.map(c=>c.prefix??"."))].filter($))}`,`uses: ${y([n])}`,...p.length>0?[`exclude: ${y(p)}`]:[]];return` ${/^[A-Za-z_$][\w$]*$/u.test(n)?n:JSON.stringify(n)}: { ${l.join(", ")} },`}),"},","","A group also owns the plain Vitest files under its roots.",`${P("Remove legacy service ownership")}.`].join(`
2
+ `)}function M(e,r){if(e===ROOT_GROUP)return[];const t=g(a(r,"discovery"),null);if(t===null)return[];const s=N(r),i=(t.exclude??[]).map(u=>d(s==="."?u:f.posix.join(s,u)));return(t.roots&&t.roots.length>0?t.roots:["."]).map(u=>{const n=d(u),o=s==="."?n:n==="."?s:d(f.posix.join(s,n));return{depth:o==="."?0:o.split("/").filter(Boolean).length,exclude:i,group:e,prefix:o==="."?null:o}})}function $(e){return S(e)!==null}function y(e){return`[${e.map(r=>JSON.stringify(r)).join(", ")}]`}function N(e){return d(E(a(a(e,"local"),"cwd")||"."))}function T(e,r){const t=a(e,"services");return t==null?{}:h(t)?t:(r.push("testkit.config.ts services must be an object"),{})}function g(e,r){try{return k(e,{allowRoots:!0})}catch(t){return r===null?null:(r.push(x(t)),{})}}function J(e){return Array.isArray(e)&&e.every(r=>typeof r=="string")}function O(e,r){return e===r||e.startsWith(`${r}/`)}function S(e){if(typeof e!="string")return null;const r=e.trim(),t=r.replaceAll("\\","/");if(t===""||f.posix.isAbsolute(t)||f.win32.isAbsolute(r))return null;const s=f.posix.normalize(t).replace(/\/+$/u,"")||".";return s===".."||s.startsWith("../")?null:s}function F(e){return f.posix.normalize(e.split(f.sep).join("/")).replace(/\/+$/u,"")}function d(e){const r=e.split(f.sep).join("/");return r==="."||r==="./"?".":r.replace(/^\.\/+/u,"").replace(/\/+$/u,"")||"."}
@@ -1 +1 @@
1
- import{selectDiscoveredSuites as L,selectsOnlyTypedFiles as B}from"./test-selection.js";import q from"path";import{isUnknownRecord as w,readDynamicProperty as i}from"../../kernel/unknown-values.js";import{suiteKey as T}from"../../domain/suite-selection.js";import{legacyServiceProblem as k,repoDiscoveryProblem as G}from"./groups.js";import{discoverOwnedTests as K}from"./owned-discovery.js";import{loadTestkitConfig as _}from"./config-loader.js";import{normalizeToolchainRegistry as V}from"../../toolchains/index.js";import{loadTestFileMetadataMap as H}from"./file-metadata.js";import{normalizeFingerprintPolicy as J}from"../../repo/fingerprint-policy.js";import{normalizeDatabaseConfig as Q}from"./database.js";import{inferEnvFiles as W,parseDotenv as X}from"./env.js";import{normalizeRuntimeIsolationConfig as Y}from"./runtime-isolation.js";import{resolveProductDir as z,resolveServiceCwd as Z}from"./paths.js";import{normalizeRepoExecution as N,normalizeRuntimeConfig as ee}from"./runtime.js";import{normalizeServiceRequirements as re,normalizeSkipConfig as te}from"./skip-config.js";import{normalizeArtifactArchiveConfig as oe}from"../../evidence/index.js";import{validateConfigCoverage as ne,validateServiceConfig as ie}from"./validation.js";import{assertPortableServiceNameSet as se,requireServiceName as ae}from"../../domain/service-name.js";import{resolveExecutionProfile as ce}from"../../engines/catalog.js";import{optionalStringValue as S}from"../../evidence/read-fields.js";const U=["65533","65534"],fe=/\{(?:baseUrl|readyUrl|publicBaseUrl|privateBaseUrl|serviceContextUrl)(?::[^}]+)?\}/gu;export{X as parseDotenv,z as resolveProductDir,Z as resolveServiceCwd};export async function loadConfigContext(e={}){const r=z(process.cwd(),e.dir),t=e.configContext||await _(r,{importCacheSalt:e.configImportCacheSalt??""}),{config:o,configFile:n}=Se(t),a=N(Ce(i(o,"execution"))),f=oe(i(o,"artifacts")),p=V(i(o,"toolchains")),s=J(i(o,"fingerprints")),h=$e(i(o,"redact"));$(G(o));const c=ye(i(o,"services")),u=!Object.hasOwn(o,"groups");e.preparedDiscovery||be(c,u);const g=e.plain??!B(e.testSelection??{}),l=e.preparedDiscovery?.owned??await K(r,o,{...e.discoveryOptions,plain:g,retired:e.retired,selection:e.vitest}),d=l.project,b=e.testSelection?L(d.suitesByService,e.testSelection):d.suitesByService,y=H(r,b,e.preparedDiscovery?.fileMetadataByPath),A=le(b,y),E=[];for(const m of Object.keys(c).sort((v,O)=>v.localeCompare(O)))E.push(pe({name:m,productDir:r,config:o,configFile:n,execution:a,artifacts:f,toolchains:p,fingerprints:s,explicitService:c[m]??{},legacy:u}));$(l.problems[0]??null),ne(E);const C=[];for(const m of[...l.groups].sort((v,O)=>v.name.localeCompare(O.name)))C.push(de({declaration:m,productDir:r,configFile:n,execution:a,artifacts:f,policy:me(o,m),redact:h,suites:A[m.name]||[],discoveredSuites:d.suitesByService[m.name]||[],fileMetadataByPath:y,plainListed:g}));return{productDir:r,config:o,configFile:n,execution:a,toolchains:p,fingerprints:s,explicitServices:c,discovery:d,groups:C,ownershipNotice:l.notice,services:E}}function le(e,r){return Object.fromEntries(Object.entries(e).map(([t,o])=>[t,(o??[]).map(n=>ue(t,n,r))]))}function ue(e,r,t){const o=r.files[0];if(!o)throw new Error(`Discovered suite ${e}/${T(r)} contains no files`);const n=P(t,o);ce({...r,...n});for(const a of r.files.slice(1)){const f=P(t,a);if(f.engineId!==n.engineId||f.profileId!==n.profileId)throw new Error(`Discovered suite ${e}/${T(r)} mixes engine profiles ${n.engineId}/${n.profileId} and ${f.engineId}/${f.profileId}`)}return{...r,engineId:n.engineId,profileId:n.profileId}}function P(e,r){const t=e.get(r);if(!t)throw new Error(`Missing validated testkit metadata for discovered file: ${r}`);return t}export async function loadConfigs(e={}){const{services:r}=await loadConfigContext(e),t=e.service?r.filter(o=>o.name===e.service):r;if(e.service&&t.length===0){const o=r.map(n=>n.name).join(", ");throw new Error(`Service "${e.service}" not found. Available: ${o}`)}return t}function pe({name:e,productDir:r,config:t,configFile:o,execution:n,artifacts:a,toolchains:f,fingerprints:p,explicitService:s,legacy:h}){const c=Object.hasOwn(s,"local")?I(i(s,"local"),e):void 0,u=W(r,s,c),g=D(i(s,"env"),`Service "${e}" env`);if(h&&$(k(e,s)),i(s,"migrate")||i(s,"seed"))throw new Error(`Service "${e}" uses removed migrate/seed hooks. Move template lifecycle to database.template.{migrate,seed,verify}.`);const l=he(s,e),d=ee(i(s,"runtime"),e,f),b=Ee(i(s,"dependsOn"),`Service "${e}" dependsOn`),y=S(i(s,"databaseFrom"),`Service "${e}" databaseFrom`);return ie({name:e,local:c,database:l,databaseFrom:y,runtime:d,dependsOn:b,productDir:r}),{name:e,productDir:r,configFile:o,stateDir:q.join(r,".testkit",e),artifacts:a,testkit:{execution:n,dependsOn:b,database:l,databaseFrom:y,envFiles:u,serviceEnv:g,runtime:d,ui:i(t,"ui")||null,fingerprints:p,local:c}}}function de({declaration:e,productDir:r,configFile:t,execution:o,artifacts:n,policy:a,redact:f,suites:p,discoveredSuites:s,fileMetadataByPath:h,plainListed:c}){const u=e.source==="services"?`Service "${e.name}"`:`Group "${e.name}"`,g=new Set(p.flatMap(l=>l.files));return{...e,roots:[...e.roots],uses:[...e.uses],artifacts:n,configFile:t,productDir:r,redact:f,suites:p,testkit:{execution:o,fileMetadataByPath:new Map([...h.entries()].filter(([l])=>g.has(l))),requirements:re(i(a,"requirements"),{owner:u,plainListed:c,suites:s}),runtimeIsolation:Y(i(a,"runtimeIsolation"),{owner:u,plainListed:c,suites:s}),skip:te(i(a,"skip"),{owner:u,plainListed:c,suites:s})}}}function me(e,r){if(r.source!=="implicit")return i(i(e,r.source==="groups"?"groups":"services"),r.name)}function I(e,r){if(e===!1)return;if(!w(e))throw new Error(`Service "${r}" local must be an object or false`);if(Object.prototype.hasOwnProperty.call(e,"port"))throw new Error(`Service "${r}" sets local.port. Testkit assigns every service a free port from the OS. Delete the line, or the port option of app.node or app.next, and use {port} in start, baseUrl, and readyUrl.`);const t=F(e.start,`Service "${r}" local.start`),{baseUrl:o,shutdownTimeoutMs:n,...a}=e;return{...a,...Oe(n,r),...ge(o,r),start:t,cwd:S(e.cwd,`Service "${r}" local.cwd`)||".",env:D(e.env,`Service "${r}" local.env`),...ve(e.readyTimeoutMs,`Service "${r}" local.readyTimeoutMs`),...je(e.readyUrl,r)}}function ge(e,r){if(e==null)return{};const t=F(e,`Service "${r}" local.baseUrl`);x(t,`Service "${r}" local.baseUrl`);const o=new RegExp(`\\{port(?::${we(r)})?\\}`,"gu");if(!U.every(n=>R(t,o,n,!1)===n))throw new Error(`Service "${r}" local.baseUrl must use {port} for its port, for example http://127.0.0.1:{port}. Testkit assigns every service a free port from the OS.`);return{baseUrl:t}}function j(e){return e.replace(/[\t\n\r]/gu,"").trim()}function M(e){return/^[a-z][a-z0-9+.-]*:[\\/]{2}(?![\\/])([^/\\?#]*)/iu.exec(j(e))?.[1]}function R(e,r,t,o){const n=j(e).replace(r,t).replace(fe,o?`http://template.invalid:${t}`:"http://template.invalid").replace(/\{[^}]+\}/gu,"template");try{return new URL(n).port}catch{return}}function we(e){return e.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")}function x(e,r){if(/^[a-z][a-z0-9+.-]*:/iu.test(j(e))&&M(e)===void 0)throw new Error(`${r} must be an absolute URL with a host, for example http://127.0.0.1:{port}/health.`);const[t,o]=U.map(a=>R(e,/\{port(?::[^}]+)?\}/gu,a,!0)),n=/:(\d+)$/u.exec(M(e)??"")?.[1]??(t&&t===o?t:void 0);if(n!==void 0)throw new Error(`${r} has a literal port (${n}). Testkit assigns every service a free port from the OS. Replace the port with {port}.`)}function he(e,r){const t=Q(e,r);if(!t)return;const o=S(i(t,"image"),`Service "${r}" database.image`),n=S(i(t,"password"),`Service "${r}" database.password`),a=S(i(t,"user"),`Service "${r}" database.user`);return{...t,...Object.prototype.hasOwnProperty.call(t,"image")?{image:o}:{},...Object.prototype.hasOwnProperty.call(t,"password")?{password:n}:{},...Object.prototype.hasOwnProperty.call(t,"user")?{user:a}:{}}}function be(e,r){for(const[t,o]of Object.entries(e))r&&$(k(t,o)),o&&Object.hasOwn(o,"local")&&I(i(o,"local"),t)}function $(e){if(e!==null)throw new Error(e)}function ye(e){if(e==null)return{};if(!w(e))throw new Error("testkit.config.ts services must be an object");const r=Object.fromEntries(Object.entries(e).map(([t,o])=>{if(ae(t,`Service name "${t}"`),o==null)return[t,{}];if(!w(o))throw new Error(`Service "${t}" config must be an object`);return[t,o]}));return se(Object.keys(r)),r}function Se(e){if(!w(e))throw new Error("Loaded Testkit config context must be an object");const r=e.config;if(typeof r!="object"||r===null)throw new Error("Loaded Testkit config must be an object");const t=e.configFile;if(t!==null&&typeof t!="string")throw new Error("Loaded Testkit config file must be a string or null");return{config:r,configFile:t}}function $e(e){if(e==null)return[];if(!Array.isArray(e))throw new Error("testkit.config.ts redact must be an array of env key names");return e.map((r,t)=>{if(typeof r!="string")throw new Error(`testkit.config.ts redact[${String(t)}] must be a string`);if(r.trim().length===0)throw new Error(`testkit.config.ts redact[${String(t)}] must not be empty`);return r.trim()})}function Ee(e,r){if(e==null)return[];if(!Array.isArray(e))throw new Error(`${r} must be an array of strings`);return e.map((t,o)=>{if(typeof t!="string")throw new Error(`${r}[${String(o)}] must be a string`);return t})}function D(e,r){if(e==null)return{};if(!w(e))throw new Error(`${r} must be an object of strings`);return Object.fromEntries(Object.entries(e).map(([t,o])=>{if(typeof o!="string")throw new Error(`${r}.${t} must be a string`);return[t,o]}))}function ve(e,r){if(e===void 0)return{};if(typeof e!="number")throw new Error(`${r} must be a number`);return{readyTimeoutMs:e}}function Oe(e,r){if(e===void 0)return{};if(typeof e!="number"||!Number.isInteger(e)||e<=0)throw new Error(`Service "${r}" local.shutdownTimeoutMs must be a positive finite integer in milliseconds`);return{shutdownTimeoutMs:e}}function je(e,r){if(e===void 0)return{};if(typeof e=="string")return x(e,`Service "${r}" local.readyUrl`),{readyUrl:e};if(e===!1||e===null)return{readyUrl:e};throw new Error(`Service "${r}" local.readyUrl must be a string, false, or null`)}function F(e,r){if(typeof e!="string")throw new Error(`${r} must be a string`);return e}function Ce(e){if(e){if(!w(e))throw new Error("testkit.config.ts execution must be an object");return e}}
1
+ import{selectDiscoveredSuites as D,selectsOnlyTypedFiles as A}from"./test-selection.js";import L from"path";import{isUnknownRecord as w,readDynamicProperty as i}from"../../kernel/unknown-values.js";import{suiteKey as C}from"../../domain/suite-selection.js";import{repoDiscoveryProblem as B}from"./groups.js";import{discoverOwnedTests as q}from"./owned-discovery.js";import{loadTestkitConfig as G}from"./config-loader.js";import{normalizeToolchainRegistry as K}from"../../toolchains/index.js";import{loadTestFileMetadataMap as _}from"./file-metadata.js";import{normalizeFingerprintPolicy as V}from"../../repo/fingerprint-policy.js";import{normalizeDatabaseConfig as H}from"./database.js";import{inferEnvFiles as J,parseDotenv as Q}from"./env.js";import{normalizeRuntimeIsolationConfig as W}from"./runtime-isolation.js";import{resolveProductDir as T,resolveServiceCwd as X}from"./paths.js";import{normalizeRepoExecution as Y,normalizeRuntimeConfig as Z}from"./runtime.js";import{normalizeServiceRequirements as N,normalizeSkipConfig as ee}from"./skip-config.js";import{normalizeArtifactArchiveConfig as re}from"../../evidence/index.js";import{validateConfigCoverage as te,validateServiceConfig as oe}from"./validation.js";import{assertPortableServiceNameSet as ne,requireServiceName as ie}from"../../domain/service-name.js";import{resolveExecutionProfile as se}from"../../engines/catalog.js";import{optionalStringValue as y}from"../../evidence/read-fields.js";const U=["65533","65534"],ae=/\{(?:baseUrl|readyUrl|publicBaseUrl|privateBaseUrl|serviceContextUrl)(?::[^}]+)?\}/gu;export{Q as parseDotenv,T as resolveProductDir,X as resolveServiceCwd};export async function loadConfigContext(e={}){const r=T(process.cwd(),e.dir),t=e.configContext||await G(r,{importCacheSalt:e.configImportCacheSalt??""}),{config:o,configFile:n}=be(t),s=Y(Oe(i(o,"execution"))),c=re(i(o,"artifacts")),m=K(i(o,"toolchains")),a=V(i(o,"fingerprints")),p=ye(i(o,"redact"));O(B(o));const l=he(i(o,"services"));e.preparedDiscovery||we(l);const d=e.plain??!A(e.testSelection??{}),u=e.preparedDiscovery?.owned??await q(r,o,{...e.discoveryOptions,plain:d,retired:e.retired,selection:e.vitest}),f=u.project,h=e.testSelection?D(f.suitesByService,e.testSelection):f.suitesByService,b=_(r,h,e.preparedDiscovery?.fileMetadataByPath),F=ce(h,b),S=[];for(const g of Object.keys(l).sort(($,E)=>$.localeCompare(E)))S.push(le({name:g,productDir:r,config:o,configFile:n,execution:s,artifacts:c,toolchains:m,fingerprints:a,explicitService:l[g]??{}}));O(u.problems[0]??null),e.allowUnmigratedServices||O(u.migration),te(S);const j=[];for(const g of[...u.groups].sort(($,E)=>$.name.localeCompare(E.name)))j.push(ue({declaration:g,productDir:r,configFile:n,execution:s,artifacts:c,policy:de(o,g),redact:p,suites:F[g.name]||[],discoveredSuites:f.suitesByService[g.name]||[],fileMetadataByPath:b,plainListed:d}));return{productDir:r,config:o,configFile:n,execution:s,toolchains:m,fingerprints:a,explicitServices:l,discovery:f,groups:j,plainFiles:u.plainFiles,services:S}}function ce(e,r){return Object.fromEntries(Object.entries(e).map(([t,o])=>[t,(o??[]).map(n=>fe(t,n,r))]))}function fe(e,r,t){const o=r.files[0];if(!o)throw new Error(`Discovered suite ${e}/${C(r)} contains no files`);const n=k(t,o);se({...r,...n});for(const s of r.files.slice(1)){const c=k(t,s);if(c.engineId!==n.engineId||c.profileId!==n.profileId)throw new Error(`Discovered suite ${e}/${C(r)} mixes engine profiles ${n.engineId}/${n.profileId} and ${c.engineId}/${c.profileId}`)}return{...r,engineId:n.engineId,profileId:n.profileId}}function k(e,r){const t=e.get(r);if(!t)throw new Error(`Missing validated testkit metadata for discovered file: ${r}`);return t}export async function loadConfigs(e={}){const{services:r}=await loadConfigContext(e),t=e.service?r.filter(o=>o.name===e.service):r;if(e.service&&t.length===0){const o=r.map(n=>n.name).join(", ");throw new Error(`Service "${e.service}" not found. Available: ${o}`)}return t}function le({name:e,productDir:r,config:t,configFile:o,execution:n,artifacts:s,toolchains:c,fingerprints:m,explicitService:a}){const p=Object.hasOwn(a,"local")?z(i(a,"local"),e):void 0,l=J(r,a,p),d=M(i(a,"env"),`Service "${e}" env`);if(i(a,"migrate")||i(a,"seed"))throw new Error(`Service "${e}" uses removed migrate/seed hooks. Move template lifecycle to database.template.{migrate,seed,verify}.`);const u=ge(a,e),f=Z(i(a,"runtime"),e,c),h=Se(i(a,"dependsOn"),`Service "${e}" dependsOn`),b=y(i(a,"databaseFrom"),`Service "${e}" databaseFrom`);return oe({name:e,local:p,database:u,databaseFrom:b,runtime:f,dependsOn:h,productDir:r}),{name:e,productDir:r,configFile:o,stateDir:L.join(r,".testkit",e),artifacts:s,testkit:{execution:n,dependsOn:h,database:u,databaseFrom:b,envFiles:l,serviceEnv:d,runtime:f,ui:i(t,"ui")||null,fingerprints:m,local:p}}}function ue({declaration:e,productDir:r,configFile:t,execution:o,artifacts:n,policy:s,redact:c,suites:m,discoveredSuites:a,fileMetadataByPath:p,plainListed:l}){const d=`Group "${e.name}"`,u=new Set(m.flatMap(f=>f.files));return{...e,roots:[...e.roots],uses:[...e.uses],artifacts:n,configFile:t,productDir:r,redact:c,suites:m,testkit:{execution:o,fileMetadataByPath:new Map([...p.entries()].filter(([f])=>u.has(f))),requirements:N(i(s,"requirements"),{owner:d,plainListed:l,suites:a}),runtimeIsolation:W(i(s,"runtimeIsolation"),{owner:d,plainListed:l,suites:a}),skip:ee(i(s,"skip"),{owner:d,plainListed:l,suites:a})}}}function de(e,r){if(r.source!=="implicit")return i(i(e,"groups"),r.name)}function z(e,r){if(e===!1)return;if(!w(e))throw new Error(`Service "${r}" local must be an object or false`);if(Object.prototype.hasOwnProperty.call(e,"port"))throw new Error(`Service "${r}" sets local.port. Testkit assigns every service a free port from the OS. Delete the line, or the port option of app.node or app.next, and use {port} in start, baseUrl, and readyUrl.`);const t=R(e.start,`Service "${r}" local.start`),{baseUrl:o,shutdownTimeoutMs:n,...s}=e;return{...s,...Ee(n,r),...me(o,r),start:t,cwd:y(e.cwd,`Service "${r}" local.cwd`)||".",env:M(e.env,`Service "${r}" local.env`),...$e(e.readyTimeoutMs,`Service "${r}" local.readyTimeoutMs`),...ve(e.readyUrl,r)}}function me(e,r){if(e==null)return{};const t=R(e,`Service "${r}" local.baseUrl`);x(t,`Service "${r}" local.baseUrl`);const o=new RegExp(`\\{port(?::${pe(r)})?\\}`,"gu");if(!U.every(n=>I(t,o,n,!1)===n))throw new Error(`Service "${r}" local.baseUrl must use {port} for its port, for example http://127.0.0.1:{port}. Testkit assigns every service a free port from the OS.`);return{baseUrl:t}}function v(e){return e.replace(/[\t\n\r]/gu,"").trim()}function P(e){return/^[a-z][a-z0-9+.-]*:[\\/]{2}(?![\\/])([^/\\?#]*)/iu.exec(v(e))?.[1]}function I(e,r,t,o){const n=v(e).replace(r,t).replace(ae,o?`http://template.invalid:${t}`:"http://template.invalid").replace(/\{[^}]+\}/gu,"template");try{return new URL(n).port}catch{return}}function pe(e){return e.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")}function x(e,r){if(/^[a-z][a-z0-9+.-]*:/iu.test(v(e))&&P(e)===void 0)throw new Error(`${r} must be an absolute URL with a host, for example http://127.0.0.1:{port}/health.`);const[t,o]=U.map(s=>I(e,/\{port(?::[^}]+)?\}/gu,s,!0)),n=/:(\d+)$/u.exec(P(e)??"")?.[1]??(t&&t===o?t:void 0);if(n!==void 0)throw new Error(`${r} has a literal port (${n}). Testkit assigns every service a free port from the OS. Replace the port with {port}.`)}function ge(e,r){const t=H(e,r);if(!t)return;const o=y(i(t,"image"),`Service "${r}" database.image`),n=y(i(t,"password"),`Service "${r}" database.password`),s=y(i(t,"user"),`Service "${r}" database.user`);return{...t,...Object.prototype.hasOwnProperty.call(t,"image")?{image:o}:{},...Object.prototype.hasOwnProperty.call(t,"password")?{password:n}:{},...Object.prototype.hasOwnProperty.call(t,"user")?{user:s}:{}}}function we(e){for(const[r,t]of Object.entries(e))t&&Object.hasOwn(t,"local")&&z(i(t,"local"),r)}function O(e){if(e!==null)throw new Error(e)}function he(e){if(e==null)return{};if(!w(e))throw new Error("testkit.config.ts services must be an object");const r=Object.fromEntries(Object.entries(e).map(([t,o])=>{if(ie(t,`Service name "${t}"`),o==null)return[t,{}];if(!w(o))throw new Error(`Service "${t}" config must be an object`);return[t,o]}));return ne(Object.keys(r)),r}function be(e){if(!w(e))throw new Error("Loaded Testkit config context must be an object");const r=e.config;if(typeof r!="object"||r===null)throw new Error("Loaded Testkit config must be an object");const t=e.configFile;if(t!==null&&typeof t!="string")throw new Error("Loaded Testkit config file must be a string or null");return{config:r,configFile:t}}function ye(e){if(e==null)return[];if(!Array.isArray(e))throw new Error("testkit.config.ts redact must be an array of env key names");return e.map((r,t)=>{if(typeof r!="string")throw new Error(`testkit.config.ts redact[${String(t)}] must be a string`);if(r.trim().length===0)throw new Error(`testkit.config.ts redact[${String(t)}] must not be empty`);return r.trim()})}function Se(e,r){if(e==null)return[];if(!Array.isArray(e))throw new Error(`${r} must be an array of strings`);return e.map((t,o)=>{if(typeof t!="string")throw new Error(`${r}[${String(o)}] must be a string`);return t})}function M(e,r){if(e==null)return{};if(!w(e))throw new Error(`${r} must be an object of strings`);return Object.fromEntries(Object.entries(e).map(([t,o])=>{if(typeof o!="string")throw new Error(`${r}.${t} must be a string`);return[t,o]}))}function $e(e,r){if(e===void 0)return{};if(typeof e!="number")throw new Error(`${r} must be a number`);return{readyTimeoutMs:e}}function Ee(e,r){if(e===void 0)return{};if(typeof e!="number"||!Number.isInteger(e)||e<=0)throw new Error(`Service "${r}" local.shutdownTimeoutMs must be a positive finite integer in milliseconds`);return{shutdownTimeoutMs:e}}function ve(e,r){if(e===void 0)return{};if(typeof e=="string")return x(e,`Service "${r}" local.readyUrl`),{readyUrl:e};if(e===!1||e===null)return{readyUrl:e};throw new Error(`Service "${r}" local.readyUrl must be a string, false, or null`)}function R(e,r){if(typeof e!="string")throw new Error(`${r} must be a string`);return e}function Oe(e){if(e){if(!w(e))throw new Error("testkit.config.ts execution must be an object");return e}}
@@ -1 +1 @@
1
- import{loadConfigContext as c,loadConfigs as d}from"./load.js";export async function loadManagedConfigs(e={}){const n=await d({dir:e.dir,plain:!1,retired:[]}),i=filterConfigsByService(n,e.service||null);return{allConfigs:n,configs:i}}export async function loadRunConfigs(e={}){const n=await c(e),i=e.group?n.groups.filter(t=>t.name===e.group):n.groups.filter(t=>t.source!=="implicit"||Object.hasOwn(n.discovery.suitesByService,t.name));if(e.group&&i.length===0){const t=n.groups.map(r=>r.name).join(", ");throw new Error(`Group "${e.group}" not found. Available: ${t}`)}return{groups:i,ownershipNotice:n.ownershipNotice,services:n.services}}export function filterConfigsByService(e,n){if(!n)return e;const i=e.filter(r=>r.name===n);if(i.length>0)return i;const t=e.map(r=>r.name).join(", ");throw new Error(`Service "${n}" not found. Available: ${t}`)}export function resolveTargetConfig(e,n=null){if(n){const r=e.find(a=>a.name===n);if(!r){const a=e.map(o=>o.name).join(", ");throw new Error(`Service "${n}" not found. Available: ${a}`)}return r}if(e.length===1)return e[0];const i=e.filter(r=>r.testkit.database);if(i.length===1)return i[0];const t=e.map(r=>r.name).join(", ");throw new Error(`Multiple services available. Pass --service. Available: ${t}`)}export function collectRequiredConfigs(e,n){const i=new Map(e.map(a=>[a.name,a])),t=new Set,r=a=>{if(t.has(a))return;const o=i.get(a);if(!o)throw new Error(`Missing config for dependency "${a}"`);t.add(a);for(const s of o.testkit.dependsOn)r(s)};return r(n),e.filter(a=>t.has(a.name))}export function topologicallySortConfigs(e){const n=new Map(e.map(o=>[o.name,o])),i=new Set,t=new Set,r=[],a=o=>{if(!i.has(o.name)){if(t.has(o.name))throw new Error(`Dependency cycle while resolving configs for "${o.name}"`);t.add(o.name);for(const s of o.testkit.dependsOn){const l=n.get(s);l&&a(l)}t.delete(o.name),i.add(o.name),r.push(o)}};for(const o of e)a(o);return r}
1
+ import{loadConfigContext as c,loadConfigs as d}from"./load.js";export async function loadManagedConfigs(e={}){const n=await d({dir:e.dir,allowUnmigratedServices:!0,plain:!1,retired:[]}),i=filterConfigsByService(n,e.service||null);return{allConfigs:n,configs:i}}export async function loadRunConfigs(e={}){const n=await c(e),i=e.group?n.groups.filter(t=>t.name===e.group):n.groups.filter(t=>t.source!=="implicit"||Object.hasOwn(n.discovery.suitesByService,t.name));if(e.group&&i.length===0){const t=n.groups.map(r=>r.name).join(", ");throw new Error(`Group "${e.group}" not found. Available: ${t}`)}return{groups:i,services:n.services}}export function filterConfigsByService(e,n){if(!n)return e;const i=e.filter(r=>r.name===n);if(i.length>0)return i;const t=e.map(r=>r.name).join(", ");throw new Error(`Service "${n}" not found. Available: ${t}`)}export function resolveTargetConfig(e,n=null){if(n){const r=e.find(a=>a.name===n);if(!r){const a=e.map(o=>o.name).join(", ");throw new Error(`Service "${n}" not found. Available: ${a}`)}return r}if(e.length===1)return e[0];const i=e.filter(r=>r.testkit.database);if(i.length===1)return i[0];const t=e.map(r=>r.name).join(", ");throw new Error(`Multiple services available. Pass --service. Available: ${t}`)}export function collectRequiredConfigs(e,n){const i=new Map(e.map(a=>[a.name,a])),t=new Set,r=a=>{if(t.has(a))return;const o=i.get(a);if(!o)throw new Error(`Missing config for dependency "${a}"`);t.add(a);for(const s of o.testkit.dependsOn)r(s)};return r(n),e.filter(a=>t.has(a.name))}export function topologicallySortConfigs(e){const n=new Map(e.map(o=>[o.name,o])),i=new Set,t=new Set,r=[],a=o=>{if(!i.has(o.name)){if(t.has(o.name))throw new Error(`Dependency cycle while resolving configs for "${o.name}"`);t.add(o.name);for(const s of o.testkit.dependsOn){const l=n.get(s);l&&a(l)}t.delete(o.name),i.add(o.name),r.push(o)}};for(const o of e)a(o);return r}
@@ -1 +1 @@
1
- import{rejectRetiredTestFile as d}from"../../domain/retired-test-types.js";import{listPlainVitestFiles as f,resolveVitestSelection as h}from"../../engines/catalog.js";import{buildDiscoveryProject as m,scanTestFiles as v}from"./discovery.js";import{readDiscoveryScan as y,resolveGroups as l}from"./groups.js";export async function discoverOwnedTests(e,o,{plain:p=!0,selection:u=h({},e),...n}={}){const r=v(e,y(o)),c=r.untypedFiles.map(t=>({kind:"typed",path:t}));for(const t of l(o,c).assignments)t.status!=="excluded"&&d(t.path,n.retired);const i=new Map;for(const t of r.suiteFiles)i.set(t,{kind:"typed",path:t});const a=p?await f(e,u):[];for(const{file:t}of a)i.set(t,{kind:"plain",path:t});const s=l(o,[...i.values()]);return{groups:s.groups,notice:s.notice,plainFiles:a,problems:s.problems,project:m(r.legacyFiles,s.assignments.flatMap(w),n)}}function w(e){return e.status==="excluded"?[]:e.status==="ambiguous"?[{filePath:e.path,kind:"ambiguous",owners:e.groups}]:[{filePath:e.path,kind:"owned",owner:e.group,relativePath:e.relativePath}]}
1
+ import{rejectRetiredTestFile as f}from"../../domain/retired-test-types.js";import{listPlainVitestFiles as m,resolveVitestSelection as h}from"../../engines/catalog.js";import{buildDiscoveryProject as v,scanTestFiles as F}from"./discovery.js";import{readDiscoveryScan as w,resolveGroups as u}from"./groups.js";export async function discoverOwnedTests(e,i,{plain:p=!0,selection:c=h({},e),...n}={}){const s=F(e,w(i)),{assignments:d,migration:o}=u(i,[...s.untypedFiles,...s.suiteFiles].map(t=>({kind:"typed",path:t})));if(o===null)for(const t of d)t.status!=="excluded"&&f(t.path,n.retired);const r=new Map;if(o===null)for(const t of s.suiteFiles)r.set(t,{kind:"typed",path:t});const a=p?await m(e,c):[];for(const{file:t}of a)r.set(t,{kind:"plain",path:t});const l=u(i,[...r.values()]);return{groups:l.groups,migration:o,plainFiles:a,problems:l.problems,project:v(s.legacyFiles,l.assignments.flatMap(y),n)}}function y(e){return e.status==="excluded"?[]:e.status==="ambiguous"?[{filePath:e.path,kind:"ambiguous",owners:e.groups}]:[{filePath:e.path,kind:"owned",owner:e.group,relativePath:e.relativePath}]}
@@ -1 +1 @@
1
- import{matchesSuiteSelectors as E,parseSuiteSelectors as T}from"../../domain/suite-selection.js";import{isTypedTestFile as O}from"../../domain/identifiers.js";import{isUnknownRecord as I}from"../../kernel/unknown-values.js";import{DEFAULT_RUNTIME_ISOLATION_POLICY as g,normalizeRuntimeIsolationPolicy as m}from"../../domain/runtime-isolation-contract.js";import{normalizePath as y}from"./paths.js";import{optionalArray as R}from"./skip-config.js";export{DEFAULT_RUNTIME_ISOLATION_POLICY,isRuntimeIsolationPolicy,normalizeRuntimeIsolationPolicy,RUNTIME_ISOLATION_POLICIES}from"../../domain/runtime-isolation-contract.js";const L=String;export function normalizeRuntimeIsolationConfig(i,{owner:t,plainListed:p=!0,suites:u}){if(!i)return h();if(typeof i=="string")return{...h(),defaultPolicy:m(i,`${t} runtimeIsolation`)};if(!I(i))throw new Error(`${t} runtimeIsolation must be a policy string or object`);const c=u??[],n=new Set(c.flatMap(o=>(o.files??[]).map(y))),f=i.default==null?g:m(i.default,`${t} runtimeIsolation.default`),l=[],$=new Set;for(const[o,a]of R(i.suites,`${t} runtimeIsolation.suites`).entries()){if(!I(a))throw new Error(`${t} runtimeIsolation.suites entries must be objects`);const r=a,e=L(r.selector||"").trim();if(!e)throw new Error(`${t} runtimeIsolation.suites[${o.toString()}] requires a selector`);const S=T([e]);if(S.length!==1)throw new Error(`${t} runtimeIsolation.suites["${e}"] is invalid`);const s=S[0];if(s.kind!=="typed")throw new Error(`${t} runtimeIsolation.suites["${e}"] must use a typed selector like ui:auth`);if($.has(s.raw))throw new Error(`${t} defines duplicate runtimeIsolation.suites selector "${s.raw}"`);if(!c.some(w=>E(w.testType,w.name,[s])))throw new Error(`${t} runtimeIsolation.suites selector "${s.raw}" did not match any discovered suite`);$.add(s.raw),l.push({selector:s,policy:m(r.policy||r.runtimeIsolation,`${t} runtimeIsolation.suites["${s.raw}"].policy`)})}const d=[],P=new Set;for(const[o,a]of R(i.files,`${t} runtimeIsolation.files`).entries()){if(!I(a))throw new Error(`${t} runtimeIsolation.files entries must be objects`);const r=a,e=y(_(r.path,`${t} runtimeIsolation.files[${o.toString()}].path`));if(!e)throw new Error(`${t} runtimeIsolation.files[${o.toString()}] requires a path`);if(!n.has(e)&&(p||O(e)))throw new Error(`${t} runtimeIsolation.files["${e}"] did not match any discovered test file`);if(P.has(e))throw new Error(`${t} defines duplicate runtimeIsolation.files path "${e}"`);P.add(e),d.push({path:e,policy:m(r.policy||r.runtimeIsolation,`${t} runtimeIsolation.files["${e}"].policy`)})}return{defaultPolicy:f,suites:l,files:d,filePolicyByPath:new Map(d.map(o=>[o.path,o.policy]))}}export function resolveRuntimeIsolationPolicy(i,t,p){const u=y(p),c=i.testkit?.fileMetadataByPath?.get(u)||null,n=i.testkit?.runtimeIsolation||h(),f=c?.runtimeIsolation||n.filePolicyByPath.get(u);if(f)return f;for(const l of n.suites)if(E(t.testType,t.name,[l.selector]))return l.policy;return n.defaultPolicy}function h(){return{defaultPolicy:g,suites:[],files:[],filePolicyByPath:new Map}}function _(i,t){if(i==null||typeof i=="string")return i;throw new Error(`${t} must be a string`)}
1
+ import{matchesSuiteSelectors as g,parseSuiteSelectors as O}from"../../domain/suite-selection.js";import{isTypedTestFile as L}from"../../domain/identifiers.js";import{rejectUnknownKeys as d}from"../../kernel/known-keys.js";import{isUnknownRecord as y}from"../../kernel/unknown-values.js";import{DEFAULT_RUNTIME_ISOLATION_POLICY as R,normalizeRuntimeIsolationPolicy as f}from"../../domain/runtime-isolation-contract.js";import{normalizePath as h}from"./paths.js";import{optionalArray as T}from"./skip-config.js";export{DEFAULT_RUNTIME_ISOLATION_POLICY,isRuntimeIsolationPolicy,normalizeRuntimeIsolationPolicy,RUNTIME_ISOLATION_POLICIES}from"../../domain/runtime-isolation-contract.js";const _=String;export function normalizeRuntimeIsolationConfig(i,{owner:t,plainListed:p=!0,suites:u}){if(!i)return $();if(typeof i=="string")return{...$(),defaultPolicy:f(i,`${t} runtimeIsolation`)};if(!y(i))throw new Error(`${t} runtimeIsolation must be a policy string or object`);d(i,["default","files","suites"],`${t} runtimeIsolation`);const c=u??[],n=new Set(c.flatMap(o=>(o.files??[]).map(h))),m=i.default==null?R:f(i.default,`${t} runtimeIsolation.default`),l=[],P=new Set;for(const[o,a]of T(i.suites,`${t} runtimeIsolation.suites`).entries()){if(!y(a))throw new Error(`${t} runtimeIsolation.suites entries must be objects`);const s=a;d(s,["policy","runtimeIsolation","selector"],`${t} runtimeIsolation.suites entry`);const e=_(s.selector||"").trim();if(!e)throw new Error(`${t} runtimeIsolation.suites[${o.toString()}] requires a selector`);const E=O([e]);if(E.length!==1)throw new Error(`${t} runtimeIsolation.suites["${e}"] is invalid`);const r=E[0];if(r.kind!=="typed")throw new Error(`${t} runtimeIsolation.suites["${e}"] must use a typed selector like ui:auth`);if(P.has(r.raw))throw new Error(`${t} defines duplicate runtimeIsolation.suites selector "${r.raw}"`);if(!c.some(w=>g(w.testType,w.name,[r])))throw new Error(`${t} runtimeIsolation.suites selector "${r.raw}" did not match any discovered suite`);P.add(r.raw),l.push({selector:r,policy:f(s.policy||s.runtimeIsolation,`${t} runtimeIsolation.suites["${r.raw}"].policy`)})}const I=[],S=new Set;for(const[o,a]of T(i.files,`${t} runtimeIsolation.files`).entries()){if(!y(a))throw new Error(`${t} runtimeIsolation.files entries must be objects`);const s=a;d(s,["path","policy","runtimeIsolation"],`${t} runtimeIsolation.files entry`);const e=h(b(s.path,`${t} runtimeIsolation.files[${o.toString()}].path`));if(!e)throw new Error(`${t} runtimeIsolation.files[${o.toString()}] requires a path`);if(!n.has(e)&&(p||L(e)))throw new Error(`${t} runtimeIsolation.files["${e}"] did not match any discovered test file`);if(S.has(e))throw new Error(`${t} defines duplicate runtimeIsolation.files path "${e}"`);S.add(e),I.push({path:e,policy:f(s.policy||s.runtimeIsolation,`${t} runtimeIsolation.files["${e}"].policy`)})}return{defaultPolicy:m,suites:l,files:I,filePolicyByPath:new Map(I.map(o=>[o.path,o.policy]))}}export function resolveRuntimeIsolationPolicy(i,t,p){const u=h(p),c=i.testkit?.fileMetadataByPath?.get(u)||null,n=i.testkit?.runtimeIsolation||$(),m=c?.runtimeIsolation||n.filePolicyByPath.get(u);if(m)return m;for(const l of n.suites)if(g(t.testType,t.name,[l.selector]))return l.policy;return n.defaultPolicy}function $(){return{defaultPolicy:R,suites:[],files:[],filePolicyByPath:new Map}}function b(i,t){if(i==null||typeof i=="string")return i;throw new Error(`${t} must be a string`)}
@@ -1 +1 @@
1
- import{isTypedTestFile as y}from"../../domain/identifiers.js";import{matchesSuiteSelectors as q,parseSuiteSelectors as S}from"../../domain/suite-selection.js";import{normalizePath as E}from"./paths.js";import{isUnknownRecord as b}from"../../kernel/unknown-values.js";import{requireObjectRecord as g}from"../../evidence/read-fields.js";const $=String;export function normalizeSkipReason(r,e){const a=$(r||"").trim();if(!a)throw new Error(`${e} requires a non-empty reason`);return a}export function normalizeSkipConfig(r,{owner:e,plainListed:a=!0,suites:u}){if(!r)return;const l=g(r,`${e} skip`),p=new Set(u.flatMap(s=>s.files.map(E))),c=new Set,f=[];for(const s of optionalArray(l.files,`${e} skip.files`)){const n=k(s,`${e} skip.files entries must be objects`),t=E(typeof n.path=="string"?n.path:void 0),i=normalizeSkipReason(n.reason,`${e} skip.files["${t}"]`);if(!t)throw new Error(`${e} skip.files entries require a non-empty path`);if(c.has(t))throw new Error(`${e} defines duplicate skip.files path "${t}"`);if(!p.has(t)&&(a||y(t)))throw new Error(`${e} skip.files path "${t}" did not match any discovered suite file`);c.add(t),f.push({path:t,reason:i})}const m=optionalArray(l.suites,`${e} skip.suites`).map((s,n)=>{const t=k(s,`${e} skip.suites entries must be objects`),i=$(t.selector||"").trim();if(!i)throw new Error(`${e} skip.suites[${String(n)}] requires a non-empty selector`);const w=normalizeSkipReason(t.reason,`${e} skip.suites["${i}"]`),h=S([i]),R=h[0];if(h.length!==1)throw new Error(`${e} skip.suites["${i}"] is invalid`);return{selector:R,reason:w}}),d=new Set,o=[];for(const s of m){if(d.has(s.selector.raw))throw new Error(`${e} defines duplicate skip.suites selector "${s.selector.raw}"`);if(!u.some(t=>q(t.testType,t.name,[s.selector]))&&(a||s.selector.kind==="typed"))throw new Error(`${e} skip.suites selector "${s.selector.raw}" did not match any discovered suite`);d.add(s.selector.raw),o.push(s)}if(!(f.length===0&&o.length===0))return{files:f,fileReasonByPath:new Map(f.map(s=>[s.path,s.reason])),suites:o}}export function normalizeServiceRequirements(r,{owner:e,plainListed:a=!0,suites:u}){if(!r)return x();const l=g(r,`${e} requirements`),p=new Set(u.flatMap(o=>o.files)),c=[],f=new Set;for(const o of optionalArray(l.suites,`${e} requirements.suites`)){const s=k(o,`${e} requirements.suites entries must be objects`),n=$(s.selector||"").trim();if(!n)throw new Error(`${e} requirements.suites entries require a selector`);const t=S([n]),i=t[0];if(t.length!==1)throw new Error(`${e} requirements.suites["${n}"] is invalid`);if(i.kind!=="typed")throw new Error(`${e} requirements.suites["${n}"] must use a typed selector like contract:health`);if(f.has(i.raw))throw new Error(`${e} defines duplicate requirements.suites selector "${i.raw}"`);if(!u.some(h=>q(h.testType,h.name,[i])))throw new Error(`${e} requirements.suites selector "${i.raw}" did not match any discovered suite`);f.add(i.raw),c.push({selector:i,locks:normalizeRequirementLocks(s.locks,`${e} requirements.suites["${i.raw}"].locks`)})}const m=[],d=new Set;for(const[o,s]of optionalArray(l.files,`${e} requirements.files`).entries()){const n=k(s,`${e} requirements.files entries must be objects`),t=$(n.path||"").trim();if(!t)throw new Error(`${e} requirements.files[${String(o)}] requires a path`);if(!p.has(t)&&(a||y(t)))throw new Error(`${e} requirements.files["${t}"] did not match any discovered test file`);if(d.has(t))throw new Error(`${e} defines duplicate requirements.files path "${t}"`);d.add(t),m.push({path:t,locks:normalizeRequirementLocks(n.locks,`${e} requirements.files["${t}"].locks`)})}return{suites:c,files:m,fileLocksByPath:new Map(m.map(o=>[o.path,o.locks]))}}export function normalizeRequirementLocks(r,e){const a=Array.isArray(r)?r:r==null?[]:[r],u=new Set,l=[];for(const p of a){const c=$(p||"").trim();if(!c)throw new Error(`${e} entries must be non-empty strings`);u.has(c)||(u.add(c),l.push(c))}return l.sort()}function x(){return{suites:[],files:[],fileLocksByPath:new Map}}function k(r,e){if(!b(r))throw new Error(e);return r}export function optionalArray(r,e){if(r==null)return[];if(!Array.isArray(r))throw new Error(`${e} must be an array`);return r}
1
+ import{isTypedTestFile as w}from"../../domain/identifiers.js";import{matchesSuiteSelectors as S,parseSuiteSelectors as E}from"../../domain/suite-selection.js";import{normalizePath as g}from"./paths.js";import{rejectUnknownKeys as p}from"../../kernel/known-keys.js";import{isUnknownRecord as j}from"../../kernel/unknown-values.js";import{requireObjectRecord as R}from"../../evidence/read-fields.js";const k=String;export function normalizeSkipReason(r,e){const u=k(r||"").trim();if(!u)throw new Error(`${e} requires a non-empty reason`);return u}export function normalizeSkipConfig(r,{owner:e,plainListed:u=!0,suites:l}){if(!r)return;const c=R(r,`${e} skip`);p(c,["files","suites"],`${e} skip`);const m=new Set(l.flatMap(t=>t.files.map(g))),a=new Set,f=[];for(const t of optionalArray(c.files,`${e} skip.files`)){const i=y(t,`${e} skip.files entries must be objects`);p(i,["path","reason"],`${e} skip.files entry`);const s=g(typeof i.path=="string"?i.path:void 0),n=normalizeSkipReason(i.reason,`${e} skip.files["${s}"]`);if(!s)throw new Error(`${e} skip.files entries require a non-empty path`);if(a.has(s))throw new Error(`${e} defines duplicate skip.files path "${s}"`);if(!m.has(s)&&(u||w(s)))throw new Error(`${e} skip.files path "${s}" did not match any discovered suite file`);a.add(s),f.push({path:s,reason:n})}const h=optionalArray(c.suites,`${e} skip.suites`).map((t,i)=>{const s=y(t,`${e} skip.suites entries must be objects`);p(s,["reason","selector"],`${e} skip.suites entry`);const n=k(s.selector||"").trim();if(!n)throw new Error(`${e} skip.suites[${String(i)}] requires a non-empty selector`);const q=normalizeSkipReason(s.reason,`${e} skip.suites["${n}"]`),$=E([n]),b=$[0];if($.length!==1)throw new Error(`${e} skip.suites["${n}"] is invalid`);return{selector:b,reason:q}}),d=new Set,o=[];for(const t of h){if(d.has(t.selector.raw))throw new Error(`${e} defines duplicate skip.suites selector "${t.selector.raw}"`);if(!l.some(s=>S(s.testType,s.name,[t.selector]))&&(u||t.selector.kind==="typed"))throw new Error(`${e} skip.suites selector "${t.selector.raw}" did not match any discovered suite`);d.add(t.selector.raw),o.push(t)}if(!(f.length===0&&o.length===0))return{files:f,fileReasonByPath:new Map(f.map(t=>[t.path,t.reason])),suites:o}}export function normalizeServiceRequirements(r,{owner:e,plainListed:u=!0,suites:l}){if(!r)return x();const c=R(r,`${e} requirements`);p(c,["files","suites"],`${e} requirements`);const m=new Set(l.flatMap(o=>o.files)),a=[],f=new Set;for(const o of optionalArray(c.suites,`${e} requirements.suites`)){const t=y(o,`${e} requirements.suites entries must be objects`);p(t,["locks","selector"],`${e} requirements.suites entry`);const i=k(t.selector||"").trim();if(!i)throw new Error(`${e} requirements.suites entries require a selector`);const s=E([i]),n=s[0];if(s.length!==1)throw new Error(`${e} requirements.suites["${i}"] is invalid`);if(n.kind!=="typed")throw new Error(`${e} requirements.suites["${i}"] must use a typed selector like ui:health`);if(f.has(n.raw))throw new Error(`${e} defines duplicate requirements.suites selector "${n.raw}"`);if(!l.some($=>S($.testType,$.name,[n])))throw new Error(`${e} requirements.suites selector "${n.raw}" did not match any discovered suite`);f.add(n.raw),a.push({selector:n,locks:normalizeRequirementLocks(t.locks,`${e} requirements.suites["${n.raw}"].locks`)})}const h=[],d=new Set;for(const[o,t]of optionalArray(c.files,`${e} requirements.files`).entries()){const i=y(t,`${e} requirements.files entries must be objects`);p(i,["locks","path"],`${e} requirements.files entry`);const s=k(i.path||"").trim();if(!s)throw new Error(`${e} requirements.files[${String(o)}] requires a path`);if(!m.has(s)&&(u||w(s)))throw new Error(`${e} requirements.files["${s}"] did not match any discovered test file`);if(d.has(s))throw new Error(`${e} defines duplicate requirements.files path "${s}"`);d.add(s),h.push({path:s,locks:normalizeRequirementLocks(i.locks,`${e} requirements.files["${s}"].locks`)})}return{suites:a,files:h,fileLocksByPath:new Map(h.map(o=>[o.path,o.locks]))}}export function normalizeRequirementLocks(r,e){const u=Array.isArray(r)?r:r==null?[]:[r],l=new Set,c=[];for(const m of u){const a=k(m||"").trim();if(!a)throw new Error(`${e} entries must be non-empty strings`);l.has(a)||(l.add(a),c.push(a))}return c.sort()}function x(){return{suites:[],files:[],fileLocksByPath:new Map}}function y(r,e){if(!j(r))throw new Error(e);return r}export function optionalArray(r,e){if(r==null)return[];if(!Array.isArray(r))throw new Error(`${e} must be an array`);return r}
@@ -1,2 +1,2 @@
1
- import{randomUUID as y}from"node:crypto";import{managedHostProcessInfrastructureError as R,runManagedHostProcess as C,startManagedHostProcess as M}from"../process/index.js";import{LOCAL_ADMIN_DB as p,LOCAL_ADMIN_QUERY_RETRY_INTERVAL_MS as L,LOCAL_ADMIN_QUERY_RETRY_MS as x,LOCAL_DROP_DATABASE_POLL_INTERVAL_MS as P,LOCAL_DROP_DATABASE_TIMEOUT_MS as h}from"./constants.js";import{buildDatabaseUrl as I,escapeIdentifier as c,escapeSqlLiteral as E}from"./naming.js";import{runDockerPsql as $}from"./postgres/index.js";import{readDynamicProperty as v}from"../kernel/unknown-values.js";export{I as buildDatabaseUrl};export const ADMIN_QUERY_COMMAND_TIMEOUT_MS=15e3;export async function databaseExists(t,e,o={}){return(await runAdminQuery(t,["-tAc",`SELECT 1 FROM pg_database WHERE datname = '${E(e)}'`],o)).trim()==="1"}export async function createEmptyDatabase(t,e,o={}){await runAdminQuery(t,["-c",`CREATE DATABASE "${c(e)}"`],o)}export async function cloneDatabaseFromTemplate(t,e,o,r={}){await runAdminQuery(t,["-c",`CREATE DATABASE "${c(e)}" TEMPLATE "${c(o)}"`],r)}export async function dropDatabaseWithForceOrDrain(t,e,o={}){const r={signal:o.signal};try{await runAdminQuery(t,["-c",`DROP DATABASE IF EXISTS "${c(e)}" WITH (FORCE)`],r);return}catch(n){if(!W(n))throw n}if(!await databaseExists(t,e,r))return;let s=!1;try{await runAdminQuery(t,["-c",`ALTER DATABASE "${c(e)}" WITH ALLOW_CONNECTIONS false`],r),s=!0,await waitForDatabaseConnectionsToDrain(t,e,{signal:o.signal}),await runAdminQuery(t,["-c",`DROP DATABASE IF EXISTS "${c(e)}"`],r),s=!1}finally{s&&await databaseExists(t,e)&&await runAdminQuery(t,["-c",`ALTER DATABASE "${c(e)}" WITH ALLOW_CONNECTIONS true`]).catch(()=>{})}}export async function waitForDatabaseConnectionsToDrain(t,e,o={}){const r=o.timeoutMs??h,s=Date.now()+r,n={signal:o.signal};for(;;){O(o.signal),await N(t,e,n);const i=await k(t,e,n);if(i===0)return;if(Date.now()>=s)throw new Error(`Timed out waiting for database "${e}" connections to close (${String(i)} remaining)`);await A(P,o.signal)}}export async function runAdminQuery(t,e,o={}){const r=buildAdminQueryCommandArgs(t,e),s=Date.now();for(;;){O(o.signal);try{let n;if(b(t)){const a=`testkit-admin-${y()}`,D=(l,m)=>C({args:buildAdminQueryCommandArgs(t,["-tAc",`SELECT ${l} FROM pg_stat_activity WHERE application_name = '${a}'`]),cwd:process.cwd(),displayLabel:`Docker Postgres admin session for ${t.containerName}`,env:process.env,executable:"docker",signal:m,timeoutMs:ADMIN_QUERY_COMMAND_TIMEOUT_MS}),_=M({args:["exec","-e",`PGAPPNAME=${a}`,...r.slice(1)],cwd:process.cwd(),displayLabel:`Docker Postgres admin query for ${t.containerName}`,env:process.env,executable:"docker",outputMode:"bounded-capture",recoveryIdentity:"none",signal:o.signal,timeoutMs:null}),d=new AbortController,f=(async()=>{for(let l=0;l<2;){await A(ADMIN_QUERY_COMMAND_TIMEOUT_MS,d.signal);const m=await D("count(*)",d.signal);if(d.signal.aborted)return;if(m.timedOut||m.exitCode!==0)break;l=m.stdout.trim()==="0"?l+1:0}d.abort("stalled"),await _.stop()})().catch(()=>{}),u=await _.completion.finally(()=>{d.abort()});await f;const g=d.signal.reason==="stalled";(u.cancelled||g)&&await D("pg_terminate_backend(pid)");const T=R(`Docker Postgres admin query for ${t.containerName}`,g?{...u,timedOut:!0}:u);if(T)throw T;n={exitCode:u.exitCode,stderr:u.stderr,stdout:u.stdout}}else{const a={...process.env,PGPASSWORD:t.password,...t.sslMode?{PGSSLMODE:t.sslMode}:{}};n=await $({args:r,env:a,timeoutMs:ADMIN_QUERY_COMMAND_TIMEOUT_MS,connection:{host:t.host,port:t.port,user:t.user,password:t.password,database:t.adminDatabase||p,sslMode:t.sslMode},signal:o.signal})}if(n.exitCode!=null&&n.exitCode!==0){const a=new Error(n.shortMessage||n.stderr||n.stdout||`psql failed with exit code ${String(n.exitCode)}`);throw Reflect.set(a,"stdout",n.stdout||""),Reflect.set(a,"stderr",n.stderr||""),a}const{stdout:i}=n;return i}catch(n){if(Date.now()-s>=x||!isTransientAdminQueryConnectionError(n))throw n;await A(L,o.signal)}}}export function buildAdminQueryCommandArgs(t,e){return b(t)?["exec","-e",`PGPASSWORD=${t.password}`,t.containerId,"psql","-v","ON_ERROR_STOP=1","-U",t.user,"-d",t.adminDatabase||p,"-p",String(t.containerPort||5432),...e]:["-v","ON_ERROR_STOP=1","-h",t.host,"-p",String(t.port),"-U",t.user,"-d",t.adminDatabase||p,...e]}export function isTransientAdminQueryConnectionError(t){const e=S(t);return e.includes('connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed')||e.includes("No such file or directory")||e.includes("the database system is starting up")||e.includes("could not connect to server")||e.includes("connection refused")||e.includes("Connection timed out")||e.includes("connection timed out")||e.includes("Operation timed out")||e.includes("i/o timeout")||e.includes("server closed the connection unexpectedly")||e.includes("terminating connection due to administrator command")}async function N(t,e,o){await runAdminQuery(t,["-c",["SELECT pg_terminate_backend(pid)","FROM pg_stat_activity",`WHERE datname = '${E(e)}' AND pid <> pg_backend_pid();`].join(" ")],o)}async function k(t,e,o){const r=await runAdminQuery(t,["-tAc",["SELECT COUNT(*)","FROM pg_stat_activity",`WHERE datname = '${E(e)}' AND pid <> pg_backend_pid();`].join(" ")],o);return Number.parseInt(r.trim(),10)||0}function W(t){const e=S(t);return e.includes('syntax error at or near "WITH"')||e.includes('option "force"')}function A(t,e){return new Promise((o,r)=>{if(e?.aborted){r(w(e));return}const s=setTimeout(n,t);e?.addEventListener("abort",i,{once:!0});function n(){e?.removeEventListener("abort",i),o()}function i(){clearTimeout(s),r(e?w(e):new Error("Database administration aborted"))}})}function O(t){if(t?.aborted)throw w(t)}function w(t){return t.reason instanceof Error?t.reason:new Error("Database administration aborted",{cause:t.reason})}function S(t){return["shortMessage","stderr","stdout","message"].map(e=>U(v(t,e)||"")).join(`
2
- `)}function U(t){if(typeof t=="symbol")throw new TypeError("Cannot convert a Symbol value to a string");return String(t)}function b(t){return!!t.containerName}
1
+ import{randomUUID as M}from"node:crypto";import{managedHostProcessInfrastructureError as _,runManagedHostProcess as L,startManagedHostProcess as h}from"../process/index.js";import{LOCAL_ADMIN_DB as p,LOCAL_ADMIN_QUERY_RETRY_INTERVAL_MS as x,LOCAL_ADMIN_QUERY_RETRY_MS as P,LOCAL_DROP_DATABASE_POLL_INTERVAL_MS as $,LOCAL_DROP_DATABASE_TIMEOUT_MS as I}from"./constants.js";import{buildDatabaseUrl as v,escapeIdentifier as d,escapeSqlLiteral as E}from"./naming.js";import{runDockerPsql as N}from"./postgres/index.js";import{readDynamicProperty as b}from"../kernel/unknown-values.js";import{toError as k}from"../kernel/error-serialization.js";export{v as buildDatabaseUrl};export const ADMIN_QUERY_COMMAND_TIMEOUT_MS=15e3;export async function databaseExists(t,e,o={}){return(await runAdminQuery(t,["-tAc",`SELECT 1 FROM pg_database WHERE datname = '${E(e)}'`],o)).trim()==="1"}export async function createEmptyDatabase(t,e,o={}){await runAdminQuery(t,["-c",`CREATE DATABASE "${d(e)}"`],o)}export async function cloneDatabaseFromTemplate(t,e,o,r={}){await runAdminQuery(t,["-c",`CREATE DATABASE "${d(e)}" TEMPLATE "${d(o)}"`],r)}export async function dropDatabaseWithForceOrDrain(t,e,o={}){const r={signal:o.signal};try{await runAdminQuery(t,["-c",`DROP DATABASE IF EXISTS "${d(e)}" WITH (FORCE)`],r);return}catch(s){if(!U(s))throw s}if(!await databaseExists(t,e,r))return;let a=!1;try{await runAdminQuery(t,["-c",`ALTER DATABASE "${d(e)}" WITH ALLOW_CONNECTIONS false`],r),a=!0,await waitForDatabaseConnectionsToDrain(t,e,{signal:o.signal}),await runAdminQuery(t,["-c",`DROP DATABASE IF EXISTS "${d(e)}"`],r),a=!1}finally{a&&await databaseExists(t,e)&&await runAdminQuery(t,["-c",`ALTER DATABASE "${d(e)}" WITH ALLOW_CONNECTIONS true`]).catch(()=>{})}}export async function waitForDatabaseConnectionsToDrain(t,e,o={}){const r=o.timeoutMs??I,a=Date.now()+r,s={signal:o.signal};for(;;){O(o.signal),await W(t,e,s);const n=await F(t,e,s);if(n===0)return;if(Date.now()>=a)throw new Error(`Timed out waiting for database "${e}" connections to close (${String(n)} remaining)`);await A($,o.signal)}}export async function runAdminQuery(t,e,o={}){const r=buildAdminQueryCommandArgs(t,e),a=Date.now();for(;;){O(o.signal);let s=null;try{let n;if(C(t)){const i=`testkit-admin-${M()}`,g=(c,m)=>L({args:buildAdminQueryCommandArgs(t,["-tAc",`SELECT ${c} FROM pg_stat_activity WHERE application_name = '${i}'`]),cwd:process.cwd(),displayLabel:`Docker Postgres admin session for ${t.containerName}`,env:process.env,executable:"docker",signal:m,timeoutMs:ADMIN_QUERY_COMMAND_TIMEOUT_MS}),D=h({args:["exec","-e",`PGAPPNAME=${i}`,...r.slice(1)],cwd:process.cwd(),displayLabel:`Docker Postgres admin query for ${t.containerName}`,env:process.env,executable:"docker",outputMode:"bounded-capture",recoveryIdentity:"none",signal:o.signal,timeoutMs:null}),u=new AbortController,y=(async()=>{for(let c=0;c<2;){await A(ADMIN_QUERY_COMMAND_TIMEOUT_MS,u.signal);const m=await g("count(*)",u.signal);if(u.signal.aborted)return;if(m.timedOut||m.exitCode!==0)break;c=m.stdout.trim()==="0"?c+1:0}u.abort("stalled"),await D.stop()})().catch(()=>{}),l=await D.completion.finally(()=>{u.abort()});await y;const T=u.signal.reason==="stalled";(l.cancelled||T)&&(s=await Promise.resolve().then(()=>g("pg_terminate_backend(pid)")).then(c=>H(t.containerName,c),k));const f=_(`Docker Postgres admin query for ${t.containerName}`,T?{...l,timedOut:!0}:l);if(f)throw B(f,s);n={exitCode:l.exitCode,stderr:l.stderr,stdout:l.stdout}}else{const i={...process.env,PGPASSWORD:t.password,...t.sslMode?{PGSSLMODE:t.sslMode}:{}};n=await N({args:r,env:i,timeoutMs:ADMIN_QUERY_COMMAND_TIMEOUT_MS,connection:{host:t.host,port:t.port,user:t.user,password:t.password,database:t.adminDatabase||p,sslMode:t.sslMode},signal:o.signal})}if(n.exitCode!=null&&n.exitCode!==0){const i=new Error(n.shortMessage||n.stderr||n.stdout||`psql failed with exit code ${String(n.exitCode)}`);throw Reflect.set(i,"stdout",n.stdout||""),Reflect.set(i,"stderr",n.stderr||""),i}const{stdout:R}=n;return R}catch(n){if(s!==null||Date.now()-a>=P||!isTransientAdminQueryConnectionError(n))throw n;await A(x,o.signal)}}}export function buildAdminQueryCommandArgs(t,e){return C(t)?["exec","-e",`PGPASSWORD=${t.password}`,t.containerId,"psql","-v","ON_ERROR_STOP=1","-U",t.user,"-d",t.adminDatabase||p,"-p",String(t.containerPort||5432),...e]:["-v","ON_ERROR_STOP=1","-h",t.host,"-p",String(t.port),"-U",t.user,"-d",t.adminDatabase||p,...e]}export function isTransientAdminQueryConnectionError(t){const e=S(t);return e.includes('connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed')||e.includes("No such file or directory")||e.includes("the database system is starting up")||e.includes("could not connect to server")||e.includes("connection refused")||e.includes("Connection timed out")||e.includes("connection timed out")||e.includes("Operation timed out")||e.includes("i/o timeout")||e.includes("server closed the connection unexpectedly")||e.includes("terminating connection due to administrator command")}async function W(t,e,o){await runAdminQuery(t,["-c",["SELECT pg_terminate_backend(pid)","FROM pg_stat_activity",`WHERE datname = '${E(e)}' AND pid <> pg_backend_pid();`].join(" ")],o)}async function F(t,e,o){const r=await runAdminQuery(t,["-tAc",["SELECT COUNT(*)","FROM pg_stat_activity",`WHERE datname = '${E(e)}' AND pid <> pg_backend_pid();`].join(" ")],o);return Number.parseInt(r.trim(),10)||0}function U(t){const e=S(t);return e.includes('syntax error at or near "WITH"')||e.includes('option "force"')}function A(t,e){return new Promise((o,r)=>{if(e?.aborted){r(w(e));return}const a=setTimeout(s,t);e?.addEventListener("abort",n,{once:!0});function s(){e?.removeEventListener("abort",n),o()}function n(){clearTimeout(a),r(e?w(e):new Error("Database administration aborted"))}})}function O(t){if(t?.aborted)throw w(t)}function w(t){return t.reason instanceof Error?t.reason:new Error("Database administration aborted",{cause:t.reason})}function H(t,e){const o=`Docker Postgres admin session for ${t}`,r=_(o,e);return r!==null||e.exitCode===0?r:new Error(`${o} exited with code ${String(e.exitCode)}: ${e.stderr.trim()}`)}function B(t,e){if(e===null)return t;const o=new AggregateError([t,e],`${t.message}; terminating its server backend also failed: ${e.message}`,{cause:t});return Object.defineProperty(o,"result",{enumerable:!1,value:b(t,"result")}),o}function S(t){return["shortMessage","stderr","stdout","message"].map(e=>q(b(t,e)||"")).join(`
2
+ `)}function q(t){if(typeof t=="symbol")throw new TypeError("Cannot convert a Symbol value to a string");return String(t)}function C(t){return!!t.containerName}
@@ -1 +1 @@
1
- import{runDockerPsql as E}from"./postgres/index.js";import{awaitCapturedResult as _}from"../evidence/step-output.js";import{applySetupNodeOptions as M,runConfiguredStep as D}from"../steps/run.js";import{announceResolvedToolchain as R,applyToolchainEnv as y,resolveConfiguredToolchain as A}from"../toolchains/index.js";import{resolveConfiguredCwd as P,resolveConfiguredPath as N,summarizeConfiguredStep as m}from"../steps/configured-steps.js";const U=3600*1e3;export async function runConfiguredSteps({config:e,steps:n=[],env:S,labelPrefix:O,ownedContainer:f=null,reporter:s=null,setupRegistry:o=null,parentOperation:g=null,startIndex:p=0,afterStep:c=null,signal:h,stepTimeoutMs:C}){if(n.length===0)return;const b=L(C),a=await A(e,{signal:h});await R(e,a,s);for(const[v,t]of n.entries()){const w=p+v+1,r=`${O}:${e.name}:${String(w)}`,u=o?.start({config:e,stage:r,kind:"setup-step",summary:m(t),parentId:$(g)});s?.phaseStarted?.(r);try{const i=y(M(S),a),l={livePrefix:`[${e.runtimeLabel||e.name}:${e.name}]`,liveWriter:s?.outputMode==="debug"?d=>{s.writeDebugLine?.(d)}:null,logRecord:u?._logRecord||null};if(t.kind==="sql-file"){const d=await E({databaseUrl:i.DATABASE_URL,args:[i.DATABASE_URL,"-v","ON_ERROR_STOP=1","-X","-f",N(e.productDir,t.cwd,t.path)],cwd:P(e.productDir,t.cwd),env:i,ownedContainer:f});_(d,l)}else await D(e,t,i,a,{...l,ownedContainer:f,signal:h,timeoutMs:b});const T=u&&o?o.finish(u,{status:"passed",summary:m(t)}):null;T&&s?.setupOperationFinished?.(T)}catch(i){const l=u&&o?o.finish(u,{status:"failed",summary:m(t),error:I(i)}):null;throw l&&s?.setupOperationFinished?.(l),i}c&&await c({step:t,index:p+v,stepNumber:w,label:r})}}function $(e){return typeof e=="string"?e||null:e?.id||null}function I(e){return(typeof e!="object"||e===null)&&typeof e!="function"?e:Reflect.get(e,"message")||e}function L(e){const n=e??U;if(!Number.isSafeInteger(n)||n<=0)throw new Error("Configured step timeout must be a positive safe integer in milliseconds");return n}
1
+ import{runDockerPsql as E}from"./postgres/index.js";import{awaitCapturedResult as M}from"../evidence/step-output.js";import{applySetupNodeOptions as _,runConfiguredStep as D}from"../steps/run.js";import{announceResolvedToolchain as R,applyToolchainEnv as y,resolveConfiguredToolchain as A}from"../toolchains/index.js";import{resolveConfiguredCwd as P,resolveConfiguredPath as N,summarizeConfiguredStep as f}from"../steps/configured-steps.js";const U=3600*1e3;export async function runConfiguredSteps({config:e,steps:n=[],env:O,labelPrefix:C,ownedContainer:p=null,reporter:s=null,setupRegistry:o=null,parentOperation:b=null,startIndex:c=0,afterStep:h=null,signal:r,stepTimeoutMs:g}){if(n.length===0)return;const v=L(g),a=await A(e,{signal:r});await R(e,a,s);for(const[w,t]of n.entries()){const T=c+w+1,m=`${C}:${e.name}:${String(T)}`,u=o?.start({config:e,stage:m,kind:"setup-step",summary:f(t),parentId:$(b)});s?.phaseStarted?.(m);try{const i=y(_(O),a),l={livePrefix:`[${e.runtimeLabel||e.name}:${e.name}]`,liveWriter:s?.outputMode==="debug"?d=>{s.writeDebugLine?.(d)}:null,logRecord:u?._logRecord||null};if(t.kind==="sql-file"){const d=await E({databaseUrl:i.DATABASE_URL,args:[i.DATABASE_URL,"-v","ON_ERROR_STOP=1","-X","-f",N(e.productDir,t.cwd,t.path)],cwd:P(e.productDir,t.cwd),env:i,ownedContainer:p,signal:r,timeoutMs:v});M(d,l)}else await D(e,t,i,a,{...l,ownedContainer:p,signal:r,timeoutMs:v});const S=u&&o?o.finish(u,{status:"passed",summary:f(t)}):null;S&&s?.setupOperationFinished?.(S)}catch(i){const l=u&&o?o.finish(u,{status:"failed",summary:f(t),error:I(i)}):null;throw l&&s?.setupOperationFinished?.(l),i}h&&await h({step:t,index:c+w,stepNumber:T,label:m})}}function $(e){return typeof e=="string"?e||null:e?.id||null}function I(e){return(typeof e!="object"||e===null)&&typeof e!="function"?e:Reflect.get(e,"message")||e}function L(e){const n=e??U;if(!Number.isSafeInteger(n)||n<=0)throw new Error("Configured step timeout must be a positive safe integer in milliseconds");return n}
@@ -1 +1 @@
1
- export const ENGINE_IDS=Object.freeze(["node-vitest","playwright"]),PUBLIC_TEST_TYPES=Object.freeze(["ui","contract","data"]),RUNNER_PROFILE_IDS=Object.freeze(["node-vitest-data","node-vitest-contract","node-vitest","playwright-ui"]);const n=new Set(ENGINE_IDS),o=new Set(PUBLIC_TEST_TYPES),r=new Set(RUNNER_PROFILE_IDS);export function isEngineId(t){return typeof t=="string"&&n.has(t)}export function isTypedTestFile(t){return PUBLIC_TEST_TYPES.some(e=>t.endsWith(`.${e}.testkit.ts`))}export function isPublicTestType(t){return typeof t=="string"&&o.has(t)}export function isRunnerProfileId(t){return typeof t=="string"&&r.has(t)}
1
+ export const ENGINE_IDS=Object.freeze(["node-vitest","playwright"]),PUBLIC_TEST_TYPES=Object.freeze(["ui"]),RUNNER_PROFILE_IDS=Object.freeze(["node-vitest","playwright-ui"]);const n=new Set(ENGINE_IDS),r=new Set(PUBLIC_TEST_TYPES),o=new Set(RUNNER_PROFILE_IDS);export function isEngineId(t){return typeof t=="string"&&n.has(t)}export function isTypedTestFile(t){return PUBLIC_TEST_TYPES.some(e=>t.endsWith(`.${e}.testkit.ts`))}export function isPublicTestType(t){return typeof t=="string"&&r.has(t)}export function isRunnerProfileId(t){return typeof t=="string"&&o.has(t)}
@@ -1 +1 @@
1
- export const RETIRED_TEST_TYPES=Object.freeze(["int","e2e","dal","load"].map(e=>({testType:e,release:"0.5.0",migration:"remove-k6"}))),RETIRED_ENGINES=Object.freeze([{engine:"k6",release:"0.5.0",migration:"remove-k6"}]);export function retiredEngineMessage(e){const t=RETIRED_ENGINES.find(n=>n.engine===e);return t===void 0?null:`Testkit ${t.release} removed the ${t.engine} engine. Migrate with docs/release-notes.md#${t.migration}`}export function rejectRetiredTestType(e,t,n=RETIRED_TEST_TYPES){const r=n.find(i=>i.testType===e);if(r!==void 0)throw new Error(`${t}: Testkit ${r.release} removed the ${r.testType} test type. Migrate with docs/release-notes.md#${r.migration}.`)}export function rejectRetiredTestFile(e,t=RETIRED_TEST_TYPES){const n=/\.([^./]+)\.testkit\.ts$/u.exec(e)?.[1];n!==void 0&&rejectRetiredTestType(n,e,t)}
1
+ export const RELEASE_NOTES_FILE="node_modules/@gfdelarue/testkit/docs/release-notes.md",RETIRED_TEST_TYPES=Object.freeze([{testType:"contract",release:"0.6.0",migration:"Remove the contract test type"},{testType:"data",release:"0.6.0",migration:"Remove the data test type"}]);export function rejectRetiredTestType(e,n,t=RETIRED_TEST_TYPES){const r=t.find(o=>o.testType===e);if(r!==void 0)throw new Error(`${n}: Testkit ${r.release} removed the ${r.testType} test type. ${migrationText(r.migration)}.`)}export function rejectRetiredTestFile(e,n=RETIRED_TEST_TYPES){const t=/\.([^./]+)\.testkit\.ts$/u.exec(e)?.[1];t!==void 0&&rejectRetiredTestType(t,e,n)}export function migrationText(e){return`Migrate with "${e}" in ${RELEASE_NOTES_FILE}`}
@@ -1 +1 @@
1
- import{RUN_TYPE_ORDER as f,RUN_TYPES as h,isPublicTestType as y,publicTestTypeListText as l}from"./test-types.js";import"./identifiers.js";import{RETIRED_TEST_TYPES as a,rejectRetiredTestType as d}from"./retired-test-types.js";const m=String;export function normalizeTypeValues(e=[],n=a){const t=[];for(const p of e)if(p!=null)for(const i of m(p).split(",")){const o=i.trim();if(!o)continue;const c=o;if(d(c,`Type "${o}"`,n),!h.has(c))throw new Error(`Unknown type "${o}". Expected one of: ${l({includeAll:!0})}.`);t.push(c)}if(t.length===0)return["all"];const r=[...new Set(t)];if(r.includes("all")&&r.length>1)throw new Error('"--type all" cannot be combined with other types.');return r.sort((p,i)=>f.indexOf(p)-f.indexOf(i))}export function isAllTypeSelection(e=[]){return e.length===0||e.length===1&&e[0]==="all"}export function parseSuiteSelectors(e=[],n=a){const t=[];for(const r of e)if(r!=null)for(const p of m(r).split(",")){const i=p.trim();if(!i)continue;const o=i.indexOf(":");if(o<0){t.push({kind:"plain",name:i,raw:i});continue}const c=i.slice(0,o),s=i.slice(o+1).trim(),u=c.trim();if(d(u,`Suite selector "${i}"`,n),!y(u))throw new Error(`Unknown suite selector type "${c}". Expected one of: ${l()}.`);if(!s)throw new Error(`Invalid suite selector "${i}". Expected "<type>:<suite-name>".`);t.push({kind:"typed",testType:u,name:s,raw:`${u}:${s}`})}return t}export function suiteKey(e){return`${e.testType??""}:${e.name}`}export function matchesSelectedTypes(e,n){return isAllTypeSelection(n)||e!==void 0&&n.includes(e)}export function matchesSuiteSelectors(e,n,t=[]){return!t||t.length===0?!0:t.some(r=>(r.kind==="plain"||r.testType===e)&&r.name===n)}export function parseTypeOption(e,n=null){const t=[];if(n&&t.push(n),Array.isArray(e)){const r=e;t.push(...r)}else e&&t.push(e);return normalizeTypeValues(t)}export function parseSuiteOption(e){const n=Array.isArray(e)?e:e?[e]:[];return parseSuiteSelectors(n)}
1
+ import{RUN_TYPE_ORDER as f,RUN_TYPES as y,isPublicTestType as T,publicTestTypeListText as l}from"./test-types.js";import"./identifiers.js";import{RETIRED_TEST_TYPES as a,rejectRetiredTestType as d}from"./retired-test-types.js";const m=String;export function normalizeTypeValues(e=[],n=a){const t=[];for(const p of e)if(p!=null)for(const i of m(p).split(",")){const o=i.trim();if(!o)continue;const c=o;if(d(c,`Type "${o}"`,n),!y.has(c))throw new Error(`Unknown type "${o}". Expected one of: ${l({includeAll:!0})}.`);t.push(c)}if(t.length===0)return["all"];const r=[...new Set(t)];if(r.includes("all")&&r.length>1)throw new Error('"--type all" cannot be combined with other types.');return r.sort((p,i)=>f.indexOf(p)-f.indexOf(i))}export function isAllTypeSelection(e=[]){return e.length===0||e.length===1&&e[0]==="all"}export function parseSuiteSelectors(e=[],n=a){const t=[];for(const r of e)if(r!=null)for(const p of m(r).split(",")){const i=p.trim();if(!i)continue;const o=i.indexOf(":");if(o<0){t.push({kind:"plain",name:i,raw:i});continue}const c=i.slice(0,o),u=i.slice(o+1).trim(),s=c.trim();if(d(s,`Suite selector "${i}"`,n),!T(s))throw new Error(`Unknown suite selector type "${c}". Expected one of: ${l()}.`);if(!u)throw new Error(`Invalid suite selector "${i}". Expected "<type>:<suite-name>".`);t.push({kind:"typed",testType:s,name:u,raw:`${s}:${u}`})}return t}export function suiteKey(e){return`${e.testType??""}:${e.name}`}export function matchesSelectedTypes(e,n){return isAllTypeSelection(n)||n.includes(e??"plain")}export function matchesSuiteSelectors(e,n,t=[]){return!t||t.length===0?!0:t.some(r=>(r.kind==="plain"||r.testType===e)&&r.name===n)}export function parseTypeOption(e,n=null){const t=[];if(n&&t.push(n),Array.isArray(e)){const r=e;t.push(...r)}else e&&t.push(e);return normalizeTypeValues(t)}export function parseSuiteOption(e){const n=Array.isArray(e)?e:e?[e]:[];return parseSuiteSelectors(n)}
@@ -1 +1 @@
1
- import{PUBLIC_TEST_TYPES as e,isPublicTestType as i}from"./identifiers.js";export const TEST_TYPE_ORDER=e;const o=[...e,"all"];export const RUN_TYPE_ORDER=o,RUN_TYPES=new Set(RUN_TYPE_ORDER);const s={ui:"UI",contract:"Contract",data:"Data"},T=String;export function isPublicTestType(t){return i(n(t))}export function isRunType(t){return RUN_TYPES.has(n(t))}export function formatPublicTestType(t){const r=n(t);return isPublicTestType(r)?s[r]:T(t||"")}export function publicTestTypeList({includeAll:t=!1}={}){return t?[...o]:[...e]}export function publicTestTypeListText(t={}){return publicTestTypeList(t).join(", ")}function n(t){return T(t||"").trim()}export function formatTestTypeLabel(t){return formatPublicTestType(t)}
1
+ import{PUBLIC_TEST_TYPES as e,isPublicTestType as i}from"./identifiers.js";export const TEST_TYPE_ORDER=e;const T=[...e,"plain","all"];export const RUN_TYPE_ORDER=T,RUN_TYPES=new Set(RUN_TYPE_ORDER);const s={ui:"UI"},o=String;export function isPublicTestType(t){return i(n(t))}export function isRunType(t){return RUN_TYPES.has(n(t))}export function formatPublicTestType(t){const r=n(t);return isPublicTestType(r)?s[r]:o(t||"")}export function publicTestTypeList({includeAll:t=!1}={}){return t?[...T]:[...e]}export function publicTestTypeListText(t={}){return publicTestTypeList(t).join(", ")}function n(t){return o(t||"").trim()}export function formatTestTypeLabel(t){return formatPublicTestType(t)}
@@ -1 +1 @@
1
- import{NODE_VITEST_PACKAGE_REQUIREMENTS as u,NODE_VITEST_PRIVATE_PACKAGE_PREFIXES as l}from"./installations/package-requirements.js";import{PLAYWRIGHT_PACKAGE_FAMILY_REQUIREMENTS as a}from"./installations/package-requirements.js";import{BUILT_IN_RUNNER_PROFILES as i,identityProfile as s}from"./definitions.js";export{isEngineProfileIdentity}from"./definitions.js";export{listPlainVitestFiles}from"./node-vitest/inventory/index.js";export{resolveVitestSelection}from"./node-vitest/selection.js";export function profilesForTestType(e){return Object.freeze(i.filter(t=>t.supportedTestTypes.includes(e)))}export{i as BUILT_IN_RUNNER_PROFILES};export function resolveEngineProfile(e,t){const n=s({engineId:t,testType:e});if(!n)throw new Error(`Unsupported engine/test-type pair: ${t}/${e}`);return n}export function resolveExecutionProfile(e){const t=s(e);if(t===void 0||t.id!==e.profileId)throw new Error(`Execution identity ${e.testType??"untyped"}/${e.engineId}/${e.profileId} conflicts with catalog profile ${t?.id??"none"}`);return t}const p=Object.freeze([...u.map(({packageName:e})=>Object.freeze({engineId:"node-vitest",name:e})),...a.map(({packageName:e})=>Object.freeze({engineId:"playwright",name:e}))]);export function expectedEngineAuthoringModule(e){switch(e){case"node-vitest-data":return"@gfdelarue/testkit/node-vitest/data";case"node-vitest-contract":return"@gfdelarue/testkit/node-vitest/contract";case"node-vitest":return null;case"playwright-ui":return"@gfdelarue/testkit/playwright"}}export function allowsEngineAuthoringImport(e,t){return t===e||t==="@gfdelarue/testkit/environment"||!(t.startsWith("@gfdelarue/testkit/")||t.startsWith("@gfdelarue/testkit-engine-"))}export function invalidEngineAuthoringImport(e,t,n,r=expectedEngineAuthoringModule(t.profileId)){const o=r===null?"":`; use "${r}" for this suite`;return new Error(`Invalid Testkit authoring in "${e}": ${t.engineId}/${t.profileId} may not import "${n}"${o}`)}export function requireMatchingEngineImport(e,t,n){if(l.some(o=>n.startsWith(o)))throw new Error(`Invalid Testkit authoring in "${e}": ${t.engineId} suites may not import private Vitest implementation package "${n}"`);const r=managedPublicPackageOwner(n);if(r!==null&&r.engineId!==t.engineId)throw new Error(`Invalid Testkit authoring in "${e}": ${t.engineId} suites may not import ${r.engineId} package "${n}"`)}export function managedPublicPackageOwner(e){for(const t of p)if(e===t.name||e.startsWith(`${t.name}/`))return t;return null}
1
+ import{NODE_VITEST_PACKAGE_REQUIREMENTS as u,NODE_VITEST_PRIVATE_PACKAGE_PREFIXES as l}from"./installations/package-requirements.js";import{PLAYWRIGHT_PACKAGE_FAMILY_REQUIREMENTS as p}from"./installations/package-requirements.js";import{BUILT_IN_RUNNER_PROFILES as i,identityProfile as s}from"./definitions.js";export{isEngineProfileIdentity}from"./definitions.js";export{listPlainVitestFiles}from"./node-vitest/inventory/index.js";export{resolveVitestSelection}from"./node-vitest/selection.js";export function profilesForTestType(e){return Object.freeze(i.filter(t=>t.supportedTestTypes.includes(e)))}export{i as BUILT_IN_RUNNER_PROFILES};export function resolveEngineProfile(e,t){const n=s({engineId:t,testType:e});if(!n)throw new Error(`Unsupported engine/test-type pair: ${t}/${e}`);return n}export function resolveExecutionProfile(e){const t=s(e);if(t===void 0||t.id!==e.profileId)throw new Error(`Execution identity ${e.testType??"untyped"}/${e.engineId}/${e.profileId} conflicts with catalog profile ${t?.id??"none"}`);return t}const f=Object.freeze([...u.map(({packageName:e})=>Object.freeze({engineId:"node-vitest",name:e})),...p.map(({packageName:e})=>Object.freeze({engineId:"playwright",name:e}))]);export function expectedEngineAuthoringModule(e){switch(e){case"node-vitest":return null;case"playwright-ui":return"@gfdelarue/testkit/playwright"}}export function allowsEngineAuthoringImport(e,t){return t===e||t==="@gfdelarue/testkit/environment"||!(t.startsWith("@gfdelarue/testkit/")||t.startsWith("@gfdelarue/testkit-engine-"))}export function invalidEngineAuthoringImport(e,t,n,r=expectedEngineAuthoringModule(t.profileId)){const o=r===null?"":`; use "${r}" for this suite`;return new Error(`Invalid Testkit authoring in "${e}": ${t.engineId}/${t.profileId} may not import "${n}"${o}`)}export function requireMatchingEngineImport(e,t,n){if(l.some(o=>n.startsWith(o)))throw new Error(`Invalid Testkit authoring in "${e}": ${t.engineId} suites may not import private Vitest implementation package "${n}"`);const r=managedPublicPackageOwner(n);if(r!==null&&r.engineId!==t.engineId)throw new Error(`Invalid Testkit authoring in "${e}": ${t.engineId} suites may not import ${r.engineId} package "${n}"`)}export function managedPublicPackageOwner(e){for(const t of f)if(e===t.name||e.startsWith(`${t.name}/`))return t;return null}
@@ -1 +1 @@
1
- export const BUILT_IN_ENGINE_DEFINITIONS=Object.freeze({"node-vitest":Object.freeze({id:"node-vitest",profiles:Object.freeze([i({taskBatching:"isolated",engineId:"node-vitest",id:"node-vitest-data",supportedTestTypes:["data"]}),i({taskBatching:"isolated",engineId:"node-vitest",id:"node-vitest-contract",supportedTestTypes:["contract"]}),i({taskBatching:"selection",engineId:"node-vitest",id:"node-vitest",supportedTestTypes:[]})]),async resolveInstallation(e){return(await import("./installations/node-vitest.js")).resolveNodeVitestInstallation(e)},async loadExecutor(){return(await import("./node-vitest/adapter/index.js")).executeNodeVitestEngine}}),playwright:Object.freeze({id:"playwright",profiles:Object.freeze([i({taskBatching:"compatible",engineId:"playwright",id:"playwright-ui",supportedTestTypes:["ui"]})]),async resolveInstallation(e){return(await import("./installations/playwright.js")).resolvePlaywrightInstallation(e)},async loadExecutor(){return(await import("./playwright/adapter/index.js")).executePlaywrightEngine}})}),BUILT_IN_RUNNER_PROFILES=Object.freeze(Object.values(BUILT_IN_ENGINE_DEFINITIONS).flatMap(e=>e.profiles));export function isEngineProfileIdentity(e){if(typeof e!="object"||e===null||Array.isArray(e))return!1;const t=e;return typeof t.profileId=="string"&&identityProfile(t)?.id===t.profileId}export function identityProfile(e){return BUILT_IN_RUNNER_PROFILES.find(t=>t.engineId===e.engineId&&(e.testType===void 0?t.supportedTestTypes.length===0:t.supportedTestTypes.some(n=>n===e.testType)))}function i(e){return Object.freeze(e.supportedTestTypes),Object.freeze(e)}
1
+ export const BUILT_IN_ENGINE_DEFINITIONS=Object.freeze({"node-vitest":Object.freeze({id:"node-vitest",profiles:Object.freeze([r({taskBatching:"selection",engineId:"node-vitest",id:"node-vitest",supportedTestTypes:[]})]),async resolveInstallation(e){return(await import("./installations/node-vitest.js")).resolveNodeVitestInstallation(e)},async loadExecutor(){return(await import("./node-vitest/adapter/index.js")).executeNodeVitestEngine}}),playwright:Object.freeze({id:"playwright",profiles:Object.freeze([r({taskBatching:"compatible",engineId:"playwright",id:"playwright-ui",supportedTestTypes:["ui"]})]),async resolveInstallation(e){return(await import("./installations/playwright.js")).resolvePlaywrightInstallation(e)},async loadExecutor(){return(await import("./playwright/adapter/index.js")).executePlaywrightEngine}})}),BUILT_IN_RUNNER_PROFILES=Object.freeze(Object.values(BUILT_IN_ENGINE_DEFINITIONS).flatMap(e=>e.profiles));export function isEngineProfileIdentity(e){if(typeof e!="object"||e===null||Array.isArray(e))return!1;const t=e;return typeof t.profileId=="string"&&identityProfile(t)?.id===t.profileId}export function identityProfile(e){return BUILT_IN_RUNNER_PROFILES.find(t=>t.engineId===e.engineId&&(e.testType===void 0?t.supportedTestTypes.length===0:t.supportedTestTypes.some(i=>i===e.testType)))}function r(e){return Object.freeze(e.supportedTestTypes),Object.freeze(e)}
@@ -1 +1 @@
1
- import{requireEnvironmentHandle as o}from"./handles.js";import{requireEnvironmentValue as t}from"../contracts/environment-values.js";import{readNodeVitestRuntimeInput as i}from"../node-vitest/runtime/runtime-input.js";import{readPlaywrightContext as a}from"../playwright/runtime/runtime-input.js";export{EnvironmentHandleAmbiguousError,EnvironmentHandleError}from"./handles.js";export{EnvironmentValueMissingError}from"../contracts/environment-values.js";let e;export function endpoint(n){return t(r(),"endpoint",n)}export function database(n){return t(r(),"database",n)}export function handle(n){return o(r()?.handles??null,n)}function r(){if(e!==void 0)return e;const n=a()??i();return n===null?null:(e={databases:n.databases,endpoints:n.endpoints,handles:n.handles},e)}
1
+ import{requireEnvironmentHandle as i}from"./handles.js";import{requireEnvironmentValue as r}from"../contracts/environment-values.js";import{readNodeVitestRuntimeInput as a}from"../node-vitest/runtime/runtime-input.js";import{readPlaywrightContext as o}from"../playwright/runtime/runtime-input.js";export{EnvironmentHandleAmbiguousError,EnvironmentHandleError}from"./handles.js";export{EnvironmentValueMissingError}from"../contracts/environment-values.js";let e;export function endpoint(n){return r(t(),"endpoint",n)}export function database(n){return r(t(),"database",n)}export function handle(n){return i(t()?.handles??null,n)}export function setupData(){const n=o()??a();if(n===null)throw new Error("setupData() reads a Testkit run's setup data, and there is none outside a run");return n.setupData===void 0?{}:n.setupData}function t(){if(e!==void 0)return e;const n=o()??a();return n===null?null:(e={databases:n.databases,endpoints:n.endpoints,handles:n.handles},e)}
@@ -1 +1 @@
1
- import{NODE_VITEST_PACKAGE_REQUIREMENTS as c}from"./package-requirements.js";import{RootPackageResolver as d}from"./package-resolution.js";export function resolveNodeVitestInstallation(e,t=new d){e.signal.throwIfAborted();const o=(n,i)=>t.resolveImportFromRoot({dependencyGroup:"dependencies",packageName:n,publicEntrypoint:i});for(const{packageName:n,publicEntrypoints:i}of c)o(n,i[0]);const{entrypoint:r,root:a,version:s}=o("vitest","vitest");return Object.freeze({authoringEntrypoints:Object.freeze({config:t.resolveRootPublicEntrypoint("@gfdelarue/testkit/config"),contract:t.resolveRootPublicEntrypoint("@gfdelarue/testkit/node-vitest/contract"),data:t.resolveRootPublicEntrypoint("@gfdelarue/testkit/node-vitest/data"),environment:t.resolveRootPublicEntrypoint("@gfdelarue/testkit/environment"),root:t.resolveRootPublicEntrypoint("@gfdelarue/testkit"),rootPackageJson:t.packageJsonPath}),engineId:"node-vitest",vitest:Object.freeze({entrypoint:o("vitest","vitest/node").entrypoint,index:r,name:"vitest",root:a,version:s})})}export function managedVitestAliases({authoringEntrypoints:e,vitest:t}){return{"@gfdelarue/testkit":e.root,"@gfdelarue/testkit/config":e.config,"@gfdelarue/testkit/environment":e.environment,"@gfdelarue/testkit/node-vitest/contract":e.contract,"@gfdelarue/testkit/node-vitest/data":e.data,"@gfdelarue/testkit/package.json":e.rootPackageJson,vitest:t.index}}
1
+ import{NODE_VITEST_PACKAGE_REQUIREMENTS as d}from"./package-requirements.js";import{RootPackageResolver as c}from"./package-resolution.js";export function resolveNodeVitestInstallation(e,t=new c){e.signal.throwIfAborted();const o=(n,i)=>t.resolveImportFromRoot({dependencyGroup:"dependencies",packageName:n,publicEntrypoint:i});for(const{packageName:n,publicEntrypoints:i}of d)o(n,i[0]);const{entrypoint:r,root:a,version:s}=o("vitest","vitest");return Object.freeze({authoringEntrypoints:Object.freeze({data:t.resolveRootPublicEntrypoint("@gfdelarue/testkit/node-vitest/data"),environment:t.resolveRootPublicEntrypoint("@gfdelarue/testkit/environment"),root:t.resolveRootPublicEntrypoint("@gfdelarue/testkit"),rootPackageJson:t.packageJsonPath}),engineId:"node-vitest",vitest:Object.freeze({entrypoint:o("vitest","vitest/node").entrypoint,index:r,name:"vitest",root:a,version:s})})}export function managedVitestAliases({authoringEntrypoints:e,vitest:t}){return{"@gfdelarue/testkit":e.root,"@gfdelarue/testkit/environment":e.environment,"@gfdelarue/testkit/node-vitest/data":e.data,"@gfdelarue/testkit/package.json":e.rootPackageJson,vitest:t.index}}
@@ -1 +1 @@
1
- import{PLAYWRIGHT_PACKAGE_FAMILY_REQUIREMENTS as R}from"./package-requirements.js";export{PLAYWRIGHT_PACKAGE_FAMILY_REQUIREMENTS}from"./package-requirements.js";import c from"node:fs";import y from"node:os";import f from"node:path";import{fileURLToPath as m}from"node:url";import{runManagedProcess as x}from"../../process/index.js";import{currentLinuxLibc as T,currentPlatformTarget as _,platformTargetKey as d,SUPPORTED_PLATFORM_TARGETS as I}from"../../process/host/platform.js";import{RootPackageResolver as h}from"./package-resolution.js";import{isUnknownRecord as l}from"../../kernel/unknown-values.js";const O=64*1024,S=6e4,b="PLAYWRIGHT_BROWSERS_PATH",A=new Set(["npm_config_playwright_browsers_path","npm_package_config_playwright_browsers_path"]);export class PlaywrightInstallationError extends Error{code="TESTKIT_PLAYWRIGHT_INSTALLATION_INVALID";constructor(r,t){super(`Playwright installation is invalid: ${r}. Run testkit setup playwright --force.`,t),this.name="PlaywrightInstallationError"}}export class UnsupportedPlaywrightPlatformError extends Error{code="TESTKIT_PLAYWRIGHT_PLATFORM_UNSUPPORTED";constructor(r,t){super(`Playwright does not support ${d(r)}${r.platform==="linux"?` with ${t??"unknown"} libc`:""}`),this.name="UnsupportedPlaywrightPlatformError"}}export function resolvePlaywrightPackageFamily(e=new h){const[r,t,o]=R,i=e.resolveRequireFromRoot(u(r)),n=e.resolveImportFromRoot(u(r));L(e,i,n);const a=e.resolveRequireFromPackage(i,u(t)),s=e.resolveRequireFromPackage(a,u(o)),P=e.resolveRequirePublicEntrypoint(i,r.publicEntrypoints[1]);return Object.freeze({cli:P,playwright:p(a,"playwright"),playwrightCore:p(s,"playwright-core"),playwrightTest:p(n,"@playwright/test")})}function L(e,r,t){if(!(r.name===t.name&&r.version===t.version&&r.root===t.root))throw e.brokenInstallation(r,"the import and require public entrypoints select different physical package roots",void 0)}export function resolvePlaywrightRegistry(e){const r=e.platform==="win32"?f.win32:f.posix;if(!r.isAbsolute(e.productRoot))throw new TypeError("Playwright product root must be absolute");const t=e.configuredValue;if(t==="0")return Object.freeze({canonicalValue:"0",mode:"hermetic"});if(t!==void 0&&t.length>0){const o=r.isAbsolute(t);return Object.freeze({canonicalValue:o?r.normalize(t):r.resolve(e.productRoot,t),mode:o?"configured-absolute":"configured-relative"})}return Object.freeze({canonicalValue:r.resolve(e.productRoot,r.join(C(e.platform,e.ambientEnvironment,e.homeDirectory??y.homedir(),r),"ms-playwright")),mode:"default"})}export function playwrightRegistryEnvironment(e,r){const t={};for(const[o,i]of Object.entries(e))typeof i=="string"&&!A.has(o.toLowerCase())&&o.toUpperCase()!==b&&(t[o]=i);return t[b]=r.canonicalValue,Object.freeze(t)}export async function resolvePlaywrightInstallation(e,r={}){e.signal.throwIfAborted();const t={linuxLibc:T(),target:_()};requireSupportedPlaywrightHost(t);const o=new h,i=r.ambientEnvironment??process.env,n=resolvePlaywrightPackageFamily(o),a=resolvePlaywrightRegistry({ambientEnvironment:i,configuredValue:r.configuredRegistry??i.PLAYWRIGHT_BROWSERS_PATH,homeDirectory:y.homedir(),platform:t.target.platform,productRoot:e.productRoot}),s=await v(e,{family:n,registry:a},i);return Object.freeze({authoringEntrypoints:Object.freeze({authoring:o.resolveRootPublicEntrypoint("@gfdelarue/testkit/playwright"),environment:o.resolveRootPublicEntrypoint("@gfdelarue/testkit/environment"),root:o.resolveRootPublicEntrypoint("@gfdelarue/testkit"),rootPackageJson:o.resolveRootPublicEntrypoint("@gfdelarue/testkit/package.json")}),browserExecutable:requireBrowserExecutable(s.browserExecutable,t.target),engineId:"playwright",family:n,registry:a})}async function v(e,r,t){const o=Buffer.from(JSON.stringify(r),"utf8").toString("base64url"),i=await x({args:[N(),o],cwd:e.productRoot,displayLabel:"Playwright installation resolver",env:playwrightRegistryEnvironment(t,r.registry),executable:process.execPath,signal:e.signal,timeoutMs:S}),n=j(i);throwIfPlaywrightProcessAborted(e.signal,i,n),requireBoundedPlaywrightChildOutput(i.stdout);let a;try{a=parsePlaywrightChildOutput(i.stdout)}catch(s){throw n!==null?new PlaywrightInstallationError(`isolated resolver failed: ${n.message}`,{cause:n}):s}if(n!==null)throw new PlaywrightInstallationError(`isolated resolver failed: ${n.message}`,{cause:n});if(!a.ok){if(i.exitCode!==1||i.exitSignal!==null){const s=E(i);throw new PlaywrightInstallationError(`isolated resolver failed: ${s.message}`,{cause:s})}throw new PlaywrightInstallationError(`isolated resolver failed: ${a.error.message}`,{cause:M(a.error)})}if(i.exitCode!==0||i.exitSignal!==null){const s=E(i);throw new PlaywrightInstallationError(`isolated resolver failed: ${s.message}`,{cause:s})}return requireMatchingPlaywrightChildRequest(a,r),a}export function throwIfPlaywrightProcessAborted(e,r,t){if(e.aborted){if(!k(r)||t===null){e.throwIfAborted();return}throw t===e.reason?t:new AggregateError([e.reason,t],"Playwright process was cancelled and process cleanup also failed",{cause:e.reason})}}function k(e){return e.terminationError!==null||e.error!==null||e.timedOut||e.outputLimitExceeded}export function requireBoundedPlaywrightChildOutput(e){if(Buffer.byteLength(e,"utf8")>O)throw new PlaywrightInstallationError("isolated resolver returned oversized output")}export function parsePlaywrightChildOutput(e){let r;try{r=JSON.parse(e)}catch(t){throw new PlaywrightInstallationError("isolated resolver did not return bounded JSON",{cause:t})}if(!l(r)||typeof r.ok!="boolean")throw new PlaywrightInstallationError("isolated resolver returned an invalid response");if(!r.ok){if(!l(r.error)||typeof r.error.name!="string"||typeof r.error.message!="string"||r.error.code!==void 0&&typeof r.error.code!="string")throw new PlaywrightInstallationError("isolated resolver returned an invalid error");return{error:{...r.error.code===void 0?{}:{code:r.error.code},message:r.error.message,name:r.error.name},ok:!1}}if(typeof r.browserExecutable!="string"||!$(r.family)||!U(r.registry))throw new PlaywrightInstallationError("isolated resolver returned an invalid installation");return{browserExecutable:r.browserExecutable,family:r.family,ok:!0,registry:r.registry}}export function requireMatchingPlaywrightChildRequest(e,r){if(JSON.stringify(e.family)!==JSON.stringify(r.family))throw new PlaywrightInstallationError("isolated resolver selected a different physical package family");if(e.registry.canonicalValue!==r.registry.canonicalValue||e.registry.mode!==r.registry.mode)throw new PlaywrightInstallationError("isolated resolver selected a different browser registry")}export function requireBrowserExecutable(e,r){if(!f.isAbsolute(e))throw new PlaywrightInstallationError(`public chromium.executablePath() returned a non-absolute path ${JSON.stringify(e)}`);let t;try{t=c.statSync(e)}catch(o){throw new PlaywrightInstallationError(`public chromium.executablePath() returned missing path ${JSON.stringify(e)}`,{cause:o})}if(!t.isFile())throw new PlaywrightInstallationError(`public chromium.executablePath() must identify a regular file; received ${JSON.stringify(e)}`);if(r.platform!=="win32")try{c.accessSync(e,c.constants.X_OK)}catch(o){throw new PlaywrightInstallationError(`public chromium.executablePath() is not executable: ${JSON.stringify(e)}`,{cause:o})}return e}export function requireSupportedPlaywrightHost(e){const r=d(e.target);if(!I.some(t=>d(t)===r)||e.target.platform==="linux"&&e.linuxLibc!=="glibc")throw new UnsupportedPlaywrightPlatformError(e.target,e.linuxLibc)}function C(e,r,t,o){return e==="linux"?r.XDG_CACHE_HOME||o.join(g(t),".cache"):e==="darwin"?o.join(g(t),"Library","Caches"):r.LOCALAPPDATA||o.join(g(t),"AppData","Local")}function g(e){if(e.length===0)throw new TypeError("Playwright default registry requires a home directory");return e}function u(e){return{dependencyGroup:"dependencies",packageName:e.packageName,publicEntrypoint:e.publicEntrypoints[0]}}function p(e,r){if(e.name!==r)throw new TypeError(`Playwright package family expected ${r}, received ${e.name}`);return Object.freeze({entrypoint:e.entrypoint,name:r,root:e.root,version:e.version})}function N(){const e=m(new URL("./playwright-resolution-child.js",import.meta.url));return c.existsSync(e)?e:m(new URL("../../../dist/engines/installations/playwright-resolution-child.js",import.meta.url))}function j(e){return e.terminationError!==null?e.terminationError:e.error!==null?e.error:e.timedOut?new Error("isolated resolver timed out"):e.cancelled?new Error("isolated resolver was cancelled"):e.outputLimitExceeded?new Error("isolated resolver output exceeded the bound"):null}function E(e){return new Error(`isolated resolver exited with ${e.exitCode===null?String(e.exitSignal):e.exitCode.toString()}`)}function M(e){const r=new Error(e.message);return r.name=e.name,e.code!==void 0&&Object.defineProperty(r,"code",{enumerable:!0,value:e.code}),r}function $(e){return l(e)&&typeof e.cli=="string"&&w(e.playwrightTest,"@playwright/test")&&w(e.playwright,"playwright")&&w(e.playwrightCore,"playwright-core")}function w(e,r){return l(e)&&e.name===r&&typeof e.version=="string"&&typeof e.root=="string"&&typeof e.entrypoint=="string"}function U(e){return l(e)&&typeof e.canonicalValue=="string"&&(e.mode==="configured-absolute"||e.mode==="configured-relative"||e.mode==="default"||e.mode==="hermetic")}
1
+ import{PLAYWRIGHT_PACKAGE_FAMILY_REQUIREMENTS as R}from"./package-requirements.js";export{PLAYWRIGHT_PACKAGE_FAMILY_REQUIREMENTS}from"./package-requirements.js";import f from"node:fs";import y from"node:os";import l from"node:path";import{fileURLToPath as h}from"node:url";import{runManagedProcess as x}from"../../process/index.js";import{currentLinuxLibc as T,currentPlatformTarget as _,platformTargetKey as g,SUPPORTED_PLATFORM_TARGETS as I}from"../../process/host/platform.js";import{RootPackageResolver as b}from"./package-resolution.js";import{isUnknownRecord as c}from"../../kernel/unknown-values.js";const O=64*1024,S=6e4,E="PLAYWRIGHT_BROWSERS_PATH",A=new Set(["npm_config_playwright_browsers_path","npm_package_config_playwright_browsers_path"]);export class PlaywrightInstallationError extends Error{code="TESTKIT_PLAYWRIGHT_INSTALLATION_INVALID";constructor(r,t){super(`Playwright installation is invalid: ${r}. Run testkit setup playwright --force.`,t),this.name="PlaywrightInstallationError"}}export class UnsupportedPlaywrightPlatformError extends Error{code="TESTKIT_PLAYWRIGHT_PLATFORM_UNSUPPORTED";constructor(r,t){super(`Playwright does not support ${g(r)}${r.platform==="linux"?` with ${t??"unknown"} libc`:""}`),this.name="UnsupportedPlaywrightPlatformError"}}export function resolvePlaywrightPackageFamily(e=new b){const[r,t,o]=R,i=e.resolveRequireFromRoot(d(r)),n=e.resolveImportFromRoot(d(r));L(e,i,n);const a=e.resolveRequireFromPackage(i,d(t)),s=e.resolveRequireFromPackage(a,d(o)),u=e.resolveRequirePublicEntrypoint(i,r.publicEntrypoints[1]);return Object.freeze({cli:u,playwright:m(a,"playwright"),playwrightCore:m(s,"playwright-core"),playwrightTest:m(n,"@playwright/test")})}function L(e,r,t){if(!(r.name===t.name&&r.version===t.version&&r.root===t.root))throw e.brokenInstallation(r,"the import and require public entrypoints select different physical package roots",void 0)}export function resolvePlaywrightRegistry(e){const r=e.platform==="win32"?l.win32:l.posix;if(!r.isAbsolute(e.productRoot))throw new TypeError("Playwright product root must be absolute");const t=e.configuredValue;if(t==="0")return Object.freeze({canonicalValue:"0",mode:"hermetic"});if(t!==void 0&&t.length>0){const o=r.isAbsolute(t);return Object.freeze({canonicalValue:o?r.normalize(t):r.resolve(e.productRoot,t),mode:o?"configured-absolute":"configured-relative"})}return Object.freeze({canonicalValue:r.resolve(e.productRoot,r.join(C(e.platform,e.ambientEnvironment,e.homeDirectory??y.homedir(),r),"ms-playwright")),mode:"default"})}export function playwrightRegistryEnvironment(e,r){const t={};for(const[o,i]of Object.entries(e))typeof i=="string"&&!A.has(o.toLowerCase())&&o.toUpperCase()!==E&&(t[o]=i);return t[E]=r.canonicalValue,Object.freeze(t)}export async function resolvePlaywrightInstallation(e,r={}){e.signal.throwIfAborted();const t={linuxLibc:T(),target:_()};requireSupportedPlaywrightHost(t);const o=new b,i=r.ambientEnvironment??process.env,n=resolvePlaywrightPackageFamily(o),a=resolvePlaywrightRegistry({ambientEnvironment:i,configuredValue:r.configuredRegistry??i.PLAYWRIGHT_BROWSERS_PATH,homeDirectory:y.homedir(),platform:t.target.platform,productRoot:e.productRoot}),s=await v(e,{family:n,registry:a},i),u=o.resolveRootPublicEntrypoint("@gfdelarue/testkit/playwright");return Object.freeze({authoringEntrypoints:Object.freeze({authoring:u,environment:o.resolveRootPublicEntrypoint("@gfdelarue/testkit/environment"),managedTest:l.join(l.dirname(u),"managed-test.js"),root:o.resolveRootPublicEntrypoint("@gfdelarue/testkit"),rootPackageJson:o.resolveRootPublicEntrypoint("@gfdelarue/testkit/package.json")}),browserExecutable:requireBrowserExecutable(s.browserExecutable,t.target),engineId:"playwright",family:n,registry:a})}async function v(e,r,t){const o=Buffer.from(JSON.stringify(r),"utf8").toString("base64url"),i=await x({args:[j(),o],cwd:e.productRoot,displayLabel:"Playwright installation resolver",env:playwrightRegistryEnvironment(t,r.registry),executable:process.execPath,signal:e.signal,timeoutMs:S}),n=N(i);throwIfPlaywrightProcessAborted(e.signal,i,n),requireBoundedPlaywrightChildOutput(i.stdout);let a;try{a=parsePlaywrightChildOutput(i.stdout)}catch(s){throw n!==null?new PlaywrightInstallationError(`isolated resolver failed: ${n.message}`,{cause:n}):s}if(n!==null)throw new PlaywrightInstallationError(`isolated resolver failed: ${n.message}`,{cause:n});if(!a.ok){if(i.exitCode!==1||i.exitSignal!==null){const s=P(i);throw new PlaywrightInstallationError(`isolated resolver failed: ${s.message}`,{cause:s})}throw new PlaywrightInstallationError(`isolated resolver failed: ${a.error.message}`,{cause:M(a.error)})}if(i.exitCode!==0||i.exitSignal!==null){const s=P(i);throw new PlaywrightInstallationError(`isolated resolver failed: ${s.message}`,{cause:s})}return requireMatchingPlaywrightChildRequest(a,r),a}export function throwIfPlaywrightProcessAborted(e,r,t){if(e.aborted){if(!k(r)||t===null){e.throwIfAborted();return}throw t===e.reason?t:new AggregateError([e.reason,t],"Playwright process was cancelled and process cleanup also failed",{cause:e.reason})}}function k(e){return e.terminationError!==null||e.error!==null||e.timedOut||e.outputLimitExceeded}export function requireBoundedPlaywrightChildOutput(e){if(Buffer.byteLength(e,"utf8")>O)throw new PlaywrightInstallationError("isolated resolver returned oversized output")}export function parsePlaywrightChildOutput(e){let r;try{r=JSON.parse(e)}catch(t){throw new PlaywrightInstallationError("isolated resolver did not return bounded JSON",{cause:t})}if(!c(r)||typeof r.ok!="boolean")throw new PlaywrightInstallationError("isolated resolver returned an invalid response");if(!r.ok){if(!c(r.error)||typeof r.error.name!="string"||typeof r.error.message!="string"||r.error.code!==void 0&&typeof r.error.code!="string")throw new PlaywrightInstallationError("isolated resolver returned an invalid error");return{error:{...r.error.code===void 0?{}:{code:r.error.code},message:r.error.message,name:r.error.name},ok:!1}}if(typeof r.browserExecutable!="string"||!$(r.family)||!U(r.registry))throw new PlaywrightInstallationError("isolated resolver returned an invalid installation");return{browserExecutable:r.browserExecutable,family:r.family,ok:!0,registry:r.registry}}export function requireMatchingPlaywrightChildRequest(e,r){if(JSON.stringify(e.family)!==JSON.stringify(r.family))throw new PlaywrightInstallationError("isolated resolver selected a different physical package family");if(e.registry.canonicalValue!==r.registry.canonicalValue||e.registry.mode!==r.registry.mode)throw new PlaywrightInstallationError("isolated resolver selected a different browser registry")}export function requireBrowserExecutable(e,r){if(!l.isAbsolute(e))throw new PlaywrightInstallationError(`public chromium.executablePath() returned a non-absolute path ${JSON.stringify(e)}`);let t;try{t=f.statSync(e)}catch(o){throw new PlaywrightInstallationError(`public chromium.executablePath() returned missing path ${JSON.stringify(e)}`,{cause:o})}if(!t.isFile())throw new PlaywrightInstallationError(`public chromium.executablePath() must identify a regular file; received ${JSON.stringify(e)}`);if(r.platform!=="win32")try{f.accessSync(e,f.constants.X_OK)}catch(o){throw new PlaywrightInstallationError(`public chromium.executablePath() is not executable: ${JSON.stringify(e)}`,{cause:o})}return e}export function requireSupportedPlaywrightHost(e){const r=g(e.target);if(!I.some(t=>g(t)===r)||e.target.platform==="linux"&&e.linuxLibc!=="glibc")throw new UnsupportedPlaywrightPlatformError(e.target,e.linuxLibc)}function C(e,r,t,o){return e==="linux"?r.XDG_CACHE_HOME||o.join(p(t),".cache"):e==="darwin"?o.join(p(t),"Library","Caches"):r.LOCALAPPDATA||o.join(p(t),"AppData","Local")}function p(e){if(e.length===0)throw new TypeError("Playwright default registry requires a home directory");return e}function d(e){return{dependencyGroup:"dependencies",packageName:e.packageName,publicEntrypoint:e.publicEntrypoints[0]}}function m(e,r){if(e.name!==r)throw new TypeError(`Playwright package family expected ${r}, received ${e.name}`);return Object.freeze({entrypoint:e.entrypoint,name:r,root:e.root,version:e.version})}function j(){const e=h(new URL("./playwright-resolution-child.js",import.meta.url));return f.existsSync(e)?e:h(new URL("../../../dist/engines/installations/playwright-resolution-child.js",import.meta.url))}function N(e){return e.terminationError!==null?e.terminationError:e.error!==null?e.error:e.timedOut?new Error("isolated resolver timed out"):e.cancelled?new Error("isolated resolver was cancelled"):e.outputLimitExceeded?new Error("isolated resolver output exceeded the bound"):null}function P(e){return new Error(`isolated resolver exited with ${e.exitCode===null?String(e.exitSignal):e.exitCode.toString()}`)}function M(e){const r=new Error(e.message);return r.name=e.name,e.code!==void 0&&Object.defineProperty(r,"code",{enumerable:!0,value:e.code}),r}function $(e){return c(e)&&typeof e.cli=="string"&&w(e.playwrightTest,"@playwright/test")&&w(e.playwright,"playwright")&&w(e.playwrightCore,"playwright-core")}function w(e,r){return c(e)&&e.name===r&&typeof e.version=="string"&&typeof e.root=="string"&&typeof e.entrypoint=="string"}function U(e){return c(e)&&typeof e.canonicalValue=="string"&&(e.mode==="configured-absolute"||e.mode==="configured-relative"||e.mode==="default"||e.mode==="hermetic")}