@vercel/cervel 0.0.9 → 0.0.11

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/cli.mjs CHANGED
@@ -1,23 +1,244 @@
1
+ import { builtinModules, createRequire } from "node:module";
1
2
  import { parseArgs } from "node:util";
2
- import { createRequire } from "module";
3
- import { existsSync } from "fs";
4
- import { readFile, rm, writeFile } from "fs/promises";
5
- import { extname, join } from "path";
3
+ import { existsSync } from "node:fs";
4
+ import { lstat, readFile, rm } from "node:fs/promises";
5
+ import { dirname, extname, join, relative } from "node:path";
6
6
  import { build } from "rolldown";
7
+ import { exports } from "resolve.exports";
8
+ import { isNativeError } from "node:util/types";
9
+ import { FileFsRef, debug, glob } from "@vercel/build-utils";
10
+ import { nodeFileTrace, resolve } from "@vercel/nft";
11
+ import { transform } from "oxc-transform";
12
+ import { createRequire as createRequire$1 } from "module";
7
13
  import { spawn } from "child_process";
14
+ import { extname as extname$1, join as join$1 } from "path";
15
+ import { existsSync as existsSync$1 } from "fs";
8
16
  import execa from "execa";
17
+ import { writeFile } from "fs/promises";
9
18
 
19
+ //#region src/node-file-trace.ts
20
+ const nodeFileTrace$1 = async (args) => {
21
+ const files = {};
22
+ const { tracedPaths } = args;
23
+ const compiledSourceFiles = await glob("**/*", {
24
+ cwd: args.outDir,
25
+ follow: true,
26
+ includeDirectories: true
27
+ });
28
+ for (const file of Object.keys(compiledSourceFiles)) files[file] = compiledSourceFiles[file];
29
+ /**
30
+ * While we're not using NFT to process source code, we are using it
31
+ * to tree shake node deps, and include any fs reads for files that are
32
+ * not part of the traced paths or compiled source files.
33
+ * Most of this is identical to the `@vercel/node` implementation
34
+ */
35
+ const result = await nodeFileTrace(Array.from(tracedPaths), {
36
+ base: args.repoRootPath,
37
+ processCwd: args.workPath,
38
+ ts: true,
39
+ mixedModules: true,
40
+ async resolve(id, parent, job, cjsResolve) {
41
+ return resolve(id, parent, job, cjsResolve);
42
+ },
43
+ async readFile(fsPath) {
44
+ try {
45
+ let source = await readFile(fsPath);
46
+ if (fsPath.endsWith(".ts") && !fsPath.endsWith(".d.ts") || fsPath.endsWith(".tsx") || fsPath.endsWith(".mts") || fsPath.endsWith(".cts")) source = (await transform(fsPath, source.toString())).code;
47
+ return source;
48
+ } catch (error) {
49
+ if (isNativeError(error) && "code" in error && (error.code === "ENOENT" || error.code === "EISDIR")) return null;
50
+ throw error;
51
+ }
52
+ }
53
+ });
54
+ if (!args.keepTracedPaths) for (const file of tracedPaths) {
55
+ const relativeFile = relative(args.repoRootPath, file);
56
+ result.fileList.delete(relativeFile);
57
+ }
58
+ debug("NFT traced files count:", result.fileList.size);
59
+ for (const file of result.fileList) {
60
+ const absolutePath = join(args.repoRootPath, file);
61
+ const stats = await lstat(absolutePath);
62
+ const outputPath = file;
63
+ if (stats.isSymbolicLink() || stats.isFile()) files[outputPath] = new FileFsRef({
64
+ fsPath: absolutePath,
65
+ mode: stats.mode
66
+ });
67
+ }
68
+ debug("Total files in context:", Object.keys(files).length);
69
+ return files;
70
+ };
71
+
72
+ //#endregion
73
+ //#region src/plugin.ts
74
+ const CJS_SHIM_PREFIX = "\0cjs-shim:";
75
+ const plugin = (args) => {
76
+ const packageJsonCache = /* @__PURE__ */ new Map();
77
+ const shimMeta = /* @__PURE__ */ new Map();
78
+ const tracedPaths = /* @__PURE__ */ new Set();
79
+ const isBareImport = (id) => {
80
+ return !id.startsWith(".") && !id.startsWith("/") && !/^[a-z][a-z0-9+.-]*:/i.test(id);
81
+ };
82
+ /**
83
+ * Read and cache package.json contents
84
+ */
85
+ const getPackageJson = async (pkgPath) => {
86
+ if (packageJsonCache.has(pkgPath)) return packageJsonCache.get(pkgPath);
87
+ try {
88
+ const contents = await readFile(pkgPath, "utf-8");
89
+ const parsed = JSON.parse(contents);
90
+ packageJsonCache.set(pkgPath, parsed);
91
+ return parsed;
92
+ } catch {
93
+ packageJsonCache.set(pkgPath, null);
94
+ return null;
95
+ }
96
+ };
97
+ /**
98
+ * Determine if a resolved module is CommonJS based on package.json exports
99
+ */
100
+ const isCommonJS = async (bareImport, resolvedPath, resolvedInfo) => {
101
+ const ext = extname(resolvedPath);
102
+ if (ext === ".cjs") return true;
103
+ if (ext === ".mjs") return false;
104
+ if (ext === ".js" || ext === ".ts") {
105
+ const pkgJsonPath = resolvedInfo.packageJsonPath;
106
+ if (!pkgJsonPath) return true;
107
+ const pkgJson = await getPackageJson(pkgJsonPath);
108
+ if (!pkgJson) return true;
109
+ const pkgDir = dirname(pkgJsonPath);
110
+ const relativePath = resolvedPath.startsWith(pkgDir) ? resolvedPath.slice(pkgDir.length + 1).replace(/\\/g, "/") : null;
111
+ if (!relativePath) return pkgJson.type !== "module";
112
+ const pkgName = pkgJson.name || "";
113
+ const subpath = bareImport.startsWith(pkgName) ? `.${bareImport.slice(pkgName.length)}` || "." : ".";
114
+ try {
115
+ if (exports(pkgJson, subpath, {
116
+ require: false,
117
+ conditions: ["node", "import"]
118
+ })?.some((p) => p === relativePath || p === `./${relativePath}`)) return false;
119
+ if (exports(pkgJson, subpath, {
120
+ require: true,
121
+ conditions: ["node", "require"]
122
+ })?.some((p) => p === relativePath || p === `./${relativePath}`)) return true;
123
+ } catch (err) {
124
+ console.warn("Export resolution failed::", err);
125
+ }
126
+ if (pkgJson.module) return false;
127
+ return pkgJson.type !== "module";
128
+ }
129
+ return true;
130
+ };
131
+ const isLocalImport = (id) => {
132
+ if (id.startsWith("node:")) return false;
133
+ if (id.includes("node_modules")) return false;
134
+ return true;
135
+ };
136
+ return {
137
+ name: "cervel",
138
+ resolveId: {
139
+ order: "pre",
140
+ async handler(id, importer, rOpts) {
141
+ if (id.startsWith(CJS_SHIM_PREFIX)) return {
142
+ id,
143
+ external: false
144
+ };
145
+ const resolved = await this.resolve(id, importer, rOpts);
146
+ if (builtinModules.includes(id)) return {
147
+ id: `node:${id}`,
148
+ external: true
149
+ };
150
+ if (resolved?.id && isLocalImport(resolved.id)) tracedPaths.add(resolved.id);
151
+ if (importer?.startsWith(CJS_SHIM_PREFIX) && isBareImport(id)) return {
152
+ id,
153
+ external: true
154
+ };
155
+ if (importer && isBareImport(id) && resolved?.id?.includes("node_modules")) {
156
+ if (args.shimBareImports) {
157
+ if (await isCommonJS(id, resolved.id, resolved)) {
158
+ const importerPkgJsonPath = (await this.resolve(importer))?.packageJsonPath;
159
+ if (importerPkgJsonPath) {
160
+ const importerPkgDir = relative(args.repoRootPath, dirname(importerPkgJsonPath));
161
+ const shimId$1 = `${CJS_SHIM_PREFIX}${importerPkgDir.replace(/\//g, "_")}_${id.replace(/\//g, "_")}`;
162
+ shimMeta.set(shimId$1, {
163
+ pkgDir: importerPkgDir,
164
+ pkgName: id
165
+ });
166
+ return {
167
+ id: shimId$1,
168
+ external: false
169
+ };
170
+ }
171
+ const shimId = `${CJS_SHIM_PREFIX}${id.replace(/\//g, "_")}`;
172
+ shimMeta.set(shimId, {
173
+ pkgDir: "",
174
+ pkgName: id
175
+ });
176
+ return {
177
+ id: shimId,
178
+ external: false
179
+ };
180
+ }
181
+ }
182
+ return {
183
+ external: true,
184
+ id
185
+ };
186
+ }
187
+ if (importer && isBareImport(id)) return resolved;
188
+ return {
189
+ external: true,
190
+ ...resolved,
191
+ id: resolved?.id || id
192
+ };
193
+ }
194
+ },
195
+ load: { async handler(id) {
196
+ if (id.startsWith(CJS_SHIM_PREFIX)) {
197
+ const meta = shimMeta.get(id);
198
+ if (!meta) return { code: `module.exports = require('${id.slice(10)}');` };
199
+ const { pkgDir, pkgName } = meta;
200
+ if (pkgDir) return { code: `
201
+ import { createRequire } from 'node:module';
202
+ import { fileURLToPath } from 'node:url';
203
+ import { dirname, join } from 'node:path';
204
+
205
+ const requireFromContext = createRequire(join(dirname(fileURLToPath(import.meta.url)), '${join("..", pkgDir, "package.json")}'));
206
+ module.exports = requireFromContext('${pkgName}');
207
+ `.trim() };
208
+ return { code: `module.exports = require('${pkgName}');` };
209
+ }
210
+ return null;
211
+ } },
212
+ writeBundle: {
213
+ order: "post",
214
+ async handler() {
215
+ const files = await nodeFileTrace$1({
216
+ outDir: args.outDir,
217
+ tracedPaths: Array.from(tracedPaths),
218
+ repoRootPath: args.repoRootPath,
219
+ workPath: args.workPath,
220
+ context: args.context,
221
+ keepTracedPaths: false
222
+ });
223
+ args.context.files = files;
224
+ }
225
+ }
226
+ };
227
+ };
228
+
229
+ //#endregion
10
230
  //#region src/rolldown.ts
11
- /**
12
- * Escapes special regex characters in a string to treat it as a literal pattern.
13
- */
14
- function escapeRegExp(string) {
15
- return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16
- }
231
+ const __dirname__filenameShim = `
232
+ import { createRequire as __createRequire } from 'node:module';
233
+ import { fileURLToPath as __fileURLToPath } from 'node:url';
234
+ import { dirname as __dirname_ } from 'node:path';
235
+ const require = __createRequire(import.meta.url);
236
+ const __filename = __fileURLToPath(import.meta.url);
237
+ const __dirname = __dirname_(__filename);
238
+ `.trim();
17
239
  const rolldown = async (args) => {
18
- const baseDir = args.repoRootPath || args.workPath;
19
240
  const entrypointPath = join(args.workPath, args.entrypoint);
20
- const shouldAddSourcemapSupport = false;
241
+ const outputDir = join(args.workPath, args.out);
21
242
  const extension = extname(args.entrypoint);
22
243
  const extensionMap = {
23
244
  ".ts": {
@@ -47,7 +268,6 @@ const rolldown = async (args) => {
47
268
  };
48
269
  const extensionInfo = extensionMap[extension] || extensionMap[".js"];
49
270
  let resolvedFormat = extensionInfo.format === "auto" ? void 0 : extensionInfo.format;
50
- const resolvedExtension = extensionInfo.extension;
51
271
  const packageJsonPath = join(args.workPath, "package.json");
52
272
  const external = [];
53
273
  let pkg = {};
@@ -65,21 +285,29 @@ const rolldown = async (args) => {
65
285
  for (const dependency of Object.keys(pkg.peerDependencies || {})) external.push(dependency);
66
286
  for (const dependency of Object.keys(pkg.optionalDependencies || {})) external.push(dependency);
67
287
  }
68
- const relativeOutputDir = args.out;
69
- const outputDir = join(baseDir, relativeOutputDir);
288
+ const resolvedExtension = resolvedFormat === "esm" ? "mjs" : "cjs";
289
+ const context = { files: {} };
70
290
  const out = await build({
71
291
  input: entrypointPath,
72
- cwd: baseDir,
292
+ cwd: args.workPath,
73
293
  platform: "node",
74
294
  tsconfig: true,
75
- external: external.map((pkg$1) => /* @__PURE__ */ new RegExp(`^${escapeRegExp(pkg$1)}`)),
295
+ plugins: [plugin({
296
+ repoRootPath: args.repoRootPath,
297
+ outDir: outputDir,
298
+ workPath: args.workPath,
299
+ shimBareImports: resolvedFormat === "esm",
300
+ context
301
+ })],
76
302
  output: {
77
303
  cleanDir: true,
78
304
  dir: outputDir,
79
305
  format: resolvedFormat,
80
306
  entryFileNames: `[name].${resolvedExtension}`,
81
307
  preserveModules: true,
82
- sourcemap: false
308
+ preserveModulesRoot: args.repoRootPath,
309
+ sourcemap: false,
310
+ banner: resolvedFormat === "esm" ? __dirname__filenameShim : void 0
83
311
  }
84
312
  });
85
313
  let handler = null;
@@ -95,10 +323,9 @@ const rolldown = async (args) => {
95
323
  };
96
324
  return {
97
325
  result: {
98
- pkg,
99
- shouldAddSourcemapSupport,
100
326
  handler,
101
- outputDir
327
+ outputDir,
328
+ outputFiles: context.files
102
329
  },
103
330
  cleanup
104
331
  };
@@ -135,9 +362,9 @@ const Colors = {
135
362
 
136
363
  //#endregion
137
364
  //#region src/typescript.ts
138
- const require_ = createRequire(import.meta.url);
365
+ const require_ = createRequire$1(import.meta.url);
139
366
  const typescript = (args) => {
140
- const extension = extname(args.entrypoint);
367
+ const extension = extname$1(args.entrypoint);
141
368
  if (![
142
369
  ".ts",
143
370
  ".mts",
@@ -184,11 +411,11 @@ async function doTypeCheck(args, tscPath) {
184
411
  child.stderr?.on("data", (data) => {
185
412
  stderr += data.toString();
186
413
  });
187
- await new Promise((resolve, reject) => {
414
+ await new Promise((resolve$1, reject) => {
188
415
  child.on("close", (code) => {
189
416
  if (code === 0) {
190
417
  console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck complete`));
191
- resolve();
418
+ resolve$1();
192
419
  } else {
193
420
  const output = stdout || stderr;
194
421
  if (output) {
@@ -211,10 +438,10 @@ const resolveTscPath = (args) => {
211
438
  }
212
439
  };
213
440
  const findNearestTsconfig = async (workPath) => {
214
- const tsconfigPath = join(workPath, "tsconfig.json");
215
- if (existsSync(tsconfigPath)) return tsconfigPath;
441
+ const tsconfigPath = join$1(workPath, "tsconfig.json");
442
+ if (existsSync$1(tsconfigPath)) return tsconfigPath;
216
443
  if (workPath === "/") return;
217
- return findNearestTsconfig(join(workPath, ".."));
444
+ return findNearestTsconfig(join$1(workPath, ".."));
218
445
  };
219
446
 
220
447
  //#endregion
@@ -290,20 +517,18 @@ const findEntrypoint = async (cwd, options) => {
290
517
  //#region src/index.ts
291
518
  const require = createRequire(import.meta.url);
292
519
  const build$1 = async (args) => {
293
- const entrypoint = args.entrypoint || await findEntrypoint(args.cwd);
520
+ const entrypoint = args.entrypoint || await findEntrypoint(args.workPath);
294
521
  const tsPromise = typescript({
295
- ...args,
296
522
  entrypoint,
297
- workPath: args.cwd
523
+ workPath: args.workPath
298
524
  });
299
525
  const rolldownResult = await rolldown({
300
- ...args,
301
526
  entrypoint,
302
- workPath: args.cwd,
303
- repoRootPath: args.cwd,
527
+ workPath: args.workPath,
528
+ repoRootPath: args.repoRootPath,
304
529
  out: args.out
305
530
  });
306
- await writeFile(join(args.cwd, args.out, ".cervel.json"), JSON.stringify({ handler: rolldownResult.result.handler }, null, 2));
531
+ await writeFile(join(args.workPath, args.out, ".cervel.json"), JSON.stringify({ handler: rolldownResult.result.handler }, null, 2));
307
532
  console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Build complete`));
308
533
  const typecheckComplete = true;
309
534
  const result = tsPromise ? await Promise.race([tsPromise.then(() => typecheckComplete), Promise.resolve(false)]) : true;
@@ -314,7 +539,7 @@ const build$1 = async (args) => {
314
539
  };
315
540
  };
316
541
  const serve = async (args) => {
317
- const entrypoint = await findEntrypoint(args.cwd);
542
+ const entrypoint = await findEntrypoint(args.workPath);
318
543
  const srvxBin = join(require.resolve("srvx"), "..", "..", "..", "bin", "srvx.mjs");
319
544
  const tsxBin = require.resolve("tsx");
320
545
  const restArgs = Object.entries(args.rest).filter(([, value]) => value !== void 0 && value !== false).map(([key, value]) => typeof value === "boolean" ? `--${key}` : `--${key}=${value}`);
@@ -324,7 +549,7 @@ const serve = async (args) => {
324
549
  ...restArgs,
325
550
  entrypoint
326
551
  ], {
327
- cwd: args.cwd,
552
+ cwd: args.workPath,
328
553
  stdio: "inherit"
329
554
  });
330
555
  };
@@ -362,15 +587,18 @@ const main = async () => {
362
587
  const options = parseArgs$1(process.argv.slice(2));
363
588
  const { cwd, out,...rest } = options.values;
364
589
  const [command, entrypoint] = options.positionals;
590
+ const workPath = cwd;
591
+ const repoRootPath = cwd;
365
592
  if (command === "build") {
366
593
  const { tsPromise } = await build$1({
367
- cwd,
594
+ workPath,
595
+ repoRootPath,
368
596
  out,
369
597
  entrypoint
370
598
  });
371
599
  await tsPromise;
372
600
  } else await serve({
373
- cwd,
601
+ workPath,
374
602
  rest
375
603
  });
376
604
  };
package/dist/index.d.mts CHANGED
@@ -1,30 +1,60 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
- import { ParseArgsOptionsConfig } from "util";
2
+ import { BuildOptions, Files } from "@vercel/build-utils";
3
+ import * as _vercel_build_utils_dist_types_js0 from "@vercel/build-utils/dist/types.js";
4
+ import { ParseArgsConfig } from "node:util";
3
5
 
4
6
  //#region src/find-entrypoint.d.ts
5
7
  declare const findEntrypoint: (cwd: string, options?: {
6
8
  ignoreRegex?: boolean;
7
9
  }) => Promise<string>;
8
10
  //#endregion
9
- //#region src/index.d.ts
10
- declare const getBuildSummary: (outputDir: string) => Promise<any>;
11
- declare const build: (args: {
11
+ //#region src/types.d.ts
12
+ /**
13
+ * Core path options derived from BuildOptions.
14
+ * - workPath: the workspace/project directory (where package.json is)
15
+ * - repoRootPath: the root of the monorepo/repo
16
+ */
17
+ type PathOptions = Pick<BuildOptions, 'workPath' | 'repoRootPath'>;
18
+ /**
19
+ * Options for the cervel build function.
20
+ */
21
+ type CervelBuildOptions = PathOptions & {
12
22
  entrypoint?: string;
13
- cwd: string;
14
23
  out: string;
15
- }) => Promise<{
24
+ };
25
+ /**
26
+ * Options for the cervel serve function.
27
+ */
28
+ type CervelServeOptions = Pick<BuildOptions, 'workPath'> & {
29
+ rest: Record<string, string | boolean | undefined>;
30
+ };
31
+ /**
32
+ * Options for node file tracing.
33
+ */
34
+ type NodeFileTraceOptions = PathOptions & {
35
+ keepTracedPaths: boolean;
36
+ outDir: string;
37
+ tracedPaths: string[];
38
+ context: {
39
+ files: Files;
40
+ };
41
+ };
42
+ //#endregion
43
+ //#region src/node-file-trace.d.ts
44
+ declare const nodeFileTrace: (args: NodeFileTraceOptions) => Promise<Files>;
45
+ //#endregion
46
+ //#region src/index.d.ts
47
+ type ParseArgsOptionsConfig = NonNullable<ParseArgsConfig['options']>;
48
+ declare const getBuildSummary: (outputDir: string) => Promise<any>;
49
+ declare const build: (args: CervelBuildOptions) => Promise<{
16
50
  rolldownResult: {
17
- pkg: Record<string, unknown>;
18
- shouldAddSourcemapSupport: boolean;
19
51
  handler: string;
20
52
  outputDir: string;
53
+ outputFiles: _vercel_build_utils_dist_types_js0.Files;
21
54
  };
22
55
  tsPromise: Promise<void> | null | undefined;
23
56
  }>;
24
- declare const serve: (args: {
25
- cwd: string;
26
- rest: Record<string, string | boolean | undefined>;
27
- }) => Promise<void>;
57
+ declare const serve: (args: CervelServeOptions) => Promise<void>;
28
58
  declare const srvxOptions: ParseArgsOptionsConfig;
29
59
  //#endregion
30
- export { build, findEntrypoint, getBuildSummary, serve, srvxOptions };
60
+ export { type CervelBuildOptions, type CervelServeOptions, type PathOptions, build, findEntrypoint, getBuildSummary, nodeFileTrace, serve, srvxOptions };
package/dist/index.mjs CHANGED
@@ -1,22 +1,243 @@
1
- import { createRequire } from "module";
2
- import { existsSync } from "fs";
3
- import { readFile, rm, writeFile } from "fs/promises";
4
- import { extname, join } from "path";
1
+ import { builtinModules, createRequire } from "node:module";
2
+ import { existsSync } from "node:fs";
3
+ import { lstat, readFile, rm } from "node:fs/promises";
4
+ import { dirname, extname, join, relative } from "node:path";
5
5
  import { build as build$1 } from "rolldown";
6
+ import { exports } from "resolve.exports";
7
+ import { isNativeError } from "node:util/types";
8
+ import { FileFsRef, debug, glob } from "@vercel/build-utils";
9
+ import { nodeFileTrace as nodeFileTrace$1, resolve } from "@vercel/nft";
10
+ import { transform } from "oxc-transform";
11
+ import { createRequire as createRequire$1 } from "module";
6
12
  import { spawn } from "child_process";
13
+ import { extname as extname$1, join as join$1 } from "path";
14
+ import { existsSync as existsSync$1 } from "fs";
7
15
  import execa from "execa";
16
+ import { readFile as readFile$1, writeFile } from "fs/promises";
8
17
 
18
+ //#region src/node-file-trace.ts
19
+ const nodeFileTrace = async (args) => {
20
+ const files = {};
21
+ const { tracedPaths } = args;
22
+ const compiledSourceFiles = await glob("**/*", {
23
+ cwd: args.outDir,
24
+ follow: true,
25
+ includeDirectories: true
26
+ });
27
+ for (const file of Object.keys(compiledSourceFiles)) files[file] = compiledSourceFiles[file];
28
+ /**
29
+ * While we're not using NFT to process source code, we are using it
30
+ * to tree shake node deps, and include any fs reads for files that are
31
+ * not part of the traced paths or compiled source files.
32
+ * Most of this is identical to the `@vercel/node` implementation
33
+ */
34
+ const result = await nodeFileTrace$1(Array.from(tracedPaths), {
35
+ base: args.repoRootPath,
36
+ processCwd: args.workPath,
37
+ ts: true,
38
+ mixedModules: true,
39
+ async resolve(id, parent, job, cjsResolve) {
40
+ return resolve(id, parent, job, cjsResolve);
41
+ },
42
+ async readFile(fsPath) {
43
+ try {
44
+ let source = await readFile(fsPath);
45
+ if (fsPath.endsWith(".ts") && !fsPath.endsWith(".d.ts") || fsPath.endsWith(".tsx") || fsPath.endsWith(".mts") || fsPath.endsWith(".cts")) source = (await transform(fsPath, source.toString())).code;
46
+ return source;
47
+ } catch (error) {
48
+ if (isNativeError(error) && "code" in error && (error.code === "ENOENT" || error.code === "EISDIR")) return null;
49
+ throw error;
50
+ }
51
+ }
52
+ });
53
+ if (!args.keepTracedPaths) for (const file of tracedPaths) {
54
+ const relativeFile = relative(args.repoRootPath, file);
55
+ result.fileList.delete(relativeFile);
56
+ }
57
+ debug("NFT traced files count:", result.fileList.size);
58
+ for (const file of result.fileList) {
59
+ const absolutePath = join(args.repoRootPath, file);
60
+ const stats = await lstat(absolutePath);
61
+ const outputPath = file;
62
+ if (stats.isSymbolicLink() || stats.isFile()) files[outputPath] = new FileFsRef({
63
+ fsPath: absolutePath,
64
+ mode: stats.mode
65
+ });
66
+ }
67
+ debug("Total files in context:", Object.keys(files).length);
68
+ return files;
69
+ };
70
+
71
+ //#endregion
72
+ //#region src/plugin.ts
73
+ const CJS_SHIM_PREFIX = "\0cjs-shim:";
74
+ const plugin = (args) => {
75
+ const packageJsonCache = /* @__PURE__ */ new Map();
76
+ const shimMeta = /* @__PURE__ */ new Map();
77
+ const tracedPaths = /* @__PURE__ */ new Set();
78
+ const isBareImport = (id) => {
79
+ return !id.startsWith(".") && !id.startsWith("/") && !/^[a-z][a-z0-9+.-]*:/i.test(id);
80
+ };
81
+ /**
82
+ * Read and cache package.json contents
83
+ */
84
+ const getPackageJson = async (pkgPath) => {
85
+ if (packageJsonCache.has(pkgPath)) return packageJsonCache.get(pkgPath);
86
+ try {
87
+ const contents = await readFile(pkgPath, "utf-8");
88
+ const parsed = JSON.parse(contents);
89
+ packageJsonCache.set(pkgPath, parsed);
90
+ return parsed;
91
+ } catch {
92
+ packageJsonCache.set(pkgPath, null);
93
+ return null;
94
+ }
95
+ };
96
+ /**
97
+ * Determine if a resolved module is CommonJS based on package.json exports
98
+ */
99
+ const isCommonJS = async (bareImport, resolvedPath, resolvedInfo) => {
100
+ const ext = extname(resolvedPath);
101
+ if (ext === ".cjs") return true;
102
+ if (ext === ".mjs") return false;
103
+ if (ext === ".js" || ext === ".ts") {
104
+ const pkgJsonPath = resolvedInfo.packageJsonPath;
105
+ if (!pkgJsonPath) return true;
106
+ const pkgJson = await getPackageJson(pkgJsonPath);
107
+ if (!pkgJson) return true;
108
+ const pkgDir = dirname(pkgJsonPath);
109
+ const relativePath = resolvedPath.startsWith(pkgDir) ? resolvedPath.slice(pkgDir.length + 1).replace(/\\/g, "/") : null;
110
+ if (!relativePath) return pkgJson.type !== "module";
111
+ const pkgName = pkgJson.name || "";
112
+ const subpath = bareImport.startsWith(pkgName) ? `.${bareImport.slice(pkgName.length)}` || "." : ".";
113
+ try {
114
+ if (exports(pkgJson, subpath, {
115
+ require: false,
116
+ conditions: ["node", "import"]
117
+ })?.some((p) => p === relativePath || p === `./${relativePath}`)) return false;
118
+ if (exports(pkgJson, subpath, {
119
+ require: true,
120
+ conditions: ["node", "require"]
121
+ })?.some((p) => p === relativePath || p === `./${relativePath}`)) return true;
122
+ } catch (err) {
123
+ console.warn("Export resolution failed::", err);
124
+ }
125
+ if (pkgJson.module) return false;
126
+ return pkgJson.type !== "module";
127
+ }
128
+ return true;
129
+ };
130
+ const isLocalImport = (id) => {
131
+ if (id.startsWith("node:")) return false;
132
+ if (id.includes("node_modules")) return false;
133
+ return true;
134
+ };
135
+ return {
136
+ name: "cervel",
137
+ resolveId: {
138
+ order: "pre",
139
+ async handler(id, importer, rOpts) {
140
+ if (id.startsWith(CJS_SHIM_PREFIX)) return {
141
+ id,
142
+ external: false
143
+ };
144
+ const resolved = await this.resolve(id, importer, rOpts);
145
+ if (builtinModules.includes(id)) return {
146
+ id: `node:${id}`,
147
+ external: true
148
+ };
149
+ if (resolved?.id && isLocalImport(resolved.id)) tracedPaths.add(resolved.id);
150
+ if (importer?.startsWith(CJS_SHIM_PREFIX) && isBareImport(id)) return {
151
+ id,
152
+ external: true
153
+ };
154
+ if (importer && isBareImport(id) && resolved?.id?.includes("node_modules")) {
155
+ if (args.shimBareImports) {
156
+ if (await isCommonJS(id, resolved.id, resolved)) {
157
+ const importerPkgJsonPath = (await this.resolve(importer))?.packageJsonPath;
158
+ if (importerPkgJsonPath) {
159
+ const importerPkgDir = relative(args.repoRootPath, dirname(importerPkgJsonPath));
160
+ const shimId$1 = `${CJS_SHIM_PREFIX}${importerPkgDir.replace(/\//g, "_")}_${id.replace(/\//g, "_")}`;
161
+ shimMeta.set(shimId$1, {
162
+ pkgDir: importerPkgDir,
163
+ pkgName: id
164
+ });
165
+ return {
166
+ id: shimId$1,
167
+ external: false
168
+ };
169
+ }
170
+ const shimId = `${CJS_SHIM_PREFIX}${id.replace(/\//g, "_")}`;
171
+ shimMeta.set(shimId, {
172
+ pkgDir: "",
173
+ pkgName: id
174
+ });
175
+ return {
176
+ id: shimId,
177
+ external: false
178
+ };
179
+ }
180
+ }
181
+ return {
182
+ external: true,
183
+ id
184
+ };
185
+ }
186
+ if (importer && isBareImport(id)) return resolved;
187
+ return {
188
+ external: true,
189
+ ...resolved,
190
+ id: resolved?.id || id
191
+ };
192
+ }
193
+ },
194
+ load: { async handler(id) {
195
+ if (id.startsWith(CJS_SHIM_PREFIX)) {
196
+ const meta = shimMeta.get(id);
197
+ if (!meta) return { code: `module.exports = require('${id.slice(10)}');` };
198
+ const { pkgDir, pkgName } = meta;
199
+ if (pkgDir) return { code: `
200
+ import { createRequire } from 'node:module';
201
+ import { fileURLToPath } from 'node:url';
202
+ import { dirname, join } from 'node:path';
203
+
204
+ const requireFromContext = createRequire(join(dirname(fileURLToPath(import.meta.url)), '${join("..", pkgDir, "package.json")}'));
205
+ module.exports = requireFromContext('${pkgName}');
206
+ `.trim() };
207
+ return { code: `module.exports = require('${pkgName}');` };
208
+ }
209
+ return null;
210
+ } },
211
+ writeBundle: {
212
+ order: "post",
213
+ async handler() {
214
+ const files = await nodeFileTrace({
215
+ outDir: args.outDir,
216
+ tracedPaths: Array.from(tracedPaths),
217
+ repoRootPath: args.repoRootPath,
218
+ workPath: args.workPath,
219
+ context: args.context,
220
+ keepTracedPaths: false
221
+ });
222
+ args.context.files = files;
223
+ }
224
+ }
225
+ };
226
+ };
227
+
228
+ //#endregion
9
229
  //#region src/rolldown.ts
10
- /**
11
- * Escapes special regex characters in a string to treat it as a literal pattern.
12
- */
13
- function escapeRegExp(string) {
14
- return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15
- }
230
+ const __dirname__filenameShim = `
231
+ import { createRequire as __createRequire } from 'node:module';
232
+ import { fileURLToPath as __fileURLToPath } from 'node:url';
233
+ import { dirname as __dirname_ } from 'node:path';
234
+ const require = __createRequire(import.meta.url);
235
+ const __filename = __fileURLToPath(import.meta.url);
236
+ const __dirname = __dirname_(__filename);
237
+ `.trim();
16
238
  const rolldown = async (args) => {
17
- const baseDir = args.repoRootPath || args.workPath;
18
239
  const entrypointPath = join(args.workPath, args.entrypoint);
19
- const shouldAddSourcemapSupport = false;
240
+ const outputDir = join(args.workPath, args.out);
20
241
  const extension = extname(args.entrypoint);
21
242
  const extensionMap = {
22
243
  ".ts": {
@@ -46,7 +267,6 @@ const rolldown = async (args) => {
46
267
  };
47
268
  const extensionInfo = extensionMap[extension] || extensionMap[".js"];
48
269
  let resolvedFormat = extensionInfo.format === "auto" ? void 0 : extensionInfo.format;
49
- const resolvedExtension = extensionInfo.extension;
50
270
  const packageJsonPath = join(args.workPath, "package.json");
51
271
  const external = [];
52
272
  let pkg = {};
@@ -64,21 +284,29 @@ const rolldown = async (args) => {
64
284
  for (const dependency of Object.keys(pkg.peerDependencies || {})) external.push(dependency);
65
285
  for (const dependency of Object.keys(pkg.optionalDependencies || {})) external.push(dependency);
66
286
  }
67
- const relativeOutputDir = args.out;
68
- const outputDir = join(baseDir, relativeOutputDir);
287
+ const resolvedExtension = resolvedFormat === "esm" ? "mjs" : "cjs";
288
+ const context = { files: {} };
69
289
  const out = await build$1({
70
290
  input: entrypointPath,
71
- cwd: baseDir,
291
+ cwd: args.workPath,
72
292
  platform: "node",
73
293
  tsconfig: true,
74
- external: external.map((pkg$1) => /* @__PURE__ */ new RegExp(`^${escapeRegExp(pkg$1)}`)),
294
+ plugins: [plugin({
295
+ repoRootPath: args.repoRootPath,
296
+ outDir: outputDir,
297
+ workPath: args.workPath,
298
+ shimBareImports: resolvedFormat === "esm",
299
+ context
300
+ })],
75
301
  output: {
76
302
  cleanDir: true,
77
303
  dir: outputDir,
78
304
  format: resolvedFormat,
79
305
  entryFileNames: `[name].${resolvedExtension}`,
80
306
  preserveModules: true,
81
- sourcemap: false
307
+ preserveModulesRoot: args.repoRootPath,
308
+ sourcemap: false,
309
+ banner: resolvedFormat === "esm" ? __dirname__filenameShim : void 0
82
310
  }
83
311
  });
84
312
  let handler = null;
@@ -94,10 +322,9 @@ const rolldown = async (args) => {
94
322
  };
95
323
  return {
96
324
  result: {
97
- pkg,
98
- shouldAddSourcemapSupport,
99
325
  handler,
100
- outputDir
326
+ outputDir,
327
+ outputFiles: context.files
101
328
  },
102
329
  cleanup
103
330
  };
@@ -134,9 +361,9 @@ const Colors = {
134
361
 
135
362
  //#endregion
136
363
  //#region src/typescript.ts
137
- const require_ = createRequire(import.meta.url);
364
+ const require_ = createRequire$1(import.meta.url);
138
365
  const typescript = (args) => {
139
- const extension = extname(args.entrypoint);
366
+ const extension = extname$1(args.entrypoint);
140
367
  if (![
141
368
  ".ts",
142
369
  ".mts",
@@ -183,11 +410,11 @@ async function doTypeCheck(args, tscPath) {
183
410
  child.stderr?.on("data", (data) => {
184
411
  stderr += data.toString();
185
412
  });
186
- await new Promise((resolve, reject) => {
413
+ await new Promise((resolve$1, reject) => {
187
414
  child.on("close", (code) => {
188
415
  if (code === 0) {
189
416
  console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck complete`));
190
- resolve();
417
+ resolve$1();
191
418
  } else {
192
419
  const output = stdout || stderr;
193
420
  if (output) {
@@ -210,10 +437,10 @@ const resolveTscPath = (args) => {
210
437
  }
211
438
  };
212
439
  const findNearestTsconfig = async (workPath) => {
213
- const tsconfigPath = join(workPath, "tsconfig.json");
214
- if (existsSync(tsconfigPath)) return tsconfigPath;
440
+ const tsconfigPath = join$1(workPath, "tsconfig.json");
441
+ if (existsSync$1(tsconfigPath)) return tsconfigPath;
215
442
  if (workPath === "/") return;
216
- return findNearestTsconfig(join(workPath, ".."));
443
+ return findNearestTsconfig(join$1(workPath, ".."));
217
444
  };
218
445
 
219
446
  //#endregion
@@ -289,24 +516,22 @@ const findEntrypoint = async (cwd, options) => {
289
516
  //#region src/index.ts
290
517
  const require = createRequire(import.meta.url);
291
518
  const getBuildSummary = async (outputDir) => {
292
- const buildSummary = await readFile(join(outputDir, ".cervel.json"), "utf-8");
519
+ const buildSummary = await readFile$1(join(outputDir, ".cervel.json"), "utf-8");
293
520
  return JSON.parse(buildSummary);
294
521
  };
295
522
  const build = async (args) => {
296
- const entrypoint = args.entrypoint || await findEntrypoint(args.cwd);
523
+ const entrypoint = args.entrypoint || await findEntrypoint(args.workPath);
297
524
  const tsPromise = typescript({
298
- ...args,
299
525
  entrypoint,
300
- workPath: args.cwd
526
+ workPath: args.workPath
301
527
  });
302
528
  const rolldownResult = await rolldown({
303
- ...args,
304
529
  entrypoint,
305
- workPath: args.cwd,
306
- repoRootPath: args.cwd,
530
+ workPath: args.workPath,
531
+ repoRootPath: args.repoRootPath,
307
532
  out: args.out
308
533
  });
309
- await writeFile(join(args.cwd, args.out, ".cervel.json"), JSON.stringify({ handler: rolldownResult.result.handler }, null, 2));
534
+ await writeFile(join(args.workPath, args.out, ".cervel.json"), JSON.stringify({ handler: rolldownResult.result.handler }, null, 2));
310
535
  console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Build complete`));
311
536
  const typecheckComplete = true;
312
537
  const result = tsPromise ? await Promise.race([tsPromise.then(() => typecheckComplete), Promise.resolve(false)]) : true;
@@ -317,7 +542,7 @@ const build = async (args) => {
317
542
  };
318
543
  };
319
544
  const serve = async (args) => {
320
- const entrypoint = await findEntrypoint(args.cwd);
545
+ const entrypoint = await findEntrypoint(args.workPath);
321
546
  const srvxBin = join(require.resolve("srvx"), "..", "..", "..", "bin", "srvx.mjs");
322
547
  const tsxBin = require.resolve("tsx");
323
548
  const restArgs = Object.entries(args.rest).filter(([, value]) => value !== void 0 && value !== false).map(([key, value]) => typeof value === "boolean" ? `--${key}` : `--${key}=${value}`);
@@ -327,7 +552,7 @@ const serve = async (args) => {
327
552
  ...restArgs,
328
553
  entrypoint
329
554
  ], {
330
- cwd: args.cwd,
555
+ cwd: args.workPath,
331
556
  stdio: "inherit"
332
557
  });
333
558
  };
@@ -360,4 +585,4 @@ const srvxOptions = {
360
585
  };
361
586
 
362
587
  //#endregion
363
- export { build, findEntrypoint, getBuildSummary, serve, srvxOptions };
588
+ export { build, findEntrypoint, getBuildSummary, nodeFileTrace, serve, srvxOptions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/cervel",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://vercel.com/docs",
6
6
  "publishConfig": {
@@ -28,16 +28,21 @@
28
28
  "dist"
29
29
  ],
30
30
  "dependencies": {
31
+ "@vercel/nft": "1.3.0",
31
32
  "execa": "3.2.0",
32
- "rolldown": "1.0.0-beta.59",
33
+ "nf3": "0.3.6",
34
+ "oxc-transform": "0.111.0",
35
+ "resolve.exports": "2.0.3",
36
+ "rolldown": "1.0.0-rc.1",
33
37
  "srvx": "0.8.9",
34
- "tsx": "4.21.0"
38
+ "tsx": "4.21.0",
39
+ "@vercel/build-utils": "13.2.16"
35
40
  },
36
41
  "peerDependencies": {
37
42
  "typescript": "^4.0.0 || ^5.0.0"
38
43
  },
39
44
  "devDependencies": {
40
- "@types/node": "24.7.0",
45
+ "@types/node": "20.11.0",
41
46
  "typescript": "4.9.5",
42
47
  "tsdown": "0.16.3",
43
48
  "vitest": "^2.0.1"