cmux/node_modules/@vercel/cervel/dist/index.mjs
2026-01-29 17:36:26 -08:00

363 lines
No EOL
9.7 KiB
JavaScript

import { createRequire } from "module";
import { existsSync } from "fs";
import { readFile, rm, writeFile } from "fs/promises";
import { extname, join } from "path";
import { build as build$1 } from "rolldown";
import { spawn } from "child_process";
import execa from "execa";
//#region src/rolldown.ts
/**
* Escapes special regex characters in a string to treat it as a literal pattern.
*/
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
const rolldown = async (args) => {
const baseDir = args.repoRootPath || args.workPath;
const entrypointPath = join(args.workPath, args.entrypoint);
const shouldAddSourcemapSupport = false;
const extension = extname(args.entrypoint);
const extensionMap = {
".ts": {
format: "auto",
extension: "js"
},
".mts": {
format: "esm",
extension: "mjs"
},
".cts": {
format: "cjs",
extension: "cjs"
},
".cjs": {
format: "cjs",
extension: "cjs"
},
".js": {
format: "auto",
extension: "js"
},
".mjs": {
format: "esm",
extension: "mjs"
}
};
const extensionInfo = extensionMap[extension] || extensionMap[".js"];
let resolvedFormat = extensionInfo.format === "auto" ? void 0 : extensionInfo.format;
const resolvedExtension = extensionInfo.extension;
const packageJsonPath = join(args.workPath, "package.json");
const external = [];
let pkg = {};
if (existsSync(packageJsonPath)) {
const source = await readFile(packageJsonPath, "utf8");
try {
pkg = JSON.parse(source.toString());
} catch (_e) {
pkg = {};
}
if (extensionInfo.format === "auto") if (pkg.type === "module") resolvedFormat = "esm";
else resolvedFormat = "cjs";
for (const dependency of Object.keys(pkg.dependencies || {})) external.push(dependency);
for (const dependency of Object.keys(pkg.devDependencies || {})) external.push(dependency);
for (const dependency of Object.keys(pkg.peerDependencies || {})) external.push(dependency);
for (const dependency of Object.keys(pkg.optionalDependencies || {})) external.push(dependency);
}
const relativeOutputDir = args.out;
const outputDir = join(baseDir, relativeOutputDir);
const out = await build$1({
input: entrypointPath,
cwd: baseDir,
platform: "node",
tsconfig: true,
external: external.map((pkg$1) => /* @__PURE__ */ new RegExp(`^${escapeRegExp(pkg$1)}`)),
output: {
cleanDir: true,
dir: outputDir,
format: resolvedFormat,
entryFileNames: `[name].${resolvedExtension}`,
preserveModules: true,
sourcemap: false
}
});
let handler = null;
for (const entry of out.output) if (entry.type === "chunk") {
if (entry.isEntry) handler = entry.fileName;
}
if (typeof handler !== "string") throw new Error(`Unable to resolve module for ${args.entrypoint}`);
const cleanup = async () => {
await rm(outputDir, {
recursive: true,
force: true
});
};
return {
result: {
pkg,
shouldAddSourcemapSupport,
handler,
outputDir
},
cleanup
};
};
//#endregion
//#region src/utils.ts
const noColor = globalThis.process?.env?.NO_COLOR === "1" || globalThis.process?.env?.TERM === "dumb";
const resets = {
1: 22,
31: 39,
32: 39,
33: 39,
34: 39,
35: 39,
36: 39,
90: 39
};
const _c = (c) => (text) => {
if (noColor) return text;
return `\u001B[${c}m${text}\u001B[${resets[c] ?? 0}m`;
};
const Colors = {
bold: _c(1),
red: _c(31),
green: _c(32),
yellow: _c(33),
blue: _c(34),
magenta: _c(35),
cyan: _c(36),
gray: _c(90),
url: (title, url) => noColor ? `[${title}](${url})` : `\u001B]8;;${url}\u001B\\${title}\u001B]8;;\u001B\\`
};
//#endregion
//#region src/typescript.ts
const require_ = createRequire(import.meta.url);
const typescript = (args) => {
const extension = extname(args.entrypoint);
if (![
".ts",
".mts",
".cts"
].includes(extension)) return;
const tscPath = resolveTscPath(args);
if (!tscPath) {
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck skipped ${Colors.gray("(TypeScript not found)")}`));
return null;
}
return doTypeCheck(args, tscPath);
};
async function doTypeCheck(args, tscPath) {
let stdout = "";
let stderr = "";
/**
* This might be subject to change.
* - if no tscPath, skip typecheck
* - if tsconfig, provide the tsconfig path
* - else provide the entrypoint path
*/
const tscArgs = [
tscPath,
"--noEmit",
"--pretty",
"--allowJs",
"--esModuleInterop",
"--skipLibCheck"
];
const tsconfig = await findNearestTsconfig(args.workPath);
if (tsconfig) tscArgs.push("--project", tsconfig);
else tscArgs.push(args.entrypoint);
const child = spawn(process.execPath, tscArgs, {
cwd: args.workPath,
stdio: [
"ignore",
"pipe",
"pipe"
]
});
child.stdout?.on("data", (data) => {
stdout += data.toString();
});
child.stderr?.on("data", (data) => {
stderr += data.toString();
});
await new Promise((resolve, reject) => {
child.on("close", (code) => {
if (code === 0) {
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Typecheck complete`));
resolve();
} else {
const output = stdout || stderr;
if (output) {
console.error("\nTypeScript type check failed:\n");
console.error(output);
}
reject(/* @__PURE__ */ new Error("TypeScript type check failed"));
}
});
child.on("error", (err) => {
reject(err);
});
});
}
const resolveTscPath = (args) => {
try {
return require_.resolve("typescript/bin/tsc", { paths: [args.workPath] });
} catch (e) {
return null;
}
};
const findNearestTsconfig = async (workPath) => {
const tsconfigPath = join(workPath, "tsconfig.json");
if (existsSync(tsconfigPath)) return tsconfigPath;
if (workPath === "/") return;
return findNearestTsconfig(join(workPath, ".."));
};
//#endregion
//#region src/find-entrypoint.ts
const frameworks = [
"express",
"hono",
"elysia",
"fastify",
"@nestjs/core",
"h3"
];
const entrypointFilenames = [
"app",
"index",
"server",
"main"
];
const entrypointExtensions = [
"js",
"cjs",
"mjs",
"ts",
"cts",
"mts"
];
const entrypoints = entrypointFilenames.flatMap((filename) => entrypointExtensions.map((extension) => `${filename}.${extension}`));
const createFrameworkRegex = (framework) => new RegExp(`(?:from|require|import)\\s*(?:\\(\\s*)?["']${framework}["']\\s*(?:\\))?`, "g");
const findEntrypoint = async (cwd, options) => {
if (options?.ignoreRegex ?? false) {
for (const entrypoint of entrypoints) if (existsSync(join(cwd, entrypoint))) return entrypoint;
for (const entrypoint of entrypoints) if (existsSync(join(cwd, "src", entrypoint))) return join("src", entrypoint);
throw new Error("No entrypoint file found");
}
const packageJson = await readFile(join(cwd, "package.json"), "utf-8");
const packageJsonObject = JSON.parse(packageJson);
const framework = frameworks.find((framework$1) => packageJsonObject.dependencies?.[framework$1]);
if (!framework) {
for (const entrypoint of entrypoints) {
const entrypointPath = join(cwd, entrypoint);
try {
await readFile(entrypointPath, "utf-8");
return entrypoint;
} catch (e) {
continue;
}
}
throw new Error("No entrypoint or framework found");
}
const regex = createFrameworkRegex(framework);
for (const entrypoint of entrypoints) {
const entrypointPath = join(cwd, entrypoint);
try {
const content = await readFile(entrypointPath, "utf-8");
if (regex.test(content)) return entrypoint;
} catch (e) {
continue;
}
}
for (const entrypoint of entrypoints) {
const entrypointPath = join(cwd, "src", entrypoint);
try {
const content = await readFile(entrypointPath, "utf-8");
if (regex.test(content)) return join("src", entrypoint);
} catch (e) {
continue;
}
}
throw new Error("No entrypoint found");
};
//#endregion
//#region src/index.ts
const require = createRequire(import.meta.url);
const getBuildSummary = async (outputDir) => {
const buildSummary = await readFile(join(outputDir, ".cervel.json"), "utf-8");
return JSON.parse(buildSummary);
};
const build = async (args) => {
const entrypoint = args.entrypoint || await findEntrypoint(args.cwd);
const tsPromise = typescript({
...args,
entrypoint,
workPath: args.cwd
});
const rolldownResult = await rolldown({
...args,
entrypoint,
workPath: args.cwd,
repoRootPath: args.cwd,
out: args.out
});
await writeFile(join(args.cwd, args.out, ".cervel.json"), JSON.stringify({ handler: rolldownResult.result.handler }, null, 2));
console.log(Colors.gray(`${Colors.bold(Colors.cyan("✓"))} Build complete`));
const typecheckComplete = true;
const result = tsPromise ? await Promise.race([tsPromise.then(() => typecheckComplete), Promise.resolve(false)]) : true;
if (tsPromise && !result) console.log(Colors.gray(`${Colors.bold(Colors.gray("*"))} Waiting for typecheck...`));
return {
rolldownResult: rolldownResult.result,
tsPromise
};
};
const serve = async (args) => {
const entrypoint = await findEntrypoint(args.cwd);
const srvxBin = join(require.resolve("srvx"), "..", "..", "..", "bin", "srvx.mjs");
const tsxBin = require.resolve("tsx");
const restArgs = Object.entries(args.rest).filter(([, value]) => value !== void 0 && value !== false).map(([key, value]) => typeof value === "boolean" ? `--${key}` : `--${key}=${value}`);
if (!args.rest.import) restArgs.push("--import", tsxBin);
await execa("npx", [
srvxBin,
...restArgs,
entrypoint
], {
cwd: args.cwd,
stdio: "inherit"
});
};
const srvxOptions = {
help: {
type: "boolean",
short: "h"
},
version: {
type: "boolean",
short: "v"
},
prod: { type: "boolean" },
port: {
type: "string",
short: "p"
},
host: {
type: "string",
short: "H"
},
static: {
type: "string",
short: "s"
},
import: { type: "string" },
tls: { type: "boolean" },
cert: { type: "string" },
key: { type: "string" }
};
//#endregion
export { build, findEntrypoint, getBuildSummary, serve, srvxOptions };