@prisma/cli 3.0.0-beta.1 → 3.0.0-beta.11
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/README.md +5 -3
- package/dist/adapters/git.js +8 -3
- package/dist/adapters/local-state.js +12 -4
- package/dist/adapters/mock-api.js +81 -2
- package/dist/adapters/token-storage.js +64 -23
- package/dist/cli.js +18 -3
- package/dist/cli2.js +9 -5
- package/dist/commands/app/index.js +84 -59
- package/dist/commands/branch/index.js +2 -27
- package/dist/commands/database/index.js +159 -0
- package/dist/commands/env.js +8 -4
- package/dist/controllers/app-env-api.js +54 -0
- package/dist/controllers/app-env-file.js +181 -0
- package/dist/controllers/app-env.js +286 -131
- package/dist/controllers/app.js +699 -244
- package/dist/controllers/auth.js +8 -8
- package/dist/controllers/branch.js +78 -48
- package/dist/controllers/database.js +318 -0
- package/dist/controllers/project.js +156 -85
- package/dist/lib/app/branch-database-deploy.js +325 -0
- package/dist/lib/app/branch-database.js +215 -0
- package/dist/lib/app/bun-project.js +12 -5
- package/dist/lib/app/compute-config.js +147 -0
- package/dist/lib/app/deploy-output.js +10 -1
- package/dist/lib/app/deploy-plan.js +58 -0
- package/dist/lib/app/env-config.js +1 -1
- package/dist/lib/app/env-file.js +82 -0
- package/dist/lib/app/env-vars.js +28 -2
- package/dist/lib/app/local-dev.js +8 -49
- package/dist/lib/app/preview-branch-database.js +102 -0
- package/dist/lib/app/preview-build-settings.js +376 -0
- package/dist/lib/app/preview-build.js +314 -97
- package/dist/lib/app/preview-provider.js +178 -65
- package/dist/lib/app/production-deploy-gate.js +161 -0
- package/dist/lib/auth/auth-ops.js +30 -21
- package/dist/lib/auth/guard.js +3 -2
- package/dist/lib/auth/login.js +116 -14
- package/dist/lib/database/provider.js +167 -0
- package/dist/lib/diagnostics.js +15 -0
- package/dist/lib/fs/home-path.js +24 -0
- package/dist/lib/git/local-branch.js +53 -0
- package/dist/lib/git/local-status.js +57 -0
- package/dist/lib/project/interactive-setup.js +1 -1
- package/dist/lib/project/local-pin.js +182 -33
- package/dist/lib/project/resolution.js +204 -50
- package/dist/lib/project/setup.js +66 -19
- package/dist/output/patterns.js +1 -1
- package/dist/presenters/app-env.js +149 -14
- package/dist/presenters/app.js +178 -23
- package/dist/presenters/branch.js +37 -102
- package/dist/presenters/database.js +274 -0
- package/dist/presenters/project.js +57 -39
- package/dist/presenters/verbose-context.js +64 -0
- package/dist/shell/command-meta.js +103 -13
- package/dist/shell/command-runner.js +57 -19
- package/dist/shell/diagnostics-output.js +57 -0
- package/dist/shell/errors.js +12 -1
- package/dist/shell/help.js +30 -20
- package/dist/shell/output.js +10 -1
- package/dist/shell/runtime.js +11 -7
- package/dist/shell/ui.js +42 -3
- package/dist/shell/update-check.js +247 -0
- package/dist/use-cases/branch.js +20 -68
- package/dist/use-cases/create-cli-gateways.js +2 -17
- package/dist/use-cases/project.js +2 -1
- package/package.json +26 -10
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { resolveBunEntrypoint } from "./bun-project.js";
|
|
2
|
-
import {
|
|
1
|
+
import { readBunPackageJson, resolveBunEntrypoint } from "./bun-project.js";
|
|
2
|
+
import { hasAnyPackageDependency, hasPackageDependency, hasRootFile, joinPosix, nextOutputRootFromStandaloneDirectory, resolvePreviewBuildSettings, runResolvedBuildCommand } from "./preview-build-settings.js";
|
|
3
|
+
import { resolveSourceRoot } from "@prisma/compute-sdk/config";
|
|
4
|
+
import { chmod, copyFile, cp, lstat, mkdir, mkdtemp, readdir, readlink, rm, stat, writeFile } from "node:fs/promises";
|
|
3
5
|
import path from "node:path";
|
|
4
|
-
import
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import { AstroBuild, BunBuild, NuxtBuild } from "@prisma/compute-sdk";
|
|
5
8
|
//#region src/lib/app/preview-build.ts
|
|
6
9
|
const PREVIEW_BUILD_TYPES = [
|
|
7
10
|
"auto",
|
|
@@ -16,24 +19,32 @@ var PreviewBuildStrategy = class {
|
|
|
16
19
|
#appPath;
|
|
17
20
|
#entrypoint;
|
|
18
21
|
#buildType;
|
|
22
|
+
#signal;
|
|
23
|
+
#buildSettings;
|
|
19
24
|
constructor(options) {
|
|
20
25
|
this.#appPath = options.appPath;
|
|
21
26
|
this.#entrypoint = options.entrypoint;
|
|
22
27
|
this.#buildType = options.buildType ?? "auto";
|
|
28
|
+
this.#signal = options.signal;
|
|
29
|
+
this.#buildSettings = options.buildSettings;
|
|
23
30
|
}
|
|
24
|
-
async canBuild() {
|
|
31
|
+
async canBuild(signal = this.#signal) {
|
|
25
32
|
const { strategy } = await resolvePreviewBuildStrategy({
|
|
26
33
|
appPath: this.#appPath,
|
|
27
34
|
entrypoint: this.#entrypoint,
|
|
28
|
-
buildType: this.#buildType
|
|
35
|
+
buildType: this.#buildType,
|
|
36
|
+
signal,
|
|
37
|
+
buildSettings: this.#buildSettings
|
|
29
38
|
});
|
|
30
|
-
return strategy.canBuild();
|
|
39
|
+
return strategy.canBuild(signal);
|
|
31
40
|
}
|
|
32
|
-
async execute() {
|
|
41
|
+
async execute(signal = this.#signal) {
|
|
33
42
|
const { artifact } = await executePreviewBuild({
|
|
34
43
|
appPath: this.#appPath,
|
|
35
44
|
entrypoint: this.#entrypoint,
|
|
36
|
-
buildType: this.#buildType
|
|
45
|
+
buildType: this.#buildType,
|
|
46
|
+
signal,
|
|
47
|
+
buildSettings: this.#buildSettings
|
|
37
48
|
});
|
|
38
49
|
return artifact;
|
|
39
50
|
}
|
|
@@ -42,12 +53,13 @@ async function executePreviewBuild(options) {
|
|
|
42
53
|
const { strategy, buildType } = await resolvePreviewBuildStrategy({
|
|
43
54
|
appPath: options.appPath,
|
|
44
55
|
entrypoint: options.entrypoint,
|
|
45
|
-
buildType: options.buildType ?? "auto"
|
|
56
|
+
buildType: options.buildType ?? "auto",
|
|
57
|
+
signal: options.signal,
|
|
58
|
+
buildSettings: options.buildSettings
|
|
46
59
|
});
|
|
47
|
-
const artifact = await strategy.execute();
|
|
60
|
+
const artifact = await strategy.execute(options.signal);
|
|
48
61
|
try {
|
|
49
|
-
|
|
50
|
-
await normalizeArtifactSymlinks(artifact.directory, options.appPath);
|
|
62
|
+
await normalizeArtifactSymlinks(artifact.directory, options.appPath, options.signal);
|
|
51
63
|
return {
|
|
52
64
|
artifact,
|
|
53
65
|
buildType
|
|
@@ -62,7 +74,9 @@ async function resolvePreviewBuildStrategy(options) {
|
|
|
62
74
|
const strategy = await createPreviewBuildStrategy({
|
|
63
75
|
appPath: options.appPath,
|
|
64
76
|
entrypoint: options.entrypoint,
|
|
65
|
-
buildType: options.buildType
|
|
77
|
+
buildType: options.buildType,
|
|
78
|
+
signal: options.signal,
|
|
79
|
+
buildSettings: options.buildSettings
|
|
66
80
|
});
|
|
67
81
|
return {
|
|
68
82
|
buildType: options.buildType,
|
|
@@ -74,9 +88,11 @@ async function resolvePreviewBuildStrategy(options) {
|
|
|
74
88
|
const strategy = await createPreviewBuildStrategy({
|
|
75
89
|
appPath: options.appPath,
|
|
76
90
|
entrypoint: options.entrypoint,
|
|
77
|
-
buildType
|
|
91
|
+
buildType,
|
|
92
|
+
signal: options.signal,
|
|
93
|
+
buildSettings: options.buildSettings
|
|
78
94
|
});
|
|
79
|
-
if (await strategy.canBuild()) return {
|
|
95
|
+
if (await strategy.canBuild(options.signal)) return {
|
|
80
96
|
buildType,
|
|
81
97
|
strategy
|
|
82
98
|
};
|
|
@@ -86,25 +102,210 @@ async function resolvePreviewBuildStrategy(options) {
|
|
|
86
102
|
strategy: await createPreviewBuildStrategy({
|
|
87
103
|
appPath: options.appPath,
|
|
88
104
|
entrypoint: options.entrypoint,
|
|
89
|
-
buildType: "bun"
|
|
105
|
+
buildType: "bun",
|
|
106
|
+
signal: options.signal,
|
|
107
|
+
buildSettings: options.buildSettings
|
|
90
108
|
})
|
|
91
109
|
};
|
|
92
110
|
}
|
|
93
111
|
async function createPreviewBuildStrategy(options) {
|
|
94
112
|
switch (options.buildType) {
|
|
95
|
-
case "nextjs": return new
|
|
113
|
+
case "nextjs": return new PreviewNextjsBuild({
|
|
114
|
+
appPath: options.appPath,
|
|
115
|
+
buildSettings: options.buildSettings
|
|
116
|
+
});
|
|
96
117
|
case "nuxt": return new NuxtBuild({ appPath: options.appPath });
|
|
97
118
|
case "astro": return new AstroBuild({ appPath: options.appPath });
|
|
98
|
-
case "tanstack-start": return new
|
|
119
|
+
case "tanstack-start": return new PreviewTanstackStartBuild({
|
|
120
|
+
appPath: options.appPath,
|
|
121
|
+
buildSettings: options.buildSettings
|
|
122
|
+
});
|
|
99
123
|
case "bun": {
|
|
100
|
-
const entrypoint = await resolveBunEntrypoint(options.appPath, options.entrypoint);
|
|
101
|
-
return new
|
|
124
|
+
const entrypoint = await resolveBunEntrypoint(options.appPath, options.entrypoint, options.signal);
|
|
125
|
+
return new PreviewBunBuild({
|
|
102
126
|
appPath: options.appPath,
|
|
103
|
-
|
|
127
|
+
strategy: new BunBuild({
|
|
128
|
+
appPath: options.appPath,
|
|
129
|
+
entrypoint
|
|
130
|
+
}),
|
|
131
|
+
buildSettings: options.buildSettings
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
var PreviewNextjsBuild = class {
|
|
137
|
+
#appPath;
|
|
138
|
+
#buildSettings;
|
|
139
|
+
constructor(options) {
|
|
140
|
+
this.#appPath = options.appPath;
|
|
141
|
+
this.#buildSettings = options.buildSettings;
|
|
142
|
+
}
|
|
143
|
+
async canBuild(signal) {
|
|
144
|
+
const packageJson = await readBunPackageJson(this.#appPath, signal);
|
|
145
|
+
return await hasRootFile(this.#appPath, NEXT_CONFIG_FILENAMES, signal) || hasPackageDependency(packageJson, "next");
|
|
146
|
+
}
|
|
147
|
+
async execute(signal) {
|
|
148
|
+
const settings = this.#buildSettings ?? await resolvePreviewBuildSettings({
|
|
149
|
+
appPath: this.#appPath,
|
|
150
|
+
buildType: "nextjs",
|
|
151
|
+
signal
|
|
152
|
+
});
|
|
153
|
+
await runResolvedBuildCommand(this.#appPath, settings, "Next.js", signal);
|
|
154
|
+
const standaloneDir = path.join(this.#appPath, settings.outputDirectory);
|
|
155
|
+
if (!await directoryExists(standaloneDir, signal)) return stageNextjsFullTreeFallbackArtifact(this.#appPath, signal);
|
|
156
|
+
const outDir = await unsupportedFilesystemBoundary(signal, () => mkdtemp(path.join(os.tmpdir(), "compute-build-")));
|
|
157
|
+
try {
|
|
158
|
+
const artifactDir = path.join(outDir, "app");
|
|
159
|
+
await stageNextjsStandaloneArtifact({
|
|
160
|
+
standaloneDir,
|
|
161
|
+
artifactDir,
|
|
162
|
+
appPath: this.#appPath,
|
|
163
|
+
signal
|
|
164
|
+
});
|
|
165
|
+
const entrypoint = await findNextStandaloneEntrypoint(artifactDir, signal);
|
|
166
|
+
await copyNextjsStaticAssets({
|
|
167
|
+
appPath: this.#appPath,
|
|
168
|
+
artifactDir,
|
|
169
|
+
outputRoot: nextOutputRootFromStandaloneDirectory(settings.outputDirectory),
|
|
170
|
+
entrypoint,
|
|
171
|
+
signal
|
|
104
172
|
});
|
|
173
|
+
return {
|
|
174
|
+
directory: artifactDir,
|
|
175
|
+
entrypoint,
|
|
176
|
+
defaultPortMapping: { http: 3e3 },
|
|
177
|
+
cleanup: () => rm(outDir, {
|
|
178
|
+
recursive: true,
|
|
179
|
+
force: true
|
|
180
|
+
})
|
|
181
|
+
};
|
|
182
|
+
} catch (error) {
|
|
183
|
+
await rm(outDir, {
|
|
184
|
+
recursive: true,
|
|
185
|
+
force: true
|
|
186
|
+
});
|
|
187
|
+
throw error;
|
|
105
188
|
}
|
|
106
189
|
}
|
|
190
|
+
};
|
|
191
|
+
const FULL_TREE_NEXT_START_ENTRYPOINT = "prisma-next-start.cjs";
|
|
192
|
+
const FULL_TREE_NEXT_START_SOURCE = [
|
|
193
|
+
"process.chdir(__dirname);",
|
|
194
|
+
"process.env.NODE_ENV = \"production\";",
|
|
195
|
+
"process.argv.push(\"start\", \"-p\", process.env.PORT ?? \"3000\");",
|
|
196
|
+
"require(\"next/dist/bin/next\");",
|
|
197
|
+
""
|
|
198
|
+
].join("\n");
|
|
199
|
+
async function stageNextjsFullTreeFallbackArtifact(appPath, signal) {
|
|
200
|
+
const outDir = await unsupportedFilesystemBoundary(signal, () => mkdtemp(path.join(os.tmpdir(), "compute-build-")));
|
|
201
|
+
try {
|
|
202
|
+
const artifactDir = path.join(outDir, "app");
|
|
203
|
+
await unsupportedFilesystemBoundary(signal, () => cp(appPath, artifactDir, {
|
|
204
|
+
recursive: true,
|
|
205
|
+
verbatimSymlinks: true,
|
|
206
|
+
filter: (source) => !isExcludedFromFullTreeArtifact(path.basename(source))
|
|
207
|
+
}));
|
|
208
|
+
await unsupportedFilesystemBoundary(signal, () => writeFile(path.join(artifactDir, FULL_TREE_NEXT_START_ENTRYPOINT), FULL_TREE_NEXT_START_SOURCE));
|
|
209
|
+
return {
|
|
210
|
+
directory: artifactDir,
|
|
211
|
+
entrypoint: FULL_TREE_NEXT_START_ENTRYPOINT,
|
|
212
|
+
defaultPortMapping: { http: 3e3 },
|
|
213
|
+
cleanup: () => rm(outDir, {
|
|
214
|
+
recursive: true,
|
|
215
|
+
force: true
|
|
216
|
+
})
|
|
217
|
+
};
|
|
218
|
+
} catch (error) {
|
|
219
|
+
await rm(outDir, {
|
|
220
|
+
recursive: true,
|
|
221
|
+
force: true
|
|
222
|
+
});
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/** Excludes VCS internals and dotenv files (local secrets, superseded by the deploy env). */
|
|
227
|
+
function isExcludedFromFullTreeArtifact(basename) {
|
|
228
|
+
return basename === ".git" || basename === ".env" || basename.startsWith(".env.");
|
|
107
229
|
}
|
|
230
|
+
var PreviewTanstackStartBuild = class {
|
|
231
|
+
#appPath;
|
|
232
|
+
#buildSettings;
|
|
233
|
+
constructor(options) {
|
|
234
|
+
this.#appPath = options.appPath;
|
|
235
|
+
this.#buildSettings = options.buildSettings;
|
|
236
|
+
}
|
|
237
|
+
async canBuild(signal) {
|
|
238
|
+
return hasAnyPackageDependency(await readBunPackageJson(this.#appPath, signal), TANSTACK_START_PACKAGES);
|
|
239
|
+
}
|
|
240
|
+
async execute(signal) {
|
|
241
|
+
const settings = this.#buildSettings ?? await resolvePreviewBuildSettings({
|
|
242
|
+
appPath: this.#appPath,
|
|
243
|
+
buildType: "tanstack-start",
|
|
244
|
+
signal
|
|
245
|
+
});
|
|
246
|
+
await runResolvedBuildCommand(this.#appPath, settings, "TanStack Start", signal);
|
|
247
|
+
const outputDir = path.join(this.#appPath, settings.outputDirectory);
|
|
248
|
+
const entrypoint = "server/index.mjs";
|
|
249
|
+
const entryPath = path.join(outputDir, entrypoint);
|
|
250
|
+
if (!(await unsupportedFilesystemBoundary(signal, () => stat(entryPath).catch(() => null)))?.isFile()) throw new Error(`TanStack Start build did not produce a Nitro node server entrypoint at ${joinPosix(settings.outputDirectory, entrypoint)}. Ensure your vite.config includes the tanstackStart() and nitro() plugins with the default node preset, or set build.outputDirectory in prisma.compute.ts.`);
|
|
251
|
+
const outDir = await unsupportedFilesystemBoundary(signal, () => mkdtemp(path.join(os.tmpdir(), "compute-build-")));
|
|
252
|
+
try {
|
|
253
|
+
const artifactDir = path.join(outDir, "app");
|
|
254
|
+
await unsupportedFilesystemBoundary(signal, () => cp(outputDir, artifactDir, {
|
|
255
|
+
recursive: true,
|
|
256
|
+
verbatimSymlinks: true
|
|
257
|
+
}));
|
|
258
|
+
return {
|
|
259
|
+
directory: artifactDir,
|
|
260
|
+
entrypoint,
|
|
261
|
+
defaultPortMapping: { http: 3e3 },
|
|
262
|
+
cleanup: () => rm(outDir, {
|
|
263
|
+
recursive: true,
|
|
264
|
+
force: true
|
|
265
|
+
})
|
|
266
|
+
};
|
|
267
|
+
} catch (error) {
|
|
268
|
+
await rm(outDir, {
|
|
269
|
+
recursive: true,
|
|
270
|
+
force: true
|
|
271
|
+
});
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
var PreviewBunBuild = class {
|
|
277
|
+
#appPath;
|
|
278
|
+
#strategy;
|
|
279
|
+
#buildSettings;
|
|
280
|
+
constructor(options) {
|
|
281
|
+
this.#appPath = options.appPath;
|
|
282
|
+
this.#strategy = options.strategy;
|
|
283
|
+
this.#buildSettings = options.buildSettings;
|
|
284
|
+
}
|
|
285
|
+
async canBuild(signal) {
|
|
286
|
+
return this.#strategy.canBuild(signal);
|
|
287
|
+
}
|
|
288
|
+
async execute(signal) {
|
|
289
|
+
const settings = this.#buildSettings ?? await resolvePreviewBuildSettings({
|
|
290
|
+
appPath: this.#appPath,
|
|
291
|
+
buildType: "bun",
|
|
292
|
+
signal
|
|
293
|
+
});
|
|
294
|
+
await runResolvedBuildCommand(this.#appPath, settings, "Bun", signal);
|
|
295
|
+
return this.#strategy.execute(signal);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
const NEXT_CONFIG_FILENAMES = [
|
|
299
|
+
"next.config.js",
|
|
300
|
+
"next.config.mjs",
|
|
301
|
+
"next.config.ts",
|
|
302
|
+
"next.config.mts"
|
|
303
|
+
];
|
|
304
|
+
const TANSTACK_START_PACKAGES = [
|
|
305
|
+
"@tanstack/react-start",
|
|
306
|
+
"@tanstack/solid-start",
|
|
307
|
+
"@tanstack/start"
|
|
308
|
+
];
|
|
108
309
|
async function stageNextjsStandaloneArtifact(options) {
|
|
109
310
|
const standaloneRoot = path.resolve(options.standaloneDir);
|
|
110
311
|
const artifactRoot = path.resolve(options.artifactDir);
|
|
@@ -112,74 +313,97 @@ async function stageNextjsStandaloneArtifact(options) {
|
|
|
112
313
|
await copyPathMaterializingSymlinks(standaloneRoot, artifactRoot, {
|
|
113
314
|
standaloneRoot,
|
|
114
315
|
appRoot,
|
|
115
|
-
sourceRoot: await resolveSourceRoot(appRoot)
|
|
316
|
+
sourceRoot: await resolveSourceRoot(appRoot, options.signal),
|
|
317
|
+
signal: options.signal
|
|
116
318
|
});
|
|
117
|
-
await
|
|
319
|
+
await hoistIsolatedStoreDependencies(path.join(artifactRoot, "node_modules"), options.signal);
|
|
118
320
|
}
|
|
119
|
-
async function
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
force: true
|
|
125
|
-
});
|
|
126
|
-
await stageNextjsStandaloneArtifact({
|
|
127
|
-
standaloneDir,
|
|
128
|
-
artifactDir,
|
|
129
|
-
appPath
|
|
130
|
-
});
|
|
131
|
-
const serverSubpath = nextjsServerSubpath(artifact.entrypoint);
|
|
132
|
-
const serverDir = serverSubpath ? path.join(artifactDir, serverSubpath) : artifactDir;
|
|
133
|
-
const publicDir = path.join(appPath, "public");
|
|
134
|
-
if (await directoryExists(publicDir)) await cp(publicDir, path.join(serverDir, "public"), {
|
|
321
|
+
async function copyNextjsStaticAssets(options) {
|
|
322
|
+
const serverSubpath = nextjsServerSubpath(options.entrypoint);
|
|
323
|
+
const serverDir = serverSubpath ? path.join(options.artifactDir, serverSubpath) : options.artifactDir;
|
|
324
|
+
const publicDir = path.join(options.appPath, "public");
|
|
325
|
+
if (await directoryExists(publicDir, options.signal)) await unsupportedFilesystemBoundary(options.signal, () => cp(publicDir, path.join(serverDir, "public"), {
|
|
135
326
|
recursive: true,
|
|
136
327
|
verbatimSymlinks: true
|
|
137
|
-
});
|
|
138
|
-
const staticDir = path.join(appPath,
|
|
139
|
-
if (await directoryExists(staticDir)) await cp(staticDir, path.join(serverDir,
|
|
328
|
+
}));
|
|
329
|
+
const staticDir = path.join(options.appPath, options.outputRoot, "static");
|
|
330
|
+
if (await directoryExists(staticDir, options.signal)) await unsupportedFilesystemBoundary(options.signal, () => cp(staticDir, path.join(serverDir, options.outputRoot, "static"), {
|
|
140
331
|
recursive: true,
|
|
141
332
|
verbatimSymlinks: true
|
|
142
|
-
});
|
|
333
|
+
}));
|
|
334
|
+
}
|
|
335
|
+
async function findNextStandaloneEntrypoint(artifactDir, signal) {
|
|
336
|
+
const rootEntrypoint = path.join(artifactDir, "server.js");
|
|
337
|
+
if ((await unsupportedFilesystemBoundary(signal, () => stat(rootEntrypoint).catch(() => null)))?.isFile()) return "server.js";
|
|
338
|
+
const candidates = [];
|
|
339
|
+
await walk(artifactDir);
|
|
340
|
+
candidates.sort((left, right) => left.split("/").length - right.split("/").length || left.localeCompare(right));
|
|
341
|
+
const selected = candidates[0];
|
|
342
|
+
if (!selected) throw new Error(`Next.js standalone output did not contain server.js in ${artifactDir}`);
|
|
343
|
+
return selected;
|
|
344
|
+
async function walk(directory) {
|
|
345
|
+
const entries = await unsupportedFilesystemBoundary(signal, () => readdir(directory, { withFileTypes: true }));
|
|
346
|
+
for (const entry of entries) {
|
|
347
|
+
if (entry.name === "node_modules") continue;
|
|
348
|
+
const fullPath = path.join(directory, entry.name);
|
|
349
|
+
if (entry.isDirectory()) {
|
|
350
|
+
await walk(fullPath);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (entry.isFile() && entry.name === "server.js") candidates.push(path.relative(artifactDir, fullPath).split(path.sep).join("/"));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
143
356
|
}
|
|
144
357
|
function nextjsServerSubpath(entrypoint) {
|
|
145
358
|
const dir = path.posix.dirname(entrypoint);
|
|
146
359
|
return dir === "." ? "" : dir;
|
|
147
360
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
361
|
+
/**
|
|
362
|
+
* pnpm and bun (isolated linker) both keep packages in a virtual store with a
|
|
363
|
+
* shared symlink farm (`.pnpm/node_modules`, `.bun/node_modules`). Hoist the
|
|
364
|
+
* farm entries to the artifact's node_modules root so Node-style resolution
|
|
365
|
+
* works after symlinks are materialized.
|
|
366
|
+
*/
|
|
367
|
+
async function hoistIsolatedStoreDependencies(nodeModulesDir, signal) {
|
|
368
|
+
await hoistStoreDependencies(nodeModulesDir, path.join(nodeModulesDir, ".pnpm", "node_modules"), signal);
|
|
369
|
+
await hoistStoreDependencies(nodeModulesDir, path.join(nodeModulesDir, ".bun", "node_modules"), signal);
|
|
370
|
+
}
|
|
371
|
+
async function hoistStoreDependencies(nodeModulesDir, storeNodeModulesDir, signal) {
|
|
372
|
+
if (!await directoryExists(storeNodeModulesDir, signal)) return;
|
|
373
|
+
const entries = await unsupportedFilesystemBoundary(signal, () => readdir(storeNodeModulesDir, { withFileTypes: true }));
|
|
152
374
|
for (const entry of entries) {
|
|
153
|
-
const sourcePath = path.join(
|
|
375
|
+
const sourcePath = path.join(storeNodeModulesDir, entry.name);
|
|
154
376
|
if (entry.name.startsWith("@") && entry.isDirectory()) {
|
|
155
|
-
const scopedEntries = await readdir(sourcePath, { withFileTypes: true });
|
|
377
|
+
const scopedEntries = await unsupportedFilesystemBoundary(signal, () => readdir(sourcePath, { withFileTypes: true }));
|
|
156
378
|
for (const scopedEntry of scopedEntries) {
|
|
157
379
|
const scopedDestination = path.join(nodeModulesDir, entry.name, scopedEntry.name);
|
|
158
|
-
if (await pathExists(scopedDestination)) continue;
|
|
159
|
-
await mkdir(path.dirname(scopedDestination), { recursive: true });
|
|
380
|
+
if (await pathExists(scopedDestination, signal)) continue;
|
|
381
|
+
await unsupportedFilesystemBoundary(signal, () => mkdir(path.dirname(scopedDestination), { recursive: true }));
|
|
160
382
|
await copyPathMaterializingSymlinks(path.join(sourcePath, scopedEntry.name), scopedDestination, {
|
|
161
|
-
standaloneRoot:
|
|
383
|
+
standaloneRoot: storeNodeModulesDir,
|
|
162
384
|
appRoot: nodeModulesDir,
|
|
163
|
-
sourceRoot: nodeModulesDir
|
|
385
|
+
sourceRoot: nodeModulesDir,
|
|
386
|
+
signal
|
|
164
387
|
});
|
|
165
388
|
}
|
|
166
389
|
continue;
|
|
167
390
|
}
|
|
168
391
|
const destinationPath = path.join(nodeModulesDir, entry.name);
|
|
169
|
-
if (await pathExists(destinationPath)) continue;
|
|
392
|
+
if (await pathExists(destinationPath, signal)) continue;
|
|
170
393
|
await copyPathMaterializingSymlinks(sourcePath, destinationPath, {
|
|
171
|
-
standaloneRoot:
|
|
394
|
+
standaloneRoot: storeNodeModulesDir,
|
|
172
395
|
appRoot: nodeModulesDir,
|
|
173
|
-
sourceRoot: nodeModulesDir
|
|
396
|
+
sourceRoot: nodeModulesDir,
|
|
397
|
+
signal
|
|
174
398
|
});
|
|
175
399
|
}
|
|
176
400
|
}
|
|
177
|
-
async function normalizeArtifactSymlinks(artifactDir, appPath) {
|
|
401
|
+
async function normalizeArtifactSymlinks(artifactDir, appPath, signal) {
|
|
178
402
|
const normalizedArtifactDir = path.resolve(artifactDir);
|
|
179
403
|
const normalizedAppPath = path.resolve(appPath);
|
|
180
404
|
await walkDirectory(normalizedArtifactDir);
|
|
181
405
|
async function walkDirectory(directory) {
|
|
182
|
-
const entries = await readdir(directory, { withFileTypes: true });
|
|
406
|
+
const entries = await unsupportedFilesystemBoundary(signal, () => readdir(directory, { withFileTypes: true }));
|
|
183
407
|
for (const entry of entries) {
|
|
184
408
|
const fullPath = path.join(directory, entry.name);
|
|
185
409
|
if (entry.isDirectory()) {
|
|
@@ -187,19 +411,19 @@ async function normalizeArtifactSymlinks(artifactDir, appPath) {
|
|
|
187
411
|
continue;
|
|
188
412
|
}
|
|
189
413
|
if (!entry.isSymbolicLink()) continue;
|
|
190
|
-
const target = await readlink(fullPath);
|
|
414
|
+
const target = await unsupportedFilesystemBoundary(signal, () => readlink(fullPath));
|
|
191
415
|
const resolvedTarget = path.resolve(path.dirname(fullPath), target);
|
|
192
416
|
if (isPathWithin(normalizedArtifactDir, resolvedTarget)) continue;
|
|
193
417
|
if (!isPathWithin(normalizedAppPath, resolvedTarget)) throw new Error(`Build artifact symlink escapes the app directory: ${resolvedTarget}`);
|
|
194
|
-
const targetStat = await stat(resolvedTarget);
|
|
195
|
-
await rm(fullPath, {
|
|
418
|
+
const targetStat = await unsupportedFilesystemBoundary(signal, () => stat(resolvedTarget));
|
|
419
|
+
await unsupportedFilesystemBoundary(signal, () => rm(fullPath, {
|
|
196
420
|
force: true,
|
|
197
421
|
recursive: true
|
|
198
|
-
});
|
|
199
|
-
await cp(resolvedTarget, fullPath, {
|
|
422
|
+
}));
|
|
423
|
+
await unsupportedFilesystemBoundary(signal, () => cp(resolvedTarget, fullPath, {
|
|
200
424
|
recursive: targetStat.isDirectory(),
|
|
201
425
|
dereference: true
|
|
202
|
-
});
|
|
426
|
+
}));
|
|
203
427
|
if (targetStat.isDirectory()) await walkDirectory(fullPath);
|
|
204
428
|
}
|
|
205
429
|
}
|
|
@@ -208,8 +432,11 @@ function isPathWithin(rootPath, candidatePath) {
|
|
|
208
432
|
const relativePath = path.relative(rootPath, candidatePath);
|
|
209
433
|
return relativePath === "" || !relativePath.startsWith(`..${path.sep}`) && relativePath !== ".." && !path.isAbsolute(relativePath);
|
|
210
434
|
}
|
|
435
|
+
function isPathWithinWorkspaceDependency(sourceRoot, candidatePath) {
|
|
436
|
+
return isPathWithin(path.join(sourceRoot, "node_modules"), candidatePath);
|
|
437
|
+
}
|
|
211
438
|
async function copyPathMaterializingSymlinks(sourcePath, destinationPath, options) {
|
|
212
|
-
const sourceStat = await lstat(sourcePath);
|
|
439
|
+
const sourceStat = await unsupportedFilesystemBoundary(options.signal, () => lstat(sourcePath));
|
|
213
440
|
if (sourceStat.isSymbolicLink()) {
|
|
214
441
|
const resolvedTarget = await resolveSymlinkTarget(sourcePath, options);
|
|
215
442
|
if (resolvedTarget === null) return;
|
|
@@ -217,27 +444,27 @@ async function copyPathMaterializingSymlinks(sourcePath, destinationPath, option
|
|
|
217
444
|
return;
|
|
218
445
|
}
|
|
219
446
|
if (sourceStat.isDirectory()) {
|
|
220
|
-
await mkdir(destinationPath, { recursive: true });
|
|
221
|
-
const entries = await readdir(sourcePath, { withFileTypes: true });
|
|
447
|
+
await unsupportedFilesystemBoundary(options.signal, () => mkdir(destinationPath, { recursive: true }));
|
|
448
|
+
const entries = await unsupportedFilesystemBoundary(options.signal, () => readdir(sourcePath, { withFileTypes: true }));
|
|
222
449
|
for (const entry of entries) await copyPathMaterializingSymlinks(path.join(sourcePath, entry.name), path.join(destinationPath, entry.name), options);
|
|
223
450
|
return;
|
|
224
451
|
}
|
|
225
452
|
if (sourceStat.isFile()) {
|
|
226
|
-
await mkdir(path.dirname(destinationPath), { recursive: true });
|
|
227
|
-
await copyFile(sourcePath, destinationPath);
|
|
228
|
-
await chmod(destinationPath, sourceStat.mode);
|
|
453
|
+
await unsupportedFilesystemBoundary(options.signal, () => mkdir(path.dirname(destinationPath), { recursive: true }));
|
|
454
|
+
await unsupportedFilesystemBoundary(options.signal, () => copyFile(sourcePath, destinationPath));
|
|
455
|
+
await unsupportedFilesystemBoundary(options.signal, () => chmod(destinationPath, sourceStat.mode));
|
|
229
456
|
}
|
|
230
457
|
}
|
|
231
458
|
async function resolveSymlinkTarget(symlinkPath, options) {
|
|
232
|
-
const linkTarget = await readlink(symlinkPath);
|
|
459
|
+
const linkTarget = await unsupportedFilesystemBoundary(options.signal, () => readlink(symlinkPath));
|
|
233
460
|
const resolvedTarget = path.resolve(path.dirname(symlinkPath), linkTarget);
|
|
234
|
-
if (await pathExists(resolvedTarget)) {
|
|
235
|
-
if (!isPathWithin(options.appRoot, resolvedTarget) && !
|
|
461
|
+
if (await pathExists(resolvedTarget, options.signal)) {
|
|
462
|
+
if (!isPathWithin(options.appRoot, resolvedTarget) && !isPathWithinWorkspaceDependency(options.sourceRoot, resolvedTarget)) throw new Error(`Build artifact symlink escapes the app directory: ${resolvedTarget}`);
|
|
236
463
|
return resolvedTarget;
|
|
237
464
|
}
|
|
238
465
|
if (isPathWithin(options.standaloneRoot, resolvedTarget)) {
|
|
239
466
|
const fallbackTarget = path.join(options.appRoot, path.relative(options.standaloneRoot, resolvedTarget));
|
|
240
|
-
if (await pathExists(fallbackTarget)) return fallbackTarget;
|
|
467
|
+
if (await pathExists(fallbackTarget, options.signal)) return fallbackTarget;
|
|
241
468
|
}
|
|
242
469
|
if (isPnpmHoistLink(symlinkPath)) return null;
|
|
243
470
|
throw new Error(`Next.js standalone symlink target is missing: ${symlinkPath} -> ${linkTarget} (resolved to ${resolvedTarget})`);
|
|
@@ -247,38 +474,28 @@ function isPnpmHoistLink(symlinkPath) {
|
|
|
247
474
|
for (let i = 0; i < parts.length - 1; i++) if (parts[i] === ".pnpm" && parts[i + 1] === "node_modules") return true;
|
|
248
475
|
return false;
|
|
249
476
|
}
|
|
250
|
-
async function pathExists(targetPath) {
|
|
477
|
+
async function pathExists(targetPath, signal) {
|
|
251
478
|
try {
|
|
252
|
-
await stat(targetPath);
|
|
479
|
+
await unsupportedFilesystemBoundary(signal, () => stat(targetPath));
|
|
253
480
|
return true;
|
|
254
|
-
} catch {
|
|
481
|
+
} catch (error) {
|
|
482
|
+
if (signal?.aborted) throw error;
|
|
255
483
|
return false;
|
|
256
484
|
}
|
|
257
485
|
}
|
|
258
|
-
async function directoryExists(targetPath) {
|
|
486
|
+
async function directoryExists(targetPath, signal) {
|
|
259
487
|
try {
|
|
260
|
-
return (await stat(targetPath)).isDirectory();
|
|
261
|
-
} catch {
|
|
488
|
+
return (await unsupportedFilesystemBoundary(signal, () => stat(targetPath))).isDirectory();
|
|
489
|
+
} catch (error) {
|
|
490
|
+
if (signal?.aborted) throw error;
|
|
262
491
|
return false;
|
|
263
492
|
}
|
|
264
493
|
}
|
|
265
|
-
async function
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (parent === current) return path.resolve(appRoot);
|
|
271
|
-
current = parent;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
async function packageJsonDeclaresWorkspaces(directory) {
|
|
275
|
-
try {
|
|
276
|
-
const content = await readFile(path.join(directory, "package.json"), "utf8");
|
|
277
|
-
const parsed = JSON.parse(content);
|
|
278
|
-
return Boolean(parsed.workspaces);
|
|
279
|
-
} catch {
|
|
280
|
-
return false;
|
|
281
|
-
}
|
|
494
|
+
async function unsupportedFilesystemBoundary(signal, operation) {
|
|
495
|
+
signal?.throwIfAborted();
|
|
496
|
+
const result = await operation();
|
|
497
|
+
signal?.throwIfAborted();
|
|
498
|
+
return result;
|
|
282
499
|
}
|
|
283
500
|
//#endregion
|
|
284
501
|
export { PREVIEW_BUILD_TYPES, PreviewBuildStrategy, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild };
|