@uniweb/build 1.3.0 → 1.3.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/build",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Build tooling for the Uniweb Component Web Platform",
5
5
  "type": "module",
6
6
  "exports": {
@@ -56,15 +56,15 @@
56
56
  "js-yaml": "^4.1.0",
57
57
  "sharp": "^0.35.3",
58
58
  "yaml": "^2.5.0",
59
+ "@uniweb/schemas": "~1.3.0",
60
+ "@uniweb/semantic-parser": "~1.7.0",
59
61
  "@uniweb/projections": "~1.1.1",
60
- "@uniweb/content-writer": "~1.1.0",
61
62
  "@uniweb/theming": "~1.2.0",
62
- "@uniweb/semantic-parser": "~1.6.0",
63
- "@uniweb/schemas": "~1.2.0",
64
- "@uniweb/content-reader": "~1.3.0"
63
+ "@uniweb/content-reader": "~1.3.1",
64
+ "@uniweb/content-writer": "~1.1.1"
65
65
  },
66
66
  "optionalDependencies": {
67
- "@uniweb/runtime": "~1.1.1"
67
+ "@uniweb/runtime": "~1.1.2"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
@@ -73,8 +73,8 @@
73
73
  "@tailwindcss/vite": "^4.0.0",
74
74
  "@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
75
75
  "vite-plugin-svgr": "^4.0.0",
76
- "@uniweb/runtime": "~1.1.1",
77
- "@uniweb/core": "~1.2.0"
76
+ "@uniweb/runtime": "~1.1.2",
77
+ "@uniweb/core": "~1.2.1"
78
78
  },
79
79
  "peerDependenciesMeta": {
80
80
  "vite": {
@@ -22,7 +22,7 @@
22
22
  */
23
23
 
24
24
  import { readFile, readdir, stat } from 'node:fs/promises'
25
- import { resolveQueriesConfig, toConfigQueries, dataKeyTyping } from './queries-config.js'
25
+ import { resolveQueriesConfig, toConfigQueries, dataKeyTyping, foundationSections } from './queries-config.js'
26
26
  import { isSectionKey } from '@uniweb/schemas/section'
27
27
  import { parseNumericPrefix, compareByNumericPrefix } from '../utils/numeric-prefix.js'
28
28
  import { isMarkdownFile, isIgnoredFolder } from '../utils/content-files.js'
@@ -43,6 +43,7 @@ import { parseFrontmatter } from '../utils/frontmatter.js'
43
43
  import { parseGrid } from '@uniweb/schemas/grid'
44
44
  import { runtimeServicesConfig, refuseRetiredServiceKeys, refuseUnreadableServices } from '../uwx/services-request.js'
45
45
  import { warnOutsideVocabulary } from './vocabulary-warnings.js'
46
+ import { warnPlacementsNotOffered } from './placement-warnings.js'
46
47
 
47
48
  // Try to import content-reader, fall back to simplified parser
48
49
  let markdownToProseMirror
@@ -428,6 +429,15 @@ function buildVersionMetadata(detectedVersions, pageConfig = {}) {
428
429
  */
429
430
  let yamlFailures = []
430
431
 
432
+ /**
433
+ * The foundation's section types for the current collect — its schema, each
434
+ * type's entry by name (`foundationSections`) — so a section's file can be told
435
+ * when it asks for a placement its type does not honour
436
+ * (`./placement-warnings.js`). Null when the foundation is not at hand. Set once
437
+ * per collect, and module-scoped for the reason `yamlFailures` gives.
438
+ */
439
+ let sectionTypes = null
440
+
431
441
  /**
432
442
  * Parse YAML, recording a failure against the file it came from.
433
443
  *
@@ -1075,6 +1085,7 @@ async function processMarkdownFile(filePath, id, siteRoot, defaultStableId = nul
1075
1085
  const warnHere = (message) => console.warn(`[content-collector] ${relative(siteRoot, filePath)}: ${message}`)
1076
1086
  const proseMirrorContent = markdownToProseMirror(markdown, { onWarning: warnHere })
1077
1087
  warnOutsideVocabulary(proseMirrorContent, warnHere)
1088
+ warnPlacementsNotOffered(proseMirrorContent, type || null, sectionTypes, warnHere)
1078
1089
 
1079
1090
  // `query: team` → `fetch: { query: team }`; a list, one config per name, each under
1080
1091
  // its own `as` — the one helper every level uses. Unrelated to a section type's
@@ -2505,9 +2516,13 @@ export async function collectSiteContent(sitePath, options = {}) {
2505
2516
  // function again, so without the reset a typo fixed three saves ago would
2506
2517
  // still be counted.
2507
2518
  yamlFailures = []
2519
+ sectionTypes = null
2508
2520
 
2509
2521
  // Read site config and raw theme config
2510
2522
  const siteConfig = await readYamlFile(join(sitePath, configFile))
2523
+ // What each section type honours — read before any section is, so a file asking
2524
+ // for a placement its type does not offer is told (`./placement-warnings.js`).
2525
+ sectionTypes = await foundationSections(sitePath, siteConfig)
2511
2526
  refuseRetiredServiceKeys(siteConfig, configFile)
2512
2527
  refuseUnreadableServices(siteConfig, configFile)
2513
2528
 
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Say, with the file in hand, where a section's content asks for a placement its
3
+ * section type does not honour.
4
+ *
5
+ * A section type declares the placements its flow honours — `sequence: {
6
+ * placement }` in its `meta.js`, lowered by `@uniweb/schemas/content` — and an
7
+ * editor offers an author only those. A file can still ask for any: the block
8
+ * then sits in the text column, as it would with none. ⭐ A warning, never a
9
+ * failure, since the page renders. ⛔ Section content only: a record's body is
10
+ * shown by whatever section a site gives it, so no foundation schema says what
11
+ * it may use [Diego, 2026-10-14].
12
+ */
13
+ import { placementsNotOffered } from '@uniweb/schemas/content'
14
+
15
+ const BLOCK = { image: 'an image', video: 'a video', table: 'a table', icon: 'an icon', document: 'a document' }
16
+
17
+ /**
18
+ * Warn, for each block in `doc` that asks for a placement its section type does
19
+ * not offer, with the file it came from.
20
+ *
21
+ * @param {Object} doc - the section's content, a ProseMirror document
22
+ * @param {string|null} type - the section's `type:`; with none, the foundation's
23
+ * `defaultSection`
24
+ * @param {Object|null} sectionTypes - the foundation's schema: each section
25
+ * type's entry by name (`foundationSections`); null when it is not at hand
26
+ * @param {(message: string) => void} warn - prints one message, prefixed with the file
27
+ */
28
+ export function warnPlacementsNotOffered(doc, type, sectionTypes, warn) {
29
+ const name = type || sectionTypes?._self?.defaultSection || null
30
+ const entry = name && !name.startsWith('_') ? sectionTypes?.[name] : null
31
+ if (!entry || typeof entry !== 'object') return
32
+
33
+ for (const finding of placementsNotOffered(doc, entry)) {
34
+ const block = BLOCK[finding.kind] || 'a block'
35
+ const asked = `\`placement=${finding.placement}\``
36
+ if (finding.kind === 'icon' || finding.kind === 'document') {
37
+ warn(`${block} takes no placement, so ${asked} does nothing`)
38
+ continue
39
+ }
40
+ const offers = finding.offered.length > 0 ? ` (it offers ${finding.offered.join(', ')})` : ''
41
+ warn(`${block} asks for ${asked}, which ${name} does not offer${offers} — it sits in the text column`)
42
+ }
43
+ }
@@ -460,9 +460,9 @@ function lowerLeaf(field, resolve, optResolve, { multiple = false, path = '' } =
460
460
  if (field.label) out.label = field.label
461
461
  if (field.description) out.description = field.description
462
462
  if (field.required) out.required = true
463
- // A field's rules, as written — `max_length`, `min`, `max`, `mime_types`, `max_size`
464
- // (`FIELD_RULES`): a registry keeps each as the field's own key and checks it on every
465
- // write. ⛔ Until 2026-10-15 the normalizer dropped them before this saw them.
463
+ // A field's rules, as written — `max_length`, `min`, `max`, `mime_types`, `max_size`,
464
+ // `pattern` (`FIELD_RULES`): a registry keeps each as the field's own key and checks it on
465
+ // every write. ⛔ Until 2026-10-15 the normalizer dropped them before this saw them.
466
466
  for (const key of Object.keys(FIELD_RULES)) {
467
467
  if (field[key] !== undefined) out[key] = field[key]
468
468
  }