@gfdelarue/testkit 0.6.5 → 0.6.7
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.
- package/README.md +134 -0
- package/dist/app/bugs/ci-report.js +2 -2
- package/dist/app/campaigns/brief.js +1 -1
- package/dist/app/cleanup/index.js +1 -1
- package/dist/app/doctor/index.js +1 -1
- package/dist/app/environments/static-values.js +1 -1
- package/dist/app/environments/supplied.js +1 -1
- package/dist/app/environments/task-databases.js +2 -2
- package/dist/app/evidence/ci-status.js +1 -0
- package/dist/app/evidence/ci.js +1 -0
- package/dist/app/evidence/index.js +1 -0
- package/dist/app/evidence/inputs.js +1 -0
- package/dist/app/evidence/report.js +1 -0
- package/dist/app/lint/index.js +1 -1
- package/dist/app/run/engine-evidence-persistence.js +1 -1
- package/dist/app/run/engine-execution-failures.js +1 -1
- package/dist/app/run/engine-execution.js +1 -1
- package/dist/app/run/operation-progress.js +1 -1
- package/dist/app/run/orchestrator.js +1 -1
- package/dist/app/run/results.js +1 -1
- package/dist/app/run/run-events.js +1 -1
- package/dist/app/run/run-finalization.js +1 -1
- package/dist/app/run/run-guards.js +3 -3
- package/dist/app/run/scheduler/estimates.js +1 -1
- package/dist/app/run/scheduler/index.js +1 -1
- package/dist/app/run/task-batches.js +1 -1
- package/dist/app/run/worker-loop.js +1 -1
- package/dist/app/status/runs.js +1 -1
- package/dist/app/typecheck/index.js +4 -4
- package/dist/cli/command-catalog.js +1 -1
- package/dist/cli/command-registry.js +1 -1
- package/dist/cli/commands/evidence.js +2 -0
- package/dist/cli/renderers/destroy/text.js +1 -1
- package/dist/cli/renderers/evidence/text.js +1 -0
- package/dist/cli/renderers/run/failure.js +1 -1
- package/dist/cli/renderers/run/status.js +1 -1
- package/dist/cli/renderers/run/text-reporter.js +23 -23
- package/dist/config/loader/decode-discovery.js +1 -1
- package/dist/config/loader/discover-tests.js +1 -1
- package/dist/config/loader/discovery-result.js +1 -1
- package/dist/config/loader/discovery.js +2 -2
- package/dist/config/loader/env.js +1 -1
- package/dist/config/loader/file-metadata.js +1 -1
- package/dist/config/loader/groups.js +2 -2
- package/dist/config/loader/index.js +1 -1
- package/dist/config/loader/load.js +1 -1
- package/dist/config/loader/path-policy.js +1 -1
- package/dist/config/loader/runtime-isolation.js +1 -1
- package/dist/config/loader/runtime.js +1 -1
- package/dist/config/loader/test-selection.js +1 -1
- package/dist/config/loader/validation.js +1 -1
- package/dist/database/admin.js +4 -4
- package/dist/database/cleanup.js +1 -1
- package/dist/database/configured-steps.js +1 -1
- package/dist/database/docker.js +3 -5
- package/dist/database/index.js +2 -2
- package/dist/database/local-postgres.js +3 -4
- package/dist/database/postgres/index.js +2 -2
- package/dist/database/progress.js +1 -1
- package/dist/database/roles.js +2 -2
- package/dist/database/runtime-config.js +1 -1
- package/dist/database/schema-source.js +3 -3
- package/dist/database/source-url.js +1 -1
- package/dist/database/template-steps.js +3 -3
- package/dist/domain/operation-reason.js +1 -1
- package/dist/domain/suite-selection.js +1 -1
- package/dist/engines/adapters/evidence-files.js +1 -1
- package/dist/engines/adapters/evidence-identity.js +1 -1
- package/dist/engines/adapters/execution.js +1 -1
- package/dist/engines/adapters/node-startup-cache.js +1 -1
- package/dist/engines/adapters/workspace.js +1 -1
- package/dist/engines/contracts/fixture-manifest.js +1 -1
- package/dist/engines/installations/package-resolution.js +1 -1
- package/dist/engines/managed-module-resolution.js +2 -2
- package/dist/engines/node-vitest/adapter/adapter.js +1 -1
- package/dist/engines/node-vitest/adapter/evidence.js +2 -2
- package/dist/engines/node-vitest/runtime/data/report.js +3 -3
- package/dist/engines/node-vitest/runtime/data/scenario.js +2 -2
- package/dist/engines/node-vitest/selection.js +1 -1
- package/dist/engines/playwright/adapter/adapter.js +1 -1
- package/dist/engines/playwright/adapter/config.js +3 -3
- package/dist/engines/playwright/adapter/index.js +1 -1
- package/dist/environment/lifecycle-controller.js +1 -1
- package/dist/environment/template.js +1 -1
- package/dist/evidence/artifacts.js +3 -3
- package/dist/evidence/bundles.js +2 -2
- package/dist/evidence/environment-record.js +1 -1
- package/dist/evidence/index.js +1 -1
- package/dist/evidence/logs.js +2 -2
- package/dist/evidence/operation-timings.js +1 -1
- package/dist/evidence/reading.js +1 -1
- package/dist/evidence/reporting.js +1 -1
- package/dist/evidence/run-evidence.js +1 -1
- package/dist/evidence/run-inputs.js +1 -0
- package/dist/evidence/run-model.js +1 -1
- package/dist/evidence/setup-progress.js +1 -1
- package/dist/evidence/storage-inventory.js +1 -1
- package/dist/evidence/storage.js +2 -2
- package/dist/process/capture-output.js +1 -1
- package/dist/process/execa-bundle.json +3 -3
- package/dist/process/host/platform.js +1 -1
- package/dist/process/locks.js +4 -4
- package/dist/process/process-group.js +1 -1
- package/dist/process/process.js +1 -1
- package/dist/process/ready-deadline.js +1 -1
- package/dist/process/runs/manifest.js +1 -1
- package/dist/process/runs/stop.js +1 -1
- package/dist/process/windows-native.js +1 -1
- package/dist/repo/content.js +4 -0
- package/dist/repo/fingerprint-policy.js +1 -1
- package/dist/repo/managed-git.js +1 -1
- package/dist/repo/metadata.js +1 -1
- package/dist/repo/state.js +4 -4
- package/dist/steps/configured-step-testkit-modules.js +1 -1
- package/dist/steps/configured-steps.js +1 -1
- package/dist/steps/environment.js +1 -1
- package/dist/steps/index.js +1 -1
- package/dist/steps/run.js +2 -2
- package/dist/toolchains/esbuild.js +1 -1
- package/dist/toolchains/index.js +3 -3
- package/dist/toolchains/node-integrity.js +1 -1
- package/dist/toolchains/node-provisioning.js +2 -2
- package/dist/toolchains/semver.js +1 -1
- package/dist/tracker/gitea.js +1 -1
- package/dist/tracker/github.js +1 -1
- package/dist/tracker/requests.js +1 -1
- package/dist/tracker/responses.js +1 -1
- package/docs/bug-records.md +18 -2
- package/docs/release-notes.md +36 -0
- package/package.json +1 -1
- package/dist/app/run/planning-types.js +0 -1
- package/dist/app/run/selection.js +0 -1
- package/dist/config/authoring/http-methods.js +0 -1
- package/dist/evidence/artifact-validation.js +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{stringFromUnknown as
|
|
2
|
-
`)}function
|
|
1
|
+
import{stringFromUnknown as y}from"./primitives.js";import k from"node:crypto";import{writeDataFlowScenario as A}from"./report.js";import{isUnknownArray as E,isUnknownRecord as p}from"../../../../kernel/unknown-values.js";const F=/(authorization|cookie|password|secret|token|api[_-]?key|private[_-]?key)/iu,S=String,g=new WeakMap;export function dataFlow(e){return T(e)}function T(e){const t=e?.task;if(t?.result===void 0||t.result.duration!==void 0||e?.onTestFinished===void 0)throw new Error("dataFlow(context) needs the context of a running Vitest test: test(name, (context) => { dataFlow(context); })");const s=g.get(e);if(s!==void 0)return s;const n=N(D(t),{path:process.env.TESTKIT_TEST_FILE??t.file.name,...t.location}),r=O(n);return g.set(e,r),e.onTestFinished(()=>{if(g.delete(e),t.result?.state==="skip")return;const i=v(n,t.result),o={attempt:(t.result?.retryCount??0)+1,repeat:t.result?.repeatCount??0,repeats:t.repeats??0};if(A(t.id,o,i),i.budgetFailures.length>0)throw new Error(U(i))}),r}function D(e){const t=[e.name];for(let s=e.suite;s!==void 0&&s!==e.file;s=s.suite)t.unshift(s.name);return t.join(" ")}export function measureJsonBytes(e){return Buffer.byteLength(JSON.stringify(e??null))}export function stableHash(e){const t=q(e);if(t===void 0)throw new TypeError("stableHash(...) requires a serializable value");return k.createHash("sha256").update(t).digest("hex")}function O(e){const t=(r,i)=>(o,u,a={})=>n.metric(o,u,{...a,kind:r,unit:a.unit||i}),s=t("count","count"),n={addFinding(r,i,o={}){return e.findings.push({severity:H(r),message:c(i,"finding message"),details:d(o)}),n},actor(r,i={}){return f(e,"actor",r,i),n},budget(r){return b(e,r)},bytes:t("bytes","bytes"),count:(r,i=1,o)=>s(r,i,o),duration:t("duration","ms"),event(r,i={}){return e.events.push({atMs:Date.now()-e.startedAtMs,name:c(r,"event name"),details:d(i)}),n},externalCall(r,i={}){const o=w(i);return f(e,"external-call",r,o),typeof o.durationMs=="number"&&n.duration(`${r}.external.duration`,o.durationMs),typeof o.requestBytes=="number"&&n.bytes(`${r}.external.request.bytes`,o.requestBytes),typeof o.responseBytes=="number"&&n.bytes(`${r}.external.response.bytes`,o.responseBytes),typeof o.costMinor=="number"&&n.money(`${r}.external.cost`,o.costMinor),n},expectMetric(r){return b(e,r)},expectSequence(r){return B(e,r)},finding(r,i,o={}){return n.addFinding(r,i,o)},metric(r,i,o={}){return V(e,r,i,o),n},money:t("money","minor"),ratio:t("ratio","ratio"),reconcile(r,i={}){return f(e,"reconciliation",r,i),n},record(r,i={}){const o=w(i);return f(e,"record",r,o),typeof o.count=="number"&&n.count(`${r}.record.count`,o.count),n},recordCache(r,{hits:i=0,misses:o=0,reads:u=0,writes:a=0}={}){const l=c(r,"cache metric name");n.count(`${l}.cache.hits`,i),n.count(`${l}.cache.misses`,o),n.count(`${l}.cache.reads`,u),n.count(`${l}.cache.writes`,a);const x=i+o;return x>0&&n.ratio(`${l}.cache.hit_rate`,i/x),n},recordHttpRequest(r){return n.count("http.request.count",1),n.bytes("http.request.bytes",r.bodyBytes||0),n.bytes("http.response.bytes",r.responseBytes||0),n.duration("http.request.duration",r.durationMs||0),n.sequence("http.methods",[r.method]),n.sequence("http.paths",[r.path]),n.event("http.request",{durationMs:r.durationMs||0,method:r.method,path:r.path,requestBytes:r.bodyBytes||0,responseBytes:r.responseBytes||0,status:r.status}),n},recordLlmUsage(r,i={}){const o=c(r,"llm usage name");for(const[u,a]of Object.entries(i))typeof a=="number"&&Number.isFinite(a)&&n.count(`${o}.${P(u)}`,a,{unit:u.toLowerCase().includes("cost")?"minor":"tokens"});return n},recordRpc(r,{method:i,requestBytes:o=0,responseBytes:u=0}={}){const a=c(r,"rpc metric name");return n.count(`${a}.rpc.count`,1),i&&n.sequence(`${a}.rpc.methods`,[i]),n.bytes(`${a}.rpc.request.bytes`,o),n.bytes(`${a}.rpc.response.bytes`,u),n},sequence(r,i,o={}){return _(e,r,i,o),n},sequenceBudget(r){return B(e,r)},system(r,i={}){return f(e,"system",r,i),n},transition(r,i={}){return f(e,"transition",r,i),n}};return Object.freeze(n)}function b(e,t){const s=c(t,"metric budget name"),n=r=>function(i,o={}){return h(e,{name:s,target:"metric",operator:r,expected:i,message:o.message}),this};return{atLeast:n(">="),atMost:n("<="),equals:n("=="),greaterThan:n(">"),lessThan:n("<"),required(r={}){return h(e,{name:s,target:"metric",operator:"exists",message:r.message}),this}}}function B(e,t){const s=c(t,"sequence budget name"),n=r=>function(i,o={}){return h(e,{name:s,target:"sequence",operator:r,expected:M(i),message:o.message}),this};return{equals:n("sequence=="),includesOnly:n("sequenceOnly"),required(r={}){return h(e,{name:s,target:"sequence",operator:"exists",message:r.message}),this}}}function N(e,t){return{budgets:[],events:[],evidence:[],findings:[],location:t,metrics:[],name:c(e,"data scenario name"),sequences:[],startedAtMs:Date.now()}}function v(e,t){const{budgets:s,startedAtMs:n,...r}=e,i=s.map(a=>j(e,a)),o=i.filter(a=>a.status==="failed"),u=t?.state==="fail"?I(t.errors?.at(-1)):null;return{...r,budgetFailures:o,budgets:i,durationMs:Date.now()-n,error:u,status:u||o.length>0?"failed":"passed"}}function V(e,t,s,n={}){const r=s;if(!Number.isFinite(r))throw new Error(`Data metric "${t}" must be a finite number`);e.metrics.push({description:m(n.description),dimensions:z(n.dimensions??{}),kind:m(n.kind)||"metric",name:c(t,"metric name"),unit:m(n.unit),value:r})}function _(e,t,s,n={}){const r=M(s).map(i=>S(i));e.sequences.push({description:m(n.description),dimensions:z(n.dimensions??{}),name:c(t,"sequence name"),values:r})}function h(e,t){e.budgets.push({...t,message:m(t.message)})}function f(e,t,s,n={}){const r=w(n);e.evidence.push({atMs:Date.now()-e.startedAtMs,details:d(r),kind:t,name:c(s,`${t} evidence name`),status:m(r.status)||null})}function w(e){return p(e)?e:{value:e}}function j(e,t){const s=t.target==="sequence"?R(e,t.name):L(e,t.name),n=C(t,s),r=t.message||J(t,s,n);return t.target==="metric"?{...t,actual:s,expected:t.expected??null,message:r,status:n?"passed":"failed"}:{...t,actual:s,expected:t.expected??null,message:r,status:n?"passed":"failed"}}function C(e,t){if(e.target==="metric")return e.operator==="exists"?t!==null:typeof t!="number"?!1:e.operator==="<="?t<=Number(e.expected):e.operator===">="?t>=Number(e.expected):e.operator==="<"?t<Number(e.expected):e.operator===">"?t>Number(e.expected):Object.is(t,Number(e.expected));if(e.operator==="exists")return t!==null;if(!Array.isArray(t))return!1;if(e.operator==="sequence==")return K(t,e.expected);const s=new Set((e.expected??[]).map(n=>y(n)));return t.every(n=>s.has(n))}function L(e,t){for(let s=e.metrics.length-1;s>=0;s-=1)if(e.metrics[s].name===t)return e.metrics[s].value;return null}function R(e,t){const s=[];for(const n of e.sequences)n.name===t&&s.push(...n.values);return s.length>0?s:null}function J(e,t,s){return e.operator==="exists"?s?`${e.target} "${e.name}" was recorded`:`${e.target} "${e.name}" was not recorded`:`${e.target} "${e.name}" expected ${e.operator} ${JSON.stringify(e.expected)}, got ${JSON.stringify(t)}`}function U(e){return[`Data-flow budgets failed in "${e.name}":`,...e.budgetFailures.map(t=>`- ${t.message}`)].join(`
|
|
2
|
+
`)}function M(e){return E(e)?[...e]:[e]}function c(e,t){const s=y(e).trim();if(!s)throw new Error(`${t} must be a non-empty string`);return s}function m(e){if(e==null)return null;const t=y(e).trim();return t||null}function H(e){const t=y(e||"info").toLowerCase();return W(t)?t:"info"}function d(e){if(Array.isArray(e))return e.map(s=>d(s));if(!p(e))return e;const t=Object.create(null);for(const[s,n]of Object.entries(e))t[s]=F.test(s)?"[redacted]":d(n);return t}function I(e){return e instanceof Error?{message:e.message,name:e.name,stack:e.stack??null}:{message:$(e,"message")??Z(e),name:$(e,"name")??"Error",stack:$(e,"stack")}}function q(e){return Array.isArray(e)?`[${e.map(t=>q(t)).join(",")}]`:p(e)?`{${Object.keys(e).sort().map(t=>{const s=q(e[t]);return`${JSON.stringify(t)}:${s??"undefined"}`}).join(",")}}`:JSON.stringify(e)}function K(e,t){return!Array.isArray(e)||!Array.isArray(t)||e.length!==t.length?!1:e.every((s,n)=>String(s)===String(t[n]))}function P(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/[^a-zA-Z0-9]+/g,"_").replace(/^_+|_+$/g,"").toLowerCase()}function z(e){const t=d(e);return p(t)?t:{}}function $(e,t){if(!p(e))return null;try{const s=Reflect.get(e,t);return typeof s=="string"?s:null}catch{return null}}function Z(e){try{return S(e)}catch{return"Unknown thrown value"}}function W(e){return e==="error"||e==="info"||e==="waste"||e==="watch"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import o from"node:fs";import{createRequire as v}from"node:module";import c from"node:path";import{isPathWithin as m}from"../../process/files/paths.js";import{NODE_VITEST_ENGINE_ENVIRONMENT_KEYS as S}from"./runtime/runtime-input.js";export const NODE_VITEST_INVENTORY_MARKER="TESTKIT_VITEST_INVENTORY=";const h=new Set(["BASE_URL","DATABASE_URL","NODE_OPTIONS",...S].map(e=>e.toUpperCase()));export function resolveVitestSelection(e,n,t=process.env){const{"vitest-config":r,"vitest-project":i,"vitest-runner":a,"vitest-shuffle":s}=e;if(s!==void 0&&!/^\d{1,9}$/u.test(s))throw new Error(`--vitest-shuffle takes a seed of up to 9 digits, not ${JSON.stringify(s)}`);if(a==="managed"&&(r??i)!==void 0)throw new Error("--vitest-config and --vitest-project select the repository's Vitest, not --vitest-runner managed");return{config:r===void 0?null:O(n,r),host:{...Object.fromEntries(Object.entries(t).filter(f=>f[1]!==void 0&&!h.has(f[0].toUpperCase()))),...t.NODE_OPTIONS===void 0?{}:{NODE_OPTIONS:t.NODE_OPTIONS}},project:i??null,runner:a??((r??i)===void 0?"auto":"repo"),shuffle:s===void 0?null:Number(s)}}function O(e,n){let t;try{t=o.realpathSync(c.resolve(e,n))}catch(r){throw new Error(`--vitest-config ${n} does not exist in ${e}`,{cause:r})}if(!m(o.realpathSync(e),t))throw new Error(`--vitest-config ${n} resolves outside ${e}`);if(!o.statSync(t).isFile())throw new Error(`--vitest-config ${n} is not a file`);return t}export function resolveRepositoryVitest(e,n=!1){const t=c.join(e,"package.json"),r=v(t);let i;try{i=r.resolve("vitest/package.json")}catch(l){const u=!r.resolve.paths("vitest")?.some(E=>o.lstatSync(c.join(E,"vitest"),{throwIfNoEntry:!1}));if(u&&n&&!N(t))return null;const d=u?"is not installed":"is installed but its package metadata is broken";throw new Error(`Vitest ${d} in ${e}; install vitest 4.x, or pass --vitest-runner managed`,{cause:l})}const a=c.dirname(i),s=JSON.parse(o.readFileSync(i,"utf8")),f=`Vitest ${s.version} at ${a}`;if(!/^4\.\d+\.\d+/u.test(s.version))throw new Error(`${f} is not supported; install vitest 4.x, or pass --vitest-runner managed`);const p=s.exports?.["."]?.import?.default;try{if(typeof p!="string")throw new Error("its package exports name no ESM entry point");return{entrypoint:r.resolve("vitest/node"),index:c.join(a,p),name:"vitest",root:a,version:s.version}}catch(l){throw new Error(`${f} is broken; reinstall it`,{cause:l})}}function N(e){if(!o.existsSync(e))return!1;const n=JSON.parse(o.readFileSync(e,"utf8"));return["dependencies","devDependencies","optionalDependencies"].some(t=>Object.hasOwn(n[t]??{},"vitest"))}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{makeTempDirAsync as
|
|
1
|
+
import{makeTempDirAsync as F}from"../../../process/files/temp-dir.js";import{toError as P}from"../../../kernel/error-serialization.js";import{classifyManagedProcessResult as O,terminalOutcome as h,processOutputEvidence as L,failureFromError as T,firstOutputLine as v,processExitDetails as S,processExitFailure as A,executeEngineTasks as N,emptyEvidence as D,taskTimeoutFailure as k,ConsumerPreparationError as b,consumerPreparationBoundary as M,evidenceCollectionFailure as C,taskOrchestrationEnvironment as x}from"../../adapters/execution.js";import{createInvocationWorkspace as U,requireConsumerFile as W,safeSegment as B}from"../../adapters/workspace.js";import H from"node:fs/promises";import Y from"node:path";import{setTimeout as $}from"node:timers/promises";import{pathToFileURL as G}from"node:url";import{isEngineTaskDeadlineExceededReason as j,resolveEngineTaskInputs as V}from"../../api/index.js";import{createNativeInvocationProcessEnvironment as K,runManagedProcess as J}from"../../../process/index.js";import{playwrightFileSuiteGrep as z,writePlaywrightTaskWorkspace as X}from"./config.js";import{collectPlaywrightEvidence as Q}from"./evidence.js";import{engineEvidenceIdentity as Z}from"../../adapters/evidence-identity.js";import{getNodeStartupCacheEnvironment as q}from"../../adapters/node-startup-cache.js";import{CHROMIUM_STARTUP_ENVIRONMENT as tt}from"../runtime/chromium-environment.js";const u={codePrefix:"PLAYWRIGHT",label:"Playwright"};export const PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES=24*1024;const R=64,rt=new Set(["EBUSY","ENOTEMPTY","EPERM"]),et=5;async function d(e){for(let t=0;;t+=1)try{await H.rm(e,{force:!0,recursive:!0});return}catch(r){const i=r instanceof Error&&"code"in r?r.code:void 0;if(t===et||typeof i!="string"||!rt.has(i))throw r;await $((t+1)*100)}}export async function executePlaywrightEngine(e,t,r){const i=await it(e,t,r);return N(e,i.tasks,u,(o,a)=>nt(e,i,o,a),ct(e,i.installation))}async function it(e,t,r){const{artifactRoot:i,invocationDirectory:o,rootDirectory:a}=await U(e,"playwright",r,d),s=await K(o);r.defer(()=>d(s.temporaryDirectory));const n=[];for(const[l,c]of e.tasks.entries()){const p=V(e,c),w=`${l.toString().padStart(3,"0")}-${B(c.id)}`,I=Y.join(o,w),f=await F(`${w}-`,{parentDir:i});try{const m=st(e,c.serviceName),y=await M("PlaywrightConsumerPreparationError",()=>W(a,c.file,"Playwright task file")),g=await X({artifactDirectory:f,baseUrl:m,browserFile:t.browserExecutable,databases:e.target.databases,endpoints:e.target.endpoints,handles:e.target.handles,inputs:p,invocationId:e.invocationId,managedModuleEntrypoints:{authoring:t.authoringEntrypoints.authoring,cli:t.family.cli,environment:t.authoringEntrypoints.environment,managedTest:t.authoringEntrypoints.managedTest,playwright:t.family.playwright.entrypoint,playwrightCore:t.family.playwrightCore.entrypoint,playwrightTest:t.family.playwrightTest.entrypoint,root:t.authoringEntrypoints.root,rootPackageJson:t.authoringEntrypoints.rootPackageJson},moduleFile:t.family.playwrightTest.entrypoint,rootDirectory:a,sourceFile:y,taskDirectory:I,taskId:c.id});r.defer(async()=>{try{await d(g.attachmentDirectory)}catch(E){if(!(E instanceof Error&&"code"in E&&E.code==="ENOTDIR"))throw E}}),n.push({artifactDirectory:f,inputs:p,task:c,preparationFailure:null,prepared:{baseUrl:m,sourceFile:y,workspace:g}})}catch(m){const y=(await lt([I,f])).map(g=>g.error);n.push({artifactDirectory:f,inputs:p,task:c,preparationFailure:playwrightPreparationFailure(m,y),prepared:null})}}return Object.freeze({installation:t,invocationEnvironment:s.environment,rootDirectory:a,tasks:Object.freeze(n)})}async function nt(e,t,r,i){const a={...await q(e.workspace.cacheDirectory,r.inputs.environment),...ot(e,t,r)};try{_(a)}catch{delete a.NODE_COMPILE_CACHE}const s=r.task.deadlineMs-Date.now();if(s<=0)return h(r.task.id,"failed",i,D(),k(r.task.timeoutMs,u));const n=await J({args:[t.installation.family.cli,"test","--grep",z(r.prepared.sourceFile),"--config",r.prepared.workspace.configFile],cwd:t.rootDirectory,displayLabel:"Playwright",env:a,executable:process.execPath,signal:e.signal,timeoutMs:s});let l;try{l=await Q({identity:{engineId:"playwright",invocationId:e.invocationId,taskId:r.task.id,sourceFile:r.prepared.sourceFile},artifactDirectory:r.artifactDirectory,outputDirectory:r.prepared.workspace.outputDirectory,reportFile:r.prepared.workspace.reportFile,reportersFinishedFile:r.prepared.workspace.reportersFinishedFile,rootDirectory:t.rootDirectory,sourceFile:r.prepared.sourceFile,stderr:n.stderr,stdout:n.stdout})}catch(p){return h(r.task.id,"failed",i,L(n),playwrightEvidenceCollectionFailure(n,p))}const c=classifyPlaywrightProcessResult(n,l,r.task.timeoutMs,j(e.signal.reason));return h(r.task.id,c.status,i,l.evidence,c.failure)}export function classifyPlaywrightProcessResult(e,t,r,i){const o=O(e,r,i,u);if(o!==null)return o;if(t.outputLimit!==null){const n=t.outputLimit;return{failure:{code:"PLAYWRIGHT_OUTPUT_ARTIFACT_LIMIT",details:{...n},message:`Playwright output exceeded its ${n.limit.toString()} ${n.kind==="bytes"?"byte aggregate":"entry"} artifact limit`},status:"failed"}}const a=t.attachmentFailures.find(n=>n.code==="PLAYWRIGHT_ATTACHMENT_LIMIT");if(a!==void 0)return{failure:a,status:"failed"};if(t.report.failure!==null){const n=t.report.failure;return{...t.report,failure:{...n,details:{...n.details,evidenceErrors:t.reportError===null?0:1,...S(e)}}}}const s=t.attachmentFailures.at(0);return s!==void 0?{failure:s,status:"failed"}:e.exitCode!==0&&(t.reportError===null||!t.reportersFinished)?A(e,u,v(e.stderr)??v(e.stdout)):t.reportError!==null?{failure:{...T(t.reportError,"PLAYWRIGHT_REPORT_INVALID"),classification:"infra"},status:"failed"}:t.report}export function playwrightEvidenceCollectionFailure(e,t){return C(e,u,t)}function ot(e,t,r){const i={...t.invocationEnvironment},o=r.task,a=r.prepared.workspace.inputBootstrapFile,s=r.prepared.workspace.resolutionBootstrapFile;return Object.assign(i,{...r.prepared.baseUrl===null?{}:{BASE_URL:r.prepared.baseUrl},...tt,FORCE_COLOR:"0",NO_COLOR:"1",PLAYWRIGHT_BROWSERS_PATH:t.installation.registry.canonicalValue,PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD:"1",TESTKIT_ACTIVE:"1",TESTKIT_INTERNAL_FILE_DEADLINE_MS:String(o.deadlineMs),TESTKIT_INTERNAL_FILE_TIMEOUT_SECONDS:String(Math.max(1,Math.ceil(o.timeoutMs/1e3))),...x(e.invocationId,o),TESTKIT_TEST_FILE:o.file,TESTKIT_TEST_ID:o.id}),i.NODE_OPTIONS=at(s,a),_(i),i}function at(e,t){return["--experimental-strip-types",`--require=${JSON.stringify(e)}`,`--import=${JSON.stringify(G(t).href)}`].join(" ")}export function playwrightSpawnEnvironmentBytes(e){return Object.entries(e).reduce((t,[r,i])=>t+(process.platform==="win32"?(r.length+i.length+2)*2:Buffer.byteLength(r,"utf8")+Buffer.byteLength(i,"utf8")+2),0)}function _(e){if(Object.keys(e).length>R)throw new Error(`Playwright physical spawn environment exceeds its ${R.toString()} entry limit`);if(playwrightSpawnEnvironmentBytes(e)>PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES)throw new Error(`Playwright physical spawn environment exceeds its ${PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES.toString()} byte limit`)}function st(e,t){const r=e.target.baseUrl;if(r===null)return null;if(typeof r!="string"||r.trim().length===0)throw new Error(`Playwright task service ${t} endpoint must be a non-empty URL`);let i;try{i=new URL(r)}catch(o){throw new Error(`Playwright task service ${t} endpoint is not a valid URL`,{cause:o})}if(i.protocol!=="http:"&&i.protocol!=="https:")throw new Error(`Playwright task service ${t} endpoint must use http or https`);return i.href}function ct(e,t){return{...Z({engineId:"playwright",installationKind:"root-playwright-family",profile:e.profile}),browserName:"chromium",packages:{playwright:t.family.playwright.version,playwrightCore:t.family.playwrightCore.version,playwrightTest:t.family.playwrightTest.version},registryMode:t.registry.mode}}export function playwrightPreparationFailure(e,t){if(t.length===0)return{...T(e,"PLAYWRIGHT_PREPARE_FAILED"),classification:e instanceof b?"product":"infra"};const r=P(e);return{classification:"infra",code:"PLAYWRIGHT_PREPARE_CLEANUP_FAILED",details:{cleanupFailures:t.map(i=>i.message),preparationFailure:r.message},message:`Playwright task preparation failed and its partial workspace could not be removed: ${t.map(i=>i.message).join("; ")}`,...r.stack===void 0?{}:{stack:r.stack}}}async function lt(e){const t=[];for(const r of e)try{await d(r)}catch(i){t.push({directory:r,error:P(i)})}return t}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{ownedRuntimeInputModuleUrl as
|
|
1
|
+
import{ownedRuntimeInputModuleUrl as g}from"../../runtime-input-file.js";import b from"node:crypto";import i from"node:fs/promises";import o from"node:path";import{fileURLToPath as w,pathToFileURL as m}from"node:url";import{createManagedNodeModuleResolution as P}from"../../managed-module-resolution.js";import{materializePlaywrightRuntimeInput as j,playwrightRuntimeInputBootstrapSource as v}from"../runtime/runtime-input.js";import{nativeReporterFile as T}from"../../adapters/native-report.js";import{requireConsumerFile as O}from"../../adapters/workspace.js";import{consumerPreparationBoundary as k}from"../../adapters/execution.js";export async function writePlaywrightTaskWorkspace(e){const t=await k("PlaywrightConsumerConfigurationError",()=>(I(e.inputs.supportModules),x(e.rootDirectory,e.inputs)));await i.mkdir(e.taskDirectory,{recursive:!0}),await i.mkdir(e.artifactDirectory,{recursive:!0});const r=b.randomUUID(),n=o.join(e.taskDirectory,`playwright-${r}.config.mjs`),a=o.join(e.taskDirectory,`playwright-${r}.input-bootstrap.mjs`),s=o.join(e.taskDirectory,`playwright-${r}.resolution-bootstrap.cjs`),h=o.join(e.taskDirectory,"testkit-playwright-report.json"),d=o.join(e.taskDirectory,"testkit-playwright-reporters-finished"),l=o.join(e.artifactDirectory,"playwright-output");await i.mkdir(l);const p=o.join(e.artifactDirectory,"playwright-attachments");await i.mkdir(p,{mode:448,recursive:!1});const c=await j({baseUrl:e.baseUrl,databases:e.databases,directory:e.taskDirectory,endpoints:e.endpoints,handles:e.handles,inputs:e.inputs,invocationId:e.invocationId,taskId:e.taskId}),f=T("playwright");return await u(s,playwrightResolutionBootstrapSource(e.managedModuleEntrypoints,{consumerRoots:[e.rootDirectory],trustedImporterFiles:[n,a],trustedInternalEntrypoints:[e.moduleFile,f,w(g)]})),await u(a,v({digest:c.digest,file:c.file,sizeBytes:c.sizeBytes,invocationId:e.invocationId,taskId:e.taskId})),await u(n,S({baseConfigFile:t,baseUrl:e.baseUrl,browserFile:e.browserFile,inputBootstrapFile:a,outputDirectory:l,reporterFile:f,report:{attachmentDirectory:p,identity:{engineId:"playwright",invocationId:e.invocationId,taskId:e.taskId,sourceFile:e.sourceFile},reportFile:h,reportersFinishedFile:d,workspaceDirectory:e.taskDirectory},retries:typeof e.inputs.configuration?.retries=="number"?e.inputs.configuration.retries:null,rootDirectory:e.rootDirectory,sourceFile:e.sourceFile,testkitPackageDirectory:await i.realpath(o.dirname(e.managedModuleEntrypoints.rootPackageJson))})),{attachmentDirectory:p,configFile:n,inputBootstrapFile:a,outputDirectory:l,reportFile:h,reportersFinishedFile:d,resolutionBootstrapFile:s}}export function playwrightResolutionBootstrapSource(e,t){const r={"@playwright/test":e.playwrightTest,playwright:e.playwright,"playwright-core":e.playwrightCore};return P({consumerRoots:t.consumerRoots,direct:{"@gfdelarue/testkit":e.root,"@gfdelarue/testkit/environment":e.environment,"@gfdelarue/testkit/package.json":e.rootPackageJson,"@gfdelarue/testkit/playwright":e.authoring,"@playwright/test":e.managedTest,"playwright/test":e.managedTest},directBridgeRoots:[o.dirname(e.root)],engineLabel:"Playwright",forbiddenConsumerPackagePrefixes:[],packageAnchors:r,reservedPackageNames:["@gfdelarue/testkit"],stateSymbol:"@gfdelarue/testkit/playwright-resolution-hooks/v1",trustedImporterFiles:t.trustedImporterFiles,trustedInternalEntrypoints:t.trustedInternalEntrypoints,verifiedParentlessPackageFiles:{playwright:["lib/loader/loaderProcessEntry.js","lib/worker/workerProcessEntry.js"]},verifiedProcessEntrypoints:[e.cli]}).bootstrapSource}function S(e){return`
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
|
|
5
|
-
const OPTIONS = ${JSON.stringify({baseConfigUrl:e.baseConfigFile===null?null:
|
|
5
|
+
const OPTIONS = ${JSON.stringify({baseConfigUrl:e.baseConfigFile===null?null:m(e.baseConfigFile).href,baseUrl:e.baseUrl,browserFile:e.browserFile,exactTaskMatchSource:playwrightExactFileFilter(e.sourceFile),originalConfigDirectory:e.baseConfigFile===null?e.rootDirectory:o.dirname(e.baseConfigFile),originalConfigFile:e.baseConfigFile??o.join(e.rootDirectory,"__testkit_playwright_config__.mjs"),inputBootstrapUrl:m(e.inputBootstrapFile).href,outputDirectory:e.outputDirectory,report:e.report,reporterFile:e.reporterFile,retries:e.retries,rootDirectory:e.rootDirectory,sourceFile:e.sourceFile,testkitPackageMatchSource:`^${y(e.testkitPackageDirectory)}[\\\\/]`})};
|
|
6
6
|
const inputBootstrap = await import(OPTIONS.inputBootstrapUrl);
|
|
7
7
|
if (typeof inputBootstrap.installPlaywrightRuntimeInput !== "function")
|
|
8
8
|
throw new Error("Managed Playwright runtime-input bootstrap is invalid");
|
|
@@ -197,4 +197,4 @@ export default {
|
|
|
197
197
|
fullyParallel: false,
|
|
198
198
|
workers: 1,
|
|
199
199
|
};
|
|
200
|
-
`}export function playwrightExactFileFilter(e){return`^${
|
|
200
|
+
`}export function playwrightExactFileFilter(e){return`^${y(e)}$`}function y(e){return e.split(/[\\/]+/u).map(t=>t.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")).join("[\\\\/]")}export function playwrightFileSuiteGrep(e){const t=e.split(/[\\/]+/u).filter(a=>a!=="").map(a=>a.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")),r=t.pop()??"";return`(?:^| )(?:\\.\\.[\\\\/])*${t.reduce((a,s)=>`(?:${a}${s}[\\\\/])?`,"")}${r}(?: |$)`}async function x(e,t){if(!Object.hasOwn(t.supportModules,"playwrightConfig"))return null;const r=t.supportModules.playwrightConfig;if(!o.isAbsolute(r))throw new Error("supportModules.playwrightConfig must be an absolute path");return O(e,r,"supportModules.playwrightConfig")}function I(e){const t=Object.keys(e).filter(r=>r!=="playwrightConfig");if(t.length>0)throw new Error(`Playwright engine does not support support module(s): ${t.join(", ")}`)}async function u(e,t){const r=await i.open(e,"wx",384);try{await r.writeFile(t,"utf8")}finally{await r.close()}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{executePlaywrightEngine}from"./adapter.js";
|
|
1
|
+
export{executePlaywrightEngine}from"./adapter.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import x from"crypto";import{isTerminalRunStatus as z}from"../process/runs/schema.js";import{observeProcessCreationIdentity as U}from"../process/process-identity.js";import{protectTestkitStateRoot as W}from"../process/files/product-dir.js";export{isRunManifestActive}from"../process/runs/inspection.js";import{normalizeOperationReason as $}from"../domain/operation-reason.js";import{captureProcessCreationIdentity as V}from"../process/service-processes.js";import{requireProductLifecycleFenceProductDir as B,withProductLifecycleFence as Y}from"../process/lifecycle-fence.js";import{createRunManifest as J,listRunManifests as K,readRunMaintenanceStopRequest as Q,removeRunManifestFile as N,requireSafeRunId as X,RUN_MANIFEST_SCHEMA_VERSION as Z,runManifestPath as ee,writeRunManifest as te}from"../process/runs/manifest.js";const ne=1e3,re=String,k=new WeakMap;export function createUnpublishedRunLifecycle(n,r){const s=r.runId===void 0?ue():X(r.runId,"Testkit run id");W(n,!0);const a=ee(n,s),o=new AbortController,I=new AbortController,p=r.pid===void 0?process.pid:r.pid,w=p>0?V(p):null;if(p>0&&w===null)throw new Error(`Could not capture the Testkit run process identity for pid ${p.toString()}`);let c={incarnationId:x.randomUUID(),schemaVersion:Z,runId:s,pid:p,processIdentity:typeof w=="string"?w:null,controller:se(r.controller),status:"starting",startedAt:new Date().toISOString(),heartbeatAt:r.heartbeat===!1?null:new Date().toISOString(),interruptReason:null,services:[],runtimeDirs:[]};const O=[];let S,C=null;const y=new Set,b=new Set,M=r.keepAlive===!0?setInterval(()=>{},ne):null;let R=null,g=null,v=null,L=!1;function _(e){const t=k.get(h);if(!t)throw new Error("Testkit run lifecycle ownership is unavailable");if(!t.published){J(n,e),t.published=!0;return}te(n,e)}function A(){const e=Q(n,s);return e===null||e.targetIncarnationId!==c.incarnationId||e.targetPid!==p||e.targetProcessIdentity!==c.processIdentity||e.coordinatorPid===C?.coordinatorPid&&e.coordinatorProcessIdentity===C.coordinatorProcessIdentity?null:U(e.coordinatorPid,e.coordinatorProcessIdentity)==="different"?(C=e,null):e}function m(e){E("mutate run state");const t=ie(c);e(t),t.heartbeatAt!==null&&(t.heartbeatAt=new Date().toISOString()),_(t),c=t}function E(e){if(g!==null)throw new Error(`Cannot ${e} after Testkit run ${s} finalization is committed`)}function G(){R||(R=setInterval(()=>{},1e3))}function P(){R&&(clearInterval(R),R=null)}function q(){for(const e of b)clearInterval(e);b.clear()}function H(e="controller process exited before Testkit run finalized"){if(L||z(c.status))return!1;const t=$(e,"controller process exited before Testkit run finalized"),i=new Date,u=c.status==="interrupting"||o.signal.aborted?"interrupted":"failed",f=u==="interrupted"?c.interruptReason??v??t:t;return m(d=>{d.status=u,d.finishedAt=i.toISOString(),d.interruptReason=f,d.heartbeatAt!==null&&(d.heartbeatAt=i.toISOString())}),r.evidence.controllerExited(n,{runId:s,reason:f,status:u,finishedAt:i}),!0}const F=()=>{H()};process.on("exit",F);const h={runId:s,manifestPath:a,maintenanceStopSignal:I.signal,signal:o.signal,commitFinalization(e="finished"){if(g!==null)return g;const t=A(),i=o.signal.aborted||t!==null||c.status==="interrupting",u=i?c.interruptReason??v??t?.reason??"run interrupted":null,f=Object.freeze({interruptReason:u,interrupted:i});return m(d=>{d.status=i?"interrupted":e,d.finishedAt=new Date().toISOString(),d.interruptReason=u}),g=f,q(),f},getInterruptReason(){return c.interruptReason??v},isMaintenanceStopCoordinated(){return A()!==null},isStopRequested(){return o.signal.aborted||g?.interrupted===!0},markRunning(){m(e=>{e.status="running"})},markFinished(e="finished"){m(t=>{t.status=e,t.finishedAt=new Date().toISOString()})},heartbeat(e={}){m(t=>{t.heartbeatAt=new Date().toISOString(),e&&typeof e=="object"&&!Array.isArray(e)&&(t.session={...t.session||{},...e})})},startHeartbeat(e=5e3,t=null){E("start a run heartbeat");const i=setInterval(()=>{let u;try{u=typeof t=="function"?t():{}}catch{u={}}h.heartbeat(u)},e);return i.unref(),b.add(i),()=>{clearInterval(i),b.delete(i)}},requestStop(e="interrupted"){if(g!==null||z(c.status))return;const t=A(),i=t?t.reason:$(e,"interrupted"),u=!o.signal.aborted;u&&(v=i),G();let f=null;if(u)try{m(l=>{l.status="interrupting",l.interruptReason=i})}catch(l){f=l instanceof Error?l:new Error("Failed to persist the Testkit stop request",{cause:l})}t!==null&&!I.signal.aborted&&I.abort(new Error(`Testkit run ${s} is being stopped by external maintenance`));const d=[...y].map(({supervisor:l})=>l.stop(t===null?void 0:Date.parse(t.requestedAt)+l.shutdownTimeoutMs));if(Promise.allSettled(d).then(P),o.signal.aborted||o.abort(new Error(`testkit run interrupted (${i})`)),!!u&&f!==null)throw f},trackGraphContext(e){m(t=>{ae(t.runtimeDirs,e.runtimeDir)})},registerService(e,t,i,u){if(E("register application service ownership"),i.pid!==t.pid||(process.platform==="win32"?i.recovery.processGroupId!==null:i.recovery.processGroupId!==i.pid))throw new Error("Application service supervisor returned an invalid ownership handoff");const f={supervisor:t,unsubscribe:()=>{}};y.add(f);const d=oe(e);m(l=>{l.services=l.services.filter(D=>D.pid!==i.pid),l.services.push({serviceName:e.name,runtimeLabel:e.runtimeLabel,command:e.testkit.local.start||null,cwd:u,pid:i.pid,processGroupId:i.recovery.processGroupId,processIdentity:i.recovery.creationIdentity,ports:d,startedAt:new Date().toISOString(),shutdownTimeoutMs:t.shutdownTimeoutMs,shutdownDeadlineAt:t.shutdownDeadlineAt})}),f.unsubscribe=t.onShutdownStarted(l=>{m(D=>{const T=D.services.find(j=>j.pid===i.pid);T&&(T.shutdownDeadlineAt=T.shutdownDeadlineAt==null?l:Math.min(T.shutdownDeadlineAt,l))})})},unregisterService(e){E("unregister application service ownership");for(const t of y)t.supervisor.pid===e&&(t.unsubscribe(),y.delete(t));m(t=>{t.services=t.services.filter(i=>i.pid!==e)})},installSignalHandlers(){if(!(O.length>0)&&!(p!==process.pid||U(p,c.processIdentity)!=="matches")){for(const e of["SIGINT","SIGTERM","SIGHUP"]){const t=h.requestStop.bind(h,e);process.on(e,t),O.push([e,t])}process.platform==="win32"&&(S=setInterval(()=>{const e=A();e!==null&&(clearInterval(S),h.requestStop(e.reason))},100),S.unref(),b.add(S))}},removeSignalHandlers(){S&&(clearInterval(S),b.delete(S),S=void 0);for(const[e,t]of O.splice(0))process.off(e,t)},async removeManifest(){await Y(n,e=>cleanupOwnedRunLifecycleWithinLifecycleFence(e,h))},finalizeForControllerExit:H,dispose(){L=!0;for(const e of y)e.unsubscribe();process.off("exit",F),h.removeSignalHandlers(),M&&clearInterval(M),P(),q()}};return k.set(h,{incarnationId:c.incarnationId,pid:p,processIdentity:c.processIdentity,productDir:n,published:!1,runId:s}),h}function ie(n){const r={...n,controller:n.controller===null?null:{...n.controller},runtimeDirs:[...n.runtimeDirs],services:n.services.map(s=>({...s,ports:s.ports.map(a=>({...a}))}))};return n.session!==void 0&&(r.session=n.session===null?null:{...n.session}),r}function se(n){return!n||typeof n!="object"||Array.isArray(n)?null:Object.fromEntries(Object.entries(n).filter(([,r])=>r!==void 0).map(([r,s])=>[r,s===null?null:re(s)]))}export function cleanupOwnedRunLifecycleWithinLifecycleFence(n,r){const{manifest:s,ownership:a,productDir:o}=readOwnedRunLifecycleWithinLifecycleFence(n,r);if(!s)return!1;if(s.services.length>0)throw new Error(`Cannot clean Testkit run ${r.runId} service ownership while holding the lifecycle fence`);return N(o,r.runId),a.published=!1,!0}export function readOwnedRunLifecycleWithinLifecycleFence(n,r){const s=B(n),a=k.get(r);if(!a||a.productDir!==s)throw new Error("Cannot clean a Testkit run without matching lifecycle ownership");if(a.runId!==r.runId)throw new Error("Testkit run lifecycle identity has been modified");const o=K(s).find(I=>I.runId===r.runId);if(o!==void 0&&(o.incarnationId!==a.incarnationId||o.pid!==a.pid||o.processIdentity!==a.processIdentity))throw new Error(`Testkit run ${r.runId} ownership no longer matches its manifest`);return{manifest:o??null,ownership:a,productDir:s}}function oe(n){const r=new Set,s=[];for(const a of[n.testkit.local.baseUrl,n.testkit.local.readyUrl])if(a)try{const o=new URL(a),I=le(o.hostname),p=Number(o.port||(o.protocol==="https:"?443:80)),w=`${I}:${p.toString()}`;if(r.has(w))continue;r.add(w),s.push({host:I,port:p})}catch{}return s}function ae(n,r){r&&(n.includes(r)||n.push(r))}function ue(){return`${Date.now().toString()}-${process.pid.toString()}-${x.randomBytes(4).toString("hex")}`}function le(n){return n?n==="::1"?"127.0.0.1":n:"127.0.0.1"}export function disposeFailedRunLifecycle(n){const r=k.get(n);n.dispose(),n.removeSignalHandlers(),r?.published===!0&&N(r.productDir,n.runId)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{finalizeRuntimeDatabaseConfig as
|
|
1
|
+
import{finalizeRuntimeDatabaseConfig as U}from"../database/runtime-config.js";import x from"path";import{finalizeConfiguredInputs as y,finalizeConfiguredSteps as C}from"../steps/configured-steps.js";import"../config/loader/index.js";import{finalizeString as v,resolveTemplateString as h}from"../steps/index.js";export function resolveRuntimeInstanceConfigs(e,t,a,s={}){const c=buildRuntimeLabel(s.graphDirName,t),o=s.ports??new Map,i=new Map,l=new Map,k=b(s.externalEndpoints),p=new Map(Object.entries(b(s.publicEndpoints))),u=new Map(Object.entries(b(s.privateEndpoints))),d=new Map(Object.entries(b(s.serviceContextEndpoints))),m={runtimeId:t,runtimeLabel:c,runtimeDir:a,portMap:o,baseUrlByService:i,readyUrlByService:l};for(const n of e){const r=n.testkit.local;if(!r)continue;const f={...m,serviceName:n.name};r.baseUrl!==void 0&&i.set(n.name,h(r.baseUrl,f)),r.readyUrl!==!1&&r.readyUrl!=null&&l.set(n.name,h(r.readyUrl,f))}for(const[n,r]of Object.entries(k))i.has(n)||i.set(n,r),l.has(n)||l.set(n,r);for(const[n,r]of i.entries())p.has(n)||p.set(n,j(r,s.publicHost||null)),d.has(n)||d.set(n,r),u.has(n)||u.set(n,r);return e.map(n=>resolveRuntimeConfig(n,t,c,a,o,i,l,p,u,d,s.publicHost||null,s.databaseBindings??new Map))}function b(e){return Object.fromEntries(Object.entries(e??{}).map(([t,a])=>[t,(a??"").trim()]).filter(t=>t[1].length>0))}export function resolveRuntimeConfig(e,t,a,s,c,o,i,l,k,p,u,d){const m=resolveServiceStateDir(s,e),n=resolveServicePrepareDir(s,e),r={runtimeId:t,runtimeLabel:a,runtimeDir:s,productDir:e.productDir,serviceName:e.name,serviceCwd:e.testkit.local?.cwd||".",serviceStateDir:m,prepareDir:n,portMap:c,baseUrlByService:o,readyUrlByService:i,publicBaseUrlByService:l,privateBaseUrlByService:k,serviceContextUrlByService:p,publicHost:u,leaseId:null,leaseDir:null,databaseBindingsByService:d},f=U(e.testkit.database,r),S={...e.testkit.runtime,prepare:B(e.testkit.runtime.prepare,r)},w=e.testkit.local?{...e.testkit.local,start:v(e.testkit.local.start,r),cwd:e.testkit.local.cwd!==void 0?v(e.testkit.local.cwd,r):e.testkit.local.cwd,assignedPort:c.get(e.name),baseUrl:o.get(e.name),readyUrl:i.get(e.name),env:{...e.testkit.local.env||{}}}:void 0;return{...e,stateDir:m,runtimeId:t,runtimeLabel:a,testkit:{...e.testkit,database:f,prepareDir:n,templateContext:r,runtime:S,local:w}}}export function resolveServiceStateDir(e,t){return x.join(e,"services",t.name)}export function resolveServicePrepareDir(e,t){return x.join(resolveServiceStateDir(e,t),"prepared")}function j(e,t){if(!t)return e;try{const a=new URL(e);a.hostname=t;let s=a.toString();return!e.endsWith("/")&&a.pathname==="/"&&s.endsWith("/")&&(s=s.slice(0,-1)),s}catch{return e}}function B(e,t){return e?{inputs:y(e.inputs,a=>v(a,t)),steps:C(e.steps,a=>v(a,t))}:{inputs:[],steps:[]}}export function buildRuntimeLabel(e,t){return e?`${e}/${t}`:t}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{isPathWithin as
|
|
1
|
+
import{isPathWithin as C}from"../process/files/paths.js";import{isUnknownArray as $,isUnknownRecord as g}from"../kernel/unknown-values.js";import{readRunDocument as B}from"./read-run.js";import f from"node:fs";import a from"node:path";import"../engines/api/index.js";import"../domain/identifiers.js";import{normalizeArtifactArchiveConfig as K}from"./artifact-config.js";import{applyRunBundleRetention as U,publishRunBundle as q,RefusedArtifactsError as j,sealRunBundle as V}from"./bundles.js";import{RETENTION_DECISION_SCHEMA_VERSION as J}from"./contracts.js";import{redactedRunValue as A,runRedaction as W}from"./redaction.js";import{applyTimingUpdates as X,createEmptyTimings as k,normalizeTimings as G}from"./timing.js";import{requireObjectRecord as H,requireStringArray as Q}from"./read-fields.js";import{errorMessage as Y,serializeJson as N,writeJsonFileAtomicWithRetry as w,readJsonFileOrNull as z}from"../process/files/json-file.js";import{recordEvidenceWrite as E}from"../process/files/evidence-writes.js";import{recordWork as D}from"../kernel/work-metrics.js";import{copyRegularFileSecurely as Z}from"./secure-file-copy.js";import{hasErrorCode as O,serializeError as tt}from"../kernel/error-serialization.js";import{redactedPrefix as et}from"../kernel/redaction.js";import{posixPath as I}from"../process/files/paths.js";import{sanitizeSegment as S}from"./storage.js";const b="timings.json",v="artifacts",rt="logs",nt="setup",T="live.json",it=1e4,st=String;export function writeRunArtifact(t,e,r={},n={}){const i=e,s=a.join(t,".testkit","results");f.mkdirSync(s,{recursive:!0});const c=i.archive,o=r.archiveConfig??K(c?.request||{}),u=r.declaredEnvironmentKeys??Q(c?.declaredEnvironmentKeys||[],"run artifact declaredEnvironmentKeys"),l=n.publishRunBundle??q,m=V(t,i,{env:r.env,archiveConfig:o,declaredEnvironmentKeys:u,recordRefusals:M=>{ut(s,i.run.id,M)},redactKeys:r.redactKeys,redactionTargets:r.redactionTargets}),d=ot(t,m.archive.runId,o.publication,l),y=at(t,m.archive.runId,o.retention),{targets:p}=W({env:r.env,redactKeys:r.redactKeys,redactionTargets:r.redactionTargets}),h=A(i.finalizationFailure,p),R={...m,...h===void 0?{}:{finalizationFailure:h},run:d.status==="failed"&&d.required===!0||m.archive.cleanup?.status==="failed"||h!==void 0&&m.run.status!=="interrupted"?{...m.run,status:"failed"}:m.run,archive:{...m.archive,publication:A(d,p),retention:A(y,p)}};return ct(s,R,p)}function ot(t,e,r,n){try{return n(t,e,r)}catch(i){return{bytesUploaded:0,configured:r!==null,error:x(i),recordedAt:new Date().toISOString(),required:r?.required??!1,runId:e,schemaVersion:1,status:"failed",target:r===null?null:a.resolve(t,r.directory)}}}function at(t,e,r){try{return U(t,e,r)}catch(n){return{currentRunId:e,decidedAt:new Date().toISOString(),error:x(n),garbageCollection:{failures:[],reclaimedBytes:0,removedObjects:0,status:"not_started"},policy:{maxAgeDays:r.maxAgeDays,maxRuns:r.maxRuns,preserveSuites:[...r.preserveSuites]},protectedRunIds:[e],removed:[],removalErrors:[],schemaVersion:J,status:"failed"}}}function ct(t,e,r){const n=a.join(t,"latest.json"),i={path:I(a.relative(a.dirname(t),n)),status:"complete"},s={...e,archive:{...e.archive,latest:i}};try{return E(n),w(n,s),s}catch(c){return{...s,archive:{...s.archive,latest:{...i,error:A(x(c),r),status:"failed"}}}}}function x(t){try{return Y(t)}catch{return"Unknown post-seal archive operation failure"}}export function writeLiveRunArtifact(t,e){const r=e,n=a.join(t,".testkit","results");f.mkdirSync(n,{recursive:!0});const i=a.join(n,T);E(i),w(i,r)}export function finalizeLiveRunArtifact(t,e={}){const r=mt(e),n=a.join(t,".testkit","results",T),i=z(n);if(i===null)return!1;const s=ft(i,r);if(!s)return!1;const c={redactKeys:e.redactKeys,redactionTargets:e.redactionTargets};try{writeRunArtifact(t,s,c)}catch(o){if(!(o instanceof j))throw o;writeRunArtifact(t,F(s,o),c)}return!0}function ut(t,e,r){const n=a.join(t,T),i=z(n);e===null||!g(i)||!g(i.run)||i.run.id!==e||w(n,F(i,new j(r)))}function F(t,e){const r=tt(e),n="finalizationFailure"in t?t.finalizationFailure:void 0,i=g(n)&&typeof n.message=="string"?n.message.split(`
|
|
2
2
|
`):[];for(const s of r.message.split(`
|
|
3
3
|
`))i.includes(s)||i.push(s);return{...t,finalizationFailure:{...r,message:i.join(`
|
|
4
|
-
`)}}}function
|
|
5
|
-
`,
|
|
4
|
+
`)}}}function ft(t,e){const r=B(t,"live run artifact"),n=r.run.id||null;if(e.runId&&n&&n!==e.runId)return null;const i=dt(e.finishedAt),s=new Date(i).toISOString(),c=pt(r.run.startedAt),o=c===null?r.run.durationMs:Math.max(0,i-c);return{...r,generatedAt:s,run:{...r.run,id:n??e.runId,status:e.status,finishedAt:s,durationMs:o,interruptReason:e.reason},timings:lt(r.timings,s,e.reason)}}function lt(t,e,r){if(t==null)return t;const n=H(t,"live run artifact.timings"),i=n.operations;if(i==null)return n;if(!$(i))throw new Error("live run artifact.timings.operations must be an array");return{...n,operations:i.map(s=>!g(s)||s.status!=="running"?s:{...s,status:"cancelled",finishedAt:L(s.finishedAt,"live run operation.finishedAt")??e,message:L(s.message,"live run operation.message")??r})}}function L(t,e){if(t==null||t==="")return null;if(typeof t!="string")throw new Error(`${e} must be a string or null`);return t}function mt(t){return{runId:t.runId??null,reason:t.reason??"run interrupted",status:t.status??"interrupted",finishedAt:t.finishedAt??Date.now()}}function dt(t){if(t instanceof Date&&Number.isFinite(t.getTime()))return t.getTime();if(typeof t=="number"&&Number.isFinite(t))return t;if(typeof t=="string"){const e=Date.parse(t);if(Number.isFinite(e))return e}return Date.now()}function pt(t){if(typeof t!="string")return null;const e=Date.parse(t);return Number.isFinite(e)?e:null}export function resetResultArtifacts(t){const e=a.join(t,".testkit","results");for(const r of[v,rt,nt])f.rmSync(a.join(e,r),{recursive:!0,force:!0});f.rmSync(a.join(e,T),{force:!0})}export function persistTaskArtifacts(t,e,r){if(r.length===0)return[];const n=S(e.serviceName||"service","artifact"),i=_(t,n);return r.map((s,c)=>{const o=yt(s),u={schemaVersion:2,source:"testkit-runtime-artifact",service:e.serviceName,suite:{engineId:e.engineId,key:e.suiteKey,name:e.suiteName,profileId:e.profileId,testType:e.testType},file:e.file,taskId:e.id,index:c,name:o.name,kind:o.kind,summary:o.summary,contentType:o.contentType,emittedAt:o.emittedAt,data:o.data},l=`${N(u,"runtime artifact payload")}
|
|
5
|
+
`,m=P(i,e.id,c,o.name,".json",y=>{f.writeFileSync(y,l,{flag:"wx"}),D("evidence.filesWritten")}),d=a.join(".testkit","results",v,n,m);return{name:u.name,kind:u.kind,summary:u.summary,contentType:u.contentType,path:I(d)}})}export function persistTaskFileArtifacts(t,e,r,n={}){if(r.length===0)return[];const i=S(e.serviceName||"service","artifact"),s=_(t,i);return r.map((c,o)=>{const u=ht(c),l=a.resolve(u.path),m=St(l,u.contentType),d=[],y=P(s,e.id,o,u.name,m,R=>{d.push(Z(l,R,{...n.maxBytes===void 0?{}:{maxBytes:n.maxBytes},...n.sourceRoot===void 0?{}:{sourceRoot:n.sourceRoot},...n.sizeLimitDescription===void 0?{}:{sizeLimitDescription:n.sizeLimitDescription}})),D("evidence.filesWritten")}),p=d.at(0);if(p===void 0)throw new Error("File artifact copy completed without a result");n.copyResults?.push(p);const h=a.join(".testkit","results",v,i,y);return{name:u.name,kind:u.kind,summary:u.summary,contentType:u.contentType??vt(l),path:I(h)}})}export function persistTaskOutputArtifacts(t,e,r){if(r.length===0)return[];const n=[];for(const i of r){const s=i.text;s.trim().length!==0&&n.push({name:i.name||"task-output",kind:i.kind||"runtime.output",summary:i.summary||At(s),contentType:i.contentType||"text/plain",emittedAt:i.emittedAt||new Date().toISOString(),data:{stream:i.stream||null,text:s}})}return persistTaskArtifacts(t,e,n)}export function loadTimings(t){const e=a.join(t,".testkit",b);if(!f.existsSync(e))return k();try{const r=JSON.parse(f.readFileSync(e,"utf8"));return G(r)}catch{return k()}}export function saveTimings(t,e,r){if(r.length===0)return;const n=X(e,r),i=a.join(t,".testkit");f.mkdirSync(i,{recursive:!0});const s=a.join(i,b);E(s),f.writeFileSync(s,N(n,"timings"))}function yt(t){return{name:t.name,kind:t.kind||null,summary:t.summary||null,contentType:t.contentType||"application/json",emittedAt:t.emittedAt||null,data:t.data}}function ht(t){return{path:t.path,name:t.name||"artifact",kind:t.kind||null,summary:t.summary||null,contentType:t.contentType||null}}function gt(t,e,r,n,i=0){const s=i===0?"":`-${(i+1).toString().padStart(2,"0")}`;return`task-${S(String(t),"artifact")}-${(e+1).toString().padStart(2,"0")}-${S(r,"artifact")}${s}${n}`}function P(t,e,r,n,i,s){for(let c=0;c<it;c+=1){const o=gt(e,r,n,i,c),u=a.join(t,o);try{return s(u),o}catch(l){if(O(l,"EEXIST"))continue;throw f.rmSync(u,{force:!0}),l}}throw new Error(`Could not allocate a unique artifact path for task ${st(e)} and artifact ${n}`)}function _(t,e){const r=a.resolve(t);if(!f.statSync(r).isDirectory())throw new Error("Artifact product directory must be a directory");const i=f.realpathSync(r);let s=r;for(const c of[".testkit","results",v,e]){s=a.join(s,c);try{f.mkdirSync(s)}catch(l){if(!O(l,"EEXIST"))throw l}const o=f.lstatSync(s);if(o.isSymbolicLink()||!o.isDirectory())throw new Error(`Artifact output directory ${JSON.stringify(s)} must be a real directory below productDir`);const u=f.realpathSync(s);if(!C(i,u))throw new Error(`Artifact output directory ${JSON.stringify(s)} escapes productDir`)}return s}function At(t){const e=t.split(/\r?\n/u).map(r=>r.trim()).find(Boolean);return e?et(e,120):"captured task output"}function St(t,e){const r=a.extname(t);if(r)return r;const n=(e??"").split(";",1)[0].trim().toLowerCase();return n==="text/markdown"?".md":n==="text/plain"?".txt":n==="application/json"?".json":n==="application/zip"?".zip":n==="image/png"?".png":n==="image/jpeg"?".jpg":n==="text/html"?".html":n==="video/webm"?".webm":".artifact"}function vt(t){const e=a.extname(t).toLowerCase();return e===".md"||e===".markdown"?"text/markdown":e===".txt"||e===".log"?"text/plain":e===".json"?"application/json":"application/octet-stream"}
|
package/dist/evidence/bundles.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{recordWork as
|
|
2
|
-
`),r.targets).text;e?.(
|
|
1
|
+
import{recordWork as ee}from"../kernel/work-metrics.js";import{recordEvidenceWrite as Se}from"../process/files/evidence-writes.js";import{isUnknownRecord as I,optionalString as te}from"../kernel/unknown-values.js";import{posixPath as U}from"../process/files/paths.js";import{readRunProvenance as we}from"./read-provenance.js";import{parseRunBundleArtifact as ne,parseRunBundleManifest as q,readRunBundleArtifact as Re}from"./read-bundle.js";import{readRunDocument as je}from"./read-run.js";import{runModel as L}from"./run-model.js";import{isUtf8 as be}from"buffer";import xe from"crypto";import u from"fs";import f from"path";import{localDirectories as Be,localPathRewriter as Ee}from"./local-paths.js";import{redactRunText as W,redactRunValue as Ie,runRedaction as Ae}from"./redaction.js";import{isCompressedArtifact as re,sealCompressedArtifact as $e,UnreadableArchiveError as ke}from"./compressed-artifacts.js";import{BUNDLE_SCHEMA_VERSION as se,OBJECTS_DIRNAME as oe,PROVENANCE_SCHEMA_VERSION as Pe,RETENTION_DECISION_SCHEMA_VERSION as Oe,RUNS_DIRNAME as Ce}from"./contracts.js";import{bundleRelativePath as z,hashFile as J,normalizeRunId as A,optionalStringArray as H,optionalUnknownArray as Te,readJsonFile as C,requirePublicationPathOutsideResults as K,requireRunId as Fe,runIncludesFile as ze,resolveManifestEntryPath as ie,resolveRunBundleDirectory as T,resolveRunsRoot as _,resultsRoot as Y,sanitizeSegment as _e,sha256 as k,stableJson as D,treeBytes as ae,walkFiles as ce,writeBufferAtomic as De,writeJson as V}from"./storage.js";import{resolveCiProvenance as Ve}from"./ci-provenance.js";import{errorMessage as $}from"../kernel/error-serialization.js";const X=["artifacts","logs","setup"];export class RefusedArtifactsError extends Error{}class P extends Error{}const ue=Buffer.from([255,254]),le=Buffer.from([254,255]),Me=new Set([9,10,11,12,13,27]),Ne=["package.json","package-lock.json","npm-shrinkwrap.json","pnpm-lock.yaml","yarn.lock","testkit.config.ts","testkit.config.mts","testkit.config.js","testkit.config.mjs"];export function sealRunBundle(e,n,t={},r={}){const s=n,a=Fe(s),o=_(e,!0),i=f.dirname(o),m=f.join(o,a),c=loadVerifiedRunBundleArtifactIfExists(e,a);if(c!==null)return{...c,archive:{...c.archive,cleanup:ve(i)}};const d=f.join(o,`.${a}.${String(process.pid)}.${String(Date.now())}.${xe.randomBytes(4).toString("hex")}.tmp`);u.mkdirSync(d,{recursive:!0});try{const h=Ae(t),R=Ee(Be(e,r.homeDirectory)),p=qe({recordRefusals:t.recordRefusals,resultsDir:i,tempDir:d,redaction:h,rewriteLocalPaths:R}),g=We(Le(s,p.changedPayloads),a,h.targets,R),y=je(g.artifact,"run artifact");p.redaction.structuredReplacements=g.replacements,p.redaction.replacements+=g.replacements;const v=He(e,y,a,{...t,redaction:p.redaction}),x={schemaVersion:se,runId:a,status:"complete",canonicalRunPath:z(a,"run.json"),manifestPath:z(a,"manifest.json"),provenancePath:z(a,"provenance.json"),integrity:"sha256",publication:{configured:!!t.archiveConfig?.publication,status:t.archiveConfig?.publication?"pending":"not_configured"},redaction:p.redaction};y.archive=x,V(f.join(d,"run.json"),y),V(f.join(d,"provenance.json"),v);const B=Ke(d),j=k(Buffer.from(D(B))),w={schemaVersion:se,runId:a,createdAt:new Date().toISOString(),algorithm:"sha256",files:B,storage:{fileCount:B.length,logicalBytes:B.reduce((E,l)=>E+l.bytes,0),objectBytesWritten:p.objectBytesWritten,deduplicatedBytes:p.deduplicatedBytes},redaction:p.redaction,digest:j};return V(f.join(d,"manifest.json"),w),u.renameSync(d,m),{...y,archive:{...x,cleanup:ve(i),manifestDigest:w.digest,storage:w.storage}}}catch(h){try{u.rmSync(d,{recursive:!0,force:!0})}catch{}try{Ge(e)}catch{}throw h}}export function loadVerifiedRunBundleArtifactIfExists(e,n){const t=A(n),r=T(e,t);if(!u.existsSync(r))return null;const s=verifyRunBundle(e,t);if(!s.ok)throw new Error(`Immutable run bundle ${t} already exists but failed integrity verification: ${s.errors.join("; ")}`);return loadRunBundleArtifact(e,t)}export function loadRunBundleArtifact(e,n){return fe(e,n).artifact}function fe(e,n){const t=A(n),r=Re(e,t),s=loadRunBundleManifest(e,t);return{manifest:s,artifact:{...r,archive:{...r.archive,manifestDigest:s.digest,storage:s.storage}}}}export function loadRunBundleManifest(e,n){const t=A(n),r=T(e,t),s=f.join(r,"manifest.json");if(!u.existsSync(s))throw new Error(`No immutable Testkit run manifest found for ${t}`);return q(C(s),s,t)}export function listRunBundles(e,n={}){return G(e,n).map(({artifact:t,manifest:r,...s})=>s)}export function loadRunBundleModels(e){const n=_(e,!1);if(!u.existsSync(n))return{models:[],rejected:[]};const t=[],r=[];for(const s of u.readdirSync(n,{withFileTypes:!0}))if(!(!s.isDirectory()||s.name.startsWith(".")))try{t.push({run:L(Ue(e,s.name)),runId:s.name})}catch(a){r.push({reason:$(a),runId:s.name})}return{models:t,rejected:r}}function Ue(e,n){const t=T(e,A(n)),r=f.join(t,"manifest.json"),s=q(C(r),r,n);if(k(Buffer.from(D(s.files)))!==s.digest)throw new Error("its manifest doesn't match the manifest digest");const a=f.join(t,"run.json"),o=s.files.find(c=>c.path==="run.json"),i=u.readFileSync(a);if(o===void 0||o.bytes!==i.byteLength||o.sha256!==k(i))throw new Error("its run.json doesn't match the manifest");const m=JSON.parse(i.toString("utf8"));return ne(m,a,n)}function G(e,n={}){const t=_(e,!1);if(!u.existsSync(t))return[];const r=u.readdirSync(t,{withFileTypes:!0});return ee("artifacts.runDirectoriesRead",1),ee("artifacts.runEntriesVisited",r.length),r.filter(a=>a.isDirectory()&&!a.name.startsWith(".")).map(a=>{try{const{artifact:o,manifest:i}=fe(e,a.name);return n.file&&!ze(L(o),n.file)?null:{artifact:o,manifest:i,runId:a.name,status:o.run.status,startedAt:o.run.startedAt,finishedAt:o.run.finishedAt??null,manifestDigest:o.archive.manifestDigest??null,canonicalRunPath:o.archive.canonicalRunPath}}catch{return null}}).filter(a=>a!==null).sort((a,o)=>(o.finishedAt??o.startedAt??"").localeCompare(a.finishedAt??a.startedAt??""))}export function verifyRunBundle(e,n){const t=A(n);try{return N(T(e,t),t)}catch(r){return{ok:!1,runId:t,errors:[$(r)],checkedFiles:0}}}export function publishRunBundle(e,n,t,r={}){const s=A(n),a=r.writePublicationReceipt??Ye;if(!t)return a(e,s,{status:"not_configured",required:!1,target:null,bytesUploaded:0});const o=f.resolve(e,t.directory);try{K(e,o)}catch(h){return a(e,s,{status:"failed",required:t.required,target:o,error:$(h),bytesUploaded:0})}const i=_e(f.basename(e),"product"),m=f.join(o,i),c=f.join(m,s),d=f.join(m,`.${s}.${String(process.pid)}.${String(Date.now())}.tmp`);try{const h=T(e,s);if(u.mkdirSync(m,{recursive:!0}),K(e,m),u.existsSync(c)){const g=u.lstatSync(c);if(g.isSymbolicLink()||!g.isDirectory())throw new Error("Publication target must be a regular bundle directory");K(e,c);const y=N(h,s);if(!y.ok)throw new Error(`Source run bundle failed integrity verification: ${y.errors.join("; ")}`);const v=N(c,s);if(!v.ok)throw new Error(`Publication target failed integrity verification: ${v.errors.join("; ")}`);if(y.manifestDigest!==v.manifestDigest)throw new Error("Publication target already exists with a different manifest digest");return a(e,s,{status:"complete",required:t.required,target:c,manifestDigest:y.manifestDigest,reused:!0,bundleBytes:ae(h),bytesUploaded:0})}const R=ae(h);u.cpSync(h,d,{recursive:!0,preserveTimestamps:!0});const p=N(d,s);if(!p.ok)throw new Error(p.errors.join("; "));return u.renameSync(d,c),a(e,s,{status:"complete",required:t.required,target:c,manifestDigest:p.manifestDigest,reused:!1,bundleBytes:R,bytesUploaded:R})}catch(h){return u.rmSync(d,{recursive:!0,force:!0}),a(e,s,{status:"failed",required:t.required,target:c,error:$(h),bytesUploaded:0})}}export function applyRunBundleRetention(e,n,t={},r={}){const s=A(n),a=r.removeObject??pe,o=r.removeRunBundle??Ze,i=t.maxRuns??null,m=t.maxAgeDays??null,c=new Set(t.preserveSuites||[]),d=G(e),h=new Set([s]);for(const l of d)Xe(L(l.artifact),c)&&h.add(l.runId);const R=new Set(i?d.slice(0,i).map(l=>l.runId):d.map(l=>l.runId)),p=m==null?null:Date.now()-m*24*60*60*1e3,g=[];for(const l of d){if(h.has(l.runId))continue;const S=p!=null&&Date.parse(l.finishedAt||l.startedAt||"")<p,b=!R.has(l.runId);if(!S&&!b)continue;const O=[];S&&O.push("max_age"),b&&O.push("max_runs"),g.push({path:f.join(Y(e),Ce,l.runId),reasons:O,runId:l.runId})}const y=he(e),v=[],x=[];for(const l of g){let S=null;try{o(l.path)}catch(O){S=Z(O)}const b=!u.existsSync(l.path);!b&&S===null&&(S="Run bundle still exists after removal completed"),b&&v.push({reasons:l.reasons,runId:l.runId}),S!==null&&x.push({...l,error:S,removed:b})}const B=new Set(v.map(l=>l.runId)),j=me(e,y,a,d.filter(l=>!B.has(l.runId))),w=new Date().toISOString(),E=[...x.map(l=>`remove ${l.runId}: ${l.error}`),...j.failures.map(l=>`garbage collect ${l.path}: ${l.error}`)];return{currentRunId:s,decidedAt:w,garbageCollection:j,policy:{maxRuns:i,maxAgeDays:m,preserveSuites:[...c].sort()},protectedRunIds:[...h].sort(),removed:v,removalErrors:x,schemaVersion:Oe,status:E.length===0?"complete":"failed",...E.length===0?{}:{error:E.join("; ")}}}function qe({recordRefusals:e,resultsDir:n,tempDir:t,redaction:r,rewriteLocalPaths:s}){const a=new Map;let o=0,i=0,m=0,c=0,d=0;const h=[],R=[],p=f.join(n,oe,"sha256");u.mkdirSync(p,{recursive:!0});for(const g of X){const y=f.join(n,g);if(u.existsSync(y))for(const v of ce(y)){const x=f.relative(y,v),B=u.readFileSync(v);let j;try{j=et(B,v,r.targets,s)}catch(b){if(!(b instanceof P))throw b;R.push(v),h.push(`Refusing to archive ${b.message}, and removed it: ${v}`);continue}m+=j.scanned,j.replacements>0&&(c+=1,d+=j.replacements);const w=j.buffer,E=k(w);w!==B&&a.set(`.testkit/results/${g}/${U(x)}`,{sha256:E,sizeBytes:w.length});const l=f.join(p,E);u.existsSync(l)?i+=w.length:(De(l,w),u.chmodSync(l,256),o+=w.length);const S=f.join(t,"files",g,x);u.mkdirSync(f.dirname(S),{recursive:!0}),Se(S);try{u.linkSync(l,S)}catch{u.copyFileSync(l,S)}}}if(h.length>0){const g=W(h.join(`
|
|
2
|
+
`),r.targets).text;e?.(g);for(const y of R)u.rmSync(y,{force:!0});throw new RefusedArtifactsError(g)}return{changedPayloads:a,objectBytesWritten:o,deduplicatedBytes:i,redaction:{policyVersion:1,status:r.status,textualFilesScanned:m,filesRedacted:c,replacements:d,sensitiveEnvironmentKeys:r.keys,valuesStored:!1}}}function Le(e,n){const t=r=>F(r,"suites",s=>F(s,"files",a=>F(a,"artifacts",o=>{const i=typeof o.path=="string"?n.get(o.path):void 0;if(i===void 0||!I(o.metadata))return o;const m={...o.metadata};return Object.hasOwn(m,"sha256")&&(m.sha256=i.sha256),Object.hasOwn(m,"sizeBytes")&&(m.sizeBytes=i.sizeBytes),{...o,metadata:m}})));return F(F(e,"groups",t),"services",t)}function F(e,n,t){const r=I(e)?e[n]:void 0;return!I(e)||!Array.isArray(r)?e:{...e,[n]:r.map(s=>I(s)?t(s):s)}}function de(e,n,t){const r=t(e.text);if(r===e.text)return e;const s=W(r,n);return{replacements:e.replacements+s.replacements,text:s.text}}function We(e,n,t,r){const s=new Map(X.map(c=>[`.testkit/results/${c}/`,`${z(n,`files/${c}`)}/`])),a=M(e,c=>{for(const[d,h]of s)if(c.startsWith(d))return`${h}${c.slice(d.length)}`;return c}),o=Ie(Je(a),t);let i=o.replacements;return{artifact:M(o.value,c=>{const d=de({replacements:0,text:c},t,r);return i+=d.replacements,d.text}),replacements:i}}function Je(e){if(!I(e))return e;const n={...e};if(I(n.product)){const t={...n.product};delete t.directory,n.product=t}if(I(n.git)){const t={...n.git};delete t.remoteUrl,delete t.repoRoot,delete t.worktreePath,n.git=t}return delete n.host,n}function M(e,n){return Array.isArray(e)?e.map(t=>M(t,n)):I(e)?Object.fromEntries(Object.entries(e).map(([t,r])=>[t,M(r,n)])):typeof e=="string"?n(e):e}function He(e,n,t,r){const s=H(n.run.fileNames),a=[...new Set([...Ne,...s])].map(o=>ot(e,o)).filter(o=>o!==null);return{schemaVersion:Pe,runId:t,ci:Ve(r.env??process.env),source:{git:n.git??null,files:a},runtime:{controller:{testkitVersion:te(n.run.testkitVersion),node:process.versions.node,platform:process.platform,architecture:process.arch},runtimeStats:n.schemaVersion===19?Te(n.run.runtimeStats):[...n.environment?.capacity?.runtimeStats??[]]},scenario:{types:H(n.run.types),suiteSelectors:H(n.run.suiteSelectors),fileNames:s,serviceFilter:te(n.schemaVersion===19?n.run.serviceFilter:n.run.groupFilter),planningDigest:k(Buffer.from(D(n.planning??null))),declaredEnvironmentKeys:[...new Set(r.declaredEnvironmentKeys??[])].sort()},redaction:r.redaction}}function Ke(e){return ce(e).map(n=>({path:U(f.relative(e,n)),bytes:u.statSync(n).size,sha256:J(n)})).sort((n,t)=>n.path.localeCompare(t.path))}function N(e,n){const t=f.join(e,"manifest.json"),r=q(C(t),t,n);ne(C(f.join(e,"run.json")),f.join(e,"run.json"),n);const s=[];for(const o of r.files){let i;try{i=ie(e,o.path)}catch(c){s.push($(c));continue}if(!u.existsSync(i)){s.push(`missing ${o.path}`);continue}const m=u.lstatSync(i);if(m.isSymbolicLink()){s.push(`symbolic link not allowed ${o.path}`);continue}if(!m.isFile()){s.push(`not a regular file ${o.path}`);continue}m.size!==o.bytes&&s.push(`size mismatch ${o.path}`),J(i)!==o.sha256&&s.push(`checksum mismatch ${o.path}`)}k(Buffer.from(D(r.files)))!==r.digest&&s.push("manifest digest mismatch"),r.storage.fileCount!==r.files.length&&s.push("manifest file count mismatch");for(const o of["run.json","provenance.json"])r.files.some(i=>i.path===o)||s.push(`manifest must include ${o}`);if(s.length===0){const o=ie(e,"provenance.json");we(C(o),o,n)}return{runId:n,checkedFiles:r.files.length,storage:r.storage,ok:s.length===0,errors:s,manifestDigest:r.digest}}function Ye(e,n,t){const r={schemaVersion:1,runId:n,recordedAt:new Date().toISOString(),...t};return V(f.join(Y(e),"publications",`${n}.json`),r),r}function Xe(e,n){return n.size===0?!1:e.groups.some(t=>t.suites.some(r=>{const s=r.testType??"";return[r.name,`${s}:${r.name}`,`${t.name}/${s}:${r.name}`].some(o=>n.has(o))}))}function Ge(e){return me(e,he(e),pe,G(e))}function me(e,n,t,r){const s=_(e,!1),a=new Map(r.map(c=>[c.runId,c.manifest])),o=new Set,i=[],m=u.existsSync(s)?u.readdirSync(s,{withFileTypes:!0}):[];for(const c of m)if(!(!c.isDirectory()||c.name.startsWith(".")))try{for(const d of(a.get(c.name)??loadRunBundleManifest(e,c.name)).files)d.path.startsWith("files/")&&o.add(d.sha256)}catch(d){i.push({error:`No stored object was removed: ${$(d)}`,path:f.join(s,c.name)})}return i.length>0?{failures:i,reclaimedBytes:0,removedObjects:0,status:"not_started"}:Qe(n,o,t)}function he(e){const n=f.join(Y(e),oe,"sha256");return u.existsSync(n)?u.readdirSync(n,{withFileTypes:!0}).filter(t=>t.isFile()).map(t=>{const r=f.join(n,t.name);return{bytes:u.statSync(r).size,digest:t.name,path:r}}):[]}function Qe(e,n,t){const r=[];let s=0,a=0;for(const o of e){if(n.has(o.digest))continue;let i=null;try{t(o.path)}catch(c){i=Z(c)}!u.existsSync(o.path)?(s+=1,a+=o.bytes):i===null&&(i="Object still exists after removal completed"),i!==null&&r.push({error:i,path:o.path})}return{failures:r,reclaimedBytes:a,removedObjects:s,status:r.length===0?"complete":"failed"}}function Ze(e){u.rmSync(e,{recursive:!0,force:!0})}function pe(e){u.rmSync(e,{force:!0})}function et(e,n,t,r){const s=a=>de(W(a,t),t,r);if(ge(e,n))return{...ye(e,"utf8",s),scanned:1};if(re(e,n))return tt(e,n,t,s);if(Q(e,t))throw new P("binary file containing sensitive environment value");return{buffer:e,replacements:0,scanned:0}}function tt(e,n,t,r){let s=0,a=0,o;try{o=$e(e,n,i=>{if(re(i.content,i.name))throw new P(`compressed file whose entry ${i.name} is itself an archive`);const m=nt(i.content,i.name);if(m===null){if(!Q(i.content,t))return i.content;throw new P(`compressed file whose binary entry ${i.name} contains sensitive environment value`)}const c=ye(i.content,m,r);return a+=1,s+=c.replacements,c.buffer})}catch(i){throw i instanceof ke?new P(`compressed file that Testkit can't read, because ${i.message}`):i}if(Q(o,t))throw new P("compressed file containing sensitive environment value outside its entries' text");return{buffer:o,replacements:s,scanned:a}}function nt(e,n){const t=e.subarray(0,2);return e.length%2===0&&t.equals(ue)?"utf16le":e.length%2===0&&t.equals(le)?"utf16be":!ge(e,n)||!be(e)||e.some(r=>r===127||r<32&&!Me.has(r))?null:"utf8"}function ye(e,n,t){const r=rt(e,n),s=t(r);return{buffer:s.text===r?e:st(s.text,n),replacements:s.replacements}}function rt(e,n){if(n==="utf8")return e.toString("utf8");const t=Buffer.from(e.subarray(2));return(n==="utf16be"?t.swap16():t).toString("utf16le")}function st(e,n){if(n==="utf8")return Buffer.from(e);const t=Buffer.from(e,"utf16le");return n==="utf16be"?Buffer.concat([le,t.swap16()]):Buffer.concat([ue,t])}function Q(e,n){return n.some(t=>{const r=Buffer.from(t,"utf16le");return[Buffer.from(t),r,Buffer.from(r).swap16()].some(s=>e.includes(s))})}function ge(e,n){if(e.includes(0))return!1;const t=f.extname(n).toLowerCase();return![".zip",".gz",".png",".jpg",".jpeg",".webm",".pdf"].includes(t)}function ot(e,n){const t=f.resolve(e,n);return!t.startsWith(`${f.resolve(e)}${f.sep}`)||!u.existsSync(t)||!u.statSync(t).isFile()?null:{path:U(f.relative(e,t)),bytes:u.statSync(t).size,sha256:J(t)}}function ve(e){const n=[];for(const t of[...X.map(r=>f.join(e,r)),f.join(e,"live.json")])try{u.rmSync(t,{recursive:!0,force:!0})}catch(r){n.push(`${t}: ${Z(r)}`)}return n.length===0?{status:"complete"}:{error:n.join("; "),status:"failed"}}function Z(e){try{return $(e)}catch{return"Unknown post-seal archive operation failure"}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{formatError as
|
|
1
|
+
import{formatError as f}from"../kernel/error-serialization.js";import{jsonRedactionTargets as q,orderRedactionTargets as C,sensitiveKeyMatcher as I}from"../kernel/redaction-targets.js";import{redactUrlSecrets as b}from"../process/url-redaction.js";import{redactedRunValue as K}from"./redaction.js";export function publishEnvironmentRecord(l){return{...l,acquisitions:l.acquisitions.map(o=>({...o,settledAt:E(o.settledAt),setups:o.setups.map(p=>({...p,settledAt:E(p.settledAt),startedAt:new Date(p.startedAt).toISOString()})),startedAt:new Date(o.startedAt).toISOString()})),dbBackend:l.groups.some(o=>o.dbBackend==="local")?"local":null}}function E(l){return l===null?null:new Date(l).toISOString()}export function createRunEnvironmentRecord(l={}){const o=[],p=[],S=[],R=new Set,k=I(l.redactKeys);let y=[],v={instanceCount:0,resourceBudgets:[],runtimeStats:[]},w=null;const m=l.now??Date.now,g=()=>{l.onPublishedChange?.()},O=(e,r)=>({releaseFailed(s){for(const n of e)n.releaseErrors.push(f(s)),n.failingReleases.add(r),n.releaseReported=!0},released(){for(const s of e)s.failingReleases.delete(r),s.releaseReported=!0}});return{redactKeys:l.redactKeys??[],service({runtimeLabel:e,serviceName:r}){let s=null;return{recordAttempt({baseUrl:n,outcome:t,port:d,readyUrl:a}){const c={baseUrl:n===null?null:b(n),readyUrl:a===null?null:b(a)};s===null&&(s={serviceName:r,runtimeLabel:e,...c,port:d,attempts:[]},o.push(s)),Object.assign(s,c,{port:d}),s.attempts.push({port:d,outcome:t}),g()},amendLastOutcome(n){const t=s?.attempts.at(-1);t&&(t.outcome=n,g())}}},acquisition({graphKey:e,leaseId:r,source:s,tasks:n}){const t={source:s,graphKey:e,leaseId:r,runtimeId:null,services:[],status:"pending",startedAt:m(),settledAt:null,error:null,releaseErrors:[],failingReleases:new Set,releaseReported:!1,setups:[],tasks:n.map(a=>({...a})),values:null};p.push(t);const d=({runtimeId:a,services:c})=>{t.runtimeId=a,t.services=c.map(i=>({...i}))};return{...O([t],"source"),assigned:d,acquired(a){d(a),t.status="acquired",t.settledAt=m()},cancelled(a){t.status="cancelled",t.error=f(a),t.settledAt??=m()},failed(a){t.status="failed",t.error=f(a),t.settledAt??=m()},rendered(a){t.values=a,g()},setup({module:a,task:c}){const i={error:null,module:a,releaseErrors:[],releaseReported:!1,settledAt:null,startedAt:m(),status:"pending",task:{...c}};t.setups.push(i);const h=(u,A)=>{i.status=u,i.error=A===null?null:f(A),i.settledAt??=m()};return{prepared(u){h("prepared",null);for(const A of q("setupData",u,k))R.add(A);t.values!==null&&(t.values={...t.values,setupData:u}),g()},failed(u){h("failed",u)},cancelled(u){h("cancelled",u)},releaseFailed(u){i.releaseErrors.push(f(u)),i.releaseReported=!0},released(){i.releaseReported=!0}}}}},releaseFor(e){const r=p.filter(s=>s.leaseId===e);return r.length===0?null:O(r,"lease")},recordCapacity({instanceCount:e,resourceBudgets:r}){v={...v,instanceCount:e,resourceBudgets:[...r]},g()},recordRuntimeStats(e){v={...v,runtimeStats:[...e]},g()},recordGroups(e){y=e.map(r=>({...r}))},recordMissingValues(e,r){for(const{kind:s,valueName:n}of r)S.push({kind:s,task:{...e},valueName:n})},recordCleanup(e){w=e==null?{status:"released"}:{status:"failed",errors:[f(e)],recovered:!1}},addRedactionTarget(e){e.length>0&&R.add(e)},redactionTargets(){return[...R]},snapshot(){const e={acquisitions:p.map(({failingReleases:r,releaseErrors:s,releaseReported:n,...t})=>({...t,setups:t.setups.map(({releaseErrors:d,releaseReported:a,...c})=>({...c,release:d.length>0?{status:"failed",errors:d,recovered:!1}:a?{status:"released"}:null})),release:s.length>0?{status:"failed",errors:s,recovered:r.size===0}:n?{status:"released"}:null})),capacity:v,cleanup:w,groups:y,logs:{services:l.logRegistry?.listServiceLogs()??[],setup:l.logRegistry?.listSetupLogs()??[]},missingValues:S,services:[...o].sort((r,s)=>r.runtimeLabel.localeCompare(s.runtimeLabel)||r.serviceName.localeCompare(s.serviceName)),setupOperations:l.setupRegistry?.listOperations()??[]};return K(e,C(R))}}}
|
package/dist/evidence/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{normalizeArtifactArchiveConfig}from"./artifact-config.js";export{loadTimings,persistTaskArtifacts,
|
|
1
|
+
export{normalizeArtifactArchiveConfig}from"./artifact-config.js";export{loadTimings,persistTaskArtifacts,persistTaskOutputArtifacts,saveTimings}from"./artifacts.js";export{listRunBundles,loadRunBundleArtifact,loadRunBundleModels,loadRunBundleManifest,verifyRunBundle}from"./bundles.js";export{collectArtifactEntries,loadCurrentRunArtifact,loadLatestRunArtifact,loadRun}from"./reading.js";export{}from"./reporting-types.js";export{runModel}from"./run-model.js";export{RUN_INPUTS_VERSION}from"./run-inputs.js";export{MAX_PERSISTED_FILE_ARTIFACT_BYTES}from"./secure-file-copy.js";export{collectTestkitStorageInventory}from"./storage-inventory.js";export{}from"./timing.js";
|
package/dist/evidence/logs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import p from"fs";import u from"path";import{posixPath as b}from"../process/files/paths.js";const S="logs",N="setup";export function createRunLogRegistry(
|
|
2
|
-
`)},listServiceLogs(){return[...m.values()].map(e=>({serviceName:e.serviceName,runtimeLabel:e.runtimeLabel,path:e.path})).sort((e,t)=>e.serviceName.localeCompare(t.serviceName)||e.runtimeLabel.localeCompare(t.runtimeLabel))},listSetupLogs(){return[...s.values()].map(e=>({serviceName:e.serviceName,runtimeLabel:e.runtimeLabel,stage:e.stage,path:e.path})).sort((e,t)=>e.serviceName.localeCompare(t.serviceName)||e.runtimeLabel.localeCompare(t.runtimeLabel)||e.stage.localeCompare(t.stage))},async closeAll(){await Promise.all([...n].map(e=>$(e)))}}}function
|
|
1
|
+
import p from"fs";import u from"path";import{posixPath as b}from"../process/files/paths.js";import{sanitizeSegment as l}from"./storage.js";const S="logs",N="setup";export function createRunLogRegistry(a){const m=new Map,s=new Map,n=new Set;function L(e){const t=u.join(a,e.path);p.mkdirSync(u.dirname(t),{recursive:!0});const r=p.createWriteStream(t,{fd:p.openSync(t,"a"),flags:"a"});return n.add(r),{...e,write(o){r.write(o)}}}return{ensureServiceLogRecord(e){const t=`${e.runtimeLabel||e.name}:${e.name}`,r=m.get(t);if(r)return r;const o=`${l(e.runtimeLabel||e.name,"log")}__${l(e.name,"log")}.log`,c=u.join(".testkit","results",S,o),i=L({serviceName:e.name,runtimeLabel:e.runtimeLabel||e.name,path:b(c)});return m.set(t,i),i},ensureSetupLogRecord(e,t){const r=`${e.runtimeLabel||e.name}:${e.name}:${t}`,o=s.get(r);if(o)return o;const c=`${l(e.runtimeLabel||e.name,"log")}__${l(e.name,"log")}__${l(t,"log")}.log`,i=u.join(".testkit","results",N,c),v=L({serviceName:e.name,runtimeLabel:e.runtimeLabel||e.name,stage:t,path:b(i)});return s.set(r,v),v},append(e,t,r){!e||typeof r!="string"||e.write(`${new Date().toISOString()} [${t}] ${r}
|
|
2
|
+
`)},listServiceLogs(){return[...m.values()].map(e=>({serviceName:e.serviceName,runtimeLabel:e.runtimeLabel,path:e.path})).sort((e,t)=>e.serviceName.localeCompare(t.serviceName)||e.runtimeLabel.localeCompare(t.runtimeLabel))},listSetupLogs(){return[...s.values()].map(e=>({serviceName:e.serviceName,runtimeLabel:e.runtimeLabel,stage:e.stage,path:e.path})).sort((e,t)=>e.serviceName.localeCompare(t.serviceName)||e.runtimeLabel.localeCompare(t.runtimeLabel)||e.stage.localeCompare(t.stage))},async closeAll(){await Promise.all([...n].map(e=>$(e)))}}}function $(a){return!a||a.destroyed||a.closed?Promise.resolve():new Promise(m=>{let s=!1;const n=()=>{s||(s=!0,a.off("close",n),a.off("error",n),m())};a.once("close",n),a.once("error",n),a.end()})}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{performance as
|
|
1
|
+
import{performance as A}from"node:perf_hooks";import{attachErrorOperation as I}from"../kernel/error-serialization.js";import{formatOperationSummary as $}from"../domain/operation-display.js";import{isUnknownRecord as O}from"../kernel/unknown-values.js";import{recordWork as y}from"../kernel/work-metrics.js";const l=String;export function createOperationTimingRegistry({onChange:t=null}={}){const r=[],a=new Map;let c=1;function o(e=[]){if(!t)return;const i=[],u=new Set;for(const n of e)!n||u.has(n.id)||(u.add(n.id),i.push(g(n)));y("operations.changedEntriesDelivered",i.length),t(i)}function p({name:e,kind:i="operation",source:u="testkit",summary:n=null,scope:d=null,parentId:s=null,metadata:m=null}){const f={id:`operation-${l(c++)}`,source:u,name:k(e),kind:i,summary:n,scope:h(d),parentId:s,status:"running",startedAt:new Date().toISOString(),updatedAt:null,finishedAt:null,durationMs:null,error:null,message:null,logRefs:[],metadata:h(m),_startedMonotonicMs:A.now()};return r.push(f),a.set(f.id,f),o([f,...w(f)]),f}function M(e,{status:i="passed",summary:u,error:n=null,metadata:d=null}={}){const s=typeof e=="string"?a.get(e):e;return s?(s.finishedAt=new Date().toISOString(),s.updatedAt=s.finishedAt,s.durationMs=Math.max(0,Math.round(A.now()-s._startedMonotonicMs)),s.status=i,u!==void 0&&(s.summary=u),n!==null&&(s.error=l(n)),d!==null&&(s.metadata={...s.metadata,...h(d)}),o([s,...w(s)]),g(s)):null}function S(){return y("operations.historyClones"),r.map(e=>g(e))}return{start:p,finish:M,listOperations:S};function w(e){if(!e.parentId)return[];const i=[],u=new Date().toISOString();let n=a.get(e.parentId);const d=new Set([e.id]);for(;n&&n.status==="running"&&!d.has(n.id);){d.add(n.id),n.updatedAt=u;const s=e.status||"unknown",m=s==="running"?"activeChild":"lastChild";n.message=x(e),n.metadata={...n.metadata,[`${m}OperationId`]:e.id,[`${m}OperationName`]:e.name,[`${m}OperationKind`]:e.kind,[`${m}OperationStatus`]:s,[`${m}OperationSummary`]:e.summary||""},i.push(n),n=n.parentId?a.get(n.parentId):void 0}return i}}export async function timeOperation(t,r,a){if(!t)return a();const c=t.start(r);try{const o=await a(c);return t.finish(c,{status:"passed"}),o}catch(o){const p=t.finish(c,{status:"failed",error:C(o)});throw I(o,p),o}}function g(t){return{id:t.id,source:t.source,name:t.name,kind:t.kind,summary:t.summary,scope:t.scope,parentId:t.parentId,status:t.status,startedAt:t.startedAt,updatedAt:t.updatedAt,finishedAt:t.finishedAt,durationMs:t.durationMs,error:t.error,message:t.message,logRefs:R(t.logRefs),metadata:t.metadata}}function k(t){const r=l(t||"").trim();if(!r)throw new Error("operation timing name is required");return r}function h(t){return O(t)?{...t}:{}}function R(t){return Array.isArray(t)?t.map(r=>O(r)?{...r}:r):[]}function x(t){const r=$(t),a=typeof r=="string"?r:"";return a?`${t.name} ${t.status||"unknown"} - ${a}`:`${t.name} ${t.status||"unknown"}`}function C(t){return(typeof t!="object"||t===null)&&typeof t!="function"?l(t):Reflect.get(t,"message")||l(t)}
|