spawnfile 0.1.10 → 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.
- package/README.md +13 -2
- package/dist/cli/lifecycleCommands.js +101 -1
- package/dist/cli/modelCommands.js +3 -0
- package/dist/cli/resolveCommandInput.d.ts +19 -0
- package/dist/cli/resolveCommandInput.js +25 -0
- package/dist/cli/runCli.d.ts +4 -1
- package/dist/cli/runCli.js +37 -13
- package/dist/cli/runtimeCommands.js +1 -0
- package/dist/cli/statusCommand.d.ts +42 -0
- package/dist/cli/statusCommand.js +394 -0
- package/dist/cli/surfaceCommands.js +4 -0
- package/dist/compiler/agentSurfaces.js +2 -4
- package/dist/compiler/buildCompilePlanTeams.js +3 -1
- package/dist/compiler/compileProject.js +19 -10
- package/dist/compiler/compileProjectSupport.d.ts +1 -0
- package/dist/compiler/compileProjectSupport.js +1 -1
- package/dist/compiler/containerArtifacts.d.ts +1 -0
- package/dist/compiler/containerArtifacts.js +208 -32
- package/dist/compiler/containerArtifactsPlans.js +7 -4
- package/dist/compiler/containerArtifactsRender.d.ts +7 -1
- package/dist/compiler/containerArtifactsRender.js +6 -0
- package/dist/compiler/containerArtifactsTypes.d.ts +9 -0
- package/dist/compiler/containerEntrypointRender.js +18 -31
- package/dist/compiler/index.d.ts +1 -0
- package/dist/compiler/index.js +1 -0
- package/dist/compiler/moltnetArtifacts.d.ts +2 -0
- package/dist/compiler/moltnetArtifacts.js +58 -99
- package/dist/compiler/moltnetClientConfig.d.ts +2 -2
- package/dist/compiler/moltnetClientConfig.js +16 -23
- package/dist/compiler/moltnetConfigLowering.d.ts +3 -0
- package/dist/compiler/moltnetConfigLowering.js +13 -2
- package/dist/compiler/moltnetNodeConfig.d.ts +27 -0
- package/dist/compiler/moltnetNodeConfig.js +68 -0
- package/dist/compiler/moltnetResolution.js +1 -1
- package/dist/compiler/moltnetRoomMemberships.js +1 -2
- package/dist/compiler/moltnetRoomPolicyCompatibility.d.ts +4 -0
- package/dist/compiler/moltnetRoomPolicyCompatibility.js +28 -0
- package/dist/compiler/moltnetRuntimeConfig.d.ts +1 -1
- package/dist/compiler/moltnetRuntimeConfig.js +1 -13
- package/dist/compiler/networkBinding.d.ts +14 -0
- package/dist/compiler/networkBinding.js +34 -0
- package/dist/compiler/publishProject.d.ts +13 -0
- package/dist/compiler/publishProject.js +72 -0
- package/dist/compiler/runProject.d.ts +26 -12
- package/dist/compiler/runProject.js +102 -125
- package/dist/compiler/runProjectAuth.d.ts +5 -0
- package/dist/compiler/runProjectAuth.js +98 -0
- package/dist/compiler/runProjectDocker.d.ts +19 -0
- package/dist/compiler/runProjectDocker.js +57 -0
- package/dist/compiler/types.d.ts +3 -2
- package/dist/compiler/upProject.d.ts +6 -0
- package/dist/compiler/upProject.js +42 -8
- package/dist/compiler/view/buildOrganizationView.js +27 -3
- package/dist/compiler/view/declarations.d.ts +4 -0
- package/dist/compiler/view/declarations.js +104 -0
- package/dist/compiler/view/renderNetworks.js +12 -3
- package/dist/compiler/view/renderTree.js +16 -3
- package/dist/compiler/view/types.d.ts +77 -1
- package/dist/deployment/dockerInspect.d.ts +29 -0
- package/dist/deployment/dockerInspect.js +130 -0
- package/dist/deployment/dockerLabels.d.ts +16 -0
- package/dist/deployment/dockerLabels.js +32 -0
- package/dist/deployment/dockerLogs.d.ts +24 -0
- package/dist/deployment/dockerLogs.js +96 -0
- package/dist/deployment/dockerManager.d.ts +59 -0
- package/dist/deployment/dockerManager.js +76 -0
- package/dist/deployment/dockerProbeGateway.d.ts +16 -0
- package/dist/deployment/dockerProbeGateway.js +48 -0
- package/dist/deployment/homeStore.d.ts +19 -0
- package/dist/deployment/homeStore.js +130 -0
- package/dist/deployment/index.d.ts +9 -0
- package/dist/deployment/index.js +9 -0
- package/dist/deployment/names.d.ts +3 -0
- package/dist/deployment/names.js +12 -0
- package/dist/deployment/record.d.ts +76 -0
- package/dist/deployment/record.js +174 -0
- package/dist/deployment/target.d.ts +35 -0
- package/dist/deployment/target.js +96 -0
- package/dist/distribution/buildDistributionReport.d.ts +22 -0
- package/dist/distribution/buildDistributionReport.js +51 -0
- package/dist/distribution/consumeImage.d.ts +29 -0
- package/dist/distribution/consumeImage.js +254 -0
- package/dist/distribution/consumeImageSupport.d.ts +18 -0
- package/dist/distribution/consumeImageSupport.js +63 -0
- package/dist/distribution/distributionReportSchema.d.ts +84 -0
- package/dist/distribution/distributionReportSchema.js +107 -0
- package/dist/distribution/dockerRunner.d.ts +5 -0
- package/dist/distribution/dockerRunner.js +21 -0
- package/dist/distribution/extractImage.d.ts +25 -0
- package/dist/distribution/extractImage.js +96 -0
- package/dist/distribution/fingerprint.d.ts +2 -0
- package/dist/distribution/fingerprint.js +14 -0
- package/dist/distribution/imageRef.d.ts +9 -0
- package/dist/distribution/imageRef.js +65 -0
- package/dist/distribution/imageRuntimeAuth.d.ts +19 -0
- package/dist/distribution/imageRuntimeAuth.js +71 -0
- package/dist/distribution/index.d.ts +24 -0
- package/dist/distribution/index.js +16 -0
- package/dist/distribution/preflight.d.ts +11 -0
- package/dist/distribution/preflight.js +65 -0
- package/dist/distribution/projectImageView.d.ts +8 -0
- package/dist/distribution/projectImageView.js +83 -0
- package/dist/distribution/projectName.d.ts +1 -0
- package/dist/distribution/projectName.js +12 -0
- package/dist/distribution/renderImageInterface.d.ts +30 -0
- package/dist/distribution/renderImageInterface.js +86 -0
- package/dist/distribution/tarReader.d.ts +10 -0
- package/dist/distribution/tarReader.js +69 -0
- package/dist/distribution/types.d.ts +80 -0
- package/dist/distribution/types.js +4 -0
- package/dist/distribution/verifyDistributionReport.d.ts +12 -0
- package/dist/distribution/verifyDistributionReport.js +29 -0
- package/dist/filesystem/paths.d.ts +6 -0
- package/dist/filesystem/paths.js +11 -0
- package/dist/manifest/loadManifest.js +22 -2
- package/dist/manifest/renderSpawnfile.js +2 -4
- package/dist/manifest/renderSpawnfileNetworks.js +5 -0
- package/dist/manifest/schemas.d.ts +71 -41
- package/dist/manifest/surfaceSchemas.d.ts +19 -47
- package/dist/manifest/surfaceSchemas.js +3 -6
- package/dist/manifest/teamNetworkAccessSchemas.d.ts +15 -0
- package/dist/manifest/teamNetworkAccessSchemas.js +8 -0
- package/dist/manifest/teamNetworkConsoleSchemas.d.ts +7 -0
- package/dist/manifest/teamNetworkConsoleSchemas.js +19 -0
- package/dist/manifest/teamNetworkSchemas.d.ts +60 -0
- package/dist/manifest/teamNetworkSchemas.js +20 -7
- package/dist/report/createReport.d.ts +7 -1
- package/dist/report/createReport.js +28 -1
- package/dist/report/types.d.ts +43 -0
- package/dist/runtime/container.js +0 -16
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/install.d.ts +0 -10
- package/dist/runtime/install.js +0 -45
- package/dist/runtime/openclaw/adapter.js +9 -1
- package/dist/runtime/openclaw/configAuth.d.ts +15 -0
- package/dist/runtime/openclaw/configAuth.js +46 -0
- package/dist/runtime/openclaw/runAuth.d.ts +6 -0
- package/dist/runtime/openclaw/runAuth.js +7 -55
- package/dist/runtime/openclaw/statusProbes.d.ts +2 -0
- package/dist/runtime/openclaw/statusProbes.js +31 -0
- package/dist/runtime/picoclaw/adapter.js +10 -0
- package/dist/runtime/picoclaw/runAuth.d.ts +7 -0
- package/dist/runtime/picoclaw/runAuth.js +8 -68
- package/dist/runtime/picoclaw/statusProbes.d.ts +2 -0
- package/dist/runtime/picoclaw/statusProbes.js +100 -0
- package/dist/runtime/registry.d.ts +0 -5
- package/dist/runtime/registry.js +2 -12
- package/dist/runtime/statusProbes.d.ts +16 -0
- package/dist/runtime/statusProbes.js +41 -0
- package/dist/runtime/types.d.ts +52 -1
- package/dist/shared/errors.d.ts +6 -0
- package/dist/shared/errors.js +9 -0
- package/dist/status/buildStatus.d.ts +13 -0
- package/dist/status/buildStatus.js +266 -0
- package/dist/status/compileReport.d.ts +96 -0
- package/dist/status/compileReport.js +230 -0
- package/dist/status/compiledContainerObservations.d.ts +5 -0
- package/dist/status/compiledContainerObservations.js +63 -0
- package/dist/status/deploymentLogs.d.ts +12 -0
- package/dist/status/deploymentLogs.js +45 -0
- package/dist/status/deployments.d.ts +14 -0
- package/dist/status/deployments.js +128 -0
- package/dist/status/imageStatusReport.d.ts +10 -0
- package/dist/status/imageStatusReport.js +73 -0
- package/dist/status/index.d.ts +14 -0
- package/dist/status/index.js +14 -0
- package/dist/status/moltnetProbes.d.ts +21 -0
- package/dist/status/moltnetProbes.js +240 -0
- package/dist/status/registryDrift.d.ts +28 -0
- package/dist/status/registryDrift.js +119 -0
- package/dist/status/renderStatus.d.ts +2 -0
- package/dist/status/renderStatus.js +230 -0
- package/dist/status/runtimeProbes.d.ts +13 -0
- package/dist/status/runtimeProbes.js +111 -0
- package/dist/status/selectionSubjects.d.ts +8 -0
- package/dist/status/selectionSubjects.js +77 -0
- package/dist/status/selectors.d.ts +14 -0
- package/dist/status/selectors.js +90 -0
- package/dist/status/traversal.d.ts +6 -0
- package/dist/status/traversal.js +15 -0
- package/dist/status/types.d.ts +113 -0
- package/dist/status/types.js +1 -0
- package/package.json +4 -2
- package/runtimes.yaml +22 -15
- package/dist/runtime/tinyclaw/adapter.d.ts +0 -2
- package/dist/runtime/tinyclaw/adapter.js +0 -284
- package/dist/runtime/tinyclaw/runAuth.d.ts +0 -2
- package/dist/runtime/tinyclaw/runAuth.js +0 -49
- package/dist/runtime/tinyclaw/scaffold-assets/AGENTS.md +0 -160
- package/dist/runtime/tinyclaw/scaffold-assets/CLAUDE.md +0 -5
- package/dist/runtime/tinyclaw/scaffold-assets/SOUL.md +0 -177
- package/dist/runtime/tinyclaw/scaffold.d.ts +0 -2
- package/dist/runtime/tinyclaw/scaffold.js +0 -24
- package/dist/runtime/tinyclaw/schedules.d.ts +0 -9
- package/dist/runtime/tinyclaw/schedules.js +0 -62
- package/dist/runtime/tinyclaw/surfaces.d.ts +0 -8
- package/dist/runtime/tinyclaw/surfaces.js +0 -94
|
@@ -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 {};
|
|
@@ -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,22 @@
|
|
|
1
|
+
import type { DistributionImageLabels, DistributionMoltnetNetwork, DistributionOrganizationSummary, DistributionPersistentMount, DistributionPortMapping, DistributionReport, DistributionRuntimeInstance, DistributionSecretCategory, DistributionWorkspaceResource } from "./types.js";
|
|
2
|
+
export interface DistributionEnvVariableInput {
|
|
3
|
+
categories: DistributionSecretCategory[];
|
|
4
|
+
generated: boolean;
|
|
5
|
+
name: string;
|
|
6
|
+
required: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface BuildDistributionReportInput {
|
|
9
|
+
envVariables: DistributionEnvVariableInput[];
|
|
10
|
+
generatedAt: string;
|
|
11
|
+
internalPorts: number[];
|
|
12
|
+
modelAuthMethods: Record<string, DistributionRuntimeInstance["model_auth_methods"][string]>;
|
|
13
|
+
moltnetNetworks: DistributionMoltnetNetwork[];
|
|
14
|
+
organization: DistributionOrganizationSummary;
|
|
15
|
+
persistentMounts: DistributionPersistentMount[];
|
|
16
|
+
portMappings: DistributionPortMapping[];
|
|
17
|
+
publishedPorts: number[];
|
|
18
|
+
resources: DistributionWorkspaceResource[];
|
|
19
|
+
runtimeInstances: DistributionRuntimeInstance[];
|
|
20
|
+
}
|
|
21
|
+
export declare const buildDistributionReport: (input: BuildDistributionReportInput) => DistributionReport;
|
|
22
|
+
export declare const createDistributionImageLabels: (projectSlug: string, compileFingerprint: string) => DistributionImageLabels;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createDistributionFingerprint } from "./fingerprint.js";
|
|
2
|
+
import { DISTRIBUTION_REPORT_IMAGE_PATH, DISTRIBUTION_REPORT_VERSION, IMAGE_CONTRACT_VERSION } from "./types.js";
|
|
3
|
+
const createSecretCategoryMap = (envVariables) => {
|
|
4
|
+
const secrets = {
|
|
5
|
+
model: [],
|
|
6
|
+
project: [],
|
|
7
|
+
runtime: [],
|
|
8
|
+
surface: []
|
|
9
|
+
};
|
|
10
|
+
for (const variable of envVariables) {
|
|
11
|
+
for (const category of variable.categories) {
|
|
12
|
+
secrets[category].push({
|
|
13
|
+
generated: variable.generated,
|
|
14
|
+
name: variable.name,
|
|
15
|
+
required: variable.required
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
for (const category of Object.keys(secrets)) {
|
|
20
|
+
secrets[category].sort((left, right) => left.name.localeCompare(right.name));
|
|
21
|
+
}
|
|
22
|
+
return secrets;
|
|
23
|
+
};
|
|
24
|
+
export const buildDistributionReport = (input) => {
|
|
25
|
+
const body = {
|
|
26
|
+
internal_ports: [...input.internalPorts].sort((left, right) => left - right),
|
|
27
|
+
model_auth_methods: input.modelAuthMethods,
|
|
28
|
+
moltnet: {
|
|
29
|
+
networks: [...input.moltnetNetworks].sort((left, right) => left.id.localeCompare(right.id))
|
|
30
|
+
},
|
|
31
|
+
organization: input.organization,
|
|
32
|
+
persistent_mounts: [...input.persistentMounts].sort((left, right) => left.id.localeCompare(right.id)),
|
|
33
|
+
port_mappings: input.portMappings,
|
|
34
|
+
ports: [...input.publishedPorts].sort((left, right) => left - right),
|
|
35
|
+
resources: input.resources,
|
|
36
|
+
runtime_instances: [...input.runtimeInstances].sort((left, right) => left.id.localeCompare(right.id)),
|
|
37
|
+
secrets: createSecretCategoryMap(input.envVariables),
|
|
38
|
+
version: DISTRIBUTION_REPORT_VERSION
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
compile_fingerprint: createDistributionFingerprint(body),
|
|
42
|
+
generated_at: input.generatedAt,
|
|
43
|
+
...body
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export const createDistributionImageLabels = (projectSlug, compileFingerprint) => ({
|
|
47
|
+
"com.spawnfile.compile_fingerprint": compileFingerprint,
|
|
48
|
+
"com.spawnfile.image_contract": IMAGE_CONTRACT_VERSION,
|
|
49
|
+
"com.spawnfile.project": projectSlug,
|
|
50
|
+
"com.spawnfile.report": DISTRIBUTION_REPORT_IMAGE_PATH
|
|
51
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type DeploymentRecord } from "../deployment/index.js";
|
|
2
|
+
import type { DockerCommandRunner } from "./dockerRunner.js";
|
|
3
|
+
import type { ResolvedAuthProfile } from "../auth/index.js";
|
|
4
|
+
export interface ConsumeImageUpOptions {
|
|
5
|
+
authProfile?: ResolvedAuthProfile | null;
|
|
6
|
+
authValues?: Record<string, string>;
|
|
7
|
+
authProfileName?: string | null;
|
|
8
|
+
deploymentName?: string;
|
|
9
|
+
dockerCommand?: string;
|
|
10
|
+
dockerContext?: string;
|
|
11
|
+
dockerHost?: string;
|
|
12
|
+
envFileEnv?: Record<string, string>;
|
|
13
|
+
envFilePath?: string | null;
|
|
14
|
+
pull?: boolean;
|
|
15
|
+
runDocker?: DockerCommandRunner;
|
|
16
|
+
}
|
|
17
|
+
export interface ConsumeImageUpResult {
|
|
18
|
+
containerName: string;
|
|
19
|
+
deploymentName: string;
|
|
20
|
+
imageRef: string;
|
|
21
|
+
/** The ref/digest this redeploy replaced, or null for a first deploy. */
|
|
22
|
+
previous: {
|
|
23
|
+
digest: string | null;
|
|
24
|
+
ref: string;
|
|
25
|
+
} | null;
|
|
26
|
+
record: DeploymentRecord;
|
|
27
|
+
recordPath: string;
|
|
28
|
+
}
|
|
29
|
+
export declare const consumeImageUp: (imageRef: string, options?: ConsumeImageUpOptions) => Promise<ConsumeImageUpResult>;
|