diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58c4210..600bb03 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,5 +90,8 @@ jobs: - name: Test npm packaging (pack, install, run) run: bash scripts/test-npm-package.sh + - name: Test the npm bootstrap script + run: bash scripts/test-bootstrap-npm.sh + - name: Verify the working tree is unchanged run: git diff --exit-code diff --git a/dist-npm/planmonster-olk-darwin-arm64-1.10.0-pm.1.tgz b/dist-npm/planmonster-olk-darwin-arm64-1.10.0-pm.1.tgz new file mode 100644 index 0000000..aca8ad2 Binary files /dev/null and b/dist-npm/planmonster-olk-darwin-arm64-1.10.0-pm.1.tgz differ diff --git a/dist-npm/planmonster-olk-darwin-x64-1.10.0-pm.1.tgz b/dist-npm/planmonster-olk-darwin-x64-1.10.0-pm.1.tgz new file mode 100644 index 0000000..a9f3478 Binary files /dev/null and b/dist-npm/planmonster-olk-darwin-x64-1.10.0-pm.1.tgz differ diff --git a/dist-npm/planmonster-olk-linux-arm64-1.10.0-pm.1.tgz b/dist-npm/planmonster-olk-linux-arm64-1.10.0-pm.1.tgz new file mode 100644 index 0000000..86fd05c Binary files /dev/null and b/dist-npm/planmonster-olk-linux-arm64-1.10.0-pm.1.tgz differ diff --git a/dist-npm/planmonster-olk-linux-x64-1.10.0-pm.1.tgz b/dist-npm/planmonster-olk-linux-x64-1.10.0-pm.1.tgz new file mode 100644 index 0000000..934bc24 Binary files /dev/null and b/dist-npm/planmonster-olk-linux-x64-1.10.0-pm.1.tgz differ diff --git a/dist-npm/planmonster-olk-win32-arm64-1.10.0-pm.1.tgz b/dist-npm/planmonster-olk-win32-arm64-1.10.0-pm.1.tgz new file mode 100644 index 0000000..c99370d Binary files /dev/null and b/dist-npm/planmonster-olk-win32-arm64-1.10.0-pm.1.tgz differ diff --git a/dist-npm/planmonster-olk-win32-x64-1.10.0-pm.1.tgz b/dist-npm/planmonster-olk-win32-x64-1.10.0-pm.1.tgz new file mode 100644 index 0000000..f99ee0c Binary files /dev/null and b/dist-npm/planmonster-olk-win32-x64-1.10.0-pm.1.tgz differ diff --git a/dist-npm/planmonster-olkcli-1.10.0-pm.1.tgz b/dist-npm/planmonster-olkcli-1.10.0-pm.1.tgz new file mode 100644 index 0000000..5d963d5 Binary files /dev/null and b/dist-npm/planmonster-olkcli-1.10.0-pm.1.tgz differ diff --git a/docs/npm-publishing.md b/docs/npm-publishing.md index ab10eea..7c803e6 100644 --- a/docs/npm-publishing.md +++ b/docs/npm-publishing.md @@ -32,7 +32,7 @@ unscoped upstream packages and for the scoped fork packages. ## Versioning -The fork must use a version that carries a suffix, for example `0.9.5-pm.1`. +The fork must use a version that carries a suffix, for example `1.10.0-pm.1`. `publish-npm.yml` rejects a bare `X.Y.Z` version. Two reasons: - Upstream `olkcli` is at 1.10.0. A shared number implies a parity that does not @@ -108,8 +108,8 @@ install a launcher that has no binary. Never use either option in CI. ## Release procedure ```sh -git tag npm-v0.9.5-pm.1 -git push origin npm-v0.9.5-pm.1 +git tag npm-v1.10.0-pm.1 +git push origin npm-v1.10.0-pm.1 ``` The tag prefix is `npm-v`, not `v`, so it does not also fire `release.yml` @@ -144,7 +144,7 @@ partial failure is safe. ### From a Daytona sandbox ```sh -npx -y @planmonster/olkcli@0.9.5-pm.1 mail list --json +npx -y @planmonster/olkcli@1.10.0-pm.1 mail list --json ``` Pin the exact version. `@latest` makes an agent runner non-reproducible. @@ -174,7 +174,7 @@ Read `stdout` for the JSON envelope. Ignore `stderr`, which carries hints only. ### As an MCP server ```sh -npx -y @planmonster/olkcli@0.9.5-pm.1 mcp +npx -y @planmonster/olkcli@1.10.0-pm.1 mcp ``` The server is read-only by default and always wraps untrusted text. @@ -182,7 +182,7 @@ The server is read-only by default and always wraps untrusted text. ### Baked into a sandbox image ```sh -npm i -g @planmonster/olkcli@0.9.5-pm.1 +npm i -g @planmonster/olkcli@1.10.0-pm.1 ``` This removes the registry round trip from a cold start. diff --git a/scripts/bootstrap-npm.sh b/scripts/bootstrap-npm.sh index 336e242..0660365 100644 --- a/scripts/bootstrap-npm.sh +++ b/scripts/bootstrap-npm.sh @@ -28,6 +28,12 @@ if [ -z "$dir" ] || [ ! -d "$dir" ]; then echo "usage: bash scripts/bootstrap-npm.sh [--dry-run] [--tag ]" >&2 exit 2 fi +# Resolve to an absolute path before building any tarball argument. npm parses a +# bare relative path like "dist-npm/pkg.tgz" as the GitHub shorthand +# / and tries to clone ssh://git@github.com/dist-npm/pkg.tgz.git, +# which fails with "code 128 ... Permission denied (publickey)". An absolute +# path (or a "./" prefix) is unambiguously a file. +dir="$(cd "$dir" && pwd)" shift dry_run=0 diff --git a/scripts/test-bootstrap-npm.sh b/scripts/test-bootstrap-npm.sh new file mode 100644 index 0000000..999b24d --- /dev/null +++ b/scripts/test-bootstrap-npm.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +# Regression test for scripts/bootstrap-npm.sh. +# +# bash scripts/test-bootstrap-npm.sh +# +# Builds the seven wrapper tarballs (no Go binaries needed — npm packs the +# placeholder layout fine), then exercises the bootstrap script in --dry-run +# mode against a *relative* directory. +# +# The main assertion is that every `npm publish` argument is an absolute path. +# npm parses a bare relative path such as "dist-npm/pkg.tgz" as the GitHub +# shorthand / and tries to clone +# ssh://git@github.com/dist-npm/pkg.tgz.git, failing with +# "npm error code 128 ... Permission denied (publickey)". +# +# Also asserts the publish order (six platform packages, launcher last) and the +# input validation (wrong tarball count, mixed versions, missing platform). +set -euo pipefail + +VERSION="${1:-0.0.0-bootstrap-test.1}" +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$root" + +tmp="$(mktemp -d)" +cleanup() { + git checkout -- npm/olk/package.json npm/olk-*/package.json 2>/dev/null || true + rm -rf "$tmp" +} +trap cleanup EXIT + +echo "==> pack 7 tarballs at $VERSION" +node scripts/build-npm.mjs "$VERSION" \ + --scope @planmonster \ + --registry https://registry.npmjs.org \ + --repository PlanMonster/olkcli >/dev/null +mkdir -p "$tmp/dist-npm" +npm pack --silent --pack-destination "$tmp/dist-npm" ./npm/olk-* ./npm/olk >/dev/null +git checkout -- npm/olk/package.json npm/olk-*/package.json +count="$(ls "$tmp"/dist-npm/*.tgz | wc -l | tr -d ' ')" +[ "$count" -eq 7 ] || { + echo "expected 7 tarballs, packed $count" >&2 + exit 1 +} + +# Run from the parent directory with a RELATIVE argument — the exact shape that +# triggered the git-shorthand misparse. +echo "==> dry run with a relative directory argument" +out="$( cd "$tmp" && bash "$root/scripts/bootstrap-npm.sh" dist-npm --dry-run )" +echo "$out" | sed 's/^/ /' + +echo "==> assert every publish argument is an absolute path" +bad=0 +while IFS= read -r line; do + arg="$(printf '%s' "$line" | sed -n 's/.*npm publish \([^ ]*\).*/\1/p')" + [ -n "$arg" ] || continue + case "$arg" in + /*) ;; + *) + echo " NOT ABSOLUTE: $arg" >&2 + bad=1 + ;; + esac +done <<< "$(printf '%s\n' "$out" | grep 'DRY RUN: npm publish')" +[ "$bad" -eq 0 ] || { + echo "npm would parse a relative path as a git shorthand" >&2 + exit 1 +} + +echo "==> assert publish order: 6 platform packages, launcher last" +order="$(printf '%s\n' "$out" | grep -c 'DRY RUN: npm publish')" +[ "$order" -eq 7 ] || { + echo "expected 7 publish commands, saw $order" >&2 + exit 1 +} +last="$(printf '%s\n' "$out" | grep 'DRY RUN: npm publish' | tail -1)" +case "$last" in + *olkcli-*) ;; + *) + echo "launcher must be published last, saw: $last" >&2 + exit 1 + ;; +esac + +expect_failure() { # description, dir + local desc="$1" d="$2" + if ( cd "$tmp" && bash "$root/scripts/bootstrap-npm.sh" "$d" --dry-run >/dev/null 2>&1 ); then + echo " expected failure: $desc" >&2 + exit 1 + fi + echo " rejected: $desc" +} + +echo "==> assert input validation" +cp -r "$tmp/dist-npm" "$tmp/extra" +cp "$tmp/extra"/*olkcli-*.tgz "$tmp/extra/unrelated-9.9.9.tgz" +expect_failure "an extra/unexpected tarball" extra + +cp -r "$tmp/dist-npm" "$tmp/missing" +rm -f "$tmp/missing"/*olk-win32-arm64*.tgz +expect_failure "a missing platform package" missing + +cp -r "$tmp/dist-npm" "$tmp/empty-ish" +rm -f "$tmp/empty-ish"/*.tgz +expect_failure "no tarballs at all" empty-ish + +if ( cd "$tmp" && bash "$root/scripts/bootstrap-npm.sh" dist-npm --tag --dry-run >/dev/null 2>&1 ); then + echo " expected failure: --tag consuming a following flag" >&2 + exit 1 +fi +echo " rejected: --tag with an option-like value" + +echo +echo "bootstrap script test: PASS"