From ef5f5dd1de5fe77140173819f7c6164e20b709c1 Mon Sep 17 00:00:00 2001 From: David Zhang Date: Sat, 2 May 2026 14:55:11 +0000 Subject: [PATCH] fix: replace import.meta.dir + ship FUSE/NFS sources, so `crm mount` works under node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `mount` crashes on a fresh install with `ERR_INVALID_ARG_TYPE` whenever ~/.crm/bin/crm-fuse (or crm-nfs) doesn't already exist: TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received undefined at join (node:path:1269:7) at mountLinux (.../dist/cli.js:3414:21) Two compounding causes: 1. `import.meta.dir` is a Bun-only extension; under the published bin's `#!/usr/bin/env node` shebang it's `undefined`, so `join(import.meta.dir, '..', 'fuse-helper.c')` throws before it can do anything useful. 2. `package.json#files` is `["dist", "README.md", "LICENSE"]`, so even with a working path resolver the FUSE/NFS sources aren't shipped to npm — the auto-compile step has nothing to compile. Fix: - Replace `import.meta.dir` with `dirname(fileURLToPath(import.meta.url))`, which works under both Bun and Node. - Add a `resolveAsset(name)` helper that looks for shipped sources in `BUNDLE_DIR/` (bundle mode) and `BUNDLE_DIR/../` (source mode), and update `mountLinux` and `mountDarwin` to use it. - Have the build copy `src/fuse-helper.c` and `src/nfs-server/` into `dist/` so they sit next to the bundled `cli.js` and ship via the existing `"files": ["dist"]` entry — no `package.json#files` change needed. Verified end-to-end on a fresh sandbox (no pre-existing helper): `node dist/cli.js mount /tmp/x` now compiles `~/.crm/bin/crm-fuse` and mounts successfully. --- package.json | 2 +- src/commands/fuse.ts | 36 +++++++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 1b1e3be..1d45aef 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "LICENSE" ], "scripts": { - "build": "bun build src/cli.ts --outdir dist --target node --format esm --define __PKG_VERSION__=\"'$(node -p \"require('./package.json').version\")'\" --external @libsql/client --external drizzle-orm --external commander --external libphonenumber-js --external normalize-url --external toml --external ulid", + "build": "bun build src/cli.ts --outdir dist --target node --format esm --define __PKG_VERSION__=\"'$(node -p \"require('./package.json').version\")'\" --external @libsql/client --external drizzle-orm --external commander --external libphonenumber-js --external normalize-url --external toml --external ulid && cp src/fuse-helper.c dist/fuse-helper.c && cp -r src/nfs-server dist/nfs-server", "crm": "bun run src/cli.ts", "check-types": "tsc --noEmit", "test": "bun test --timeout 30000", diff --git a/src/commands/fuse.ts b/src/commands/fuse.ts index 114c158..b111696 100644 --- a/src/commands/fuse.ts +++ b/src/commands/fuse.ts @@ -8,7 +8,29 @@ import { writeFileSync, } from 'node:fs' import { homedir, tmpdir } from 'node:os' -import { join } from 'node:path' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +// Directory holding the compiled bundle (or the source file in dev). Works +// under both Node and Bun, unlike `import.meta.dir` which is Bun-only and +// resolves to `undefined` under Node. +const BUNDLE_DIR = dirname(fileURLToPath(import.meta.url)) + +// Resolve an asset shipped with the package (fuse-helper.c, nfs-server/). +// In bundle mode the build copies these into `dist/`, so they sit next to +// cli.js. In source mode (running `bun run src/cli.ts`) BUNDLE_DIR is +// `src/commands` and the assets live one level up, in `src/`. +function resolveAsset(name: string): string | null { + for (const candidate of [ + join(BUNDLE_DIR, name), + join(BUNDLE_DIR, '..', name), + ]) { + if (existsSync(candidate)) { + return candidate + } + } + return null +} import type { Command } from 'commander' @@ -72,9 +94,9 @@ async function mountDarwin( if (!existsSync(nfsHelperPath)) { // Auto-compile the Rust NFS server - const nfsSrcDir = join(import.meta.dir, '..', 'nfs-server') - if (!existsSync(join(nfsSrcDir, 'Cargo.toml'))) { - die(`Error: NFS server source not found at ${nfsSrcDir}`) + const nfsSrcDir = resolveAsset('nfs-server') + if (!(nfsSrcDir && existsSync(join(nfsSrcDir, 'Cargo.toml')))) { + die('Error: NFS server source not found in package.') } ensureDir(join(homedir(), '.crm', 'bin')) const cargoPath = @@ -244,10 +266,10 @@ async function mountLinux( const helperPath = join(homedir(), '.crm', 'bin', 'crm-fuse') if (!existsSync(helperPath)) { - const srcPath = join(import.meta.dir, '..', 'fuse-helper.c') - if (!existsSync(srcPath)) { + const srcPath = resolveAsset('fuse-helper.c') + if (!srcPath) { die( - 'Error: FUSE helper not found. Install FUSE dependencies and rebuild, or use `crm export-fs` instead.', + 'Error: FUSE helper source not found in package. Reinstall crm.cli or use `crm export-fs` instead.', ) } ensureDir(join(homedir(), '.crm', 'bin'))