[DX-1453] fix: make npx @ably/cli <command> work without the redundant ably#419
[DX-1453] fix: make npx @ably/cli <command> work without the redundant ably#419mattheworiordan wants to merge 6 commits into
npx @ably/cli <command> work without the redundant ably#419Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis PR fixes Changes
Review Notes
|
There was a problem hiding this comment.
Review Summary
Clean, well-targeted fix. The root cause analysis is correct (two bins pointing at different targets trips npm's resolver) and the solution is the right one. No bugs found.
What this PR does:
- Removes the ably-interactive bin entry from package.json, leaving a single ably bin
- Adds backwards-compat stripping of a redundant leading 'ably' token in bin/run.js so 'npx @ably/cli ably ' keeps working
- Adds a unit test pinning the npm resolution algorithm and a regression guard if a second bin creeps back
- Adds an integration test covering the backwards-compat stripping
Correctness checks:
bin/run.js stripping logic: process.argv.splice(2, 1) fires before execute(), so oclif never sees the redundant token. The process.argv.includes('interactive') check runs after the strip, so 'npx @ably/cli ably interactive' correctly sets ABLY_INTERACTIVE_MODE. No issues.
Edge cases covered:
- No args: process.argv[2] is undefined, comparison is false — safe
- Double 'ably': only the first is stripped, second becomes the command name, oclif returns 'not found' — covered by test 3
- 'ably --help' strips to top-level help — correct
The algorithm copy in the unit test: The test reimplements npm's bin resolution from libnpmexec/lib/get-bin-from-manifest.js and the comment explicitly flags it as an intentional copy kept in sync. This is the right tradeoff: you get a regression guard that fails loudly if a second bin returns, at the cost of the algorithm potentially drifting from npm. Worth the call — this algorithm has been stable across npm major versions.
One thing to coordinate before merge:
The PR description flags it already, but worth calling out explicitly: removing ably-interactive as a global bin BREAKS THE TERMINAL SERVER until the companion Dockerfile symlink lands. These two PRs need to be merged and deployed in the right order. If the terminal server picks up this package version before the Dockerfile change, ably-interactive won't be on PATH and interactive mode will stop working.
This is already documented in the PR — just making sure it's not lost.
Verdict: Approve. The code is correct, the tests cover the key cases, and the coordination dependency is documented. No code changes requested.
There was a problem hiding this comment.
There's still some places in the code where we suggest people use ably-interactive, but by removing it from package.json it'll no longer go on peoples PATH and won't work.
This also needs a terminal server PR because that relies on running ably-interactive assuming that the CLI has placed it on PATH.
Why do we do that instead of
You mean another or this one https://github.com/ably/cli-terminal-server/pull/163? I think that is done |
|
thanks for raising this @mattheworiordan - since we have The only purpose of I'll fix up this PR with @AndyTWF's comments in mind and raise a new terminal server PR too |
This repo records CHANGELOG entries per release — `main` has no [Unreleased] section — so #419's changes are documented at release time, not in the PR. Restores CHANGELOG.md to match `main`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oken `npx @ably/cli init` (and any other command) failed with "could not determine executable to run", forcing users into the verbose `npx -p @ably/cli ably init`. People expect a single command with npx; the fact that we couldn't do that was a packaging smell. Root cause: the package declared two `bin` entries pointing at different targets (`ably` -> ./bin/run.js, `ably-interactive` -> ./bin/ably-interactive). npm's bin resolver (libnpmexec/get-bin-from-manifest) only auto-selects an executable when either every bin points at the same target, or a bin is named after the unscoped package name (`cli`). Two differently-targeted bins, neither named `cli`, hit the "could not determine executable to run" branch. Fix: expose a single `bin` (`ably`) — the same shape every other scoped CLI uses (`@angular/cli` -> ng, `@vue/cli` -> vue). `npx @ably/cli <command>` now resolves deterministically. Verified against npm's own resolver (old manifest throws the exact error; new manifest returns `ably`) and end-to-end via `npx <packed tarball> --version`. Backwards compatible: `npx -p @ably/cli ably <command>` is unaffected, and run.js now drops a redundant leading `ably` token so the historical `npx @ably/cli ably <command>` form keeps working too. There is no top-level `ably` command, so a leading `ably` is unambiguously the repeated bin name. Global installs (`npm install -g @ably/cli` then `ably ...`) are unchanged. The `bin/ably-interactive` auto-restart wrapper script is retained in the package (still used by `ably interactive` and its tests) but is no longer installed as a separate global executable. The one runtime consumer of that binary, ably/cli-terminal-server, installs `@ably/cli@latest`, so its image build needs a companion one-line Dockerfile symlink landed alongside this change. Tests: a hermetic unit test re-implements npm's resolver and asserts the package always resolves to a single `ably` executable; an integration test covers the redundant-`ably` backwards-compatible invocation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pnpm strips the execute bit from node-pty's prebuilt spawn-helper, so `pnpm test:tty` fails with 'posix_spawnp failed'. Add a pretest:tty hook that restores it (no-op on Windows / when already executable). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0d53c7a to
a6c5e81
Compare
a6c5e81 to
555c2ae
Compare
… in-process The ably-interactive bin was a bash wrapper whose only job was to relaunch the CLI after a Ctrl+C force-quit. `ably interactive` already handles Ctrl+C in-process (single Ctrl+C interrupts the running command and returns to the prompt), so the wrapper is redundant — and removing it is what lets the package expose a single `ably` bin (fixing npx). - Delete bin/ably-interactive and the never-wired-up bin/ably-interactive.ps1 - Fix the welcome hint: single Ctrl+C interrupts and returns to the prompt; it no longer (falsely) warns that Ctrl+C exits the shell or points at a wrapper - Non-TTY Ctrl+C now delivers an in-process SIGINT instead of process.exit(130) (there is no wrapper to restart the process) Hosts that want the shell to survive a double-Ctrl+C force-quit (e.g. the terminal server) wrap `ably interactive` in their own restart loop, keyed off exit codes 42/130 with ABLY_WRAPPER_MODE=1 (contract preserved). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sweep user-facing and architecture references now that the wrapper binary is gone and ably interactive handles Ctrl+C in-process: - env-vars: ABLY_HISTORY_FILE example uses `ably interactive`; reword the note (HistoryManager already defaults to ~/.ably/history); update coupled unit test - Troubleshooting/Debugging: accurate Ctrl+C guidance; no wrapper run-commands - Exit-Codes: reframe the exit-code consumer as a host session restart loop - Interactive-REPL: status note marking the bash-wrapper design as superseded - Project-Structure: drop the deleted bin/ably-interactive(.ps1) tree entries - manual tests: run `node bin/run.js interactive`; keep the exit-42 contract Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The old suite spawned bin/ably-interactive (now deleted) to assert wrapper restart/EIO behaviour. Rewrite as interactive-integration.test.ts covering the no-wrapper reality: starts/exits cleanly, a real SIGINT interrupts a running command and the SAME process re-prompts and stays usable, and `exit` emits 42 under ABLY_WRAPPER_MODE (the host restart-loop contract). Real-terminal SIGINT and terminal-corruption/EIO coverage lives in test/tty/commands/interactive-sigint.test.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hoist the spawn/output-capture/waitFor boilerplate into a single `startInteractive()` helper used by all four interactive integration tests. Replace the mid-command 0x03 test with the reachable at-prompt case. A 0x03 (ETX) byte cannot interrupt a *running* command over piped stdin: handleCommand calls rl.pause() during execution, which pauses the stdin stream so the process.stdin "data" handler never receives the byte. The at-prompt branch (emit SIGINT to readline -> "^C" + hint) is the part that actually works in non-TTY mode. Mid-command interruption stays covered by the real-SIGINT test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
50e4676 to
2dcd3f5
Compare
npx @ably/cli <command> work without the redundant ablynpx @ably/cli <command> work without the redundant ably
|
Update since the original description (which covered just the npx fix):
npx behaviour is unchanged from the description; the above is the wrapper-removal follow-through. 🤖 Generated with Claude Code |
|
@claude review |
WalkthroughThis PR fixes `npx @ably/cli ` by collapsing the package to a single `ably` bin — removing the separate `ably-interactive` binary entry that caused npm's resolver to fail to auto-select an executable. The `bin/ably-interactive` bash/PowerShell wrapper scripts are deleted entirely (Ctrl+C is already handled in-process since a prior refactor), and `bin/run.js` gains backwards-compat logic to strip a redundant leading `ably` token so `npx @ably/cli ably ` keeps working for old docs and scripts. All tests and docs are updated to match the new single-bin, wrapper-free design. Changes
Review Notes
|
Whilst watching someone today onboard and try the one click install, it became evident
npx -p @ably/cli ably inithas unnecessary duplication ofably, and looking at Neon for comparison, they've kept it simpler withnpx neonctl initwhich works well.Note trying
npx @ably/cli initfails with "could not determine executable to run", so you fall back tonpx -p @ably/cli ably init. The repeatedablyis pointless, and one command is what people expect from npx. We couldn't give them that, which is a smell. This fixes it.The cause: the package shipped two bins pointing at different files (
ablyandably-interactive). npm's resolver only auto-picks an executable when there's a single bin, or they all point at the same target. Two different ones, so npx can't choose. The fix is a singleablybin, same shape as every other scoped CLI (@angular/cligives young,@vue/cligives youvue).npx @ably/cli <command>now resolves cleanly.Nothing breaks:
npx @ably/cli <command>now worksnpx -p @ably/cli ably <command>still worksnpx @ably/cli ably <command>still works (run.js drops the redundant leadingably)npm install -g @ably/clithenably <command>is unchangedOne thing to coordinate:
ably-interactiveis no longer a separate global binary, though the wrapper script still ships in the package. The only runtime consumer is our terminal server, which installs@ably/cli@latest, so it needs a one-line Dockerfile symlink to land alongside this. Companion PR links to this PR.Tests: a unit test pins the single-bin resolution and fails loudly if a second bin creeps back, and an integration test covers the redundant-
ablyform.Docs follow-up if we're happy with this: the onboarding that says
npx -p @ably/cli ably initcan becomenpx @ably/cli init.