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

- Version switcher now labels the 4.0 stable docs "v4.0 (current)" (was "v4.0.0"); the vestigial pre-GA `v4-0-1-snapshot` guides tree is removed and its one unique page, "Reading the Changelog", is salvaged into `v4-0-0/upgrading/`. Both sites deploy from `develop`, so in-progress patch docs already live in the `v4-0-0` tree; a separate `*-snapshot` tree is only warranted when a different minor/major (e.g. `v4-1-snapshot`) is under development. Courtesy redirects cover the high-traffic `/v4-0-1-snapshot/*` paths (#2827)
- CLI path normalisation now lives in a single, unit-tested `Helpers.normalizePath()`; `Module.$normalizePath()` (added in #2835 to fix the Windows `Resource provider [c]` crash) delegates to it instead of carrying a private copy, so the regression coverage exercises the real bootstrap path rather than a decoy. The CLI installation guide also gains a Windows troubleshooting entry for the original `there is no Resource provider available with the name [c]` error (#2841)

### Fixed

Expand Down
17 changes: 10 additions & 7 deletions cli/lucli/Module.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,19 @@ component extends="modules.BaseModule" {
}

/**
* Normalize a filesystem path for safe handoff to Lucee file APIs on
* Windows. Replaces all backslashes with forward slashes — Lucee
* accepts both on Windows, but mixed-slash strings can trip
* ResourceUtil's URI scheme detection (see init() comment).
* Bootstrap-safe wrapper around `Helpers.normalizePath()` — the single
* source of truth for path normalisation (GH #2841). Collapses Windows
* backslashes to forward slashes so a mixed-slash path like
* `C:\Users\cy/blog` can't trip Lucee's Resource API into reading `c:`
* as a URI scheme (see init() comment).
*
* No-op on a path that already uses forward slashes (Mac/Linux,
* already-normalized Windows paths).
* Helpers is instantiated directly rather than via `getService()`
* because `$normalizePath()` runs inside `init()` before
* `variables.services` exists. Helpers is a dependency-free leaf
* utility, so constructing it at bootstrap is cheap and safe.
*/
private string function $normalizePath(required string p) {
return replace(arguments.p, "\", "/", "all");
return new services.Helpers().normalizePath(arguments.p);
}

/**
Expand Down
31 changes: 31 additions & 0 deletions cli/lucli/services/Helpers.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,37 @@ component {
return trim(reReplace(str, "[{}()^$&%##!@=<>:;,~`'*?/+|\[\]\-\\]", "", "all"));
}

/**
* Convert any filesystem path to a single-slash, forward-slash form so
* it is safe to hand to Lucee's file APIs on Windows.
*
* Regression: GH #2841 — `wheels new` / `wheels start` on Windows blew
* up with `lucee.runtime.exp.NativeException: there is no Resource
* provider available with the name [c]`. The bootstrap handed
* `java.io.File.getCanonicalPath()` output (e.g. `C:\Users\tim\Projects`)
* to `directoryExists(... & "/vendor/wheels")`, producing the mixed-slash
* string `C:\Users\tim\Projects/vendor/wheels`. Lucee's Resource API
* parsed `c:` as a URI scheme and bailed because no `c` provider is
* registered. Normalising to pure forward slashes keeps the path
* unambiguous on Windows while being a no-op on POSIX.
*
* `Module.$normalizePath()` delegates here so the bootstrap path and the
* unit tests exercise one implementation (#2835 originally carried a
* private copy inside Module.cfc).
*/
public string function normalizePath(required string path) {
if (!len(arguments.path)) return "";
var rv = replace(arguments.path, "\", "/", "all");
// Collapse doubled slashes from naïve concatenation, but preserve a
// leading `//` (UNC / network-share prefix on Windows). Guard the
// mid() against a count of 0 (when rv is exactly "//"), which can
// trip Lucee 7's string-range handling (cf. CLAUDE.md cross-engine #8).
var leading = left(rv, 2) == "//" ? "//" : "";
var body = len(leading) ? (len(rv) > 2 ? mid(rv, 3, len(rv) - 2) : "") : rv;
body = reReplace(body, "/{2,}", "/", "all");
return leading & body;
}

/**
* Generate a migration timestamp (YYYYMMDDHHMMSS)
*/
Expand Down
58 changes: 58 additions & 0 deletions cli/lucli/tests/specs/services/HelpersSpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,64 @@ component extends="wheels.wheelstest.system.BaseSpec" {

});

describe("normalizePath()", () => {

// Regression: GH #2841 — `wheels new`/`wheels start` on Windows
// failed with "lucee.runtime.exp.NativeException: there is no
// Resource provider available with the name [c]". The CLI
// concatenated a Windows-form path (backslashes from
// java.io.File.getCanonicalPath()) with "/vendor/wheels" and
// fed the mixed-slash result to Lucee's Resource API, which
// then parsed "c:" as a URI scheme. Forward-slash normalization
// makes the path unambiguous on Windows while being a no-op on
// POSIX. Module.$normalizePath() delegates to this method, so
// these cases cover the real bootstrap path — not a copy.

it("converts Windows backslashes to forward slashes", () => {
expect(helpers.normalizePath("C:\Users\tim\Projects"))
.toBe("C:/Users/tim/Projects");
});

it("leaves POSIX paths unchanged", () => {
expect(helpers.normalizePath("/home/runner/work/wheels"))
.toBe("/home/runner/work/wheels");
});

it("returns an empty string for empty input", () => {
expect(helpers.normalizePath("")).toBe("");
});

it("collapses doubled forward slashes from concatenation", () => {
expect(helpers.normalizePath("/a/b//c")).toBe("/a/b/c");
});

it("preserves a Windows drive-letter prefix after normalization", () => {
var normalized = helpers.normalizePath("C:\Users\tim\Projects");
expect(normalized & "/vendor/wheels")
.toBe("C:/Users/tim/Projects/vendor/wheels");
// Sanity: no remaining backslash means downstream
// directoryExists() won't trip Lucee's scheme parser.
expect(find("\", normalized)).toBe(0);
});

it("preserves a UNC network-share prefix", () => {
expect(helpers.normalizePath("//server/share/path"))
.toBe("//server/share/path");
});

it("collapses doubled slashes inside a UNC path without eating the prefix", () => {
expect(helpers.normalizePath("//server//share"))
.toBe("//server/share");
});

it("handles a bare double-slash root without a mid() range error", () => {
// Degenerate UNC root: rv === "//" makes the internal mid()
// count 0. Guarded so it can't trip Lucee 7 (cross-engine #8).
expect(helpers.normalizePath("//")).toBe("//");
});

});

});

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,18 @@ On Linux, the `.deb`/`.rpm` package installs `/usr/bin/wheels`, which should be

The wheels formula deliberately isolates runtime state under `~/.wheels/` (via `LUCLI_HOME`) so a standalone `lucli` install — which uses `~/.lucli/` — stays out of the way. If you previously had LuCLI installed directly and see odd module-resolution errors, check that the wrapper set `LUCLI_HOME` correctly (`wheels system env` will dump the resolved environment) and that `~/.wheels/modules/wheels/` contains a current `Module.cfc` and `.module-version` file.

### Windows: `there is no Resource provider available with the name [c]`

On Windows, `wheels new`, `wheels start`, and most other subcommands crashed before any work could happen with:

```
lucee.runtime.exp.NativeException: there is no Resource provider available
with the name [c], available resource providers are [ftp, zip, tar, tgz,
http, https, ram, s3]
```

The cause was mixed-slash paths: `java.io.File.getCanonicalPath()` on Windows returns backslash form (`C:\Users\tim\Projects`), which — when concatenated with a forward-slash suffix — produced a string like `C:\Users\tim\Projects/vendor/wheels`. Lucee's Resource API parsed `c:` as a URI scheme and bailed because no `c` provider is registered. This is fixed in the release that includes #2841. Update to the latest version and the error will not recur — `scoop update wheels` for Scoop installs, or re-fetch the latest Wheels Module (see **Manual JAR install** above) if you wired it up by hand. `wheels --version` was unaffected because LuCLI handles that flag before dispatching to the module.

## Related commands

<CardGrid>
Expand Down
Loading