@uniweb/build 0.44.2 → 0.44.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -7
- package/package.json +4 -4
- package/src/schema.js +29 -2
- package/src/site/config.js +1 -0
- package/src/utils/foundation-resolution-check.js +55 -2
package/README.md
CHANGED
|
@@ -324,11 +324,15 @@ After building, your foundation will contain:
|
|
|
324
324
|
|
|
325
325
|
```
|
|
326
326
|
dist/
|
|
327
|
-
├──
|
|
328
|
-
├──
|
|
327
|
+
├── entry.js # The foundation — section types, layouts, declarations
|
|
328
|
+
├── entry.js.map # Source map
|
|
329
|
+
├── entry-ssr.js # Single-file twin of entry.js, for server-side prerender
|
|
330
|
+
├── runtime-pin.json # The @uniweb/runtime version it was built against
|
|
331
|
+
├── *.js # Code-split chunks, when the foundation has any
|
|
332
|
+
├── assets/ # Styles and static assets
|
|
329
333
|
└── meta/ # Editor metadata (not needed at runtime)
|
|
330
334
|
├── schema.json # Full component metadata for editors
|
|
331
|
-
└── previews/ # Preset preview images
|
|
335
|
+
└── previews/ # Preset preview images, when presets declare them
|
|
332
336
|
└── Hero/
|
|
333
337
|
└── default.webp
|
|
334
338
|
```
|
|
@@ -357,9 +361,9 @@ The `_self` object contains foundation-level metadata:
|
|
|
357
361
|
| `name` | `package.json` | Foundation package name |
|
|
358
362
|
| `version` | `package.json` | Foundation version |
|
|
359
363
|
| `description` | `package.json` | Foundation description |
|
|
360
|
-
| `vars` | `
|
|
364
|
+
| `vars` | `main.js` | CSS custom properties sites can override |
|
|
361
365
|
|
|
362
|
-
Identity fields (`name`, `version`, `description`) come from the foundation's `package.json`. Configuration fields (`vars`, etc.) come from `
|
|
366
|
+
Identity fields (`name`, `version`, `description`) come from the foundation's `package.json`. Configuration fields (`vars`, etc.) come from the foundation's `main.js`.
|
|
363
367
|
|
|
364
368
|
## API Reference
|
|
365
369
|
|
|
@@ -370,7 +374,7 @@ Identity fields (`name`, `version`, `description`) come from the foundation's `p
|
|
|
370
374
|
| `discoverComponents(srcDir)` | Discover all section types (folders with meta.js) |
|
|
371
375
|
| `loadComponentMeta(componentDir)` | Load meta file for a component |
|
|
372
376
|
| `loadPackageJson(srcDir)` | Load identity from package.json |
|
|
373
|
-
| `loadFoundationConfig(srcDir)` | Load foundation.js
|
|
377
|
+
| `loadFoundationConfig(srcDir)` | Load the foundation's `main.js` declarations. A leftover `foundation.js` with no `main.js` beside it throws |
|
|
374
378
|
| `buildSchema(srcDir)` | Build complete schema object |
|
|
375
379
|
|
|
376
380
|
### Entry Generation
|
|
@@ -387,7 +391,7 @@ The generated `_entry.generated.js` file exports:
|
|
|
387
391
|
|--------|-------------|
|
|
388
392
|
| `components` | Object map of component name → React component |
|
|
389
393
|
| Named exports | Each component exported by name (e.g., `Hero`, `Features`) |
|
|
390
|
-
| `capabilities` | Custom Layout and props from `
|
|
394
|
+
| `capabilities` | Custom Layout and props from `main.js` (or `null`) |
|
|
391
395
|
| `meta` | Runtime metadata extracted from component `meta.js` files |
|
|
392
396
|
|
|
393
397
|
#### Runtime Metadata (`meta` export)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.44.
|
|
3
|
+
"version": "0.44.3",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -57,12 +57,12 @@
|
|
|
57
57
|
"js-yaml": "^4.1.0",
|
|
58
58
|
"sharp": "^0.35.3",
|
|
59
59
|
"yaml": "^2.5.0",
|
|
60
|
+
"@uniweb/schemas": "^0.2.13",
|
|
61
|
+
"@uniweb/content-reader": "^1.2.4",
|
|
60
62
|
"@uniweb/projections": "^0.6.0",
|
|
61
|
-
"@uniweb/semantic-parser": "^1.4.0",
|
|
62
63
|
"@uniweb/theming": "^0.1.15",
|
|
63
64
|
"@uniweb/content-writer": "^0.3.4",
|
|
64
|
-
"@uniweb/
|
|
65
|
-
"@uniweb/content-reader": "^1.2.4"
|
|
65
|
+
"@uniweb/semantic-parser": "^1.4.0"
|
|
66
66
|
},
|
|
67
67
|
"optionalDependencies": {
|
|
68
68
|
"@uniweb/runtime": "^0.19.4"
|
package/src/schema.js
CHANGED
|
@@ -184,7 +184,11 @@ export async function loadPackageJson(srcDir) {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
/**
|
|
187
|
-
* Load foundation
|
|
187
|
+
* Load the foundation's declarations from `src/main.js`.
|
|
188
|
+
*
|
|
189
|
+
* ⛔ There is no `foundation.js` fallback — the legacy name was removed
|
|
190
|
+
* 2026-09-05. A leftover `foundation.js` with no `main.js` beside it is
|
|
191
|
+
* REFUSED rather than ignored; see `loadFoundationConfig`.
|
|
188
192
|
*
|
|
189
193
|
* Contains foundation-wide configuration:
|
|
190
194
|
* - vars: CSS custom properties sites can override
|
|
@@ -340,7 +344,30 @@ export async function loadFoundationConfig(srcDir) {
|
|
|
340
344
|
break
|
|
341
345
|
}
|
|
342
346
|
}
|
|
343
|
-
if (!filePath)
|
|
347
|
+
if (!filePath) {
|
|
348
|
+
// ⛔ A LEFTOVER `foundation.js` IS NOT "NO CONFIG", AND MUST NOT READ AS ONE.
|
|
349
|
+
//
|
|
350
|
+
// The legacy name was removed 2026-09-05 (`FOUNDATION_FILE_NAMES` is
|
|
351
|
+
// `main.js` alone). This line used to return `{}` here regardless — so a
|
|
352
|
+
// project that kept the old name built "successfully" with every
|
|
353
|
+
// declaration silently gone: no `vars`, no `defaultLayout`, no
|
|
354
|
+
// `defaultSection`, no `props`. That is the exact outcome the guard below
|
|
355
|
+
// refuses on the import-failure path, reached through a door it did not
|
|
356
|
+
// cover. Found 2026-09-10 checking two downstream projects before an update;
|
|
357
|
+
// one would have lost its default layout and its output-format declarations.
|
|
358
|
+
//
|
|
359
|
+
// ⇒ Throw, like the guard below, rather than warn: a build log is where a
|
|
360
|
+
// warning goes to be missed, and the fix is a one-file rename.
|
|
361
|
+
const legacy = join(srcDir, 'foundation.js')
|
|
362
|
+
if (existsSync(legacy)) {
|
|
363
|
+
throw new Error(
|
|
364
|
+
`${legacy} is no longer read — rename it to main.js.\n` +
|
|
365
|
+
` A foundation's declarations (vars, defaultLayout, defaultSection, props, name) live in\n` +
|
|
366
|
+
` main.js. Left as foundation.js, the build would continue with none of them.`,
|
|
367
|
+
)
|
|
368
|
+
}
|
|
369
|
+
return {}
|
|
370
|
+
}
|
|
344
371
|
|
|
345
372
|
let module
|
|
346
373
|
try {
|
package/src/site/config.js
CHANGED
|
@@ -244,6 +244,7 @@ export async function defineSiteConfig(options = {}) {
|
|
|
244
244
|
const agreement = checkFoundationResolution({
|
|
245
245
|
name: foundationInfo.name,
|
|
246
246
|
generatedInto: srcDir,
|
|
247
|
+
generatedEntry: entryPath,
|
|
247
248
|
siteRoot,
|
|
248
249
|
})
|
|
249
250
|
if (!agreement.ok) console.warn(`\n${agreement.message}\n`)
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
* consulted. That is the step whose answer we need.
|
|
60
60
|
*/
|
|
61
61
|
|
|
62
|
-
import { existsSync, realpathSync } from 'node:fs'
|
|
62
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs'
|
|
63
63
|
import { dirname, join, parse } from 'node:path'
|
|
64
64
|
|
|
65
65
|
/**
|
|
@@ -85,6 +85,50 @@ export function findPackageDir(name, fromDir) {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Does Vite's resolution of this package land on the very file we generated?
|
|
90
|
+
*
|
|
91
|
+
* ⭐ **This is the question the check exists to ask.** Comparing DIRECTORIES
|
|
92
|
+
* asked a different one and got it wrong for the most common layout there is: a
|
|
93
|
+
* package rooted at `foundations/<x>/`, its entry generated into
|
|
94
|
+
* `foundations/<x>/src/`, and `main` pointing there. That is two directories and
|
|
95
|
+
* ONE file. Measured 2026-09-10 against every site of a ten-site project: the
|
|
96
|
+
* directory check fired for six — five healthy `src/` layouts, and the single
|
|
97
|
+
* genuine case, a pnpm `file:` dependency copied into the store, buried among
|
|
98
|
+
* them. The entry comparison leaves exactly that one.
|
|
99
|
+
* A warning that fires on healthy projects is the failure this module's own
|
|
100
|
+
* header warns against.
|
|
101
|
+
*
|
|
102
|
+
* ⛔ **True only when the match is PROVEN** — both files exist and realpath to
|
|
103
|
+
* one place. Any failure to resolve returns false, so the caller reports the
|
|
104
|
+
* directory disagreement rather than silencing it: a copy with no entry, or one
|
|
105
|
+
* carrying a stale entry of its own, is still caught.
|
|
106
|
+
*
|
|
107
|
+
* Follows what Vite reads for a bare specifier: `exports['.']` (a string, or its
|
|
108
|
+
* `import`/`default` condition), else `main`, else `index.js`.
|
|
109
|
+
*/
|
|
110
|
+
function resolvesToSameEntry(generatedEntry, packageDir) {
|
|
111
|
+
let pkg
|
|
112
|
+
try {
|
|
113
|
+
pkg = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8'))
|
|
114
|
+
} catch {
|
|
115
|
+
return false
|
|
116
|
+
}
|
|
117
|
+
const pick = (v) =>
|
|
118
|
+
typeof v === 'string' ? v : v && typeof v === 'object' ? pick(v.import ?? v.default) : null
|
|
119
|
+
const exp = pkg.exports
|
|
120
|
+
let rel = null
|
|
121
|
+
if (typeof exp === 'string') rel = exp
|
|
122
|
+
else if (exp && typeof exp === 'object') rel = pick('.' in exp ? exp['.'] : exp)
|
|
123
|
+
rel ??= typeof pkg.main === 'string' ? pkg.main : null
|
|
124
|
+
rel ??= 'index.js'
|
|
125
|
+
try {
|
|
126
|
+
return realpathSync(generatedEntry) === realpathSync(join(packageDir, rel))
|
|
127
|
+
} catch {
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
88
132
|
/**
|
|
89
133
|
* Compare where we generated the foundation entry against where vite will look.
|
|
90
134
|
*
|
|
@@ -100,9 +144,11 @@ export function findPackageDir(name, fromDir) {
|
|
|
100
144
|
* @param {string} args.name - the foundation's declared name (the bare specifier)
|
|
101
145
|
* @param {string} args.generatedInto - the directory we wrote `_entry.generated.js` to
|
|
102
146
|
* @param {string} args.siteRoot - where vite resolves the bare specifier from
|
|
147
|
+
* @param {string} [args.generatedEntry] - the entry file we wrote; defaults to
|
|
148
|
+
* `_entry.generated.js` inside `generatedInto`
|
|
103
149
|
* @returns {{ ok: true } | { ok: false, ours: string, theirs: string, message: string }}
|
|
104
150
|
*/
|
|
105
|
-
export function checkFoundationResolution({ name, generatedInto, siteRoot }) {
|
|
151
|
+
export function checkFoundationResolution({ name, generatedInto, siteRoot, generatedEntry }) {
|
|
106
152
|
const theirsRaw = findPackageDir(name, siteRoot)
|
|
107
153
|
if (!theirsRaw) return { ok: true }
|
|
108
154
|
|
|
@@ -118,6 +164,13 @@ export function checkFoundationResolution({ name, generatedInto, siteRoot }) {
|
|
|
118
164
|
|
|
119
165
|
if (ours === theirs) return { ok: true }
|
|
120
166
|
|
|
167
|
+
// Two directories are not two foundations. When the package Vite reaches
|
|
168
|
+
// resolves to the entry file we generated, the build reads what we wrote —
|
|
169
|
+
// the `src/` layout, where the entry sits one level below the package root.
|
|
170
|
+
if (resolvesToSameEntry(generatedEntry ?? join(generatedInto, '_entry.generated.js'), theirsRaw)) {
|
|
171
|
+
return { ok: true }
|
|
172
|
+
}
|
|
173
|
+
|
|
121
174
|
return {
|
|
122
175
|
ok: false,
|
|
123
176
|
ours,
|