@vercel/backends 0.1.0-canary.20260211174907.cdd2da6 → 0.1.1
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/index.d.mts +4 -1
- package/dist/index.mjs +763 -200
- package/dist/rolldown/index.mjs +16 -5
- package/package.json +6 -3
package/dist/index.d.mts
CHANGED
|
@@ -97,9 +97,12 @@ declare const introspectApp: (args: {
|
|
|
97
97
|
additionalDeps: string[];
|
|
98
98
|
}>;
|
|
99
99
|
//#endregion
|
|
100
|
+
//#region src/diagnostics.d.ts
|
|
101
|
+
declare const diagnostics: _vercel_build_utils0.Diagnostics;
|
|
102
|
+
//#endregion
|
|
100
103
|
//#region src/index.d.ts
|
|
101
104
|
declare const version = 2;
|
|
102
105
|
declare const build: BuildV2;
|
|
103
106
|
declare const prepareCache: PrepareCache;
|
|
104
107
|
//#endregion
|
|
105
|
-
export { type CervelBuildOptions, type CervelServeOptions, type PathOptions, build, build$1 as cervelBuild, serve as cervelServe, findEntrypoint, findEntrypointOrThrow, getBuildSummary, introspectApp, nodeFileTrace, prepareCache, srvxOptions, version };
|
|
108
|
+
export { type CervelBuildOptions, type CervelServeOptions, type PathOptions, build, build$1 as cervelBuild, serve as cervelServe, diagnostics, findEntrypoint, findEntrypointOrThrow, getBuildSummary, introspectApp, nodeFileTrace, prepareCache, srvxOptions, version };
|
package/dist/index.mjs
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { builtinModules, createRequire } from "node:module";
|
|
2
|
-
import { delimiter, dirname,
|
|
3
|
-
import { FileBlob, FileFsRef, NodejsLambda, Span, debug, defaultCachePathGlob, download, execCommand, getEnvForPackageManager, getNodeBinPaths, getNodeVersion, glob, isBackendFramework, isBunVersion, isExperimentalBackendsWithoutIntrospectionEnabled, runNpmInstall, runPackageJsonScript, scanParentDirs } from "@vercel/build-utils";
|
|
2
|
+
import path, { delimiter, dirname, join } from "path";
|
|
3
|
+
import { FileBlob, FileFsRef, MANIFEST_VERSION, NodejsLambda, Span, createDiagnostics, debug, defaultCachePathGlob, download, execCommand, getEnvForPackageManager, getLambdaOptionsFromFunction, getNodeBinPaths, getNodeVersion, glob, isBackendFramework, isBunVersion, isExperimentalBackendsWithoutIntrospectionEnabled, runNpmInstall, runPackageJsonScript, scanParentDirs, writeProjectManifest } from "@vercel/build-utils";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import yaml from "js-yaml";
|
|
6
|
+
import { parseSyml } from "@yarnpkg/parsers";
|
|
4
7
|
import { createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
8
|
import { lstat, readFile, rm, stat } from "node:fs/promises";
|
|
6
|
-
import { dirname as dirname$1, extname
|
|
9
|
+
import { basename, dirname as dirname$1, extname, isAbsolute, join as join$1, relative, resolve, sep } from "node:path";
|
|
7
10
|
import { build as build$2 } from "rolldown";
|
|
8
11
|
import { exports } from "resolve.exports";
|
|
9
12
|
import { isNativeError } from "node:util/types";
|
|
10
|
-
import { nodeFileTrace as nodeFileTrace$1, resolve } from "@vercel/nft";
|
|
13
|
+
import { nodeFileTrace as nodeFileTrace$1, resolve as resolve$1 } from "@vercel/nft";
|
|
11
14
|
import { transform } from "oxc-transform";
|
|
12
|
-
import { createRequire as createRequire$1 } from "module";
|
|
13
|
-
import { spawn } from "child_process";
|
|
14
|
-
import { existsSync as existsSync$1 } from "fs";
|
|
15
15
|
import execa from "execa";
|
|
16
16
|
import { readFile as readFile$1, writeFile } from "fs/promises";
|
|
17
|
-
import { spawn
|
|
17
|
+
import { spawn } from "node:child_process";
|
|
18
18
|
import { tmpdir } from "node:os";
|
|
19
19
|
import { z } from "zod";
|
|
20
20
|
|
|
@@ -23,7 +23,7 @@ async function downloadInstallAndBundle(args) {
|
|
|
23
23
|
const { entrypoint, files, workPath, meta, config, repoRootPath } = args;
|
|
24
24
|
await download(files, workPath, meta);
|
|
25
25
|
const entrypointFsDirname = join(workPath, dirname(entrypoint));
|
|
26
|
-
const { cliType, lockfileVersion, packageJsonPackageManager, turboSupportsCorepackHome } = await scanParentDirs(entrypointFsDirname, true, repoRootPath);
|
|
26
|
+
const { cliType, lockfilePath, lockfileVersion, packageJsonPackageManager, turboSupportsCorepackHome } = await scanParentDirs(entrypointFsDirname, true, repoRootPath);
|
|
27
27
|
const spawnEnv = getEnvForPackageManager({
|
|
28
28
|
cliType,
|
|
29
29
|
lockfileVersion,
|
|
@@ -43,10 +43,13 @@ async function downloadInstallAndBundle(args) {
|
|
|
43
43
|
else await runNpmInstall(entrypointFsDirname, [], { env: spawnEnv }, meta, config.projectSettings?.createdAt);
|
|
44
44
|
return {
|
|
45
45
|
entrypointFsDirname,
|
|
46
|
-
spawnEnv
|
|
46
|
+
spawnEnv,
|
|
47
|
+
cliType,
|
|
48
|
+
lockfilePath,
|
|
49
|
+
lockfileVersion
|
|
47
50
|
};
|
|
48
51
|
}
|
|
49
|
-
async function maybeExecBuildCommand(args, { spawnEnv
|
|
52
|
+
async function maybeExecBuildCommand(args, { spawnEnv }) {
|
|
50
53
|
const projectBuildCommand = args.config.projectSettings?.buildCommand;
|
|
51
54
|
if (projectBuildCommand) {
|
|
52
55
|
const nodeBinPath = getNodeBinPaths({
|
|
@@ -61,8 +64,351 @@ async function maybeExecBuildCommand(args, { spawnEnv, entrypointFsDirname }) {
|
|
|
61
64
|
cwd: args.workPath
|
|
62
65
|
});
|
|
63
66
|
}
|
|
64
|
-
return runPackageJsonScript(
|
|
67
|
+
return runPackageJsonScript(args.workPath, ["build"], { env: spawnEnv }, args.config.projectSettings?.createdAt);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/diagnostics.ts
|
|
72
|
+
function classifySource(resolvedUrl) {
|
|
73
|
+
if (!resolvedUrl) return {};
|
|
74
|
+
if (resolvedUrl.startsWith("file:")) return {
|
|
75
|
+
source: "file",
|
|
76
|
+
sourceUrl: resolvedUrl.slice(5)
|
|
77
|
+
};
|
|
78
|
+
if (resolvedUrl.startsWith("git+") || resolvedUrl.startsWith("git://")) return {
|
|
79
|
+
source: "git",
|
|
80
|
+
sourceUrl: resolvedUrl.replace(/^git\+/, "")
|
|
81
|
+
};
|
|
82
|
+
try {
|
|
83
|
+
return {
|
|
84
|
+
source: "registry",
|
|
85
|
+
sourceUrl: new URL(resolvedUrl).origin
|
|
86
|
+
};
|
|
87
|
+
} catch {
|
|
88
|
+
return {};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function npmEntryScopes(entry) {
|
|
92
|
+
const scopes = [];
|
|
93
|
+
if (entry.dev) scopes.push("dev");
|
|
94
|
+
if (entry.peer) scopes.push("peer");
|
|
95
|
+
if (entry.optional) scopes.push("optional");
|
|
96
|
+
if (scopes.length === 0) scopes.push("prod");
|
|
97
|
+
return scopes;
|
|
98
|
+
}
|
|
99
|
+
function parseNpmLock(content, lockfileVersion) {
|
|
100
|
+
const lockMap = /* @__PURE__ */ new Map();
|
|
101
|
+
const parsed = JSON.parse(content);
|
|
102
|
+
if ((lockfileVersion ?? parsed.lockfileVersion ?? 1) >= 2) {
|
|
103
|
+
const packages = parsed.packages;
|
|
104
|
+
if (!packages) return lockMap;
|
|
105
|
+
for (const [key, entry] of Object.entries(packages)) {
|
|
106
|
+
if (key === "") continue;
|
|
107
|
+
if (!key.startsWith("node_modules/")) continue;
|
|
108
|
+
if (entry.link === true) continue;
|
|
109
|
+
const rest = key.slice(13);
|
|
110
|
+
const isScoped = rest.startsWith("@");
|
|
111
|
+
const slashCount = (rest.match(/\//g) ?? []).length;
|
|
112
|
+
if (isScoped ? slashCount !== 1 : slashCount !== 0) continue;
|
|
113
|
+
const resolved = entry.resolved;
|
|
114
|
+
if (resolved?.startsWith("file:")) continue;
|
|
115
|
+
const version$1 = entry.version ?? "";
|
|
116
|
+
const existing = lockMap.get(rest);
|
|
117
|
+
if (existing && !isHigherVersion(version$1, existing.resolved)) continue;
|
|
118
|
+
const { source, sourceUrl } = classifySource(resolved);
|
|
119
|
+
const lockEntry = {
|
|
120
|
+
resolved: version$1,
|
|
121
|
+
scopes: npmEntryScopes(entry)
|
|
122
|
+
};
|
|
123
|
+
if (source) lockEntry.source = source;
|
|
124
|
+
if (sourceUrl) lockEntry.sourceUrl = sourceUrl;
|
|
125
|
+
lockMap.set(rest, lockEntry);
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
const dependencies = parsed.dependencies;
|
|
129
|
+
if (!dependencies) return lockMap;
|
|
130
|
+
const walk = (deps) => {
|
|
131
|
+
for (const [name, entry] of Object.entries(deps)) {
|
|
132
|
+
const resolved = entry.resolved;
|
|
133
|
+
if (!resolved?.startsWith("file:")) {
|
|
134
|
+
const version$1 = entry.version ?? "";
|
|
135
|
+
const existing = lockMap.get(name);
|
|
136
|
+
if (!existing || isHigherVersion(version$1, existing.resolved)) {
|
|
137
|
+
const { source, sourceUrl } = classifySource(resolved);
|
|
138
|
+
const lockEntry = {
|
|
139
|
+
resolved: version$1,
|
|
140
|
+
scopes: npmEntryScopes(entry)
|
|
141
|
+
};
|
|
142
|
+
if (source) lockEntry.source = source;
|
|
143
|
+
if (sourceUrl) lockEntry.sourceUrl = sourceUrl;
|
|
144
|
+
lockMap.set(name, lockEntry);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const nested = entry.dependencies;
|
|
148
|
+
if (nested) walk(nested);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
walk(dependencies);
|
|
152
|
+
}
|
|
153
|
+
return lockMap;
|
|
154
|
+
}
|
|
155
|
+
function isHigherVersion(a, b) {
|
|
156
|
+
const seg = (v) => v.split(/[.\-+]/).map((s) => parseInt(s, 10) || 0);
|
|
157
|
+
const pa = seg(a);
|
|
158
|
+
const pb = seg(b);
|
|
159
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
160
|
+
if ((pa[i] ?? 0) > (pb[i] ?? 0)) return true;
|
|
161
|
+
if ((pa[i] ?? 0) < (pb[i] ?? 0)) return false;
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
function extractPackageName(spec) {
|
|
166
|
+
const s = spec.replace(/\(.*\)$/, "");
|
|
167
|
+
if (s.startsWith("@")) {
|
|
168
|
+
const i$1 = s.indexOf("@", 1);
|
|
169
|
+
return i$1 === -1 ? null : s.slice(0, i$1);
|
|
170
|
+
}
|
|
171
|
+
const i = s.lastIndexOf("@");
|
|
172
|
+
return i <= 0 ? null : s.slice(0, i);
|
|
173
|
+
}
|
|
174
|
+
function parsePnpmV9Key(key) {
|
|
175
|
+
const name = extractPackageName(key);
|
|
176
|
+
if (!name) return null;
|
|
177
|
+
return {
|
|
178
|
+
name,
|
|
179
|
+
version: key.replace(/\(.*\)$/, "").slice(name.length + 1)
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function parsePnpmV5Key(key) {
|
|
183
|
+
if (!key.startsWith("/")) return null;
|
|
184
|
+
const rest = key.slice(1);
|
|
185
|
+
if (rest.startsWith("@")) {
|
|
186
|
+
const firstSlash = rest.indexOf("/");
|
|
187
|
+
if (firstSlash === -1) return null;
|
|
188
|
+
const secondSlash = rest.indexOf("/", firstSlash + 1);
|
|
189
|
+
if (secondSlash === -1) return null;
|
|
190
|
+
return {
|
|
191
|
+
name: rest.slice(0, secondSlash),
|
|
192
|
+
version: rest.slice(secondSlash + 1).split("_")[0]
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const slashIndex = rest.indexOf("/");
|
|
196
|
+
if (slashIndex === -1) return null;
|
|
197
|
+
return {
|
|
198
|
+
name: rest.slice(0, slashIndex),
|
|
199
|
+
version: rest.slice(slashIndex + 1).split("_")[0]
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function parsePnpmV6Key(key) {
|
|
203
|
+
if (!key.startsWith("/")) return null;
|
|
204
|
+
const rest = key.slice(1);
|
|
205
|
+
let atIdx;
|
|
206
|
+
if (rest.startsWith("@")) atIdx = rest.indexOf("@", 1);
|
|
207
|
+
else atIdx = rest.indexOf("@");
|
|
208
|
+
if (atIdx === -1) return null;
|
|
209
|
+
return {
|
|
210
|
+
name: rest.slice(0, atIdx),
|
|
211
|
+
version: rest.slice(atIdx + 1).split("_")[0].replace(/\(.*\)$/, "")
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function classifyPnpmResolution(resolution) {
|
|
215
|
+
if (!resolution) return {};
|
|
216
|
+
if (resolution.type === "directory" || resolution.directory) return { local: true };
|
|
217
|
+
if (typeof resolution.tarball === "string") return classifySource(resolution.tarball);
|
|
218
|
+
if (resolution.type === "git" || typeof resolution.repo === "string") return {
|
|
219
|
+
source: "git",
|
|
220
|
+
sourceUrl: resolution.repo ?? void 0
|
|
221
|
+
};
|
|
222
|
+
return {};
|
|
223
|
+
}
|
|
224
|
+
function parsePnpmLock(content, lockfileVersion) {
|
|
225
|
+
const lockMap = /* @__PURE__ */ new Map();
|
|
226
|
+
const docs = [];
|
|
227
|
+
yaml.safeLoadAll(content, (doc) => docs.push(doc));
|
|
228
|
+
const parsedYaml = docs[0];
|
|
229
|
+
if (!parsedYaml) return lockMap;
|
|
230
|
+
const lv = lockfileVersion ?? Number(parsedYaml.lockfileVersion ?? "0");
|
|
231
|
+
const packages = parsedYaml.packages;
|
|
232
|
+
if (!packages) return lockMap;
|
|
233
|
+
const parseKey = lv >= 9 ? parsePnpmV9Key : lv >= 6 ? parsePnpmV6Key : parsePnpmV5Key;
|
|
234
|
+
for (const [key, entry] of Object.entries(packages)) {
|
|
235
|
+
const keyParsed = parseKey(key);
|
|
236
|
+
if (!keyParsed) continue;
|
|
237
|
+
const { name, version: version$1 } = keyParsed;
|
|
238
|
+
const resolution = entry.resolution;
|
|
239
|
+
const { local, source, sourceUrl } = classifyPnpmResolution(resolution);
|
|
240
|
+
if (local) continue;
|
|
241
|
+
const existing = lockMap.get(name);
|
|
242
|
+
if (existing && !isHigherVersion(version$1, existing.resolved)) continue;
|
|
243
|
+
const lockEntry = {
|
|
244
|
+
resolved: version$1,
|
|
245
|
+
scopes: ["prod"]
|
|
246
|
+
};
|
|
247
|
+
if (source) lockEntry.source = source;
|
|
248
|
+
if (sourceUrl) lockEntry.sourceUrl = sourceUrl;
|
|
249
|
+
lockMap.set(name, lockEntry);
|
|
250
|
+
}
|
|
251
|
+
return lockMap;
|
|
252
|
+
}
|
|
253
|
+
function parseYarnLock(content, lockfileVersion) {
|
|
254
|
+
const lockMap = /* @__PURE__ */ new Map();
|
|
255
|
+
const isBerry = (lockfileVersion ?? 1) >= 2;
|
|
256
|
+
const parsed = parseSyml(content);
|
|
257
|
+
for (const [key, entry] of Object.entries(parsed)) {
|
|
258
|
+
if (key === "__metadata" || !entry) continue;
|
|
259
|
+
if (isBerry && entry.linkType === "soft") continue;
|
|
260
|
+
const version$1 = entry.version;
|
|
261
|
+
if (!version$1) continue;
|
|
262
|
+
let source;
|
|
263
|
+
let sourceUrl;
|
|
264
|
+
if (!isBerry && entry.resolved) {
|
|
265
|
+
if (entry.resolved.startsWith("file:")) continue;
|
|
266
|
+
const classified = classifySource(entry.resolved);
|
|
267
|
+
source = classified.source;
|
|
268
|
+
sourceUrl = classified.sourceUrl;
|
|
269
|
+
}
|
|
270
|
+
const specifiers = key.split(",").map((s) => s.trim().replace(/^"|"$/g, ""));
|
|
271
|
+
let name = null;
|
|
272
|
+
for (const spec of specifiers) {
|
|
273
|
+
name = extractPackageName(spec);
|
|
274
|
+
if (name) break;
|
|
275
|
+
}
|
|
276
|
+
if (!name) continue;
|
|
277
|
+
const existing = lockMap.get(name);
|
|
278
|
+
if (existing && !isHigherVersion(version$1, existing.resolved)) continue;
|
|
279
|
+
const lockEntry = {
|
|
280
|
+
resolved: version$1,
|
|
281
|
+
scopes: ["prod"]
|
|
282
|
+
};
|
|
283
|
+
if (source) lockEntry.source = source;
|
|
284
|
+
if (sourceUrl) lockEntry.sourceUrl = sourceUrl;
|
|
285
|
+
lockMap.set(name, lockEntry);
|
|
286
|
+
}
|
|
287
|
+
return lockMap;
|
|
288
|
+
}
|
|
289
|
+
function parseBunLock(content) {
|
|
290
|
+
const lockMap = /* @__PURE__ */ new Map();
|
|
291
|
+
const json = content.replace(/,(\s*[}\]])/g, "$1");
|
|
292
|
+
const packages = JSON.parse(json).packages;
|
|
293
|
+
if (!packages) return lockMap;
|
|
294
|
+
for (const [name, value] of Object.entries(packages)) {
|
|
295
|
+
if (!Array.isArray(value)) continue;
|
|
296
|
+
const ref = value[0];
|
|
297
|
+
if (!ref || typeof ref !== "string") continue;
|
|
298
|
+
const pkgName = extractPackageName(ref);
|
|
299
|
+
if (!pkgName) continue;
|
|
300
|
+
const version$1 = ref.slice(pkgName.length + 1);
|
|
301
|
+
if (!version$1) continue;
|
|
302
|
+
if (version$1.startsWith("file:") || version$1.startsWith("workspace:")) continue;
|
|
303
|
+
const existingBun = lockMap.get(name);
|
|
304
|
+
if (existingBun && !isHigherVersion(version$1, existingBun.resolved)) continue;
|
|
305
|
+
lockMap.set(name, {
|
|
306
|
+
resolved: version$1,
|
|
307
|
+
scopes: ["prod"]
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
return lockMap;
|
|
311
|
+
}
|
|
312
|
+
async function parseLockfile(cliType, lockfilePath, lockfileVersion) {
|
|
313
|
+
if (cliType === "bun" && lockfileVersion === 0) return /* @__PURE__ */ new Map();
|
|
314
|
+
if (cliType === "vlt") return /* @__PURE__ */ new Map();
|
|
315
|
+
const content = await fs.promises.readFile(lockfilePath, "utf-8");
|
|
316
|
+
switch (cliType) {
|
|
317
|
+
case "npm": return parseNpmLock(content, lockfileVersion);
|
|
318
|
+
case "pnpm": return parsePnpmLock(content, lockfileVersion);
|
|
319
|
+
case "yarn": return parseYarnLock(content, lockfileVersion);
|
|
320
|
+
case "bun": return parseBunLock(content);
|
|
321
|
+
default: return /* @__PURE__ */ new Map();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function readPackageJson(startDir) {
|
|
325
|
+
let current = startDir;
|
|
326
|
+
for (;;) try {
|
|
327
|
+
const content = await fs.promises.readFile(path.join(current, "package.json"), "utf-8");
|
|
328
|
+
return JSON.parse(content);
|
|
329
|
+
} catch {
|
|
330
|
+
const parent = path.dirname(current);
|
|
331
|
+
if (parent === current) return null;
|
|
332
|
+
current = parent;
|
|
333
|
+
}
|
|
65
334
|
}
|
|
335
|
+
function buildDirectMaps(pkgJson) {
|
|
336
|
+
const directScopes = /* @__PURE__ */ new Map();
|
|
337
|
+
const directRequested = /* @__PURE__ */ new Map();
|
|
338
|
+
const add = (deps, scope) => {
|
|
339
|
+
if (!deps || typeof deps !== "object") return;
|
|
340
|
+
for (const [name, specifier] of Object.entries(deps)) {
|
|
341
|
+
if (!directScopes.has(name)) directScopes.set(name, /* @__PURE__ */ new Set());
|
|
342
|
+
directScopes.get(name).add(scope);
|
|
343
|
+
if (!directRequested.has(name)) directRequested.set(name, specifier);
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
add(pkgJson.dependencies, "prod");
|
|
347
|
+
add(pkgJson.devDependencies, "dev");
|
|
348
|
+
add(pkgJson.peerDependencies, "peer");
|
|
349
|
+
add(pkgJson.optionalDependencies, "optional");
|
|
350
|
+
return {
|
|
351
|
+
directScopes,
|
|
352
|
+
directRequested
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
async function generateProjectManifest({ workPath, nodeVersion, cliType, lockfilePath, lockfileVersion }) {
|
|
356
|
+
try {
|
|
357
|
+
const pkgJson = await readPackageJson(workPath);
|
|
358
|
+
if (!pkgJson) return;
|
|
359
|
+
const { directScopes, directRequested } = buildDirectMaps(pkgJson);
|
|
360
|
+
const lockMap = lockfilePath ? await parseLockfile(cliType, lockfilePath, lockfileVersion) : /* @__PURE__ */ new Map();
|
|
361
|
+
const directDeps = [];
|
|
362
|
+
const transitiveDeps = [];
|
|
363
|
+
for (const [name, scopes] of directScopes) {
|
|
364
|
+
const lock = lockMap.get(name);
|
|
365
|
+
const dep = {
|
|
366
|
+
name,
|
|
367
|
+
type: "direct",
|
|
368
|
+
scopes: [...scopes].sort(),
|
|
369
|
+
requested: directRequested.get(name),
|
|
370
|
+
resolved: lock?.resolved ?? ""
|
|
371
|
+
};
|
|
372
|
+
if (lock?.source) dep.source = lock.source;
|
|
373
|
+
if (lock?.sourceUrl) dep.sourceUrl = lock.sourceUrl;
|
|
374
|
+
directDeps.push(dep);
|
|
375
|
+
}
|
|
376
|
+
for (const [name, lock] of lockMap) {
|
|
377
|
+
if (directScopes.has(name)) continue;
|
|
378
|
+
const dep = {
|
|
379
|
+
name,
|
|
380
|
+
type: "transitive",
|
|
381
|
+
scopes: lock.scopes,
|
|
382
|
+
resolved: lock.resolved
|
|
383
|
+
};
|
|
384
|
+
if (lock.source) dep.source = lock.source;
|
|
385
|
+
if (lock.sourceUrl) dep.sourceUrl = lock.sourceUrl;
|
|
386
|
+
transitiveDeps.push(dep);
|
|
387
|
+
}
|
|
388
|
+
const runtimeVersion = { resolved: String(nodeVersion.major) };
|
|
389
|
+
const enginesNode = pkgJson.engines?.node;
|
|
390
|
+
if (enginesNode) {
|
|
391
|
+
runtimeVersion.requested = enginesNode;
|
|
392
|
+
runtimeVersion.requestedSource = "package.json";
|
|
393
|
+
} else for (const filename of [".node-version", ".nvmrc"]) try {
|
|
394
|
+
const trimmed = (await fs.promises.readFile(path.join(workPath, filename), "utf-8")).trim();
|
|
395
|
+
if (trimmed) {
|
|
396
|
+
runtimeVersion.requested = trimmed;
|
|
397
|
+
runtimeVersion.requestedSource = filename;
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
} catch {}
|
|
401
|
+
await writeProjectManifest({
|
|
402
|
+
version: MANIFEST_VERSION,
|
|
403
|
+
runtime: "node",
|
|
404
|
+
runtimeVersion,
|
|
405
|
+
dependencies: [...directDeps.sort((a, b) => a.name.localeCompare(b.name)), ...transitiveDeps.sort((a, b) => a.name.localeCompare(b.name))]
|
|
406
|
+
}, workPath, "node");
|
|
407
|
+
} catch (err) {
|
|
408
|
+
debug(`generateProjectManifest: ${err instanceof Error ? err.message : String(err)}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
const diagnostics = createDiagnostics("node");
|
|
66
412
|
|
|
67
413
|
//#endregion
|
|
68
414
|
//#region src/cervel/plugin.ts
|
|
@@ -93,7 +439,7 @@ const plugin = (args) => {
|
|
|
93
439
|
* Determine if a resolved module is CommonJS based on package.json exports
|
|
94
440
|
*/
|
|
95
441
|
const isCommonJS = async (bareImport, resolvedPath, resolvedInfo) => {
|
|
96
|
-
const ext = extname
|
|
442
|
+
const ext = extname(resolvedPath);
|
|
97
443
|
if (ext === ".cjs") return true;
|
|
98
444
|
if (ext === ".mjs") return false;
|
|
99
445
|
if (ext === ".js" || ext === ".ts") {
|
|
@@ -231,7 +577,7 @@ const nodeFileTrace = async (args) => {
|
|
|
231
577
|
ts: true,
|
|
232
578
|
mixedModules: true,
|
|
233
579
|
async resolve(id, parent, job, cjsResolve) {
|
|
234
|
-
return resolve(id, parent, job, cjsResolve);
|
|
580
|
+
return resolve$1(id, parent, job, cjsResolve);
|
|
235
581
|
},
|
|
236
582
|
async readFile(fsPath) {
|
|
237
583
|
try {
|
|
@@ -276,7 +622,7 @@ var __dirname = typeof __dirname !== 'undefined' ? __dirname : __dirname_(__file
|
|
|
276
622
|
const rolldown$1 = async (args) => {
|
|
277
623
|
const entrypointPath = join$1(args.workPath, args.entrypoint);
|
|
278
624
|
const outputDir = join$1(args.workPath, args.out);
|
|
279
|
-
const extension = extname
|
|
625
|
+
const extension = extname(args.entrypoint);
|
|
280
626
|
const extensionMap = {
|
|
281
627
|
".ts": {
|
|
282
628
|
format: "auto",
|
|
@@ -407,9 +753,24 @@ const Colors = {
|
|
|
407
753
|
};
|
|
408
754
|
|
|
409
755
|
//#endregion
|
|
410
|
-
//#region src/
|
|
411
|
-
|
|
412
|
-
|
|
756
|
+
//#region src/typescript.ts
|
|
757
|
+
/**
|
|
758
|
+
* Typecheck via the TypeScript compiler API (`createProgram`, `getPreEmitDiagnostics`),
|
|
759
|
+
* not by spawning the `tsc` binary.
|
|
760
|
+
*
|
|
761
|
+
* We only want to validate the deployment entrypoint and its import graph, not every
|
|
762
|
+
* file matched by `tsconfig` `include`. The CLI cannot combine `--project` with explicit
|
|
763
|
+
* root files (TS5042), so expressing 'project options + entry-only roots' in one `tsc`
|
|
764
|
+
* call requires a generated tsconfig on disk. Writing beside the user's config is
|
|
765
|
+
* invasive; a temp config elsewhere often breaks `node_modules` / `@types` resolution
|
|
766
|
+
* relative to the real project. The API lets us reuse `parseJsonConfigFileContent` (same
|
|
767
|
+
* options as `-p`) with explicit `rootNames`, no files written, and a compiler host whose
|
|
768
|
+
* current directory stays `workPath`.
|
|
769
|
+
*
|
|
770
|
+
* The `typescript` package is resolved with `require` from the user's app (peer dependency), not bundled.
|
|
771
|
+
*/
|
|
772
|
+
const require_ = createRequire(import.meta.url);
|
|
773
|
+
const typescript = (args) => {
|
|
413
774
|
const { span } = args;
|
|
414
775
|
return span.child("vc.builder.backends.tsCompile").trace(async () => {
|
|
415
776
|
const extension = extname(args.entrypoint);
|
|
@@ -418,79 +779,76 @@ const typescript$1 = (args) => {
|
|
|
418
779
|
".mts",
|
|
419
780
|
".cts"
|
|
420
781
|
].includes(extension)) return;
|
|
421
|
-
const
|
|
422
|
-
if (!
|
|
782
|
+
const ts = resolveTypeScriptModule(args.workPath);
|
|
783
|
+
if (!ts) {
|
|
423
784
|
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck skipped ${Colors.gray("(TypeScript not found)")}`));
|
|
424
785
|
return null;
|
|
425
786
|
}
|
|
426
|
-
return doTypeCheck
|
|
787
|
+
return doTypeCheck(args, ts);
|
|
427
788
|
});
|
|
428
789
|
};
|
|
429
|
-
async function doTypeCheck
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
reject(err);
|
|
479
|
-
});
|
|
480
|
-
});
|
|
790
|
+
async function doTypeCheck(args, ts) {
|
|
791
|
+
const entryAbsolute = resolve(args.workPath, args.entrypoint);
|
|
792
|
+
const tsconfig = await findNearestTsconfig(args.workPath);
|
|
793
|
+
const formatDiagnostics = process.stdout.isTTY ? ts.formatDiagnosticsWithColorAndContext : ts.formatDiagnostics;
|
|
794
|
+
const diagnosticHost = {
|
|
795
|
+
getNewLine: () => ts.sys.newLine,
|
|
796
|
+
getCanonicalFileName: (fileName) => ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(),
|
|
797
|
+
getCurrentDirectory: () => args.workPath
|
|
798
|
+
};
|
|
799
|
+
let options;
|
|
800
|
+
let parseDiagnostics = [];
|
|
801
|
+
if (tsconfig) {
|
|
802
|
+
const configRead = ts.readConfigFile(tsconfig, ts.sys.readFile);
|
|
803
|
+
if (configRead.error) {
|
|
804
|
+
const message = formatDiagnostics([configRead.error], diagnosticHost);
|
|
805
|
+
console.error("\nTypeScript type check failed:\n");
|
|
806
|
+
console.error(message);
|
|
807
|
+
throw new Error("TypeScript type check failed");
|
|
808
|
+
}
|
|
809
|
+
const parsed = ts.parseJsonConfigFileContent(configRead.config, ts.sys, dirname$1(tsconfig), void 0, tsconfig);
|
|
810
|
+
parseDiagnostics = parsed.errors;
|
|
811
|
+
options = {
|
|
812
|
+
...parsed.options,
|
|
813
|
+
noEmit: true,
|
|
814
|
+
skipLibCheck: true,
|
|
815
|
+
allowJs: true,
|
|
816
|
+
esModuleInterop: true
|
|
817
|
+
};
|
|
818
|
+
} else options = {
|
|
819
|
+
noEmit: true,
|
|
820
|
+
skipLibCheck: true,
|
|
821
|
+
allowJs: true,
|
|
822
|
+
esModuleInterop: true,
|
|
823
|
+
target: ts.ScriptTarget.ES2022,
|
|
824
|
+
module: ts.ModuleKind.NodeNext,
|
|
825
|
+
moduleResolution: ts.ModuleResolutionKind.NodeNext
|
|
826
|
+
};
|
|
827
|
+
const compilerHost = ts.createCompilerHost(options);
|
|
828
|
+
compilerHost.getCurrentDirectory = () => args.workPath;
|
|
829
|
+
const program = ts.createProgram([entryAbsolute], options, compilerHost);
|
|
830
|
+
const errors = [...parseDiagnostics, ...ts.getPreEmitDiagnostics(program)].filter((d) => d.category === ts.DiagnosticCategory.Error);
|
|
831
|
+
if (errors.length === 0) {
|
|
832
|
+
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck complete`));
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
const output = formatDiagnostics(errors, diagnosticHost);
|
|
836
|
+
console.error("\nTypeScript type check failed:\n");
|
|
837
|
+
console.error(output);
|
|
838
|
+
throw new Error("TypeScript type check failed");
|
|
481
839
|
}
|
|
482
|
-
|
|
840
|
+
function resolveTypeScriptModule(workPath) {
|
|
483
841
|
try {
|
|
484
|
-
return require_
|
|
485
|
-
} catch (
|
|
842
|
+
return require_(require_.resolve("typescript", { paths: [workPath] }));
|
|
843
|
+
} catch (_e) {
|
|
486
844
|
return null;
|
|
487
845
|
}
|
|
488
|
-
}
|
|
489
|
-
const findNearestTsconfig
|
|
490
|
-
const tsconfigPath = join(workPath, "tsconfig.json");
|
|
491
|
-
if (existsSync
|
|
846
|
+
}
|
|
847
|
+
const findNearestTsconfig = async (workPath) => {
|
|
848
|
+
const tsconfigPath = join$1(workPath, "tsconfig.json");
|
|
849
|
+
if (existsSync(tsconfigPath)) return tsconfigPath;
|
|
492
850
|
if (workPath === "/") return;
|
|
493
|
-
return findNearestTsconfig$1(
|
|
851
|
+
return findNearestTsconfig(join$1(workPath, ".."));
|
|
494
852
|
};
|
|
495
853
|
|
|
496
854
|
//#endregion
|
|
@@ -524,12 +882,24 @@ const entrypointExtensions = [
|
|
|
524
882
|
const entrypoints = entrypointFilenames.flatMap((filename) => entrypointExtensions.map((extension) => `${filename}.${extension}`));
|
|
525
883
|
const createFrameworkRegex = (framework) => new RegExp(`(?:from|require|import)\\s*(?:\\(\\s*)?["']${framework}["']\\s*(?:\\))?`, "g");
|
|
526
884
|
const findEntrypoint = async (cwd) => {
|
|
527
|
-
let
|
|
885
|
+
let packageJsonObject = null;
|
|
528
886
|
try {
|
|
529
887
|
const packageJson = await readFile(join$1(cwd, "package.json"), "utf-8");
|
|
530
|
-
|
|
531
|
-
framework = frameworks.find((framework$1) => packageJsonObject.dependencies?.[framework$1]);
|
|
888
|
+
packageJsonObject = JSON.parse(packageJson);
|
|
532
889
|
} catch (_) {}
|
|
890
|
+
if (packageJsonObject) {
|
|
891
|
+
const main = typeof packageJsonObject.main === "string" ? packageJsonObject.main.trim() : "";
|
|
892
|
+
if (main) {
|
|
893
|
+
const abs = resolve(cwd, main);
|
|
894
|
+
const rel = relative(cwd, abs);
|
|
895
|
+
if (!rel.startsWith("..") && rel !== "") try {
|
|
896
|
+
await readFile(abs, "utf-8");
|
|
897
|
+
return rel.split(sep).join("/");
|
|
898
|
+
} catch {}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
let framework;
|
|
902
|
+
if (packageJsonObject) framework = frameworks.find((framework$1) => packageJsonObject.dependencies?.[framework$1]);
|
|
533
903
|
if (!framework) for (const entrypoint of entrypoints) {
|
|
534
904
|
const entrypointPath = join$1(cwd, entrypoint);
|
|
535
905
|
try {
|
|
@@ -550,7 +920,7 @@ const findEntrypoint = async (cwd) => {
|
|
|
550
920
|
};
|
|
551
921
|
const findEntrypointOrThrow = async (cwd) => {
|
|
552
922
|
const entrypoint = await findEntrypoint(cwd);
|
|
553
|
-
if (!entrypoint) throw new Error(`No entrypoint found in "${cwd}".
|
|
923
|
+
if (!entrypoint) throw new Error(`No entrypoint found in "${cwd}". Set package.json "main" to a server file, or add one of: ${entrypoints.join(", ")}`);
|
|
554
924
|
return entrypoint;
|
|
555
925
|
};
|
|
556
926
|
|
|
@@ -564,7 +934,7 @@ const getBuildSummary = async (outputDir) => {
|
|
|
564
934
|
const build$1 = async (args) => {
|
|
565
935
|
const entrypoint = args.entrypoint || await findEntrypointOrThrow(args.workPath);
|
|
566
936
|
const span = args.span ?? new Span({ name: "cervel-build" });
|
|
567
|
-
const [, rolldownResult] = await Promise.all([typescript
|
|
937
|
+
const [, rolldownResult] = await Promise.all([typescript({
|
|
568
938
|
entrypoint,
|
|
569
939
|
workPath: args.workPath,
|
|
570
940
|
span
|
|
@@ -576,7 +946,7 @@ const build$1 = async (args) => {
|
|
|
576
946
|
span
|
|
577
947
|
})]);
|
|
578
948
|
await writeFile(join$1(args.workPath, args.out, ".cervel.json"), JSON.stringify({ handler: rolldownResult.result.handler }, null, 2));
|
|
579
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Build complete
|
|
949
|
+
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Build complete — Using ${Colors.bold(entrypoint)} as the root entrypoint.`));
|
|
580
950
|
return { rolldownResult: rolldownResult.result };
|
|
581
951
|
};
|
|
582
952
|
const serve = async (args) => {
|
|
@@ -625,7 +995,7 @@ const srvxOptions = {
|
|
|
625
995
|
//#endregion
|
|
626
996
|
//#region src/rolldown/resolve-format.ts
|
|
627
997
|
const resolveEntrypointAndFormat = async (args) => {
|
|
628
|
-
const extension = extname
|
|
998
|
+
const extension = extname(args.entrypoint);
|
|
629
999
|
const extensionMap = {
|
|
630
1000
|
".ts": {
|
|
631
1001
|
format: "auto",
|
|
@@ -673,13 +1043,174 @@ const resolveEntrypointAndFormat = async (args) => {
|
|
|
673
1043
|
};
|
|
674
1044
|
};
|
|
675
1045
|
|
|
1046
|
+
//#endregion
|
|
1047
|
+
//#region src/service-vc-init.ts
|
|
1048
|
+
async function applyServiceVcInit(args) {
|
|
1049
|
+
const { format, extension } = await resolveShimFormat(args);
|
|
1050
|
+
const handlerDir = dirname$1(args.handler);
|
|
1051
|
+
const vcInitName = `${basename(args.handler, extname(args.handler))}.__vc_service_vc_init${extension}`;
|
|
1052
|
+
const vcInitHandler = handlerDir === "." ? vcInitName : join$1(handlerDir, vcInitName);
|
|
1053
|
+
const handlerImportPath = `./${basename(args.handler)}`;
|
|
1054
|
+
const vcInitSource = format === "esm" ? createEsmServiceVcInit(handlerImportPath) : createCjsServiceVcInit(handlerImportPath);
|
|
1055
|
+
return {
|
|
1056
|
+
handler: vcInitHandler,
|
|
1057
|
+
files: {
|
|
1058
|
+
...args.files,
|
|
1059
|
+
[vcInitHandler]: new FileBlob({
|
|
1060
|
+
data: vcInitSource,
|
|
1061
|
+
mode: 420
|
|
1062
|
+
})
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
async function resolveShimFormat(args) {
|
|
1067
|
+
const { format } = await resolveEntrypointAndFormat({
|
|
1068
|
+
entrypoint: args.handler,
|
|
1069
|
+
workPath: args.workPath
|
|
1070
|
+
});
|
|
1071
|
+
return {
|
|
1072
|
+
format,
|
|
1073
|
+
extension: extname(args.handler) || (format === "esm" ? ".mjs" : ".cjs")
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
const sharedShimPrelude = String.raw`
|
|
1077
|
+
const PATCH_SYMBOL = Symbol.for('vc.service.route-prefix-strip.patch')
|
|
1078
|
+
|
|
1079
|
+
function normalizeServiceRoutePrefix(rawPrefix) {
|
|
1080
|
+
if (!rawPrefix) {
|
|
1081
|
+
return ''
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
let prefix = String(rawPrefix).trim()
|
|
1085
|
+
if (!prefix) {
|
|
1086
|
+
return ''
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
if (!prefix.startsWith('/')) {
|
|
1090
|
+
prefix = '/' + prefix
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
if (prefix !== '/') {
|
|
1094
|
+
prefix = prefix.replace(/\/+$/, '')
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
return prefix === '/' ? '' : prefix
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function getServiceRoutePrefix() {
|
|
1101
|
+
const enabled = String(
|
|
1102
|
+
process.env.VERCEL_SERVICE_ROUTE_PREFIX_STRIP || ''
|
|
1103
|
+
).toLowerCase()
|
|
1104
|
+
if (enabled !== '1' && enabled !== 'true') {
|
|
1105
|
+
return ''
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
return normalizeServiceRoutePrefix(process.env.VERCEL_SERVICE_ROUTE_PREFIX || '')
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function stripServiceRoutePrefix(requestUrl, prefix) {
|
|
1112
|
+
if (typeof requestUrl !== 'string' || requestUrl === '*') {
|
|
1113
|
+
return requestUrl
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
const queryIndex = requestUrl.indexOf('?')
|
|
1117
|
+
const rawPath =
|
|
1118
|
+
queryIndex === -1 ? requestUrl : requestUrl.slice(0, queryIndex)
|
|
1119
|
+
const query = queryIndex === -1 ? '' : requestUrl.slice(queryIndex)
|
|
1120
|
+
|
|
1121
|
+
let path = rawPath || '/'
|
|
1122
|
+
if (!path.startsWith('/')) {
|
|
1123
|
+
path = '/' + path
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
if (!prefix) {
|
|
1127
|
+
return path + query
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
if (path === prefix) {
|
|
1131
|
+
return '/' + query
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
if (path.startsWith(prefix + '/')) {
|
|
1135
|
+
return path.slice(prefix.length) + query
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
return path + query
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
function patchServerRequestUrl(ServerCtor) {
|
|
1142
|
+
const prefix = getServiceRoutePrefix()
|
|
1143
|
+
if (!prefix || globalThis[PATCH_SYMBOL]) {
|
|
1144
|
+
return
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
globalThis[PATCH_SYMBOL] = true
|
|
1148
|
+
|
|
1149
|
+
const originalEmit = ServerCtor.prototype.emit
|
|
1150
|
+
ServerCtor.prototype.emit = function patchedEmit(event, request, ...args) {
|
|
1151
|
+
if (event === 'request' && request && typeof request.url === 'string') {
|
|
1152
|
+
request.url = stripServiceRoutePrefix(request.url, prefix)
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
return originalEmit.call(this, event, request, ...args)
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
`;
|
|
1159
|
+
function createEsmServiceVcInit(handlerImportPath) {
|
|
1160
|
+
return `
|
|
1161
|
+
import { Server } from 'node:http'
|
|
1162
|
+
|
|
1163
|
+
${sharedShimPrelude}
|
|
1164
|
+
|
|
1165
|
+
// Patch the HTTP server before loading user code so apps that attach request
|
|
1166
|
+
// listeners during module evaluation see the stripped service-relative URL.
|
|
1167
|
+
patchServerRequestUrl(Server)
|
|
1168
|
+
|
|
1169
|
+
const originalModule = await import(${JSON.stringify(handlerImportPath)})
|
|
1170
|
+
|
|
1171
|
+
/**
|
|
1172
|
+
* Match the Node serverless loader behavior: TS/CJS/ESM interop can leave us
|
|
1173
|
+
* with nested \`.default\` wrappers, so peel off a few layers to recover the
|
|
1174
|
+
* actual user entrypoint shape.
|
|
1175
|
+
*/
|
|
1176
|
+
function unwrapDefaultExport(value) {
|
|
1177
|
+
let current = value
|
|
1178
|
+
for (let i = 0; i < 5; i++) {
|
|
1179
|
+
if (current && typeof current === 'object' && 'default' in current && current.default) {
|
|
1180
|
+
current = current.default
|
|
1181
|
+
} else {
|
|
1182
|
+
break
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
return current
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
const entrypoint = unwrapDefaultExport(originalModule)
|
|
1189
|
+
|
|
1190
|
+
// Re-export the resolved entrypoint so the surrounding runtime still sees the
|
|
1191
|
+
// same handler shape after this service bootstrap runs.
|
|
1192
|
+
export default typeof entrypoint === 'undefined' ? originalModule : entrypoint
|
|
1193
|
+
`.trimStart();
|
|
1194
|
+
}
|
|
1195
|
+
function createCjsServiceVcInit(handlerImportPath) {
|
|
1196
|
+
return `
|
|
1197
|
+
const { Server } = require('node:http')
|
|
1198
|
+
|
|
1199
|
+
${sharedShimPrelude}
|
|
1200
|
+
|
|
1201
|
+
patchServerRequestUrl(Server)
|
|
1202
|
+
|
|
1203
|
+
module.exports = require(${JSON.stringify(handlerImportPath)})
|
|
1204
|
+
`.trimStart();
|
|
1205
|
+
}
|
|
1206
|
+
|
|
676
1207
|
//#endregion
|
|
677
1208
|
//#region src/rolldown/nft.ts
|
|
678
1209
|
const nft = async (args) => {
|
|
679
1210
|
const nftSpan = args.span.child("vc.builder.backends.nft");
|
|
680
1211
|
const runNft = async () => {
|
|
681
1212
|
const ignorePatterns = [...args.ignoreNodeModules ? ["**/node_modules/**"] : [], ...args.ignore ? Array.isArray(args.ignore) ? args.ignore : [args.ignore] : []];
|
|
682
|
-
const nftResult = await nodeFileTrace$1(Array.from(args.localBuildFiles), {
|
|
1213
|
+
const nftResult = await nodeFileTrace$1(Array.from(args.localBuildFiles).filter((p) => existsSync(p)), {
|
|
683
1214
|
base: args.repoRootPath,
|
|
684
1215
|
processCwd: args.workPath,
|
|
685
1216
|
ts: true,
|
|
@@ -699,7 +1230,13 @@ const nft = async (args) => {
|
|
|
699
1230
|
});
|
|
700
1231
|
for (const file of nftResult.fileList) {
|
|
701
1232
|
const absolutePath = join$1(args.repoRootPath, file);
|
|
702
|
-
|
|
1233
|
+
let stats;
|
|
1234
|
+
try {
|
|
1235
|
+
stats = await lstat(absolutePath);
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
if (isNativeError(error) && "code" in error && error.code === "ENOENT") continue;
|
|
1238
|
+
throw error;
|
|
1239
|
+
}
|
|
703
1240
|
const outputPath = file;
|
|
704
1241
|
if (args.localBuildFiles.has(join$1(args.repoRootPath, outputPath))) continue;
|
|
705
1242
|
if (stats.isSymbolicLink() || stats.isFile()) if (args.ignoreNodeModules) {
|
|
@@ -719,6 +1256,7 @@ const nft = async (args) => {
|
|
|
719
1256
|
await nftSpan.trace(runNft);
|
|
720
1257
|
};
|
|
721
1258
|
const isTypeScriptFile = (fsPath) => {
|
|
1259
|
+
if (fsPath.endsWith(".d.ts") || fsPath.endsWith(".d.mts") || fsPath.endsWith(".d.cts")) return false;
|
|
722
1260
|
return fsPath.endsWith(".ts") || fsPath.endsWith(".tsx") || fsPath.endsWith(".mts") || fsPath.endsWith(".cts");
|
|
723
1261
|
};
|
|
724
1262
|
|
|
@@ -750,7 +1288,7 @@ const rolldown = async (args) => {
|
|
|
750
1288
|
}
|
|
751
1289
|
};
|
|
752
1290
|
const isCommonJS = async (bareImport, resolvedPath, resolvedInfo) => {
|
|
753
|
-
const ext = extname
|
|
1291
|
+
const ext = extname(resolvedPath);
|
|
754
1292
|
if (ext === ".cjs") return true;
|
|
755
1293
|
if (ext === ".mjs") return false;
|
|
756
1294
|
if (ext === ".js" || ext === ".ts") {
|
|
@@ -812,8 +1350,12 @@ const rolldown = async (args) => {
|
|
|
812
1350
|
id,
|
|
813
1351
|
external: true
|
|
814
1352
|
};
|
|
815
|
-
if (resolved?.id && isLocalImport(resolved.id))
|
|
816
|
-
|
|
1353
|
+
if (resolved?.id && isLocalImport(resolved.id)) {
|
|
1354
|
+
if (existsSync(resolved.id)) localBuildFiles.add(resolved.id);
|
|
1355
|
+
} else if (!resolved && !(isBareImport(id) && importer)) {
|
|
1356
|
+
const candidate = join$1(args.workPath, id);
|
|
1357
|
+
if (existsSync(candidate)) localBuildFiles.add(candidate);
|
|
1358
|
+
}
|
|
817
1359
|
if (importer?.startsWith(CJS_SHIM_PREFIX) && isBareImport(id)) return {
|
|
818
1360
|
id,
|
|
819
1361
|
external: true
|
|
@@ -915,7 +1457,7 @@ module.exports = requireFromContext('${pkgName}');
|
|
|
915
1457
|
ignore: args.config.excludeFiles
|
|
916
1458
|
});
|
|
917
1459
|
if (!handler) throw new Error(`Unable to resolve build handler for entrypoint: ${args.entrypoint}`);
|
|
918
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Build complete
|
|
1460
|
+
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Build complete — Using ${Colors.bold(args.entrypoint)} as the root entrypoint.`));
|
|
919
1461
|
return {
|
|
920
1462
|
files,
|
|
921
1463
|
handler,
|
|
@@ -962,6 +1504,7 @@ const introspection = async (args) => {
|
|
|
962
1504
|
writeFileSync(filePath, value.data);
|
|
963
1505
|
}
|
|
964
1506
|
let introspectionData;
|
|
1507
|
+
let introspectionError;
|
|
965
1508
|
await new Promise((resolvePromise) => {
|
|
966
1509
|
try {
|
|
967
1510
|
debug("Spawning introspection process");
|
|
@@ -969,7 +1512,7 @@ const introspection = async (args) => {
|
|
|
969
1512
|
const tempFilePath = join$1(outputTempDir, "output.txt");
|
|
970
1513
|
const writeStream = createWriteStream(tempFilePath);
|
|
971
1514
|
let streamClosed = false;
|
|
972
|
-
const child = spawn
|
|
1515
|
+
const child = spawn("node", [
|
|
973
1516
|
"-r",
|
|
974
1517
|
rolldownCjsLoaderPath,
|
|
975
1518
|
"--import",
|
|
@@ -1004,11 +1547,11 @@ const introspection = async (args) => {
|
|
|
1004
1547
|
const timeout = setTimeout(() => {
|
|
1005
1548
|
debug("Introspection timeout, killing process with SIGTERM");
|
|
1006
1549
|
child.kill("SIGTERM");
|
|
1007
|
-
},
|
|
1550
|
+
}, 5e3);
|
|
1008
1551
|
const timeout2 = setTimeout(() => {
|
|
1009
1552
|
debug("Introspection timeout, killing process with SIGKILL");
|
|
1010
1553
|
child.kill("SIGKILL");
|
|
1011
|
-
},
|
|
1554
|
+
}, 6e3);
|
|
1012
1555
|
const cleanup = () => {
|
|
1013
1556
|
clearTimeout(timeout);
|
|
1014
1557
|
clearTimeout(timeout2);
|
|
@@ -1023,7 +1566,8 @@ const introspection = async (args) => {
|
|
|
1023
1566
|
};
|
|
1024
1567
|
child.on("error", (err) => {
|
|
1025
1568
|
cleanup();
|
|
1026
|
-
|
|
1569
|
+
introspectionError = `Loader error: ${err.message}`;
|
|
1570
|
+
debug(introspectionError);
|
|
1027
1571
|
if (!streamClosed) writeStream.end(() => {
|
|
1028
1572
|
streamClosed = true;
|
|
1029
1573
|
try {
|
|
@@ -1054,8 +1598,12 @@ const introspection = async (args) => {
|
|
|
1054
1598
|
introspectionData = introspectionSchema.parse(JSON.parse(introspectionString));
|
|
1055
1599
|
debug("Introspection data parsed successfully");
|
|
1056
1600
|
}
|
|
1057
|
-
} else
|
|
1601
|
+
} else {
|
|
1602
|
+
introspectionError = `Introspection markers not found. stderr: ${stderrBuffer}`;
|
|
1603
|
+
debug(`Introspection markers not found.\nstdout:\n${stdoutBuffer}\nstderr:\n${stderrBuffer}`);
|
|
1604
|
+
}
|
|
1058
1605
|
} catch (error) {
|
|
1606
|
+
introspectionError = `Error parsing introspection data: ${error}. stderr: ${stderrBuffer}`;
|
|
1059
1607
|
debug(`Error parsing introspection data: ${error}\nstdout:\n${stdoutBuffer}\nstderr:\n${stderrBuffer}`);
|
|
1060
1608
|
} finally {
|
|
1061
1609
|
try {
|
|
@@ -1072,6 +1620,7 @@ const introspection = async (args) => {
|
|
|
1072
1620
|
else resolvePromise();
|
|
1073
1621
|
});
|
|
1074
1622
|
} catch (error) {
|
|
1623
|
+
introspectionError = `Introspection error: ${error}`;
|
|
1075
1624
|
debug("Introspection error", error);
|
|
1076
1625
|
resolvePromise();
|
|
1077
1626
|
}
|
|
@@ -1079,7 +1628,8 @@ const introspection = async (args) => {
|
|
|
1079
1628
|
if (!introspectionData) {
|
|
1080
1629
|
introspectionSpan.setAttributes({
|
|
1081
1630
|
"introspection.success": "false",
|
|
1082
|
-
"introspection.routes": "0"
|
|
1631
|
+
"introspection.routes": "0",
|
|
1632
|
+
...introspectionError && { "introspection.error": introspectionError.slice(0, 1024) }
|
|
1083
1633
|
});
|
|
1084
1634
|
return defaultResult$1;
|
|
1085
1635
|
}
|
|
@@ -1102,6 +1652,20 @@ const introspection = async (args) => {
|
|
|
1102
1652
|
|
|
1103
1653
|
//#endregion
|
|
1104
1654
|
//#region src/build.ts
|
|
1655
|
+
/**
|
|
1656
|
+
* `outputDirectory` is usually the same project setting the static builder uses
|
|
1657
|
+
* (Vite/Webpack/etc. client output). We only reuse it for the Node lambda when we
|
|
1658
|
+
* find a known server entry file under that folder; otherwise we bundle from
|
|
1659
|
+
* source with rolldown. Errors here are swallowed so static-only output trees do
|
|
1660
|
+
* not fail the build.
|
|
1661
|
+
*/
|
|
1662
|
+
async function findEntrypointInOutputDir(dir) {
|
|
1663
|
+
try {
|
|
1664
|
+
return await findEntrypoint(dir);
|
|
1665
|
+
} catch {
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1105
1669
|
const maybeDoBuildCommand = async (args, downloadResult) => {
|
|
1106
1670
|
const buildCommandResult = await maybeExecBuildCommand(args, downloadResult);
|
|
1107
1671
|
const outputSetting = args.config.outputDirectory;
|
|
@@ -1109,7 +1673,7 @@ const maybeDoBuildCommand = async (args, downloadResult) => {
|
|
|
1109
1673
|
let entrypoint;
|
|
1110
1674
|
if (buildCommandResult && outputSetting) if (outputSetting) {
|
|
1111
1675
|
const _outputDir = join$1(args.workPath, outputSetting);
|
|
1112
|
-
const _entrypoint = await
|
|
1676
|
+
const _entrypoint = await findEntrypointInOutputDir(_outputDir);
|
|
1113
1677
|
if (_entrypoint) {
|
|
1114
1678
|
outputDir = _outputDir;
|
|
1115
1679
|
entrypoint = _entrypoint;
|
|
@@ -1121,7 +1685,7 @@ const maybeDoBuildCommand = async (args, downloadResult) => {
|
|
|
1121
1685
|
]) {
|
|
1122
1686
|
const _outputDir = join$1(args.workPath, outputDirectory);
|
|
1123
1687
|
if (existsSync(_outputDir)) {
|
|
1124
|
-
const _entrypoint = await
|
|
1688
|
+
const _entrypoint = await findEntrypointInOutputDir(_outputDir);
|
|
1125
1689
|
if (_entrypoint) {
|
|
1126
1690
|
outputDir = _outputDir;
|
|
1127
1691
|
entrypoint = _entrypoint;
|
|
@@ -1143,93 +1707,6 @@ const maybeDoBuildCommand = async (args, downloadResult) => {
|
|
|
1143
1707
|
};
|
|
1144
1708
|
};
|
|
1145
1709
|
|
|
1146
|
-
//#endregion
|
|
1147
|
-
//#region src/typescript.ts
|
|
1148
|
-
const require_ = createRequire(import.meta.url);
|
|
1149
|
-
const typescript = (args) => {
|
|
1150
|
-
const { span } = args;
|
|
1151
|
-
return span.child("vc.builder.backends.tsCompile").trace(async () => {
|
|
1152
|
-
const extension = extname$1(args.entrypoint);
|
|
1153
|
-
if (![
|
|
1154
|
-
".ts",
|
|
1155
|
-
".mts",
|
|
1156
|
-
".cts"
|
|
1157
|
-
].includes(extension)) return;
|
|
1158
|
-
const tscPath = resolveTscPath(args);
|
|
1159
|
-
if (!tscPath) {
|
|
1160
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck skipped ${Colors.gray("(TypeScript not found)")}`));
|
|
1161
|
-
return null;
|
|
1162
|
-
}
|
|
1163
|
-
return doTypeCheck(args, tscPath);
|
|
1164
|
-
});
|
|
1165
|
-
};
|
|
1166
|
-
async function doTypeCheck(args, tscPath) {
|
|
1167
|
-
let stdout = "";
|
|
1168
|
-
let stderr = "";
|
|
1169
|
-
/**
|
|
1170
|
-
* This might be subject to change.
|
|
1171
|
-
* - if no tscPath, skip typecheck
|
|
1172
|
-
* - if tsconfig, provide the tsconfig path
|
|
1173
|
-
* - else provide the entrypoint path
|
|
1174
|
-
*/
|
|
1175
|
-
const tscArgs = [
|
|
1176
|
-
tscPath,
|
|
1177
|
-
"--noEmit",
|
|
1178
|
-
"--pretty",
|
|
1179
|
-
"--allowJs",
|
|
1180
|
-
"--esModuleInterop",
|
|
1181
|
-
"--skipLibCheck"
|
|
1182
|
-
];
|
|
1183
|
-
const tsconfig = await findNearestTsconfig(args.workPath);
|
|
1184
|
-
if (tsconfig) tscArgs.push("--project", tsconfig);
|
|
1185
|
-
else tscArgs.push(args.entrypoint);
|
|
1186
|
-
const child = spawn$1(process.execPath, tscArgs, {
|
|
1187
|
-
cwd: args.workPath,
|
|
1188
|
-
stdio: [
|
|
1189
|
-
"ignore",
|
|
1190
|
-
"pipe",
|
|
1191
|
-
"pipe"
|
|
1192
|
-
]
|
|
1193
|
-
});
|
|
1194
|
-
child.stdout?.on("data", (data) => {
|
|
1195
|
-
stdout += data.toString();
|
|
1196
|
-
});
|
|
1197
|
-
child.stderr?.on("data", (data) => {
|
|
1198
|
-
stderr += data.toString();
|
|
1199
|
-
});
|
|
1200
|
-
await new Promise((resolve$1, reject) => {
|
|
1201
|
-
child.on("close", (code) => {
|
|
1202
|
-
if (code === 0) {
|
|
1203
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck complete`));
|
|
1204
|
-
resolve$1();
|
|
1205
|
-
} else {
|
|
1206
|
-
const output = stdout || stderr;
|
|
1207
|
-
if (output) {
|
|
1208
|
-
console.error("\nTypeScript type check failed:\n");
|
|
1209
|
-
console.error(output);
|
|
1210
|
-
}
|
|
1211
|
-
reject(/* @__PURE__ */ new Error("TypeScript type check failed"));
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
child.on("error", (err) => {
|
|
1215
|
-
reject(err);
|
|
1216
|
-
});
|
|
1217
|
-
});
|
|
1218
|
-
}
|
|
1219
|
-
const resolveTscPath = (args) => {
|
|
1220
|
-
try {
|
|
1221
|
-
return require_.resolve("typescript/bin/tsc", { paths: [args.workPath] });
|
|
1222
|
-
} catch (e) {
|
|
1223
|
-
return null;
|
|
1224
|
-
}
|
|
1225
|
-
};
|
|
1226
|
-
const findNearestTsconfig = async (workPath) => {
|
|
1227
|
-
const tsconfigPath = join$1(workPath, "tsconfig.json");
|
|
1228
|
-
if (existsSync(tsconfigPath)) return tsconfigPath;
|
|
1229
|
-
if (workPath === "/") return;
|
|
1230
|
-
return findNearestTsconfig(join$1(workPath, ".."));
|
|
1231
|
-
};
|
|
1232
|
-
|
|
1233
1710
|
//#endregion
|
|
1234
1711
|
//#region src/introspection/index.ts
|
|
1235
1712
|
const require = createRequire(import.meta.url);
|
|
@@ -1259,7 +1736,7 @@ const introspectApp = async (args) => {
|
|
|
1259
1736
|
await new Promise((resolvePromise) => {
|
|
1260
1737
|
try {
|
|
1261
1738
|
debug("Spawning introspection process");
|
|
1262
|
-
const child = spawn
|
|
1739
|
+
const child = spawn("node", [
|
|
1263
1740
|
"-r",
|
|
1264
1741
|
cjsLoaderPath,
|
|
1265
1742
|
"--import",
|
|
@@ -1419,6 +1896,11 @@ const getFramework = (args) => {
|
|
|
1419
1896
|
//#endregion
|
|
1420
1897
|
//#region src/index.ts
|
|
1421
1898
|
const version = 2;
|
|
1899
|
+
/** Non-empty Build Command from project settings / vercel.json (not the default `build` script). */
|
|
1900
|
+
function hasExplicitBuildCommand(config) {
|
|
1901
|
+
const cmd = config.buildCommand ?? config.projectSettings?.buildCommand;
|
|
1902
|
+
return typeof cmd === "string" && cmd.trim().length > 0;
|
|
1903
|
+
}
|
|
1422
1904
|
const build = async (args) => {
|
|
1423
1905
|
const downloadResult = await downloadInstallAndBundle(args);
|
|
1424
1906
|
const nodeVersion = await getNodeVersion(args.workPath, void 0, args.config, args.meta);
|
|
@@ -1441,13 +1923,21 @@ const build = async (args) => {
|
|
|
1441
1923
|
...args,
|
|
1442
1924
|
span: buildSpan
|
|
1443
1925
|
});
|
|
1444
|
-
const introspectionPromise = introspection({
|
|
1926
|
+
const introspectionPromise = rolldownResult.framework.slug === "hono" ? introspection({
|
|
1445
1927
|
...args,
|
|
1446
1928
|
span: buildSpan,
|
|
1447
1929
|
files: rolldownResult.files,
|
|
1448
1930
|
handler: rolldownResult.handler
|
|
1931
|
+
}) : Promise.resolve({
|
|
1932
|
+
routes: [],
|
|
1933
|
+
additionalFolders: [],
|
|
1934
|
+
additionalDeps: []
|
|
1449
1935
|
});
|
|
1450
|
-
|
|
1936
|
+
let typescriptPromise;
|
|
1937
|
+
if (hasExplicitBuildCommand(args.config)) {
|
|
1938
|
+
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck skipped ${Colors.gray("(Build Command is configured)")}`));
|
|
1939
|
+
typescriptPromise = Promise.resolve();
|
|
1940
|
+
} else typescriptPromise = typescript({
|
|
1451
1941
|
entrypoint,
|
|
1452
1942
|
workPath: args.workPath,
|
|
1453
1943
|
span: buildSpan
|
|
@@ -1466,33 +1956,79 @@ const build = async (args) => {
|
|
|
1466
1956
|
conditions: isBun ? ["bun"] : void 0,
|
|
1467
1957
|
span: buildSpan
|
|
1468
1958
|
});
|
|
1959
|
+
try {
|
|
1960
|
+
await generateProjectManifest({
|
|
1961
|
+
workPath: args.workPath,
|
|
1962
|
+
nodeVersion,
|
|
1963
|
+
cliType: downloadResult.cliType,
|
|
1964
|
+
lockfilePath: downloadResult.lockfilePath,
|
|
1965
|
+
lockfileVersion: downloadResult.lockfileVersion
|
|
1966
|
+
});
|
|
1967
|
+
} catch (err) {
|
|
1968
|
+
debug(`Failed to write node manifest: ${err instanceof Error ? err.message : String(err)}`);
|
|
1969
|
+
}
|
|
1469
1970
|
const baseDir = args.repoRootPath || args.workPath;
|
|
1470
1971
|
const includeResults = await Promise.all(normalizeArray(args.config.includeFiles).map((pattern) => glob(pattern, baseDir)));
|
|
1471
1972
|
for (const matched of includeResults) for (const [relPath, entry] of Object.entries(matched)) files[relPath] = entry;
|
|
1472
1973
|
const introspectionResult = await introspectionPromise;
|
|
1473
1974
|
await typescriptPromise;
|
|
1975
|
+
const functionConfigOverrides = await getLambdaOptionsFromFunction({
|
|
1976
|
+
sourceFile: entrypoint,
|
|
1977
|
+
config: args.config
|
|
1978
|
+
});
|
|
1979
|
+
const serviceRoutePrefix = normalizeServiceRoutePrefix(args.config?.routePrefix ?? args.service?.routePrefix);
|
|
1980
|
+
const shouldStripServiceRoutePrefix = !!serviceRoutePrefix && (typeof args.config?.serviceName === "string" || !!args.service);
|
|
1981
|
+
let lambdaFiles = files;
|
|
1982
|
+
let lambdaHandler = handler;
|
|
1983
|
+
if (shouldStripServiceRoutePrefix) {
|
|
1984
|
+
const shimmedLambda = await applyServiceVcInit({
|
|
1985
|
+
files,
|
|
1986
|
+
handler,
|
|
1987
|
+
workPath: nftWorkPath
|
|
1988
|
+
});
|
|
1989
|
+
lambdaFiles = shimmedLambda.files;
|
|
1990
|
+
lambdaHandler = shimmedLambda.handler;
|
|
1991
|
+
}
|
|
1474
1992
|
const lambda = new NodejsLambda({
|
|
1475
1993
|
runtime: nodeVersion.runtime,
|
|
1476
|
-
handler,
|
|
1477
|
-
files,
|
|
1994
|
+
handler: lambdaHandler,
|
|
1995
|
+
files: lambdaFiles,
|
|
1478
1996
|
framework: rolldownResult.framework,
|
|
1479
1997
|
shouldAddHelpers: false,
|
|
1480
1998
|
shouldAddSourcemapSupport: true,
|
|
1481
1999
|
awsLambdaHandler: "",
|
|
2000
|
+
...functionConfigOverrides,
|
|
1482
2001
|
shouldDisableAutomaticFetchInstrumentation: process.env.VERCEL_TRACING_DISABLE_AUTOMATIC_FETCH_INSTRUMENTATION === "1"
|
|
1483
2002
|
});
|
|
2003
|
+
if (shouldStripServiceRoutePrefix && serviceRoutePrefix) lambda.environment = {
|
|
2004
|
+
...lambda.environment,
|
|
2005
|
+
VERCEL_SERVICE_ROUTE_PREFIX: serviceRoutePrefix,
|
|
2006
|
+
VERCEL_SERVICE_ROUTE_PREFIX_STRIP: "1"
|
|
2007
|
+
};
|
|
2008
|
+
const serviceName = typeof args.config?.serviceName === "string" && args.config.serviceName !== "" ? args.config.serviceName : void 0;
|
|
2009
|
+
const internalServiceFunctionPath = typeof serviceName === "string" && serviceName !== "" ? `/_svc/${serviceName}/index` : void 0;
|
|
2010
|
+
const internalServiceOutputPath = internalServiceFunctionPath?.slice(1);
|
|
2011
|
+
const remapRouteDestination = (route) => {
|
|
2012
|
+
const prefixedRoute = maybePrefixServiceRouteSource(route, serviceRoutePrefix);
|
|
2013
|
+
if (!internalServiceFunctionPath || !route.dest) return prefixedRoute;
|
|
2014
|
+
return {
|
|
2015
|
+
...prefixedRoute,
|
|
2016
|
+
dest: internalServiceFunctionPath
|
|
2017
|
+
};
|
|
2018
|
+
};
|
|
1484
2019
|
const routes = [
|
|
1485
2020
|
{ handle: "filesystem" },
|
|
1486
|
-
...introspectionResult.routes,
|
|
2021
|
+
...introspectionResult.routes.map(remapRouteDestination),
|
|
1487
2022
|
{
|
|
1488
|
-
src:
|
|
1489
|
-
dest: "/"
|
|
2023
|
+
src: getServiceCatchallSource(serviceRoutePrefix),
|
|
2024
|
+
dest: internalServiceFunctionPath ?? "/"
|
|
1490
2025
|
}
|
|
1491
2026
|
];
|
|
1492
|
-
const output = { index: lambda };
|
|
2027
|
+
const output = internalServiceOutputPath ? { [internalServiceOutputPath]: lambda } : { index: lambda };
|
|
1493
2028
|
for (const route of routes) if (route.dest) {
|
|
1494
2029
|
if (route.dest === "/") continue;
|
|
1495
|
-
|
|
2030
|
+
const outputPath = route.dest === internalServiceFunctionPath && internalServiceOutputPath ? internalServiceOutputPath : route.dest;
|
|
2031
|
+
output[outputPath] = lambda;
|
|
1496
2032
|
}
|
|
1497
2033
|
return {
|
|
1498
2034
|
routes,
|
|
@@ -1504,6 +2040,33 @@ const prepareCache = ({ repoRootPath, workPath }) => {
|
|
|
1504
2040
|
return glob(defaultCachePathGlob, repoRootPath || workPath);
|
|
1505
2041
|
};
|
|
1506
2042
|
const normalizeArray = (value) => Array.isArray(value) ? value : value ? [value] : [];
|
|
2043
|
+
const normalizeServiceRoutePrefix = (routePrefix) => {
|
|
2044
|
+
if (typeof routePrefix !== "string" || routePrefix === "" || routePrefix === ".") return;
|
|
2045
|
+
let normalized = routePrefix.startsWith("/") ? routePrefix : `/${routePrefix}`;
|
|
2046
|
+
if (normalized !== "/" && normalized.endsWith("/")) normalized = normalized.slice(0, -1);
|
|
2047
|
+
return normalized === "/" ? void 0 : normalized;
|
|
2048
|
+
};
|
|
2049
|
+
const maybePrefixServiceRouteSource = (route, routePrefix) => {
|
|
2050
|
+
if (!routePrefix || typeof route.dest !== "string" || !route.dest.startsWith("/")) return route;
|
|
2051
|
+
return {
|
|
2052
|
+
...route,
|
|
2053
|
+
src: getPrefixedRouteSource(route.src, route.dest, routePrefix)
|
|
2054
|
+
};
|
|
2055
|
+
};
|
|
2056
|
+
const getPrefixedRouteSource = (routeSource, routePath, routePrefix) => {
|
|
2057
|
+
if (!routeSource) return routeSource;
|
|
2058
|
+
if (routePath === routePrefix || routePath.startsWith(`${routePrefix}/`)) return routeSource;
|
|
2059
|
+
const escapedRoutePrefix = toRegexSource(routePrefix);
|
|
2060
|
+
if (routeSource.startsWith("^(?:")) return `^(?:${escapedRoutePrefix}${routeSource.slice(4)}`;
|
|
2061
|
+
if (routeSource.startsWith("^")) return `^${escapedRoutePrefix}${routeSource.slice(1)}`;
|
|
2062
|
+
return `${escapedRoutePrefix}${routeSource}`;
|
|
2063
|
+
};
|
|
2064
|
+
const getServiceCatchallSource = (routePrefix) => {
|
|
2065
|
+
if (!routePrefix) return "/(.*)";
|
|
2066
|
+
return `^${escapeForRegex(routePrefix)}(?:/(.*))?$`;
|
|
2067
|
+
};
|
|
2068
|
+
const escapeForRegex = (value) => value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
|
|
2069
|
+
const toRegexSource = (value) => escapeForRegex(value).replaceAll("/", "\\/");
|
|
1507
2070
|
|
|
1508
2071
|
//#endregion
|
|
1509
|
-
export { build, build$1 as cervelBuild, serve as cervelServe, findEntrypoint, findEntrypointOrThrow, getBuildSummary, introspectApp, nodeFileTrace, prepareCache, srvxOptions, version };
|
|
2072
|
+
export { build, build$1 as cervelBuild, serve as cervelServe, diagnostics, findEntrypoint, findEntrypointOrThrow, getBuildSummary, introspectApp, nodeFileTrace, prepareCache, srvxOptions, version };
|
package/dist/rolldown/index.mjs
CHANGED
|
@@ -94,7 +94,7 @@ const nft = async (args) => {
|
|
|
94
94
|
const nftSpan = args.span.child("vc.builder.backends.nft");
|
|
95
95
|
const runNft = async () => {
|
|
96
96
|
const ignorePatterns = [...args.ignoreNodeModules ? ["**/node_modules/**"] : [], ...args.ignore ? Array.isArray(args.ignore) ? args.ignore : [args.ignore] : []];
|
|
97
|
-
const nftResult = await nodeFileTrace(Array.from(args.localBuildFiles), {
|
|
97
|
+
const nftResult = await nodeFileTrace(Array.from(args.localBuildFiles).filter((p) => existsSync(p)), {
|
|
98
98
|
base: args.repoRootPath,
|
|
99
99
|
processCwd: args.workPath,
|
|
100
100
|
ts: true,
|
|
@@ -114,7 +114,13 @@ const nft = async (args) => {
|
|
|
114
114
|
});
|
|
115
115
|
for (const file of nftResult.fileList) {
|
|
116
116
|
const absolutePath = join(args.repoRootPath, file);
|
|
117
|
-
|
|
117
|
+
let stats;
|
|
118
|
+
try {
|
|
119
|
+
stats = await lstat(absolutePath);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (isNativeError(error) && "code" in error && error.code === "ENOENT") continue;
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
118
124
|
const outputPath = file;
|
|
119
125
|
if (args.localBuildFiles.has(join(args.repoRootPath, outputPath))) continue;
|
|
120
126
|
if (stats.isSymbolicLink() || stats.isFile()) if (args.ignoreNodeModules) {
|
|
@@ -134,6 +140,7 @@ const nft = async (args) => {
|
|
|
134
140
|
await nftSpan.trace(runNft);
|
|
135
141
|
};
|
|
136
142
|
const isTypeScriptFile = (fsPath) => {
|
|
143
|
+
if (fsPath.endsWith(".d.ts") || fsPath.endsWith(".d.mts") || fsPath.endsWith(".d.cts")) return false;
|
|
137
144
|
return fsPath.endsWith(".ts") || fsPath.endsWith(".tsx") || fsPath.endsWith(".mts") || fsPath.endsWith(".cts");
|
|
138
145
|
};
|
|
139
146
|
|
|
@@ -227,8 +234,12 @@ const rolldown = async (args) => {
|
|
|
227
234
|
id,
|
|
228
235
|
external: true
|
|
229
236
|
};
|
|
230
|
-
if (resolved?.id && isLocalImport(resolved.id))
|
|
231
|
-
|
|
237
|
+
if (resolved?.id && isLocalImport(resolved.id)) {
|
|
238
|
+
if (existsSync(resolved.id)) localBuildFiles.add(resolved.id);
|
|
239
|
+
} else if (!resolved && !(isBareImport(id) && importer)) {
|
|
240
|
+
const candidate = join(args.workPath, id);
|
|
241
|
+
if (existsSync(candidate)) localBuildFiles.add(candidate);
|
|
242
|
+
}
|
|
232
243
|
if (importer?.startsWith(CJS_SHIM_PREFIX) && isBareImport(id)) return {
|
|
233
244
|
id,
|
|
234
245
|
external: true
|
|
@@ -330,7 +341,7 @@ module.exports = requireFromContext('${pkgName}');
|
|
|
330
341
|
ignore: args.config.excludeFiles
|
|
331
342
|
});
|
|
332
343
|
if (!handler) throw new Error(`Unable to resolve build handler for entrypoint: ${args.entrypoint}`);
|
|
333
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Build complete
|
|
344
|
+
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Build complete — Using ${Colors.bold(args.entrypoint)} as the root entrypoint.`));
|
|
334
345
|
return {
|
|
335
346
|
files,
|
|
336
347
|
handler,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/backends",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "./dist/index.mjs",
|
|
6
6
|
"homepage": "https://vercel.com/docs",
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
"dist"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@
|
|
27
|
+
"@yarnpkg/parsers": "^3.0.0",
|
|
28
|
+
"js-yaml": "^3.13.1",
|
|
29
|
+
"@vercel/nft": "1.5.0",
|
|
28
30
|
"execa": "3.2.0",
|
|
29
31
|
"fs-extra": "11.1.0",
|
|
30
32
|
"oxc-transform": "0.111.0",
|
|
@@ -34,13 +36,14 @@
|
|
|
34
36
|
"srvx": "0.8.9",
|
|
35
37
|
"tsx": "4.21.0",
|
|
36
38
|
"zod": "3.22.4",
|
|
37
|
-
"@vercel/build-utils": "13.
|
|
39
|
+
"@vercel/build-utils": "13.19.0"
|
|
38
40
|
},
|
|
39
41
|
"peerDependencies": {
|
|
40
42
|
"typescript": "^4.0.0 || ^5.0.0"
|
|
41
43
|
},
|
|
42
44
|
"devDependencies": {
|
|
43
45
|
"@types/express": "5.0.3",
|
|
46
|
+
"@types/js-yaml": "3.12.1",
|
|
44
47
|
"@types/fs-extra": "11",
|
|
45
48
|
"@types/jest": "27.5.1",
|
|
46
49
|
"@types/node": "20.11.0",
|