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/3378-packages-add-verb.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Docs, `--help`, and the packages website now agree that the install verb is `wheels packages add` — `wheels packages install` is intercepted by LuCLI before the Wheels module runs and does not install anything. The Basecoat bonus chapter also says to copy the showcase from `vendor/` after `add`, not from the raw GitHub tree (#3378)
4 changes: 2 additions & 2 deletions cli/lucli/Module.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -2615,7 +2615,7 @@ component extends="modules.BaseModule" {
// ─────────────────────────────────────────────────

/**
* hint: Install, update, and list Wheels packages — use `add` (not `install`) to install
* hint: Add, update, and list Wheels packages (verb is `add`, not `install`)
*
* The verb is `add`, NOT `install`. Typing `wheels packages install <name>`
* is intercepted by LuCLI's built-in extension installer before dispatch
Expand Down Expand Up @@ -2716,7 +2716,7 @@ component extends="modules.BaseModule" {
var regCli = new modules.wheels.services.packages.PackagesRegistryCli();
return invoke(regCli, regVerb, [opts]);
default:
throw(message="Unknown packages subcommand: #sub#");
throw(message="Unknown packages subcommand: #sub#. The install verb is `add` (not `install`): wheels packages add <name>");
}
}

Expand Down
28 changes: 28 additions & 0 deletions vendor/wheels/tests/specs/cli/PackagesCommandHelpSpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,34 @@ component extends="wheels.WheelsTest" {
}
});

it("packages() hint metadata leads with `Add`, not the intercepted `Install` verb", () => {
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.
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`."
);
});

it("unknown-subcommand error points users at `wheels packages add`", () => {
var source = fileRead(ctx.modulePath);

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`."
);
});

});

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* 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).
*
* Mentions of `install` that explicitly say it is not the verb are fine;
* copy-to-clipboard snippets and recommended commands are not.
*/
component extends="wheels.WheelsTest" {

function run() {

var ctx = {repoRoot: expandPath("/wheels/../..")};

describe("User-facing surfaces advertise `packages add`", () => {

it("the in-app packages page copies `wheels packages add`", () => {
var path = expandPath("/wheels/public/views/packagelist.cfm");
expect(fileExists(path)).toBeTrue("Missing file: " & path);
var source = fileRead(path);

expect(source contains "wheels packages add ").toBeTrue(
"packagelist.cfm copy snippet must use `wheels packages add`."
);
expect(source contains "wheels packages install ").toBeFalse(
"packagelist.cfm must not put `wheels packages install` in a copy snippet. "
& "LuCLI intercepts that verb before Module.cfc."
);
});

it("the packages website copy snippets use `add`, not `install`", () => {
var files = [
ctx.repoRoot & "/web/sites/packages/src/pages/index.astro",
ctx.repoRoot & "/web/sites/packages/src/pages/[name].astro",
ctx.repoRoot & "/web/sites/packages/src/components/PackageCard.astro"
];
var i = 0;
var n = arrayLen(files);
for (i = 1; i <= n; i++) {
expect(fileExists(files[i])).toBeTrue("Missing file: " & files[i]);
var source = fileRead(files[i]);

// Copy-to-clipboard / recommended command shapes.
expect(find("wheels packages install {", source) > 0).toBeFalse(
files[i] & " still has a copy snippet `wheels packages install {name}`. Use `add`."
);
expect(find("wheels packages install &lt;name&gt;", source) > 0).toBeFalse(
files[i] & " still recommends `wheels packages install <name>`. Use `add`."
);
}
});

it("the packages website index recommends `wheels packages add`", () => {
var path = ctx.repoRoot & "/web/sites/packages/src/pages/index.astro";
var source = fileRead(path);
expect(source contains "wheels packages add &lt;name&gt;").toBeTrue(
"packages site index should recommend `wheels packages add <name>`."
);
});

});

}

}
6 changes: 6 additions & 0 deletions web/sites/guides/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ export default defineConfig({
'/v4-0-0/digging-deeper/security/https-detection': '/v4-0-0/deployment/security-hardening/',
'/v4-0-0/configuration': '/v4-0-0/core-concepts/environments-and-configuration/',
'/v4-0-0/troubleshooting/cross-engine-compatibility': '/v4-0-0/contributing/coding-standards/',
// The `packages add` reference lived at .../packages/install/ while the
// page title already said `add`. The old slug advertised the broken
// LuCLI-intercepted verb. Keep the old URL working. One source only —
// listing both `/install` and `/install/` made Astro warn that the
// route was defined twice.
'/v4-0-0/command-line-tools/commands/packages/install': '/v4-0-0/command-line-tools/commands/packages/add/',
},
integrations: [
starlight({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ sidebar:

`wheels packages` is the CLI surface for the [wheels-packages registry](https://github.com/wheels-dev/wheels-packages) — a curated, git-based distribution channel for Wheels ecosystem packages. Every verb talks to the registry over plain HTTPS and installs into `vendor/<name>/`, where `PackageLoader` picks it up on next reload.

The install verb is [`add`](./add). `wheels packages install <name>` never reaches this command — LuCLI's built-in extension installer intercepts the literal `install` subcommand and prints `No git or extension dependencies to install` without touching `vendor/`.

There is no ForgeBox and no CommandBox. The registry manifest is authoritative, tarballs live on the registry's GitHub Releases, and every tarball has a sha256 in the manifest that the installer verifies before extraction. Supply-chain attacks via force-pushed tags or drifted source archives are defeated by this design.

## Synopsis
Expand All @@ -31,7 +33,7 @@ wheels packages registry info
| [`list`](./list) | Show every package in the registry, optionally filtered by `--tag`. |
| [`search`](./search) | Substring match against name, description, and tags. |
| [`show`](./show) | Detail page for a package: versions, homepage, license, install state. |
| [`add`](./install) | Download, verify, extract into `vendor/<name>/`. |
| [`add`](./add) | Download, verify, extract into `vendor/<name>/`. |
| [`update`](./update) | Re-install the latest compatible version. Explicit: requires `--yes`. |
| [`remove`](./remove) | Delete `vendor/<name>/`. Refuses dirs without a `package.json`. |
| [`registry refresh`](./registry/refresh) | Bust the 24h cache. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ The reload re-runs `PackageLoader`, which rediscovers what's in `vendor/` and re
Six packages are maintained as first-party modules under the `wheels-dev` GitHub org. All are optional; the framework core runs fine with none of them installed.

- **[`wheels-hotwire`](https://github.com/wheels-dev/wheels-hotwire)** — Turbo Drive, Turbo Frames, Turbo Streams, and Stimulus integration for server-rendered UI. Mixes into controllers (and therefore views). Used heavily in the tutorial app.
- **[`wheels-basecoat`](https://github.com/wheels-dev/wheels-basecoat)** — UI component helpers styled with Tailwind CSS. shadcn/ui-quality forms, buttons, and cards without React. Mixes into controllers.
- **[`wheels-basecoat`](https://github.com/wheels-dev/wheels-basecoat)** — UI component helpers styled with Tailwind CSS. shadcn/ui-quality forms, buttons, and cards without React. Mixes into controllers. Add it with `wheels packages add wheels-basecoat` (the verb is `add`, not `install`), then copy CSS and the optional showcase from `vendor/wheels-basecoat/` — not from the raw GitHub tree. The [bonus tutorial chapter](/v4-0-0/start-here/tutorial/08-bonus-basecoat/) walks through both.
- **[`wheels-sentry`](https://github.com/wheels-dev/wheels-sentry)** — Sentry.io error tracking with framework-aware context enrichment. Captures exceptions with request, user, and route context. Mixes into controllers.
- **[`wheels-legacy-adapter`](https://github.com/wheels-dev/wheels-legacy-adapter)** — Backward-compatibility shim for Wheels 3.x plugins. Deprecation logging, API adapters, and a scanner that flags 3.x patterns you should modernize. Mixes into controllers.
- **[`wheels-i18n`](https://github.com/wheels-dev/wheels-i18n)** — Internationalization with JSON-file or database-backed translations, parameter interpolation, and pluralization. Mixes into controllers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,52 @@ Three things to verify:

**Card content is wrapped in a frame but no styling** — basecoat's JS bundle is failing to load. Check `public/assets/basecoat/basecoat.min.js` exists and look in the browser console for a network error on `/assets/basecoat/`.

## Optional: the Basecoat showcase

The package ships a full component gallery under `vendor/wheels-basecoat/examples/showcase/`. Use it to browse every helper (`uiBadge`, `uiAlert`, `uiDropdown`, …) against the version you just added.

<Aside type="caution">
Copy the showcase from `vendor/wheels-basecoat/` after `wheels packages add` — not from the raw GitHub tree. The GitHub `main` branch can be newer than the tarball the registry served, so a view copied from GitHub may call helpers (for example `uiBadge`) that the installed package does not yet mix in.
</Aside>

<Steps>

1. Add the package if you have not already:

```bash title="your shell"
wheels packages add wheels-basecoat
```

2. Copy the bundled CSS+JS (same step as [Publish the basecoat assets](#publish-the-basecoat-assets)) and the showcase controller + views from `vendor/`:

```bash title="macOS / Linux"
cp -r vendor/wheels-basecoat/assets/basecoat public/assets/basecoat
cp vendor/wheels-basecoat/examples/showcase/controllers/Showcase.cfc app/controllers/Showcase.cfc
cp -r vendor/wheels-basecoat/examples/showcase/views/showcase app/views/showcase
```

```bat title="Windows (cmd)"
xcopy /E /I vendor\wheels-basecoat\assets\basecoat public\assets\basecoat
copy vendor\wheels-basecoat\examples\showcase\controllers\Showcase.cfc app\controllers\Showcase.cfc
xcopy /E /I vendor\wheels-basecoat\examples\showcase\views\showcase app\views\showcase
```

3. Add a named route in `config/routes.cfm` (before `.wildcard()`):

```cfm title="config/routes.cfm"
.get(name="basecoatShowcase", pattern="/basecoat-showcase", to="showcase##index")
```

4. Reload and open the gallery:

```bash title="your shell"
wheels reload
```

Visit `/basecoat-showcase`. Always check `vendor/wheels-basecoat/INSTALL.md` for the version you added — the asset and showcase layout can change between majors.

</Steps>

## What's next

This chapter converted one view. The other views (`index.cfm`, `new.cfm`, `edit.cfm`, the comment partials, the auth screens from Part 6) are still on simple.css. Converting them is a mechanical exercise — the helpers are documented in the [wheels-basecoat README](https://github.com/wheels-dev/wheels-basecoat#component-reference).
Expand Down
2 changes: 1 addition & 1 deletion web/sites/guides/src/sidebars/v4-0-0.json
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@
{ "label": "packages list", "link": "/v4-0-0/command-line-tools/commands/packages/list/" },
{ "label": "packages search", "link": "/v4-0-0/command-line-tools/commands/packages/search/" },
{ "label": "packages show", "link": "/v4-0-0/command-line-tools/commands/packages/show/" },
{ "label": "packages add", "link": "/v4-0-0/command-line-tools/commands/packages/install/" },
{ "label": "packages add", "link": "/v4-0-0/command-line-tools/commands/packages/add/" },
{ "label": "packages update", "link": "/v4-0-0/command-line-tools/commands/packages/update/" },
{ "label": "packages remove", "link": "/v4-0-0/command-line-tools/commands/packages/remove/" },
{
Expand Down
4 changes: 2 additions & 2 deletions web/sites/packages/src/components/PackageCard.astro
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ const homepage = /^https?:\/\//.test(pkg.homepage) ? pkg.homepage : '';
</ul>
)}
<div class="install">
<code id={`install-${pkg.name}`}>wheels packages install {pkg.name}</code>
<code id={`install-${pkg.name}`}>wheels packages add {pkg.name}</code>
<button
type="button"
class="copy-btn"
data-target={`install-${pkg.name}`}
aria-label={`Copy install command for ${pkg.name}`}
aria-label={`Copy add command for ${pkg.name}`}
>Copy</button>
</div>
{homepage && (
Expand Down
25 changes: 20 additions & 5 deletions web/sites/packages/src/pages/[name].astro
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,18 @@ const versionsDesc = [...manifest.versions].reverse();
)}

<section class="install-section">
<h2>Install</h2>
<h2>Add the package</h2>
<p class="install-note">
The verb is <code>add</code>, not <code>install</code>.
<code>wheels packages install</code> is intercepted by LuCLI and does not install anything.
</p>
<div class="install">
<code id="install-latest">wheels packages install {manifest.name}</code>
<button type="button" class="copy-btn" data-target="install-latest" aria-label={`Copy latest install command for ${manifest.name}`}>Copy</button>
<code id="install-latest">wheels packages add {manifest.name}</code>
<button type="button" class="copy-btn" data-target="install-latest" aria-label={`Copy latest add command for ${manifest.name}`}>Copy</button>
</div>
<div class="install">
<code id="install-pinned">wheels packages install {manifest.name}@{latest.version}</code>
<button type="button" class="copy-btn" data-target="install-pinned" aria-label={`Copy pinned install command for ${manifest.name}`}>Copy</button>
<code id="install-pinned">wheels packages add {manifest.name}@{latest.version}</code>
<button type="button" class="copy-btn" data-target="install-pinned" aria-label={`Copy pinned add command for ${manifest.name}`}>Copy</button>
</div>
</section>

Expand Down Expand Up @@ -157,6 +161,17 @@ const versionsDesc = [...manifest.versions].reverse();
.install-section h2, .versions-section h2, .readme-section h2 {
font-size: 1.25rem; margin-bottom: 0.75rem;
}
.install-note {
margin: 0 0 0.75rem;
color: var(--color-fg-muted, #555);
font-size: 0.9375rem;
}
.install-note code {
font-size: 0.875em;
background: var(--color-surface-2, #f3f3f3);
padding: 0.125rem 0.375rem;
border-radius: 4px;
}
.install { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; }
.install code {
flex: 1;
Expand Down
3 changes: 2 additions & 1 deletion web/sites/packages/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const packages = await listAll();
<h1>Wheels packages</h1>
<p class="subhead">
First-party and community packages for the Wheels framework. Install with
<code>wheels packages install &lt;name&gt;</code>.
<code>wheels packages add &lt;name&gt;</code>
(the verb is <code>add</code>, not <code>install</code>).
</p>
<label class="filter">
<span class="visually-hidden">Filter packages</span>
Expand Down
Binary file modified web/tests/visual-baselines/packages-index.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified web/tests/visual-baselines/packages-wheels-sentry.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading