diff --git a/changelog.d/3181-starter-app-forgebox-dependency.fixed.md b/changelog.d/3181-starter-app-forgebox-dependency.fixed.md new file mode 100644 index 0000000000..476e9f168f --- /dev/null +++ b/changelog.d/3181-starter-app-forgebox-dependency.fixed.md @@ -0,0 +1 @@ +- `box install wheels-starter-app` no longer aborts with "Error getting ForgeBox entry [wheels-authenticateThis] — the entry slug sent is invalid or does not exist". The starter app's `box.json` declared the unpublished `wheels-authenticateThis` slug as a ForgeBox dependency even though the plugin already ships bundled under `examples/starter-app/plugins/authenticateThis/`; the broken dependency (and its stale `installPaths` entry) is dropped so `wheels-core` is the only fetched dependency and the bundled plugin is used instead. `flashMessages()` is already in core, so no other plugin needs fetching (#3181) diff --git a/examples/starter-app/box.json b/examples/starter-app/box.json index 3602847d9f..49c3401cf5 100644 --- a/examples/starter-app/box.json +++ b/examples/starter-app/box.json @@ -28,12 +28,10 @@ ".env.example" ], "dependencies": { - "wheels-core": "^4.0.0", - "wheels-authenticateThis":"^1" + "wheels-core": "^4.0.0" }, "installPaths": { - "wheels-core": "vendor/wheels/", - "wheels-authenticateThis":"plugins/authenticateThis/" + "wheels-core": "vendor/wheels/" }, "private":false, "license":[ diff --git a/vendor/wheels/tests/specs/cli/StarterAppBoxJsonSpec.cfc b/vendor/wheels/tests/specs/cli/StarterAppBoxJsonSpec.cfc new file mode 100644 index 0000000000..fd91cebb27 --- /dev/null +++ b/vendor/wheels/tests/specs/cli/StarterAppBoxJsonSpec.cfc @@ -0,0 +1,79 @@ +/** + * Regression (issue #3181): examples/starter-app/box.json declared + * "wheels-authenticateThis":"^1" as a ForgeBox dependency, but that slug was + * never published to ForgeBox. `box install wheels-starter-app` fetched + * wheels-core, then aborted with: + * + * ERROR: Error getting ForgeBox entry [wheels-authenticateThis] + * The entry slug sent is invalid or does not exist + * + * leaving the install broken with no usable app. + * + * The authenticateThis plugin already ships BUNDLED under + * examples/starter-app/plugins/authenticateThis/ (alongside + * FlashMessagesBootstrap and jsconfirm), and flashMessages() lives in core + * (vendor/wheels/view/miscellaneous.cfc). So no plugin needs to be fetched + * from ForgeBox at all — the only resolvable ForgeBox dependency the starter + * app requires is wheels-core. Declaring a bundled plugin as a ForgeBox + * dependency is exactly what breaks the install. + * + * Structural assertion against the published manifest — mirrors the + * file-source regression-guard pattern in + * ApplicationCfcInjectorAssignmentSpec.cfc / buildArtifactLicenseSpec.cfc. + * Running the box.install itself would require network access to ForgeBox. + */ +component extends="wheels.WheelsTest" { + + function run() { + + describe("Starter App box.json dependency contract", () => { + + // expandPath("/wheels") resolves to vendor/wheels via the + // configured Lucee mapping; the repo root is two levels above. + var repoRoot = expandPath("/wheels/../.."); + var boxJsonPath = repoRoot & "/examples/starter-app/box.json"; + var pluginsDir = repoRoot & "/examples/starter-app/plugins"; + + it("does not declare the unpublished wheels-authenticateThis ForgeBox dependency", () => { + expect(fileExists(boxJsonPath)).toBeTrue("Missing file: " & boxJsonPath); + + var manifest = deserializeJSON(fileRead(boxJsonPath)); + var deps = structKeyExists(manifest, "dependencies") ? manifest.dependencies : {}; + + expect(structKeyExists(deps, "wheels-authenticateThis")).toBeFalse( + "box.json must not declare 'wheels-authenticateThis' as a ForgeBox dependency — the slug is unpublished, so `box install wheels-starter-app` 404s and aborts (##3181). The plugin ships bundled under plugins/authenticateThis/ instead." + ); + }); + + it("keeps wheels-core as the only ForgeBox dependency", () => { + var manifest = deserializeJSON(fileRead(boxJsonPath)); + var deps = structKeyExists(manifest, "dependencies") ? manifest.dependencies : {}; + + expect(structKeyExists(deps, "wheels-core")).toBeTrue( + "box.json must still depend on the published wheels-core package." + ); + expect(structCount(deps)).toBe(1, + "wheels-core must be the starter app's only ForgeBox dependency; auth ships bundled under plugins/ and flash lives in core (##3181)." + ); + }); + + it("does not leave a stale installPaths entry for the removed dependency", () => { + var manifest = deserializeJSON(fileRead(boxJsonPath)); + var installPaths = structKeyExists(manifest, "installPaths") ? manifest.installPaths : {}; + + expect(structKeyExists(installPaths, "wheels-authenticateThis")).toBeFalse( + "box.json installPaths must not reference 'wheels-authenticateThis' once the dependency is dropped — every installPaths key should correspond to a declared dependency (##3181)." + ); + }); + + it("bundles the authenticateThis plugin so the app boots without a ForgeBox fetch", () => { + expect(fileExists(pluginsDir & "/authenticateThis/authenticateThis.cfc")).toBeTrue( + "plugins/authenticateThis/authenticateThis.cfc must ship in the artifact so app/models/User.cfc's authenticateThis() call resolves without the unpublished ForgeBox dependency (##3181)." + ); + }); + + }); + + } + +}