From b57842158bd0060cefbdda9a86899ebfef8cfaf7 Mon Sep 17 00:00:00 2001 From: MickaelRoger Date: Thu, 23 Jul 2026 10:06:11 +0200 Subject: [PATCH] fix(npm): preserve executable launcher permissions --- .github/workflows/pipeline.yml | 5 +++- bin/opencode-manager | 0 package.json | 2 +- scripts/verify-package.js | 36 ++++++++++++++++++++----- scripts/verify-package.test.js | 48 ++++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 9 deletions(-) mode change 100644 => 100755 bin/opencode-manager create mode 100644 scripts/verify-package.test.js diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index c2ecc86..95405f4 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -63,8 +63,11 @@ jobs: with: go-version-file: go.mod cache: true + - uses: actions/setup-node@v4 + with: + node-version: 24 - name: Test - run: go test ./... + run: npm test build: name: Build ${{ matrix.goos }}/${{ matrix.goarch }} diff --git a/bin/opencode-manager b/bin/opencode-manager old mode 100644 new mode 100755 diff --git a/package.json b/package.json index 7824978..1a4b28e 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ }, "scripts": { "build": "go build -o dist/opencode-manager-$(node -p \"process.platform\")-$(node -p \"process.arch\") ./cmd/opencode-manager", - "test": "go test ./...", + "test": "go test ./... && node --test scripts/verify-package.test.js", "prepack": "node scripts/verify-package.js", "postinstall": "node scripts/postinstall.js" }, diff --git a/scripts/verify-package.js b/scripts/verify-package.js index f75da5b..87cc4aa 100644 --- a/scripts/verify-package.js +++ b/scripts/verify-package.js @@ -1,7 +1,6 @@ const fs = require("node:fs"); const path = require("node:path"); -const packageRoot = path.resolve(__dirname, ".."); const binaries = [ "opencode-manager-linux-x64", "opencode-manager-linux-arm64", @@ -9,12 +8,35 @@ const binaries = [ "opencode-manager-darwin-arm64", ]; -const missing = binaries.filter((binary) => !fs.existsSync(path.join(packageRoot, "dist", binary))); +const launchers = ["bin/ocm", "bin/opencode-manager"]; +function packageErrors(packageRoot) { + const errors = []; + const missing = binaries.filter((binary) => !fs.existsSync(path.join(packageRoot, "dist", binary))); + if (missing.length > 0) { + errors.push("Cannot pack opencode-manager; missing prebuilt binaries:", ...missing.map((binary) => ` dist/${binary}`)); + } + + const invalidLaunchers = launchers.filter((launcher) => { + try { + const stat = fs.statSync(path.join(packageRoot, launcher)); + return !stat.isFile() || (stat.mode & 0o111) === 0; + } catch { + return true; + } + }); + if (invalidLaunchers.length > 0) { + errors.push("Cannot pack opencode-manager; launchers must be executable files:", ...invalidLaunchers.map((launcher) => ` ${launcher}`)); + } -if (missing.length > 0) { - console.error("Cannot pack opencode-manager; missing prebuilt binaries:"); - for (const binary of missing) { - console.error(` dist/${binary}`); + return errors; +} + +if (require.main === module) { + const errors = packageErrors(path.resolve(__dirname, "..")); + if (errors.length > 0) { + console.error(errors.join("\n")); + process.exit(1); } - process.exit(1); } + +module.exports = { packageErrors }; diff --git a/scripts/verify-package.test.js b/scripts/verify-package.test.js new file mode 100644 index 0000000..eb4dc34 --- /dev/null +++ b/scripts/verify-package.test.js @@ -0,0 +1,48 @@ +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const test = require("node:test"); + +const { packageErrors } = require("./verify-package"); + +const binaries = [ + "opencode-manager-linux-x64", + "opencode-manager-linux-arm64", + "opencode-manager-darwin-x64", + "opencode-manager-darwin-arm64", +]; + +function packageFixture(t) { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-manager-package-")); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + fs.mkdirSync(path.join(root, "bin")); + fs.mkdirSync(path.join(root, "dist")); + for (const binary of binaries) { + fs.writeFileSync(path.join(root, "dist", binary), ""); + } + for (const launcher of ["ocm", "opencode-manager"]) { + fs.writeFileSync(path.join(root, "bin", launcher), "#!/usr/bin/env node\n", { mode: 0o755 }); + } + return root; +} + +test("packageErrors accepts executable launcher files", (t) => { + assert.deepEqual(packageErrors(packageFixture(t)), []); +}); + +test("packageErrors rejects a non-executable launcher", (t) => { + const root = packageFixture(t); + fs.chmodSync(path.join(root, "bin", "opencode-manager"), 0o644); + + assert.match(packageErrors(root).join("\n"), /bin\/opencode-manager/); +}); + +test("packageErrors rejects a launcher directory", (t) => { + const root = packageFixture(t); + const launcher = path.join(root, "bin", "ocm"); + fs.unlinkSync(launcher); + fs.mkdirSync(launcher); + + assert.match(packageErrors(root).join("\n"), /bin\/ocm/); +});