Skip to content

Commit f4f9bfe

Browse files
authored
fix(cli): wheels new throws on remaining silent-exit paths (#2214) (#2221)
Three error paths in scaffoldNewApp() and its caller new() still used `return ""`, producing exit 0 with a friendly stderr message — enough to fool shell automation into thinking scaffolding succeeded. Mirror the #2211/#2216 pattern: keep the out() diagnostic, then throw a typed Wheels.* exception so Picocli's ExecutionExceptionHandler emits exit 1. - Wheels.InvalidArguments — args supplied but none parsed as app name - Wheels.TargetDirectoryExists — target dir already present - Wheels.TemplateNotFound — broken install, templates/app/ missing The zero-args help path at line 395 is intentionally left alone, per the issue's "Not in scope" note: bare `wheels new` prints usage and still exits 0 so the help-seeking UX doesn't regress. Extends the existing #2211 regression shell test with cases for the two new throws that are practical to exercise end-to-end (the template- missing case requires corrupting an installed module, so it's covered by type parity with the other throws rather than a live assertion).
1 parent 2165e79 commit f4f9bfe

2 files changed

Lines changed: 98 additions & 5 deletions

File tree

cli/lucli/Module.cfc

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,13 @@ component extends="modules.BaseModule" {
446446
if (!len(appName)) {
447447
out("Error: app name is required.", "red");
448448
out("Usage: wheels new <appname>");
449-
return "";
449+
// Args were supplied (the zero-args branch above already returned
450+
// usage help) but none parsed as an app name — e.g. `wheels new
451+
// --port=3000`. Throw so LuCLI surfaces a non-zero exit (GH #2214).
452+
throw(
453+
type="Wheels.InvalidArguments",
454+
message="wheels new: app name argument is required"
455+
);
450456
}
451457

452458
// Default datasource to app name, generate random reload password
@@ -3216,7 +3222,12 @@ component extends="modules.BaseModule" {
32163222

32173223
if (directoryExists(targetDir)) {
32183224
out("Directory already exists: #appName#", "red");
3219-
return "";
3225+
// Throw so LuCLI exits non-zero instead of silently succeeding and
3226+
// fooling automation (GH #2214). Done before any files are written.
3227+
throw(
3228+
type="Wheels.TargetDirectoryExists",
3229+
message="wheels new #appName#: target directory already exists at #targetDir#"
3230+
);
32203231
}
32213232

32223233
// Merge defaults for any missing options
@@ -3243,7 +3254,13 @@ component extends="modules.BaseModule" {
32433254
var templateDir = variables.moduleRoot & "templates/app";
32443255
if (!directoryExists(templateDir)) {
32453256
out("Project template not found at: #templateDir#", "red");
3246-
return "";
3257+
// Indicates a broken install (distribution zip missing templates/app/).
3258+
// Throw so LuCLI exits non-zero — otherwise the partial scaffold from
3259+
// an earlier step would look successful to automation (GH #2214).
3260+
throw(
3261+
type="Wheels.TemplateNotFound",
3262+
message="wheels new #appName#: project template directory not found at #templateDir#"
3263+
);
32473264
}
32483265

32493266
// Template variable context — all config values flow through here

cli/lucli/tests/test-new-exit-codes.sh

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
#!/usr/bin/env bash
2-
# Regression test for GH #2211: wheels new must exit non-zero when the
3-
# Wheels framework source cannot be located.
2+
# Regression test for GH #2211 and GH #2214: `wheels new` must exit
3+
# non-zero on every user-facing error path, not silently succeed.
4+
#
5+
# Coverage:
6+
# - #2211: framework source not found
7+
# - #2214: target directory already exists
8+
# - #2214: app name missing (args supplied but none parsed as appname)
49
#
510
# Prerequisites:
611
# - wheels binary on PATH
@@ -63,6 +68,77 @@ else
6368
fail "partial fixture/ directory left behind"
6469
fi
6570

71+
echo ""
72+
echo "=== GH #2214: wheels new target-directory-exists regression ==="
73+
74+
# Use a second tmpdir so we have a predictable pre-existing directory and a
75+
# discoverable framework source (so the only failure surface is the dir check).
76+
TMPDIR2=$(mktemp -d)
77+
cleanup2() { rm -rf "$TMPDIR2"; }
78+
trap 'cleanup; cleanup2' EXIT
79+
80+
# Point at this repo's checkout so we bypass the framework-not-found path
81+
# and exercise only the target-directory-exists path. Resolve the repo root
82+
# relative to this script so it works regardless of cwd.
83+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
84+
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
85+
if [ -d "$REPO_ROOT/vendor/wheels" ]; then
86+
export WHEELS_FRAMEWORK_PATH="$REPO_ROOT/vendor/wheels"
87+
else
88+
echo " SKIP: could not locate vendor/wheels from $REPO_ROOT — skipping #2214 dir-exists case"
89+
echo ""
90+
echo "=== Summary: $PASS pass, $FAIL fail (dir-exists case skipped) ==="
91+
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
92+
fi
93+
94+
cd "$TMPDIR2"
95+
mkdir collision
96+
OUT=$(wheels new collision --no-open-browser 2>&1)
97+
CODE=$?
98+
99+
echo "--- output (last 6 lines) ---"
100+
echo "$OUT" | tail -6
101+
echo "--- exit code: $CODE ---"
102+
echo ""
103+
104+
if [ "$CODE" -ne 0 ]; then
105+
pass "wheels new exits non-zero when target directory already exists"
106+
else
107+
fail "wheels new exited 0 despite target-directory-exists error (GH #2214)"
108+
fi
109+
110+
if echo "$OUT" | grep -qi "already exists"; then
111+
pass "error message mentions 'already exists'"
112+
else
113+
fail "error message missing 'already exists' diagnostic"
114+
fi
115+
116+
echo ""
117+
echo "=== GH #2214: wheels new app-name-missing regression ==="
118+
119+
cd "$TMPDIR2"
120+
# Args supplied, but none parse as an app name. The zero-args path still
121+
# prints usage and exits 0 (see issue "Not in scope"); this case must throw.
122+
OUT=$(wheels new --port=3000 --no-open-browser 2>&1)
123+
CODE=$?
124+
125+
echo "--- output (last 6 lines) ---"
126+
echo "$OUT" | tail -6
127+
echo "--- exit code: $CODE ---"
128+
echo ""
129+
130+
if [ "$CODE" -ne 0 ]; then
131+
pass "wheels new exits non-zero when args supplied but no app name parsed"
132+
else
133+
fail "wheels new exited 0 despite missing app name (GH #2214)"
134+
fi
135+
136+
if echo "$OUT" | grep -qi "app name is required"; then
137+
pass "error message mentions 'app name is required'"
138+
else
139+
fail "error message missing 'app name is required' diagnostic"
140+
fi
141+
66142
echo ""
67143
echo "=== Summary: $PASS pass, $FAIL fail ==="
68144
[ "$FAIL" -eq 0 ]

0 commit comments

Comments
 (0)