spawnfile 0.1.17 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/auth/importers.js +21 -5
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/profileStore.js +4 -1
- package/dist/auth/trainingGrokWriteback.d.ts +4 -0
- package/dist/auth/trainingGrokWriteback.js +119 -0
- package/dist/auth/trainingGrokWritebackLock.d.ts +5 -0
- package/dist/auth/trainingGrokWritebackLock.js +56 -0
- package/dist/cli/authCommands.js +9 -5
- package/dist/cli/compileBuildCommands.js +4 -0
- package/dist/cli/devCommands.js +4 -2
- package/dist/cli/downCommand.d.ts +2 -0
- package/dist/cli/downCommand.js +3 -0
- package/dist/cli/envFileOption.d.ts +18 -0
- package/dist/cli/envFileOption.js +20 -0
- package/dist/cli/lifecycleCommands.d.ts +1 -1
- package/dist/cli/lifecycleCommands.js +3 -1
- package/dist/cli/lifecyclePlan.d.ts +1 -0
- package/dist/cli/lifecyclePlan.js +2 -0
- package/dist/cli/paideiaDelegation.d.ts +2 -1
- package/dist/cli/paideiaDelegation.js +5 -4
- package/dist/cli/recordCommands.d.ts +17 -0
- package/dist/cli/recordCommands.js +43 -0
- package/dist/cli/releaseCommand.d.ts +6 -0
- package/dist/cli/releaseCommand.js +91 -0
- package/dist/cli/runCli.d.ts +7 -1
- package/dist/cli/runCli.js +11 -6
- package/dist/cli/runPublishCommands.js +5 -3
- package/dist/cli/trainCommand.js +73 -20
- package/dist/cli/upCommand.js +5 -1
- package/dist/cli/volumeCommands.d.ts +8 -0
- package/dist/cli/volumeCommands.js +53 -0
- package/dist/compiler/agentNodeMerge.js +3 -2
- package/dist/compiler/buildCompilePlan.js +4 -0
- package/dist/compiler/buildCompilePlanAgent.js +3 -1
- package/dist/compiler/buildCompilePlanTraversal.js +2 -0
- package/dist/compiler/buildCompilePlanTraversalHelpers.d.ts +2 -2
- package/dist/compiler/buildCompilePlanTraversalHelpers.js +4 -3
- package/dist/compiler/buildProject.d.ts +10 -0
- package/dist/compiler/buildProject.js +14 -2
- package/dist/compiler/cleanCompileOutput.d.ts +2 -0
- package/dist/compiler/cleanCompileOutput.js +14 -0
- package/dist/compiler/compilePlanHelpers.js +2 -0
- package/dist/compiler/compileProject.d.ts +10 -0
- package/dist/compiler/compileProject.js +29 -3
- package/dist/compiler/containerArtifactSummaries.js +1 -0
- package/dist/compiler/containerArtifacts.d.ts +4 -4
- package/dist/compiler/containerArtifacts.js +18 -34
- package/dist/compiler/containerArtifactsPlans.js +7 -0
- package/dist/compiler/containerArtifactsTypes.d.ts +7 -0
- package/dist/compiler/containerDaimonBrokerRender.d.ts +3 -1
- package/dist/compiler/containerDaimonBrokerRender.js +6 -3
- package/dist/compiler/containerDaimonGrokTaskClock.d.ts +5 -0
- package/dist/compiler/containerDaimonGrokTaskClock.js +29 -0
- package/dist/compiler/containerDaimonGrokWorkerProvisioning.d.ts +1 -1
- package/dist/compiler/containerDaimonGrokWorkerProvisioning.js +38 -16
- package/dist/compiler/containerDaimonGrokWorkerRender.d.ts +33 -8
- package/dist/compiler/containerDaimonGrokWorkerRender.js +18 -5
- package/dist/compiler/containerDaimonOwnershipGuardRender.d.ts +14 -1
- package/dist/compiler/containerDaimonOwnershipGuardRender.js +15 -2
- package/dist/compiler/containerDaimonUidEntrypointRender.js +6 -12
- package/dist/compiler/containerTargetResources.d.ts +10 -0
- package/dist/compiler/containerTargetResources.js +21 -13
- package/dist/compiler/containerWorkspaceResourceRender.js +4 -1
- package/dist/compiler/index.d.ts +2 -0
- package/dist/compiler/index.js +2 -0
- package/dist/compiler/mcpPlaceholders.d.ts +11 -0
- package/dist/compiler/mcpPlaceholders.js +58 -0
- package/dist/compiler/mcpServerInheritance.d.ts +18 -0
- package/dist/compiler/mcpServerInheritance.js +43 -0
- package/dist/compiler/moltnetArtifactTypes.d.ts +1 -0
- package/dist/compiler/moltnetArtifacts.js +1 -0
- package/dist/compiler/preflightCompilePlan.d.ts +9 -0
- package/dist/compiler/preflightCompilePlan.js +38 -0
- package/dist/compiler/recordingAgentFacts.d.ts +4 -0
- package/dist/compiler/recordingAgentFacts.js +35 -0
- package/dist/compiler/recordingCapability.d.ts +4 -0
- package/dist/compiler/recordingCapability.js +22 -0
- package/dist/compiler/recordingReport.d.ts +4 -0
- package/dist/compiler/recordingReport.js +18 -0
- package/dist/compiler/runProject.d.ts +2 -1
- package/dist/compiler/runProject.js +2 -0
- package/dist/compiler/surfaces.d.ts +1 -2
- package/dist/compiler/surfaces.js +0 -1
- package/dist/compiler/syncProjectAuth.js +2 -1
- package/dist/compiler/training/broker/clockProbe.d.ts +6 -0
- package/dist/compiler/training/broker/clockProbe.js +27 -0
- package/dist/compiler/training/broker/declaration.d.ts +4 -2
- package/dist/compiler/training/broker/declaration.js +4 -2
- package/dist/compiler/training/broker/entrypoint.js +33 -18
- package/dist/compiler/training/broker/evaluatorSecrets.d.ts +2 -0
- package/dist/compiler/training/broker/evaluatorSecrets.js +60 -0
- package/dist/compiler/training/broker/index.d.ts +3 -2
- package/dist/compiler/training/broker/index.js +3 -2
- package/dist/compiler/training/broker/main.js +1 -1
- package/dist/compiler/training/broker/paths.d.ts +96 -56
- package/dist/compiler/training/broker/paths.js +97 -89
- package/dist/compiler/training/broker/processes.d.ts +9 -0
- package/dist/compiler/training/broker/processes.js +4 -1
- package/dist/compiler/training/broker/projection.d.ts +3 -2
- package/dist/compiler/training/broker/projection.js +21 -9
- package/dist/compiler/training/broker/provisioning.d.ts +6 -15
- package/dist/compiler/training/broker/provisioning.js +71 -53
- package/dist/compiler/training/broker/registration.d.ts +3 -1
- package/dist/compiler/training/broker/registration.js +29 -21
- package/dist/compiler/training/broker/reset.d.ts +9 -0
- package/dist/compiler/training/broker/reset.js +71 -0
- package/dist/compiler/training/broker/runtime.d.ts +10 -13
- package/dist/compiler/training/broker/runtime.js +69 -115
- package/dist/compiler/training/broker/supervisor.d.ts +10 -11
- package/dist/compiler/training/broker/supervisor.js +22 -23
- package/dist/compiler/training/broker/taskClock.d.ts +5 -0
- package/dist/compiler/training/broker/taskClock.js +41 -0
- package/dist/compiler/training/compiledAgent.d.ts +21 -0
- package/dist/compiler/training/compiledAgent.js +62 -0
- package/dist/compiler/training/container/contract.d.ts +23 -0
- package/dist/compiler/training/container/contract.js +23 -5
- package/dist/compiler/training/container/evaluators.d.ts +48 -0
- package/dist/compiler/training/container/evaluators.js +50 -0
- package/dist/compiler/training/container/grokWriteback.d.ts +8 -0
- package/dist/compiler/training/container/grokWriteback.js +26 -0
- package/dist/compiler/training/container/index.d.ts +2 -1
- package/dist/compiler/training/container/index.js +2 -1
- package/dist/compiler/training/container/launch.d.ts +3 -0
- package/dist/compiler/training/container/launch.js +119 -28
- package/dist/compiler/training/container/nativeSlots.d.ts +14 -0
- package/dist/compiler/training/container/nativeSlots.js +13 -0
- package/dist/compiler/training/container/outputCopy.d.ts +2 -0
- package/dist/compiler/training/container/outputCopy.js +72 -0
- package/dist/compiler/training/container/outputVolume.d.ts +27 -0
- package/dist/compiler/training/container/outputVolume.js +161 -0
- package/dist/compiler/training/container/prepare.d.ts +3 -1
- package/dist/compiler/training/container/prepare.js +57 -12
- package/dist/compiler/training/container/security.d.ts +5 -3
- package/dist/compiler/training/container/security.js +15 -5
- package/dist/compiler/training/container/subjectIsolation.d.ts +5 -0
- package/dist/compiler/training/container/subjectIsolation.js +15 -0
- package/dist/compiler/training/context.d.ts +8 -0
- package/dist/compiler/training/context.js +12 -12
- package/dist/compiler/training/contract.d.ts +2 -0
- package/dist/compiler/training/contract.js +2 -0
- package/dist/compiler/training/execution.d.ts +417 -0
- package/dist/compiler/training/execution.js +160 -0
- package/dist/compiler/training/index.d.ts +8 -0
- package/dist/compiler/training/index.js +7 -0
- package/dist/compiler/training/moments.d.ts +80 -0
- package/dist/compiler/training/moments.js +118 -0
- package/dist/compiler/training/output.d.ts +2 -0
- package/dist/compiler/training/output.js +20 -0
- package/dist/compiler/training/plan/contract.d.ts +287 -0
- package/dist/compiler/training/plan/contract.js +49 -0
- package/dist/compiler/training/plan/index.d.ts +5 -0
- package/dist/compiler/training/plan/index.js +4 -0
- package/dist/compiler/training/plan/load.d.ts +9 -0
- package/dist/compiler/training/plan/load.js +153 -0
- package/dist/compiler/training/plan/references.d.ts +19 -0
- package/dist/compiler/training/plan/references.js +57 -0
- package/dist/compiler/training/plan/resolve.d.ts +4 -0
- package/dist/compiler/training/plan/resolve.js +73 -0
- package/dist/compiler/training/plan/targets.d.ts +11 -0
- package/dist/compiler/training/plan/targets.js +71 -0
- package/dist/compiler/training/plan/types.d.ts +36 -0
- package/dist/compiler/training/plan/types.js +1 -0
- package/dist/compiler/training/preparation/assets/Dockerfile +13 -2
- package/dist/compiler/training/preparation/assets/package-lock.json +2 -2
- package/dist/compiler/training/preparation/broker.d.ts +3 -3
- package/dist/compiler/training/preparation/broker.js +6 -4
- package/dist/compiler/training/preparation/contract.d.ts +27 -2
- package/dist/compiler/training/preparation/contract.js +23 -4
- package/dist/compiler/training/preparation/files.d.ts +5 -1
- package/dist/compiler/training/preparation/files.js +76 -15
- package/dist/compiler/training/preparation/image.js +9 -4
- package/dist/compiler/training/preparation/inputs.d.ts +1 -0
- package/dist/compiler/training/preparation/inputs.js +18 -10
- package/dist/compiler/training/preparation/prepare.d.ts +5 -0
- package/dist/compiler/training/preparation/prepare.js +66 -29
- package/dist/compiler/training/preparation/scratch.d.ts +6 -17
- package/dist/compiler/training/preparation/scratch.js +89 -23
- package/dist/compiler/training/repair/contract.d.ts +338 -4
- package/dist/compiler/training/repair/contract.js +2 -0
- package/dist/compiler/training/repair/index.d.ts +1 -1
- package/dist/compiler/training/repair/index.js +1 -1
- package/dist/compiler/training/repair/prepare.d.ts +155 -3
- package/dist/compiler/training/repair/prepare.js +6 -2
- package/dist/compiler/training/repair/witness.d.ts +152 -3
- package/dist/compiler/training/repair/witness.js +8 -2
- package/dist/compiler/training/resolution.d.ts +25 -0
- package/dist/compiler/training/resolution.js +69 -0
- package/dist/compiler/training/rewind/contract.d.ts +321 -0
- package/dist/compiler/training/rewind/contract.js +39 -0
- package/dist/compiler/training/rewind/deployedResource.d.ts +22 -0
- package/dist/compiler/training/rewind/deployedResource.js +19 -0
- package/dist/compiler/training/rewind/feed.d.ts +12 -0
- package/dist/compiler/training/rewind/feed.js +182 -0
- package/dist/compiler/training/rewind/feedContent.d.ts +6 -0
- package/dist/compiler/training/rewind/feedContent.js +38 -0
- package/dist/compiler/training/rewind/git.d.ts +8 -0
- package/dist/compiler/training/rewind/git.js +159 -0
- package/dist/compiler/training/rewind/gitArchive.d.ts +2 -0
- package/dist/compiler/training/rewind/gitArchive.js +7 -0
- package/dist/compiler/training/rewind/gitHistory.d.ts +3 -0
- package/dist/compiler/training/rewind/gitHistory.js +55 -0
- package/dist/compiler/training/rewind/gitProcess.d.ts +19 -0
- package/dist/compiler/training/rewind/gitProcess.js +81 -0
- package/dist/compiler/training/rewind/gitTree.d.ts +14 -0
- package/dist/compiler/training/rewind/gitTree.js +90 -0
- package/dist/compiler/training/rewind/history.d.ts +28 -0
- package/dist/compiler/training/rewind/history.js +239 -0
- package/dist/compiler/training/rewind/index.d.ts +18 -0
- package/dist/compiler/training/rewind/index.js +15 -0
- package/dist/compiler/training/rewind/inputs.d.ts +9 -0
- package/dist/compiler/training/rewind/inputs.js +67 -0
- package/dist/compiler/training/rewind/instant.d.ts +4 -0
- package/dist/compiler/training/rewind/instant.js +24 -0
- package/dist/compiler/training/rewind/leakCheck.d.ts +10 -0
- package/dist/compiler/training/rewind/leakCheck.js +56 -0
- package/dist/compiler/training/rewind/leakFeed.d.ts +5 -0
- package/dist/compiler/training/rewind/leakFeed.js +25 -0
- package/dist/compiler/training/rewind/leakFiles.d.ts +10 -0
- package/dist/compiler/training/rewind/leakFiles.js +106 -0
- package/dist/compiler/training/rewind/leakGit.d.ts +3 -0
- package/dist/compiler/training/rewind/leakGit.js +78 -0
- package/dist/compiler/training/rewind/leakGitPayload.d.ts +4 -0
- package/dist/compiler/training/rewind/leakGitPayload.js +57 -0
- package/dist/compiler/training/rewind/leakInputs.d.ts +7 -0
- package/dist/compiler/training/rewind/leakInputs.js +135 -0
- package/dist/compiler/training/rewind/leakLayout.d.ts +2 -0
- package/dist/compiler/training/rewind/leakLayout.js +17 -0
- package/dist/compiler/training/rewind/leakProvenance.d.ts +3 -0
- package/dist/compiler/training/rewind/leakProvenance.js +88 -0
- package/dist/compiler/training/rewind/materialize.d.ts +136 -0
- package/dist/compiler/training/rewind/materialize.js +212 -0
- package/dist/compiler/training/rewind/memory.d.ts +10 -0
- package/dist/compiler/training/rewind/memory.js +84 -0
- package/dist/compiler/training/rewind/memorySchemas.d.ts +87 -0
- package/dist/compiler/training/rewind/memorySchemas.js +50 -0
- package/dist/compiler/training/rewind/moltnet.d.ts +2 -0
- package/dist/compiler/training/rewind/moltnet.js +54 -0
- package/dist/compiler/training/rewind/moltnetJson.d.ts +13 -0
- package/dist/compiler/training/rewind/moltnetJson.js +180 -0
- package/dist/compiler/training/rewind/moltnetSchemas.d.ts +1226 -0
- package/dist/compiler/training/rewind/moltnetSchemas.js +75 -0
- package/dist/compiler/training/rewind/moltnetSqlite.d.ts +31 -0
- package/dist/compiler/training/rewind/moltnetSqlite.js +192 -0
- package/dist/compiler/training/rewind/moltnetSqliteData.d.ts +10 -0
- package/dist/compiler/training/rewind/moltnetSqliteData.js +113 -0
- package/dist/compiler/training/rewind/recordInput.d.ts +40 -0
- package/dist/compiler/training/rewind/recordInput.js +49 -0
- package/dist/compiler/training/rewind/recordedBundle.d.ts +4 -0
- package/dist/compiler/training/rewind/recordedBundle.js +58 -0
- package/dist/compiler/training/rewind/recordedCommit.d.ts +2 -0
- package/dist/compiler/training/rewind/recordedCommit.js +10 -0
- package/dist/compiler/training/rewind/select.d.ts +18 -0
- package/dist/compiler/training/rewind/select.js +64 -0
- package/dist/compiler/training/rewind/snapshotManifest.d.ts +42 -0
- package/dist/compiler/training/rewind/snapshotManifest.js +134 -0
- package/dist/compiler/training/rewind/taskClock.d.ts +12 -0
- package/dist/compiler/training/rewind/taskClock.js +40 -0
- package/dist/compiler/training/rewind/timestamps.d.ts +4 -0
- package/dist/compiler/training/rewind/timestamps.js +26 -0
- package/dist/compiler/training/rewind/types.d.ts +43 -0
- package/dist/compiler/training/rewind/types.js +1 -0
- package/dist/compiler/training/rewind/volumes.d.ts +12 -0
- package/dist/compiler/training/rewind/volumes.js +58 -0
- package/dist/compiler/training/selection.d.ts +7 -0
- package/dist/compiler/training/selection.js +15 -0
- package/dist/compiler/types.d.ts +6 -3
- package/dist/compiler/upProject.d.ts +1 -0
- package/dist/compiler/upProject.js +21 -5
- package/dist/compiler/workspaceBundleArtifacts.d.ts +16 -1
- package/dist/compiler/workspaceBundleArtifacts.js +69 -20
- package/dist/compiler/workspaceBundleCache.d.ts +23 -0
- package/dist/compiler/workspaceBundleCache.js +79 -0
- package/dist/compiler/workspaceBundleDependencies.d.ts +21 -0
- package/dist/compiler/workspaceBundleDependencies.js +122 -0
- package/dist/compiler/workspaceBundleFiles.d.ts +51 -0
- package/dist/compiler/workspaceBundleFiles.js +251 -0
- package/dist/compiler/workspaceBundleGenerated.d.ts +25 -0
- package/dist/compiler/workspaceBundleGenerated.js +103 -0
- package/dist/compiler/workspaceBundleGit.d.ts +64 -0
- package/dist/compiler/workspaceBundleGit.js +154 -0
- package/dist/compiler/workspaceBundleKey.d.ts +34 -0
- package/dist/compiler/workspaceBundleKey.js +17 -0
- package/dist/compiler/workspaceBundleResolve.d.ts +56 -0
- package/dist/compiler/workspaceBundleResolve.js +144 -0
- package/dist/compiler/workspaceBundleRun.d.ts +41 -0
- package/dist/compiler/workspaceBundleRun.js +79 -0
- package/dist/compiler/workspaceBundleTar.d.ts +47 -0
- package/dist/compiler/workspaceBundleTar.js +140 -0
- package/dist/compiler/workspaceBundleTree.d.ts +15 -0
- package/dist/compiler/workspaceBundleTree.js +58 -0
- package/dist/compiler/workspaceGitFetch.d.ts +41 -0
- package/dist/compiler/workspaceGitFetch.js +155 -0
- package/dist/compiler/workspaceGitPins.d.ts +33 -0
- package/dist/compiler/workspaceGitPins.js +127 -0
- package/dist/compiler/workspaceResources.d.ts +5 -1
- package/dist/compiler/workspaceResources.js +32 -5
- package/dist/deployment/activation.d.ts +3 -0
- package/dist/deployment/activation.js +12 -0
- package/dist/deployment/canaryCutover.d.ts +1 -1
- package/dist/deployment/candidateRecording.d.ts +8 -0
- package/dist/deployment/candidateRecording.js +22 -0
- package/dist/deployment/dockerManager.d.ts +5 -3
- package/dist/deployment/dockerManager.js +12 -2
- package/dist/deployment/downDeployment.d.ts +2 -0
- package/dist/deployment/downDeployment.js +6 -2
- package/dist/deployment/homeStore.d.ts +9 -1
- package/dist/deployment/homeStore.js +16 -53
- package/dist/deployment/index.d.ts +6 -0
- package/dist/deployment/index.js +6 -0
- package/dist/deployment/pidLock.d.ts +25 -0
- package/dist/deployment/pidLock.js +81 -0
- package/dist/deployment/record.d.ts +85 -1
- package/dist/deployment/record.js +11 -5
- package/dist/deployment/recordPublication.d.ts +15 -0
- package/dist/deployment/recordPublication.js +76 -0
- package/dist/deployment/recordedMemoryRetention.d.ts +18 -0
- package/dist/deployment/recordedMemoryRetention.js +27 -0
- package/dist/deployment/resourceLog.d.ts +140 -0
- package/dist/deployment/resourceLog.js +78 -0
- package/dist/distribution/buildDistributionReport.d.ts +3 -0
- package/dist/distribution/buildDistributionReport.js +5 -0
- package/dist/distribution/consumeImage.d.ts +3 -0
- package/dist/distribution/consumeImage.js +23 -31
- package/dist/distribution/consumeImageLifecycle.d.ts +4 -1
- package/dist/distribution/consumeImageLifecycle.js +14 -3
- package/dist/distribution/consumeImageSupport.d.ts +7 -0
- package/dist/distribution/consumeImageSupport.js +25 -0
- package/dist/distribution/distributionReportSchema.d.ts +47 -0
- package/dist/distribution/distributionReportSchema.js +20 -0
- package/dist/distribution/renderImageInterface.js +1 -1
- package/dist/distribution/types.d.ts +15 -0
- package/dist/filesystem/durableWrite.d.ts +10 -0
- package/dist/filesystem/durableWrite.js +87 -0
- package/dist/filesystem/index.d.ts +2 -0
- package/dist/filesystem/index.js +2 -0
- package/dist/filesystem/retainedJsonl.d.ts +23 -0
- package/dist/filesystem/retainedJsonl.js +123 -0
- package/dist/manifest/commandSchemas.d.ts +5 -0
- package/dist/manifest/commandSchemas.js +5 -0
- package/dist/manifest/executionSchemas.d.ts +20 -0
- package/dist/manifest/executionSchemas.js +13 -3
- package/dist/manifest/index.d.ts +2 -0
- package/dist/manifest/index.js +2 -0
- package/dist/manifest/loadManifest.js +3 -26
- package/dist/manifest/mcpSchemas.d.ts +61 -0
- package/dist/manifest/mcpSchemas.js +57 -11
- package/dist/manifest/memorySchemas.d.ts +1 -1
- package/dist/manifest/memorySchemas.js +1 -1
- package/dist/manifest/parseManifest.d.ts +7 -0
- package/dist/manifest/parseManifest.js +65 -0
- package/dist/manifest/renderSpawnfile.js +4 -1
- package/dist/manifest/renderSpawnfileWorkspace.js +6 -2
- package/dist/manifest/schemas.d.ts +1206 -132
- package/dist/manifest/schemas.js +14 -4
- package/dist/manifest/teamNetworkSchemas.d.ts +1 -1
- package/dist/manifest/trainingDataSchemas.d.ts +357 -0
- package/dist/manifest/trainingDataSchemas.js +31 -0
- package/dist/manifest/trainingSchemas.d.ts +49 -0
- package/dist/manifest/trainingSchemas.js +44 -0
- package/dist/manifest/volumeFeedSchemas.d.ts +52 -0
- package/dist/manifest/volumeFeedSchemas.js +104 -0
- package/dist/manifest/workspaceSchemas.d.ts +182 -5
- package/dist/manifest/workspaceSchemas.js +111 -7
- package/dist/recording/compose.d.ts +3 -0
- package/dist/recording/compose.js +93 -0
- package/dist/recording/deployment.d.ts +15 -0
- package/dist/recording/deployment.js +38 -0
- package/dist/recording/history.d.ts +16 -0
- package/dist/recording/history.js +47 -0
- package/dist/recording/index.d.ts +9 -0
- package/dist/recording/index.js +9 -0
- package/dist/recording/moment.d.ts +61 -0
- package/dist/recording/moment.js +36 -0
- package/dist/recording/parse.d.ts +15 -0
- package/dist/recording/parse.js +74 -0
- package/dist/recording/read.d.ts +8 -0
- package/dist/recording/read.js +61 -0
- package/dist/recording/store.d.ts +10 -0
- package/dist/recording/store.js +94 -0
- package/dist/recording/sync.d.ts +24 -0
- package/dist/recording/sync.js +126 -0
- package/dist/recording/types.d.ts +45 -0
- package/dist/recording/types.js +2 -0
- package/dist/release/drainControl.d.ts +54 -0
- package/dist/release/drainControl.js +137 -0
- package/dist/release/drainPhase.d.ts +37 -0
- package/dist/release/drainPhase.js +161 -0
- package/dist/release/index.d.ts +11 -0
- package/dist/release/index.js +11 -0
- package/dist/release/ledger.d.ts +75 -0
- package/dist/release/ledger.js +111 -0
- package/dist/release/notify.d.ts +58 -0
- package/dist/release/notify.js +126 -0
- package/dist/release/pending.d.ts +35 -0
- package/dist/release/pending.js +60 -0
- package/dist/release/postDeploy.d.ts +32 -0
- package/dist/release/postDeploy.js +62 -0
- package/dist/release/releaseDefaults.d.ts +9 -0
- package/dist/release/releaseDefaults.js +115 -0
- package/dist/release/releaseDocker.d.ts +38 -0
- package/dist/release/releaseDocker.js +94 -0
- package/dist/release/releaseTypes.d.ts +94 -0
- package/dist/release/releaseTypes.js +1 -0
- package/dist/release/runRelease.d.ts +9 -0
- package/dist/release/runRelease.js +218 -0
- package/dist/release/swap.d.ts +23 -0
- package/dist/release/swap.js +55 -0
- package/dist/release/types.d.ts +50 -0
- package/dist/release/types.js +29 -0
- package/dist/report/index.d.ts +1 -0
- package/dist/report/index.js +1 -0
- package/dist/report/recording.d.ts +57 -0
- package/dist/report/recording.js +26 -0
- package/dist/report/types.d.ts +58 -0
- package/dist/runtime/container.js +8 -13
- package/dist/runtime/daimon/adapter.js +15 -4
- package/dist/runtime/daimon/config.d.ts +4 -0
- package/dist/runtime/daimon/config.js +53 -4
- package/dist/runtime/daimon/contract-manifest.json +1 -0
- package/dist/runtime/daimon/contract-manifest.sha256 +1 -0
- package/dist/runtime/daimon/contractManifest.d.ts +59 -8
- package/dist/runtime/daimon/contractManifest.js +26 -10
- package/dist/runtime/daimon/grokTurnLimits.d.ts +25 -0
- package/dist/runtime/daimon/grokTurnLimits.js +51 -0
- package/dist/runtime/daimon/grokWorkerConfigBytes.d.ts +17 -0
- package/dist/runtime/daimon/grokWorkerConfigBytes.js +17 -0
- package/dist/runtime/daimon/grokWorkerContract.d.ts +5 -0
- package/dist/runtime/daimon/grokWorkerContract.js +13 -1
- package/dist/runtime/daimon/recording.d.ts +19 -0
- package/dist/runtime/daimon/recording.js +50 -0
- package/dist/runtime/daimon/scheduleAuthority.d.ts +8 -0
- package/dist/runtime/daimon/scheduleAuthority.js +19 -5
- package/dist/runtime/daimon/wakeMoments.d.ts +15 -0
- package/dist/runtime/daimon/wakeMoments.js +17 -0
- package/dist/runtime/daimonInstall.d.ts +3 -0
- package/dist/runtime/daimonInstall.js +25 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/install.d.ts +2 -0
- package/dist/runtime/install.js +1 -0
- package/dist/runtime/openclaw/adapter.js +3 -0
- package/dist/runtime/pi/adapter.js +3 -0
- package/dist/runtime/pi/containerTargets.js +6 -0
- package/dist/runtime/picoclaw/adapter.js +3 -0
- package/dist/runtime/registry.d.ts +3 -0
- package/dist/runtime/registry.js +13 -1
- package/dist/runtime/types.d.ts +15 -0
- package/dist/runtime/usageLedgerRead.d.ts +6 -0
- package/dist/runtime/usageLedgerRead.js +3 -3
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/recording.d.ts +8 -0
- package/dist/shared/recording.js +25 -0
- package/dist/target/dockerCommandExecutorCore.js +1 -1
- package/dist/target/dockerTargetBinding.js +2 -1
- package/dist/volume/feedLand.d.ts +31 -0
- package/dist/volume/feedLand.js +174 -0
- package/dist/volume/feedLandings.d.ts +45 -0
- package/dist/volume/feedLandings.js +111 -0
- package/dist/volume/feedLayout.d.ts +61 -0
- package/dist/volume/feedLayout.js +234 -0
- package/dist/volume/feedLock.d.ts +7 -0
- package/dist/volume/feedLock.js +131 -0
- package/dist/volume/feedManifest.d.ts +27 -0
- package/dist/volume/feedManifest.js +123 -0
- package/dist/volume/feedPrepare.d.ts +16 -0
- package/dist/volume/feedPrepare.js +228 -0
- package/dist/volume/feedProject.d.ts +39 -0
- package/dist/volume/feedProject.js +92 -0
- package/dist/volume/feedRecord.d.ts +55 -0
- package/dist/volume/feedRecord.js +148 -0
- package/dist/volume/feedRef.d.ts +43 -0
- package/dist/volume/feedRef.js +117 -0
- package/dist/volume/feedRefresh.d.ts +15 -0
- package/dist/volume/feedRefresh.js +187 -0
- package/dist/volume/feedSource.d.ts +66 -0
- package/dist/volume/feedSource.js +104 -0
- package/dist/volume/feedTarget.d.ts +70 -0
- package/dist/volume/feedTarget.js +9 -0
- package/dist/volume/feedTestKit.d.ts +11 -0
- package/dist/volume/feedTestKit.js +45 -0
- package/dist/volume/feedVerify.d.ts +18 -0
- package/dist/volume/feedVerify.js +104 -0
- package/dist/volume/index.d.ts +13 -0
- package/dist/volume/index.js +13 -0
- package/package.json +3 -3
- package/runtimes.yaml +8 -6
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { lstat, readFile, realpath, stat } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
6
|
+
import { listCommittedTree, listIndex, readStableSnapshot, readStatus, readStatusAgainstHead, resolveGitLocation, resolveCommit, resolveHead, streamBlobs } from "./workspaceBundleGit.js";
|
|
7
|
+
import { BundleTarWriter, normalizeBundleMode, WORKSPACE_BUNDLE_MAX_BYTES, WORKSPACE_BUNDLE_MAX_ENTRIES } from "./workspaceBundleTar.js";
|
|
8
|
+
const fail = (message) => {
|
|
9
|
+
throw new SpawnfileError("validation_error", message);
|
|
10
|
+
};
|
|
11
|
+
const SYMLINK = 0o120000, GITLINK = 0o160000;
|
|
12
|
+
const escapeRegExp = (value) => value.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
13
|
+
/**
|
|
14
|
+
* Exclude patterns match the path relative to the bundle root: `*` and `?`
|
|
15
|
+
* stay inside one segment, `**` crosses segments, and a pattern that matches
|
|
16
|
+
* a directory excludes everything beneath it.
|
|
17
|
+
*/
|
|
18
|
+
export const compileExcludePatterns = (patterns = []) => {
|
|
19
|
+
const expressions = patterns.map((pattern) => {
|
|
20
|
+
const trimmed = pattern.replace(/^\.\//u, "").replace(/\/+$/u, "");
|
|
21
|
+
if (!trimmed || trimmed.startsWith("/") || trimmed.split("/").some((segment) => segment === ".." || segment === ".")) {
|
|
22
|
+
fail(`Workspace bundle exclude pattern must be a relative path glob: ${pattern}`);
|
|
23
|
+
}
|
|
24
|
+
let source = "";
|
|
25
|
+
for (let index = 0; index < trimmed.length;) {
|
|
26
|
+
if (trimmed.startsWith("**/", index)) {
|
|
27
|
+
source += "(?:.*/)?";
|
|
28
|
+
index += 3;
|
|
29
|
+
}
|
|
30
|
+
else if (trimmed.startsWith("**", index)) {
|
|
31
|
+
source += ".*";
|
|
32
|
+
index += 2;
|
|
33
|
+
}
|
|
34
|
+
else if (trimmed[index] === "*") {
|
|
35
|
+
source += "[^/]*";
|
|
36
|
+
index += 1;
|
|
37
|
+
}
|
|
38
|
+
else if (trimmed[index] === "?") {
|
|
39
|
+
source += "[^/]";
|
|
40
|
+
index += 1;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
source += escapeRegExp(trimmed[index]);
|
|
44
|
+
index += 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return new RegExp(`^${source}$`, "u");
|
|
48
|
+
});
|
|
49
|
+
return (relativePath) => {
|
|
50
|
+
if (expressions.length === 0)
|
|
51
|
+
return false;
|
|
52
|
+
const segments = relativePath.split("/");
|
|
53
|
+
for (let length = 1; length <= segments.length; length += 1) {
|
|
54
|
+
const candidate = segments.slice(0, length).join("/");
|
|
55
|
+
if (expressions.some((expression) => expression.test(candidate)))
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
const validEntryPath = (relativePath) => {
|
|
62
|
+
if (!relativePath || relativePath.includes("\\") || relativePath.split("/").some((part) => !part || part === "." || part === "..")) {
|
|
63
|
+
fail(`Workspace bundle input path cannot be archived safely: ${relativePath}`);
|
|
64
|
+
}
|
|
65
|
+
return relativePath;
|
|
66
|
+
};
|
|
67
|
+
const refuseLinks = (relativePath, mode) => {
|
|
68
|
+
if (mode === SYMLINK)
|
|
69
|
+
fail(`Workspace bundle input is a symlink; exclude it or replace it with a file: ${relativePath}`);
|
|
70
|
+
if (mode === GITLINK)
|
|
71
|
+
fail(`Workspace bundle input is a git submodule; exclude it or declare it as its own resource: ${relativePath}`);
|
|
72
|
+
};
|
|
73
|
+
const relativeTo = (status, prefix) => status.map((entry) => ({ ...entry, path: entry.path.startsWith(prefix) ? entry.path.slice(prefix.length) : entry.path }));
|
|
74
|
+
const sortEntries = (entries) => entries.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
|
|
75
|
+
export const resolveBundleRoot = async (directory) => {
|
|
76
|
+
const info = await lstat(directory).catch(() => fail(`Workspace bundle root does not exist: ${directory}`));
|
|
77
|
+
if (!info.isDirectory())
|
|
78
|
+
fail(`Workspace bundle root must be a real directory, not a file or symlink: ${directory}`);
|
|
79
|
+
return realpath(directory);
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Release identity: the root must be clean against HEAD (ignoring excluded
|
|
83
|
+
* paths), and the identity is the committed tree's blob ids and modes. No
|
|
84
|
+
* input file is read.
|
|
85
|
+
*/
|
|
86
|
+
export const resolveReleaseFiles = async (directory, exclude = []) => {
|
|
87
|
+
const excluded = compileExcludePatterns(exclude);
|
|
88
|
+
const [{ prefix }, status] = await Promise.all([resolveGitLocation(directory), readStatusAgainstHead(directory)]);
|
|
89
|
+
if (!status.head)
|
|
90
|
+
fail(`Release workspace bundle requires a commit; the repository has none: ${directory}`);
|
|
91
|
+
const dirty = status.changed.map((changed) => changed.startsWith(prefix) ? changed.slice(prefix.length) : changed).filter((changed) => !excluded(changed));
|
|
92
|
+
if (dirty.length > 0) {
|
|
93
|
+
fail(`Release workspace bundle requires a clean commit; ${dirty.length} uncommitted change(s) under ${directory}, first: ${dirty[0]}`);
|
|
94
|
+
}
|
|
95
|
+
// The tree of exactly the commit status verified clean against; bytes come from its objects, so later edits cannot leak in.
|
|
96
|
+
// Status reads HEAD twice internally; if HEAD has moved since, it may have compared against a different commit.
|
|
97
|
+
const [tree, headNow] = await Promise.all([listCommittedTree(directory, status.head), resolveHead(directory)]);
|
|
98
|
+
if (headNow !== status.head)
|
|
99
|
+
fail(`Release workspace bundle HEAD moved while it was checked; retry: ${directory}`);
|
|
100
|
+
return { directory, entries: treeEntries(tree, excluded), mode: "release" };
|
|
101
|
+
};
|
|
102
|
+
const treeEntries = (tree, excluded) => {
|
|
103
|
+
const entries = [];
|
|
104
|
+
for (const entry of tree) {
|
|
105
|
+
if (excluded(entry.path))
|
|
106
|
+
continue;
|
|
107
|
+
refuseLinks(entry.path, entry.mode);
|
|
108
|
+
if (entry.type !== "blob")
|
|
109
|
+
fail(`Workspace bundle input has unsupported git type ${entry.type}: ${entry.path}`);
|
|
110
|
+
entries.push({ identity: `git:${entry.objectId}`, mode: normalizeBundleMode(entry.mode), objectId: entry.objectId, path: validEntryPath(entry.path) });
|
|
111
|
+
}
|
|
112
|
+
return sortEntries(entries);
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Release rule for recipe inputs that are named files rather than a tree:
|
|
116
|
+
* each must be tracked at HEAD with no staged, unstaged or untracked change.
|
|
117
|
+
*/
|
|
118
|
+
export const assertCommittedInputs = async (directory, names) => {
|
|
119
|
+
const [{ prefix }, status] = await Promise.all([resolveGitLocation(directory), readStatusAgainstHead(directory)]);
|
|
120
|
+
if (!status.head)
|
|
121
|
+
fail(`Release workspace bundle requires a commit; the repository has none: ${directory}`);
|
|
122
|
+
const wanted = new Set(names.map((name) => `${prefix}${name}`));
|
|
123
|
+
const dirty = status.changed.find((changed) => wanted.has(changed));
|
|
124
|
+
if (dirty)
|
|
125
|
+
fail(`Release workspace bundle requires a clean commit; ${dirty} has uncommitted changes`);
|
|
126
|
+
const committed = new Set((await listCommittedTree(directory, status.head)).map((entry) => entry.path));
|
|
127
|
+
const missing = names.find((name) => !committed.has(name));
|
|
128
|
+
if (missing)
|
|
129
|
+
fail(`Release workspace bundle input ${missing} is not committed in ${directory}`);
|
|
130
|
+
if (await resolveHead(directory) !== status.head)
|
|
131
|
+
fail(`Release workspace bundle HEAD moved while it was checked; retry: ${directory}`);
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* A pinned revision: identity and bytes come from the tree of `ref` (resolved
|
|
135
|
+
* to a commit once), so the work tree's state is irrelevant and no clean
|
|
136
|
+
* checkout is needed. The same in dev and release compiles.
|
|
137
|
+
*/
|
|
138
|
+
export const resolveCommittedFiles = async (directory, ref, exclude = []) => {
|
|
139
|
+
const commit = await resolveCommit(directory, ref);
|
|
140
|
+
if (!commit)
|
|
141
|
+
fail(`Workspace bundle ref ${ref} does not name a commit in ${directory}; fetch it first`);
|
|
142
|
+
return { directory, entries: treeEntries(await listCommittedTree(directory, commit), compileExcludePatterns(exclude)), mode: "release" };
|
|
143
|
+
};
|
|
144
|
+
const HASH_CONCURRENCY = 8;
|
|
145
|
+
const hashFile = async (filePath, spend) => {
|
|
146
|
+
const hash = createHash("sha256");
|
|
147
|
+
for await (const chunk of createReadStream(filePath)) {
|
|
148
|
+
spend(chunk.length);
|
|
149
|
+
hash.update(chunk);
|
|
150
|
+
}
|
|
151
|
+
return `sha256:${hash.digest("hex")}`;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Dev identity: the working tree as it is now. Files git reports as unchanged
|
|
155
|
+
* keep their index blob id; modified and untracked (not ignored) files are
|
|
156
|
+
* hashed from disk, so an uncommitted edit or a new file always moves the key.
|
|
157
|
+
*/
|
|
158
|
+
export const resolveDevFiles = async (directory, exclude = []) => {
|
|
159
|
+
const excluded = compileExcludePatterns(exclude), location = await resolveGitLocation(directory), { prefix } = location;
|
|
160
|
+
const [index, rawStatus] = await readStableSnapshot(location, () => Promise.all([listIndex(directory), readStatus(directory)]));
|
|
161
|
+
const status = relativeTo(rawStatus, prefix);
|
|
162
|
+
const untracked = status.filter((entry) => entry.index === "?").map((entry) => entry.path);
|
|
163
|
+
const changed = new Set(status.filter((entry) => entry.worktree !== " " && entry.worktree !== "?").map((entry) => entry.path));
|
|
164
|
+
const conflicted = new Set(index.filter((entry) => entry.stage !== 0).map((entry) => entry.path));
|
|
165
|
+
const entries = new Map(), toHash = [];
|
|
166
|
+
for (const entry of index) {
|
|
167
|
+
if (excluded(entry.path) || entries.has(entry.path))
|
|
168
|
+
continue;
|
|
169
|
+
if (changed.has(entry.path) || conflicted.has(entry.path)) {
|
|
170
|
+
toHash.push(entry.path);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
refuseLinks(entry.path, entry.mode);
|
|
174
|
+
entries.set(entry.path, { identity: `git:${entry.objectId}`, mode: normalizeBundleMode(entry.mode), objectId: entry.objectId, path: validEntryPath(entry.path) });
|
|
175
|
+
}
|
|
176
|
+
for (const relativePath of untracked) {
|
|
177
|
+
if (excluded(relativePath.replace(/\/$/u, "")))
|
|
178
|
+
continue;
|
|
179
|
+
if (relativePath.endsWith("/"))
|
|
180
|
+
fail(`Workspace bundle input contains a nested git repository; exclude it: ${relativePath}`);
|
|
181
|
+
toHash.push(relativePath);
|
|
182
|
+
}
|
|
183
|
+
const pending = [...new Set(toHash)];
|
|
184
|
+
let hashedBytes = 0;
|
|
185
|
+
// Bounded: at most HASH_CONCURRENCY files open, and the byte budget is spent before a file is read.
|
|
186
|
+
const hashNext = async () => {
|
|
187
|
+
for (let relativePath = pending.pop(); relativePath !== undefined; relativePath = pending.pop()) {
|
|
188
|
+
const filePath = path.join(directory, relativePath);
|
|
189
|
+
const info = await lstat(filePath).catch(() => undefined);
|
|
190
|
+
if (!info)
|
|
191
|
+
continue; // deleted in the work tree: not an input any more
|
|
192
|
+
if (info.isSymbolicLink())
|
|
193
|
+
refuseLinks(relativePath, SYMLINK);
|
|
194
|
+
if (!info.isFile())
|
|
195
|
+
fail(`Workspace bundle input is not a regular file: ${relativePath}`);
|
|
196
|
+
entries.set(relativePath, { identity: await hashFile(filePath, spend), mode: normalizeBundleMode(info.mode), path: validEntryPath(relativePath) });
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
// Budget the bytes actually streamed, so a file that grows after lstat cannot overrun it.
|
|
200
|
+
const spend = (bytes) => { hashedBytes += bytes; if (hashedBytes > WORKSPACE_BUNDLE_MAX_BYTES)
|
|
201
|
+
fail("Workspace bundle exceeds the maximum archive size"); };
|
|
202
|
+
await Promise.all(Array.from({ length: HASH_CONCURRENCY }, hashNext));
|
|
203
|
+
if (entries.size > WORKSPACE_BUNDLE_MAX_ENTRIES)
|
|
204
|
+
fail("Workspace bundle exceeds the maximum entry count");
|
|
205
|
+
return { directory, entries: sortEntries([...entries.values()]), mode: "dev" };
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Writes the archive for a resolved input set. Every `git:` entry is read from
|
|
209
|
+
* the object store, so its bytes are exactly the blob its identity names;
|
|
210
|
+
* every `sha256:` entry is read from disk and re-checked against its digest.
|
|
211
|
+
* The archive is therefore a pure function of the cache key.
|
|
212
|
+
*/
|
|
213
|
+
export const writeBundleFiles = async (input, outputPath) => {
|
|
214
|
+
const writer = await BundleTarWriter.create(outputPath);
|
|
215
|
+
const objects = input.entries.filter((entry) => entry.objectId !== undefined);
|
|
216
|
+
const disk = input.entries.filter((entry) => entry.objectId === undefined);
|
|
217
|
+
let next = 0;
|
|
218
|
+
const writeDiskBefore = async (limit) => {
|
|
219
|
+
for (; next < disk.length && (limit === undefined || disk[next].path < limit); next += 1) {
|
|
220
|
+
const entry = disk[next], filePath = path.join(input.directory, entry.path);
|
|
221
|
+
if ((await stat(filePath)).size > WORKSPACE_BUNDLE_MAX_BYTES)
|
|
222
|
+
fail("Workspace bundle exceeds the maximum archive size");
|
|
223
|
+
const bytes = await readFile(filePath);
|
|
224
|
+
if (`sha256:${createHash("sha256").update(bytes).digest("hex")}` !== entry.identity) {
|
|
225
|
+
fail(`Workspace bundle input changed while the bundle was built: ${entry.path}`);
|
|
226
|
+
}
|
|
227
|
+
await writer.begin(entry.path, bytes.length, entry.mode);
|
|
228
|
+
await writer.data(bytes);
|
|
229
|
+
await writer.end();
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
try {
|
|
233
|
+
if (objects.length > 0) {
|
|
234
|
+
await streamBlobs(input.directory, objects.map((entry) => entry.objectId), {
|
|
235
|
+
begin: async (index, size) => {
|
|
236
|
+
const entry = objects[index];
|
|
237
|
+
await writeDiskBefore(entry.path);
|
|
238
|
+
await writer.begin(entry.path, size, entry.mode);
|
|
239
|
+
},
|
|
240
|
+
data: (chunk) => writer.data(chunk),
|
|
241
|
+
end: () => writer.end()
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
await writeDiskBefore();
|
|
245
|
+
return await writer.finish();
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
await writer.abort();
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WorkspaceBundleBuild } from "../manifest/index.js";
|
|
2
|
+
import { type BundleFilesInput } from "./workspaceBundleFiles.js";
|
|
3
|
+
import { type BundleBuildContext, type BundleBuildPlan } from "./workspaceBundleKey.js";
|
|
4
|
+
type FilesSpec = NonNullable<WorkspaceBundleBuild["files"]>;
|
|
5
|
+
type GeneratedSpec = NonNullable<WorkspaceBundleBuild["generated"]>;
|
|
6
|
+
export declare const GENERATED_OUTPUT_PLACEHOLDER = "${output}";
|
|
7
|
+
/** Git identity for one declared file input: a pinned ref, a clean commit (release) or the work tree (dev). */
|
|
8
|
+
export declare const resolveFilesInput: (spec: FilesSpec, context: BundleBuildContext) => Promise<BundleFilesInput>;
|
|
9
|
+
export declare const planFilesBundle: (spec: FilesSpec, context: BundleBuildContext) => Promise<BundleBuildPlan>;
|
|
10
|
+
/**
|
|
11
|
+
* `generated`: a declared command writes an output directory from declared
|
|
12
|
+
* inputs. The key is the recipe above plus the target platform; the command
|
|
13
|
+
* runs only on a cache miss. After it runs the recipe is computed again and
|
|
14
|
+
* the build fails if any input or tool changed meanwhile, so output is never
|
|
15
|
+
* cached under a key that does not describe what it was built from.
|
|
16
|
+
* Undeclared inputs and host environment are not part of the key, so declare
|
|
17
|
+
* everything the command reads. Inputs come from the work tree the command
|
|
18
|
+
* reads, so `ref` is refused; with `image`, only `cwd` is mounted, so every
|
|
19
|
+
* input must live inside it. `captureTools: false` keys tools by argv only and
|
|
20
|
+
* runs nothing (used for pins).
|
|
21
|
+
*/
|
|
22
|
+
export declare const planGeneratedBundle: (spec: GeneratedSpec, context: BundleBuildContext, options?: {
|
|
23
|
+
captureTools?: boolean;
|
|
24
|
+
}) => Promise<BundleBuildPlan>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
5
|
+
import { resolveBundleRoot, resolveCommittedFiles, resolveDevFiles, resolveReleaseFiles, writeBundleFiles } from "./workspaceBundleFiles.js";
|
|
6
|
+
import { computeRecipeBundleKey, computeWorkspaceBundleKey, filesIdentity } from "./workspaceBundleKey.js";
|
|
7
|
+
import { bundleContainerName, runBundleCommand, runContainerStep } from "./workspaceBundleRun.js";
|
|
8
|
+
import { walkBuiltTree } from "./workspaceBundleTree.js";
|
|
9
|
+
const DEFAULT_TIMEOUT_SECONDS = 600;
|
|
10
|
+
const TOOL_TIMEOUT_MS = 120_000;
|
|
11
|
+
const CONTAINER_WORKDIR = "/spawnfile/work", CONTAINER_OUTPUT = "/spawnfile/output";
|
|
12
|
+
export const GENERATED_OUTPUT_PLACEHOLDER = "${output}";
|
|
13
|
+
/** The compile output is never an input, even when an input root contains it. */
|
|
14
|
+
const withOutputExcluded = (root, exclude, outputReal) => {
|
|
15
|
+
const inside = path.relative(root, outputReal);
|
|
16
|
+
return inside && !inside.startsWith("..") && !path.isAbsolute(inside) ? [...(exclude ?? []), inside.split(path.sep).join("/")] : exclude;
|
|
17
|
+
};
|
|
18
|
+
/** Git identity for one declared file input: a pinned ref, a clean commit (release) or the work tree (dev). */
|
|
19
|
+
export const resolveFilesInput = async (spec, context) => {
|
|
20
|
+
const root = await resolveBundleRoot(spec.root), exclude = withOutputExcluded(root, spec.exclude, context.outputReal);
|
|
21
|
+
if (spec.ref !== undefined)
|
|
22
|
+
return resolveCommittedFiles(root, spec.ref, exclude);
|
|
23
|
+
return context.identity === "release" ? resolveReleaseFiles(root, exclude) : resolveDevFiles(root, exclude);
|
|
24
|
+
};
|
|
25
|
+
export const planFilesBundle = async (spec, context) => {
|
|
26
|
+
const input = await resolveFilesInput(spec, context);
|
|
27
|
+
return { input: "files", key: computeWorkspaceBundleKey(input, context.platform), write: (temporaryTar) => writeBundleFiles(input, temporaryTar) };
|
|
28
|
+
};
|
|
29
|
+
const inside = (root, candidate) => {
|
|
30
|
+
const relative = path.relative(root, candidate);
|
|
31
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* The recipe a generated bundle is keyed by: every input's git identity, the
|
|
35
|
+
* command, `cwd`, the image, and each tool command with its exact captured
|
|
36
|
+
* stdout (or, without capture, just its argv). Captured output is hashed as
|
|
37
|
+
* bytes, so whitespace changes count.
|
|
38
|
+
*/
|
|
39
|
+
const generatedRecipe = async (spec, cwd, context, captureTools) => {
|
|
40
|
+
const inputs = await Promise.all(spec.inputs.map((input) => resolveFilesInput(input, context)));
|
|
41
|
+
const tools = await Promise.all((spec.tools ?? []).map(async (tool) => ({
|
|
42
|
+
argv: tool,
|
|
43
|
+
output: captureTools ? createHash("sha256").update(spec.image
|
|
44
|
+
? await runContainerStep({ argv: tool, dockerCommand: context.dockerCommand, image: spec.image, mounts: [[cwd, CONTAINER_WORKDIR]], name: bundleContainerName(), platform: context.platform, workdir: CONTAINER_WORKDIR }, TOOL_TIMEOUT_MS, `Generated bundle tool ${tool.join(" ")}`, cwd)
|
|
45
|
+
: await runBundleCommand({ argv: tool, cwd, timeoutMs: TOOL_TIMEOUT_MS }, `Generated bundle tool ${tool.join(" ")}`)).digest("hex") : null
|
|
46
|
+
})));
|
|
47
|
+
return {
|
|
48
|
+
command: spec.command, cwd, image: spec.image ?? null,
|
|
49
|
+
inputs: inputs.map((input) => ({ entries: filesIdentity(input), root: input.directory })),
|
|
50
|
+
tools
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* `generated`: a declared command writes an output directory from declared
|
|
55
|
+
* inputs. The key is the recipe above plus the target platform; the command
|
|
56
|
+
* runs only on a cache miss. After it runs the recipe is computed again and
|
|
57
|
+
* the build fails if any input or tool changed meanwhile, so output is never
|
|
58
|
+
* cached under a key that does not describe what it was built from.
|
|
59
|
+
* Undeclared inputs and host environment are not part of the key, so declare
|
|
60
|
+
* everything the command reads. Inputs come from the work tree the command
|
|
61
|
+
* reads, so `ref` is refused; with `image`, only `cwd` is mounted, so every
|
|
62
|
+
* input must live inside it. `captureTools: false` keys tools by argv only and
|
|
63
|
+
* runs nothing (used for pins).
|
|
64
|
+
*/
|
|
65
|
+
export const planGeneratedBundle = async (spec, context, options = {}) => {
|
|
66
|
+
if (spec.inputs.some((input) => input.ref !== undefined)) {
|
|
67
|
+
throw new SpawnfileError("validation_error", "Generated bundle inputs cannot pin a ref: the command reads the work tree");
|
|
68
|
+
}
|
|
69
|
+
const cwd = await resolveBundleRoot(spec.cwd), captureTools = options.captureTools ?? true;
|
|
70
|
+
if (spec.image && (await Promise.all(spec.inputs.map((input) => resolveBundleRoot(input.root)))).some((root) => !inside(cwd, root))) {
|
|
71
|
+
throw new SpawnfileError("validation_error", "Generated bundle inputs must live inside cwd when the command runs in an image: only cwd is mounted");
|
|
72
|
+
}
|
|
73
|
+
const recipe = await generatedRecipe(spec, cwd, context, captureTools);
|
|
74
|
+
return {
|
|
75
|
+
input: "generated",
|
|
76
|
+
key: computeRecipeBundleKey("generated", recipe, context.platform),
|
|
77
|
+
write: async (temporaryTar) => {
|
|
78
|
+
await mkdir(context.workRoot, { mode: 0o700, recursive: true });
|
|
79
|
+
const output = await mkdtemp(path.join(context.workRoot, "generated-"));
|
|
80
|
+
try {
|
|
81
|
+
const target = spec.image ? CONTAINER_OUTPUT : output;
|
|
82
|
+
const argv = spec.command.map((part) => part.split(GENERATED_OUTPUT_PLACEHOLDER).join(target));
|
|
83
|
+
const label = `Generated bundle command ${spec.command.join(" ")}`, timeoutMs = (spec.timeout_seconds ?? DEFAULT_TIMEOUT_SECONDS) * 1_000;
|
|
84
|
+
if (spec.image) {
|
|
85
|
+
await runContainerStep({
|
|
86
|
+
argv, dockerCommand: context.dockerCommand, env: { SPAWNFILE_BUNDLE_OUTPUT: target }, image: spec.image,
|
|
87
|
+
mounts: [[cwd, CONTAINER_WORKDIR], [output, CONTAINER_OUTPUT]], name: bundleContainerName(), platform: context.platform, workdir: CONTAINER_WORKDIR
|
|
88
|
+
}, timeoutMs, label, cwd);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
await runBundleCommand({ argv, cwd, env: { ...process.env, SPAWNFILE_BUNDLE_OUTPUT: target }, timeoutMs }, label);
|
|
92
|
+
}
|
|
93
|
+
if (JSON.stringify(await generatedRecipe(spec, cwd, context, captureTools)) !== JSON.stringify(recipe)) {
|
|
94
|
+
throw new SpawnfileError("validation_error", `${label}: inputs or tools changed while it ran; nothing was cached, retry`);
|
|
95
|
+
}
|
|
96
|
+
return await writeBundleFiles(await walkBuiltTree(output), temporaryTar);
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
await rm(output, { force: true, recursive: true });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export interface GitTreeEntry {
|
|
2
|
+
mode: number;
|
|
3
|
+
objectId: string;
|
|
4
|
+
path: string;
|
|
5
|
+
type: string;
|
|
6
|
+
}
|
|
7
|
+
export interface GitIndexEntry {
|
|
8
|
+
mode: number;
|
|
9
|
+
objectId: string;
|
|
10
|
+
path: string;
|
|
11
|
+
stage: number;
|
|
12
|
+
}
|
|
13
|
+
export interface GitLocation {
|
|
14
|
+
indexPath: string;
|
|
15
|
+
prefix: string;
|
|
16
|
+
topLevel: string;
|
|
17
|
+
}
|
|
18
|
+
/** The repository top level, this directory's path prefix inside it ("" at the top), and the index file. */
|
|
19
|
+
export declare const resolveGitLocation: (directory: string) => Promise<GitLocation>;
|
|
20
|
+
/**
|
|
21
|
+
* Runs `read` against one consistent index: the index file must be identical
|
|
22
|
+
* before and after, else a concurrent `git add` could pair an old index
|
|
23
|
+
* listing with a new status. Retries a few times, then fails.
|
|
24
|
+
*/
|
|
25
|
+
export declare const readStableSnapshot: <T>(location: GitLocation, read: () => Promise<T>) => Promise<T>;
|
|
26
|
+
/** Committed tree under `directory`: blob ids and modes from the tree objects alone, no blob or file reads. */
|
|
27
|
+
export declare const listCommittedTree: (directory: string, commit?: string) => Promise<GitTreeEntry[]>;
|
|
28
|
+
/** Index entries under `directory` (paths relative to it). */
|
|
29
|
+
export declare const listIndex: (directory: string) => Promise<GitIndexEntry[]>;
|
|
30
|
+
export interface GitStatusEntry {
|
|
31
|
+
index: string;
|
|
32
|
+
path: string;
|
|
33
|
+
worktree: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* `git status` under `directory`, paths relative to the repository top level
|
|
37
|
+
* (strip the location prefix). Untracked, not-ignored files appear
|
|
38
|
+
* individually as `??`; a nested repository appears once with a trailing "/".
|
|
39
|
+
* Ignored paths never appear.
|
|
40
|
+
*/
|
|
41
|
+
export declare const readStatus: (directory: string) => Promise<GitStatusEntry[]>;
|
|
42
|
+
/** The commit a revision names, or "" when it names none. */
|
|
43
|
+
export declare const resolveCommit: (directory: string, revision: string) => Promise<string>;
|
|
44
|
+
/** The HEAD commit id, or "" when the repository has none. */
|
|
45
|
+
export declare const resolveHead: (directory: string) => Promise<string>;
|
|
46
|
+
/**
|
|
47
|
+
* The commit `git status` compared against and every changed path under
|
|
48
|
+
* `directory` (repository-relative), from one porcelain v2 run. Pairing the
|
|
49
|
+
* two in one process means a concurrent commit can never split them.
|
|
50
|
+
*/
|
|
51
|
+
export declare const readStatusAgainstHead: (directory: string) => Promise<{
|
|
52
|
+
changed: string[];
|
|
53
|
+
head: string;
|
|
54
|
+
}>;
|
|
55
|
+
/**
|
|
56
|
+
* Streams blob contents for `objectIds`, in order, through `onBlob`. One
|
|
57
|
+
* `git cat-file --batch` process serves the whole tree; content never touches
|
|
58
|
+
* the work tree, so the result is a pure function of the committed objects.
|
|
59
|
+
*/
|
|
60
|
+
export declare const streamBlobs: (directory: string, objectIds: string[], handlers: {
|
|
61
|
+
begin: (index: number, size: number) => Promise<void>;
|
|
62
|
+
data: (chunk: Buffer) => Promise<void>;
|
|
63
|
+
end: () => Promise<void>;
|
|
64
|
+
}) => Promise<void>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
6
|
+
const run = promisify(execFile);
|
|
7
|
+
const MAX_BUFFER = 268_435_456;
|
|
8
|
+
// Never take git's optional index lock: a compile reads the repository, it does not maintain it.
|
|
9
|
+
// Never honour replacement refs: the cache key names object ids, so their bytes must be the objects'.
|
|
10
|
+
const gitEnv = () => ({ ...process.env, GIT_NO_REPLACE_OBJECTS: "1", GIT_OPTIONAL_LOCKS: "0" });
|
|
11
|
+
const git = async (cwd, args) => {
|
|
12
|
+
try {
|
|
13
|
+
const { stdout } = await run("git", args, { cwd, encoding: "buffer", env: gitEnv(), maxBuffer: MAX_BUFFER });
|
|
14
|
+
return stdout;
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
const stderr = error.stderr?.toString("utf8").trim();
|
|
18
|
+
throw new SpawnfileError("validation_error", `Workspace bundle git query failed in ${cwd}: git ${args[0]}${stderr ? `: ${stderr}` : ""}`);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const records = (output) => output.toString("utf8").split("\0").filter(Boolean);
|
|
22
|
+
/** The repository top level, this directory's path prefix inside it ("" at the top), and the index file. */
|
|
23
|
+
export const resolveGitLocation = async (directory) => {
|
|
24
|
+
const [topLevel, prefix = "", indexPath = ""] = (await git(directory, ["rev-parse", "--show-toplevel", "--show-prefix", "--git-path", "index"])).toString("utf8").split("\n");
|
|
25
|
+
if (!topLevel || !indexPath)
|
|
26
|
+
throw new SpawnfileError("validation_error", `Workspace bundle root is not inside a git work tree: ${directory}`);
|
|
27
|
+
return { indexPath: path.resolve(directory, indexPath), prefix, topLevel };
|
|
28
|
+
};
|
|
29
|
+
const indexStamp = async (location) => {
|
|
30
|
+
const index = await stat(location.indexPath).catch(() => undefined);
|
|
31
|
+
return JSON.stringify([index?.ino, index?.size, index?.mtimeMs]);
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Runs `read` against one consistent index: the index file must be identical
|
|
35
|
+
* before and after, else a concurrent `git add` could pair an old index
|
|
36
|
+
* listing with a new status. Retries a few times, then fails.
|
|
37
|
+
*/
|
|
38
|
+
export const readStableSnapshot = async (location, read) => {
|
|
39
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
40
|
+
const before = await indexStamp(location);
|
|
41
|
+
const value = await read();
|
|
42
|
+
if (before === await indexStamp(location))
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
throw new SpawnfileError("validation_error", `Workspace bundle inputs kept changing while they were read: ${location.topLevel}`);
|
|
46
|
+
};
|
|
47
|
+
/** Committed tree under `directory`: blob ids and modes from the tree objects alone, no blob or file reads. */
|
|
48
|
+
export const listCommittedTree = async (directory, commit = "HEAD") => records(await git(directory, ["ls-tree", "-r", "-z", commit, "--", "."])).map((record) => {
|
|
49
|
+
const tab = record.indexOf("\t");
|
|
50
|
+
const [mode, type, objectId] = record.slice(0, tab).split(" ");
|
|
51
|
+
return { mode: Number.parseInt(mode, 8), objectId: objectId, path: record.slice(tab + 1), type: type };
|
|
52
|
+
});
|
|
53
|
+
/** Index entries under `directory` (paths relative to it). */
|
|
54
|
+
export const listIndex = async (directory) => records(await git(directory, ["ls-files", "-s", "-z", "--", "."])).map((record) => {
|
|
55
|
+
const tab = record.indexOf("\t");
|
|
56
|
+
const [mode, objectId, stage] = record.slice(0, tab).split(" ");
|
|
57
|
+
return { mode: Number.parseInt(mode, 8), objectId: objectId, path: record.slice(tab + 1), stage: Number(stage) };
|
|
58
|
+
});
|
|
59
|
+
/**
|
|
60
|
+
* `git status` under `directory`, paths relative to the repository top level
|
|
61
|
+
* (strip the location prefix). Untracked, not-ignored files appear
|
|
62
|
+
* individually as `??`; a nested repository appears once with a trailing "/".
|
|
63
|
+
* Ignored paths never appear.
|
|
64
|
+
*/
|
|
65
|
+
export const readStatus = async (directory) => records(await git(directory, ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--no-renames", "--", "."])).map((record) => ({ index: record[0], path: record.slice(3), worktree: record[1] }));
|
|
66
|
+
/** The commit a revision names, or "" when it names none. */
|
|
67
|
+
export const resolveCommit = async (directory, revision) => revision.startsWith("-") ? "" : git(directory, ["rev-parse", "-q", "--verify", "--end-of-options", `${revision}^{commit}`]).then((output) => output.toString("utf8").trim(), () => "");
|
|
68
|
+
/** The HEAD commit id, or "" when the repository has none. */
|
|
69
|
+
export const resolveHead = async (directory) => resolveCommit(directory, "HEAD");
|
|
70
|
+
/**
|
|
71
|
+
* The commit `git status` compared against and every changed path under
|
|
72
|
+
* `directory` (repository-relative), from one porcelain v2 run. Pairing the
|
|
73
|
+
* two in one process means a concurrent commit can never split them.
|
|
74
|
+
*/
|
|
75
|
+
export const readStatusAgainstHead = async (directory) => {
|
|
76
|
+
let head = "";
|
|
77
|
+
const changed = [];
|
|
78
|
+
for (const record of records(await git(directory, ["status", "--porcelain=v2", "--branch", "-z", "--untracked-files=all", "--no-renames", "--", "."]))) {
|
|
79
|
+
if (record.startsWith("# branch.oid ")) {
|
|
80
|
+
const oid = record.slice(13);
|
|
81
|
+
head = /^[a-f0-9]{40,64}$/u.test(oid) ? oid : "";
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (record.startsWith("#"))
|
|
85
|
+
continue;
|
|
86
|
+
const fields = record.split(" "), skip = record[0] === "1" ? 8 : record[0] === "u" ? 10 : 1;
|
|
87
|
+
changed.push(fields.slice(skip).join(" "));
|
|
88
|
+
}
|
|
89
|
+
return { changed, head };
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Streams blob contents for `objectIds`, in order, through `onBlob`. One
|
|
93
|
+
* `git cat-file --batch` process serves the whole tree; content never touches
|
|
94
|
+
* the work tree, so the result is a pure function of the committed objects.
|
|
95
|
+
*/
|
|
96
|
+
export const streamBlobs = async (directory, objectIds, handlers) => {
|
|
97
|
+
const child = spawn("git", ["cat-file", "--batch"], { cwd: directory, env: gitEnv(), stdio: ["pipe", "pipe", "pipe"] });
|
|
98
|
+
const exited = new Promise((resolve, reject) => { child.once("error", reject); child.once("close", resolve); });
|
|
99
|
+
let stderr = "";
|
|
100
|
+
child.stderr.on("data", (chunk) => { stderr += chunk.toString("utf8"); });
|
|
101
|
+
child.stdin.on("error", () => undefined);
|
|
102
|
+
child.stdin.end(objectIds.map((id) => `${id}\n`).join(""));
|
|
103
|
+
let index = 0, remaining = -1, trailer = false, header = Buffer.alloc(0);
|
|
104
|
+
try {
|
|
105
|
+
for await (const raw of child.stdout) {
|
|
106
|
+
let chunk = raw;
|
|
107
|
+
while (chunk.length > 0) {
|
|
108
|
+
if (trailer) {
|
|
109
|
+
if (chunk[0] !== 0x0a)
|
|
110
|
+
throw new SpawnfileError("validation_error", "Workspace bundle blob stream is malformed");
|
|
111
|
+
chunk = chunk.subarray(1);
|
|
112
|
+
trailer = false;
|
|
113
|
+
await handlers.end();
|
|
114
|
+
index += 1;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (remaining < 0) {
|
|
118
|
+
const newline = chunk.indexOf(0x0a);
|
|
119
|
+
if (newline < 0) {
|
|
120
|
+
header = Buffer.concat([header, chunk]);
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
const line = Buffer.concat([header, chunk.subarray(0, newline)]).toString("utf8");
|
|
124
|
+
header = Buffer.alloc(0);
|
|
125
|
+
chunk = chunk.subarray(newline + 1);
|
|
126
|
+
const [objectId, type, size] = line.split(" ");
|
|
127
|
+
if (index >= objectIds.length || objectId !== objectIds[index] || type !== "blob" || !/^\d+$/u.test(size ?? "")) {
|
|
128
|
+
throw new SpawnfileError("validation_error", `Workspace bundle blob ${objectIds[index] ?? "?"} is not readable: ${line}`);
|
|
129
|
+
}
|
|
130
|
+
remaining = Number(size);
|
|
131
|
+
await handlers.begin(index, remaining);
|
|
132
|
+
}
|
|
133
|
+
const take = Math.min(remaining, chunk.length);
|
|
134
|
+
if (take > 0)
|
|
135
|
+
await handlers.data(chunk.subarray(0, take));
|
|
136
|
+
chunk = chunk.subarray(take);
|
|
137
|
+
remaining -= take;
|
|
138
|
+
if (remaining === 0) {
|
|
139
|
+
remaining = -1;
|
|
140
|
+
trailer = true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
child.kill();
|
|
147
|
+
await exited.catch(() => undefined);
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
const code = await exited;
|
|
151
|
+
if (code !== 0 || index !== objectIds.length || remaining !== -1 || trailer) {
|
|
152
|
+
throw new SpawnfileError("validation_error", `Workspace bundle blob stream ended early${stderr ? `: ${stderr.trim()}` : ""}`);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { BundleFilesInput, BundleIdentityMode } from "./workspaceBundleFiles.js";
|
|
2
|
+
import { type BundleTarSummary } from "./workspaceBundleTar.js";
|
|
3
|
+
export type BundleInputKind = "dependencies" | "files" | "generated";
|
|
4
|
+
/** Everything a build step needs that is not part of one bundle's declaration. */
|
|
5
|
+
export interface BundleBuildContext {
|
|
6
|
+
dockerCommand: string;
|
|
7
|
+
identity: BundleIdentityMode;
|
|
8
|
+
/** Real path of this compile's output directory, excluded from every input root that contains it. */
|
|
9
|
+
outputReal: string;
|
|
10
|
+
/** `linux/<arch>` of the target container. */
|
|
11
|
+
platform: string;
|
|
12
|
+
/** Private scratch space for installs and generated output. */
|
|
13
|
+
workRoot: string;
|
|
14
|
+
}
|
|
15
|
+
/** A bundle whose key is known; `write` builds its archive only on a cache miss. */
|
|
16
|
+
export interface BundleBuildPlan {
|
|
17
|
+
input: BundleInputKind;
|
|
18
|
+
key: string;
|
|
19
|
+
write: (temporaryTar: string) => Promise<BundleTarSummary>;
|
|
20
|
+
}
|
|
21
|
+
/** The identity of a file input set: every entry's path, mode and content identity. */
|
|
22
|
+
export declare const filesIdentity: (input: Pick<BundleFilesInput, "entries">) => Array<[string, number, string]>;
|
|
23
|
+
/**
|
|
24
|
+
* The cache key of a `files` bundle: every input file's path, mode and
|
|
25
|
+
* content identity, the archive writer that turns them into bytes, and the
|
|
26
|
+
* target platform.
|
|
27
|
+
*/
|
|
28
|
+
export declare const computeWorkspaceBundleKey: (input: Pick<BundleFilesInput, "entries">, platform: string) => string;
|
|
29
|
+
/**
|
|
30
|
+
* The cache key of a recipe bundle: its kind, the full recipe (input
|
|
31
|
+
* identities, commands, pinned images, captured tool versions), the archive
|
|
32
|
+
* writer and the target platform. Callers build `recipe` with a fixed key order.
|
|
33
|
+
*/
|
|
34
|
+
export declare const computeRecipeBundleKey: (input: Exclude<BundleInputKind, "files">, recipe: unknown, platform: string) => string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { WORKSPACE_BUNDLE_TAR_WRITER } from "./workspaceBundleTar.js";
|
|
3
|
+
const KEY_VERSION = "spawnfile.workspace-bundle-key.v1";
|
|
4
|
+
/** The identity of a file input set: every entry's path, mode and content identity. */
|
|
5
|
+
export const filesIdentity = (input) => input.entries.map((entry) => [entry.path, entry.mode, entry.identity]);
|
|
6
|
+
/**
|
|
7
|
+
* The cache key of a `files` bundle: every input file's path, mode and
|
|
8
|
+
* content identity, the archive writer that turns them into bytes, and the
|
|
9
|
+
* target platform.
|
|
10
|
+
*/
|
|
11
|
+
export const computeWorkspaceBundleKey = (input, platform) => createHash("sha256").update(JSON.stringify({ entries: filesIdentity(input), input: "files", platform, version: KEY_VERSION, writer: WORKSPACE_BUNDLE_TAR_WRITER })).digest("hex");
|
|
12
|
+
/**
|
|
13
|
+
* The cache key of a recipe bundle: its kind, the full recipe (input
|
|
14
|
+
* identities, commands, pinned images, captured tool versions), the archive
|
|
15
|
+
* writer and the target platform. Callers build `recipe` with a fixed key order.
|
|
16
|
+
*/
|
|
17
|
+
export const computeRecipeBundleKey = (input, recipe, platform) => createHash("sha256").update(JSON.stringify({ input, platform, recipe, version: KEY_VERSION, writer: WORKSPACE_BUNDLE_TAR_WRITER })).digest("hex");
|