@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 +1 @@
|
|
|
1
|
-
export function createRunReporterFromListener(r,{debug:i=!1}={}){return{outputMode:i?"debug":"compact",setServicePlans(e){r({type:"run.group_plans",groups:e.map(p=>({group:p.config.name,skipped:!!p.skipped,suites:p.suites.map(n=>({name:n.name,...n.testType===void 0?{}:{testType:n.testType},engineId:n.engineId,profileId:n.profileId,files:n.files}))}))})},setTotalFileCount(e){r({type:"run.total_files",total:e})},phaseStarted(e){r({type:"run.phase_started",label:e})},toolchainResolved(e,p){r({type:"run.toolchain_resolved",service:e.serviceName,runtimeLabel:e.runtimeLabel,summary:p})},setupOperationFinished(e){
|
|
1
|
+
export function createRunReporterFromListener(r,{debug:i=!1}={}){return{outputMode:i?"debug":"compact",setServicePlans(e){r({type:"run.group_plans",groups:e.map(p=>({group:p.config.name,skipped:!!p.skipped,suites:p.suites.map(n=>({name:n.name,...n.testType===void 0?{}:{testType:n.testType},engineId:n.engineId,profileId:n.profileId,files:n.files}))}))})},setTotalFileCount(e){r({type:"run.total_files",total:e})},phaseStarted(e){r({type:"run.phase_started",label:e})},toolchainResolved(e,p){r({type:"run.toolchain_resolved",service:e.serviceName,runtimeLabel:e.runtimeLabel,summary:p})},setupOperationFinished(e){r({type:"run.setup_finished",operation:e})},operationStarted(e){r({type:"run.operation_started",operation:e})},operationProgress(e){r({type:"run.operation_progress",operation:e})},operationFinished(e){r({type:"run.operation_finished",operation:e})},localServiceStarting(e){r({type:"run.local_service_starting",service:e.serviceName,runtimeLabel:e.runtimeLabel,command:e.command})},serviceSkipped(e,p){r({type:"run.group_skipped",group:e.name,reason:p})},plannedSkip(e){r({type:"run.task_planned_skip",group:e.serviceName,file:e.file,...e.testType===void 0?{}:{testType:e.testType},engineId:e.engineId,profileId:e.profileId,reason:e.reason||null})},taskStarted(e,p){r({type:"run.task_started",group:e.serviceName,file:e.file,suite:e.suiteName,...e.testType===void 0?{}:{testType:e.testType},engineId:e.engineId,profileId:e.profileId,targetName:p.name})},taskFinished(e,p){r({type:"run.task_finished",group:e.serviceName,file:e.file,suite:e.suiteName,...e.testType===void 0?{}:{testType:e.testType},engineId:e.engineId,profileId:e.profileId,outcome:p})},runError(e){r({type:"run.error",message:e})},runtimeError(e,p){r({type:"run.runtime_error",group:e.serviceName,file:e.file,message:p})},warning(e){r({type:"run.warning",message:e})},writeDebugLine(e=""){r({type:"run.debug",line:e})},runSummary(e,p,n){r({type:"run.summary",results:e,durationMs:p,runStatus:n})}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{writeRunArtifact as m}from"../../evidence/artifacts.js";import{collectTestkitStorageInventory as p}from"../../evidence/index.js";import{buildRunArtifact as g}from"../../evidence/reporting.js";import{isUnknownRecord as v}from"../../kernel/unknown-values.js";import{formatError as R}from"../../kernel/error-serialization.js";export async function finalizeRunArtifacts(t){const{productDir:r,results:e,startedAt:u,finishedAt:c,reporter:l,runStatus:s}=t,n=buildFinalizationRunArtifact(t),o=h(n.storage),d=y(r);n.storage={...o??{},testkit:d};const f=s==="failed"||e.some(i=>i.failed||i.infraFailed)?"failed":"passed";return await t.publishRunFinalization(f,i=>{A(n,i);const a=sealFinalizedRunArtifacts(t,n);return l?.runSummary?.(e,c-u,a.runArtifact.run.status==="interrupted"?"interrupted":a.runArtifact.run.status==="failed"?"failed":"passed"),a})}export function buildFinalizationRunArtifact(t){const{selection:r}=t;return g({productDir:t.productDir,runId:t.runId,results:t.results,startedAt:t.startedAt,finishedAt:t.finishedAt,execution:t.execution,workerCount:t.workerCount,typeValues:r.typeValues,suiteSelectors:r.suiteSelectors,fileNames:r.fileNames,groupFilter:r.groupFilter,planning:r.planning??null,archiveConfig:t.archiveConfig??null,declaredEnvironmentKeys:t.declaredEnvironmentKeys??[],metadata:t.metadata,environment:t.environmentRecord.snapshot(),operationTimings:t.operationTimingRegistry?.listOperations()??[],runInterruptReason:t.runInterruptReason??null,runStatus:t.runStatus??null})}export function sealFinalizedRunArtifacts(t,r){const e=m(t.productDir,r,{archiveConfig:t.archiveConfig??null,declaredEnvironmentKeys:t.declaredEnvironmentKeys??[],redactKeys:t.environmentRecord.redactKeys,redactionTargets:t.environmentRecord.redactionTargets()});return{runArtifact:e,shouldFailArchiveCleanup:e.archive.cleanup?.status==="failed",shouldFailArchivePublication:e.archive.publication.status==="failed"&&e.archive.publication.required===!0}}function A(t,r){if(t.run.status=r.artifactStatus,r.artifactStatus!=="interrupted"){delete t.run.interruptReason;return}t.run.interruptReason=r.interruptReason??"run interrupted"}function h(t){return v(t)?t:null}function y(t){try{return p(t,{rootRelativePath:".testkit/results"})}catch(r){return{status:"failed",error:R(r)}}}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{collectSuites as
|
|
2
|
-
`+
|
|
3
|
-
`))}function
|
|
1
|
+
import{collectSuites as a}from"./planning.js";export function ensureRequestedFilesMatch(t,s,f,o=[]){if(o.length===0)return;const i=new Set;for(const e of t)for(const c of a(e,s,f,[],{ignoreSkipRules:!0}))for(const l of c.files)i.add(r(l));const n=[...new Set(o.map(r))].filter(e=>!i.has(e));if(n.length!==0)throw new Error(`Requested file${n.length===1?"":"s"} did not match any selected suites:
|
|
2
|
+
`+n.map(e=>`- ${e}`).join(`
|
|
3
|
+
`))}function r(t){return t.split("\\").join("/")}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const DEFAULT_ESTIMATE_SOURCE="default";export function estimateTask({task:
|
|
1
|
+
export const DEFAULT_ESTIMATE_SOURCE="default";export function estimateTask({task:n,suite:e,timings:i}){const t=s(i,n);return t||{durationMs:u(n,e),source:DEFAULT_ESTIMATE_SOURCE,confidence:"low",runs:0}}function u(n,e){const i=n.engineId==="playwright"?2e4:8e3,t=e.weight??1,o=Math.max(1,e.fileCount);return Math.max(1e3,Math.round(i*t/o))}function s(n,e){const i=e.timingKey;if(!i)return null;const t=n?.files[i],o=t?.durationMs||t?.avgDurationMs||0;if(o<=0)return null;const r=t?.runs??0;return{durationMs:Math.max(1,Math.round(o)),source:"timings",confidence:r>=3?"high":"medium",runs:r}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{suiteKey as f}from"../../../domain/suite-selection.js";import{buildSchedulerTaskKey as y}from"./identity.js";import{estimateTask as g}from"./estimates.js";export const SCHEDULER_POLICY="longest-estimated-duration-first",SCHEDULER_VERSION=2;export function buildScheduledQueue(i,{timings:e}={}){const
|
|
1
|
+
import{suiteKey as f}from"../../../domain/suite-selection.js";import{buildSchedulerTaskKey as y}from"./identity.js";import{estimateTask as g}from"./estimates.js";export const SCHEDULER_POLICY="longest-estimated-duration-first",SCHEDULER_VERSION=2;export function buildScheduledQueue(i,{timings:e}={}){const s=[];let r=1;for(const o of i)for(const t of o.suites)for(const{file:m,hasSetup:c,locks:d,runtimeIsolation:l}of t.tasks){const u={id:r,graphKey:o.graphKey,targetName:o.owner,serviceName:o.owner,suiteKey:f(t),suiteName:t.name,testType:t.testType,engineId:t.engineId,profileId:t.profileId,orderIndex:t.orderIndex,file:m,hasSetup:c,locks:[...d],runtimeIsolation:l,resourceCost:1},p=y(u),a={...u,timingKey:p},n=g({task:a,suite:{...t,fileCount:t.tasks.length},timings:e});s.push({...a,estimatedDurationMs:n.durationMs,estimateSource:n.source,estimateConfidence:n.confidence,estimateRuns:n.runs}),r+=1}return s.sort(compareScheduledTasks).map((o,t)=>({...o,schedulerRank:t+1}))}export function buildRunPlanningMetadata(i){return{scheduler:{policy:SCHEDULER_POLICY,version:SCHEDULER_VERSION},tasks:(i||[]).map(e=>({rank:e.schedulerRank||null,id:e.id,group:e.serviceName,suite:e.suiteName,testType:e.testType,engineId:e.engineId,profileId:e.profileId,file:e.file,graphKey:e.graphKey,runtimeIsolation:e.runtimeIsolation,estimatedDurationMs:e.estimatedDurationMs,estimateSource:e.estimateSource,estimateConfidence:e.estimateConfidence,estimateRuns:e.estimateRuns||0,timingKey:e.timingKey}))}}export function compareScheduledTasks(i,e){return e.estimatedDurationMs-i.estimatedDurationMs||i.serviceName.localeCompare(e.serviceName)||i.suiteKey.localeCompare(e.suiteKey)||i.file.localeCompare(e.file)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveExecutionProfile as
|
|
1
|
+
import{resolveExecutionProfile as f}from"../../engines/catalog.js";export const DEFAULT_COMPATIBLE_BATCH_MAX_FILES=25;export function claimCompatibleTaskBatch(e,t,i={}){const r=i.maxFiles??DEFAULT_COMPATIBLE_BATCH_MAX_FILES,l=[t],u=p(t);if(u?g(t):!isBatchableEngineTask(t)||r<=1)return l;for(let o=0;o<e.length&&(u||l.length<r);){const n=e[o];if(u?n.engineId!==t.engineId||n.profileId!==t.profileId||g(n)||!m(t,n,i.environmentKeys):!isCompatibleEngineBatchTask(t,n)){o+=1;continue}l.push(n),e.splice(o,1)}return l}export function buildTaskBatch(e){if(e.length<=1)return e[0]??null;const t=e[0];return{...t,batchId:c(e),batchTasks:[t,e[1],...e.slice(2)]}}export function isBatchableEngineTask(e){return f(e).taskBatching==="compatible"&&e.resourceCost===1&&!g(e)}function g(e){return e.hasSetup||e.locks.length>0||e.runtimeIsolation!=="shared"}export function isCompatibleEngineBatchTask(e,t){return!isBatchableEngineTask(e)||!isBatchableEngineTask(t)?!1:t.graphKey===e.graphKey&&t.targetName===e.targetName&&t.serviceName===e.serviceName&&t.testType===e.testType&&t.engineId===e.engineId&&t.profileId===e.profileId}function m(e,t,i=new Map){if(t.targetName===e.targetName)return!0;const r=i.get(e.targetName);return r!==void 0&&r===i.get(t.targetName)}function p(e){return f(e).taskBatching==="selection"}function c(e){return`${e[0].engineId}-batch-${e.map(t=>t.id).join("-")}`}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildTaskDeadline as
|
|
1
|
+
import{buildTaskDeadline as z,createDeadlineSignal as U,EngineExecutionBridgeError as S,executeEngineTask as H,normalizeTimeoutMs as J}from"./engine-execution.js";import{cancelledBridgeDisposition as Q,infraBridgeDisposition as $,preparationFailure as V,settlePreparedTask as X,taskStartedFailure as Y}from"./engine-execution-failures.js";import{GroupDefaultConflictError as W}from"./environment.js";import{formatError as Z,toError as x}from"../../kernel/error-serialization.js";import{formatEngineInvocationStack as ee}from"./engine-failure-stack.js";import{DEFAULT_ENGINE_REPORTER_CALLBACK_TIMEOUT_MS as A,invokeBoundedReporterCallback as ie,invokeEngineReporterCallback as L}from"./engine-execution-reporting.js";import{drainRuntimeFailedTasks as ae}from"./runtime-failures.js";import{buildTimingObservation as te}from"./scheduler/observations.js";import{buildTaskBatch as ne,claimCompatibleTaskBatch as re}from"./task-batches.js";export function createWorker(e){return{workerId:e,currentGraphKey:null,graphSwitches:0,taskCount:0}}export async function runWorker(e,t,i,o,l,g,f,k,m,p=null){const v=Date.now(),w=[],q=[];try{for(;!i.isStopRequested();){const s=f(t,e.currentGraphKey,a=>i.locks.available(a.locks)&&i.environments.canAcquire(a));if(!s){const a=ae(t,i.environments);if(a.length>0){const n=Date.now(),r=new Set;for(const d of a)k(l,d.task,{status:"not_run",reason:d.reason,durationMs:0,startedAt:n,finishedAt:n,error:null,infraFailed:d.infraFailed,blockedByRuntime:d.blockedByRuntime,failureDetails:d.failureDetails}),r.has(d.graph.key)||(m(l,d.graph,d.message,n,{classification:"infra"}),r.add(d.graph.key));e.taskCount+=a.length;continue}if(t.length===0)break;await de(10);continue}const T=i.leases.allocate(s);i.locks.acquire(s.locks,T.id);let M=null,R=[s],I=s,B=!1,E=null;const u={phase:"acquire",prepared:null};let F=[],K=null;try{R=re(t,s,{environmentKeys:i.environmentKeys}),I=ne(R)??s,e.currentGraphKey!==null&&e.currentGraphKey!==s.graphKey&&(e.graphSwitches+=1),e.currentGraphKey=s.graphKey;const a=await i.environments.acquire(I,T);M=a,i.leases.create(T),u.phase="execute",F=(await o.withEngine(s.engineId,i.signal,async r=>{let d=null;if(R.length===1){u.phase="start",await L("taskStarted",()=>p?.taskStarted?.(s,{name:s.targetName}),{signal:i.signal,timeoutMs:A}),u.phase="prepare";const O=J(i.request.fileTimeoutSeconds),y=z(Date.now(),O),D=U(i.signal,y);try{u.prepared=await a.prepare(s,{deadlineMs:y,signal:D.signal,timeoutMs:O})}finally{D.dispose()}d={deadlineMs:y,prepared:u.prepared===null?null:{artifacts:u.prepared.artifacts,handles:u.prepared.handles,setupData:u.prepared.setupData},taskId:String(s.id)},u.phase="execute"}return H(r,{environment:u.prepared?.environment??a.environment,lease:T,request:i.request,started:d,task:I},i,p)})).outcomes,F.some(r=>r.infraFailed&&r.blockedByRuntime)&&(B=!0)}catch(a){const n=u.phase==="start"?Y(a,String(s.id)):u.phase==="prepare"?V(a,String(s.id)):a,r=u.phase==="acquire"?ue(a,i):ce(n);E={disposition:r,error:n},B=M!==null&&r.runtimeImpact!=="none"}finally{if(u.prepared!==null){const a=await u.prepared.release(),n=await X(E===null?{outcomes:F}:{error:E.error},a,String(s.id),r=>L("taskFinished",()=>p?.taskFinished?.(s,r),{signal:i.signal,timeoutMs:A}));n!==null&&(E={disposition:n.disposition,error:n},B||=n.disposition.runtimeImpact!=="none")}K=await oe(i.leases,M,T,B),i.locks.release(s.locks,T.id)}const c=[];let b=null;const G=()=>{if(b!==null)return b;try{b=fe(i.environments,s)}catch(a){c.push(P("Runtime failure graph resolution failed",a)),b=_(s)}return b};if(E===null){for(const n of F)await N({operationErrors:c,outcome:n,recordTaskOutcome:k,recordTiming:!0,reporter:p,reportTaskFinished:!1,task:n.task,timingUpdates:g,trackers:l});e.taskCount+=F.length;const a=F.filter(n=>n.infraFailed&&n.blockedByRuntime);if(a.length>0){const n=a.map(r=>r.reason??r.error??"Runtime readiness failed before engine execution").join("; ");w.push(n),await C(c,"Runtime error reporter failed",()=>p?.runtimeError?.(s,n)),await h(c,"Runtime graph failure recording failed",()=>{i.environments.markGraphFailed(s.graphKey,n)}),await h(c,"Result graph error recording failed",()=>{m(l,G(),n,Date.now(),{classification:"infra"})})}}else{const{disposition:a,error:n}=E,r=Z(n),d=se(R,n);if(a.status!=="cancelled"&&w.push(r),a.notifyRuntimeError&&await C(c,"Runtime error reporter failed",()=>p?.runtimeError?.(d,r)),a.status!=="cancelled"){a.runtimeImpact==="fail-graph"&&await h(c,"Runtime graph failure recording failed",()=>{i.environments.markGraphFailed(s.graphKey,r)});const y=n instanceof W?_(s):G();await h(c,"Result graph error recording failed",()=>{m(l,y,r,Date.now(),{classification:a.classification??"product"})})}const O=Date.now();for(const y of R){const D=n instanceof S&&n.completedOutcomesByTaskId.has(String(y.id)),j=le(y,I,n,a,r,O);await N({operationErrors:c,outcome:j,recordTaskOutcome:k,recordTiming:D,reporter:p,reportTaskFinished:!D,task:y,timingUpdates:g,trackers:l})}e.taskCount+=R.length}if(K!==null){const a=K.message;w.push(a),await C(c,"Runtime error reporter failed",()=>p?.runtimeError?.(s,a)),K.recovered||await h(c,"Runtime graph failure recording failed",()=>{i.environments.markGraphFailed(s.graphKey,a)}),await h(c,"Result graph error recording failed",()=>{m(l,G(),a,Date.now(),{classification:"infra"})})}if(c.length>0){const a=[...c];for(const r of a)w.push(r.message);const n=a.map(r=>r.message).join("; ");await h(c,"Result graph error recording failed while reporting a Testkit operation failure",()=>{m(l,G(),n,Date.now(),{classification:"infra"})});for(const r of c.slice(a.length))w.push(r.message);c.length>a.length&&q.push(...a.map(r=>r.message),...c.slice(a.length).map(r=>r.message))}}}finally{e.currentGraphKey=null}return{workerId:e.workerId,failed:w.length>0,durationMs:Date.now()-v,taskCount:e.taskCount,graphSwitches:e.graphSwitches,errors:w,unpersistedErrors:q}}async function N({operationErrors:e,outcome:t,recordTaskOutcome:i,recordTiming:o,reporter:l,reportTaskFinished:g,task:f,timingUpdates:k,trackers:m}){await h(e,`Task outcome recording failed for ${f.file}`,()=>{i(m,f,t)}),g&&await C(e,`Task-finished reporter failed for ${f.file}`,()=>l?.taskFinished?.(f,t)),o&&await h(e,`Task timing recording failed for ${f.file}`,()=>{const p=te(f,t);p&&k.push(p)})}async function h(e,t,i){try{await i()}catch(o){e.push(P(t,o))}}async function C(e,t,i){await h(e,t,()=>ie(t,i,{timeoutMs:A}))}function P(e,t){const i=x(t);return new Error(`${e}: ${i.message}`,{cause:i})}function se(e,t){const i=e[0];if(!(t instanceof S))return i;if(t.failingTaskId!==null){const o=e.find(l=>String(l.id)===t.failingTaskId);if(o!==void 0)return o}return e.find(o=>!t.completedOutcomesByTaskId.has(String(o.id)))??i}function _(e){return{key:e.graphKey,targetNames:[e.targetName]}}async function oe(e,t,i,o){const l=async g=>{await t?.release({invalidate:g}),e.remove(i)};try{return await l(o),null}catch(g){const f=x(g);try{return await l(!0),{message:`Runtime lease release failed but recovered on idempotent retry: ${f.message}`,recovered:!0}}catch(k){const m=x(k);return{message:`Runtime lease release failed: ${f.message}; idempotent retry also failed: ${m.message}`,recovered:!1}}}}function le(e,t,i,o,l,g){const f=ee(i),k={kind:"engine-invocation",key:"engine_invocation_failed",title:"Engine invocation failed",count:1,...o.classification===void 0?{}:{classification:o.classification},message:l,...f===void 0?{}:{stack:f}},m=i instanceof S?i.completedOutcomesByTaskId.get(String(e.id)):void 0;if(m!==void 0)return m;const p=i instanceof S?i.artifactReferencesByTaskId.get(String(e.id))??[]:[];return{task:e,status:o.status,error:o.status==="failed"?l:null,reason:o.status==="failed"?null:l,infraFailed:o.classification==="infra",blockedByRuntime:o.status==="not_run",durationMs:0,startedAt:g,finishedAt:g,artifacts:[...p],failureDetails:o.status==="cancelled"?[]:[k],..."batchTasks"in t?{runtimeBatch:{id:t.batchId,index:t.batchTasks.findIndex(v=>v.id===e.id&&v.file===e.file),fileCount:t.batchTasks.length,files:t.batchTasks.map(v=>v.file)}}:{}}}function ce(e){return e instanceof S?e.disposition:$()}function ue(e,t){return t.signal.aborted&&e===t.signal.reason?Q():$(e instanceof W?"none":"fail-graph")}function fe(e,t){return e.getGraph(t.graphKey)??_(t)}function de(e){return new Promise(t=>setTimeout(t,e))}
|
package/dist/app/status/runs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import c from"node:fs";import{inspectRunManifests as
|
|
1
|
+
import c from"node:fs";import{inspectRunManifests as d}from"../../process/runs/inspection.js";import{resolveProductDir as l}from"../../process/files/product-dir.js";import{asUnknownRecord as s,displayString as o,optionalString as u}from"../../kernel/unknown-values.js";import{readLiveRunArtifact as f,summarizeLiveArtifact as p}from"../../evidence/live-run-summary.js";export{latestOperation}from"../../evidence/live-run-summary.js";export async function collectRunStatus(r){if(r.runId){const e=l(process.cwd(),r.dir);if(!c.statSync(e).isDirectory())throw new Error(`Product directory is not a directory: ${e}`);return buildRunStatus({productDir:e},r)}const{loadConfigContext:t}=await import("../../config/loader/index.js"),n=await t({dir:r.dir,allowUnmigratedServices:!0,plain:!1,retired:[]});return buildRunStatus(n,r)}export function buildRunStatus(r,t={}){const n=d(r.productDir,t.runId||void 0),e=f(r.productDir);if(t.full)return Promise.resolve({productDir:r.productDir,mode:"full",runs:n,live:e});const i={productDir:r.productDir,mode:"summary",runs:n.map(w),live:p(e)};return i.live&&(i.live.state=describeLiveArtifactState(i)),Promise.resolve(i)}export function describeLiveArtifactState(r){const t=s(r.live),n=s(t?.run),e=o(n?.id,"unknown"),i=o(n?.status,"unknown"),a=m(r,e);return i==="running"&&!a?{runId:e,status:"stale",reason:"no active run matches this live artifact"}:{runId:e,status:i,reason:u(n?.interruptReason)}}function m(r,t){if(!t||t==="unknown")return!1;for(const n of r.runs??[])if(n.active&&v(t,n.runId))return!0;return!1}function v(r,t){return t===r||t.startsWith(`${r}:`)}function w(r){const t=S(r.status);return{runId:r.runId,pid:r.pid,status:r.status||"unknown",startedAt:r.startedAt||null,heartbeatAt:r.heartbeatAt||null,interruptReason:r.interruptReason||null,active:r.active,terminal:t,stale:!r.active&&!t,session:h(r.session)}}function S(r){return typeof r=="string"&&["finished","failed","interrupted"].includes(r)}function h(r){const t=s(r);return t?{kind:u(t.kind),status:u(t.status),stage:u(t.stage)}:null}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{attachErrorDiagnostics as
|
|
2
|
-
`),s}function W({productDir:e,companion:n,cwd:t,engineId:s,extendsPath:o,files:r,outputDir:c,scopeName:
|
|
3
|
-
`),p}function
|
|
4
|
-
`)}`)},s=n.getParsedCommandLineOfConfigFile(e,{},{...n.sys,onUnRecoverableConfigFileDiagnostic:t});for(const o of s?.errors??[])o.code!==18002&&o.code!==18003&&t(o);return s?.fileNames??[]}function
|
|
1
|
+
import{attachErrorDiagnostics as C}from"../../kernel/error-serialization.js";import{isPathWithin as w}from"../../process/files/paths.js";import{resolveProductDir as M}from"../../process/files/product-dir.js";import d from"fs";import i from"path";import{createHash as R}from"crypto";import{createRequire as _}from"module";import{fileURLToPath as O}from"url";import{resolveVitestSelection as I}from"../../engines/catalog.js";import{NODE_VITEST_PACKAGE_REQUIREMENTS as N}from"../../engines/installations/package-requirements.js";import{PLAYWRIGHT_PACKAGE_FAMILY_REQUIREMENTS as D}from"../../engines/installations/package-requirements.js";import{hasDiscoveryErrors as A,buildDiscoveryErrorFromDiagnostics as J,detectNextApp as U,loadDiscoveredConfigContext as L}from"../../config/loader/index.js";import{managedHostProcessInfrastructureError as B,runManagedHostProcess as G}from"../../process/index.js";import{findNearestPlaywrightConfigFile as q}from"../../engines/playwright/runtime/config-file.js";import{isUnknownRecord as v}from"../../kernel/unknown-values.js";const b=/\.[cm]?tsx?$/u,K=600*1e3,k=i.resolve(i.dirname(O(import.meta.url)),"..","..",".."),x=_(i.join(k,"package.json")),P=()=>x("typescript");export class TestkitTypecheckError extends Error{result;results;constructor(n,t,s){super(`testkit typecheck failed for ${n}`),this.result=t,this.results=s,C(this,{result:t,results:s})}}export async function runTestkitTypecheck(e={}){const n=M(process.cwd(),e.dir),t=I(e.vitest??{},n),{context:s,discovery:o}=await L({dir:n,group:e.group,vitest:t});return runTestkitTypecheckForContext(s,o,e)}export async function runTestkitTypecheckForContext(e,n,t={}){const s=t.timeoutMs??K;if(A(e.discovery.diagnostics))throw J(e.discovery.diagnostics);const o=e.productDir,r=i.join(o,".testkit","_typecheck");d.mkdirSync(r,{recursive:!0});const c=[];if(e.configFile){const l=H({productDir:o,configFile:e.configFile,outputDir:r});c.push({label:"config",cwd:o,tsconfigPath:l})}const a=Y(e),u=n.files.filter(l=>b.test(l.path)).flatMap(l=>(a.get(l.path)??[[]]).map(g=>({engineId:l.engineId,path:l.path,setupFiles:g}))),m=new Set(n.files.filter(l=>l.engineId==="playwright").map(l=>l.group));for(const l of e.groups){if(!m.has(l.name))continue;const g=q(o,l.roots);g&&u.push({engineId:"playwright",path:g,setupFiles:[]})}const p=z(o,u);for(const l of p){const g=W({...l,outputDir:r,productDir:o});c.push({label:Z(l,o),cwd:l.cwd,tsconfigPath:g})}const y=[];for(const l of c){const g=await te(l,s,t.signal);if(y.push(g),!g.ok)throw new TestkitTypecheckError(l.label,g,y)}return{ok:!0,productDir:o,results:y}}function H({productDir:e,configFile:n,outputDir:t}){const s=i.join(t,"config.tsconfig.json"),o=E(e),r={...o?{extends:f(s,o)}:{},compilerOptions:buildTypecheckCompilerOptions({tsconfigPath:s,extendsPath:o,libs:["ES2022"],rootDir:f(s,e),types:["node"]}),files:[f(s,n)],include:[],exclude:[h(s,e,"node_modules"),h(s,e,"dist"),h(s,e,".testkit")]};return d.writeFileSync(s,`${JSON.stringify(r,null,2)}
|
|
2
|
+
`),s}function W({productDir:e,companion:n,cwd:t,engineId:s,extendsPath:o,files:r,outputDir:c,scopeName:a,setupFiles:u}){const m=u.length===0?"":`.setup-${T(u)}`,p=i.join(c,`${s}.${ee(a)}${m}.tsconfig.json`),y=s==="playwright",l=y&&U(t),g={...o?{extends:f(p,o)}:{},compilerOptions:{...buildTypecheckCompilerOptions({engineId:s,tsconfigPath:p,extendsPath:o,libs:Q(s,o),...l?{extraPaths:nextRuntimeTypePaths({tsconfigPath:p,serviceDir:t})}:{},types:["node"]}),...y?{allowJs:!0,checkJs:!1}:{}},files:r.map($=>f(p,$)),include:l&&!n?[...d.existsSync(i.join(t,"next-env.d.ts"))?[f(p,i.join(t,"next-env.d.ts"))]:[],h(p,t,"tests/playwright-fixtures/**/*.ts"),h(p,t,"tests/playwright-fixtures/**/*.tsx"),h(p,t,"tests/playwright-fixtures/**/*.mts"),h(p,t,".next/types/**/*.ts"),h(p,t,".next/dev/types/**/*.ts"),h(p,e,".next-testkit/**/dist/types/**/*.ts"),h(p,e,".next-testkit/**/dist/dev/types/**/*.ts")]:[],exclude:["node_modules",".next/cache",".next/dev"]};return d.writeFileSync(p,`${JSON.stringify(g,null,2)}
|
|
3
|
+
`),p}function z(e,n){const t=new Map,s=new Map;for(const o of n){const r=i.resolve(e,o.path),c=[o.engineId,r,...o.setupFiles].join("\0");s.set(c,{...o,path:r})}for(const o of s.values()){const r=o.path,c=o.engineId==="playwright"?S(i.dirname(r),e,["tsconfig.playwright.json"]):null,a=c??S(i.dirname(r),e),u=a?i.dirname(a):e,m=X(e,u),p=[o.engineId,a??"",m,...o.setupFiles].join("\0"),y=t.get(p);if(y){y.files.push(r);continue}t.set(p,{companion:c!==null,cwd:u,engineId:o.engineId,extendsPath:a,files:[r,...o.setupFiles,...c?V(c):[]],scopeName:m,setupFiles:o.setupFiles})}return[...t.values()].map(o=>({...o,files:[...new Set(o.files)].sort((r,c)=>r.localeCompare(c))})).sort((o,r)=>j(o.engineId)-j(r.engineId)||o.scopeName.localeCompare(r.scopeName)||o.setupFiles.join("\0").localeCompare(r.setupFiles.join("\0")))}function Y(e){const n=new Map;for(const{file:t,setupFiles:s}of e.plainFiles){const o=[...new Set(s.filter(c=>b.test(c)))].sort((c,a)=>c.localeCompare(a)),r=n.get(t)??[];r.some(c=>c.join("\0")===o.join("\0"))||r.push(o),n.set(t,r)}return n}function V(e){const n=P(),t=o=>{throw new Error(`${e}: ${n.flattenDiagnosticMessageText(o.messageText,`
|
|
4
|
+
`)}`)},s=n.getParsedCommandLineOfConfigFile(e,{},{...n.sys,onUnRecoverableConfigFileDiagnostic:t});for(const o of s?.errors??[])o.code!==18002&&o.code!==18003&&t(o);return s?.fileNames??[]}function Q(e,n){return e==="playwright"?["ES2022","DOM","DOM.Iterable"]:n?void 0:["ES2022"]}function j(e){return["node-vitest","playwright"].indexOf(e)}function Z({engineId:e,scopeName:n,setupFiles:t},s){const o=n==="repo"?e:`${e}:${n}`;if(t.length===0)return o;const r=t.map(c=>i.relative(s,c).split(i.sep).join("/"));return`${o} with ${r.join(", ")}`}function X(e,n){const t=i.relative(e,n);return t===""?"repo":t.split(i.sep).join("/")}function ee(e){const n=e.replace(/[^a-zA-Z0-9_-]+/g,"-");return n===e?e:`${n}-${T([e])}`}function T(e){return R("sha256").update(e.join("\0")).digest("hex").slice(0,12)}function te({label:e,cwd:n,tsconfigPath:t},s,o){const r=ne(n);return G({args:[...r.args,"--noEmit","-p",t],cwd:n,displayLabel:`Testkit typecheck ${e}`,env:process.env,executable:r.command,signal:o,timeoutMs:s}).then(c=>{const a=B(`Testkit typecheck ${e}`,c);if(a)throw a;return{label:e,tsconfigPath:t,ok:c.exitCode===0,exitCode:c.exitCode,stdout:c.stdout,stderr:c.stderr}})}function ne(e){let n=e;for(;;){const s=i.join(n,"node_modules","typescript","bin","tsc");if(d.existsSync(s))return{command:process.execPath,args:[s]};const o=i.dirname(n);if(o===n)break;n=o}const t=i.join(k,"node_modules","typescript","bin","tsc");if(d.existsSync(t))return{command:process.execPath,args:[t]};throw new Error(`TypeScript compiler not found from ${e} upward`)}function E(e,n=["tsconfig.json","tsconfig.build.json"]){for(const t of n){const s=i.join(e,t);if(d.existsSync(s))return s}return null}function S(e,n,t){const s=i.resolve(n);let o=i.resolve(e);for(;;){if(!w(s,o))return null;const r=E(o,t);if(r)return r;if(o===s)return null;const c=i.dirname(o);if(c===o)return null;o=c}}function f(e,n){const t=i.relative(i.dirname(e),n).split(i.sep).join("/");return t.startsWith("../")||i.isAbsolute(t)?t:`./${t}`}function h(e,n,t){return i.join(f(e,n),t).split(i.sep).join("/")}function oe(e){const n={};for(const[t,s]of Object.entries(F("@gfdelarue/testkit",k)))!v(s)||typeof s.types!="string"||(n[`@gfdelarue/testkit${t.slice(1)}`]=[f(e,i.join(k,s.types))]);return n}function se(e,n){const t={},s=f(n,i.join(k,"dist","app","typecheck","unavailable.d.ts"));for(const o of re(e)){const r=ie(o),c=F(o,r);t[`${o}/*`]=[s];for(const a of Object.keys(c).sort()){if(a!=="."&&!a.startsWith("./")||a.includes("*"))continue;const u=a==="."?o:`${o}${a.slice(1)}`,m=ae(u,o,r);t[u]=[m===null?s:f(n,m)]}}return t}function re(e){switch(e){case"node-vitest":return N.filter(({authoringResolution:n})=>n==="package-exports").map(({packageName:n})=>n);case"playwright":return D.map(({packageName:n})=>n);case void 0:return[]}}function ie(e){const n=ce(e);let t;try{t=x.resolve(`${e}/package.json`)}catch(r){throw new Error(`Testkit typecheck requires managed public package ${e}`,{cause:r})}const s=d.realpathSync.native(i.dirname(t)),o=JSON.parse(d.readFileSync(i.join(s,"package.json"),"utf8"));return requireManagedPublicPackageIdentity(o,e,n),s}export function requireManagedPublicPackageIdentity(e,n,t){if(!v(e)||e.name!==n||e.version!==t)throw new Error(`Testkit managed public package has invalid identity: expected ${n}@${t}`)}function ce(e){const n=JSON.parse(d.readFileSync(i.join(k,"package.json"),"utf8"));if(!v(n)||!v(n.dependencies))throw new Error("Testkit package has invalid dependency metadata");const t=n.dependencies[e];if(typeof t!="string"||!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u.test(t))throw new Error(`Testkit managed public package ${e} must be an exact root dependency`);return t}function F(e,n){const t=JSON.parse(d.readFileSync(i.join(n,"package.json"),"utf8"));if(!v(t)||t.name!==e||!v(t.exports))throw new Error(`Testkit managed public package has invalid exports: ${e}`);return t.exports}function ae(e,n,t){const s=P(),o=s.resolveModuleName(e,i.join(k,"__testkit_managed_public_types__.ts"),{module:s.ModuleKind.ESNext,moduleResolution:s.ModuleResolutionKind.Bundler,resolveJsonModule:!0,target:s.ScriptTarget.ES2022},s.sys).resolvedModule;if(o===void 0)return null;const r=d.realpathSync.native(o.resolvedFileName);if(!w(t,r))throw new Error(`Managed public package type ${e} resolved outside ${n}`);return r}export function buildTypecheckCompilerOptions({engineId:e,tsconfigPath:n,extendsPath:t,rootDir:s,extraPaths:o={},libs:r,types:c}){const u={...t?ue({tsconfigPath:n,extendsPath:t}):{},...o,...oe(n),...se(e,n)};return{baseUrl:null,declarationDir:null,...r?{lib:[...r]}:{},mapRoot:null,module:"ESNext",moduleResolution:"Bundler",noEmit:!0,outDir:null,outFile:null,rootDir:s??null,sourceRoot:null,target:"ES2022",tsBuildInfoFile:f(n,n.replace(/\.json$/u,".tsbuildinfo")),typeRoots:le(c,n),types:[...c],paths:u}}function le(e,n){const t=new Set;for(const s of e){const o=pe(s);let r;try{r=x.resolve(`${o}/package.json`)}catch(a){throw new Error(`Testkit typecheck requires installed type package ${o}`,{cause:a})}const c=i.dirname(r);t.add(i.dirname(c))}return[...t].map(s=>f(n,s))}function pe(e){return e==="node"?`@types/${e}`:e}function ue({tsconfigPath:e,extendsPath:n}){const t=P(),s=t.getParsedCommandLineOfConfigFile(n,{},{...t.sys,onUnRecoverableConfigFileDiagnostic:()=>{}}),o=s?.options.paths;if(!o||Object.keys(o).length===0)return{};const r=s.options.baseUrl||(typeof s.options.pathsBasePath=="string"?s.options.pathsBasePath:i.dirname(n)),c={};for(const[a,u]of Object.entries(o))c[a]=u.map(m=>f(e,i.resolve(r,m)));return c}export function nextRuntimeTypePaths({tsconfigPath:e,serviceDir:n}){const t=fe(n,[i.join("next","types.d.ts"),i.join("next","types.js")]);if(!t)return{};const s=f(e,t);return{"next/types":[s],"next/types.js":[s]}}function fe(e,n){let t=i.resolve(e);for(;;){for(const o of n){const r=i.join(t,"node_modules",o);if(d.existsSync(r))return r}const s=i.dirname(t);if(s===t)return null;t=s}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Args as i,Flags as e}from"./framework/definitions.js";import{defineCommand as t}from"./framework/command.js";import{runArgs as
|
|
1
|
+
import{Args as i,Flags as e}from"./framework/definitions.js";import{defineCommand as t}from"./framework/command.js";import{runArgs as c,runFlags as n,schemaOperationFlags as o,sharedFlags as r,suppliedEnvironmentFlags as d,testSelectionFlags as a,timezoneFlag as u,vitestSelectionFlags as s}from"./command-flags.js";import{publicTestTypeListText as l}from"../domain/test-types.js";export const commandCatalog=Object.freeze({artifacts:t({summary:"List, verify, or inspect artifacts from immutable Testkit runs",args:{file:i.string({description:"Optional test file selector",required:!1})},flags:{...a,kind:e.string({description:"Limit artifacts to one kind"}),format:e.string({description:"Output format",options:["list","text"],default:"list"}),run:e.string({description:"Read one immutable run ID instead of latest"}),list:e.boolean({description:"List retained immutable runs",default:!1}),verify:e.boolean({description:"Verify the selected run manifest and checksums",default:!1})}}),"bug bisect":t({summary:"Find the commit at which a recorded bug's test starts to fail",args:{id:i.string({description:"Draft id that `testkit bug record` printed",required:!0})},flags:{dir:e.string({description:"Product directory"}),"max-probes":e.string({description:"Most commits to run the test at, besides HEAD (default 20)"})}}),"bug file":t({summary:"Run a recorded bug's test and file the bug as an issue",args:{id:i.string({description:"Draft id that `testkit bug record` printed; with --batch, any number of them",multiple:!0})},flags:{batch:e.boolean({description:"File the named drafts, or every unfiled draft when none is named, and report each one",default:!1}),dir:e.string({description:"Product directory"}),...d,...s}}),"bug guide":t({summary:"Print the bug record format, the severities, and the category lists"}),"bug record":t({summary:"Record a confirmed bug as a local draft",args:{file:i.string({description:"Bug record JSON file, or - to read standard input",required:!0})},flags:{dir:e.string({description:"Product directory"}),...s,...u}}),"campaign finish":t({summary:"Finish the active campaign and write its pull request summary",flags:{dir:e.string({description:"Product directory"})}}),"campaign start":t({summary:"Start a bug-hunting campaign and print the agent's brief",flags:{dir:e.string({description:"Product directory"}),id:e.string({description:"Campaign id, instead of one made from the target and the date"}),target:e.string({description:"What the agent should hunt, in one line of at most 200 characters",required:!0})}}),"campaign status":t({summary:"Show the active campaign, its recorded bugs, and the area map coverage",flags:{dir:e.string({description:"Product directory"})}}),evidence:t({summary:"Show which recorded test results hold for the current code",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),offline:e.boolean({description:"Don't read CI commit statuses",default:!1})}}),"ci report":t({summary:"Post the last run as a commit status, a step summary, and one pull request comment",flags:{context:e.string({description:"Commit status context, instead of testkit"}),dir:e.string({description:"Product directory"})}}),cleanup:t({summary:"Clean stale local testkit state",flags:{...r,"dry-run":e.boolean({description:"Show cleanup actions without deleting state",default:!1}),cache:e.string({description:"Clean cache state: runtime, bundles, or all",multiple:!0,options:["runtime","bundles","all"]}),resources:e.boolean({description:"Clean Testkit-owned Docker resources that are stale or orphaned",default:!1})}}),"db schema refresh":t({summary:"Refresh a source database schema cache",flags:{...r,...o}}),"db schema verify":t({summary:"Verify local schema replay against the source database schema",flags:{...r,...o,"skip-schema-source-verify":e.boolean({description:"Skip the check that local schema replay matches the source database schema",default:!1})}}),destroy:t({summary:"Remove runtime state, databases, and Docker resources; run results and other .testkit files stay",flags:r}),discover:t({summary:"Discover managed tests and report their metadata",flags:{...a,...s,type:e.string({char:"t",multiple:!0,description:`Filter by suite type(s): ${l({includeAll:!0})}`}),suite:e.string({char:"s",multiple:!0,description:"Filter by suite selector(s)"}),file:e.string({char:"f",multiple:!0,description:"Filter by exact file path(s)"}),"runnable-only":e.boolean({description:"Show only files not skipped by repo rules",default:!1}),strict:e.boolean({description:"Fail instead of reporting discovery/config diagnostics",default:!1}),output:e.string({description:"Write machine-readable discovery JSON to a file"}),"output-mode":e.string({description:"Human-readable output mode",options:["compact","verbose"],default:"compact"})}}),doctor:t({summary:"Run built-in config, discovery, and hygiene checks",flags:{dir:e.string({description:"Product directory"}),typecheck:e.boolean({description:"Include generated TypeScript checks",default:!0,allowNo:!0})}}),"guard unsafe-entrypoint":t({summary:"Fail a local script that must be run through Testkit",enableJsonFlag:!1,flags:{command:e.string({char:"c",description:"Disabled command name shown in the error message",required:!0}),use:e.string({char:"u",description:"Replacement command shown in the error message",required:!0})}}),lint:t({summary:"Run built-in Testkit repository hygiene checks",flags:{dir:e.string({description:"Product directory"})}}),"report prepare":t({summary:"Prepare a local report with optional template, bug records, and selected run evidence",args:{directory:i.string({description:"New bundle directory",required:!0})},flags:{dir:e.string({description:"Product directory containing bug records and runs"}),template:e.string({description:"Copy this file unchanged to report.md"}),instructions:e.string({description:"Print this repository-owned drafting guide for the author"}),bug:e.string({description:"Include this recorded bug ID as context",multiple:!0}),run:e.string({description:"Immutable run ID containing the selected artifacts"}),artifact:e.string({description:"Exact artifact ref.path from testkit artifacts --json",multiple:!0})}}),"report publish":t({summary:"Publish a prepared directory at one Git revision without changing your checkout",args:{directory:i.string({description:"Directory containing only the files to publish",required:!0})},flags:{dir:e.string({description:"Product directory used to resolve provider credentials"}),repository:e.string({description:"Destination GitHub or Gitea repository URL, or owner/name",required:!0}),branch:e.string({description:"Destination branch",required:!0}),path:e.string({description:"Bundle directory within the destination repository",required:!0}),message:e.string({description:"Publication commit message"})}}),"run status":t({summary:"Show active Testkit run progress",flags:{...r,run:e.string({description:"Specific run id to show"}),full:e.boolean({description:"Include the full live status payload in JSON output"})}}),"run stop":t({summary:"Stop active Testkit run work",flags:{...r,run:e.string({description:"Specific run id to stop; defaults to the newest active run"}),all:e.boolean({description:"Stop all active runs for this product",default:!1}),reason:e.string({description:"Operator-visible reason recorded with cancelled runs and dirty slots"})}}),prove:t({summary:"Mutate changed product code and check that the selected tests notice",flags:{...a,...s,type:n.type,suite:n.suite,file:n.file,since:e.string({description:"Mutate code changed since the merge base with this ref (default origin/HEAD)"}),"max-mutants":e.string({description:"Most mutants to run (default 25)"}),mutants:e.string({description:"JSON file of authored mutants to run instead of mutating the changed lines"}),"fail-under":e.string({description:"Exit 1 when the mutation score is below this percentage"})}}),run:t({summary:"Run test suites",args:c,flags:n}),shake:t({summary:"Rerun test files under conditions that expose flaky tests, and report which condition fails them",flags:{dir:e.string({description:"Product directory"}),...s,file:e.string({char:"f",multiple:!0,description:"Test file to shake"}),since:e.string({description:"Shake the test files changed since the merge base with this ref"}),runs:e.string({description:"Runs for each condition (default 5)"}),copies:e.string({description:"Copies of each file that run at once in the copies condition (default 3)"}),only:e.string({multiple:!0,description:"Run only these conditions: control, shuffle, copies, timezone-east, timezone-west, load"})}}),"setup playwright":t({summary:"Install the managed Playwright Chromium runtime",flags:{dir:e.string({description:"Product directory"}),force:e.boolean({default:!1,description:"Force Playwright to repair the browser installation"}),"with-deps":e.boolean({default:!1,description:"Install required host dependencies with Chromium"})}}),status:t({summary:"Show local testkit state",flags:r}),stop:t({summary:"Stop all Testkit-owned runs",flags:{...r,reason:e.string({description:"Operator-visible reason recorded with cancelled runs"})}}),typecheck:t({summary:"Typecheck testkit config, helpers, and suites",flags:{dir:e.string({description:"Product directory"}),group:a.group,...s}}),workspace:t({summary:"Inspect Testkit workstation data for a product",flags:{...a,"target-limit":e.integer({description:"Maximum number of quick-run target previews",default:24}),"no-status":e.boolean({description:"Skip runtime status collection",default:!1})}})});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{commandCatalog as
|
|
1
|
+
import{commandCatalog as e}from"./command-catalog.js";export const commandRegistry=Object.freeze([t("artifacts",()=>import("./commands/artifacts.js")),t("bug bisect",()=>import("./commands/bug/bisect.js")),t("bug file",()=>import("./commands/bug/file.js")),t("bug guide",()=>import("./commands/bug/guide.js")),t("bug record",()=>import("./commands/bug/record.js")),t("campaign finish",()=>import("./commands/campaign/finish.js")),t("campaign start",()=>import("./commands/campaign/start.js")),t("campaign status",()=>import("./commands/campaign/status.js")),t("ci report",()=>import("./commands/ci/report.js")),t("cleanup",()=>import("./commands/cleanup.js")),t("db schema refresh",()=>import("./commands/db/schema/refresh.js")),t("db schema verify",()=>import("./commands/db/schema/verify.js")),t("destroy",()=>import("./commands/destroy.js")),t("discover",()=>import("./commands/discover.js")),t("doctor",()=>import("./commands/doctor.js")),t("evidence",()=>import("./commands/evidence.js")),t("guard unsafe-entrypoint",()=>import("./commands/guard/unsafe-entrypoint.js")),t("lint",()=>import("./commands/lint.js")),t("prove",()=>import("./commands/prove.js")),t("report prepare",()=>import("./commands/report/prepare.js")),t("report publish",()=>import("./commands/report/publish.js")),t("run status",()=>import("./commands/run/status.js")),t("run stop",()=>import("./commands/run/stop.js")),t("run",()=>import("./commands/run.js")),t("shake",()=>import("./commands/shake.js")),t("setup playwright",()=>import("./commands/setup/playwright.js")),t("status",()=>import("./commands/status.js")),t("stop",()=>import("./commands/stop.js")),t("typecheck",()=>import("./commands/typecheck.js")),t("workspace",()=>import("./commands/workspace.js"))]),removedCommands=new Map([["cloud","The testkit cloud commands were removed in Testkit 0.4.0. Testkit no longer publishes runs to Testkit Cloud."]]);function t(r,o){return Object.freeze({enableJsonFlag:!0,...e[r],id:r.split(" "),load:o})}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{collectEvidence as c}from"../../app/evidence/index.js";import{renderEvidenceReport as i}from"../renderers/evidence/text.js";export default async function t({flags:e,json:o,log:n}){const r=await c({ci:!e.offline,cwd:process.cwd(),dir:e.dir,env:process.env});return o||n(i(r).join(`
|
|
2
|
+
`)),{ok:!0,...r}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function renderDestroyResult(
|
|
1
|
+
export function renderDestroyResult(e){return e.results.map(r=>`Destroyed ${r.name}`)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CI_NOT_RECORDED as v}from"../../../app/evidence/ci.js";import{describeContent as C}from"../../../app/evidence/ci-status.js";const d=20,R={added:"A",deleted:"D",modified:"M"};export function renderEvidenceReport(n){const e=[`Code: ${j(n.checkout)}`];for(const s of n.discovery.errors)e.push(`Discovery failed, so the files come from recorded runs only: ${s}`);for(const s of n.unreadable)e.push(`Testkit skipped results in ${s.location}: ${s.reason}`);const{summary:t}=n;e.push("",`Files: ${String(n.files.length)}. On this code and environment: ${String(t.passed)} passed, ${String(t.failed)} failed, ${String(t.skipped)} skipped, ${String(t.inconsistent)} inconsistent. Older code or environment only: ${String(t.stale)}. No result: ${String(t.unrun)}.`,...y(n.ci));const o=n.runs.filter(s=>s.exact);if(o.length>0){e.push("","Runs on this code and environment");for(const s of o)e.push(` ${h(s)}`,...g(s," "))}const i=s=>n.files.filter(r=>r.state===s);a(e,"Failed on this code and environment",i("failed"),p),a(e,"Passed and failed on this code and environment",i("inconsistent"),p);const u=n.runs.filter(s=>!s.exact);if(u.length>0){e.push("","Results from other code or environments");for(const s of u){const r=n.files.filter(f=>f.state==="stale"&&f.runs.includes(s.id));e.push(` ${h(s)}: ${N(r)}`),e.push(...g(s," "),...S(s.differences," "))}}const c=n.ci.kind==="found"?n.ci.statuses.filter(s=>s.exact):[],$=c.length>0&&c.every(s=>s.passed);if(c.length>0&&n.needsRun.length>0){const s=c.map(r=>`${r.context}: ${r.state}, ${r.outcome}`).join("; ");e.push("",`CI ${$?"passed":"ran"} the full suite on this code and recorded environment. ${s}.`)}return a(e,`${$?"No local result":"Needs a run for a result"} on this code: ${String(n.needsRun.length)}`,n.needsRun,s=>s),n.needsRun.length===0&&n.files.length>0&&n.discovery.errors.length===0&&e.push("","Nothing needs a run: every file has a result on this code and environment."),n.rerun!==null&&e.push(` ${T(n.rerun)??"The command's exact arguments are in --json, under rerun."}`),e.push("",`Compared: ${n.compared.join("; ")}.`,`Not compared: ${n.notCompared.join("; ")}.`),e}function j(n){if(n.kind==="unavailable")return`unknown, because ${n.reason}`;const e=n.base.slice(0,12),t=n.unidentified.length===0?"":`; can't identify ${n.unidentified.join(", ")}`;return n.changes.length===0?`commit ${e}, no changes${t}`:`commit ${e} with ${l(n.changes.length,"change")}: ${b(n.changes)}${t}`}function h(n){const e=n.durationMs===null?"":`, ${k(n.durationMs)}`,t=n.location===null?"":` in ${n.location}`,o=n.finishedAt===null?"":`, finished ${m(n.finishedAt)}`;return`run ${n.id}${t}, ${n.scope}, ${n.status}${e}${o}`}function m(n){const e=new Date(n);return Number.isNaN(e.getTime())?n:`${e.toISOString().slice(0,16).replace("T"," ")} UTC`}function k(n){const e=Math.round(n/1e3);return e<60?`${String(e)}s`:`${String(Math.floor(e/60))}m ${String(e%60)}s`}function T(n){return n.some(e=>e.includes("'"))?null:n.map(e=>/^[\w./@=:-]+$/u.test(e)?e:`'${e}'`).join(" ")}function g(n,e){return n.problems.map(t=>`${e}${t}`)}function p(n){return`${n.path} (run${n.runs.length===1?"":"s"} ${n.runs.join(", ")})`}function N(n){const e=new Map;for(const t of n)e.set(t.outcome??"no outcome",(e.get(t.outcome??"no outcome")??0)+1);return[...e].map(([t,o])=>`${String(o)} ${t}`).join(", ")}function S(n,e){const t=[];if(n.content.length>0&&t.push(`${e}code: ${b(n.content)}`),n.unidentified.length>0&&t.push(`${e}can't identify: ${n.unidentified.join(", ")}`),n.environment.length>0){const o=n.environment.map(i=>`${i.field} ${i.there} there, ${i.here} here`);t.push(`${e}environment: ${o.join("; ")}`)}for(const o of n.reasons)t.push(`${e}${o}`);return t}function y(n){if(n.kind==="unchecked")return[`CI: not checked, because ${n.reason}`];if(n.kind==="none")return[`CI: no Testkit commit status on the last ${l(n.commitsChecked,"commit")}`];const e=n.commitsBack===0?"HEAD":`${l(n.commitsBack,"commit")} before HEAD`,t=[`CI: commit ${n.commit.slice(0,12)} (${e})`];for(const o of n.statuses){const{facts:i}=o,u=i.product===""?"":` in ${i.product}`;t.push(` ${o.context}: ${o.state}, ${o.outcome}${o.createdAt===null?"":`, posted ${m(o.createdAt)}`}`,` ${i.scope} of ${C(i.content)}${u} on ${i.platform}-${i.arch} Node ${i.node}, Testkit ${i.testkit}`);const c=S(o.differences," ");t.push(...c.length===0?[" same code and recorded environment as this checkout"]:c),o.targetUrl!==null&&t.push(` ${o.targetUrl}`)}return t.push(` CI statuses don't record ${v.join(", ")}.`),t}function b(n){const e=n.slice(0,d).map(o=>`${R[o.change]} ${o.path}`),t=n.length-e.length;return t>0?`${e.join(", ")} and ${String(t)} more`:e.join(", ")}function a(n,e,t,o){if(t.length!==0){n.push("",e);for(const i of t.slice(0,d))n.push(` ${o(i)}`);t.length>d&&n.push(` and ${String(t.length-d)} more`)}}function l(n,e){return`${String(n)} ${e}${n===1?"":"s"}`}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildFailurePresentation as o}from"./formatting.js";import{renderIndentedBlock as
|
|
1
|
+
import{buildFailurePresentation as o}from"./formatting.js";import{renderIndentedBlock as l}from"../../terminal/layout.js";export function renderFailureBlock(i,{width:n=100}={}){const r=o({error:i.error??null,failureDetails:i.failureDetails??[],suiteError:null}),e=[];r.primary&&e.push(...l(r.primary,{width:n,indent:" "}));for(const t of r.details)e.push(...l(t,{width:n,indent:" "}));return e}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{describeLiveArtifactState as c,latestOperation as l}from"../../../app/status/runs.js";import{formatOperationSummary as m}from"../../../domain/operation-display.js";import{asUnknownRecord as a,displayString as u}from"../../../kernel/unknown-values.js";export function renderRunStatusLines(s){const t=["Testkit Run Status",` product: ${s.productDir}`],r=s.runs??[];if(r.length===0)t.push(" local runs: none");else{t.push(" local runs:");for(const n of r)t.push(` ${
|
|
1
|
+
import{describeLiveArtifactState as c,latestOperation as l}from"../../../app/status/runs.js";import{formatOperationSummary as m}from"../../../domain/operation-display.js";import{asUnknownRecord as a,displayString as u}from"../../../kernel/unknown-values.js";export function renderRunStatusLines(s){const t=["Testkit Run Status",` product: ${s.productDir}`],r=s.runs??[];if(r.length===0)t.push(" local runs: none");else{t.push(" local runs:");for(const n of r)t.push(` ${n.runId}: ${n.active?"active":n.terminal?"terminal":"stale"} pid=${String(n.pid)} status=${n.status||"unknown"}`)}const o=a(s.live),i=o?a(o.operation):null,p=o?a(o.timings):null,e=l(i?[i]:p?.operations??[]);if(o){const n=c({...s,live:o});t.push(" live artifact:"),t.push(` run: ${n.runId} status=${n.status}`),n.reason&&t.push(` reason: ${n.reason}`),e&&t.push(` operation: ${formatStatusOperation(e)}`)}return t}export function formatStatusOperation(s={}){const t=a(s)??{},r=m(t),o=u(t.name,"operation"),i=u(t.status,"unknown");return`${o} ${i}${r?` - ${r}`:""}`}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import
|
|
2
|
-
`);return;case"run.phase_started":$(n,
|
|
3
|
-
`);return;case"run.operation_finished":{if(!w(n,
|
|
4
|
-
`);return}case"run.local_service_starting":if(n!=="debug")return;
|
|
5
|
-
`);return;case"run.group_skipped":
|
|
6
|
-
`);return;case"run.task_planned_skip":
|
|
7
|
-
`);return;case"run.task_started":if(n!=="debug")return;
|
|
8
|
-
`);return;case"run.task_finished":{u+=1;const i=
|
|
9
|
-
`),o==="FAIL"){const
|
|
10
|
-
`)}return}case"run.error":
|
|
11
|
-
`),
|
|
12
|
-
`);return;case"run.runtime_error":
|
|
13
|
-
`),
|
|
14
|
-
`);return;case"run.summary":x(n,
|
|
15
|
-
`)}}}function $(
|
|
16
|
-
\u2500\u2500 ${
|
|
17
|
-
`)}function g(
|
|
18
|
-
`)}function S(
|
|
19
|
-
`);return}if(
|
|
20
|
-
`)}}function x(
|
|
21
|
-
`);for(const d of o)n.write(`${
|
|
22
|
-
`);return}const
|
|
1
|
+
import N from"node:path";import{buildDebugRunSummaryLines as F,buildRunSummaryData as I,formatDuration as m}from"./formatting.js";import{formatOperationSummary as A,isNeutralOperationStatus as M,isSuccessfulOperationStatus as O,operationStatusLabel as P}from"../../../domain/operation-display.js";import{boldRed as p,colorSectionLine as T,dim as c,statusLabel as a}from"../../terminal/colors.js";import{getTerminalWidth as D}from"../../terminal/layout.js";import{renderFailureBlock as U}from"./failure.js";export function createRunTextListener(e={}){const{outputMode:n="compact",stdout:r=process.stdout}=e;let u=0,s=0;return t=>{switch(t.type){case"run.total_files":s=t.total;return;case"run.debug":r.write(`${t.line}
|
|
2
|
+
`);return;case"run.phase_started":$(n,r,t.label);return;case"run.toolchain_resolved":g(n,r,t.service,t.runtimeLabel,t.summary);return;case"run.setup_finished":S(n,r,t.operation);return;case"run.operation_started":case"run.operation_progress":if(!w(n,t.operation))return;r.write(`${a("RUN")} INFRA ${b(t.operation)}
|
|
3
|
+
`);return;case"run.operation_finished":{if(!w(n,t.operation,{finished:!0}))return;const i=P(t.operation.status);r.write(`${a(i)} INFRA ${b(t.operation)}
|
|
4
|
+
`);return}case"run.local_service_starting":if(n!=="debug")return;r.write(`Starting ${t.runtimeLabel}:${t.service}: ${t.command}
|
|
5
|
+
`);return;case"run.group_skipped":r.write(`${a("SKIP")} ${t.group} ${t.reason}
|
|
6
|
+
`);return;case"run.task_planned_skip":r.write(`${a("SKIP")} ${f(t.group,t.testType,h(t.file))} ${c("0s")} ${l(t.reason||"skipped")}
|
|
7
|
+
`);return;case"run.task_started":if(n!=="debug")return;r.write(`${a("RUN")} ${f(t.targetName,t.testType,t.file)}
|
|
8
|
+
`);return;case"run.task_finished":{u+=1;const i=t.outcome,o=i.status==="not_run"?"NOT RUN":i.status==="skipped"||i.status==="cancelled"||i.status==="interrupted"?"SKIP":i.status==="failed"?"FAIL":"PASS",d=m(i.durationMs||0),y=i.status==="not_run"||i.status==="cancelled"||i.status==="interrupted"?` ${c(l(i.reason||"not run"))}`:"",L=n==="compact"&&s>0?`${c(`[${String(u)}/${String(s)}]`)} `:"";if(r.write(`${L}${a(o)} ${f(t.group,t.testType,h(t.file))} ${c(d)}${y}
|
|
9
|
+
`),o==="FAIL"){const R=U(i,{width:D(r,100)});for(const k of R)r.write(`${k}
|
|
10
|
+
`)}return}case"run.error":r.write(`${a("FAIL")} run error
|
|
11
|
+
`),r.write(` ${p(l(t.message))}
|
|
12
|
+
`);return;case"run.runtime_error":r.write(`${a("FAIL")} ${t.group} runtime error
|
|
13
|
+
`),r.write(` ${p(l(t.message||"runtime error"))}
|
|
14
|
+
`);return;case"run.summary":x(n,r,t.results,t.durationMs,t.runStatus);return;case"run.group_plans":case"run.warning":return}}}export function createTextStepProgressReporter(e={}){const{debug:n=!1,stdout:r=process.stdout}=e,u=n?"debug":"compact";return{outputMode:u,phaseStarted(s){$(u,r,s)},setupOperationFinished(s){s&&S(u,r,s)},toolchainResolved(s,t){g(u,r,s.serviceName,s.runtimeLabel,t)},writeDebugLine(s=""){r.write(`${s}
|
|
15
|
+
`)}}}function $(e,n,r){e==="debug"&&n.write(`
|
|
16
|
+
\u2500\u2500 ${r} \u2500\u2500
|
|
17
|
+
`)}function g(e,n,r,u,s){e==="debug"&&n.write(`[testkit] ${u}:${r} toolchain ${s}
|
|
18
|
+
`)}function S(e,n,r){if(r.status==="cached")return;const u=m(r.durationMs||0);if(r.status==="failed"){const s=l(r.error||r.summary||r.stage);n.write(`${a("FAIL")} SETUP ${r.serviceName} ${r.stage} ${c(u)} ${p(s)}
|
|
19
|
+
`);return}if(e==="compact"&&K(r)&&(r.durationMs||0)>=5e3){const s=l(r.summary||r.stage);n.write(`${a("RUN")} SETUP ${r.serviceName} ${s} ${c(u)}
|
|
20
|
+
`)}}function x(e,n,r,u,s){if(e==="debug"){const o=F(r,u,s);n.write(`
|
|
21
|
+
`);for(const d of o)n.write(`${T(d)}
|
|
22
|
+
`);return}const t=I(r,u,s),i=[["Result",t.result],["Passed",String(t.passed)],["Failed",String(t.failed)],["Skipped",String(t.skipped)],["Not run",String(t.notRun)],["Files",String(t.files)]];t.serviceErrors>0&&i.push(["Runtime errors",String(t.serviceErrors)]),i.push(["Duration",t.duration]),n.write(`
|
|
23
23
|
`),n.write(`Summary
|
|
24
24
|
`);for(const[o,d]of i)n.write(`${o}: ${d}
|
|
25
|
-
`)}function l(
|
|
25
|
+
`)}function l(e){return(e??"").replace(/\s+/g," ").trim().slice(0,180)}function h(e){return e.split(N.sep).join("/")}function K(e){return e.kind==="database-template"||e.kind==="runtime-prepare"}function w(e,n,{finished:r=!1}={}){return e==="debug"?!0:E(n)?r?O(n.status)?(n.durationMs||0)>=5e3:!M(n.status):!0:!1}function E(e){return e.kind==="runtime"}function b(e){const n=e.scope??{},r=j(e.name||e.kind||"operation"),u=_(n.serviceName),s=_(n.graphKey),t=[u?`service=${u}`:"",s?`graph=${s}`:""].filter(Boolean).join(" "),i=l(A(e)),o=e.durationMs==null?B(e.startedAt):m(e.durationMs||0);return[r,t?c(t):"",i?l(i):"",o?c(o):""].filter(Boolean).join(" ")}function _(e){return typeof e=="string"?e:""}function j(e){return e.replace(/^testkit\./,"").replaceAll("_","-")}function B(e){const n=Date.parse(e||"");return Number.isFinite(n)?m(Math.max(0,Date.now()-n)):""}function f(e,n,r){return n===void 0?`${e} ${r}`:`${e} ${n} ${r}`}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import s from"node:path";import{rejectUnknownKeys as n}from"../../kernel/known-keys.js";import{normalizeDiscoveryPath as c}from"./path-policy.js";const f=Object.keys({exclude:!0,roots:!0});export function normalizeDiscoveryConfig(t
|
|
1
|
+
import s from"node:path";import{rejectUnknownKeys as n}from"../../kernel/known-keys.js";import{normalizeDiscoveryPath as c}from"./path-policy.js";const f=Object.keys({exclude:!0,roots:!0});export function normalizeDiscoveryConfig(t){if(t==null)return{};if(typeof t!="object"||Array.isArray(t))throw new Error("testkit discovery config must be an object");n(t,f,"testkit discovery config");const r={};return Object.prototype.hasOwnProperty.call(t,"roots")&&(r.roots=o(Reflect.get(t,"roots"),"testkit discovery roots")),Object.prototype.hasOwnProperty.call(t,"exclude")&&(r.exclude=o(Reflect.get(t,"exclude"),"testkit discovery exclude")),r}function o(t,r){if(!Array.isArray(t))throw new Error(`${r} must be an array of relative paths`);const i=t.map(e=>{if(typeof e!="string"||e.trim().length===0)throw new Error(`${r} entries must be non-empty strings`);if(s.isAbsolute(e))throw new Error(`${r} entries must be relative paths`);return c(e)}).filter(Boolean);return[...new Set(i)]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import{randomUUID as m}from"node:crypto";import{loadConfigContext as f,resolveProductDir as y}from"./load.js";import{hasDiscoveryErrors as v,buildDiscoveryErrorFromDiagnostics as D}from"./discovery.js";import{discoverOwnedTests as h}from"./owned-discovery.js";import{loadTestkitConfig as w}from"./config-loader.js";import{createDiscoveryResult as M,finalizeDiscoveryResult as u,normalizeDiscoveryFilters as P,selectDiscoveredTests as C,validateRequestedGroup as F}from"./discovery-result.js";import{errorMessage as g}from"../../kernel/error-serialization.js";export async function discoverTests(i={}){return(await loadDiscoveryContext(i)).discovery}export async function loadDiscoveredConfigContext(i={}){const{group:o,...r}=i,e=await f({...r,configImportCacheSalt:m()}),{discovery:s}=await loadDiscoveryContext({dir:e.productDir,diagnostics:"error",group:o},e);return{context:e,discovery:s}}export async function loadDiscoveryContext(i={},o){const r=o?.productDir??y(process.cwd(),i.dir),e=P(i);if(o&&e.diagnosticsMode==="error"&&v(o.discovery.diagnostics))throw D(o.discovery.diagnostics);const s=o?{config:o.config,configFile:o.configFile,diagnostics:[]}:await k(r,e.diagnosticsMode),t=M({productDir:r,configFile:s.configFile,filters:e,diagnostics:s.diagnostics});if(!s.config)return{discovery:u(t),context:null};const n=o?{groups:o.groups,migration:null,plainFiles:[],problems:[],project:o.discovery}:await h(r,s.config,{selection:i.vitest,strict:e.diagnosticsMode==="error"}),d=n.project,l={owned:n,fileMetadataByPath:new Map(o?.groups.flatMap(a=>[...a.testkit.fileMetadataByPath])??[])};t.diagnostics.push(...d.diagnostics),F(e.groupFilter,n.groups);let c=o??null;try{c=o??await f({dir:r,preparedDiscovery:l,configContext:{config:s.config,configFile:s.configFile},discoveryOptions:{strict:e.diagnosticsMode==="error"}})}catch(a){if(e.diagnosticsMode==="error")throw a;t.diagnostics.push({code:"config_invalid",severity:"error",message:g(a)})}const p=C({productDir:r,rawDiscovery:d,declarations:n.groups,policies:(c?.groups??[]).map(B),fileMetadataByPath:l.fileMetadataByPath,filters:e});return{discovery:u({...t,...p}),context:c}}async function k(i,o){try{const{config:r,configFile:e}=await w(i);return{config:r,configFile:e,diagnostics:[]}}catch(r){if(o==="error")throw r;return{config:null,configFile:null,diagnostics:[{code:"config_invalid",severity:"error",message:g(r)}]}}}export function projectGroupTestPolicy(i){return{fileLocks:i.requirements.fileLocksByPath,fileMetadata:i.fileMetadataByPath,fileSkipReasons:i.skip?.fileReasonByPath??new Map,runtimeIsolation:{defaultPolicy:i.runtimeIsolation.defaultPolicy,files:i.runtimeIsolation.filePolicyByPath,suites:i.runtimeIsolation.suites},suiteLocks:i.requirements.suites,suiteSkips:i.skip?.suites??[]}}function B(i){return{...projectGroupTestPolicy(i.testkit),name:i.name,suites:i.suites}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import m from"path";import{TEST_TYPE_ORDER as F
|
|
1
|
+
import m from"path";import{TEST_TYPE_ORDER as F}from"../../domain/test-types.js";import{DEFAULT_RUNTIME_ISOLATION_POLICY as b}from"../../domain/runtime-isolation-contract.js";import{normalizeTypeValues as $,parseSuiteSelectors as x,suiteKey as w}from"../../domain/suite-selection.js";import{resolveExecutionProfile as D}from"../../engines/catalog.js";import{buildDiscoverySummary as O,emptyDiscoverySummary as M}from"./discovery-summary.js";import{readTestFileMetadata as V}from"./file-metadata.js";import{findSuiteLocks as _,findSuiteSkipReason as z,matchesSelectedTypes as A,matchesSuiteSelectors as L,resolveRuntimeIsolation as j}from"./test-selection.js";const B=10,d=String;export function createDiscoveryResult({configFile:e,diagnostics:i,filters:o,productDir:t}){return{schemaVersion:B,source:"testkit-discovery",product:{name:m.basename(t),directory:t},configFile:e?m.relative(t,e)||m.basename(e):null,filters:{group:o.groupFilter,types:o.typeValues,suiteSelectors:o.suiteSelectors.map(r=>r.raw),fileNames:o.fileNames,runnableOnly:o.runnableOnly,diagnostics:o.diagnosticsMode},groups:[],suites:[],files:[],diagnostics:[...i],summary:M()}}export function formatDisplayName(e){return d(e||"").split(/[-_/]+/g).filter(Boolean).map(i=>i.replace(/^\w/,o=>o.toUpperCase())).join(" ")}export function testTypeContract(e,i,o){return D({engineId:i,profileId:o,testType:e}),i==="playwright"?{authoringApi:"@gfdelarue/testkit/playwright"}:{}}export function selectDiscoveredTests({declarations:e,fileMetadataByPath:i,filters:o,policies:t,productDir:r,rawDiscovery:u}){const l=new Map(t.map(n=>[n.name,n])),f=new Map,g=new Map,N=[];for(const n of e)o.groupFilter&&n.name!==o.groupFilter||f.set(n.name,{name:n.name,roots:[...n.roots],uses:[...n.uses],source:n.source,suiteCount:0,fileCount:0,activeFileCount:0,skippedFileCount:0});for(const n of u.files){const s=n.testType;if(!A(s,o.typeValues)||!L(s,n.suiteName,o.suiteSelectors)||o.groupFilter&&n.serviceName!==o.groupFilter||o.fileNameSet.size>0&&!o.fileNameSet.has(n.filePath))continue;const p=i.get(n.filePath)??V(r,n.filePath);i.set(n.filePath,p);const a=l.get(n.serviceName),c=a?.suites.find(k=>k.testType===s&&k.name===n.suiteName);if(a&&(!c||c.engineId!==p.engineId||c.profileId!==p.profileId))throw new Error(`Normalized suite ${n.serviceName}/${w({name:n.suiteName,testType:s})} conflicts with file ${n.filePath}`);const C=a&&(p.skipReason||a.fileSkipReasons.get(n.filePath)||z(a,s,n.suiteName))||null;if(o.runnableOnly&&C)continue;const R=a?[...new Set([..._(a,s,n.suiteName),...a.fileLocks.get(n.filePath)||[],...p.locks])].sort():[],y=U({groupName:n.serviceName,suiteName:n.suiteName,testType:s,filePath:n.filePath,engineId:p.engineId,profileId:p.profileId,skipReason:C,locks:R,runtimeIsolation:a&&c?j(a,c,n.filePath):b});N.push(y);const S=[n.serviceName,s??"",n.suiteName].join("|"),v=g.get(S);v?v.files.push(y):g.set(S,{groupName:n.serviceName,testType:s,suiteName:n.suiteName,files:[y]})}const h=[...g.values()].map(n=>Y(n.groupName,n.testType,n.suiteName,n.files));for(const n of h){const s=f.get(n.group);if(s===void 0)throw new Error(`Discovered suite ${n.id} belongs to no declared group`);s.suiteCount+=1,s.fileCount+=n.fileCount,s.activeFileCount+=n.activeFileCount,s.skippedFileCount+=n.skippedFileCount}return{groups:[...f.values()].sort((n,s)=>n.name.localeCompare(s.name)),suites:h.sort(G),files:N.sort(K)}}function U(e){const i=testTypeContract(e.testType,e.engineId,e.profileId);return{id:T(e.groupName,e.testType,e.engineId,e.profileId,E(e.filePath)),path:e.filePath,displayName:fileDisplayName(e.filePath),group:e.groupName,suiteName:e.suiteName,suiteLabel:formatDisplayName(e.suiteName),...e.testType===void 0?{}:{testType:e.testType},engineId:e.engineId,profileId:e.profileId,skipped:!!e.skipReason,skipReason:e.skipReason,locks:e.locks,runtimeIsolation:e.runtimeIsolation,contract:i}}function Y(e,i,o,t){const r=q(e,i,o,t),u=t.filter(l=>l.skipped).length;return{id:T(e,i,r.engineId,r.profileId,o),group:e,name:o,displayName:formatDisplayName(o),...i===void 0?{}:{testType:i},engineId:r.engineId,profileId:r.profileId,suiteLabel:formatDisplayName(o),fileCount:t.length,activeFileCount:t.length-u,skippedFileCount:u,locks:[...new Set(t.flatMap(l=>l.locks))].sort(),filePaths:t.map(l=>l.path),contract:r.contract}}function q(e,i,o,t){const r=t[0];for(const u of t.slice(1))if(u.engineId!==r.engineId||u.profileId!==r.profileId)throw new Error(`Discovery suite ${e}/${w({name:o,testType:i})} mixes engine profiles ${r.engineId}/${r.profileId} and ${u.engineId}/${u.profileId}`);return{contract:r.contract,engineId:r.engineId,profileId:r.profileId}}export function finalizeDiscoveryResult(e){return{...e,diagnostics:e.diagnostics.sort(H),summary:O({diagnostics:e.diagnostics,files:e.files,groupCount:e.groups.length,suiteCount:e.suites.length})}}export function validateRequestedGroup(e,i){if(!e||i.some(t=>t.name===e))return;const o=i.map(t=>t.name).sort((t,r)=>t.localeCompare(r)).join(", ");throw new Error(`Group "${e}" not found. Available: ${o}`)}export function normalizeDiscoveryFilters(e){const i=$(I(e.type)),o=x(I(e.suite)),t=I(e.file).map(E);return{typeValues:i,suiteSelectors:o,fileNames:t,fileNameSet:new Set(t),groupFilter:e.group||null,runnableOnly:!!e.runnableOnly,diagnosticsMode:e.diagnostics==="error"?"error":"report"}}function I(e){return Array.isArray(e)?e.filter(o=>o!=null).map(o=>d(o)):e==null||e===""?[]:[d(e)]}function E(e){return d(e).split(m.sep).join("/").replace(/^\.\/+/,"")}export function fileDisplayName(e){const i=new RegExp(`\\.(${F.join("|")})\\.testkit\\.ts$`),o=m.posix.basename(e).replace(i,"");return formatDisplayName(o)}function T(e,i,o,t,r){return[e,...i===void 0?[]:[i],o,t,r].join("|")}function G(e,i){return e.group.localeCompare(i.group)||(e.testType??"").localeCompare(i.testType??"")||e.name.localeCompare(i.name)}function K(e,i){return e.group.localeCompare(i.group)||(e.testType??"").localeCompare(i.testType??"")||e.suiteName.localeCompare(i.suiteName)||e.path.localeCompare(i.path)}function H(e,i){return P(e.severity)-P(i.severity)||e.code.localeCompare(i.code)||(e.path||"").localeCompare(i.path||"")}function P(e){return e==="error"?1:2}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
`))}function
|
|
1
|
+
import w from"fs";import y from"path";import{recordWork as v}from"../../kernel/work-metrics.js";import{DEFAULT_DISCOVERY_EXCLUDES as T,normalizeDiscoveryPath as k,normalizeRelativePath as P,resolveDiscoveryRoots as C,shouldExcludeDiscoveryPath as D}from"./path-policy.js";const E=String,x="__testkit__";export const PLAIN_TEST_SUFFIX=/\.(?:test|spec)\.[cm]?[jt]sx?$/u;const N=[{suffix:".ui.testkit.ts",testType:"ui"}];export function scanTestFiles(o,t={}){const i=[],r=[],e=[],a=[...new Set([...T,...(t.exclude||[]).map(P)])],u=C(o,t.roots);for(;u.length>0;){const l=u.pop();if(l!==void 0){v("discovery.directoriesRead");for(const s of w.readdirSync(l,{withFileTypes:!0})){v("discovery.entriesVisited");const n=y.join(l,s.name);if(s.isSymbolicLink())continue;if(s.isDirectory()){const f=k(y.relative(o,n));if(D(f,a))continue;u.push(n);continue}if(!s.isFile())continue;const c=P(y.relative(o,n));if(!b(c)){c.endsWith(".testkit.ts")&&e.push(c);continue}c.split("/").includes(x)?i.push(c):r.push(c)}}}return{suiteFiles:i.sort((l,s)=>l.localeCompare(s)),legacyFiles:r.sort((l,s)=>l.localeCompare(s)),untypedFiles:e.sort((l,s)=>l.localeCompare(s))}}export function buildDiscoveryProject(o,t,i={}){const r=i.strict!==!1,e=L(o),a=new Map;for(const s of[...t].sort((n,c)=>n.filePath.localeCompare(c.filePath))){const n=s.filePath;if(s.kind==="ambiguous"){const _=[...s.owners].sort((F,S)=>F.localeCompare(S));e.push({code:"ambiguous_test",severity:"error",message:`Ambiguous test file: ${n} -> ${_.join(", ")}`,path:n,groupNames:_});continue}const c=b(n),f=`${s.owner}|${c?.testType??""}`,m=a.get(f)??new Map;a.set(f,m);const p=$(s.relativePath,c),h=p.join("/"),d=c===null?{}:{testType:c.testType},g=m.get(h)??{_suiteKey:h,_suitePath:p,serviceName:s.owner,suite:{files:[],name:"",...d},type:d};m.set(h,g),g.suite.files.push(n)}if(r&&hasDiscoveryErrors(e))throw buildDiscoveryErrorFromDiagnostics(e);const u=[],l={};for(const s of a.values()){j([...s.values()]);for(const{_suitePath:n,serviceName:c,suite:f,type:m}of s.values()){f.files.sort((p,h)=>p.localeCompare(h));for(const p of f.files)u.push({...m,filePath:p,serviceName:c,suiteName:f.name,suitePath:[...n]});(l[c]??=[]).push(f)}}for(const s of Object.values(l))s?.sort((n,c)=>(n.testType??"").localeCompare(c.testType??"")||n.name.localeCompare(c.name));return{suitesByService:l,files:u.sort((s,n)=>s.serviceName.localeCompare(n.serviceName)||(s.testType??"").localeCompare(n.testType??"")||s.suiteName.localeCompare(n.suiteName)||s.filePath.localeCompare(n.filePath)),diagnostics:e}}export function hasDiscoveryErrors(o=[]){return o.some(t=>t?.severity==="error")}export function buildDiscoveryErrorFromDiagnostics(o=[]){const t=["Filesystem discovery failed for one or more .testkit.ts files."],i=o.filter(e=>e.code==="legacy_path").map(e=>e.path),r=o.filter(e=>e.code==="ambiguous_test");if(i.length>0){t.push(""),t.push("Legacy test files outside __testkit__:");for(const e of i)t.push(`- ${e}`)}if(r.length>0){t.push(""),t.push("Ambiguous test files:");for(const e of r)t.push(`- ${e.path} -> ${e.groupNames.join(", ")}`)}return t.push(""),t.push('Expected test files to live under a "__testkit__" directory within a group root.'),new Error(t.join(`
|
|
2
|
+
`))}function j(o){const t=new Map;for(const i of o){const r=i._suitePath;let e="";for(let a=1;a<=r.length;a+=1){e=r.slice(-a).join("-");const u=t.get(e);if(!u){t.set(e,i._suiteKey);break}if(u===i._suiteKey)break;e=""}e||(e=r.join("-")),i.suite.name=e}if(new Set(o.map(({suite:i})=>i.name)).size!==o.length){const i=new Set;for(const r of o){const e=r._suitePath.join("-");let a=e;for(let u=2;i.has(a);u+=1)a=`${e}-${E(u)}`;i.add(a),r.suite.name=a}}}function $(o,t){const i=o.split("/").slice(0,-1);if(t===null)return i.length>0?i:[y.posix.basename(o).replace(PLAIN_TEST_SUFFIX,"")];const r=i.indexOf(x),e=r===-1?[]:[...i.slice(0,r),...i.slice(r+1,r+2)];return e.length>0?e:[y.posix.basename(o,t.suffix)]}function b(o){return N.find(t=>o.endsWith(t.suffix))||null}function L(o){return o.map(t=>({code:"legacy_path",severity:"error",message:`Legacy test file outside __testkit__: ${t}`,path:t}))}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import a from"fs";import p from"path";import{parseDotenv as h}from"../../process/files/dotenv.js";import{resolveServiceCwd as l}from"./paths.js";import{isUnknownArray as d}from"../../kernel/unknown-values.js";import{normalizeOptionalRecord as m}from"../../evidence/artifact-config.js";export function inferEnvFiles(e,n,t){const o=m(n,"service config"),u=m(t,"local config"),s=v(o?.envFile,"service.envFile"),c=w(o?.envFiles,"service.envFiles");if(s||c){const r=[];return s&&r.push(s),c&&r.push(...c),r}const i=[],f=v(u?.cwd,"service.local.cwd")??".";return f!=="."&&(i.push(p.posix.join(f,".env.testkit")),i.push(p.posix.join(f,".env"))),i.push(".env.testkit"),i.push(".env"),[...new Set(i)].map(r=>r.split(p.sep).join("/")).filter(r=>a.existsSync(l(e,r)))}export function loadServiceEnv(e,n){const t={};for(const o of n)Object.assign(t,h(l(e,o)));return t}export function withEnvFileValues(e){const n=e.testkit.envFiles??[];return n.length===0?e:{...e,testkit:{...e.testkit,serviceEnv:{...loadServiceEnv(e.productDir,n),...e.testkit.serviceEnv}}}}function v(e,n){if(e!=null){if(typeof e!="string"||e.trim().length===0)throw new Error(`${n} must be a non-empty string`);return e.trim()}}function w(e,n){if(e!=null){if(!d(e))throw new Error(`${n} must be an array of non-empty strings`);return e.map(t=>{if(typeof t!="string"||t.trim().length===0)throw new Error(`${n} must be an array of non-empty strings`);return t.trim()})}}
|