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
@@ -0,0 +1,130 @@
1
+ import { execFile as execFileCallback } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { dockerContextNameForTarget, verifyDockerDeploymentTarget } from "./target.js";
4
+ const execFile = promisify(execFileCallback);
5
+ const toStringOrNull = (value) => typeof value === "string" && value.length > 0 ? value : null;
6
+ const toNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
7
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8
+ const targetRefForUnit = (unit) => unit.container_id ?? unit.container_name;
9
+ const argsForInspect = (record, targetRef) => {
10
+ const context = dockerContextNameForTarget(record.target);
11
+ if (context) {
12
+ return ["--context", context, "inspect", targetRef];
13
+ }
14
+ if (record.target.kind === "host") {
15
+ return ["--host", record.target.value, "inspect", targetRef];
16
+ }
17
+ return ["inspect", targetRef];
18
+ };
19
+ const matchesDockerId = (recorded, live) => !recorded
20
+ || !live
21
+ || recorded === live
22
+ || live.startsWith(recorded)
23
+ || recorded.startsWith(live);
24
+ const createDriftMessages = (unit, inspected) => {
25
+ const containerId = toStringOrNull(inspected.Id);
26
+ const imageId = toStringOrNull(inspected.Image);
27
+ const drift = [];
28
+ if (!matchesDockerId(unit.container_id, containerId)) {
29
+ drift.push(`container id drift: recorded ${unit.container_id}, live ${containerId}`);
30
+ }
31
+ if (!matchesDockerId(unit.image_id, imageId)) {
32
+ drift.push(`image id drift: recorded ${unit.image_id}, live ${imageId}`);
33
+ }
34
+ return { containerId, drift, imageId };
35
+ };
36
+ const inspectionFromState = (unitId, unit, inspected, state) => {
37
+ const running = typeof state.Running === "boolean" ? state.Running : null;
38
+ const status = toStringOrNull(state.Status);
39
+ const { containerId, drift, imageId } = createDriftMessages(unit, inspected);
40
+ const baseMessage = running === true
41
+ ? `container is running (${status ?? "running"})`
42
+ : `container is not running (${status ?? "unknown"})`;
43
+ return {
44
+ containerId,
45
+ drift,
46
+ exists: true,
47
+ exitCode: toNumberOrNull(state.ExitCode),
48
+ finishedAt: toStringOrNull(state.FinishedAt),
49
+ imageId,
50
+ message: drift.length > 0 ? `${baseMessage}; ${drift.join("; ")}` : baseMessage,
51
+ restartCount: toNumberOrNull(state.RestartCount),
52
+ running,
53
+ severity: drift.length > 0 ? "warn" : running === false ? "error" : "ok",
54
+ startedAt: toStringOrNull(state.StartedAt),
55
+ status,
56
+ unitId
57
+ };
58
+ };
59
+ const missingInspection = (unitId, message) => ({
60
+ containerId: null,
61
+ drift: [],
62
+ exists: false,
63
+ exitCode: null,
64
+ finishedAt: null,
65
+ imageId: null,
66
+ message,
67
+ restartCount: null,
68
+ running: false,
69
+ severity: "warn",
70
+ startedAt: null,
71
+ status: null,
72
+ unitId
73
+ });
74
+ const unknownInspection = (unitId, message, severity = "unknown") => ({
75
+ containerId: null,
76
+ drift: [],
77
+ exists: null,
78
+ exitCode: null,
79
+ finishedAt: null,
80
+ imageId: null,
81
+ message,
82
+ restartCount: null,
83
+ running: null,
84
+ severity,
85
+ startedAt: null,
86
+ status: null,
87
+ unitId
88
+ });
89
+ const inspectUnit = async (record, unit, options) => {
90
+ const targetRef = targetRefForUnit(unit);
91
+ if (!targetRef) {
92
+ return unknownInspection(unit.id, "deployment unit has no recorded container id or name");
93
+ }
94
+ try {
95
+ const { stdout } = await options.execFile(options.dockerCommand, argsForInspect(record, targetRef), { timeout: options.timeoutMs });
96
+ const parsed = JSON.parse(stdout);
97
+ const [first] = Array.isArray(parsed) ? parsed : [];
98
+ const state = isRecord(first) && isRecord(first.State) ? first.State : null;
99
+ return state
100
+ ? inspectionFromState(unit.id, unit, first, state)
101
+ : unknownInspection(unit.id, `docker inspect returned no state for ${targetRef}`);
102
+ }
103
+ catch (error) {
104
+ const stderr = isRecord(error) ? toStringOrNull(error.stderr) : null;
105
+ const message = stderr ?? (error instanceof Error ? error.message : String(error));
106
+ if (/No such object/i.test(message)) {
107
+ return missingInspection(unit.id, `recorded container ${targetRef} is missing`);
108
+ }
109
+ return unknownInspection(unit.id, `unable to inspect container ${targetRef}: ${message}`, "error");
110
+ }
111
+ };
112
+ export const inspectDockerDeployment = async (record, options = {}) => {
113
+ const resolvedOptions = {
114
+ dockerCommand: options.dockerCommand ?? "docker",
115
+ execFile: options.execFile ?? execFile,
116
+ timeoutMs: options.timeoutMs ?? 10_000
117
+ };
118
+ try {
119
+ await verifyDockerDeploymentTarget(record.target, resolvedOptions);
120
+ }
121
+ catch (error) {
122
+ const message = error instanceof Error ? error.message : String(error);
123
+ return new Map(record.units.map((unit) => [
124
+ unit.id,
125
+ unknownInspection(unit.id, `unable to verify recorded Docker target: ${message}`, "error")
126
+ ]));
127
+ }
128
+ const inspections = await Promise.all(record.units.map(async (unit) => inspectUnit(record, unit, resolvedOptions)));
129
+ return new Map(inspections.map((inspection) => [inspection.unitId, inspection]));
130
+ };
@@ -0,0 +1,16 @@
1
+ export declare const dockerDeploymentLabelKeys: {
2
+ readonly compileFingerprint: "com.spawnfile.compile_fingerprint";
3
+ readonly deployment: "com.spawnfile.deployment";
4
+ readonly project: "com.spawnfile.project";
5
+ readonly unit: "com.spawnfile.unit";
6
+ readonly version: "com.spawnfile.version";
7
+ };
8
+ export interface DockerDeploymentLabelInput {
9
+ compileFingerprint: string;
10
+ deployment: string;
11
+ project: string;
12
+ unit: string;
13
+ version: string;
14
+ }
15
+ export declare const createDockerDeploymentLabels: (input: DockerDeploymentLabelInput) => Record<string, string>;
16
+ export declare const appendDockerLabelArgs: (args: string[], labels: Record<string, string>) => void;
@@ -0,0 +1,32 @@
1
+ import { SpawnfileError } from "../shared/index.js";
2
+ export const dockerDeploymentLabelKeys = {
3
+ compileFingerprint: "com.spawnfile.compile_fingerprint",
4
+ deployment: "com.spawnfile.deployment",
5
+ project: "com.spawnfile.project",
6
+ unit: "com.spawnfile.unit",
7
+ version: "com.spawnfile.version"
8
+ };
9
+ const dockerLabelValuePattern = /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/;
10
+ const assertIdentifierLabelValue = (key, value) => {
11
+ if (!dockerLabelValuePattern.test(value)) {
12
+ throw new SpawnfileError("validation_error", `Docker deployment label ${key} must be an identifier`);
13
+ }
14
+ };
15
+ export const createDockerDeploymentLabels = (input) => {
16
+ const labels = {
17
+ [dockerDeploymentLabelKeys.compileFingerprint]: input.compileFingerprint,
18
+ [dockerDeploymentLabelKeys.deployment]: input.deployment,
19
+ [dockerDeploymentLabelKeys.project]: input.project,
20
+ [dockerDeploymentLabelKeys.unit]: input.unit,
21
+ [dockerDeploymentLabelKeys.version]: input.version
22
+ };
23
+ for (const [key, value] of Object.entries(labels)) {
24
+ assertIdentifierLabelValue(key, value);
25
+ }
26
+ return labels;
27
+ };
28
+ export const appendDockerLabelArgs = (args, labels) => {
29
+ for (const [key, value] of Object.entries(labels).sort(([left], [right]) => left.localeCompare(right))) {
30
+ args.push("--label", `${key}=${value}`);
31
+ }
32
+ };
@@ -0,0 +1,24 @@
1
+ import type { DeploymentRecord } from "./record.js";
2
+ export type DockerLogsExecFile = (file: string, args: string[], options: {
3
+ timeout: number;
4
+ }) => Promise<{
5
+ stderr: string;
6
+ stdout: string;
7
+ }>;
8
+ export interface DockerUnitLogs {
9
+ containerRef: string | null;
10
+ message: string;
11
+ severity: "error" | "ok" | "unknown" | "warn";
12
+ text: string;
13
+ unitId: string;
14
+ }
15
+ export type DockerLogsResult = Map<string, DockerUnitLogs>;
16
+ export interface DockerLogsOptions {
17
+ dockerCommand?: string;
18
+ execFile?: DockerLogsExecFile;
19
+ secretValues?: string[];
20
+ tail?: number;
21
+ timeoutMs?: number;
22
+ }
23
+ export declare const redactDockerLogText: (text: string, secretValues?: string[]) => string;
24
+ export declare const readDockerDeploymentLogs: (record: DeploymentRecord, options?: DockerLogsOptions) => Promise<DockerLogsResult>;
@@ -0,0 +1,96 @@
1
+ import { execFile as execFileCallback } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { dockerContextNameForTarget } from "./target.js";
4
+ const execFile = promisify(execFileCallback);
5
+ const targetRefForUnit = (unit) => unit.container_id ?? unit.container_name;
6
+ const withDockerTarget = (record, args) => {
7
+ const context = dockerContextNameForTarget(record.target);
8
+ if (context) {
9
+ return ["--context", context, ...args];
10
+ }
11
+ if (record.target.kind === "host") {
12
+ return ["--host", record.target.value, ...args];
13
+ }
14
+ return args;
15
+ };
16
+ const normalizeTail = (tail) => typeof tail === "number" && Number.isInteger(tail) && tail > 0 ? tail : 100;
17
+ const redactKnownSecrets = (text, secretValues) => secretValues
18
+ .filter((secret) => secret.length > 0)
19
+ .reduce((redacted, secret) => redacted.replaceAll(secret, "[REDACTED]"), text);
20
+ const redactTokenLikeText = (text) => {
21
+ let redacted = text;
22
+ redacted = redacted.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi, "Bearer [REDACTED]");
23
+ redacted = redacted.replace(/\bmagt_v1_[A-Za-z0-9_-]{16,}\b/g, "[REDACTED]");
24
+ redacted = redacted.replace(/\b(?:sk|sk-proj)-[A-Za-z0-9_-]{20,}\b/g, "[REDACTED]");
25
+ redacted = redacted.replace(/\b([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD)[A-Z0-9_]*=)([^\s"'`]+)/gi, "$1[REDACTED]");
26
+ redacted = redacted.replace(/("([^"]*(?:api[_-]?key|token|secret|password)[^"]*)"\s*:\s*")([^"]+)(")/gi, "$1[REDACTED]$4");
27
+ return redacted;
28
+ };
29
+ export const redactDockerLogText = (text, secretValues = []) => redactTokenLikeText(redactKnownSecrets(text, secretValues));
30
+ const combineLogStreams = (stdout, stderr) => {
31
+ if (!stdout) {
32
+ return stderr;
33
+ }
34
+ if (!stderr) {
35
+ return stdout;
36
+ }
37
+ return `${stdout}${stdout.endsWith("\n") ? "" : "\n"}${stderr}`;
38
+ };
39
+ const missingUnitLogs = (unitId, message) => ({
40
+ containerRef: null,
41
+ message,
42
+ severity: "unknown",
43
+ text: "",
44
+ unitId
45
+ });
46
+ const failedUnitLogs = (unitId, containerRef, message, severity = "error") => ({
47
+ containerRef,
48
+ message,
49
+ severity,
50
+ text: "",
51
+ unitId
52
+ });
53
+ const unitLogs = (unitId, containerRef, text) => ({
54
+ containerRef,
55
+ message: text.length > 0 ? "logs collected" : "logs collected; no output",
56
+ severity: "ok",
57
+ text,
58
+ unitId
59
+ });
60
+ const toErrorMessage = (error) => {
61
+ if (typeof error === "object" && error !== null && "stderr" in error) {
62
+ const stderr = error.stderr;
63
+ if (typeof stderr === "string" && stderr.length > 0) {
64
+ return stderr.trim();
65
+ }
66
+ }
67
+ return error instanceof Error ? error.message : String(error);
68
+ };
69
+ const readUnitLogs = async (record, unit, options) => {
70
+ const containerRef = targetRefForUnit(unit);
71
+ if (!containerRef) {
72
+ return missingUnitLogs(unit.id, "deployment unit has no recorded container id or name");
73
+ }
74
+ try {
75
+ const result = await options.execFile(options.dockerCommand, withDockerTarget(record, ["logs", "--tail", String(options.tail), containerRef]), { timeout: options.timeoutMs });
76
+ return unitLogs(unit.id, containerRef, redactDockerLogText(combineLogStreams(result.stdout, result.stderr), options.secretValues));
77
+ }
78
+ catch (error) {
79
+ const message = redactDockerLogText(toErrorMessage(error), options.secretValues);
80
+ if (/No such (?:object|container)/i.test(message)) {
81
+ return failedUnitLogs(unit.id, containerRef, `recorded container ${containerRef} is missing`, "warn");
82
+ }
83
+ return failedUnitLogs(unit.id, containerRef, `unable to collect logs for ${containerRef}: ${message}`);
84
+ }
85
+ };
86
+ export const readDockerDeploymentLogs = async (record, options = {}) => {
87
+ const resolvedOptions = {
88
+ dockerCommand: options.dockerCommand ?? "docker",
89
+ execFile: options.execFile ?? execFile,
90
+ secretValues: options.secretValues ?? [],
91
+ tail: normalizeTail(options.tail),
92
+ timeoutMs: options.timeoutMs ?? 10_000
93
+ };
94
+ const results = await Promise.all(record.units.map(async (unit) => readUnitLogs(record, unit, resolvedOptions)));
95
+ return new Map(results.map((result) => [result.unitId, result]));
96
+ };
@@ -0,0 +1,59 @@
1
+ import type { DeploymentRecordSource } from "./record.js";
2
+ import { type DockerDeploymentTarget, type DockerTargetExecFile } from "./target.js";
3
+ import { type DeploymentRecord } from "./record.js";
4
+ export interface DockerDeploymentNodeInput {
5
+ id: string;
6
+ kind: "agent" | "team";
7
+ }
8
+ export interface DockerDeploymentRunMetadata {
9
+ containerId?: string;
10
+ imageId?: string;
11
+ }
12
+ export interface DockerDeploymentRunInvocationInput {
13
+ command: string;
14
+ containerName: string | null;
15
+ deploymentName?: string | null;
16
+ detach: boolean;
17
+ dockerContext?: string | null;
18
+ dockerHost?: string | null;
19
+ }
20
+ export interface DockerDeploymentReportInput {
21
+ compile_fingerprint?: string;
22
+ container?: {
23
+ runtime_instances: Array<{
24
+ id: string;
25
+ }>;
26
+ };
27
+ nodes: DockerDeploymentNodeInput[];
28
+ root: string;
29
+ }
30
+ export interface WriteDockerDeploymentRecordInput {
31
+ authProfileName: string | null;
32
+ compileFingerprint: string;
33
+ containerName: string | null;
34
+ deploymentName?: string;
35
+ envFilePath?: string;
36
+ imageTag: string;
37
+ nodes: DockerDeploymentNodeInput[];
38
+ outputDirectory: string;
39
+ projectRoot: string;
40
+ runMetadata?: DockerDeploymentRunMetadata;
41
+ runtimeInstanceIds: string[];
42
+ source?: DeploymentRecordSource;
43
+ target: DockerDeploymentTarget;
44
+ }
45
+ export declare const createDockerDeploymentUnitId: (deploymentName: string) => string;
46
+ export declare const createDockerDeploymentRecord: (input: WriteDockerDeploymentRecordInput) => DeploymentRecord;
47
+ export declare const createDockerProjectLabel: (projectRoot: string, projectName?: string) => string;
48
+ export declare const writeDockerDeploymentRecord: (input: WriteDockerDeploymentRecordInput) => Promise<string>;
49
+ export declare const writeDockerDeploymentRecordForRun: (input: {
50
+ authProfileName: string | null;
51
+ envFilePath?: string;
52
+ imageTag: string;
53
+ invocation: DockerDeploymentRunInvocationInput;
54
+ outputDirectory: string;
55
+ report: DockerDeploymentReportInput;
56
+ runMetadata?: DockerDeploymentRunMetadata;
57
+ targetExecFile?: DockerTargetExecFile;
58
+ targetTimeoutMs?: number;
59
+ }) => Promise<string | null>;
@@ -0,0 +1,76 @@
1
+ import path from "node:path";
2
+ import { normalizeProjectLabelSlug } from "../distribution/index.js";
3
+ import { SpawnfileError } from "../shared/index.js";
4
+ import { normalizeDeploymentName } from "./names.js";
5
+ import { resolveDockerDeploymentTarget } from "./target.js";
6
+ import { writeDeploymentRecord } from "./record.js";
7
+ const resolveProjectSlug = (projectRoot) => {
8
+ const base = path.basename(projectRoot).toLowerCase() === "spawnfile"
9
+ ? path.basename(path.dirname(projectRoot))
10
+ : path.basename(projectRoot);
11
+ return base
12
+ .toLowerCase()
13
+ .replace(/[^a-z0-9]+/g, "-")
14
+ .replace(/^-+|-+$/g, "") || "project";
15
+ };
16
+ export const createDockerDeploymentUnitId = (deploymentName) => `${deploymentName}-container`;
17
+ export const createDockerDeploymentRecord = (input) => {
18
+ const name = normalizeDeploymentName(input.deploymentName);
19
+ return {
20
+ auth_profile: input.authProfileName,
21
+ compile_fingerprint: input.compileFingerprint,
22
+ created_at: new Date().toISOString(),
23
+ ...(input.envFilePath ? { env_file: path.resolve(input.envFilePath) } : {}),
24
+ manager: "docker",
25
+ name,
26
+ output_directory: path.resolve(input.outputDirectory),
27
+ source: input.source ?? { kind: "project", root: path.resolve(input.projectRoot) },
28
+ target: input.target,
29
+ units: [
30
+ {
31
+ container_id: input.runMetadata?.containerId ?? null,
32
+ container_name: input.containerName,
33
+ contains: input.nodes.map((node) => ({ id: node.id, kind: node.kind })),
34
+ id: createDockerDeploymentUnitId(name),
35
+ image_id: input.runMetadata?.imageId ?? null,
36
+ image_tag: input.imageTag,
37
+ kind: "container",
38
+ runtime_instances: [...input.runtimeInstanceIds].sort()
39
+ }
40
+ ],
41
+ version: "spawnfile.deployment.v2"
42
+ };
43
+ };
44
+ export const createDockerProjectLabel = (projectRoot, projectName) => projectName ? normalizeProjectLabelSlug(projectName) : resolveProjectSlug(projectRoot);
45
+ const requireCompileFingerprint = (fingerprint) => {
46
+ if (!fingerprint) {
47
+ throw new SpawnfileError("validation_error", "Detached deployment records require compile_fingerprint in the compile report");
48
+ }
49
+ return fingerprint;
50
+ };
51
+ export const writeDockerDeploymentRecord = async (input) => writeDeploymentRecord(input.outputDirectory, createDockerDeploymentRecord(input));
52
+ export const writeDockerDeploymentRecordForRun = async (input) => {
53
+ if (!input.invocation.detach || !input.invocation.deploymentName) {
54
+ return null;
55
+ }
56
+ return writeDockerDeploymentRecord({
57
+ authProfileName: input.authProfileName,
58
+ compileFingerprint: requireCompileFingerprint(input.report.compile_fingerprint),
59
+ containerName: input.invocation.containerName,
60
+ deploymentName: input.invocation.deploymentName,
61
+ envFilePath: input.envFilePath,
62
+ imageTag: input.imageTag,
63
+ nodes: input.report.nodes,
64
+ outputDirectory: input.outputDirectory,
65
+ projectRoot: input.report.root,
66
+ runMetadata: input.runMetadata,
67
+ runtimeInstanceIds: input.report.container?.runtime_instances.map((instance) => instance.id) ?? [],
68
+ target: await resolveDockerDeploymentTarget({
69
+ context: input.invocation.dockerContext ?? undefined,
70
+ dockerCommand: input.invocation.command,
71
+ dockerHost: input.invocation.dockerHost ?? undefined,
72
+ execFile: input.targetExecFile,
73
+ timeoutMs: input.targetTimeoutMs
74
+ })
75
+ });
76
+ };
@@ -0,0 +1,16 @@
1
+ import type { RuntimeProbeGateway } from "../runtime/index.js";
2
+ import type { DeploymentRecord } from "./record.js";
3
+ import type { DockerUnitInspection } from "./dockerInspect.js";
4
+ export type DockerProbeExecFile = (file: string, args: string[], options: {
5
+ timeout: number;
6
+ }) => Promise<{
7
+ stderr: string;
8
+ stdout: string;
9
+ }>;
10
+ export interface DockerProbeGatewayOptions {
11
+ dockerCommand?: string;
12
+ execFile?: DockerProbeExecFile;
13
+ inspection: DockerUnitInspection;
14
+ timeoutMs?: number;
15
+ }
16
+ export declare const createDockerProbeGateway: (record: DeploymentRecord, unit: DeploymentRecord["units"][number], options: DockerProbeGatewayOptions) => RuntimeProbeGateway;
@@ -0,0 +1,48 @@
1
+ import { execFile as execFileCallback } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { dockerContextNameForTarget } from "./target.js";
4
+ const execFile = promisify(execFileCallback);
5
+ const targetRefForUnit = (unit) => {
6
+ const targetRef = unit.container_id ?? unit.container_name;
7
+ if (!targetRef) {
8
+ throw new Error(`deployment unit ${unit.id} has no recorded container id or name`);
9
+ }
10
+ return targetRef;
11
+ };
12
+ const withDockerTarget = (record, args) => {
13
+ const context = dockerContextNameForTarget(record.target);
14
+ if (context) {
15
+ return ["--context", context, ...args];
16
+ }
17
+ if (record.target.kind === "host") {
18
+ return ["--host", record.target.value, ...args];
19
+ }
20
+ return args;
21
+ };
22
+ export const createDockerProbeGateway = (record, unit, options) => {
23
+ const dockerCommand = options.dockerCommand ?? "docker";
24
+ const runExec = options.execFile ?? execFile;
25
+ const timeout = options.timeoutMs ?? 10_000;
26
+ const exec = async (command) => {
27
+ const targetRef = targetRefForUnit(unit);
28
+ return runExec(dockerCommand, withDockerTarget(record, ["exec", targetRef, ...command]), { timeout });
29
+ };
30
+ return {
31
+ exec,
32
+ async httpGet(port, requestPath, headers = {}) {
33
+ const url = `http://127.0.0.1:${port}${requestPath.startsWith("/") ? requestPath : `/${requestPath}`}`;
34
+ try {
35
+ const headerArgs = Object.entries(headers).flatMap(([name, value]) => ["-H", `${name}: ${value}`]);
36
+ const result = await exec(["curl", "-fsS", ...headerArgs, url]);
37
+ return { body: result.stdout, ok: true };
38
+ }
39
+ catch (error) {
40
+ const message = error instanceof Error ? error.message : String(error);
41
+ return { body: "", error: message, ok: false };
42
+ }
43
+ },
44
+ async inspectUnit() {
45
+ return options.inspection;
46
+ }
47
+ };
48
+ };
@@ -0,0 +1,17 @@
1
+ import type { DeploymentRecord } from "./record.js";
2
+ import { type DockerTargetExecFile } from "./target.js";
3
+ export interface RecoverDockerDeploymentRecordsOptions {
4
+ contains?: DeploymentRecord["units"][number]["contains"];
5
+ context: string;
6
+ dockerCommand?: string;
7
+ execFile?: DockerTargetExecFile;
8
+ outputDirectory: string;
9
+ projectLabel: string;
10
+ runtimeInstanceIds?: string[];
11
+ sourceRoot: string;
12
+ timeoutMs?: number;
13
+ }
14
+ export declare const recoverDockerDeploymentRecords: (options: RecoverDockerDeploymentRecordsOptions) => Promise<Array<{
15
+ path: string;
16
+ record: DeploymentRecord;
17
+ }>>;
@@ -0,0 +1,129 @@
1
+ import { execFile as execFileCallback } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { SpawnfileError } from "../shared/index.js";
4
+ import { dockerDeploymentLabelKeys } from "./dockerLabels.js";
5
+ import { resolveDockerDeploymentTarget } from "./target.js";
6
+ const execFile = promisify(execFileCallback);
7
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8
+ const toStringOrNull = (value) => typeof value === "string" && value.length > 0 ? value : null;
9
+ const labelsFrom = (inspection) => {
10
+ const labels = inspection.Config?.Labels;
11
+ if (!isRecord(labels)) {
12
+ return {};
13
+ }
14
+ return Object.fromEntries(Object.entries(labels).filter((entry) => typeof entry[1] === "string"));
15
+ };
16
+ const withContext = (context, args) => [
17
+ "--context",
18
+ context,
19
+ ...args
20
+ ];
21
+ const containerNameFrom = (inspection) => {
22
+ const name = toStringOrNull(inspection.Name);
23
+ return name ? name.replace(/^\//u, "") : null;
24
+ };
25
+ const parseInspections = (stdout) => {
26
+ let parsed;
27
+ try {
28
+ parsed = JSON.parse(stdout);
29
+ }
30
+ catch (error) {
31
+ throw new SpawnfileError("runtime_error", `Unable to parse recovered Docker container inspection: ${error instanceof Error ? error.message : String(error)}`);
32
+ }
33
+ return Array.isArray(parsed) ? parsed.filter(isRecord) : [];
34
+ };
35
+ const deploymentNameFor = (labels) => labels[dockerDeploymentLabelKeys.deployment] ?? null;
36
+ const buildUnit = (inspection, labels, contains, runtimeInstanceIds) => {
37
+ const unitId = labels[dockerDeploymentLabelKeys.unit];
38
+ const imageTag = toStringOrNull(inspection.Config?.Image);
39
+ if (!unitId || !imageTag) {
40
+ return null;
41
+ }
42
+ return {
43
+ container_id: toStringOrNull(inspection.Id),
44
+ container_name: containerNameFrom(inspection),
45
+ contains,
46
+ id: unitId,
47
+ image_id: toStringOrNull(inspection.Image),
48
+ image_tag: imageTag,
49
+ kind: "container",
50
+ runtime_instances: runtimeInstanceIds
51
+ };
52
+ };
53
+ const groupByDeployment = (inspections, options, target) => {
54
+ const records = new Map();
55
+ const unitsByDeployment = new Map();
56
+ const contains = options.contains ?? [];
57
+ const runtimeInstanceIds = options.runtimeInstanceIds ?? [];
58
+ for (const inspection of inspections) {
59
+ const labels = labelsFrom(inspection);
60
+ if (labels[dockerDeploymentLabelKeys.project] !== options.projectLabel) {
61
+ continue;
62
+ }
63
+ const name = deploymentNameFor(labels);
64
+ const compileFingerprint = labels[dockerDeploymentLabelKeys.compileFingerprint];
65
+ if (!name || !compileFingerprint) {
66
+ continue;
67
+ }
68
+ const unit = buildUnit(inspection, labels, contains, runtimeInstanceIds);
69
+ if (!unit) {
70
+ continue;
71
+ }
72
+ const seenUnits = unitsByDeployment.get(name) ?? new Set();
73
+ if (seenUnits.has(unit.id)) {
74
+ throw new SpawnfileError("validation_error", `Recovered deployment "${name}" has multiple containers for unit "${unit.id}"`);
75
+ }
76
+ seenUnits.add(unit.id);
77
+ unitsByDeployment.set(name, seenUnits);
78
+ const record = records.get(name) ?? {
79
+ auth_profile: null,
80
+ compile_fingerprint: compileFingerprint,
81
+ created_at: toStringOrNull(inspection.Created) ?? new Date().toISOString(),
82
+ manager: "docker",
83
+ name,
84
+ output_directory: options.outputDirectory,
85
+ source: { kind: "project", root: options.sourceRoot },
86
+ target,
87
+ units: [],
88
+ version: "spawnfile.deployment.v2"
89
+ };
90
+ record.units.push(unit);
91
+ records.set(name, record);
92
+ }
93
+ return [...records.values()]
94
+ .sort((left, right) => left.name.localeCompare(right.name))
95
+ .map((record) => ({
96
+ path: `docker-context://${options.context}/${record.name}`,
97
+ record: {
98
+ ...record,
99
+ units: [...record.units].sort((left, right) => left.id.localeCompare(right.id))
100
+ }
101
+ }));
102
+ };
103
+ export const recoverDockerDeploymentRecords = async (options) => {
104
+ const resolved = {
105
+ dockerCommand: options.dockerCommand ?? "docker",
106
+ execFile: options.execFile ?? execFile,
107
+ timeoutMs: options.timeoutMs ?? 10_000
108
+ };
109
+ const target = await resolveDockerDeploymentTarget({
110
+ context: options.context,
111
+ dockerCommand: resolved.dockerCommand,
112
+ execFile: resolved.execFile,
113
+ timeoutMs: resolved.timeoutMs
114
+ });
115
+ const { stdout: listStdout } = await resolved.execFile(resolved.dockerCommand, withContext(options.context, [
116
+ "ps",
117
+ "-a",
118
+ "--filter",
119
+ `label=${dockerDeploymentLabelKeys.version}`,
120
+ "--format",
121
+ "{{.ID}}"
122
+ ]), { timeout: resolved.timeoutMs });
123
+ const ids = listStdout.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
124
+ if (ids.length === 0) {
125
+ return [];
126
+ }
127
+ const { stdout } = await resolved.execFile(resolved.dockerCommand, withContext(options.context, ["inspect", ...ids]), { timeout: resolved.timeoutMs });
128
+ return groupByDeployment(parseInspections(stdout), options, target);
129
+ };
@@ -0,0 +1,19 @@
1
+ import type { DistributionReport } from "../distribution/index.js";
2
+ import { type DeploymentRecord } from "./record.js";
3
+ export declare const resolveHomeDeploymentsDirectory: () => string;
4
+ export declare const resolveHomeDeploymentDirectory: (deploymentName: string) => string;
5
+ export declare const resolveHomeRecordPath: (deploymentName: string) => string;
6
+ export declare const resolveHomeReportPath: (deploymentName: string) => string;
7
+ export declare const acquireHomeDeploymentLock: (deploymentName: string) => Promise<() => Promise<void>>;
8
+ /** Writes the record and the cached distribution report together, record last. */
9
+ export declare const writeHomeDeployment: (record: DeploymentRecord, report: DistributionReport) => Promise<{
10
+ recordPath: string;
11
+ reportPath: string;
12
+ }>;
13
+ export declare const readHomeDeploymentRecord: (deploymentName: string) => Promise<DeploymentRecord>;
14
+ export declare const readHomeDeploymentReport: (deploymentName: string) => Promise<string>;
15
+ export declare const homeDeploymentExists: (deploymentName: string) => Promise<boolean>;
16
+ export declare const listHomeDeploymentRecords: () => Promise<Array<{
17
+ path: string;
18
+ record: DeploymentRecord;
19
+ }>>;