From 24db0151a4d7c4e3896e4fb82340bd8e152b889e Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Fri, 27 Feb 2026 14:05:08 +0000 Subject: [PATCH 01/32] Changes required by Gorelase version > 2 --- .github/workflows/release.yml | 1 + .gitignore | 2 + .goreleaser.yml | 67 +++++++++++++++++++------------ npm/README.md | 66 ++++++++++++++++++++++++++++++ npm/cli-darwin-arm64/.npmrc | 2 + npm/cli-darwin-arm64/README.md | 1 + npm/cli-darwin-arm64/package.json | 19 +++++++++ npm/cli-darwin-x64/.npmrc | 2 + npm/cli-darwin-x64/README.md | 1 + npm/cli-darwin-x64/package.json | 19 +++++++++ npm/cli-linux-arm/.npmrc | 2 + npm/cli-linux-arm/README.md | 1 + npm/cli-linux-arm/package.json | 19 +++++++++ npm/cli-linux-arm64/.npmrc | 2 + npm/cli-linux-arm64/README.md | 1 + npm/cli-linux-arm64/package.json | 19 +++++++++ npm/cli-linux-x64/.npmrc | 2 + npm/cli-linux-x64/README.md | 1 + npm/cli-linux-x64/package.json | 19 +++++++++ npm/cli-win32-arm64/.npmrc | 2 + npm/cli-win32-arm64/README.md | 1 + npm/cli-win32-arm64/package.json | 19 +++++++++ npm/cli-win32-x64/.npmrc | 2 + npm/cli-win32-x64/README.md | 1 + npm/cli-win32-x64/package.json | 19 +++++++++ npm/wrapper/.npmrc | 2 + npm/wrapper/install.js | 54 +++++++++++++++++++++++++ npm/wrapper/package.json | 45 +++++++++++++++++++++ scripts/npm-publish.sh | 42 +++++++++++++++++++ 29 files changed, 408 insertions(+), 25 deletions(-) create mode 100644 npm/README.md create mode 100644 npm/cli-darwin-arm64/.npmrc create mode 100644 npm/cli-darwin-arm64/README.md create mode 100644 npm/cli-darwin-arm64/package.json create mode 100644 npm/cli-darwin-x64/.npmrc create mode 100644 npm/cli-darwin-x64/README.md create mode 100644 npm/cli-darwin-x64/package.json create mode 100644 npm/cli-linux-arm/.npmrc create mode 100644 npm/cli-linux-arm/README.md create mode 100644 npm/cli-linux-arm/package.json create mode 100644 npm/cli-linux-arm64/.npmrc create mode 100644 npm/cli-linux-arm64/README.md create mode 100644 npm/cli-linux-arm64/package.json create mode 100644 npm/cli-linux-x64/.npmrc create mode 100644 npm/cli-linux-x64/README.md create mode 100644 npm/cli-linux-x64/package.json create mode 100644 npm/cli-win32-arm64/.npmrc create mode 100644 npm/cli-win32-arm64/README.md create mode 100644 npm/cli-win32-arm64/package.json create mode 100644 npm/cli-win32-x64/.npmrc create mode 100644 npm/cli-win32-x64/README.md create mode 100644 npm/cli-win32-x64/package.json create mode 100644 npm/wrapper/.npmrc create mode 100644 npm/wrapper/install.js create mode 100644 npm/wrapper/package.json create mode 100755 scripts/npm-publish.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5c2aba652..f36745dc5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -154,6 +154,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} FURY_TOKEN: ${{ secrets.FURY_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - uses: actions/upload-artifact@v7 with: diff --git a/.gitignore b/.gitignore index acc7c1a17..eb9181b1f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,8 @@ docs.kosli.com/resources/_gen/* docs.kosli.com/content/client_reference/kosli* docs.kosli.com/public/ docs.kosli.com/.netlify +npm/cli*/bin/* +npm/wrapper/bin/* *.tar.gz *~ /.idea diff --git a/.goreleaser.yml b/.goreleaser.yml index b212574cc..4b36b0d23 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -3,6 +3,7 @@ project_name: kosli before: hooks: - go mod tidy + - rm -rf npm/cli-*/bin builds: - id: kosli binary: kosli @@ -27,6 +28,19 @@ builds: - goos: windows goarch: arm main: ./cmd/kosli/ + hooks: + post: + - cmd: >- + bash -c ' + OS="{{ .Os }}"; + ARCH="{{ .Arch }}"; + [ "$OS" = "windows" ] && OS="win32"; + [ "$ARCH" = "amd64" ] && ARCH="x64"; + EXT=""; + [ "{{ .Os }}" = "windows" ] && EXT=".exe"; + mkdir -p npm/cli-${OS}-${ARCH}/bin && + cp "{{ .Path }}" npm/cli-${OS}-${ARCH}/bin/kosli${EXT} && + chmod +x npm/cli-${OS}-${ARCH}/bin/kosli${EXT}' archives: - @@ -42,39 +56,42 @@ archives: nfpms: - id: kosli - # You can change the file name of the package. - # - # Default:`{{ .PackageName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}` - file_name_template: >- - {{ .ProjectName }}_ - {{- title .Os }}_ - {{- if eq .Arch "amd64" }}x86_64 - {{- else if eq .Arch "386" }}i386 - {{- else }}{{ .Arch }}{{ end }} +# # You can change the file name of the package. +# # +# # Default:`{{ .PackageName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}` + +# # TODO Maybe enable the template again after we have a better understanding of how to use it. It seems to be working without it, but it would be nice to have more control over the file name. + +# # file_name_template: >- +# # {{ .ProjectName }}_ +# # {{- title .Os }}_ +# # {{- if eq .Arch "amd64" }}x86_64 +# # {{- else if eq .Arch "386" }}i386 +# # {{- else }}{{ .Arch }}{{ end }} ids: - kosli - vendor: Kosli Inc. - homepage: https://kosli.com/ - maintainer: Mike Long - description: CLI client for reporting compliance events to https://kosli.com - license: MIT +# vendor: Kosli Inc. +# homepage: https://kosli.com/ +# maintainer: Mike Long +# description: CLI client for reporting compliance events to https://kosli.com +# license: MIT - # Formats to be generated. - formats: - - deb - - rpm +# # Formats to be generated. +# formats: +# - deb +# - rpm - # Template to the path that the binaries should be installed. - # Defaults to `/usr/bin`. - bindir: /usr/bin +# # Template to the path that the binaries should be installed. +# # Defaults to `/usr/bin`. +# bindir: /usr/bin - # Section. - section: misc +# # Section. +# section: misc - # Priority. - priority: optional +# # Priority. +# priority: optional # Contents to add to the package. # GoReleaser will automatically add the binaries. diff --git a/npm/README.md b/npm/README.md new file mode 100644 index 000000000..2bad31b28 --- /dev/null +++ b/npm/README.md @@ -0,0 +1,66 @@ +# NPM Packaging + +This directory contains the npm package structure for distributing the Kosli CLI via npm, following the same pattern used by [esbuild](https://github.com/evanw/esbuild). + +## Structure + +``` +npm/ +├── wrapper/ # @jbrejner/cli — the package users install +│ ├── package.json # declares optionalDependencies for all platforms +│ ├── bin/kosli # JS shim that detects the platform and runs the binary +│ └── install.js # postinstall script that validates the binary +├── cli-linux-x64/ # @jbrejner/cli-linux-x64 +├── cli-linux-arm64/ # @jbrejner/cli-linux-arm64 +├── cli-darwin-x64/ # @jbrejner/cli-darwin-x64 +└── cli-darwin-arm64/ # @jbrejner/cli-darwin-arm64 + ├── package.json # declares os/cpu fields for platform filtering + └── bin/kosli # the native binary — see below +``` + +## How it works + +Users install a single package: + +```sh +npm install @jbrejner/cli +``` + +npm resolves the `optionalDependencies` declared in the wrapper's `package.json` and installs only the platform-specific package that matches the current OS and CPU architecture — all non-matching packages are silently skipped. The wrapper's `bin/kosli` JS shim then locates the binary inside the installed platform package and executes it. + +## The `bin/` directories are populated by goreleaser + +The platform package `bin/` directories are **not committed to git**. They are populated automatically during the release process by a post-build hook in [`.goreleaser.yml`](../.goreleaser.yml): + +```yaml +hooks: + post: + - cmd: bash -c 'ARCH="{{ .Arch }}"; [ "$ARCH" = "amd64" ] && ARCH="x64"; mkdir -p npm/cli-{{ .Os }}-${ARCH}/bin && cp "{{ .Path }}" npm/cli-{{ .Os }}-${ARCH}/bin/kosli && chmod +x npm/cli-{{ .Os }}-${ARCH}/bin/kosli' +``` + +This hook runs once per build target immediately after goreleaser compiles the binary. It maps goreleaser's architecture naming (`amd64` → `x64`, `arm64` → `arm64`) to the npm naming convention and copies the binary into the correct platform package directory. + +## Publishing + +Platform packages must be published before the wrapper, since the wrapper's `optionalDependencies` references them. After a goreleaser build has populated the `bin/` directories: + +```sh +# Publish platform packages first +(cd npm/cli-linux-x64 && npm publish) +(cd npm/cli-linux-arm64 && npm publish) +(cd npm/cli-darwin-x64 && npm publish) +(cd npm/cli-darwin-arm64 && npm publish) + +# Then publish the wrapper +(cd npm/wrapper && npm publish) +``` + +Or as a one-liner that publishes platform packages first, then the wrapper: + +```sh +find npm -name package.json ! -path "npm/wrapper/*" | while read f; do pushd "$(dirname "$f")" && npm publish; popd; done && pushd npm/wrapper && npm publish; popd +``` + +## Versioning + +All packages must share the same version number. When bumping the version, update it in all five `package.json` files — the four platform packages and the wrapper — as well as the `optionalDependencies` versions in `npm/wrapper/package.json`. diff --git a/npm/cli-darwin-arm64/.npmrc b/npm/cli-darwin-arm64/.npmrc new file mode 100644 index 000000000..983929573 --- /dev/null +++ b/npm/cli-darwin-arm64/.npmrc @@ -0,0 +1,2 @@ +@jbrejner:registry=https://npm.pkg.github.com +//npm.pkg.github.com/:_authToken=${MY_LOCAL_NPM_TOKEN} diff --git a/npm/cli-darwin-arm64/README.md b/npm/cli-darwin-arm64/README.md new file mode 100644 index 000000000..1e2832d6f --- /dev/null +++ b/npm/cli-darwin-arm64/README.md @@ -0,0 +1 @@ +This is the macOS ARM 64-bit binary for the Kosli CLI (Apple Silicon). See https://github.com/kosli-dev/cli for details. diff --git a/npm/cli-darwin-arm64/package.json b/npm/cli-darwin-arm64/package.json new file mode 100644 index 000000000..60fef5904 --- /dev/null +++ b/npm/cli-darwin-arm64/package.json @@ -0,0 +1,19 @@ +{ + "name": "@jbrejner/cli-darwin-arm64", + "version": "0.0.0", + "description": "macOS arm64 binary for @jbrejner/cli", + "license": "MIT", + "os": [ + "darwin" + ], + "cpu": [ + "arm64" + ], + "files": [ + "bin/" + ], + "publishConfig": { + "registry": "https://npm.pkg.github.com", + "access": "public" + } +} \ No newline at end of file diff --git a/npm/cli-darwin-x64/.npmrc b/npm/cli-darwin-x64/.npmrc new file mode 100644 index 000000000..983929573 --- /dev/null +++ b/npm/cli-darwin-x64/.npmrc @@ -0,0 +1,2 @@ +@jbrejner:registry=https://npm.pkg.github.com +//npm.pkg.github.com/:_authToken=${MY_LOCAL_NPM_TOKEN} diff --git a/npm/cli-darwin-x64/README.md b/npm/cli-darwin-x64/README.md new file mode 100644 index 000000000..50ec71e1b --- /dev/null +++ b/npm/cli-darwin-x64/README.md @@ -0,0 +1 @@ +This is the macOS 64-bit binary for the Kosli CLI. See https://github.com/kosli-dev/cli for details. diff --git a/npm/cli-darwin-x64/package.json b/npm/cli-darwin-x64/package.json new file mode 100644 index 000000000..a33355611 --- /dev/null +++ b/npm/cli-darwin-x64/package.json @@ -0,0 +1,19 @@ +{ + "name": "@jbrejner/cli-darwin-x64", + "version": "0.0.0", + "description": "macOS x64 binary for @jbrejner/cli", + "license": "MIT", + "os": [ + "darwin" + ], + "cpu": [ + "x64" + ], + "files": [ + "bin/" + ], + "publishConfig": { + "registry": "https://npm.pkg.github.com", + "access": "public" + } +} \ No newline at end of file diff --git a/npm/cli-linux-arm/.npmrc b/npm/cli-linux-arm/.npmrc new file mode 100644 index 000000000..983929573 --- /dev/null +++ b/npm/cli-linux-arm/.npmrc @@ -0,0 +1,2 @@ +@jbrejner:registry=https://npm.pkg.github.com +//npm.pkg.github.com/:_authToken=${MY_LOCAL_NPM_TOKEN} diff --git a/npm/cli-linux-arm/README.md b/npm/cli-linux-arm/README.md new file mode 100644 index 000000000..2bcadcc0b --- /dev/null +++ b/npm/cli-linux-arm/README.md @@ -0,0 +1 @@ +This is the Linux ARM 32-bit binary for the Kosli CLI. See https://github.com/kosli-dev/cli for details. diff --git a/npm/cli-linux-arm/package.json b/npm/cli-linux-arm/package.json new file mode 100644 index 000000000..d3e282ec2 --- /dev/null +++ b/npm/cli-linux-arm/package.json @@ -0,0 +1,19 @@ +{ + "name": "@jbrejner/cli-linux-arm", + "version": "0.0.0", + "description": "Linux arm binary for @jbrejner/cli", + "license": "MIT", + "os": [ + "linux" + ], + "cpu": [ + "arm" + ], + "files": [ + "bin/" + ], + "publishConfig": { + "registry": "https://npm.pkg.github.com", + "access": "public" + } +} diff --git a/npm/cli-linux-arm64/.npmrc b/npm/cli-linux-arm64/.npmrc new file mode 100644 index 000000000..983929573 --- /dev/null +++ b/npm/cli-linux-arm64/.npmrc @@ -0,0 +1,2 @@ +@jbrejner:registry=https://npm.pkg.github.com +//npm.pkg.github.com/:_authToken=${MY_LOCAL_NPM_TOKEN} diff --git a/npm/cli-linux-arm64/README.md b/npm/cli-linux-arm64/README.md new file mode 100644 index 000000000..cbe44264c --- /dev/null +++ b/npm/cli-linux-arm64/README.md @@ -0,0 +1 @@ +This is the Linux ARM 64-bit binary for the Kosli CLI. See https://github.com/kosli-dev/cli for details. diff --git a/npm/cli-linux-arm64/package.json b/npm/cli-linux-arm64/package.json new file mode 100644 index 000000000..960e33ebb --- /dev/null +++ b/npm/cli-linux-arm64/package.json @@ -0,0 +1,19 @@ +{ + "name": "@jbrejner/cli-linux-arm64", + "version": "0.0.0", + "description": "Linux arm64 binary for @jbrejner/cli", + "license": "MIT", + "os": [ + "linux" + ], + "cpu": [ + "arm64" + ], + "files": [ + "bin/" + ], + "publishConfig": { + "registry": "https://npm.pkg.github.com", + "access": "public" + } +} \ No newline at end of file diff --git a/npm/cli-linux-x64/.npmrc b/npm/cli-linux-x64/.npmrc new file mode 100644 index 000000000..983929573 --- /dev/null +++ b/npm/cli-linux-x64/.npmrc @@ -0,0 +1,2 @@ +@jbrejner:registry=https://npm.pkg.github.com +//npm.pkg.github.com/:_authToken=${MY_LOCAL_NPM_TOKEN} diff --git a/npm/cli-linux-x64/README.md b/npm/cli-linux-x64/README.md new file mode 100644 index 000000000..d9e5df161 --- /dev/null +++ b/npm/cli-linux-x64/README.md @@ -0,0 +1 @@ +This is the Linux 64-bit binary for the Kosli CLI. See https://github.com/kosli-dev/cli for details. diff --git a/npm/cli-linux-x64/package.json b/npm/cli-linux-x64/package.json new file mode 100644 index 000000000..95d051225 --- /dev/null +++ b/npm/cli-linux-x64/package.json @@ -0,0 +1,19 @@ +{ + "name": "@jbrejner/cli-linux-x64", + "version": "0.0.0", + "description": "Linux x64 binary for @jbrejner/cli", + "license": "MIT", + "os": [ + "linux" + ], + "cpu": [ + "x64" + ], + "files": [ + "bin/" + ], + "publishConfig": { + "registry": "https://npm.pkg.github.com", + "access": "public" + } +} \ No newline at end of file diff --git a/npm/cli-win32-arm64/.npmrc b/npm/cli-win32-arm64/.npmrc new file mode 100644 index 000000000..983929573 --- /dev/null +++ b/npm/cli-win32-arm64/.npmrc @@ -0,0 +1,2 @@ +@jbrejner:registry=https://npm.pkg.github.com +//npm.pkg.github.com/:_authToken=${MY_LOCAL_NPM_TOKEN} diff --git a/npm/cli-win32-arm64/README.md b/npm/cli-win32-arm64/README.md new file mode 100644 index 000000000..60b15f5f1 --- /dev/null +++ b/npm/cli-win32-arm64/README.md @@ -0,0 +1 @@ +This is the Windows ARM 64-bit binary for the Kosli CLI. See https://github.com/kosli-dev/cli for details. diff --git a/npm/cli-win32-arm64/package.json b/npm/cli-win32-arm64/package.json new file mode 100644 index 000000000..a6dd4fd7e --- /dev/null +++ b/npm/cli-win32-arm64/package.json @@ -0,0 +1,19 @@ +{ + "name": "@jbrejner/cli-win32-arm64", + "version": "0.0.0", + "description": "Windows arm64 binary for @jbrejner/cli", + "license": "MIT", + "os": [ + "win32" + ], + "cpu": [ + "arm64" + ], + "files": [ + "bin/" + ], + "publishConfig": { + "registry": "https://npm.pkg.github.com", + "access": "public" + } +} \ No newline at end of file diff --git a/npm/cli-win32-x64/.npmrc b/npm/cli-win32-x64/.npmrc new file mode 100644 index 000000000..983929573 --- /dev/null +++ b/npm/cli-win32-x64/.npmrc @@ -0,0 +1,2 @@ +@jbrejner:registry=https://npm.pkg.github.com +//npm.pkg.github.com/:_authToken=${MY_LOCAL_NPM_TOKEN} diff --git a/npm/cli-win32-x64/README.md b/npm/cli-win32-x64/README.md new file mode 100644 index 000000000..c4b7bc5be --- /dev/null +++ b/npm/cli-win32-x64/README.md @@ -0,0 +1 @@ +This is the Windows 64-bit binary for the Kosli CLI. See https://github.com/kosli-dev/cli for details. diff --git a/npm/cli-win32-x64/package.json b/npm/cli-win32-x64/package.json new file mode 100644 index 000000000..87a5aab13 --- /dev/null +++ b/npm/cli-win32-x64/package.json @@ -0,0 +1,19 @@ +{ + "name": "@jbrejner/cli-win32-x64", + "version": "0.0.0", + "description": "Windows x64 binary for @jbrejner/cli", + "license": "MIT", + "os": [ + "win32" + ], + "cpu": [ + "x64" + ], + "files": [ + "bin/" + ], + "publishConfig": { + "registry": "https://npm.pkg.github.com", + "access": "public" + } +} \ No newline at end of file diff --git a/npm/wrapper/.npmrc b/npm/wrapper/.npmrc new file mode 100644 index 000000000..983929573 --- /dev/null +++ b/npm/wrapper/.npmrc @@ -0,0 +1,2 @@ +@jbrejner:registry=https://npm.pkg.github.com +//npm.pkg.github.com/:_authToken=${MY_LOCAL_NPM_TOKEN} diff --git a/npm/wrapper/install.js b/npm/wrapper/install.js new file mode 100644 index 000000000..5b257db52 --- /dev/null +++ b/npm/wrapper/install.js @@ -0,0 +1,54 @@ +"use strict"; + +// Postinstall script: validates that the platform binary was installed correctly. +// Runs after `npm install @jbrejner/cli`. + +const { execFileSync } = require("child_process"); +const path = require("path"); +const fs = require("fs"); + +const SUPPORTED = { + linux: { x64: true, arm64: true, arm: true }, + darwin: { x64: true, arm64: true }, + win32: { x64: true, arm64: true }, +}; + +const platform = process.platform; +const arch = process.arch; + +if (!SUPPORTED[platform] || !SUPPORTED[platform][arch]) { + // Not a supported platform — exit cleanly so npm install doesn't fail. + process.exit(0); +} + +const packageName = `@jbrejner/cli-${platform}-${arch}`; + +let binaryPath; +try { + const packageDir = path.dirname( + require.resolve(`${packageName}/package.json`) + ); + const binaryName = platform === "win32" ? "kosli.exe" : "kosli"; + binaryPath = path.join(packageDir, "bin", binaryName); +} catch (e) { + // Optional dependency was skipped (e.g. --no-optional). Warn but don't fail. + process.stderr.write( + `[kosli] Warning: ${packageName} is not installed.\n` + + `[kosli] The kosli binary will not be available.\n` + + `[kosli] Re-run without --no-optional to fix this.\n` + ); + process.exit(0); +} + +if (!fs.existsSync(binaryPath)) { + process.stderr.write(`[kosli] Warning: binary not found at ${binaryPath}\n`); + process.exit(0); +} + +try { + execFileSync(binaryPath, ["version"], { stdio: "ignore" }); +} catch (e) { + process.stderr.write( + `[kosli] Warning: binary validation failed: ${e.message}\n` + ); +} diff --git a/npm/wrapper/package.json b/npm/wrapper/package.json new file mode 100644 index 000000000..aa8c794ef --- /dev/null +++ b/npm/wrapper/package.json @@ -0,0 +1,45 @@ +{ + "name": "@jbrejner/cli", + "version": "0.0.0", + "description": "CLI client for reporting compliance events to https://kosli.com", + "license": "MIT", + "author": "Kosli Inc. ", + "homepage": "https://kosli.com", + "repository": { + "type": "git", + "url": "git+https://github.com/kosli-dev/cli.git" + }, + "bugs": { + "url": "https://github.com/kosli-dev/cli/issues" + }, + "keywords": [ + "cli", + "kosli", + "compliance", + "supply-chain", + "devops" + ], + "bin": { + "kosli": "bin/kosli" + }, + "files": [ + "bin/", + "install.js" + ], + "scripts": { + "postinstall": "node install.js" + }, + "optionalDependencies": { + "@jbrejner/cli-linux-x64": "0.0.0", + "@jbrejner/cli-linux-arm64": "0.0.0", + "@jbrejner/cli-linux-arm": "0.0.0", + "@jbrejner/cli-darwin-x64": "0.0.0", + "@jbrejner/cli-darwin-arm64": "0.0.0", + "@jbrejner/cli-win32-x64": "0.0.0", + "@jbrejner/cli-win32-arm64": "0.0.0" + }, + "publishConfig": { + "registry": "https://npm.pkg.github.com", + "access": "public" + } +} \ No newline at end of file diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh new file mode 100755 index 000000000..9879bd437 --- /dev/null +++ b/scripts/npm-publish.sh @@ -0,0 +1,42 @@ +#!/bin/bash +set -e + +VERSION="$1" +if [ -z "$VERSION" ]; then + echo "Usage: $0 " + exit 1 +fi + +# Regex for stable: X.Y.Z (where X, Y, Z are numbers) +STABLE_REGEX="^[0-9]+\.[0-9]+\.[0-9]+$" + +# Regex for pre-release: X.Y.Z-TAG (where TAG starts with a hyphen) +PRE_REGEX="^[0-9]+\.[0-9]+\.[0-9]+-.*$" + +# Determine npm dist-tag: pre-release versions must not go to "latest" +if [[ $VERSION =~ $STABLE_REGEX ]]; then + echo "✅ '$VERSION' is a STABLE release." + NPM_TAG="latest" +elif [[ $VERSION =~ $PRE_REGEX ]]; then + echo "🧪 '$VERSION' is a PRE-RELEASE version." + NPM_TAG="snapshot" +else + echo "❌ '$VERSION' is not a valid SemVer format." + exit 1 +fi + +# Inject version into all platform package.json files +find npm -name package.json -exec sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"${VERSION}\"/" {} \; + +# Also update the optionalDependencies version references in the wrapper +sed -i "s/\(\"@jbrejner\/cli-[^\"]*\": \)\"[^\"]*\"/\1\"${VERSION}\"/g" npm/wrapper/package.json + +# Publish platform packages first (wrapper depends on them) +find npm -name package.json ! -path "npm/wrapper/*" | while read -r f; do + echo "Publishing $(basename "$(dirname "$f")")..." + (cd "$(dirname "$f")" && npm publish --tag "$NPM_TAG") +done + +# Publish wrapper last +echo "Publishing wrapper..." +(cd npm/wrapper && npm publish --tag "$NPM_TAG") From dd7342b58f47a49f6b4c35b5c15c59663051be9d Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Fri, 20 Mar 2026 12:31:34 +0000 Subject: [PATCH 02/32] npm-publish script supports conditional publishing. If gorelease in not in "Release" mode, npm packages are not pushed, so we can avoid multitudes of snapshot packages being pushed to public registry --- scripts/npm-publish.sh | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index 9879bd437..df620e3a6 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -7,6 +7,13 @@ if [ -z "$VERSION" ]; then exit 1 fi +# When called from goreleaser, $2 is "true" if snapshot build +DRY_RUN=false +if [ "$2" == "true" ] || [ "$2" == "--dry-run" ]; then + echo "Running in DRY-RUN mode. Packages will be created but not published." + DRY_RUN=true +fi + # Regex for stable: X.Y.Z (where X, Y, Z are numbers) STABLE_REGEX="^[0-9]+\.[0-9]+\.[0-9]+$" @@ -31,12 +38,22 @@ find npm -name package.json -exec sed -i "s/\"version\": \"[^\"]*\"/\"version\": # Also update the optionalDependencies version references in the wrapper sed -i "s/\(\"@jbrejner\/cli-[^\"]*\": \)\"[^\"]*\"/\1\"${VERSION}\"/g" npm/wrapper/package.json -# Publish platform packages first (wrapper depends on them) +# Pack and optionally publish platform packages first (wrapper depends on them) find npm -name package.json ! -path "npm/wrapper/*" | while read -r f; do - echo "Publishing $(basename "$(dirname "$f")")..." - (cd "$(dirname "$f")" && npm publish --tag "$NPM_TAG") + PKG_DIR="$(dirname "$f")" + PKG_NAME="$(basename "$PKG_DIR")" + echo "Packing ${PKG_NAME}..." + (cd "$PKG_DIR" && npm pack) + if [ "$DRY_RUN" = false ]; then + echo "Publishing ${PKG_NAME}..." + (cd "$PKG_DIR" && npm publish --tag "$NPM_TAG") + fi done -# Publish wrapper last -echo "Publishing wrapper..." -(cd npm/wrapper && npm publish --tag "$NPM_TAG") +# Pack and optionally publish wrapper last +echo "Packing wrapper..." +(cd npm/wrapper && npm pack) +if [ "$DRY_RUN" = false ]; then + echo "Publishing wrapper..." + (cd npm/wrapper && npm publish --tag "$NPM_TAG") +fi From c43ed83134a8fef573c2092f13fdcf6780208806 Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Fri, 20 Mar 2026 12:34:36 +0000 Subject: [PATCH 03/32] Create npm packages after goreleaser via a hook script The hook runs after. If goreaser is not in release mode, npm will build in dry-run mode, so the packages are not pushed to registry --- .goreleaser.yml | 64 ++++++++-------- npm/README.md | 120 +++++++++++++++++++++++++++--- npm/cli-darwin-arm64/.npmrc | 3 +- npm/cli-darwin-arm64/package.json | 2 +- npm/cli-darwin-x64/.npmrc | 3 +- npm/cli-darwin-x64/package.json | 2 +- npm/cli-linux-arm/.npmrc | 3 +- npm/cli-linux-arm/package.json | 2 +- npm/cli-linux-arm64/.npmrc | 3 +- npm/cli-linux-arm64/package.json | 2 +- npm/cli-linux-x64/.npmrc | 3 +- npm/cli-linux-x64/package.json | 2 +- npm/cli-win32-arm64/.npmrc | 3 +- npm/cli-win32-arm64/package.json | 2 +- npm/cli-win32-x64/.npmrc | 3 +- npm/cli-win32-x64/package.json | 2 +- npm/wrapper/.npmrc | 3 +- npm/wrapper/package.json | 14 ++-- 18 files changed, 163 insertions(+), 73 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 4b36b0d23..8d3b20593 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -4,6 +4,7 @@ before: hooks: - go mod tidy - rm -rf npm/cli-*/bin + - find npm -name "*.tgz" -delete builds: - id: kosli binary: kosli @@ -51,55 +52,56 @@ archives: - goos: windows formats: [zip] - # docs for nfpm can be found here: https://goreleaser.com/customization/nfpm/ nfpms: - id: kosli - -# # You can change the file name of the package. -# # -# # Default:`{{ .PackageName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}` - -# # TODO Maybe enable the template again after we have a better understanding of how to use it. It seems to be working without it, but it would be nice to have more control over the file name. - -# # file_name_template: >- -# # {{ .ProjectName }}_ -# # {{- title .Os }}_ -# # {{- if eq .Arch "amd64" }}x86_64 -# # {{- else if eq .Arch "386" }}i386 -# # {{- else }}{{ .Arch }}{{ end }} + # You can change the file name of the package. + # + # Default:`{{ .PackageName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}` + file_name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} ids: - kosli -# vendor: Kosli Inc. -# homepage: https://kosli.com/ -# maintainer: Mike Long -# description: CLI client for reporting compliance events to https://kosli.com -# license: MIT + vendor: Kosli Inc. + homepage: https://kosli.com/ + maintainer: Mike Long + description: CLI client for reporting compliance events to https://kosli.com + license: MIT -# # Formats to be generated. -# formats: -# - deb -# - rpm + # Formats to be generated. + formats: + - deb + - rpm -# # Template to the path that the binaries should be installed. -# # Defaults to `/usr/bin`. -# bindir: /usr/bin + # Template to the path that the binaries should be installed. + # Defaults to `/usr/bin`. + bindir: /usr/bin -# # Section. -# section: misc + # Section. + section: misc -# # Priority. -# priority: optional + # Priority. + priority: optional # Contents to add to the package. # GoReleaser will automatically add the binaries. contents: # The src and dst attributes also supports name templates - - src: dist/{{ .ProjectName }}_{{ .Os }}_{{ if .Amd64 }}{{ .Arch }}_v1{{ else if .Arm }}{{ .Arch }}_6{{ else if eq .Arch "arm64" }}{{ .Arch }}_v8.0{{ else }}{{ .Arch }}{{ end }}/kosli + - src: dist/{{ .ProjectName }}_{{ .Os }}_{{ if .Amd64 }}{{ .Arch }}_v1{{ else if .Arm }}{{ .Arch }}_6{{ else }}{{ .Arch }}{{ end }}/kosli dst: /usr/local/bin/kosli +after: + hooks: + - cmd: bash scripts/npm-publish.sh "{{ .Version }}"{{ if or .IsSnapshot (not .IsRelease) }} --dry-run{{ end }} + # after hooks suppresses output by default. You need to add output: true to the hook to see the script's messages. + output: true + publishers: - name: fury.io # by specifying `packages` id here goreleaser will only use this publisher diff --git a/npm/README.md b/npm/README.md index 2bad31b28..bb6768f71 100644 --- a/npm/README.md +++ b/npm/README.md @@ -11,11 +11,26 @@ npm/ │ ├── bin/kosli # JS shim that detects the platform and runs the binary │ └── install.js # postinstall script that validates the binary ├── cli-linux-x64/ # @jbrejner/cli-linux-x64 +│ ├── package.json # declares os/cpu fields for platform filtering +│ └── bin/kosli # the native binary — see below ├── cli-linux-arm64/ # @jbrejner/cli-linux-arm64 +│ ├── package.json # declares os/cpu fields for platform filtering +│ └── bin/kosli # the native binary — see below +├── cli-linux-arm/ # @jbrejner/cli-linux-arm +│ ├── package.json # declares os/cpu fields for platform filtering +│ └── bin/kosli # the native binary — see below ├── cli-darwin-x64/ # @jbrejner/cli-darwin-x64 -└── cli-darwin-arm64/ # @jbrejner/cli-darwin-arm64 +│ ├── package.json # declares os/cpu fields for platform filtering +│ └── bin/kosli # the native binary — see below +├── cli-darwin-arm64/ # @jbrejner/cli-darwin-arm64 +│ ├── package.json # declares os/cpu fields for platform filtering +│ └── bin/kosli # the native binary — see below +└── cli-win32-x64/ # @jbrejner/cli-win32-x64 +│ ├── package.json # declares os/cpu fields for platform filtering +│ └── bin/kosli[.exe] # the native binary — see below +└── cli-win32-arm64/ # @jbrejner/cli-win32-arm64 ├── package.json # declares os/cpu fields for platform filtering - └── bin/kosli # the native binary — see below + └── bin/kosli[.exe] # the native binary — see below ``` ## How it works @@ -26,6 +41,12 @@ Users install a single package: npm install @jbrejner/cli ``` +or if using in continuous integration you can install globally: + +```sh +npm install -g @jbrejner/cli +``` + npm resolves the `optionalDependencies` declared in the wrapper's `package.json` and installs only the platform-specific package that matches the current OS and CPU architecture — all non-matching packages are silently skipped. The wrapper's `bin/kosli` JS shim then locates the binary inside the installed platform package and executes it. ## The `bin/` directories are populated by goreleaser @@ -35,32 +56,107 @@ The platform package `bin/` directories are **not committed to git**. They are p ```yaml hooks: post: - - cmd: bash -c 'ARCH="{{ .Arch }}"; [ "$ARCH" = "amd64" ] && ARCH="x64"; mkdir -p npm/cli-{{ .Os }}-${ARCH}/bin && cp "{{ .Path }}" npm/cli-{{ .Os }}-${ARCH}/bin/kosli && chmod +x npm/cli-{{ .Os }}-${ARCH}/bin/kosli' + - cmd: >- + bash -c ' + OS="{{ .Os }}"; + ARCH="{{ .Arch }}"; + [ "$OS" = "windows" ] && OS="win32"; + [ "$ARCH" = "amd64" ] && ARCH="x64"; + EXT=""; + [ "{{ .Os }}" = "windows" ] && EXT=".exe"; + mkdir -p npm/cli-${OS}-${ARCH}/bin && + cp "{{ .Path }}" npm/cli-${OS}-${ARCH}/bin/kosli${EXT} && + chmod +x npm/cli-${OS}-${ARCH}/bin/kosli${EXT}' ``` -This hook runs once per build target immediately after goreleaser compiles the binary. It maps goreleaser's architecture naming (`amd64` → `x64`, `arm64` → `arm64`) to the npm naming convention and copies the binary into the correct platform package directory. +This hook runs once per build target immediately after goreleaser compiles the binary. It applies the following naming conventions: + +| goreleaser | npm package dir | +|------------|-----------------| +| `linux` | `linux` | +| `darwin` | `darwin` | +| `windows` | `win32` | +| `amd64` | `x64` | +| `arm64` | `arm64` | +| `arm` | `arm` | + +Windows binaries are copied as `kosli.exe`; all others as `kosli`. The `windows/arm` combination is excluded from builds. + +The `before` hooks in `.goreleaser.yml` clean up stale artifacts before each build run: + +```yaml +before: + hooks: + - rm -rf npm/cli-*/bin + - find npm -name "*.tgz" -delete +``` ## Publishing -Platform packages must be published before the wrapper, since the wrapper's `optionalDependencies` references them. After a goreleaser build has populated the `bin/` directories: +Packages are published to the [npm public registry](https://registry.npmjs.org). Platform packages must be published before the wrapper, since the wrapper's `optionalDependencies` references them by version. After a goreleaser build has populated the `bin/` directories: ```sh # Publish platform packages first -(cd npm/cli-linux-x64 && npm publish) -(cd npm/cli-linux-arm64 && npm publish) -(cd npm/cli-darwin-x64 && npm publish) +(cd npm/cli-linux-x64 && npm publish) +(cd npm/cli-linux-arm64 && npm publish) +(cd npm/cli-linux-arm && npm publish) +(cd npm/cli-darwin-x64 && npm publish) (cd npm/cli-darwin-arm64 && npm publish) +(cd npm/cli-win32-x64 && npm publish) +(cd npm/cli-win32-arm64 && npm publish) # Then publish the wrapper (cd npm/wrapper && npm publish) ``` -Or as a one-liner that publishes platform packages first, then the wrapper: +Each package directory contains an `.npmrc` that sets the auth token: -```sh -find npm -name package.json ! -path "npm/wrapper/*" | while read f; do pushd "$(dirname "$f")" && npm publish; popd; done && pushd npm/wrapper && npm publish; popd ``` +//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} +``` + +## Automated Publishing with npm-publish.sh + +The `scripts/npm-publish.sh` script automates the npm packaging and publishing process. It injects the version into all `package.json` files, packs each package into a `.tgz`, and optionally publishes them. + +### Usage + +```bash +scripts/npm-publish.sh [--dry-run] +``` + +### Arguments + +- ``: Required. A SemVer string — either `X.Y.Z` (stable) or `X.Y.Z-TAG` (pre-release). +- `--dry-run` (optional second argument): Pack packages but skip publishing. + +### Behavior + +1. Injects `` into the `version` field of all `package.json` files. +2. Updates the `optionalDependencies` version references in `npm/wrapper/package.json` to match. +3. Runs `npm pack` on each platform package, then on the wrapper. +4. Unless `--dry-run` is set, runs `npm publish --tag ` on each package. + +The dist-tag is determined by the version format: + +| Version format | npm dist-tag | +|----------------|--------------| +| `X.Y.Z` | `latest` | +| `X.Y.Z-*` | `snapshot` | + +### Integration with GoReleaser + +GoReleaser calls this script automatically via the `after` hook once all platform binaries have been built and copied into the `bin/` directories: + +```yaml +after: + hooks: + - cmd: bash scripts/npm-publish.sh "{{ .Version }}" ... + output: true +``` + +The script output is surfaced in the goreleaser log (`output: true`). ## Versioning -All packages must share the same version number. When bumping the version, update it in all five `package.json` files — the four platform packages and the wrapper — as well as the `optionalDependencies` versions in `npm/wrapper/package.json`. +All packages share the same version number. When releasing, `npm-publish.sh` updates it automatically in all eight `package.json` files — the seven platform packages and the wrapper — as well as the `optionalDependencies` version pins in `npm/wrapper/package.json`. There is no need to edit these files manually. diff --git a/npm/cli-darwin-arm64/.npmrc b/npm/cli-darwin-arm64/.npmrc index 983929573..2762f8466 100644 --- a/npm/cli-darwin-arm64/.npmrc +++ b/npm/cli-darwin-arm64/.npmrc @@ -1,2 +1 @@ -@jbrejner:registry=https://npm.pkg.github.com -//npm.pkg.github.com/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} diff --git a/npm/cli-darwin-arm64/package.json b/npm/cli-darwin-arm64/package.json index 60fef5904..aff827c5f 100644 --- a/npm/cli-darwin-arm64/package.json +++ b/npm/cli-darwin-arm64/package.json @@ -13,7 +13,7 @@ "bin/" ], "publishConfig": { - "registry": "https://npm.pkg.github.com", + "registry": "https://registry.npmjs.org", "access": "public" } } \ No newline at end of file diff --git a/npm/cli-darwin-x64/.npmrc b/npm/cli-darwin-x64/.npmrc index 983929573..2762f8466 100644 --- a/npm/cli-darwin-x64/.npmrc +++ b/npm/cli-darwin-x64/.npmrc @@ -1,2 +1 @@ -@jbrejner:registry=https://npm.pkg.github.com -//npm.pkg.github.com/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} diff --git a/npm/cli-darwin-x64/package.json b/npm/cli-darwin-x64/package.json index a33355611..a651a4a82 100644 --- a/npm/cli-darwin-x64/package.json +++ b/npm/cli-darwin-x64/package.json @@ -13,7 +13,7 @@ "bin/" ], "publishConfig": { - "registry": "https://npm.pkg.github.com", + "registry": "https://registry.npmjs.org", "access": "public" } } \ No newline at end of file diff --git a/npm/cli-linux-arm/.npmrc b/npm/cli-linux-arm/.npmrc index 983929573..2762f8466 100644 --- a/npm/cli-linux-arm/.npmrc +++ b/npm/cli-linux-arm/.npmrc @@ -1,2 +1 @@ -@jbrejner:registry=https://npm.pkg.github.com -//npm.pkg.github.com/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} diff --git a/npm/cli-linux-arm/package.json b/npm/cli-linux-arm/package.json index d3e282ec2..4ee0d3b73 100644 --- a/npm/cli-linux-arm/package.json +++ b/npm/cli-linux-arm/package.json @@ -13,7 +13,7 @@ "bin/" ], "publishConfig": { - "registry": "https://npm.pkg.github.com", + "registry": "https://registry.npmjs.org", "access": "public" } } diff --git a/npm/cli-linux-arm64/.npmrc b/npm/cli-linux-arm64/.npmrc index 983929573..2762f8466 100644 --- a/npm/cli-linux-arm64/.npmrc +++ b/npm/cli-linux-arm64/.npmrc @@ -1,2 +1 @@ -@jbrejner:registry=https://npm.pkg.github.com -//npm.pkg.github.com/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} diff --git a/npm/cli-linux-arm64/package.json b/npm/cli-linux-arm64/package.json index 960e33ebb..784706c3b 100644 --- a/npm/cli-linux-arm64/package.json +++ b/npm/cli-linux-arm64/package.json @@ -13,7 +13,7 @@ "bin/" ], "publishConfig": { - "registry": "https://npm.pkg.github.com", + "registry": "https://registry.npmjs.org", "access": "public" } } \ No newline at end of file diff --git a/npm/cli-linux-x64/.npmrc b/npm/cli-linux-x64/.npmrc index 983929573..2762f8466 100644 --- a/npm/cli-linux-x64/.npmrc +++ b/npm/cli-linux-x64/.npmrc @@ -1,2 +1 @@ -@jbrejner:registry=https://npm.pkg.github.com -//npm.pkg.github.com/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} diff --git a/npm/cli-linux-x64/package.json b/npm/cli-linux-x64/package.json index 95d051225..950a393f8 100644 --- a/npm/cli-linux-x64/package.json +++ b/npm/cli-linux-x64/package.json @@ -13,7 +13,7 @@ "bin/" ], "publishConfig": { - "registry": "https://npm.pkg.github.com", + "registry": "https://registry.npmjs.org", "access": "public" } } \ No newline at end of file diff --git a/npm/cli-win32-arm64/.npmrc b/npm/cli-win32-arm64/.npmrc index 983929573..2762f8466 100644 --- a/npm/cli-win32-arm64/.npmrc +++ b/npm/cli-win32-arm64/.npmrc @@ -1,2 +1 @@ -@jbrejner:registry=https://npm.pkg.github.com -//npm.pkg.github.com/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} diff --git a/npm/cli-win32-arm64/package.json b/npm/cli-win32-arm64/package.json index a6dd4fd7e..4a43dd2c2 100644 --- a/npm/cli-win32-arm64/package.json +++ b/npm/cli-win32-arm64/package.json @@ -13,7 +13,7 @@ "bin/" ], "publishConfig": { - "registry": "https://npm.pkg.github.com", + "registry": "https://registry.npmjs.org", "access": "public" } } \ No newline at end of file diff --git a/npm/cli-win32-x64/.npmrc b/npm/cli-win32-x64/.npmrc index 983929573..2762f8466 100644 --- a/npm/cli-win32-x64/.npmrc +++ b/npm/cli-win32-x64/.npmrc @@ -1,2 +1 @@ -@jbrejner:registry=https://npm.pkg.github.com -//npm.pkg.github.com/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} diff --git a/npm/cli-win32-x64/package.json b/npm/cli-win32-x64/package.json index 87a5aab13..3f6943d4b 100644 --- a/npm/cli-win32-x64/package.json +++ b/npm/cli-win32-x64/package.json @@ -13,7 +13,7 @@ "bin/" ], "publishConfig": { - "registry": "https://npm.pkg.github.com", + "registry": "https://registry.npmjs.org", "access": "public" } } \ No newline at end of file diff --git a/npm/wrapper/.npmrc b/npm/wrapper/.npmrc index 983929573..2762f8466 100644 --- a/npm/wrapper/.npmrc +++ b/npm/wrapper/.npmrc @@ -1,2 +1 @@ -@jbrejner:registry=https://npm.pkg.github.com -//npm.pkg.github.com/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} diff --git a/npm/wrapper/package.json b/npm/wrapper/package.json index aa8c794ef..c62fbe913 100644 --- a/npm/wrapper/package.json +++ b/npm/wrapper/package.json @@ -30,16 +30,16 @@ "postinstall": "node install.js" }, "optionalDependencies": { - "@jbrejner/cli-linux-x64": "0.0.0", - "@jbrejner/cli-linux-arm64": "0.0.0", - "@jbrejner/cli-linux-arm": "0.0.0", - "@jbrejner/cli-darwin-x64": "0.0.0", "@jbrejner/cli-darwin-arm64": "0.0.0", - "@jbrejner/cli-win32-x64": "0.0.0", - "@jbrejner/cli-win32-arm64": "0.0.0" + "@jbrejner/cli-darwin-x64": "0.0.0", + "@jbrejner/cli-linux-arm": "0.0.0", + "@jbrejner/cli-linux-arm64": "0.0.0", + "@jbrejner/cli-linux-x64": "0.0.0", + "@jbrejner/cli-win32-arm64": "0.0.0", + "@jbrejner/cli-win32-x64": "0.0.0" }, "publishConfig": { - "registry": "https://npm.pkg.github.com", + "registry": "https://registry.npmjs.org", "access": "public" } } \ No newline at end of file From 153ff0d4a648dcbfda7920e4ee9ca96ae202e1ff Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Fri, 20 Mar 2026 14:57:01 +0000 Subject: [PATCH 04/32] Update documentation with NPM installation instructions --- docs.kosli.com/content/getting_started/install.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs.kosli.com/content/getting_started/install.md b/docs.kosli.com/content/getting_started/install.md index d07e53400..504b06070 100644 --- a/docs.kosli.com/content/getting_started/install.md +++ b/docs.kosli.com/content/getting_started/install.md @@ -1,4 +1,4 @@ ---- + --- title: "Part 2: Install Kosli CLI" bookCollapseSection: false weight: 220 @@ -89,6 +89,15 @@ sudo mv kosli /usr/local/bin/kosli {{< /tab >}} +{{< tab "NPM" >}} +You can install Kosli CLI system-wide with `npm` from the default registry + +```shell {.command} +npm install -g @kosli/cli +``` + +{{< /tab >}} + {{< tab "From source" >}} You can build Kosli CLI from source by running: ```shell {.command} @@ -100,7 +109,6 @@ make build {{< /tabs >}} - ## Verifying the installation worked Run this command: From a79527b9ab80c12df5415462b123a52a3b3677fd Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Mon, 23 Mar 2026 12:56:17 +0000 Subject: [PATCH 05/32] Switch npm package scope to @kosli --- npm/README.md | 20 ++++++++++---------- npm/cli-darwin-arm64/package.json | 4 ++-- npm/cli-darwin-x64/package.json | 4 ++-- npm/cli-linux-arm/package.json | 4 ++-- npm/cli-linux-arm64/package.json | 4 ++-- npm/cli-linux-x64/package.json | 4 ++-- npm/cli-win32-arm64/package.json | 4 ++-- npm/cli-win32-x64/package.json | 4 ++-- npm/wrapper/install.js | 4 ++-- npm/wrapper/package.json | 16 ++++++++-------- scripts/npm-publish.sh | 2 +- 11 files changed, 35 insertions(+), 35 deletions(-) diff --git a/npm/README.md b/npm/README.md index bb6768f71..016155733 100644 --- a/npm/README.md +++ b/npm/README.md @@ -6,29 +6,29 @@ This directory contains the npm package structure for distributing the Kosli CLI ``` npm/ -├── wrapper/ # @jbrejner/cli — the package users install +├── wrapper/ # @kosli/cli — the package users install │ ├── package.json # declares optionalDependencies for all platforms │ ├── bin/kosli # JS shim that detects the platform and runs the binary │ └── install.js # postinstall script that validates the binary -├── cli-linux-x64/ # @jbrejner/cli-linux-x64 +├── cli-linux-x64/ # @kosli/cli-linux-x64 │ ├── package.json # declares os/cpu fields for platform filtering │ └── bin/kosli # the native binary — see below -├── cli-linux-arm64/ # @jbrejner/cli-linux-arm64 +├── cli-linux-arm64/ # @kosli/cli-linux-arm64 │ ├── package.json # declares os/cpu fields for platform filtering │ └── bin/kosli # the native binary — see below -├── cli-linux-arm/ # @jbrejner/cli-linux-arm +├── cli-linux-arm/ # @kosli/cli-linux-arm │ ├── package.json # declares os/cpu fields for platform filtering │ └── bin/kosli # the native binary — see below -├── cli-darwin-x64/ # @jbrejner/cli-darwin-x64 +├── cli-darwin-x64/ # @kosli/cli-darwin-x64 │ ├── package.json # declares os/cpu fields for platform filtering │ └── bin/kosli # the native binary — see below -├── cli-darwin-arm64/ # @jbrejner/cli-darwin-arm64 +├── cli-darwin-arm64/ # @kosli/cli-darwin-arm64 │ ├── package.json # declares os/cpu fields for platform filtering │ └── bin/kosli # the native binary — see below -└── cli-win32-x64/ # @jbrejner/cli-win32-x64 +└── cli-win32-x64/ # @kosli/cli-win32-x64 │ ├── package.json # declares os/cpu fields for platform filtering │ └── bin/kosli[.exe] # the native binary — see below -└── cli-win32-arm64/ # @jbrejner/cli-win32-arm64 +└── cli-win32-arm64/ # @kosli/cli-win32-arm64 ├── package.json # declares os/cpu fields for platform filtering └── bin/kosli[.exe] # the native binary — see below ``` @@ -38,13 +38,13 @@ npm/ Users install a single package: ```sh -npm install @jbrejner/cli +npm install @kosli/cli ``` or if using in continuous integration you can install globally: ```sh -npm install -g @jbrejner/cli +npm install -g @kosli/cli ``` npm resolves the `optionalDependencies` declared in the wrapper's `package.json` and installs only the platform-specific package that matches the current OS and CPU architecture — all non-matching packages are silently skipped. The wrapper's `bin/kosli` JS shim then locates the binary inside the installed platform package and executes it. diff --git a/npm/cli-darwin-arm64/package.json b/npm/cli-darwin-arm64/package.json index aff827c5f..cc8708895 100644 --- a/npm/cli-darwin-arm64/package.json +++ b/npm/cli-darwin-arm64/package.json @@ -1,7 +1,7 @@ { - "name": "@jbrejner/cli-darwin-arm64", + "name": "@kosli/cli-darwin-arm64", "version": "0.0.0", - "description": "macOS arm64 binary for @jbrejner/cli", + "description": "macOS arm64 binary for @kosli/cli", "license": "MIT", "os": [ "darwin" diff --git a/npm/cli-darwin-x64/package.json b/npm/cli-darwin-x64/package.json index a651a4a82..d1c81a221 100644 --- a/npm/cli-darwin-x64/package.json +++ b/npm/cli-darwin-x64/package.json @@ -1,7 +1,7 @@ { - "name": "@jbrejner/cli-darwin-x64", + "name": "@kosli/cli-darwin-x64", "version": "0.0.0", - "description": "macOS x64 binary for @jbrejner/cli", + "description": "macOS x64 binary for @kosli/cli", "license": "MIT", "os": [ "darwin" diff --git a/npm/cli-linux-arm/package.json b/npm/cli-linux-arm/package.json index 4ee0d3b73..a62339dfc 100644 --- a/npm/cli-linux-arm/package.json +++ b/npm/cli-linux-arm/package.json @@ -1,7 +1,7 @@ { - "name": "@jbrejner/cli-linux-arm", + "name": "@kosli/cli-linux-arm", "version": "0.0.0", - "description": "Linux arm binary for @jbrejner/cli", + "description": "Linux arm binary for @kosli/cli", "license": "MIT", "os": [ "linux" diff --git a/npm/cli-linux-arm64/package.json b/npm/cli-linux-arm64/package.json index 784706c3b..f70151fb1 100644 --- a/npm/cli-linux-arm64/package.json +++ b/npm/cli-linux-arm64/package.json @@ -1,7 +1,7 @@ { - "name": "@jbrejner/cli-linux-arm64", + "name": "@kosli/cli-linux-arm64", "version": "0.0.0", - "description": "Linux arm64 binary for @jbrejner/cli", + "description": "Linux arm64 binary for @kosli/cli", "license": "MIT", "os": [ "linux" diff --git a/npm/cli-linux-x64/package.json b/npm/cli-linux-x64/package.json index 950a393f8..eb440270e 100644 --- a/npm/cli-linux-x64/package.json +++ b/npm/cli-linux-x64/package.json @@ -1,7 +1,7 @@ { - "name": "@jbrejner/cli-linux-x64", + "name": "@kosli/cli-linux-x64", "version": "0.0.0", - "description": "Linux x64 binary for @jbrejner/cli", + "description": "Linux x64 binary for @kosli/cli", "license": "MIT", "os": [ "linux" diff --git a/npm/cli-win32-arm64/package.json b/npm/cli-win32-arm64/package.json index 4a43dd2c2..6f65ff022 100644 --- a/npm/cli-win32-arm64/package.json +++ b/npm/cli-win32-arm64/package.json @@ -1,7 +1,7 @@ { - "name": "@jbrejner/cli-win32-arm64", + "name": "@kosli/cli-win32-arm64", "version": "0.0.0", - "description": "Windows arm64 binary for @jbrejner/cli", + "description": "Windows arm64 binary for @kosli/cli", "license": "MIT", "os": [ "win32" diff --git a/npm/cli-win32-x64/package.json b/npm/cli-win32-x64/package.json index 3f6943d4b..f1fb6feb4 100644 --- a/npm/cli-win32-x64/package.json +++ b/npm/cli-win32-x64/package.json @@ -1,7 +1,7 @@ { - "name": "@jbrejner/cli-win32-x64", + "name": "@kosli/cli-win32-x64", "version": "0.0.0", - "description": "Windows x64 binary for @jbrejner/cli", + "description": "Windows x64 binary for @kosli/cli", "license": "MIT", "os": [ "win32" diff --git a/npm/wrapper/install.js b/npm/wrapper/install.js index 5b257db52..807058d98 100644 --- a/npm/wrapper/install.js +++ b/npm/wrapper/install.js @@ -1,7 +1,7 @@ "use strict"; // Postinstall script: validates that the platform binary was installed correctly. -// Runs after `npm install @jbrejner/cli`. +// Runs after `npm install @kosli/cli`. const { execFileSync } = require("child_process"); const path = require("path"); @@ -21,7 +21,7 @@ if (!SUPPORTED[platform] || !SUPPORTED[platform][arch]) { process.exit(0); } -const packageName = `@jbrejner/cli-${platform}-${arch}`; +const packageName = `@kosli/cli-${platform}-${arch}`; let binaryPath; try { diff --git a/npm/wrapper/package.json b/npm/wrapper/package.json index c62fbe913..b729ff208 100644 --- a/npm/wrapper/package.json +++ b/npm/wrapper/package.json @@ -1,5 +1,5 @@ { - "name": "@jbrejner/cli", + "name": "@kosli/cli", "version": "0.0.0", "description": "CLI client for reporting compliance events to https://kosli.com", "license": "MIT", @@ -30,13 +30,13 @@ "postinstall": "node install.js" }, "optionalDependencies": { - "@jbrejner/cli-darwin-arm64": "0.0.0", - "@jbrejner/cli-darwin-x64": "0.0.0", - "@jbrejner/cli-linux-arm": "0.0.0", - "@jbrejner/cli-linux-arm64": "0.0.0", - "@jbrejner/cli-linux-x64": "0.0.0", - "@jbrejner/cli-win32-arm64": "0.0.0", - "@jbrejner/cli-win32-x64": "0.0.0" + "@kosli/cli-darwin-arm64": "0.0.0", + "@kosli/cli-darwin-x64": "0.0.0", + "@kosli/cli-linux-arm": "0.0.0", + "@kosli/cli-linux-arm64": "0.0.0", + "@kosli/cli-linux-x64": "0.0.0", + "@kosli/cli-win32-arm64": "0.0.0", + "@kosli/cli-win32-x64": "0.0.0" }, "publishConfig": { "registry": "https://registry.npmjs.org", diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index df620e3a6..d65f0f469 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -36,7 +36,7 @@ fi find npm -name package.json -exec sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"${VERSION}\"/" {} \; # Also update the optionalDependencies version references in the wrapper -sed -i "s/\(\"@jbrejner\/cli-[^\"]*\": \)\"[^\"]*\"/\1\"${VERSION}\"/g" npm/wrapper/package.json +sed -i "s/\(\"@kosli\/cli-[^\"]*\": \)\"[^\"]*\"/\1\"${VERSION}\"/g" npm/wrapper/package.json # Pack and optionally publish platform packages first (wrapper depends on them) find npm -name package.json ! -path "npm/wrapper/*" | while read -r f; do From 9cd8341617a0d0e8366459f17511c6208167555c Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Wed, 25 Mar 2026 18:25:42 +0000 Subject: [PATCH 06/32] Fix: Add the missing bin/kosli JS Shim --- .gitignore | 2 +- npm/README.md | 2 +- npm/cli-darwin-arm64/package.json | 1 + npm/cli-darwin-x64/package.json | 1 + npm/cli-linux-arm/package.json | 3 ++- npm/cli-linux-arm64/package.json | 1 + npm/cli-linux-x64/package.json | 1 + npm/cli-win32-arm64/package.json | 1 + npm/cli-win32-x64/package.json | 1 + npm/wrapper/bin/kosli | 32 +++++++++++++++++++++++++++++++ npm/wrapper/package.json | 2 +- 11 files changed, 43 insertions(+), 4 deletions(-) create mode 100755 npm/wrapper/bin/kosli diff --git a/.gitignore b/.gitignore index eb9181b1f..3bcee113c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ docs.kosli.com/content/client_reference/kosli* docs.kosli.com/public/ docs.kosli.com/.netlify npm/cli*/bin/* -npm/wrapper/bin/* +npm/*/kosli*.tgz *.tar.gz *~ /.idea diff --git a/npm/README.md b/npm/README.md index 016155733..8793c64a6 100644 --- a/npm/README.md +++ b/npm/README.md @@ -111,7 +111,7 @@ Packages are published to the [npm public registry](https://registry.npmjs.org). Each package directory contains an `.npmrc` that sets the auth token: -``` +```text //registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} ``` diff --git a/npm/cli-darwin-arm64/package.json b/npm/cli-darwin-arm64/package.json index cc8708895..d48cbf394 100644 --- a/npm/cli-darwin-arm64/package.json +++ b/npm/cli-darwin-arm64/package.json @@ -9,6 +9,7 @@ "cpu": [ "arm64" ], + "bin": {"kosli": "bin/kosli"}, "files": [ "bin/" ], diff --git a/npm/cli-darwin-x64/package.json b/npm/cli-darwin-x64/package.json index d1c81a221..a8b56200c 100644 --- a/npm/cli-darwin-x64/package.json +++ b/npm/cli-darwin-x64/package.json @@ -9,6 +9,7 @@ "cpu": [ "x64" ], + "bin": {"kosli": "bin/kosli"}, "files": [ "bin/" ], diff --git a/npm/cli-linux-arm/package.json b/npm/cli-linux-arm/package.json index a62339dfc..6c6b0d61a 100644 --- a/npm/cli-linux-arm/package.json +++ b/npm/cli-linux-arm/package.json @@ -9,6 +9,7 @@ "cpu": [ "arm" ], + "bin": {"kosli": "bin/kosli"}, "files": [ "bin/" ], @@ -16,4 +17,4 @@ "registry": "https://registry.npmjs.org", "access": "public" } -} +} \ No newline at end of file diff --git a/npm/cli-linux-arm64/package.json b/npm/cli-linux-arm64/package.json index f70151fb1..3d2b04516 100644 --- a/npm/cli-linux-arm64/package.json +++ b/npm/cli-linux-arm64/package.json @@ -9,6 +9,7 @@ "cpu": [ "arm64" ], + "bin": {"kosli": "bin/kosli"}, "files": [ "bin/" ], diff --git a/npm/cli-linux-x64/package.json b/npm/cli-linux-x64/package.json index eb440270e..29e8f51c5 100644 --- a/npm/cli-linux-x64/package.json +++ b/npm/cli-linux-x64/package.json @@ -9,6 +9,7 @@ "cpu": [ "x64" ], + "bin": {"kosli": "bin/kosli"}, "files": [ "bin/" ], diff --git a/npm/cli-win32-arm64/package.json b/npm/cli-win32-arm64/package.json index 6f65ff022..ad2ae567a 100644 --- a/npm/cli-win32-arm64/package.json +++ b/npm/cli-win32-arm64/package.json @@ -9,6 +9,7 @@ "cpu": [ "arm64" ], + "bin": {"kosli": "bin/kosli.exe"}, "files": [ "bin/" ], diff --git a/npm/cli-win32-x64/package.json b/npm/cli-win32-x64/package.json index f1fb6feb4..da12b0b5c 100644 --- a/npm/cli-win32-x64/package.json +++ b/npm/cli-win32-x64/package.json @@ -9,6 +9,7 @@ "cpu": [ "x64" ], + "bin": {"kosli": "bin/kosli.exe"}, "files": [ "bin/" ], diff --git a/npm/wrapper/bin/kosli b/npm/wrapper/bin/kosli new file mode 100755 index 000000000..c9d405e4c --- /dev/null +++ b/npm/wrapper/bin/kosli @@ -0,0 +1,32 @@ +#!/usr/bin/env node +"use strict"; + +const { spawnSync } = require("child_process"); +const path = require("path"); + +const platform = process.platform; +const arch = process.arch; + +const packageName = `@kosli/cli-${platform}-${arch}`; + +let binaryPath; +try { + const packageDir = path.dirname( + require.resolve(`${packageName}/package.json`) + ); + const binaryName = platform === "win32" ? "kosli.exe" : "kosli"; + binaryPath = path.join(packageDir, "bin", binaryName); +} catch (e) { + process.stderr.write( + `[kosli] Error: platform package ${packageName} is not installed.\n` + + `[kosli] Try reinstalling: npm install -g @kosli/cli\n` + ); + process.exit(1); +} + +const result = spawnSync(binaryPath, process.argv.slice(2), { stdio: "inherit" }); +if (result.error) { + process.stderr.write(`[kosli] Error: failed to run binary: ${result.error.message}\n`); + process.exit(1); +} +process.exit(result.status); diff --git a/npm/wrapper/package.json b/npm/wrapper/package.json index b729ff208..897621837 100644 --- a/npm/wrapper/package.json +++ b/npm/wrapper/package.json @@ -42,4 +42,4 @@ "registry": "https://registry.npmjs.org", "access": "public" } -} \ No newline at end of file +} From 0d566780e52b7d68213d5802345c2f99fc58df2a Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Wed, 25 Mar 2026 18:30:36 +0000 Subject: [PATCH 07/32] Fix: Token Variable Mismatch --- npm/README.md | 2 +- npm/cli-darwin-arm64/.npmrc | 2 +- npm/cli-darwin-x64/.npmrc | 2 +- npm/cli-linux-arm/.npmrc | 2 +- npm/cli-linux-arm64/.npmrc | 2 +- npm/cli-linux-x64/.npmrc | 2 +- npm/cli-win32-arm64/.npmrc | 2 +- npm/cli-win32-x64/.npmrc | 2 +- npm/wrapper/.npmrc | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/npm/README.md b/npm/README.md index 8793c64a6..ee283bab8 100644 --- a/npm/README.md +++ b/npm/README.md @@ -112,7 +112,7 @@ Packages are published to the [npm public registry](https://registry.npmjs.org). Each package directory contains an `.npmrc` that sets the auth token: ```text -//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${NPM_TOKEN} ``` ## Automated Publishing with npm-publish.sh diff --git a/npm/cli-darwin-arm64/.npmrc b/npm/cli-darwin-arm64/.npmrc index 2762f8466..ae643592e 100644 --- a/npm/cli-darwin-arm64/.npmrc +++ b/npm/cli-darwin-arm64/.npmrc @@ -1 +1 @@ -//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${NPM_TOKEN} diff --git a/npm/cli-darwin-x64/.npmrc b/npm/cli-darwin-x64/.npmrc index 2762f8466..ae643592e 100644 --- a/npm/cli-darwin-x64/.npmrc +++ b/npm/cli-darwin-x64/.npmrc @@ -1 +1 @@ -//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${NPM_TOKEN} diff --git a/npm/cli-linux-arm/.npmrc b/npm/cli-linux-arm/.npmrc index 2762f8466..ae643592e 100644 --- a/npm/cli-linux-arm/.npmrc +++ b/npm/cli-linux-arm/.npmrc @@ -1 +1 @@ -//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${NPM_TOKEN} diff --git a/npm/cli-linux-arm64/.npmrc b/npm/cli-linux-arm64/.npmrc index 2762f8466..ae643592e 100644 --- a/npm/cli-linux-arm64/.npmrc +++ b/npm/cli-linux-arm64/.npmrc @@ -1 +1 @@ -//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${NPM_TOKEN} diff --git a/npm/cli-linux-x64/.npmrc b/npm/cli-linux-x64/.npmrc index 2762f8466..ae643592e 100644 --- a/npm/cli-linux-x64/.npmrc +++ b/npm/cli-linux-x64/.npmrc @@ -1 +1 @@ -//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${NPM_TOKEN} diff --git a/npm/cli-win32-arm64/.npmrc b/npm/cli-win32-arm64/.npmrc index 2762f8466..ae643592e 100644 --- a/npm/cli-win32-arm64/.npmrc +++ b/npm/cli-win32-arm64/.npmrc @@ -1 +1 @@ -//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${NPM_TOKEN} diff --git a/npm/cli-win32-x64/.npmrc b/npm/cli-win32-x64/.npmrc index 2762f8466..ae643592e 100644 --- a/npm/cli-win32-x64/.npmrc +++ b/npm/cli-win32-x64/.npmrc @@ -1 +1 @@ -//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${NPM_TOKEN} diff --git a/npm/wrapper/.npmrc b/npm/wrapper/.npmrc index 2762f8466..ae643592e 100644 --- a/npm/wrapper/.npmrc +++ b/npm/wrapper/.npmrc @@ -1 +1 @@ -//registry.npmjs.org/:_authToken=${MY_LOCAL_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${NPM_TOKEN} From 110bed9c03f30ff6ed2eabbf16bfd073509c02ec Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Wed, 25 Mar 2026 18:40:32 +0000 Subject: [PATCH 08/32] Fix: Silent Postinstall Failures --- npm/wrapper/bin/kosli | 16 +++++++++++++++- npm/wrapper/install.js | 16 ++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/npm/wrapper/bin/kosli b/npm/wrapper/bin/kosli index c9d405e4c..fb2ca85de 100755 --- a/npm/wrapper/bin/kosli +++ b/npm/wrapper/bin/kosli @@ -7,6 +7,20 @@ const path = require("path"); const platform = process.platform; const arch = process.arch; +const SUPPORTED = { + linux: { x64: true, arm64: true, arm: true }, + darwin: { x64: true, arm64: true }, + win32: { x64: true, arm64: true }, +}; + +if (!SUPPORTED[platform] || !SUPPORTED[platform][arch]) { + process.stderr.write( + `[kosli] Error: ${platform}/${arch} is not a supported platform.\n` + + `[kosli] See https://github.com/kosli-dev/cli for supported platforms.\n` + ); + process.exit(1); +} + const packageName = `@kosli/cli-${platform}-${arch}`; let binaryPath; @@ -29,4 +43,4 @@ if (result.error) { process.stderr.write(`[kosli] Error: failed to run binary: ${result.error.message}\n`); process.exit(1); } -process.exit(result.status); +process.exit(result.status ?? 1); diff --git a/npm/wrapper/install.js b/npm/wrapper/install.js index 807058d98..d364f69c2 100644 --- a/npm/wrapper/install.js +++ b/npm/wrapper/install.js @@ -17,7 +17,10 @@ const platform = process.platform; const arch = process.arch; if (!SUPPORTED[platform] || !SUPPORTED[platform][arch]) { - // Not a supported platform — exit cleanly so npm install doesn't fail. + process.stderr.write( + `[kosli] Note: ${platform}/${arch} is not a supported platform.\n` + + `[kosli] The kosli binary will not be available on this system.\n` + ); process.exit(0); } @@ -41,14 +44,19 @@ try { } if (!fs.existsSync(binaryPath)) { - process.stderr.write(`[kosli] Warning: binary not found at ${binaryPath}\n`); - process.exit(0); + process.stderr.write( + `[kosli] Error: binary not found at ${binaryPath}\n` + + `[kosli] Try reinstalling: npm install -g @kosli/cli\n` + ); + process.exit(1); } try { execFileSync(binaryPath, ["version"], { stdio: "ignore" }); } catch (e) { process.stderr.write( - `[kosli] Warning: binary validation failed: ${e.message}\n` + `[kosli] Error: binary validation failed: ${e.message}\n` + + `[kosli] Try reinstalling: npm install -g @kosli/cli\n` ); + process.exit(1); } From 0731aafafb2651236dea7128b743adfaa1996245 Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Thu, 26 Mar 2026 09:55:03 +0100 Subject: [PATCH 09/32] Update npm/README.md Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- npm/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/README.md b/npm/README.md index ee283bab8..b918d1ff4 100644 --- a/npm/README.md +++ b/npm/README.md @@ -25,7 +25,7 @@ npm/ ├── cli-darwin-arm64/ # @kosli/cli-darwin-arm64 │ ├── package.json # declares os/cpu fields for platform filtering │ └── bin/kosli # the native binary — see below -└── cli-win32-x64/ # @kosli/cli-win32-x64 +├── cli-win32-x64/ # @kosli/cli-win32-x64 │ ├── package.json # declares os/cpu fields for platform filtering │ └── bin/kosli[.exe] # the native binary — see below └── cli-win32-arm64/ # @kosli/cli-win32-arm64 From ceea475303d1e39e0ebccd91b43bf3eb7dec5715 Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Mon, 30 Mar 2026 11:43:54 +0000 Subject: [PATCH 10/32] refactor(npm-publish): replace sed/perl with jq and harden publish script - Use jq instead of sed/perl for JSON version updates (portable across macOS and Linux, handles JSON correctly) - Separate pack and publish into two distinct phases so all packages are packed before any are published - Add npm_publish_with_retry with exponential backoff (3 attempts) - Fail fast with clear error messages on pack or publish failure --- scripts/npm-publish.sh | 57 +++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index d65f0f469..0c1fdb535 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -33,27 +33,54 @@ else fi # Inject version into all platform package.json files -find npm -name package.json -exec sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"${VERSION}\"/" {} \; +find npm -name package.json | while IFS= read -r f; do + tmp="$(mktemp)" + jq --arg v "$VERSION" '.version = $v' "$f" > "$tmp" && mv "$tmp" "$f" +done # Also update the optionalDependencies version references in the wrapper -sed -i "s/\(\"@kosli\/cli-[^\"]*\": \)\"[^\"]*\"/\1\"${VERSION}\"/g" npm/wrapper/package.json +tmp="$(mktemp)" +jq --arg v "$VERSION" '.optionalDependencies = (.optionalDependencies | with_entries(.value = $v))' \ + npm/wrapper/package.json > "$tmp" && mv "$tmp" npm/wrapper/package.json + +# Build ordered package list: platform packages first, wrapper last +PACKAGES=() +while IFS= read -r f; do + PACKAGES+=("$(dirname "$f")") +done < <(find npm -name package.json ! -path "npm/wrapper/*" | sort) +PACKAGES+=("npm/wrapper") -# Pack and optionally publish platform packages first (wrapper depends on them) -find npm -name package.json ! -path "npm/wrapper/*" | while read -r f; do - PKG_DIR="$(dirname "$f")" +# Phase 1: pack all packages — exit immediately on any failure +for PKG_DIR in "${PACKAGES[@]}"; do PKG_NAME="$(basename "$PKG_DIR")" echo "Packing ${PKG_NAME}..." - (cd "$PKG_DIR" && npm pack) - if [ "$DRY_RUN" = false ]; then - echo "Publishing ${PKG_NAME}..." - (cd "$PKG_DIR" && npm publish --tag "$NPM_TAG") - fi + (cd "$PKG_DIR" && npm pack) || { echo "❌ Failed to pack ${PKG_NAME}"; exit 1; } done -# Pack and optionally publish wrapper last -echo "Packing wrapper..." -(cd npm/wrapper && npm pack) +# Phase 2: publish all packages if not a dry run — exit immediately on any failure +npm_publish_with_retry() { + local pkg_dir="$1" + local tag="$2" + local max_attempts=3 + local delay=5 + + for attempt in $(seq 1 "$max_attempts"); do + if (cd "$pkg_dir" && npm publish --tag "$tag"); then + return 0 + fi + if [ "$attempt" -lt "$max_attempts" ]; then + echo "⚠️ Attempt ${attempt}/${max_attempts} failed. Retrying in ${delay}s..." + sleep "$delay" + delay=$(( delay * 2 )) + fi + done + return 1 +} + if [ "$DRY_RUN" = false ]; then - echo "Publishing wrapper..." - (cd npm/wrapper && npm publish --tag "$NPM_TAG") + for PKG_DIR in "${PACKAGES[@]}"; do + PKG_NAME="$(basename "$PKG_DIR")" + echo "Publishing ${PKG_NAME}..." + npm_publish_with_retry "$PKG_DIR" "$NPM_TAG" || { echo "❌ Failed to publish ${PKG_NAME} after ${max_attempts} attempts"; exit 1; } + done fi From 3390c1471341f439fabdf101f34d3c678ded291c Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Mon, 30 Mar 2026 11:52:47 +0000 Subject: [PATCH 11/32] Fix: Frontmatter formatting --- docs.kosli.com/content/getting_started/install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs.kosli.com/content/getting_started/install.md b/docs.kosli.com/content/getting_started/install.md index 504b06070..2a9ae2701 100644 --- a/docs.kosli.com/content/getting_started/install.md +++ b/docs.kosli.com/content/getting_started/install.md @@ -1,4 +1,4 @@ - --- +--- title: "Part 2: Install Kosli CLI" bookCollapseSection: false weight: 220 From 8d539fa74ba9350e50735443bd072bfeb077d957 Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Mon, 30 Mar 2026 12:01:26 +0000 Subject: [PATCH 12/32] Consistent formatting of package.json files --- npm/cli-darwin-arm64/package.json | 6 ++++-- npm/cli-darwin-x64/package.json | 6 ++++-- npm/cli-linux-arm/package.json | 6 ++++-- npm/cli-linux-arm64/package.json | 6 ++++-- npm/cli-linux-x64/package.json | 6 ++++-- npm/cli-win32-arm64/package.json | 6 ++++-- npm/cli-win32-x64/package.json | 6 ++++-- 7 files changed, 28 insertions(+), 14 deletions(-) diff --git a/npm/cli-darwin-arm64/package.json b/npm/cli-darwin-arm64/package.json index d48cbf394..7d3b9587c 100644 --- a/npm/cli-darwin-arm64/package.json +++ b/npm/cli-darwin-arm64/package.json @@ -9,7 +9,9 @@ "cpu": [ "arm64" ], - "bin": {"kosli": "bin/kosli"}, + "bin": { + "kosli": "bin/kosli" + }, "files": [ "bin/" ], @@ -17,4 +19,4 @@ "registry": "https://registry.npmjs.org", "access": "public" } -} \ No newline at end of file +} diff --git a/npm/cli-darwin-x64/package.json b/npm/cli-darwin-x64/package.json index a8b56200c..cc8afb3b2 100644 --- a/npm/cli-darwin-x64/package.json +++ b/npm/cli-darwin-x64/package.json @@ -9,7 +9,9 @@ "cpu": [ "x64" ], - "bin": {"kosli": "bin/kosli"}, + "bin": { + "kosli": "bin/kosli" + }, "files": [ "bin/" ], @@ -17,4 +19,4 @@ "registry": "https://registry.npmjs.org", "access": "public" } -} \ No newline at end of file +} diff --git a/npm/cli-linux-arm/package.json b/npm/cli-linux-arm/package.json index 6c6b0d61a..373936017 100644 --- a/npm/cli-linux-arm/package.json +++ b/npm/cli-linux-arm/package.json @@ -9,7 +9,9 @@ "cpu": [ "arm" ], - "bin": {"kosli": "bin/kosli"}, + "bin": { + "kosli": "bin/kosli" + }, "files": [ "bin/" ], @@ -17,4 +19,4 @@ "registry": "https://registry.npmjs.org", "access": "public" } -} \ No newline at end of file +} diff --git a/npm/cli-linux-arm64/package.json b/npm/cli-linux-arm64/package.json index 3d2b04516..3756e6d77 100644 --- a/npm/cli-linux-arm64/package.json +++ b/npm/cli-linux-arm64/package.json @@ -9,7 +9,9 @@ "cpu": [ "arm64" ], - "bin": {"kosli": "bin/kosli"}, + "bin": { + "kosli": "bin/kosli" + }, "files": [ "bin/" ], @@ -17,4 +19,4 @@ "registry": "https://registry.npmjs.org", "access": "public" } -} \ No newline at end of file +} diff --git a/npm/cli-linux-x64/package.json b/npm/cli-linux-x64/package.json index 29e8f51c5..84c743b30 100644 --- a/npm/cli-linux-x64/package.json +++ b/npm/cli-linux-x64/package.json @@ -9,7 +9,9 @@ "cpu": [ "x64" ], - "bin": {"kosli": "bin/kosli"}, + "bin": { + "kosli": "bin/kosli" + }, "files": [ "bin/" ], @@ -17,4 +19,4 @@ "registry": "https://registry.npmjs.org", "access": "public" } -} \ No newline at end of file +} diff --git a/npm/cli-win32-arm64/package.json b/npm/cli-win32-arm64/package.json index ad2ae567a..9ffddd48a 100644 --- a/npm/cli-win32-arm64/package.json +++ b/npm/cli-win32-arm64/package.json @@ -9,7 +9,9 @@ "cpu": [ "arm64" ], - "bin": {"kosli": "bin/kosli.exe"}, + "bin": { + "kosli": "bin/kosli.exe" + }, "files": [ "bin/" ], @@ -17,4 +19,4 @@ "registry": "https://registry.npmjs.org", "access": "public" } -} \ No newline at end of file +} diff --git a/npm/cli-win32-x64/package.json b/npm/cli-win32-x64/package.json index da12b0b5c..cc95acaa4 100644 --- a/npm/cli-win32-x64/package.json +++ b/npm/cli-win32-x64/package.json @@ -9,7 +9,9 @@ "cpu": [ "x64" ], - "bin": {"kosli": "bin/kosli.exe"}, + "bin": { + "kosli": "bin/kosli.exe" + }, "files": [ "bin/" ], @@ -17,4 +19,4 @@ "registry": "https://registry.npmjs.org", "access": "public" } -} \ No newline at end of file +} From 3121294964d90398c72b90a9705ab6b3779e9f5e Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Mon, 30 Mar 2026 13:05:27 +0000 Subject: [PATCH 13/32] Include npm packages in binary provenance processing --- .github/workflows/release.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f36745dc5..3699ae167 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -156,6 +156,9 @@ jobs: FURY_TOKEN: ${{ secrets.FURY_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Copy npm packages into dist for provenance + run: find npm -name "*.tgz" -exec cp {} dist/ \; + - uses: actions/upload-artifact@v7 with: name: dist @@ -165,7 +168,7 @@ jobs: - name: Prepare artifacts list id: prepare-artifacts-list run: | - ARTIFACTS=$(jq '[reduce .[] as $item ( + GORELEASER_ARTIFACTS=$(jq '[reduce .[] as $item ( []; if ($item.type == "Archive") then . + [{ template_name: ($item.goos + "-" + $item.goarch), path: $item.path }] @@ -176,6 +179,18 @@ jobs: end )][]' dist/artifacts.json) + NPM_ARTIFACTS=$(find dist -maxdepth 1 -name "*.tgz" -printf '%f\n' \ + | jq -R '{ + template_name: ("npm-" + sub("-[0-9]+\\.[0-9]+\\.[0-9]+.*\\.tgz$"; "")), + path: ("dist/" + .) + }' \ + | jq -s '.') + + ARTIFACTS=$(jq -n \ + --argjson g "$GORELEASER_ARTIFACTS" \ + --argjson n "$NPM_ARTIFACTS" \ + '$g + $n') + echo "artifacts<> $GITHUB_OUTPUT echo "${ARTIFACTS}" >> $GITHUB_OUTPUT echo "nEOFn" >> $GITHUB_OUTPUT From f0ab843a7386198fb2c08c3cd433b094c8221daf Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Mon, 30 Mar 2026 13:07:33 +0000 Subject: [PATCH 14/32] Add directory and engines specification to packages --- npm/cli-darwin-arm64/package.json | 5 +++++ npm/cli-darwin-x64/package.json | 5 +++++ npm/cli-linux-arm/package.json | 5 +++++ npm/cli-linux-arm64/package.json | 5 +++++ npm/cli-linux-x64/package.json | 5 +++++ npm/cli-win32-arm64/package.json | 5 +++++ npm/cli-win32-x64/package.json | 5 +++++ npm/wrapper/package.json | 6 +++++- 8 files changed, 40 insertions(+), 1 deletion(-) diff --git a/npm/cli-darwin-arm64/package.json b/npm/cli-darwin-arm64/package.json index 7d3b9587c..3334cce65 100644 --- a/npm/cli-darwin-arm64/package.json +++ b/npm/cli-darwin-arm64/package.json @@ -3,6 +3,11 @@ "version": "0.0.0", "description": "macOS arm64 binary for @kosli/cli", "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/kosli-dev/cli.git", + "directory": "npm/cli-darwin-arm64" + }, "os": [ "darwin" ], diff --git a/npm/cli-darwin-x64/package.json b/npm/cli-darwin-x64/package.json index cc8afb3b2..2375d9bd1 100644 --- a/npm/cli-darwin-x64/package.json +++ b/npm/cli-darwin-x64/package.json @@ -3,6 +3,11 @@ "version": "0.0.0", "description": "macOS x64 binary for @kosli/cli", "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/kosli-dev/cli.git", + "directory": "npm/cli-darwin-x64" + }, "os": [ "darwin" ], diff --git a/npm/cli-linux-arm/package.json b/npm/cli-linux-arm/package.json index 373936017..3d8f190a0 100644 --- a/npm/cli-linux-arm/package.json +++ b/npm/cli-linux-arm/package.json @@ -3,6 +3,11 @@ "version": "0.0.0", "description": "Linux arm binary for @kosli/cli", "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/kosli-dev/cli.git", + "directory": "npm/cli-linux-arm" + }, "os": [ "linux" ], diff --git a/npm/cli-linux-arm64/package.json b/npm/cli-linux-arm64/package.json index 3756e6d77..1bd7e9346 100644 --- a/npm/cli-linux-arm64/package.json +++ b/npm/cli-linux-arm64/package.json @@ -3,6 +3,11 @@ "version": "0.0.0", "description": "Linux arm64 binary for @kosli/cli", "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/kosli-dev/cli.git", + "directory": "npm/cli-linux-arm64" + }, "os": [ "linux" ], diff --git a/npm/cli-linux-x64/package.json b/npm/cli-linux-x64/package.json index 84c743b30..c3f851316 100644 --- a/npm/cli-linux-x64/package.json +++ b/npm/cli-linux-x64/package.json @@ -3,6 +3,11 @@ "version": "0.0.0", "description": "Linux x64 binary for @kosli/cli", "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/kosli-dev/cli.git", + "directory": "npm/cli-linux-x64" + }, "os": [ "linux" ], diff --git a/npm/cli-win32-arm64/package.json b/npm/cli-win32-arm64/package.json index 9ffddd48a..122531d56 100644 --- a/npm/cli-win32-arm64/package.json +++ b/npm/cli-win32-arm64/package.json @@ -3,6 +3,11 @@ "version": "0.0.0", "description": "Windows arm64 binary for @kosli/cli", "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/kosli-dev/cli.git", + "directory": "npm/cli-win32-arm64" + }, "os": [ "win32" ], diff --git a/npm/cli-win32-x64/package.json b/npm/cli-win32-x64/package.json index cc95acaa4..b24646d1b 100644 --- a/npm/cli-win32-x64/package.json +++ b/npm/cli-win32-x64/package.json @@ -3,6 +3,11 @@ "version": "0.0.0", "description": "Windows x64 binary for @kosli/cli", "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/kosli-dev/cli.git", + "directory": "npm/cli-win32-x64" + }, "os": [ "win32" ], diff --git a/npm/wrapper/package.json b/npm/wrapper/package.json index 897621837..1bba5c209 100644 --- a/npm/wrapper/package.json +++ b/npm/wrapper/package.json @@ -7,7 +7,8 @@ "homepage": "https://kosli.com", "repository": { "type": "git", - "url": "git+https://github.com/kosli-dev/cli.git" + "url": "git+https://github.com/kosli-dev/cli.git", + "directory": "npm/wrapper" }, "bugs": { "url": "https://github.com/kosli-dev/cli/issues" @@ -19,6 +20,9 @@ "supply-chain", "devops" ], + "engines": { + "node": ">=18" + }, "bin": { "kosli": "bin/kosli" }, From 5834afb610e2160a33a1a4fe21d6431c356c36d9 Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Mon, 30 Mar 2026 13:10:00 +0000 Subject: [PATCH 15/32] Documention updates: - npx is not supported - package @kosli/cli should be used to install --- docs.kosli.com/content/getting_started/install.md | 3 +++ npm/README.md | 2 ++ npm/cli-darwin-arm64/README.md | 12 +++++++++++- npm/cli-darwin-x64/README.md | 12 +++++++++++- npm/cli-linux-arm/README.md | 12 +++++++++++- npm/cli-linux-arm64/README.md | 12 +++++++++++- npm/cli-linux-x64/README.md | 12 +++++++++++- npm/cli-win32-arm64/README.md | 12 +++++++++++- npm/cli-win32-x64/README.md | 12 +++++++++++- 9 files changed, 82 insertions(+), 7 deletions(-) diff --git a/docs.kosli.com/content/getting_started/install.md b/docs.kosli.com/content/getting_started/install.md index 2a9ae2701..4541896c7 100644 --- a/docs.kosli.com/content/getting_started/install.md +++ b/docs.kosli.com/content/getting_started/install.md @@ -96,6 +96,9 @@ You can install Kosli CLI system-wide with `npm` from the default registry }} {{< tab "From source" >}} diff --git a/npm/README.md b/npm/README.md index b918d1ff4..e8c1e1341 100644 --- a/npm/README.md +++ b/npm/README.md @@ -49,6 +49,8 @@ npm install -g @kosli/cli npm resolves the `optionalDependencies` declared in the wrapper's `package.json` and installs only the platform-specific package that matches the current OS and CPU architecture — all non-matching packages are silently skipped. The wrapper's `bin/kosli` JS shim then locates the binary inside the installed platform package and executes it. +> **`npx` is not supported.** `npx @kosli/cli` does not install optional dependencies, so the platform binary is never fetched and the command fails. Always install the package before running it. + ## The `bin/` directories are populated by goreleaser The platform package `bin/` directories are **not committed to git**. They are populated automatically during the release process by a post-build hook in [`.goreleaser.yml`](../.goreleaser.yml): diff --git a/npm/cli-darwin-arm64/README.md b/npm/cli-darwin-arm64/README.md index 1e2832d6f..04f0e822f 100644 --- a/npm/cli-darwin-arm64/README.md +++ b/npm/cli-darwin-arm64/README.md @@ -1 +1,11 @@ -This is the macOS ARM 64-bit binary for the Kosli CLI (Apple Silicon). See https://github.com/kosli-dev/cli for details. +# @kosli/cli-darwin-arm64 + +This is the macOS ARM64 platform binary for the Kosli CLI (Apple Silicon). **Do not install this package directly.** + +Install the main package instead, which selects the right binary for your platform automatically: + +```sh +npm install -g @kosli/cli +``` + +See the [Kosli CLI repository](https://github.com/kosli-dev/cli) for documentation and source code. diff --git a/npm/cli-darwin-x64/README.md b/npm/cli-darwin-x64/README.md index 50ec71e1b..a101a35d8 100644 --- a/npm/cli-darwin-x64/README.md +++ b/npm/cli-darwin-x64/README.md @@ -1 +1,11 @@ -This is the macOS 64-bit binary for the Kosli CLI. See https://github.com/kosli-dev/cli for details. +# @kosli/cli-darwin-x64 + +This is the macOS x64 platform binary for the Kosli CLI. **Do not install this package directly.** + +Install the main package instead, which selects the right binary for your platform automatically: + +```sh +npm install -g @kosli/cli +``` + +See the [Kosli CLI repository](https://github.com/kosli-dev/cli) for documentation and source code. diff --git a/npm/cli-linux-arm/README.md b/npm/cli-linux-arm/README.md index 2bcadcc0b..ef4e8a981 100644 --- a/npm/cli-linux-arm/README.md +++ b/npm/cli-linux-arm/README.md @@ -1 +1,11 @@ -This is the Linux ARM 32-bit binary for the Kosli CLI. See https://github.com/kosli-dev/cli for details. +# @kosli/cli-linux-arm + +This is the Linux ARM 32-bit platform binary for the Kosli CLI. **Do not install this package directly.** + +Install the main package instead, which selects the right binary for your platform automatically: + +```sh +npm install -g @kosli/cli +``` + +See the [Kosli CLI repository](https://github.com/kosli-dev/cli) for documentation and source code. diff --git a/npm/cli-linux-arm64/README.md b/npm/cli-linux-arm64/README.md index cbe44264c..00408f7f1 100644 --- a/npm/cli-linux-arm64/README.md +++ b/npm/cli-linux-arm64/README.md @@ -1 +1,11 @@ -This is the Linux ARM 64-bit binary for the Kosli CLI. See https://github.com/kosli-dev/cli for details. +# @kosli/cli-linux-arm64 + +This is the Linux ARM64 platform binary for the Kosli CLI. **Do not install this package directly.** + +Install the main package instead, which selects the right binary for your platform automatically: + +```sh +npm install -g @kosli/cli +``` + +See the [Kosli CLI repository](https://github.com/kosli-dev/cli) for documentation and source code. diff --git a/npm/cli-linux-x64/README.md b/npm/cli-linux-x64/README.md index d9e5df161..e10cc2501 100644 --- a/npm/cli-linux-x64/README.md +++ b/npm/cli-linux-x64/README.md @@ -1 +1,11 @@ -This is the Linux 64-bit binary for the Kosli CLI. See https://github.com/kosli-dev/cli for details. +# @kosli/cli-linux-x64 + +This is the Linux x64 platform binary for the Kosli CLI. **Do not install this package directly.** + +Install the main package instead, which selects the right binary for your platform automatically: + +```sh +npm install -g @kosli/cli +``` + +See the [Kosli CLI repository](https://github.com/kosli-dev/cli) for documentation and source code. diff --git a/npm/cli-win32-arm64/README.md b/npm/cli-win32-arm64/README.md index 60b15f5f1..80031d8a1 100644 --- a/npm/cli-win32-arm64/README.md +++ b/npm/cli-win32-arm64/README.md @@ -1 +1,11 @@ -This is the Windows ARM 64-bit binary for the Kosli CLI. See https://github.com/kosli-dev/cli for details. +# @kosli/cli-win32-arm64 + +This is the Windows ARM64 platform binary for the Kosli CLI. **Do not install this package directly.** + +Install the main package instead, which selects the right binary for your platform automatically: + +```sh +npm install -g @kosli/cli +``` + +See the [Kosli CLI repository](https://github.com/kosli-dev/cli) for documentation and source code. diff --git a/npm/cli-win32-x64/README.md b/npm/cli-win32-x64/README.md index c4b7bc5be..f8347a151 100644 --- a/npm/cli-win32-x64/README.md +++ b/npm/cli-win32-x64/README.md @@ -1 +1,11 @@ -This is the Windows 64-bit binary for the Kosli CLI. See https://github.com/kosli-dev/cli for details. +# @kosli/cli-win32-x64 + +This is the Windows x64 platform binary for the Kosli CLI. **Do not install this package directly.** + +Install the main package instead, which selects the right binary for your platform automatically: + +```sh +npm install -g @kosli/cli +``` + +See the [Kosli CLI repository](https://github.com/kosli-dev/cli) for documentation and source code. From b09a04a240b8ab4c36abcb2064fdd4aaff0cc3aa Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Mon, 30 Mar 2026 14:38:40 +0000 Subject: [PATCH 16/32] Fix three issues in npm postinstall and publish script - postinstall: exit 1 on unsupported platform to match bin/kosli shim behaviour - npm-publish.sh: use process substitution instead of pipe to while loop so set -e catches failures inside the loop - npm-publish.sh: fix out-of-scope max_attempts variable in publish error message --- npm/wrapper/install.js | 7 ++++--- scripts/npm-publish.sh | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/npm/wrapper/install.js b/npm/wrapper/install.js index d364f69c2..10fc10d51 100644 --- a/npm/wrapper/install.js +++ b/npm/wrapper/install.js @@ -18,10 +18,11 @@ const arch = process.arch; if (!SUPPORTED[platform] || !SUPPORTED[platform][arch]) { process.stderr.write( - `[kosli] Note: ${platform}/${arch} is not a supported platform.\n` + - `[kosli] The kosli binary will not be available on this system.\n` + `[kosli] Error: ${platform}/${arch} is not a supported platform.\n` + + `[kosli] See https://github.com/kosli-dev/cli for supported platforms.\n` + + `[kosli] Use --ignore-scripts to skip this check.\n` ); - process.exit(0); + process.exit(1); } const packageName = `@kosli/cli-${platform}-${arch}`; diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index 0c1fdb535..9c35251be 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -33,10 +33,10 @@ else fi # Inject version into all platform package.json files -find npm -name package.json | while IFS= read -r f; do +while IFS= read -r f; do tmp="$(mktemp)" jq --arg v "$VERSION" '.version = $v' "$f" > "$tmp" && mv "$tmp" "$f" -done +done < <(find npm -name package.json) # Also update the optionalDependencies version references in the wrapper tmp="$(mktemp)" @@ -81,6 +81,6 @@ if [ "$DRY_RUN" = false ]; then for PKG_DIR in "${PACKAGES[@]}"; do PKG_NAME="$(basename "$PKG_DIR")" echo "Publishing ${PKG_NAME}..." - npm_publish_with_retry "$PKG_DIR" "$NPM_TAG" || { echo "❌ Failed to publish ${PKG_NAME} after ${max_attempts} attempts"; exit 1; } + npm_publish_with_retry "$PKG_DIR" "$NPM_TAG" || { echo "❌ Failed to publish ${PKG_NAME} after retrying"; exit 1; } done fi From 1a8be2db98287b79d9652affa50974ed4f73be71 Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Mon, 30 Mar 2026 16:54:14 +0200 Subject: [PATCH 17/32] Update scripts/npm-publish.sh Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- scripts/npm-publish.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index 9c35251be..44153dc55 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -81,6 +81,6 @@ if [ "$DRY_RUN" = false ]; then for PKG_DIR in "${PACKAGES[@]}"; do PKG_NAME="$(basename "$PKG_DIR")" echo "Publishing ${PKG_NAME}..." - npm_publish_with_retry "$PKG_DIR" "$NPM_TAG" || { echo "❌ Failed to publish ${PKG_NAME} after retrying"; exit 1; } + npm_publish_with_retry "$PKG_DIR" "$NPM_TAG" || { echo "❌ Failed to publish ${PKG_NAME} after retries"; exit 1; } done fi From 3b4efbcc9b73584bb0e3626324622e22db7debfe Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Mon, 30 Mar 2026 15:30:50 +0000 Subject: [PATCH 18/32] Integrate npm package build and publish into GoReleaser pipeline - Copy each platform binary into its npm package dir via per-build post hooks - Run npm-publish.sh after release (dry-run on snapshots) via after hook - Clean npm bin dirs and tarballs before each build via before hooks --- .goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 8d3b20593..53609aef8 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -93,7 +93,7 @@ nfpms: # GoReleaser will automatically add the binaries. contents: # The src and dst attributes also supports name templates - - src: dist/{{ .ProjectName }}_{{ .Os }}_{{ if .Amd64 }}{{ .Arch }}_v1{{ else if .Arm }}{{ .Arch }}_6{{ else }}{{ .Arch }}{{ end }}/kosli + - src: dist/{{ .ProjectName }}_{{ .Os }}_{{ if .Amd64 }}{{ .Arch }}_v1{{ else if .Arm }}{{ .Arch }}_6{{ else if eq .Arch "arm64" }}{{ .Arch }}_v8.0{{ else }}{{ .Arch }}{{ end }}/kosli dst: /usr/local/bin/kosli after: From fd4d77c3e1bf2f9d926c39ba4a4ddde4c3f2c390 Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Tue, 31 Mar 2026 09:39:15 +0200 Subject: [PATCH 19/32] Update scripts/npm-publish.sh Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- scripts/npm-publish.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index 44153dc55..13ecc2bfc 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -9,7 +9,7 @@ fi # When called from goreleaser, $2 is "true" if snapshot build DRY_RUN=false -if [ "$2" == "true" ] || [ "$2" == "--dry-run" ]; then +# Second argument: "true" or "--dry-run" to skip publishing echo "Running in DRY-RUN mode. Packages will be created but not published." DRY_RUN=true fi From 7d3eb0349a04aeee9bc58d886ca81a3c33a184ca Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Tue, 31 Mar 2026 09:39:35 +0200 Subject: [PATCH 20/32] Update scripts/npm-publish.sh Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- scripts/npm-publish.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index 13ecc2bfc..7e0f5c741 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -36,6 +36,9 @@ fi while IFS= read -r f; do tmp="$(mktemp)" jq --arg v "$VERSION" '.version = $v' "$f" > "$tmp" && mv "$tmp" "$f" +while IFS= read -r f; do + tmp="$(mktemp)" + jq --arg v "$VERSION" '.version = $v' "$f" > "$tmp" && mv "$tmp" "$f" || { rm -f "$tmp"; exit 1; } done < <(find npm -name package.json) # Also update the optionalDependencies version references in the wrapper From 92bc42b8e4e953acee051261a4e12b46c5155e46 Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Tue, 31 Mar 2026 09:46:10 +0200 Subject: [PATCH 21/32] Update scripts/npm-publish.sh Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- scripts/npm-publish.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index 7e0f5c741..1022b54f3 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -33,9 +33,6 @@ else fi # Inject version into all platform package.json files -while IFS= read -r f; do - tmp="$(mktemp)" - jq --arg v "$VERSION" '.version = $v' "$f" > "$tmp" && mv "$tmp" "$f" while IFS= read -r f; do tmp="$(mktemp)" jq --arg v "$VERSION" '.version = $v' "$f" > "$tmp" && mv "$tmp" "$f" || { rm -f "$tmp"; exit 1; } From a4798c90fd81dd4da3a9dc8c84f91b8fceac24de Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Tue, 31 Mar 2026 09:46:54 +0200 Subject: [PATCH 22/32] Update scripts/npm-publish.sh Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- scripts/npm-publish.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index 1022b54f3..5c148f7bb 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -9,7 +9,7 @@ fi # When called from goreleaser, $2 is "true" if snapshot build DRY_RUN=false -# Second argument: "true" or "--dry-run" to skip publishing +if [ "$2" = "true" ] || [ "$2" = "--dry-run" ]; then echo "Running in DRY-RUN mode. Packages will be created but not published." DRY_RUN=true fi From 9fa3c327fc5b10fdce55899feb00a52ebe750b42 Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Tue, 31 Mar 2026 07:54:26 +0000 Subject: [PATCH 23/32] Add --provenance flag to npm publish when running in GitHub Actions --- scripts/npm-publish.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index 5c148f7bb..202dc2ba3 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -65,7 +65,9 @@ npm_publish_with_retry() { local delay=5 for attempt in $(seq 1 "$max_attempts"); do - if (cd "$pkg_dir" && npm publish --tag "$tag"); then + local provenance_flag="" + [ "${GITHUB_ACTIONS:-false}" = "true" ] && provenance_flag="--provenance" + if (cd "$pkg_dir" && npm publish --tag "$tag" $provenance_flag); then return 0 fi if [ "$attempt" -lt "$max_attempts" ]; then From a2221b5fa943eafce75d423a5d4c302954150359 Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Tue, 31 Mar 2026 08:25:51 +0000 Subject: [PATCH 24/32] Fix temp file leak and add npm provenance in GitHub Actions - Clean up temp file on jq/mv failure for wrapper package.json update, consistent with the platform loop - Pass --provenance to npm publish when running in GitHub Actions --- scripts/npm-publish.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index 202dc2ba3..5f5fc38d4 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -7,7 +7,7 @@ if [ -z "$VERSION" ]; then exit 1 fi -# When called from goreleaser, $2 is "true" if snapshot build +# When called from goreleaser, $2 is "--dry-run" if snapshot build DRY_RUN=false if [ "$2" = "true" ] || [ "$2" = "--dry-run" ]; then echo "Running in DRY-RUN mode. Packages will be created but not published." @@ -41,7 +41,7 @@ done < <(find npm -name package.json) # Also update the optionalDependencies version references in the wrapper tmp="$(mktemp)" jq --arg v "$VERSION" '.optionalDependencies = (.optionalDependencies | with_entries(.value = $v))' \ - npm/wrapper/package.json > "$tmp" && mv "$tmp" npm/wrapper/package.json + npm/wrapper/package.json > "$tmp" && mv "$tmp" npm/wrapper/package.json || { rm -f "$tmp"; exit 1; } # Build ordered package list: platform packages first, wrapper last PACKAGES=() From 119cd7db048607d0a9adb6cdcdef157c5bde275f Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Tue, 31 Mar 2026 11:10:51 +0000 Subject: [PATCH 25/32] =?UTF-8?q?Added=20distribution:=20goreleaser-pro=20?= =?UTF-8?q?and=20GORELEASER=5FKEY:=20${{=20secrets.KOSLI=5FGORELEASERPRO?= =?UTF-8?q?=20}}=20=E2=80=94=20that's=20the=20standard=20way=20the=20gorel?= =?UTF-8?q?easer-action=20picks=20up=20the=20pro=20license.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3699ae167..150f4dd85 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -149,12 +149,14 @@ jobs: - name: Run GoReleaser uses: goreleaser/goreleaser-action@v7 with: + distribution: goreleaser-pro version: '~> v2' # latest args: release --clean ${{ steps.get-tag-notes.outputs.args }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} FURY_TOKEN: ${{ secrets.FURY_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + GORELEASER_KEY: ${{ secrets.KOSLI_GORELEASERPRO }} - name: Copy npm packages into dist for provenance run: find npm -name "*.tgz" -exec cp {} dist/ \; From f7866ed1a86df0f178bb95f1dea9dcbfadeb49da Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Tue, 31 Mar 2026 12:50:19 +0000 Subject: [PATCH 26/32] Add npm installation test job to install-script-tests workflow - Test npm install -g @kosli/cli on all 6 supported platforms - Trigger on release (published) to test newly published packages - Also trigger on push/PR to npm/**, .goreleaser.yml, and scripts/npm-publish.sh --- .github/workflows/install-script-tests.yml | 35 +++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/.github/workflows/install-script-tests.yml b/.github/workflows/install-script-tests.yml index f4efeeb16..ce376cf15 100644 --- a/.github/workflows/install-script-tests.yml +++ b/.github/workflows/install-script-tests.yml @@ -7,13 +7,21 @@ on: - '.github/workflows/install-script-tests.yml' - 'bin/test_install_script.sh' - 'bin/test_install_script_over_homebrew.sh' + - 'npm/**' + - '.goreleaser.yml' + - 'scripts/npm-publish.sh' pull_request: paths: - 'install-cli.sh' - '.github/workflows/install-script-tests.yml' - 'bin/test_install_script.sh' - 'bin/test_install_script_over_homebrew.sh' + - 'npm/**' + - '.goreleaser.yml' + - 'scripts/npm-publish.sh' workflow_dispatch: + release: + types: [published] jobs: test-script: @@ -63,4 +71,29 @@ jobs: shell: bash run: | chmod +x install-cli.sh - bash bin/test_install_script_over_homebrew.sh --token ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + bash bin/test_install_script_over_homebrew.sh --token ${{ secrets.GITHUB_TOKEN }} + + # Note: this job installs from the public npm registry, so on push/PR + # it tests the currently published version — not the code being changed. + # That still catches regressions. The release trigger is what tests new releases. + test-npm: + name: Test npm install on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest # linux/x64 + - ubuntu-24.04-arm # linux/arm64 + - macos-latest # darwin/arm64 + - macos-13 # darwin/x64 + - windows-latest # win32/x64 + - windows-11-arm # win32/arm64 + + steps: + - name: Install @kosli/cli via npm + shell: bash + run: npm install -g @kosli/cli + + - name: Verify kosli binary works + shell: bash + run: kosli version \ No newline at end of file From da52c025cc854224d52fc985c8e06fb54230e4ed Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Tue, 31 Mar 2026 14:17:00 +0000 Subject: [PATCH 27/32] Select npm tag snapshot for now --- .github/workflows/install-script-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/install-script-tests.yml b/.github/workflows/install-script-tests.yml index ce376cf15..b79962a61 100644 --- a/.github/workflows/install-script-tests.yml +++ b/.github/workflows/install-script-tests.yml @@ -92,7 +92,7 @@ jobs: steps: - name: Install @kosli/cli via npm shell: bash - run: npm install -g @kosli/cli + run: npm install -g @kosli/cli@snapshot - name: Verify kosli binary works shell: bash From dbbefa8924c28ab34e732d44188b3572d8b115fd Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Tue, 31 Mar 2026 14:28:45 +0000 Subject: [PATCH 28/32] Removed macos-13. macos-13 is the only GitHub-hosted x64 macOS runner but it's not available in all GitHub org configuration --- .github/workflows/install-script-tests.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/install-script-tests.yml b/.github/workflows/install-script-tests.yml index b79962a61..e8a6a1b9f 100644 --- a/.github/workflows/install-script-tests.yml +++ b/.github/workflows/install-script-tests.yml @@ -85,14 +85,19 @@ jobs: - ubuntu-latest # linux/x64 - ubuntu-24.04-arm # linux/arm64 - macos-latest # darwin/arm64 - - macos-13 # darwin/x64 - windows-latest # win32/x64 - windows-11-arm # win32/arm64 steps: - name: Install @kosli/cli via npm shell: bash - run: npm install -g @kosli/cli@snapshot + run: | + TAG="${{ github.event.release.tag_name }}" + if [[ "${{ github.event_name }}" == "release" && "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + npm install -g @kosli/cli@latest + else + npm install -g @kosli/cli@snapshot + fi - name: Verify kosli binary works shell: bash From 6c960ed4c32037503cfd7c57b53d2b1d3dafd309 Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Tue, 31 Mar 2026 15:06:18 +0000 Subject: [PATCH 29/32] Refine dry-run condition in npm-publish script for clarity --- npm/README.md | 44 +++++++++++++++++++++--------------------- scripts/npm-publish.sh | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/npm/README.md b/npm/README.md index e8c1e1341..b7f24cdcc 100644 --- a/npm/README.md +++ b/npm/README.md @@ -7,30 +7,30 @@ This directory contains the npm package structure for distributing the Kosli CLI ``` npm/ ├── wrapper/ # @kosli/cli — the package users install -│ ├── package.json # declares optionalDependencies for all platforms │ ├── bin/kosli # JS shim that detects the platform and runs the binary -│ └── install.js # postinstall script that validates the binary -├── cli-linux-x64/ # @kosli/cli-linux-x64 -│ ├── package.json # declares os/cpu fields for platform filtering -│ └── bin/kosli # the native binary — see below -├── cli-linux-arm64/ # @kosli/cli-linux-arm64 -│ ├── package.json # declares os/cpu fields for platform filtering -│ └── bin/kosli # the native binary — see below -├── cli-linux-arm/ # @kosli/cli-linux-arm -│ ├── package.json # declares os/cpu fields for platform filtering -│ └── bin/kosli # the native binary — see below -├── cli-darwin-x64/ # @kosli/cli-darwin-x64 -│ ├── package.json # declares os/cpu fields for platform filtering -│ └── bin/kosli # the native binary — see below +│ ├── install.js # postinstall script that validates the binary +│ └── package.json # declares optionalDependencies for all platforms ├── cli-darwin-arm64/ # @kosli/cli-darwin-arm64 -│ ├── package.json # declares os/cpu fields for platform filtering -│ └── bin/kosli # the native binary — see below -├── cli-win32-x64/ # @kosli/cli-win32-x64 -│ ├── package.json # declares os/cpu fields for platform filtering -│ └── bin/kosli[.exe] # the native binary — see below -└── cli-win32-arm64/ # @kosli/cli-win32-arm64 - ├── package.json # declares os/cpu fields for platform filtering - └── bin/kosli[.exe] # the native binary — see below +│ ├── bin/kosli # the native binary — see below +│ └── package.json # declares os/cpu fields for platform filtering +├── cli-darwin-x64/ # @kosli/cli-darwin-x64 +│ ├── bin/kosli # the native binary — see below +│ └── package.json # declares os/cpu fields for platform filtering +├── cli-linux-arm/ # @kosli/cli-linux-arm +│ ├── bin/kosli # the native binary — see below +│ └── package.json # declares os/cpu fields for platform filtering +├── cli-linux-arm64/ # @kosli/cli-linux-arm64 +│ ├── bin/kosli # the native binary — see below +│ └── package.json # declares os/cpu fields for platform filtering +├── cli-linux-x64/ # @kosli/cli-linux-x64 +│ ├── bin/kosli # the native binary — see below +│ └── package.json # declares os/cpu fields for platform filtering +├── cli-win32-arm64/ # @kosli/cli-win32-arm64 +│ ├── bin/kosli.exe # the native binary — see below +│ └── package.json # declares os/cpu fields for platform filtering +└── cli-win32-x64/ # @kosli/cli-win32-x64 + ├── bin/kosli.exe # the native binary — see below + └── package.json # declares os/cpu fields for platform filtering ``` ## How it works diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index 5f5fc38d4..b681d5c04 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -9,7 +9,7 @@ fi # When called from goreleaser, $2 is "--dry-run" if snapshot build DRY_RUN=false -if [ "$2" = "true" ] || [ "$2" = "--dry-run" ]; then +if [ "$2" = "--dry-run" ]; then echo "Running in DRY-RUN mode. Packages will be created but not published." DRY_RUN=true fi From 42b73f6ebcbc5c9ac102a2207376d2cd43f23ba5 Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Wed, 1 Apr 2026 12:58:08 +0000 Subject: [PATCH 30/32] Add prepack scripts to fail fast when binary is missing npm pack silently succeeds even if the binary is absent, producing a broken package. Each platform package now checks for bin/kosli (or bin/kosli.exe on Windows) before packing; the wrapper also checks for install.js. --- npm/cli-darwin-arm64/package.json | 3 +++ npm/cli-darwin-x64/package.json | 3 +++ npm/cli-linux-arm/package.json | 3 +++ npm/cli-linux-arm64/package.json | 3 +++ npm/cli-linux-x64/package.json | 3 +++ npm/cli-win32-arm64/package.json | 3 +++ npm/cli-win32-x64/package.json | 3 +++ npm/wrapper/package.json | 1 + 8 files changed, 22 insertions(+) diff --git a/npm/cli-darwin-arm64/package.json b/npm/cli-darwin-arm64/package.json index 3334cce65..d09eb0547 100644 --- a/npm/cli-darwin-arm64/package.json +++ b/npm/cli-darwin-arm64/package.json @@ -20,6 +20,9 @@ "files": [ "bin/" ], + "scripts": { + "prepack": "test -f bin/kosli || (echo 'ERROR: bin/kosli is missing' && exit 1)" + }, "publishConfig": { "registry": "https://registry.npmjs.org", "access": "public" diff --git a/npm/cli-darwin-x64/package.json b/npm/cli-darwin-x64/package.json index 2375d9bd1..a84ffbed4 100644 --- a/npm/cli-darwin-x64/package.json +++ b/npm/cli-darwin-x64/package.json @@ -20,6 +20,9 @@ "files": [ "bin/" ], + "scripts": { + "prepack": "test -f bin/kosli || (echo 'ERROR: bin/kosli is missing' && exit 1)" + }, "publishConfig": { "registry": "https://registry.npmjs.org", "access": "public" diff --git a/npm/cli-linux-arm/package.json b/npm/cli-linux-arm/package.json index 3d8f190a0..862fa0392 100644 --- a/npm/cli-linux-arm/package.json +++ b/npm/cli-linux-arm/package.json @@ -20,6 +20,9 @@ "files": [ "bin/" ], + "scripts": { + "prepack": "test -f bin/kosli || (echo 'ERROR: bin/kosli is missing' && exit 1)" + }, "publishConfig": { "registry": "https://registry.npmjs.org", "access": "public" diff --git a/npm/cli-linux-arm64/package.json b/npm/cli-linux-arm64/package.json index 1bd7e9346..8c7378818 100644 --- a/npm/cli-linux-arm64/package.json +++ b/npm/cli-linux-arm64/package.json @@ -20,6 +20,9 @@ "files": [ "bin/" ], + "scripts": { + "prepack": "test -f bin/kosli || (echo 'ERROR: bin/kosli is missing' && exit 1)" + }, "publishConfig": { "registry": "https://registry.npmjs.org", "access": "public" diff --git a/npm/cli-linux-x64/package.json b/npm/cli-linux-x64/package.json index c3f851316..1a870bb4c 100644 --- a/npm/cli-linux-x64/package.json +++ b/npm/cli-linux-x64/package.json @@ -20,6 +20,9 @@ "files": [ "bin/" ], + "scripts": { + "prepack": "test -f bin/kosli || (echo 'ERROR: bin/kosli is missing' && exit 1)" + }, "publishConfig": { "registry": "https://registry.npmjs.org", "access": "public" diff --git a/npm/cli-win32-arm64/package.json b/npm/cli-win32-arm64/package.json index 122531d56..4fcece13d 100644 --- a/npm/cli-win32-arm64/package.json +++ b/npm/cli-win32-arm64/package.json @@ -20,6 +20,9 @@ "files": [ "bin/" ], + "scripts": { + "prepack": "test -f bin/kosli.exe || (echo 'ERROR: bin/kosli.exe is missing' && exit 1)" + }, "publishConfig": { "registry": "https://registry.npmjs.org", "access": "public" diff --git a/npm/cli-win32-x64/package.json b/npm/cli-win32-x64/package.json index b24646d1b..0c9fa4c41 100644 --- a/npm/cli-win32-x64/package.json +++ b/npm/cli-win32-x64/package.json @@ -20,6 +20,9 @@ "files": [ "bin/" ], + "scripts": { + "prepack": "test -f bin/kosli.exe || (echo 'ERROR: bin/kosli.exe is missing' && exit 1)" + }, "publishConfig": { "registry": "https://registry.npmjs.org", "access": "public" diff --git a/npm/wrapper/package.json b/npm/wrapper/package.json index 1bba5c209..b4a18bae4 100644 --- a/npm/wrapper/package.json +++ b/npm/wrapper/package.json @@ -31,6 +31,7 @@ "install.js" ], "scripts": { + "prepack": "test -f bin/kosli || (echo 'ERROR: bin/kosli is missing' && exit 1) && test -f install.js || (echo 'ERROR: install.js is missing' && exit 1)", "postinstall": "node install.js" }, "optionalDependencies": { From 3dc8c388d01646e5f2ece222468eeef3936273bb Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Wed, 1 Apr 2026 15:40:34 +0000 Subject: [PATCH 31/32] Exclude node_modules from package.json search in npm-publish script --- scripts/npm-publish.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index b681d5c04..4f6d932e0 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -36,7 +36,7 @@ fi while IFS= read -r f; do tmp="$(mktemp)" jq --arg v "$VERSION" '.version = $v' "$f" > "$tmp" && mv "$tmp" "$f" || { rm -f "$tmp"; exit 1; } -done < <(find npm -name package.json) +done < <(find npm -name package.json ! -path '*/node_modules/*') # Also update the optionalDependencies version references in the wrapper tmp="$(mktemp)" From 8d0fefc74801841cef63cb6af68838d95cdac412 Mon Sep 17 00:00:00 2001 From: Jens Brejner Date: Wed, 1 Apr 2026 15:44:24 +0000 Subject: [PATCH 32/32] Refactor binary validation in postinstall script to use fs.accessSync for executable check to address @dangrondahl ai review kosli version false negatives in install.js --- npm/wrapper/install.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/npm/wrapper/install.js b/npm/wrapper/install.js index 10fc10d51..2d73b865d 100644 --- a/npm/wrapper/install.js +++ b/npm/wrapper/install.js @@ -3,7 +3,6 @@ // Postinstall script: validates that the platform binary was installed correctly. // Runs after `npm install @kosli/cli`. -const { execFileSync } = require("child_process"); const path = require("path"); const fs = require("fs"); @@ -53,10 +52,10 @@ if (!fs.existsSync(binaryPath)) { } try { - execFileSync(binaryPath, ["version"], { stdio: "ignore" }); + fs.accessSync(binaryPath, fs.constants.X_OK); } catch (e) { process.stderr.write( - `[kosli] Error: binary validation failed: ${e.message}\n` + + `[kosli] Error: binary is not executable: ${e.message}\n` + `[kosli] Try reinstalling: npm install -g @kosli/cli\n` ); process.exit(1);