@vercel/backends 0.0.63 → 0.1.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/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,6 +1,9 @@
1
1
  import { builtinModules, createRequire } from "node:module";
2
- import { delimiter, dirname, join } from "path";
3
- import { FileBlob, FileFsRef, NodejsLambda, Span, debug, defaultCachePathGlob, download, execCommand, getEnvForPackageManager, getLambdaOptionsFromFunction, 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
9
  import { basename, dirname as dirname$1, extname, isAbsolute, join as join$1, relative, resolve, sep } from "node:path";
@@ -20,7 +23,7 @@ async function downloadInstallAndBundle(args) {
20
23
  const { entrypoint, files, workPath, meta, config, repoRootPath } = args;
21
24
  await download(files, workPath, meta);
22
25
  const entrypointFsDirname = join(workPath, dirname(entrypoint));
23
- const { cliType, lockfileVersion, packageJsonPackageManager, turboSupportsCorepackHome } = await scanParentDirs(entrypointFsDirname, true, repoRootPath);
26
+ const { cliType, lockfilePath, lockfileVersion, packageJsonPackageManager, turboSupportsCorepackHome } = await scanParentDirs(entrypointFsDirname, true, repoRootPath);
24
27
  const spawnEnv = getEnvForPackageManager({
25
28
  cliType,
26
29
  lockfileVersion,
@@ -40,7 +43,10 @@ async function downloadInstallAndBundle(args) {
40
43
  else await runNpmInstall(entrypointFsDirname, [], { env: spawnEnv }, meta, config.projectSettings?.createdAt);
41
44
  return {
42
45
  entrypointFsDirname,
43
- spawnEnv
46
+ spawnEnv,
47
+ cliType,
48
+ lockfilePath,
49
+ lockfileVersion
44
50
  };
45
51
  }
46
52
  async function maybeExecBuildCommand(args, { spawnEnv }) {
@@ -61,6 +67,349 @@ async function maybeExecBuildCommand(args, { spawnEnv }) {
61
67
  return runPackageJsonScript(args.workPath, ["build"], { env: spawnEnv }, args.config.projectSettings?.createdAt);
62
68
  }
63
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
+ }
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");
412
+
64
413
  //#endregion
65
414
  //#region src/cervel/plugin.ts
66
415
  const CJS_SHIM_PREFIX$1 = "\0cjs-shim:";
@@ -1607,6 +1956,17 @@ const build = async (args) => {
1607
1956
  conditions: isBun ? ["bun"] : void 0,
1608
1957
  span: buildSpan
1609
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
+ }
1610
1970
  const baseDir = args.repoRootPath || args.workPath;
1611
1971
  const includeResults = await Promise.all(normalizeArray(args.config.includeFiles).map((pattern) => glob(pattern, baseDir)));
1612
1972
  for (const matched of includeResults) for (const [relPath, entry] of Object.entries(matched)) files[relPath] = entry;
@@ -1709,4 +2069,4 @@ const escapeForRegex = (value) => value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
1709
2069
  const toRegexSource = (value) => escapeForRegex(value).replaceAll("/", "\\/");
1710
2070
 
1711
2071
  //#endregion
1712
- 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/backends",
3
- "version": "0.0.63",
3
+ "version": "0.1.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./dist/index.mjs",
6
6
  "homepage": "https://vercel.com/docs",
@@ -24,6 +24,8 @@
24
24
  "dist"
25
25
  ],
26
26
  "dependencies": {
27
+ "@yarnpkg/parsers": "^3.0.0",
28
+ "js-yaml": "^3.13.1",
27
29
  "@vercel/nft": "1.5.0",
28
30
  "execa": "3.2.0",
29
31
  "fs-extra": "11.1.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.17.1"
39
+ "@vercel/build-utils": "13.18.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",