From 47d08329188370c4d7ec193eacb70e04b03997b8 Mon Sep 17 00:00:00 2001 From: Pawan Kumar Date: Wed, 29 Jul 2026 20:08:21 +0530 Subject: [PATCH] Fail the build when the packaged binding does not match the package version postbuild copies nodejs/lib/binding/node_setapp_binding.node into nodejs/dist/lib/binding/, and nodejs/dist is what `main` loads. That source directory is gitignored, so on a machine where node-gyp has not run for the current version it holds a binding left over from an earlier one - and the copy succeeds silently. The binding statically links the Swift library, so the version it was built against is the version consumers actually run, whatever package.json says. Nothing downstream can detect or correct it. This adds a check to the end of postbuild comparing the copied binary's embedded __scv__ stamp with the package version, and fails the build on a mismatch. scripts/ is outside the tsconfig include ("nodejs") and outside the published files list, so it is neither compiled nor shipped. --- package.json | 2 +- scripts/check-binding-version.js | 59 ++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 scripts/check-binding-version.js diff --git a/package.json b/package.json index bbe31fb..9e046ad 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "prebuild": "rimraf ./nodejs/dist", "build": "tsc", - "postbuild": "mkdir ./nodejs/dist/lib/binding && cp ./nodejs/lib/binding/node_setapp_binding.node ./nodejs/dist/lib/binding/node_setapp_binding.node", + "postbuild": "mkdir ./nodejs/dist/lib/binding && cp ./nodejs/lib/binding/node_setapp_binding.node ./nodejs/dist/lib/binding/node_setapp_binding.node && node ./scripts/check-binding-version.js", "prepublishOnly": "npm run build" }, "dependencies": { diff --git a/scripts/check-binding-version.js b/scripts/check-binding-version.js new file mode 100644 index 0000000..0c2d4ae --- /dev/null +++ b/scripts/check-binding-version.js @@ -0,0 +1,59 @@ +#!/usr/bin/env node +/** + * Fail the build if the packaged N-API binding was compiled against a different + * Setapp library than this package version. + * + * `postbuild` copies `nodejs/lib/binding/node_setapp_binding.node` into + * `nodejs/dist/lib/binding/`, and `nodejs/dist` is what `main` loads. That + * source directory is gitignored, so on a machine where node-gyp has not run + * for the current version it holds a binding left over from an earlier one - + * and the copy succeeds silently. + * + * The binding statically links the Swift library, so the version it was built + * against is the version consumers actually run, whatever package.json says. + * Nothing downstream can detect or correct that. + * + * Every Mach-O the framework produces carries an embedded `__scv__` + * stamp. This compares that stamp with the package version. + */ +const fs = require("fs"); +const path = require("path"); + +const root = path.join(__dirname, ".."); +const pkg = require(path.join(root, "package.json")); +const binding = path.join( + root, "nodejs", "dist", "lib", "binding", "node_setapp_binding.node", +); + +if (!fs.existsSync(binding)) { + console.error(`[check-binding-version] no binding at ${path.relative(root, binding)}`); + console.error(" postbuild should have copied it from nodejs/lib/binding."); + process.exit(1); +} + +// latin1 so bytes survive decoding; the stamp is plain ASCII inside a binary. +const stamp = fs.readFileSync(binding, "latin1").match(/__scv__(\d+\.\d+\.\d+)/); + +if (!stamp) { + console.error(`[check-binding-version] no __scv__ version stamp found in the binding.`); + process.exit(1); +} + +if (stamp[1] !== pkg.version) { + console.error( + `[check-binding-version] the binding is built against Setapp ${stamp[1]}, ` + + `but this package is ${pkg.version}.\n` + + `\n` + + ` ${path.relative(root, binding)}\n` + + `\n` + + `Publishing this would ship a library ${stamp[1]} runtime as ${pkg.version}: the\n` + + `binding statically links the Swift library, so its version is what consumers run,\n` + + `and any API added since ${stamp[1]} would be missing with no way to detect it.\n` + + `\n` + + `Rebuild the native binding for this version, then run the build again:\n` + + ` npx node-gyp rebuild && npm run build\n`, + ); + process.exit(1); +} + +console.log(`[check-binding-version] binding matches package (Setapp ${pkg.version})`);