Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ All historical references to "CFWheels" in this changelog have been preserved fo

----

## [Unreleased]

### 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)

----

# [4.0.1](https://github.com/wheels-dev/wheels/releases/tag/v4.0.1) => 2026-05-20

> **Wheels 4.0.1** — first patch on the 4.0 line. Hardens Adobe ColdFusion 2023/2025 compatibility (Adobe-specific `cfheader` attributeCollection rejection, `env()` reserved-word parameter, Vite asset-walk array-by-value), fixes the Windows Scoop install regressions (`wheels.cmd` cmd.exe pre-parser, `.zip.sha512` sidecar layout), and adds `viewStyle` framework presets to `paginationNav()` plus plural `mappings` aliases to `package.json`. ~100 PRs since the 4.0.0 GA (2026-05-12).
Expand Down
9 changes: 8 additions & 1 deletion tools/distribution-drafts/linux-packages/nfpm-wheels-be.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion tools/distribution-drafts/linux-packages/nfpm-wheels.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 52 additions & 0 deletions vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Loading