@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.
- package/authoring/skills/assembly-line-authoring/SKILL.md +24 -21
- package/dist/add.d.ts +2 -13
- package/dist/add.d.ts.map +1 -1
- package/dist/add.js +169 -478
- package/dist/add.js.map +1 -1
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +1 -2
- package/dist/args.js.map +1 -1
- package/dist/auth.d.ts +1 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +38 -45
- package/dist/auth.js.map +1 -1
- package/dist/channels.d.ts +7 -30
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +21 -161
- package/dist/channels.js.map +1 -1
- package/dist/deploy-definition.d.ts +1 -8
- package/dist/deploy-definition.d.ts.map +1 -1
- package/dist/deploy-definition.js +18 -66
- package/dist/deploy-definition.js.map +1 -1
- package/dist/deploy-migrations.d.ts +15 -0
- package/dist/deploy-migrations.d.ts.map +1 -0
- package/dist/deploy-migrations.js +72 -0
- package/dist/deploy-migrations.js.map +1 -0
- package/dist/deploy-preflight.d.ts +6 -0
- package/dist/deploy-preflight.d.ts.map +1 -0
- package/dist/deploy-preflight.js +40 -0
- package/dist/deploy-preflight.js.map +1 -0
- package/dist/deploy-receipts.d.ts +9 -0
- package/dist/deploy-receipts.d.ts.map +1 -0
- package/dist/deploy-receipts.js +45 -0
- package/dist/deploy-receipts.js.map +1 -0
- package/dist/deploy-sandbox-environments.d.ts +6 -0
- package/dist/deploy-sandbox-environments.d.ts.map +1 -0
- package/dist/deploy-sandbox-environments.js +109 -0
- package/dist/deploy-sandbox-environments.js.map +1 -0
- package/dist/deploy-secrets.d.ts +21 -0
- package/dist/deploy-secrets.d.ts.map +1 -0
- package/dist/deploy-secrets.js +83 -0
- package/dist/deploy-secrets.js.map +1 -0
- package/dist/deploy.d.ts +5 -31
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +50 -636
- package/dist/deploy.js.map +1 -1
- package/dist/eval-case-runner.d.ts +1 -1
- package/dist/eval-case-runner.d.ts.map +1 -1
- package/dist/eval-case-runner.js.map +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +41 -89
- package/dist/help.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +89 -32
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +1 -3
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +36 -39
- package/dist/init.js.map +1 -1
- package/dist/inspection.d.ts +44 -0
- package/dist/inspection.d.ts.map +1 -0
- package/dist/inspection.js +144 -0
- package/dist/inspection.js.map +1 -0
- package/dist/models.d.ts +2 -1
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +7 -13
- package/dist/models.js.map +1 -1
- package/dist/plugin-add.d.ts +36 -0
- package/dist/plugin-add.d.ts.map +1 -0
- package/dist/plugin-add.js +283 -0
- package/dist/plugin-add.js.map +1 -0
- package/dist/plugin-init.d.ts +2 -0
- package/dist/plugin-init.d.ts.map +1 -0
- package/dist/plugin-init.js +34 -0
- package/dist/plugin-init.js.map +1 -0
- package/dist/provider-deploy.d.ts +7 -0
- package/dist/provider-deploy.d.ts.map +1 -0
- package/dist/provider-deploy.js +163 -0
- package/dist/provider-deploy.js.map +1 -0
- package/dist/secrets.d.ts.map +1 -1
- package/dist/secrets.js +8 -28
- package/dist/secrets.js.map +1 -1
- package/package.json +9 -11
- package/dist/hosts.d.ts +0 -3
- package/dist/hosts.d.ts.map +0 -1
- package/dist/hosts.js +0 -60
- package/dist/hosts.js.map +0 -1
- package/dist/model-providers.d.ts +0 -5
- package/dist/model-providers.d.ts.map +0 -1
- package/dist/model-providers.js +0 -17
- package/dist/model-providers.js.map +0 -1
- package/dist/state.d.ts +0 -3
- package/dist/state.d.ts.map +0 -1
- package/dist/state.js +0 -79
- package/dist/state.js.map +0 -1
package/dist/deploy.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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 {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
360
|
-
|
|
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(
|
|
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, "
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
|
|
504
|
-
|
|
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
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
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
|