@vercel/cervel 0.0.11 → 0.0.13
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 +8 -591
- package/dist/index.d.mts +1 -59
- package/dist/index.mjs +1 -586
- package/package.json +2 -10
package/dist/cli.mjs
CHANGED
|
@@ -1,587 +1,6 @@
|
|
|
1
|
-
import { builtinModules, createRequire } from "node:module";
|
|
2
1
|
import { parseArgs } from "node:util";
|
|
3
|
-
import {
|
|
4
|
-
import { lstat, readFile, rm } from "node:fs/promises";
|
|
5
|
-
import { dirname, extname, join, relative } from "node:path";
|
|
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";
|
|
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";
|
|
16
|
-
import execa from "execa";
|
|
17
|
-
import { writeFile } from "fs/promises";
|
|
2
|
+
import { cervelBuild as build, cervelServe as serve, srvxOptions } from "@vercel/backends";
|
|
18
3
|
|
|
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
|
|
230
|
-
//#region src/rolldown.ts
|
|
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();
|
|
239
|
-
const rolldown = async (args) => {
|
|
240
|
-
const entrypointPath = join(args.workPath, args.entrypoint);
|
|
241
|
-
const outputDir = join(args.workPath, args.out);
|
|
242
|
-
const extension = extname(args.entrypoint);
|
|
243
|
-
const extensionMap = {
|
|
244
|
-
".ts": {
|
|
245
|
-
format: "auto",
|
|
246
|
-
extension: "js"
|
|
247
|
-
},
|
|
248
|
-
".mts": {
|
|
249
|
-
format: "esm",
|
|
250
|
-
extension: "mjs"
|
|
251
|
-
},
|
|
252
|
-
".cts": {
|
|
253
|
-
format: "cjs",
|
|
254
|
-
extension: "cjs"
|
|
255
|
-
},
|
|
256
|
-
".cjs": {
|
|
257
|
-
format: "cjs",
|
|
258
|
-
extension: "cjs"
|
|
259
|
-
},
|
|
260
|
-
".js": {
|
|
261
|
-
format: "auto",
|
|
262
|
-
extension: "js"
|
|
263
|
-
},
|
|
264
|
-
".mjs": {
|
|
265
|
-
format: "esm",
|
|
266
|
-
extension: "mjs"
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
const extensionInfo = extensionMap[extension] || extensionMap[".js"];
|
|
270
|
-
let resolvedFormat = extensionInfo.format === "auto" ? void 0 : extensionInfo.format;
|
|
271
|
-
const packageJsonPath = join(args.workPath, "package.json");
|
|
272
|
-
const external = [];
|
|
273
|
-
let pkg = {};
|
|
274
|
-
if (existsSync(packageJsonPath)) {
|
|
275
|
-
const source = await readFile(packageJsonPath, "utf8");
|
|
276
|
-
try {
|
|
277
|
-
pkg = JSON.parse(source.toString());
|
|
278
|
-
} catch (_e) {
|
|
279
|
-
pkg = {};
|
|
280
|
-
}
|
|
281
|
-
if (extensionInfo.format === "auto") if (pkg.type === "module") resolvedFormat = "esm";
|
|
282
|
-
else resolvedFormat = "cjs";
|
|
283
|
-
for (const dependency of Object.keys(pkg.dependencies || {})) external.push(dependency);
|
|
284
|
-
for (const dependency of Object.keys(pkg.devDependencies || {})) external.push(dependency);
|
|
285
|
-
for (const dependency of Object.keys(pkg.peerDependencies || {})) external.push(dependency);
|
|
286
|
-
for (const dependency of Object.keys(pkg.optionalDependencies || {})) external.push(dependency);
|
|
287
|
-
}
|
|
288
|
-
const resolvedExtension = resolvedFormat === "esm" ? "mjs" : "cjs";
|
|
289
|
-
const context = { files: {} };
|
|
290
|
-
const out = await build({
|
|
291
|
-
input: entrypointPath,
|
|
292
|
-
cwd: args.workPath,
|
|
293
|
-
platform: "node",
|
|
294
|
-
tsconfig: true,
|
|
295
|
-
plugins: [plugin({
|
|
296
|
-
repoRootPath: args.repoRootPath,
|
|
297
|
-
outDir: outputDir,
|
|
298
|
-
workPath: args.workPath,
|
|
299
|
-
shimBareImports: resolvedFormat === "esm",
|
|
300
|
-
context
|
|
301
|
-
})],
|
|
302
|
-
output: {
|
|
303
|
-
cleanDir: true,
|
|
304
|
-
dir: outputDir,
|
|
305
|
-
format: resolvedFormat,
|
|
306
|
-
entryFileNames: `[name].${resolvedExtension}`,
|
|
307
|
-
preserveModules: true,
|
|
308
|
-
preserveModulesRoot: args.repoRootPath,
|
|
309
|
-
sourcemap: false,
|
|
310
|
-
banner: resolvedFormat === "esm" ? __dirname__filenameShim : void 0
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
let handler = null;
|
|
314
|
-
for (const entry of out.output) if (entry.type === "chunk") {
|
|
315
|
-
if (entry.isEntry) handler = entry.fileName;
|
|
316
|
-
}
|
|
317
|
-
if (typeof handler !== "string") throw new Error(`Unable to resolve module for ${args.entrypoint}`);
|
|
318
|
-
const cleanup = async () => {
|
|
319
|
-
await rm(outputDir, {
|
|
320
|
-
recursive: true,
|
|
321
|
-
force: true
|
|
322
|
-
});
|
|
323
|
-
};
|
|
324
|
-
return {
|
|
325
|
-
result: {
|
|
326
|
-
handler,
|
|
327
|
-
outputDir,
|
|
328
|
-
outputFiles: context.files
|
|
329
|
-
},
|
|
330
|
-
cleanup
|
|
331
|
-
};
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
//#endregion
|
|
335
|
-
//#region src/utils.ts
|
|
336
|
-
const noColor = globalThis.process?.env?.NO_COLOR === "1" || globalThis.process?.env?.TERM === "dumb";
|
|
337
|
-
const resets = {
|
|
338
|
-
1: 22,
|
|
339
|
-
31: 39,
|
|
340
|
-
32: 39,
|
|
341
|
-
33: 39,
|
|
342
|
-
34: 39,
|
|
343
|
-
35: 39,
|
|
344
|
-
36: 39,
|
|
345
|
-
90: 39
|
|
346
|
-
};
|
|
347
|
-
const _c = (c) => (text) => {
|
|
348
|
-
if (noColor) return text;
|
|
349
|
-
return `\u001B[${c}m${text}\u001B[${resets[c] ?? 0}m`;
|
|
350
|
-
};
|
|
351
|
-
const Colors = {
|
|
352
|
-
bold: _c(1),
|
|
353
|
-
red: _c(31),
|
|
354
|
-
green: _c(32),
|
|
355
|
-
yellow: _c(33),
|
|
356
|
-
blue: _c(34),
|
|
357
|
-
magenta: _c(35),
|
|
358
|
-
cyan: _c(36),
|
|
359
|
-
gray: _c(90),
|
|
360
|
-
url: (title, url) => noColor ? `[${title}](${url})` : `\u001B]8;;${url}\u001B\\${title}\u001B]8;;\u001B\\`
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
//#endregion
|
|
364
|
-
//#region src/typescript.ts
|
|
365
|
-
const require_ = createRequire$1(import.meta.url);
|
|
366
|
-
const typescript = (args) => {
|
|
367
|
-
const extension = extname$1(args.entrypoint);
|
|
368
|
-
if (![
|
|
369
|
-
".ts",
|
|
370
|
-
".mts",
|
|
371
|
-
".cts"
|
|
372
|
-
].includes(extension)) return;
|
|
373
|
-
const tscPath = resolveTscPath(args);
|
|
374
|
-
if (!tscPath) {
|
|
375
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck skipped ${Colors.gray("(TypeScript not found)")}`));
|
|
376
|
-
return null;
|
|
377
|
-
}
|
|
378
|
-
return doTypeCheck(args, tscPath);
|
|
379
|
-
};
|
|
380
|
-
async function doTypeCheck(args, tscPath) {
|
|
381
|
-
let stdout = "";
|
|
382
|
-
let stderr = "";
|
|
383
|
-
/**
|
|
384
|
-
* This might be subject to change.
|
|
385
|
-
* - if no tscPath, skip typecheck
|
|
386
|
-
* - if tsconfig, provide the tsconfig path
|
|
387
|
-
* - else provide the entrypoint path
|
|
388
|
-
*/
|
|
389
|
-
const tscArgs = [
|
|
390
|
-
tscPath,
|
|
391
|
-
"--noEmit",
|
|
392
|
-
"--pretty",
|
|
393
|
-
"--allowJs",
|
|
394
|
-
"--esModuleInterop",
|
|
395
|
-
"--skipLibCheck"
|
|
396
|
-
];
|
|
397
|
-
const tsconfig = await findNearestTsconfig(args.workPath);
|
|
398
|
-
if (tsconfig) tscArgs.push("--project", tsconfig);
|
|
399
|
-
else tscArgs.push(args.entrypoint);
|
|
400
|
-
const child = spawn(process.execPath, tscArgs, {
|
|
401
|
-
cwd: args.workPath,
|
|
402
|
-
stdio: [
|
|
403
|
-
"ignore",
|
|
404
|
-
"pipe",
|
|
405
|
-
"pipe"
|
|
406
|
-
]
|
|
407
|
-
});
|
|
408
|
-
child.stdout?.on("data", (data) => {
|
|
409
|
-
stdout += data.toString();
|
|
410
|
-
});
|
|
411
|
-
child.stderr?.on("data", (data) => {
|
|
412
|
-
stderr += data.toString();
|
|
413
|
-
});
|
|
414
|
-
await new Promise((resolve$1, reject) => {
|
|
415
|
-
child.on("close", (code) => {
|
|
416
|
-
if (code === 0) {
|
|
417
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck complete`));
|
|
418
|
-
resolve$1();
|
|
419
|
-
} else {
|
|
420
|
-
const output = stdout || stderr;
|
|
421
|
-
if (output) {
|
|
422
|
-
console.error("\nTypeScript type check failed:\n");
|
|
423
|
-
console.error(output);
|
|
424
|
-
}
|
|
425
|
-
reject(/* @__PURE__ */ new Error("TypeScript type check failed"));
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
child.on("error", (err) => {
|
|
429
|
-
reject(err);
|
|
430
|
-
});
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
const resolveTscPath = (args) => {
|
|
434
|
-
try {
|
|
435
|
-
return require_.resolve("typescript/bin/tsc", { paths: [args.workPath] });
|
|
436
|
-
} catch (e) {
|
|
437
|
-
return null;
|
|
438
|
-
}
|
|
439
|
-
};
|
|
440
|
-
const findNearestTsconfig = async (workPath) => {
|
|
441
|
-
const tsconfigPath = join$1(workPath, "tsconfig.json");
|
|
442
|
-
if (existsSync$1(tsconfigPath)) return tsconfigPath;
|
|
443
|
-
if (workPath === "/") return;
|
|
444
|
-
return findNearestTsconfig(join$1(workPath, ".."));
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
//#endregion
|
|
448
|
-
//#region src/find-entrypoint.ts
|
|
449
|
-
const frameworks = [
|
|
450
|
-
"express",
|
|
451
|
-
"hono",
|
|
452
|
-
"elysia",
|
|
453
|
-
"fastify",
|
|
454
|
-
"@nestjs/core",
|
|
455
|
-
"h3"
|
|
456
|
-
];
|
|
457
|
-
const entrypointFilenames = [
|
|
458
|
-
"app",
|
|
459
|
-
"index",
|
|
460
|
-
"server",
|
|
461
|
-
"main"
|
|
462
|
-
];
|
|
463
|
-
const entrypointExtensions = [
|
|
464
|
-
"js",
|
|
465
|
-
"cjs",
|
|
466
|
-
"mjs",
|
|
467
|
-
"ts",
|
|
468
|
-
"cts",
|
|
469
|
-
"mts"
|
|
470
|
-
];
|
|
471
|
-
const entrypoints = entrypointFilenames.flatMap((filename) => entrypointExtensions.map((extension) => `${filename}.${extension}`));
|
|
472
|
-
const createFrameworkRegex = (framework) => new RegExp(`(?:from|require|import)\\s*(?:\\(\\s*)?["']${framework}["']\\s*(?:\\))?`, "g");
|
|
473
|
-
const findEntrypoint = async (cwd, options) => {
|
|
474
|
-
if (options?.ignoreRegex ?? false) {
|
|
475
|
-
for (const entrypoint of entrypoints) if (existsSync(join(cwd, entrypoint))) return entrypoint;
|
|
476
|
-
for (const entrypoint of entrypoints) if (existsSync(join(cwd, "src", entrypoint))) return join("src", entrypoint);
|
|
477
|
-
throw new Error("No entrypoint file found");
|
|
478
|
-
}
|
|
479
|
-
const packageJson = await readFile(join(cwd, "package.json"), "utf-8");
|
|
480
|
-
const packageJsonObject = JSON.parse(packageJson);
|
|
481
|
-
const framework = frameworks.find((framework$1) => packageJsonObject.dependencies?.[framework$1]);
|
|
482
|
-
if (!framework) {
|
|
483
|
-
for (const entrypoint of entrypoints) {
|
|
484
|
-
const entrypointPath = join(cwd, entrypoint);
|
|
485
|
-
try {
|
|
486
|
-
await readFile(entrypointPath, "utf-8");
|
|
487
|
-
return entrypoint;
|
|
488
|
-
} catch (e) {
|
|
489
|
-
continue;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
throw new Error("No entrypoint or framework found");
|
|
493
|
-
}
|
|
494
|
-
const regex = createFrameworkRegex(framework);
|
|
495
|
-
for (const entrypoint of entrypoints) {
|
|
496
|
-
const entrypointPath = join(cwd, entrypoint);
|
|
497
|
-
try {
|
|
498
|
-
const content = await readFile(entrypointPath, "utf-8");
|
|
499
|
-
if (regex.test(content)) return entrypoint;
|
|
500
|
-
} catch (e) {
|
|
501
|
-
continue;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
for (const entrypoint of entrypoints) {
|
|
505
|
-
const entrypointPath = join(cwd, "src", entrypoint);
|
|
506
|
-
try {
|
|
507
|
-
const content = await readFile(entrypointPath, "utf-8");
|
|
508
|
-
if (regex.test(content)) return join("src", entrypoint);
|
|
509
|
-
} catch (e) {
|
|
510
|
-
continue;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
throw new Error("No entrypoint found");
|
|
514
|
-
};
|
|
515
|
-
|
|
516
|
-
//#endregion
|
|
517
|
-
//#region src/index.ts
|
|
518
|
-
const require = createRequire(import.meta.url);
|
|
519
|
-
const build$1 = async (args) => {
|
|
520
|
-
const entrypoint = args.entrypoint || await findEntrypoint(args.workPath);
|
|
521
|
-
const tsPromise = typescript({
|
|
522
|
-
entrypoint,
|
|
523
|
-
workPath: args.workPath
|
|
524
|
-
});
|
|
525
|
-
const rolldownResult = await rolldown({
|
|
526
|
-
entrypoint,
|
|
527
|
-
workPath: args.workPath,
|
|
528
|
-
repoRootPath: args.repoRootPath,
|
|
529
|
-
out: args.out
|
|
530
|
-
});
|
|
531
|
-
await writeFile(join(args.workPath, args.out, ".cervel.json"), JSON.stringify({ handler: rolldownResult.result.handler }, null, 2));
|
|
532
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Build complete`));
|
|
533
|
-
const typecheckComplete = true;
|
|
534
|
-
const result = tsPromise ? await Promise.race([tsPromise.then(() => typecheckComplete), Promise.resolve(false)]) : true;
|
|
535
|
-
if (tsPromise && !result) console.log(Colors.gray(`${Colors.bold(Colors.gray("*"))} Waiting for typecheck...`));
|
|
536
|
-
return {
|
|
537
|
-
rolldownResult: rolldownResult.result,
|
|
538
|
-
tsPromise
|
|
539
|
-
};
|
|
540
|
-
};
|
|
541
|
-
const serve = async (args) => {
|
|
542
|
-
const entrypoint = await findEntrypoint(args.workPath);
|
|
543
|
-
const srvxBin = join(require.resolve("srvx"), "..", "..", "..", "bin", "srvx.mjs");
|
|
544
|
-
const tsxBin = require.resolve("tsx");
|
|
545
|
-
const restArgs = Object.entries(args.rest).filter(([, value]) => value !== void 0 && value !== false).map(([key, value]) => typeof value === "boolean" ? `--${key}` : `--${key}=${value}`);
|
|
546
|
-
if (!args.rest.import) restArgs.push("--import", tsxBin);
|
|
547
|
-
await execa("npx", [
|
|
548
|
-
srvxBin,
|
|
549
|
-
...restArgs,
|
|
550
|
-
entrypoint
|
|
551
|
-
], {
|
|
552
|
-
cwd: args.workPath,
|
|
553
|
-
stdio: "inherit"
|
|
554
|
-
});
|
|
555
|
-
};
|
|
556
|
-
const srvxOptions = {
|
|
557
|
-
help: {
|
|
558
|
-
type: "boolean",
|
|
559
|
-
short: "h"
|
|
560
|
-
},
|
|
561
|
-
version: {
|
|
562
|
-
type: "boolean",
|
|
563
|
-
short: "v"
|
|
564
|
-
},
|
|
565
|
-
prod: { type: "boolean" },
|
|
566
|
-
port: {
|
|
567
|
-
type: "string",
|
|
568
|
-
short: "p"
|
|
569
|
-
},
|
|
570
|
-
host: {
|
|
571
|
-
type: "string",
|
|
572
|
-
short: "H"
|
|
573
|
-
},
|
|
574
|
-
static: {
|
|
575
|
-
type: "string",
|
|
576
|
-
short: "s"
|
|
577
|
-
},
|
|
578
|
-
import: { type: "string" },
|
|
579
|
-
tls: { type: "boolean" },
|
|
580
|
-
cert: { type: "string" },
|
|
581
|
-
key: { type: "string" }
|
|
582
|
-
};
|
|
583
|
-
|
|
584
|
-
//#endregion
|
|
585
4
|
//#region src/cli.ts
|
|
586
5
|
const main = async () => {
|
|
587
6
|
const options = parseArgs$1(process.argv.slice(2));
|
|
@@ -589,15 +8,13 @@ const main = async () => {
|
|
|
589
8
|
const [command, entrypoint] = options.positionals;
|
|
590
9
|
const workPath = cwd;
|
|
591
10
|
const repoRootPath = cwd;
|
|
592
|
-
if (command === "build") {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
await tsPromise;
|
|
600
|
-
} else await serve({
|
|
11
|
+
if (command === "build") await build({
|
|
12
|
+
workPath,
|
|
13
|
+
repoRootPath,
|
|
14
|
+
out,
|
|
15
|
+
entrypoint
|
|
16
|
+
});
|
|
17
|
+
else await serve({
|
|
601
18
|
workPath,
|
|
602
19
|
rest
|
|
603
20
|
});
|
package/dist/index.d.mts
CHANGED
|
@@ -1,60 +1,2 @@
|
|
|
1
|
-
|
|
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";
|
|
5
|
-
|
|
6
|
-
//#region src/find-entrypoint.d.ts
|
|
7
|
-
declare const findEntrypoint: (cwd: string, options?: {
|
|
8
|
-
ignoreRegex?: boolean;
|
|
9
|
-
}) => Promise<string>;
|
|
10
|
-
//#endregion
|
|
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 & {
|
|
22
|
-
entrypoint?: string;
|
|
23
|
-
out: string;
|
|
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<{
|
|
50
|
-
rolldownResult: {
|
|
51
|
-
handler: string;
|
|
52
|
-
outputDir: string;
|
|
53
|
-
outputFiles: _vercel_build_utils_dist_types_js0.Files;
|
|
54
|
-
};
|
|
55
|
-
tsPromise: Promise<void> | null | undefined;
|
|
56
|
-
}>;
|
|
57
|
-
declare const serve: (args: CervelServeOptions) => Promise<void>;
|
|
58
|
-
declare const srvxOptions: ParseArgsOptionsConfig;
|
|
59
|
-
//#endregion
|
|
1
|
+
import { CervelBuildOptions, CervelServeOptions, PathOptions, cervelBuild as build, cervelServe as serve, findEntrypoint, getBuildSummary, nodeFileTrace, srvxOptions } from "@vercel/backends";
|
|
60
2
|
export { type CervelBuildOptions, type CervelServeOptions, type PathOptions, build, findEntrypoint, getBuildSummary, nodeFileTrace, serve, srvxOptions };
|
package/dist/index.mjs
CHANGED
|
@@ -1,588 +1,3 @@
|
|
|
1
|
-
import {
|
|
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
|
-
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";
|
|
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";
|
|
15
|
-
import execa from "execa";
|
|
16
|
-
import { readFile as readFile$1, writeFile } from "fs/promises";
|
|
1
|
+
import { cervelBuild as build, cervelServe as serve, findEntrypoint, getBuildSummary, nodeFileTrace, srvxOptions } from "@vercel/backends";
|
|
17
2
|
|
|
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
|
|
229
|
-
//#region src/rolldown.ts
|
|
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();
|
|
238
|
-
const rolldown = async (args) => {
|
|
239
|
-
const entrypointPath = join(args.workPath, args.entrypoint);
|
|
240
|
-
const outputDir = join(args.workPath, args.out);
|
|
241
|
-
const extension = extname(args.entrypoint);
|
|
242
|
-
const extensionMap = {
|
|
243
|
-
".ts": {
|
|
244
|
-
format: "auto",
|
|
245
|
-
extension: "js"
|
|
246
|
-
},
|
|
247
|
-
".mts": {
|
|
248
|
-
format: "esm",
|
|
249
|
-
extension: "mjs"
|
|
250
|
-
},
|
|
251
|
-
".cts": {
|
|
252
|
-
format: "cjs",
|
|
253
|
-
extension: "cjs"
|
|
254
|
-
},
|
|
255
|
-
".cjs": {
|
|
256
|
-
format: "cjs",
|
|
257
|
-
extension: "cjs"
|
|
258
|
-
},
|
|
259
|
-
".js": {
|
|
260
|
-
format: "auto",
|
|
261
|
-
extension: "js"
|
|
262
|
-
},
|
|
263
|
-
".mjs": {
|
|
264
|
-
format: "esm",
|
|
265
|
-
extension: "mjs"
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
const extensionInfo = extensionMap[extension] || extensionMap[".js"];
|
|
269
|
-
let resolvedFormat = extensionInfo.format === "auto" ? void 0 : extensionInfo.format;
|
|
270
|
-
const packageJsonPath = join(args.workPath, "package.json");
|
|
271
|
-
const external = [];
|
|
272
|
-
let pkg = {};
|
|
273
|
-
if (existsSync(packageJsonPath)) {
|
|
274
|
-
const source = await readFile(packageJsonPath, "utf8");
|
|
275
|
-
try {
|
|
276
|
-
pkg = JSON.parse(source.toString());
|
|
277
|
-
} catch (_e) {
|
|
278
|
-
pkg = {};
|
|
279
|
-
}
|
|
280
|
-
if (extensionInfo.format === "auto") if (pkg.type === "module") resolvedFormat = "esm";
|
|
281
|
-
else resolvedFormat = "cjs";
|
|
282
|
-
for (const dependency of Object.keys(pkg.dependencies || {})) external.push(dependency);
|
|
283
|
-
for (const dependency of Object.keys(pkg.devDependencies || {})) external.push(dependency);
|
|
284
|
-
for (const dependency of Object.keys(pkg.peerDependencies || {})) external.push(dependency);
|
|
285
|
-
for (const dependency of Object.keys(pkg.optionalDependencies || {})) external.push(dependency);
|
|
286
|
-
}
|
|
287
|
-
const resolvedExtension = resolvedFormat === "esm" ? "mjs" : "cjs";
|
|
288
|
-
const context = { files: {} };
|
|
289
|
-
const out = await build$1({
|
|
290
|
-
input: entrypointPath,
|
|
291
|
-
cwd: args.workPath,
|
|
292
|
-
platform: "node",
|
|
293
|
-
tsconfig: true,
|
|
294
|
-
plugins: [plugin({
|
|
295
|
-
repoRootPath: args.repoRootPath,
|
|
296
|
-
outDir: outputDir,
|
|
297
|
-
workPath: args.workPath,
|
|
298
|
-
shimBareImports: resolvedFormat === "esm",
|
|
299
|
-
context
|
|
300
|
-
})],
|
|
301
|
-
output: {
|
|
302
|
-
cleanDir: true,
|
|
303
|
-
dir: outputDir,
|
|
304
|
-
format: resolvedFormat,
|
|
305
|
-
entryFileNames: `[name].${resolvedExtension}`,
|
|
306
|
-
preserveModules: true,
|
|
307
|
-
preserveModulesRoot: args.repoRootPath,
|
|
308
|
-
sourcemap: false,
|
|
309
|
-
banner: resolvedFormat === "esm" ? __dirname__filenameShim : void 0
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
let handler = null;
|
|
313
|
-
for (const entry of out.output) if (entry.type === "chunk") {
|
|
314
|
-
if (entry.isEntry) handler = entry.fileName;
|
|
315
|
-
}
|
|
316
|
-
if (typeof handler !== "string") throw new Error(`Unable to resolve module for ${args.entrypoint}`);
|
|
317
|
-
const cleanup = async () => {
|
|
318
|
-
await rm(outputDir, {
|
|
319
|
-
recursive: true,
|
|
320
|
-
force: true
|
|
321
|
-
});
|
|
322
|
-
};
|
|
323
|
-
return {
|
|
324
|
-
result: {
|
|
325
|
-
handler,
|
|
326
|
-
outputDir,
|
|
327
|
-
outputFiles: context.files
|
|
328
|
-
},
|
|
329
|
-
cleanup
|
|
330
|
-
};
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
//#endregion
|
|
334
|
-
//#region src/utils.ts
|
|
335
|
-
const noColor = globalThis.process?.env?.NO_COLOR === "1" || globalThis.process?.env?.TERM === "dumb";
|
|
336
|
-
const resets = {
|
|
337
|
-
1: 22,
|
|
338
|
-
31: 39,
|
|
339
|
-
32: 39,
|
|
340
|
-
33: 39,
|
|
341
|
-
34: 39,
|
|
342
|
-
35: 39,
|
|
343
|
-
36: 39,
|
|
344
|
-
90: 39
|
|
345
|
-
};
|
|
346
|
-
const _c = (c) => (text) => {
|
|
347
|
-
if (noColor) return text;
|
|
348
|
-
return `\u001B[${c}m${text}\u001B[${resets[c] ?? 0}m`;
|
|
349
|
-
};
|
|
350
|
-
const Colors = {
|
|
351
|
-
bold: _c(1),
|
|
352
|
-
red: _c(31),
|
|
353
|
-
green: _c(32),
|
|
354
|
-
yellow: _c(33),
|
|
355
|
-
blue: _c(34),
|
|
356
|
-
magenta: _c(35),
|
|
357
|
-
cyan: _c(36),
|
|
358
|
-
gray: _c(90),
|
|
359
|
-
url: (title, url) => noColor ? `[${title}](${url})` : `\u001B]8;;${url}\u001B\\${title}\u001B]8;;\u001B\\`
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
//#endregion
|
|
363
|
-
//#region src/typescript.ts
|
|
364
|
-
const require_ = createRequire$1(import.meta.url);
|
|
365
|
-
const typescript = (args) => {
|
|
366
|
-
const extension = extname$1(args.entrypoint);
|
|
367
|
-
if (![
|
|
368
|
-
".ts",
|
|
369
|
-
".mts",
|
|
370
|
-
".cts"
|
|
371
|
-
].includes(extension)) return;
|
|
372
|
-
const tscPath = resolveTscPath(args);
|
|
373
|
-
if (!tscPath) {
|
|
374
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck skipped ${Colors.gray("(TypeScript not found)")}`));
|
|
375
|
-
return null;
|
|
376
|
-
}
|
|
377
|
-
return doTypeCheck(args, tscPath);
|
|
378
|
-
};
|
|
379
|
-
async function doTypeCheck(args, tscPath) {
|
|
380
|
-
let stdout = "";
|
|
381
|
-
let stderr = "";
|
|
382
|
-
/**
|
|
383
|
-
* This might be subject to change.
|
|
384
|
-
* - if no tscPath, skip typecheck
|
|
385
|
-
* - if tsconfig, provide the tsconfig path
|
|
386
|
-
* - else provide the entrypoint path
|
|
387
|
-
*/
|
|
388
|
-
const tscArgs = [
|
|
389
|
-
tscPath,
|
|
390
|
-
"--noEmit",
|
|
391
|
-
"--pretty",
|
|
392
|
-
"--allowJs",
|
|
393
|
-
"--esModuleInterop",
|
|
394
|
-
"--skipLibCheck"
|
|
395
|
-
];
|
|
396
|
-
const tsconfig = await findNearestTsconfig(args.workPath);
|
|
397
|
-
if (tsconfig) tscArgs.push("--project", tsconfig);
|
|
398
|
-
else tscArgs.push(args.entrypoint);
|
|
399
|
-
const child = spawn(process.execPath, tscArgs, {
|
|
400
|
-
cwd: args.workPath,
|
|
401
|
-
stdio: [
|
|
402
|
-
"ignore",
|
|
403
|
-
"pipe",
|
|
404
|
-
"pipe"
|
|
405
|
-
]
|
|
406
|
-
});
|
|
407
|
-
child.stdout?.on("data", (data) => {
|
|
408
|
-
stdout += data.toString();
|
|
409
|
-
});
|
|
410
|
-
child.stderr?.on("data", (data) => {
|
|
411
|
-
stderr += data.toString();
|
|
412
|
-
});
|
|
413
|
-
await new Promise((resolve$1, reject) => {
|
|
414
|
-
child.on("close", (code) => {
|
|
415
|
-
if (code === 0) {
|
|
416
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck complete`));
|
|
417
|
-
resolve$1();
|
|
418
|
-
} else {
|
|
419
|
-
const output = stdout || stderr;
|
|
420
|
-
if (output) {
|
|
421
|
-
console.error("\nTypeScript type check failed:\n");
|
|
422
|
-
console.error(output);
|
|
423
|
-
}
|
|
424
|
-
reject(/* @__PURE__ */ new Error("TypeScript type check failed"));
|
|
425
|
-
}
|
|
426
|
-
});
|
|
427
|
-
child.on("error", (err) => {
|
|
428
|
-
reject(err);
|
|
429
|
-
});
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
const resolveTscPath = (args) => {
|
|
433
|
-
try {
|
|
434
|
-
return require_.resolve("typescript/bin/tsc", { paths: [args.workPath] });
|
|
435
|
-
} catch (e) {
|
|
436
|
-
return null;
|
|
437
|
-
}
|
|
438
|
-
};
|
|
439
|
-
const findNearestTsconfig = async (workPath) => {
|
|
440
|
-
const tsconfigPath = join$1(workPath, "tsconfig.json");
|
|
441
|
-
if (existsSync$1(tsconfigPath)) return tsconfigPath;
|
|
442
|
-
if (workPath === "/") return;
|
|
443
|
-
return findNearestTsconfig(join$1(workPath, ".."));
|
|
444
|
-
};
|
|
445
|
-
|
|
446
|
-
//#endregion
|
|
447
|
-
//#region src/find-entrypoint.ts
|
|
448
|
-
const frameworks = [
|
|
449
|
-
"express",
|
|
450
|
-
"hono",
|
|
451
|
-
"elysia",
|
|
452
|
-
"fastify",
|
|
453
|
-
"@nestjs/core",
|
|
454
|
-
"h3"
|
|
455
|
-
];
|
|
456
|
-
const entrypointFilenames = [
|
|
457
|
-
"app",
|
|
458
|
-
"index",
|
|
459
|
-
"server",
|
|
460
|
-
"main"
|
|
461
|
-
];
|
|
462
|
-
const entrypointExtensions = [
|
|
463
|
-
"js",
|
|
464
|
-
"cjs",
|
|
465
|
-
"mjs",
|
|
466
|
-
"ts",
|
|
467
|
-
"cts",
|
|
468
|
-
"mts"
|
|
469
|
-
];
|
|
470
|
-
const entrypoints = entrypointFilenames.flatMap((filename) => entrypointExtensions.map((extension) => `${filename}.${extension}`));
|
|
471
|
-
const createFrameworkRegex = (framework) => new RegExp(`(?:from|require|import)\\s*(?:\\(\\s*)?["']${framework}["']\\s*(?:\\))?`, "g");
|
|
472
|
-
const findEntrypoint = async (cwd, options) => {
|
|
473
|
-
if (options?.ignoreRegex ?? false) {
|
|
474
|
-
for (const entrypoint of entrypoints) if (existsSync(join(cwd, entrypoint))) return entrypoint;
|
|
475
|
-
for (const entrypoint of entrypoints) if (existsSync(join(cwd, "src", entrypoint))) return join("src", entrypoint);
|
|
476
|
-
throw new Error("No entrypoint file found");
|
|
477
|
-
}
|
|
478
|
-
const packageJson = await readFile(join(cwd, "package.json"), "utf-8");
|
|
479
|
-
const packageJsonObject = JSON.parse(packageJson);
|
|
480
|
-
const framework = frameworks.find((framework$1) => packageJsonObject.dependencies?.[framework$1]);
|
|
481
|
-
if (!framework) {
|
|
482
|
-
for (const entrypoint of entrypoints) {
|
|
483
|
-
const entrypointPath = join(cwd, entrypoint);
|
|
484
|
-
try {
|
|
485
|
-
await readFile(entrypointPath, "utf-8");
|
|
486
|
-
return entrypoint;
|
|
487
|
-
} catch (e) {
|
|
488
|
-
continue;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
throw new Error("No entrypoint or framework found");
|
|
492
|
-
}
|
|
493
|
-
const regex = createFrameworkRegex(framework);
|
|
494
|
-
for (const entrypoint of entrypoints) {
|
|
495
|
-
const entrypointPath = join(cwd, entrypoint);
|
|
496
|
-
try {
|
|
497
|
-
const content = await readFile(entrypointPath, "utf-8");
|
|
498
|
-
if (regex.test(content)) return entrypoint;
|
|
499
|
-
} catch (e) {
|
|
500
|
-
continue;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
for (const entrypoint of entrypoints) {
|
|
504
|
-
const entrypointPath = join(cwd, "src", entrypoint);
|
|
505
|
-
try {
|
|
506
|
-
const content = await readFile(entrypointPath, "utf-8");
|
|
507
|
-
if (regex.test(content)) return join("src", entrypoint);
|
|
508
|
-
} catch (e) {
|
|
509
|
-
continue;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
throw new Error("No entrypoint found");
|
|
513
|
-
};
|
|
514
|
-
|
|
515
|
-
//#endregion
|
|
516
|
-
//#region src/index.ts
|
|
517
|
-
const require = createRequire(import.meta.url);
|
|
518
|
-
const getBuildSummary = async (outputDir) => {
|
|
519
|
-
const buildSummary = await readFile$1(join(outputDir, ".cervel.json"), "utf-8");
|
|
520
|
-
return JSON.parse(buildSummary);
|
|
521
|
-
};
|
|
522
|
-
const build = async (args) => {
|
|
523
|
-
const entrypoint = args.entrypoint || await findEntrypoint(args.workPath);
|
|
524
|
-
const tsPromise = typescript({
|
|
525
|
-
entrypoint,
|
|
526
|
-
workPath: args.workPath
|
|
527
|
-
});
|
|
528
|
-
const rolldownResult = await rolldown({
|
|
529
|
-
entrypoint,
|
|
530
|
-
workPath: args.workPath,
|
|
531
|
-
repoRootPath: args.repoRootPath,
|
|
532
|
-
out: args.out
|
|
533
|
-
});
|
|
534
|
-
await writeFile(join(args.workPath, args.out, ".cervel.json"), JSON.stringify({ handler: rolldownResult.result.handler }, null, 2));
|
|
535
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Build complete`));
|
|
536
|
-
const typecheckComplete = true;
|
|
537
|
-
const result = tsPromise ? await Promise.race([tsPromise.then(() => typecheckComplete), Promise.resolve(false)]) : true;
|
|
538
|
-
if (tsPromise && !result) console.log(Colors.gray(`${Colors.bold(Colors.gray("*"))} Waiting for typecheck...`));
|
|
539
|
-
return {
|
|
540
|
-
rolldownResult: rolldownResult.result,
|
|
541
|
-
tsPromise
|
|
542
|
-
};
|
|
543
|
-
};
|
|
544
|
-
const serve = async (args) => {
|
|
545
|
-
const entrypoint = await findEntrypoint(args.workPath);
|
|
546
|
-
const srvxBin = join(require.resolve("srvx"), "..", "..", "..", "bin", "srvx.mjs");
|
|
547
|
-
const tsxBin = require.resolve("tsx");
|
|
548
|
-
const restArgs = Object.entries(args.rest).filter(([, value]) => value !== void 0 && value !== false).map(([key, value]) => typeof value === "boolean" ? `--${key}` : `--${key}=${value}`);
|
|
549
|
-
if (!args.rest.import) restArgs.push("--import", tsxBin);
|
|
550
|
-
await execa("npx", [
|
|
551
|
-
srvxBin,
|
|
552
|
-
...restArgs,
|
|
553
|
-
entrypoint
|
|
554
|
-
], {
|
|
555
|
-
cwd: args.workPath,
|
|
556
|
-
stdio: "inherit"
|
|
557
|
-
});
|
|
558
|
-
};
|
|
559
|
-
const srvxOptions = {
|
|
560
|
-
help: {
|
|
561
|
-
type: "boolean",
|
|
562
|
-
short: "h"
|
|
563
|
-
},
|
|
564
|
-
version: {
|
|
565
|
-
type: "boolean",
|
|
566
|
-
short: "v"
|
|
567
|
-
},
|
|
568
|
-
prod: { type: "boolean" },
|
|
569
|
-
port: {
|
|
570
|
-
type: "string",
|
|
571
|
-
short: "p"
|
|
572
|
-
},
|
|
573
|
-
host: {
|
|
574
|
-
type: "string",
|
|
575
|
-
short: "H"
|
|
576
|
-
},
|
|
577
|
-
static: {
|
|
578
|
-
type: "string",
|
|
579
|
-
short: "s"
|
|
580
|
-
},
|
|
581
|
-
import: { type: "string" },
|
|
582
|
-
tls: { type: "boolean" },
|
|
583
|
-
cert: { type: "string" },
|
|
584
|
-
key: { type: "string" }
|
|
585
|
-
};
|
|
586
|
-
|
|
587
|
-
//#endregion
|
|
588
3
|
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.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://vercel.com/docs",
|
|
6
6
|
"publishConfig": {
|
|
@@ -28,15 +28,7 @@
|
|
|
28
28
|
"dist"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@vercel/
|
|
32
|
-
"execa": "3.2.0",
|
|
33
|
-
"nf3": "0.3.6",
|
|
34
|
-
"oxc-transform": "0.111.0",
|
|
35
|
-
"resolve.exports": "2.0.3",
|
|
36
|
-
"rolldown": "1.0.0-rc.1",
|
|
37
|
-
"srvx": "0.8.9",
|
|
38
|
-
"tsx": "4.21.0",
|
|
39
|
-
"@vercel/build-utils": "13.2.16"
|
|
31
|
+
"@vercel/backends": "0.0.26"
|
|
40
32
|
},
|
|
41
33
|
"peerDependencies": {
|
|
42
34
|
"typescript": "^4.0.0 || ^5.0.0"
|