diff --git a/npm/ast-grep b/npm/ast-grep old mode 100644 new mode 100755 index bc32727d..b34841d6 --- a/npm/ast-grep +++ b/npm/ast-grep @@ -1,3 +1,149 @@ -echo "ast-grep shim file was executed. This file exists so that npm tries to create the binary. It should be replaced after the 'postinstall' script is executed." -echo "Did your package manager execute the post-install script? (N.B. not all of them do)" -exit 1 +#!/usr/bin/env node + +const { spawn } = require("node:child_process"); +const fs = require("fs"); +const path = require("path"); + +const binaryName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep"; + +function detectPackageName() { + const { platform, arch } = process; + + switch (platform) { + case "darwin": + if (arch === "arm64") return "@ast-grep/cli-darwin-arm64"; + if (arch === "x64") return "@ast-grep/cli-darwin-x64"; + break; + case "linux": { + const { MUSL, familySync } = require("detect-libc"); + if (familySync() === MUSL) return null; + if (arch === "arm64") return "@ast-grep/cli-linux-arm64-gnu"; + if (arch === "x64") return "@ast-grep/cli-linux-x64-gnu"; + break; + } + case "win32": + if (arch === "arm64") return "@ast-grep/cli-win32-arm64-msvc"; + if (arch === "ia32") return "@ast-grep/cli-win32-ia32-msvc"; + if (arch === "x64") return "@ast-grep/cli-win32-x64-msvc"; + break; + default: + break; + } + + return null; +} + +function failToLocateBinary(pkgName, originalError) { + const messages = ["Failed to locate the ast-grep native binary."]; + + if (pkgName) { + messages.push(`Expected package: ${pkgName}`); + } else { + messages.push( + "This platform does not have a published prebuilt ast-grep binary.", + ); + } + + if (originalError) { + messages.push(`Underlying error: ${originalError.message}`); + } + + messages.push( + "If you are installing from npm, ensure optional dependencies are enabled (e.g. do not use --no-optional).", + ); + messages.push( + "For local development, run `cargo build --release` before invoking this script.", + ); + + console.error(messages.join("\n")); + process.exit(1); +} + +function resolveBinaryPath() { + const pkgName = detectPackageName(); + let moduleError = null; + + if (pkgName) { + try { + const pkgDir = path.dirname( + require.resolve(`${pkgName}/package.json`, { paths: [__dirname] }), + ); + const candidate = path.join(pkgDir, binaryName); + if (fs.existsSync(candidate)) { + return { binaryPath: candidate, pkgName }; + } + } catch (err) { + moduleError = err; + } + } + + const releaseCandidate = path.join( + __dirname, + "..", + "target", + "release", + binaryName, + ); + if (fs.existsSync(releaseCandidate)) { + return { binaryPath: releaseCandidate, pkgName: null }; + } + + const debugCandidate = path.join( + __dirname, + "..", + "target", + "debug", + binaryName, + ); + if (fs.existsSync(debugCandidate)) { + return { binaryPath: debugCandidate, pkgName: null }; + } + + failToLocateBinary(pkgName, moduleError); +} + +function attachChildLifecycle(child) { + child.on("error", (error) => { + console.error("Failed to execute ast-grep binary."); + console.error(error); + process.exit(1); + }); + + const signals = ["SIGINT", "SIGTERM", "SIGHUP"]; + const forwarders = signals.map((signal) => { + const handler = () => { + if (child.killed) { + return; + } + try { + child.kill(signal); + } catch (_) { + // Ignore + } + }; + process.on(signal, handler); + return { signal, handler }; + }); + + child.on("exit", (code, signal) => { + forwarders.forEach(({ signal, handler }) => { + process.removeListener(signal, handler); + }); + + if (signal) { + process.kill(process.pid, signal); + } else { + process.exit(code == null ? 1 : code); + } + }); +} + +function main() { + const { binaryPath } = resolveBinaryPath(); + const child = spawn(binaryPath, process.argv.slice(2), { + stdio: "inherit", + }); + attachChildLifecycle(child); +} + +main(); diff --git a/npm/package.json b/npm/package.json index ecc4fb3c..dde705a7 100644 --- a/npm/package.json +++ b/npm/package.json @@ -20,15 +20,11 @@ ], "files": [ "sg", - "ast-grep", - "postinstall.js" + "ast-grep" ], "dependencies": { "detect-libc": "2.1.2" }, - "scripts": { - "postinstall": "node postinstall.js" - }, "optionalDependencies": { "@ast-grep/cli-win32-arm64-msvc": "0.42.1", "@ast-grep/cli-win32-ia32-msvc": "0.42.1", diff --git a/npm/postinstall.js b/npm/postinstall.js deleted file mode 100644 index 884420a4..00000000 --- a/npm/postinstall.js +++ /dev/null @@ -1,49 +0,0 @@ -let fs = require('fs'); -let path = require('path'); - -let parts = [process.platform, process.arch]; -if (process.platform === 'linux') { - const {MUSL, familySync} = require('detect-libc'); - if (familySync() === MUSL) { - parts.push('musl'); - } else if (process.arch === 'arm') { - parts.push('gnueabihf'); - } else { - parts.push('gnu'); - } -} else if (process.platform === 'win32') { - parts.push('msvc'); -} - -let binary = process.platform === 'win32' ? 'ast-grep.exe' : 'ast-grep'; -let alternative = process.platform === 'win32' ? 'sg.exe' : 'sg'; - -let pkgPath; -try { - pkgPath = path.dirname(require.resolve(`@ast-grep/cli-${parts.join('-')}/package.json`)); -} catch (err) { - pkgPath = path.join(__dirname, '..', 'target', 'release'); - if (!fs.existsSync(path.join(pkgPath, binary))) { - pkgPath = path.join(__dirname, '..', 'target', 'debug'); - } -} - -try { - fs.linkSync(path.join(pkgPath, binary), path.join(__dirname, binary)); - fs.linkSync(path.join(pkgPath, binary), path.join(__dirname, alternative)); -} catch (err) { - try { - fs.copyFileSync(path.join(pkgPath, binary), path.join(__dirname, binary)); - fs.copyFileSync(path.join(pkgPath, binary), path.join(__dirname, alternative)); - } catch (err) { - console.error('Failed to move @ast-grep/cli binary into place.'); - process.exit(1); - } -} - -if (process.platform === 'win32') { - try { - fs.unlinkSync(path.join(__dirname, 'sg')); - fs.unlinkSync(path.join(__dirname, 'ast-grep')); - } catch (err) {} -} diff --git a/npm/sg b/npm/sg old mode 100644 new mode 100755 index db345574..900a23c2 --- a/npm/sg +++ b/npm/sg @@ -1,2 +1,2 @@ -This file is required so that dumb npm creates the sg binary. -N.B. pnpm does not have this issue. +#!/usr/bin/env node +require("./ast-grep");