From d3e5a403b9b9e00b66485a427db6a86fb10d3268 Mon Sep 17 00:00:00 2001 From: meh Date: Fri, 14 Aug 2026 13:13:56 +0700 Subject: [PATCH] fix(library): say why source mode wants an exports list The message was "must declare its public Layout exports", which reads as a missing required field. It sends you to write the list, when the question worth asking is whether you wanted source mode at all: convention-based discovery needs no such list, and the documentation says so in as many words. It also said nothing about the cost of keeping one. Nothing compares the list to the barrel, so a rename that misses it leaves the manifest naming components that no longer exist while rejecting the ones that do, with no diagnostic anywhere. That is worth knowing before you choose the mode, not after. The message now names the trade and points at both ways out. --- packages/solid-layouts-oxc/library.js | 37 +++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/solid-layouts-oxc/library.js b/packages/solid-layouts-oxc/library.js index 053007f..5b26a08 100644 --- a/packages/solid-layouts-oxc/library.js +++ b/packages/solid-layouts-oxc/library.js @@ -219,6 +219,39 @@ function lintLibrary(options = {}) { }; } +/** + * Why source mode asks for a list when nothing else does. + * + * Convention-based discovery reads every `*.layout.tsx` and emits a `generated` + * manifest entry carrying the recipe, the Layout and their exports, so the + * application compiler can verify the join. Source mode generates alongside an + * existing package instead, where the public surface is whatever that package's + * own barrel exports, including parts like `AccordionItem` that share a parent's + * layout file and have no file of their own. The compiler cannot infer that, so + * source mode is told. + * + * The old message said only "must declare its public Layout exports", which + * reads as a missing field rather than as a consequence of the mode, and sends + * you to write the list rather than to ask whether you wanted the mode. The + * documentation says no authored component manifest is required, and for + * convention-based discovery that is true. + */ +function sourceModeExportsMessage(configPath) { + return [ + `${configPath}: source mode needs an "exports" array naming this package's public components.`, + "", + 'Only `mode: "source"` needs one. Convention-based discovery derives the manifest from', + "every `*.layout.tsx` it finds, and emits richer entries the application compiler can", + "verify; source mode generates alongside a package whose public surface it cannot see,", + "so the list is how it learns the names.", + "", + "If this package does not need adjacent generation, delete the config and let discovery", + "do it. If it does, the list has to be kept in step with the barrel by hand: nothing", + "compares them, and a rename that misses it leaves the manifest naming components that", + "no longer exist while rejecting the ones that do.", + ].join("\n"); +} + function generateLibrarySource(options = {}) { const root = resolve(options.root || process.cwd()); const { configPath, config } = readLibraryConfig(root, options); @@ -226,7 +259,7 @@ function generateLibrarySource(options = {}) { throw new Error(`${configPath || root} must set mode to "source" for adjacent generation`); } if (!Array.isArray(config.exports) || config.exports.length === 0) { - throw new Error(`${configPath} must declare its public Layout exports`); + throw new Error(sourceModeExportsMessage(configPath)); } const lint = lintLibrary({ ...options, root }); if (lint.failed) { @@ -256,7 +289,7 @@ function emitSourceManifest(options = {}) { const { configPath, config } = readLibraryConfig(root, options); if (config.mode !== "source") throw new Error(`${configPath || root} is not a source library config`); const exports = config.exports || []; - if (exports.length === 0) throw new Error(`${configPath} must declare its public Layout exports`); + if (exports.length === 0) throw new Error(sourceModeExportsMessage(configPath)); const duplicates = exports.filter((name, index) => exports.indexOf(name) !== index); if (duplicates.length) throw new Error(`${configPath} repeats Layout export ${duplicates[0]}`); const sourcePackage = readJson(resolve(root, "package.json"));