diff --git a/cli/lucli/services/packages/PackagesMainCli.cfc b/cli/lucli/services/packages/PackagesMainCli.cfc index 16718a43ca..53a0c759ab 100644 --- a/cli/lucli/services/packages/PackagesMainCli.cfc +++ b/cli/lucli/services/packages/PackagesMainCli.cfc @@ -252,8 +252,16 @@ component { } private struct function $parseTarget(required string target) { - if (Find("@", arguments.target)) { - local.at = Find("@", arguments.target); + local.at = Find("@", arguments.target); + if (local.at == 1) { + // Target starts with "@" — no name before the pin. Reject cleanly + // rather than crashing on Left(str, 0) (a documented Lucee 7 hazard). + Throw( + type = "Wheels.Packages.BadInput", + message = "Package name is required before '@'. Use: wheels packages install [@]" + ); + } + if (local.at > 1) { return { name: Left(arguments.target, local.at - 1), pin: Mid(arguments.target, local.at + 1, Len(arguments.target)) diff --git a/cli/lucli/tests/specs/packages/PackagesMainCliSpec.cfc b/cli/lucli/tests/specs/packages/PackagesMainCliSpec.cfc index 0975814ff8..d2e659b516 100644 --- a/cli/lucli/tests/specs/packages/PackagesMainCliSpec.cfc +++ b/cli/lucli/tests/specs/packages/PackagesMainCliSpec.cfc @@ -190,6 +190,24 @@ component extends="wheels.wheelstest.system.BaseSpec" { stack.cache.refresh(); DirectoryDelete(proj, true); }); + + it("install throws BadInput when target starts with '@' (empty name)", () => { + // Regression guard: `@1.0.0` used to hit Left(str, 0), which + // crashes on Lucee 7 and produces a cryptic error on other + // engines. Must reject cleanly with BadInput. + var proj = $scratch(); + var stack = $buildStack(proj); + var threw = false; + try { + stack.cli.install({target: "@1.0.0"}); + } catch (any e) { + threw = true; + expect(e.type).toBe("Wheels.Packages.BadInput"); + } + expect(threw).toBeTrue(); + stack.cache.refresh(); + DirectoryDelete(proj, true); + }); }); describe("PackagesRegistryCli", () => {