Skip to content

feat(cli): add 'bluebubbles update' to self-update from npm#9

Merged
anmho merged 1 commit into
mainfrom
feat/cli-update-command
May 26, 2026
Merged

feat(cli): add 'bluebubbles update' to self-update from npm#9
anmho merged 1 commit into
mainfrom
feat/cli-update-command

Conversation

@anmho

@anmho anmho commented May 26, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a top-level update subcommand so bluebubbles update becomes a no-op when current, or upgrades via the user's chosen package manager when behind.

bluebubbles update              # install latest via npm (default)
bluebubbles update --check      # report "up-to-date" or "X → Y" without installing
bluebubbles update --package-manager bun|pnpm|yarn

Reads installed version from package.json, fetches https://registry.npmjs.org/bluebubbles-cli/latest, semver-compares, and either reports up-to-date or invokes the chosen package manager's global-install command for bluebubbles-cli@latest.

Why

bluebubbles update was a natural typing-blind reflex when a new release shipped, but the command didn't exist and the error message (unknown command "update") was unhelpful. Doubles as a way to surface stale installs.

Test plan

  • bun run build passes
  • bun run test (16 tests) passes
  • bun run dev update --check → "bluebubbles-cli is up to date (installed 0.1.7, latest 0.1.7)."
  • After a real release, on a stale machine: bluebubbles update upgrades
  • bluebubbles update --package-manager bun works on bun installs

🤖 Generated with Claude Code

New top-level subcommand:

  bluebubbles update            # install latest via npm (default)
  bluebubbles update --check    # report 'up-to-date' or 'X -> Y' without installing
  bluebubbles update --package-manager bun|pnpm|yarn

Reads installed version from package.json, queries
https://registry.npmjs.org/bluebubbles-cli/latest, compares with
semver, and either reports up-to-date or invokes the chosen package
manager's global-install command for bluebubbles-cli@latest.

Why: 'bluebubbles update' was a natural typing-blind reflex when a
new release shipped, but the command didn't exist and the error
('unknown command "update"') was unhelpful. Doubles as a discoverable
way to flag stale installs.
@anmho anmho merged commit 80a8f04 into main May 26, 2026
1 check passed

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12807736c3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/commands/update.ts
? ["yarn", "global", "add", `${PACKAGE_NAME}@latest`]
: ["npm", "install", "-g", `${PACKAGE_NAME}@latest`];
return new Promise((resolve) => {
const child = spawn(command[0]!, command.slice(1), { stdio: "inherit" });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use shell when spawning package managers on Windows

runInstall calls spawn() with default shell: false, but on Windows the npm/pnpm/yarn entrypoints are .cmd wrappers; Node's child_process docs for “Spawning .bat and .cmd files on Windows” state these cannot be launched directly without a shell. In that environment this path will emit an error and resolve to failure, so bluebubbles update will consistently fail for Windows users even when the package manager is installed.

Useful? React with 👍 / 👎.

Comment thread src/commands/update.ts
Comment on lines +28 to +33
const parse = (s: string) => s.split(".").map((n) => Number.parseInt(n, 10) || 0);
const [aMaj, aMin, aPatch] = parse(a);
const [bMaj, bMin, bPatch] = parse(b);
if (aMaj !== bMaj) return (aMaj ?? 0) - (bMaj ?? 0);
if (aMin !== bMin) return (aMin ?? 0) - (bMin ?? 0);
return (aPatch ?? 0) - (bPatch ?? 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use real semver precedence for prerelease versions

The version comparator strips each dot-separated token with parseInt, which drops prerelease semantics (for example, 1.2.3-beta.1 compares equal to 1.2.3). That causes --check and update decisions to misreport prerelease installs as up to date when a stable release with the same core version exists.

Useful? React with 👍 / 👎.

Comment thread src/commands/update.ts
latest = await fetchLatestVersion();
} catch (error) {
printError(new CliError(error instanceof Error ? error.message : "Failed to query npm registry", "network"));
process.exitCode = 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve typed exit codes for update command failures

This handler constructs typed CliErrors (network, validation) but then hard-codes process.exitCode = 1, which bypasses the exit-code mapping defined in lib/errors.ts. Scripts that distinguish retryable network failures from user validation errors will get the wrong status from bluebubbles update; propagate CliError.exitCode (or throw the error) instead of forcing 1.

Useful? React with 👍 / 👎.

anmho added a commit that referenced this pull request May 26, 2026
## Summary
Merges to `main` no longer sit unreleased. Each merge triggers an automated workflow that:

1. Scans commits since the last release for `feat:` / `fix:` / etc prefixes
2. Opens (or updates) a "release PR" that bumps `package.json`, regenerates `CHANGELOG.md`, and proposes the next semver version
3. When that release PR is merged, the matching `vX.Y.Z` tag is created
4. The existing `release.yml` (already wired to `v*` tag push) handles the npm publish

This fixes the visible bug: PR #8 (private-API doctor check) and PR #9 (`bluebubbles update` command) merged into `main` but never reached npm, because `release.yml` only fires on tag push and nothing was automating the tag.

## Files added
- `.github/workflows/release-please.yml` — runs on every push to `main`
- `release-please-config.json` — release-type=node, v-in-tag=true
- `.release-please-manifest.json` — current version (`0.1.7`)

## What happens after this merges
1. release-please run on `main` scans commits since `v0.1.7`
2. Sees two `feat:` commits → opens "chore(release): 0.2.0" PR with CHANGELOG diff
3. You merge that PR → `v0.2.0` tag + GitHub Release created
4. `release.yml` fires on tag push → `npm publish`

## Implementation note
The bot is [`googleapis/release-please-action`](https://github.com/googleapis/release-please) — Google's standard tool for conventional-commit-driven releases. Internally named "release please" (the polite-ask meme stuck when they open-sourced it in 2020). All the logic lives in their action; this PR is just the config to point it at this repo.

## Requires
- Commit messages already follow conventional commits (`feat:`, `fix:`, `chore:`, etc.) — verified against current log
- No npm secrets/config changes — publish still goes through the existing trusted-publishing setup

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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