@prisma/compute-sdk 0.26.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.
- package/dist/artifact-stage.d.ts +24 -0
- package/dist/artifact-stage.d.ts.map +1 -0
- package/dist/artifact-stage.js +165 -0
- package/dist/artifact-stage.js.map +1 -0
- package/dist/astro-build.d.ts +2 -0
- package/dist/astro-build.d.ts.map +1 -1
- package/dist/astro-build.js +8 -0
- package/dist/astro-build.js.map +1 -1
- package/dist/auto-build.d.ts +7 -4
- package/dist/auto-build.d.ts.map +1 -1
- package/dist/auto-build.js +25 -33
- package/dist/auto-build.js.map +1 -1
- package/dist/build-settings.d.ts +55 -0
- package/dist/build-settings.d.ts.map +1 -0
- package/dist/build-settings.js +301 -0
- package/dist/build-settings.js.map +1 -0
- package/dist/build-strategy.d.ts +4 -0
- package/dist/build-strategy.d.ts.map +1 -1
- package/dist/build-strategy.js +13 -20
- package/dist/build-strategy.js.map +1 -1
- package/dist/build.d.ts +45 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +86 -0
- package/dist/build.js.map +1 -0
- package/dist/bun-build.d.ts +8 -0
- package/dist/bun-build.d.ts.map +1 -1
- package/dist/bun-build.js +47 -23
- package/dist/bun-build.js.map +1 -1
- package/dist/config/frameworks.d.ts +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/nextjs-build.d.ts +12 -2
- package/dist/nextjs-build.d.ts.map +1 -1
- package/dist/nextjs-build.js +156 -75
- package/dist/nextjs-build.js.map +1 -1
- package/dist/nuxt-build.d.ts +2 -0
- package/dist/nuxt-build.d.ts.map +1 -1
- package/dist/nuxt-build.js +8 -0
- package/dist/nuxt-build.js.map +1 -1
- package/dist/tanstack-start-build.d.ts +6 -1
- package/dist/tanstack-start-build.d.ts.map +1 -1
- package/dist/tanstack-start-build.js +35 -15
- package/dist/tanstack-start-build.js.map +1 -1
- package/dist/workspace.d.ts +90 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +205 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +4 -3
- package/src/artifact-stage.ts +280 -0
- package/src/astro-build.ts +11 -1
- package/src/auto-build.ts +28 -34
- package/src/build-settings.ts +430 -0
- package/src/build-strategy.ts +22 -29
- package/src/build.ts +124 -0
- package/src/bun-build.ts +60 -27
- package/src/index.ts +28 -0
- package/src/nextjs-build.ts +206 -103
- package/src/nuxt-build.ts +11 -1
- package/src/tanstack-start-build.ts +44 -18
- package/src/workspace.ts +319 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { type ASTNode, parseModule } from "magicast";
|
|
5
|
+
|
|
6
|
+
import type { FrameworkBuildType } from "./config/frameworks.ts";
|
|
7
|
+
import {
|
|
8
|
+
type PackageManifest,
|
|
9
|
+
readBuildScript,
|
|
10
|
+
readPackageManifest,
|
|
11
|
+
resolvePackageManager,
|
|
12
|
+
} from "./workspace.ts";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The build command and output directory for a framework build, with a
|
|
16
|
+
* human-readable source for each value (for display by a consumer). Resolving
|
|
17
|
+
* settings is location-agnostic: package manager and binaries come from the
|
|
18
|
+
* app's workspace, not the current working directory.
|
|
19
|
+
*/
|
|
20
|
+
export interface BuildSettings {
|
|
21
|
+
buildCommand: string | null;
|
|
22
|
+
buildCommandSource: string | null;
|
|
23
|
+
outputDirectory: string;
|
|
24
|
+
outputDirectorySource: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolves build settings from framework inference: the user's
|
|
29
|
+
* `package.json` build script (run via the detected package manager) over the
|
|
30
|
+
* framework default, plus the framework's output directory (reading
|
|
31
|
+
* `next.config` `distDir` for Next.js).
|
|
32
|
+
*/
|
|
33
|
+
export async function resolveBuildSettings(options: {
|
|
34
|
+
appPath: string;
|
|
35
|
+
buildType: FrameworkBuildType;
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
}): Promise<BuildSettings> {
|
|
38
|
+
switch (options.buildType) {
|
|
39
|
+
// Nuxt and Astro run their framework CLI and stage fixed output; these
|
|
40
|
+
// settings only describe that for display.
|
|
41
|
+
case "nuxt":
|
|
42
|
+
return {
|
|
43
|
+
buildCommand: "nuxt build",
|
|
44
|
+
buildCommandSource: "Nuxt default",
|
|
45
|
+
outputDirectory: ".output",
|
|
46
|
+
outputDirectorySource: "Nuxt output",
|
|
47
|
+
};
|
|
48
|
+
case "astro":
|
|
49
|
+
return {
|
|
50
|
+
buildCommand: "astro build",
|
|
51
|
+
buildCommandSource: "Astro default",
|
|
52
|
+
outputDirectory: "dist",
|
|
53
|
+
outputDirectorySource: "Astro output",
|
|
54
|
+
};
|
|
55
|
+
case "nextjs": {
|
|
56
|
+
const manifest = await readPackageManifest(
|
|
57
|
+
options.appPath,
|
|
58
|
+
options.signal,
|
|
59
|
+
);
|
|
60
|
+
const buildCommand = await resolveFrameworkBuildCommand(
|
|
61
|
+
options.appPath,
|
|
62
|
+
manifest,
|
|
63
|
+
{ command: "next build", source: "Next.js default" },
|
|
64
|
+
options.signal,
|
|
65
|
+
);
|
|
66
|
+
const outputRoot = await resolveNextOutputRoot(
|
|
67
|
+
options.appPath,
|
|
68
|
+
options.signal,
|
|
69
|
+
);
|
|
70
|
+
return {
|
|
71
|
+
buildCommand: buildCommand.command,
|
|
72
|
+
buildCommandSource: buildCommand.source,
|
|
73
|
+
outputDirectory: joinPosix(outputRoot, "standalone"),
|
|
74
|
+
outputDirectorySource:
|
|
75
|
+
outputRoot === ".next" ? "Next.js output" : "next.config distDir",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
case "tanstack-start": {
|
|
79
|
+
const manifest = await readPackageManifest(
|
|
80
|
+
options.appPath,
|
|
81
|
+
options.signal,
|
|
82
|
+
);
|
|
83
|
+
const buildCommand = await resolveFrameworkBuildCommand(
|
|
84
|
+
options.appPath,
|
|
85
|
+
manifest,
|
|
86
|
+
{ command: "vite build", source: "TanStack Start default" },
|
|
87
|
+
options.signal,
|
|
88
|
+
);
|
|
89
|
+
return {
|
|
90
|
+
buildCommand: buildCommand.command,
|
|
91
|
+
buildCommandSource: buildCommand.source,
|
|
92
|
+
outputDirectory: ".output",
|
|
93
|
+
outputDirectorySource: "TanStack Start output",
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
case "bun": {
|
|
97
|
+
const manifest = await readPackageManifest(
|
|
98
|
+
options.appPath,
|
|
99
|
+
options.signal,
|
|
100
|
+
);
|
|
101
|
+
const buildCommand = await resolveFrameworkBuildCommand(
|
|
102
|
+
options.appPath,
|
|
103
|
+
manifest,
|
|
104
|
+
{ command: null, source: null },
|
|
105
|
+
options.signal,
|
|
106
|
+
);
|
|
107
|
+
return {
|
|
108
|
+
buildCommand: buildCommand.command,
|
|
109
|
+
buildCommandSource: buildCommand.source,
|
|
110
|
+
outputDirectory: ".",
|
|
111
|
+
outputDirectorySource: "app root",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Build settings when committed config owns them: configured fields win,
|
|
119
|
+
* omitted fields fall back to framework inference. `source` labels configured
|
|
120
|
+
* values with the config filename so a consumer can show provenance.
|
|
121
|
+
*/
|
|
122
|
+
export async function resolveConfiguredBuildSettings(options: {
|
|
123
|
+
appPath: string;
|
|
124
|
+
buildType: FrameworkBuildType;
|
|
125
|
+
configured: {
|
|
126
|
+
command: string | null | undefined;
|
|
127
|
+
outputDirectory: string | undefined;
|
|
128
|
+
};
|
|
129
|
+
/** Label for configured values, e.g. the config file basename. */
|
|
130
|
+
source: string;
|
|
131
|
+
signal?: AbortSignal;
|
|
132
|
+
}): Promise<BuildSettings> {
|
|
133
|
+
const needsFallback =
|
|
134
|
+
options.configured.command === undefined ||
|
|
135
|
+
options.configured.outputDirectory === undefined;
|
|
136
|
+
const fallback = needsFallback ? await resolveBuildSettings(options) : null;
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
buildCommand:
|
|
140
|
+
options.configured.command !== undefined
|
|
141
|
+
? options.configured.command
|
|
142
|
+
: (fallback as BuildSettings).buildCommand,
|
|
143
|
+
buildCommandSource:
|
|
144
|
+
options.configured.command !== undefined
|
|
145
|
+
? options.source
|
|
146
|
+
: (fallback as BuildSettings).buildCommandSource,
|
|
147
|
+
outputDirectory:
|
|
148
|
+
options.configured.outputDirectory ??
|
|
149
|
+
(fallback as BuildSettings).outputDirectory,
|
|
150
|
+
outputDirectorySource:
|
|
151
|
+
options.configured.outputDirectory !== undefined
|
|
152
|
+
? options.source
|
|
153
|
+
: (fallback as BuildSettings).outputDirectorySource,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function resolveFrameworkBuildCommand(
|
|
158
|
+
appPath: string,
|
|
159
|
+
manifest: PackageManifest | null,
|
|
160
|
+
fallback: { command: string | null; source: string | null },
|
|
161
|
+
signal?: AbortSignal,
|
|
162
|
+
): Promise<{ command: string | null; source: string | null }> {
|
|
163
|
+
const buildScript = readBuildScript(manifest);
|
|
164
|
+
if (buildScript) {
|
|
165
|
+
const packageManager = await resolvePackageManager(appPath, signal);
|
|
166
|
+
return {
|
|
167
|
+
command: packageManager ? `${packageManager} run build` : buildScript,
|
|
168
|
+
source: "package.json scripts.build",
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return fallback;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Joins posix-style path segments, collapsing duplicate separators. */
|
|
175
|
+
export function joinPosix(...parts: string[]): string {
|
|
176
|
+
return parts.join("/").replace(/\/+/g, "/");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Maps a Next.js standalone output directory (e.g. ".next/standalone") back to
|
|
181
|
+
* its output root (".next"), so static assets can be staged alongside it.
|
|
182
|
+
*/
|
|
183
|
+
export function nextOutputRootFromStandaloneDirectory(
|
|
184
|
+
outputDirectory: string,
|
|
185
|
+
): string {
|
|
186
|
+
const normalized = outputDirectory.replace(/\/+$/g, "");
|
|
187
|
+
if (normalized === "standalone") {
|
|
188
|
+
return ".";
|
|
189
|
+
}
|
|
190
|
+
if (normalized.endsWith("/standalone")) {
|
|
191
|
+
const outputRoot = normalized.slice(0, -"/standalone".length);
|
|
192
|
+
return outputRoot.length > 0 ? outputRoot : ".";
|
|
193
|
+
}
|
|
194
|
+
const dirname = path.posix.dirname(normalized);
|
|
195
|
+
return dirname === "." ? "." : dirname;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
interface StaticNextConfig {
|
|
199
|
+
distDir?: string;
|
|
200
|
+
output?: "standalone" | "export";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const NEXT_CONFIG_FILENAMES = [
|
|
204
|
+
"next.config.js",
|
|
205
|
+
"next.config.mjs",
|
|
206
|
+
"next.config.ts",
|
|
207
|
+
"next.config.mts",
|
|
208
|
+
] as const;
|
|
209
|
+
|
|
210
|
+
async function resolveNextOutputRoot(
|
|
211
|
+
appPath: string,
|
|
212
|
+
signal?: AbortSignal,
|
|
213
|
+
): Promise<string> {
|
|
214
|
+
const config = await readNextConfig(appPath, signal);
|
|
215
|
+
return config.distDir ?? ".next";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function readNextConfig(
|
|
219
|
+
appPath: string,
|
|
220
|
+
signal?: AbortSignal,
|
|
221
|
+
): Promise<StaticNextConfig> {
|
|
222
|
+
for (const fileName of NEXT_CONFIG_FILENAMES) {
|
|
223
|
+
signal?.throwIfAborted();
|
|
224
|
+
let content: string;
|
|
225
|
+
try {
|
|
226
|
+
content = await readFile(path.join(appPath, fileName), {
|
|
227
|
+
encoding: "utf8",
|
|
228
|
+
signal,
|
|
229
|
+
});
|
|
230
|
+
} catch (error) {
|
|
231
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
return readStaticNextConfig(content);
|
|
237
|
+
}
|
|
238
|
+
return {};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Best-effort static read of `distDir`/`output` from a next.config source. */
|
|
242
|
+
export function readStaticNextConfig(content: string): StaticNextConfig {
|
|
243
|
+
try {
|
|
244
|
+
const module = parseModule(content);
|
|
245
|
+
const program = asAstNode(module.$ast);
|
|
246
|
+
const bindings = program
|
|
247
|
+
? collectStaticBindings(program)
|
|
248
|
+
: new Map<string, AstNode>();
|
|
249
|
+
const configObject = program
|
|
250
|
+
? findExportedConfigObject(program, bindings)
|
|
251
|
+
: null;
|
|
252
|
+
if (!configObject) {
|
|
253
|
+
return {};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const rawDistDir = readStaticStringProperty(configObject, "distDir");
|
|
257
|
+
const output = readStaticStringProperty(configObject, "output");
|
|
258
|
+
return {
|
|
259
|
+
distDir: rawDistDir ? normalizeRelativePath(rawDistDir) : undefined,
|
|
260
|
+
output:
|
|
261
|
+
output === "standalone" || output === "export" ? output : undefined,
|
|
262
|
+
};
|
|
263
|
+
} catch {
|
|
264
|
+
return {};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function normalizeRelativePath(value: string): string | undefined {
|
|
269
|
+
const raw = value.trim().replace(/\\/g, "/");
|
|
270
|
+
if (raw.length === 0 || raw.split("/").includes("..")) {
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
if (/^[A-Za-z]:/.test(raw)) {
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
const normalized = path.posix.normalize(raw);
|
|
277
|
+
if (
|
|
278
|
+
path.win32.isAbsolute(value) ||
|
|
279
|
+
path.posix.isAbsolute(normalized) ||
|
|
280
|
+
normalized.split("/").includes("..")
|
|
281
|
+
) {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
return normalized === "." ? "." : normalized;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
type AstNode = ASTNode & { type: string; [key: string]: unknown };
|
|
288
|
+
|
|
289
|
+
function asAstNode(value: unknown): AstNode | null {
|
|
290
|
+
if (!value || typeof value !== "object") {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
const type = (value as { type?: unknown }).type;
|
|
294
|
+
return typeof type === "string" ? (value as AstNode) : null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function astNodes(value: unknown): AstNode[] {
|
|
298
|
+
if (!Array.isArray(value)) {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
return value.map(asAstNode).filter((node): node is AstNode => Boolean(node));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function collectStaticBindings(program: AstNode): Map<string, AstNode> {
|
|
305
|
+
const bindings = new Map<string, AstNode>();
|
|
306
|
+
for (const statement of astNodes(program.body)) {
|
|
307
|
+
if (statement.type !== "VariableDeclaration") {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
for (const declaration of astNodes(statement.declarations)) {
|
|
311
|
+
const id = asAstNode(declaration.id);
|
|
312
|
+
const init = asAstNode(declaration.init);
|
|
313
|
+
if (id?.type === "Identifier" && typeof id.name === "string" && init) {
|
|
314
|
+
bindings.set(id.name, init);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return bindings;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function findExportedConfigObject(
|
|
322
|
+
program: AstNode,
|
|
323
|
+
bindings: Map<string, AstNode>,
|
|
324
|
+
): AstNode | null {
|
|
325
|
+
for (const statement of astNodes(program.body)) {
|
|
326
|
+
if (statement.type === "ExportDefaultDeclaration") {
|
|
327
|
+
return resolveConfigObject(statement.declaration, bindings);
|
|
328
|
+
}
|
|
329
|
+
if (statement.type !== "ExpressionStatement") {
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const expression = asAstNode(statement.expression);
|
|
333
|
+
if (
|
|
334
|
+
expression?.type !== "AssignmentExpression" ||
|
|
335
|
+
expression.operator !== "="
|
|
336
|
+
) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (isModuleExports(expression.left)) {
|
|
340
|
+
return resolveConfigObject(expression.right, bindings);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function resolveConfigObject(
|
|
347
|
+
value: unknown,
|
|
348
|
+
bindings: Map<string, AstNode>,
|
|
349
|
+
depth = 0,
|
|
350
|
+
): AstNode | null {
|
|
351
|
+
if (depth > 4) {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
const node = unwrapStaticExpression(asAstNode(value));
|
|
355
|
+
if (!node) {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
if (node.type === "ObjectExpression") {
|
|
359
|
+
return node;
|
|
360
|
+
}
|
|
361
|
+
if (node.type === "Identifier" && typeof node.name === "string") {
|
|
362
|
+
return resolveConfigObject(bindings.get(node.name), bindings, depth + 1);
|
|
363
|
+
}
|
|
364
|
+
if (node.type === "CallExpression") {
|
|
365
|
+
return resolveConfigObject(
|
|
366
|
+
astNodes(node.arguments)[0],
|
|
367
|
+
bindings,
|
|
368
|
+
depth + 1,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function unwrapStaticExpression(node: AstNode | null): AstNode | null {
|
|
375
|
+
let current = node;
|
|
376
|
+
while (
|
|
377
|
+
current?.type === "TSAsExpression" ||
|
|
378
|
+
current?.type === "TSSatisfiesExpression" ||
|
|
379
|
+
current?.type === "TSNonNullExpression"
|
|
380
|
+
) {
|
|
381
|
+
current = asAstNode(current.expression);
|
|
382
|
+
}
|
|
383
|
+
return current;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function isModuleExports(value: unknown): boolean {
|
|
387
|
+
const node = asAstNode(value);
|
|
388
|
+
if (node?.type !== "MemberExpression" || node.computed === true) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
const object = asAstNode(node.object);
|
|
392
|
+
const property = asAstNode(node.property);
|
|
393
|
+
return (
|
|
394
|
+
object?.type === "Identifier" &&
|
|
395
|
+
object.name === "module" &&
|
|
396
|
+
property?.type === "Identifier" &&
|
|
397
|
+
property.name === "exports"
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function readStaticStringProperty(
|
|
402
|
+
objectExpression: AstNode,
|
|
403
|
+
propertyName: string,
|
|
404
|
+
): string | undefined {
|
|
405
|
+
for (const property of astNodes(objectExpression.properties)) {
|
|
406
|
+
if (property.type !== "ObjectProperty" || property.computed === true) {
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (propertyKeyName(property.key) !== propertyName) {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
const value = unwrapStaticExpression(asAstNode(property.value));
|
|
413
|
+
if (value?.type === "StringLiteral" && typeof value.value === "string") {
|
|
414
|
+
const trimmed = value.value.trim();
|
|
415
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return undefined;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function propertyKeyName(value: unknown): string | undefined {
|
|
422
|
+
const key = asAstNode(value);
|
|
423
|
+
if (key?.type === "Identifier" && typeof key.name === "string") {
|
|
424
|
+
return key.name;
|
|
425
|
+
}
|
|
426
|
+
if (key?.type === "StringLiteral" && typeof key.value === "string") {
|
|
427
|
+
return key.value;
|
|
428
|
+
}
|
|
429
|
+
return undefined;
|
|
430
|
+
}
|
package/src/build-strategy.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
1
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import type { PortMapping } from "./types.ts";
|
|
4
|
+
import { buildCommandEnv, runChildProcess } from "./workspace.ts";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* The result of executing a build strategy.
|
|
@@ -159,8 +159,20 @@ export async function runPackageCli(opts: {
|
|
|
159
159
|
failurePrefix: string;
|
|
160
160
|
/** Thrown when no launcher is available. */
|
|
161
161
|
missingMessage: string;
|
|
162
|
+
/** Extra environment merged over `process.env` for the build child. */
|
|
163
|
+
env?: NodeJS.ProcessEnv;
|
|
164
|
+
/** Per-line tap for build output (stdout/stderr). */
|
|
165
|
+
onOutput?: (line: string, source: "stdout" | "stderr") => void;
|
|
162
166
|
signal?: AbortSignal;
|
|
163
167
|
}): Promise<void> {
|
|
168
|
+
// Every ancestor `node_modules/.bin` on PATH plus any injected deploy vars,
|
|
169
|
+
// so a workspace-hoisted CLI resolves and the build sees its deploy env.
|
|
170
|
+
const env = await buildCommandEnv(
|
|
171
|
+
opts.appPath,
|
|
172
|
+
{ ...process.env, ...opts.env },
|
|
173
|
+
opts.signal,
|
|
174
|
+
);
|
|
175
|
+
|
|
164
176
|
const localBin = path.join(
|
|
165
177
|
opts.appPath,
|
|
166
178
|
"node_modules",
|
|
@@ -176,7 +188,15 @@ export async function runPackageCli(opts: {
|
|
|
176
188
|
for (const { command, args } of candidates) {
|
|
177
189
|
opts.signal?.throwIfAborted();
|
|
178
190
|
try {
|
|
179
|
-
await
|
|
191
|
+
await runChildProcess({
|
|
192
|
+
command,
|
|
193
|
+
args,
|
|
194
|
+
cwd: opts.appPath,
|
|
195
|
+
env,
|
|
196
|
+
failurePrefix: opts.failurePrefix,
|
|
197
|
+
onOutput: opts.onOutput,
|
|
198
|
+
signal: opts.signal,
|
|
199
|
+
});
|
|
180
200
|
return;
|
|
181
201
|
} catch (error) {
|
|
182
202
|
if (isENOENT(error)) continue;
|
|
@@ -198,30 +218,3 @@ function isENOENT(error: unknown): boolean {
|
|
|
198
218
|
(error as { code?: string }).code === "ENOENT"
|
|
199
219
|
);
|
|
200
220
|
}
|
|
201
|
-
|
|
202
|
-
function exec(
|
|
203
|
-
command: string,
|
|
204
|
-
args: string[],
|
|
205
|
-
cwd: string,
|
|
206
|
-
failurePrefix: string,
|
|
207
|
-
signal?: AbortSignal,
|
|
208
|
-
): Promise<void> {
|
|
209
|
-
return new Promise((resolve, reject) => {
|
|
210
|
-
execFile(command, args, { cwd, signal }, (error, _stdout, stderr) => {
|
|
211
|
-
if (error) {
|
|
212
|
-
if ("code" in error && error.code === "ENOENT") {
|
|
213
|
-
reject(
|
|
214
|
-
Object.assign(new Error(`${command} not found`), {
|
|
215
|
-
code: "ENOENT",
|
|
216
|
-
}),
|
|
217
|
-
);
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
const message = stderr.trim() || error.message;
|
|
221
|
-
reject(new Error(`${failurePrefix} build failed:\n${message}`));
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
resolve();
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
}
|
package/src/build.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { AstroBuild } from "./astro-build.ts";
|
|
2
|
+
import type { BuildSettings } from "./build-settings.ts";
|
|
3
|
+
import type { BuildStrategy } from "./build-strategy.ts";
|
|
4
|
+
import { BunBuild } from "./bun-build.ts";
|
|
5
|
+
import { FRAMEWORKS, type FrameworkBuildType } from "./config/frameworks.ts";
|
|
6
|
+
import { NextjsBuild } from "./nextjs-build.ts";
|
|
7
|
+
import { NuxtBuild } from "./nuxt-build.ts";
|
|
8
|
+
import { TanstackStartBuild } from "./tanstack-start-build.ts";
|
|
9
|
+
import type { BuildCommandIo } from "./workspace.ts";
|
|
10
|
+
|
|
11
|
+
/** A concrete framework build type, or `"auto"` to detect it. */
|
|
12
|
+
export type BuildType = FrameworkBuildType | "auto";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Consumer hooks shared across `createBuildStrategy` / `resolveBuildStrategy`.
|
|
16
|
+
* All optional: the CLI passes none, while a headless consumer injects build
|
|
17
|
+
* `env`, taps output, and requires standalone Next output.
|
|
18
|
+
*/
|
|
19
|
+
export interface BuildStrategyHooks {
|
|
20
|
+
/** Build `env` and per-line output tap, forwarded to the build command. */
|
|
21
|
+
io?: BuildCommandIo;
|
|
22
|
+
/** Fail instead of falling back to the full-tree Next `next start` artifact. */
|
|
23
|
+
requireStandalone?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Detection order, derived from the framework registry so a new framework is
|
|
28
|
+
* picked up automatically. `bun` is forced last — it is the universal fallback
|
|
29
|
+
* (its `canBuild` always returns true).
|
|
30
|
+
*/
|
|
31
|
+
const DETECTION_ORDER: FrameworkBuildType[] = (() => {
|
|
32
|
+
const buildTypes = [
|
|
33
|
+
...new Set(FRAMEWORKS.map((framework) => framework.buildType)),
|
|
34
|
+
];
|
|
35
|
+
return [...buildTypes.filter((type) => type !== "bun"), "bun"];
|
|
36
|
+
})();
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Constructs the build strategy for an explicit framework build type, wiring
|
|
40
|
+
* through optional committed build settings and (for bun) the entrypoint.
|
|
41
|
+
*/
|
|
42
|
+
export function createBuildStrategy(
|
|
43
|
+
options: {
|
|
44
|
+
appPath: string;
|
|
45
|
+
buildType: FrameworkBuildType;
|
|
46
|
+
entrypoint?: string;
|
|
47
|
+
buildSettings?: BuildSettings;
|
|
48
|
+
} & BuildStrategyHooks,
|
|
49
|
+
): BuildStrategy {
|
|
50
|
+
const { appPath, buildSettings, io } = options;
|
|
51
|
+
switch (options.buildType) {
|
|
52
|
+
case "nextjs":
|
|
53
|
+
return new NextjsBuild({
|
|
54
|
+
appPath,
|
|
55
|
+
buildSettings,
|
|
56
|
+
io,
|
|
57
|
+
requireStandalone: options.requireStandalone,
|
|
58
|
+
});
|
|
59
|
+
case "nuxt":
|
|
60
|
+
return new NuxtBuild({ appPath, io });
|
|
61
|
+
case "astro":
|
|
62
|
+
return new AstroBuild({ appPath, io });
|
|
63
|
+
case "tanstack-start":
|
|
64
|
+
return new TanstackStartBuild({ appPath, buildSettings, io });
|
|
65
|
+
case "bun":
|
|
66
|
+
return new BunBuild({
|
|
67
|
+
appPath,
|
|
68
|
+
entrypoint: options.entrypoint,
|
|
69
|
+
buildSettings,
|
|
70
|
+
io,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Resolves the build strategy and the concrete build type for an app. With an
|
|
77
|
+
* explicit `buildType` the matching strategy is returned directly; with
|
|
78
|
+
* `"auto"` (or omitted) each framework is tried in detection order and the
|
|
79
|
+
* first match wins, with bun as the fallback. Returning the resolved build
|
|
80
|
+
* type lets a consumer report what it detected.
|
|
81
|
+
*/
|
|
82
|
+
export async function resolveBuildStrategy(
|
|
83
|
+
options: {
|
|
84
|
+
appPath: string;
|
|
85
|
+
buildType?: BuildType;
|
|
86
|
+
entrypoint?: string;
|
|
87
|
+
buildSettings?: BuildSettings;
|
|
88
|
+
signal?: AbortSignal;
|
|
89
|
+
} & BuildStrategyHooks,
|
|
90
|
+
): Promise<{ strategy: BuildStrategy; buildType: FrameworkBuildType }> {
|
|
91
|
+
const shared = {
|
|
92
|
+
appPath: options.appPath,
|
|
93
|
+
entrypoint: options.entrypoint,
|
|
94
|
+
buildSettings: options.buildSettings,
|
|
95
|
+
io: options.io,
|
|
96
|
+
requireStandalone: options.requireStandalone,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
if (options.buildType && options.buildType !== "auto") {
|
|
100
|
+
return {
|
|
101
|
+
buildType: options.buildType,
|
|
102
|
+
strategy: createBuildStrategy({
|
|
103
|
+
...shared,
|
|
104
|
+
buildType: options.buildType,
|
|
105
|
+
}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const buildType of DETECTION_ORDER) {
|
|
110
|
+
if (buildType === "bun") {
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
const strategy = createBuildStrategy({ ...shared, buildType });
|
|
114
|
+
options.signal?.throwIfAborted();
|
|
115
|
+
if (await strategy.canBuild(options.signal)) {
|
|
116
|
+
return { buildType, strategy };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
buildType: "bun",
|
|
122
|
+
strategy: createBuildStrategy({ ...shared, buildType: "bun" }),
|
|
123
|
+
};
|
|
124
|
+
}
|