Skip to content

ci: run on Blacksmith, add npm packaging gate and a scoped @planmonster publish path - #2

Merged
ajay-bhargava merged 1 commit into
mainfrom
ci/blacksmith-npm-publishing
Jul 27, 2026
Merged

ajay-bhargava merged 1 commit into
mainfrom
ci/blacksmith-npm-publishing

Conversation

@ajay-bhargava

@ajay-bhargava ajay-bhargava commented Jul 27, 2026 •

Copy link
Copy Markdown
Member

Scope

Three things, all fork-specific infrastructure:

  1. Blacksmith runners — all Linux jobs move to blacksmith-4vcpu-ubuntu-2404 with useblacksmith/checkout + useblacksmith/setup-go.
  2. A CI pathway that matches the local gates — plus workflow_dispatch and a new package job that tests npm packaging on every PR.
  3. Our own npm distribution — @planmonster/olkcli + six @planmonster/olk-<os>-<arch> packages, published from CI with Trusted Publishing (no secrets).

Why two jobs stay on GitHub-hosted runners

Job Runner Reason
build-darwin, release macos-latest Blacksmith has no macOS runners; the darwin build needs CGO for the Keychain
any npm publish job ubuntu-latest npm Trusted Publishing accepts cloud-hosted runners only — a Blacksmith runner reports itself as self-hosted in the OIDC claims

Measurements that drove the design

Cross-compile cost for this module, measured locally:

Target Cold cache Warm cache
linux/amd64 9 s 3 s
linux/arm64 507 s 3 s
windows/amd64 457 s 3 s

Hence useblacksmith/setup-go for the build cache. Artifact sizes: 119 MB unpacked per binary package, 23 MB compressed (matches upstream olk-linux-x64@1.10.0 at 118.6 MB).

npm details

  • Scope required: the unscoped names (olkcli, olk-linux-x64, …) belong to upstream.
  • Trigger tag is npm-v*, not v*, so it does not also fire release.yml.
  • Versions must carry a fork suffix (0.9.5-pm.1); a bare X.Y.Z is rejected.
  • Binary packages publish first, launcher last — a consumer never sees a launcher with missing optional dependencies.
  • Re-runs are safe: already-published versions are skipped.
  • npm/olk/bin/olk.js resolves its platform package from its own optionalDependencies, so one launcher serves the unscoped and scoped layouts.

One-time bootstrap is required before the first real publish (npm Trusted Publishing can only be configured on a package that already exists). Documented in docs/npm-publishing.md.

Testing

Local gates, all green (Go 1.26.4, golangci-lint 2.11.4):

  • go mod tidy → no drift; go vet ./..., go build ./... → clean
  • go test -race -count=1 ./... → all packages pass
  • golangci-lint run → 0 issues
  • bash scripts/test-npm-package.sh → PASS in both unscoped and scoped modes: packs, installs, runs olk version --json through the launcher (returns the stamped version), and confirms exit-code propagation (80)
  • All three workflow files parse; job/runner mapping verified

This PR is also the first real test of whether Actions now dispatches on this fork.


View with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is enabled.

Establishes a CI and distribution pathway for this fork.

Runners: all Linux jobs move to blacksmith-4vcpu-ubuntu-2404 with
useblacksmith/checkout + useblacksmith/setup-go. Measured on this module, a
cold cross-compile costs ~8 min per target and a warm one ~3 s, so the
restored Go build cache dominates release time. Two jobs deliberately stay on
GitHub-hosted runners: macOS (Blacksmith has none, and the darwin build needs
CGO for the Keychain) and any npm publish job (npm Trusted Publishing accepts
cloud-hosted runners only — a Blacksmith runner reports itself as self-hosted
in the OIDC claims).

ci.yml also gains workflow_dispatch, so a maintainer can force a run without
pushing an empty commit, and a `package` job that runs the new
scripts/test-npm-package.sh: it builds a host binary, stamps and packs the
wrapper packages, installs the tarballs, runs `olk version` through the JS
launcher, and checks exit-code propagation — in both the unscoped upstream
layout and the scoped fork layout. npm packaging breakage is now a PR failure
instead of a tag-time failure.

npm: publish-npm.yml publishes @planmonster/olkcli plus six
@planmonster/olk-<os>-<arch> packages to npmjs.org via Trusted Publishing.
The unscoped names belong to upstream, so the fork takes a scope. The trigger
tag is npm-v* rather than v*, so it does not also fire release.yml (which
cannot run here: no TAP_GITHUB_TOKEN, and io.github.rlrghb/outlook is not our
MCP namespace). Versions must carry a fork suffix such as 0.9.5-pm.1; a bare
X.Y.Z is rejected because upstream owns those numbers.

scripts/build-npm.mjs gains --scope, --registry, --repository, --access and
--skip-binary-check (one-time bootstrap only); re-scoping is idempotent.
npm/olk/bin/olk.js now resolves its platform package from its own
optionalDependencies instead of a hardcoded name, so one launcher serves both
layouts.

Also corrects the golangci-lint version in AGENTS.md: the workflow pins
v2.11.4, and v2.5.0 cannot lint a module targeting go 1.26.4.
@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds scoped npm packaging for the fork, platform-specific binary publishing, launcher resolution for scoped dependencies, end-to-end package tests, Blacksmith-backed CI, and documentation for fork release and consumption workflows.

Changes

Forked npm package tooling

Layer / File(s) Summary
Package stamping and launcher resolution
scripts/build-npm.mjs, npm/olk/bin/olk.js
Adds scoped package metadata, configurable registries and publishing options, placeholder support, and launcher discovery through optional dependencies.
End-to-end package validation
scripts/test-npm-package.sh
Builds and packages host binaries, tests scoped and unscoped installations, verifies version output, dependency discovery, and exit-code propagation.
Build and publish workflow
.github/workflows/publish-npm.yml
Validates fork versions, builds six platform binaries, supports dry runs, publishes packages, and retries installation verification.
CI and release runner integration
.github/workflows/ci.yml, .github/workflows/release.yml
Adds manual CI dispatch, Blacksmith runners, longer timeouts, package validation, and GitHub-hosted npm publishing guidance.
Fork publishing documentation
AGENTS.md, docs/npm-publishing.md
Documents fork versioning, npm bootstrap and release procedures, OIDC constraints, package consumption, MCP usage, and local testing.

Sequence Diagram(s)

sequenceDiagram
  participant Maintainer
  participant GitHubActions
  participant BuildJobs
  participant NPM
  Maintainer->>GitHubActions: Dispatch workflow or push npm-v tag
  GitHubActions->>BuildJobs: Build platform binaries
  BuildJobs-->>GitHubActions: Upload binary artifacts
  GitHubActions->>NPM: Publish scoped packages
  GitHubActions->>NPM: Verify package installation
Loading
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 markdownlint-cli2 (0.23.0)
AGENTS.md

markdownlint-cli2 wrapper config setup failed
/bin/bash: line 1: printf: write error: No space left on device

docs/npm-publishing.md

markdownlint-cli2 wrapper config setup failed
/bin/bash: line 1: printf: write error: No space left on device


Comment @coderabbitai help to get the list of available commands.

@ajay-bhargava
ajay-bhargava merged commit 9725abe into main Jul 27, 2026
4 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
.github/workflows/publish-npm.yml (1)

176-177: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Pin the npm CLI used by the publish job. npm@latest makes release builds non-reproducible and pulls an unreviewed registry version. Use npm@11.5.1 instead.

Source: Linters/SAST tools

scripts/test-npm-package.sh (1)

42-46: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Defensive-quote the rm -rf targets.

$pkgdir/bin/$exe can't realistically be empty today (guarded by the case-statement bailout and set -u), but Shellcheck's "${var:?}" pattern is a cheap safeguard against future refactors that could leave the variable empty.

🛡️ Optional hardening
-  rm -rf "$tmp" "$pkgdir/bin/$exe"
+  rm -rf "${tmp:?}" "${pkgdir:?}/bin/$exe"

Source: Linters/SAST tools


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1b7d323f-f300-4949-b88e-f4deabe8cf2e

📥 Commits

Reviewing files that changed from the base of the PR and between 3398752 and 1ebc6d7.

📒 Files selected for processing (8)
  • .github/workflows/ci.yml
  • .github/workflows/publish-npm.yml
  • .github/workflows/release.yml
  • AGENTS.md
  • docs/npm-publishing.md
  • npm/olk/bin/olk.js
  • scripts/build-npm.mjs
  • scripts/test-npm-package.sh

Comment on lines +50 to +52
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
version="${{ inputs.version }}"
dry_run="${{ inputs.dry_run }}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove dispatcher input from shell source.

inputs.version is rendered into the script before Bash runs. An authorized workflow dispatcher can break the quoted assignment and execute commands in the version job. Pass it through env and read $GITHUB_EVENT_NAME instead; also reject CR/LF before writing $GITHUB_OUTPUT.

Proposed fix
       - name: Resolve version and mode
         id: v
+        env:
+          DISPATCH_VERSION: ${{ inputs.version }}
+          DISPATCH_DRY_RUN: ${{ inputs.dry_run }}
         run: |
           set -euo pipefail
-          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
-            version="${{ inputs.version }}"
-            dry_run="${{ inputs.dry_run }}"
+          if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
+            version="$DISPATCH_VERSION"
+            dry_run="$DISPATCH_DRY_RUN"
           else
             version="${GITHUB_REF_NAME#npm-v}"
             dry_run="false"
           fi
           version="${version#v}"
+          if [[ "$version" == *$'\n'* || "$version" == *$'\r'* ]]; then
+            echo "version must be a single line" >&2
+            exit 1
+          fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
version="${{ inputs.version }}"
dry_run="${{ inputs.dry_run }}"
- name: Resolve version and mode
id: v
env:
DISPATCH_VERSION: ${{ inputs.version }}
DISPATCH_DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
version="$DISPATCH_VERSION"
dry_run="$DISPATCH_DRY_RUN"
else
version="${GITHUB_REF_NAME#npm-v}"
dry_run="false"
fi
version="${version#v}"
if [[ "$version" == *$'\n'* || "$version" == *$'\r'* ]]; then
echo "version must be a single line" >&2
exit 1
fi
🧰 Tools
🪛 zizmor (1.26.1)

[error] 51-51: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

Source: Linters/SAST tools

@ajay-bhargava
ajay-bhargava deleted the ci/blacksmith-npm-publishing branch July 27, 2026 20:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant