Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added dist-npm/planmonster-olkcli-1.10.0-pm.1.tgz
Binary file not shown.
12 changes: 6 additions & 6 deletions docs/npm-publishing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -174,15 +174,15 @@ 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.

### 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.
Expand Down
6 changes: 6 additions & 0 deletions scripts/bootstrap-npm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ if [ -z "$dir" ] || [ ! -d "$dir" ]; then
echo "usage: bash scripts/bootstrap-npm.sh <dir-with-tarballs> [--dry-run] [--tag <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
# <owner>/<repo> 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
Expand Down
113 changes: 113 additions & 0 deletions scripts/test-bootstrap-npm.sh
Original file line number Diff line number Diff line change
@@ -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 <owner>/<repo> 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
Comment on lines +25 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not discard the caller’s working-tree changes.

Both cleanup paths use git checkout, which can erase a developer’s unstaged edits to the package manifests when this regression test is run locally. Snapshot the files and restore those exact bytes, or run the build in an isolated temporary worktree instead. This also prevents the CI working-tree check from masking manifest mutations.

Also applies to: 38-38


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
Comment on lines +69 to +82

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the complete publish sequence, not just its length.

The current check only requires seven publish commands and a launcher-like final argument. It would still pass if a platform package were duplicated, omitted, or published in the wrong position. Capture the seven basenames and compare them with an explicit expected sequence containing each platform package exactly once and the launcher last.


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
Comment on lines +93 to +104

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add an isolated mixed-version fixture.

The “extra/unexpected tarball” case adds an eighth file named 9.9.9; it does not test a valid seven-tarball set containing one package from a different version. Add that scenario separately so version-consistency validation cannot regress without this test failing.


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"
Loading