@uniweb/build 0.28.1 → 0.29.0
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 +7 -9
- package/src/i18n/audit.js +2 -2
- package/src/i18n/collections.js +3 -3
- package/src/i18n/freeform-manifest.js +3 -3
- package/src/i18n/freeform.js +2 -2
- package/src/i18n/hash.js +1 -1
- package/src/i18n/index.js +2 -2
- package/src/resolve-data-schema.js +53 -8
- package/src/uwx/collections.js +28 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -59,17 +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-reader": "^1.2.4",
|
|
63
63
|
"@uniweb/semantic-parser": "^1.3.1",
|
|
64
|
+
"@uniweb/projections": "^0.5.1",
|
|
65
|
+
"@uniweb/content-writer": "^0.3.4",
|
|
64
66
|
"@uniweb/schemas": "^0.2.11",
|
|
65
|
-
"@uniweb/theming": "^0.1.15"
|
|
66
|
-
"@uniweb/content-writer": "^0.3.4"
|
|
67
|
+
"@uniweb/theming": "^0.1.15"
|
|
67
68
|
},
|
|
68
69
|
"optionalDependencies": {
|
|
69
|
-
"@uniweb/
|
|
70
|
-
"@uniweb/schemas": "^0.2.11",
|
|
71
|
-
"@uniweb/semantic-parser": "^1.3.1",
|
|
72
|
-
"@uniweb/runtime": "^0.13.0"
|
|
70
|
+
"@uniweb/runtime": "^0.13.1"
|
|
73
71
|
},
|
|
74
72
|
"peerDependencies": {
|
|
75
73
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
@@ -78,7 +76,7 @@
|
|
|
78
76
|
"@tailwindcss/vite": "^4.0.0",
|
|
79
77
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
80
78
|
"vite-plugin-svgr": "^4.0.0",
|
|
81
|
-
"@uniweb/core": "^0.13.
|
|
79
|
+
"@uniweb/core": "^0.13.1"
|
|
82
80
|
},
|
|
83
81
|
"peerDependenciesMeta": {
|
|
84
82
|
"vite": {
|
package/src/i18n/audit.js
CHANGED
package/src/i18n/collections.js
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { readFile, writeFile, readdir, mkdir } from 'fs/promises'
|
|
14
|
-
import { existsSync } from 'fs'
|
|
15
|
-
import { join } from 'path'
|
|
16
|
-
import { pathToFileURL } from 'url'
|
|
14
|
+
import { existsSync } from 'node:fs'
|
|
15
|
+
import { join } from 'node:path'
|
|
16
|
+
import { pathToFileURL } from 'node:url'
|
|
17
17
|
import { DATA_DIR } from '@uniweb/core'
|
|
18
18
|
import { computeHash } from './hash.js'
|
|
19
19
|
import { loadFreeformCollectionItem } from './freeform.js'
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { readFile, writeFile, mkdir } from 'fs/promises'
|
|
17
|
-
import { existsSync } from 'fs'
|
|
18
|
-
import { join, dirname } from 'path'
|
|
19
|
-
import { createHash } from 'crypto'
|
|
17
|
+
import { existsSync } from 'node:fs'
|
|
18
|
+
import { join, dirname } from 'node:path'
|
|
19
|
+
import { createHash } from 'node:crypto'
|
|
20
20
|
|
|
21
21
|
const MANIFEST_FILENAME = '.manifest.json'
|
|
22
22
|
|
package/src/i18n/freeform.js
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { readFile, readdir, stat } from 'fs/promises'
|
|
22
|
-
import { existsSync } from 'fs'
|
|
23
|
-
import { join, relative, dirname } from 'path'
|
|
22
|
+
import { existsSync } from 'node:fs'
|
|
23
|
+
import { join, relative, dirname } from 'node:path'
|
|
24
24
|
import yaml from 'js-yaml'
|
|
25
25
|
|
|
26
26
|
// Try to import content-reader for markdown → ProseMirror conversion
|
package/src/i18n/hash.js
CHANGED
package/src/i18n/index.js
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { readFile, writeFile, mkdir, readdir } from 'fs/promises'
|
|
11
|
-
import { existsSync } from 'fs'
|
|
12
|
-
import { join, dirname } from 'path'
|
|
11
|
+
import { existsSync } from 'node:fs'
|
|
12
|
+
import { join, dirname } from 'node:path'
|
|
13
13
|
|
|
14
14
|
import { computeHash, normalizeText } from './hash.js'
|
|
15
15
|
import { extractTranslatableContent } from './extract.js'
|
|
@@ -426,12 +426,29 @@ async function loadSchemaFile(filePath) {
|
|
|
426
426
|
}
|
|
427
427
|
|
|
428
428
|
/**
|
|
429
|
-
* Load
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
* `@org/schemas`
|
|
434
|
-
*
|
|
429
|
+
* Load a scope's schema package and pull the named schema.
|
|
430
|
+
*
|
|
431
|
+
* The FOUNDATION's context is the primary resolution and stays that way: it is
|
|
432
|
+
* what lets each foundation pin its own version of a shared schema package. An
|
|
433
|
+
* `@org/schemas` is genuinely third-party — we cannot know it exists — so a
|
|
434
|
+
* foundation that references one must declare it.
|
|
435
|
+
*
|
|
436
|
+
* ⭐ `@std` IS THE ONE EXCEPTION, BECAUSE IT IS OURS. It ships in
|
|
437
|
+
* `@uniweb/schemas`, a framework package `@uniweb/build` already depends on and
|
|
438
|
+
* imports directly (the `@uniweb/schemas/format` import at the top of this
|
|
439
|
+
* file). Requiring a foundation author to install a package the tool doing the
|
|
440
|
+
* resolution is already carrying is a requirement with nothing behind it, so
|
|
441
|
+
* when the foundation has no copy we fall back to ours.
|
|
442
|
+
*
|
|
443
|
+
* ⚠️ A FALLBACK, NOT A REDIRECT. A foundation that installs `@uniweb/schemas`
|
|
444
|
+
* still gets its own — pinning and vendoring keep working — and `@org`
|
|
445
|
+
* behaviour does not change at all.
|
|
446
|
+
*
|
|
447
|
+
* ⛔ AND IT THROWS RATHER THAN DEGRADING. Neighbouring code soft-skips a schema
|
|
448
|
+
* it cannot resolve (a collection falls back to delivery-only), which is right
|
|
449
|
+
* for "the author never asked for one" and wrong here: a `@std/` ref IS the
|
|
450
|
+
* asking. Answering it with silence would turn a missing package into a schema
|
|
451
|
+
* that merely appears not to exist.
|
|
435
452
|
*/
|
|
436
453
|
async function resolveScopedSchema(pkg, name, srcDir) {
|
|
437
454
|
const req = createRequire(join(srcDir, 'package.json'))
|
|
@@ -439,12 +456,40 @@ async function resolveScopedSchema(pkg, name, srcDir) {
|
|
|
439
456
|
try {
|
|
440
457
|
entry = req.resolve(pkg)
|
|
441
458
|
} catch {
|
|
459
|
+
if (pkg === SCOPE_PACKAGE.std) return readNamedSchema(await loadOwnStandardSchemas(pkg), name)
|
|
442
460
|
throw new Error(
|
|
443
461
|
`'${pkg}' is not installed in this foundation, but a schema ref needs it. ` +
|
|
444
|
-
`Add '${pkg}' to the foundation's
|
|
462
|
+
`Add '${pkg}' to the foundation's devDependencies to resolve those refs ` +
|
|
463
|
+
`(it is needed to BUILD the foundation; nothing it provides reaches the bundle).`
|
|
445
464
|
)
|
|
446
465
|
}
|
|
447
|
-
|
|
466
|
+
return readNamedSchema(await import(pathToFileURL(entry).href), name)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** Pull one named schema out of a loaded schemas package. */
|
|
470
|
+
function readNamedSchema(mod, name) {
|
|
448
471
|
if (typeof mod.getSchema === 'function') return mod.getSchema(name)
|
|
449
472
|
return mod.schemas?.[name] ?? mod.default?.[name]
|
|
450
473
|
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* The build's own copy of the standard schemas — the `@std` fallback above.
|
|
477
|
+
*
|
|
478
|
+
* Imported by bare specifier so it resolves from THIS module's context, i.e.
|
|
479
|
+
* `@uniweb/build`'s own dependency rather than the foundation's. It cannot
|
|
480
|
+
* realistically be missing: this file statically imports `@uniweb/schemas/format`,
|
|
481
|
+
* so the module would not have loaded. Throwing anyway, with the reason, beats
|
|
482
|
+
* returning undefined and having the caller report it as "exports no schema
|
|
483
|
+
* named X" — which would name the wrong problem.
|
|
484
|
+
*/
|
|
485
|
+
async function loadOwnStandardSchemas(pkg) {
|
|
486
|
+
try {
|
|
487
|
+
return await import('@uniweb/schemas')
|
|
488
|
+
} catch (err) {
|
|
489
|
+
throw new Error(
|
|
490
|
+
`'${pkg}' is not installed in this foundation, and @uniweb/build's own copy could not ` +
|
|
491
|
+
`be loaded either (${err.message}). Reinstall @uniweb/build; if the install omitted ` +
|
|
492
|
+
`optional or otherwise-skippable packages, run it again without that flag.`
|
|
493
|
+
)
|
|
494
|
+
}
|
|
495
|
+
}
|
package/src/uwx/collections.js
CHANGED
|
@@ -81,12 +81,36 @@ const SKIP_KEYS = new Set([
|
|
|
81
81
|
function stripSigils(value) {
|
|
82
82
|
if (Array.isArray(value)) return value.map(stripSigils)
|
|
83
83
|
if (value && typeof value === 'object') {
|
|
84
|
+
// ⛔ A `@uniweb/folder` REF LEAF ENCODES ONE REFERENCE TWO WAYS, and hashing the
|
|
85
|
+
// encoding rather than the reference made the folder's hash unreproducible.
|
|
86
|
+
//
|
|
87
|
+
// `refLeaf` (uwx/folder.js) emits `$ref: "<collection>/<slug>"` while the record
|
|
88
|
+
// is brand-new and `entry: { model, entity: <uuid> }` once it has been minted.
|
|
89
|
+
// Both denote the same record. A push hashes the folder BEFORE submitting, then
|
|
90
|
+
// back-fills the minted `$uuid` into every record's source file — so the very
|
|
91
|
+
// next emit builds the OTHER encoding, and the hash the push just banked can
|
|
92
|
+
// never be recomputed. Measured on the matinee manor 2026-08-29: `uniweb status`
|
|
93
|
+
// reported the folder changed immediately after a successful push, permanently.
|
|
94
|
+
// Stripping the back-filled uuids from the sources reproduced the banked hash
|
|
95
|
+
// exactly, which is what identified the encoding as the variable.
|
|
96
|
+
//
|
|
97
|
+
// ⭐ Neither encoding is content. What the folder SAYS is "this branch contains
|
|
98
|
+
// this record, here, in this order" — and that is already hashed: a leaf carries
|
|
99
|
+
// `path_segment` (the record's slug) inside a branch carrying the collection's.
|
|
100
|
+
// A `folders:` branch's entries are COLLECTION names, so every leaf under one
|
|
101
|
+
// comes from a single collection, where a slug is unique. Position plus segment
|
|
102
|
+
// therefore identify the record on their own; `$ref` adds a payload-local handle
|
|
103
|
+
// and `entry` adds identity, and both are exactly what `$uuid` is stripped for.
|
|
104
|
+
//
|
|
105
|
+
// ⚖️ The previous rule kept `$ref` "so a reference change is visible". It still
|
|
106
|
+
// is: point a leaf at a different record and its `path_segment` moves with it.
|
|
107
|
+
const isFolderRefLeaf = value.kind === 'ref'
|
|
84
108
|
const out = {}
|
|
85
109
|
for (const [k, v] of Object.entries(value)) {
|
|
86
|
-
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
if (k === '$children'
|
|
110
|
+
if (isFolderRefLeaf && (k === '$ref' || k === 'entry')) continue
|
|
111
|
+
// `$children` (a self-nesting subtree) is structural CONTENT, not an identity
|
|
112
|
+
// sigil — kept and recursed into, so a nesting change stays visible.
|
|
113
|
+
if (k === '$children') {
|
|
90
114
|
out[k] = stripSigils(v)
|
|
91
115
|
continue
|
|
92
116
|
}
|