@vercel/backends 0.4.1 → 0.6.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.
Files changed (2) hide show
  1. package/dist/index.mjs +10 -355
  2. package/package.json +2 -4
package/dist/index.mjs CHANGED
@@ -1,11 +1,8 @@
1
1
  import { builtinModules, createRequire } from "node:module";
2
2
  import { createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import { basename, dirname, extname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
4
- import path, { delimiter, dirname as dirname$1, join as join$1 } from "path";
5
- import { FileBlob, FileFsRef, MANIFEST_VERSION, NodejsLambda, Span, createDiagnostics, debug, defaultCachePathGlob, download, execCommand, getEnvForPackageManager, getInternalServiceCronPath, getLambdaOptionsFromFunction, getNodeBinPaths, getNodeVersion, getReportedServiceType, glob, isBackendFramework, isBunVersion, isExperimentalBackendsWithoutIntrospectionEnabled, isScheduleTriggeredService, runNpmInstall, runPackageJsonScript, scanParentDirs, writeProjectManifest } from "@vercel/build-utils";
6
- import fs from "fs";
7
- import yaml from "js-yaml";
8
- import { parseSyml } from "@yarnpkg/parsers";
4
+ import { delimiter, dirname as dirname$1, join as join$1 } from "path";
5
+ import { FileBlob, FileFsRef, NodejsLambda, Span, createDiagnostics, debug, defaultCachePathGlob, download, execCommand, generateProjectManifest, getEnvForPackageManager, getInternalServiceCronPath, getLambdaOptionsFromFunction, getNodeBinPaths, getNodeVersion, getReportedServiceType, glob, isBackendFramework, isBunVersion, isExperimentalBackendsWithoutIntrospectionEnabled, isScheduleTriggeredService, runNpmInstall, runPackageJsonScript, scanParentDirs } from "@vercel/build-utils";
9
6
  import { lstat, readFile, rm, stat } from "node:fs/promises";
10
7
  import { build as build$2 } from "rolldown";
11
8
  import { exports } from "resolve.exports";
@@ -70,347 +67,6 @@ async function maybeExecBuildCommand(args, { spawnEnv }) {
70
67
 
71
68
  //#endregion
72
69
  //#region src/diagnostics.ts
73
- function classifySource(resolvedUrl) {
74
- if (!resolvedUrl) return {};
75
- if (resolvedUrl.startsWith("file:")) return {
76
- source: "file",
77
- sourceUrl: resolvedUrl.slice(5)
78
- };
79
- if (resolvedUrl.startsWith("git+") || resolvedUrl.startsWith("git://")) return {
80
- source: "git",
81
- sourceUrl: resolvedUrl.replace(/^git\+/, "")
82
- };
83
- try {
84
- return {
85
- source: "registry",
86
- sourceUrl: new URL(resolvedUrl).origin
87
- };
88
- } catch {
89
- return {};
90
- }
91
- }
92
- function npmEntryScopes(entry) {
93
- const scopes = [];
94
- if (entry.dev) scopes.push("dev");
95
- if (entry.peer) scopes.push("peer");
96
- if (entry.optional) scopes.push("optional");
97
- if (scopes.length === 0) scopes.push("prod");
98
- return scopes;
99
- }
100
- function parseNpmLock(content, lockfileVersion) {
101
- const lockMap = /* @__PURE__ */ new Map();
102
- const parsed = JSON.parse(content);
103
- if ((lockfileVersion ?? parsed.lockfileVersion ?? 1) >= 2) {
104
- const packages = parsed.packages;
105
- if (!packages) return lockMap;
106
- for (const [key, entry] of Object.entries(packages)) {
107
- if (key === "") continue;
108
- if (!key.startsWith("node_modules/")) continue;
109
- if (entry.link === true) continue;
110
- const rest = key.slice(13);
111
- const isScoped = rest.startsWith("@");
112
- const slashCount = (rest.match(/\//g) ?? []).length;
113
- if (isScoped ? slashCount !== 1 : slashCount !== 0) continue;
114
- const resolved = entry.resolved;
115
- if (resolved?.startsWith("file:")) continue;
116
- const version$1 = entry.version ?? "";
117
- const existing = lockMap.get(rest);
118
- if (existing && !isHigherVersion(version$1, existing.resolved)) continue;
119
- const { source, sourceUrl } = classifySource(resolved);
120
- const lockEntry = {
121
- resolved: version$1,
122
- scopes: npmEntryScopes(entry)
123
- };
124
- if (source) lockEntry.source = source;
125
- if (sourceUrl) lockEntry.sourceUrl = sourceUrl;
126
- lockMap.set(rest, lockEntry);
127
- }
128
- } else {
129
- const dependencies = parsed.dependencies;
130
- if (!dependencies) return lockMap;
131
- const walk = (deps) => {
132
- for (const [name, entry] of Object.entries(deps)) {
133
- const resolved = entry.resolved;
134
- if (!resolved?.startsWith("file:")) {
135
- const version$1 = entry.version ?? "";
136
- const existing = lockMap.get(name);
137
- if (!existing || isHigherVersion(version$1, existing.resolved)) {
138
- const { source, sourceUrl } = classifySource(resolved);
139
- const lockEntry = {
140
- resolved: version$1,
141
- scopes: npmEntryScopes(entry)
142
- };
143
- if (source) lockEntry.source = source;
144
- if (sourceUrl) lockEntry.sourceUrl = sourceUrl;
145
- lockMap.set(name, lockEntry);
146
- }
147
- }
148
- const nested = entry.dependencies;
149
- if (nested) walk(nested);
150
- }
151
- };
152
- walk(dependencies);
153
- }
154
- return lockMap;
155
- }
156
- function isHigherVersion(a, b) {
157
- const seg = (v) => v.split(/[.\-+]/).map((s) => parseInt(s, 10) || 0);
158
- const pa = seg(a);
159
- const pb = seg(b);
160
- for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
161
- if ((pa[i] ?? 0) > (pb[i] ?? 0)) return true;
162
- if ((pa[i] ?? 0) < (pb[i] ?? 0)) return false;
163
- }
164
- return false;
165
- }
166
- function extractPackageName(spec) {
167
- const s = spec.replace(/\(.*\)$/, "");
168
- if (s.startsWith("@")) {
169
- const i$1 = s.indexOf("@", 1);
170
- return i$1 === -1 ? null : s.slice(0, i$1);
171
- }
172
- const i = s.lastIndexOf("@");
173
- return i <= 0 ? null : s.slice(0, i);
174
- }
175
- function parsePnpmV9Key(key) {
176
- const name = extractPackageName(key);
177
- if (!name) return null;
178
- return {
179
- name,
180
- version: key.replace(/\(.*\)$/, "").slice(name.length + 1)
181
- };
182
- }
183
- function parsePnpmV5Key(key) {
184
- if (!key.startsWith("/")) return null;
185
- const rest = key.slice(1);
186
- if (rest.startsWith("@")) {
187
- const firstSlash = rest.indexOf("/");
188
- if (firstSlash === -1) return null;
189
- const secondSlash = rest.indexOf("/", firstSlash + 1);
190
- if (secondSlash === -1) return null;
191
- return {
192
- name: rest.slice(0, secondSlash),
193
- version: rest.slice(secondSlash + 1).split("_")[0]
194
- };
195
- }
196
- const slashIndex = rest.indexOf("/");
197
- if (slashIndex === -1) return null;
198
- return {
199
- name: rest.slice(0, slashIndex),
200
- version: rest.slice(slashIndex + 1).split("_")[0]
201
- };
202
- }
203
- function parsePnpmV6Key(key) {
204
- if (!key.startsWith("/")) return null;
205
- const rest = key.slice(1);
206
- let atIdx;
207
- if (rest.startsWith("@")) atIdx = rest.indexOf("@", 1);
208
- else atIdx = rest.indexOf("@");
209
- if (atIdx === -1) return null;
210
- return {
211
- name: rest.slice(0, atIdx),
212
- version: rest.slice(atIdx + 1).split("_")[0].replace(/\(.*\)$/, "")
213
- };
214
- }
215
- function classifyPnpmResolution(resolution) {
216
- if (!resolution) return {};
217
- if (resolution.type === "directory" || resolution.directory) return { local: true };
218
- if (typeof resolution.tarball === "string") return classifySource(resolution.tarball);
219
- if (resolution.type === "git" || typeof resolution.repo === "string") return {
220
- source: "git",
221
- sourceUrl: resolution.repo ?? void 0
222
- };
223
- return {};
224
- }
225
- function parsePnpmLock(content, lockfileVersion) {
226
- const lockMap = /* @__PURE__ */ new Map();
227
- const docs = [];
228
- yaml.safeLoadAll(content, (doc) => docs.push(doc));
229
- const parsedYaml = docs[0];
230
- if (!parsedYaml) return lockMap;
231
- const lv = lockfileVersion ?? Number(parsedYaml.lockfileVersion ?? "0");
232
- const packages = parsedYaml.packages;
233
- if (!packages) return lockMap;
234
- const parseKey = lv >= 9 ? parsePnpmV9Key : lv >= 6 ? parsePnpmV6Key : parsePnpmV5Key;
235
- for (const [key, entry] of Object.entries(packages)) {
236
- const keyParsed = parseKey(key);
237
- if (!keyParsed) continue;
238
- const { name, version: version$1 } = keyParsed;
239
- const resolution = entry.resolution;
240
- const { local, source, sourceUrl } = classifyPnpmResolution(resolution);
241
- if (local) continue;
242
- const existing = lockMap.get(name);
243
- if (existing && !isHigherVersion(version$1, existing.resolved)) continue;
244
- const lockEntry = {
245
- resolved: version$1,
246
- scopes: ["prod"]
247
- };
248
- if (source) lockEntry.source = source;
249
- if (sourceUrl) lockEntry.sourceUrl = sourceUrl;
250
- lockMap.set(name, lockEntry);
251
- }
252
- return lockMap;
253
- }
254
- function parseYarnLock(content, lockfileVersion) {
255
- const lockMap = /* @__PURE__ */ new Map();
256
- const isBerry = (lockfileVersion ?? 1) >= 2;
257
- const parsed = parseSyml(content);
258
- for (const [key, entry] of Object.entries(parsed)) {
259
- if (key === "__metadata" || !entry) continue;
260
- if (isBerry && entry.linkType === "soft") continue;
261
- const version$1 = entry.version;
262
- if (!version$1) continue;
263
- let source;
264
- let sourceUrl;
265
- if (!isBerry && entry.resolved) {
266
- if (entry.resolved.startsWith("file:")) continue;
267
- const classified = classifySource(entry.resolved);
268
- source = classified.source;
269
- sourceUrl = classified.sourceUrl;
270
- }
271
- const specifiers = key.split(",").map((s) => s.trim().replace(/^"|"$/g, ""));
272
- let name = null;
273
- for (const spec of specifiers) {
274
- name = extractPackageName(spec);
275
- if (name) break;
276
- }
277
- if (!name) continue;
278
- const existing = lockMap.get(name);
279
- if (existing && !isHigherVersion(version$1, existing.resolved)) continue;
280
- const lockEntry = {
281
- resolved: version$1,
282
- scopes: ["prod"]
283
- };
284
- if (source) lockEntry.source = source;
285
- if (sourceUrl) lockEntry.sourceUrl = sourceUrl;
286
- lockMap.set(name, lockEntry);
287
- }
288
- return lockMap;
289
- }
290
- function parseBunLock(content) {
291
- const lockMap = /* @__PURE__ */ new Map();
292
- const json = content.replace(/,(\s*[}\]])/g, "$1");
293
- const packages = JSON.parse(json).packages;
294
- if (!packages) return lockMap;
295
- for (const [name, value] of Object.entries(packages)) {
296
- if (!Array.isArray(value)) continue;
297
- const ref = value[0];
298
- if (!ref || typeof ref !== "string") continue;
299
- const pkgName = extractPackageName(ref);
300
- if (!pkgName) continue;
301
- const version$1 = ref.slice(pkgName.length + 1);
302
- if (!version$1) continue;
303
- if (version$1.startsWith("file:") || version$1.startsWith("workspace:")) continue;
304
- const existingBun = lockMap.get(name);
305
- if (existingBun && !isHigherVersion(version$1, existingBun.resolved)) continue;
306
- lockMap.set(name, {
307
- resolved: version$1,
308
- scopes: ["prod"]
309
- });
310
- }
311
- return lockMap;
312
- }
313
- async function parseLockfile(cliType, lockfilePath, lockfileVersion) {
314
- if (cliType === "bun" && lockfileVersion === 0) return /* @__PURE__ */ new Map();
315
- if (cliType === "vlt") return /* @__PURE__ */ new Map();
316
- const content = await fs.promises.readFile(lockfilePath, "utf-8");
317
- switch (cliType) {
318
- case "npm": return parseNpmLock(content, lockfileVersion);
319
- case "pnpm": return parsePnpmLock(content, lockfileVersion);
320
- case "yarn": return parseYarnLock(content, lockfileVersion);
321
- case "bun": return parseBunLock(content);
322
- default: return /* @__PURE__ */ new Map();
323
- }
324
- }
325
- async function readPackageJson(startDir) {
326
- let current = startDir;
327
- for (;;) try {
328
- const content = await fs.promises.readFile(path.join(current, "package.json"), "utf-8");
329
- return JSON.parse(content);
330
- } catch {
331
- const parent = path.dirname(current);
332
- if (parent === current) return null;
333
- current = parent;
334
- }
335
- }
336
- function buildDirectMaps(pkgJson) {
337
- const directScopes = /* @__PURE__ */ new Map();
338
- const directRequested = /* @__PURE__ */ new Map();
339
- const add = (deps, scope) => {
340
- if (!deps || typeof deps !== "object") return;
341
- for (const [name, specifier] of Object.entries(deps)) {
342
- if (!directScopes.has(name)) directScopes.set(name, /* @__PURE__ */ new Set());
343
- directScopes.get(name).add(scope);
344
- if (!directRequested.has(name)) directRequested.set(name, specifier);
345
- }
346
- };
347
- add(pkgJson.dependencies, "prod");
348
- add(pkgJson.devDependencies, "dev");
349
- add(pkgJson.peerDependencies, "peer");
350
- add(pkgJson.optionalDependencies, "optional");
351
- return {
352
- directScopes,
353
- directRequested
354
- };
355
- }
356
- async function generateProjectManifest({ workPath, nodeVersion, cliType, lockfilePath, lockfileVersion, framework, serviceType }) {
357
- try {
358
- const pkgJson = await readPackageJson(workPath);
359
- if (!pkgJson) return;
360
- const { directScopes, directRequested } = buildDirectMaps(pkgJson);
361
- const lockMap = lockfilePath ? await parseLockfile(cliType, lockfilePath, lockfileVersion) : /* @__PURE__ */ new Map();
362
- const directDeps = [];
363
- const transitiveDeps = [];
364
- for (const [name, scopes] of directScopes) {
365
- const lock = lockMap.get(name);
366
- const dep = {
367
- name,
368
- type: "direct",
369
- scopes: [...scopes].sort(),
370
- requested: directRequested.get(name),
371
- resolved: lock?.resolved ?? ""
372
- };
373
- if (lock?.source) dep.source = lock.source;
374
- if (lock?.sourceUrl) dep.sourceUrl = lock.sourceUrl;
375
- directDeps.push(dep);
376
- }
377
- for (const [name, lock] of lockMap) {
378
- if (directScopes.has(name)) continue;
379
- const dep = {
380
- name,
381
- type: "transitive",
382
- scopes: lock.scopes,
383
- resolved: lock.resolved
384
- };
385
- if (lock.source) dep.source = lock.source;
386
- if (lock.sourceUrl) dep.sourceUrl = lock.sourceUrl;
387
- transitiveDeps.push(dep);
388
- }
389
- const runtimeVersion = { resolved: String(nodeVersion.major) };
390
- const enginesNode = pkgJson.engines?.node;
391
- if (enginesNode) {
392
- runtimeVersion.requested = enginesNode;
393
- runtimeVersion.requestedSource = "package.json";
394
- } else for (const filename of [".node-version", ".nvmrc"]) try {
395
- const trimmed = (await fs.promises.readFile(path.join(workPath, filename), "utf-8")).trim();
396
- if (trimmed) {
397
- runtimeVersion.requested = trimmed;
398
- runtimeVersion.requestedSource = filename;
399
- break;
400
- }
401
- } catch {}
402
- await writeProjectManifest({
403
- version: MANIFEST_VERSION,
404
- runtime: "node",
405
- ...framework ? { framework } : {},
406
- ...serviceType ? { serviceType } : {},
407
- runtimeVersion,
408
- dependencies: [...directDeps.sort((a, b) => a.name.localeCompare(b.name)), ...transitiveDeps.sort((a, b) => a.name.localeCompare(b.name))]
409
- }, workPath, "node");
410
- } catch (err) {
411
- debug(`generateProjectManifest: ${err instanceof Error ? err.message : String(err)}`);
412
- }
413
- }
414
70
  const diagnostics = createDiagnostics("node");
415
71
 
416
72
  //#endregion
@@ -1267,10 +923,10 @@ async function resolveShimFormat(args) {
1267
923
  //#endregion
1268
924
  //#region src/crons.ts
1269
925
  const DYNAMIC_SCHEDULE = "<dynamic>";
1270
- /** Build the JSON route table embedded in `__VC_CRON_ROUTES`. */
926
+ /** Build the JSON route table embedded in the dispatcher shim. */
1271
927
  function buildCronRouteTable(crons) {
1272
928
  const table = {};
1273
- for (const cron of crons) table[cron.path] = "default";
929
+ for (const cron of crons) table[cron.path] = cron.exportName;
1274
930
  return table;
1275
931
  }
1276
932
  /**
@@ -1279,11 +935,6 @@ function buildCronRouteTable(crons) {
1279
935
  * Mirrors `packages/python/src/crons.ts` for static schedules. Returns
1280
936
  * `undefined` when the service is not schedule-triggered. Throws on
1281
937
  * `<dynamic>` schedules — that path is reserved for a follow-up.
1282
- *
1283
- * v1 always invokes the user module's default export, so this returns
1284
- * plain `Cron[]` (no handler-name field). When `handlerFunction` or
1285
- * `<dynamic>` support lands, this will need to grow a per-path handler
1286
- * name back.
1287
938
  */
1288
939
  function getServiceCrons(opts) {
1289
940
  const { service, entrypoint } = opts;
@@ -1293,7 +944,8 @@ function getServiceCrons(opts) {
1293
944
  if (service.schedule === DYNAMIC_SCHEDULE) throw new Error("Dynamic cron schedules (\"<dynamic>\") are not yet supported for JavaScript/TypeScript services. Use a static cron expression in vercel.json.");
1294
945
  return [{
1295
946
  path: getInternalServiceCronPath(service.name, entrypoint, "cron"),
1296
- schedule: service.schedule
947
+ schedule: service.schedule,
948
+ exportName: "default"
1297
949
  }];
1298
950
  }
1299
951
 
@@ -2145,7 +1797,10 @@ const build = async (args) => {
2145
1797
  return {
2146
1798
  routes,
2147
1799
  output,
2148
- ...cronEntries ? { crons: cronEntries } : {}
1800
+ ...cronEntries ? { crons: cronEntries.map(({ path, schedule }) => ({
1801
+ path,
1802
+ schedule
1803
+ })) } : {}
2149
1804
  };
2150
1805
  });
2151
1806
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/backends",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./dist/index.mjs",
6
6
  "homepage": "https://vercel.com/docs",
@@ -25,8 +25,6 @@
25
25
  "templates"
26
26
  ],
27
27
  "dependencies": {
28
- "@yarnpkg/parsers": "^3.0.0",
29
- "js-yaml": "^3.13.1",
30
28
  "@vercel/nft": "1.5.0",
31
29
  "execa": "3.2.0",
32
30
  "fs-extra": "11.1.0",
@@ -37,7 +35,7 @@
37
35
  "srvx": "0.8.9",
38
36
  "tsx": "4.21.0",
39
37
  "zod": "3.22.4",
40
- "@vercel/build-utils": "13.22.1"
38
+ "@vercel/build-utils": "13.24.0"
41
39
  },
42
40
  "devDependencies": {
43
41
  "@types/express": "5.0.3",