Skip to content

fix(cli): wheels new emits app-relative paths in 'create' log#2342

Merged
bpamiri merged 1 commit intodevelopfrom
claude/batch-3-investigation
Apr 27, 2026
Merged

fix(cli): wheels new emits app-relative paths in 'create' log#2342
bpamiri merged 1 commit intodevelopfrom
claude/batch-3-investigation

Conversation

@bpamiri
Copy link
Copy Markdown
Collaborator

@bpamiri bpamiri commented Apr 27, 2026

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:

  1. 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.

  2. 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

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.
@bpamiri bpamiri merged commit 49224ae into develop Apr 27, 2026
4 checks passed
@bpamiri bpamiri deleted the claude/batch-3-investigation branch April 27, 2026 17:48
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.
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.

fix(cli): wheels new prints misleading file paths missing app/ and public/ prefixes

1 participant