feat(KB-131): bundle workspace dependencies into CLI for npm publish

- Replace tsc with tsup (esbuild) to inline @kb/* workspace code into a single dist/bin.js
- Move @kb/core, @kb/dashboard, @kb/engine from dependencies to devDependencies
- Add tsup.config.ts that copies dashboard client assets into dist/client/
- Add bundle output tests verifying shebang, inlined code, and no bare @kb/* imports
- Add changeset for the bundled CLI build fix
This commit is contained in:
Dustin Byrne
2026-03-27 19:55:45 -04:00
parent a32897921c
commit b4577994b4
6 changed files with 314 additions and 8 deletions

View File

@@ -11,19 +11,23 @@
],
"scripts": {
"dev": "tsx src/bin.ts",
"build": "tsc",
"build": "tsup",
"build:exe": "bun run build.ts",
"build:exe:all": "bun run build.ts --all",
"typecheck": "tsc --noEmit",
"test": "vitest run"
},
"dependencies": {
"@mariozechner/pi-ai": "^0.62.0",
"@mariozechner/pi-coding-agent": "^0.62.0",
"express": "^5.1.0",
"multer": "^2.1.1"
},
"devDependencies": {
"@kb/core": "workspace:*",
"@kb/dashboard": "workspace:*",
"@kb/engine": "workspace:*",
"@mariozechner/pi-coding-agent": "^0.62.0"
},
"devDependencies": {
"tsup": "^8.5.1",
"tsx": "^4.19.0",
"typescript": "^5.7.0",
"vitest": "^3.1.0",

View File

@@ -0,0 +1,37 @@
import { describe, it, expect } from "vitest";
import { readFileSync, existsSync } from "node:fs";
import { join } from "node:path";
const cliRoot = join(__dirname, "..", "..");
const bundlePath = join(cliRoot, "dist", "bin.js");
describe("CLI bundle output", () => {
it("dist/bin.js exists", () => {
expect(existsSync(bundlePath)).toBe(true);
});
it("starts with a shebang", () => {
const content = readFileSync(bundlePath, "utf-8");
expect(content.startsWith("#!/usr/bin/env node")).toBe(true);
});
it("does not contain bare @kb/* import specifiers", () => {
const content = readFileSync(bundlePath, "utf-8");
expect(content).not.toMatch(/from\s+["']@kb\/core["']/);
expect(content).not.toMatch(/from\s+["']@kb\/dashboard["']/);
expect(content).not.toMatch(/from\s+["']@kb\/engine["']/);
});
it("contains inlined workspace code", () => {
const content = readFileSync(bundlePath, "utf-8");
// TaskStore from @kb/core
expect(content).toContain("TaskStore");
// createServer from @kb/dashboard
expect(content).toContain("createServer");
});
it("dashboard client assets are included", () => {
const clientIndex = join(cliRoot, "dist", "client", "index.html");
expect(existsSync(clientIndex)).toBe(true);
});
});

View File

@@ -33,6 +33,12 @@ describe("CLI package.json publishing config", () => {
it("is not private", () => {
expect(pkg.private).not.toBe(true);
});
it("does not have @kb/* workspace packages in dependencies", () => {
const deps = Object.keys(pkg.dependencies || {});
const kbDeps = deps.filter((d) => d.startsWith("@kb/"));
expect(kbDeps).toEqual([]);
});
});
describe("Scoped @kb/* packages publishing config", () => {

View File

@@ -0,0 +1,26 @@
import { defineConfig } from "tsup";
import { cpSync, existsSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const dashboardClientSrc = join(__dirname, "..", "dashboard", "dist", "client");
const dashboardClientDest = join(__dirname, "dist", "client");
export default defineConfig({
entry: ["src/bin.ts"],
format: ["esm"],
platform: "node",
target: "node22",
noExternal: [/^@kb\//],
splitting: false,
clean: true,
onSuccess: async () => {
if (existsSync(dashboardClientSrc)) {
cpSync(dashboardClientSrc, dashboardClientDest, { recursive: true });
console.log("Copied dashboard client assets to dist/client/");
} else {
console.warn("WARNING: Dashboard client assets not found at", dashboardClientSrc);
}
},
});