From b29c3ab01cf76265a20f6a3c9899001754955e22 Mon Sep 17 00:00:00 2001 From: meh Date: Thu, 20 Aug 2026 15:46:52 +0700 Subject: [PATCH] fix(application): check the boundary of the major being compiled `boundaryFor()` maps Solid 1 to `solid-layouts/application-boundary` and Solid 2 to `solid-layouts/solid-2/application-boundary`, and the library generator uses it, so a `solid: 2` build writes the solid-2 spelling into its entry. `validateComponent()` did not ask. It greped for the hardcoded 1.9 specifier, which made the pairing this package generates the pairing it rejects: a correct Solid 2 bundle failed with : entry has no application compiler boundary naming a file that was exactly right. Neither specifier contains the other as written, so `includes` cannot accidentally accept the wrong one either way round. Thread the major from `compileApplication` through `resolveLayoutSource` to `validateComponent` and ask `boundaryFor()`, which is what the plugin two functions away already does. `boundaryFor(undefined)` still returns the 1.9 boundary, so nothing changes for a consumer that never sets `solid`. Found from the consumer side: a Chuzz build on Solid 2 could not get a library past validation with either arm. Generating for 1.9 emits `solid-js/web` imports, a subpath Solid 2 does not export; generating for 2 failed the check above. There was no combination that worked. Two tests, both of which fail without the change: a solid-2 entry is accepted under `solid: 2`, and a 1.9 entry is rejected under it. The boundary check had no coverage at all, which is how the mismatch shipped. --- packages/solid-layouts-oxc/application.js | 22 ++++++++++--- .../solid-layouts-oxc/application.test.js | 31 +++++++++++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/packages/solid-layouts-oxc/application.js b/packages/solid-layouts-oxc/application.js index e3e026f..87d961b 100644 --- a/packages/solid-layouts-oxc/application.js +++ b/packages/solid-layouts-oxc/application.js @@ -186,7 +186,7 @@ function resolvePublicPackageEntry(root, module, subpath = ".") { ); } -function validateComponent(module, packageRoot, name, component) { +function validateComponent(module, packageRoot, name, component, solid) { if (component?.kind === "embedded") return; if (component?.kind !== "generated") { throw new Error(`${module}: component ${name} has unsupported manifest kind ${JSON.stringify(component?.kind)}`); @@ -214,7 +214,19 @@ function validateComponent(module, packageRoot, name, component) { } } - if (!entrySource.includes(APPLICATION_BOUNDARY)) { + /* + * The boundary this major actually emits, not the 1.9 one. + * + * `boundaryFor()` already encodes the mapping and the library generator uses + * it, so a `solid: 2` build writes `solid-layouts/solid-2/application-boundary` + * into the entry. Checking for the hardcoded 1.9 spelling made the pairing we + * generate the pairing we reject: the entry is correct and validation fails on + * output this package just produced. + * + * `includes` on the Solid 2 specifier is not satisfied by the 1.9 one either + * way round, because neither string contains the other as written. + */ + if (!entrySource.includes(boundaryFor(solid).specifier)) { throw new Error(`${module}: ${name} entry has no application compiler boundary`); } if (!new RegExp(`\\bexport\\s+const\\s+${component.recipeExport}\\b`).test(recipeSource)) { @@ -229,7 +241,7 @@ function validateComponent(module, packageRoot, name, component) { } } -function resolveLayoutSource(root, configured) { +function resolveLayoutSource(root, configured, solid) { const module = typeof configured === "string" ? configured : configured.module; if (!module) throw new Error("configured Layout source is missing its module name"); const packageRoot = typeof configured === "string" @@ -271,7 +283,7 @@ function resolveLayoutSource(root, configured) { } const exports = Object.keys(components).sort(); if (exports.length === 0) throw new Error(`${module} Layout manifest has no components`); - for (const name of exports) validateComponent(module, packageRoot, name, components[name]); + for (const name of exports) validateComponent(module, packageRoot, name, components[name], solid); return { module, @@ -291,7 +303,7 @@ function compileApplication(options = {}) { if (!Array.isArray(layouts) || layouts.length === 0) { throw new Error("application compiler requires at least one Layout package"); } - const sources = layouts.map((configured) => resolveLayoutSource(root, configured)); + const sources = layouts.map((configured) => resolveLayoutSource(root, configured, options.solid)); const rootSources = sources.map(({ module, exports, publicEntry }) => ({ module, exports, diff --git a/packages/solid-layouts-oxc/application.test.js b/packages/solid-layouts-oxc/application.test.js index a1d3de3..fd969d4 100644 --- a/packages/solid-layouts-oxc/application.test.js +++ b/packages/solid-layouts-oxc/application.test.js @@ -266,3 +266,34 @@ test("rejects a generated entry that disagrees with its component record", () => "entry call site disagrees", ); }); + +/* + * The boundary check has to follow the major being compiled. + * + * `boundaryFor()` already maps 1 and 2 to different specifiers, and the library + * generator uses it, so a `solid: 2` library writes the solid-2 spelling into + * its entry. Validation greping for the hardcoded 1.9 spelling made the pairing + * this package generates the pairing it rejects: a correct Solid 2 bundle failed + * with "entry has no application compiler boundary", naming a file that was + * exactly right. + */ +test("accepts a solid-2 entry when compiling for Solid 2", () => { + const { root, packageRoot } = fixture(); + const copy = makePackageEditable(root, packageRoot); + const entryPath = join(copy, "index.ts"); + const entry = readFileSync(entryPath, "utf8").replaceAll( + "solid-layouts/application-boundary", + "solid-layouts/solid-2/application-boundary", + ); + writeFileSync(entryPath, entry); + expect(() => + compileApplication({ root, layouts: ["@pathscale/test-ui"], solid: 2 }), + ).not.toThrow(); +}); + +test("rejects a 1.9 entry when compiling for Solid 2", () => { + const { root, packageRoot } = fixture(); + expect(() => compileApplication({ root, layouts: ["@pathscale/test-ui"], solid: 2 })).toThrow( + "entry has no application compiler boundary", + ); +});