feat(cli): add binary build support for interactive CLI
Add esbuild-based build script to bundle CLI tools into standalone binaries that can be run directly with node. The built binaries externalize dependencies to keep bundle size small and avoid bundling issues with native modules. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4f50b3b399
commit
7f25378e4b
4 changed files with 86 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -8,6 +8,7 @@ dist
|
||||||
out
|
out
|
||||||
.turbo
|
.turbo
|
||||||
build
|
build
|
||||||
|
bin
|
||||||
dist-electron
|
dist-electron
|
||||||
release
|
release
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,12 @@
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
"bin": {
|
||||||
|
"multica": "./bin/multica-interactive.mjs",
|
||||||
|
"multica-interactive": "./bin/multica-interactive.mjs",
|
||||||
|
"multica-cli": "./bin/multica-cli.mjs",
|
||||||
|
"multica-profile": "./bin/multica-profile.mjs"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx src/index.ts",
|
"dev": "tsx src/index.ts",
|
||||||
"agent:cli": "tsx src/agent/cli.ts",
|
"agent:cli": "tsx src/agent/cli.ts",
|
||||||
|
|
@ -15,6 +21,7 @@
|
||||||
"dev:desktop": "pnpm --filter @multica/desktop dev",
|
"dev:desktop": "pnpm --filter @multica/desktop dev",
|
||||||
"build": "turbo build",
|
"build": "turbo build",
|
||||||
"build:sdk": "pnpm --filter @multica/sdk build",
|
"build:sdk": "pnpm --filter @multica/sdk build",
|
||||||
|
"build:cli": "node scripts/build-cli.js",
|
||||||
"start": "node dist/index.js",
|
"start": "node dist/index.js",
|
||||||
"typecheck": "turbo typecheck",
|
"typecheck": "turbo typecheck",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
|
|
@ -30,6 +37,7 @@
|
||||||
"@types/turndown": "^5.0.6",
|
"@types/turndown": "^5.0.6",
|
||||||
"@types/uuid": "^11.0.0",
|
"@types/uuid": "^11.0.0",
|
||||||
"@vitest/coverage-v8": "^4.0.18",
|
"@vitest/coverage-v8": "^4.0.18",
|
||||||
|
"esbuild": "^0.27.2",
|
||||||
"tsx": "^4.21.0",
|
"tsx": "^4.21.0",
|
||||||
"turbo": "^2.3.4",
|
"turbo": "^2.3.4",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
|
|
|
||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
|
|
@ -120,6 +120,9 @@ importers:
|
||||||
'@vitest/coverage-v8':
|
'@vitest/coverage-v8':
|
||||||
specifier: ^4.0.18
|
specifier: ^4.0.18
|
||||||
version: 4.0.18(vitest@4.0.18(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.10)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.2))
|
version: 4.0.18(vitest@4.0.18(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.10)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.2))
|
||||||
|
esbuild:
|
||||||
|
specifier: ^0.27.2
|
||||||
|
version: 0.27.2
|
||||||
tsx:
|
tsx:
|
||||||
specifier: ^4.21.0
|
specifier: ^4.21.0
|
||||||
version: 4.21.0
|
version: 4.21.0
|
||||||
|
|
|
||||||
74
scripts/build-cli.js
Normal file
74
scripts/build-cli.js
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
import * as esbuild from "esbuild";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { dirname, resolve } from "path";
|
||||||
|
import { readFileSync, chmodSync } from "fs";
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const rootDir = resolve(__dirname, "..");
|
||||||
|
|
||||||
|
// Read package.json to get all dependencies
|
||||||
|
const pkg = JSON.parse(readFileSync(resolve(rootDir, "package.json"), "utf8"));
|
||||||
|
const allDeps = [
|
||||||
|
...Object.keys(pkg.dependencies || {}),
|
||||||
|
...Object.keys(pkg.devDependencies || {}),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Plugin to strip shebangs from source files (they get bundled otherwise)
|
||||||
|
const stripShebangPlugin = {
|
||||||
|
name: "strip-shebang",
|
||||||
|
setup(build) {
|
||||||
|
build.onLoad({ filter: /\.ts$/ }, async (args) => {
|
||||||
|
const source = readFileSync(args.path, "utf8");
|
||||||
|
// Remove shebang if present
|
||||||
|
const contents = source.replace(/^#!.*\n/, "");
|
||||||
|
return { contents, loader: "ts" };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function build() {
|
||||||
|
const entryPoints = [
|
||||||
|
{ entry: "src/agent/interactive-cli.ts", outfile: "bin/multica-interactive.mjs" },
|
||||||
|
{ entry: "src/agent/cli.ts", outfile: "bin/multica-cli.mjs" },
|
||||||
|
{ entry: "src/agent/profile-cli.ts", outfile: "bin/multica-profile.mjs" },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const { entry, outfile } of entryPoints) {
|
||||||
|
console.log(`Building ${entry} -> ${outfile}...`);
|
||||||
|
|
||||||
|
await esbuild.build({
|
||||||
|
entryPoints: [resolve(rootDir, entry)],
|
||||||
|
outfile: resolve(rootDir, outfile),
|
||||||
|
bundle: true,
|
||||||
|
platform: "node",
|
||||||
|
target: "node20",
|
||||||
|
format: "esm",
|
||||||
|
banner: {
|
||||||
|
js: "#!/usr/bin/env node",
|
||||||
|
},
|
||||||
|
plugins: [stripShebangPlugin],
|
||||||
|
sourcemap: true,
|
||||||
|
minify: false,
|
||||||
|
// Externalize all dependencies - they will be loaded from node_modules at runtime
|
||||||
|
external: allDeps,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make executable
|
||||||
|
chmodSync(resolve(rootDir, outfile), 0o755);
|
||||||
|
console.log(` ✓ ${outfile}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("\nBuild complete! Binaries are in ./bin/");
|
||||||
|
console.log("\nUsage:");
|
||||||
|
console.log(" node bin/multica-interactive.mjs # Interactive CLI");
|
||||||
|
console.log(" node bin/multica-cli.mjs # Non-interactive CLI");
|
||||||
|
console.log(" node bin/multica-profile.mjs # Profile management");
|
||||||
|
console.log("\nNote: The built binaries require node_modules to be present.");
|
||||||
|
console.log("Run 'pnpm install --prod' to install only production dependencies.");
|
||||||
|
}
|
||||||
|
|
||||||
|
build().catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue