Skip to content

fix(cli): substitute @build.version@ for brew/chocolatey installs via cliVersion fallback - #2343

Merged
bpamiri merged 1 commit into
developfrom
claude/fix-2333-toolbar-version
Apr 27, 2026
Merged

bpamiri merged 1 commit into
developfrom
claude/fix-2333-toolbar-version

Conversation

@bpamiri

@bpamiri bpamiri commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

On brew/chocolatey installs, the dev toolbar shows "Wheels Version 0.0.0-dev" even though `wheels --version` reports the real version (e.g. 0.3.7).

Root cause chain

  1. The framework's `vendor/wheels/box.json` ships with `"version": "@build.version@"`. The release pipeline is supposed to substitute this at build time, but the homebrew/chocolatey formula paths don't guarantee it — the framework tarball can ship with the placeholder intact.
  2. `$readFrameworkVersion()` (vendor/wheels/Global.cfc) has a fallback that walks two levels up from `vendor/wheels/` looking for the wheels-dev/wheels monorepo's root box.json (slug="wheels" / name="Wheels.fw"). For an installed app, that lookup never resolves.
  3. `FrameworkInstaller.rewriteVersionPlaceholder()` (added in Homepage still shows 0.0.0-dev in new apps #2279) had the same monorepo-only limitation: if the source's enclosing root box.json identified the monorepo, substitute with `-dev`; otherwise no-op. For brew/chocolatey, source isn't inside the monorepo → no-op → placeholder persists → runtime returns "0.0.0-dev".

Fix

Extend `rewriteVersionPlaceholder` with a `cliVersion` parameter. When the monorepo signal is missing but a usable `cliVersion` is passed in, write it directly. The brew/chocolatey formulas bundle LuCLI and the framework together at the same version, so the LuCLI's own version is the right answer for the framework too.

Resolution order:

Priority Source When it applies
1 `-dev` Source is a wheels-dev/wheels monorepo dev checkout
2 cliVersion (new) Monorepo signal missing, LuCLI version usable
3 leave placeholder Neither signal — runtime returns "0.0.0-dev"

`Module.cfc.copyFrameworkToVendor` passes its `version()` (read from `module.json` by BaseModule) so the substitution happens transparently at `wheels new` time.

Defenses against bad inputs

  • `"Version not specified"` sentinel (BaseModule's default when `module.json` lacks a version) → ignored.
  • Self-placeholder `"@build.version@"` in `cliVersion` → ignored. Prevents pathological propagation if the CLI itself was shipped with an unsubstituted version.

Test plan

Closes #2333

… cliVersion fallback

The bug: on brew/chocolatey installs, the dev toolbar shows
"Wheels Version 0.0.0-dev" even though `wheels --version` reports
the correct version.

The framework's vendor/wheels/box.json ships with "version":
"@build.version@". The release pipeline is supposed to substitute
this at build time, but the homebrew/chocolatey formula paths don't
guarantee that (the framework tarball can ship with the placeholder
intact). The framework's own runtime fallback in
$readFrameworkVersion() looks for the wheels-dev/wheels monorepo's
root box.json, but for an installed app that lookup never finds the
monorepo — so the fallback returns "0.0.0-dev".

FrameworkInstaller.rewriteVersionPlaceholder() (added in #2279) was
similarly limited to the monorepo path: if the source's enclosing
root box.json identified the wheels monorepo, substitute with
"<rootversion>-dev". For brew/chocolatey, the source isn't inside the
monorepo — so the rewriter no-op'd, the placeholder persisted, and
the runtime fallback fired.

Fix: extend rewriteVersionPlaceholder with a `cliVersion` parameter.
When the monorepo signal is missing but a usable cliVersion is passed
in, write it directly. The brew/chocolatey formulas bundle LuCLI and
the framework together at the same version, so the LuCLI's known
version is the right answer for the framework too.

Resolution order, highest priority first:
1. Monorepo dev checkout — "<rootversion>-dev" (existing behavior)
2. cliVersion fallback — the LuCLI module's own version (new)
3. No usable signal — placeholder stays, runtime returns "0.0.0-dev"

Module.cfc.copyFrameworkToVendor passes its `version()` (read from
module.json by BaseModule) so the substitution happens transparently
at `wheels new` time.

Defenses against bad inputs:
- "Version not specified" sentinel (BaseModule's default when
  module.json lacks version) is ignored.
- Self-placeholder ("@build.version@") in cliVersion is ignored —
  prevents pathological propagation if the CLI itself was shipped
  with an unsubstituted version.

Fixes #2333.

Test plan:
- bash tools/test-cli-local.sh — 447 pass / 3 fail / 0 error. Three
  failures are pre-existing Doctor Service issues (#2260) unrelated.
- 4 new FrameworkInstaller specs cover: cliVersion fallback when
  monorepo missing, monorepo precedence over cliVersion, sentinel
  rejection, self-placeholder rejection.
- bash tools/test-onboarding.sh Phase 15 still PASSes (the harness's
  symlink mode satisfies the monorepo path; the new fallback only
  fires on installed-package layouts which aren't reproducible in
  symlink mode).
@bpamiri
bpamiri merged commit 38fef03 into develop Apr 27, 2026
4 checks passed
@bpamiri
bpamiri deleted the claude/fix-2333-toolbar-version branch April 27, 2026 18:02
bpamiri added a commit that referenced this pull request Apr 28, 2026
#2348)

* feat(cli): add Wheels SQLite Lucee extension (.lex) for fresh-VM bundle gap

Build, install, and ship a Lucee CFML extension that packages the xerial
sqlite-jdbc OSGi bundle so Lucee 7 can resolve `org.sqlite.JDBC` for
`wheels new` SQLite-by-default datasources without manual JAR drops.

This addresses fresh-VM onboarding finding F8 — the SQLite class-load
cliff that blocks every fresh `wheels migrate latest` because Lucee 7
ships drivers for MySQL/MSSQL/PostgreSQL/HSQLDB but not SQLite.

What's in tools/lucee-extensions/sqlite/:

- src/build.properties + src/SQLite.cfc + src/sqlite-jdbc-3.49.1.0.jar
  (vendored from Maven Central) — extension source.
- build.sh produces dist/org.xerial.sqlite-jdbc-3.49.1.0.lex (~14 MB).
  Mirrors the layout/manifest of lucee/extension-jdbc-postgresql and
  lucee/extension-jdbc-duckdb but uses bash+zip instead of Ant.
- install.sh drops the patched bundle straight into a Lucee server's
  lucee-server/bundles/ directory — deterministic on Lucee 7 and the
  recommended path until the deploy/ auto-install quirk is solved.
- README documents three install paths (bundle/.lex/admin UI), the
  upstream upgrade plan, and the OSGi manifest patch.

The non-obvious fix: xerial's JAR is a valid OSGi bundle, but it
declares `Require-Capability: osgi.ee;version=1.8` (exact match in some
resolvers). Felix on Java 21 cannot satisfy that, so the bundle is
silently rejected. build.sh patches the JAR's MANIFEST.MF to
`(version>=1.8)` (matching PostgreSQL's filter) and drops the
`;singleton:=true` directive on Bundle-SymbolicName. Original JAR in
src/ is untouched — the patch is applied during build.

Verified end-to-end on macOS arm64 + Lucee 7.0.0.395:

  BEFORE install.sh: dbtest -> FAIL "org.xerial.sqlite-jdbc not available locally"
  AFTER  install.sh: dbtest -> OK datasource=sqliteapp result=1
  AFTER  install.sh: wheels migrate latest creates tables in db/development.sqlite

Follow-ups (separate PRs):
1. Wire `wheels new` (or first `wheels start`) to auto-run install.sh
   against the freshly-created server so SQLite-by-default works
   zero-config on fresh VMs.
2. Debug Lucee 7's deploy/ -> failed-to-deploy/ silent quarantine so
   the canonical deploy/ install path works without falling back to
   the bundle drop.
3. File upstream PR as lucee/extension-jdbc-sqlite once stable so the
   Lucee community gets it via the standard update server, and a
   separate issue with xerial/sqlite-jdbc to relax their OSGi
   Require-Capability filter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cli): close 0.0.0-dev version display gap on installed Wheels apps

Two compounding bugs caused the dev toolbar / /wheels/info to report
"Wheels Version: 0.0.0-dev" on installed (brew/chocolatey) apps even
though every release artifact already carries the precise SNAPSHOT
build version. Both are fixed here.

## Bug 1 — wheels-module tarball never substitutes module.json

release.yml's "Build Wheels Module Tarball" step tars cli/lucli/
directly with no version substitution, while wheels-core/wheels-base
(which run through prepare-*.sh) get @build.version@ properly replaced.
As a result every wheels-module-*.tar.gz on the GitHub releases page
ships with module.json's hardcoded "version": "4.0.0" — even though
the tag itself is "v4.0.0-SNAPSHOT+1625".

That hardcoded value is then read by BaseModule.version() at runtime
and fed into FrameworkInstaller.rewriteVersionPlaceholder as the
cliVersion fallback (added in #2343 / #2333), so on installs whose
vendor/wheels/box.json went through that fallback path, the framework
"version" is the imprecise "4.0.0" instead of the build's
"4.0.0-SNAPSHOT+1625".

Fix: change cli/lucli/module.json to use "version": "@build.version@"
(matching every other manifest in the repo), and add the same find +
sed substitution release.yml's other build steps already do, in the
"Build Wheels Module Tarball" step before tar/zip. Uses an env: block
to satisfy our workflow injection lint and avoids inlining the version
into shell commands.

## Bug 2 — runtime fallback only recognizes the monorepo's box.json

When vendor/wheels/box.json's @build.version@ leaks through scaffold-
time substitution (e.g. user's CLI is pre-#2343, or any other gap in
the install pipeline), Global.cfc:$readFrameworkVersion's fallback
walks two levels up looking for the wheels-dev/wheels monorepo's
box.json — and only synthesizes "<rootversion>-dev" when slug=="wheels"
or name=="Wheels.fw". An installed app's app-root box.json comes from
wheels-base-template (slug=wheels-base-template), which carries the
precise framework SNAPSHOT version stamped at release time — but the
fallback rejects it because the monorepo signal is absent. Fall-through
returns the bare "0.0.0-dev" sentinel.

Fix: extend the fallback so when the monorepo signal is missing AND
the parent's box.json self-identifies as wheels-base-template (slug or
name match), use its version verbatim (no -dev suffix — that version is
the actual installed framework version, not an upcoming dev build).
The monorepo signal still wins when both could match; both signals are
ignored if their version is itself the unreplaced placeholder.

Three new specs in frameworkVersionSpec.cfc cover the new path,
including the precedence ordering and the placeholder guard. Existing
specs (including the regression for #2291) still pass.

## Verification

- Framework suite: 3333 passed (no regressions).
- CLI suite: 452 pass, 3 fail — pre-existing DoctorSpec issues (#2260),
  unrelated.
- Local dry-run of the release.yml sed substitution: cli/lucli/module.json
  picks up MODULE_VERSION, no other false positives in cli/lucli/.

Closes #2326.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bpamiri added a commit that referenced this pull request Apr 28, 2026
…#2349)

* feat(cli): add Wheels SQLite Lucee extension (.lex) for fresh-VM bundle gap

Build, install, and ship a Lucee CFML extension that packages the xerial
sqlite-jdbc OSGi bundle so Lucee 7 can resolve `org.sqlite.JDBC` for
`wheels new` SQLite-by-default datasources without manual JAR drops.

This addresses fresh-VM onboarding finding F8 — the SQLite class-load
cliff that blocks every fresh `wheels migrate latest` because Lucee 7
ships drivers for MySQL/MSSQL/PostgreSQL/HSQLDB but not SQLite.

What's in tools/lucee-extensions/sqlite/:

- src/build.properties + src/SQLite.cfc + src/sqlite-jdbc-3.49.1.0.jar
  (vendored from Maven Central) — extension source.
- build.sh produces dist/org.xerial.sqlite-jdbc-3.49.1.0.lex (~14 MB).
  Mirrors the layout/manifest of lucee/extension-jdbc-postgresql and
  lucee/extension-jdbc-duckdb but uses bash+zip instead of Ant.
- install.sh drops the patched bundle straight into a Lucee server's
  lucee-server/bundles/ directory — deterministic on Lucee 7 and the
  recommended path until the deploy/ auto-install quirk is solved.
- README documents three install paths (bundle/.lex/admin UI), the
  upstream upgrade plan, and the OSGi manifest patch.

The non-obvious fix: xerial's JAR is a valid OSGi bundle, but it
declares `Require-Capability: osgi.ee;version=1.8` (exact match in some
resolvers). Felix on Java 21 cannot satisfy that, so the bundle is
silently rejected. build.sh patches the JAR's MANIFEST.MF to
`(version>=1.8)` (matching PostgreSQL's filter) and drops the
`;singleton:=true` directive on Bundle-SymbolicName. Original JAR in
src/ is untouched — the patch is applied during build.

Verified end-to-end on macOS arm64 + Lucee 7.0.0.395:

  BEFORE install.sh: dbtest -> FAIL "org.xerial.sqlite-jdbc not available locally"
  AFTER  install.sh: dbtest -> OK datasource=sqliteapp result=1
  AFTER  install.sh: wheels migrate latest creates tables in db/development.sqlite

Follow-ups (separate PRs):
1. Wire `wheels new` (or first `wheels start`) to auto-run install.sh
   against the freshly-created server so SQLite-by-default works
   zero-config on fresh VMs.
2. Debug Lucee 7's deploy/ -> failed-to-deploy/ silent quarantine so
   the canonical deploy/ install path works without falling back to
   the bundle drop.
3. File upstream PR as lucee/extension-jdbc-sqlite once stable so the
   Lucee community gets it via the standard update server, and a
   separate issue with xerial/sqlite-jdbc to relax their OSGi
   Require-Capability filter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cli): close 0.0.0-dev version display gap on installed Wheels apps

Two compounding bugs caused the dev toolbar / /wheels/info to report
"Wheels Version: 0.0.0-dev" on installed (brew/chocolatey) apps even
though every release artifact already carries the precise SNAPSHOT
build version. Both are fixed here.

## Bug 1 — wheels-module tarball never substitutes module.json

release.yml's "Build Wheels Module Tarball" step tars cli/lucli/
directly with no version substitution, while wheels-core/wheels-base
(which run through prepare-*.sh) get @build.version@ properly replaced.
As a result every wheels-module-*.tar.gz on the GitHub releases page
ships with module.json's hardcoded "version": "4.0.0" — even though
the tag itself is "v4.0.0-SNAPSHOT+1625".

That hardcoded value is then read by BaseModule.version() at runtime
and fed into FrameworkInstaller.rewriteVersionPlaceholder as the
cliVersion fallback (added in #2343 / #2333), so on installs whose
vendor/wheels/box.json went through that fallback path, the framework
"version" is the imprecise "4.0.0" instead of the build's
"4.0.0-SNAPSHOT+1625".

Fix: change cli/lucli/module.json to use "version": "@build.version@"
(matching every other manifest in the repo), and add the same find +
sed substitution release.yml's other build steps already do, in the
"Build Wheels Module Tarball" step before tar/zip. Uses an env: block
to satisfy our workflow injection lint and avoids inlining the version
into shell commands.

## Bug 2 — runtime fallback only recognizes the monorepo's box.json

When vendor/wheels/box.json's @build.version@ leaks through scaffold-
time substitution (e.g. user's CLI is pre-#2343, or any other gap in
the install pipeline), Global.cfc:$readFrameworkVersion's fallback
walks two levels up looking for the wheels-dev/wheels monorepo's
box.json — and only synthesizes "<rootversion>-dev" when slug=="wheels"
or name=="Wheels.fw". An installed app's app-root box.json comes from
wheels-base-template (slug=wheels-base-template), which carries the
precise framework SNAPSHOT version stamped at release time — but the
fallback rejects it because the monorepo signal is absent. Fall-through
returns the bare "0.0.0-dev" sentinel.

Fix: extend the fallback so when the monorepo signal is missing AND
the parent's box.json self-identifies as wheels-base-template (slug or
name match), use its version verbatim (no -dev suffix — that version is
the actual installed framework version, not an upcoming dev build).
The monorepo signal still wins when both could match; both signals are
ignored if their version is itself the unreplaced placeholder.

Three new specs in frameworkVersionSpec.cfc cover the new path,
including the precedence ordering and the placeholder guard. Existing
specs (including the regression for #2291) still pass.

## Verification

- Framework suite: 3333 passed (no regressions).
- CLI suite: 452 pass, 3 fail — pre-existing DoctorSpec issues (#2260),
  unrelated.
- Local dry-run of the release.yml sed substitution: cli/lucli/module.json
  picks up MODULE_VERSION, no other false positives in cli/lucli/.

Closes #2326.

* fix(cli): wheels start auto-stages SQLite driver to close fresh-VM F8

Closes #2326. After this PR, `wheels new` + `wheels start` works with the
zero-config SQLite default datasource on any install path — brew, choco,
dev checkout, manual — without the user needing to drop a JAR by hand.

## Background

Lucee 7's stock Express distribution ships JDBC drivers for MySQL, MSSQL,
PostgreSQL, and HSQLDB but not SQLite. `wheels new`, however, writes
SQLite as the zero-config default (`class: "org.sqlite.JDBC"`,
`connectionString: "jdbc:sqlite:..."`). Without a SQLite JAR somewhere
Lucee can find it (Tomcat classpath via lib/ext/ or an OSGi bundle in
lucee-server/bundles/), every fresh app fails on the first request:

  ClassException: org.sqlite.JDBC

The brew formula already drops the upstream xerial JAR into
`~/.wheels/express/<lucee>/lib/ext/` from a wrapper script, but that only
covers brew installs. Chocolatey, dev checkouts, and manual installs all
hit F8 on the first VM cycle.

## What this changes

- `cli/lucli/services/BundleStager.cfc` (new) — extracted from Module.cfc
  for unit-testability. Two methods:
  - `projectUsesSqliteDatasource(projectRoot)` — true if config/app.cfm
    references `jdbc:sqlite:`, `org.sqlite.JDBC`, or
    `org.xerial.sqlite-jdbc`. Detection covers both the legacy app.cfm
    format (which carried the bundleName hint) and the current format
    post-#2304 which omits the hint and relies on classpath resolution.
  - `stageIntoLibExt(bundleSrc, expressRoot, jarFileName)` — copies the
    JAR into every Lucee version's `lib/ext/`. Idempotent, best-effort,
    returns `{staged, skipped, failed}` for testability.

- `cli/lucli/Module.cfc` — `start()` calls `$ensureWheelsBundles()` both
  before and after delegating to LuCLI's `server start`. Pre-stage covers
  the case where the express dir already exists; post-stage covers the
  very first invocation that just extracted express. Either way, `wheels
  start` and `wheels start --force` end up with the JAR on the classpath
  before Lucee resolves any SQLite datasource.

  `$resolveLucliHome()` follows LuCLI's own resolution order:
    1. `$LUCLI_HOME` env var (e.g. brew wrapper exports `$HOME/.wheels`),
    2. `$HOME/.<lucli.binary.name>` — picks `~/.wheels` for `wheels`
       invocations and `~/.lucli` for bare `lucli`,
    3. `$HOME/.lucli` fallback.

- `cli/lucli/resources/extensions/sqlite/org.xerial.sqlite-jdbc-3.49.1.0.jar`
  (~13 MB) — patched JAR baked into the wheels-module distribution. Built
  from `tools/lucee-extensions/sqlite/build.sh` (#2347), which patches
  upstream's `Require-Capability: osgi.ee;version=1.8` (exact match — fails
  on Felix + Java 21) to `(version>=1.8)` matching the PostgreSQL bundle's
  filter.

- `cli/lucli/tests/specs/services/BundleStagerSpec.cfc` (new, 10 specs) —
  detection edge cases, multi-version Express layouts, idempotency, and
  defensive behavior when the bundle source or express dir is missing.

## Bypass cases (separate follow-ups)

- `wheels server start --force` is a LuCLI built-in passthrough (not
  routed through Module.cfc). Users hit by this can use `wheels start
  --force` instead. F5 in the journal already tracks updating the error
  message that points users at the bypass form.
- Lucee caches a per-process negative bundle resolution. If a user hits a
  SQLite endpoint before the JAR is staged, restart the server. The
  pre-stage in `start()` makes this only matter on a single end-of-train
  user; the post-stage seeds for the next start.

## Verification

- Framework suite: 3333 passed (no regressions).
- CLI suite: 462 passed, 3 fail — pre-existing DoctorSpec issues (#2260),
  unrelated. Of the 462, 10 are new BundleStagerSpec coverage.
- End-to-end: on a clean macOS arm64 + JDK 21 env with no SQLite JAR
  anywhere, `wheels new finalapp` + `wheels start` + `curl /dbtest.cfm`
  returns `OK datasource=finalapp result=1` zero-config. Pre-stage logs
  show the JAR copied into `~/.wheels/express/7.0.0.395/lib/ext/`.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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(view): dev toolbar shows 'Wheels Version 0.0.0-dev' instead of installed version

1 participant