@assemblyline-agents/cli 5.0.10 → 6.0.0

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 (93) hide show
  1. package/authoring/skills/assembly-line-authoring/SKILL.md +24 -21
  2. package/dist/add.d.ts +2 -13
  3. package/dist/add.d.ts.map +1 -1
  4. package/dist/add.js +169 -478
  5. package/dist/add.js.map +1 -1
  6. package/dist/args.d.ts.map +1 -1
  7. package/dist/args.js +1 -2
  8. package/dist/args.js.map +1 -1
  9. package/dist/auth.d.ts +1 -2
  10. package/dist/auth.d.ts.map +1 -1
  11. package/dist/auth.js +38 -45
  12. package/dist/auth.js.map +1 -1
  13. package/dist/channels.d.ts +7 -30
  14. package/dist/channels.d.ts.map +1 -1
  15. package/dist/channels.js +21 -161
  16. package/dist/channels.js.map +1 -1
  17. package/dist/deploy-definition.d.ts +1 -8
  18. package/dist/deploy-definition.d.ts.map +1 -1
  19. package/dist/deploy-definition.js +18 -66
  20. package/dist/deploy-definition.js.map +1 -1
  21. package/dist/deploy-migrations.d.ts +15 -0
  22. package/dist/deploy-migrations.d.ts.map +1 -0
  23. package/dist/deploy-migrations.js +72 -0
  24. package/dist/deploy-migrations.js.map +1 -0
  25. package/dist/deploy-preflight.d.ts +6 -0
  26. package/dist/deploy-preflight.d.ts.map +1 -0
  27. package/dist/deploy-preflight.js +40 -0
  28. package/dist/deploy-preflight.js.map +1 -0
  29. package/dist/deploy-receipts.d.ts +9 -0
  30. package/dist/deploy-receipts.d.ts.map +1 -0
  31. package/dist/deploy-receipts.js +45 -0
  32. package/dist/deploy-receipts.js.map +1 -0
  33. package/dist/deploy-sandbox-environments.d.ts +6 -0
  34. package/dist/deploy-sandbox-environments.d.ts.map +1 -0
  35. package/dist/deploy-sandbox-environments.js +109 -0
  36. package/dist/deploy-sandbox-environments.js.map +1 -0
  37. package/dist/deploy-secrets.d.ts +21 -0
  38. package/dist/deploy-secrets.d.ts.map +1 -0
  39. package/dist/deploy-secrets.js +83 -0
  40. package/dist/deploy-secrets.js.map +1 -0
  41. package/dist/deploy.d.ts +5 -31
  42. package/dist/deploy.d.ts.map +1 -1
  43. package/dist/deploy.js +50 -636
  44. package/dist/deploy.js.map +1 -1
  45. package/dist/eval-case-runner.d.ts +1 -1
  46. package/dist/eval-case-runner.d.ts.map +1 -1
  47. package/dist/eval-case-runner.js.map +1 -1
  48. package/dist/help.d.ts.map +1 -1
  49. package/dist/help.js +41 -89
  50. package/dist/help.js.map +1 -1
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +89 -32
  53. package/dist/index.js.map +1 -1
  54. package/dist/init.d.ts +1 -3
  55. package/dist/init.d.ts.map +1 -1
  56. package/dist/init.js +36 -39
  57. package/dist/init.js.map +1 -1
  58. package/dist/inspection.d.ts +44 -0
  59. package/dist/inspection.d.ts.map +1 -0
  60. package/dist/inspection.js +144 -0
  61. package/dist/inspection.js.map +1 -0
  62. package/dist/models.d.ts +2 -1
  63. package/dist/models.d.ts.map +1 -1
  64. package/dist/models.js +7 -13
  65. package/dist/models.js.map +1 -1
  66. package/dist/plugin-add.d.ts +36 -0
  67. package/dist/plugin-add.d.ts.map +1 -0
  68. package/dist/plugin-add.js +283 -0
  69. package/dist/plugin-add.js.map +1 -0
  70. package/dist/plugin-init.d.ts +2 -0
  71. package/dist/plugin-init.d.ts.map +1 -0
  72. package/dist/plugin-init.js +34 -0
  73. package/dist/plugin-init.js.map +1 -0
  74. package/dist/provider-deploy.d.ts +7 -0
  75. package/dist/provider-deploy.d.ts.map +1 -0
  76. package/dist/provider-deploy.js +163 -0
  77. package/dist/provider-deploy.js.map +1 -0
  78. package/dist/secrets.d.ts.map +1 -1
  79. package/dist/secrets.js +8 -28
  80. package/dist/secrets.js.map +1 -1
  81. package/package.json +9 -11
  82. package/dist/hosts.d.ts +0 -3
  83. package/dist/hosts.d.ts.map +0 -1
  84. package/dist/hosts.js +0 -60
  85. package/dist/hosts.js.map +0 -1
  86. package/dist/model-providers.d.ts +0 -5
  87. package/dist/model-providers.d.ts.map +0 -1
  88. package/dist/model-providers.js +0 -17
  89. package/dist/model-providers.js.map +0 -1
  90. package/dist/state.d.ts +0 -3
  91. package/dist/state.d.ts.map +0 -1
  92. package/dist/state.js +0 -79
  93. package/dist/state.js.map +0 -1
package/dist/deploy.js CHANGED
@@ -1,22 +1,23 @@
1
- import { spawn } from "node:child_process";
2
- import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
- import { dirname, resolve } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import { buildAgent, compileAgent, deployTargetCredentialNames, planDeployment } from "@assemblyline-agents/compiler";
6
- import { adapterProviderMetadata, isSensitiveSandboxEnvName, sanitizeDeployName } from "@assemblyline-agents/core";
7
- import { createCredentialBoundary, installSignalHandlers, listenNodeRuntime, resolveCredentialInventory, resolveGatewayCredentials, resolveProvider } from "@assemblyline-agents/node";
8
- import { FileStateAdapter, LocalBlobAdapter, LocalSandboxAdapter, AssemblyLineRuntime, loadRuntimeBundle } from "@assemblyline-agents/runtime";
1
+ import { rmSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { buildAgent, compileAgent, planDeployment } from "@assemblyline-agents/compiler";
4
+ import { installSignalHandlers, listenNodeRuntime } from "@assemblyline-agents/node";
5
+ import { AssemblyLineRuntime, FileStateAdapter, LocalBlobAdapter, LocalSandboxAdapter, loadRuntimeBundle } from "@assemblyline-agents/runtime";
9
6
  import { booleanFlag, buildOptions, numberFlag, stringFlag } from "./args.js";
10
- import { printIssues } from "./help.js";
11
- import { missingPackageFixLines } from "./init.js";
12
- import { manifestUsesModelProvider } from "./model-providers.js";
13
- import { checkChannelInstallations } from "./channels.js";
14
- import { wireConnectionEvents } from "./connections.js";
15
7
  import { resolveDeployDefinition, resolveDeployEnvironment } from "./deploy-definition.js";
16
- import { executeDeployCleanup, executeDeployLifecycle } from "./deploy-lifecycle.js";
17
- import { readEnvSecretsFile } from "./project-env.js";
18
- import { prepareReleaseSnapshot, releaseSnapshotForReceipt, withReleaseSnapshot, } from "./release-history.js";
8
+ import { effectiveMissingRequirements, preflightChannelInstallations, printDeployWarnings } from "./deploy-preflight.js";
9
+ import { assertDestroyAllowed, localDeployReceipt, removeDeployReceipts, writeDeployReceipt } from "./deploy-receipts.js";
10
+ import { reusableSandboxEnvironmentResolutions } from "./deploy-sandbox-environments.js";
11
+ import { resolveDeploySecretsEnv } from "./deploy-secrets.js";
12
+ import { printIssues } from "./help.js";
13
+ import { runProviderDeploy } from "./provider-deploy.js";
14
+ import { prepareReleaseSnapshot } from "./release-history.js";
19
15
  export { parseEnvSecrets } from "./project-env.js";
16
+ export { runCommand } from "./deploy-migrations.js";
17
+ export { writeJson } from "./deploy-receipts.js";
18
+ export { reusableSandboxEnvironmentResolutions, sandboxPassthroughEnv } from "./deploy-sandbox-environments.js";
19
+ export { splitDeploySecrets, withDeclaredRuntimeEnv } from "./deploy-secrets.js";
20
+ /** Compile, plan, and dispatch one deployment through the selected plugin graph. */
20
21
  export async function runDeployCommand(agentRoot, parsed) {
21
22
  const deployOptions = {
22
23
  environment: "development",
@@ -32,8 +33,7 @@ export async function runDeployCommand(agentRoot, parsed) {
32
33
  const overlay = await resolveDeploySecretsEnv(manifest, agentRoot, target ?? manifest.gateway.deploy?.kind ?? "local");
33
34
  const plan = planDeployment(manifest, { ...deployOptions, env: overlay.env });
34
35
  plan.storeHeldCredentials = overlay.storeNames;
35
- const definition = resolveDeployDefinition(manifest, plan.target, parsed);
36
- plan.missingRequirements = await effectiveMissingRequirements(plan, definition, manifest);
36
+ plan.missingRequirements = effectiveMissingRequirements(plan, manifest);
37
37
  printDeployWarnings(plan);
38
38
  console.log(JSON.stringify(plan, null, 2));
39
39
  return;
@@ -51,65 +51,21 @@ export async function runDeployCommand(agentRoot, parsed) {
51
51
  const definition = resolveDeployDefinition(artifact.manifest, plan.target, parsed);
52
52
  const effectivePlan = {
53
53
  ...plan,
54
- missingRequirements: await effectiveMissingRequirements(plan, definition, artifact.manifest, reusableSandboxEnvironmentResolutions(artifact, plan.environment)),
54
+ missingRequirements: effectiveMissingRequirements(plan, artifact.manifest, reusableSandboxEnvironmentResolutions(artifact, plan.environment)),
55
55
  canPublish: false
56
56
  };
57
57
  effectivePlan.canPublish = !effectivePlan.dryRun && effectivePlan.missingRequirements.length === 0;
58
58
  printDeployWarnings(effectivePlan);
59
59
  console.log(JSON.stringify(effectivePlan, null, 2));
60
- if (effectivePlan.dryRun)
61
- return;
62
60
  if (effectivePlan.missingRequirements.length > 0) {
63
61
  console.error("Missing deploy setup:");
64
62
  for (const requirement of effectivePlan.missingRequirements) {
65
63
  console.error(`- ${requirement.name}: ${requirement.message}`);
66
- for (const line of missingPackageFixLines(requirement.adapter, agentRoot))
67
- console.error(line);
68
64
  }
69
65
  process.exit(1);
70
66
  }
71
67
  if (effectivePlan.target === "local") {
72
- if (booleanFlag(parsed, "destroy")) {
73
- assertDestroyAllowed(effectivePlan, parsed);
74
- if (booleanFlag(parsed, "purge-data")) {
75
- for (const entry of ["deploy-state.json", "blobs", "sandbox"]) {
76
- rmSync(resolve(artifact.root, entry), { recursive: true, force: true });
77
- }
78
- }
79
- removeDeployReceipts(artifact.root, effectivePlan);
80
- console.log(`Local deploy environment "${effectivePlan.environment}" destroyed${booleanFlag(parsed, "purge-data") ? " (data purged)" : ""}.`);
81
- return;
82
- }
83
- if (booleanFlag(parsed, "serve") || booleanFlag(parsed, "once")) {
84
- const runtimeOptions = loadRuntimeBundle(artifact.root);
85
- if (booleanFlag(parsed, "once")) {
86
- new AssemblyLineRuntime({ ...runtimeOptions, devMode: true }).boot();
87
- writeDeployReceipt(artifact.root, effectivePlan, localDeployReceipt(artifact, effectivePlan, "checked", null));
88
- console.log(`Local deploy serving revision ${effectivePlan.agentRevision}`);
89
- console.log("local deploy check completed without binding a socket");
90
- return;
91
- }
92
- const handle = await listenNodeRuntime({
93
- ...runtimeOptions,
94
- state: new FileStateAdapter(resolve(artifact.root, "deploy-state.json")),
95
- blob: new LocalBlobAdapter(resolve(artifact.root, "blobs")),
96
- sandbox: new LocalSandboxAdapter(resolve(artifact.root, "sandbox")),
97
- env: overlay.boundary.config,
98
- credentialStore: overlay.boundary.store,
99
- gatewayCredentials: await resolveGatewayCredentials(overlay.boundary, runtimeOptions.manifest),
100
- devMode: true,
101
- port: numberFlag(parsed, "port") ?? 0
102
- });
103
- writeDeployReceipt(artifact.root, effectivePlan, localDeployReceipt(artifact, effectivePlan, "serving", handle.url));
104
- console.log(`Local deploy serving revision ${effectivePlan.agentRevision}`);
105
- console.log(handle.url);
106
- // SIGTERM/SIGINT drain in-flight runs before exit; a second signal exits immediately.
107
- installSignalHandlers(handle);
108
- await handle.closed;
109
- return;
110
- }
111
- writeDeployReceipt(artifact.root, effectivePlan, localDeployReceipt(artifact, effectivePlan, "prepared", null));
112
- console.log(`Local deploy prepared for revision ${effectivePlan.agentRevision}. Run assembly-line serve to keep it online.`);
68
+ await runLocalDeploy(artifact, effectivePlan, parsed, deployEnv);
113
69
  return;
114
70
  }
115
71
  const release = !booleanFlag(parsed, "destroy") && !booleanFlag(parsed, "ingress-only")
@@ -117,586 +73,44 @@ export async function runDeployCommand(agentRoot, parsed) {
117
73
  : undefined;
118
74
  await runProviderDeploy(artifact, effectivePlan, definition, parsed, agentRoot, release, deployEnv);
119
75
  }
120
- /**
121
- * The env view deploy planning and secret sync work from: the process
122
- * environment overlaid with the gateway's secrets store when one is
123
- * configured, so declared secrets held only in the store still satisfy
124
- * preflight and reach `--sync-secrets` without per-agent `.env` copies.
125
- */
126
- async function resolveDeploySecretsEnv(manifest, agentRoot, target) {
127
- const boundary = await createCredentialBoundary({
128
- manifest,
129
- env: process.env,
130
- additionalCredentialNames: deployTargetCredentialNames(manifest, target),
131
- artifactRoot: agentRoot,
132
- providerRoot: fileURLToPath(new URL("..", import.meta.url)),
133
- devMode: false
134
- });
135
- return { ...(await resolveCredentialInventory(boundary)), boundary };
136
- }
137
- async function preflightChannelInstallations(artifact, deployEnv) {
138
- const checks = await checkChannelInstallations(artifact.manifest, deployEnv, fetch);
139
- for (const check of checks) {
140
- const workspace = check.workspace ? ` (${check.workspace})` : "";
141
- if (check.skipped) {
142
- console.warn(`Channel preflight skipped for ${check.provider}${workspace}: ${check.detail}`);
143
- continue;
144
- }
145
- if (!check.ok)
146
- throw new Error(`Channel preflight failed for ${check.provider}${workspace}: ${check.detail}`);
147
- console.log(`Channel preflight passed for ${check.provider}${workspace}.`);
148
- if (check.missingOptionalScopes.length > 0) {
149
- console.warn(`Optional ${check.provider} scopes not granted: ${check.missingOptionalScopes.join(", ")}.`);
150
- }
151
- }
152
- }
153
- /**
154
- * The canonical receipt is per environment (`deployments/<environment>.json`).
155
- * The legacy `deployment.json` keeps its exact historical behavior — always
156
- * written, last deploy wins — so existing readers are unaffected; new readers
157
- * should prefer the environment-scoped file.
158
- */
159
- function environmentReceiptPath(artifactRoot, environment) {
160
- return resolve(artifactRoot, "deployments", `${sanitizeDeployName(environment)}.json`);
161
- }
162
- function writeDeployReceipt(artifactRoot, plan, receipt) {
163
- writeJson(environmentReceiptPath(artifactRoot, plan.environment), receipt);
164
- writeJson(resolve(artifactRoot, "deployment.json"), receipt);
165
- }
166
- function removeDeployReceipts(artifactRoot, plan) {
167
- rmSync(environmentReceiptPath(artifactRoot, plan.environment), { force: true });
168
- // The legacy mirror is last-deploy-wins; drop it only when it belongs to the
169
- // environment being destroyed, otherwise it still describes a live deploy.
170
- const legacyPath = resolve(artifactRoot, "deployment.json");
171
- try {
172
- const legacy = JSON.parse(readFileSync(legacyPath, "utf8"));
173
- if (legacy.environment === plan.environment)
174
- rmSync(legacyPath, { force: true });
175
- }
176
- catch {
177
- // Unreadable or absent legacy receipt: leave it alone.
178
- }
179
- }
180
- function assertDestroyAllowed(plan, parsed) {
181
- if (plan.environment === plan.defaultEnvironment && !booleanFlag(parsed, "force")) {
182
- throw new Error(`Refusing to destroy the default environment "${plan.environment}" without --force. Alternate environments (--env <name>) are presumed disposable; the default one is not.`);
183
- }
184
- }
185
- async function effectiveMissingRequirements(plan, definition, manifest, reusableSandboxEnvironments) {
186
- let missing = plan.missingRequirements;
187
- // VPS runtime credentials are validated from the remote 0600 secret file
188
- // after optional sync. This allows repeat deploys without copying secrets
189
- // from the VPS back onto the operator machine.
190
- if (plan.target === "vps" && !plan.dryRun) {
191
- const sandboxKind = manifest.gateway.sandbox?.kind ?? "local";
192
- const provisioningEnv = new Set(adapterProviderMetadata(sandboxKind, "sandbox")?.sandboxEnvironment?.provisioningEnv ?? []);
193
- missing = missing.filter((requirement) => requirement.kind !== "env"
194
- || (provisioningEnv.has(requirement.name) && !reusableSandboxEnvironments));
195
- }
196
- if (plan.target === "railway" && missing.some((requirement) => requirement.name === "RAILWAY_TOKEN")) {
197
- const railwayBin = stringOption(definition, "railwayBin") ?? "railway";
198
- if (await railwayCliAuthenticated(railwayBin)) {
199
- missing = missing.filter((requirement) => requirement.name !== "RAILWAY_TOKEN");
200
- }
201
- }
202
- if (plan.target === "fly" && !stringOption(definition, "app")) {
203
- missing = [
204
- ...missing,
205
- {
206
- kind: "deploy",
207
- name: "fly-app",
208
- required: true,
209
- adapter: "fly",
210
- message: "Fly deploys require --fly-app or FLY_APP_NAME."
211
- }
212
- ];
213
- }
214
- if (plan.target === "vps") {
215
- if (!stringOption(definition, "host")) {
216
- missing = [
217
- ...missing,
218
- {
219
- kind: "deploy",
220
- name: "vps-host",
221
- required: true,
222
- adapter: "vps",
223
- message: "VPS deploys require a named host through vpsDeploy() or --vps-host."
224
- }
225
- ];
226
- }
227
- }
228
- return missing;
229
- }
230
- function printDeployWarnings(plan) {
231
- if (plan.warnings.length === 0)
232
- return;
233
- console.warn("Deploy warnings:");
234
- for (const warning of plan.warnings)
235
- console.warn(`- ${warning.name}: ${warning.message}`);
236
- }
237
- /**
238
- * Push the project's local secrets to the deploy target when `--sync-secrets`
239
- * is set and the publisher supports it. An optional `--secrets-from <path>`
240
- * is explicit private input; project `.env` files are never loaded implicitly.
241
- */
242
- async function syncDeploySecretsIfRequested(publisher, plan, parsed, deployEnv) {
243
- if (!booleanFlag(parsed, "sync-secrets"))
244
- return;
245
- if (!publisher.syncSecrets) {
246
- console.warn(`Deploy target ${publisher.target} does not support secret sync; set variables on the target manually.`);
247
- return;
248
- }
249
- const secretsPath = stringFlag(parsed, "secrets-from");
250
- if (secretsPath && !existsSync(secretsPath)) {
251
- throw new Error(`Secrets file not found: ${secretsPath}`);
252
- }
253
- const fileSecrets = secretsPath ? readEnvSecretsFile(secretsPath) : {};
254
- const secrets = withDeclaredRuntimeEnv(fileSecrets, plan, deployEnv);
255
- const { runtimeSecrets, localDeployKeys, storeHeldKeys } = splitDeploySecrets(secrets, plan);
256
- if (localDeployKeys.length > 0) {
257
- console.log(`Kept ${localDeployKeys.length} deploy credential(s) local: ${localDeployKeys.join(", ")}`);
258
- }
259
- if (storeHeldKeys.length > 0) {
260
- console.log(`Left ${storeHeldKeys.length} credential(s) in the gateway store (resolved on use, not synced): ${storeHeldKeys.join(", ")}`);
261
- }
262
- const receipt = await publisher.syncSecrets(runtimeSecrets, plan);
263
- console.log(`Synced ${receipt.syncedKeys.length} secret(s) to ${publisher.target}: ${receipt.syncedKeys.join(", ") || "(none)"}`);
264
- if (receipt.skippedKeys.length > 0) {
265
- console.log(`Skipped ${receipt.skippedKeys.length} empty key(s): ${receipt.skippedKeys.join(", ")}`);
266
- }
267
- }
268
- export function withDeclaredRuntimeEnv(fileSecrets, plan, env) {
269
- const committedConfig = new Set(plan.committedConfig ?? []);
270
- const secrets = Object.fromEntries(Object.entries(fileSecrets).filter(([name]) => !committedConfig.has(name)));
271
- for (const requirement of plan.requirements) {
272
- if (requirement.kind !== "env" || requirement.adapter === plan.target)
273
- continue;
274
- for (const key of [requirement.name, ...(requirement.alternatives ?? [])]) {
275
- if (committedConfig.has(key))
276
- continue;
277
- if (env[key] !== undefined)
278
- secrets[key] = env[key] ?? "";
279
- }
280
- }
281
- return secrets;
282
- }
283
- /**
284
- * Partition candidate sync values: direct deploy credentials stay on the
285
- * operator's machine, deploy sub-capability credentials are handed to the
286
- * trusted publisher, ordinary store-held runtime credentials stay in the
287
- * store, and everything else is pushed to the target.
288
- */
289
- export function splitDeploySecrets(secrets, plan) {
290
- const storeHeld = new Set(plan.storeHeldCredentials ?? []);
291
- const deployCredentials = new Set(plan.deployCredentialNames ?? []);
292
- const committedConfig = new Set(plan.committedConfig ?? []);
293
- const deployKeys = new Set(plan.requirements
294
- .filter((requirement) => requirement.kind === "env" && requirement.adapter === plan.target)
295
- .flatMap((requirement) => [requirement.name, ...(requirement.alternatives ?? [])]));
296
- const runtimeKeys = new Set(plan.requirements
297
- .filter((requirement) => requirement.kind === "env" && requirement.adapter !== plan.target)
298
- .flatMap((requirement) => [requirement.name, ...(requirement.alternatives ?? [])]));
299
- const runtimeSecrets = {};
300
- const localDeployKeys = [];
301
- const storeHeldKeys = [];
302
- for (const [key, value] of Object.entries(secrets)) {
303
- if (committedConfig.has(key)) {
304
- continue;
305
- }
306
- else if (deployKeys.has(key) && !runtimeKeys.has(key)) {
307
- localDeployKeys.push(key);
308
- }
309
- else if (storeHeld.has(key) && !deployCredentials.has(key)) {
310
- storeHeldKeys.push(key);
311
- }
312
- else {
313
- runtimeSecrets[key] = value;
314
- }
315
- }
316
- return { runtimeSecrets, localDeployKeys, storeHeldKeys: storeHeldKeys.sort() };
317
- }
318
- async function railwayCliAuthenticated(railwayBin) {
319
- try {
320
- const result = await runCommand(railwayBin, ["whoami"], {
321
- cwd: process.cwd(),
322
- env: process.env,
323
- silent: true
324
- });
325
- return result.exitCode === 0;
326
- }
327
- catch {
328
- return false;
329
- }
330
- }
331
- /**
332
- * Every non-local target resolves through the open provider registry. Built-in
333
- * flags only normalize provider options; publishing follows this single path.
334
- */
335
- async function runProviderDeploy(artifact, plan, definition, parsed, agentRoot, release, deployEnv) {
336
- if (parsed.flags["domains-only"] !== undefined) {
337
- throw new Error("--domains-only was removed. Use --ingress-only.");
338
- }
339
- // Preflight checks requirements against the store-overlaid deploy env, so
340
- // the publisher must resolve from the same view or a store-held deploy
341
- // credential would pass preflight and then fail provider resolution.
342
- const publisher = await resolveProvider("deploy", definition, {
343
- env: deployEnv,
344
- fetch,
345
- manifest: artifact.manifest,
346
- artifactRoot: artifact.root,
347
- providerRoot: fileURLToPath(new URL("..", import.meta.url)),
348
- devMode: false
349
- });
350
- const modeFlags = ["prepare-only", "activate", "rollback", "ingress-only", "destroy"]
351
- .filter((flag) => booleanFlag(parsed, flag));
352
- if (modeFlags.length > 1) {
353
- throw new Error(`Deploy lifecycle flags are mutually exclusive: ${modeFlags.map((flag) => `--${flag}`).join(", ")}.`);
354
- }
76
+ async function runLocalDeploy(artifact, plan, parsed, deployEnv) {
355
77
  if (booleanFlag(parsed, "destroy")) {
356
- if (!publisher.destroy)
357
- throw new Error(`Deploy target ${plan.target} does not support destroy.`);
358
78
  assertDestroyAllowed(plan, parsed);
359
- // No preflight: destroy must not provision anything on the way out.
360
- const receipt = await publisher.destroy(artifact, plan, { purgeData: booleanFlag(parsed, "purge-data") });
79
+ if (booleanFlag(parsed, "purge-data")) {
80
+ for (const entry of ["deploy-state.json", "blobs", "sandbox"]) {
81
+ rmSync(resolve(artifact.root, entry), { recursive: true, force: true });
82
+ }
83
+ }
361
84
  removeDeployReceipts(artifact.root, plan);
362
- console.log(`${deployTargetLabel(plan.target)} environment "${plan.environment}" destroyed${booleanFlag(parsed, "purge-data") ? " (data purged)" : ""}.`);
363
- console.log(JSON.stringify(receipt, null, 2));
364
- return;
365
- }
366
- if (booleanFlag(parsed, "activate")) {
367
- if (!publisher.activate)
368
- throw new Error(`Deploy target ${plan.target} does not support separate activation.`);
369
- const activate = publisher.activate;
370
- await publisher.preflight?.(artifact, plan);
371
- const providerReceipt = await deployActionWithCleanup(publisher, artifact, plan, () => activate.call(publisher, artifact, plan));
372
- const receipt = withReleaseSnapshot(providerReceipt, releaseSnapshotForReceipt(release, providerReceipt, true));
373
- writeDeployReceipt(artifact.root, plan, receipt);
374
- await wireDeployedConnectionEvents(artifact.manifest, receipt);
375
- console.log(`${deployTargetLabel(plan.target)} prepared release activated.`);
376
- console.log(JSON.stringify(receipt, null, 2));
377
- return;
378
- }
379
- if (booleanFlag(parsed, "rollback")) {
380
- if (!publisher.rollback)
381
- throw new Error(`Deploy target ${plan.target} does not support rollback.`);
382
- const rollback = publisher.rollback;
383
- await publisher.preflight?.(artifact, plan);
384
- const providerReceipt = await deployActionWithCleanup(publisher, artifact, plan, () => rollback.call(publisher, artifact, plan));
385
- const receipt = withReleaseSnapshot(providerReceipt, releaseSnapshotForReceipt(release, providerReceipt, true));
386
- writeDeployReceipt(artifact.root, plan, receipt);
387
- await wireDeployedConnectionEvents(artifact.manifest, receipt);
388
- console.log(`${deployTargetLabel(plan.target)} release rolled back.`);
389
- console.log(JSON.stringify(receipt, null, 2));
85
+ console.log(`Local deploy environment "${plan.environment}" destroyed${booleanFlag(parsed, "purge-data") ? " (data purged)" : ""}.`);
390
86
  return;
391
87
  }
392
- if (booleanFlag(parsed, "ingress-only")) {
393
- if (!publisher.reconcileIngress)
394
- throw new Error(`Deploy target ${plan.target} does not support ingress reconciliation.`);
395
- const reconcileIngress = publisher.reconcileIngress;
396
- await publisher.preflight?.(artifact, plan);
397
- const receipt = await deployActionWithCleanup(publisher, artifact, plan, () => reconcileIngress.call(publisher, artifact, plan));
398
- writeDeployReceipt(artifact.root, plan, receipt);
399
- console.log(`${deployTargetLabel(plan.target)} ingress reconciled.`);
400
- console.log(JSON.stringify(receipt, null, 2));
401
- return;
402
- }
403
- const prepareOnly = booleanFlag(parsed, "prepare-only");
404
- const reconcileSandboxEnvironment = sandboxEnvironmentReconciler(artifact, deployEnv, reusableSandboxEnvironmentResolutions(artifact, plan.environment));
405
- console.log(`${prepareOnly ? "Preparing" : "Publishing"} Assembly Line revision ${plan.agentRevision} with ${plan.target}.`);
406
- const migrationRequest = configuredMigrationRequest(artifact, parsed);
407
- const progressStages = [
408
- "preflight",
409
- ...(reconcileSandboxEnvironment ? ["sandbox-environment"] : []),
410
- "prepare",
411
- "secrets",
412
- "migrations",
413
- ...(!prepareOnly ? ["activate"] : []),
414
- ...(publisher.cleanup ? ["cleanup"] : [])
415
- ];
416
- const progressLabels = {
417
- preflight: "Checking provider and host",
418
- "sandbox-environment": "Reconciling sandbox environment",
419
- prepare: "Uploading artifact and preparing runtime image",
420
- secrets: "Reconciling runtime secrets",
421
- migrations: "Running migrations",
422
- activate: "Activating and health-checking the release",
423
- cleanup: "Cleaning managed deployment artifacts"
424
- };
425
- const { providerReceipt, migrationStatus, lifecycle, sandboxEnvironments } = await executeDeployLifecycle({
426
- publisher,
427
- artifact,
428
- plan,
429
- migrationRequest,
430
- activate: !prepareOnly,
431
- ...(reconcileSandboxEnvironment ? { reconcileSandboxEnvironment } : {}),
432
- syncSecrets: () => syncDeploySecretsIfRequested(publisher, plan, parsed, deployEnv),
433
- runFallbackMigrations: () => runConfiguredMigrations(artifact, plan, migrationRequest),
434
- onProgress: (stage) => {
435
- const index = progressStages.indexOf(stage);
436
- if (index >= 0)
437
- console.log(`[${index + 1}/${progressStages.length}] ${progressLabels[stage]}`);
88
+ if (booleanFlag(parsed, "serve") || booleanFlag(parsed, "once")) {
89
+ const runtimeOptions = loadRuntimeBundle(artifact.root);
90
+ if (booleanFlag(parsed, "once")) {
91
+ new AssemblyLineRuntime({ ...runtimeOptions, devMode: true }).boot();
92
+ writeDeployReceipt(artifact.root, plan, localDeployReceipt(artifact, plan, "checked", null));
93
+ console.log(`Local deploy serving revision ${plan.agentRevision}`);
94
+ console.log("local deploy check completed without binding a socket");
95
+ return;
438
96
  }
439
- });
440
- const providerLifecycleReceipt = {
441
- ...providerReceipt,
442
- migrationStatus,
443
- ...(sandboxEnvironments ? { sandboxEnvironments } : {}),
444
- ...(Object.keys(lifecycle).length > 0 ? { lifecycle } : {})
445
- };
446
- const receipt = withReleaseSnapshot(providerLifecycleReceipt, releaseSnapshotForReceipt(release, providerLifecycleReceipt, !prepareOnly));
447
- writeDeployReceipt(artifact.root, plan, receipt);
448
- if (!prepareOnly)
449
- await wireDeployedConnectionEvents(artifact.manifest, receipt);
450
- console.log(`${deployTargetLabel(plan.target)} deploy ${prepareOnly ? "prepared" : "completed"}.`);
451
- if (manifestUsesModelProvider(artifact.manifest, "openai-codex")) {
452
- console.log(`The release is ready for provider login. Run \`assembly-line auth openai-codex <agentRoot> --target ${plan.target} --env ${plan.environment}${prepareOnly ? " --prepared" : ""}\`; OAuth tokens never appear in command arguments or deployment receipts.`);
453
- }
454
- console.log(JSON.stringify(receipt, null, 2));
455
- }
456
- async function deployActionWithCleanup(publisher, artifact, plan, action) {
457
- try {
458
- const receipt = await action();
459
- const cleanup = await executeDeployCleanup({ publisher, artifact, plan });
460
- return cleanup ? { ...receipt, cleanup } : receipt;
461
- }
462
- catch (error) {
463
- await executeDeployCleanup({ publisher, artifact, plan });
464
- throw error;
465
- }
466
- }
467
- async function wireDeployedConnectionEvents(manifest, receipt) {
468
- if (!manifest.connections.some((connection) => connection.events?.enabled))
469
- return;
470
- const deploymentUrl = receiptField(receipt, "deploymentUrl");
471
- if (!deploymentUrl) {
472
- console.warn("Connection events were not wired because the deploy target did not return a public deployment URL. Run `assembly-line connections wire <agentRoot> --url <publicUrl>` after ingress is available.");
473
- return;
474
- }
475
- const result = await wireConnectionEvents({
476
- baseUrl: deploymentUrl,
477
- ...(process.env.ASSEMBLY_LINE_ADMIN_TOKEN ? { token: process.env.ASSEMBLY_LINE_ADMIN_TOKEN } : {}),
478
- force: false
479
- });
480
- console.log("Connection event subscriptions reconciled.");
481
- console.log(JSON.stringify(result, null, 2));
482
- }
483
- function receiptField(receipt, name) {
484
- if (!receipt || typeof receipt !== "object" || Array.isArray(receipt))
485
- return undefined;
486
- const value = receipt[name];
487
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
488
- }
489
- // deployEnv is the store-overlaid environment: sandbox provisioning env and
490
- // passthrough projections must see secrets held only in the secret store.
491
- function sandboxEnvironmentReconciler(artifact, deployEnv, reusableResolutions) {
492
- const definition = artifact.manifest.gateway.sandbox;
493
- if (!definition || definition.kind === "local")
494
- return undefined;
495
- const sandboxes = artifact.manifest.sandboxes.filter((candidate) => candidate.adapter === definition.kind && candidate.environment);
496
- if (sandboxes.length === 0)
497
- return undefined;
498
- if (reusableResolutions)
499
- return async () => reusableResolutions;
500
- return async () => {
501
- const provider = await resolveProvider("sandbox", definition, {
97
+ const handle = await listenNodeRuntime({
98
+ ...runtimeOptions,
99
+ state: new FileStateAdapter(resolve(artifact.root, "deploy-state.json")),
100
+ blob: new LocalBlobAdapter(resolve(artifact.root, "blobs")),
101
+ sandbox: new LocalSandboxAdapter(resolve(artifact.root, "sandbox")),
502
102
  env: deployEnv,
503
- fetch,
504
- manifest: artifact.manifest,
505
- artifactRoot: artifact.root,
506
- providerRoot: fileURLToPath(new URL("..", import.meta.url)),
507
- devMode: false
103
+ devMode: true,
104
+ port: numberFlag(parsed, "port") ?? 0
508
105
  });
509
- if (!provider.ensureEnvironment) {
510
- throw new Error(`Sandbox provider ${definition.kind} cannot reconcile compiled environments.`);
511
- }
512
- const resolutions = {};
513
- for (const sandbox of sandboxes) {
514
- const environment = sandbox.environment;
515
- if (!environment)
516
- throw new Error(`Sandbox profile ${sandbox.name} has no compiled environment.`);
517
- const passthroughEnv = sandboxPassthroughEnv(deployEnv, sandbox.env);
518
- resolutions[sandbox.name] = await provider.ensureEnvironment({
519
- agentId: artifact.manifest.agent.id ?? artifact.manifest.agent.name ?? "agent",
520
- sandboxName: sandbox.name,
521
- adapter: definition.kind,
522
- artifactRoot: artifact.root,
523
- environment,
524
- ...(Object.keys(passthroughEnv).length > 0 ? { env: passthroughEnv } : {})
525
- });
526
- }
527
- return resolutions;
528
- };
529
- }
530
- /**
531
- * Reuse a sandbox artifact proven by a successful receipt when its provider and
532
- * content fingerprint are unchanged. Runtime credentials stay on the host;
533
- * only the provider-safe artifact reference crosses deploys.
534
- */
535
- export function reusableSandboxEnvironmentResolutions(artifact, environmentName) {
536
- const definition = artifact.manifest.gateway.sandbox;
537
- if (!definition || definition.kind === "local")
538
- return undefined;
539
- const sandboxes = artifact.manifest.sandboxes.filter((candidate) => candidate.adapter === definition.kind && candidate.environment);
540
- if (sandboxes.length === 0)
541
- return undefined;
542
- const candidates = [
543
- environmentReceiptPath(artifact.root, environmentName),
544
- resolve(artifact.root, "deployment.json")
545
- ];
546
- for (const path of candidates) {
547
- try {
548
- const receipt = JSON.parse(readFileSync(path, "utf8"));
549
- if (receipt.status !== "published" || receipt.environment !== environmentName)
550
- continue;
551
- const stored = receipt.sandboxEnvironments;
552
- if (!stored || typeof stored !== "object" || Array.isArray(stored))
553
- continue;
554
- const resolutions = {};
555
- let matches = true;
556
- for (const sandbox of sandboxes) {
557
- const fingerprint = sandbox.environment?.fingerprint;
558
- const resolution = stored[sandbox.name];
559
- if (!fingerprint
560
- || !isSandboxEnvironmentResolution(resolution)
561
- || resolution.provider !== definition.kind
562
- || resolution.fingerprint !== fingerprint) {
563
- matches = false;
564
- break;
565
- }
566
- resolutions[sandbox.name] = { ...resolution, action: "reused" };
567
- }
568
- if (matches)
569
- return resolutions;
570
- }
571
- catch {
572
- // Missing, stale, or malformed receipts are not reuse evidence.
573
- }
574
- }
575
- return undefined;
576
- }
577
- function isSandboxEnvironmentResolution(value) {
578
- if (!value || typeof value !== "object" || Array.isArray(value))
579
- return false;
580
- const record = value;
581
- return ["provider", "artifactKind", "fingerprint", "artifactId", "runtimeReference"]
582
- .every((key) => typeof record[key] === "string" && record[key].length > 0)
583
- && ["reused", "built", "external"].includes(String(record.action));
584
- }
585
- export function sandboxPassthroughEnv(env, names) {
586
- const values = {};
587
- for (const name of names) {
588
- const value = env[name];
589
- if (value !== undefined)
590
- values[name] = value;
591
- }
592
- const sensitive = Object.keys(values).filter(isSensitiveSandboxEnvName);
593
- if (sensitive.length > 0) {
594
- throw new Error(`Refusing host/control-plane secrets in sandbox profile env: ${sensitive.join(", ")}.`);
595
- }
596
- return values;
597
- }
598
- function deployTargetLabel(target) {
599
- if (target === "railway")
600
- return "Railway";
601
- if (target === "docker")
602
- return "Docker";
603
- if (target === "fly")
604
- return "Fly";
605
- if (target === "vps")
606
- return "VPS";
607
- return target;
608
- }
609
- function stringOption(definition, name) {
610
- const value = definition.options?.[name];
611
- return typeof value === "string" ? value : undefined;
612
- }
613
- function configuredMigrationRequest(artifact, parsed) {
614
- const entries = migrationEntriesForArtifact(artifact);
615
- const command = stringFlag(parsed, "migration-command") ?? process.env.ASSEMBLY_LINE_MIGRATION_COMMAND;
616
- if (entries.length > 0 && !command) {
617
- throw new Error("Migration files are present, but no migration runner was configured. Pass --migration-command or set ASSEMBLY_LINE_MIGRATION_COMMAND.");
618
- }
619
- return {
620
- entries,
621
- ...(command ? { command } : {})
622
- };
623
- }
624
- async function runConfiguredMigrations(artifact, plan, request) {
625
- const entries = request.entries;
626
- if (entries.length === 0)
627
- return "none";
628
- const commandName = request.command;
629
- console.log(`Running ${entries.length} migration file(s) with ${commandName}.`);
630
- const result = await runCommand(commandName, [], {
631
- cwd: artifact.root,
632
- env: {
633
- ...process.env,
634
- ASSEMBLY_LINE_ARTIFACT_ROOT: artifact.root,
635
- ASSEMBLY_LINE_AGENT_REVISION: plan.agentRevision,
636
- ASSEMBLY_LINE_DEPLOY_ENV: plan.environment,
637
- ASSEMBLY_LINE_MIGRATION_FILES: JSON.stringify(entries)
638
- }
639
- });
640
- if (result.exitCode !== 0) {
641
- throw new Error(`Migration command failed with exit code ${result.exitCode}.`);
106
+ writeDeployReceipt(artifact.root, plan, localDeployReceipt(artifact, plan, "serving", handle.url));
107
+ console.log(`Local deploy serving revision ${plan.agentRevision}`);
108
+ console.log(handle.url);
109
+ installSignalHandlers(handle);
110
+ await handle.closed;
111
+ return;
642
112
  }
643
- return `ran:${entries.length}`;
644
- }
645
- function migrationEntriesForArtifact(artifact) {
646
- const migrationsDir = resolve(artifact.root, "migrations");
647
- if (!existsSync(migrationsDir))
648
- return [];
649
- return readdirSync(migrationsDir)
650
- .filter((entry) => entry !== "README.md" && !entry.startsWith("."))
651
- .sort();
652
- }
653
- export function runCommand(commandName, args, options = {}) {
654
- return new Promise((resolvePromise, reject) => {
655
- const child = spawn(commandName, args, {
656
- cwd: options.cwd ?? process.cwd(),
657
- env: options.env ?? process.env,
658
- stdio: ["ignore", "pipe", "pipe"]
659
- });
660
- let stdout = "";
661
- let stderr = "";
662
- child.stdout.on("data", (chunk) => {
663
- const text = chunk.toString("utf8");
664
- stdout += text;
665
- if (!options.silent)
666
- process.stdout.write(text);
667
- });
668
- child.stderr.on("data", (chunk) => {
669
- const text = chunk.toString("utf8");
670
- stderr += text;
671
- if (!options.silent)
672
- process.stderr.write(text);
673
- });
674
- child.on("error", (error) => {
675
- if (error.code === "ENOENT") {
676
- reject(new Error(`${commandName} not found. Install it, or pass the matching --*-bin flag with the executable path.`));
677
- return;
678
- }
679
- reject(error);
680
- });
681
- child.on("close", (code) => {
682
- resolvePromise({ exitCode: code ?? 1, stdout, stderr });
683
- });
684
- });
685
- }
686
- function localDeployReceipt(artifact, plan, status, deploymentUrl) {
687
- return {
688
- target: "local",
689
- status,
690
- environment: plan.environment,
691
- agentRevision: plan.agentRevision,
692
- buildRevision: plan.buildRevision ?? plan.agentRevision,
693
- artifactRoot: artifact.root,
694
- deploymentUrl,
695
- completedAt: new Date().toISOString()
696
- };
697
- }
698
- export function writeJson(path, value) {
699
- mkdirSync(dirname(path), { recursive: true });
700
- writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
113
+ writeDeployReceipt(artifact.root, plan, localDeployReceipt(artifact, plan, "prepared", null));
114
+ console.log(`Local deploy prepared for revision ${plan.agentRevision}. Run assembly-line serve to keep it online.`);
701
115
  }
702
116
  //# sourceMappingURL=deploy.js.map