Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/4206-packages-install-alias-help.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- `wheels packages` help and messages no longer say `wheels packages install` doesn't work. `install` is an alias of `add`, and the help now says so; `add` stays the documented verb.
53 changes: 19 additions & 34 deletions cli/lucli/Module.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,7 @@ component extends="modules.BaseModule" {
help &= " stats Project statistics (lines of code, model counts, etc.)" & nl;
help &= " notes Find TODO / FIXME / OPTIMIZE comments (--annotations to customize)" & nl & nl;
help &= "Packages & Deployment:" & nl;
help &= " packages Add, update, search Wheels packages (verb is `add`, not `install`)" & nl;
help &= " packages Add (or install), update, search Wheels packages" & nl;
help &= " upgrade Upgrade the Wheels framework in your app (vendor/wheels/); `check` scans, `apply` swaps" & nl;
help &= " framework Install the framework (vendor/wheels/) into an app that has none (e.g. moving off CommandBox)" & nl;
help &= " deploy Deploy your app (Kamal-compatible)" & nl & nl;
Expand Down Expand Up @@ -5209,20 +5209,19 @@ component extends="modules.BaseModule" {
// packages — registry-backed package manager
// ─────────────────────────────────────────────────

// The verb is `add`, NOT `install`. Typing `wheels packages install <name>`
// is intercepted by LuCLI's built-in extension installer before dispatch
// reaches this module, and prints `[INFO] No git or extension dependencies
// to install` without actually installing anything. See chapter 8 of the
// tutorial for the explanation.
// `add` is the documented install verb and `install` is an alias for it
// (see the dispatch below). Older LuCLI runtimes intercepted the literal
// `install` verb before it reached this module (#2713); the runtime the
// CLI ships on now passes it through (#4206).
// Kept out of the docblock below: `wheels packages --help` prints it.
/**
* hint: Add, update, and list Wheels packages (verb is `add`, not `install`)
* hint: Add, update, and list Wheels packages (`install` is an alias of `add`)
*
* Usage:
* wheels packages list [--tag=<tag>]
* wheels packages search <query>
* wheels packages show <name>
* wheels packages add <name>[@<version>] [--force] ← install verb
* wheels packages add <name>[@<version>] [--force] (alias: install)
* wheels packages update <name> --yes
* wheels packages update --all --yes
* wheels packages remove <name>
Expand Down Expand Up @@ -5255,8 +5254,7 @@ component extends="modules.BaseModule" {

// `--help` / `-h` short-circuits to a deterministic help string the
// module owns directly. LuCLI's auto-introspected help previously
// drifted from the real CLI surface — advertising the dead `install`
// verb that LuCLI itself intercepts (#2713). Owning the text here
// drifted from the real CLI surface (#2713). Owning the text here
// guarantees `wheels packages help`, `wheels packages --help`, and
// `wheels packages -h` all reach $packagesHelp().
//
Expand Down Expand Up @@ -5294,19 +5292,13 @@ component extends="modules.BaseModule" {
arguments.opts.name = $packagesRequireArg(arguments.positional, "show requires a name: wheels packages show <name>");
return $packagesMainCli().show(arguments.opts);
case "install":
// LuCLI's built-in extension installer intercepts the
// literal verb `install` on the user-facing CLI surface
// — same trap that bit `wheels browser install` (renamed
// to `wheels browser setup` in #2345). But every other
// caller path reaches this dispatch directly: the
// stdio MCP server (`wheels mcp wheels`), scripted
// in-process clients, and the bundle's own spec suite.
// `PackagesMainCli.install()` has been a transparent
// alias for `add()` since #2729, so the dispatch layer
// must match — otherwise `install <name>` silently
// no-ops on the only paths LuCLI does NOT intercept.
// Fall through to the `add` branch (same validation,
// same error shape, same install behavior).
// `install` is an alias of `add` on every path: the CLI
// (older LuCLI runtimes intercepted the literal verb
// before dispatch; #2713, #4206), the stdio MCP server
// (`wheels mcp wheels`), scripted in-process clients and
// the spec suite. `PackagesMainCli.install()` is the same
// alias (#2729). Fall through to the `add` branch (same
// validation, same error shape, same install behavior).
case "add":
arguments.opts.target = $packagesRequireArg(arguments.positional, "add requires a name: wheels packages add <name>[@<version>]");
return $packagesMainCli().add(arguments.opts);
Expand All @@ -5319,7 +5311,7 @@ component extends="modules.BaseModule" {
case "registry":
return $dispatchPackagesRegistry(arguments.positional, arguments.opts);
default:
throw(message="Unknown packages subcommand: #arguments.sub#. The install verb is `add` (not `install`): wheels packages add <name>");
throw(message="Unknown packages subcommand: #arguments.sub#. To install a package: wheels packages add <name>");
}
}

Expand Down Expand Up @@ -5357,10 +5349,7 @@ component extends="modules.BaseModule" {

// Hand-written help for `wheels packages`. Owned by the module rather than
// auto-derived from picocli introspection because the auto-help drifted
// from the real CLI surface (#2713 — advertised `install <name> [--force]`
// even though LuCLI's built-in extension installer intercepts the literal
// `install` verb before dispatch reaches this module). Same trap that hit
// `wheels browser install` (renamed to `setup` in #2345).
// from the real CLI surface (#2713).
private string function $packagesHelp() {
var nl = chr(10);
var help = "Usage: wheels packages <subcommand> [options]" & nl;
Expand All @@ -5376,12 +5365,8 @@ component extends="modules.BaseModule" {
help &= " registry refresh Bust the 24-hour registry cache" & nl;
help &= " registry info Show the registry URL and cache state" & nl;
help &= " help, --help, -h Show this help" & nl & nl;
help &= "Note: the install verb is `add`, NOT `install`." & nl;
help &= " Typing `wheels packages install <name>` is intercepted by LuCLI's built-in" & nl;
help &= " extension installer before dispatch reaches this module, and prints" & nl;
help &= " '[INFO] No git or extension dependencies to install' without installing" & nl;
help &= " anything. Use `wheels packages add <name>` instead. Same trap that bit" & nl;
help &= " `wheels browser install` (renamed to `wheels browser setup` in ##2345)." & nl & nl;
help &= "Note: `install` is an alias of `add`, so `wheels packages install <name>`" & nl;
help &= " works the same way." & nl & nl;
help &= "Examples:" & nl;
help &= " wheels packages list" & nl;
help &= " wheels packages search ui" & nl;
Expand Down
19 changes: 6 additions & 13 deletions cli/lucli/services/packages/PackagesMainCli.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,10 @@
* update --all --yes
* remove <name>
*
* `add` (not `install`!) is the install verb because LuCLI's built-in
* extension installer intercepts the literal subcommand `install`
* across all modules — same trap that bit `wheels browser install`
* (renamed to `wheels browser setup` in #2345). User input
* `wheels packages install <name>` never reaches Module.cfc; LuCLI
* runs its own dependency installer against `lucee.json` instead and
* prints "No git or extension dependencies to install".
* `add` is the documented install verb; `install` is an alias (see
* install() below). Older LuCLI runtimes intercepted the literal
* subcommand `install` before Module.cfc dispatched it (#2713); the
* runtime the CLI ships on now passes it through (#4206).
*
* Outputs plain text suitable for a terminal. Exit-code semantics are
* the caller's job (Module.cfc throws to signal non-zero on fatal error;
Expand Down Expand Up @@ -168,12 +165,8 @@ component {
return $doInstall(local.parsed.name, local.parsed.pin, local.force);
}

// `install` is preserved as an alias for any in-process callers
// (specs, scripted clients) that haven't migrated to `add`. Note
// that the public CLI surface — `wheels packages install <name>` —
// never reaches this method because LuCLI's built-in extension
// installer intercepts the literal `install` subcommand before
// Module.cfc dispatches. See the component header.
// `install` is an alias for `add` on every path: the CLI, the MCP
// server, specs and scripted clients. See the component header.
public string function install(struct opts = {}) {
return add(argumentCollection = arguments);
}
Expand Down
2 changes: 0 additions & 2 deletions cli/lucli/templates/app/plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ wheels packages add wheels-hotwire
wheels stop && wheels start
```

Note: the install verb is `add`, not `install`.

See [Packages](https://guides.wheels.dev/v4-2-0/digging-deeper/packages/) in the guides for details.

## Migrating from a 3.x plugin
Expand Down
2 changes: 1 addition & 1 deletion cli/lucli/tests/specs/commands/CommandHelpTextSpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ component extends="wheels.wheelstest.system.BaseSpec" {
expect(upgrade).notToInclude("deliberately does nothing");

var packages = mod.showHelp("packages");
expect(packages).toInclude("verb is `add`, not `install`");
expect(packages).toInclude("`install` is an alias of `add`");
expect(packages).notToInclude("chapter 8");
});

Expand Down
45 changes: 32 additions & 13 deletions cli/lucli/tests/specs/commands/PackagesCommandSpec.cfc
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/**
* Tests `wheels packages help` / `wheels packages --help` via Module.cfc.
*
* Issue #2713: the help output must document `add` (not `install`) as the
* canonical install verb, and must explain why `install` does not work
* (LuCLI's built-in extension installer intercepts the literal verb before
* dispatch reaches this module).
* Issue #2713: the help output documents `add` as the canonical install
* verb. Issue #4206: `install` is an alias of `add` that works on the CLI
* too, so the help says so instead of warning that it is intercepted.
*/
component extends="wheels.wheelstest.system.BaseSpec" {

Expand Down Expand Up @@ -60,6 +59,28 @@ component extends="wheels.wheelstest.system.BaseSpec" {

});

// `help` returns the module-owned text before any registry call, so this
// block runs in every environment, unlike the skipped one below (#4206).
describe("wheels packages help — `install` is an alias of `add`", () => {

it("says so in `wheels packages help`, without the old intercept warning", () => {
mod.__arguments = ["help"];
var out = mod.packages();
expect(out).toInclude("wheels packages add");
expect(out).toInclude("`install` is an alias of `add`");
expect(REFindNoCase("intercept", out)).toBe(0);
expect(out).notToInclude("NOT `install`");
});

it("does not warn against `install` in the command or global help", () => {
var commandHelp = mod.showHelp("packages");
expect(commandHelp).toInclude("`install` is an alias of `add`");
expect(commandHelp).notToInclude("not `install`");
expect(mod.showHelp()).notToInclude("not `install`");
});

});

xdescribe("wheels packages help", () => {

it("treats `help` positional as a help request (no network call)", () => {
Expand Down Expand Up @@ -89,19 +110,19 @@ component extends="wheels.wheelstest.system.BaseSpec" {
expect(out).toInclude("wheels packages add");
});

it("does not advertise `install <name>` as a working verb", () => {
it("lists `add`, not `install`, as the subcommand row", () => {
mod.__arguments = ["help"];
var out = mod.packages();
// The historic help row "install <name> [--force] Install a package"
// must not appear — it advertises a verb that LuCLI intercepts.
// `add` is the documented verb; `install` appears only as its alias.
expect(REFindNoCase("install[[:space:]]+<name>[[:space:]]+\[--force\][[:space:]]+Install a package", out)).toBe(0);
});

it("explains that `install` is intercepted by LuCLI", () => {
it("says `install` is an alias of `add`, not that it is intercepted", () => {
mod.__arguments = ["help"];
var out = mod.packages();
expect(out).toInclude("LuCLI");
expect(REFindNoCase("intercept", out)).toBeGT(0);
expect(out).toInclude("`install` is an alias of `add`");
expect(REFindNoCase("intercept", out)).toBe(0);
expect(out).notToInclude("NOT `install`");
});

it("lists every canonical sub-verb", () => {
Expand All @@ -127,9 +148,7 @@ component extends="wheels.wheelstest.system.BaseSpec" {
// silently got nothing back when typing `install` — even though
// `PackagesMainCli.install()` itself has always been a true alias
// for `add()`. The alias must be wired through the dispatch
// layer too, so that the only place `install` ever no-ops is the
// LuCLI extension-installer intercept (which we cannot patch),
// and every in-process caller gets the same behavior as `add`.
// layer too, so every caller gets the same behavior as `add`.
it("throws the same BadInput error as `add` when name is missing", () => {
mod.__arguments = ["install"];
var threw = {flag: false, message: ""};
Expand Down
5 changes: 2 additions & 3 deletions vendor/wheels/public/views/packagelist.cfm
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,8 @@ for (local.key in packageMeta) {
<cfelse>
<!--- rp.name is registry-schema-constrained to ^[a-z0-9][a-z0-9-]*$, so both id and JS string are already safe.
JSStringFormat() is applied in the onclick defensively in case that invariant ever loosens.
`add` (not `install`) is the canonical verb — LuCLI's built-in extension
installer intercepts the literal subcommand `install`, so `wheels packages
install <name>` never reaches Module.cfc. See PR #2374 / cli/lucli/services/packages/PackagesMainCli.cfc. --->
`add` is the canonical verb and works on every 4.x CLI; `install` is an alias of it
in Wheels 4.2 (#4206). See cli/lucli/services/packages/PackagesMainCli.cfc. --->
<code id="install-#EncodeForHTML(local.rpKey)#">wheels packages add #EncodeForHTML(local.rp.name)#</code>
<button type="button"
class="ui tiny button"
Expand Down
43 changes: 18 additions & 25 deletions vendor/wheels/tests/specs/cli/PackagesCommandHelpSpec.cfc
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
/**
* Regression: `wheels --help` advertises the `packages` command with the
* summary "Install, update, search Wheels packages." Users naturally try
* `wheels packages install <name>` next, which is intercepted by LuCLI's
* built-in extension installer before dispatch reaches Module.cfc — they
* see `[INFO] No git or extension dependencies to install`, exit 0, and
* nothing actually installs. The canonical verb is `wheels packages add`.
* The `packages` help leads with the canonical verb, `add`.
*
* Issue #2706. The help summary line must stop leading with the broken
* `Install` verb and point at `add` (the same trap that earlier renamed
* `wheels browser install` to `wheels browser setup`).
* Issue #2706: `wheels --help` summarised `packages` as "Install, update,
* search Wheels packages", so users typed `wheels packages install <name>`.
* On the CLIs of the time LuCLI's built-in extension installer took that
* verb before Module.cfc, and nothing installed. Since #4206 `install` is
* an alias of `add` in Wheels 4.2; `add` stays the documented verb because
* it works on every 4.x CLI.
*/
component extends="wheels.WheelsTest" {

Expand All @@ -27,16 +25,15 @@ component extends="wheels.WheelsTest" {
expect(fileExists(ctx.modulePath)).toBeTrue("Missing file: " & ctx.modulePath);
});

it("showHelp() summary line no longer leads with the broken `Install` verb", () => {
it("showHelp() summary line leads with `Add`, not `Install`", () => {
$requireRepoPath("cli/lucli/Module.cfc");
var source = fileRead(ctx.modulePath);

// The legacy phrasing leads with "Install" — the exact verb
// users will then try to type, which LuCLI intercepts.
// The legacy phrasing led with "Install", the verb older CLIs
// did not deliver to the module (#2706).
expect(source contains "packages Install, update, search Wheels packages").toBeFalse(
"showHelp() still summarises `wheels packages` with `Install, update, search ...`. "
& "The verb users will try (`wheels packages install`) is intercepted by LuCLI's "
& "built-in extension installer and silently no-ops. Lead with `Add` (the canonical verb) instead."
& "Lead with `Add`, the canonical verb, which works on every 4.x CLI."
);
});

Expand All @@ -60,27 +57,23 @@ component extends="wheels.WheelsTest" {

expect(reFindNoCase("\badd\b", summaryLine) > 0).toBeTrue(
"The `packages` summary line in showHelp() should mention `add` — the "
& "canonical install verb — so users don't reach for the intercepted "
& "`install` verb. Current line: " & summaryLine
& "canonical install verb. Current line: " & summaryLine
);
}
});

it("packages() hint metadata leads with `Add`, not the intercepted `Install` verb", () => {
it("packages() hint metadata leads with `Add`, not `Install`", () => {
$requireRepoPath("cli/lucli/Module.cfc");
var source = fileRead(ctx.modulePath);

// LuCLI surfaces the `hint:` javadoc on the packages() function
// in auto-introspected help. Leading with "Install" nudges
// users toward `wheels packages install`, which never reaches
// this module.
// `wheels packages --help` prints the packages() `hint:` line.
// It leads with `Add`, the documented verb, to match showHelp().
expect(source contains "hint: Install, update, and list Wheels packages").toBeFalse(
"packages() hint still leads with `Install`. Lead with `Add` "
& "(the canonical verb) so auto-introspected help matches showHelp()."
);
expect(source contains "hint: Add, update, and list Wheels packages").toBeTrue(
"packages() hint should lead with `Add, update, and list ...` "
& "and mention that the verb is `add`, not `install`."
"packages() hint should lead with `Add, update, and list ...`."
);
});

Expand All @@ -91,8 +84,8 @@ component extends="wheels.WheelsTest" {
expect(source contains "Unknown packages subcommand").toBeTrue(
"Expected the packages() default branch to throw an unknown-subcommand error."
);
expect(source contains "The install verb is `add` (not `install`): wheels packages add <name>").toBeTrue(
"The unknown-subcommand error should tell users the install verb is `add`."
expect(source contains "To install a package: wheels packages add <name>").toBeTrue(
"The unknown-subcommand error should point users at `wheels packages add`."
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ component extends="wheels.WheelsTest" {

describe("Generated app templates — package install verb", () => {

// Regression guard for issue #2610: `wheels packages install <name>`
// is intercepted by LuCLI's built-in extension installer and never
// reaches Module.cfc, so it prints "No git or extension
// dependencies to install" and exits without installing anything.
// The canonical install verb is `wheels packages add`. Templates
// that ship with every new app (via `wheels new`) must not
// advertise the broken verb.
// Regression guard for issue #2610: on older LuCLI runtimes
// `wheels packages install <name>` is intercepted by the built-in
// extension installer and never reaches Module.cfc. The current
// runtime passes it through as an alias of `add` (#4206), but
// `wheels packages add` works on every CLI version, so templates
// that ship with every new app (via `wheels new`) advertise `add`.

it("the generated app's _gitignore does not reference `wheels install`", () => {
$requireRepoPath("cli/lucli/templates/app");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/**
* User-facing surfaces must advertise `wheels packages add`, not
* `wheels packages install`. LuCLI intercepts the literal `install`
* subcommand before Module.cfc runs (#2610, #2706, #3378).
* `wheels packages install`. Older LuCLI runtimes intercept the literal
* `install` subcommand before Module.cfc runs (#2610, #2706, #3378); the
* current one passes it through as an alias (#4206), but `add` works on
* every CLI version.
*
* Mentions of `install` that explicitly say it is not the verb are fine;
* copy-to-clipboard snippets and recommended commands are not.
Expand Down
Loading