fix(cli): drop bundlename hint from new-app sqlite datasources - #2304
Merged
Conversation
`wheels new` was emitting `bundleName: "org.xerial.sqlite-jdbc"` into the generated config/app.cfm, which routes Lucee 7's datasource resolver through BundleProvider. That code path currently fails on all platforms because the upstream S3 listing at bundle-download.s3.amazonaws.com contains a malformed entry (`javassist-3.9.0.GA copy.jar`) whose space + " copy" suffix is both an illegal URI character and an invalid OSGi version qualifier. The listing parse throws, bundle resolution dies, and the SQLite driver never loads — leaving fresh `wheels new` apps with a 0-byte database and a `wheels migrate latest` that silently no-ops. Removing the bundleName hint lets Lucee fall through to the standard classpath, which resolves org.sqlite.JDBC normally as long as sqlite-jdbc is on the lib path. Companion changes to bundle the JAR in homebrew-wheels and chocolatey-wheels will follow. Reported by a fresh-VM tutorial onboarding run on macOS arm64. Note: cli/src/ (legacy CommandBox surface) still emits the same bundleName hint and will need a matching change if that distribution is still being shipped.
3 tasks
bpamiri
added a commit
to wheels-dev/homebrew-wheels
that referenced
this pull request
Apr 25, 2026
Lucee 7's BundleProvider crashes when resolving sqlite-jdbc via the bundleName hint (malformed entry in upstream S3 bundle listing), so wheels-dev/wheels#2304 dropped the hint from the datasource emitted by `wheels new`. Without the hint Lucee falls back to classpath resolution, which requires the JAR to be on the Lucee lib path. This formula now: - Adds a `sqlite_jdbc` resource (3.49.1.0 from Maven Central, sha256 pinned) and stages it into share/wheels/lib/. - Drops the JAR into ~/.wheels/express/<lucee>/lib/ext/ on every run via the wrapper, idempotent — copies only if missing. The express directory only exists after the first LuCLI extraction, so the copy is a no-op on the first invocation and self-heals from the second invocation onward (which is fine — `wheels new` doesn't touch the database; `wheels migrate latest` does). - Adds a brew-test assertion that the JAR is staged into the Cellar. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 26, 2026
Closed
bpamiri
added a commit
that referenced
this pull request
Apr 26, 2026
Local harness that simulates the brand-new-user onboarding flow without touching the user's daily wheels install. Uses LUCLI_HOME isolation so an isolated copy of the worktree's cli/lucli/ is exercised, with the worktree's vendor/wheels/ as WHEELS_FRAMEWORK_PATH. Phases (mirrors fresh-VM journal format for direct comparison): - Phase 1: Setup isolated LUCLI_HOME (worktree mount, framework path, Lucee Express symlink to avoid the 74MB re-download) - Phase 2: wheels new (assertions for F3 dup create lines, F4 file tree shape, F1 no bundleName in generated config/app.cfm) - Phase 3: server boot via 'lucli server run' + sqlite-jdbc shim - Phase 4: migration cliff (F2/F5 false-success — verifies the actual sqlite db has the migrated tables, not just exit 0) - Phase 5: seed (cfscript wrapper + idempotency) - Phase 6: CRUD walkthrough through chapters 2-3 - Phase 7: wheels packages list (F7 — currently SKIP pending a separate fix; reports the bug signature when it reproduces) Closes the iteration loop: instead of fix → push → fresh-VM test (30+ min/round), we can now fix → run harness (~90s) and only burn a fresh VM as the final confidence check. Modes: - bash tools/test-onboarding.sh # symlink mount (default) - MODE=copy bash tools/test-onboarding.sh # copy (closer to brew install) - BASELINE=1 bash tools/test-onboarding.sh # use brew-installed wheels - KEEP_TEMP=1 bash tools/test-onboarding.sh # preserve temp dirs - FROM_PHASE=4 bash tools/test-onboarding.sh # skip earlier phases JAVA_HOME is auto-resolved (via /usr/libexec/java_home or brew-prefix fallback for keg-only openjdk@21). LUCLI_HOME ends in '.lucli' so LuCLI's getComponentPath() picks the absolute-file-path mode for Module.cfc loading. Without this harness we'd have shipped PR #2304 + #2306 + the formula updates, watched the next VM run still fail at migrate (the migrateToLatest bug surfaced in PR #2307 was masked by the silent exit-0), and needed a third VM iteration to find it.
bpamiri
added a commit
that referenced
this pull request
Apr 26, 2026
…2308) Local harness that simulates the brand-new-user onboarding flow without touching the user's daily wheels install. Uses LUCLI_HOME isolation so an isolated copy of the worktree's cli/lucli/ is exercised, with the worktree's vendor/wheels/ as WHEELS_FRAMEWORK_PATH. Phases (mirrors fresh-VM journal format for direct comparison): - Phase 1: Setup isolated LUCLI_HOME (worktree mount, framework path, Lucee Express symlink to avoid the 74MB re-download) - Phase 2: wheels new (assertions for F3 dup create lines, F4 file tree shape, F1 no bundleName in generated config/app.cfm) - Phase 3: server boot via 'lucli server run' + sqlite-jdbc shim - Phase 4: migration cliff (F2/F5 false-success — verifies the actual sqlite db has the migrated tables, not just exit 0) - Phase 5: seed (cfscript wrapper + idempotency) - Phase 6: CRUD walkthrough through chapters 2-3 - Phase 7: wheels packages list (F7 — currently SKIP pending a separate fix; reports the bug signature when it reproduces) Closes the iteration loop: instead of fix → push → fresh-VM test (30+ min/round), we can now fix → run harness (~90s) and only burn a fresh VM as the final confidence check. Modes: - bash tools/test-onboarding.sh # symlink mount (default) - MODE=copy bash tools/test-onboarding.sh # copy (closer to brew install) - BASELINE=1 bash tools/test-onboarding.sh # use brew-installed wheels - KEEP_TEMP=1 bash tools/test-onboarding.sh # preserve temp dirs - FROM_PHASE=4 bash tools/test-onboarding.sh # skip earlier phases JAVA_HOME is auto-resolved (via /usr/libexec/java_home or brew-prefix fallback for keg-only openjdk@21). LUCLI_HOME ends in '.lucli' so LuCLI's getComponentPath() picks the absolute-file-path mode for Module.cfc loading. Without this harness we'd have shipped PR #2304 + #2306 + the formula updates, watched the next VM run still fail at migrate (the migrateToLatest bug surfaced in PR #2307 was masked by the silent exit-0), and needed a third VM iteration to find it.
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
bundleName: \"org.xerial.sqlite-jdbc\"from the SQLite datasource block emitted bywheels newinto a fresh app'sconfig/app.cfm.configureSQLiteDatabaseto reflect the new (classpath) loading path.Why
A fresh-VM tutorial onboarding run (macOS arm64, only Homebrew + Claude Code pre-installed) found that the very first migration after
wheels newsilently no-ops — the database file stays at 0 bytes andwheels migrate latestexits 0. The server log reveals the real failure:```
java.io.IOException: java.lang.IllegalArgumentException: invalid version "3.9.0.GA copy":
invalid qualifier "GA copy"
...at lucee.runtime.config.s3.BundleProvider.read(BundleProvider.java:520)
Caused by: java.lang.IllegalArgumentException: Illegal character in query at index 67:
https://bundle-download.s3.amazonaws.com/?marker=javassist-3.9.0.GA copy.jar
```
Lucee 7's
BundleProvideris being invoked because of thebundleNamehint in the generated datasource. It tries to enumerate the upstream S3 bucket and dies on a malformed listing entry (javassist-3.9.0.GA copy.jar— looks like a Mac-Finder "copy" duplicate uploaded to the bucket). The space + " copy" suffix is both an illegal URI character and an invalid OSGi version qualifier, so the entire bundle resolution path crashes and the SQLite driver never loads.Without the hint, Lucee falls through to the standard classpath resolution, which resolves
org.sqlite.JDBCnormally — provided the JAR is shipped on the lib path. Companion changes to bundlesqlite-jdbcdirectly in wheels-dev/homebrew-wheels and wheels-dev/chocolatey-wheels will follow at the next release cut.Out of scope
cli/src/(the legacy CommandBox surface) still emits the samebundleNamehint atcommands/wheels/db/create.cfc:974andmodels/EnvironmentService.cfc:1009. Worth a follow-up if that distribution is still being shipped, but not addressed here.BundleProvidershould not crash on a malformed S3 listing entry — courtesy bug report to be filed separately.Test plan
wheels new blogteston a clean machine, inspect generatedconfig/app.cfm— confirm nobundleNameline appears in either datasource block.wheels migrate latestafter creating a sample migration — confirm tables are actually created (verify withsqlite3 db/development.sqlite \".schema\").configureSQLiteDatabase, but the broader suite needs to be green).