Skip to content

stop: one verb for a port or a name, with free and kill kept as they are - #27

Merged
pyjeebz merged 2 commits into
mainfrom
feat/stop-verb
Nov 3, 2026
Merged

pyjeebz merged 2 commits into
mainfrom
feat/stop-verb

Conversation

@pyjeebz

@pyjeebz pyjeebz commented Nov 3, 2026

Copy link
Copy Markdown
Owner

Closes #21. Phase 2's first piece.

Nobody should have to remember whether the verb takes a port (free) or a name (kill). ports stop <port|name> decides by its argument:

  • A port (3000 or :3000) stops whatever holds it, exactly as free does.
  • Anything else is a name, and stops every match exactly as kill does. Other users' rows it can't stop are skipped, as since kill: other users' pid-less rows are skipped, not failed #17.
  • stop takes the same flags: --force, --dry-run, --expect and --json.

free and kill keep their v0.1.0 behaviour. They shipped, so scripts may call them. They're hidden from --help but otherwise untouched. All three commands are thin wrappers over the same two functions, freePort and killName, so they can't drift apart. They are deliberately not aliases of stop: aliasing would turn free shop from an error (exit 2) into a stop.

Everything points at stop now:

  • --help, the README, the docs (one ports stop section; the old #cmd-free and #cmd-kill anchors still land on it), the quickstart and the man page (ports-stop.1);
  • every hint ports prints: doctor's fixes, the sudo hint on skipped rows, and wait's timeout hint;
  • the landing page's interactive terminal and command list;
  • typo suggestions. stp suggests stop, and a typo of a hidden-but-working command still suggests it (frees → free).

One rule to know: anything all-digits is read as a port, as with wait. So ports stop 2048 stops port 2048. A project literally named 2048 stops by name with ports kill 2048. Both the help text and the docs say this. The integration test hit it for real: the test fixture's temp folder is named 003, and stop 003 read it as port 3.

How it was verified

  • Go gate, locally with CI's versions: unit ✓, build ✓, integration ✓ (uncached), installers ✓, gofmt ✓, vet ✓, lint ✓ (v2.13.2).
  • Site: npm run lint ✓ and npm run build ✓.

stop vs free/kill on real listeners, normalised for pid and start time. Every case gave the same output and the same exit code:

Case Exit
stop 8795 vs free 8795 0
stop :8795 vs free :8795 0
stop shop --json vs kill shop --json 0
--dry-run 0
nothing on the port 1
no name match 1
stop 70000 vs free 70000 2

New TestStopCommand (integration) covers a bare port, :port, a name, and a miss exiting 1. The docs check (#20) now includes stop forms.

Risk

Tier: risky (the kill path and exit codes).

  • No new stopping power: stop only dispatches to the existing free/kill bodies.
  • Not byte-identical, to be precise: the JSON shape is unchanged, but the fix text on kill's skipped rows and doctor's suggested fixes now read ports stop … instead of ports free …. The hidden free and kill no longer have man pages; v0.1.0 had none.
  • Rollback: revert the squash commit. That removes stop and leaves free and kill exactly as they are.

A fresh-context review said ok to ship. It compared old and new binaries side by side: across 14 inputs, free/kill gave identical stderr and exit codes, and no input makes stop act more broadly than free/kill would. Its should-fixes are the second commit:

  • the landing terminal's help line listed here and wait, which it can't answer;
  • its stop didn't reject 0, 70000 and :abc the way the CLI does.

The fix-text change it flagged is named above.

Notes for the release

New ports stop <port|name>: one command for stopping by port or by name. ports free and ports kill still work as before.


Generated by Claude Code

@pyjeebz
pyjeebz merged commit 66bd5b7 into main Nov 3, 2026
5 checks passed
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.

stop: one verb for a port or a name, with free and kill kept as they are

2 participants