@mastra/deployer-sandbox 0.1.2 → 0.1.3

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/dist/index.cjs CHANGED
@@ -1,292 +1,305 @@
1
- 'use strict';
2
-
3
- var chunkCSXC6CZL_cjs = require('./chunk-CSXC6CZL.cjs');
4
- var promises = require('fs/promises');
5
- var path = require('path');
6
- var url = require('url');
7
- var deployer = require('@mastra/deployer');
8
- var esm = require('fs-extra/esm');
9
- var child_process = require('child_process');
10
- var crypto = require('crypto');
11
- var fs = require('fs');
12
- var os = require('os');
13
- var util = require('util');
14
- var workspace = require('@mastra/core/workspace');
15
-
16
- var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
17
- // src/alias.ts
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_shared = require("./shared-H9GioaPM.cjs");
3
+ let fs_promises = require("fs/promises");
4
+ let path = require("path");
5
+ let url = require("url");
6
+ let _mastra_deployer = require("@mastra/deployer");
7
+ let fs_extra_esm = require("fs-extra/esm");
8
+ let child_process = require("child_process");
9
+ let crypto = require("crypto");
10
+ let fs = require("fs");
11
+ let os = require("os");
12
+ let util = require("util");
13
+ let _mastra_core_workspace = require("@mastra/core/workspace");
14
+ //#region src/alias.ts
15
+ /**
16
+ * Upsert a Vercel Edge Config item so a stable key always points at the
17
+ * current sandbox URL. Used for Tier 3 routing: apps read the key from Edge
18
+ * Config (e.g. in middleware) instead of hardcoding the rotating sandbox URL.
19
+ */
18
20
  async function updateEdgeConfigAlias(options) {
19
- const { token, teamId } = options;
20
- if (!token) {
21
- throw new Error("Updating the Edge Config alias requires a Vercel API token. Pass `alias.token`.");
22
- }
23
- const endpoint = new URL(`https://api.vercel.com/v1/edge-config/${options.edgeConfigId}/items`);
24
- if (teamId) {
25
- endpoint.searchParams.set("teamId", teamId);
26
- }
27
- const res = await fetch(endpoint, {
28
- method: "PATCH",
29
- headers: {
30
- Authorization: `Bearer ${token}`,
31
- "Content-Type": "application/json"
32
- },
33
- body: JSON.stringify({
34
- items: [{ operation: "upsert", key: options.key, value: options.url }]
35
- }),
36
- // Bounded so a hung Vercel API request can't keep `mastra build` open
37
- // after the sandbox itself is already deployed.
38
- signal: AbortSignal.timeout(3e4)
39
- });
40
- if (!res.ok) {
41
- const body = await res.text().catch(() => "");
42
- throw new Error(`Failed to update Edge Config alias "${options.key}" (${res.status}): ${body}`);
43
- }
21
+ const { token, teamId } = options;
22
+ if (!token) throw new Error("Updating the Edge Config alias requires a Vercel API token. Pass `alias.token`.");
23
+ const endpoint = new URL(`https://api.vercel.com/v1/edge-config/${options.edgeConfigId}/items`);
24
+ if (teamId) endpoint.searchParams.set("teamId", teamId);
25
+ const res = await fetch(endpoint, {
26
+ method: "PATCH",
27
+ headers: {
28
+ Authorization: `Bearer ${token}`,
29
+ "Content-Type": "application/json"
30
+ },
31
+ body: JSON.stringify({ items: [{
32
+ operation: "upsert",
33
+ key: options.key,
34
+ value: options.url
35
+ }] }),
36
+ signal: AbortSignal.timeout(3e4)
37
+ });
38
+ if (!res.ok) {
39
+ const body = await res.text().catch(() => "");
40
+ throw new Error(`Failed to update Edge Config alias "${options.key}" (${res.status}): ${body}`);
41
+ }
44
42
  }
45
- var execFileAsync = util.promisify(child_process.execFile);
46
- var noopLogger = {
47
- debug: () => {
48
- },
49
- info: () => {
50
- },
51
- warn: () => {
52
- },
53
- error: () => {
54
- }
43
+ //#endregion
44
+ //#region src/engine.ts
45
+ const execFileAsync = (0, util.promisify)(child_process.execFile);
46
+ const noopLogger = {
47
+ debug: () => {},
48
+ info: () => {},
49
+ warn: () => {},
50
+ error: () => {}
55
51
  };
56
- var UPLOAD_CHUNK_SIZE = 96e3;
52
+ /** Max shell-command payload per chunk for the base64 upload fallback. */
53
+ const UPLOAD_CHUNK_SIZE = 96e3;
54
+ /**
55
+ * Deploy a prebuilt Mastra server directory into any workspace sandbox that
56
+ * supports networking. Provider-agnostic: only uses the core WorkspaceSandbox
57
+ * contract (`executeCommand` + `networking`, with `writeFiles` / `processes`
58
+ * as fast paths).
59
+ */
57
60
  async function deployToSandbox(options) {
58
- const {
59
- sandbox,
60
- dir,
61
- port = chunkCSXC6CZL_cjs.DEFAULT_PORT,
62
- env = {},
63
- studio = false,
64
- healthCheckPath = "/api",
65
- healthCheckTimeoutMs = 6e4,
66
- healthCheckIntervalMs = 1e3,
67
- installCommand = "npm install --omit=dev",
68
- logger = noopLogger
69
- } = options;
70
- if (!fs.existsSync(path.join(dir, "index.mjs"))) {
71
- throw new Error(`No index.mjs found in "${dir}" \u2014 did the build succeed?`);
72
- }
73
- logger.info(`Starting ${sandbox.provider} sandbox...`);
74
- await sandbox.start?.();
75
- if (!workspace.supportsNetworking(sandbox)) {
76
- throw new Error(
77
- `Sandbox provider "${sandbox.provider}" does not support networking (public port URLs), which is required for sandbox deploys.`
78
- );
79
- }
80
- if (!sandbox.executeCommand) {
81
- throw new Error(
82
- `Sandbox provider "${sandbox.provider}" does not support executeCommand, which is required for sandbox deploys.`
83
- );
84
- }
85
- const url = await sandbox.networking.getPortUrl(port);
86
- if (!url) {
87
- throw new Error(
88
- `Sandbox provider "${sandbox.provider}" did not expose a public URL for port ${port}. Make sure the port is declared when constructing the sandbox (e.g. \`ports: [${port}]\`).`
89
- );
90
- }
91
- const remoteDir = await chunkCSXC6CZL_cjs.resolveRemoteDir(sandbox, options.remoteDir);
92
- const mergedEnv = { ...env };
93
- if (studio && mergedEnv.MASTRA_STUDIO_PATH === void 0) {
94
- mergedEnv.MASTRA_STUDIO_PATH = `${remoteDir}/studio`;
95
- }
96
- logger.info(`Uploading build output from ${dir}...`);
97
- const tarball = await createTarball(dir);
98
- logger.debug(`Tarball size: ${(tarball.length / 1024 / 1024).toFixed(2)} MB`);
99
- const remoteTarball = `${remoteDir}/.deploy.tgz`;
100
- await chunkCSXC6CZL_cjs.runInSandbox(sandbox, `mkdir -p ${chunkCSXC6CZL_cjs.shellQuote(remoteDir)}`);
101
- await uploadFile(sandbox, remoteTarball, tarball);
102
- await chunkCSXC6CZL_cjs.killPreviousServer(sandbox, remoteDir);
103
- await chunkCSXC6CZL_cjs.runInSandbox(sandbox, `cd ${chunkCSXC6CZL_cjs.shellQuote(remoteDir)} && tar -xzf .deploy.tgz && rm -f .deploy.tgz`, {
104
- timeout: 12e4
105
- });
106
- const installHash = await hashInstallInputs(dir, installCommand);
107
- const marker = `${remoteDir}/${chunkCSXC6CZL_cjs.INSTALL_MARKER}`;
108
- const markerCheck = await chunkCSXC6CZL_cjs.runInSandbox(sandbox, `cat ${chunkCSXC6CZL_cjs.shellQuote(marker)} 2>/dev/null || true`, {
109
- allowFailure: true
110
- });
111
- if (installHash && markerCheck.stdout.trim() === installHash) {
112
- logger.info("Dependencies unchanged \u2014 skipping install.");
113
- } else {
114
- logger.info(`Installing dependencies (${installCommand})...`);
115
- await chunkCSXC6CZL_cjs.runInSandbox(sandbox, `cd ${chunkCSXC6CZL_cjs.shellQuote(remoteDir)} && ${installCommand}`, {
116
- timeout: 6e5,
117
- label: `install dependencies (${installCommand})`
118
- });
119
- if (installHash) {
120
- await chunkCSXC6CZL_cjs.runInSandbox(sandbox, `printf '%s' ${chunkCSXC6CZL_cjs.shellQuote(installHash)} > ${chunkCSXC6CZL_cjs.shellQuote(marker)}`);
121
- }
122
- }
123
- const launchScript = buildLaunchScript({ remoteDir, port, env: mergedEnv });
124
- await uploadFile(sandbox, `${remoteDir}/${chunkCSXC6CZL_cjs.SERVER_SCRIPT}`, Buffer.from(launchScript));
125
- await chunkCSXC6CZL_cjs.runInSandbox(sandbox, `chmod 700 ${chunkCSXC6CZL_cjs.shellQuote(`${remoteDir}/${chunkCSXC6CZL_cjs.SERVER_SCRIPT}`)}`);
126
- logger.info("Starting Mastra server...");
127
- await chunkCSXC6CZL_cjs.launchServer(sandbox, remoteDir);
128
- const healthy = await chunkCSXC6CZL_cjs.waitForHealthy(url, {
129
- path: healthCheckPath,
130
- timeoutMs: healthCheckTimeoutMs,
131
- intervalMs: healthCheckIntervalMs
132
- });
133
- if (!healthy) {
134
- const log = await chunkCSXC6CZL_cjs.tailServerLog(sandbox, remoteDir).catch(() => "");
135
- throw new Error(
136
- `Mastra server did not become healthy at ${url}${healthCheckPath} within ${healthCheckTimeoutMs}ms.` + (log ? `
137
-
138
- Server log:
139
- ${log}` : "\n\n(no server log output captured)")
140
- );
141
- }
142
- const info = await chunkCSXC6CZL_cjs.getInfoSafe(sandbox);
143
- return {
144
- url,
145
- sandboxId: info?.id ?? sandbox.id,
146
- expiresAt: info?.timeoutAt,
147
- stop: async () => {
148
- await sandbox.stop?.();
149
- },
150
- destroy: async () => {
151
- await sandbox.destroy?.();
152
- },
153
- logs: (lines) => chunkCSXC6CZL_cjs.tailServerLog(sandbox, remoteDir, lines)
154
- };
61
+ const { sandbox, dir, port = require_shared.DEFAULT_PORT, env = {}, studio = false, healthCheckPath = "/api", healthCheckTimeoutMs = 6e4, healthCheckIntervalMs = 1e3, installCommand = "npm install --omit=dev", logger = noopLogger } = options;
62
+ if (!(0, fs.existsSync)((0, path.join)(dir, "index.mjs"))) throw new Error(`No index.mjs found in "${dir}" — did the build succeed?`);
63
+ logger.info(`Starting ${sandbox.provider} sandbox...`);
64
+ await sandbox.start?.();
65
+ if (!(0, _mastra_core_workspace.supportsNetworking)(sandbox)) throw new Error(`Sandbox provider "${sandbox.provider}" does not support networking (public port URLs), which is required for sandbox deploys.`);
66
+ if (!sandbox.executeCommand) throw new Error(`Sandbox provider "${sandbox.provider}" does not support executeCommand, which is required for sandbox deploys.`);
67
+ const url = await sandbox.networking.getPortUrl(port);
68
+ if (!url) throw new Error(`Sandbox provider "${sandbox.provider}" did not expose a public URL for port ${port}. Make sure the port is declared when constructing the sandbox (e.g. \`ports: [${port}]\`).`);
69
+ const remoteDir = await require_shared.resolveRemoteDir(sandbox, options.remoteDir);
70
+ const mergedEnv = { ...env };
71
+ if (studio && mergedEnv.MASTRA_STUDIO_PATH === void 0) mergedEnv.MASTRA_STUDIO_PATH = `${remoteDir}/studio`;
72
+ logger.info(`Uploading build output from ${dir}...`);
73
+ const tarball = await createTarball(dir);
74
+ logger.debug(`Tarball size: ${(tarball.length / 1024 / 1024).toFixed(2)} MB`);
75
+ const remoteTarball = `${remoteDir}/.deploy.tgz`;
76
+ await require_shared.runInSandbox(sandbox, `mkdir -p ${require_shared.shellQuote(remoteDir)}`);
77
+ await uploadFile(sandbox, remoteTarball, tarball);
78
+ await require_shared.killPreviousServer(sandbox, remoteDir);
79
+ await require_shared.runInSandbox(sandbox, `cd ${require_shared.shellQuote(remoteDir)} && tar -xzf .deploy.tgz && rm -f .deploy.tgz`, { timeout: 12e4 });
80
+ const installHash = await hashInstallInputs(dir, installCommand);
81
+ const marker = `${remoteDir}/${require_shared.INSTALL_MARKER}`;
82
+ const markerCheck = await require_shared.runInSandbox(sandbox, `cat ${require_shared.shellQuote(marker)} 2>/dev/null || true`, { allowFailure: true });
83
+ if (installHash && markerCheck.stdout.trim() === installHash) logger.info("Dependencies unchanged — skipping install.");
84
+ else {
85
+ logger.info(`Installing dependencies (${installCommand})...`);
86
+ await require_shared.runInSandbox(sandbox, `cd ${require_shared.shellQuote(remoteDir)} && ${installCommand}`, {
87
+ timeout: 6e5,
88
+ label: `install dependencies (${installCommand})`
89
+ });
90
+ if (installHash) await require_shared.runInSandbox(sandbox, `printf '%s' ${require_shared.shellQuote(installHash)} > ${require_shared.shellQuote(marker)}`);
91
+ }
92
+ const launchScript = buildLaunchScript({
93
+ remoteDir,
94
+ port,
95
+ env: mergedEnv
96
+ });
97
+ await uploadFile(sandbox, `${remoteDir}/${require_shared.SERVER_SCRIPT}`, Buffer.from(launchScript));
98
+ await require_shared.runInSandbox(sandbox, `chmod 700 ${require_shared.shellQuote(`${remoteDir}/${require_shared.SERVER_SCRIPT}`)}`);
99
+ logger.info("Starting Mastra server...");
100
+ await require_shared.launchServer(sandbox, remoteDir);
101
+ if (!await require_shared.waitForHealthy(url, {
102
+ path: healthCheckPath,
103
+ timeoutMs: healthCheckTimeoutMs,
104
+ intervalMs: healthCheckIntervalMs
105
+ })) {
106
+ const log = await require_shared.tailServerLog(sandbox, remoteDir).catch(() => "");
107
+ throw new Error(`Mastra server did not become healthy at ${url}${healthCheckPath} within ${healthCheckTimeoutMs}ms.` + (log ? `\n\nServer log:\n${log}` : "\n\n(no server log output captured)"));
108
+ }
109
+ const info = await require_shared.getInfoSafe(sandbox);
110
+ return {
111
+ url,
112
+ sandboxId: info?.id ?? sandbox.id,
113
+ expiresAt: info?.timeoutAt,
114
+ stop: async () => {
115
+ await sandbox.stop?.();
116
+ },
117
+ destroy: async () => {
118
+ await sandbox.destroy?.();
119
+ },
120
+ logs: (lines) => require_shared.tailServerLog(sandbox, remoteDir, lines)
121
+ };
155
122
  }
123
+ /**
124
+ * Build the POSIX launch script. Re-running the script restarts the server —
125
+ * the wake path uses this after a snapshot resume (which restores the
126
+ * filesystem but not processes).
127
+ */
156
128
  function buildLaunchScript(opts) {
157
- const lines = ["#!/bin/sh", `cd ${chunkCSXC6CZL_cjs.shellQuote(opts.remoteDir)}`];
158
- const env = {
159
- MASTRA_AUTO_DETECT_URL: "true",
160
- ...opts.env,
161
- PORT: String(opts.port),
162
- MASTRA_HOST: "0.0.0.0"
163
- };
164
- for (const [key, value] of Object.entries(env)) {
165
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
166
- throw new Error(`Invalid environment variable name: "${key}"`);
167
- }
168
- lines.push(`export ${key}=${chunkCSXC6CZL_cjs.shellQuote(value)}`);
169
- }
170
- lines.push(`echo $$ > ${chunkCSXC6CZL_cjs.shellQuote(chunkCSXC6CZL_cjs.SERVER_PIDFILE)}`);
171
- lines.push(`exec node index.mjs >> ${chunkCSXC6CZL_cjs.shellQuote(chunkCSXC6CZL_cjs.SERVER_LOGFILE)} 2>&1`);
172
- return lines.join("\n") + "\n";
129
+ const lines = ["#!/bin/sh", `cd ${require_shared.shellQuote(opts.remoteDir)}`];
130
+ const env = {
131
+ MASTRA_AUTO_DETECT_URL: "true",
132
+ ...opts.env,
133
+ PORT: String(opts.port),
134
+ MASTRA_HOST: "0.0.0.0"
135
+ };
136
+ for (const [key, value] of Object.entries(env)) {
137
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`Invalid environment variable name: "${key}"`);
138
+ lines.push(`export ${key}=${require_shared.shellQuote(value)}`);
139
+ }
140
+ lines.push(`echo $$ > ${require_shared.shellQuote(require_shared.SERVER_PIDFILE)}`);
141
+ lines.push(`exec node index.mjs >> ${require_shared.shellQuote(require_shared.SERVER_LOGFILE)} 2>&1`);
142
+ return lines.join("\n") + "\n";
173
143
  }
144
+ /** Create a gzipped tarball of the directory contents (excluding node_modules). */
174
145
  async function createTarball(dir) {
175
- const tmp = await promises.mkdtemp(path.join(os.tmpdir(), "mastra-sandbox-"));
176
- const tarPath = path.join(tmp, "deploy.tgz");
177
- try {
178
- await execFileAsync("tar", ["-czf", tarPath, "--exclude=node_modules", "-C", dir, "."]);
179
- return await promises.readFile(tarPath);
180
- } finally {
181
- await promises.rm(tmp, { recursive: true, force: true });
182
- }
146
+ const tmp = await (0, fs_promises.mkdtemp)((0, path.join)((0, os.tmpdir)(), "mastra-sandbox-"));
147
+ const tarPath = (0, path.join)(tmp, "deploy.tgz");
148
+ try {
149
+ await execFileAsync("tar", [
150
+ "-czf",
151
+ tarPath,
152
+ "--exclude=node_modules",
153
+ "-C",
154
+ dir,
155
+ "."
156
+ ]);
157
+ return await (0, fs_promises.readFile)(tarPath);
158
+ } finally {
159
+ await (0, fs_promises.rm)(tmp, {
160
+ recursive: true,
161
+ force: true
162
+ });
163
+ }
183
164
  }
165
+ /**
166
+ * Upload a file into the sandbox. Uses the provider's native `writeFiles` fast
167
+ * path when available, otherwise falls back to base64 chunks over
168
+ * `executeCommand` — so `executeCommand` + `networking` is the minimum contract.
169
+ */
184
170
  async function uploadFile(sandbox, remotePath, content) {
185
- if (sandbox.writeFiles) {
186
- await sandbox.writeFiles([{ path: remotePath, content }]);
187
- return;
188
- }
189
- const b64 = content.toString("base64");
190
- const tmpPath = `${remotePath}.b64`;
191
- await chunkCSXC6CZL_cjs.runInSandbox(sandbox, `rm -f ${chunkCSXC6CZL_cjs.shellQuote(tmpPath)}`);
192
- for (let i = 0; i < b64.length; i += UPLOAD_CHUNK_SIZE) {
193
- const chunk = b64.slice(i, i + UPLOAD_CHUNK_SIZE);
194
- await chunkCSXC6CZL_cjs.runInSandbox(sandbox, `printf '%s' ${chunkCSXC6CZL_cjs.shellQuote(chunk)} >> ${chunkCSXC6CZL_cjs.shellQuote(tmpPath)}`, {
195
- label: `upload chunk to ${remotePath}`
196
- });
197
- }
198
- await chunkCSXC6CZL_cjs.runInSandbox(
199
- sandbox,
200
- `base64 -d ${chunkCSXC6CZL_cjs.shellQuote(tmpPath)} > ${chunkCSXC6CZL_cjs.shellQuote(remotePath)} && rm -f ${chunkCSXC6CZL_cjs.shellQuote(tmpPath)}`,
201
- { label: `decode upload at ${remotePath}` }
202
- );
171
+ if (sandbox.writeFiles) {
172
+ await sandbox.writeFiles([{
173
+ path: remotePath,
174
+ content
175
+ }]);
176
+ return;
177
+ }
178
+ const b64 = content.toString("base64");
179
+ const tmpPath = `${remotePath}.b64`;
180
+ await require_shared.runInSandbox(sandbox, `rm -f ${require_shared.shellQuote(tmpPath)}`);
181
+ for (let i = 0; i < b64.length; i += UPLOAD_CHUNK_SIZE) await require_shared.runInSandbox(sandbox, `printf '%s' ${require_shared.shellQuote(b64.slice(i, i + UPLOAD_CHUNK_SIZE))} >> ${require_shared.shellQuote(tmpPath)}`, { label: `upload chunk to ${remotePath}` });
182
+ await require_shared.runInSandbox(sandbox, `base64 -d ${require_shared.shellQuote(tmpPath)} > ${require_shared.shellQuote(remotePath)} && rm -f ${require_shared.shellQuote(tmpPath)}`, { label: `decode upload at ${remotePath}` });
203
183
  }
204
- var LOCKFILES = ["package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock"];
184
+ /** Lockfiles that, when present in the build output, participate in the install-skip hash. */
185
+ const LOCKFILES = [
186
+ "package-lock.json",
187
+ "npm-shrinkwrap.json",
188
+ "pnpm-lock.yaml",
189
+ "yarn.lock",
190
+ "bun.lock"
191
+ ];
192
+ /**
193
+ * Hash everything that determines the outcome of a dependency install:
194
+ * package.json, any bundled lockfile, and the install command itself. A
195
+ * matching hash means the previous `node_modules` can be reused.
196
+ */
205
197
  async function hashInstallInputs(dir, installCommand) {
206
- const hash = crypto.createHash("sha256");
207
- try {
208
- hash.update(await promises.readFile(path.join(dir, "package.json")));
209
- } catch {
210
- return null;
211
- }
212
- for (const lockfile of LOCKFILES) {
213
- let content;
214
- try {
215
- content = await promises.readFile(path.join(dir, lockfile));
216
- } catch {
217
- continue;
218
- }
219
- hash.update(lockfile).update(content);
220
- }
221
- hash.update(installCommand);
222
- return hash.digest("hex");
198
+ const hash = (0, crypto.createHash)("sha256");
199
+ try {
200
+ hash.update(await (0, fs_promises.readFile)((0, path.join)(dir, "package.json")));
201
+ } catch {
202
+ return null;
203
+ }
204
+ for (const lockfile of LOCKFILES) {
205
+ let content;
206
+ try {
207
+ content = await (0, fs_promises.readFile)((0, path.join)(dir, lockfile));
208
+ } catch {
209
+ continue;
210
+ }
211
+ hash.update(lockfile).update(content);
212
+ }
213
+ hash.update(installCommand);
214
+ return hash.digest("hex");
223
215
  }
224
- var MANIFEST_FILENAME = "sandbox-deployment.json";
216
+ //#endregion
217
+ //#region src/manifest.ts
218
+ const MANIFEST_FILENAME = "sandbox-deployment.json";
219
+ /** Write `sandbox-deployment.json` into the build output directory. */
225
220
  async function writeDeploymentManifest(outputDir, manifest) {
226
- await promises.writeFile(path.join(outputDir, MANIFEST_FILENAME), JSON.stringify(manifest, null, 2));
221
+ await (0, fs_promises.writeFile)((0, path.join)(outputDir, MANIFEST_FILENAME), JSON.stringify(manifest, null, 2));
227
222
  }
223
+ /** Read `sandbox-deployment.json` from the build output directory, or null when absent. */
228
224
  async function readDeploymentManifest(outputDir) {
229
- let raw;
230
- try {
231
- raw = await promises.readFile(path.join(outputDir, MANIFEST_FILENAME), "utf-8");
232
- } catch (error) {
233
- if (error.code === "ENOENT") {
234
- return null;
235
- }
236
- throw error;
237
- }
238
- return JSON.parse(raw);
225
+ let raw;
226
+ try {
227
+ raw = await (0, fs_promises.readFile)((0, path.join)(outputDir, MANIFEST_FILENAME), "utf-8");
228
+ } catch (error) {
229
+ if (error.code === "ENOENT") return null;
230
+ throw error;
231
+ }
232
+ return JSON.parse(raw);
239
233
  }
240
-
241
- // src/deployer.ts
242
- var SandboxDeployer = class extends deployer.Deployer {
243
- /** Sandbox deploys are push-style: `mastra build` runs `deploy()` after bundling. */
244
- deployOnBuild = true;
245
- sandbox;
246
- port;
247
- studio;
248
- /** Explicit remote dir, when configured. The engine defaults to `$HOME/mastra-app` inside the sandbox. */
249
- remoteDir;
250
- env;
251
- alias;
252
- healthCheckTimeoutMs;
253
- constructor(options) {
254
- super({ name: "SANDBOX" });
255
- this.sandbox = options.sandbox;
256
- this.port = options.port ?? chunkCSXC6CZL_cjs.DEFAULT_PORT;
257
- this.studio = options.studio ?? true;
258
- this.remoteDir = options.remoteDir;
259
- this.env = options.env ?? {};
260
- this.alias = options.alias;
261
- this.healthCheckTimeoutMs = options.healthCheckTimeoutMs;
262
- }
263
- /**
264
- * Merge all existing env files instead of only the first one (base behavior).
265
- * Later files win in `loadEnvVars()`, so order least → most specific: a
266
- * `.env.local` written by `vercel env pull` shouldn't shadow the `.env` that
267
- * holds the app's own keys.
268
- */
269
- async getEnvFiles() {
270
- const candidates = [".env", ".env.production", ".env.local"];
271
- const existing = [];
272
- for (const file of candidates) {
273
- try {
274
- await promises.access(file);
275
- existing.push(file);
276
- } catch {
277
- }
278
- }
279
- return existing;
280
- }
281
- async getUserBundlerOptions(mastraEntryFile, outputDirectory) {
282
- const bundlerOptions = await super.getUserBundlerOptions(mastraEntryFile, outputDirectory);
283
- return {
284
- ...bundlerOptions,
285
- externals: true
286
- };
287
- }
288
- getEntry() {
289
- return `
234
+ //#endregion
235
+ //#region src/deployer.ts
236
+ /**
237
+ * Deploy a full Mastra server into any workspace sandbox that supports
238
+ * networking (Vercel Sandbox, E2B, ...) and get a live public URL.
239
+ *
240
+ * Positioning: ephemeral environments — instant previews, PR/CI smoke deploys,
241
+ * agent-built-app verification. Not production hosting.
242
+ *
243
+ * @example
244
+ * ```typescript
245
+ * import { SandboxDeployer } from '@mastra/deployer-sandbox';
246
+ * import { VercelSandbox } from '@mastra/vercel';
247
+ *
248
+ * export const mastra = new Mastra({
249
+ * deployer: new SandboxDeployer({
250
+ * sandbox: new VercelSandbox({ sandboxName: 'my-preview', timeout: 3_600_000, ports: [4111] }),
251
+ * }),
252
+ * });
253
+ * ```
254
+ */
255
+ var SandboxDeployer = class extends _mastra_deployer.Deployer {
256
+ /** Sandbox deploys are push-style: `mastra build` runs `deploy()` after bundling. */
257
+ deployOnBuild = true;
258
+ sandbox;
259
+ port;
260
+ studio;
261
+ /** Explicit remote dir, when configured. The engine defaults to `$HOME/mastra-app` inside the sandbox. */
262
+ remoteDir;
263
+ env;
264
+ alias;
265
+ healthCheckTimeoutMs;
266
+ constructor(options) {
267
+ super({ name: "SANDBOX" });
268
+ this.sandbox = options.sandbox;
269
+ this.port = options.port ?? 4111;
270
+ this.studio = options.studio ?? true;
271
+ this.remoteDir = options.remoteDir;
272
+ this.env = options.env ?? {};
273
+ this.alias = options.alias;
274
+ this.healthCheckTimeoutMs = options.healthCheckTimeoutMs;
275
+ }
276
+ /**
277
+ * Merge all existing env files instead of only the first one (base behavior).
278
+ * Later files win in `loadEnvVars()`, so order least → most specific: a
279
+ * `.env.local` written by `vercel env pull` shouldn't shadow the `.env` that
280
+ * holds the app's own keys.
281
+ */
282
+ async getEnvFiles() {
283
+ const candidates = [
284
+ ".env",
285
+ ".env.production",
286
+ ".env.local"
287
+ ];
288
+ const existing = [];
289
+ for (const file of candidates) try {
290
+ await (0, fs_promises.access)(file);
291
+ existing.push(file);
292
+ } catch {}
293
+ return existing;
294
+ }
295
+ async getUserBundlerOptions(mastraEntryFile, outputDirectory) {
296
+ return {
297
+ ...await super.getUserBundlerOptions(mastraEntryFile, outputDirectory),
298
+ externals: true
299
+ };
300
+ }
301
+ getEntry() {
302
+ return `
290
303
  // @ts-expect-error
291
304
  import { scoreTracesWorkflow } from '@mastra/core/evals/scoreTraces';
292
305
  import { mastra } from '#mastra';
@@ -304,72 +317,69 @@ var SandboxDeployer = class extends deployer.Deployer {
304
317
  mastra.__registerInternalWorkflow(scoreTracesWorkflow);
305
318
  }
306
319
  `;
307
- }
308
- async prepare(outputDirectory) {
309
- await super.prepare(outputDirectory);
310
- if (this.studio) {
311
- const __filename = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
312
- const __dirname = path.dirname(__filename);
313
- const studioSource = path.join(path.dirname(__dirname), "dist", "studio");
314
- const studioServePath = path.join(outputDirectory, this.outputDir, "studio");
315
- try {
316
- await esm.copy(studioSource, studioServePath, { overwrite: true });
317
- } catch (err) {
318
- throw new Error(
319
- `Failed to copy studio assets from "${studioSource}" to "${studioServePath}": ${err instanceof Error ? err.message : err}`
320
- );
321
- }
322
- }
323
- }
324
- async bundle(entryFile, outputDirectory, { toolsPaths, projectRoot }) {
325
- return this._bundle(this.getEntry(), entryFile, { outputDirectory, projectRoot }, toolsPaths);
326
- }
327
- /**
328
- * Deploy the built output into the sandbox and wait for the server to come
329
- * up on its public URL. Writes `sandbox-deployment.json` into the output
330
- * directory and updates the Edge Config alias when configured.
331
- */
332
- async deploy(outputDirectory) {
333
- const dir = path.join(outputDirectory, this.outputDir);
334
- const envVars = await this.loadEnvVars();
335
- const env = { ...Object.fromEntries(envVars), ...this.env };
336
- if (envVars.size > 0) {
337
- this.logger.warn(
338
- "Environment variables from your .env file are injected into the remote sandbox. Anyone with access to the sandbox can read them."
339
- );
340
- }
341
- const deployment = await deployToSandbox({
342
- sandbox: this.sandbox,
343
- dir,
344
- port: this.port,
345
- env,
346
- studio: this.studio,
347
- remoteDir: this.remoteDir,
348
- healthCheckTimeoutMs: this.healthCheckTimeoutMs,
349
- logger: this.logger
350
- });
351
- await writeDeploymentManifest(dir, {
352
- provider: this.sandbox.provider,
353
- sandboxId: deployment.sandboxId,
354
- url: deployment.url,
355
- port: this.port,
356
- deployedAt: (/* @__PURE__ */ new Date()).toISOString(),
357
- expiresAt: deployment.expiresAt?.toISOString()
358
- });
359
- if (this.alias) {
360
- await updateEdgeConfigAlias({ ...this.alias, url: deployment.url });
361
- this.logger.info(`Edge Config alias "${this.alias.key}" now points at ${deployment.url}`);
362
- }
363
- this.logger.info(`Mastra server deployed: ${deployment.url}/api`);
364
- if (this.studio) {
365
- this.logger.info(`Studio: ${deployment.url}`);
366
- }
367
- if (deployment.expiresAt) {
368
- this.logger.warn(`Sandbox expires at ${deployment.expiresAt.toISOString()} (provider runtime cap).`);
369
- }
370
- }
320
+ }
321
+ async prepare(outputDirectory) {
322
+ await super.prepare(outputDirectory);
323
+ if (this.studio) {
324
+ const studioSource = (0, path.join)((0, path.dirname)((0, path.dirname)((0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href))), "dist", "studio");
325
+ const studioServePath = (0, path.join)(outputDirectory, this.outputDir, "studio");
326
+ try {
327
+ await (0, fs_extra_esm.copy)(studioSource, studioServePath, { overwrite: true });
328
+ } catch (err) {
329
+ throw new Error(`Failed to copy studio assets from "${studioSource}" to "${studioServePath}": ${err instanceof Error ? err.message : err}`);
330
+ }
331
+ }
332
+ }
333
+ async bundle(entryFile, outputDirectory, { toolsPaths, projectRoot }) {
334
+ return this._bundle(this.getEntry(), entryFile, {
335
+ outputDirectory,
336
+ projectRoot
337
+ }, toolsPaths);
338
+ }
339
+ /**
340
+ * Deploy the built output into the sandbox and wait for the server to come
341
+ * up on its public URL. Writes `sandbox-deployment.json` into the output
342
+ * directory and updates the Edge Config alias when configured.
343
+ */
344
+ async deploy(outputDirectory) {
345
+ const dir = (0, path.join)(outputDirectory, this.outputDir);
346
+ const envVars = await this.loadEnvVars();
347
+ const env = {
348
+ ...Object.fromEntries(envVars),
349
+ ...this.env
350
+ };
351
+ if (envVars.size > 0) this.logger.warn("Environment variables from your .env file are injected into the remote sandbox. Anyone with access to the sandbox can read them.");
352
+ const deployment = await deployToSandbox({
353
+ sandbox: this.sandbox,
354
+ dir,
355
+ port: this.port,
356
+ env,
357
+ studio: this.studio,
358
+ remoteDir: this.remoteDir,
359
+ healthCheckTimeoutMs: this.healthCheckTimeoutMs,
360
+ logger: this.logger
361
+ });
362
+ await writeDeploymentManifest(dir, {
363
+ provider: this.sandbox.provider,
364
+ sandboxId: deployment.sandboxId,
365
+ url: deployment.url,
366
+ port: this.port,
367
+ deployedAt: (/* @__PURE__ */ new Date()).toISOString(),
368
+ expiresAt: deployment.expiresAt?.toISOString()
369
+ });
370
+ if (this.alias) {
371
+ await updateEdgeConfigAlias({
372
+ ...this.alias,
373
+ url: deployment.url
374
+ });
375
+ this.logger.info(`Edge Config alias "${this.alias.key}" now points at ${deployment.url}`);
376
+ }
377
+ this.logger.info(`Mastra server deployed: ${deployment.url}/api`);
378
+ if (this.studio) this.logger.info(`Studio: ${deployment.url}`);
379
+ if (deployment.expiresAt) this.logger.warn(`Sandbox expires at ${deployment.expiresAt.toISOString()} (provider runtime cap).`);
380
+ }
371
381
  };
372
-
382
+ //#endregion
373
383
  exports.MANIFEST_FILENAME = MANIFEST_FILENAME;
374
384
  exports.SandboxDeployer = SandboxDeployer;
375
385
  exports.buildLaunchScript = buildLaunchScript;
@@ -377,5 +387,5 @@ exports.deployToSandbox = deployToSandbox;
377
387
  exports.readDeploymentManifest = readDeploymentManifest;
378
388
  exports.updateEdgeConfigAlias = updateEdgeConfigAlias;
379
389
  exports.writeDeploymentManifest = writeDeploymentManifest;
380
- //# sourceMappingURL=index.cjs.map
390
+
381
391
  //# sourceMappingURL=index.cjs.map