diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1e9b51e44..052bf6bb0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -143,6 +143,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} FURY_TOKEN: ${{ secrets.FURY_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - uses: actions/upload-artifact@v4 with: diff --git a/.gitignore b/.gitignore index acc7c1a17..3bcee113c 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/*/kosli*.tgz *.tar.gz *~ /.idea diff --git a/.goreleaser.yml b/.goreleaser.yml index 8c3429bf3..60c0ea0ae 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -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 @@ -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: - @@ -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 }}` @@ -80,9 +93,15 @@ 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 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/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: diff --git a/npm/README.md b/npm/README.md new file mode 100644 index 000000000..b918d1ff4 --- /dev/null +++ b/npm/README.md @@ -0,0 +1,162 @@ +# 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 +│ ├── 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 +├── 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 +``` + +## 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. + +## 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 [--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 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 new file mode 100644 index 000000000..ae643592e --- /dev/null +++ b/npm/cli-darwin-arm64/.npmrc @@ -0,0 +1 @@ +//registry.npmjs.org/:_authToken=${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..d48cbf394 --- /dev/null +++ b/npm/cli-darwin-arm64/package.json @@ -0,0 +1,20 @@ +{ + "name": "@kosli/cli-darwin-arm64", + "version": "0.0.0", + "description": "macOS arm64 binary for @kosli/cli", + "license": "MIT", + "os": [ + "darwin" + ], + "cpu": [ + "arm64" + ], + "bin": {"kosli": "bin/kosli"}, + "files": [ + "bin/" + ], + "publishConfig": { + "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 new file mode 100644 index 000000000..ae643592e --- /dev/null +++ b/npm/cli-darwin-x64/.npmrc @@ -0,0 +1 @@ +//registry.npmjs.org/:_authToken=${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..a8b56200c --- /dev/null +++ b/npm/cli-darwin-x64/package.json @@ -0,0 +1,20 @@ +{ + "name": "@kosli/cli-darwin-x64", + "version": "0.0.0", + "description": "macOS x64 binary for @kosli/cli", + "license": "MIT", + "os": [ + "darwin" + ], + "cpu": [ + "x64" + ], + "bin": {"kosli": "bin/kosli"}, + "files": [ + "bin/" + ], + "publishConfig": { + "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 new file mode 100644 index 000000000..ae643592e --- /dev/null +++ b/npm/cli-linux-arm/.npmrc @@ -0,0 +1 @@ +//registry.npmjs.org/:_authToken=${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..6c6b0d61a --- /dev/null +++ b/npm/cli-linux-arm/package.json @@ -0,0 +1,20 @@ +{ + "name": "@kosli/cli-linux-arm", + "version": "0.0.0", + "description": "Linux arm binary for @kosli/cli", + "license": "MIT", + "os": [ + "linux" + ], + "cpu": [ + "arm" + ], + "bin": {"kosli": "bin/kosli"}, + "files": [ + "bin/" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org", + "access": "public" + } +} \ No newline at end of file diff --git a/npm/cli-linux-arm64/.npmrc b/npm/cli-linux-arm64/.npmrc new file mode 100644 index 000000000..ae643592e --- /dev/null +++ b/npm/cli-linux-arm64/.npmrc @@ -0,0 +1 @@ +//registry.npmjs.org/:_authToken=${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..3d2b04516 --- /dev/null +++ b/npm/cli-linux-arm64/package.json @@ -0,0 +1,20 @@ +{ + "name": "@kosli/cli-linux-arm64", + "version": "0.0.0", + "description": "Linux arm64 binary for @kosli/cli", + "license": "MIT", + "os": [ + "linux" + ], + "cpu": [ + "arm64" + ], + "bin": {"kosli": "bin/kosli"}, + "files": [ + "bin/" + ], + "publishConfig": { + "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 new file mode 100644 index 000000000..ae643592e --- /dev/null +++ b/npm/cli-linux-x64/.npmrc @@ -0,0 +1 @@ +//registry.npmjs.org/:_authToken=${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..29e8f51c5 --- /dev/null +++ b/npm/cli-linux-x64/package.json @@ -0,0 +1,20 @@ +{ + "name": "@kosli/cli-linux-x64", + "version": "0.0.0", + "description": "Linux x64 binary for @kosli/cli", + "license": "MIT", + "os": [ + "linux" + ], + "cpu": [ + "x64" + ], + "bin": {"kosli": "bin/kosli"}, + "files": [ + "bin/" + ], + "publishConfig": { + "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 new file mode 100644 index 000000000..ae643592e --- /dev/null +++ b/npm/cli-win32-arm64/.npmrc @@ -0,0 +1 @@ +//registry.npmjs.org/:_authToken=${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..ad2ae567a --- /dev/null +++ b/npm/cli-win32-arm64/package.json @@ -0,0 +1,20 @@ +{ + "name": "@kosli/cli-win32-arm64", + "version": "0.0.0", + "description": "Windows arm64 binary for @kosli/cli", + "license": "MIT", + "os": [ + "win32" + ], + "cpu": [ + "arm64" + ], + "bin": {"kosli": "bin/kosli.exe"}, + "files": [ + "bin/" + ], + "publishConfig": { + "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 new file mode 100644 index 000000000..ae643592e --- /dev/null +++ b/npm/cli-win32-x64/.npmrc @@ -0,0 +1 @@ +//registry.npmjs.org/:_authToken=${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..da12b0b5c --- /dev/null +++ b/npm/cli-win32-x64/package.json @@ -0,0 +1,20 @@ +{ + "name": "@kosli/cli-win32-x64", + "version": "0.0.0", + "description": "Windows x64 binary for @kosli/cli", + "license": "MIT", + "os": [ + "win32" + ], + "cpu": [ + "x64" + ], + "bin": {"kosli": "bin/kosli.exe"}, + "files": [ + "bin/" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org", + "access": "public" + } +} \ No newline at end of file diff --git a/npm/wrapper/.npmrc b/npm/wrapper/.npmrc new file mode 100644 index 000000000..ae643592e --- /dev/null +++ b/npm/wrapper/.npmrc @@ -0,0 +1 @@ +//registry.npmjs.org/:_authToken=${NPM_TOKEN} diff --git a/npm/wrapper/bin/kosli b/npm/wrapper/bin/kosli new file mode 100755 index 000000000..fb2ca85de --- /dev/null +++ b/npm/wrapper/bin/kosli @@ -0,0 +1,46 @@ +#!/usr/bin/env node +"use strict"; + +const { spawnSync } = require("child_process"); +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; +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 ?? 1); diff --git a/npm/wrapper/install.js b/npm/wrapper/install.js new file mode 100644 index 000000000..d364f69c2 --- /dev/null +++ b/npm/wrapper/install.js @@ -0,0 +1,62 @@ +"use strict"; + +// 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"); + +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]) { + 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); +} + +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) { + // 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] 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] Error: binary validation failed: ${e.message}\n` + + `[kosli] Try reinstalling: npm install -g @kosli/cli\n` + ); + process.exit(1); +} diff --git a/npm/wrapper/package.json b/npm/wrapper/package.json new file mode 100644 index 000000000..897621837 --- /dev/null +++ b/npm/wrapper/package.json @@ -0,0 +1,45 @@ +{ + "name": "@kosli/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": { + "@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", + "access": "public" + } +} diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh new file mode 100755 index 000000000..d65f0f469 --- /dev/null +++ b/scripts/npm-publish.sh @@ -0,0 +1,59 @@ +#!/bin/bash +set -e + +VERSION="$1" +if [ -z "$VERSION" ]; then + echo "Usage: $0 " + 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]+$" + +# 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/\(\"@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 + 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 + +# 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