Skip to content

chore: a readable version, a readable run, and one subject per file - #14

Merged
ttncode merged 5 commits into
mainfrom
chore/version-output-and-structure
Sep 11, 2026
Merged

chore: a readable version, a readable run, and one subject per file#14
ttncode merged 5 commits into
mainfrom
chore/version-output-and-structure

Conversation

@ttncode

@ttncode ttncode commented Sep 11, 2026

Copy link
Copy Markdown
Owner

What this changes

Three things from the review that were left over. None blocked using the toolbox; all three were paid for on every use.

1. A version people can read. git tag returned nothing, so scaffold --version answered with a bare sha and every .scaffold.toml recorded one. CONTRIBUTING now says when a tag is cut and why tagging stays manual: there is no package to publish, and what a tag buys is a readable answer in --version and in every manifest written after it β€” not a distribution channel. v0.1.0 gets tagged on merge.

2. A readable run. scaffold new handed the terminal several minutes of a package manager's progress bars, through which the one line that mattered β€” which application is being generated β€” never appeared at all. It now prints this:

β†’ generating apps/api with nestjs (a framework generator, this takes a few minutes)
β†’ configuring apps/api
β†’ wiring postgres into api
β†’ reconciling the workspace lockfile
β†’ checking quiet-demo's lockfile against the supply-chain policy
β†’ locking the toolchain and committing

created …/quiet-demo

next:
  cd …/quiet-demo
  mise install && mise exec -- lefthook install
  scaffold publish              # create its GitHub repository
  mise run checklist            # what CI will run

The technique is pnpm_install's, which has captured its own output and shown it only on failure since it was written; run_quietly is that, named, so the adapter generators, post-generate hooks and service drivers can use it too. A failing step still prints everything β€” there is a test for exactly that, because hiding output is only acceptable if failure shows all of it. SCAFFOLD_VERBOSE=1 passes output through for a run that hangs rather than fails, where there is otherwise nothing to look at.

3. One subject per file. lib/project.sh carried the pnpm workspace, ADR-0017's supply-chain policy, the config_roots manifest, the GitHub account, the project name rule and the git commit β€” 545 lines whose only relation was being needed by scaffold new.

  • lib/pnpm.sh β€” the workspace and the policy
  • lib/manifest.sh β€” config_roots, the CI matrix derived from it, the build targets derived from the applications
  • lib/project.sh β€” what a project is made of and how it is finished, now 264 lines

cmd_new's all-typescript/mixed branch becomes join_typescript_workspace and keep_apps_standalone. It was an if/else long enough that the condition and its consequence never appeared on screen together; the decision is eleven lines now.

One detail worth the reviewer's eye: the pair passed to those two functions is <rel>:<adapter>, not just the path, because the second branch asks the adapter whether it is TypeScript. "Does it have a package.json" is a different question with a different answer β€” laravel-inertia has one, for vite, and is not TypeScript β€” and substituting it would have been a silent behaviour change inside a refactor.

How it was verified

  • No behaviour change intended in (3), so both branches were generated end to end before and after: an all-TypeScript project (packages/types joined, per-app lockfiles gone, two images built from context .) and a single-adapter one.
  • Every test site that sourced lib/project.sh for a moved function now sources the file that has it, rather than project.sh quietly re-exporting its siblings β€” the dependency is the point of the split.
  • Capturing the output broke three tests that were reading the old invocation text rather than the guarantee behind it. They now assert the guarantee: cli.bats checks the negative too (no value handed to a shell directly, one script runs both through mise exec), and service.bats checks that a driver failure names the service and the family, which is the actual requirement β€” a bare "a driver failed" sends the reader to the wrong one of eight.
  • mise run lint and mise run test-unit green locally; the lanes run here.

Checklist

  • mise run lint passes
  • mise run test-runner passes
  • New behaviour has a test that fails without the change
  • Docs that describe changed behaviour were updated in the same commit
  • No unrelated changes

iam-truongtrungnghia added 5 commits September 11, 2026 23:50
Generating a project handed the terminal several minutes of a package
manager's progress bars, through which the one line that mattered β€” which
application is being generated β€” never appeared at all. It now prints six step
lines and ends with the commands to run next.

The technique is pnpm_install's, which has captured its own output and shown
it only on failure since it was written; run_quietly is that, named, so the
adapter generators, post-generate hooks and service drivers can use it too. A
failing step still prints everything. SCAFFOLD_VERBOSE=1 passes output through
for a run that hangs rather than fails, where there is otherwise nothing to
look at.
It carried the pnpm workspace, ADR-0017's supply-chain policy, the
config_roots manifest, the GitHub account, the project name rule and the git
commit β€” 545 lines whose only relation was being needed by `scaffold new`.

lib/pnpm.sh takes the workspace and the policy; lib/manifest.sh takes
config_roots, the CI matrix derived from it and the build targets derived from
the applications. project.sh keeps what a project is made of and how it is
finished, at 264 lines.

cmd_new's all-typescript/mixed branch becomes join_typescript_workspace and
keep_apps_standalone. It was an if/else long enough that the condition and its
consequence never appeared on screen together; the decision is now eleven
lines. The pair carries <rel>:<adapter> because the second branch asks the
adapter whether it is typescript β€” 'does it have a package.json' is a
different question with a different answer, and laravel-inertia is where the
two part company.

No behaviour change intended: both branches were exercised end to end against
real generations before and after.
Capturing the generators' output moved the invocation, and three tests were
reading the old text rather than the guarantee behind it.

cli.bats now asserts the negative as well: no value may be handed to a shell
directly, and one script runs both through mise exec. That survives the shape
being rearranged, which it has now been once.

service.bats asserts the failure names the service and the family β€” the actual
requirement, since a bare 'a driver failed' sends the reader to the wrong one
of eight β€” instead of one sentence that run_quietly rephrased.
The body check lived in ci.yml, which runs on opened and synchronize. A body
that fails it is fixed by editing the body β€” which ci.yml does not run on β€” so
the check stayed red for a fix that had already been made. Observed on this
branch's own pull request.

Its own workflow rather than adding `edited` to ci.yml: that would re-run the
integration lane, twenty minutes, every time somebody touched a description.
Moving the body check to its own workflow was safe; renaming the job to
`body` in the same commit was not. main requires a status check called
pull-request-body, and a required check that never reports blocks every merge
β€” including the pull request carrying the rename, which is how this was found.

The name is now commented as load-bearing. Nothing in this repository records
that setting, which is exactly what ADR-0004 says about the one guardrail that
is not a file.
@ttncode
ttncode merged commit c994bab into main Sep 11, 2026
18 checks passed
@ttncode
ttncode deleted the chore/version-output-and-structure branch September 11, 2026 17:52
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.

1 participant