Skip to content

fix(cli): wheels destroy accepts <type> <name> order (#2313 F16)#2360

Merged
bpamiri merged 1 commit intodevelopfrom
claude/naughty-wu-3369aa
Apr 29, 2026
Merged

fix(cli): wheels destroy accepts <type> <name> order (#2313 F16)#2360
bpamiri merged 1 commit intodevelopfrom
claude/naughty-wu-3369aa

Conversation

@bpamiri
Copy link
Copy Markdown
Collaborator

@bpamiri bpamiri commented Apr 29, 2026

Summary

  • Smart-parse wheels destroy positionals so wheels destroy <type> <name> works (the form the v3 docs and wheels generate both use).
  • Legacy wheels destroy <name> [type] order still recognized — no breaking change.
  • Updated inline help to lead with the preferred <type> <name> form.

Closes the F16 line in #2313:

wheels destroy <type> <name> rejects the documented argument order. The CLI is treating the second positional as the type, so wheels destroy controller Posts errors with "Unknown type: posts. Valid types: resource, model, controller, view". (Tutorial currently falls back to manual rm so this is cosmetic.)

The CLI used to greet users with Unknown type: posts when they followed the docs. Now both orderings dispatch correctly:

==> Test 1: 'wheels destroy controller Posts' (was broken)
The following will be deleted:
  app/controllers/Posts.cfc
  ...
==> Test 2: legacy 'wheels destroy Post model'
The following will be deleted:
  app/models/Post.cfc
  ...

How the parse works

If the first positional matches a known type (resource/model/controller/view), treat it as the type; otherwise fall through to the legacy <name> [type] ordering. The only ambiguity case is "first arg matches a known type" — in which case there's a single sensible interpretation. Existing tests using <name> [type] keep passing because Destroyable doesn't match any known type.

Bonus catch

The first cut used inline # explanation comments in help-text examples. CFML treats # as an expression delimiter inside double-quoted strings, which crashes the whole CFC at parse time (the dreaded Invalid Syntax Closing [#] not found from the project's CLAUDE.md gotcha list). Live-loading the patched module into ~/.wheels/modules/wheels/Module.cfc surfaced this immediately — the unit-test path can't, since a broken file fails to load at all. Switched to parenthetical notes.

Test plan

  • wheels destroy (no args) prints help with new <type> <name> synopsis.
  • wheels destroy controller Posts --force removes app/controllers/Posts.cfc (was rejected before).
  • wheels destroy Post model --force (legacy form) still removes app/models/Post.cfc.
  • wheels destroy User --force (single arg) still defaults to resource.
  • Three new specs in DestroyCommandSpec.cfc covering the new order.
  • Full CLI test suite: 474 pass, 3 pre-existing fails in DoctorSpec > checkMixinCollisions (unrelated, predates this change).

Out of scope

The v3-0-0 destroy guide at web/sites/guides/.../core/destroy.md has separate stale issues (claims there is no --force flag, never mentions a [type] argument). Worth a dedicated docs PR alongside the broader guides rewrite — kept out of this scope to keep the diff minimal.

Other items in #2313 still outstanding (separate PRs):

The CLI documented `wheels destroy [type] [name]` (matching `wheels
generate <type> <name>`) but the parser only accepted `<name> [type]`,
so users following the docs hit "Unknown type: posts" when running
`wheels destroy controller Posts`. Tutorials had to fall back to manual
`rm` to clean up.

Smart-parse positionals so both orderings work:
  - `<type> <name>` — preferred, matches `wheels generate` and v3 docs
  - `<name> [type]` — legacy form still recognized

If the first positional matches a known type (resource/model/controller/
view), treat it as the type; otherwise fall through to the legacy
ordering. No deprecation warning — both forms are intentionally
supported so existing scripts keep working.

Also: avoid unescaped `#` in the help-text string literals (CFML treats
`#` as an expression delimiter inside double quotes; the comment-style
`# remove the User resource` examples crash the whole CFC at parse
time). Use parentheses for the inline note instead.
@bpamiri bpamiri merged commit c39f5e5 into develop Apr 29, 2026
4 checks passed
@bpamiri bpamiri deleted the claude/naughty-wu-3369aa branch April 29, 2026 14:39
bpamiri added a commit that referenced this pull request Apr 29, 2026
Records the four CLI fixes landed in batch B (gitkeep, migrate output,
test runner compile-error surfacing, reload hint) and adds a Batch D
shipped entry now that PR #2361 is merged. Marks #7 closed via the
out-of-band PR #2360 (Rails-style argument order). Crosses out
April 19's #15 ("test runner output format needs verification") which
is subsumed by 2026-04-29 finding #2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bpamiri added a commit that referenced this pull request Apr 29, 2026
* docs(docs): add batch B plan for CLI output polish

Plan covers findings #2, #3, #8, and a new sub-finding (.gitkeep files
not copied by scaffolder) from the 2026-04-29 fresh-VM onboarding
triage. Implementation lands in subsequent commits on this branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cli): copy .gitkeep files so empty test dirs survive git commit

copyTemplateDir() previously skipped any file named .gitkeep with a
comment that they "exist only to keep empty dirs in git" — but skipping
them meant the scaffolded app's tests/specs/{controllers,functional,
models}/ directories vanished on first git commit, contradicting the
tutorial's chapter 1 file tree. Same problem hits app/lib, app/jobs,
app/mailers, public/{files,images,javascripts,stylesheets}, and other
intentionally-empty directories that ship with .gitkeep markers in the
template tree (14 in total).

Copy .gitkeep files byte-for-byte (no placeholder processing — they're
empty by design). Extend NewCommandTemplateSpec to assert the .gitkeep
files exist on disk for three representative paths.

Note on test coverage: a deeper test that scaffolds via the production
copyTemplateDir code path would require reflective invocation of a
private method on Module.cfc; the existing template-existence check
plus CI's full integration run cover the regression sufficiently.

Closes the new sub-finding from
docs/superpowers/plans/2026-04-29-fresh-vm-onboarding-findings.md
(top of the "Shipped" section, surfaced during batch A's Task 0
reconnaissance).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(migration): emit CRLF (not bare CR) so migrator output renders correctly

Migrator.cfc and migrator/Base.cfc::announce() built their output using
bare Chr(13). On macOS, Linux, and the LuCLI out() pipe, bare CR moves
the cursor to column 0 without advancing the line, so subsequent text
overwrites. On a fresh `wheels migrate latest`, the section header,
divider, and per-table summary all collapsed onto a single line where
the tutorial promised three.

Switch every CR to CRLF (Chr(13) & Chr(10)) — 29 occurrences in
Migrator.cfc plus the single line in migrator/Base.cfc::announce().

Add MigratorOutputSpec to pin the announce() contract so future
migrator hacks can't drop the LF again. Update the existing
"is appending announcements" assertion in migrationSpec.cfc that was
previously asserting the buggy bare-CR concatenation.

Closes finding #3 in
docs/superpowers/plans/2026-04-29-fresh-vm-onboarding-findings.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cli): surface specs that fail to compile in wheels test output

TestBox silently skips bundles it cannot instantiate (e.g. CFML parse
errors) — its JSON response shows totalPass: 0 with no failures and no
errors, indistinguishable from "all clear" or "no specs found." The
fresh-VM tutorial run lost ~10 minutes when an unescaped `#` inside a
CSS selector crashed Lucee's parser silently. The user only discovered
the broken spec by loading /wheels/app/tests in a browser.

Add countSpecsOnDisk() and listSpecsOnDisk() helpers to TestRunner
that walk the project's filesystem under variables.projectRoot and
return *Spec.cfc counts/names as dotted bundle names. Wire them into
Module.cfc::displayTestResults: if disk count exceeds TestBox's loaded
bundle count, emit a "WARN  N spec file(s) failed to compile and were
silently skipped:" block listing the unloaded paths, and append "X
failed to load" to the summary line.

The exit code is unchanged — strict-loading mode is intentionally out
of scope. The new code path is best-effort: any probe error is logged
in verbose mode and never crashes the test report.

Closes finding #2 in
docs/superpowers/plans/2026-04-29-fresh-vm-onboarding-findings.md
(subsumes April 19 #15).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cli): hint at cold reload from wheels reload output

`wheels reload` re-fires the framework reload path (?reload=true) but
does NOT re-run onApplicationStart — surprising to users coming from
Rails or Django where restart is the default. Append a one-line cyan
note pointing readers at `wheels stop && wheels start` whenever they
need init code to re-execute.

Pairs with the chapter 6 doc fix in batch A: the contract is now
visible at both surfaces a fresh-VM user encounters (the auth tutorial
and the CLI itself).

Closes finding #8 in
docs/superpowers/plans/2026-04-29-fresh-vm-onboarding-findings.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(docs): mark batch B + D items shipped, close April 19 #15

Records the four CLI fixes landed in batch B (gitkeep, migrate output,
test runner compile-error surfacing, reload hint) and adds a Batch D
shipped entry now that PR #2361 is merged. Marks #7 closed via the
out-of-band PR #2360 (Rails-style argument order). Crosses out
April 19's #15 ("test runner output format needs verification") which
is subsumed by 2026-04-29 finding #2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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