spawnfile 0.1.11 → 0.1.13
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 +21 -3
- package/dist/auth/runtimeCredentials.js +18 -2
- package/dist/cli/devCommands.d.ts +3 -0
- package/dist/cli/devCommands.js +115 -0
- package/dist/cli/lifecycleCommands.js +103 -1
- package/dist/cli/modelCommands.js +3 -0
- package/dist/cli/resolveCommandInput.d.ts +19 -0
- package/dist/cli/resolveCommandInput.js +25 -0
- package/dist/cli/runCli.d.ts +10 -1
- package/dist/cli/runCli.js +41 -13
- package/dist/cli/runtimeCommands.js +1 -0
- package/dist/cli/statusCommand.d.ts +43 -0
- package/dist/cli/statusCommand.js +428 -0
- package/dist/cli/surfaceCommands.js +4 -0
- package/dist/compiler/agentSurfaces.js +2 -4
- package/dist/compiler/buildProject.d.ts +9 -1
- package/dist/compiler/buildProject.js +44 -5
- package/dist/compiler/compileProject.d.ts +2 -0
- package/dist/compiler/compileProject.js +22 -11
- package/dist/compiler/containerArtifacts.d.ts +1 -0
- package/dist/compiler/containerArtifacts.js +208 -32
- package/dist/compiler/containerArtifactsPlans.js +7 -4
- package/dist/compiler/containerArtifactsRender.d.ts +7 -1
- package/dist/compiler/containerArtifactsRender.js +31 -69
- package/dist/compiler/containerArtifactsTypes.d.ts +9 -0
- package/dist/compiler/containerEntrypointRender.js +22 -32
- package/dist/compiler/containerPackageRender.d.ts +15 -0
- package/dist/compiler/containerPackageRender.js +63 -0
- package/dist/compiler/containerTargetResources.js +3 -1
- package/dist/compiler/index.d.ts +1 -0
- package/dist/compiler/index.js +1 -0
- package/dist/compiler/moltnetArtifacts.js +21 -1
- package/dist/compiler/moltnetBinaries.d.ts +4 -1
- package/dist/compiler/moltnetBinaries.js +24 -3
- package/dist/compiler/moltnetClientConfig.d.ts +1 -1
- package/dist/compiler/moltnetClientConfig.js +14 -16
- package/dist/compiler/moltnetNodeConfig.js +2 -4
- package/dist/compiler/moltnetResolution.js +1 -1
- package/dist/compiler/moltnetRoomMemberships.js +1 -2
- package/dist/compiler/moltnetRuntimeConfig.d.ts +1 -1
- package/dist/compiler/moltnetRuntimeConfig.js +9 -10
- package/dist/compiler/networkBinding.d.ts +14 -0
- package/dist/compiler/networkBinding.js +34 -0
- package/dist/compiler/publishProject.d.ts +13 -0
- package/dist/compiler/publishProject.js +72 -0
- package/dist/compiler/runProject.d.ts +26 -12
- package/dist/compiler/runProject.js +109 -126
- package/dist/compiler/runProjectAuth.d.ts +5 -0
- package/dist/compiler/runProjectAuth.js +98 -0
- package/dist/compiler/runProjectDocker.d.ts +19 -0
- package/dist/compiler/runProjectDocker.js +202 -0
- package/dist/compiler/types.d.ts +1 -2
- package/dist/compiler/upProject.d.ts +6 -0
- package/dist/compiler/upProject.js +43 -8
- package/dist/compiler/view/buildOrganizationView.js +4 -1
- package/dist/compiler/view/declarations.d.ts +4 -0
- package/dist/compiler/view/declarations.js +104 -0
- package/dist/compiler/view/renderNetworks.js +1 -2
- package/dist/compiler/view/renderTree.js +6 -2
- package/dist/compiler/view/types.d.ts +64 -1
- package/dist/deployment/dockerInspect.d.ts +29 -0
- package/dist/deployment/dockerInspect.js +130 -0
- package/dist/deployment/dockerLabels.d.ts +16 -0
- package/dist/deployment/dockerLabels.js +32 -0
- package/dist/deployment/dockerLogs.d.ts +24 -0
- package/dist/deployment/dockerLogs.js +96 -0
- package/dist/deployment/dockerManager.d.ts +59 -0
- package/dist/deployment/dockerManager.js +76 -0
- package/dist/deployment/dockerProbeGateway.d.ts +16 -0
- package/dist/deployment/dockerProbeGateway.js +48 -0
- package/dist/deployment/dockerRecover.d.ts +17 -0
- package/dist/deployment/dockerRecover.js +129 -0
- package/dist/deployment/homeStore.d.ts +19 -0
- package/dist/deployment/homeStore.js +130 -0
- package/dist/deployment/index.d.ts +10 -0
- package/dist/deployment/index.js +10 -0
- package/dist/deployment/names.d.ts +3 -0
- package/dist/deployment/names.js +12 -0
- package/dist/deployment/record.d.ts +76 -0
- package/dist/deployment/record.js +174 -0
- package/dist/deployment/target.d.ts +35 -0
- package/dist/deployment/target.js +96 -0
- package/dist/dev/activity.d.ts +14 -0
- package/dist/dev/activity.js +48 -0
- package/dist/dev/docker.d.ts +16 -0
- package/dist/dev/docker.js +71 -0
- package/dist/dev/index.d.ts +2 -0
- package/dist/dev/index.js +2 -0
- package/dist/dev/project.d.ts +35 -0
- package/dist/dev/project.js +164 -0
- package/dist/distribution/buildDistributionReport.d.ts +22 -0
- package/dist/distribution/buildDistributionReport.js +51 -0
- package/dist/distribution/consumeImage.d.ts +29 -0
- package/dist/distribution/consumeImage.js +254 -0
- package/dist/distribution/consumeImageSupport.d.ts +18 -0
- package/dist/distribution/consumeImageSupport.js +63 -0
- package/dist/distribution/distributionReportSchema.d.ts +84 -0
- package/dist/distribution/distributionReportSchema.js +107 -0
- package/dist/distribution/dockerRunner.d.ts +5 -0
- package/dist/distribution/dockerRunner.js +21 -0
- package/dist/distribution/extractImage.d.ts +25 -0
- package/dist/distribution/extractImage.js +96 -0
- package/dist/distribution/fingerprint.d.ts +2 -0
- package/dist/distribution/fingerprint.js +14 -0
- package/dist/distribution/imageRef.d.ts +9 -0
- package/dist/distribution/imageRef.js +65 -0
- package/dist/distribution/imageRuntimeAuth.d.ts +19 -0
- package/dist/distribution/imageRuntimeAuth.js +71 -0
- package/dist/distribution/index.d.ts +24 -0
- package/dist/distribution/index.js +16 -0
- package/dist/distribution/preflight.d.ts +11 -0
- package/dist/distribution/preflight.js +65 -0
- package/dist/distribution/projectImageView.d.ts +8 -0
- package/dist/distribution/projectImageView.js +83 -0
- package/dist/distribution/projectName.d.ts +1 -0
- package/dist/distribution/projectName.js +12 -0
- package/dist/distribution/renderImageInterface.d.ts +30 -0
- package/dist/distribution/renderImageInterface.js +86 -0
- package/dist/distribution/tarReader.d.ts +10 -0
- package/dist/distribution/tarReader.js +69 -0
- package/dist/distribution/types.d.ts +80 -0
- package/dist/distribution/types.js +4 -0
- package/dist/distribution/verifyDistributionReport.d.ts +12 -0
- package/dist/distribution/verifyDistributionReport.js +29 -0
- package/dist/filesystem/paths.d.ts +6 -0
- package/dist/filesystem/paths.js +11 -0
- package/dist/manifest/loadManifest.js +22 -2
- package/dist/manifest/renderSpawnfile.js +2 -4
- package/dist/manifest/schemas.d.ts +17 -41
- package/dist/manifest/surfaceSchemas.d.ts +19 -47
- package/dist/manifest/surfaceSchemas.js +3 -6
- package/dist/report/createReport.d.ts +7 -1
- package/dist/report/createReport.js +28 -1
- package/dist/report/types.d.ts +43 -0
- package/dist/runtime/common.d.ts +2 -0
- package/dist/runtime/common.js +1 -1
- package/dist/runtime/container.d.ts +1 -0
- package/dist/runtime/container.js +82 -15
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/install.d.ts +1 -11
- package/dist/runtime/install.js +27 -30
- package/dist/runtime/openclaw/adapter.js +9 -1
- package/dist/runtime/openclaw/configAuth.d.ts +15 -0
- package/dist/runtime/openclaw/configAuth.js +46 -0
- package/dist/runtime/openclaw/runAuth.d.ts +6 -0
- package/dist/runtime/openclaw/runAuth.js +7 -55
- package/dist/runtime/openclaw/statusProbes.d.ts +2 -0
- package/dist/runtime/openclaw/statusProbes.js +31 -0
- package/dist/runtime/pi/adapter.d.ts +4 -0
- package/dist/runtime/pi/adapter.js +198 -0
- package/dist/runtime/pi/appActivitySource.d.ts +1 -0
- package/dist/runtime/pi/appActivitySource.js +87 -0
- package/dist/runtime/pi/appCoreSource.d.ts +1 -0
- package/dist/runtime/pi/appCoreSource.js +354 -0
- package/dist/runtime/pi/appPreludeSource.d.ts +1 -0
- package/dist/runtime/pi/appPreludeSource.js +103 -0
- package/dist/runtime/pi/appSource.d.ts +1 -0
- package/dist/runtime/pi/appSource.js +8 -0
- package/dist/runtime/pi/appTemplate.d.ts +28 -0
- package/dist/runtime/pi/appTemplate.js +144 -0
- package/dist/runtime/pi/runAuth.d.ts +2 -0
- package/dist/runtime/pi/runAuth.js +68 -0
- package/dist/runtime/picoclaw/adapter.js +10 -0
- package/dist/runtime/picoclaw/runAuth.d.ts +7 -0
- package/dist/runtime/picoclaw/runAuth.js +8 -68
- package/dist/runtime/picoclaw/statusProbes.d.ts +2 -0
- package/dist/runtime/picoclaw/statusProbes.js +100 -0
- package/dist/runtime/registry.d.ts +0 -5
- package/dist/runtime/registry.js +5 -12
- package/dist/runtime/statusProbes.d.ts +16 -0
- package/dist/runtime/statusProbes.js +41 -0
- package/dist/runtime/types.d.ts +53 -1
- package/dist/shared/errors.d.ts +6 -0
- package/dist/shared/errors.js +9 -0
- package/dist/status/buildStatus.d.ts +13 -0
- package/dist/status/buildStatus.js +266 -0
- package/dist/status/compileReport.d.ts +96 -0
- package/dist/status/compileReport.js +230 -0
- package/dist/status/compiledContainerObservations.d.ts +5 -0
- package/dist/status/compiledContainerObservations.js +63 -0
- package/dist/status/deploymentLogs.d.ts +12 -0
- package/dist/status/deploymentLogs.js +45 -0
- package/dist/status/deployments.d.ts +14 -0
- package/dist/status/deployments.js +121 -0
- package/dist/status/imageStatusReport.d.ts +10 -0
- package/dist/status/imageStatusReport.js +73 -0
- package/dist/status/index.d.ts +14 -0
- package/dist/status/index.js +14 -0
- package/dist/status/moltnetProbes.d.ts +21 -0
- package/dist/status/moltnetProbes.js +240 -0
- package/dist/status/registryDrift.d.ts +28 -0
- package/dist/status/registryDrift.js +119 -0
- package/dist/status/renderStatus.d.ts +2 -0
- package/dist/status/renderStatus.js +230 -0
- package/dist/status/runtimeProbes.d.ts +13 -0
- package/dist/status/runtimeProbes.js +111 -0
- package/dist/status/selectionSubjects.d.ts +8 -0
- package/dist/status/selectionSubjects.js +77 -0
- package/dist/status/selectors.d.ts +14 -0
- package/dist/status/selectors.js +90 -0
- package/dist/status/traversal.d.ts +6 -0
- package/dist/status/traversal.js +15 -0
- package/dist/status/types.d.ts +113 -0
- package/dist/status/types.js +1 -0
- package/package.json +9 -2
- package/runtimes.yaml +44 -21
- package/dist/runtime/tinyclaw/adapter.d.ts +0 -2
- package/dist/runtime/tinyclaw/adapter.js +0 -284
- package/dist/runtime/tinyclaw/runAuth.d.ts +0 -2
- package/dist/runtime/tinyclaw/runAuth.js +0 -49
- package/dist/runtime/tinyclaw/scaffold-assets/AGENTS.md +0 -160
- package/dist/runtime/tinyclaw/scaffold-assets/CLAUDE.md +0 -5
- package/dist/runtime/tinyclaw/scaffold-assets/SOUL.md +0 -177
- package/dist/runtime/tinyclaw/scaffold.d.ts +0 -2
- package/dist/runtime/tinyclaw/scaffold.js +0 -24
- package/dist/runtime/tinyclaw/schedules.d.ts +0 -9
- package/dist/runtime/tinyclaw/schedules.js +0 -62
- package/dist/runtime/tinyclaw/surfaces.d.ts +0 -8
- package/dist/runtime/tinyclaw/surfaces.js +0 -94
|
@@ -1,36 +1,50 @@
|
|
|
1
1
|
import { type ResolvedAuthProfile } from "../auth/index.js";
|
|
2
|
+
import { type DockerTargetExecFile } from "../deployment/index.js";
|
|
2
3
|
import { type CompileProjectOptions, type CompileProjectResult } from "./compileProject.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
containerName: string | null;
|
|
7
|
-
cwd: string;
|
|
8
|
-
detach: boolean;
|
|
9
|
-
envFilePath: string;
|
|
10
|
-
imageTag: string;
|
|
11
|
-
supportDirectory: string;
|
|
12
|
-
}
|
|
13
|
-
export type DockerRunRunner = (invocation: DockerRunInvocation) => Promise<void>;
|
|
4
|
+
import { runDockerContainer, type DockerRunInvocation, type DockerRunResult, type DockerRunRunner } from "./runProjectDocker.js";
|
|
5
|
+
export { runDockerContainer };
|
|
6
|
+
export type { DockerRunInvocation, DockerRunResult, DockerRunRunner };
|
|
14
7
|
export interface RunProjectOptions extends CompileProjectOptions {
|
|
15
8
|
authProfile?: string;
|
|
16
9
|
containerName?: string;
|
|
17
10
|
detach?: boolean;
|
|
11
|
+
deploymentName?: string;
|
|
18
12
|
dockerCommand?: string;
|
|
13
|
+
dockerContext?: string;
|
|
14
|
+
dockerHost?: string;
|
|
19
15
|
envFilePath?: string;
|
|
20
16
|
imageTag?: string;
|
|
21
17
|
runRunner?: DockerRunRunner;
|
|
18
|
+
targetExecFile?: DockerTargetExecFile;
|
|
22
19
|
}
|
|
23
20
|
export interface RunProjectResult extends CompileProjectResult {
|
|
24
21
|
authProfileName: string | null;
|
|
25
22
|
containerName: string | null;
|
|
23
|
+
deploymentRecordPath?: string | null;
|
|
26
24
|
imageTag: string;
|
|
27
25
|
}
|
|
28
26
|
export declare const createDockerRunInvocation: (compileResult: CompileProjectResult, imageTag: string, options?: {
|
|
29
27
|
authProfile?: ResolvedAuthProfile | null;
|
|
30
28
|
containerName?: string;
|
|
31
29
|
detach?: boolean;
|
|
30
|
+
deploymentName?: string;
|
|
32
31
|
dockerCommand?: string;
|
|
32
|
+
dockerContext?: string;
|
|
33
|
+
dockerHost?: string;
|
|
33
34
|
envFilePath?: string;
|
|
34
35
|
}) => Promise<DockerRunInvocation>;
|
|
35
|
-
|
|
36
|
+
interface ResolvedRunDeploymentOptions {
|
|
37
|
+
authProfile?: string;
|
|
38
|
+
containerName?: string;
|
|
39
|
+
deploymentName?: string;
|
|
40
|
+
dockerCommand?: string;
|
|
41
|
+
dockerContext?: string;
|
|
42
|
+
dockerHost?: string;
|
|
43
|
+
envFilePath?: string;
|
|
44
|
+
imageTag?: string;
|
|
45
|
+
targetExecFile?: DockerTargetExecFile;
|
|
46
|
+
}
|
|
47
|
+
export declare const resolveDetachedDeploymentOptions: (outputDirectory: string, options: ResolvedRunDeploymentOptions & {
|
|
48
|
+
detach?: boolean;
|
|
49
|
+
}) => Promise<ResolvedRunDeploymentOptions>;
|
|
36
50
|
export declare const runProject: (inputPath: string, options?: RunProjectOptions) => Promise<RunProjectResult>;
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { mkdtemp } from "node:fs/promises";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { SpawnfileError } from "../shared/index.js";
|
|
4
|
+
import { requireAuthProfile } from "../auth/index.js";
|
|
5
|
+
import { ensureDirectory, fileExists, removeDirectory, writeUtf8File } from "../filesystem/index.js";
|
|
6
|
+
import { appendDockerLabelArgs, createDockerDeploymentLabels, createDockerDeploymentUnitId, createDockerProjectLabel, dockerContextNameForTarget, dockerHostValueForTarget, normalizeDeploymentName, readDeploymentRecordFromOutput, resolveDeploymentRecordPath, verifyDockerDeploymentTarget, writeDockerDeploymentRecordForRun } from "../deployment/index.js";
|
|
7
|
+
import { DEFAULT_OUTPUT_DIRECTORY, SpawnfileError } from "../shared/index.js";
|
|
9
8
|
import { compileProject } from "./compileProject.js";
|
|
10
|
-
import { createDefaultImageTag } from "./buildProject.js";
|
|
9
|
+
import { createDefaultImageTag, resolveDockerBuildArchitecture } from "./buildProject.js";
|
|
11
10
|
import { slugify } from "./helpers.js";
|
|
12
|
-
import {
|
|
11
|
+
import { runDockerContainer } from "./runProjectDocker.js";
|
|
12
|
+
import { assertDeclaredModelAuthSatisfied, assertRunEnvironmentSatisfied, prepareRuntimeAuthMounts, readRunEnvFile, renderDockerEnvFile, resolveAuthMountArgs, resolveRunEnvironment } from "./runProjectAuth.js";
|
|
13
|
+
export { runDockerContainer };
|
|
13
14
|
const resolveImageTagRoot = (inputPath) => {
|
|
14
15
|
const resolvedPath = path.resolve(inputPath);
|
|
15
16
|
return path.basename(resolvedPath).toLowerCase() === "spawnfile"
|
|
@@ -20,100 +21,6 @@ const createDefaultContainerName = (imageTag) => {
|
|
|
20
21
|
const containerName = slugify(imageTag.replaceAll("/", "-").replaceAll(":", "-"));
|
|
21
22
|
return containerName || "spawnfile-run";
|
|
22
23
|
};
|
|
23
|
-
const getImportMountTargetName = (kind) => kind === "claude-code" ? ".claude" : ".codex";
|
|
24
|
-
const createGeneratedRuntimeSecret = (secretName) => {
|
|
25
|
-
if (secretName === "OPENCLAW_GATEWAY_TOKEN" || secretName === "OPENCLAW_HOOKS_TOKEN") {
|
|
26
|
-
return randomBytes(24).toString("hex");
|
|
27
|
-
}
|
|
28
|
-
return null;
|
|
29
|
-
};
|
|
30
|
-
const hasEnvValue = (env, name) => typeof env[name] === "string" && env[name].length > 0;
|
|
31
|
-
const collectMissingRequiredSecrets = (containerReport, env, coveredModelSecrets) => {
|
|
32
|
-
const missing = new Set();
|
|
33
|
-
for (const secretName of containerReport.secrets_required) {
|
|
34
|
-
if (hasEnvValue(env, secretName)) {
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
if (!containerReport.model_secrets_required.includes(secretName)) {
|
|
38
|
-
missing.add(secretName);
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
const requiredInstances = (containerReport.runtime_instances ?? []).filter((instance) => instance.model_secrets_required.includes(secretName));
|
|
42
|
-
const isCoveredEverywhere = requiredInstances.length > 0 &&
|
|
43
|
-
requiredInstances.every((instance) => coveredModelSecrets.has(`${instance.id}:${secretName}`));
|
|
44
|
-
if (!isCoveredEverywhere) {
|
|
45
|
-
missing.add(secretName);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return [...missing].sort();
|
|
49
|
-
};
|
|
50
|
-
const resolveRunEnvironment = (containerReport, authProfile, envFileEnv = {}) => {
|
|
51
|
-
const env = {
|
|
52
|
-
...(authProfile?.env ?? {}),
|
|
53
|
-
...envFileEnv
|
|
54
|
-
};
|
|
55
|
-
for (const name of new Set([...Object.keys(env), ...containerReport.secrets_required])) {
|
|
56
|
-
const processValue = process.env[name];
|
|
57
|
-
if (typeof processValue === "string" && processValue.length > 0) {
|
|
58
|
-
env[name] = processValue;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
for (const name of containerReport.runtime_secrets_required) {
|
|
62
|
-
if (hasEnvValue(env, name)) {
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
const generatedValue = createGeneratedRuntimeSecret(name);
|
|
66
|
-
if (generatedValue) {
|
|
67
|
-
env[name] = generatedValue;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
for (const [name, value] of Object.entries(env)) {
|
|
71
|
-
if (value.includes("\n")) {
|
|
72
|
-
throw new SpawnfileError("validation_error", `Env value for ${name} contains a newline and cannot be written to a Docker env file`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return env;
|
|
76
|
-
};
|
|
77
|
-
const assertRunEnvironmentSatisfied = (containerReport, env, coveredModelSecrets) => {
|
|
78
|
-
const missing = collectMissingRequiredSecrets(containerReport, env, coveredModelSecrets);
|
|
79
|
-
if (missing.length > 0) {
|
|
80
|
-
throw new SpawnfileError("validation_error", `Missing required runtime env: ${missing.sort().join(", ")}`);
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
const renderDockerEnvFile = (env) => `${Object.entries(env)
|
|
84
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
85
|
-
.map(([name, value]) => `${name}=${value}`)
|
|
86
|
-
.join("\n")}\n`;
|
|
87
|
-
const readRunEnvFile = async (envFilePath) => {
|
|
88
|
-
if (!envFilePath) {
|
|
89
|
-
return {};
|
|
90
|
-
}
|
|
91
|
-
try {
|
|
92
|
-
return parseEnvFile(await readUtf8File(envFilePath));
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
96
|
-
throw new SpawnfileError("validation_error", `Unable to read env file ${envFilePath}: ${reason}`);
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
const resolveAuthMountArgs = async (containerReport, authProfile) => {
|
|
100
|
-
if (!authProfile || containerReport.runtime_homes.length === 0) {
|
|
101
|
-
return [];
|
|
102
|
-
}
|
|
103
|
-
const args = [];
|
|
104
|
-
for (const [kind, entry] of Object.entries(authProfile.imports)) {
|
|
105
|
-
if (!entry) {
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
if (!(await fileExists(entry.path))) {
|
|
109
|
-
throw new SpawnfileError("validation_error", `Imported auth path for ${kind} does not exist: ${entry.path}`);
|
|
110
|
-
}
|
|
111
|
-
for (const runtimeHome of containerReport.runtime_homes) {
|
|
112
|
-
args.push("-v", `${entry.path}:${path.posix.join(runtimeHome, getImportMountTargetName(kind))}`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return args;
|
|
116
|
-
};
|
|
117
24
|
export const createDockerRunInvocation = async (compileResult, imageTag, options = {}) => {
|
|
118
25
|
const containerReport = compileResult.report.container;
|
|
119
26
|
if (!containerReport) {
|
|
@@ -128,8 +35,17 @@ export const createDockerRunInvocation = async (compileResult, imageTag, options
|
|
|
128
35
|
assertRunEnvironmentSatisfied(containerReport, env, preparedRuntimeAuth.coveredModelSecrets);
|
|
129
36
|
await ensureDirectory(supportDirectory);
|
|
130
37
|
await writeUtf8File(envFilePath, renderDockerEnvFile(env));
|
|
38
|
+
const deploymentName = options.deploymentName
|
|
39
|
+
? normalizeDeploymentName(options.deploymentName)
|
|
40
|
+
: options.detach
|
|
41
|
+
? normalizeDeploymentName(undefined)
|
|
42
|
+
: null;
|
|
131
43
|
const containerName = options.containerName ?? createDefaultContainerName(imageTag);
|
|
132
|
-
const args =
|
|
44
|
+
const args = options.dockerContext
|
|
45
|
+
? ["--context", options.dockerContext, "run"]
|
|
46
|
+
: options.dockerHost
|
|
47
|
+
? ["--host", options.dockerHost, "run"]
|
|
48
|
+
: ["run"];
|
|
133
49
|
if (options.detach) {
|
|
134
50
|
args.push("-d");
|
|
135
51
|
}
|
|
@@ -143,6 +59,16 @@ export const createDockerRunInvocation = async (compileResult, imageTag, options
|
|
|
143
59
|
for (const mount of containerReport.persistent_mounts ?? []) {
|
|
144
60
|
args.push("-v", `${mount.volume_name}:${mount.mount_path}`);
|
|
145
61
|
}
|
|
62
|
+
if (options.detach && deploymentName) {
|
|
63
|
+
const compileFingerprint = compileResult.report.compile_fingerprint ?? "sf1:unknown";
|
|
64
|
+
appendDockerLabelArgs(args, createDockerDeploymentLabels({
|
|
65
|
+
compileFingerprint,
|
|
66
|
+
deployment: deploymentName,
|
|
67
|
+
project: createDockerProjectLabel(compileResult.report.root, compileResult.report.project_name),
|
|
68
|
+
unit: createDockerDeploymentUnitId(deploymentName),
|
|
69
|
+
version: compileResult.report.spawnfile_version
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
146
72
|
args.push("--env-file", envFilePath);
|
|
147
73
|
args.push(...(await resolveAuthMountArgs(containerReport, options.authProfile ?? null)));
|
|
148
74
|
args.push(...preparedRuntimeAuth.mountArgs);
|
|
@@ -153,6 +79,9 @@ export const createDockerRunInvocation = async (compileResult, imageTag, options
|
|
|
153
79
|
containerName,
|
|
154
80
|
cwd: compileResult.outputDirectory,
|
|
155
81
|
detach: options.detach ?? false,
|
|
82
|
+
deploymentName,
|
|
83
|
+
dockerContext: options.dockerContext ?? null,
|
|
84
|
+
dockerHost: options.dockerHost ?? null,
|
|
156
85
|
envFilePath,
|
|
157
86
|
imageTag,
|
|
158
87
|
supportDirectory
|
|
@@ -163,52 +92,106 @@ export const createDockerRunInvocation = async (compileResult, imageTag, options
|
|
|
163
92
|
throw error;
|
|
164
93
|
}
|
|
165
94
|
};
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
95
|
+
const readExistingDeploymentRecord = async (outputDirectory, deploymentName) => {
|
|
96
|
+
const recordPath = resolveDeploymentRecordPath(outputDirectory, deploymentName);
|
|
97
|
+
if (!await fileExists(recordPath)) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
return readDeploymentRecordFromOutput(outputDirectory, deploymentName);
|
|
101
|
+
};
|
|
102
|
+
const firstDeploymentUnit = (record) => record.units[0] ?? null;
|
|
103
|
+
export const resolveDetachedDeploymentOptions = async (outputDirectory, options) => {
|
|
104
|
+
if (!options.detach) {
|
|
105
|
+
return options;
|
|
106
|
+
}
|
|
107
|
+
const deploymentName = normalizeDeploymentName(options.deploymentName);
|
|
108
|
+
const record = await readExistingDeploymentRecord(outputDirectory, deploymentName);
|
|
109
|
+
if (!record) {
|
|
110
|
+
return {
|
|
111
|
+
...options,
|
|
112
|
+
deploymentName
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const unit = firstDeploymentUnit(record);
|
|
116
|
+
if (!options.dockerContext && !options.dockerHost) {
|
|
117
|
+
await verifyDockerDeploymentTarget(record.target, {
|
|
118
|
+
dockerCommand: options.dockerCommand,
|
|
119
|
+
execFile: options.targetExecFile
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
authProfile: options.authProfile ?? record.auth_profile ?? undefined,
|
|
124
|
+
containerName: options.containerName ?? unit?.container_name ?? undefined,
|
|
125
|
+
deploymentName,
|
|
126
|
+
dockerContext: options.dockerContext ?? dockerContextNameForTarget(record.target) ?? undefined,
|
|
127
|
+
dockerHost: options.dockerHost ?? dockerHostValueForTarget(record.target) ?? undefined,
|
|
128
|
+
envFilePath: options.envFilePath ?? record.env_file,
|
|
129
|
+
imageTag: options.imageTag ?? unit?.image_tag
|
|
130
|
+
};
|
|
131
|
+
};
|
|
184
132
|
export const runProject = async (inputPath, options = {}) => {
|
|
133
|
+
const resolvedOptions = await resolveDetachedDeploymentOptions(path.resolve(options.outputDirectory ?? DEFAULT_OUTPUT_DIRECTORY), {
|
|
134
|
+
authProfile: options.authProfile,
|
|
135
|
+
containerName: options.containerName,
|
|
136
|
+
deploymentName: options.deploymentName,
|
|
137
|
+
detach: options.detach,
|
|
138
|
+
dockerCommand: options.dockerCommand,
|
|
139
|
+
dockerContext: options.dockerContext,
|
|
140
|
+
dockerHost: options.dockerHost,
|
|
141
|
+
envFilePath: options.envFilePath,
|
|
142
|
+
imageTag: options.imageTag,
|
|
143
|
+
targetExecFile: options.targetExecFile
|
|
144
|
+
});
|
|
145
|
+
const targetArchitecture = options.containerArchitecture ??
|
|
146
|
+
await resolveDockerBuildArchitecture({
|
|
147
|
+
dockerCommand: options.dockerCommand,
|
|
148
|
+
dockerContext: resolvedOptions.dockerContext
|
|
149
|
+
});
|
|
185
150
|
const compileResult = await compileProject(inputPath, {
|
|
186
151
|
clean: options.clean,
|
|
152
|
+
containerArchitecture: targetArchitecture,
|
|
187
153
|
outputDirectory: options.outputDirectory
|
|
188
154
|
});
|
|
189
|
-
const imageTag =
|
|
190
|
-
const authProfile =
|
|
191
|
-
? await requireAuthProfile(
|
|
155
|
+
const imageTag = resolvedOptions.imageTag ?? createDefaultImageTag(resolveImageTagRoot(inputPath));
|
|
156
|
+
const authProfile = resolvedOptions.authProfile
|
|
157
|
+
? await requireAuthProfile(resolvedOptions.authProfile)
|
|
192
158
|
: null;
|
|
193
159
|
const invocation = await createDockerRunInvocation(compileResult, imageTag, {
|
|
194
160
|
authProfile,
|
|
195
|
-
containerName:
|
|
161
|
+
containerName: resolvedOptions.containerName,
|
|
196
162
|
detach: options.detach,
|
|
163
|
+
deploymentName: resolvedOptions.deploymentName,
|
|
197
164
|
dockerCommand: options.dockerCommand,
|
|
198
|
-
|
|
165
|
+
dockerContext: resolvedOptions.dockerContext,
|
|
166
|
+
dockerHost: resolvedOptions.dockerHost,
|
|
167
|
+
envFilePath: resolvedOptions.envFilePath
|
|
199
168
|
});
|
|
169
|
+
let runMetadata;
|
|
200
170
|
try {
|
|
201
|
-
await (options.runRunner ?? runDockerContainer)(invocation);
|
|
171
|
+
runMetadata = await (options.runRunner ?? runDockerContainer)(invocation);
|
|
202
172
|
}
|
|
203
173
|
finally {
|
|
204
174
|
if (!invocation.detach) {
|
|
205
175
|
await removeDirectory(invocation.supportDirectory);
|
|
206
176
|
}
|
|
207
177
|
}
|
|
178
|
+
const deploymentRecordPath = invocation.detach && invocation.deploymentName
|
|
179
|
+
? await writeDockerDeploymentRecordForRun({
|
|
180
|
+
authProfileName: authProfile?.name ?? null,
|
|
181
|
+
envFilePath: resolvedOptions.envFilePath,
|
|
182
|
+
imageTag,
|
|
183
|
+
invocation,
|
|
184
|
+
outputDirectory: compileResult.outputDirectory,
|
|
185
|
+
report: compileResult.report,
|
|
186
|
+
runMetadata: runMetadata ?? undefined,
|
|
187
|
+
targetExecFile: options.targetExecFile
|
|
188
|
+
})
|
|
189
|
+
: null;
|
|
208
190
|
return {
|
|
209
191
|
...compileResult,
|
|
210
192
|
authProfileName: authProfile?.name ?? null,
|
|
211
193
|
containerName: invocation.containerName,
|
|
194
|
+
deploymentRecordPath,
|
|
212
195
|
imageTag
|
|
213
196
|
};
|
|
214
197
|
};
|
|
@@ -6,4 +6,9 @@ interface PreparedRunAuth {
|
|
|
6
6
|
}
|
|
7
7
|
export declare const prepareRuntimeAuthMounts: (outputDirectory: string, containerReport: ContainerReport, authProfile: ResolvedAuthProfile | null, env: Record<string, string>, tempRoot: string) => Promise<PreparedRunAuth>;
|
|
8
8
|
export declare const assertDeclaredModelAuthSatisfied: (containerReport: ContainerReport, authProfile: ResolvedAuthProfile | null) => void;
|
|
9
|
+
export declare const resolveRunEnvironment: (containerReport: ContainerReport, authProfile: ResolvedAuthProfile | null, envFileEnv?: Record<string, string>) => Record<string, string>;
|
|
10
|
+
export declare const assertRunEnvironmentSatisfied: (containerReport: ContainerReport, env: Record<string, string>, coveredModelSecrets: Set<string>) => void;
|
|
11
|
+
export declare const renderDockerEnvFile: (env: Record<string, string>) => string;
|
|
12
|
+
export declare const readRunEnvFile: (envFilePath: string | undefined) => Promise<Record<string, string>>;
|
|
13
|
+
export declare const resolveAuthMountArgs: (containerReport: ContainerReport, authProfile: ResolvedAuthProfile | null) => Promise<string[]>;
|
|
9
14
|
export {};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { parseEnvFile } from "../auth/index.js";
|
|
4
|
+
import { fileExists, readUtf8File } from "../filesystem/index.js";
|
|
1
5
|
import { SpawnfileError } from "../shared/index.js";
|
|
2
6
|
import { getRuntimeAdapter } from "../runtime/index.js";
|
|
3
7
|
const MODEL_AUTH_IMPORT_KINDS = {
|
|
@@ -57,3 +61,97 @@ export const assertDeclaredModelAuthSatisfied = (containerReport, authProfile) =
|
|
|
57
61
|
throw new SpawnfileError("validation_error", `Auth profile ${authProfile.name} is missing required auth imports: ${missingImportKinds.join(", ")}`);
|
|
58
62
|
}
|
|
59
63
|
};
|
|
64
|
+
const getImportMountTargetName = (kind) => kind === "claude-code" ? ".claude" : ".codex";
|
|
65
|
+
const createGeneratedRuntimeSecret = (secretName) => {
|
|
66
|
+
if (secretName === "OPENCLAW_GATEWAY_TOKEN" || secretName === "OPENCLAW_HOOKS_TOKEN") {
|
|
67
|
+
return randomBytes(24).toString("hex");
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
};
|
|
71
|
+
const hasEnvValue = (env, name) => typeof env[name] === "string" && env[name].length > 0;
|
|
72
|
+
const collectMissingRequiredSecrets = (containerReport, env, coveredModelSecrets) => {
|
|
73
|
+
const missing = new Set();
|
|
74
|
+
for (const secretName of containerReport.secrets_required) {
|
|
75
|
+
if (hasEnvValue(env, secretName)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (!containerReport.model_secrets_required.includes(secretName)) {
|
|
79
|
+
missing.add(secretName);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const requiredInstances = (containerReport.runtime_instances ?? []).filter((instance) => instance.model_secrets_required.includes(secretName));
|
|
83
|
+
const isCoveredEverywhere = requiredInstances.length > 0 &&
|
|
84
|
+
requiredInstances.every((instance) => coveredModelSecrets.has(`${instance.id}:${secretName}`));
|
|
85
|
+
if (!isCoveredEverywhere) {
|
|
86
|
+
missing.add(secretName);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return [...missing].sort();
|
|
90
|
+
};
|
|
91
|
+
export const resolveRunEnvironment = (containerReport, authProfile, envFileEnv = {}) => {
|
|
92
|
+
const env = {
|
|
93
|
+
...(authProfile?.env ?? {}),
|
|
94
|
+
...envFileEnv
|
|
95
|
+
};
|
|
96
|
+
for (const name of new Set([...Object.keys(env), ...containerReport.secrets_required])) {
|
|
97
|
+
const processValue = process.env[name];
|
|
98
|
+
if (typeof processValue === "string" && processValue.length > 0) {
|
|
99
|
+
env[name] = processValue;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
for (const name of containerReport.runtime_secrets_required) {
|
|
103
|
+
if (hasEnvValue(env, name)) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const generatedValue = createGeneratedRuntimeSecret(name);
|
|
107
|
+
if (generatedValue) {
|
|
108
|
+
env[name] = generatedValue;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const [name, value] of Object.entries(env)) {
|
|
112
|
+
if (value.includes("\n")) {
|
|
113
|
+
throw new SpawnfileError("validation_error", `Env value for ${name} contains a newline and cannot be written to a Docker env file`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return env;
|
|
117
|
+
};
|
|
118
|
+
export const assertRunEnvironmentSatisfied = (containerReport, env, coveredModelSecrets) => {
|
|
119
|
+
const missing = collectMissingRequiredSecrets(containerReport, env, coveredModelSecrets);
|
|
120
|
+
if (missing.length > 0) {
|
|
121
|
+
throw new SpawnfileError("validation_error", `Missing required runtime env: ${missing.sort().join(", ")}`);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
export const renderDockerEnvFile = (env) => `${Object.entries(env)
|
|
125
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
126
|
+
.map(([name, value]) => `${name}=${value}`)
|
|
127
|
+
.join("\n")}\n`;
|
|
128
|
+
export const readRunEnvFile = async (envFilePath) => {
|
|
129
|
+
if (!envFilePath) {
|
|
130
|
+
return {};
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
return parseEnvFile(await readUtf8File(envFilePath));
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
137
|
+
throw new SpawnfileError("validation_error", `Unable to read env file ${envFilePath}: ${reason}`);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
export const resolveAuthMountArgs = async (containerReport, authProfile) => {
|
|
141
|
+
if (!authProfile || containerReport.runtime_homes.length === 0) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
const args = [];
|
|
145
|
+
for (const [kind, entry] of Object.entries(authProfile.imports)) {
|
|
146
|
+
if (!entry) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (!(await fileExists(entry.path))) {
|
|
150
|
+
throw new SpawnfileError("validation_error", `Imported auth path for ${kind} does not exist: ${entry.path}`);
|
|
151
|
+
}
|
|
152
|
+
for (const runtimeHome of containerReport.runtime_homes) {
|
|
153
|
+
args.push("-v", `${entry.path}:${path.posix.join(runtimeHome, getImportMountTargetName(kind))}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return args;
|
|
157
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface DockerRunInvocation {
|
|
2
|
+
args: string[];
|
|
3
|
+
command: string;
|
|
4
|
+
containerName: string | null;
|
|
5
|
+
cwd: string;
|
|
6
|
+
detach: boolean;
|
|
7
|
+
deploymentName?: string | null;
|
|
8
|
+
dockerContext?: string | null;
|
|
9
|
+
dockerHost?: string | null;
|
|
10
|
+
envFilePath: string;
|
|
11
|
+
imageTag: string;
|
|
12
|
+
supportDirectory: string;
|
|
13
|
+
}
|
|
14
|
+
export interface DockerRunResult {
|
|
15
|
+
containerId?: string;
|
|
16
|
+
imageId?: string;
|
|
17
|
+
}
|
|
18
|
+
export type DockerRunRunner = (invocation: DockerRunInvocation) => Promise<DockerRunResult | void>;
|
|
19
|
+
export declare const runDockerContainer: DockerRunRunner;
|