@uniweb/build 0.16.12 → 0.16.14
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/package.json +5 -5
- package/src/index.js +8 -0
- package/src/uwx/index.js +3 -0
- package/src/uwx/site-project.js +10 -2
- package/src/uwx/site.js +60 -3
- package/src/uwx/sync-package.js +20 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.14",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -59,15 +59,15 @@
|
|
|
59
59
|
"js-yaml": "^4.1.0",
|
|
60
60
|
"sharp": "^0.35.3",
|
|
61
61
|
"yaml": "^2.5.0",
|
|
62
|
-
"@uniweb/
|
|
62
|
+
"@uniweb/content-writer": "0.3.3",
|
|
63
63
|
"@uniweb/projections": "0.2.5",
|
|
64
|
-
"@uniweb/
|
|
64
|
+
"@uniweb/theming": "0.1.15"
|
|
65
65
|
},
|
|
66
66
|
"optionalDependencies": {
|
|
67
67
|
"@uniweb/content-reader": "1.2.2",
|
|
68
68
|
"@uniweb/schemas": "0.2.5",
|
|
69
|
-
"@uniweb/
|
|
70
|
-
"@uniweb/
|
|
69
|
+
"@uniweb/semantic-parser": "1.2.1",
|
|
70
|
+
"@uniweb/runtime": "0.9.6"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
73
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
package/src/index.js
CHANGED
|
@@ -57,6 +57,14 @@ export { defineFoundationConfig } from './foundation/config.js'
|
|
|
57
57
|
|
|
58
58
|
// Site config
|
|
59
59
|
export { defineSiteConfig, detectFoundationType } from './site/config.js'
|
|
60
|
+
// Extension declaration helpers — an extension is a foundation and is declared
|
|
61
|
+
// like one, so "is this a URL or a name/ref?" must be decided in ONE place that
|
|
62
|
+
// both the wire projection and the CLI's bring-along/validation agree on.
|
|
63
|
+
export {
|
|
64
|
+
extensionDeclaration,
|
|
65
|
+
isExtensionUrl,
|
|
66
|
+
isSiteRelativeExtensionUrl
|
|
67
|
+
} from './uwx/site.js'
|
|
60
68
|
|
|
61
69
|
// Foundation source root resolution (reads package.json::main)
|
|
62
70
|
export { resolveFoundationSrcDir, resolveFoundationSrcPath } from './utils/foundation-source-root.js'
|
package/src/uwx/index.js
CHANGED
package/src/uwx/site-project.js
CHANGED
|
@@ -150,9 +150,17 @@ export function siteInfoToConfig({ document, siteRoot, sourceLocale = LOCALIZED_
|
|
|
150
150
|
if (info[infoKey] !== undefined) siteChanges[ymlKey] = info[infoKey]
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
// extensions[]
|
|
153
|
+
// extensions[] → site.yml::extensions. Each entry carries EITHER `ref` (a
|
|
154
|
+
// catalog ref or a local name — an extension is a foundation and is declared
|
|
155
|
+
// like one) OR `url`. Project back whichever is present so a ref survives a
|
|
156
|
+
// pull instead of being dropped or rewritten into a URL. `$id` is the authored
|
|
157
|
+
// declaration and is deliberately NOT used here: after a publish that released
|
|
158
|
+
// a local extension, `ref` is the pinned `@scope/name@version` and is the value
|
|
159
|
+
// the site should now carry.
|
|
154
160
|
const extensions = Array.isArray(document?.extensions)
|
|
155
|
-
? document.extensions
|
|
161
|
+
? document.extensions
|
|
162
|
+
.map((e) => (typeof e?.ref === 'string' ? e.ref : e?.url))
|
|
163
|
+
.filter((u) => typeof u === 'string' && u)
|
|
156
164
|
: []
|
|
157
165
|
if (extensions.length > 0) siteChanges.extensions = extensions
|
|
158
166
|
|
package/src/uwx/site.js
CHANGED
|
@@ -498,17 +498,74 @@ async function collectLayoutNested(layoutDir, siteRoot) {
|
|
|
498
498
|
return items
|
|
499
499
|
}
|
|
500
500
|
|
|
501
|
+
// An extension IS a foundation (same build, same output — it just contributes no
|
|
502
|
+
// Layout or theme vars), so it is DECLARED the same way the primary is: a catalog
|
|
503
|
+
// ref, a URL, or a local name the producer resolves. Ruling, 2026-08-04: "extensions
|
|
504
|
+
// should be delivered like any other foundation."
|
|
505
|
+
//
|
|
506
|
+
// The wire therefore carries whichever the author wrote, never a resolution:
|
|
507
|
+
// `@org/name@1.2.3` → { ref } the host resolves it, exactly as for the primary
|
|
508
|
+
// `https://…` → { url } an explicit URL, any origin
|
|
509
|
+
// anything else → { ref } a local name; `publish` releases it and stamps
|
|
510
|
+
// the pinned `@scope/name@version` over this entry
|
|
511
|
+
// (see injectExtensions in sync-package.js)
|
|
512
|
+
//
|
|
513
|
+
// `$id` is the authored declaration, so an entry keeps its identity across a
|
|
514
|
+
// re-publish even when the ref it resolves to moves.
|
|
515
|
+
//
|
|
516
|
+
// ⚠️ A SITE-RELATIVE url (`/effects/entry.js`) is the self-hosted shape and does not
|
|
517
|
+
// work on a backend-hosted site: the site ships no JS, so nothing serves that path
|
|
518
|
+
// and the request falls through to the SPA shell — 200 with `text/html`, which
|
|
519
|
+
// `import()` then fails to parse. `publish` rejects it with a pointer to the ref
|
|
520
|
+
// form; `export` / `deploy --host` keep working, since there the site does serve
|
|
521
|
+
// its own files.
|
|
501
522
|
function extensionsNested(siteYml) {
|
|
502
523
|
const ext = siteYml.extensions
|
|
503
524
|
if (!Array.isArray(ext)) return []
|
|
504
525
|
const out = []
|
|
505
|
-
for (const
|
|
506
|
-
|
|
507
|
-
out.push(withIdentity(
|
|
526
|
+
for (const entry of ext) {
|
|
527
|
+
const decl = extensionDeclaration(entry)
|
|
528
|
+
if (decl) out.push(withIdentity(decl.$id, decl.fields))
|
|
508
529
|
}
|
|
509
530
|
return out
|
|
510
531
|
}
|
|
511
532
|
|
|
533
|
+
/**
|
|
534
|
+
* One authored `extensions:` entry → its wire fields, or null when unusable.
|
|
535
|
+
* Exported for the publish-time validator, so "what counts as a URL" is decided
|
|
536
|
+
* in exactly one place.
|
|
537
|
+
*
|
|
538
|
+
* @param {string|{url?: string, ref?: string, name?: string}} entry
|
|
539
|
+
* @returns {{ $id: string, fields: { url: string } | { ref: string } } | null}
|
|
540
|
+
*/
|
|
541
|
+
export function extensionDeclaration(entry) {
|
|
542
|
+
if (entry && typeof entry === 'object') {
|
|
543
|
+
if (typeof entry.url === 'string' && entry.url)
|
|
544
|
+
return { $id: entry.url, fields: { url: entry.url } }
|
|
545
|
+
const named = entry.ref || entry.name
|
|
546
|
+
return typeof named === 'string' && named
|
|
547
|
+
? { $id: named, fields: { ref: named } }
|
|
548
|
+
: null
|
|
549
|
+
}
|
|
550
|
+
if (typeof entry !== 'string' || !entry) return null
|
|
551
|
+
return isExtensionUrl(entry)
|
|
552
|
+
? { $id: entry, fields: { url: entry } }
|
|
553
|
+
: { $id: entry, fields: { ref: entry } }
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/** True for declarations that are URLs rather than names — absolute or site-relative. */
|
|
557
|
+
export function isExtensionUrl(decl) {
|
|
558
|
+
return (
|
|
559
|
+
typeof decl === 'string' &&
|
|
560
|
+
(/^https?:\/\//.test(decl) || decl.startsWith('/'))
|
|
561
|
+
)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** True only for the site-relative form, which no backend-hosted site can serve. */
|
|
565
|
+
export function isSiteRelativeExtensionUrl(decl) {
|
|
566
|
+
return typeof decl === 'string' && decl.startsWith('/')
|
|
567
|
+
}
|
|
568
|
+
|
|
512
569
|
// The collection DECLARATIONS carried inside site-content `info`-adjacent metadata.
|
|
513
570
|
// Merges the co-located `collections.yml` (the home for file-based decls) over the
|
|
514
571
|
// legacy `site.yml::collections` (kept for remote `url:` sources + back-compat).
|
package/src/uwx/sync-package.js
CHANGED
|
@@ -160,6 +160,11 @@ function rewriteEntityAssets(node, map) {
|
|
|
160
160
|
* @param {object} [opts.injectInfo] - deploy-derived `info.*` to stamp on the
|
|
161
161
|
* site-content document (e.g. `{ data_bundle }`, the static-data ball URL);
|
|
162
162
|
* wire-only — never authored in site.yml, never projected back on pull.
|
|
163
|
+
* @param {Object<string,string>} [opts.injectExtensions] - authored extension
|
|
164
|
+
* declaration (`$id`) → the pinned `@scope/name@version` to stamp over it.
|
|
165
|
+
* `publish` fills this for the site's LOCAL extensions after releasing them;
|
|
166
|
+
* the parallel of `info.foundation`, and needed because `extensions` is a
|
|
167
|
+
* sibling of `info` rather than a field in it.
|
|
163
168
|
* @param {Object<string,string>} [opts.assetRewrite] - map of local asset ref →
|
|
164
169
|
* backend serve URL; rewrites the entities' media refs before push (the
|
|
165
170
|
* deploy's 2nd emit). Absent → no rewrite (the f225 sync path is unchanged).
|
|
@@ -209,6 +214,21 @@ export async function emitSyncPackages(siteRoot, opts = {}) {
|
|
|
209
214
|
if (siteDoc && opts.injectInfo && typeof opts.injectInfo === 'object') {
|
|
210
215
|
siteDoc.info = { ...siteDoc.info, ...opts.injectInfo }
|
|
211
216
|
}
|
|
217
|
+
// Same idea one level out: `extensions` is a sibling of `info`, not a field in
|
|
218
|
+
// it, so a pinned extension ref cannot ride `injectInfo`. `publish` releases a
|
|
219
|
+
// site's LOCAL extensions and stamps the resulting `@scope/name@version` over
|
|
220
|
+
// the authored declaration here — the exact parallel of `info.foundation`, and
|
|
221
|
+
// for the same reason: delivery is version-pinned, so an unpinned local name on
|
|
222
|
+
// the wire points at code the host cannot serve. Keyed by `$id` (the authored
|
|
223
|
+
// declaration), so entries the publish didn't touch are left verbatim.
|
|
224
|
+
if (siteDoc && opts.injectExtensions && Array.isArray(siteDoc.extensions)) {
|
|
225
|
+
siteDoc.extensions = siteDoc.extensions.map((e) => {
|
|
226
|
+
const pinned = opts.injectExtensions[e?.$id]
|
|
227
|
+
if (!pinned) return e
|
|
228
|
+
const { url: _dropped, ...rest } = e
|
|
229
|
+
return { ...rest, ref: pinned }
|
|
230
|
+
})
|
|
231
|
+
}
|
|
212
232
|
|
|
213
233
|
// Per-item identity. `pages`, `page_sections` and `layout_sections` are all
|
|
214
234
|
// `multi` sections on the backend's Model, and its reconcile matches a record by
|