create-kerf-component 5.0.0-beta.51 → 5.0.0-beta.53
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/README.md +5 -0
- package/catalog.js +87 -0
- package/component-catalog-v2.schema.json +7 -0
- package/component-metadata.schema.json +7 -0
- package/package.json +1 -1
- package/template/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,6 +54,11 @@ A ready-to-publish component package that encodes the rules from the kerf docs
|
|
|
54
54
|
not render, remove, or treat those attributes as their own state. The field
|
|
55
55
|
is optional; names must be unique per component, and the checker rejects a
|
|
56
56
|
helper that is not listed in `wiring.helpers`.
|
|
57
|
+
A wrapper that renders a cataloged component declares it under
|
|
58
|
+
`composition.rendersAs` (for example
|
|
59
|
+
`["@kerfjs/ui:toolbar-control-group"]`), so the `ui-composition` lint treats
|
|
60
|
+
it as that root. Every key must resolve to an entry generated in the same run
|
|
61
|
+
or to an installed package's catalog.
|
|
57
62
|
|
|
58
63
|
## Layout produced
|
|
59
64
|
|
package/catalog.js
CHANGED
|
@@ -186,8 +186,21 @@ function valueAt(value, path) {
|
|
|
186
186
|
return path.split('.').reduce((current, part) => current?.[part], value);
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
// Paths where an explicit `null` is itself the author's decision: a component
|
|
190
|
+
// whose root element belongs to another catalog (a `rendersAs` wrapper) or that
|
|
191
|
+
// exposes no public root class.
|
|
192
|
+
const NULLABLE_DECISIONS = new Set(['boundaries.rootClass']);
|
|
193
|
+
|
|
189
194
|
function requireDecision(component, path, at, diagnostics) {
|
|
190
195
|
const value = valueAt(component, path);
|
|
196
|
+
const explicitNull =
|
|
197
|
+
value === null &&
|
|
198
|
+
NULLABLE_DECISIONS.has(path) &&
|
|
199
|
+
Object.hasOwn(
|
|
200
|
+
valueAt(component, path.slice(0, path.lastIndexOf('.'))) ?? {},
|
|
201
|
+
path.slice(path.lastIndexOf('.') + 1),
|
|
202
|
+
);
|
|
203
|
+
if (explicitNull) return;
|
|
191
204
|
if (value === undefined || value === null || value === '') {
|
|
192
205
|
diagnostics.push(
|
|
193
206
|
`${at}.${path}: author decision required; the generator does not infer semantics or geometry from rendered appearance`,
|
|
@@ -621,6 +634,9 @@ function toEntry(packageName, component) {
|
|
|
621
634
|
publicExports: component.publicExports,
|
|
622
635
|
sourceLinks: component.sourceLinks,
|
|
623
636
|
source: component.source,
|
|
637
|
+
...(component.composition.rendersAs
|
|
638
|
+
? { rendersAs: component.composition.rendersAs }
|
|
639
|
+
: {}),
|
|
624
640
|
parents: component.composition.parents,
|
|
625
641
|
contexts: component.composition.contexts,
|
|
626
642
|
zones: component.composition.zones,
|
|
@@ -636,6 +652,75 @@ function toEntry(packageName, component) {
|
|
|
636
652
|
};
|
|
637
653
|
}
|
|
638
654
|
|
|
655
|
+
// The catalog keys a package's `rendersAs` may name: entries generated in this
|
|
656
|
+
// run, or those of an installed dependency's shipped catalog (its
|
|
657
|
+
// package.json#kerfComponentCatalog.output, or @kerfjs/ui's ai/ catalog).
|
|
658
|
+
function dependencyCatalogKeys(packageRoot, packageName, cache) {
|
|
659
|
+
const cacheKey = `${packageRoot}\0${packageName}`;
|
|
660
|
+
if (cache.has(cacheKey)) return cache.get(cacheKey);
|
|
661
|
+
let keys;
|
|
662
|
+
for (let dir = packageRoot; ; dir = dirname(dir)) {
|
|
663
|
+
const manifestPath = join(dir, 'node_modules', packageName, 'package.json');
|
|
664
|
+
if (existsSync(manifestPath)) {
|
|
665
|
+
const dependencyRoot = dirname(manifestPath);
|
|
666
|
+
let catalogPath;
|
|
667
|
+
try {
|
|
668
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
669
|
+
if (manifest[CONFIG_KEY]?.output)
|
|
670
|
+
catalogPath = resolve(dependencyRoot, manifest[CONFIG_KEY].output);
|
|
671
|
+
} catch {
|
|
672
|
+
// An unreadable manifest leaves the package unresolved below.
|
|
673
|
+
}
|
|
674
|
+
catalogPath ??= join(dependencyRoot, 'ai', 'component-catalog-v2.json');
|
|
675
|
+
try {
|
|
676
|
+
const catalog = JSON.parse(readFileSync(catalogPath, 'utf8'));
|
|
677
|
+
keys = new Set(
|
|
678
|
+
(catalog.entries ?? []).map(
|
|
679
|
+
(entry) => entry.key ?? `${catalog.package}:${entry.id}`,
|
|
680
|
+
),
|
|
681
|
+
);
|
|
682
|
+
} catch {
|
|
683
|
+
// No readable catalog: the package stays unresolved.
|
|
684
|
+
}
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
if (dirname(dir) === dir) break;
|
|
688
|
+
}
|
|
689
|
+
cache.set(cacheKey, keys);
|
|
690
|
+
return keys;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function validateRendersAs(results, diagnostics) {
|
|
694
|
+
const generated = new Map(
|
|
695
|
+
results.map(({ catalog }) => [
|
|
696
|
+
catalog.package,
|
|
697
|
+
new Set(catalog.entries.map((entry) => entry.key)),
|
|
698
|
+
]),
|
|
699
|
+
);
|
|
700
|
+
const cache = new Map();
|
|
701
|
+
for (const { packageRoot, metadataPath, catalog } of results)
|
|
702
|
+
for (const entry of catalog.entries)
|
|
703
|
+
for (const root of entry.rendersAs ?? []) {
|
|
704
|
+
const at = `${metadataPath}: components.${entry.id}.composition.rendersAs`;
|
|
705
|
+
const rootPackage = root.slice(0, root.lastIndexOf(':'));
|
|
706
|
+
if (root === entry.key) {
|
|
707
|
+
diagnostics.push(`${at}: ${root} names the component itself`);
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
const keys =
|
|
711
|
+
generated.get(rootPackage) ??
|
|
712
|
+
dependencyCatalogKeys(packageRoot, rootPackage, cache);
|
|
713
|
+
if (!keys)
|
|
714
|
+
diagnostics.push(
|
|
715
|
+
`${at}: cannot resolve a component catalog for package ${rootPackage}; install it or generate its catalog`,
|
|
716
|
+
);
|
|
717
|
+
else if (!keys.has(root))
|
|
718
|
+
diagnostics.push(
|
|
719
|
+
`${at}: ${root} is not an entry in ${rootPackage}'s catalog`,
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
639
724
|
export function generateCatalogs(root = process.cwd()) {
|
|
640
725
|
const diagnostics = [];
|
|
641
726
|
const packages = configuredPackages(resolve(root), diagnostics);
|
|
@@ -707,10 +792,12 @@ export function generateCatalogs(root = process.cwd()) {
|
|
|
707
792
|
);
|
|
708
793
|
results.push({
|
|
709
794
|
packageRoot,
|
|
795
|
+
metadataPath,
|
|
710
796
|
outputPath: resolve(packageRoot, config.output),
|
|
711
797
|
catalog,
|
|
712
798
|
});
|
|
713
799
|
}
|
|
800
|
+
validateRendersAs(results, diagnostics);
|
|
714
801
|
if (diagnostics.length) throw new CatalogError(diagnostics.sort());
|
|
715
802
|
return results;
|
|
716
803
|
}
|
|
@@ -151,6 +151,13 @@
|
|
|
151
151
|
},
|
|
152
152
|
"sourceLinks": { "$ref": "#/$defs/stringList" },
|
|
153
153
|
"source": { "type": "string", "minLength": 1 },
|
|
154
|
+
"rendersAs": {
|
|
155
|
+
"description": "For a wrapper component: the cataloged roots it renders (package-qualified keys), any one of them or nothing. Composition checks treat an element of this entry as each declared root: a zone accepts it only if it accepts every root, and each root's parent contract applies to where it is placed.",
|
|
156
|
+
"type": "array",
|
|
157
|
+
"minItems": 1,
|
|
158
|
+
"items": { "type": "string", "pattern": "^[^:]+:[^:]+$" },
|
|
159
|
+
"uniqueItems": true
|
|
160
|
+
},
|
|
154
161
|
"parents": {
|
|
155
162
|
"type": "object",
|
|
156
163
|
"required": ["mode", "entries"],
|
|
@@ -159,6 +159,13 @@
|
|
|
159
159
|
"layout"
|
|
160
160
|
],
|
|
161
161
|
"properties": {
|
|
162
|
+
"rendersAs": {
|
|
163
|
+
"description": "For a wrapper component: the cataloged roots it renders (package-qualified keys such as @kerfjs/ui:toolbar-control-group), any one of them or nothing. UI composition checks treat the wrapper as each declared root.",
|
|
164
|
+
"type": "array",
|
|
165
|
+
"minItems": 1,
|
|
166
|
+
"items": { "type": "string", "pattern": "^[^:]+:[^:]+$" },
|
|
167
|
+
"uniqueItems": true
|
|
168
|
+
},
|
|
162
169
|
"parents": {
|
|
163
170
|
"type": "object",
|
|
164
171
|
"required": ["mode", "entries"],
|
package/package.json
CHANGED