@prisma/compute-sdk 0.26.0 → 0.28.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 (76) 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 +8 -0
  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 +311 -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 +89 -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 +47 -23
  28. package/dist/bun-build.js.map +1 -1
  29. package/dist/config/frameworks.d.ts +2 -2
  30. package/dist/config/frameworks.d.ts.map +1 -1
  31. package/dist/config/frameworks.js +12 -0
  32. package/dist/config/frameworks.js.map +1 -1
  33. package/dist/config/types.d.ts +1 -1
  34. package/dist/config/types.d.ts.map +1 -1
  35. package/dist/config/types.js +1 -0
  36. package/dist/config/types.js.map +1 -1
  37. package/dist/index.d.ts +5 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +5 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/nestjs-build.d.ts +23 -0
  42. package/dist/nestjs-build.d.ts.map +1 -0
  43. package/dist/nestjs-build.js +317 -0
  44. package/dist/nestjs-build.js.map +1 -0
  45. package/dist/nextjs-build.d.ts +12 -2
  46. package/dist/nextjs-build.d.ts.map +1 -1
  47. package/dist/nextjs-build.js +156 -75
  48. package/dist/nextjs-build.js.map +1 -1
  49. package/dist/nuxt-build.d.ts +2 -0
  50. package/dist/nuxt-build.d.ts.map +1 -1
  51. package/dist/nuxt-build.js +8 -0
  52. package/dist/nuxt-build.js.map +1 -1
  53. package/dist/tanstack-start-build.d.ts +6 -1
  54. package/dist/tanstack-start-build.d.ts.map +1 -1
  55. package/dist/tanstack-start-build.js +35 -15
  56. package/dist/tanstack-start-build.js.map +1 -1
  57. package/dist/workspace.d.ts +90 -0
  58. package/dist/workspace.d.ts.map +1 -0
  59. package/dist/workspace.js +205 -0
  60. package/dist/workspace.js.map +1 -0
  61. package/package.json +5 -3
  62. package/src/artifact-stage.ts +280 -0
  63. package/src/astro-build.ts +11 -1
  64. package/src/auto-build.ts +28 -34
  65. package/src/build-settings.ts +448 -0
  66. package/src/build-strategy.ts +22 -29
  67. package/src/build.ts +127 -0
  68. package/src/bun-build.ts +60 -27
  69. package/src/config/frameworks.ts +13 -0
  70. package/src/config/types.ts +1 -0
  71. package/src/index.ts +29 -0
  72. package/src/nestjs-build.ts +425 -0
  73. package/src/nextjs-build.ts +206 -103
  74. package/src/nuxt-build.ts +11 -1
  75. package/src/tanstack-start-build.ts +44 -18
  76. package/src/workspace.ts +319 -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,6 +1,8 @@
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,
@@ -8,6 +10,7 @@ import {
8
10
  runPackageCli,
9
11
  } from "./build-strategy.ts";
10
12
  import { defaultHttpPortForBuildType } from "./config/frameworks.ts";
13
+ import type { BuildCommandIo } from "./workspace.ts";
11
14
 
12
15
  const ASTRO_CONFIG_FILENAMES = [
13
16
  "astro.config.js",
@@ -23,9 +26,11 @@ const ASTRO_CONFIG_FILENAMES = [
23
26
  */
24
27
  export class AstroBuild implements BuildStrategy {
25
28
  readonly #appPath: string;
29
+ readonly #io?: BuildCommandIo;
26
30
 
27
- constructor(options: { appPath: string }) {
31
+ constructor(options: { appPath: string; io?: BuildCommandIo }) {
28
32
  this.#appPath = options.appPath;
33
+ this.#io = options.io;
29
34
  }
30
35
 
31
36
  async canBuild(signal?: AbortSignal): Promise<boolean> {
@@ -44,6 +49,8 @@ export class AstroBuild implements BuildStrategy {
44
49
  failurePrefix: "Astro",
45
50
  missingMessage:
46
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,
47
54
  signal,
48
55
  });
49
56
 
@@ -64,6 +71,9 @@ export class AstroBuild implements BuildStrategy {
64
71
  recursive: true,
65
72
  verbatimSymlinks: true,
66
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);
67
77
 
68
78
  return {
69
79
  directory: artifactDir,
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
  }