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.
- package/README.md +21 -3
- package/dist/auth/runtimeCredentials.js +18 -2
- package/dist/cli/devCommands.d.ts +3 -0
- package/dist/cli/devCommands.js +115 -0
- package/dist/cli/lifecycleCommands.js +103 -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 +10 -1
- package/dist/cli/runCli.js +41 -13
- package/dist/cli/runtimeCommands.js +1 -0
- package/dist/cli/statusCommand.d.ts +43 -0
- package/dist/cli/statusCommand.js +428 -0
- package/dist/cli/surfaceCommands.js +4 -0
- package/dist/compiler/agentSurfaces.js +2 -4
- package/dist/compiler/buildProject.d.ts +9 -1
- package/dist/compiler/buildProject.js +44 -5
- package/dist/compiler/compileProject.d.ts +2 -0
- package/dist/compiler/compileProject.js +22 -11
- 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 +31 -69
- package/dist/compiler/containerArtifactsTypes.d.ts +9 -0
- package/dist/compiler/containerEntrypointRender.js +22 -32
- package/dist/compiler/containerPackageRender.d.ts +15 -0
- package/dist/compiler/containerPackageRender.js +63 -0
- package/dist/compiler/containerTargetResources.js +3 -1
- package/dist/compiler/index.d.ts +1 -0
- package/dist/compiler/index.js +1 -0
- package/dist/compiler/moltnetArtifacts.js +21 -1
- package/dist/compiler/moltnetBinaries.d.ts +4 -1
- package/dist/compiler/moltnetBinaries.js +24 -3
- package/dist/compiler/moltnetClientConfig.d.ts +1 -1
- package/dist/compiler/moltnetClientConfig.js +14 -16
- package/dist/compiler/moltnetNodeConfig.js +2 -4
- package/dist/compiler/moltnetResolution.js +1 -1
- package/dist/compiler/moltnetRoomMemberships.js +1 -2
- package/dist/compiler/moltnetRuntimeConfig.d.ts +1 -1
- package/dist/compiler/moltnetRuntimeConfig.js +9 -10
- 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 +109 -126
- 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 +202 -0
- package/dist/compiler/types.d.ts +1 -2
- package/dist/compiler/upProject.d.ts +6 -0
- package/dist/compiler/upProject.js +43 -8
- package/dist/compiler/view/buildOrganizationView.js +4 -1
- package/dist/compiler/view/declarations.d.ts +4 -0
- package/dist/compiler/view/declarations.js +104 -0
- package/dist/compiler/view/renderNetworks.js +1 -2
- package/dist/compiler/view/renderTree.js +6 -2
- package/dist/compiler/view/types.d.ts +64 -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/dockerRecover.d.ts +17 -0
- package/dist/deployment/dockerRecover.js +129 -0
- package/dist/deployment/homeStore.d.ts +19 -0
- package/dist/deployment/homeStore.js +130 -0
- package/dist/deployment/index.d.ts +10 -0
- package/dist/deployment/index.js +10 -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/dev/activity.d.ts +14 -0
- package/dist/dev/activity.js +48 -0
- package/dist/dev/docker.d.ts +16 -0
- package/dist/dev/docker.js +71 -0
- package/dist/dev/index.d.ts +2 -0
- package/dist/dev/index.js +2 -0
- package/dist/dev/project.d.ts +35 -0
- package/dist/dev/project.js +164 -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/schemas.d.ts +17 -41
- package/dist/manifest/surfaceSchemas.d.ts +19 -47
- package/dist/manifest/surfaceSchemas.js +3 -6
- 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/common.d.ts +2 -0
- package/dist/runtime/common.js +1 -1
- package/dist/runtime/container.d.ts +1 -0
- package/dist/runtime/container.js +82 -15
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/install.d.ts +1 -11
- package/dist/runtime/install.js +27 -30
- 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/pi/adapter.d.ts +4 -0
- package/dist/runtime/pi/adapter.js +198 -0
- package/dist/runtime/pi/appActivitySource.d.ts +1 -0
- package/dist/runtime/pi/appActivitySource.js +87 -0
- package/dist/runtime/pi/appCoreSource.d.ts +1 -0
- package/dist/runtime/pi/appCoreSource.js +354 -0
- package/dist/runtime/pi/appPreludeSource.d.ts +1 -0
- package/dist/runtime/pi/appPreludeSource.js +103 -0
- package/dist/runtime/pi/appSource.d.ts +1 -0
- package/dist/runtime/pi/appSource.js +8 -0
- package/dist/runtime/pi/appTemplate.d.ts +28 -0
- package/dist/runtime/pi/appTemplate.js +144 -0
- package/dist/runtime/pi/runAuth.d.ts +2 -0
- package/dist/runtime/pi/runAuth.js +68 -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 +5 -12
- package/dist/runtime/statusProbes.d.ts +16 -0
- package/dist/runtime/statusProbes.js +41 -0
- package/dist/runtime/types.d.ts +53 -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 +121 -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 +9 -2
- package/runtimes.yaml +44 -21
- 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
|
@@ -22,9 +22,7 @@ const createTeamOutputDirectory = (baseDirectory, runtimeName, node) => path.joi
|
|
|
22
22
|
const compileAgentNode = async (baseDirectory, node) => {
|
|
23
23
|
const runtime = await assertRuntimeCanCompile(node.runtimeName ?? node.value.runtime.name);
|
|
24
24
|
const adapter = getRuntimeAdapter(runtime.name);
|
|
25
|
-
const diagnostics = [
|
|
26
|
-
...createRuntimeLifecycleDiagnostics(runtime)
|
|
27
|
-
];
|
|
25
|
+
const diagnostics = [...createRuntimeLifecycleDiagnostics(runtime)];
|
|
28
26
|
for (const diagnostic of adapter.validateRuntimeOptions?.(node.value.runtime.options) ?? []) {
|
|
29
27
|
diagnostics.push(diagnostic);
|
|
30
28
|
}
|
|
@@ -37,6 +35,7 @@ const compileAgentNode = async (baseDirectory, node) => {
|
|
|
37
35
|
await writeEmittedFiles(outputDirectory, result.files);
|
|
38
36
|
return {
|
|
39
37
|
emittedFiles: result.files,
|
|
38
|
+
id: node.id,
|
|
40
39
|
kind: node.kind,
|
|
41
40
|
report: {
|
|
42
41
|
capabilities: result.capabilities,
|
|
@@ -63,6 +62,7 @@ const compileTeamNode = async (baseDirectory, node) => {
|
|
|
63
62
|
if (!runtimeName) {
|
|
64
63
|
return {
|
|
65
64
|
emittedFiles: [],
|
|
65
|
+
id: node.id,
|
|
66
66
|
kind: node.kind,
|
|
67
67
|
report: {
|
|
68
68
|
capabilities: createTeamCapabilities("degraded", "Team spans multiple runtimes and cannot lower to one native team artifact in v0.1"),
|
|
@@ -88,6 +88,7 @@ const compileTeamNode = async (baseDirectory, node) => {
|
|
|
88
88
|
if (!adapter.compileTeam) {
|
|
89
89
|
return {
|
|
90
90
|
emittedFiles: [],
|
|
91
|
+
id: node.id,
|
|
91
92
|
kind: node.kind,
|
|
92
93
|
report: {
|
|
93
94
|
capabilities: createTeamCapabilities("degraded", `Runtime ${runtime.name} does not provide native team compilation in v0.1`),
|
|
@@ -113,6 +114,7 @@ const compileTeamNode = async (baseDirectory, node) => {
|
|
|
113
114
|
await writeEmittedFiles(outputDirectory, result.files);
|
|
114
115
|
return {
|
|
115
116
|
emittedFiles: result.files,
|
|
117
|
+
id: node.id,
|
|
116
118
|
kind: node.kind,
|
|
117
119
|
report: {
|
|
118
120
|
capabilities: result.capabilities,
|
|
@@ -200,7 +202,11 @@ const augmentNodeReports = (compiledNodes, support) => {
|
|
|
200
202
|
};
|
|
201
203
|
export const compileProject = async (inputPath, options = {}) => {
|
|
202
204
|
const plan = await buildCompilePlan(inputPath);
|
|
203
|
-
|
|
205
|
+
// The default output directory lives under the resolved project root, not the
|
|
206
|
+
// process working directory, so `spawnfile compile ./org` writes ./org/.spawn.
|
|
207
|
+
const outputDirectory = options.outputDirectory
|
|
208
|
+
? path.resolve(options.outputDirectory)
|
|
209
|
+
: path.join(path.dirname(plan.root), DEFAULT_OUTPUT_DIRECTORY);
|
|
204
210
|
if (options.clean ?? true) {
|
|
205
211
|
await removeDirectory(outputDirectory);
|
|
206
212
|
}
|
|
@@ -226,20 +232,25 @@ export const compileProject = async (inputPath, options = {}) => {
|
|
|
226
232
|
}
|
|
227
233
|
const moltnetArtifacts = await generateMoltnetArtifacts(plan);
|
|
228
234
|
const hasStagedMoltnetBinaries = moltnetArtifacts
|
|
229
|
-
? await stageMoltnetBinaries(outputDirectory
|
|
235
|
+
? await stageMoltnetBinaries(outputDirectory, {
|
|
236
|
+
architecture: options.containerArchitecture
|
|
237
|
+
})
|
|
230
238
|
: false;
|
|
231
239
|
await injectMoltnetWorkspaceFiles(outputDirectory, compiledNodes, moltnetArtifacts);
|
|
240
|
+
const generatedAt = new Date().toISOString();
|
|
232
241
|
const containerArtifacts = await createContainerArtifacts(plan, compiledNodes, {
|
|
242
|
+
generatedAt,
|
|
233
243
|
hasStagedMoltnetBinaries,
|
|
234
244
|
moltnet: moltnetArtifacts
|
|
235
245
|
});
|
|
236
246
|
await writeEmittedFiles(outputDirectory, containerArtifacts.files);
|
|
237
247
|
await Promise.all(containerArtifacts.executablePaths.map((filePath) => chmod(path.join(outputDirectory, filePath), 0o755)));
|
|
238
|
-
const report = createCompileReport(plan.root, nodeReports, [], containerArtifacts.report
|
|
239
|
-
|
|
240
|
-
|
|
248
|
+
const report = createCompileReport(plan.root, nodeReports, [], containerArtifacts.report, {
|
|
249
|
+
compileFingerprint: containerArtifacts.distribution.fingerprint,
|
|
250
|
+
generatedAt,
|
|
241
251
|
outputDirectory,
|
|
242
|
-
report
|
|
243
|
-
|
|
244
|
-
|
|
252
|
+
projectName: containerArtifacts.distribution.report.organization.project
|
|
253
|
+
});
|
|
254
|
+
const reportPath = await writeCompileReport(outputDirectory, report);
|
|
255
|
+
return { outputDirectory, report, reportPath };
|
|
245
256
|
};
|
|
@@ -3,6 +3,7 @@ import type { CompiledNodeArtifact, GeneratedContainerArtifacts } from "./contai
|
|
|
3
3
|
import type { CompilePlan } from "./types.js";
|
|
4
4
|
export type { CompiledNodeArtifact, GeneratedContainerArtifacts } from "./containerArtifactsTypes.js";
|
|
5
5
|
export interface ContainerArtifactOptions {
|
|
6
|
+
generatedAt?: string;
|
|
6
7
|
hasStagedMoltnetBinaries?: boolean;
|
|
7
8
|
moltnet?: MoltnetArtifacts | null;
|
|
8
9
|
}
|
|
@@ -1,5 +1,106 @@
|
|
|
1
|
+
import { buildDistributionReport, createDistributionImageLabels, DISTRIBUTION_REPORT_OUTPUT_FILE, normalizeProjectLabelSlug } from "../distribution/index.js";
|
|
2
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
1
3
|
import { createEnvVariableMap, createRuntimeTargetPlans } from "./containerArtifactsPlans.js";
|
|
2
4
|
import { createRootfsFiles, renderDockerfile, renderEntrypoint, renderEnvExample } from "./containerArtifactsRender.js";
|
|
5
|
+
const createOrganizationSummary = (plan, compiledNodes) => {
|
|
6
|
+
const nodes = compiledNodes.map((node) => ({
|
|
7
|
+
id: node.id ?? `${node.kind}:${node.slug}`,
|
|
8
|
+
kind: node.kind,
|
|
9
|
+
name: node.value.name,
|
|
10
|
+
runtimeName: node.runtimeName,
|
|
11
|
+
source: node.value.source
|
|
12
|
+
}));
|
|
13
|
+
const rootNode = nodes.find((node) => node.source === plan.root)
|
|
14
|
+
?? nodes.find((node) => !plan.edges.some((edge) => edge.to === node.id))
|
|
15
|
+
?? nodes[0];
|
|
16
|
+
if (!rootNode) {
|
|
17
|
+
throw new SpawnfileError("compile_error", `Unable to resolve the root node for ${plan.root}`);
|
|
18
|
+
}
|
|
19
|
+
const memberEdges = plan.edges.filter((edge) => edge.kind === "team_member");
|
|
20
|
+
const teamsByAgent = new Map();
|
|
21
|
+
for (const edge of memberEdges) {
|
|
22
|
+
teamsByAgent.set(edge.to, [...(teamsByAgent.get(edge.to) ?? []), edge.from]);
|
|
23
|
+
}
|
|
24
|
+
const agentNodes = nodes.filter((node) => node.kind === "agent");
|
|
25
|
+
const teamNodes = nodes.filter((node) => node.kind === "team");
|
|
26
|
+
const agentIds = new Set(agentNodes.map((node) => node.id));
|
|
27
|
+
return {
|
|
28
|
+
agents: agentNodes
|
|
29
|
+
.map((node) => ({
|
|
30
|
+
id: node.id,
|
|
31
|
+
name: node.name,
|
|
32
|
+
runtime: node.runtimeName,
|
|
33
|
+
teams: [...(teamsByAgent.get(node.id) ?? [])].sort()
|
|
34
|
+
}))
|
|
35
|
+
.sort((left, right) => left.id.localeCompare(right.id)),
|
|
36
|
+
project: rootNode.name,
|
|
37
|
+
teams: teamNodes
|
|
38
|
+
.map((node) => ({
|
|
39
|
+
agents: memberEdges
|
|
40
|
+
.filter((edge) => edge.from === node.id && agentIds.has(edge.to))
|
|
41
|
+
.map((edge) => edge.to)
|
|
42
|
+
.sort(),
|
|
43
|
+
id: node.id,
|
|
44
|
+
name: node.name
|
|
45
|
+
}))
|
|
46
|
+
.sort((left, right) => left.id.localeCompare(right.id))
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
const resolveMoltnetOperatorTokenSecret = (plan) => {
|
|
50
|
+
const client = plan.server.auth.client;
|
|
51
|
+
if (!client) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
if (client.token_env) {
|
|
55
|
+
return client.token_env;
|
|
56
|
+
}
|
|
57
|
+
if (client.token_path) {
|
|
58
|
+
return client.token_path;
|
|
59
|
+
}
|
|
60
|
+
if (!client.token_id) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
return plan.server.auth.tokens?.find((token) => token.id === client.token_id)?.secret;
|
|
64
|
+
};
|
|
65
|
+
const createMoltnetSummary = (moltnet) => {
|
|
66
|
+
if (!moltnet) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
node_plans: moltnet.nodePlans.map((plan) => ({
|
|
71
|
+
config_path: plan.configPath,
|
|
72
|
+
network_id: plan.networkId
|
|
73
|
+
})),
|
|
74
|
+
server_plans: moltnet.serverPlans.map((plan) => {
|
|
75
|
+
const operatorTokenSecret = resolveMoltnetOperatorTokenSecret(plan);
|
|
76
|
+
return {
|
|
77
|
+
auth_mode: plan.server.auth.mode,
|
|
78
|
+
base_url: plan.baseUrl,
|
|
79
|
+
...(plan.configPath ? { config_path: plan.configPath } : {}),
|
|
80
|
+
...(plan.server.mode === "managed"
|
|
81
|
+
? { direct_messages: plan.server.direct_messages }
|
|
82
|
+
: {}),
|
|
83
|
+
id: plan.id,
|
|
84
|
+
mode: plan.mode,
|
|
85
|
+
network_id: plan.networkId,
|
|
86
|
+
...(operatorTokenSecret ? { operator_token_secret: operatorTokenSecret } : {}),
|
|
87
|
+
...(plan.port ? { port: plan.port } : {}),
|
|
88
|
+
...(plan.server.auth.public_read !== undefined
|
|
89
|
+
? { public_read: plan.server.auth.public_read }
|
|
90
|
+
: {}),
|
|
91
|
+
rooms: plan.rooms.map((room) => ({
|
|
92
|
+
id: room.id,
|
|
93
|
+
members: [...room.members],
|
|
94
|
+
...(room.visibility ? { visibility: room.visibility } : {}),
|
|
95
|
+
...(room.write_policy ? { write_policy: room.write_policy } : {})
|
|
96
|
+
})),
|
|
97
|
+
...(plan.server.mode === "managed"
|
|
98
|
+
? { store_kind: plan.server.store.kind }
|
|
99
|
+
: {})
|
|
100
|
+
};
|
|
101
|
+
})
|
|
102
|
+
};
|
|
103
|
+
};
|
|
3
104
|
export const createContainerArtifacts = async (plan, compiledNodes, options = {}) => {
|
|
4
105
|
const runtimePlans = await createRuntimeTargetPlans(plan, compiledNodes);
|
|
5
106
|
const envVariables = [...createEnvVariableMap(compiledNodes, runtimePlans).values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
@@ -23,11 +124,108 @@ export const createContainerArtifacts = async (plan, compiledNodes, options = {}
|
|
|
23
124
|
volume_name: mount.volumeName
|
|
24
125
|
}))
|
|
25
126
|
.sort((left, right) => left.id.localeCompare(right.id));
|
|
127
|
+
const runtimeInternalPorts = runtimePlans.flatMap((plan) => plan.port ? [plan.port] : []);
|
|
128
|
+
const internalPorts = [...new Set([...runtimeInternalPorts, ...(options.moltnet?.ports ?? [])])].sort((left, right) => left - right);
|
|
129
|
+
const runtimePublishedPorts = runtimePlans.flatMap((plan) => plan.publishedPort ? [plan.publishedPort] : []);
|
|
130
|
+
const publishedPorts = [
|
|
131
|
+
...new Set([...runtimePublishedPorts, ...(options.moltnet?.publishedPorts ?? [])])
|
|
132
|
+
].sort((left, right) => left - right);
|
|
133
|
+
const portMappings = runtimePlans
|
|
134
|
+
.flatMap((plan) => plan.port && plan.publishedPort
|
|
135
|
+
? [{ internal_port: plan.port, published_port: plan.publishedPort }]
|
|
136
|
+
: [])
|
|
137
|
+
.sort((left, right) => left.published_port - right.published_port
|
|
138
|
+
|| left.internal_port - right.internal_port);
|
|
139
|
+
const runtimeHomes = [
|
|
140
|
+
...new Set(runtimePlans.flatMap((plan) => (plan.instancePaths.homePath ? [plan.instancePaths.homePath] : [])))
|
|
141
|
+
].sort();
|
|
142
|
+
const runtimeInstances = runtimePlans
|
|
143
|
+
.map((plan) => ({
|
|
144
|
+
config_path: plan.instancePaths.configPath,
|
|
145
|
+
home_path: plan.instancePaths.homePath ?? null,
|
|
146
|
+
id: plan.id,
|
|
147
|
+
internal_port: plan.port ?? null,
|
|
148
|
+
model_auth_methods: plan.modelAuthMethods,
|
|
149
|
+
model_secrets_required: plan.modelSecretsRequired,
|
|
150
|
+
node_ids: [...(plan.sourceIds ?? [])],
|
|
151
|
+
published_port: plan.publishedPort ?? null,
|
|
152
|
+
runtime: plan.runtimeName,
|
|
153
|
+
workspace_path: plan.instancePaths.workspacePath
|
|
154
|
+
}))
|
|
155
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
156
|
+
const runtimesInstalled = [...new Set(runtimePlans.map((plan) => plan.runtimeName))].sort();
|
|
157
|
+
const workspaceResources = [
|
|
158
|
+
...new Map(runtimePlans.flatMap((plan) => (plan.resources ?? []).map((resource) => [
|
|
159
|
+
`${resource.kind}:${resource.id}:${resource.linkPath}`,
|
|
160
|
+
{
|
|
161
|
+
backing_path: resource.backingPath,
|
|
162
|
+
id: resource.id,
|
|
163
|
+
kind: resource.kind,
|
|
164
|
+
link_path: resource.linkPath,
|
|
165
|
+
mode: resource.mode,
|
|
166
|
+
mount: resource.mount,
|
|
167
|
+
sharing: resource.sharing
|
|
168
|
+
}
|
|
169
|
+
]))).values()
|
|
170
|
+
].sort((left, right) => left.link_path.localeCompare(right.link_path) || left.id.localeCompare(right.id));
|
|
171
|
+
const moltnetSummary = createMoltnetSummary(options.moltnet);
|
|
172
|
+
const organization = createOrganizationSummary(plan, compiledNodes);
|
|
173
|
+
const projectSlug = normalizeProjectLabelSlug(organization.project);
|
|
174
|
+
const mergedModelAuthMethods = Object.assign({}, ...runtimePlans.map((runtimePlan) => runtimePlan.modelAuthMethods));
|
|
175
|
+
const moltnetNetworks = [
|
|
176
|
+
...new Map((options.moltnet?.serverPlans ?? []).map((serverPlan) => [
|
|
177
|
+
serverPlan.networkId,
|
|
178
|
+
{
|
|
179
|
+
binding: "env",
|
|
180
|
+
id: serverPlan.networkId,
|
|
181
|
+
server_mode: serverPlan.mode
|
|
182
|
+
}
|
|
183
|
+
])).values()
|
|
184
|
+
];
|
|
185
|
+
const distributionReport = buildDistributionReport({
|
|
186
|
+
envVariables: envVariables.map((variable) => ({
|
|
187
|
+
categories: variable.categories,
|
|
188
|
+
generated: variable.generated,
|
|
189
|
+
name: variable.name,
|
|
190
|
+
required: variable.required
|
|
191
|
+
})),
|
|
192
|
+
generatedAt: options.generatedAt ?? new Date().toISOString(),
|
|
193
|
+
internalPorts,
|
|
194
|
+
modelAuthMethods: mergedModelAuthMethods,
|
|
195
|
+
moltnetNetworks,
|
|
196
|
+
organization,
|
|
197
|
+
persistentMounts: persistentMounts.map((mount) => ({
|
|
198
|
+
durability: "persistent",
|
|
199
|
+
id: mount.id,
|
|
200
|
+
kind: "volume",
|
|
201
|
+
target: mount.mount_path
|
|
202
|
+
})),
|
|
203
|
+
portMappings,
|
|
204
|
+
publishedPorts,
|
|
205
|
+
resources: workspaceResources.map((resource) => ({
|
|
206
|
+
id: resource.id,
|
|
207
|
+
kind: resource.kind,
|
|
208
|
+
link_path: resource.link_path,
|
|
209
|
+
mode: resource.mode,
|
|
210
|
+
mount: resource.mount,
|
|
211
|
+
sharing: resource.sharing
|
|
212
|
+
})),
|
|
213
|
+
runtimeInstances
|
|
214
|
+
});
|
|
215
|
+
const distributionLabels = createDistributionImageLabels(projectSlug, distributionReport.compile_fingerprint);
|
|
26
216
|
const files = [
|
|
27
217
|
...createRootfsFiles(runtimePlans),
|
|
28
218
|
...(options.moltnet?.files ?? []),
|
|
219
|
+
{
|
|
220
|
+
content: `${JSON.stringify(distributionReport, null, 2)}\n`,
|
|
221
|
+
path: DISTRIBUTION_REPORT_OUTPUT_FILE
|
|
222
|
+
},
|
|
29
223
|
{
|
|
30
224
|
content: await renderDockerfile(runtimePlans, {
|
|
225
|
+
distribution: {
|
|
226
|
+
labels: distributionLabels,
|
|
227
|
+
reportOutputFile: DISTRIBUTION_REPORT_OUTPUT_FILE
|
|
228
|
+
},
|
|
31
229
|
hasMoltnet: Boolean(options.moltnet),
|
|
32
230
|
hasStagedMoltnetBinaries: options.hasStagedMoltnetBinaries,
|
|
33
231
|
moltnetPublishedPorts: options.moltnet?.publishedPorts ?? [],
|
|
@@ -51,38 +249,12 @@ export const createContainerArtifacts = async (plan, compiledNodes, options = {}
|
|
|
51
249
|
path: ".env.example"
|
|
52
250
|
}
|
|
53
251
|
];
|
|
54
|
-
const runtimePorts = runtimePlans.flatMap((plan) => plan.publishedPort ? [plan.publishedPort] : []);
|
|
55
|
-
const moltnetPorts = options.moltnet?.publishedPorts ?? [];
|
|
56
|
-
const ports = [...new Set([...runtimePorts, ...moltnetPorts])].sort((left, right) => left - right);
|
|
57
|
-
const runtimeHomes = [
|
|
58
|
-
...new Set(runtimePlans.flatMap((plan) => (plan.instancePaths.homePath ? [plan.instancePaths.homePath] : [])))
|
|
59
|
-
].sort();
|
|
60
|
-
const runtimeInstances = runtimePlans
|
|
61
|
-
.map((plan) => ({
|
|
62
|
-
config_path: plan.instancePaths.configPath,
|
|
63
|
-
home_path: plan.instancePaths.homePath ?? null,
|
|
64
|
-
id: plan.id,
|
|
65
|
-
model_auth_methods: plan.modelAuthMethods,
|
|
66
|
-
model_secrets_required: plan.modelSecretsRequired,
|
|
67
|
-
runtime: plan.runtimeName
|
|
68
|
-
}))
|
|
69
|
-
.sort((left, right) => left.id.localeCompare(right.id));
|
|
70
|
-
const runtimesInstalled = [...new Set(runtimePlans.map((plan) => plan.runtimeName))].sort();
|
|
71
|
-
const workspaceResources = [
|
|
72
|
-
...new Map(runtimePlans.flatMap((plan) => (plan.resources ?? []).map((resource) => [
|
|
73
|
-
`${resource.kind}:${resource.id}:${resource.linkPath}`,
|
|
74
|
-
{
|
|
75
|
-
backing_path: resource.backingPath,
|
|
76
|
-
id: resource.id,
|
|
77
|
-
kind: resource.kind,
|
|
78
|
-
link_path: resource.linkPath,
|
|
79
|
-
mode: resource.mode,
|
|
80
|
-
mount: resource.mount,
|
|
81
|
-
sharing: resource.sharing
|
|
82
|
-
}
|
|
83
|
-
]))).values()
|
|
84
|
-
].sort((left, right) => left.link_path.localeCompare(right.link_path) || left.id.localeCompare(right.id));
|
|
85
252
|
return {
|
|
253
|
+
distribution: {
|
|
254
|
+
fingerprint: distributionReport.compile_fingerprint,
|
|
255
|
+
labels: distributionLabels,
|
|
256
|
+
report: distributionReport
|
|
257
|
+
},
|
|
86
258
|
executablePaths: ["entrypoint.sh"],
|
|
87
259
|
files,
|
|
88
260
|
...(options.moltnet
|
|
@@ -97,8 +269,12 @@ export const createContainerArtifacts = async (plan, compiledNodes, options = {}
|
|
|
97
269
|
dockerfile: "Dockerfile",
|
|
98
270
|
entrypoint: "entrypoint.sh",
|
|
99
271
|
env_example: ".env.example",
|
|
272
|
+
internal_ports: internalPorts,
|
|
100
273
|
model_secrets_required: modelSecretsRequired,
|
|
101
|
-
|
|
274
|
+
...(moltnetSummary ? { moltnet: moltnetSummary } : {}),
|
|
275
|
+
port_mappings: portMappings,
|
|
276
|
+
ports: publishedPorts,
|
|
277
|
+
published_ports: publishedPorts,
|
|
102
278
|
runtime_instances: runtimeInstances,
|
|
103
279
|
runtime_homes: runtimeHomes,
|
|
104
280
|
runtime_secrets_required: runtimeSecretsRequired,
|
|
@@ -65,12 +65,13 @@ const resolveInstancePaths = (runtimeName, targetId, meta) => {
|
|
|
65
65
|
};
|
|
66
66
|
export const createEnvVariableMap = (compiledNodes, runtimePlans) => {
|
|
67
67
|
const variables = new Map();
|
|
68
|
-
const register = (name, required, description, category) => {
|
|
68
|
+
const register = (name, required, description, category, generated = false) => {
|
|
69
69
|
const current = variables.get(name);
|
|
70
70
|
if (!current) {
|
|
71
71
|
variables.set(name, {
|
|
72
72
|
categories: [category],
|
|
73
73
|
description,
|
|
74
|
+
generated,
|
|
74
75
|
name,
|
|
75
76
|
required
|
|
76
77
|
});
|
|
@@ -79,6 +80,7 @@ export const createEnvVariableMap = (compiledNodes, runtimePlans) => {
|
|
|
79
80
|
variables.set(name, {
|
|
80
81
|
...current,
|
|
81
82
|
categories: [...new Set([...current.categories, category])],
|
|
83
|
+
generated: current.generated && generated,
|
|
82
84
|
required: current.required || required
|
|
83
85
|
});
|
|
84
86
|
};
|
|
@@ -104,10 +106,10 @@ export const createEnvVariableMap = (compiledNodes, runtimePlans) => {
|
|
|
104
106
|
}
|
|
105
107
|
for (const runtimePlan of runtimePlans) {
|
|
106
108
|
for (const variable of runtimePlan.meta.env ?? []) {
|
|
107
|
-
register(variable.name, variable.required, variable.description, "runtime");
|
|
109
|
+
register(variable.name, variable.required, variable.description, "runtime", variable.generated ?? false);
|
|
108
110
|
}
|
|
109
111
|
for (const binding of runtimePlan.targetConfigEnvBindings ?? []) {
|
|
110
|
-
register(binding.envName, true, `Injected into ${runtimePlan.runtimeName} runtime config`, "runtime");
|
|
112
|
+
register(binding.envName, true, `Injected into ${runtimePlan.runtimeName} runtime config`, "runtime", binding.generated ?? false);
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
return variables;
|
|
@@ -165,7 +167,7 @@ export const createRuntimeTargetPlans = async (plan, compiledNodes) => {
|
|
|
165
167
|
.filter((node) => node.runtimeName === runtimeName && node.emittedFiles.length > 0)
|
|
166
168
|
.map((node) => ({
|
|
167
169
|
emittedFiles: node.emittedFiles,
|
|
168
|
-
id: `${node.kind}:${node.slug}`,
|
|
170
|
+
id: node.id ?? `${node.kind}:${node.slug}`,
|
|
169
171
|
kind: node.kind,
|
|
170
172
|
slug: node.slug,
|
|
171
173
|
value: node.value
|
|
@@ -192,6 +194,7 @@ export const createRuntimeTargetPlans = async (plan, compiledNodes) => {
|
|
|
192
194
|
resources: resolveTargetResources(target, targetInputs, instancePaths, adapter.container),
|
|
193
195
|
runtimeName,
|
|
194
196
|
runtimeRoot: recipe.runtimeRoot,
|
|
197
|
+
sourceIds: [...(target.sourceIds ?? [])].sort(),
|
|
195
198
|
targetConfigEnvBindings: target.configEnvBindings,
|
|
196
199
|
targetFiles: target.files
|
|
197
200
|
});
|
|
@@ -4,5 +4,11 @@ import type { EntrypointOptions } from "./containerEntrypointRender.js";
|
|
|
4
4
|
export { renderEntrypoint } from "./containerEntrypointRender.js";
|
|
5
5
|
export type { EntrypointOptions } from "./containerEntrypointRender.js";
|
|
6
6
|
export declare const renderEnvExample: (variables: ContainerEnvVariable[]) => string;
|
|
7
|
-
export
|
|
7
|
+
export interface DockerfileDistributionOptions {
|
|
8
|
+
labels: Record<string, string>;
|
|
9
|
+
reportOutputFile: string;
|
|
10
|
+
}
|
|
11
|
+
export declare const renderDockerfile: (runtimePlans: RuntimeTargetPlan[], options?: EntrypointOptions & {
|
|
12
|
+
distribution?: DockerfileDistributionOptions;
|
|
13
|
+
}) => Promise<string>;
|
|
8
14
|
export declare const createRootfsFiles: (runtimePlans: RuntimeTargetPlan[]) => EmittedFile[];
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { DISTRIBUTION_REPORT_IMAGE_PATH } from "../distribution/index.js";
|
|
2
3
|
import { createRuntimeInstallRecipe } from "../runtime/index.js";
|
|
3
4
|
import { SpawnfileError } from "../shared/index.js";
|
|
4
5
|
export { renderEntrypoint } from "./containerEntrypointRender.js";
|
|
5
6
|
import { MOLTNET_BIN_DIRECTORY, MOLTNET_BINARY_NAMES } from "./moltnetBinaries.js";
|
|
7
|
+
import { collectPackagesByManager, createAptPackageInstallItem, createNpmPackageInstallCommand, createNpmPackageInstallItem, createPackageInstallCommand, createPipxPackageInstallCommand, createPipxPackageInstallItem, getNpmInstallItemName } from "./containerPackageRender.js";
|
|
6
8
|
const CONTAINER_ROOTFS_ROOT = "container/rootfs";
|
|
7
9
|
const GATEWAY_PORT_PLACEHOLDER = "<gateway-port>";
|
|
8
10
|
const MOLTNET_INSTALL_SCRIPT_URL = "https://moltnet.dev/install.sh";
|
|
9
11
|
const MOLTNET_RELEASE_METADATA_URL = "https://api.github.com/repos/noopolis/moltnet/releases/latest";
|
|
10
12
|
const WORKSPACE_PLACEHOLDER = "<workspace-path>";
|
|
13
|
+
const RUNTIME_ROOT_PLACEHOLDER = "<runtime-root>";
|
|
11
14
|
const shellQuote = (value) => `'${value.replace(/'/g, `'\"'\"'`)}'`;
|
|
12
15
|
const extractNodeMajorVersion = (image) => Number(image.match(/^node:(\d+)/)?.[1] ?? "0");
|
|
13
|
-
const createPackageInstallCommand = (packages) => `RUN apt-get update && apt-get install -y --no-install-recommends ${packages.join(" ")} && rm -rf /var/lib/apt/lists/*`;
|
|
14
|
-
const createNpmPackageInstallCommand = (packages) => `RUN npm install -g --omit=dev --no-fund --no-audit ${packages.join(" ")}`;
|
|
15
|
-
const createPipxPackageInstallCommand = (packages) => `RUN PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install ${packages.join(" ")}`;
|
|
16
16
|
const createStateOwnershipCommand = (persistentMountPaths = []) => {
|
|
17
17
|
const mountPaths = [...new Set(persistentMountPaths)].sort();
|
|
18
18
|
const mkdirPaths = [...new Set(["/var/lib/spawnfile", ...mountPaths])].sort();
|
|
@@ -20,7 +20,6 @@ const createStateOwnershipCommand = (persistentMountPaths = []) => {
|
|
|
20
20
|
const chownPaths = [
|
|
21
21
|
...new Set([
|
|
22
22
|
"/var/lib/spawnfile",
|
|
23
|
-
"/opt/spawnfile",
|
|
24
23
|
...mountPaths.filter((mountPath) => !mountPath.startsWith("/var/lib/spawnfile/"))
|
|
25
24
|
])
|
|
26
25
|
].sort();
|
|
@@ -30,70 +29,13 @@ const createStateOwnershipCommand = (persistentMountPaths = []) => {
|
|
|
30
29
|
`chown -R spawnfile:spawnfile ${chownPaths.map(shellQuote).join(" ")}`
|
|
31
30
|
].join(" && ");
|
|
32
31
|
};
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
};
|
|
40
|
-
const createPackageIdentity = (pkg) => `${pkg.manager}\u0000${pkg.name}\u0000${pkg.version ?? ""}\u0000${pkg.scope ?? ""}`;
|
|
41
|
-
const createPackageConflictIdentity = (pkg) => `${pkg.manager}\u0000${pkg.name}`;
|
|
42
|
-
const assertImagePackageCompatibility = (packages) => {
|
|
43
|
-
const byName = new Map();
|
|
44
|
-
for (const currentPackage of packages) {
|
|
45
|
-
const conflictIdentity = createPackageConflictIdentity(currentPackage);
|
|
46
|
-
const existingPackage = byName.get(conflictIdentity);
|
|
47
|
-
if (!existingPackage) {
|
|
48
|
-
byName.set(conflictIdentity, currentPackage);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (createPackageIdentity(existingPackage) !== createPackageIdentity(currentPackage)) {
|
|
52
|
-
throw new SpawnfileError("validation_error", `Generated container declares conflicting package definitions for ${currentPackage.manager} package ${currentPackage.name}`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
const createAptPackageInstallItem = (pkg) => pkg.version ? `${pkg.name}=${pkg.version}` : pkg.name;
|
|
57
|
-
const createNpmPackageInstallItem = (pkg) => pkg.version ? `${pkg.name}@${pkg.version}` : pkg.name;
|
|
58
|
-
const createPipxPackageInstallItem = (pkg) => pkg.version ? `${pkg.name}==${pkg.version}` : pkg.name;
|
|
59
|
-
const getNpmInstallItemName = (item) => {
|
|
60
|
-
if (!item.startsWith("@")) {
|
|
61
|
-
const versionMarker = item.indexOf("@");
|
|
62
|
-
return versionMarker === -1 ? item : item.slice(0, versionMarker);
|
|
63
|
-
}
|
|
64
|
-
const slashIndex = item.indexOf("/");
|
|
65
|
-
if (slashIndex === -1) {
|
|
66
|
-
return item;
|
|
67
|
-
}
|
|
68
|
-
const versionMarker = item.indexOf("@", slashIndex);
|
|
69
|
-
return versionMarker === -1 ? item : item.slice(0, versionMarker);
|
|
70
|
-
};
|
|
71
|
-
const collectPackagesByManager = (runtimePlans) => {
|
|
72
|
-
const packagesByManager = {
|
|
73
|
-
apt: [],
|
|
74
|
-
npm: [],
|
|
75
|
-
pipx: []
|
|
76
|
-
};
|
|
77
|
-
const imagePackages = runtimePlans.flatMap((plan) => plan.packages ?? []);
|
|
78
|
-
assertImagePackageCompatibility(imagePackages);
|
|
79
|
-
const resolvedPackages = dedupePackages(imagePackages);
|
|
80
|
-
for (const packageConfig of resolvedPackages) {
|
|
81
|
-
if (packageConfig.manager === "apt") {
|
|
82
|
-
packagesByManager.apt.push(packageConfig);
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
if (packageConfig.manager === "npm") {
|
|
86
|
-
packagesByManager.npm.push(packageConfig);
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
if (packageConfig.manager === "pipx") {
|
|
90
|
-
packagesByManager.pipx.push(packageConfig);
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
32
|
+
const selectBaseImage = (runtimePlans, runtimeRecipes) => {
|
|
33
|
+
const recipeBaseImages = [
|
|
34
|
+
...new Set(runtimeRecipes.flatMap((recipe) => recipe.baseImage ? [recipe.baseImage] : []))
|
|
35
|
+
];
|
|
36
|
+
if (recipeBaseImages.length === 1 && runtimePlans.length === runtimeRecipes.length) {
|
|
37
|
+
return recipeBaseImages[0];
|
|
93
38
|
}
|
|
94
|
-
return packagesByManager;
|
|
95
|
-
};
|
|
96
|
-
const selectBaseImage = (runtimePlans) => {
|
|
97
39
|
const firstRuntimeMeta = runtimePlans[0]?.meta;
|
|
98
40
|
if (runtimePlans.length <= 1) {
|
|
99
41
|
return firstRuntimeMeta?.standaloneBaseImage ?? "debian:bookworm-slim";
|
|
@@ -132,12 +74,13 @@ export const renderEnvExample = (variables) => {
|
|
|
132
74
|
export const renderDockerfile = async (runtimePlans, options = {}) => {
|
|
133
75
|
const runtimeNames = [...new Set(runtimePlans.map((plan) => plan.runtimeName))];
|
|
134
76
|
const runtimeRecipes = await Promise.all(runtimeNames.map((runtimeName) => createRuntimeInstallRecipe(runtimeName)));
|
|
135
|
-
const
|
|
77
|
+
const recipeByRuntimeName = new Map(runtimeRecipes.map((recipe) => [recipe.runtimeName, recipe]));
|
|
78
|
+
const baseImage = selectBaseImage(runtimePlans, runtimeRecipes);
|
|
136
79
|
const needsJsonEnvWriter = runtimePlans.some((plan) => (plan.configEnvBindings?.length ?? 0) > 0);
|
|
137
80
|
const needsGit = runtimePlans.some((plan) => (plan.resources ?? []).some((resource) => resource.kind === "git"));
|
|
138
81
|
const systemDeps = [
|
|
139
82
|
...new Set([
|
|
140
|
-
...runtimePlans.flatMap((plan) => plan.meta.systemDeps),
|
|
83
|
+
...runtimePlans.flatMap((plan) => recipeByRuntimeName.get(plan.runtimeName)?.baseImage ? [] : plan.meta.systemDeps),
|
|
141
84
|
...(needsGit ? ["git"] : []),
|
|
142
85
|
...(options.hasMoltnet && !options.hasStagedMoltnetBinaries
|
|
143
86
|
? ["ca-certificates", "curl", "tar"]
|
|
@@ -194,6 +137,17 @@ export const renderDockerfile = async (runtimePlans, options = {}) => {
|
|
|
194
137
|
}
|
|
195
138
|
lines.push('RUN if ! id -u spawnfile >/dev/null 2>&1; then useradd --create-home --home-dir /home/spawnfile --shell /bin/bash spawnfile; fi', "");
|
|
196
139
|
lines.push("COPY container/rootfs/ /", "COPY .env.example /opt/spawnfile/.env.example", 'COPY entrypoint.sh /opt/spawnfile/entrypoint.sh', "RUN chmod +x /opt/spawnfile/entrypoint.sh");
|
|
140
|
+
const postRootfsCommands = [
|
|
141
|
+
...new Set(runtimePlans.flatMap((plan) => plan.meta.postRootfsCommands ?? []))
|
|
142
|
+
];
|
|
143
|
+
for (const command of postRootfsCommands) {
|
|
144
|
+
lines.push(`RUN ${command}`);
|
|
145
|
+
}
|
|
146
|
+
if (options.distribution) {
|
|
147
|
+
lines.push("", `COPY ${options.distribution.reportOutputFile} ${DISTRIBUTION_REPORT_IMAGE_PATH}`, ...Object.entries(options.distribution.labels)
|
|
148
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
149
|
+
.map(([name, value]) => `LABEL ${name}=${shellQuote(value)}`));
|
|
150
|
+
}
|
|
197
151
|
lines.push(`RUN ${createStateOwnershipCommand(options.persistentMountPaths)}`);
|
|
198
152
|
if (exposedPorts.length > 0) {
|
|
199
153
|
lines.push(`EXPOSE ${exposedPorts.join(" ")}`);
|
|
@@ -207,11 +161,19 @@ export const createRootfsFiles = (runtimePlans) => runtimePlans.flatMap((plan) =
|
|
|
207
161
|
return {
|
|
208
162
|
content: file.content
|
|
209
163
|
.replaceAll(WORKSPACE_PLACEHOLDER, plan.instancePaths.workspacePath)
|
|
164
|
+
.replaceAll(RUNTIME_ROOT_PLACEHOLDER, plan.runtimeRoot)
|
|
210
165
|
.replaceAll(`"${GATEWAY_PORT_PLACEHOLDER}"`, plan.port ? String(plan.port) : "0")
|
|
211
166
|
.replaceAll(GATEWAY_PORT_PLACEHOLDER, plan.port ? String(plan.port) : ""),
|
|
212
167
|
path: `${CONTAINER_ROOTFS_ROOT}${plan.instancePaths.configPath}`
|
|
213
168
|
};
|
|
214
169
|
}
|
|
170
|
+
if (file.path.startsWith("runtime/")) {
|
|
171
|
+
const relativeRuntimePath = file.path.slice("runtime/".length);
|
|
172
|
+
return {
|
|
173
|
+
content: file.content,
|
|
174
|
+
path: `${CONTAINER_ROOTFS_ROOT}${path.posix.join(plan.runtimeRoot, relativeRuntimePath)}`
|
|
175
|
+
};
|
|
176
|
+
}
|
|
215
177
|
if (file.path.startsWith("workspace/")) {
|
|
216
178
|
const relativeWorkspacePath = file.path.slice("workspace/".length);
|
|
217
179
|
return {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DistributionReport } from "../distribution/index.js";
|
|
1
2
|
import type { ContainerReport } from "../report/index.js";
|
|
2
3
|
import type { EmittedFile, RuntimeContainerConfigEnvBinding, RuntimeContainerMeta } from "../runtime/index.js";
|
|
3
4
|
import type { ModelAuthMethod } from "../shared/index.js";
|
|
@@ -7,6 +8,7 @@ import type { WorkspaceResourcePlan } from "./workspaceResources.js";
|
|
|
7
8
|
export interface ContainerEnvVariable {
|
|
8
9
|
categories: Array<"model" | "project" | "runtime" | "surface">;
|
|
9
10
|
description: string;
|
|
11
|
+
generated: boolean;
|
|
10
12
|
name: string;
|
|
11
13
|
required: boolean;
|
|
12
14
|
}
|
|
@@ -32,17 +34,24 @@ export interface RuntimeTargetPlan {
|
|
|
32
34
|
resources?: WorkspaceResourcePlan[];
|
|
33
35
|
runtimeName: string;
|
|
34
36
|
runtimeRoot: string;
|
|
37
|
+
sourceIds?: string[];
|
|
35
38
|
targetConfigEnvBindings?: RuntimeContainerConfigEnvBinding[];
|
|
36
39
|
targetFiles: EmittedFile[];
|
|
37
40
|
}
|
|
38
41
|
export interface CompiledNodeArtifact {
|
|
39
42
|
emittedFiles: EmittedFile[];
|
|
43
|
+
id?: string;
|
|
40
44
|
kind: "agent" | "team";
|
|
41
45
|
runtimeName: string | null;
|
|
42
46
|
slug: string;
|
|
43
47
|
value: ResolvedAgentNode | ResolvedTeamNode;
|
|
44
48
|
}
|
|
45
49
|
export interface GeneratedContainerArtifacts {
|
|
50
|
+
distribution: {
|
|
51
|
+
fingerprint: string;
|
|
52
|
+
labels: Record<string, string>;
|
|
53
|
+
report: DistributionReport;
|
|
54
|
+
};
|
|
46
55
|
executablePaths: string[];
|
|
47
56
|
files: EmittedFile[];
|
|
48
57
|
moltnet?: {
|