trigger.dev
Version:
A Command-Line Interface for Trigger.dev (v3) projects
239 lines (237 loc) • 9.09 kB
JavaScript
import * as esbuild from "esbuild";
import { createHash } from "node:crypto";
import { join, relative, resolve } from "node:path";
import { createFile } from "../utilities/fileSystem.js";
import { logger } from "../utilities/logger.js";
import { deployEntryPoints, devEntryPoints, isIndexControllerForTarget, isIndexWorkerForTarget, isLoaderEntryPoint, isRunControllerForTarget, isRunWorkerForTarget, shims, telemetryEntryPoint, } from "./packageModules.js";
import { buildPlugins } from "./plugins.js";
import { createEntryPointManager } from "./entryPoints.js";
import { cliLink, prettyError } from "../utilities/cliOutput.js";
import { SkipLoggingError } from "../cli/common.js";
export async function bundleWorker(options) {
const { resolvedConfig } = options;
let currentContext;
const entryPointManager = await createEntryPointManager(resolvedConfig.dirs, resolvedConfig, options.target, typeof options.watch === "boolean" ? options.watch : false, async (newEntryPoints) => {
if (currentContext) {
// Rebuild with new entry points
await currentContext.cancel();
await currentContext.dispose();
const buildOptions = await createBuildOptions({
...options,
entryPoints: newEntryPoints,
});
logger.debug("Rebuilding worker with options", buildOptions);
currentContext = await esbuild.context(buildOptions);
await currentContext.watch();
}
});
if (entryPointManager.entryPoints.length === 0) {
const errorMessageBody = `
Dirs config:
${resolvedConfig.dirs.join("\n- ")}
Search patterns:
${entryPointManager.patterns.join("\n- ")}
Possible solutions:
1. Check if the directory paths in your config are correct
2. Verify that your files match the search patterns
3. Update the search patterns in your config
`.replace(/^ {6}/gm, "");
prettyError("No trigger files found", errorMessageBody, cliLink("View the config docs", "https://trigger.dev/docs/config/config-file"));
throw new SkipLoggingError();
}
let initialBuildResult;
const initialBuildResultPromise = new Promise((resolve) => (initialBuildResult = resolve));
const buildResultPlugin = {
name: "Initial build result plugin",
setup(build) {
build.onEnd(initialBuildResult);
},
};
const buildOptions = await createBuildOptions({
...options,
entryPoints: entryPointManager.entryPoints,
buildResultPlugin,
});
let result;
let stop;
logger.debug("Building worker with options", buildOptions);
if (options.watch) {
currentContext = await esbuild.context(buildOptions);
await currentContext.watch();
result = await initialBuildResultPromise;
if (result.errors.length > 0) {
throw new Error("Failed to build");
}
stop = async function () {
await entryPointManager.stop();
await currentContext?.dispose();
};
}
else {
result = await esbuild.build(buildOptions);
stop = async function () {
await entryPointManager.stop();
};
}
const bundleResult = await getBundleResultFromBuild(options.target, options.cwd, options.resolvedConfig, result);
if (!bundleResult) {
throw new Error("Failed to get bundle result");
}
return { ...bundleResult, stop };
}
// Helper function to create build options
async function createBuildOptions(options) {
const customConditions = options.resolvedConfig.build?.conditions ?? [];
const conditions = [...customConditions, "trigger.dev", "module", "node"];
const $buildPlugins = await buildPlugins(options.target, options.resolvedConfig);
return {
entryPoints: options.entryPoints,
outdir: options.destination,
absWorkingDir: options.cwd,
bundle: true,
metafile: true,
write: false,
minify: false,
splitting: true,
charset: "utf8",
platform: "node",
sourcemap: true,
sourcesContent: options.target === "dev",
conditions,
format: "esm",
target: ["node20", "es2022"],
loader: {
".js": "jsx",
".mjs": "jsx",
".cjs": "jsx",
".wasm": "copy",
},
outExtension: { ".js": ".mjs" },
inject: [...shims], // TODO: copy this into the working dir to work with Yarn PnP
jsx: options.jsxAutomatic ? "automatic" : undefined,
jsxDev: options.jsxAutomatic && options.target === "dev" ? true : undefined,
plugins: [
...$buildPlugins,
...(options.plugins ?? []),
...(options.buildResultPlugin ? [options.buildResultPlugin] : []),
],
...(options.jsxFactory && { jsxFactory: options.jsxFactory }),
...(options.jsxFragment && { jsxFragment: options.jsxFragment }),
logLevel: "silent",
logOverride: {
"empty-glob": "silent",
},
};
}
export async function getBundleResultFromBuild(target, workingDir, resolvedConfig, result) {
const hasher = createHash("md5");
for (const outputFile of result.outputFiles) {
hasher.update(outputFile.hash);
await createFile(outputFile.path, outputFile.contents);
}
const files = [];
let configPath;
let loaderEntryPoint;
let runWorkerEntryPoint;
let runControllerEntryPoint;
let indexWorkerEntryPoint;
let indexControllerEntryPoint;
const configEntryPoint = resolvedConfig.configFile
? relative(resolvedConfig.workingDir, resolvedConfig.configFile)
: "trigger.config.ts";
for (const [outputPath, outputMeta] of Object.entries(result.metafile.outputs)) {
if (outputPath.endsWith(".mjs")) {
const $outputPath = resolve(workingDir, outputPath);
if (!outputMeta.entryPoint) {
continue;
}
if (outputMeta.entryPoint.startsWith(configEntryPoint)) {
configPath = $outputPath;
}
else if (isLoaderEntryPoint(outputMeta.entryPoint)) {
loaderEntryPoint = $outputPath;
}
else if (isRunControllerForTarget(outputMeta.entryPoint, target)) {
runControllerEntryPoint = $outputPath;
}
else if (isRunWorkerForTarget(outputMeta.entryPoint, target)) {
runWorkerEntryPoint = $outputPath;
}
else if (isIndexControllerForTarget(outputMeta.entryPoint, target)) {
indexControllerEntryPoint = $outputPath;
}
else if (isIndexWorkerForTarget(outputMeta.entryPoint, target)) {
indexWorkerEntryPoint = $outputPath;
}
else {
if (!outputMeta.entryPoint.startsWith("..") &&
!outputMeta.entryPoint.includes("node_modules")) {
files.push({
entry: outputMeta.entryPoint,
out: $outputPath,
});
}
}
}
}
if (!configPath) {
return undefined;
}
return {
files,
configPath: configPath,
loaderEntryPoint,
runWorkerEntryPoint,
runControllerEntryPoint,
indexWorkerEntryPoint,
indexControllerEntryPoint,
contentHash: hasher.digest("hex"),
};
}
async function getEntryPoints(target, config) {
const projectEntryPoints = config.dirs.flatMap((dir) => dirToEntryPointGlob(dir));
if (config.configFile) {
projectEntryPoints.push(config.configFile);
}
if (target === "dev") {
projectEntryPoints.push(...devEntryPoints);
}
else {
projectEntryPoints.push(...deployEntryPoints);
}
if (config.instrumentedPackageNames?.length ?? 0 > 0) {
projectEntryPoints.push(telemetryEntryPoint);
}
return projectEntryPoints;
}
// Converts a directory to a glob that matches all the entry points in that
function dirToEntryPointGlob(dir) {
return [
join(dir, "**", "*.ts"),
join(dir, "**", "*.tsx"),
join(dir, "**", "*.mts"),
join(dir, "**", "*.cts"),
join(dir, "**", "*.js"),
join(dir, "**", "*.jsx"),
join(dir, "**", "*.mjs"),
join(dir, "**", "*.cjs"),
];
}
export function logBuildWarnings(warnings) {
const logs = esbuild.formatMessagesSync(warnings, { kind: "warning", color: true });
for (const log of logs) {
console.warn(log);
}
}
/**
* Logs all errors/warnings associated with an esbuild BuildFailure in the same
* style esbuild would.
*/
export function logBuildFailure(errors, warnings) {
const logs = esbuild.formatMessagesSync(errors, { kind: "error", color: true });
for (const log of logs) {
console.error(log);
}
logBuildWarnings(warnings);
}
//# sourceMappingURL=bundle.js.map