Preserve script order in generated README#2875
Conversation
`devbox generate readme` listed scripts in alphabetical order instead of the order they are defined in devbox.json. This happened because scripts are stored in a map and the README template ranged over it directly; Go's text/template (like map iteration generally) visits keys in sorted order, so user-defined ordering was lost. Add order-preserving APIs driven by the existing hujson AST: - configAST.objectKeysInOrder walks to an object and returns its member names in source order. - ConfigFile.ScriptOrder / Config.ScriptOrder expose the script names in the order they appear (included plugins first, then the root config, matching Scripts' merge precedence). - Scripts.InOrder turns the script map into an ordered []ScriptWithName, appending any scripts not covered by the order slice alphabetically so output stays deterministic. docgen now renders scripts via this ordered slice, and the README template ranges over it. Adds unit tests covering source-order preservation, the alphabetical fallback, and de-duplication of unknown scripts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012kMnjYNx5Y5YzyqLPzeroQ
There was a problem hiding this comment.
Pull request overview
This PR updates devbox generate readme so scripts are rendered in the same order they appear in devbox.json (instead of the deterministic-but-unhelpful ordering produced when ranging over a map in Go templates). It does this by extracting script-name order from the existing hujson AST and by converting the Scripts map into an explicitly ordered slice for templating.
Changes:
- Add AST-backed script order APIs (
configAST.objectKeysInOrder,ConfigFile.ScriptOrder,Config.ScriptOrder) and an ordering helper (Scripts.InOrder). - Update
docgento pass ordered scripts into the README template and update the template to range over that ordered slice. - Add unit tests covering source-order preservation, alphabetical fallback, append behavior, and command propagation.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/devconfig/configfile/scripts.go | Adds ordered iteration support (ScriptWithName, ScriptOrder, InOrder) for scripts stored in a map. |
| internal/devconfig/configfile/scripts_test.go | Adds unit tests for the new script ordering behavior. |
| internal/devconfig/configfile/ast.go | Adds objectKeysInOrder to extract object-member key order from the hujson AST. |
| internal/devconfig/config.go | Exposes merged script-order across included configs and the root config. |
| internal/devbox/docgen/readme.tmpl | Updates template to iterate over an ordered script slice rather than a map. |
| internal/devbox/docgen/docgen.go | Builds ordered scripts (InOrder(cfg.ScriptOrder())) before executing the template. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…rence - Reword the ScriptWithName comment: only text/template sorts map keys; plain Go map iteration is randomized, not alphabetical. - Scripts.InOrder now keeps the last occurrence of a duplicated name in order, so a script overridden by a later (root) definition appears at that definition's position, matching merge precedence. - Add a unit test covering the duplicate-name / keep-last-occurrence case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012kMnjYNx5Y5YzyqLPzeroQ
The add_platforms_flakeref testscript fetches and nix-builds github:F1bonacc1/process-compose from GitHub; it timed out on the previous run. The failure is unrelated to this PR's changes (README script ordering). Empty commit to re-run CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012kMnjYNx5Y5YzyqLPzeroQ
Run on c0a718f flaked on add_platforms_flakeref.test (plugin.test passed); run on 15b1dcb flaked on plugin.test (add_platforms_flakeref passed). A different heavy/network integration testscript fails each run. This PR only changes README generation and additive config script-ordering APIs, which these testscripts do not exercise; build, vet, lint and unit tests all pass. Empty commit to re-run CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012kMnjYNx5Y5YzyqLPzeroQ
|
The remaining red check is a flaky integration test unrelated to this change, which only touches README generation and additive config script-ordering APIs. A different heavy/network testscript failed on each of three runs:
On the latest run, 22/24 checks are green, including The two empty Generated by Claude Code |
Summary
Fixes #1991.
devbox generate readmelisted scripts in alphabetical order instead of the order they are defined indevbox.json. For example, given scripts defined asstep-one,step-two,step-three,step-four, the generated README showed them asstep-four,step-one,step-three,step-two— losing the meaningful ordering authors use to communicate "do this first".Root cause
Scripts are stored in a
map[string]*script, and the README template ranged over that map directly:Go's
text/template(like Go map iteration in general) visits map keys in sorted order, so the user'sdevbox.jsonordering was discarded.Fix
Add order-preserving APIs driven by the existing hujson AST (which already retains source order for comment lookups):
configAST.objectKeysInOrder(path...)— walks to an object and returns its member names in source order.ConfigFile.ScriptOrder()/Config.ScriptOrder()— expose script names in definition order (included plugins first, then the root config, matchingScripts' existing merge precedence).Scripts.InOrder(order)— converts the script map into an ordered[]ScriptWithName, appending any scripts not present inorderalphabetically so output stays deterministic even when the AST is unavailable (e.g. a config not parsed from a file).docgennow builds the ordered slice (cfg.Scripts().WithRelativePaths(...).InOrder(cfg.ScriptOrder())) and the README template ranges over it.Testing
internal/devconfig/configfile/scripts_test.go:devbox.jsongo build ./internal/...,go vet, andgofmtare clean.devboxand randevbox generate readmeon a project whose scripts are definedstep-one/step-two/step-three/step-four; the generated README now lists them in that exact order (both the script list and the "Script Details" section), instead of alphabetically.cc @ametad (issue reporter) — thanks for the clear repro.
🤖 Generated with Claude Code
https://claude.ai/code/session_012kMnjYNx5Y5YzyqLPzeroQ
Generated by Claude Code