@vercel/cervel 0.0.10 → 0.0.12
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 +11 -366
- package/dist/index.d.mts +2 -31
- package/dist/index.mjs +2 -362
- package/package.json +2 -5
package/dist/cli.mjs
CHANGED
|
@@ -1,376 +1,21 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
|
-
import {
|
|
3
|
-
import { existsSync } from "fs";
|
|
4
|
-
import { readFile, rm, writeFile } from "fs/promises";
|
|
5
|
-
import { extname, join } from "path";
|
|
6
|
-
import { build } from "rolldown";
|
|
7
|
-
import { spawn } from "child_process";
|
|
8
|
-
import execa from "execa";
|
|
2
|
+
import { cervelBuild as build, cervelServe as serve, srvxOptions } from "@vercel/backends";
|
|
9
3
|
|
|
10
|
-
//#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
|
-
}
|
|
17
|
-
const rolldown = async (args) => {
|
|
18
|
-
const baseDir = args.repoRootPath || args.workPath;
|
|
19
|
-
const entrypointPath = join(args.workPath, args.entrypoint);
|
|
20
|
-
const shouldAddSourcemapSupport = false;
|
|
21
|
-
const extension = extname(args.entrypoint);
|
|
22
|
-
const extensionMap = {
|
|
23
|
-
".ts": {
|
|
24
|
-
format: "auto",
|
|
25
|
-
extension: "js"
|
|
26
|
-
},
|
|
27
|
-
".mts": {
|
|
28
|
-
format: "esm",
|
|
29
|
-
extension: "mjs"
|
|
30
|
-
},
|
|
31
|
-
".cts": {
|
|
32
|
-
format: "cjs",
|
|
33
|
-
extension: "cjs"
|
|
34
|
-
},
|
|
35
|
-
".cjs": {
|
|
36
|
-
format: "cjs",
|
|
37
|
-
extension: "cjs"
|
|
38
|
-
},
|
|
39
|
-
".js": {
|
|
40
|
-
format: "auto",
|
|
41
|
-
extension: "js"
|
|
42
|
-
},
|
|
43
|
-
".mjs": {
|
|
44
|
-
format: "esm",
|
|
45
|
-
extension: "mjs"
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
const extensionInfo = extensionMap[extension] || extensionMap[".js"];
|
|
49
|
-
let resolvedFormat = extensionInfo.format === "auto" ? void 0 : extensionInfo.format;
|
|
50
|
-
const resolvedExtension = extensionInfo.extension;
|
|
51
|
-
const packageJsonPath = join(args.workPath, "package.json");
|
|
52
|
-
const external = [];
|
|
53
|
-
let pkg = {};
|
|
54
|
-
if (existsSync(packageJsonPath)) {
|
|
55
|
-
const source = await readFile(packageJsonPath, "utf8");
|
|
56
|
-
try {
|
|
57
|
-
pkg = JSON.parse(source.toString());
|
|
58
|
-
} catch (_e) {
|
|
59
|
-
pkg = {};
|
|
60
|
-
}
|
|
61
|
-
if (extensionInfo.format === "auto") if (pkg.type === "module") resolvedFormat = "esm";
|
|
62
|
-
else resolvedFormat = "cjs";
|
|
63
|
-
for (const dependency of Object.keys(pkg.dependencies || {})) external.push(dependency);
|
|
64
|
-
for (const dependency of Object.keys(pkg.devDependencies || {})) external.push(dependency);
|
|
65
|
-
for (const dependency of Object.keys(pkg.peerDependencies || {})) external.push(dependency);
|
|
66
|
-
for (const dependency of Object.keys(pkg.optionalDependencies || {})) external.push(dependency);
|
|
67
|
-
}
|
|
68
|
-
const relativeOutputDir = args.out;
|
|
69
|
-
const outputDir = join(baseDir, relativeOutputDir);
|
|
70
|
-
const out = await build({
|
|
71
|
-
input: entrypointPath,
|
|
72
|
-
cwd: baseDir,
|
|
73
|
-
platform: "node",
|
|
74
|
-
tsconfig: true,
|
|
75
|
-
external: external.map((pkg$1) => /* @__PURE__ */ new RegExp(`^${escapeRegExp(pkg$1)}`)),
|
|
76
|
-
output: {
|
|
77
|
-
cleanDir: true,
|
|
78
|
-
dir: outputDir,
|
|
79
|
-
format: resolvedFormat,
|
|
80
|
-
entryFileNames: `[name].${resolvedExtension}`,
|
|
81
|
-
preserveModules: true,
|
|
82
|
-
sourcemap: false
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
let handler = null;
|
|
86
|
-
for (const entry of out.output) if (entry.type === "chunk") {
|
|
87
|
-
if (entry.isEntry) handler = entry.fileName;
|
|
88
|
-
}
|
|
89
|
-
if (typeof handler !== "string") throw new Error(`Unable to resolve module for ${args.entrypoint}`);
|
|
90
|
-
const cleanup = async () => {
|
|
91
|
-
await rm(outputDir, {
|
|
92
|
-
recursive: true,
|
|
93
|
-
force: true
|
|
94
|
-
});
|
|
95
|
-
};
|
|
96
|
-
return {
|
|
97
|
-
result: {
|
|
98
|
-
pkg,
|
|
99
|
-
shouldAddSourcemapSupport,
|
|
100
|
-
handler,
|
|
101
|
-
outputDir
|
|
102
|
-
},
|
|
103
|
-
cleanup
|
|
104
|
-
};
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
//#endregion
|
|
108
|
-
//#region src/utils.ts
|
|
109
|
-
const noColor = globalThis.process?.env?.NO_COLOR === "1" || globalThis.process?.env?.TERM === "dumb";
|
|
110
|
-
const resets = {
|
|
111
|
-
1: 22,
|
|
112
|
-
31: 39,
|
|
113
|
-
32: 39,
|
|
114
|
-
33: 39,
|
|
115
|
-
34: 39,
|
|
116
|
-
35: 39,
|
|
117
|
-
36: 39,
|
|
118
|
-
90: 39
|
|
119
|
-
};
|
|
120
|
-
const _c = (c) => (text) => {
|
|
121
|
-
if (noColor) return text;
|
|
122
|
-
return `\u001B[${c}m${text}\u001B[${resets[c] ?? 0}m`;
|
|
123
|
-
};
|
|
124
|
-
const Colors = {
|
|
125
|
-
bold: _c(1),
|
|
126
|
-
red: _c(31),
|
|
127
|
-
green: _c(32),
|
|
128
|
-
yellow: _c(33),
|
|
129
|
-
blue: _c(34),
|
|
130
|
-
magenta: _c(35),
|
|
131
|
-
cyan: _c(36),
|
|
132
|
-
gray: _c(90),
|
|
133
|
-
url: (title, url) => noColor ? `[${title}](${url})` : `\u001B]8;;${url}\u001B\\${title}\u001B]8;;\u001B\\`
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
//#endregion
|
|
137
|
-
//#region src/typescript.ts
|
|
138
|
-
const require_ = createRequire(import.meta.url);
|
|
139
|
-
const typescript = (args) => {
|
|
140
|
-
const extension = extname(args.entrypoint);
|
|
141
|
-
if (![
|
|
142
|
-
".ts",
|
|
143
|
-
".mts",
|
|
144
|
-
".cts"
|
|
145
|
-
].includes(extension)) return;
|
|
146
|
-
const tscPath = resolveTscPath(args);
|
|
147
|
-
if (!tscPath) {
|
|
148
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck skipped ${Colors.gray("(TypeScript not found)")}`));
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
return doTypeCheck(args, tscPath);
|
|
152
|
-
};
|
|
153
|
-
async function doTypeCheck(args, tscPath) {
|
|
154
|
-
let stdout = "";
|
|
155
|
-
let stderr = "";
|
|
156
|
-
/**
|
|
157
|
-
* This might be subject to change.
|
|
158
|
-
* - if no tscPath, skip typecheck
|
|
159
|
-
* - if tsconfig, provide the tsconfig path
|
|
160
|
-
* - else provide the entrypoint path
|
|
161
|
-
*/
|
|
162
|
-
const tscArgs = [
|
|
163
|
-
tscPath,
|
|
164
|
-
"--noEmit",
|
|
165
|
-
"--pretty",
|
|
166
|
-
"--allowJs",
|
|
167
|
-
"--esModuleInterop",
|
|
168
|
-
"--skipLibCheck"
|
|
169
|
-
];
|
|
170
|
-
const tsconfig = await findNearestTsconfig(args.workPath);
|
|
171
|
-
if (tsconfig) tscArgs.push("--project", tsconfig);
|
|
172
|
-
else tscArgs.push(args.entrypoint);
|
|
173
|
-
const child = spawn(process.execPath, tscArgs, {
|
|
174
|
-
cwd: args.workPath,
|
|
175
|
-
stdio: [
|
|
176
|
-
"ignore",
|
|
177
|
-
"pipe",
|
|
178
|
-
"pipe"
|
|
179
|
-
]
|
|
180
|
-
});
|
|
181
|
-
child.stdout?.on("data", (data) => {
|
|
182
|
-
stdout += data.toString();
|
|
183
|
-
});
|
|
184
|
-
child.stderr?.on("data", (data) => {
|
|
185
|
-
stderr += data.toString();
|
|
186
|
-
});
|
|
187
|
-
await new Promise((resolve, reject) => {
|
|
188
|
-
child.on("close", (code) => {
|
|
189
|
-
if (code === 0) {
|
|
190
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck complete`));
|
|
191
|
-
resolve();
|
|
192
|
-
} else {
|
|
193
|
-
const output = stdout || stderr;
|
|
194
|
-
if (output) {
|
|
195
|
-
console.error("\nTypeScript type check failed:\n");
|
|
196
|
-
console.error(output);
|
|
197
|
-
}
|
|
198
|
-
reject(/* @__PURE__ */ new Error("TypeScript type check failed"));
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
child.on("error", (err) => {
|
|
202
|
-
reject(err);
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
const resolveTscPath = (args) => {
|
|
207
|
-
try {
|
|
208
|
-
return require_.resolve("typescript/bin/tsc", { paths: [args.workPath] });
|
|
209
|
-
} catch (e) {
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
const findNearestTsconfig = async (workPath) => {
|
|
214
|
-
const tsconfigPath = join(workPath, "tsconfig.json");
|
|
215
|
-
if (existsSync(tsconfigPath)) return tsconfigPath;
|
|
216
|
-
if (workPath === "/") return;
|
|
217
|
-
return findNearestTsconfig(join(workPath, ".."));
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
//#endregion
|
|
221
|
-
//#region src/find-entrypoint.ts
|
|
222
|
-
const frameworks = [
|
|
223
|
-
"express",
|
|
224
|
-
"hono",
|
|
225
|
-
"elysia",
|
|
226
|
-
"fastify",
|
|
227
|
-
"@nestjs/core",
|
|
228
|
-
"h3"
|
|
229
|
-
];
|
|
230
|
-
const entrypointFilenames = [
|
|
231
|
-
"app",
|
|
232
|
-
"index",
|
|
233
|
-
"server",
|
|
234
|
-
"main"
|
|
235
|
-
];
|
|
236
|
-
const entrypointExtensions = [
|
|
237
|
-
"js",
|
|
238
|
-
"cjs",
|
|
239
|
-
"mjs",
|
|
240
|
-
"ts",
|
|
241
|
-
"cts",
|
|
242
|
-
"mts"
|
|
243
|
-
];
|
|
244
|
-
const entrypoints = entrypointFilenames.flatMap((filename) => entrypointExtensions.map((extension) => `${filename}.${extension}`));
|
|
245
|
-
const createFrameworkRegex = (framework) => new RegExp(`(?:from|require|import)\\s*(?:\\(\\s*)?["']${framework}["']\\s*(?:\\))?`, "g");
|
|
246
|
-
const findEntrypoint = async (cwd, options) => {
|
|
247
|
-
if (options?.ignoreRegex ?? false) {
|
|
248
|
-
for (const entrypoint of entrypoints) if (existsSync(join(cwd, entrypoint))) return entrypoint;
|
|
249
|
-
for (const entrypoint of entrypoints) if (existsSync(join(cwd, "src", entrypoint))) return join("src", entrypoint);
|
|
250
|
-
throw new Error("No entrypoint file found");
|
|
251
|
-
}
|
|
252
|
-
const packageJson = await readFile(join(cwd, "package.json"), "utf-8");
|
|
253
|
-
const packageJsonObject = JSON.parse(packageJson);
|
|
254
|
-
const framework = frameworks.find((framework$1) => packageJsonObject.dependencies?.[framework$1]);
|
|
255
|
-
if (!framework) {
|
|
256
|
-
for (const entrypoint of entrypoints) {
|
|
257
|
-
const entrypointPath = join(cwd, entrypoint);
|
|
258
|
-
try {
|
|
259
|
-
await readFile(entrypointPath, "utf-8");
|
|
260
|
-
return entrypoint;
|
|
261
|
-
} catch (e) {
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
throw new Error("No entrypoint or framework found");
|
|
266
|
-
}
|
|
267
|
-
const regex = createFrameworkRegex(framework);
|
|
268
|
-
for (const entrypoint of entrypoints) {
|
|
269
|
-
const entrypointPath = join(cwd, entrypoint);
|
|
270
|
-
try {
|
|
271
|
-
const content = await readFile(entrypointPath, "utf-8");
|
|
272
|
-
if (regex.test(content)) return entrypoint;
|
|
273
|
-
} catch (e) {
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
for (const entrypoint of entrypoints) {
|
|
278
|
-
const entrypointPath = join(cwd, "src", entrypoint);
|
|
279
|
-
try {
|
|
280
|
-
const content = await readFile(entrypointPath, "utf-8");
|
|
281
|
-
if (regex.test(content)) return join("src", entrypoint);
|
|
282
|
-
} catch (e) {
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
throw new Error("No entrypoint found");
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
//#endregion
|
|
290
|
-
//#region src/index.ts
|
|
291
|
-
const require = createRequire(import.meta.url);
|
|
292
|
-
const build$1 = async (args) => {
|
|
293
|
-
const entrypoint = args.entrypoint || await findEntrypoint(args.cwd);
|
|
294
|
-
const tsPromise = typescript({
|
|
295
|
-
...args,
|
|
296
|
-
entrypoint,
|
|
297
|
-
workPath: args.cwd
|
|
298
|
-
});
|
|
299
|
-
const rolldownResult = await rolldown({
|
|
300
|
-
...args,
|
|
301
|
-
entrypoint,
|
|
302
|
-
workPath: args.cwd,
|
|
303
|
-
repoRootPath: args.cwd,
|
|
304
|
-
out: args.out
|
|
305
|
-
});
|
|
306
|
-
await writeFile(join(args.cwd, args.out, ".cervel.json"), JSON.stringify({ handler: rolldownResult.result.handler }, null, 2));
|
|
307
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Build complete`));
|
|
308
|
-
const typecheckComplete = true;
|
|
309
|
-
const result = tsPromise ? await Promise.race([tsPromise.then(() => typecheckComplete), Promise.resolve(false)]) : true;
|
|
310
|
-
if (tsPromise && !result) console.log(Colors.gray(`${Colors.bold(Colors.gray("*"))} Waiting for typecheck...`));
|
|
311
|
-
return {
|
|
312
|
-
rolldownResult: rolldownResult.result,
|
|
313
|
-
tsPromise
|
|
314
|
-
};
|
|
315
|
-
};
|
|
316
|
-
const serve = async (args) => {
|
|
317
|
-
const entrypoint = await findEntrypoint(args.cwd);
|
|
318
|
-
const srvxBin = join(require.resolve("srvx"), "..", "..", "..", "bin", "srvx.mjs");
|
|
319
|
-
const tsxBin = require.resolve("tsx");
|
|
320
|
-
const restArgs = Object.entries(args.rest).filter(([, value]) => value !== void 0 && value !== false).map(([key, value]) => typeof value === "boolean" ? `--${key}` : `--${key}=${value}`);
|
|
321
|
-
if (!args.rest.import) restArgs.push("--import", tsxBin);
|
|
322
|
-
await execa("npx", [
|
|
323
|
-
srvxBin,
|
|
324
|
-
...restArgs,
|
|
325
|
-
entrypoint
|
|
326
|
-
], {
|
|
327
|
-
cwd: args.cwd,
|
|
328
|
-
stdio: "inherit"
|
|
329
|
-
});
|
|
330
|
-
};
|
|
331
|
-
const srvxOptions = {
|
|
332
|
-
help: {
|
|
333
|
-
type: "boolean",
|
|
334
|
-
short: "h"
|
|
335
|
-
},
|
|
336
|
-
version: {
|
|
337
|
-
type: "boolean",
|
|
338
|
-
short: "v"
|
|
339
|
-
},
|
|
340
|
-
prod: { type: "boolean" },
|
|
341
|
-
port: {
|
|
342
|
-
type: "string",
|
|
343
|
-
short: "p"
|
|
344
|
-
},
|
|
345
|
-
host: {
|
|
346
|
-
type: "string",
|
|
347
|
-
short: "H"
|
|
348
|
-
},
|
|
349
|
-
static: {
|
|
350
|
-
type: "string",
|
|
351
|
-
short: "s"
|
|
352
|
-
},
|
|
353
|
-
import: { type: "string" },
|
|
354
|
-
tls: { type: "boolean" },
|
|
355
|
-
cert: { type: "string" },
|
|
356
|
-
key: { type: "string" }
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
//#endregion
|
|
360
4
|
//#region src/cli.ts
|
|
361
5
|
const main = async () => {
|
|
362
6
|
const options = parseArgs$1(process.argv.slice(2));
|
|
363
7
|
const { cwd, out,...rest } = options.values;
|
|
364
8
|
const [command, entrypoint] = options.positionals;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
|
|
9
|
+
const workPath = cwd;
|
|
10
|
+
const repoRootPath = cwd;
|
|
11
|
+
if (command === "build") await build({
|
|
12
|
+
workPath,
|
|
13
|
+
repoRootPath,
|
|
14
|
+
out,
|
|
15
|
+
entrypoint
|
|
16
|
+
});
|
|
17
|
+
else await serve({
|
|
18
|
+
workPath,
|
|
374
19
|
rest
|
|
375
20
|
});
|
|
376
21
|
};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,31 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
//#region src/find-entrypoint.d.ts
|
|
5
|
-
declare const findEntrypoint: (cwd: string, options?: {
|
|
6
|
-
ignoreRegex?: boolean;
|
|
7
|
-
}) => Promise<string>;
|
|
8
|
-
//#endregion
|
|
9
|
-
//#region src/index.d.ts
|
|
10
|
-
type ParseArgsOptionsConfig = NonNullable<ParseArgsConfig['options']>;
|
|
11
|
-
declare const getBuildSummary: (outputDir: string) => Promise<any>;
|
|
12
|
-
declare const build: (args: {
|
|
13
|
-
entrypoint?: string;
|
|
14
|
-
cwd: string;
|
|
15
|
-
out: string;
|
|
16
|
-
}) => Promise<{
|
|
17
|
-
rolldownResult: {
|
|
18
|
-
pkg: Record<string, unknown>;
|
|
19
|
-
shouldAddSourcemapSupport: boolean;
|
|
20
|
-
handler: string;
|
|
21
|
-
outputDir: string;
|
|
22
|
-
};
|
|
23
|
-
tsPromise: Promise<void> | null | undefined;
|
|
24
|
-
}>;
|
|
25
|
-
declare const serve: (args: {
|
|
26
|
-
cwd: string;
|
|
27
|
-
rest: Record<string, string | boolean | undefined>;
|
|
28
|
-
}) => Promise<void>;
|
|
29
|
-
declare const srvxOptions: ParseArgsOptionsConfig;
|
|
30
|
-
//#endregion
|
|
31
|
-
export { build, findEntrypoint, getBuildSummary, serve, srvxOptions };
|
|
1
|
+
import { CervelBuildOptions, CervelServeOptions, PathOptions, cervelBuild as build, cervelServe as serve, findEntrypoint, getBuildSummary, nodeFileTrace, srvxOptions } from "@vercel/backends";
|
|
2
|
+
export { type CervelBuildOptions, type CervelServeOptions, type PathOptions, build, findEntrypoint, getBuildSummary, nodeFileTrace, serve, srvxOptions };
|
package/dist/index.mjs
CHANGED
|
@@ -1,363 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { existsSync } from "fs";
|
|
3
|
-
import { readFile, rm, writeFile } from "fs/promises";
|
|
4
|
-
import { extname, join } from "path";
|
|
5
|
-
import { build as build$1 } from "rolldown";
|
|
6
|
-
import { spawn } from "child_process";
|
|
7
|
-
import execa from "execa";
|
|
1
|
+
import { cervelBuild as build, cervelServe as serve, findEntrypoint, getBuildSummary, nodeFileTrace, srvxOptions } from "@vercel/backends";
|
|
8
2
|
|
|
9
|
-
|
|
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
|
-
}
|
|
16
|
-
const rolldown = async (args) => {
|
|
17
|
-
const baseDir = args.repoRootPath || args.workPath;
|
|
18
|
-
const entrypointPath = join(args.workPath, args.entrypoint);
|
|
19
|
-
const shouldAddSourcemapSupport = false;
|
|
20
|
-
const extension = extname(args.entrypoint);
|
|
21
|
-
const extensionMap = {
|
|
22
|
-
".ts": {
|
|
23
|
-
format: "auto",
|
|
24
|
-
extension: "js"
|
|
25
|
-
},
|
|
26
|
-
".mts": {
|
|
27
|
-
format: "esm",
|
|
28
|
-
extension: "mjs"
|
|
29
|
-
},
|
|
30
|
-
".cts": {
|
|
31
|
-
format: "cjs",
|
|
32
|
-
extension: "cjs"
|
|
33
|
-
},
|
|
34
|
-
".cjs": {
|
|
35
|
-
format: "cjs",
|
|
36
|
-
extension: "cjs"
|
|
37
|
-
},
|
|
38
|
-
".js": {
|
|
39
|
-
format: "auto",
|
|
40
|
-
extension: "js"
|
|
41
|
-
},
|
|
42
|
-
".mjs": {
|
|
43
|
-
format: "esm",
|
|
44
|
-
extension: "mjs"
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
const extensionInfo = extensionMap[extension] || extensionMap[".js"];
|
|
48
|
-
let resolvedFormat = extensionInfo.format === "auto" ? void 0 : extensionInfo.format;
|
|
49
|
-
const resolvedExtension = extensionInfo.extension;
|
|
50
|
-
const packageJsonPath = join(args.workPath, "package.json");
|
|
51
|
-
const external = [];
|
|
52
|
-
let pkg = {};
|
|
53
|
-
if (existsSync(packageJsonPath)) {
|
|
54
|
-
const source = await readFile(packageJsonPath, "utf8");
|
|
55
|
-
try {
|
|
56
|
-
pkg = JSON.parse(source.toString());
|
|
57
|
-
} catch (_e) {
|
|
58
|
-
pkg = {};
|
|
59
|
-
}
|
|
60
|
-
if (extensionInfo.format === "auto") if (pkg.type === "module") resolvedFormat = "esm";
|
|
61
|
-
else resolvedFormat = "cjs";
|
|
62
|
-
for (const dependency of Object.keys(pkg.dependencies || {})) external.push(dependency);
|
|
63
|
-
for (const dependency of Object.keys(pkg.devDependencies || {})) external.push(dependency);
|
|
64
|
-
for (const dependency of Object.keys(pkg.peerDependencies || {})) external.push(dependency);
|
|
65
|
-
for (const dependency of Object.keys(pkg.optionalDependencies || {})) external.push(dependency);
|
|
66
|
-
}
|
|
67
|
-
const relativeOutputDir = args.out;
|
|
68
|
-
const outputDir = join(baseDir, relativeOutputDir);
|
|
69
|
-
const out = await build$1({
|
|
70
|
-
input: entrypointPath,
|
|
71
|
-
cwd: baseDir,
|
|
72
|
-
platform: "node",
|
|
73
|
-
tsconfig: true,
|
|
74
|
-
external: external.map((pkg$1) => /* @__PURE__ */ new RegExp(`^${escapeRegExp(pkg$1)}`)),
|
|
75
|
-
output: {
|
|
76
|
-
cleanDir: true,
|
|
77
|
-
dir: outputDir,
|
|
78
|
-
format: resolvedFormat,
|
|
79
|
-
entryFileNames: `[name].${resolvedExtension}`,
|
|
80
|
-
preserveModules: true,
|
|
81
|
-
sourcemap: false
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
let handler = null;
|
|
85
|
-
for (const entry of out.output) if (entry.type === "chunk") {
|
|
86
|
-
if (entry.isEntry) handler = entry.fileName;
|
|
87
|
-
}
|
|
88
|
-
if (typeof handler !== "string") throw new Error(`Unable to resolve module for ${args.entrypoint}`);
|
|
89
|
-
const cleanup = async () => {
|
|
90
|
-
await rm(outputDir, {
|
|
91
|
-
recursive: true,
|
|
92
|
-
force: true
|
|
93
|
-
});
|
|
94
|
-
};
|
|
95
|
-
return {
|
|
96
|
-
result: {
|
|
97
|
-
pkg,
|
|
98
|
-
shouldAddSourcemapSupport,
|
|
99
|
-
handler,
|
|
100
|
-
outputDir
|
|
101
|
-
},
|
|
102
|
-
cleanup
|
|
103
|
-
};
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
//#endregion
|
|
107
|
-
//#region src/utils.ts
|
|
108
|
-
const noColor = globalThis.process?.env?.NO_COLOR === "1" || globalThis.process?.env?.TERM === "dumb";
|
|
109
|
-
const resets = {
|
|
110
|
-
1: 22,
|
|
111
|
-
31: 39,
|
|
112
|
-
32: 39,
|
|
113
|
-
33: 39,
|
|
114
|
-
34: 39,
|
|
115
|
-
35: 39,
|
|
116
|
-
36: 39,
|
|
117
|
-
90: 39
|
|
118
|
-
};
|
|
119
|
-
const _c = (c) => (text) => {
|
|
120
|
-
if (noColor) return text;
|
|
121
|
-
return `\u001B[${c}m${text}\u001B[${resets[c] ?? 0}m`;
|
|
122
|
-
};
|
|
123
|
-
const Colors = {
|
|
124
|
-
bold: _c(1),
|
|
125
|
-
red: _c(31),
|
|
126
|
-
green: _c(32),
|
|
127
|
-
yellow: _c(33),
|
|
128
|
-
blue: _c(34),
|
|
129
|
-
magenta: _c(35),
|
|
130
|
-
cyan: _c(36),
|
|
131
|
-
gray: _c(90),
|
|
132
|
-
url: (title, url) => noColor ? `[${title}](${url})` : `\u001B]8;;${url}\u001B\\${title}\u001B]8;;\u001B\\`
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
//#endregion
|
|
136
|
-
//#region src/typescript.ts
|
|
137
|
-
const require_ = createRequire(import.meta.url);
|
|
138
|
-
const typescript = (args) => {
|
|
139
|
-
const extension = extname(args.entrypoint);
|
|
140
|
-
if (![
|
|
141
|
-
".ts",
|
|
142
|
-
".mts",
|
|
143
|
-
".cts"
|
|
144
|
-
].includes(extension)) return;
|
|
145
|
-
const tscPath = resolveTscPath(args);
|
|
146
|
-
if (!tscPath) {
|
|
147
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck skipped ${Colors.gray("(TypeScript not found)")}`));
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
return doTypeCheck(args, tscPath);
|
|
151
|
-
};
|
|
152
|
-
async function doTypeCheck(args, tscPath) {
|
|
153
|
-
let stdout = "";
|
|
154
|
-
let stderr = "";
|
|
155
|
-
/**
|
|
156
|
-
* This might be subject to change.
|
|
157
|
-
* - if no tscPath, skip typecheck
|
|
158
|
-
* - if tsconfig, provide the tsconfig path
|
|
159
|
-
* - else provide the entrypoint path
|
|
160
|
-
*/
|
|
161
|
-
const tscArgs = [
|
|
162
|
-
tscPath,
|
|
163
|
-
"--noEmit",
|
|
164
|
-
"--pretty",
|
|
165
|
-
"--allowJs",
|
|
166
|
-
"--esModuleInterop",
|
|
167
|
-
"--skipLibCheck"
|
|
168
|
-
];
|
|
169
|
-
const tsconfig = await findNearestTsconfig(args.workPath);
|
|
170
|
-
if (tsconfig) tscArgs.push("--project", tsconfig);
|
|
171
|
-
else tscArgs.push(args.entrypoint);
|
|
172
|
-
const child = spawn(process.execPath, tscArgs, {
|
|
173
|
-
cwd: args.workPath,
|
|
174
|
-
stdio: [
|
|
175
|
-
"ignore",
|
|
176
|
-
"pipe",
|
|
177
|
-
"pipe"
|
|
178
|
-
]
|
|
179
|
-
});
|
|
180
|
-
child.stdout?.on("data", (data) => {
|
|
181
|
-
stdout += data.toString();
|
|
182
|
-
});
|
|
183
|
-
child.stderr?.on("data", (data) => {
|
|
184
|
-
stderr += data.toString();
|
|
185
|
-
});
|
|
186
|
-
await new Promise((resolve, reject) => {
|
|
187
|
-
child.on("close", (code) => {
|
|
188
|
-
if (code === 0) {
|
|
189
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck complete`));
|
|
190
|
-
resolve();
|
|
191
|
-
} else {
|
|
192
|
-
const output = stdout || stderr;
|
|
193
|
-
if (output) {
|
|
194
|
-
console.error("\nTypeScript type check failed:\n");
|
|
195
|
-
console.error(output);
|
|
196
|
-
}
|
|
197
|
-
reject(/* @__PURE__ */ new Error("TypeScript type check failed"));
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
child.on("error", (err) => {
|
|
201
|
-
reject(err);
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
const resolveTscPath = (args) => {
|
|
206
|
-
try {
|
|
207
|
-
return require_.resolve("typescript/bin/tsc", { paths: [args.workPath] });
|
|
208
|
-
} catch (e) {
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
const findNearestTsconfig = async (workPath) => {
|
|
213
|
-
const tsconfigPath = join(workPath, "tsconfig.json");
|
|
214
|
-
if (existsSync(tsconfigPath)) return tsconfigPath;
|
|
215
|
-
if (workPath === "/") return;
|
|
216
|
-
return findNearestTsconfig(join(workPath, ".."));
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
//#endregion
|
|
220
|
-
//#region src/find-entrypoint.ts
|
|
221
|
-
const frameworks = [
|
|
222
|
-
"express",
|
|
223
|
-
"hono",
|
|
224
|
-
"elysia",
|
|
225
|
-
"fastify",
|
|
226
|
-
"@nestjs/core",
|
|
227
|
-
"h3"
|
|
228
|
-
];
|
|
229
|
-
const entrypointFilenames = [
|
|
230
|
-
"app",
|
|
231
|
-
"index",
|
|
232
|
-
"server",
|
|
233
|
-
"main"
|
|
234
|
-
];
|
|
235
|
-
const entrypointExtensions = [
|
|
236
|
-
"js",
|
|
237
|
-
"cjs",
|
|
238
|
-
"mjs",
|
|
239
|
-
"ts",
|
|
240
|
-
"cts",
|
|
241
|
-
"mts"
|
|
242
|
-
];
|
|
243
|
-
const entrypoints = entrypointFilenames.flatMap((filename) => entrypointExtensions.map((extension) => `${filename}.${extension}`));
|
|
244
|
-
const createFrameworkRegex = (framework) => new RegExp(`(?:from|require|import)\\s*(?:\\(\\s*)?["']${framework}["']\\s*(?:\\))?`, "g");
|
|
245
|
-
const findEntrypoint = async (cwd, options) => {
|
|
246
|
-
if (options?.ignoreRegex ?? false) {
|
|
247
|
-
for (const entrypoint of entrypoints) if (existsSync(join(cwd, entrypoint))) return entrypoint;
|
|
248
|
-
for (const entrypoint of entrypoints) if (existsSync(join(cwd, "src", entrypoint))) return join("src", entrypoint);
|
|
249
|
-
throw new Error("No entrypoint file found");
|
|
250
|
-
}
|
|
251
|
-
const packageJson = await readFile(join(cwd, "package.json"), "utf-8");
|
|
252
|
-
const packageJsonObject = JSON.parse(packageJson);
|
|
253
|
-
const framework = frameworks.find((framework$1) => packageJsonObject.dependencies?.[framework$1]);
|
|
254
|
-
if (!framework) {
|
|
255
|
-
for (const entrypoint of entrypoints) {
|
|
256
|
-
const entrypointPath = join(cwd, entrypoint);
|
|
257
|
-
try {
|
|
258
|
-
await readFile(entrypointPath, "utf-8");
|
|
259
|
-
return entrypoint;
|
|
260
|
-
} catch (e) {
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
throw new Error("No entrypoint or framework found");
|
|
265
|
-
}
|
|
266
|
-
const regex = createFrameworkRegex(framework);
|
|
267
|
-
for (const entrypoint of entrypoints) {
|
|
268
|
-
const entrypointPath = join(cwd, entrypoint);
|
|
269
|
-
try {
|
|
270
|
-
const content = await readFile(entrypointPath, "utf-8");
|
|
271
|
-
if (regex.test(content)) return entrypoint;
|
|
272
|
-
} catch (e) {
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
for (const entrypoint of entrypoints) {
|
|
277
|
-
const entrypointPath = join(cwd, "src", entrypoint);
|
|
278
|
-
try {
|
|
279
|
-
const content = await readFile(entrypointPath, "utf-8");
|
|
280
|
-
if (regex.test(content)) return join("src", entrypoint);
|
|
281
|
-
} catch (e) {
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
throw new Error("No entrypoint found");
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
//#endregion
|
|
289
|
-
//#region src/index.ts
|
|
290
|
-
const require = createRequire(import.meta.url);
|
|
291
|
-
const getBuildSummary = async (outputDir) => {
|
|
292
|
-
const buildSummary = await readFile(join(outputDir, ".cervel.json"), "utf-8");
|
|
293
|
-
return JSON.parse(buildSummary);
|
|
294
|
-
};
|
|
295
|
-
const build = async (args) => {
|
|
296
|
-
const entrypoint = args.entrypoint || await findEntrypoint(args.cwd);
|
|
297
|
-
const tsPromise = typescript({
|
|
298
|
-
...args,
|
|
299
|
-
entrypoint,
|
|
300
|
-
workPath: args.cwd
|
|
301
|
-
});
|
|
302
|
-
const rolldownResult = await rolldown({
|
|
303
|
-
...args,
|
|
304
|
-
entrypoint,
|
|
305
|
-
workPath: args.cwd,
|
|
306
|
-
repoRootPath: args.cwd,
|
|
307
|
-
out: args.out
|
|
308
|
-
});
|
|
309
|
-
await writeFile(join(args.cwd, args.out, ".cervel.json"), JSON.stringify({ handler: rolldownResult.result.handler }, null, 2));
|
|
310
|
-
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Build complete`));
|
|
311
|
-
const typecheckComplete = true;
|
|
312
|
-
const result = tsPromise ? await Promise.race([tsPromise.then(() => typecheckComplete), Promise.resolve(false)]) : true;
|
|
313
|
-
if (tsPromise && !result) console.log(Colors.gray(`${Colors.bold(Colors.gray("*"))} Waiting for typecheck...`));
|
|
314
|
-
return {
|
|
315
|
-
rolldownResult: rolldownResult.result,
|
|
316
|
-
tsPromise
|
|
317
|
-
};
|
|
318
|
-
};
|
|
319
|
-
const serve = async (args) => {
|
|
320
|
-
const entrypoint = await findEntrypoint(args.cwd);
|
|
321
|
-
const srvxBin = join(require.resolve("srvx"), "..", "..", "..", "bin", "srvx.mjs");
|
|
322
|
-
const tsxBin = require.resolve("tsx");
|
|
323
|
-
const restArgs = Object.entries(args.rest).filter(([, value]) => value !== void 0 && value !== false).map(([key, value]) => typeof value === "boolean" ? `--${key}` : `--${key}=${value}`);
|
|
324
|
-
if (!args.rest.import) restArgs.push("--import", tsxBin);
|
|
325
|
-
await execa("npx", [
|
|
326
|
-
srvxBin,
|
|
327
|
-
...restArgs,
|
|
328
|
-
entrypoint
|
|
329
|
-
], {
|
|
330
|
-
cwd: args.cwd,
|
|
331
|
-
stdio: "inherit"
|
|
332
|
-
});
|
|
333
|
-
};
|
|
334
|
-
const srvxOptions = {
|
|
335
|
-
help: {
|
|
336
|
-
type: "boolean",
|
|
337
|
-
short: "h"
|
|
338
|
-
},
|
|
339
|
-
version: {
|
|
340
|
-
type: "boolean",
|
|
341
|
-
short: "v"
|
|
342
|
-
},
|
|
343
|
-
prod: { type: "boolean" },
|
|
344
|
-
port: {
|
|
345
|
-
type: "string",
|
|
346
|
-
short: "p"
|
|
347
|
-
},
|
|
348
|
-
host: {
|
|
349
|
-
type: "string",
|
|
350
|
-
short: "H"
|
|
351
|
-
},
|
|
352
|
-
static: {
|
|
353
|
-
type: "string",
|
|
354
|
-
short: "s"
|
|
355
|
-
},
|
|
356
|
-
import: { type: "string" },
|
|
357
|
-
tls: { type: "boolean" },
|
|
358
|
-
cert: { type: "string" },
|
|
359
|
-
key: { type: "string" }
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
//#endregion
|
|
363
|
-
export { build, findEntrypoint, getBuildSummary, serve, srvxOptions };
|
|
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.12",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://vercel.com/docs",
|
|
6
6
|
"publishConfig": {
|
|
@@ -28,10 +28,7 @@
|
|
|
28
28
|
"dist"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"
|
|
32
|
-
"rolldown": "1.0.0-beta.59",
|
|
33
|
-
"srvx": "0.8.9",
|
|
34
|
-
"tsx": "4.21.0"
|
|
31
|
+
"@vercel/backends": "0.0.25"
|
|
35
32
|
},
|
|
36
33
|
"peerDependencies": {
|
|
37
34
|
"typescript": "^4.0.0 || ^5.0.0"
|