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 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,10 @@
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 "./dockerRecover.js";
7
+ export * from "./homeStore.js";
8
+ export * from "./names.js";
9
+ export * from "./record.js";
10
+ export * from "./target.js";
@@ -0,0 +1,10 @@
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 "./dockerRecover.js";
7
+ export * from "./homeStore.js";
8
+ export * from "./names.js";
9
+ export * from "./record.js";
10
+ 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 {};
@@ -0,0 +1,174 @@
1
+ import path from "node:path";
2
+ import { readdir, rename, writeFile } from "node:fs/promises";
3
+ import { z } from "zod";
4
+ import { ensureDirectory, readUtf8File } from "../filesystem/index.js";
5
+ import { SpawnfileError } from "../shared/index.js";
6
+ import { normalizeDeploymentName, resolveDeploymentRecordPath, resolveDeploymentRecordsDirectory } from "./names.js";
7
+ const contextTargetSchema = z.object({
8
+ endpoint_fingerprint: z.string().regex(/^sha256:[a-f0-9]{32}$/),
9
+ kind: z.literal("context"),
10
+ name: z.string().min(1)
11
+ }).strict();
12
+ const legacyContextTargetSchema = z.object({
13
+ context: z.string().min(1),
14
+ endpoint_fingerprint: z.string().regex(/^sha256:[a-f0-9]{32}$/),
15
+ kind: z.literal("docker-context")
16
+ }).strict();
17
+ const hostTargetSchema = z.object({
18
+ kind: z.literal("host"),
19
+ value: z.string().min(1)
20
+ }).strict();
21
+ const targetSchema = z.union([
22
+ contextTargetSchema,
23
+ legacyContextTargetSchema,
24
+ hostTargetSchema
25
+ ]);
26
+ const unitV1Schema = z.object({
27
+ container_id: z.string().min(1).nullable(),
28
+ container_name: z.string().min(1).nullable(),
29
+ contains: z.array(z.object({
30
+ id: z.string().min(1),
31
+ kind: z.union([z.literal("agent"), z.literal("team")])
32
+ }).strict()),
33
+ id: z.string().min(1),
34
+ image_id: z.string().min(1).nullable(),
35
+ image_tag: z.string().min(1),
36
+ kind: z.literal("container"),
37
+ runtime_instances: z.array(z.string().min(1))
38
+ }).strict();
39
+ const unitSchema = z.object({
40
+ container_id: z.string().min(1).nullable(),
41
+ container_name: z.string().min(1).nullable(),
42
+ contains: z.array(z.object({
43
+ id: z.string().min(1),
44
+ kind: z.union([z.literal("agent"), z.literal("network"), z.literal("team")])
45
+ }).strict()),
46
+ id: z.string().min(1),
47
+ image_id: z.string().min(1).nullable(),
48
+ image_tag: z.string().min(1),
49
+ kind: z.literal("container"),
50
+ manager: z.string().min(1).optional(),
51
+ runtime_instances: z.array(z.string().min(1)),
52
+ target: targetSchema.optional()
53
+ }).strict();
54
+ const recordSourceSchema = z.union([
55
+ z.object({
56
+ kind: z.literal("project"),
57
+ root: z.string().min(1)
58
+ }).strict(),
59
+ z.object({
60
+ digest: z.string().min(1).nullable(),
61
+ kind: z.literal("image"),
62
+ ref: z.string().min(1)
63
+ }).strict()
64
+ ]);
65
+ const deploymentRecordV1Schema = z.object({
66
+ auth_profile: z.string().min(1).nullable(),
67
+ compile_fingerprint: z.string().min(1),
68
+ created_at: z.string().min(1),
69
+ env_file: z.string().min(1).optional(),
70
+ manager: z.literal("docker"),
71
+ name: z.string().min(1),
72
+ output_directory: z.string().min(1),
73
+ project_root: z.string().min(1),
74
+ target: targetSchema,
75
+ units: z.array(unitV1Schema).min(1),
76
+ version: z.literal("spawnfile.deployment.v1")
77
+ }).strict();
78
+ export const deploymentRecordSchema = z.object({
79
+ auth_profile: z.string().min(1).nullable(),
80
+ compile_fingerprint: z.string().min(1),
81
+ created_at: z.string().min(1),
82
+ env_file: z.string().min(1).optional(),
83
+ manager: z.string().min(1),
84
+ name: z.string().min(1),
85
+ output_directory: z.string().min(1).nullable(),
86
+ source: recordSourceSchema,
87
+ target: targetSchema,
88
+ units: z.array(unitSchema).min(1),
89
+ version: z.literal("spawnfile.deployment.v2")
90
+ }).strict();
91
+ const upgradeV1Record = (record) => ({
92
+ auth_profile: record.auth_profile,
93
+ compile_fingerprint: record.compile_fingerprint,
94
+ created_at: record.created_at,
95
+ ...(record.env_file ? { env_file: record.env_file } : {}),
96
+ manager: record.manager,
97
+ name: record.name,
98
+ output_directory: record.output_directory,
99
+ source: { kind: "project", root: record.project_root },
100
+ target: record.target,
101
+ units: record.units,
102
+ version: "spawnfile.deployment.v2"
103
+ });
104
+ const hasVersion = (source, version) => typeof source === "object"
105
+ && source !== null
106
+ && source.version === version;
107
+ export const parseDeploymentRecord = (source, sourceLabel = "deployment record") => {
108
+ let record;
109
+ if (hasVersion(source, "spawnfile.deployment.v1")) {
110
+ const parsed = deploymentRecordV1Schema.safeParse(source);
111
+ if (!parsed.success) {
112
+ throw new SpawnfileError("validation_error", `Invalid ${sourceLabel}: ${z.prettifyError(parsed.error)}`);
113
+ }
114
+ record = upgradeV1Record(parsed.data);
115
+ }
116
+ else {
117
+ const parsed = deploymentRecordSchema.safeParse(source);
118
+ if (!parsed.success) {
119
+ throw new SpawnfileError("validation_error", `Invalid ${sourceLabel}: ${z.prettifyError(parsed.error)}`);
120
+ }
121
+ record = parsed.data;
122
+ }
123
+ const normalizedName = normalizeDeploymentName(record.name);
124
+ if (normalizedName !== record.name) {
125
+ throw new SpawnfileError("validation_error", `Invalid ${sourceLabel}: deployment name must be normalized`);
126
+ }
127
+ return record;
128
+ };
129
+ export const readDeploymentRecord = async (recordPath) => {
130
+ let parsed;
131
+ try {
132
+ parsed = JSON.parse(await readUtf8File(recordPath));
133
+ }
134
+ catch (error) {
135
+ const reason = error instanceof Error ? error.message : String(error);
136
+ throw new SpawnfileError("validation_error", `Unable to read deployment record ${recordPath}: ${reason}`);
137
+ }
138
+ return parseDeploymentRecord(parsed, recordPath);
139
+ };
140
+ export const readDeploymentRecordFromOutput = async (outputDirectory, deploymentName) => readDeploymentRecord(resolveDeploymentRecordPath(outputDirectory, deploymentName));
141
+ export const listDeploymentRecords = async (outputDirectory) => {
142
+ const recordsDirectory = resolveDeploymentRecordsDirectory(outputDirectory);
143
+ let entries;
144
+ try {
145
+ entries = await readdir(recordsDirectory);
146
+ }
147
+ catch (error) {
148
+ if (error.code === "ENOENT") {
149
+ return [];
150
+ }
151
+ const reason = error instanceof Error ? error.message : String(error);
152
+ throw new SpawnfileError("validation_error", `Unable to read deployment records directory ${recordsDirectory}: ${reason}`);
153
+ }
154
+ const records = await Promise.all(entries
155
+ .filter((entry) => entry.endsWith(".json"))
156
+ .sort()
157
+ .map(async (entry) => {
158
+ const recordPath = path.join(recordsDirectory, entry);
159
+ return {
160
+ path: recordPath,
161
+ record: await readDeploymentRecord(recordPath)
162
+ };
163
+ }));
164
+ return records.sort((left, right) => left.record.name.localeCompare(right.record.name));
165
+ };
166
+ export const writeDeploymentRecord = async (outputDirectory, record) => {
167
+ const parsed = parseDeploymentRecord(record);
168
+ const recordPath = resolveDeploymentRecordPath(outputDirectory, parsed.name);
169
+ await ensureDirectory(path.dirname(recordPath));
170
+ const temporaryPath = path.join(path.dirname(recordPath), `.${path.basename(recordPath)}.${process.pid}.${Date.now()}.tmp`);
171
+ await writeFile(temporaryPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
172
+ await rename(temporaryPath, recordPath);
173
+ return recordPath;
174
+ };
@@ -0,0 +1,35 @@
1
+ export type DockerDeploymentTarget = {
2
+ endpoint_fingerprint: string;
3
+ kind: "context";
4
+ name: string;
5
+ } | {
6
+ endpoint_fingerprint: string;
7
+ kind: "docker-context";
8
+ context: string;
9
+ } | {
10
+ kind: "host";
11
+ value: string;
12
+ };
13
+ export type DockerTargetExecFile = (file: string, args: string[], options: {
14
+ timeout: number;
15
+ }) => Promise<{
16
+ stderr: string;
17
+ stdout: string;
18
+ }>;
19
+ export interface ResolveDockerDeploymentTargetOptions {
20
+ context?: string | null;
21
+ dockerCommand?: string;
22
+ dockerHost?: string | null;
23
+ execFile?: DockerTargetExecFile;
24
+ timeoutMs?: number;
25
+ }
26
+ export declare const createEndpointFingerprint: (endpoint: string) => string;
27
+ export declare const createDockerDeploymentTarget: (input: {
28
+ context?: string;
29
+ endpoint?: string;
30
+ host?: string;
31
+ }) => DockerDeploymentTarget;
32
+ export declare const dockerContextNameForTarget: (target: DockerDeploymentTarget) => string | null;
33
+ export declare const dockerHostValueForTarget: (target: DockerDeploymentTarget) => string | null;
34
+ export declare const resolveDockerDeploymentTarget: (input?: ResolveDockerDeploymentTargetOptions) => Promise<DockerDeploymentTarget>;
35
+ export declare const verifyDockerDeploymentTarget: (target: DockerDeploymentTarget, options?: Omit<ResolveDockerDeploymentTargetOptions, "context" | "dockerHost">) => Promise<string | null>;
@@ -0,0 +1,96 @@
1
+ import { createHash } from "node:crypto";
2
+ import { execFile as execFileCallback } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+ import { SpawnfileError } from "../shared/index.js";
5
+ const execFile = promisify(execFileCallback);
6
+ export const createEndpointFingerprint = (endpoint) => {
7
+ const normalized = endpoint.trim();
8
+ if (normalized.length === 0) {
9
+ throw new SpawnfileError("validation_error", "Deployment target endpoint must not be empty");
10
+ }
11
+ return `sha256:${createHash("sha256").update(normalized).digest("hex").slice(0, 32)}`;
12
+ };
13
+ const parseDockerEndpoint = (stdout) => {
14
+ const trimmed = stdout.trim();
15
+ if (trimmed.length === 0) {
16
+ throw new SpawnfileError("runtime_error", "Docker context endpoint was empty");
17
+ }
18
+ try {
19
+ const parsed = JSON.parse(trimmed);
20
+ if (typeof parsed === "string" && parsed.trim().length > 0) {
21
+ return parsed.trim();
22
+ }
23
+ }
24
+ catch {
25
+ // Docker/podman variants may return a plain string instead of JSON.
26
+ }
27
+ return trimmed;
28
+ };
29
+ const resolveDockerContextEndpoint = async (context, options) => {
30
+ try {
31
+ const { stdout } = await options.execFile(options.dockerCommand, ["context", "inspect", context, "--format", "{{json .Endpoints.docker.Host}}"], { timeout: options.timeoutMs });
32
+ return parseDockerEndpoint(stdout);
33
+ }
34
+ catch (error) {
35
+ const reason = error instanceof Error ? error.message : String(error);
36
+ throw new SpawnfileError("runtime_error", `Unable to resolve Docker context "${context}": ${reason}`);
37
+ }
38
+ };
39
+ export const createDockerDeploymentTarget = (input) => {
40
+ const host = input.host?.trim();
41
+ if (host) {
42
+ return {
43
+ kind: "host",
44
+ value: host
45
+ };
46
+ }
47
+ const context = input.context?.trim();
48
+ const endpoint = input.endpoint?.trim();
49
+ if (!context || !endpoint) {
50
+ throw new SpawnfileError("validation_error", "Docker context deployment targets require both context and endpoint");
51
+ }
52
+ return {
53
+ name: context,
54
+ endpoint_fingerprint: createEndpointFingerprint(endpoint),
55
+ kind: "context"
56
+ };
57
+ };
58
+ export const dockerContextNameForTarget = (target) => target.kind === "context"
59
+ ? target.name
60
+ : target.kind === "docker-context"
61
+ ? target.context
62
+ : null;
63
+ export const dockerHostValueForTarget = (target) => target.kind === "host" ? target.value : null;
64
+ export const resolveDockerDeploymentTarget = async (input = {}) => {
65
+ const explicitContext = input.context?.trim();
66
+ const host = explicitContext ? undefined : input.dockerHost?.trim() ?? process.env.DOCKER_HOST?.trim();
67
+ if (host) {
68
+ return createDockerDeploymentTarget({ host });
69
+ }
70
+ const resolvedOptions = {
71
+ dockerCommand: input.dockerCommand ?? "docker",
72
+ execFile: input.execFile ?? execFile,
73
+ timeoutMs: input.timeoutMs ?? 10_000
74
+ };
75
+ const context = explicitContext || "default";
76
+ const endpoint = await resolveDockerContextEndpoint(context, resolvedOptions);
77
+ return createDockerDeploymentTarget({ context, endpoint });
78
+ };
79
+ export const verifyDockerDeploymentTarget = async (target, options = {}) => {
80
+ if (target.kind === "host") {
81
+ return null;
82
+ }
83
+ const context = dockerContextNameForTarget(target);
84
+ if (!context) {
85
+ return null;
86
+ }
87
+ const current = await resolveDockerDeploymentTarget({
88
+ ...options,
89
+ context
90
+ });
91
+ const currentFingerprint = current.kind === "host" ? null : current.endpoint_fingerprint;
92
+ if (currentFingerprint !== target.endpoint_fingerprint) {
93
+ throw new SpawnfileError("runtime_error", `Docker context "${context}" endpoint changed since deployment`);
94
+ }
95
+ return context;
96
+ };
@@ -0,0 +1,14 @@
1
+ import { type DevExecFile } from "./docker.js";
2
+ import { type DevProjectOptions } from "./project.js";
3
+ export interface DevActivityProjectOptions extends DevProjectOptions {
4
+ agent?: string;
5
+ execFile?: DevExecFile;
6
+ tail?: number;
7
+ }
8
+ export interface DevActivityProjectResult {
9
+ containerName: string;
10
+ deploymentName: string;
11
+ events: unknown[];
12
+ outputDirectory: string;
13
+ }
14
+ export declare const devActivityProject: (inputPath: string, options?: DevActivityProjectOptions) => Promise<DevActivityProjectResult>;
@@ -0,0 +1,48 @@
1
+ import { defaultExecFile, dockerArgsForRecord, firstContainerName, runDocker } from "./docker.js";
2
+ import { PI_CONTROL_URL, resolveDevOutputDirectory, selectDeploymentRecord } from "./project.js";
3
+ import { SpawnfileError } from "../shared/index.js";
4
+ const activityPath = (options) => {
5
+ const params = new URLSearchParams();
6
+ if (options.agent) {
7
+ params.set("agent", options.agent);
8
+ }
9
+ if (typeof options.tail === "number" && Number.isInteger(options.tail) && options.tail > 0) {
10
+ params.set("tail", String(options.tail));
11
+ }
12
+ const query = params.toString();
13
+ return `${PI_CONTROL_URL}/spawnfile/activity${query ? `?${query}` : ""}`;
14
+ };
15
+ const parseActivityEvents = (stdout) => {
16
+ let parsed;
17
+ try {
18
+ parsed = JSON.parse(stdout);
19
+ }
20
+ catch {
21
+ throw new SpawnfileError("runtime_error", "Pi activity endpoint returned invalid JSON");
22
+ }
23
+ if (typeof parsed !== "object" ||
24
+ parsed === null ||
25
+ !Array.isArray(parsed.events)) {
26
+ throw new SpawnfileError("runtime_error", "Pi activity endpoint response did not include an events array");
27
+ }
28
+ return parsed.events;
29
+ };
30
+ export const devActivityProject = async (inputPath, options = {}) => {
31
+ const outputDirectory = await resolveDevOutputDirectory(inputPath, options.outputDirectory);
32
+ const record = await selectDeploymentRecord(outputDirectory, options.deploymentName);
33
+ const containerName = firstContainerName(record);
34
+ const stdout = await runDocker(options.dockerCommand ?? "docker", [
35
+ ...dockerArgsForRecord(record),
36
+ "exec",
37
+ containerName,
38
+ "curl",
39
+ "-fsS",
40
+ activityPath(options)
41
+ ], options.execFile ?? defaultExecFile);
42
+ return {
43
+ containerName,
44
+ deploymentName: record.name,
45
+ events: parseActivityEvents(stdout),
46
+ outputDirectory
47
+ };
48
+ };
@@ -0,0 +1,16 @@
1
+ import { type CompileProjectResult } from "../compiler/index.js";
2
+ import { type DeploymentRecord } from "../deployment/index.js";
3
+ export type DevExecFile = (file: string, args: string[], options: {
4
+ timeout: number;
5
+ }) => Promise<{
6
+ stderr: string;
7
+ stdout: string;
8
+ }>;
9
+ export declare const defaultExecFile: DevExecFile;
10
+ export declare const dockerArgsForRecord: (record: DeploymentRecord) => string[];
11
+ export declare const firstContainerName: (record: DeploymentRecord) => string;
12
+ export declare const compileForDevApply: (inputPath: string, outputDirectory: string, record: DeploymentRecord, dockerCommand: string, execFileImpl: DevExecFile) => Promise<CompileProjectResult>;
13
+ export declare const runDocker: (command: string, args: string[], execFileImpl: DevExecFile, timeout?: number) => Promise<string>;
14
+ export declare const copyIntoContainer: (dockerCommand: string, dockerPrefix: string[], source: string, destination: string, execFileImpl: DevExecFile) => Promise<void>;
15
+ export declare const ensureContainerDirectories: (dockerCommand: string, dockerPrefix: string[], containerName: string, paths: string[], execFileImpl: DevExecFile) => Promise<void>;
16
+ export declare const chownContainerPaths: (dockerCommand: string, dockerPrefix: string[], containerName: string, paths: string[], execFileImpl: DevExecFile) => Promise<void>;