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