import { promises as fs } from "fs"; import * as path from "path"; import { transformEnvAccess } from "./env-scramble"; import { generateBuildPassphrase, generateBuildSalt, generateFunctionName, rewriteRuntimeDecoder, RUNTIME_DECODER_PATH, transformSource, } from "./scramble-shared"; import { stripLogCalls } from "./strip-logs"; (globalThis as any).scramble = (s: string) => s; const { StringScrambler } = await import("../src/utils/stringtool"); const PASSPHRASE = generateBuildPassphrase(); const SALT = generateBuildSalt(); const FN_NAME = generateFunctionName(); console.log( `[BUILD-CLI] Generated build passphrase (${PASSPHRASE.length} chars)`, ); console.log(`[BUILD-CLI] Generated build salt (${SALT.length} chars)`); console.log(`[BUILD-CLI] Generated function name: ${FN_NAME}`); const scrambler = new StringScrambler(PASSPHRASE, SALT); const loggerSource = await fs.readFile("src/utils/logger.ts", "utf-8"); const isSilent = /const\s+isSilent\s*=\s*true/.test(loggerSource); console.log(`[BUILD-CLI] isSilent = ${isSilent}`); const CLI_DIR = "src/cli"; const OUT_DIR = "dist-cli"; const BEAUTIFY_IDENTITY = /^\(globalThis as Record\)\.beautify\s*=\s*\(s:\s*string\):\s*string\s*=>\s*s;?\s*\n?/m; const SCRAMBLE_IDENTITY = /^\(globalThis as Record\)\.scramble\s*=\s*\(s:\s*string\):\s*string\s*=>\s*s;?\s*\n?/m; const SHEBANG = /^#!.*\n/; function stripCLIGlobals(code: string): string { return code .replace(SHEBANG, "") .replace(SCRAMBLE_IDENTITY, "") .replace(BEAUTIFY_IDENTITY, ""); } async function transformFile(filePath: string): Promise { const code = await fs.readFile(filePath, "utf-8"); console.log(`[TRANSFORM] Processing: ${filePath}`); const { code: envRewritten } = transformEnvAccess( code, "[TRANSFORM]", filePath, ); const { code: scrambled } = transformSource( envRewritten, scrambler, FN_NAME, "[TRANSFORM]", filePath, ); if (isSilent) { const { code: stripped } = stripLogCalls( scrambled, "[TRANSFORM]", filePath, ); return stripped; } return scrambled; } async function walkDir(dir: string): Promise { const files: string[] = []; const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { files.push(...(await walkDir(fullPath))); } else if (entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) { files.push(fullPath); } } return files; } async function buildCLI() { const tempDir = "./.bun-temp-cli"; await fs.rm(tempDir, { recursive: true, force: true }); await fs.mkdir(tempDir, { recursive: true }); console.log("[BUILD-CLI] Copying and transforming source files..."); const files = await walkDir("./src"); console.log(`[BUILD-CLI] Processing ${files.length} TypeScript files`); for (const file of files) { let transformed = await transformFile(file); // Strip identity scramble/beautify from CLI entrypoints — the built // bundle uses runtimeDecoder instead. if (file.startsWith(CLI_DIR + "/")) { transformed = stripCLIGlobals(transformed); } const tempFile = path.join(tempDir, path.relative("./src", file)); await fs.mkdir(path.dirname(tempFile), { recursive: true }); await fs.writeFile(tempFile, transformed, "utf-8"); } const tempDecoderPath = path.join( tempDir, path.relative("./src", path.resolve(RUNTIME_DECODER_PATH)), ); const rewrittenDecoder = await rewriteRuntimeDecoder( RUNTIME_DECODER_PATH, PASSPHRASE, SALT, FN_NAME, ); await fs.mkdir(path.dirname(tempDecoderPath), { recursive: true }); await fs.writeFile(tempDecoderPath, rewrittenDecoder, "utf-8"); console.log( `[BUILD-CLI] Injected build passphrase into ${path.relative(tempDir, tempDecoderPath)}`, ); const cliTempDir = path.join(tempDir, "cli"); const cliEntries = (await fs.readdir(cliTempDir)) .filter((f) => f.endsWith(".ts") && f !== "common.ts") .map((f) => path.join(cliTempDir, f)); console.log(`[BUILD-CLI] Found ${cliEntries.length} CLI entrypoints`); // Prepend runtimeDecoder import (relative from src/cli/ → src/utils/) for (const ep of cliEntries) { const epCode = await fs.readFile(ep, "utf-8"); await fs.writeFile( ep, `import "../utils/runtimeDecoder";\n${epCode}`, "utf-8", ); } const allEntrypoints = [...cliEntries]; console.log( `[BUILD-CLI] Building ${allEntrypoints.length} entrypoints...`, ); await Bun.build({ entrypoints: allEntrypoints, outdir: OUT_DIR, root: tempDir, naming: { entry: "[dir]/[name].[ext]", }, target: "bun", minify: true, }); console.log("[BUILD-CLI] Cleaning up temp directory..."); await fs.rm(tempDir, { recursive: true, force: true }); console.log("[BUILD-CLI] ✓ Build complete!"); console.log("[BUILD-CLI]"); for (const ep of cliEntries) { const name = path.basename(ep, ".ts"); console.log(`[BUILD-CLI] dist-cli/cli/${name}.js`); } } buildCLI().catch(console.error);