@uniweb/build 0.80.0 → 0.80.1
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/site/queries-config.js +4 -2
- package/src/uwx/queries-config.js +0 -1
- package/src/uwx/records-project.js +25 -18
- package/src/uwx/site-project.js +10 -27
- package/src/uwx/yaml-edit.js +22 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.80.
|
|
3
|
+
"version": "0.80.1",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -57,11 +57,11 @@
|
|
|
57
57
|
"js-yaml": "^4.1.0",
|
|
58
58
|
"sharp": "^0.35.3",
|
|
59
59
|
"yaml": "^2.5.0",
|
|
60
|
+
"@uniweb/content-reader": "^1.2.8",
|
|
60
61
|
"@uniweb/semantic-parser": "^1.5.1",
|
|
61
62
|
"@uniweb/projections": "^0.7.15",
|
|
62
|
-
"@uniweb/content-writer": "^0.3.4",
|
|
63
63
|
"@uniweb/schemas": "^0.13.0",
|
|
64
|
-
"@uniweb/content-
|
|
64
|
+
"@uniweb/content-writer": "^0.3.4",
|
|
65
65
|
"@uniweb/theming": "^0.2.0"
|
|
66
66
|
},
|
|
67
67
|
"optionalDependencies": {
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
"@tailwindcss/vite": "^4.0.0",
|
|
75
75
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
76
76
|
"vite-plugin-svgr": "^4.0.0",
|
|
77
|
-
"@uniweb/
|
|
78
|
-
"@uniweb/
|
|
77
|
+
"@uniweb/core": "^0.38.0",
|
|
78
|
+
"@uniweb/runtime": "^0.29.4"
|
|
79
79
|
},
|
|
80
80
|
"peerDependenciesMeta": {
|
|
81
81
|
"vite": {
|
|
@@ -78,8 +78,10 @@ export const QUERIES_YML_RELPATH = 'queries.yml'
|
|
|
78
78
|
// Identity has no boundary to get wrong, and an author wanting a different schema
|
|
79
79
|
// name writes `schema:`, which is one line and says what it means.
|
|
80
80
|
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
81
|
+
// ⛔ A pull does not drop a `schema:` equal to this default: it writes the schema the site holds,
|
|
82
|
+
// in its one spelling (`uwx/records-project.js::declToFileShape`). Until 2026-10-08 it did, keeping
|
|
83
|
+
// a projected queries.yml as terse as the author left it — and a clone, with no author's file to
|
|
84
|
+
// look at, wrote `series: { schema: '@/series' }` back as `series: {}`.
|
|
83
85
|
export function defaultSchema(name) {
|
|
84
86
|
return `@/${name}`
|
|
85
87
|
}
|
|
@@ -41,7 +41,7 @@ import yaml from 'js-yaml'
|
|
|
41
41
|
import { YAML_OPTIONS } from '../utils/yaml-schema.js'
|
|
42
42
|
import { parseFrontmatter } from './entity-source.js'
|
|
43
43
|
import { writeRecordFile, writeQueriesConfig, writeRecordsConfig } from './project-writer.js'
|
|
44
|
-
import {
|
|
44
|
+
import { foundationDataSchemas, foundationSchemaJson, QUERIES_YML_RELPATH } from './queries-config.js'
|
|
45
45
|
import { dataKeyTypes } from './data-key-types.js'
|
|
46
46
|
import { poolDirsForSchema, schemaForPoolDirs, resolveRecordsDir } from '../site/entity-pool.js'
|
|
47
47
|
import { folderYmlPath } from '../site/records-config.js'
|
|
@@ -277,8 +277,8 @@ function setIf(obj, key, value) {
|
|
|
277
277
|
// - `path:` is written VERBATIM, and omitted entirely when it equals the default
|
|
278
278
|
// (the query's own name under the pool).
|
|
279
279
|
// - `url:` (remote source) and a bare `source:` object are carried as-is.
|
|
280
|
-
// - `schema:` is
|
|
281
|
-
//
|
|
280
|
+
// - `schema:` is written as the site holds it — the query-name default included —
|
|
281
|
+
// except a query typed by a data key, kept short where its type can be read again.
|
|
282
282
|
//
|
|
283
283
|
// ⛔ THE `collections/`-PREFIX STRIP AND THE site.yml ROUTING ARE BOTH GONE, and
|
|
284
284
|
// they went together. They existed because `collections.yml` sat INSIDE
|
|
@@ -342,24 +342,31 @@ function declToFileShape(wire, scope = null, own = null, keyTypes = null, author
|
|
|
342
342
|
decl.source = source
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
// `
|
|
351
|
-
//
|
|
352
|
-
//
|
|
353
|
-
//
|
|
354
|
-
//
|
|
345
|
+
// ⭐ THE SCHEMA THE SITE HOLDS IS WRITTEN — one spelling per meaning [Diego, 2026-10-08]. A
|
|
346
|
+
// query left to its name's default (`series` → `@/series`) comes back with that schema stated:
|
|
347
|
+
// `series: {}` and `series: { schema: '@/series' }` read the same records, and a clone, with no
|
|
348
|
+
// file of the author's to look at, could not tell which was written. ⛔ Until 2026-10-08 a schema
|
|
349
|
+
// equal to the default stayed unwritten unless the local file stated it, so a clone turned an
|
|
350
|
+
// author's `series: { schema: '@/series' }` into `series: {}`.
|
|
351
|
+
//
|
|
352
|
+
// ⛔ EXCEPT A QUERY TYPED BY A DATA KEY, which is not a second spelling of anything: `team:` reads
|
|
353
|
+
// its own folder, `records/team/`, as the type the foundation's data key declares
|
|
354
|
+
// (`data-key-types.js`), where `team: { schema: '@/member' }` would read `records/member/`. It
|
|
355
|
+
// stays as written — short — but only where this project can read it as that type again: the
|
|
356
|
+
// foundation's types (its own, or the registered version a clone keeps) give its name the schema
|
|
357
|
+
// the site holds. Then the push's mark says so (`typed_by_data_key`), or the author's file
|
|
358
|
+
// declares it short, or — the foundation in this project — the file does not declare it. An
|
|
359
|
+
// author's stated schema is never taken for one. ⛔ With no types to read, short would mean
|
|
360
|
+
// `@/team`: the schema is written, and the records go to its folder, so the copy still pushes it.
|
|
355
361
|
const written = name ? authored?.get(name) : undefined
|
|
356
|
-
const
|
|
357
|
-
const keyType = name && !own?.has(name) && typedHere ? keyTypes?.get(name) : null
|
|
362
|
+
const keyType = name && !own?.has(name) ? keyTypes?.get(name) : null
|
|
358
363
|
// In the author's form: the registered version names the type qualified (`@acme/member`).
|
|
359
364
|
const keyDefault = keyType ? unresolveSelfScope(keyType, scope, own) : null
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
365
|
+
const typedByKey =
|
|
366
|
+
keyDefault !== null &&
|
|
367
|
+
d.schema === keyDefault &&
|
|
368
|
+
(wire.typed_by_data_key === true || written === null || (written === undefined && !markedOnly))
|
|
369
|
+
if (d.schema && !typedByKey) decl.schema = d.schema
|
|
363
370
|
setIf(decl, 'sort', d.sort)
|
|
364
371
|
setIf(decl, 'where', d.where)
|
|
365
372
|
setIf(decl, 'limit', d.limit)
|
package/src/uwx/site-project.js
CHANGED
|
@@ -47,8 +47,7 @@ import { declarationsToQueriesYml } from './records-project.js'
|
|
|
47
47
|
import { FILLED_SECTION_TYPE, buildRouteOf } from './site.js'
|
|
48
48
|
import { translationContext, SITE_META_CONTEXT } from '../i18n/extract.js'
|
|
49
49
|
import { authorableDeclaration, DECLARATION_KEYS } from '../site/fetch-shapes.js'
|
|
50
|
-
import { createTranslationCollector, writeLocaleTranslations, writeFreeformTranslations, unwrapLocalizedContent
|
|
51
|
-
import { resolveLocaleList } from '../i18n/locales.js'
|
|
50
|
+
import { createTranslationCollector, writeLocaleTranslations, writeFreeformTranslations, unwrapLocalizedContent } from './locale-sync.js'
|
|
52
51
|
import { buildFreeformPath, freeformPathsFor } from '../i18n/freeform.js'
|
|
53
52
|
import { unwrapLocalized, unwrapLocalizedList } from './backfill.js'
|
|
54
53
|
import { LOCALIZED_FIELD_ASSUMPTION } from './localize.js'
|
|
@@ -214,7 +213,7 @@ function readAuthoredYaml(filePath) {
|
|
|
214
213
|
* @returns {{ siteConfig: string, theme?: string, headHtml?: string }} per-file
|
|
215
214
|
* write status ('updated' | 'unchanged')
|
|
216
215
|
*/
|
|
217
|
-
export function siteInfoToConfig({ document, siteRoot, backend = null, sourceLocale = LOCALIZED_FIELD_ASSUMPTION.defaultSourceLocale, collector, keepAuthoredFoundation = false
|
|
216
|
+
export function siteInfoToConfig({ document, siteRoot, backend = null, sourceLocale = LOCALIZED_FIELD_ASSUMPTION.defaultSourceLocale, collector, keepAuthoredFoundation = false }) {
|
|
218
217
|
const info = document?.info || {}
|
|
219
218
|
const settingsSection = document?.settings || {}
|
|
220
219
|
|
|
@@ -325,18 +324,14 @@ export function siteInfoToConfig({ document, siteRoot, backend = null, sourceLoc
|
|
|
325
324
|
}
|
|
326
325
|
}
|
|
327
326
|
|
|
328
|
-
// ⭐ `languages`
|
|
329
|
-
//
|
|
330
|
-
// files make (`uwx/site.js::settingsNested`), and
|
|
331
|
-
//
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
delete siteChanges.languages
|
|
336
|
-
}
|
|
337
|
-
|
|
327
|
+
// ⭐ `languages` IS WRITTEN AS THE SITE HOLDS IT — one spelling per meaning [Diego, 2026-10-08]:
|
|
328
|
+
// "It's definitely good for the case of languages." A site whose `site.yml` declared none sent the
|
|
329
|
+
// list its translation files make (`uwx/site.js::settingsNested`), and that list comes back stated,
|
|
330
|
+
// so a clone and the copy that pushed it say the same thing. ⛔ Until 2026-10-08 the list was held
|
|
331
|
+
// back where the translation files the pull wrote made the same one, so a clone of a site that
|
|
332
|
+
// declared `languages: [en, fr]` had no `languages:` at all — and followed its translation files
|
|
333
|
+
// where the site it came from had a fixed list.
|
|
338
334
|
const result = { siteConfig: writeSiteConfig(siteRoot, siteChanges) }
|
|
339
|
-
if (pendingLanguages !== undefined) result.pendingLanguages = pendingLanguages
|
|
340
335
|
|
|
341
336
|
// theme (whole object) → theme.yml.
|
|
342
337
|
if (settingsSection.theme && typeof settingsSection.theme === 'object') {
|
|
@@ -1121,7 +1116,7 @@ export function siteContentDocumentToProject({ document, siteRoot, backend = nul
|
|
|
1121
1116
|
backend ? readBackendState(siteRoot, backend).assets || {} : {}
|
|
1122
1117
|
)
|
|
1123
1118
|
|
|
1124
|
-
report.config = siteInfoToConfig({ document, siteRoot, backend, sourceLocale, collector, keepAuthoredFoundation
|
|
1119
|
+
report.config = siteInfoToConfig({ document, siteRoot, backend, sourceLocale, collector, keepAuthoredFoundation })
|
|
1125
1120
|
report.queries = declarationsToQueriesYml({ document, siteRoot, scope })
|
|
1126
1121
|
|
|
1127
1122
|
// The uuid identity index (gitignored `.uniweb/`): read the prior map to anchor
|
|
@@ -1162,18 +1157,6 @@ export function siteContentDocumentToProject({ document, siteRoot, backend = nul
|
|
|
1162
1157
|
report.locales = writeLocaleTranslations(siteRoot, collector.byLocale)
|
|
1163
1158
|
// Target-locale FREE-FORM bodies → locales/freeform/{locale}/<relpath> + manifest.
|
|
1164
1159
|
report.freeform = writeFreeformTranslations(siteRoot, collector.freeformPending)
|
|
1165
|
-
// The site's languages, for a site.yml that declares none (`siteInfoToConfig`) — written only where
|
|
1166
|
-
// the translation files, now written, would not make the same list, or where `publishLanguages`
|
|
1167
|
-
// needs a declared list to draw from: the build refuses one without it.
|
|
1168
|
-
if (report.config?.pendingLanguages !== undefined) {
|
|
1169
|
-
const pulled = report.config.pendingLanguages
|
|
1170
|
-
delete report.config.pendingLanguages
|
|
1171
|
-
const site = readAuthoredYaml(join(siteRoot, 'site.yml')) || {}
|
|
1172
|
-
const source = site.defaultLanguage || sourceLocale
|
|
1173
|
-
const made = new Set([source, ...resolveLocaleList(undefined, localesDir(siteRoot))])
|
|
1174
|
-
const same = Array.isArray(pulled) && pulled.length === made.size && pulled.every((l) => made.has(l))
|
|
1175
|
-
if (!same || site.publishLanguages !== undefined) writeSiteConfig(siteRoot, { languages: pulled })
|
|
1176
|
-
}
|
|
1177
1160
|
writePullIndex(siteRoot, ctx.newIndex)
|
|
1178
1161
|
return report
|
|
1179
1162
|
}
|
package/src/uwx/yaml-edit.js
CHANGED
|
@@ -45,9 +45,18 @@ function columnOf(text, key) {
|
|
|
45
45
|
return /^ *$/.test(lead) ? lead.length : null
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Where a pair's text ends: after its value, its trailing comment and its newline.
|
|
50
|
+
*
|
|
51
|
+
* ⛔ A SCALAR ENDS WHERE ITS OWN TEXT DOES — `range[1]`, then the rest of that line. Its
|
|
52
|
+
* `range[2]` also spans the comment lines that follow it, and for an empty value (`team:`)
|
|
53
|
+
* every comment and blank line up to the next key, at any indentation: those belong to what
|
|
54
|
+
* comes next. ⛔ Until 2026-10-08 this read `range[2]`, and a pull that gave a bare `team:` its
|
|
55
|
+
* schema deleted the blank line and the `# Build options` heading under it in `site.yml`.
|
|
56
|
+
*/
|
|
49
57
|
function pairEnd(text, pair) {
|
|
50
|
-
|
|
58
|
+
const node = pair.value ?? pair.key
|
|
59
|
+
let end = isScalar(node) ? node.range[1] : node.range[2]
|
|
51
60
|
if (text[end - 1] !== '\n') {
|
|
52
61
|
const nl = text.indexOf('\n', end)
|
|
53
62
|
const rest = nl === -1 ? text.slice(end) : text.slice(end, nl)
|
|
@@ -169,6 +178,17 @@ function editMap(text, map, before, after, edits, dump) {
|
|
|
169
178
|
continue
|
|
170
179
|
}
|
|
171
180
|
}
|
|
181
|
+
// ⭐ A BARE KEY GIVEN A VALUE KEEPS ITS LINE — and any comment the author wrote on it — and
|
|
182
|
+
// the value goes under it: `team: # the people` gains ` schema: '@/team'` beneath.
|
|
183
|
+
const bare = isScalar(value) && value.value === null && value.range[0] === value.range[1]
|
|
184
|
+
if (bare && (isPlainObject(next) || Array.isArray(next))) {
|
|
185
|
+
const whole = indented(dump({ [key]: next }), col, false)
|
|
186
|
+
const nl = whole.indexOf('\n')
|
|
187
|
+
if (nl !== -1 && nl < whole.length - 1) {
|
|
188
|
+
edits.push({ start: stop, end: stop, text: (text[stop - 1] === '\n' ? '' : '\n') + whole.slice(nl + 1) })
|
|
189
|
+
continue
|
|
190
|
+
}
|
|
191
|
+
}
|
|
172
192
|
// Written whole — in flow style when the author wrote it that way (`tags: [a, b]`).
|
|
173
193
|
const flow = (isMap(value) || isSeq(value)) && value.flow
|
|
174
194
|
edits.push({
|