UNPKG

@anthropic-ai/claude-code

Version:

Use Claude, Anthropic's AI assistant, right from your terminal. Claude can understand your codebase, edit files, run terminal commands, and handle entire workflows for you.

195 lines (182 loc) 6.31 kB
#!/usr/bin/env node // Postinstall for the claude wrapper package (name in ./package.json). // // Detects the platform, finds the matching native binary from optionalDependencies, // and copies it over the bin/claude.exe placeholder. After this runs, `claude` execs // the native binary directly — no Node.js process stays resident. // // If the native package isn't present (--omit=optional), prints instructions and // leaves the placeholder stub in place. cli-wrapper.cjs (same directory) can be // invoked manually as a fallback that keeps working via require.resolve + spawn. // // Platform detection + PLATFORMS map is duplicated in cli-wrapper.cjs — keep in sync. const { spawnSync } = require('child_process') const { copyFileSync, linkSync, unlinkSync, chmodSync, readFileSync, writeFileSync, statSync, } = require('fs') const { arch } = require('os') const path = require('path') // Replaced at build time via sed. Keep the literals below as markers. const PACKAGE_PREFIX = '@anthropic-ai/claude-code' const BINARY_NAME = 'claude' const WRAPPER_NAME = require('./package.json').name const PLATFORMS = { 'darwin-arm64': { pkg: PACKAGE_PREFIX + '-darwin-arm64', bin: BINARY_NAME }, 'darwin-x64': { pkg: PACKAGE_PREFIX + '-darwin-x64', bin: BINARY_NAME }, 'linux-x64': { pkg: PACKAGE_PREFIX + '-linux-x64', bin: BINARY_NAME }, 'linux-arm64': { pkg: PACKAGE_PREFIX + '-linux-arm64', bin: BINARY_NAME }, 'linux-x64-musl': { pkg: PACKAGE_PREFIX + '-linux-x64-musl', bin: BINARY_NAME, }, 'linux-arm64-musl': { pkg: PACKAGE_PREFIX + '-linux-arm64-musl', bin: BINARY_NAME, }, 'win32-x64': { pkg: PACKAGE_PREFIX + '-win32-x64', bin: BINARY_NAME + '.exe', }, 'win32-arm64': { pkg: PACKAGE_PREFIX + '-win32-arm64', bin: BINARY_NAME + '.exe', }, } function detectMusl() { if (process.platform !== 'linux') { return false } // process.report is available in Node ≥12; glibcVersionRuntime is absent on musl. // Faster than spawning `ldd` and avoids the ENOENT→musl false positive when ldd // isn't on PATH (minimal containers). const report = typeof process.report?.getReport === 'function' ? process.report.getReport() : null return report != null && report.header?.glibcVersionRuntime === undefined } function getPlatformKey() { const platform = process.platform let cpu = arch() if (platform === 'linux') { return 'linux-' + cpu + (detectMusl() ? '-musl' : '') } // Rosetta 2: an x64 Node on Apple Silicon reports arch()==='x64'. Prefer the // native arm64 binary — the x64 build needs AVX, which Rosetta doesn't emulate. if (platform === 'darwin' && cpu === 'x64') { const r = spawnSync('sysctl', ['-n', 'sysctl.proc_translated'], { encoding: 'utf8', }) if (r.stdout?.trim() === '1') { cpu = 'arm64' } } return platform + '-' + cpu } function placeBinary(src, dest) { // Try hardlink first (instant, zero extra disk for a ~500MB binary; src and // dest are both under node_modules/ so same-filesystem is the common case). // We attempt the link BEFORE touching dest — if src is missing (partial // extraction) the first linkSync throws ENOENT and the fallback stub stays. try { linkSync(src, dest) } catch (err) { if (err.code === 'EEXIST') { // Read the stub before unlinking so we can restore it if both link and // copy fail (ENOSPC, NFS error mid-500MB-copy). Only read if dest is // stub-sized — on re-install dest is the ~500MB binary. const stub = statSync(dest).size < 4096 ? readFileSync(dest) : null unlinkSync(dest) try { linkSync(src, dest) } catch { try { copyFileSync(src, dest) } catch (copyErr) { if (stub) { try { writeFileSync(dest, stub, { mode: 0o755 }) } catch { // Do nothing } } throw copyErr } } } else if (err.code === 'EXDEV' || err.code === 'EPERM') { // Cross-device or no-link-perms — copyFileSync overwrites existing dest. copyFileSync(src, dest) } else { throw err } } if (process.platform !== 'win32') { chmodSync(dest, 0o755) } } function main() { const platformKey = getPlatformKey() const info = PLATFORMS[platformKey] if (!info) { console.error( `[${WRAPPER_NAME} postinstall] Unsupported platform: ${process.platform} ${arch()}`, ) console.error(` Supported: ${Object.keys(PLATFORMS).join(', ')}`) return } let src try { const pkgDir = path.dirname(require.resolve(info.pkg + '/package.json')) src = path.join(pkgDir, info.bin) } catch { console.error( `[${WRAPPER_NAME} postinstall] Native package "${info.pkg}" not found.`, ) if (platformKey === 'darwin-arm64' && arch() === 'x64') { console.error( ' You are running x64 Node under Rosetta 2 on Apple Silicon. npm only', ) console.error( ' installed the darwin-x64 binary, which requires AVX (not emulated by', ) console.error( ' Rosetta). Install arm64 Node and reinstall — e.g. via nvm:', ) console.error( ' arch -arm64 zsh -c "nvm install --lts && npm i -g ' + WRAPPER_NAME + '"', ) return } console.error( ' This happens with --omit=optional or when the download failed.', ) console.error( ' The `claude` command will print instructions when invoked.', ) console.error(' Fallback: node ' + path.join(__dirname, 'cli-wrapper.cjs')) return } // Always write to bin/claude.exe — the package.json bin field points here. // The .exe extension + no-shebang stub makes npm's cmd-shim (generated at // install time, before postinstall) emit a direct exec on Windows; Unix // ignores the extension. Same pattern as Bun's npm package. const dest = path.join(__dirname, 'bin', 'claude.exe') try { placeBinary(src, dest) } catch (err) { console.error( `[${WRAPPER_NAME} postinstall] Failed to place binary: ${err.message}`, ) console.error(' Fallback: node ' + path.join(__dirname, 'cli-wrapper.cjs')) process.exitCode = 1 } } main()