From ff7b40ccb9a5cbcca13a802863d7f23ffb0df8f7 Mon Sep 17 00:00:00 2001 From: Rinse Date: Sat, 13 Jun 2026 03:57:52 +0000 Subject: [PATCH 1/2] perf(cli): enable V8 compile cache in bin/run (#90) Call module.enableCompileCache() before the @oclif/core import so the CLI's own dist/ graph, oclif and the rest of the dependency closure get bytecode- cached across invocations. Optional-chained so it is a no-op on Node < 22.8 and never throws (failures are reported via its return value). Honors NODE_COMPILE_CACHE and NODE_DISABLE_COMPILE_CACHE=1. Recovers ~0.5-1s of per-command startup on slow hosts on second-and-later runs; pkc-js already self-caches its own graph, so this covers the part it does not (the CLI's own files + oclif + other deps). Adds test/cli/compile-cache.test.ts: spawns the built bin/run --version with an isolated TMPDIR and asserts the default cache dir is populated from bin/run itself (and that NODE_DISABLE_COMPILE_CACHE=1 writes nothing). --- bin/run | 8 ++++ test/cli/compile-cache.test.ts | 78 ++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 test/cli/compile-cache.test.ts diff --git a/bin/run b/bin/run index c1816b9..14140ac 100755 --- a/bin/run +++ b/bin/run @@ -1,5 +1,13 @@ #!/usr/bin/env node +// Enable the V8 compile cache before any imports so the CLI's own dist/, oclif and all +// deps get bytecode-cached across invocations. No-op on Node < 22.8, and on any failure +// it reports via its return value rather than throwing. Cache dir defaults to +// os.tmpdir()/node-compile-cache; override with NODE_COMPILE_CACHE, disable with +// NODE_DISABLE_COMPILE_CACHE=1. +const { enableCompileCache } = await import("node:module"); +enableCompileCache?.(); + // Save and strip DEBUG before any imports so the debug module // doesn't auto-enable stderr output. The daemon command will // re-enable namespaces programmatically and redirect to the log file. diff --git a/test/cli/compile-cache.test.ts b/test/cli/compile-cache.test.ts new file mode 100644 index 0000000..12518ac --- /dev/null +++ b/test/cli/compile-cache.test.ts @@ -0,0 +1,78 @@ +import { spawn } from "node:child_process"; +import { describe, it, expect } from "vitest"; +import { directory as randomDirectory } from "tempy"; +import * as nodeModule from "node:module"; +import fs from "node:fs"; +import path from "node:path"; + +// bin/run calls module.enableCompileCache() before importing anything, so the CLI's own +// dist/, @oclif/core and the rest of the dependency closure get bytecode-cached across +// invocations (issue #90). enableCompileCache exists from Node 22.8; skip below it. +const compileCacheSupported = typeof (nodeModule as { enableCompileCache?: unknown }).enableCompileCache === "function"; + +const runBitsocial = ( + args: string[], + env: NodeJS.ProcessEnv, + timeoutMs = 30_000 +): Promise<{ stdout: string; stderr: string; exitCode: number | null }> => { + return new Promise((resolve, reject) => { + const proc = spawn("node", ["./bin/run", ...args], { stdio: ["pipe", "pipe", "pipe"], env }); + let stdout = ""; + let stderr = ""; + proc.stdout.on("data", (d: Buffer) => (stdout += d.toString())); + proc.stderr.on("data", (d: Buffer) => (stderr += d.toString())); + const timer = setTimeout(() => { + proc.kill("SIGKILL"); + reject(new Error(`Timed out after ${timeoutMs}ms\nstdout: ${stdout}\nstderr: ${stderr}`)); + }, timeoutMs); + proc.on("close", (exitCode) => { + clearTimeout(timer); + resolve({ stdout, stderr, exitCode }); + }); + }); +}; + +describe.skipIf(!compileCacheSupported)("bin/run enables the V8 compile cache (issue #90)", () => { + it("populates the default compile-cache dir on a plain --version run", { timeout: 40_000 }, async () => { + const tmpDir = randomDirectory(); + // enableCompileCache() with no dir and no NODE_COMPILE_CACHE writes to + // os.tmpdir()/node-compile-cache. Redirect os.tmpdir() to an isolated dir + // (TMPDIR on POSIX, TEMP/TMP on Windows) so the run can't touch the real cache. + const cacheDir = path.join(tmpDir, "node-compile-cache"); + + // Strip any inherited NODE_COMPILE_CACHE / disable flag so the ONLY thing that can + // populate cacheDir is bin/run's own enableCompileCache() call, not the env var. + const env: NodeJS.ProcessEnv = { ...process.env, TMPDIR: tmpDir, TMP: tmpDir, TEMP: tmpDir }; + delete env.NODE_COMPILE_CACHE; + delete env.NODE_DISABLE_COMPILE_CACHE; + + expect(fs.existsSync(cacheDir), "compile cache dir should not exist before the run").toBe(false); + + const result = await runBitsocial(["--version"], env); + expect(result.exitCode, `stderr: ${result.stderr}\nstdout: ${result.stdout}`).toBe(0); + expect(result.stdout).toContain("bitsocial-cli"); + + expect(fs.existsSync(cacheDir), `expected compile cache to be created at ${cacheDir}`).toBe(true); + const entries = fs.readdirSync(cacheDir); + expect(entries.length, `compile cache dir was empty: ${cacheDir}`).toBeGreaterThan(0); + }); + + it("respects NODE_DISABLE_COMPILE_CACHE=1 (no cache written)", { timeout: 40_000 }, async () => { + const tmpDir = randomDirectory(); + const cacheDir = path.join(tmpDir, "node-compile-cache"); + + const env: NodeJS.ProcessEnv = { + ...process.env, + TMPDIR: tmpDir, + TMP: tmpDir, + TEMP: tmpDir, + NODE_DISABLE_COMPILE_CACHE: "1" + }; + delete env.NODE_COMPILE_CACHE; + + const result = await runBitsocial(["--version"], env); + expect(result.exitCode, `stderr: ${result.stderr}\nstdout: ${result.stdout}`).toBe(0); + // With the cache disabled the dir is never created (CLI still works normally). + expect(fs.existsSync(cacheDir), `compile cache should be disabled, found ${cacheDir}`).toBe(false); + }); +}); From c04145f701f8cb04eba993f16363a2fb14c92996 Mon Sep 17 00:00:00 2001 From: Rinse Date: Sat, 13 Jun 2026 03:57:53 +0000 Subject: [PATCH 2/2] docs(community/list): note -q is much faster than the default (#76) The default (non-quiet) list spins up every community over RPC to fill the 'started' column, which is 2-30s slower than -q on a busy daemon. Document that in the -q flag summary (and the generated README). --- README.md | 2 +- src/cli/commands/community/list.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 61120b0..3f0f8d1 100644 --- a/README.md +++ b/README.md @@ -793,7 +793,7 @@ USAGE $ bitsocial community list --pkcRpcUrl [-q] FLAGS - -q, --quiet Only display community addresses + -q, --quiet Only display community addresses (much faster: skips the per-community 'started' lookup) --pkcRpcUrl= (required) [default: ws://localhost:9138/] URL to PKC RPC DESCRIPTION diff --git a/src/cli/commands/community/list.ts b/src/cli/commands/community/list.ts index 366ad98..6afa7d3 100644 --- a/src/cli/commands/community/list.ts +++ b/src/cli/commands/community/list.ts @@ -10,7 +10,10 @@ export default class List extends BaseCommand { static override examples = ["bitsocial community list -q", "bitsocial community list"]; static override flags = { - quiet: Flags.boolean({ char: "q", summary: "Only display community addresses" }) + quiet: Flags.boolean({ + char: "q", + summary: "Only display community addresses (much faster: skips the per-community 'started' lookup)" + }) }; async run(): Promise {