feat: add registerUpdateCommand to ./commands subpath#9
Merged
Conversation
Extract the `update` and `update switch` commands shared by todoist-cli, twist-cli, and outline-cli into `@doist/cli-core/commands`, parameterised by package name, current version, and config path. Channel set fixed to `'stable'` / `'pre-release'` (npm tags `latest` / `next`). Both subcommands respect `--json` / `--ndjson` via `ViewOptions`. Errors surface as `CliError` (`INVALID_FLAGS`, `UPDATE_CHECK_FAILED`, `UPDATE_INSTALL_FAILED`, plus broken-config translation through `BROKEN_CONFIG_STATE_TO_CODE`). Also introduce `CoreConfig` and `UpdateChannel` at the root config module so future config-owning extractions (auth, token storage) can extend the same shape rather than redeclaring it per CLI. Pure semver helpers (`parseVersion`, `compareVersions`, `isNewer`, `getInstallTag`, `fetchLatestVersion`, `getConfiguredUpdateChannel`) are also exported for ad-hoc use. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
doistbot
reviewed
May 9, 2026
Member
doistbot
left a comment
There was a problem hiding this comment.
This PR successfully extracts the update and update switch subcommands into the core library alongside new shared configuration and semver helpers. Centralizing this logic is a great step toward reducing duplication and ensuring consistent update behavior across all Doist CLIs. A few adjustments are needed regarding cross-platform process execution, package manager detection for global installs, and stdout buffer deadlocks, alongside some smaller refinements for semver parsing, strict config validation, and test coverage.
Parametrise channel reporting, machine-output envelopes, install-spawn shapes, changelog-tip stable/pre-release, registry error paths, switch persistence, and INVALID_FLAGS cases. Flatten the EACCES + non-zero-exit assertions to `rejects.toMatchObject` to drop the `promise.catch` dance, collapse `mockSpawnSuccess` into `mockSpawnExit(0)`, and generalise the permission-error helper to `mockSpawnError(error)`. Same coverage, ~37% fewer lines (448 → ~280). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P1 — bugs that would break installs: - Spawn `npm`/`pnpm` with `shell: process.platform === 'win32'` so the `.cmd` shims resolve on Windows. - Switch stdio to `['ignore', 'ignore', 'pipe']` so a chatty install can't deadlock by filling an unread stdout buffer; stderr stays piped for the CliError hint. - Use the new `readConfigOrThrow` for the channel read and validate the `update_channel` value (`INVALID_UPDATE_CHANNEL`) so a broken or garbage config no longer silently degrades to `'stable'`. - Widen pnpm detection beyond `npm_execpath` (which is unset for globally-installed CLIs run from the shell) to also inspect `process.argv[1]` and `process.execPath`. P2 — substantive: - `update --check` now reports `Downgrade available` when current > latest instead of falsely claiming "Already up to date" before then performing a downgrade in the install flow. - `parseVersion` strips `+build` metadata per semver §10 and throws on inputs that lack a numeric `major.minor.patch`, so `compareVersions` can no longer be fed a typed-but-NaN result. - Request the abbreviated registry payload via `Accept: application/vnd.npm.install-v1+json` (~50× smaller than the default manifest). - Promote the strict-read/CliError translation into shared `readConfigOrThrow` and `updateConfigOrThrow` helpers in `src/config.ts` and consume them from update — switch now does a single read pass instead of pre-checking + re-reading. P3 — refactors: - Extract `formatChannel(channel)` and `runWithSpinner(text, op)` to DRY the chalk-coloured channel rendering and the optional-spinner branching. Tests: - Cover `parseVersion` build-metadata + NaN rejection, `INVALID_UPDATE_CHANNEL`, `--check` downgrade reporting, and the parent-parsed `--json switch` path that the `optsWithGlobals()` workaround was added for. - Verify `withSpinner` is threaded around both the fetch and install ops with the right text/colour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
doist-release-bot Bot
added a commit
that referenced
this pull request
May 9, 2026
## [0.7.0](v0.6.0...v0.7.0) (2026-05-09) ### Features * add registerUpdateCommand to ./commands subpath ([#9](#9)) ([17c6dc7](17c6dc7))
Contributor
|
🎉 This PR is included in version 0.7.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
update+update switchfrom todoist/twist/outline into@doist/cli-core/commandsasregisterUpdateCommand, parameterised by package name, current version, and config path.CoreConfig+UpdateChannelat the root config module so future config-owning extractions (auth, token storage) extend a single shape instead of redeclaring per CLI.CliError(INVALID_FLAGS,UPDATE_CHECK_FAILED,UPDATE_INSTALL_FAILED, plus broken-config translation viaBROKEN_CONFIG_STATE_TO_CODE); both subcommands respect--json/--ndjsonthroughViewOptions.Channel set fixed to
'stable'/'pre-release'(npm tagslatest/next). Pure semver helpers (parseVersion,compareVersions,isNewer,getInstallTag,fetchLatestVersion,getConfiguredUpdateChannel) are also exported. Skill-sync hook stays per-CLI via npmpostinstall— separate concern; todoist / twist / outline wiring tracked as follow-up after the cli-core release.Test plan
npm test— 182 tests pass (34 new insrc/commands/update.test.tscovering semver helpers,--channel,--check, install flow, error paths, switch persist + broken-config translation, JSON / NDJSON envelopes)npm run type-checkcleannpm run check(oxlint + oxfmt) cleannpm run buildcleannpm packcli-core, install into a todoist working copy, swapregisterUpdateCommandin, exercisetd update --check [--json]andtd update switch --pre-releaseagainst a real config🤖 Generated with Claude Code