@uniweb/build 0.52.1 → 0.52.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/package.json +7 -7
- package/src/foundation/derive-supports.js +104 -14
- package/src/generate-entry.js +12 -0
- package/src/resolve-data-schema.js +15 -4
- package/src/schema.js +63 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.52.
|
|
3
|
+
"version": "0.52.3",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -58,14 +58,14 @@
|
|
|
58
58
|
"sharp": "^0.35.3",
|
|
59
59
|
"yaml": "^2.5.0",
|
|
60
60
|
"@uniweb/content-reader": "^1.2.5",
|
|
61
|
-
"@uniweb/
|
|
62
|
-
"@uniweb/projections": "^0.7.3",
|
|
61
|
+
"@uniweb/schemas": "^0.2.15",
|
|
63
62
|
"@uniweb/semantic-parser": "^1.4.0",
|
|
64
|
-
"@uniweb/
|
|
65
|
-
"@uniweb/theming": "^0.1.15"
|
|
63
|
+
"@uniweb/projections": "^0.7.3",
|
|
64
|
+
"@uniweb/theming": "^0.1.15",
|
|
65
|
+
"@uniweb/content-writer": "^0.3.4"
|
|
66
66
|
},
|
|
67
67
|
"optionalDependencies": {
|
|
68
|
-
"@uniweb/runtime": "^0.26.
|
|
68
|
+
"@uniweb/runtime": "^0.26.4"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
@@ -74,7 +74,7 @@
|
|
|
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/core": "^0.29.
|
|
77
|
+
"@uniweb/core": "^0.29.4"
|
|
78
78
|
},
|
|
79
79
|
"peerDependenciesMeta": {
|
|
80
80
|
"vite": {
|
|
@@ -44,16 +44,35 @@
|
|
|
44
44
|
* `submit`, because the framework has no list of permitted names
|
|
45
45
|
* (`core/src/services.js`).
|
|
46
46
|
*
|
|
47
|
-
*
|
|
47
|
+
* ## ⛔ `tracking` IS NEVER EMITTED — AND THAT IS NOT A DETECTION GAP
|
|
48
|
+
*
|
|
49
|
+
* ⭐ **Tracking renders nothing.** This field exists because a service with no
|
|
50
|
+
* surface to draw is invisible from outside — a search endpoint with no search
|
|
51
|
+
* box. But the runtime emits `page_view` / `outbound_click` / `section_view` for
|
|
52
|
+
* every foundation alike (`wireTracker`), so a foundation neither supplies nor
|
|
53
|
+
* withholds tracking, and under the three-state rule (*these and only these*)
|
|
54
|
+
* emitting it conditionally **asserts something false about every foundation that
|
|
55
|
+
* does not call these** — not a missing niche capability.
|
|
56
|
+
*
|
|
57
|
+
* ⛔ **So do not "fix" the obvious misses.** `block.track()` (a method in
|
|
58
|
+
* externalized core, invisible here) and `kit/src/hooks/useReadingDepth.js` both
|
|
59
|
+
* emit custom events and derive nothing. **A matcher for either was considered
|
|
60
|
+
* and rejected**, along with a separate `own-events` name. There is nothing here
|
|
61
|
+
* to detect. `NEVER_EMITTED` below is what enforces it, because the generic
|
|
62
|
+
* `isServiceEnabled` matcher would otherwise pick the name back up from any
|
|
63
|
+
* `isTrackingEnabled()` call.
|
|
64
|
+
*
|
|
65
|
+
* One gate needs help, and only one:
|
|
48
66
|
*
|
|
49
|
-
* - **`useTracker`** resolves nothing itself — it calls through to
|
|
50
|
-
* `getUniweb()?.tracking`, and the `'tracking'` literal lives in
|
|
51
|
-
* `runtime/src/wire-foundation.js`, which is never bundled into a
|
|
52
|
-
* foundation. Its module presence is the signal.
|
|
53
67
|
* - **`isSearchEnabled()`** is a `Website` method, and its literal is in
|
|
54
68
|
* `core/src/website.js` — external, so never a survivor. The call is the
|
|
55
69
|
* signal.
|
|
56
70
|
*
|
|
71
|
+
* ⚠️ **`records` is not derived here at all** — its resolver is in externalized
|
|
72
|
+
* core too, so nothing naming it survives the shake. It comes from the component
|
|
73
|
+
* schema instead (`declaredKeys`, applied in `build/src/schema.js`), which is a
|
|
74
|
+
* second derivation over a different artifact.
|
|
75
|
+
*
|
|
57
76
|
* ⚠️ `@uniweb/api` deliberately needs no entry: it calls
|
|
58
77
|
* `resolveService(website, SERVICE_NAME)` against a module-level `const`, which
|
|
59
78
|
* `readModuleConsts` resolves. Without that resolution `api` would be both
|
|
@@ -72,8 +91,38 @@
|
|
|
72
91
|
* @module @uniweb/build/foundation/derive-supports
|
|
73
92
|
*/
|
|
74
93
|
|
|
75
|
-
|
|
76
|
-
|
|
94
|
+
import { declaredKeys } from '@uniweb/core/data-keys'
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Service names this never emits, however a foundation reaches for them.
|
|
98
|
+
*
|
|
99
|
+
* ⛔ **Excluding a name is not the same as failing to detect one**, which is why
|
|
100
|
+
* this is a list and not an omission. `tracking` arrives through the generic
|
|
101
|
+
* `isServiceEnabled` matcher from any `isTrackingEnabled()` call, so deleting
|
|
102
|
+
* the old module rule was not enough on its own — a foundation that guards a
|
|
103
|
+
* consent banner would have put the name back.
|
|
104
|
+
*
|
|
105
|
+
* See the header: a service with no rendered surface cannot be claimed or
|
|
106
|
+
* withheld by a foundation, and naming it conditionally asserts a falsehood
|
|
107
|
+
* about every foundation that does not call these.
|
|
108
|
+
*/
|
|
109
|
+
const NEVER_EMITTED = new Set(['tracking'])
|
|
110
|
+
|
|
111
|
+
/** The names in `names` that this field may carry. */
|
|
112
|
+
function nameable(names) {
|
|
113
|
+
return names.filter((s) => !NEVER_EMITTED.has(s))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The names in `names` that it may not — for the caller's warning.
|
|
118
|
+
*
|
|
119
|
+
* Exported because an AUTHORED name is dropped too (`composeSupports`), and a
|
|
120
|
+
* developer who typed one deserves to be told rather than to find it missing
|
|
121
|
+
* from the registry.
|
|
122
|
+
*/
|
|
123
|
+
export function unnameableIn(names) {
|
|
124
|
+
return (names || []).filter((s) => NEVER_EMITTED.has(s))
|
|
125
|
+
}
|
|
77
126
|
|
|
78
127
|
/**
|
|
79
128
|
* Walk an ESTree tree, visiting every node.
|
|
@@ -191,7 +240,8 @@ function collectSurvivors(bundle) {
|
|
|
191
240
|
* Three call shapes are read, and two of them carry the name as an argument so
|
|
192
241
|
* they cover the open registry without a rule per service:
|
|
193
242
|
* `resolveService(website, name)`, `website.isServiceEnabled(name)` (what every
|
|
194
|
-
* `@uniweb/kit` predicate compiles to), and the
|
|
243
|
+
* `@uniweb/kit` predicate compiles to), and the one hand-written gate,
|
|
244
|
+
* `isSearchEnabled()`. Names in `NEVER_EMITTED` are dropped from the result.
|
|
195
245
|
*
|
|
196
246
|
* @param {object} bundle - Rollup's bundle, as handed to `writeBundle`
|
|
197
247
|
* @param {object} ctx - the Rollup plugin context (`this` in the hook)
|
|
@@ -207,8 +257,6 @@ export function deriveSupports(bundle, ctx) {
|
|
|
207
257
|
const blindAt = new Set()
|
|
208
258
|
|
|
209
259
|
for (const id of collectSurvivors(bundle)) {
|
|
210
|
-
if (TRACKER_MODULE.test(id)) services.add('tracking')
|
|
211
|
-
|
|
212
260
|
const info = ctx?.getModuleInfo?.(id)
|
|
213
261
|
const ast = info?.ast
|
|
214
262
|
if (!ast) continue
|
|
@@ -240,12 +288,47 @@ export function deriveSupports(bundle, ctx) {
|
|
|
240
288
|
}
|
|
241
289
|
|
|
242
290
|
return {
|
|
243
|
-
services: [...services].sort(),
|
|
291
|
+
services: nameable([...services]).sort(),
|
|
244
292
|
blind: blindAt.size > 0,
|
|
245
293
|
blindAt: [...blindAt].sort(),
|
|
246
294
|
}
|
|
247
295
|
}
|
|
248
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Does this foundation support `records`?
|
|
299
|
+
*
|
|
300
|
+
* ⭐ **The second derivation, over a different artifact.** `records` has no
|
|
301
|
+
* module-graph signature — `resolveRecordsService` is in externalized core, so
|
|
302
|
+
* nothing naming it survives a foundation's tree-shake. The evidence is the
|
|
303
|
+
* component schema instead.
|
|
304
|
+
*
|
|
305
|
+
* Since the 2026-09-14 ruling a section receives only the `data:` keys it
|
|
306
|
+
* declares (`assembleData`, `runtime/src/prepare-props.js`), so a foundation
|
|
307
|
+
* declaring none at either tier can never deliver a record to anything.
|
|
308
|
+
*
|
|
309
|
+
* ⛔ **Key presence, not ref presence.** A `data:` entry may name a schema, but
|
|
310
|
+
* that is a shape hint; the Model ref deciding whether records are live or
|
|
311
|
+
* static is on the site's QUERY, defaulted to the query's own name, and is
|
|
312
|
+
* chosen after this foundation is registered. Counting only ref-bearing entries
|
|
313
|
+
* measured backwards across the templates: it named the forms template and
|
|
314
|
+
* missed the one built to demonstrate query-driven pages.
|
|
315
|
+
*
|
|
316
|
+
* What it claims: *given query results, this foundation will render them* — not
|
|
317
|
+
* that any site supplies them. A declared key may equally be filled by a data
|
|
318
|
+
* block authored inline in the markdown (`block.heldData`).
|
|
319
|
+
*
|
|
320
|
+
* @param {Object} components - componentName → meta, each with its `data:`
|
|
321
|
+
* @param {Object|false|null|undefined} foundationData - `main.js` `data:`
|
|
322
|
+
* @returns {boolean}
|
|
323
|
+
*/
|
|
324
|
+
export function deriveRecordsSupport(components, foundationData) {
|
|
325
|
+
// The foundation tier reaches every section, so one key there is enough — and
|
|
326
|
+
// it is checked separately because a foundation declaring one still supports
|
|
327
|
+
// records when it has no components of its own.
|
|
328
|
+
if (declaredKeys(undefined, foundationData).length > 0) return true
|
|
329
|
+
return Object.values(components || {}).some((meta) => declaredKeys(meta?.data).length > 0)
|
|
330
|
+
}
|
|
331
|
+
|
|
249
332
|
/**
|
|
250
333
|
* Compose what the foundation publishes from the authored and derived halves.
|
|
251
334
|
*
|
|
@@ -276,16 +359,23 @@ export function deriveSupports(bundle, ctx) {
|
|
|
276
359
|
* @returns {{supports?: string[]}} spread into `_self`; `{}` keeps the key absent
|
|
277
360
|
*/
|
|
278
361
|
export function composeSupports(authored, derived) {
|
|
362
|
+
// An authored name this field may not carry is dropped HERE rather than at
|
|
363
|
+
// its source: `package.json` is the developer's file, and rewriting it is
|
|
364
|
+
// `doctor --fix`'s job. `reportSupports` says what was dropped and why.
|
|
365
|
+
// ⛔ Absence must survive the filter — `undefined` is UNKNOWN, `[]` is a
|
|
366
|
+
// declaration, and mapping one to the other loses a state.
|
|
367
|
+
const declared = authored === undefined ? undefined : nameable(authored)
|
|
368
|
+
|
|
279
369
|
// No derivation ran (dev rebuild): the authored value is the whole answer,
|
|
280
370
|
// and an absent one stays absent.
|
|
281
|
-
if (!derived) return
|
|
371
|
+
if (!derived) return declared === undefined ? {} : { supports: declared }
|
|
282
372
|
|
|
283
373
|
const { services, blind } = derived
|
|
284
374
|
|
|
285
|
-
if (
|
|
375
|
+
if (declared === undefined && services.length === 0) {
|
|
286
376
|
return blind ? {} : { supports: [] }
|
|
287
377
|
}
|
|
288
378
|
|
|
289
|
-
const union = [...new Set([...(
|
|
379
|
+
const union = [...new Set([...(declared || []), ...services])].sort()
|
|
290
380
|
return { supports: union }
|
|
291
381
|
}
|
package/src/generate-entry.js
CHANGED
|
@@ -322,6 +322,18 @@ export async function generateEntryPoint(srcDir, outputPath = null, options = {}
|
|
|
322
322
|
// Resolve the data schemas referenced by section bindings, then extract
|
|
323
323
|
// per-component runtime metadata (which lean-extracts field defaults from
|
|
324
324
|
// the resolved schemas into meta.schemas[<key>]).
|
|
325
|
+
// ⛔ Components only, unlike `schema.js`, which also passes the foundation's
|
|
326
|
+
// `main.js` `data:`. Not an oversight: this map feeds `extractAllRuntimeSchemas`,
|
|
327
|
+
// and `extractRuntimeSchema` reads `fullMeta.data` — per component. A
|
|
328
|
+
// foundation-tier key reaches every section (`declaredKeys`) but has nowhere to
|
|
329
|
+
// put its lean schema in a PER-COMPONENT runtime meta, so resolving its ref here
|
|
330
|
+
// would produce an entry nothing reads.
|
|
331
|
+
//
|
|
332
|
+
// ⚠️ The consequence is real and still open: a foundation-tier `data:` entry
|
|
333
|
+
// naming a schema gets no field defaults from `applySchemas` at runtime. Latent
|
|
334
|
+
// today — every foundation-tier declaration in the templates is ref-less — and
|
|
335
|
+
// fixing it means deciding where a foundation-wide schema lives in the runtime
|
|
336
|
+
// meta, which is a design question, not a missing argument.
|
|
325
337
|
const dataSchemaMap = await buildDataSchemaMap(collectSchemaRefs(components), { srcDir })
|
|
326
338
|
const meta = extractAllRuntimeSchemas(components, dataSchemaMap)
|
|
327
339
|
|
|
@@ -80,19 +80,30 @@ const packageForScope = (scope) => SCOPE_PACKAGE[scope] ?? `@${scope}/schemas`
|
|
|
80
80
|
* Reads `data: { key: '<ref>' }` (short) and `data: { key: { schema: '<ref>' } }`
|
|
81
81
|
* (full). Non-string / schemaless entries are ignored.
|
|
82
82
|
*
|
|
83
|
+
* ⛔ **The foundation's own `data:` counts too, and did not until 2026-09-16.**
|
|
84
|
+
* `main.js` may declare `data:` and those keys reach EVERY section
|
|
85
|
+
* (`declaredKeys`, `@uniweb/core/data-keys`) — but only components were scanned
|
|
86
|
+
* here, so a ref at that tier was neither resolved into `dataSchemas` nor
|
|
87
|
+
* rejected when dangling, while the key was still delivered at runtime. The
|
|
88
|
+
* symptom would have been no schema for the editor and no field defaults from
|
|
89
|
+
* `applySchemas`, with no build error. It bit nobody only because every
|
|
90
|
+
* foundation-tier declaration in the templates is ref-less.
|
|
91
|
+
*
|
|
83
92
|
* @param {Object} components - Map of componentName → full meta (with `data`)
|
|
93
|
+
* @param {Object|false|null} [foundationData] - the foundation's `main.js` `data:`
|
|
84
94
|
* @returns {Set<string>}
|
|
85
95
|
*/
|
|
86
|
-
export function collectSchemaRefs(components) {
|
|
96
|
+
export function collectSchemaRefs(components, foundationData = null) {
|
|
87
97
|
const refs = new Set()
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (!data || typeof data !== 'object' || data === false) continue
|
|
98
|
+
const readData = (data) => {
|
|
99
|
+
if (!data || typeof data !== 'object' || data === false) return
|
|
91
100
|
for (const binding of Object.values(data)) {
|
|
92
101
|
const ref = typeof binding === 'string' ? binding : binding?.schema
|
|
93
102
|
if (typeof ref === 'string') refs.add(ref)
|
|
94
103
|
}
|
|
95
104
|
}
|
|
105
|
+
for (const meta of Object.values(components || {})) readData(meta?.data)
|
|
106
|
+
readData(foundationData)
|
|
96
107
|
return refs
|
|
97
108
|
}
|
|
98
109
|
|
package/src/schema.js
CHANGED
|
@@ -18,7 +18,11 @@ import { join, dirname, extname, basename } from 'node:path'
|
|
|
18
18
|
import { pathToFileURL } from 'node:url'
|
|
19
19
|
import { inferTitle } from './utils/infer-title.js'
|
|
20
20
|
import { collectSchemaRefs, buildDataSchemaMap } from './resolve-data-schema.js'
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
composeSupports,
|
|
23
|
+
deriveRecordsSupport,
|
|
24
|
+
unnameableIn,
|
|
25
|
+
} from './foundation/derive-supports.js'
|
|
22
26
|
|
|
23
27
|
// Component meta file name
|
|
24
28
|
const META_FILE_NAME = 'meta.js'
|
|
@@ -464,11 +468,27 @@ function hasEntryFile(dirPath, dirName) {
|
|
|
464
468
|
* Create an implicit empty meta for a section type discovered without meta.js
|
|
465
469
|
*/
|
|
466
470
|
function createImplicitMeta(name) {
|
|
467
|
-
return { title: inferTitle(name) }
|
|
471
|
+
return { title: inferTitle(name), titleInferred: true }
|
|
468
472
|
}
|
|
469
473
|
|
|
470
474
|
/**
|
|
471
|
-
* Build a component entry with title inference applied
|
|
475
|
+
* Build a component entry with title inference applied.
|
|
476
|
+
*
|
|
477
|
+
* ⭐ `titleInferred` SAYS THE TITLE IS OURS, NOT THE AUTHOR'S, and without it a
|
|
478
|
+
* consumer cannot tell. We fill `title` from the component name whenever
|
|
479
|
+
* `meta.js` declares none, so `title` is ALWAYS present — which means an editor
|
|
480
|
+
* showing section names has no way to know that `"Hero"` is a string the build
|
|
481
|
+
* invented rather than one a developer chose.
|
|
482
|
+
*
|
|
483
|
+
* ⛔ THAT DIFFERENCE DECIDES WHETHER THE NAME CAN BE TRANSLATED. An authored
|
|
484
|
+
* title is the foundation's own words and must be shown verbatim in every UI
|
|
485
|
+
* language. An inferred one is a placeholder, and a consumer that knows the
|
|
486
|
+
* section's `family` has a better string available — one it can localize. A
|
|
487
|
+
* foundation that never named its Hero would otherwise ship the English
|
|
488
|
+
* `"Hero"` to every author in the world.
|
|
489
|
+
*
|
|
490
|
+
* ⚖️ The build itself reads neither field. This is a statement ABOUT the title,
|
|
491
|
+
* for whoever renders it.
|
|
472
492
|
*/
|
|
473
493
|
function buildComponentEntry(name, relativePath, meta) {
|
|
474
494
|
const entry = {
|
|
@@ -479,6 +499,7 @@ function buildComponentEntry(name, relativePath, meta) {
|
|
|
479
499
|
// Apply title inference if meta has no explicit title
|
|
480
500
|
if (!entry.title) {
|
|
481
501
|
entry.title = inferTitle(name)
|
|
502
|
+
entry.titleInferred = true
|
|
482
503
|
}
|
|
483
504
|
return entry
|
|
484
505
|
}
|
|
@@ -773,6 +794,19 @@ export async function discoverComponents(srcDir, sectionPaths = DEFAULT_SECTION_
|
|
|
773
794
|
* the artifact is already correct without it.
|
|
774
795
|
*/
|
|
775
796
|
function reportSupports(srcDir, authored, derived, emitted) {
|
|
797
|
+
// ⛔ BEFORE the dev-rebuild bail: a name this field may not carry is dropped
|
|
798
|
+
// from what ships, and a developer who typed one has to hear it. Left below
|
|
799
|
+
// the bail it would be silent in dev, which is where they are working.
|
|
800
|
+
const dropped = unnameableIn(authored)
|
|
801
|
+
if (dropped.length > 0) {
|
|
802
|
+
console.warn(
|
|
803
|
+
`Warning: ${srcDir}/package.json declares \`uniweb.supports: ${dropped.join(', ')}\`, ` +
|
|
804
|
+
`which this field does not carry. A foundation renders no surface for it — the runtime ` +
|
|
805
|
+
`handles it for every foundation alike — so naming it would tell a consumer that any ` +
|
|
806
|
+
`foundation NOT naming it lacks the capability, which is false. Dropped.`,
|
|
807
|
+
)
|
|
808
|
+
}
|
|
809
|
+
|
|
776
810
|
if (!derived) return
|
|
777
811
|
|
|
778
812
|
const added = (emitted || []).filter((s) => !(authored || []).includes(s))
|
|
@@ -848,7 +882,10 @@ export async function buildSchema(srcDir, sectionPaths, derivedSupports = null)
|
|
|
848
882
|
|
|
849
883
|
// Resolve the data schemas referenced by section bindings (carried in
|
|
850
884
|
// schema.json for the editor/platform — see named-data-schemas.md).
|
|
851
|
-
const dataSchemas = await buildDataSchemaMap(
|
|
885
|
+
const dataSchemas = await buildDataSchemaMap(
|
|
886
|
+
collectSchemaRefs(components, foundationConfig.data),
|
|
887
|
+
{ srcDir },
|
|
888
|
+
)
|
|
852
889
|
|
|
853
890
|
// Discover layouts from src/layouts/
|
|
854
891
|
const layouts = await discoverLayoutsInPath(srcDir)
|
|
@@ -869,13 +906,31 @@ export async function buildSchema(srcDir, sectionPaths, derivedSupports = null)
|
|
|
869
906
|
// Build _self, stripping the raw extension boolean in favor of normalized role
|
|
870
907
|
const { extension: _ext, ...configWithoutExtension } = foundationConfig
|
|
871
908
|
|
|
872
|
-
// `supports` is the one identity field the
|
|
909
|
+
// `supports` is the one identity field the build knows better than the file.
|
|
873
910
|
// `identity.supports` is already normalized (absent stays absent, `[]` stays
|
|
874
911
|
// `[]`), and composeSupports keeps that three-state distinction while adding
|
|
875
|
-
// what the
|
|
912
|
+
// what the build proves — see its table. Spread AFTER `...identity` so it
|
|
876
913
|
// replaces the authored-only value rather than being overwritten by it.
|
|
877
|
-
|
|
878
|
-
|
|
914
|
+
//
|
|
915
|
+
// ⭐ TWO DERIVATIONS, TWO ARTIFACTS. `derivedSupports` is read off the bundle's
|
|
916
|
+
// post-tree-shake module graph; `records` cannot be, because its resolver is in
|
|
917
|
+
// externalized core. It comes from the component schema, which is right here.
|
|
918
|
+
//
|
|
919
|
+
// ⛔ GATED WITH THE GRAPH DERIVATION, DELIBERATELY. This one is free to compute
|
|
920
|
+
// and would work on a dev rebuild, where `derivedSupports` is null. It is
|
|
921
|
+
// skipped there anyway: dev never registers, and a dev-built schema.json whose
|
|
922
|
+
// `supports` differs from the one a real build produces is a trap rather than a
|
|
923
|
+
// convenience — the artifact would disagree with itself depending on which
|
|
924
|
+
// command wrote it. One rule: the derivation runs on a real build.
|
|
925
|
+
const derived = derivedSupports && {
|
|
926
|
+
...derivedSupports,
|
|
927
|
+
services: deriveRecordsSupport(components, foundationConfig.data)
|
|
928
|
+
? [...new Set([...derivedSupports.services, 'records'])].sort()
|
|
929
|
+
: derivedSupports.services,
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
const supports = composeSupports(identity.supports, derived)
|
|
933
|
+
reportSupports(srcDir, identity.supports, derived, supports.supports)
|
|
879
934
|
|
|
880
935
|
return {
|
|
881
936
|
_self: {
|