diff --git a/CHANGELOG.md b/CHANGELOG.md index 961baedff..bc365070e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ All historical references to "CFWheels" in this changelog have been preserved fo ### Fixed +- Linux `.deb` / `.rpm` packages double-nested the framework at `/opt/wheels/module/vendor/wheels/wheels/` instead of `/opt/wheels/module/vendor/wheels/`. `wheels-core-VER.zip` carries a top-level `wheels/` directory that `unzip` preserves; the nfpm `type: tree` rule then copied the entire `build/framework/` tree (wrapper and all) into the destination, leaving `Injector.cfc` one level too deep. Every fresh `wheels new` install on Ubuntu/Fedora then crashed on first request with `could not find component or class with name [wheels.Injector]`, cascading into the cryptic `The key [WO] does not exist.` error in `onError`. The brew formula handles this correctly via `(share/"wheels/framework/wheels").install Dir["*"]`; the Linux nfpm configs now pin `src` at `./build/framework/wheels/` to match. Regression spec at `vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc` (#2773) - `onError` in the generated app template and demo `public/Application.cfc` now guards `application.wo` with `StructKeyExists(application, "wo")` after the recovery try/catch. When `new wheels.Injector(...)` fails during `onApplicationStart` (e.g. a stale `/wheels` mapping under Lucee Express 7), the original error is preserved via a minimal HTML fallback instead of cascading into the cryptic "The key [WO] does not exist" exception that hit "Your First 15 Minutes" tutorial users on fresh installs ---- diff --git a/tools/distribution-drafts/linux-packages/nfpm-wheels-be.yaml b/tools/distribution-drafts/linux-packages/nfpm-wheels-be.yaml index 7239b33cc..2e19d64fe 100644 --- a/tools/distribution-drafts/linux-packages/nfpm-wheels-be.yaml +++ b/tools/distribution-drafts/linux-packages/nfpm-wheels-be.yaml @@ -34,7 +34,14 @@ contents: - src: ./build/module/ dst: /opt/wheels/module/ type: tree - - src: ./build/framework/ + # Framework source — see nfpm-wheels.yaml for the full explainer of why src + # MUST be ./build/framework/wheels/ (with trailing /wheels/), not bare + # ./build/framework/. Short version: nfpm `type: tree` copies src contents + # into dst, and the wheels-core-VER.zip preserves a top-level wheels/ dir, + # so without the inner /wheels/ we get a double-nested + # /opt/wheels/module/vendor/wheels/wheels/ that breaks runtime resolution + # of `wheels.Injector`. See issue #2773. + - src: ./build/framework/wheels/ dst: /opt/wheels/module/vendor/wheels/ type: tree - src: ./build/sqlite-jdbc.jar diff --git a/tools/distribution-drafts/linux-packages/nfpm-wheels.yaml b/tools/distribution-drafts/linux-packages/nfpm-wheels.yaml index 823985983..331c4dc85 100644 --- a/tools/distribution-drafts/linux-packages/nfpm-wheels.yaml +++ b/tools/distribution-drafts/linux-packages/nfpm-wheels.yaml @@ -38,7 +38,16 @@ contents: dst: /opt/wheels/module/ type: tree # Framework source — staged into vendor/wheels/ in scaffolded apps. - - src: ./build/framework/ + # IMPORTANT: src points at ./build/framework/wheels/ (the inner directory), + # NOT ./build/framework/. wheels-core-VER.zip has a top-level wheels/ dir + # that `unzip` preserves, leaving the framework at ./build/framework/wheels/. + # nfpm's `type: tree` copies the *contents* of src into dst, so without the + # trailing /wheels/ on src, the framework double-nests as + # /opt/wheels/module/vendor/wheels/wheels/Injector.cfc and Lucee can't + # resolve `wheels.Injector` at app startup. See issue #2773. The brew + # formula handles this the equivalent way — see homebrew-wheels + # Formula/wheels.rb's `(share/"wheels/framework/wheels").install Dir["*"]`. + - src: ./build/framework/wheels/ dst: /opt/wheels/module/vendor/wheels/ type: tree # SQLite JDBC — required at first run by the wrapper script (cliff fix from diff --git a/vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc b/vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc index 225c3b1f8..9baa66d56 100644 --- a/vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc +++ b/vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc @@ -164,6 +164,58 @@ component extends="wheels.WheelsTest" { ); }); + it("stages framework src from ./build/framework/wheels/ so contents flatten under vendor/wheels/", () => { + var src = fileRead(t.path); + // wheels-core-VER.zip has a top-level `wheels/` directory inside it + // (the smoke test asserts this at tools/ci/smoke-test-module.sh:112). + // nfpm `type: tree` copies the *contents* of src into dst, so if src + // points at ./build/framework/ (one level above the inner wheels/), + // the entire wheels/ subdirectory itself lands at dst — producing + // /opt/wheels/module/vendor/wheels/wheels/Injector.cfc instead of + // /opt/wheels/module/vendor/wheels/Injector.cfc. The framework then + // never loads at runtime ("could not find component or class with + // name [wheels.Injector]" — see issue ##2773). + // + // The brew formula handles this by explicitly re-introducing the + // wheels/ wrapper at stage time — see homebrew-wheels Formula/wheels.rb:62 + // — (share/"wheels/framework/wheels").install Dir["*"]. The .deb/.rpm + // equivalent is to point src at the inner wheels/ directory directly. + // + // `[[:space:]]+` matches across the YAML line break between the src + // value and `dst:` — POSIX `[[:space:]]` resolves to Java's `\s` in + // both Lucee and Adobe CF, which includes `\n`. + var hasFixedPair = reFindNoCase( + "src:[[:space:]]+\./build/framework/wheels/[[:space:]]+dst:[[:space:]]+/opt/wheels/module/vendor/wheels/", + src + ) > 0; + expect(hasFixedPair).toBeTrue( + t.label & " must declare `src: ./build/framework/wheels/` (with the " + & "trailing /wheels/) for the framework contents entry. Without the " + & "inner /wheels/ segment, nfpm's `type: tree` double-nests the " + & "framework at /opt/wheels/module/vendor/wheels/wheels/, and Lucee " + & "fails to resolve `wheels.Injector` at app startup. See issue ##2773." + ); + + // Negative guard: the buggy bare-framework form must not coexist + // with the fixed form. A future copy-paste could leave both entries + // in the file, and nfpm would happily stage both — the bare one + // reintroduces the double-nesting. Pairs with the toBeTrue above + // per the dual-assertion pattern already used by the wrapper-routing + // checks at lines 60-68 / 81-106. + var hasBuggyPair = reFindNoCase( + "src:[[:space:]]+\./build/framework/[[:space:]]+dst:[[:space:]]+/opt/wheels/module/vendor/wheels/", + src + ) > 0; + expect(hasBuggyPair).toBeFalse( + t.label & " must NOT declare `src: ./build/framework/` (without " + & "the trailing /wheels/) for any contents entry targeting " + & "/opt/wheels/module/vendor/wheels/. If both the bare and the " + & "/wheels/-suffixed entries coexist, nfpm stages the inner " + & "wheels/ wrapper as a subdirectory and the framework " + & "double-nests. See issue ##2773." + ); + }); + }); })(target); }