trigger.dev
Version:
A Command-Line Interface for Trigger.dev projects
110 lines • 5.04 kB
JavaScript
import { readFile } from "node:fs/promises";
import { isAbsolute, join, resolve as resolvePath } from "node:path";
import { copyDirectoryRecursive } from "@trigger.dev/build/internal";
import { indexWorkerManifest } from "../indexing/indexWorkerManifest.js";
import { execOptionsForRuntime } from "@trigger.dev/core/v3/build";
/**
* Copy each skill's source folder to `{destinationRoot}/{id}/`. Validates
* that `SKILL.md` exists and has the required frontmatter. Pure file IO —
* no indexer subprocess, no env handling.
*
* Used by the dev path (driven by the main worker indexer's skills list)
* and indirectly by the deploy path (via `bundleSkills` which discovers
* skills via its own indexer pass first, then delegates here).
*/
export async function copySkillFolders(options) {
const { skills, destinationRoot, workingDir, logger } = options;
if (skills.length === 0) {
return [];
}
for (const skill of skills) {
const callerDir = skill.filePath ? resolvePath(workingDir, skill.filePath, "..") : workingDir;
const sourcePath = isAbsolute(skill.sourcePath)
? skill.sourcePath
: resolvePath(callerDir, skill.sourcePath);
const skillMdPath = join(sourcePath, "SKILL.md");
let skillMd;
try {
skillMd = await readFile(skillMdPath, "utf8");
}
catch {
throw new Error(`Skill "${skill.id}": SKILL.md not found at ${skillMdPath}. ` +
`Registered via skills.define({ id: "${skill.id}", path: "${skill.sourcePath}" }) ` +
`at ${skill.filePath}.`);
}
if (!/^---\r?\n[\s\S]*?\r?\n---/.test(skillMd)) {
throw new Error(`Skill "${skill.id}": SKILL.md at ${skillMdPath} is missing a frontmatter block.`);
}
if (!/\bname:\s*\S/.test(skillMd) || !/\bdescription:\s*\S/.test(skillMd)) {
throw new Error(`Skill "${skill.id}": SKILL.md at ${skillMdPath} frontmatter must include both \`name\` and \`description\`.`);
}
const skillDest = join(destinationRoot, skill.id);
logger.debug(`[copySkillFolders] Copying ${sourcePath} → ${skillDest}`);
await copyDirectoryRecursive(sourcePath, skillDest);
}
return [...skills].sort((a, b) => a.id.localeCompare(b.id));
}
/**
* Built-in skill bundler — not an extension. Runs the indexer locally
* against the bundled worker output to discover `skills.define(...)`
* registrations, validates each skill's `SKILL.md`, and copies the
* folder into `{outputPath}/.trigger/skills/{id}/` so the deploy image
* picks it up via the existing Dockerfile `COPY`.
*
* Used by the deploy path. The dev path uses `copySkillFolders` directly,
* driven by the main worker indexer that already runs in `BackgroundWorker.initialize` —
* no duplicate indexer pass needed there.
*
* No `trigger.config.ts` changes required — discovery is side-effect
* based, same mechanism as task/prompt registration.
*/
export async function bundleSkills(options) {
const { buildManifest, buildManifestPath, workingDir, env, logger } = options;
let skills;
try {
const workerManifest = await indexWorkerManifest({
runtime: buildManifest.runtime,
indexWorkerPath: buildManifest.indexWorkerEntryPoint,
buildManifestPath,
nodeOptions: execOptionsForRuntime(buildManifest.runtime, buildManifest),
env,
cwd: workingDir,
otelHookInclude: buildManifest.otelImportHook?.include,
otelHookExclude: buildManifest.otelImportHook?.exclude,
handleStdout(data) {
logger.debug(`[bundleSkills] ${data}`);
},
handleStderr(data) {
if (!data.includes("Debugger attached")) {
logger.debug(`[bundleSkills:stderr] ${data}`);
}
},
});
skills = workerManifest.skills ?? [];
}
catch (err) {
// Skill discovery via the indexer is best-effort — if the user's
// bundle doesn't load cleanly here the downstream full indexer will
// surface the real error. Warn so the user sees what went wrong.
logger.warn(`[bundleSkills] skill discovery failed, skipping skill bundling: ${err.message}`);
return { buildManifest, skills: [] };
}
if (skills.length === 0) {
return { buildManifest, skills: [] };
}
// Deploy target: the Dockerfile COPY picks up everything under outputPath
// into /app, so we target {outputPath}/.trigger/skills/{id}/ and the
// container's cwd (/app) resolves correctly.
const destinationRoot = join(buildManifest.outputPath, ".trigger", "skills");
const sortedSkills = await copySkillFolders({
skills,
destinationRoot,
workingDir,
logger,
});
return {
buildManifest: { ...buildManifest, skills: sortedSkills },
skills: sortedSkills,
};
}
//# sourceMappingURL=bundleSkills.js.map