@gfdelarue/testkit 0.3.2 → 0.3.3
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/bin/testkit-windows-owner-arm64.exe +0 -0
- package/bin/testkit-windows-owner-x64.exe +0 -0
- package/dist/app/cloud/control.js +2 -2
- package/dist/app/cloud/login.d.ts +4 -4
- package/dist/app/inspect/artifacts.d.ts +4 -4
- package/dist/app/run/engine-failure-stack.d.ts +1 -0
- package/dist/app/run/engine-failure-stack.js +2 -0
- package/dist/app/run/worker-loop.js +1 -1
- package/dist/app/setup/playwright.js +1 -1
- package/dist/app/typecheck/index.js +4 -4
- package/dist/cli/command-catalog.d.ts +24 -24
- package/dist/cli/command-flags.d.ts +3 -3
- package/dist/cli/commands/artifacts.d.ts +16 -16
- package/dist/cli/commands/cleanup.d.ts +1 -1
- package/dist/cli/commands/cloud/login.d.ts +1 -1
- package/dist/cli/commands/cloud/status.d.ts +3 -3
- package/dist/config/loader/discover-tests.d.ts +5 -0
- package/dist/config/loader/discover-tests.js +1 -1
- package/dist/config/loader/file-metadata.d.ts +1 -1
- package/dist/config/loader/file-metadata.js +1 -1
- package/dist/config/loader/index.d.ts +1 -1
- package/dist/config/loader/index.js +1 -1
- package/dist/database/docker-metadata.d.ts +3 -0
- package/dist/database/docker-metadata.js +3 -0
- package/dist/database/docker.js +5 -5
- package/dist/database/index.js +1 -1
- package/dist/database/local-postgres.js +1 -1
- package/dist/database/postgres/index.js +2 -2
- package/dist/database/runtime-config.d.ts +5 -5
- package/dist/database/schema-source.d.ts +4 -1
- package/dist/database/schema-source.js +3 -3
- package/dist/database/template-steps.d.ts +4 -0
- package/dist/database/template-steps.js +4 -4
- package/dist/engines/managed-module-resolution.js +5 -3
- package/dist/engines/node-vitest/adapter/consumer-typescript-paths.js +2 -2
- package/dist/engines/node-vitest/runtime/runtime-input.js +1 -1
- package/dist/engines/playwright/runtime/runtime-input.js +1 -1
- package/dist/environment/template.d.ts +20 -20
- package/dist/process/execa-bundle-licenses.txt +411 -0
- package/dist/process/execa-bundle.json +874 -0
- package/dist/process/files/json-file.js +1 -1
- package/dist/process/host-process.d.ts +6 -1
- package/dist/process/host-process.js +1 -1
- package/dist/process/index.d.ts +2 -2
- package/dist/process/locks.js +3 -3
- package/dist/process/process-contracts.d.ts +10 -2
- package/dist/process/process-identity.js +1 -1
- package/dist/process/process.d.ts +3 -2
- package/dist/process/process.js +46 -1
- package/dist/repo/metadata.js +1 -1
- package/dist/repo/state.d.ts +1 -0
- package/dist/repo/state.js +4 -4
- package/dist/steps/configured-step-testkit-modules.d.ts +1 -1
- package/dist/steps/run.js +2 -2
- package/dist/toolchains/archive.js +1 -1
- package/dist/toolchains/node-integrity.d.ts +2 -0
- package/dist/toolchains/node-integrity.js +1 -1
- package/dist/toolchains/node-provisioning.js +2 -2
- package/package.json +4 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
import s from"node:fs";import
|
|
1
|
+
import s from"node:fs";import c from"node:path";const p=String;export function isPropertyContainer(t){return typeof t=="object"&&t!==null||typeof t=="function"}export function readProperty(t,r){return Reflect.get(t,r)}export function serializeJson(t,r){let e;try{e=JSON.stringify(t,null,2)}catch(o){throw new Error(`${r} is not JSON serializable`,{cause:o})}if(typeof e!="string")throw new Error(`${r} is not JSON serializable`);return e}export function errorMessage(t){if(t instanceof Error)return t.message;if(isPropertyContainer(t)){const r=readProperty(t,"message");if(typeof r=="string"&&r.length>0)return r}return p(t)}const y=new Set(["EPERM","EACCES","EBUSY"]),u=process.platform==="win32"?100:25,l=10,S=100;class A extends Error{attempts;code="TESTKIT_ATOMIC_WRITE_FAILED";cause;path;tempPath;constructor(r,e,o,n){super(`Failed to atomically replace ${r} after ${o.toString()} attempt${o===1?"":"s"}: ${errorMessage(n)}`,{cause:n}),this.name="AtomicWriteError",this.attempts=o,this.cause=n,this.path=r,this.tempPath=e}}export function writeJsonFileAtomicWithRetry(t,r,e={}){const o=c.dirname(t),n=c.join(o,`.${c.basename(t)}.${process.pid.toString()}.${Date.now().toString()}.${Math.random().toString(16).slice(2)}.tmp`),a=e.attempts??u,E=e.baseDelayMs??l,m=e.maxDelayMs??S;s.writeFileSync(n,serializeJson(r,`atomic JSON file ${t}`));try{for(let i=1;i<=a;i+=1)try{s.renameSync(n,t);return}catch(f){if(!isRetryableAtomicReplaceError(f)||i>=a||R(t))throw new A(t,n,i,f);_(Math.min(m,E*i))}}finally{s.rmSync(n,{force:!0})}}export function isRetryableAtomicReplaceError(t){if(!isPropertyContainer(t))return!1;const r=readProperty(t,"code");return typeof r=="string"&&y.has(r)}function R(t){try{return s.lstatSync(t).isDirectory()}catch{return!1}}function _(t){if(t<=0)return;const r=new SharedArrayBuffer(4);Atomics.wait(new Int32Array(r),0,0,t)}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { type ManagedProcessRequest, type ManagedProcessResult, type SupervisedProcessHandle, type SupervisedProcessOutputMode } from "./process.js";
|
|
1
|
+
import { type ManagedProcessRequest, type ManagedProcessResult, type SupervisedProcessHandle, type SupervisedProcessLiveHandle, type SupervisedProcessOutputMode } from "./process.js";
|
|
2
2
|
export interface ManagedHostProcessRequest extends Omit<ManagedProcessRequest, "env" | "signal"> {
|
|
3
3
|
readonly env?: Readonly<NodeJS.ProcessEnv>;
|
|
4
4
|
readonly signal?: AbortSignal;
|
|
5
5
|
}
|
|
6
6
|
export interface SupervisedHostProcessRequest extends ManagedHostProcessRequest {
|
|
7
7
|
readonly outputMode: SupervisedProcessOutputMode;
|
|
8
|
+
readonly recoveryIdentity?: "capture";
|
|
9
|
+
}
|
|
10
|
+
export interface SupervisedHostProcessWithoutRecoveryRequest extends Omit<SupervisedHostProcessRequest, "recoveryIdentity"> {
|
|
11
|
+
readonly recoveryIdentity: "none";
|
|
8
12
|
}
|
|
9
13
|
export declare function runManagedHostProcess(request: Readonly<ManagedHostProcessRequest>): Promise<ManagedProcessResult>;
|
|
14
|
+
export declare function startManagedHostProcess(request: Readonly<SupervisedHostProcessWithoutRecoveryRequest>): SupervisedProcessLiveHandle;
|
|
10
15
|
export declare function startManagedHostProcess(request: Readonly<SupervisedHostProcessRequest>): SupervisedProcessHandle;
|
|
11
16
|
export declare function managedHostProcessInfrastructureError(displayLabel: string, result: Readonly<ManagedProcessResult>): Error | null;
|
|
12
17
|
export declare function normalizeManagedHostEnvironment(environment?: Readonly<NodeJS.ProcessEnv>, platform?: NodeJS.Platform): Record<string, string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import l from"node:fs";import s from"node:path";import{runManagedProcess as E,startSupervisedProcess as f}from"./process.js";export function runManagedHostProcess(e){const t=m(e);return E(t)}export function startManagedHostProcess(e){const t=m(e);return e.recoveryIdentity==="none"?f({...t,outputMode:e.outputMode,recoveryIdentity:"none"}):f({...t,outputMode:e.outputMode})}export function managedHostProcessInfrastructureError(e,t){const o=[];if(t.terminationError&&o.push(`process-scope termination was incomplete: ${t.terminationError.message}`),t.error&&o.push(`could not execute: ${t.error.message}`),t.timedOut&&o.push("exceeded its managed deadline"),t.cancelled&&o.push("was cancelled"),t.outputLimitExceeded&&o.push("exceeded the managed stdout/stderr capture limit"),o.length===0)return null;const r=new Error(`${e} ${o.join("; ")}`);return Object.defineProperty(r,"result",{enumerable:!1,value:Object.freeze({...t})}),r}export function normalizeManagedHostEnvironment(e=process.env,t=process.platform){const o=Object.entries(e).filter(n=>typeof n[1]=="string");if(t!=="win32")return Object.fromEntries(o);const r=new Map;for(const[n,a]of o){const c=n.toLowerCase();r.set(c,{key:y(c,n),value:a})}return Object.fromEntries([...r.entries()].sort(([n],[a])=>n.localeCompare(a)).map(([,n])=>[n.key,n.value]))}export function resolveManagedHostExecutable(e,t,o,r=process.platform){const n=resolveManagedHostExecutableIfAvailable(e,t,o,r);if(n)return n;throw o.PATH?new Error(`Could not resolve managed host executable ${JSON.stringify(e)}`):new Error(`Could not resolve managed host executable ${JSON.stringify(e)} because PATH is unavailable`)}export function resolveManagedHostExecutableIfAvailable(e,t,o,r=process.platform){if(e.trim().length===0)throw new Error("Managed host process executable must be non-empty");if(!s.isAbsolute(t))throw new Error("Managed host process working directory must be absolute");if(s.isAbsolute(e))return p(e,r);if(e.includes("/")||e.includes("\\"))throw new Error(`Managed host process executable paths must be absolute, got ${JSON.stringify(e)}`);const n=o.PATH;if(!n)return null;const a=b(e,o,r),c=r==="win32"?";":":";for(const u of n.split(c)){if(u.length===0)continue;const g=s.isAbsolute(u)?u:s.resolve(t,u);for(const h of a){const d=s.join(g,`${e}${h}`);try{const i=l.statSync(d,{throwIfNoEntry:!1});if(i!==void 0&&i.isFile()&&(r==="win32"||(i.mode&73)!==0))return p(d,r)}catch(i){if(!w(i,"ENOENT")&&!w(i,"ENOTDIR"))throw i}}}return null}function m(e){const t=s.resolve(e.cwd),o=normalizeManagedHostEnvironment(e.env);return{args:e.args,cwd:t,displayLabel:e.displayLabel,env:o,executable:resolveManagedHostExecutable(e.executable,t,o),signal:e.signal??new AbortController().signal,timeoutMs:e.timeoutMs,windowsVerbatimArguments:e.windowsVerbatimArguments}}function b(e,t,o){return o!=="win32"?[""]:s.extname(e).length>0?[""]:(t.PATHEXT||".COM;.EXE").split(";").map(n=>n.trim()).filter(n=>[".com",".exe"].includes(n.toLowerCase()))}function p(e,t){if(t==="win32"&&[".bat",".cmd"].includes(s.extname(e).toLowerCase()))throw new Error(`Windows batch wrappers are not managed host executables: ${e}`);const o=l.statSync(e);if(!o.isFile())throw new Error(`Managed host executable is not a regular file: ${e}`);if(t!=="win32"&&(o.mode&73)===0)throw new Error(`Managed host executable is not executable: ${e}`);return e}function y(e,t){return e==="path"?"PATH":e==="pathext"?"PATHEXT":e==="comspec"?"ComSpec":t}function w(e,t){return e!==null&&typeof e=="object"&&"code"in e&&Reflect.get(e,"code")===t}
|
package/dist/process/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { runManagedProcess, ProcessExitedBeforeReadyError, startSupervisedProcess, terminateAttributedProcessLineage, terminateOwnedPosixProcessGroup, type AttributedProcessLineageTerminationRequest, type ManagedProcessCancellationSignal, type ManagedProcessRequest, type ManagedProcessResult, type OwnedPosixProcessGroupTerminationRequest, type SupervisedProcessHandle, type SupervisedProcessOutputMode, type SupervisedProcessRecoveryIdentity, type SupervisedProcessReady, type SupervisedProcessRequest, } from "./process.js";
|
|
2
|
-
export { managedHostProcessInfrastructureError, normalizeManagedHostEnvironment, resolveManagedHostExecutable, resolveManagedHostExecutableIfAvailable, runManagedHostProcess, startManagedHostProcess, type ManagedHostProcessRequest, type SupervisedHostProcessRequest, } from "./host-process.js";
|
|
1
|
+
export { runManagedProcess, ProcessExitedBeforeReadyError, startSupervisedProcess, terminateAttributedProcessLineage, terminateOwnedPosixProcessGroup, type AttributedProcessLineageTerminationRequest, type ManagedProcessCancellationSignal, type ManagedProcessRequest, type ManagedProcessResult, type OwnedPosixProcessGroupTerminationRequest, type SupervisedProcessHandle, type SupervisedProcessLiveHandle, type SupervisedProcessWithoutRecoveryRequest, type SupervisedProcessOutputMode, type SupervisedProcessRecoveryIdentity, type SupervisedProcessReady, type SupervisedProcessRequest, } from "./process.js";
|
|
2
|
+
export { managedHostProcessInfrastructureError, normalizeManagedHostEnvironment, resolveManagedHostExecutable, resolveManagedHostExecutableIfAvailable, runManagedHostProcess, startManagedHostProcess, type ManagedHostProcessRequest, type SupervisedHostProcessRequest, type SupervisedHostProcessWithoutRecoveryRequest, } from "./host-process.js";
|
|
3
3
|
export { requireManagedHostCommandSuccess, runManagedHostCommand } from "./host-command.js";
|
|
4
4
|
export { resolvePosixCommandShell } from "./command-shell.js";
|
|
5
5
|
export { captureOutput, type CaptureOutputOptions, type OutputLineHandler } from "./capture-output.js";
|
package/dist/process/locks.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{AsyncLocalStorage as
|
|
1
|
+
import{AsyncLocalStorage as Z}from"node:async_hooks";import{randomUUID as ee}from"node:crypto";import c from"node:fs";import te from"node:os";import l from"node:path";import{setTimeout as re}from"node:timers/promises";import{captureProcessCreationIdentity as D}from"./process-identity.js";function ne(e){try{c.mkdirSync(e,{mode:448})}catch(t){if(!i(t,"EEXIST"))throw t}return j(e)}function oe(e){try{const t=c.lstatSync(e);return t.isDirectory()&&!t.isSymbolicLink()?"real":"unsafe"}catch(t){if(i(t,"ENOENT"))return"absent";throw t}}function F(e){const t=c.lstatSync(e,{bigint:!0});if(!t.isDirectory()||t.isSymbolicLink())throw new Error(`Process lock path is not a real directory: ${e}`);return{birthtimeNs:t.birthtimeNs,dev:t.dev,ino:t.ino}}function j(e){try{return F(e)}catch(t){if(i(t,"ENOENT")||ce(t))return null;throw t}}function h(e,t){const n=j(e);return n!==null&&n.birthtimeNs===t.birthtimeNs&&n.dev===t.dev&&n.ino===t.ino}function se(e,t,n){return h(e,t)?M(n)?"unsafe":"retry":h(n,t)?"published":"unsafe"}function ce(e){return e instanceof Error&&e.message.startsWith("Process lock path is not a real directory:")}const x="owner.json",R=1,A="claim-",g="choosing",K="ticket",ie=100,V=65e3,ae=100,ue=new Set(["EACCES","EBUSY","EPERM"]),le=5e3,U=te.hostname(),fe=process.platform==="win32"?le:0,T=new Z;class W extends Error{constructor(t,n){super(`Timed out waiting for process lock ${l.basename(t)}${Ce(n)}`),this.name="ProcessLockTimeoutError"}}export async function withProcessLock(e,t,n={}){const r=X(e);Oe(r);const o=await me(r,n),s=T.getStore(),m=new Set(s??[]);m.add(r);try{return await T.run(m,t)}finally{o()}}export async function tryWithProcessLock(e,t,n={}){let r;try{r=await withProcessLock(e,async()=>{try{return{ok:!0,value:await t()}}catch(o){return{error:o,ok:!1}}},{...n,timeoutMs:0})}catch(o){if(o instanceof W)return{acquired:!1};throw o}if(!r.ok)throw r.error;return{acquired:!0,value:r.value}}export function probeProcessLock(e,t={}){const n=X(e),r=B(n);if(!M(r))return"absent";const o=t.identityObservations??new Map,s=b(r,o,t);return s.rootAvailable&&s.claims.length===0?(_(r),"absent"):"active"}async function me(e,t){const n=$e(t.timeoutMs,null,"process lock timeout"),r=ve(t.retryIntervalMs,ie,"process lock retry interval"),o=t.identityObservations??new Map,s=D(process.pid);if(s===null)throw new Error(`Cannot acquire process lock ${e}: current process creation identity is unavailable`);const m=Date.now(),f={createdAt:new Date(m).toISOString(),hostname:U,pid:process.pid,processIdentity:s,schemaVersion:R,token:ee()},a=B(e),u=l.join(a,`${A}${f.token}`),w=`${a}.preparing-${f.token}`;he(w,f);let y=null,$=!1,p=null,S=!1,v=0;try{for(y=F(w);;){N(t.signal,e);const E=ne(a);if(E!==null){try{c.renameSync(w,u),S=!0,p=E}catch(d){if(!i(d,"ENOENT"))if(de(d)){const k=se(w,y,u);if(k==="published")S=!0,p=E;else if(k==="retry"&&v<ae)v+=1;else throw d}else throw d}if(S){if(!h(a,E))throw new Error(`Process lock directory changed during claim publication: ${a}`);if(!h(u,y))throw new Error(`Process lock claim changed during publication: ${u}`);break}}else b(a,o,t);z(e,null,m,n),await G(r,t.signal,e)}if(p===null)throw new Error(`Process lock claim was published without its directory identity: ${u}`);const I=p,J=b(a,o,t,I),L=Ee(J.claims);for(ye(u,L);;){N(t.signal,e);const E=b(a,o,t,I),d=E.claims.find(Q=>Q.claimDirectory===u);if(d?.state!=="ready"||d.ticket!==L||d.ownerRecord===null||!H(d.ownerRecord,f))throw new Error(`Process lock claim disappeared or changed before acquisition: ${u}`);const k=we(E.claims,d);if(k===null)return $=!0,()=>{Ie(u,a,I,f,L)};z(e,k.ownerRecord,m,n),await G(r,t.signal,e)}}finally{S?!$&&p!==null&&y!==null&&h(a,p)&&h(u,y)&&(c.rmSync(u,{force:!0,recursive:!0}),o.delete(u),_(a)):y!==null&&h(w,y)&&c.rmSync(w,{force:!0,recursive:!0})}}function de(e){return process.platform==="win32"&&C(e)&&typeof e.code=="string"&&ue.has(e.code)}function he(e,t){c.mkdirSync(e,{mode:448});try{c.writeFileSync(l.join(e,x),`${JSON.stringify(t)}
|
|
2
2
|
`,{encoding:"utf8",flag:"wx",mode:384}),c.writeFileSync(l.join(e,g),`${t.token}
|
|
3
|
-
`,{encoding:"utf8",flag:"wx",mode:384})}catch(n){throw c.rmSync(e,{force:!0,recursive:!0}),n}}function
|
|
4
|
-
`,{encoding:"utf8",flag:"wx",mode:384}),c.rmSync(l.join(e,g))}function
|
|
3
|
+
`,{encoding:"utf8",flag:"wx",mode:384})}catch(n){throw c.rmSync(e,{force:!0,recursive:!0}),n}}function ye(e,t){c.writeFileSync(l.join(e,K),`${t.toString()}
|
|
4
|
+
`,{encoding:"utf8",flag:"wx",mode:384}),c.rmSync(l.join(e,g))}function Ee(e){const t=e.reduce((n,r)=>r.ticket===null?n:Math.max(n,r.ticket),0);if(t>=Number.MAX_SAFE_INTEGER)throw new Error("Process lock ticket space is exhausted");return t+1}function we(e,t){if(t.ticket===null||t.ownerRecord===null)return t;const n=t.ownerRecord.token;for(const r of e){if(r.claimDirectory===t.claimDirectory)continue;if(r.state!=="ready"||r.ticket===null||r.ownerRecord===null)return r;const o=r.ownerRecord.token;if(pe(r.ticket,o,t.ticket,n)<0)return r}return null}function pe(e,t,n,r){return e===n?t.localeCompare(r):e-n}function b(e,t,n,r=null){const o=oe(e);if(o==="absent")return{claims:[],rootAvailable:!0};if(o==="unsafe"){const f=O(e);return Ne(e,t,n)?{claims:[],rootAvailable:!0}:{claims:[f],rootAvailable:!1}}if(r!==null&&!h(e,r))return{claims:[O(e)],rootAvailable:!1};let s;try{s=c.readdirSync(e,{withFileTypes:!0})}catch(f){if(i(f,"ENOENT"))return{claims:[],rootAvailable:!0};throw f}const m=[];for(const f of s){const a=l.join(e,f.name),u=!f.name.startsWith(A)||!f.isDirectory()?O(a):q(a);Se(u,t,n)?be(a,t):m.push(u)}return{claims:m,rootAvailable:!0}}function O(e){return{claimDirectory:e,ownerRecord:null,state:"malformed",ticket:null}}function q(e){const t=Ae(e),n=M(l.join(e,g)),r=ke(e),o=l.basename(e).slice(A.length),s=t===null||t.token===o;return{claimDirectory:e,ownerRecord:t,state:t===null||!s?"malformed":n?"choosing":r===null?"malformed":"ready",ticket:r}}function ke(e){try{const t=c.readFileSync(l.join(e,K),"utf8").trim(),n=Number(t);return Number.isSafeInteger(n)&&n>0?n:null}catch{return null}}function Se(e,t,n){const r=e.ownerRecord;if(r===null||r.hostname!==U)return n.hostScope==="current-host"&&_e(e.claimDirectory)>=V;if(Pe(r.pid)==="absent")return!0;const o=P(r),s=t.get(e.claimDirectory);if(s?.ownerKey===o&&Date.now()-s.checkedAtMs<fe)return!1;const m=D(r.pid);return t.set(e.claimDirectory,{checkedAtMs:Date.now(),ownerKey:o}),m!==null&&m!==r.processIdentity}function be(e,t){t.delete(e),c.rmSync(e,{force:!0,recursive:!0})}function Ne(e,t,n){if(n.hostScope!=="current-host")return!1;const r=Y(e);if(r==="absent")return!0;if(r==="real-directory"||Re(r)<V)return!1;const o=Y(e);if(o==="absent")return!0;if(o==="real-directory"||!Le(o,r))return!1;try{return c.unlinkSync(e),t.delete(e),!0}catch(s){if(i(s,"ENOENT"))return!0;if(i(s,"EISDIR")||i(s,"EPERM"))return!1;throw s}}function Ie(e,t,n,r,o){if(!h(t,n))throw new Error(`Process lock directory changed before release: ${t}`);const s=q(e);if(s.state!=="ready"||s.ticket!==o||s.ownerRecord===null||!H(s.ownerRecord,r))throw new Error(`Process lock ownership changed before release: ${e}`);c.rmSync(e,{force:!0,recursive:!0}),_(t)}function Y(e){try{const t=c.lstatSync(e,{bigint:!0});return t.isDirectory()&&!t.isSymbolicLink()?"real-directory":{birthtimeNs:t.birthtimeNs,ctimeNs:t.ctimeNs,dev:t.dev,ino:t.ino,mode:t.mode,mtimeNs:t.mtimeNs,size:t.size}}catch(t){if(i(t,"ENOENT"))return"absent";throw t}}function Le(e,t){return e.birthtimeNs===t.birthtimeNs&&e.ctimeNs===t.ctimeNs&&e.dev===t.dev&&e.ino===t.ino&&e.mode===t.mode&&e.mtimeNs===t.mtimeNs&&e.size===t.size}function Re(e){return Math.max(0,Date.now()-Number(e.mtimeNs)/1e6)}function _(e){try{c.rmdirSync(e)}catch(t){if(!i(t,"ENOENT")&&!i(t,"ENOTEMPTY")&&!i(t,"EEXIST"))throw t}}function Ae(e){let t;try{t=JSON.parse(c.readFileSync(l.join(e,x),"utf8"))}catch{return null}return C(t)?ge(t):null}function ge(e){const t=Object.keys(e).sort(),n=["createdAt","hostname","pid","processIdentity","schemaVersion","token"];return t.length!==n.length||t.some((r,o)=>r!==n[o])||e.schemaVersion!==R||!De(e.createdAt)||typeof e.hostname!="string"||e.hostname.length===0||!Fe(e.pid)||typeof e.processIdentity!="string"||e.processIdentity.length===0||typeof e.token!="string"||e.token.length===0?null:{createdAt:e.createdAt,hostname:e.hostname,pid:e.pid,processIdentity:e.processIdentity,schemaVersion:R,token:e.token}}function H(e,t){return P(e)===P(t)}function P(e){return[e.hostname,e.pid.toString(),e.processIdentity,e.token,e.createdAt].join("\0")}function B(e){return`${e}.dir`}function X(e){const t=l.resolve(Te(e)),n=c.realpathSync.native(l.dirname(t));return l.join(n,l.basename(t))}function Te(e){if(e.trim().length===0)throw new Error("Process lock path must not be empty");return e}function Oe(e){if(T.getStore()?.has(e))throw new Error(`Reentrant process lock acquisition is not allowed: ${e}`)}function z(e,t,n,r){if(r!==null&&Date.now()-n>=r)throw new W(e,t)}function _e(e){try{return Math.max(0,Date.now()-c.lstatSync(e).mtimeMs)}catch(t){if(i(t,"ENOENT"))return Number.POSITIVE_INFINITY;throw t}}function Pe(e){try{return process.kill(e,0),"alive"}catch(t){return i(t,"ESRCH")?"absent":i(t,"EPERM")?"alive":"unknown"}}async function G(e,t,n){N(t,n);try{await re(e,void 0,{signal:t})}catch(r){throw N(t,n),r}}function N(e,t){if(e?.aborted)throw Me(e,t)}function Me(e,t){return e.reason instanceof Error?e.reason:new Error(`Waiting for process lock was aborted: ${t}`)}function Ce(e){return e===null?"":` (host=${e.hostname} pid=${e.pid.toString()} identity=${e.processIdentity} createdAt=${e.createdAt})`}function $e(e,t,n){const r=e===void 0?t:e;if(r!==null&&(!Number.isSafeInteger(r)||r<0))throw new Error(`${n} must be null or a non-negative safe integer`);return r}function ve(e,t,n){const r=e??t;if(!Number.isSafeInteger(r)||r<=0)throw new Error(`${n} must be a positive safe integer`);return r}function De(e){return typeof e=="string"&&e.length>0&&Number.isFinite(Date.parse(e))}function Fe(e){return Number.isSafeInteger(e)&&Number(e)>0}function M(e){try{return c.lstatSync(e),!0}catch(t){if(i(t,"ENOENT"))return!1;throw t}}function i(e,t){return C(e)&&e.code===t}function C(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}
|
|
@@ -18,10 +18,15 @@ export interface ManagedProcessRequest {
|
|
|
18
18
|
}
|
|
19
19
|
export type SupervisedProcessOutputMode = "bounded-capture" | "caller-drained";
|
|
20
20
|
export interface SupervisedProcessRequest extends Omit<ManagedProcessRequest, "timeoutMs"> {
|
|
21
|
+
readonly recoveryIdentity?: "capture";
|
|
21
22
|
readonly shutdownTimeoutMs?: number;
|
|
22
23
|
readonly outputMode: SupervisedProcessOutputMode;
|
|
23
24
|
readonly timeoutMs: number | null;
|
|
24
25
|
}
|
|
26
|
+
/** Live supervision without publishing an identity for later recovery. */
|
|
27
|
+
export interface SupervisedProcessWithoutRecoveryRequest extends Omit<SupervisedProcessRequest, "recoveryIdentity"> {
|
|
28
|
+
readonly recoveryIdentity: "none";
|
|
29
|
+
}
|
|
25
30
|
export interface ManagedProcessResult {
|
|
26
31
|
readonly cancelled: boolean;
|
|
27
32
|
readonly error: Error | null;
|
|
@@ -43,9 +48,12 @@ export interface SupervisedProcessRecoveryIdentity {
|
|
|
43
48
|
readonly processGroupId: number | null;
|
|
44
49
|
}
|
|
45
50
|
/** A live child handle plus the identity needed for later best-effort recovery. */
|
|
46
|
-
export interface SupervisedProcessHandle {
|
|
47
|
-
readonly completion: Promise<ManagedProcessResult>;
|
|
51
|
+
export interface SupervisedProcessHandle extends SupervisedProcessLiveHandle {
|
|
48
52
|
readonly ready: Promise<SupervisedProcessReady>;
|
|
53
|
+
}
|
|
54
|
+
/** Controls the child through its live handle, without persisted recovery metadata. */
|
|
55
|
+
export interface SupervisedProcessLiveHandle {
|
|
56
|
+
readonly completion: Promise<ManagedProcessResult>;
|
|
49
57
|
readonly stderr: Readable;
|
|
50
58
|
readonly stdin: Writable;
|
|
51
59
|
readonly stdout: Readable;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{execFileSync as
|
|
1
|
+
import{execFileSync as a}from"node:child_process";import d from"node:fs";import{DARWIN_PS_EXECUTABLE as l,HOST_POSIX_INSPECTION_ENVIRONMENT as f,requireWindowsHostPowerShellCommand as m}from"./host-inspection-command.js";import{formatWindowsKernelProcessIdentity as p,parseWindowsKernelProcessIdentity as w}from"./windows-process-identity.js";import{windowsOwnerExecutable as I}from"./windows-native.js";const S=3e3,E=16*1024,T=8e3;let o;export function captureProcessCreationIdentity(e){const t=Number(e);if(!Number.isSafeInteger(t)||t<=0)return null;if(t===process.pid&&o!==void 0)return o;try{let r=null;return process.platform==="linux"?r=y(t):process.platform==="darwin"?r=P(t):process.platform==="win32"&&(r=g(t)),t===process.pid&&r!==null&&(o=r),r}catch{return null}}export function processCreationIdentityMatches(e,t){return observeProcessCreationIdentity(e,t)==="matches"}export function observeProcessCreationIdentity(e,t,r={}){const n=Number(e);if(!Number.isSafeInteger(n)||n<=0||typeof t!="string"||t.length===0)return"unknown";const i=(r.captureIdentity??captureProcessCreationIdentity)(n);return i!==null?i===t?"matches":"different":(r.inspectLiveness??inspectProcessLiveness)(n)==="absent"?"different":"unknown"}export function parseWindowsProcessCreationIdentity(e){return w(e)}function y(e){return parseLinuxProcessCreationIdentity(e,d.readFileSync(`/proc/${e.toString()}/stat`,"utf8"))}export function parseLinuxProcessCreationIdentity(e,t){if(!Number.isSafeInteger(e)||e<=0)throw new Error("Linux process stat identity requires a positive pid");const r=t.trim(),n=r.lastIndexOf(")"),i=r.indexOf(" ");if(n<0||i<1||Number(r.slice(0,i))!==e)throw new Error("Linux process stat identity is malformed");const s=r.slice(n+1).trim().split(/\s+/u)[19];if(!/^\d+$/u.test(s))throw new Error("Linux process stat start time is malformed");return`linux-proc-start:${s}`}function P(e){const t=runBoundedProcessIdentityQuery({args:["-p",e.toString(),"-o","pid=,lstart="],environment:f,executable:l,timeoutMs:S,windowsHide:!1});return parseDarwinProcessCreationIdentity(e,t)}export function parseDarwinProcessCreationIdentity(e,t){if(!Number.isSafeInteger(e)||e<=0)throw new Error("Darwin process start identity requires a positive pid");const r=t.trim().replace(/\s+/gu," "),n=/^(\d+) ((?:Sun|Mon|Tue|Wed|Thu|Fri|Sat) (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?:[1-9]|[12]\d|3[01]) (?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d \d{4})$/u.exec(r);if(n===null||Number(n[1])!==e)throw new Error("Darwin process start identity is malformed");return`darwin-ps-start:${n[2]}`}function g(e){const t=runBoundedProcessIdentityQuery({args:["process-identity",e.toString()],environment:m().environment,executable:I(),timeoutMs:T,windowsHide:!0});if(!/^\d+$/u.test(t))throw new Error("Windows process creation identity is malformed");return p(t)}export function windowsProcessCreationIdentityScript(e){if(!Number.isSafeInteger(e)||e<=0)throw new Error("Windows process creation identity script requires a positive pid");return`$ErrorActionPreference = "Stop"; $p = [Diagnostics.Process]::GetProcessById(${e.toString()}); try { $fileTime = [Int64]$p.StartTime.ToUniversalTime().ToFileTimeUtc(); if ($fileTime -le 0) { exit 3 }; [Console]::Write($fileTime.ToString([Globalization.CultureInfo]::InvariantCulture)) } finally { $p.Dispose() }`}export function runBoundedProcessIdentityQuery(e){if(!Number.isSafeInteger(e.timeoutMs)||e.timeoutMs<=0)throw new Error("Process identity query timeout must be a positive safe integer in milliseconds");return a(e.executable,[...e.args],{encoding:"utf8",env:e.environment,killSignal:"SIGKILL",maxBuffer:E,stdio:["ignore","pipe","ignore"],timeout:e.timeoutMs,windowsHide:e.windowsHide}).trim()}export function inspectProcessLiveness(e){try{return process.kill(e,0),"alive"}catch(t){return u(t,"ESRCH")?"absent":u(t,"EPERM")?"alive":"unknown"}}function u(e,t){return typeof e=="object"&&e!==null&&Reflect.get(e,"code")===t}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type ManagedProcessRequest, type ManagedProcessResult, type SupervisedProcessHandle, type SupervisedProcessRequest } from "./process-contracts.js";
|
|
2
|
-
export { type AttributedProcessLineageTerminationRequest, type ManagedProcessCancellationSignal, type ManagedProcessRequest, type ManagedProcessResult, type OwnedPosixProcessGroupTerminationRequest, type SupervisedProcessHandle, type SupervisedProcessOutputMode, type SupervisedProcessRecoveryIdentity, type SupervisedProcessReady, type SupervisedProcessRequest, } from "./process-contracts.js";
|
|
1
|
+
import { type ManagedProcessRequest, type ManagedProcessResult, type SupervisedProcessHandle, type SupervisedProcessLiveHandle, type SupervisedProcessWithoutRecoveryRequest, type SupervisedProcessRequest } from "./process-contracts.js";
|
|
2
|
+
export { type AttributedProcessLineageTerminationRequest, type ManagedProcessCancellationSignal, type ManagedProcessRequest, type ManagedProcessResult, type OwnedPosixProcessGroupTerminationRequest, type SupervisedProcessHandle, type SupervisedProcessLiveHandle, type SupervisedProcessWithoutRecoveryRequest, type SupervisedProcessOutputMode, type SupervisedProcessRecoveryIdentity, type SupervisedProcessReady, type SupervisedProcessRequest, } from "./process-contracts.js";
|
|
3
3
|
export { terminateAttributedProcessLineage, terminateOwnedPosixProcessGroup } from "./recovery.js";
|
|
4
4
|
export { cleanupExitedProcessGroup } from "./process-group.js";
|
|
5
5
|
export declare function runManagedProcess(request: Readonly<ManagedProcessRequest>): Promise<ManagedProcessResult>;
|
|
@@ -8,4 +8,5 @@ export declare class ProcessExitedBeforeReadyError extends Error {
|
|
|
8
8
|
readonly exitSignal: NodeJS.Signals | null;
|
|
9
9
|
constructor(exitCode: number | null, exitSignal: NodeJS.Signals | null);
|
|
10
10
|
}
|
|
11
|
+
export declare function startSupervisedProcess(request: Readonly<SupervisedProcessWithoutRecoveryRequest>): SupervisedProcessLiveHandle;
|
|
11
12
|
export declare function startSupervisedProcess(request: Readonly<SupervisedProcessRequest>): SupervisedProcessHandle;
|