diff --git a/packages/cli-workflow/__tests__/init-workspace.test.ts b/packages/cli-workflow/__tests__/init-workspace.test.ts index c29ca5b..7c682c9 100644 --- a/packages/cli-workflow/__tests__/init-workspace.test.ts +++ b/packages/cli-workflow/__tests__/init-workspace.test.ts @@ -38,8 +38,16 @@ describe("init workspace", () => { const rootPkg = JSON.parse(await readFile(join(root, "package.json"), "utf8")) as { workspaces: string[]; + scripts: { bundle: string }; }; expect(rootPkg.workspaces).toEqual(["templates/*", "workflows"]); + expect(rootPkg.scripts.bundle).toBe("bun run scripts/bundle.ts"); + + expect(await pathExists(join(root, "scripts", "bundle.ts"))).toBe(true); + const bundleSrc = await readFile(join(root, "scripts", "bundle.ts"), "utf8"); + expect(bundleSrc).toContain("Bun.build"); + expect(bundleSrc).toContain("-entry.ts"); + expect(bundleSrc).toContain("distDir"); const wfPkg = JSON.parse(await readFile(join(root, "workflows", "package.json"), "utf8")) as { type: string; diff --git a/packages/cli-workflow/src/commands/init/workspace.ts b/packages/cli-workflow/src/commands/init/workspace.ts index 49b8aa2..6534067 100644 --- a/packages/cli-workflow/src/commands/init/workspace.ts +++ b/packages/cli-workflow/src/commands/init/workspace.ts @@ -14,6 +14,9 @@ function rootPackageJson(workspaceName: string): string { private: true, type: "module", workspaces: ["templates/*", "workflows"], + scripts: { + bundle: "bun run scripts/bundle.ts", + }, }, null, 2, @@ -42,7 +45,7 @@ function biomeJson(): string { { $schema: "https://biomejs.dev/schemas/2.4.14/schema.json", files: { - includes: ["**", "!**/node_modules", "!**/dist"], + includes: ["**", "!**/node_modules", "!**/dist", "!scripts/bundle.ts"], }, formatter: { indentWidth: 2, @@ -190,6 +193,104 @@ uncaged-workflow init workspace ${workspaceName} `; } +function bundleTs(): string { + return [ + 'import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";', + 'import { join } from "node:path";', + "", + 'const rootDir = join(import.meta.dir, "..");', + 'const workflowsDir = join(rootDir, "workflows");', + 'const distDir = join(rootDir, "dist");', + "", + "type JsonDeps = {", + " dependencies: Record | null;", + " devDependencies: Record | null;", + "};", + "", + "function isEntryFile(name: string): boolean {", + ' return name.endsWith("-entry.ts");', + "}", + "", + "function entryStem(name: string): string {", + ' return name.slice(0, -".ts".length);', + "}", + "", + "async function uncagedWorkflowExternals(): Promise {", + " const names = new Set();", + ' const paths = [join(rootDir, "package.json"), join(workflowsDir, "package.json")];', + " for (const pkgPath of paths) {", + " let raw: string;", + " try {", + ' raw = await readFile(pkgPath, "utf8");', + " } catch {", + " continue;", + " }", + " const parsed = JSON.parse(raw) as JsonDeps;", + " const blocks = [parsed.dependencies, parsed.devDependencies];", + " for (const block of blocks) {", + " if (block == null) {", + " continue;", + " }", + " for (const key of Object.keys(block)) {", + ' if (key.startsWith("@uncaged/workflow")) {', + " names.add(key);", + " }", + " }", + " }", + " }", + " if (names.size === 0) {", + ' names.add("@uncaged/workflow-runtime");', + ' names.add("@uncaged/workflow-protocol");', + " }", + " return [...names];", + "}", + "", + "async function main(): Promise {", + " await mkdir(distDir, { recursive: true });", + " let files: string[];", + " try {", + " files = await readdir(workflowsDir);", + " } catch {", + ' console.error("bundle: missing workflows/ directory");', + " process.exitCode = 1;", + " return;", + " }", + " const entries = files.filter(isEntryFile);", + " if (entries.length === 0) {", + ' console.warn("bundle: no *-entry.ts files under workflows/");', + " return;", + " }", + " const external = await uncagedWorkflowExternals();", + " for (const file of entries) {", + " const stem = entryStem(file);", + " const entryPath = join(workflowsDir, file);", + " const result = await Bun.build({", + " entrypoints: [entryPath],", + " outdir: distDir,", + ' format: "esm",', + ' target: "node",', + " splitting: false,", + ' naming: { entry: "[name].esm.js" },', + " external,", + " });", + " if (!result.success) {", + " for (const log of result.logs) {", + " console.error(log);", + " }", + ` throw new Error(\`bundle failed for \${file}\`);`, + " }", + " const dts =", + ` 'export { run, descriptor } from "../workflows/' + stem + '.js";\\n';`, + ` await writeFile(join(distDir, \`\${stem}.d.ts\`), dts, "utf8");`, + ` console.log(\`bundle: \${stem} -> dist/\${stem}.esm.js\`);`, + " }", + "}", + "", + "await main();", + "", + ].join("\n"); +} + export async function cmdInitWorkspace( parentDir: string, workspaceName: string, @@ -207,6 +308,7 @@ export async function cmdInitWorkspace( await mkdir(rootPath, { recursive: false }); await mkdir(join(rootPath, "templates"), { recursive: false }); await mkdir(join(rootPath, "workflows"), { recursive: false }); + await mkdir(join(rootPath, "scripts"), { recursive: false }); await Promise.all([ writeFile(join(rootPath, "package.json"), rootPackageJson(workspaceName), "utf8"), @@ -217,6 +319,7 @@ export async function cmdInitWorkspace( writeFile(join(rootPath, "templates", ".gitkeep"), "", "utf8"), writeFile(join(rootPath, "workflows", "package.json"), workflowsPackageJson(), "utf8"), writeFile(join(rootPath, "bunfig.toml"), bunfigToml(), "utf8"), + writeFile(join(rootPath, "scripts", "bundle.ts"), bundleTs(), "utf8"), ]); return ok({ rootPath });