feat: port the shell tools to TypeScript, with types and tests - #2
Conversation
Ports all five commands out of profullstack/scripts: gh-prs, gh-prs-merge, gh-prs-fix-all, tcfeed and domainjson. The point is not the language. It is the two things bash was making expensive. Typed, validated responses. Every gh call went through `jq -r` into a string compare, and `jq -r '.mergeable'` on a response that never had the field prints the four characters `null` — which is not MERGEABLE, so a mergeable PR read as ineligible for a reason nobody wrote, and the failure was indistinguishable from a real verdict. Responses are now parsed once and validated by shape, with the offending field named. Tests. The originals had none, so verifying a change meant running it against live pull requests. 51 tests here stub the subprocess layer, so gh is never invoked and the suite finishes in under a second. gh-prs-merge keeps the --fix behaviour and its refusals: a conflict GitHub declines to merge is left alone with its message printed, a check that ran and failed is a result rather than an obstacle, and there is no --admin. Two structural rules the port enforces: Nothing under bin/ does work at import time. Every entry guards on isMain(import.meta.url) and anything testable lives in src/. That is not decorative — a test that imported bin/gh-prs-fix-all.ts to reach one pure function ran the tool, taking the suite from 60ms to 93 seconds and sweeping live pull requests with --fix implied. isMain resolves the realpath first. These install as symlinks, so argv[1] is the link while import.meta.url is its target; comparing raw reports "imported" for every installed command at once. Commands install as files on PATH rather than shell aliases, because the moshcode pit runs aliases with `zsh -c`, which reads neither ~/.zshrc nor ~/.zsh_aliases. install-links refuses to take over a name it does not own without --force, and refuses a real file even with it. Verified against the originals: gh-prs-merge dry run is byte-identical, and domainjson is set-identical across every record type, rdap key and axfr entry (only DNS round-robin ordering differs). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
ThreatCrush Security Scan0 finding(s) No findings. |
Socket blocked the PR on GHSA-fx2h-pf6j-xcff (high) — vite's `server.fs.deny` bypass on Windows alternate paths. vitest 2.1.9 pulled in vite 5.4.21, and that range is only patched at 6.4.3. vitest 4.1.10 resolves vite 8.2.1, above the 8.0.16 fix. Not exploitable here — it is a dev-server path check in a devDependency of a repo with no dev server — but the fix is a version bump, and arguing for an exception costs more than taking it. All 51 tests pass unchanged on vitest 4. Also corrects the README. It repeated the older tools' claim that the moshcode pit runs aliases with a non-interactive `zsh -c`, which is why they had to be files on PATH. That is no longer true: src/aliases.mjs runs `$SHELL -ic`, which does source ~/.zsh_aliases. Verified both ways and recorded the actual, weaker reason to stay on PATH — a file works from every caller without anything having been sourced first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ports all five commands out of
profullstack/scripts:gh-prs,gh-prs-merge,gh-prs-fix-all,tcfeed,domainjson.Why, given the originals worked
Not the language. The two things bash was making expensive:
Typed, validated responses. Every
ghcall went throughjq -rinto a string compare.jq -r '.mergeable'on a response that never had the field prints the four charactersnull— which is notMERGEABLE, so a perfectly mergeable PR read as ineligible for a reason nobody wrote, and the failure was indistinguishable from a real verdict. Responses are now parsed once and validated by shape, with the offending field named in the error.Tests. The originals had none, so verifying a change meant running it against live pull requests. There are now 51, stubbing the subprocess layer so
ghis never invoked.What carried over unchanged
gh-prs-merge --fixkeeps its behaviour and its refusals:FIXME. Resolving one means choosing between two authors' intent.--admin. Branch protections stay enforced.Two structural rules
Nothing under
bin/does work at import time. Every entry guards onisMain(import.meta.url); anything testable lives insrc/.That is not decorative. A test that imported
bin/gh-prs-fix-all.tsto reach one pure function ran the tool — the suite went from 60ms to 93 seconds and swept live pull requests with--fiximplied. I checked for damage (it only reported; nothing was pushed) and then fixed the shape rather than the symptom. The suite is back to 711ms, and that timing is itself the regression test.isMainresolves the realpath first. These install as symlinks, soprocess.argv[1]is the link whileimport.meta.urlis its target. Comparing them raw reports "imported" for every installed command, disabling all of them at once.Install
Commands install as files on PATH, never shell aliases — the moshcode pit runs aliases with
zsh -c, which reads neither~/.zshrcnor~/.zsh_aliases, so a function defined there is simply not there.The names already point at
~/scripts/bin, so a plain run reports them as not-ours and changes nothing.--forcetakes over a symlink; a real file of the same name is still refused.Verification
--fixpath, response validation, arg parsing, table padding, and time-ago thresholds.pnpm typecheckclean understrict+noUncheckedIndexedAccess+exactOptionalPropertyTypes.gh-prs-mergedry run — byte-identical.domainjson— set-identical across every record type, rdap key and axfr entry. Only array ordering differs, because DNS answers arrive round-robin; that varies between runs of either version.gh-prs— one deliberate change:No open PRs found.instead of a bare header row.Deliberate differences
gh-prsprintsNo open PRs found.rather than a lone header.gh-prs-mergeaddsfixed=to its summary line.🤖 Generated with Claude Code