Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
24db015
Changes required by Gorelase version > 2
jbrejner Feb 27, 2026
dd7342b
npm-publish script supports conditional publishing.
jbrejner Mar 20, 2026
c43ed83
Create npm packages after goreleaser via a hook script
jbrejner Mar 20, 2026
153ff0d
Update documentation with NPM installation instructions
jbrejner Mar 20, 2026
a79527b
Switch npm package scope to @kosli
jbrejner Mar 23, 2026
9cd8341
Fix: Add the missing bin/kosli JS Shim
jbrejner Mar 25, 2026
0d56678
Fix: Token Variable Mismatch
jbrejner Mar 25, 2026
110bed9
Fix: Silent Postinstall Failures
jbrejner Mar 25, 2026
0731aaf
Update npm/README.md
jbrejner Mar 26, 2026
ceea475
refactor(npm-publish): replace sed/perl with jq and harden publish sc…
jbrejner Mar 30, 2026
3390c14
Fix: Frontmatter formatting
jbrejner Mar 30, 2026
8d539fa
Consistent formatting of package.json files
jbrejner Mar 30, 2026
3121294
Include npm packages in binary provenance processing
jbrejner Mar 30, 2026
f0ab843
Add directory and engines specification to packages
jbrejner Mar 30, 2026
5834afb
Documention updates:
jbrejner Mar 30, 2026
b09a04a
Fix three issues in npm postinstall and publish script
jbrejner Mar 30, 2026
1a8be2d
Update scripts/npm-publish.sh
jbrejner Mar 30, 2026
3b4efbc
Integrate npm package build and publish into GoReleaser pipeline
jbrejner Mar 30, 2026
fd4d77c
Update scripts/npm-publish.sh
jbrejner Mar 31, 2026
7d3eb03
Update scripts/npm-publish.sh
jbrejner Mar 31, 2026
92bc42b
Update scripts/npm-publish.sh
jbrejner Mar 31, 2026
a4798c9
Update scripts/npm-publish.sh
jbrejner Mar 31, 2026
9fa3c32
Add --provenance flag to npm publish when running in GitHub Actions
jbrejner Mar 31, 2026
a2221b5
Fix temp file leak and add npm provenance in GitHub Actions
jbrejner Mar 31, 2026
119cd7d
Added distribution: goreleaser-pro and GORELEASER_KEY: ${{ secrets.KO…
jbrejner Mar 31, 2026
f7866ed
Add npm installation test job to install-script-tests workflow
jbrejner Mar 31, 2026
da52c02
Select npm tag snapshot for now
jbrejner Mar 31, 2026
dbbefa8
Removed macos-13. macos-13 is the only GitHub-hosted x64 macOS runner…
jbrejner Mar 31, 2026
6c960ed
Refine dry-run condition in npm-publish script for clarity
jbrejner Mar 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion .github/workflows/install-script-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -63,4 +71,34 @@ jobs:
shell: bash
run: |
chmod +x install-cli.sh
bash bin/test_install_script_over_homebrew.sh --token ${{ secrets.GITHUB_TOKEN }}
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
- windows-latest # win32/x64
- windows-11-arm # win32/arm64

steps:
- name: Install @kosli/cli via npm
shell: bash
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
run: kosli version
20 changes: 19 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,17 @@ 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/ \;

- uses: actions/upload-artifact@v7
with:
Expand All @@ -164,7 +170,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 }]
Expand All @@ -175,6 +181,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<<nEOFn" >> $GITHUB_OUTPUT
echo "${ARTIFACTS}" >> $GITHUB_OUTPUT
echo "nEOFn" >> $GITHUB_OUTPUT
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/*/kosli*.tgz
*.tar.gz
*~
/.idea
Expand Down
23 changes: 21 additions & 2 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ project_name: kosli
before:
hooks:
- go mod tidy
- rm -rf npm/cli-*/bin
- find npm -name "*.tgz" -delete
builds:
- id: kosli
binary: kosli
Expand All @@ -27,6 +29,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:
-
Expand All @@ -37,11 +52,9 @@ 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 }}`
Expand Down Expand Up @@ -83,6 +96,12 @@ nfpms:
- 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:
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
Expand Down
13 changes: 12 additions & 1 deletion docs.kosli.com/content/getting_started/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,18 @@ curl -L https://github.com/kosli-dev/cli/releases/download/v{{< cli-version >}}/
sudo mv kosli /usr/local/bin/kosli
```

{{< /tab >}}

{{< tab "NPM" >}}
You can install Kosli CLI system-wide with `npm` from the default registry <https://registry.npmjs.org>

```shell {.command}
npm install -g @kosli/cli
```

Using `npx` is currently not supported


{{< /tab >}}

{{< tab "From source" >}}
Expand All @@ -100,7 +112,6 @@ make build

{{< /tabs >}}


## Verifying the installation worked

Run this command:
Expand Down
164 changes: 164 additions & 0 deletions npm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# 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/ # @kosli/cli — the package users install
│ ├── bin/kosli # JS shim that detects the platform and runs the binary
│ ├── install.js # postinstall script that validates the binary
│ └── package.json # declares optionalDependencies for all platforms
├── cli-darwin-arm64/ # @kosli/cli-darwin-arm64
│ ├── 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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Tree diagram formatting. cli-win32-arm64 and cli-win32-x64 both use └── at the same indentation level — the second-to-last should use ├──:

Suggested change
├── bin/kosli.exe # the native binary — see below
├── cli-win32-arm64/ # @kosli/cli-win32-arm64

│ └── 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

Users install a single package:

```sh
npm install @kosli/cli
```

or if using in continuous integration you can install globally:

```sh
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):

```yaml
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}'
```

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

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-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)
```

Each package directory contains an `.npmrc` that sets the auth token:

```text
//registry.npmjs.org/:_authToken=${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 <version> [--dry-run]
```

### Arguments

- `<version>`: 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 `<version>` 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 <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 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.
1 change: 1 addition & 0 deletions npm/cli-darwin-arm64/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
11 changes: 11 additions & 0 deletions npm/cli-darwin-arm64/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# @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.
Loading
Loading