spawnfile 0.1.11 → 0.1.12

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 (182) hide show
  1. package/README.md +13 -2
  2. package/dist/cli/lifecycleCommands.js +101 -1
  3. package/dist/cli/modelCommands.js +3 -0
  4. package/dist/cli/resolveCommandInput.d.ts +19 -0
  5. package/dist/cli/resolveCommandInput.js +25 -0
  6. package/dist/cli/runCli.d.ts +4 -1
  7. package/dist/cli/runCli.js +37 -13
  8. package/dist/cli/runtimeCommands.js +1 -0
  9. package/dist/cli/statusCommand.d.ts +42 -0
  10. package/dist/cli/statusCommand.js +394 -0
  11. package/dist/cli/surfaceCommands.js +4 -0
  12. package/dist/compiler/agentSurfaces.js +2 -4
  13. package/dist/compiler/compileProject.js +19 -10
  14. package/dist/compiler/containerArtifacts.d.ts +1 -0
  15. package/dist/compiler/containerArtifacts.js +208 -32
  16. package/dist/compiler/containerArtifactsPlans.js +7 -4
  17. package/dist/compiler/containerArtifactsRender.d.ts +7 -1
  18. package/dist/compiler/containerArtifactsRender.js +6 -0
  19. package/dist/compiler/containerArtifactsTypes.d.ts +9 -0
  20. package/dist/compiler/containerEntrypointRender.js +18 -31
  21. package/dist/compiler/index.d.ts +1 -0
  22. package/dist/compiler/index.js +1 -0
  23. package/dist/compiler/moltnetArtifacts.js +5 -0
  24. package/dist/compiler/moltnetClientConfig.d.ts +1 -1
  25. package/dist/compiler/moltnetClientConfig.js +3 -16
  26. package/dist/compiler/moltnetNodeConfig.js +2 -4
  27. package/dist/compiler/moltnetResolution.js +1 -1
  28. package/dist/compiler/moltnetRoomMemberships.js +1 -2
  29. package/dist/compiler/moltnetRuntimeConfig.d.ts +1 -1
  30. package/dist/compiler/moltnetRuntimeConfig.js +1 -13
  31. package/dist/compiler/networkBinding.d.ts +14 -0
  32. package/dist/compiler/networkBinding.js +34 -0
  33. package/dist/compiler/publishProject.d.ts +13 -0
  34. package/dist/compiler/publishProject.js +72 -0
  35. package/dist/compiler/runProject.d.ts +26 -12
  36. package/dist/compiler/runProject.js +102 -125
  37. package/dist/compiler/runProjectAuth.d.ts +5 -0
  38. package/dist/compiler/runProjectAuth.js +98 -0
  39. package/dist/compiler/runProjectDocker.d.ts +19 -0
  40. package/dist/compiler/runProjectDocker.js +57 -0
  41. package/dist/compiler/types.d.ts +1 -2
  42. package/dist/compiler/upProject.d.ts +6 -0
  43. package/dist/compiler/upProject.js +42 -8
  44. package/dist/compiler/view/buildOrganizationView.js +4 -1
  45. package/dist/compiler/view/declarations.d.ts +4 -0
  46. package/dist/compiler/view/declarations.js +104 -0
  47. package/dist/compiler/view/renderNetworks.js +1 -2
  48. package/dist/compiler/view/renderTree.js +6 -2
  49. package/dist/compiler/view/types.d.ts +64 -1
  50. package/dist/deployment/dockerInspect.d.ts +29 -0
  51. package/dist/deployment/dockerInspect.js +130 -0
  52. package/dist/deployment/dockerLabels.d.ts +16 -0
  53. package/dist/deployment/dockerLabels.js +32 -0
  54. package/dist/deployment/dockerLogs.d.ts +24 -0
  55. package/dist/deployment/dockerLogs.js +96 -0
  56. package/dist/deployment/dockerManager.d.ts +59 -0
  57. package/dist/deployment/dockerManager.js +76 -0
  58. package/dist/deployment/dockerProbeGateway.d.ts +16 -0
  59. package/dist/deployment/dockerProbeGateway.js +48 -0
  60. package/dist/deployment/homeStore.d.ts +19 -0
  61. package/dist/deployment/homeStore.js +130 -0
  62. package/dist/deployment/index.d.ts +9 -0
  63. package/dist/deployment/index.js +9 -0
  64. package/dist/deployment/names.d.ts +3 -0
  65. package/dist/deployment/names.js +12 -0
  66. package/dist/deployment/record.d.ts +76 -0
  67. package/dist/deployment/record.js +174 -0
  68. package/dist/deployment/target.d.ts +35 -0
  69. package/dist/deployment/target.js +96 -0
  70. package/dist/distribution/buildDistributionReport.d.ts +22 -0
  71. package/dist/distribution/buildDistributionReport.js +51 -0
  72. package/dist/distribution/consumeImage.d.ts +29 -0
  73. package/dist/distribution/consumeImage.js +254 -0
  74. package/dist/distribution/consumeImageSupport.d.ts +18 -0
  75. package/dist/distribution/consumeImageSupport.js +63 -0
  76. package/dist/distribution/distributionReportSchema.d.ts +84 -0
  77. package/dist/distribution/distributionReportSchema.js +107 -0
  78. package/dist/distribution/dockerRunner.d.ts +5 -0
  79. package/dist/distribution/dockerRunner.js +21 -0
  80. package/dist/distribution/extractImage.d.ts +25 -0
  81. package/dist/distribution/extractImage.js +96 -0
  82. package/dist/distribution/fingerprint.d.ts +2 -0
  83. package/dist/distribution/fingerprint.js +14 -0
  84. package/dist/distribution/imageRef.d.ts +9 -0
  85. package/dist/distribution/imageRef.js +65 -0
  86. package/dist/distribution/imageRuntimeAuth.d.ts +19 -0
  87. package/dist/distribution/imageRuntimeAuth.js +71 -0
  88. package/dist/distribution/index.d.ts +24 -0
  89. package/dist/distribution/index.js +16 -0
  90. package/dist/distribution/preflight.d.ts +11 -0
  91. package/dist/distribution/preflight.js +65 -0
  92. package/dist/distribution/projectImageView.d.ts +8 -0
  93. package/dist/distribution/projectImageView.js +83 -0
  94. package/dist/distribution/projectName.d.ts +1 -0
  95. package/dist/distribution/projectName.js +12 -0
  96. package/dist/distribution/renderImageInterface.d.ts +30 -0
  97. package/dist/distribution/renderImageInterface.js +86 -0
  98. package/dist/distribution/tarReader.d.ts +10 -0
  99. package/dist/distribution/tarReader.js +69 -0
  100. package/dist/distribution/types.d.ts +80 -0
  101. package/dist/distribution/types.js +4 -0
  102. package/dist/distribution/verifyDistributionReport.d.ts +12 -0
  103. package/dist/distribution/verifyDistributionReport.js +29 -0
  104. package/dist/filesystem/paths.d.ts +6 -0
  105. package/dist/filesystem/paths.js +11 -0
  106. package/dist/manifest/loadManifest.js +22 -2
  107. package/dist/manifest/renderSpawnfile.js +2 -4
  108. package/dist/manifest/schemas.d.ts +17 -41
  109. package/dist/manifest/surfaceSchemas.d.ts +19 -47
  110. package/dist/manifest/surfaceSchemas.js +3 -6
  111. package/dist/report/createReport.d.ts +7 -1
  112. package/dist/report/createReport.js +28 -1
  113. package/dist/report/types.d.ts +43 -0
  114. package/dist/runtime/container.js +0 -16
  115. package/dist/runtime/index.d.ts +1 -0
  116. package/dist/runtime/index.js +1 -0
  117. package/dist/runtime/install.d.ts +0 -10
  118. package/dist/runtime/install.js +0 -45
  119. package/dist/runtime/openclaw/adapter.js +9 -1
  120. package/dist/runtime/openclaw/configAuth.d.ts +15 -0
  121. package/dist/runtime/openclaw/configAuth.js +46 -0
  122. package/dist/runtime/openclaw/runAuth.d.ts +6 -0
  123. package/dist/runtime/openclaw/runAuth.js +7 -55
  124. package/dist/runtime/openclaw/statusProbes.d.ts +2 -0
  125. package/dist/runtime/openclaw/statusProbes.js +31 -0
  126. package/dist/runtime/picoclaw/adapter.js +10 -0
  127. package/dist/runtime/picoclaw/runAuth.d.ts +7 -0
  128. package/dist/runtime/picoclaw/runAuth.js +8 -68
  129. package/dist/runtime/picoclaw/statusProbes.d.ts +2 -0
  130. package/dist/runtime/picoclaw/statusProbes.js +100 -0
  131. package/dist/runtime/registry.d.ts +0 -5
  132. package/dist/runtime/registry.js +2 -12
  133. package/dist/runtime/statusProbes.d.ts +16 -0
  134. package/dist/runtime/statusProbes.js +41 -0
  135. package/dist/runtime/types.d.ts +52 -1
  136. package/dist/shared/errors.d.ts +6 -0
  137. package/dist/shared/errors.js +9 -0
  138. package/dist/status/buildStatus.d.ts +13 -0
  139. package/dist/status/buildStatus.js +266 -0
  140. package/dist/status/compileReport.d.ts +96 -0
  141. package/dist/status/compileReport.js +230 -0
  142. package/dist/status/compiledContainerObservations.d.ts +5 -0
  143. package/dist/status/compiledContainerObservations.js +63 -0
  144. package/dist/status/deploymentLogs.d.ts +12 -0
  145. package/dist/status/deploymentLogs.js +45 -0
  146. package/dist/status/deployments.d.ts +14 -0
  147. package/dist/status/deployments.js +128 -0
  148. package/dist/status/imageStatusReport.d.ts +10 -0
  149. package/dist/status/imageStatusReport.js +73 -0
  150. package/dist/status/index.d.ts +14 -0
  151. package/dist/status/index.js +14 -0
  152. package/dist/status/moltnetProbes.d.ts +21 -0
  153. package/dist/status/moltnetProbes.js +240 -0
  154. package/dist/status/registryDrift.d.ts +28 -0
  155. package/dist/status/registryDrift.js +119 -0
  156. package/dist/status/renderStatus.d.ts +2 -0
  157. package/dist/status/renderStatus.js +230 -0
  158. package/dist/status/runtimeProbes.d.ts +13 -0
  159. package/dist/status/runtimeProbes.js +111 -0
  160. package/dist/status/selectionSubjects.d.ts +8 -0
  161. package/dist/status/selectionSubjects.js +77 -0
  162. package/dist/status/selectors.d.ts +14 -0
  163. package/dist/status/selectors.js +90 -0
  164. package/dist/status/traversal.d.ts +6 -0
  165. package/dist/status/traversal.js +15 -0
  166. package/dist/status/types.d.ts +113 -0
  167. package/dist/status/types.js +1 -0
  168. package/package.json +4 -2
  169. package/runtimes.yaml +22 -15
  170. package/dist/runtime/tinyclaw/adapter.d.ts +0 -2
  171. package/dist/runtime/tinyclaw/adapter.js +0 -284
  172. package/dist/runtime/tinyclaw/runAuth.d.ts +0 -2
  173. package/dist/runtime/tinyclaw/runAuth.js +0 -49
  174. package/dist/runtime/tinyclaw/scaffold-assets/AGENTS.md +0 -160
  175. package/dist/runtime/tinyclaw/scaffold-assets/CLAUDE.md +0 -5
  176. package/dist/runtime/tinyclaw/scaffold-assets/SOUL.md +0 -177
  177. package/dist/runtime/tinyclaw/scaffold.d.ts +0 -2
  178. package/dist/runtime/tinyclaw/scaffold.js +0 -24
  179. package/dist/runtime/tinyclaw/schedules.d.ts +0 -9
  180. package/dist/runtime/tinyclaw/schedules.js +0 -62
  181. package/dist/runtime/tinyclaw/surfaces.d.ts +0 -8
  182. package/dist/runtime/tinyclaw/surfaces.js +0 -94
@@ -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,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
+ }>>;
@@ -0,0 +1,130 @@
1
+ import path from "node:path";
2
+ import { open, readdir, rename, rm, writeFile } from "node:fs/promises";
3
+ import { resolveSpawnfileHome } from "../auth/index.js";
4
+ import { ensureDirectory, readUtf8File } from "../filesystem/index.js";
5
+ import { SpawnfileError } from "../shared/index.js";
6
+ import { normalizeDeploymentName } from "./names.js";
7
+ import { parseDeploymentRecord } from "./record.js";
8
+ export const resolveHomeDeploymentsDirectory = () => path.join(resolveSpawnfileHome(), "deployments");
9
+ export const resolveHomeDeploymentDirectory = (deploymentName) => path.join(resolveHomeDeploymentsDirectory(), normalizeDeploymentName(deploymentName));
10
+ export const resolveHomeRecordPath = (deploymentName) => path.join(resolveHomeDeploymentDirectory(deploymentName), "record.json");
11
+ export const resolveHomeReportPath = (deploymentName) => path.join(resolveHomeDeploymentDirectory(deploymentName), "spawnfile-report.json");
12
+ /**
13
+ * Acquires an exclusive lock for a home deployment so concurrent `up`
14
+ * invocations cannot race on the same record (and orphan each other's
15
+ * containers). Returns a release function. Throws if the deployment is already
16
+ * locked by another in-flight operation.
17
+ */
18
+ const isProcessAlive = (pid) => {
19
+ try {
20
+ process.kill(pid, 0);
21
+ return true;
22
+ }
23
+ catch (error) {
24
+ // ESRCH means no such process (stale); EPERM means it exists but is ours to
25
+ // not signal — treat as alive to stay safe.
26
+ return error.code === "EPERM";
27
+ }
28
+ };
29
+ export const acquireHomeDeploymentLock = async (deploymentName) => {
30
+ const directory = resolveHomeDeploymentDirectory(deploymentName);
31
+ await ensureDirectory(directory);
32
+ const lockPath = path.join(directory, ".lock");
33
+ const write = async () => {
34
+ const handle = await open(lockPath, "wx");
35
+ try {
36
+ await handle.write(JSON.stringify({ pid: process.pid }));
37
+ }
38
+ finally {
39
+ await handle.close();
40
+ }
41
+ };
42
+ try {
43
+ await write();
44
+ }
45
+ catch (error) {
46
+ if (error.code !== "EEXIST") {
47
+ throw error;
48
+ }
49
+ // A lock exists. Reclaim it only if the owning process is gone (a crashed
50
+ // deploy must not lock the deployment forever); otherwise it is genuinely busy.
51
+ let ownerPid = null;
52
+ try {
53
+ ownerPid = JSON.parse(await readUtf8File(lockPath)).pid ?? null;
54
+ }
55
+ catch {
56
+ ownerPid = null;
57
+ }
58
+ if (ownerPid !== null && isProcessAlive(ownerPid)) {
59
+ throw new SpawnfileError("runtime_error", `Deployment "${normalizeDeploymentName(deploymentName)}" is already being modified by another operation`);
60
+ }
61
+ await rm(lockPath, { force: true });
62
+ await write();
63
+ }
64
+ return async () => {
65
+ await rm(lockPath, { force: true });
66
+ };
67
+ };
68
+ const writeAtomic = async (filePath, content) => {
69
+ const temporaryPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
70
+ await writeFile(temporaryPath, content, "utf8");
71
+ await rename(temporaryPath, filePath);
72
+ };
73
+ /** Writes the record and the cached distribution report together, record last. */
74
+ export const writeHomeDeployment = async (record, report) => {
75
+ const parsed = parseDeploymentRecord(record);
76
+ const directory = resolveHomeDeploymentDirectory(parsed.name);
77
+ await ensureDirectory(directory);
78
+ const reportPath = resolveHomeReportPath(parsed.name);
79
+ const recordPath = resolveHomeRecordPath(parsed.name);
80
+ await writeAtomic(reportPath, `${JSON.stringify(report, null, 2)}\n`);
81
+ await writeAtomic(recordPath, `${JSON.stringify(parsed, null, 2)}\n`);
82
+ return { recordPath, reportPath };
83
+ };
84
+ export const readHomeDeploymentRecord = async (deploymentName) => {
85
+ const recordPath = resolveHomeRecordPath(deploymentName);
86
+ let parsed;
87
+ try {
88
+ parsed = JSON.parse(await readUtf8File(recordPath));
89
+ }
90
+ catch (error) {
91
+ const reason = error instanceof Error ? error.message : String(error);
92
+ throw new SpawnfileError("validation_error", `Unable to read home deployment record ${recordPath}: ${reason}`);
93
+ }
94
+ return parseDeploymentRecord(parsed, recordPath);
95
+ };
96
+ export const readHomeDeploymentReport = async (deploymentName) => readUtf8File(resolveHomeReportPath(deploymentName));
97
+ export const homeDeploymentExists = async (deploymentName) => {
98
+ const records = await listHomeDeploymentRecords();
99
+ const target = normalizeDeploymentName(deploymentName);
100
+ return records.some((entry) => entry.record.name === target);
101
+ };
102
+ export const listHomeDeploymentRecords = async () => {
103
+ const directory = resolveHomeDeploymentsDirectory();
104
+ let entries;
105
+ try {
106
+ entries = await readdir(directory);
107
+ }
108
+ catch (error) {
109
+ if (error.code === "ENOENT") {
110
+ return [];
111
+ }
112
+ const reason = error instanceof Error ? error.message : String(error);
113
+ throw new SpawnfileError("validation_error", `Unable to read home deployments directory ${directory}: ${reason}`);
114
+ }
115
+ const records = await Promise.all(entries
116
+ .sort()
117
+ .map(async (entry) => {
118
+ const recordPath = path.join(directory, entry, "record.json");
119
+ try {
120
+ const parsed = JSON.parse(await readUtf8File(recordPath));
121
+ return { path: recordPath, record: parseDeploymentRecord(parsed, recordPath) };
122
+ }
123
+ catch {
124
+ return null;
125
+ }
126
+ }));
127
+ return records
128
+ .filter((entry) => entry !== null)
129
+ .sort((left, right) => left.record.name.localeCompare(right.record.name));
130
+ };
@@ -0,0 +1,9 @@
1
+ export * from "./dockerLabels.js";
2
+ export * from "./dockerInspect.js";
3
+ export * from "./dockerLogs.js";
4
+ export * from "./dockerManager.js";
5
+ export * from "./dockerProbeGateway.js";
6
+ export * from "./homeStore.js";
7
+ export * from "./names.js";
8
+ export * from "./record.js";
9
+ export * from "./target.js";
@@ -0,0 +1,9 @@
1
+ export * from "./dockerLabels.js";
2
+ export * from "./dockerInspect.js";
3
+ export * from "./dockerLogs.js";
4
+ export * from "./dockerManager.js";
5
+ export * from "./dockerProbeGateway.js";
6
+ export * from "./homeStore.js";
7
+ export * from "./names.js";
8
+ export * from "./record.js";
9
+ export * from "./target.js";
@@ -0,0 +1,3 @@
1
+ export declare const normalizeDeploymentName: (name: string | undefined) => string;
2
+ export declare const resolveDeploymentRecordsDirectory: (outputDirectory: string) => string;
3
+ export declare const resolveDeploymentRecordPath: (outputDirectory: string, deploymentName: string) => string;
@@ -0,0 +1,12 @@
1
+ import path from "node:path";
2
+ import { SpawnfileError } from "../shared/index.js";
3
+ const deploymentNamePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
4
+ export const normalizeDeploymentName = (name) => {
5
+ const normalized = (name ?? "default").trim();
6
+ if (!deploymentNamePattern.test(normalized)) {
7
+ throw new SpawnfileError("validation_error", `Deployment name must be kebab-case: ${name ?? ""}`);
8
+ }
9
+ return normalized;
10
+ };
11
+ export const resolveDeploymentRecordsDirectory = (outputDirectory) => path.join(outputDirectory, "deployments");
12
+ export const resolveDeploymentRecordPath = (outputDirectory, deploymentName) => path.join(resolveDeploymentRecordsDirectory(outputDirectory), `${normalizeDeploymentName(deploymentName)}.json`);
@@ -0,0 +1,76 @@
1
+ import { z } from "zod";
2
+ declare const recordSourceSchema: z.ZodUnion<readonly [z.ZodObject<{
3
+ kind: z.ZodLiteral<"project">;
4
+ root: z.ZodString;
5
+ }, z.core.$strict>, z.ZodObject<{
6
+ digest: z.ZodNullable<z.ZodString>;
7
+ kind: z.ZodLiteral<"image">;
8
+ ref: z.ZodString;
9
+ }, z.core.$strict>]>;
10
+ export type DeploymentRecordSource = z.infer<typeof recordSourceSchema>;
11
+ export declare const deploymentRecordSchema: z.ZodObject<{
12
+ auth_profile: z.ZodNullable<z.ZodString>;
13
+ compile_fingerprint: z.ZodString;
14
+ created_at: z.ZodString;
15
+ env_file: z.ZodOptional<z.ZodString>;
16
+ manager: z.ZodString;
17
+ name: z.ZodString;
18
+ output_directory: z.ZodNullable<z.ZodString>;
19
+ source: z.ZodUnion<readonly [z.ZodObject<{
20
+ kind: z.ZodLiteral<"project">;
21
+ root: z.ZodString;
22
+ }, z.core.$strict>, z.ZodObject<{
23
+ digest: z.ZodNullable<z.ZodString>;
24
+ kind: z.ZodLiteral<"image">;
25
+ ref: z.ZodString;
26
+ }, z.core.$strict>]>;
27
+ target: z.ZodUnion<readonly [z.ZodObject<{
28
+ endpoint_fingerprint: z.ZodString;
29
+ kind: z.ZodLiteral<"context">;
30
+ name: z.ZodString;
31
+ }, z.core.$strict>, z.ZodObject<{
32
+ context: z.ZodString;
33
+ endpoint_fingerprint: z.ZodString;
34
+ kind: z.ZodLiteral<"docker-context">;
35
+ }, z.core.$strict>, z.ZodObject<{
36
+ kind: z.ZodLiteral<"host">;
37
+ value: z.ZodString;
38
+ }, z.core.$strict>]>;
39
+ units: z.ZodArray<z.ZodObject<{
40
+ container_id: z.ZodNullable<z.ZodString>;
41
+ container_name: z.ZodNullable<z.ZodString>;
42
+ contains: z.ZodArray<z.ZodObject<{
43
+ id: z.ZodString;
44
+ kind: z.ZodUnion<readonly [z.ZodLiteral<"agent">, z.ZodLiteral<"network">, z.ZodLiteral<"team">]>;
45
+ }, z.core.$strict>>;
46
+ id: z.ZodString;
47
+ image_id: z.ZodNullable<z.ZodString>;
48
+ image_tag: z.ZodString;
49
+ kind: z.ZodLiteral<"container">;
50
+ manager: z.ZodOptional<z.ZodString>;
51
+ runtime_instances: z.ZodArray<z.ZodString>;
52
+ target: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
53
+ endpoint_fingerprint: z.ZodString;
54
+ kind: z.ZodLiteral<"context">;
55
+ name: z.ZodString;
56
+ }, z.core.$strict>, z.ZodObject<{
57
+ context: z.ZodString;
58
+ endpoint_fingerprint: z.ZodString;
59
+ kind: z.ZodLiteral<"docker-context">;
60
+ }, z.core.$strict>, z.ZodObject<{
61
+ kind: z.ZodLiteral<"host">;
62
+ value: z.ZodString;
63
+ }, z.core.$strict>]>>;
64
+ }, z.core.$strict>>;
65
+ version: z.ZodLiteral<"spawnfile.deployment.v2">;
66
+ }, z.core.$strict>;
67
+ export type DeploymentRecord = z.infer<typeof deploymentRecordSchema>;
68
+ export declare const parseDeploymentRecord: (source: unknown, sourceLabel?: string) => DeploymentRecord;
69
+ export declare const readDeploymentRecord: (recordPath: string) => Promise<DeploymentRecord>;
70
+ export declare const readDeploymentRecordFromOutput: (outputDirectory: string, deploymentName: string) => Promise<DeploymentRecord>;
71
+ export declare const listDeploymentRecords: (outputDirectory: string) => Promise<Array<{
72
+ path: string;
73
+ record: DeploymentRecord;
74
+ }>>;
75
+ export declare const writeDeploymentRecord: (outputDirectory: string, record: DeploymentRecord) => Promise<string>;
76
+ export {};