fix(cli): wheels new emits app-relative paths in 'create' log#2342
Merged
fix(cli): wheels new emits app-relative paths in 'create' log#2342
Conversation
The bug: `wheels new <app>` printed misleading filesystem paths in its per-file `create` log lines. Files that landed correctly under <app>/app/models/, <app>/app/snippets/, etc. on disk showed up in the output as <app>/Model.cfc, <app>/BoxJSON.txt — without the app/ or public/ subdir prefix. Multiple READMEs printed three times because each subdir's README.md flattened to the same logged path. Root cause: `copyTemplateDir()` computed the relative path as `appName & replace(targetPath, arguments.targetDir, "")` — but `targetDir` is the CURRENT recursion's target, not the project root. Each recursion stripped more of the path. A file two levels deep (<root>/app/models/Model.cfc) had `<root>/app/models` stripped, leaving just `<app>/Model.cfc`. Fix: - Add `rootTargetDir` parameter to copyTemplateDir, defaulting to `targetDir` on the initial call. Pass it down through recursion unchanged. Compute relativePath relative to root, not the current level. - Initial caller in scaffoldNewApp passes targetDir as both parameters to anchor the recursion. Also remove a duplicate `create <app>/db/` line from configureSQLiteDatabase: the template copy already emits it when the template ships an empty db/ dir, and configureSQLiteDatabase was re-emitting it. Now only logs creation if it actually had to make the directory. Harness Phase 2 was silently false-PASSing this whole time. Two coordinated fixes there: 1. Both the F3 dup-line check and the misplaced-path check ran their greps against the raw log. `wheels new` emits ANSI color escapes (\x1b[...m) at the start AND end of every line, so `^[[:space:]]*` never matched and the greps returned zero hits regardless of bug state. Strip ANSI to a plain log file, grep against that. 2. The DUP_COUNT computation used `grep -cv '^$' ... || echo 0`, which on zero matches emits "0\n0" (grep returns 0 to stdout AND exits 1 → fallback also emits 0). Replace with `printf '%s' "$DUP_LINES" | grep -v '^$' | wc -l | tr -d ' '` — the same pattern used elsewhere in this file for the misplaced count check. After both fixes: harness Phase 2 reports "F3: no duplicate 'create' lines in wheels new output" and "wheels new prints paths under proper app/ public/ subdirectories", both PASS. Score 43 pass / 0 fail / 2 skip / 45 total — the only remaining tracked SKIP is #2332 (browser install) which needs real Playwright integration work, not a small fix. Fixes #2328. Test plan: - bash tools/test-cli-local.sh — 448 pass / 3 fail / 0 error. Three failures are pre-existing Doctor Service issues (#2260) unrelated. - bash tools/test-onboarding.sh — Phase 2 fully PASSes; manual probe shows `create <app>/app/models/Model.cfc` instead of `<app>/Model.cfc`, no duplicate README lines.
4 tasks
bpamiri
added a commit
that referenced
this pull request
Apr 29, 2026
#2358) The duplicate `create blog/Application.cfc` line reported in #2311 was a side-effect of the `copyTemplateDir()` recursion bug fixed in #2342: both `public/Application.cfc` and `public/miscellaneous/Application.cfc` emitted as the same flattened path. The current scaffold no longer reproduces it (verified via the onboarding harness F3 check and a manual `wheels new blog` run on the worktree), but follow the issue's own suggested approach and add defense-in-depth so a future regression in any `printCreated()` caller can't surface as a confusing duplicate. - `printCreated()` now consults `variables.$createdPathTracker` when set: same path twice is logged as a `verbose()` diagnostic and the user-visible line is suppressed. - `scaffoldNewApp()` opens the tracker at the start of the scaffold and closes it in a `finally` block so generator commands later in the same long-lived process (MCP / REPL) emit unconditionally. - `NewCommandTemplateSpec` now asserts both `public/Application.cfc` and `public/miscellaneous/Application.cfc` exist — the second is an intentional empty override so requests under `miscellaneous/` don't run through Wheels, and we don't want a future "cleanup" deleting it on the assumption it's the duplicate. Closes #2311.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
`wheels new ` printed misleading filesystem paths in its per-file `create` log lines:
```
$ wheels new blog
create blog/.gitignore
create blog/app/
create blog/snippets/
create blog/BoxJSON.txt ← actual: blog/app/snippets/BoxJSON.txt
create blog/ControllerContent.txt ← actual: blog/app/snippets/ControllerContent.txt
...
create blog/migrator/ ← actual: blog/app/migrator/
create blog/Model.cfc ← actual: blog/app/models/Model.cfc
create blog/README.md ← actually multiple READMEs in subdirs, all flattened to root
create blog/README.md ← (printed 3+ times)
```
Files landed correctly under `blog/app/models/`, `blog/app/snippets/`, etc. on disk — only the log was wrong. But it was misleading enough to make the harness's Phase 2 grep miss the path layout entirely (not just because of ANSI escapes — see below).
Root cause
`copyTemplateDir()` computed:
```cfm
var relativePath = arguments.appName & replace(targetPath, arguments.targetDir, "");
```
— but `targetDir` is the CURRENT recursion's target, not the project root. Each recursion level stripped more of the path. A file at `/app/models/Model.cfc` had `/app/models` stripped (the deepest recursion's targetDir), leaving the log line `/Model.cfc`.
Multiple READMEs (one per template subdir) collided into the same logged path for the same reason.
Fix
Added `rootTargetDir` parameter to `copyTemplateDir`, defaulting to `targetDir` on the initial call. Passed unchanged through recursion. Compute `relativePath` relative to root, not the current level. The initial caller in `scaffoldNewApp` passes `targetDir` as both arguments to anchor the recursion.
Also removed a duplicate `create /db/` line from `configureSQLiteDatabase` — the template copy already emits it when the template ships an empty `db/` dir.
Harness Phase 2 was silently false-PASSing
Two separate bugs in the harness's Phase 2 hid this issue for prior runs:
ANSI color escapes broke the grep. Both the F3 dup-line check and the misplaced-path check ran greps with `^[[:space:]]*create` against the raw log. But `wheels new` emits ANSI color escapes (`\x1b[...m`) at the start AND end of every line, so the regex never matched and the greps returned zero hits regardless of bug state. Strip ANSI to a plain log file first.
Multi-line `DUP_COUNT`. The dup-count computation used `grep -cv '^$' ... || echo 0`. On zero matches, `grep -c` emits "0" to stdout AND exits 1, so the `|| echo 0` fallback ALSO ran, producing "0\n0" — which broke `[ "$DUP_COUNT" -eq 0 ]` with "integer expression expected." Replaced with the `printf | grep | wc -l | tr -d ' '` pattern already used elsewhere in this script.
Result
Harness now reports: 43 pass / 0 fail / 2 skip / 45 total. Phase 2 reports both:
```
✓ F3: no duplicate 'create' lines in wheels new output
✓ wheels new prints paths under proper app/ public/ subdirectories
```
Only remaining tracked SKIP is #2332 (browser install) which needs real Playwright integration work, not a small fix.
Test plan
Closes #2328