Repository navigation
refactor(scripts): functional pipelines in Nushell scripts - #1524
Conversation
The three CI scripts each drove output through a statement sequence where a pipeline expression says the same thing directly. pricing-lock.nu ran `save --append` once per line inside `each`, then discarded the resulting list of nothings with `ignore`. `to text` already renders a list as newline-terminated lines, so the whole thing collapses to one append with no per-iteration side effect. generate-e2e-fixture.nu bound the generated rows to `lines` only to immediately join them, and wrapped each `claude_line` call in a redundant `$"(...)"` interpolation of an already-string value. Feeding the range straight through `each | flatten | to text | save` drops the binding and the string round-trip. Verified byte-identical output against the previous implementation. upsert-pr-comment.nu staged the request body in a `mktemp` file, passed the path to `gh api --input`, and removed it afterwards. `gh api --input -` reads the body from standard input, and pipeline input reaches an external command inside a custom command via `$in`, so the temp file, its `save`, and its `rm` all go away. Callers that send no body pipe nothing, which `run-external` treats as empty stdin. Exercised upsert-pr-comment.nu end to end against a stub `gh` covering the create, update, HTTP 404 recreate, and HTTP 403 skip paths.
ensure-native-binary.nu and stage-native-package.nu both defined `binary_name`
and both turned `otool -L` output into a list of linked dylibs, so the two
copies of the parse had already drifted: the staging copy kept blank rows the
checking copy filtered out. Extract both into a `native-binary.nu` module
alongside them, following the `pricing-lock.nu` precedent for a shared
non-executable module.
`linked-dylibs` returns `{ok, stderr, dylibs}` rather than erroring, because the
two callers want different failure handling — staging aborts with the captured
stderr, the portability check treats an otool failure as not portable.
Selecting dylib rows by their tab indent instead of `skip 1` also fixes a latent
bug. `otool -L` repeats an unindented `<binary> (architecture <arch>):` header
once per architecture, so `skip 1` dropped only the first and fed the remaining
headers through as if they were linked libraries. Any fat binary was therefore
reported as depending on itself, and since a binary path does not start with
/usr/lib/ or /System/Library/ the portability check failed it. Cargo emits
single-architecture binaries, so the shipped path never hit this, and old and
new parsing agree exactly on single-architecture input.
Verified against real Mach-O binaries: identical dylib lists to the previous
implementations for single-architecture input, and /bin/ls now reports its 6
real dylibs and passes the portability check instead of failing it. Also ran a
full darwin staging cycle over a nix-store binary that links nix-store libiconv,
confirming the install_name_tool rewrite and the prepack verification still pass.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
ccusage-guide | 411fded | Commit Preview URL Branch Preview URL |
Jul 28 2026, 12:33 PM |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThe changes add shared Nushell helpers for native binary naming and macOS dylib inspection, update native packaging scripts to use them, simplify GitHub output serialization, and pipe pull request comment request bodies directly into ChangesNative binary tooling
GitHub output scripts
Pull request comment API
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — replaces side-effecting imperative patterns in Nushell CI scripts with functional pipelines, extracts shared Mach-O dylib parsing into a new module, and fixes a latent fat-binary bug in otool -L parsing.
- Functional pipelines in
pricing-lock.nuandgenerate-e2e-fixture.nu—to text | savereplaces per-lineeach+save --append; removed a redundantletbinding and unnecessary$"($expr)"interpolations. Byte-identical output confirmed. - Stdin-driven
gh apiinupsert-pr-comment.nu—gh_api_completeaccepts pipeline input via$inand forwards it togh api --input -, eliminating the temp-file dance.gh_api_jsoncallers (GET, no--input) pipenothing→ empty stdin, so behavior is unchanged. apps/ccusage/scripts/native-binary.numodule —binary-name(kebab-case) andlinked-dylibsextracted from the two native package scripts, following thepricing-lock.numodule precedent.- Fat-binary fix in
linked-dylibs—where {|line| $line | str starts-with (char tab)}replacesskip 1, correctly filtering repeated architecture headers instead of letting them leak into dylib results.
@v0 or keep the SHA fresh with Dependabot | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
ccusage
@ccusage/ccusage-darwin-arm64
@ccusage/ccusage-darwin-x64
@ccusage/ccusage-linux-arm64
@ccusage/ccusage-linux-x64
@ccusage/ccusage-win32-x64
commit: |
ccusage performance comparisonPR SHA: Performance comparison skipped. Base package URL was not ready before 300.000s. Fixture performance comparison requires a base package when --base-dir is not provided. Base package: |
ccusage performance comparisonPR SHA: Performance comparison skipped. Base package URL was not ready before 300.000s. Fixture performance comparison requires a base package when --base-dir is not provided. Base package: |

Summary
Applies the expression-oriented style from the Nushell book (Thinking in Nu, Nu map from functional languages) to the repo's
.nuscripts. An earlier pass (#1485) removed themut/foraccumulators; what was left was side-effecting statement sequences where a pipeline expression says the same thing, plus a duplicatedotoolparse.Two commits, separately revertable.
refactor(scripts): replace side-effecting loops with Nushell pipelinespricing-lock.nu—reportcalledsave --appendonce per line insideeach, then discarded the resulting list of nothings withignore.to textalready renders a list as newline-terminated lines, so this is one append with no per-iteration side effect.generate-e2e-fixture.nu— dropped alet linesbinding that existed only to be immediately joined, and removed a redundant$"(...)"interpolation wrapping an already-stringclaude_lineresult.upsert-pr-comment.nu— the request body was staged in amktempfile, passed asgh api --input <path>, then removed.gh api --input -reads stdin and$inforwards pipeline input to an external inside a custom command, so the temp file, itssave, and itsrmare gone.refactor(scripts): share otool parsing between native package scriptsensure-native-binary.nuandstage-native-package.nueach definedbinary_nameand each parsedotool -Loutput. The two parses had already drifted — the staging copy kept blank rows the checking copy filtered. Both now come from a newapps/ccusage/scripts/native-binary.numodule, following thepricing-lock.nuprecedent for a shared non-executable module.linked-dylibsreturns{ok, stderr, dylibs}instead of erroring, because the callers want different failure handling: staging aborts with the captured stderr, the portability check treats an otool failure as not portable.Drive-by bug fix
Selecting dylib rows by their tab indent instead of
skip 1fixes a latent bug.otool -Lrepeats an unindented<binary> (architecture <arch>):header once per architecture:skip 1dropped only the first header and fed the rest through as linked libraries, so any fat binary was reported as depending on itself — and since a binary path starts with neither/usr/lib/nor/System/Library/, the portability check failed it. Cargo emits single-architecture binaries, so the shipped path never hit this.Verification
nu-checkpasses on all 9.nufiles;just fmt(treefmt/nufmt) reports no changes.generate-e2e-fixture.nuoutput is byte-identical to before (291880 bytes, same sha1).pricing-lock.nureportappends the samechanged=/paths=lines across repeated calls.upsert-pr-comment.nudriven end to end against a stubghcovering create, update, HTTP 404 recreate, and HTTP 403 skip — body arrives as correct JSON on stdin in every write path, and the marker match still picks thegithub-actions[bot]comment over another author's.linked-dylibscompared against both previous implementations on real Mach-O binaries: identical for single-architecture input;/bin/lsnow reports its 6 real dylibs and passes the portability check instead of failing it.libiconv, confirming theinstall_name_toolrewrite to/usr/lib/libiconv.2.dylib, thechmod 755, and theprepackverification all still pass. Test artefacts removed.No user-facing behaviour changes, so no docs impact.
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is enabled.Summary by cubic
Refactors Nushell scripts to use functional pipelines and centralizes Mach-O dylib parsing to remove side effects and duplicate code. Fixes dylib parsing for fat binaries on macOS. No user-facing changes.
Refactors
.github/scriptsand now stream JSON bodies togh apivia stdin (no temp files).binary-nameandlinked-dylibsintoapps/ccusage/scripts/native-binary.nu, used byensure-native-binary.nuandstage-native-package.nu.Bug Fixes
otool -Loutput by selecting tab-indented rows, correctly handling fat binaries with repeated headers.Written for commit 411fded. Summary will update on new commits.
Summary by CodeRabbit
Bug Fixes
Refactor