Skip to content

Commit 4f1914d

Browse files
bpamiriclaude
andauthored
fix(cli): close 0.0.0-dev version display gap on installed Wheels apps (#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>
1 parent 322b43b commit 4f1914d

4 files changed

Lines changed: 88 additions & 20 deletions

File tree

‎.github/workflows/release.yml‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,16 +203,26 @@ jobs:
203203
./tools/build/scripts/build-starterApp.sh "${{ env.WHEELS_VERSION }}"
204204
205205
- name: Build Wheels Module Tarball
206+
env:
207+
MODULE_VERSION: ${{ env.WHEELS_VERSION }}
206208
run: |
207-
mkdir -p artifacts/wheels/${{ env.WHEELS_VERSION }}
209+
mkdir -p "artifacts/wheels/${MODULE_VERSION}"
208210
# Bundle codegen templates into the module so Templates.cfc::resolveTemplateDir()
209211
# finds them at moduleRoot/templates/codegen/ in installed distributions.
210212
# Without this, every template-driven generator (model, controller, view,
211213
# scaffold, helper, api-resource) fails or emits empty output. See #1944.
212214
mkdir -p cli/lucli/templates/codegen
213215
cp -R cli/src/templates/. cli/lucli/templates/codegen/
214-
tar czf artifacts/wheels/${{ env.WHEELS_VERSION }}/wheels-module-${{ env.WHEELS_VERSION }}.tar.gz -C cli/lucli .
215-
cd cli/lucli && zip -r ../../artifacts/wheels/${{ env.WHEELS_VERSION }}/wheels-module-${{ env.WHEELS_VERSION }}.zip . && cd ../..
216+
# Substitute @build.version@ placeholders in the LuCLI module before
217+
# tarring. Without this, module.json ships with the literal placeholder
218+
# (or a stale hardcoded version), causing BaseModule.version() to return
219+
# the wrong value at runtime and the dev toolbar / `wheels --help` to
220+
# report a stale version. Mirrors the substitution prepare-cli.sh does
221+
# for the wheels-cli ForgeBox artifact. See #2326.
222+
find cli/lucli -type f \( -name "*.json" -o -name "*.md" -o -name "*.cfm" -o -name "*.cfc" \) -exec sed -i.bak "s/@build\.version@/${MODULE_VERSION}/g" {} \;
223+
find cli/lucli -name "*.bak" -delete
224+
tar czf "artifacts/wheels/${MODULE_VERSION}/wheels-module-${MODULE_VERSION}.tar.gz" -C cli/lucli .
225+
cd cli/lucli && zip -r "../../artifacts/wheels/${MODULE_VERSION}/wheels-module-${MODULE_VERSION}.zip" . && cd ../..
216226
cd artifacts/wheels/${{ env.WHEELS_VERSION }}
217227
md5sum wheels-module-${{ env.WHEELS_VERSION }}.tar.gz > wheels-module-${{ env.WHEELS_VERSION }}.tar.gz.md5 || md5 -r wheels-module-${{ env.WHEELS_VERSION }}.tar.gz > wheels-module-${{ env.WHEELS_VERSION }}.tar.gz.md5
218228
sha512sum wheels-module-${{ env.WHEELS_VERSION }}.tar.gz > wheels-module-${{ env.WHEELS_VERSION }}.tar.gz.sha512 || shasum -a 512 wheels-module-${{ env.WHEELS_VERSION }}.tar.gz > wheels-module-${{ env.WHEELS_VERSION }}.tar.gz.sha512

‎cli/lucli/module.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "wheels",
3-
"version": "4.0.0",
3+
"version": "@build.version@",
44
"description": "Wheels CLI - Code generation, migrations, testing, and server management for Wheels",
55
"author": "Wheels Team",
66
"license": "Apache-2.0",

‎vendor/wheels/Global.cfc‎

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2782,28 +2782,38 @@ return local.$wheels;
27822782
);
27832783
}
27842784
if (local.box.version == "@build.version@") {
2785-
// Dev checkout. Try to synthesize a more useful version by reading the
2786-
// enclosing repo's root box.json — when that box.json identifies itself as
2787-
// the wheels-dev/wheels monorepo, report "<rootversion>-dev" so the
2788-
// homepage shows the upcoming version rather than a blank placeholder.
2785+
// The framework's own box.json was shipped with the placeholder
2786+
// unsubstituted (dev checkout, or release-pipeline gap). Try, in
2787+
// order: (1) the enclosing repo's root box.json — for a wheels-dev/
2788+
// wheels monorepo dev checkout, that resolves to a `<rootversion>-dev`
2789+
// version reflecting the upcoming release; (2) the app's own box.json
2790+
// — for an installed app scaffolded from `wheels-base-template`, that
2791+
// box.json carries the precise framework SNAPSHOT version stamped in
2792+
// at release time, so we use it verbatim. Without this second probe,
2793+
// the runtime returns a misleading "0.0.0-dev" on installed apps when
2794+
// the framework's box.json substitution slipped through (see GH #2326).
27892795
local.rootPath = Len(arguments.rootBoxJsonPath)
27902796
? arguments.rootBoxJsonPath
27912797
: GetDirectoryFromPath(GetCurrentTemplatePath()) & "../../box.json";
27922798
try {
27932799
if (FileExists(local.rootPath)) {
27942800
local.rootBox = DeserializeJSON(FileRead(local.rootPath));
2795-
local.isWheelsRepo = IsStruct(local.rootBox)
2796-
&& (
2797-
(StructKeyExists(local.rootBox, "slug") && local.rootBox.slug == "wheels")
2798-
|| (StructKeyExists(local.rootBox, "name") && local.rootBox.name == "Wheels.fw")
2799-
);
2800-
if (
2801-
local.isWheelsRepo
2802-
&& StructKeyExists(local.rootBox, "version")
2803-
&& Len(local.rootBox.version)
2804-
&& local.rootBox.version != "@build.version@"
2805-
) {
2806-
return local.rootBox.version & "-dev";
2801+
if (IsStruct(local.rootBox) && StructKeyExists(local.rootBox, "version") && Len(local.rootBox.version) && local.rootBox.version != "@build.version@") {
2802+
local.isWheelsRepo = (StructKeyExists(local.rootBox, "slug") && local.rootBox.slug == "wheels")
2803+
|| (StructKeyExists(local.rootBox, "name") && local.rootBox.name == "Wheels.fw");
2804+
if (local.isWheelsRepo) {
2805+
return local.rootBox.version & "-dev";
2806+
}
2807+
// 2. Installed app: the parent's box.json is the user's
2808+
// scaffolded app.box.json, which `wheels-base-template`
2809+
// fills with the precise framework version at release
2810+
// time. Recognize the template by slug so we don't pick
2811+
// up a hand-rolled box.json with an unrelated version.
2812+
local.isBaseTemplate = (StructKeyExists(local.rootBox, "slug") && local.rootBox.slug == "wheels-base-template")
2813+
|| (StructKeyExists(local.rootBox, "name") && local.rootBox.name == "Wheels Base Template");
2814+
if (local.isBaseTemplate) {
2815+
return local.rootBox.version;
2816+
}
28072817
}
28082818
}
28092819
} catch (any e) {

‎vendor/wheels/tests/specs/events/frameworkVersionSpec.cfc‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,54 @@ component extends="wheels.WheelsTest" {
6161
}
6262
});
6363

64+
it("$readFrameworkVersion uses wheels-base-template's version verbatim when the framework's own box.json placeholder slipped through (##2326)", () => {
65+
// Installed-app path: the user's `wheels new`-scaffolded app has a
66+
// box.json at the app root populated from `wheels-base-template`
67+
// (slug=wheels-base-template), and that box.json carries the
68+
// precise framework SNAPSHOT version stamped at release time. If
69+
// the framework's own box.json substitution slipped through, we
70+
// still surface the correct version instead of "0.0.0-dev".
71+
var fwTmp = getTempDirectory() & "wheels-version-fw-#CreateUUID()#.json";
72+
var rootTmp = getTempDirectory() & "wheels-version-root-#CreateUUID()#.json";
73+
fileWrite(fwTmp, '{"version":"@build.version@"}');
74+
fileWrite(rootTmp, '{"name":"Wheels Base Template","slug":"wheels-base-template","version":"4.0.0-SNAPSHOT+1625"}');
75+
try {
76+
expect(g.$readFrameworkVersion(fwTmp, rootTmp)).toBe("4.0.0-SNAPSHOT+1625");
77+
} finally {
78+
fileDelete(fwTmp);
79+
fileDelete(rootTmp);
80+
}
81+
});
82+
83+
it("$readFrameworkVersion ignores wheels-base-template when its own version is also the unreplaced placeholder (##2326)", () => {
84+
var fwTmp = getTempDirectory() & "wheels-version-fw-#CreateUUID()#.json";
85+
var rootTmp = getTempDirectory() & "wheels-version-root-#CreateUUID()#.json";
86+
fileWrite(fwTmp, '{"version":"@build.version@"}');
87+
fileWrite(rootTmp, '{"name":"Wheels Base Template","slug":"wheels-base-template","version":"@build.version@"}');
88+
try {
89+
expect(g.$readFrameworkVersion(fwTmp, rootTmp)).toBe("0.0.0-dev");
90+
} finally {
91+
fileDelete(fwTmp);
92+
fileDelete(rootTmp);
93+
}
94+
});
95+
96+
it("$readFrameworkVersion prefers the monorepo signal over the base-template signal when both could match (##2326)", () => {
97+
// Defensive: if both the monorepo and base-template markers somehow
98+
// coexist at the same path, the monorepo signal (a dev checkout)
99+
// is the authoritative one and the suffix should be "-dev".
100+
var fwTmp = getTempDirectory() & "wheels-version-fw-#CreateUUID()#.json";
101+
var rootTmp = getTempDirectory() & "wheels-version-root-#CreateUUID()#.json";
102+
fileWrite(fwTmp, '{"version":"@build.version@"}');
103+
fileWrite(rootTmp, '{"name":"Wheels.fw","slug":"wheels","version":"4.0.0"}');
104+
try {
105+
expect(g.$readFrameworkVersion(fwTmp, rootTmp)).toBe("4.0.0-dev");
106+
} finally {
107+
fileDelete(fwTmp);
108+
fileDelete(rootTmp);
109+
}
110+
});
111+
64112
it("$readFrameworkVersion falls back to 0.0.0-dev when the enclosing box.json is also unreplaced", () => {
65113
var fwTmp = getTempDirectory() & "wheels-version-fw-#CreateUUID()#.json";
66114
var rootTmp = getTempDirectory() & "wheels-version-root-#CreateUUID()#.json";

0 commit comments

Comments
 (0)