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.
Files changed (220) hide show
  1. package/README.md +21 -3
  2. package/dist/auth/runtimeCredentials.js +18 -2
  3. package/dist/cli/devCommands.d.ts +3 -0
  4. package/dist/cli/devCommands.js +115 -0
  5. package/dist/cli/lifecycleCommands.js +103 -1
  6. package/dist/cli/modelCommands.js +3 -0
  7. package/dist/cli/resolveCommandInput.d.ts +19 -0
  8. package/dist/cli/resolveCommandInput.js +25 -0
  9. package/dist/cli/runCli.d.ts +10 -1
  10. package/dist/cli/runCli.js +41 -13
  11. package/dist/cli/runtimeCommands.js +1 -0
  12. package/dist/cli/statusCommand.d.ts +43 -0
  13. package/dist/cli/statusCommand.js +428 -0
  14. package/dist/cli/surfaceCommands.js +4 -0
  15. package/dist/compiler/agentSurfaces.js +2 -4
  16. package/dist/compiler/buildProject.d.ts +9 -1
  17. package/dist/compiler/buildProject.js +44 -5
  18. package/dist/compiler/compileProject.d.ts +2 -0
  19. package/dist/compiler/compileProject.js +22 -11
  20. package/dist/compiler/containerArtifacts.d.ts +1 -0
  21. package/dist/compiler/containerArtifacts.js +208 -32
  22. package/dist/compiler/containerArtifactsPlans.js +7 -4
  23. package/dist/compiler/containerArtifactsRender.d.ts +7 -1
  24. package/dist/compiler/containerArtifactsRender.js +31 -69
  25. package/dist/compiler/containerArtifactsTypes.d.ts +9 -0
  26. package/dist/compiler/containerEntrypointRender.js +22 -32
  27. package/dist/compiler/containerPackageRender.d.ts +15 -0
  28. package/dist/compiler/containerPackageRender.js +63 -0
  29. package/dist/compiler/containerTargetResources.js +3 -1
  30. package/dist/compiler/index.d.ts +1 -0
  31. package/dist/compiler/index.js +1 -0
  32. package/dist/compiler/moltnetArtifacts.js +21 -1
  33. package/dist/compiler/moltnetBinaries.d.ts +4 -1
  34. package/dist/compiler/moltnetBinaries.js +24 -3
  35. package/dist/compiler/moltnetClientConfig.d.ts +1 -1
  36. package/dist/compiler/moltnetClientConfig.js +14 -16
  37. package/dist/compiler/moltnetNodeConfig.js +2 -4
  38. package/dist/compiler/moltnetResolution.js +1 -1
  39. package/dist/compiler/moltnetRoomMemberships.js +1 -2
  40. package/dist/compiler/moltnetRuntimeConfig.d.ts +1 -1
  41. package/dist/compiler/moltnetRuntimeConfig.js +9 -10
  42. package/dist/compiler/networkBinding.d.ts +14 -0
  43. package/dist/compiler/networkBinding.js +34 -0
  44. package/dist/compiler/publishProject.d.ts +13 -0
  45. package/dist/compiler/publishProject.js +72 -0
  46. package/dist/compiler/runProject.d.ts +26 -12
  47. package/dist/compiler/runProject.js +109 -126
  48. package/dist/compiler/runProjectAuth.d.ts +5 -0
  49. package/dist/compiler/runProjectAuth.js +98 -0
  50. package/dist/compiler/runProjectDocker.d.ts +19 -0
  51. package/dist/compiler/runProjectDocker.js +202 -0
  52. package/dist/compiler/types.d.ts +1 -2
  53. package/dist/compiler/upProject.d.ts +6 -0
  54. package/dist/compiler/upProject.js +43 -8
  55. package/dist/compiler/view/buildOrganizationView.js +4 -1
  56. package/dist/compiler/view/declarations.d.ts +4 -0
  57. package/dist/compiler/view/declarations.js +104 -0
  58. package/dist/compiler/view/renderNetworks.js +1 -2
  59. package/dist/compiler/view/renderTree.js +6 -2
  60. package/dist/compiler/view/types.d.ts +64 -1
  61. package/dist/deployment/dockerInspect.d.ts +29 -0
  62. package/dist/deployment/dockerInspect.js +130 -0
  63. package/dist/deployment/dockerLabels.d.ts +16 -0
  64. package/dist/deployment/dockerLabels.js +32 -0
  65. package/dist/deployment/dockerLogs.d.ts +24 -0
  66. package/dist/deployment/dockerLogs.js +96 -0
  67. package/dist/deployment/dockerManager.d.ts +59 -0
  68. package/dist/deployment/dockerManager.js +76 -0
  69. package/dist/deployment/dockerProbeGateway.d.ts +16 -0
  70. package/dist/deployment/dockerProbeGateway.js +48 -0
  71. package/dist/deployment/dockerRecover.d.ts +17 -0
  72. package/dist/deployment/dockerRecover.js +129 -0
  73. package/dist/deployment/homeStore.d.ts +19 -0
  74. package/dist/deployment/homeStore.js +130 -0
  75. package/dist/deployment/index.d.ts +10 -0
  76. package/dist/deployment/index.js +10 -0
  77. package/dist/deployment/names.d.ts +3 -0
  78. package/dist/deployment/names.js +12 -0
  79. package/dist/deployment/record.d.ts +76 -0
  80. package/dist/deployment/record.js +174 -0
  81. package/dist/deployment/target.d.ts +35 -0
  82. package/dist/deployment/target.js +96 -0
  83. package/dist/dev/activity.d.ts +14 -0
  84. package/dist/dev/activity.js +48 -0
  85. package/dist/dev/docker.d.ts +16 -0
  86. package/dist/dev/docker.js +71 -0
  87. package/dist/dev/index.d.ts +2 -0
  88. package/dist/dev/index.js +2 -0
  89. package/dist/dev/project.d.ts +35 -0
  90. package/dist/dev/project.js +164 -0
  91. package/dist/distribution/buildDistributionReport.d.ts +22 -0
  92. package/dist/distribution/buildDistributionReport.js +51 -0
  93. package/dist/distribution/consumeImage.d.ts +29 -0
  94. package/dist/distribution/consumeImage.js +254 -0
  95. package/dist/distribution/consumeImageSupport.d.ts +18 -0
  96. package/dist/distribution/consumeImageSupport.js +63 -0
  97. package/dist/distribution/distributionReportSchema.d.ts +84 -0
  98. package/dist/distribution/distributionReportSchema.js +107 -0
  99. package/dist/distribution/dockerRunner.d.ts +5 -0
  100. package/dist/distribution/dockerRunner.js +21 -0
  101. package/dist/distribution/extractImage.d.ts +25 -0
  102. package/dist/distribution/extractImage.js +96 -0
  103. package/dist/distribution/fingerprint.d.ts +2 -0
  104. package/dist/distribution/fingerprint.js +14 -0
  105. package/dist/distribution/imageRef.d.ts +9 -0
  106. package/dist/distribution/imageRef.js +65 -0
  107. package/dist/distribution/imageRuntimeAuth.d.ts +19 -0
  108. package/dist/distribution/imageRuntimeAuth.js +71 -0
  109. package/dist/distribution/index.d.ts +24 -0
  110. package/dist/distribution/index.js +16 -0
  111. package/dist/distribution/preflight.d.ts +11 -0
  112. package/dist/distribution/preflight.js +65 -0
  113. package/dist/distribution/projectImageView.d.ts +8 -0
  114. package/dist/distribution/projectImageView.js +83 -0
  115. package/dist/distribution/projectName.d.ts +1 -0
  116. package/dist/distribution/projectName.js +12 -0
  117. package/dist/distribution/renderImageInterface.d.ts +30 -0
  118. package/dist/distribution/renderImageInterface.js +86 -0
  119. package/dist/distribution/tarReader.d.ts +10 -0
  120. package/dist/distribution/tarReader.js +69 -0
  121. package/dist/distribution/types.d.ts +80 -0
  122. package/dist/distribution/types.js +4 -0
  123. package/dist/distribution/verifyDistributionReport.d.ts +12 -0
  124. package/dist/distribution/verifyDistributionReport.js +29 -0
  125. package/dist/filesystem/paths.d.ts +6 -0
  126. package/dist/filesystem/paths.js +11 -0
  127. package/dist/manifest/loadManifest.js +22 -2
  128. package/dist/manifest/renderSpawnfile.js +2 -4
  129. package/dist/manifest/schemas.d.ts +17 -41
  130. package/dist/manifest/surfaceSchemas.d.ts +19 -47
  131. package/dist/manifest/surfaceSchemas.js +3 -6
  132. package/dist/report/createReport.d.ts +7 -1
  133. package/dist/report/createReport.js +28 -1
  134. package/dist/report/types.d.ts +43 -0
  135. package/dist/runtime/common.d.ts +2 -0
  136. package/dist/runtime/common.js +1 -1
  137. package/dist/runtime/container.d.ts +1 -0
  138. package/dist/runtime/container.js +82 -15
  139. package/dist/runtime/index.d.ts +1 -0
  140. package/dist/runtime/index.js +1 -0
  141. package/dist/runtime/install.d.ts +1 -11
  142. package/dist/runtime/install.js +27 -30
  143. package/dist/runtime/openclaw/adapter.js +9 -1
  144. package/dist/runtime/openclaw/configAuth.d.ts +15 -0
  145. package/dist/runtime/openclaw/configAuth.js +46 -0
  146. package/dist/runtime/openclaw/runAuth.d.ts +6 -0
  147. package/dist/runtime/openclaw/runAuth.js +7 -55
  148. package/dist/runtime/openclaw/statusProbes.d.ts +2 -0
  149. package/dist/runtime/openclaw/statusProbes.js +31 -0
  150. package/dist/runtime/pi/adapter.d.ts +4 -0
  151. package/dist/runtime/pi/adapter.js +198 -0
  152. package/dist/runtime/pi/appActivitySource.d.ts +1 -0
  153. package/dist/runtime/pi/appActivitySource.js +87 -0
  154. package/dist/runtime/pi/appCoreSource.d.ts +1 -0
  155. package/dist/runtime/pi/appCoreSource.js +354 -0
  156. package/dist/runtime/pi/appPreludeSource.d.ts +1 -0
  157. package/dist/runtime/pi/appPreludeSource.js +103 -0
  158. package/dist/runtime/pi/appSource.d.ts +1 -0
  159. package/dist/runtime/pi/appSource.js +8 -0
  160. package/dist/runtime/pi/appTemplate.d.ts +28 -0
  161. package/dist/runtime/pi/appTemplate.js +144 -0
  162. package/dist/runtime/pi/runAuth.d.ts +2 -0
  163. package/dist/runtime/pi/runAuth.js +68 -0
  164. package/dist/runtime/picoclaw/adapter.js +10 -0
  165. package/dist/runtime/picoclaw/runAuth.d.ts +7 -0
  166. package/dist/runtime/picoclaw/runAuth.js +8 -68
  167. package/dist/runtime/picoclaw/statusProbes.d.ts +2 -0
  168. package/dist/runtime/picoclaw/statusProbes.js +100 -0
  169. package/dist/runtime/registry.d.ts +0 -5
  170. package/dist/runtime/registry.js +5 -12
  171. package/dist/runtime/statusProbes.d.ts +16 -0
  172. package/dist/runtime/statusProbes.js +41 -0
  173. package/dist/runtime/types.d.ts +53 -1
  174. package/dist/shared/errors.d.ts +6 -0
  175. package/dist/shared/errors.js +9 -0
  176. package/dist/status/buildStatus.d.ts +13 -0
  177. package/dist/status/buildStatus.js +266 -0
  178. package/dist/status/compileReport.d.ts +96 -0
  179. package/dist/status/compileReport.js +230 -0
  180. package/dist/status/compiledContainerObservations.d.ts +5 -0
  181. package/dist/status/compiledContainerObservations.js +63 -0
  182. package/dist/status/deploymentLogs.d.ts +12 -0
  183. package/dist/status/deploymentLogs.js +45 -0
  184. package/dist/status/deployments.d.ts +14 -0
  185. package/dist/status/deployments.js +121 -0
  186. package/dist/status/imageStatusReport.d.ts +10 -0
  187. package/dist/status/imageStatusReport.js +73 -0
  188. package/dist/status/index.d.ts +14 -0
  189. package/dist/status/index.js +14 -0
  190. package/dist/status/moltnetProbes.d.ts +21 -0
  191. package/dist/status/moltnetProbes.js +240 -0
  192. package/dist/status/registryDrift.d.ts +28 -0
  193. package/dist/status/registryDrift.js +119 -0
  194. package/dist/status/renderStatus.d.ts +2 -0
  195. package/dist/status/renderStatus.js +230 -0
  196. package/dist/status/runtimeProbes.d.ts +13 -0
  197. package/dist/status/runtimeProbes.js +111 -0
  198. package/dist/status/selectionSubjects.d.ts +8 -0
  199. package/dist/status/selectionSubjects.js +77 -0
  200. package/dist/status/selectors.d.ts +14 -0
  201. package/dist/status/selectors.js +90 -0
  202. package/dist/status/traversal.d.ts +6 -0
  203. package/dist/status/traversal.js +15 -0
  204. package/dist/status/types.d.ts +113 -0
  205. package/dist/status/types.js +1 -0
  206. package/package.json +9 -2
  207. package/runtimes.yaml +44 -21
  208. package/dist/runtime/tinyclaw/adapter.d.ts +0 -2
  209. package/dist/runtime/tinyclaw/adapter.js +0 -284
  210. package/dist/runtime/tinyclaw/runAuth.d.ts +0 -2
  211. package/dist/runtime/tinyclaw/runAuth.js +0 -49
  212. package/dist/runtime/tinyclaw/scaffold-assets/AGENTS.md +0 -160
  213. package/dist/runtime/tinyclaw/scaffold-assets/CLAUDE.md +0 -5
  214. package/dist/runtime/tinyclaw/scaffold-assets/SOUL.md +0 -177
  215. package/dist/runtime/tinyclaw/scaffold.d.ts +0 -2
  216. package/dist/runtime/tinyclaw/scaffold.js +0 -24
  217. package/dist/runtime/tinyclaw/schedules.d.ts +0 -9
  218. package/dist/runtime/tinyclaw/schedules.js +0 -62
  219. package/dist/runtime/tinyclaw/surfaces.d.ts +0 -8
  220. 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
- export interface DockerRunInvocation {
4
- args: string[];
5
- command: string;
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
- export declare const runDockerContainer: DockerRunRunner;
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 { randomBytes } from "node:crypto";
5
- import { spawn } from "node:child_process";
6
- import { parseEnvFile, requireAuthProfile } from "../auth/index.js";
7
- import { ensureDirectory, fileExists, readUtf8File, removeDirectory, writeUtf8File } from "../filesystem/index.js";
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 { assertDeclaredModelAuthSatisfied, prepareRuntimeAuthMounts } from "./runProjectAuth.js";
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 = ["run"];
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
- export const runDockerContainer = async (invocation) => new Promise((resolve, reject) => {
167
- const child = spawn(invocation.command, invocation.args, {
168
- cwd: invocation.cwd,
169
- stdio: "inherit"
170
- });
171
- child.once("error", (error) => {
172
- reject(new SpawnfileError("runtime_error", `Unable to start docker run for ${invocation.imageTag}: ${error.message}`));
173
- });
174
- child.once("exit", (code, signal) => {
175
- if (code === 0) {
176
- resolve();
177
- return;
178
- }
179
- reject(new SpawnfileError("runtime_error", signal
180
- ? `Docker run for ${invocation.imageTag} exited from signal ${signal}`
181
- : `Docker run for ${invocation.imageTag} failed with exit code ${code ?? "unknown"}`));
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 = options.imageTag ?? createDefaultImageTag(resolveImageTagRoot(inputPath));
190
- const authProfile = options.authProfile
191
- ? await requireAuthProfile(options.authProfile)
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: options.containerName,
161
+ containerName: resolvedOptions.containerName,
196
162
  detach: options.detach,
163
+ deploymentName: resolvedOptions.deploymentName,
197
164
  dockerCommand: options.dockerCommand,
198
- envFilePath: options.envFilePath
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;