@prisma/compute-sdk 0.25.0 → 0.27.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 (71) hide show
  1. package/dist/artifact-stage.d.ts +24 -0
  2. package/dist/artifact-stage.d.ts.map +1 -0
  3. package/dist/artifact-stage.js +165 -0
  4. package/dist/artifact-stage.js.map +1 -0
  5. package/dist/astro-build.d.ts +2 -0
  6. package/dist/astro-build.d.ts.map +1 -1
  7. package/dist/astro-build.js +10 -1
  8. package/dist/astro-build.js.map +1 -1
  9. package/dist/auto-build.d.ts +7 -4
  10. package/dist/auto-build.d.ts.map +1 -1
  11. package/dist/auto-build.js +25 -33
  12. package/dist/auto-build.js.map +1 -1
  13. package/dist/build-settings.d.ts +55 -0
  14. package/dist/build-settings.d.ts.map +1 -0
  15. package/dist/build-settings.js +301 -0
  16. package/dist/build-settings.js.map +1 -0
  17. package/dist/build-strategy.d.ts +4 -0
  18. package/dist/build-strategy.d.ts.map +1 -1
  19. package/dist/build-strategy.js +13 -20
  20. package/dist/build-strategy.js.map +1 -1
  21. package/dist/build.d.ts +45 -0
  22. package/dist/build.d.ts.map +1 -0
  23. package/dist/build.js +86 -0
  24. package/dist/build.js.map +1 -0
  25. package/dist/bun-build.d.ts +8 -0
  26. package/dist/bun-build.d.ts.map +1 -1
  27. package/dist/bun-build.js +49 -23
  28. package/dist/bun-build.js.map +1 -1
  29. package/dist/config/frameworks.d.ts +8 -1
  30. package/dist/config/frameworks.d.ts.map +1 -1
  31. package/dist/config/frameworks.js +28 -0
  32. package/dist/config/frameworks.js.map +1 -1
  33. package/dist/config/index.d.ts +1 -1
  34. package/dist/config/index.d.ts.map +1 -1
  35. package/dist/config/index.js +1 -1
  36. package/dist/config/index.js.map +1 -1
  37. package/dist/index.d.ts +4 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +4 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/nextjs-build.d.ts +12 -2
  42. package/dist/nextjs-build.d.ts.map +1 -1
  43. package/dist/nextjs-build.js +158 -76
  44. package/dist/nextjs-build.js.map +1 -1
  45. package/dist/nuxt-build.d.ts +2 -0
  46. package/dist/nuxt-build.d.ts.map +1 -1
  47. package/dist/nuxt-build.js +10 -1
  48. package/dist/nuxt-build.js.map +1 -1
  49. package/dist/tanstack-start-build.d.ts +6 -1
  50. package/dist/tanstack-start-build.d.ts.map +1 -1
  51. package/dist/tanstack-start-build.js +39 -16
  52. package/dist/tanstack-start-build.js.map +1 -1
  53. package/dist/workspace.d.ts +90 -0
  54. package/dist/workspace.d.ts.map +1 -0
  55. package/dist/workspace.js +205 -0
  56. package/dist/workspace.js.map +1 -0
  57. package/package.json +4 -3
  58. package/src/artifact-stage.ts +280 -0
  59. package/src/astro-build.ts +13 -2
  60. package/src/auto-build.ts +28 -34
  61. package/src/build-settings.ts +430 -0
  62. package/src/build-strategy.ts +22 -29
  63. package/src/build.ts +124 -0
  64. package/src/bun-build.ts +62 -27
  65. package/src/config/frameworks.ts +38 -0
  66. package/src/config/index.ts +1 -0
  67. package/src/index.ts +28 -0
  68. package/src/nextjs-build.ts +208 -104
  69. package/src/nuxt-build.ts +13 -2
  70. package/src/tanstack-start-build.ts +48 -19
  71. package/src/workspace.ts +319 -0
@@ -0,0 +1,205 @@
1
+ import { spawn } from "node:child_process";
2
+ import { readFile, stat } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { sourceRootLineage } from "./config/source-root.js";
5
+ /**
6
+ * Detects the package manager governing `appPath` by walking from the app up
7
+ * to the source root: the nearest `packageManager` field wins, then the
8
+ * nearest lockfile. Workspace repos keep both at the root, so a package
9
+ * deep in a monorepo still resolves correctly. Returns undefined when no
10
+ * signal is found.
11
+ */
12
+ export async function resolvePackageManager(appPath, signal) {
13
+ for (const directory of await sourceRootLineage(appPath, signal)) {
14
+ const manifest = await readPackageManifest(directory, signal);
15
+ const fromField = packageManagerFromField(manifest?.packageManager);
16
+ if (fromField) {
17
+ return fromField;
18
+ }
19
+ if ((await pathExists(path.join(directory, "bun.lock"), signal)) ||
20
+ (await pathExists(path.join(directory, "bun.lockb"), signal))) {
21
+ return "bun";
22
+ }
23
+ if (await pathExists(path.join(directory, "pnpm-lock.yaml"), signal)) {
24
+ return "pnpm";
25
+ }
26
+ if (await pathExists(path.join(directory, "yarn.lock"), signal)) {
27
+ return "yarn";
28
+ }
29
+ if (await pathExists(path.join(directory, "package-lock.json"), signal)) {
30
+ return "npm";
31
+ }
32
+ }
33
+ return undefined;
34
+ }
35
+ /**
36
+ * Build environment for `appPath`: every `node_modules/.bin` between the app
37
+ * and its source root, prepended to PATH. Workspace repos hoist binaries like
38
+ * `next` to the workspace root, so a build run from a package directory still
39
+ * finds them. The returned object is suitable as the `env` for a child build
40
+ * process.
41
+ */
42
+ export async function buildCommandEnv(appPath, baseEnv, signal) {
43
+ const binDirs = (await sourceRootLineage(appPath, signal)).map((directory) => path.join(directory, "node_modules", ".bin"));
44
+ return {
45
+ ...baseEnv,
46
+ PATH: [...binDirs, baseEnv.PATH].filter(Boolean).join(path.delimiter),
47
+ };
48
+ }
49
+ /**
50
+ * Runs a build command string inside `appPath`, with every ancestor
51
+ * `node_modules/.bin` on PATH so workspace-hoisted binaries resolve. Rejects
52
+ * with the framework prefix and captured output on a non-zero exit.
53
+ */
54
+ export async function runBuildCommand(options) {
55
+ const env = await buildCommandEnv(options.appPath, { ...process.env, ...options.env }, options.signal);
56
+ // `shell: true` preserves the platform shell so a command string like
57
+ // `pnpm run build` works the same as the CLI's previous `exec`.
58
+ await runChildProcess({
59
+ command: options.command,
60
+ args: [],
61
+ cwd: options.appPath,
62
+ env,
63
+ shell: true,
64
+ failurePrefix: options.failurePrefix,
65
+ onOutput: options.onOutput,
66
+ signal: options.signal,
67
+ });
68
+ }
69
+ /**
70
+ * Spawns a child build process, streaming each output line to `onOutput` and
71
+ * keeping a bounded tail for the failure message instead of buffering the whole
72
+ * build. Resolves on a zero exit; rejects on a non-zero exit (with the prefix,
73
+ * exit code, and tail) or a spawn error (whose `code`, e.g. ENOENT, is
74
+ * preserved on the rejection so a launcher ladder can branch on it).
75
+ *
76
+ * Shared by `runBuildCommand` (a shell command string) and `runPackageCli`
77
+ * (an explicit binary + args).
78
+ */
79
+ export function runChildProcess(options) {
80
+ return new Promise((resolve, reject) => {
81
+ const tail = createBoundedTail();
82
+ const child = spawn(options.command, [...options.args], {
83
+ cwd: options.cwd,
84
+ env: options.env,
85
+ signal: options.signal,
86
+ shell: options.shell ?? false,
87
+ });
88
+ const streamed = Promise.all([
89
+ streamLines(child.stdout, "stdout", tail, options.onOutput),
90
+ streamLines(child.stderr, "stderr", tail, options.onOutput),
91
+ ]);
92
+ child.on("error", (error) => {
93
+ // Preserve `code` (e.g. ENOENT) so a launcher ladder can fall through.
94
+ reject(Object.assign(new Error(`${options.failurePrefix} build failed:\n${error.message}`), { code: error.code }));
95
+ });
96
+ child.on("close", (code) => {
97
+ streamed.then(() => {
98
+ if (code === 0) {
99
+ resolve();
100
+ return;
101
+ }
102
+ const detail = tail.text().trim();
103
+ const exit = code != null ? ` (exit code ${code})` : "";
104
+ reject(new Error(`${options.failurePrefix} build failed${exit}${detail ? `:\n${detail}` : "."}`));
105
+ }, reject);
106
+ });
107
+ });
108
+ }
109
+ /** Splits a child stream into lines, taps each, and records it in the tail. */
110
+ function streamLines(stream, source, tail, onOutput) {
111
+ return new Promise((resolve) => {
112
+ if (!stream) {
113
+ resolve();
114
+ return;
115
+ }
116
+ stream.setEncoding("utf8");
117
+ let buffer = "";
118
+ const emit = (line) => {
119
+ tail.push(line);
120
+ onOutput?.(line, source);
121
+ };
122
+ stream.on("data", (chunk) => {
123
+ buffer += chunk;
124
+ let newline = buffer.indexOf("\n");
125
+ while (newline !== -1) {
126
+ emit(buffer.slice(0, newline));
127
+ buffer = buffer.slice(newline + 1);
128
+ newline = buffer.indexOf("\n");
129
+ }
130
+ });
131
+ stream.on("end", () => {
132
+ if (buffer.length > 0) {
133
+ emit(buffer);
134
+ }
135
+ resolve();
136
+ });
137
+ // A stream error surfaces through the child's own error/close handlers.
138
+ stream.on("error", () => resolve());
139
+ });
140
+ }
141
+ /** A ring of the most recent output lines, for the failure message. */
142
+ function createBoundedTail(maxLines = 200) {
143
+ const lines = [];
144
+ return {
145
+ push(line) {
146
+ lines.push(line);
147
+ if (lines.length > maxLines) {
148
+ lines.shift();
149
+ }
150
+ },
151
+ text() {
152
+ return lines.join("\n");
153
+ },
154
+ };
155
+ }
156
+ /** Reads and parses `appPath/package.json`, returning null on any failure. */
157
+ export async function readPackageManifest(appPath, signal) {
158
+ signal?.throwIfAborted();
159
+ try {
160
+ const content = await readFile(path.join(appPath, "package.json"), {
161
+ encoding: "utf-8",
162
+ signal,
163
+ });
164
+ const parsed = JSON.parse(content);
165
+ return isRecord(parsed) ? parsed : null;
166
+ }
167
+ catch (error) {
168
+ // A missing file or invalid JSON is expected; an abort must propagate.
169
+ if (signal?.aborted)
170
+ throw error;
171
+ return null;
172
+ }
173
+ }
174
+ /** The trimmed `scripts.build` string, or null when absent or empty. */
175
+ export function readBuildScript(manifest) {
176
+ const build = manifest?.scripts?.build;
177
+ if (typeof build !== "string") {
178
+ return null;
179
+ }
180
+ const trimmed = build.trim();
181
+ return trimmed.length > 0 ? trimmed : null;
182
+ }
183
+ function packageManagerFromField(value) {
184
+ if (typeof value !== "string") {
185
+ return null;
186
+ }
187
+ const name = value.split("@")[0];
188
+ return name === "bun" || name === "pnpm" || name === "yarn" || name === "npm"
189
+ ? name
190
+ : null;
191
+ }
192
+ async function pathExists(targetPath, signal) {
193
+ signal?.throwIfAborted();
194
+ try {
195
+ await stat(targetPath);
196
+ return true;
197
+ }
198
+ catch {
199
+ return false;
200
+ }
201
+ }
202
+ function isRecord(value) {
203
+ return typeof value === "object" && value !== null;
204
+ }
205
+ //# sourceMappingURL=workspace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/compute-sdk",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "description": "TypeScript SDK for deploying and managing applications on Prisma Compute",
5
5
  "license": "Apache-2.0",
6
6
  "repository": "prisma/project-compute",
@@ -38,10 +38,11 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "better-result": "^2.7.0",
41
+ "jiti": "^2.7.0",
42
+ "magicast": "^0.5.3",
41
43
  "tar-stream": "^3.1.8",
42
44
  "tiny-invariant": "1.3.3",
43
- "ws": "^8.20.0",
44
- "jiti": "^2.7.0"
45
+ "ws": "^8.20.0"
45
46
  },
46
47
  "peerDependencies": {
47
48
  "@prisma/management-api-sdk": ">=1.36.0"
@@ -0,0 +1,280 @@
1
+ import {
2
+ chmod,
3
+ copyFile,
4
+ lstat,
5
+ mkdir,
6
+ readdir,
7
+ readlink,
8
+ stat,
9
+ } from "node:fs/promises";
10
+ import path from "node:path";
11
+
12
+ import { resolveSourceRoot } from "./config/source-root.ts";
13
+
14
+ /**
15
+ * Stages a framework's standalone output (e.g. Next.js `.next/standalone`)
16
+ * into a deployable artifact directory, materializing the symlinks the output
17
+ * carries into the workspace `node_modules` and hoisting isolated-store
18
+ * dependencies (pnpm `.pnpm`, bun `.bun`) so Node resolution works once the
19
+ * artifact is unpacked elsewhere.
20
+ *
21
+ * Location-agnostic: the workspace boundary is resolved from `appPath`, not
22
+ * the current working directory.
23
+ */
24
+ export async function stageStandaloneArtifact(options: {
25
+ standaloneDir: string;
26
+ artifactDir: string;
27
+ appPath: string;
28
+ signal?: AbortSignal;
29
+ }): Promise<void> {
30
+ const standaloneRoot = path.resolve(options.standaloneDir);
31
+ const artifactRoot = path.resolve(options.artifactDir);
32
+ const appRoot = path.resolve(options.appPath);
33
+ const sourceRoot = await resolveSourceRoot(appRoot, options.signal);
34
+
35
+ await copyPathMaterializingSymlinks(standaloneRoot, artifactRoot, {
36
+ standaloneRoot,
37
+ appRoot,
38
+ sourceRoot,
39
+ signal: options.signal,
40
+ });
41
+ await hoistIsolatedStoreDependencies(
42
+ path.join(artifactRoot, "node_modules"),
43
+ options.signal,
44
+ );
45
+ }
46
+
47
+ /** Options shared by the symlink-materializing copy walk. */
48
+ interface MaterializeOptions {
49
+ /** Root of the standalone output being copied (for fallback resolution). */
50
+ standaloneRoot: string;
51
+ /** The app directory; symlink targets here are materialized. */
52
+ appRoot: string;
53
+ /** The workspace/source root; targets in its node_modules are materialized. */
54
+ sourceRoot: string;
55
+ signal?: AbortSignal;
56
+ }
57
+
58
+ /**
59
+ * pnpm and bun (isolated linker) both keep packages in a virtual store with a
60
+ * shared symlink farm (`.pnpm/node_modules`, `.bun/node_modules`). Hoist the
61
+ * farm entries to the artifact's `node_modules` root so Node-style resolution
62
+ * works after symlinks are materialized.
63
+ */
64
+ export async function hoistIsolatedStoreDependencies(
65
+ nodeModulesDir: string,
66
+ signal?: AbortSignal,
67
+ ): Promise<void> {
68
+ await hoistStoreDependencies(
69
+ nodeModulesDir,
70
+ path.join(nodeModulesDir, ".pnpm", "node_modules"),
71
+ signal,
72
+ );
73
+ await hoistStoreDependencies(
74
+ nodeModulesDir,
75
+ path.join(nodeModulesDir, ".bun", "node_modules"),
76
+ signal,
77
+ );
78
+ }
79
+
80
+ async function hoistStoreDependencies(
81
+ nodeModulesDir: string,
82
+ storeNodeModulesDir: string,
83
+ signal?: AbortSignal,
84
+ ): Promise<void> {
85
+ if (!(await directoryExists(storeNodeModulesDir, signal))) {
86
+ return;
87
+ }
88
+
89
+ const entries = await readEntries(storeNodeModulesDir, signal);
90
+ for (const entry of entries) {
91
+ const sourcePath = path.join(storeNodeModulesDir, entry.name);
92
+
93
+ if (entry.name.startsWith("@") && entry.isDirectory()) {
94
+ const scopedEntries = await readEntries(sourcePath, signal);
95
+ for (const scopedEntry of scopedEntries) {
96
+ const scopedDestination = path.join(
97
+ nodeModulesDir,
98
+ entry.name,
99
+ scopedEntry.name,
100
+ );
101
+ if (await pathExists(scopedDestination, signal)) {
102
+ continue;
103
+ }
104
+ signal?.throwIfAborted();
105
+ await mkdir(path.dirname(scopedDestination), { recursive: true });
106
+ await copyPathMaterializingSymlinks(
107
+ path.join(sourcePath, scopedEntry.name),
108
+ scopedDestination,
109
+ storeMaterializeOptions(storeNodeModulesDir, nodeModulesDir, signal),
110
+ );
111
+ }
112
+ continue;
113
+ }
114
+
115
+ const destinationPath = path.join(nodeModulesDir, entry.name);
116
+ if (await pathExists(destinationPath, signal)) {
117
+ continue;
118
+ }
119
+
120
+ await copyPathMaterializingSymlinks(
121
+ sourcePath,
122
+ destinationPath,
123
+ storeMaterializeOptions(storeNodeModulesDir, nodeModulesDir, signal),
124
+ );
125
+ }
126
+ }
127
+
128
+ function storeMaterializeOptions(
129
+ storeNodeModulesDir: string,
130
+ nodeModulesDir: string,
131
+ signal?: AbortSignal,
132
+ ): MaterializeOptions {
133
+ return {
134
+ standaloneRoot: storeNodeModulesDir,
135
+ appRoot: nodeModulesDir,
136
+ sourceRoot: nodeModulesDir,
137
+ signal,
138
+ };
139
+ }
140
+
141
+ async function copyPathMaterializingSymlinks(
142
+ sourcePath: string,
143
+ destinationPath: string,
144
+ options: MaterializeOptions,
145
+ ): Promise<void> {
146
+ options.signal?.throwIfAborted();
147
+ const sourceStat = await lstat(sourcePath);
148
+
149
+ if (sourceStat.isSymbolicLink()) {
150
+ const resolvedTarget = await resolveSymlinkTarget(sourcePath, options);
151
+ if (resolvedTarget === null) {
152
+ return;
153
+ }
154
+ await copyPathMaterializingSymlinks(
155
+ resolvedTarget,
156
+ destinationPath,
157
+ options,
158
+ );
159
+ return;
160
+ }
161
+
162
+ if (sourceStat.isDirectory()) {
163
+ await mkdir(destinationPath, { recursive: true });
164
+ const entries = await readEntries(sourcePath, options.signal);
165
+ for (const entry of entries) {
166
+ await copyPathMaterializingSymlinks(
167
+ path.join(sourcePath, entry.name),
168
+ path.join(destinationPath, entry.name),
169
+ options,
170
+ );
171
+ }
172
+ return;
173
+ }
174
+
175
+ if (sourceStat.isFile()) {
176
+ await mkdir(path.dirname(destinationPath), { recursive: true });
177
+ await copyFile(sourcePath, destinationPath);
178
+ await chmod(destinationPath, sourceStat.mode);
179
+ }
180
+ }
181
+
182
+ async function resolveSymlinkTarget(
183
+ symlinkPath: string,
184
+ options: MaterializeOptions,
185
+ ): Promise<string | null> {
186
+ options.signal?.throwIfAborted();
187
+ const linkTarget = await readlink(symlinkPath);
188
+ const resolvedTarget = path.resolve(path.dirname(symlinkPath), linkTarget);
189
+
190
+ if (await pathExists(resolvedTarget, options.signal)) {
191
+ if (
192
+ !isPathWithin(options.appRoot, resolvedTarget) &&
193
+ !isPathWithinWorkspaceDependency(options.sourceRoot, resolvedTarget)
194
+ ) {
195
+ throw new Error(
196
+ `Build artifact symlink escapes the app directory: ${resolvedTarget}`,
197
+ );
198
+ }
199
+ return resolvedTarget;
200
+ }
201
+
202
+ if (isPathWithin(options.standaloneRoot, resolvedTarget)) {
203
+ const fallbackTarget = path.join(
204
+ options.appRoot,
205
+ path.relative(options.standaloneRoot, resolvedTarget),
206
+ );
207
+ if (await pathExists(fallbackTarget, options.signal)) {
208
+ return fallbackTarget;
209
+ }
210
+ }
211
+
212
+ // pnpm's hoist layer (.pnpm/node_modules/*) contains speculative links that
213
+ // are routinely dangling — Next's tracer doesn't always align with what pnpm
214
+ // populated. Drop these silently. Dangling links elsewhere still throw so a
215
+ // real missing dep doesn't get masked.
216
+ if (isPnpmHoistLink(symlinkPath)) {
217
+ return null;
218
+ }
219
+
220
+ throw new Error(
221
+ `Standalone symlink target is missing: ${symlinkPath} -> ${linkTarget} (resolved to ${resolvedTarget})`,
222
+ );
223
+ }
224
+
225
+ function isPnpmHoistLink(symlinkPath: string): boolean {
226
+ const parts = path.dirname(symlinkPath).split(path.sep);
227
+ for (let i = 0; i < parts.length - 1; i++) {
228
+ if (parts[i] === ".pnpm" && parts[i + 1] === "node_modules") {
229
+ return true;
230
+ }
231
+ }
232
+ return false;
233
+ }
234
+
235
+ function isPathWithin(rootPath: string, candidatePath: string): boolean {
236
+ const relativePath = path.relative(rootPath, candidatePath);
237
+ return (
238
+ relativePath === "" ||
239
+ (!relativePath.startsWith(`..${path.sep}`) &&
240
+ relativePath !== ".." &&
241
+ !path.isAbsolute(relativePath))
242
+ );
243
+ }
244
+
245
+ function isPathWithinWorkspaceDependency(
246
+ sourceRoot: string,
247
+ candidatePath: string,
248
+ ): boolean {
249
+ return isPathWithin(path.join(sourceRoot, "node_modules"), candidatePath);
250
+ }
251
+
252
+ async function readEntries(directory: string, signal?: AbortSignal) {
253
+ signal?.throwIfAborted();
254
+ return readdir(directory, { withFileTypes: true });
255
+ }
256
+
257
+ async function pathExists(
258
+ targetPath: string,
259
+ signal?: AbortSignal,
260
+ ): Promise<boolean> {
261
+ signal?.throwIfAborted();
262
+ try {
263
+ await stat(targetPath);
264
+ return true;
265
+ } catch {
266
+ return false;
267
+ }
268
+ }
269
+
270
+ async function directoryExists(
271
+ targetPath: string,
272
+ signal?: AbortSignal,
273
+ ): Promise<boolean> {
274
+ signal?.throwIfAborted();
275
+ try {
276
+ return (await stat(targetPath)).isDirectory();
277
+ } catch {
278
+ return false;
279
+ }
280
+ }
@@ -1,12 +1,16 @@
1
1
  import { cp, mkdtemp, rm, stat } from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
+
5
+ import { normalizeArtifactSymlinks } from "./artifact-postprocess.ts";
4
6
  import type { BuildArtifact, BuildStrategy } from "./build-strategy.ts";
5
7
  import {
6
8
  hasPackageDependency,
7
9
  hasRootFile,
8
10
  runPackageCli,
9
11
  } from "./build-strategy.ts";
12
+ import { defaultHttpPortForBuildType } from "./config/frameworks.ts";
13
+ import type { BuildCommandIo } from "./workspace.ts";
10
14
 
11
15
  const ASTRO_CONFIG_FILENAMES = [
12
16
  "astro.config.js",
@@ -22,9 +26,11 @@ const ASTRO_CONFIG_FILENAMES = [
22
26
  */
23
27
  export class AstroBuild implements BuildStrategy {
24
28
  readonly #appPath: string;
29
+ readonly #io?: BuildCommandIo;
25
30
 
26
- constructor(options: { appPath: string }) {
31
+ constructor(options: { appPath: string; io?: BuildCommandIo }) {
27
32
  this.#appPath = options.appPath;
33
+ this.#io = options.io;
28
34
  }
29
35
 
30
36
  async canBuild(signal?: AbortSignal): Promise<boolean> {
@@ -43,6 +49,8 @@ export class AstroBuild implements BuildStrategy {
43
49
  failurePrefix: "Astro",
44
50
  missingMessage:
45
51
  "Could not find the Astro CLI. Install it with `npm install astro` or ensure npx/bunx is available.",
52
+ env: this.#io?.env,
53
+ onOutput: this.#io?.onOutput,
46
54
  signal,
47
55
  });
48
56
 
@@ -63,11 +71,14 @@ export class AstroBuild implements BuildStrategy {
63
71
  recursive: true,
64
72
  verbatimSymlinks: true,
65
73
  });
74
+ // Materialize any symlinks into the app/workspace node_modules so the
75
+ // artifact is self-contained once unpacked elsewhere.
76
+ await normalizeArtifactSymlinks(artifactDir, this.#appPath, signal);
66
77
 
67
78
  return {
68
79
  directory: artifactDir,
69
80
  entrypoint: "server/entry.mjs",
70
- defaultPortMapping: { http: 4321 },
81
+ defaultPortMapping: { http: defaultHttpPortForBuildType("astro") },
71
82
  cleanup: () => rm(outDir, { recursive: true, force: true }),
72
83
  };
73
84
  } catch (error) {
package/src/auto-build.ts CHANGED
@@ -1,47 +1,41 @@
1
- import { AstroBuild } from "./astro-build.ts";
1
+ import { type BuildStrategyHooks, resolveBuildStrategy } from "./build.ts";
2
2
  import type { BuildArtifact, BuildStrategy } from "./build-strategy.ts";
3
- import { BunBuild } from "./bun-build.ts";
4
- import { NextjsBuild } from "./nextjs-build.ts";
5
- import { NuxtBuild } from "./nuxt-build.ts";
6
- import { TanstackStartBuild } from "./tanstack-start-build.ts";
7
3
 
8
4
  /**
9
- * Meta-strategy that auto-detects the appropriate build strategy.
10
- * Iterates over candidates and delegates to the first one that can handle the app.
5
+ * Meta-strategy that auto-detects the appropriate framework build strategy and
6
+ * delegates to it. Detection runs in registry order with bun as the universal
7
+ * fallback, so a buildable strategy always resolves. Use `resolveBuildStrategy`
8
+ * directly when the resolved build type is also needed.
11
9
  */
12
10
  export class AutoBuild implements BuildStrategy {
13
- readonly #strategies: BuildStrategy[];
11
+ readonly #appPath: string;
12
+ readonly #entrypoint?: string;
13
+ readonly #hooks: BuildStrategyHooks;
14
14
 
15
- constructor(options: { appPath: string; entrypoint?: string }) {
16
- this.#strategies = [
17
- new NextjsBuild({ appPath: options.appPath }),
18
- new NuxtBuild({ appPath: options.appPath }),
19
- new AstroBuild({ appPath: options.appPath }),
20
- new TanstackStartBuild({ appPath: options.appPath }),
21
- new BunBuild({
22
- appPath: options.appPath,
23
- entrypoint: options.entrypoint,
24
- }),
25
- ];
15
+ constructor(
16
+ options: { appPath: string; entrypoint?: string } & BuildStrategyHooks,
17
+ ) {
18
+ this.#appPath = options.appPath;
19
+ this.#entrypoint = options.entrypoint;
20
+ this.#hooks = {
21
+ io: options.io,
22
+ requireStandalone: options.requireStandalone,
23
+ };
26
24
  }
27
25
 
28
- async canBuild(signal?: AbortSignal): Promise<boolean> {
29
- for (const strategy of this.#strategies) {
30
- signal?.throwIfAborted();
31
- if (await strategy.canBuild(signal)) {
32
- return true;
33
- }
34
- }
35
- return false;
26
+ async canBuild(_signal?: AbortSignal): Promise<boolean> {
27
+ // Detection always resolves (bun is the universal fallback).
28
+ return true;
36
29
  }
37
30
 
38
31
  async execute(signal?: AbortSignal): Promise<BuildArtifact> {
39
- for (const strategy of this.#strategies) {
40
- signal?.throwIfAborted();
41
- if (await strategy.canBuild(signal)) {
42
- return strategy.execute(signal);
43
- }
44
- }
45
- throw new Error("No suitable build strategy found for this application");
32
+ const { strategy } = await resolveBuildStrategy({
33
+ appPath: this.#appPath,
34
+ entrypoint: this.#entrypoint,
35
+ buildType: "auto",
36
+ signal,
37
+ ...this.#hooks,
38
+ });
39
+ return strategy.execute(signal);
46
40
  }
47
41
  }