@yahoo/uds-create-config 2.34.0 → 2.36.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.
@@ -0,0 +1,99 @@
1
+ import { slugifyLinkedName } from "./registry-aliases.js";
2
+ //#region src/linkedNodePrefixes.ts
3
+ /**
4
+ * Node-scoped CSS-prefix assignment for materialized linked systems.
5
+ *
6
+ * Every linked node (`{systemId}@{version}`) regenerates its CSS at consumer
7
+ * build time from its sealed `config.json`, with its `prefix` overridden to a
8
+ * composed, collision-free identity: `<consumerPrefix>-linked-<slug>`. One
9
+ * prefix per NODE (not per link path) — the module graph resolves each node's
10
+ * files once, so its lowered class names can only carry one prefix; sharing a
11
+ * node between parents shares byte-identical sealed content, so sharing the
12
+ * prefix is correct.
13
+ *
14
+ * The assignment must be identical everywhere it's consumed (pull-time shim
15
+ * generation, `uds build` CSS regeneration, the bundler loaders), so every
16
+ * consumer derives it from the same inputs with the same pure functions:
17
+ * node identities from the registry tree, display names from the declaring
18
+ * configs' `registerLinkedSystems` entries ({@link resolveDeclaredNames}),
19
+ * and the consumer's own prefix. Nothing is persisted — the registry tree is
20
+ * the source of truth.
21
+ */
22
+ /** CSS identifiers can't start with a digit (or a hyphen-digit pair) —
23
+ * prepend a letter sentinel when a candidate would. */
24
+ function ensureCssIdentStart(candidate) {
25
+ return /^[a-zA-Z_]/.test(candidate) ? candidate : `x${candidate}`;
26
+ }
27
+ /** `1.5.0` → `v1-5-0` — a CSS-safe version fragment. */
28
+ function versionFragment(version) {
29
+ return `v${version.replace(/[^a-zA-Z0-9]+/g, "-")}`;
30
+ }
31
+ function nodeKey(node) {
32
+ return `${node.systemId}@${node.version}`;
33
+ }
34
+ /**
35
+ * Assign each node in a linked closure its slug + composed CSS prefix,
36
+ * keyed by `{systemId}@{version}`.
37
+ *
38
+ * Deterministic and input-order independent: nodes are processed sorted by
39
+ * `(systemId, version)`. Within a slug-collision group the first sorted node
40
+ * keeps the bare slug; a different system appends `-x<systemId[0..8]>`; the
41
+ * same system at another version appends `-v<version>` — so every node in
42
+ * the closure gets a unique, stable prefix.
43
+ *
44
+ * `consumerPrefix` is the consuming system's own `Config.prefix`. An empty
45
+ * prefix is disallowed as a base (a bare `linked-…` could collide with the
46
+ * consumer's unprefixed classes) — the node's own uuid fragment substitutes.
47
+ */
48
+ function assignLinkedNodePrefixes(nodes, consumerPrefix) {
49
+ const sorted = [...nodes].sort((a, b) => a.systemId.localeCompare(b.systemId) || a.version.localeCompare(b.version));
50
+ const byslug = /* @__PURE__ */ new Map();
51
+ for (const node of sorted) {
52
+ const slug = slugifyLinkedName(node.name);
53
+ const group = byslug.get(slug);
54
+ if (group) group.push(node);
55
+ else byslug.set(slug, [node]);
56
+ }
57
+ const out = /* @__PURE__ */ new Map();
58
+ const usedSlugs = /* @__PURE__ */ new Set();
59
+ for (const [slug, group] of byslug) {
60
+ const systemsInOrder = [];
61
+ for (const node of group) if (!systemsInOrder.includes(node.systemId)) systemsInOrder.push(node.systemId);
62
+ for (const node of group) {
63
+ const systemRank = systemsInOrder.indexOf(node.systemId);
64
+ const versionsOfSystem = group.filter((n) => n.systemId === node.systemId);
65
+ let finalSlug = slug;
66
+ if (systemRank > 0) finalSlug += `-x${node.systemId.slice(0, 8)}`;
67
+ if (versionsOfSystem.length > 1 && versionsOfSystem.indexOf(node) > 0) finalSlug += `-${versionFragment(node.version)}`;
68
+ const collisionBase = finalSlug;
69
+ let collisionIndex = 2;
70
+ while (usedSlugs.has(finalSlug)) {
71
+ finalSlug = `${collisionBase}-${collisionIndex}`;
72
+ collisionIndex++;
73
+ }
74
+ usedSlugs.add(finalSlug);
75
+ const base = ensureCssIdentStart(consumerPrefix === "" ? `x${node.systemId.slice(0, 8)}` : consumerPrefix);
76
+ out.set(nodeKey(node), {
77
+ slug: finalSlug,
78
+ cssPrefix: `${base}-linked-${finalSlug}`
79
+ });
80
+ }
81
+ }
82
+ return out;
83
+ }
84
+ /**
85
+ * Resolve each node's display name from the closure's declarations. A node
86
+ * can be declared by several parents under different name snapshots; the
87
+ * winner is the declaration with the lowest `declarerKey` in sort order (the
88
+ * consumer's own declarations sort first as `''`), so every tool that walks
89
+ * the same closure derives the same names — and therefore the same slugs and
90
+ * CSS prefixes — without any persisted mapping.
91
+ */
92
+ function resolveDeclaredNames(declarations) {
93
+ const sorted = [...declarations].sort((a, b) => a.key.localeCompare(b.key) || a.declarerKey.localeCompare(b.declarerKey));
94
+ const names = /* @__PURE__ */ new Map();
95
+ for (const decl of sorted) if (!names.has(decl.key)) names.set(decl.key, decl.name);
96
+ return names;
97
+ }
98
+ //#endregion
99
+ export { assignLinkedNodePrefixes, resolveDeclaredNames };
package/dist/refs.d.ts CHANGED
@@ -85,6 +85,18 @@ type TagName = string;
85
85
  interface TokenRef<TName extends string = string> {
86
86
  readonly __kind: 'token';
87
87
  readonly ref: TName;
88
+ /**
89
+ * Namespace scope for refs authored in extension payloads. Absent on
90
+ * source-authored refs (they resolve in their own system's namespace).
91
+ * `'consumer'` is stamped by the extension merge on plain `token()` refs
92
+ * — a consumer-authored ref always means the consumer's own tokens.
93
+ */
94
+ readonly scope?: 'consumer';
95
+ /** Pinned-system scope from a registry barrel's `token()` utility. */
96
+ readonly system?: {
97
+ readonly id: string;
98
+ readonly version: string;
99
+ };
88
100
  }
89
101
  interface TokenGroupRef<TName extends string = string> {
90
102
  readonly __kind: 'tokenGroup';
@@ -153,6 +165,13 @@ type AnyRef = TokenRef | TokenGroupRef | CompositeRef | ModeRef | StylePropRef |
153
165
  /** Doc-alignment alias kept for ergonomic import. */
154
166
  type TokenQualifiedName = TokenName;
155
167
  declare function token<TName extends TokenName>(ref: TName): TokenRef<TName>;
168
+ /**
169
+ * Curried factory behind a registry barrel's `token()` utility —
170
+ * `yahooOs.token('color/red-1')` names THAT system's token, letting a
171
+ * consumer mix references across pinned systems unambiguously. The plain
172
+ * `token()` import always means the consumer's own namespace.
173
+ */
174
+ declare function systemToken(systemId: string, version: string): <TName extends TokenName>(ref: TName) => TokenRef<TName>;
156
175
  declare function tokenGroup<TName extends TokenGroupName>(ref: TName): TokenGroupRef<TName>;
157
176
  /**
158
177
  * `composite(ref)` returns a bare `CompositeRef` plus an `.on(layer)`
@@ -182,4 +201,4 @@ declare function isComponentRef(value: unknown): value is ComponentRef;
182
201
  declare function isAssetGroupRef(value: unknown): value is AssetGroupRef;
183
202
  declare function isAssetRef(value: unknown): value is AssetRef;
184
203
  //#endregion
185
- export { AnyRef, AssetGroupName, AssetGroupRef, AssetName, AssetRef, ComponentName, ComponentRef, CompositeName, CompositeRef, CompositeRefBuilder, ModeName, ModeRef, MotionName, RegisteredAssetGroups, RegisteredComponents, RegisteredCompositeStyles, RegisteredModes, RegisteredModifiers, RegisteredMotion, RegisteredStyleProps, RegisteredTokenGroups, StylePropName, StylePropRef, TagName, TagRef, TokenGroupName, TokenGroupRef, TokenName, TokenQualifiedName, TokenRawNameRegistry, TokenRef, asset, assetGroup, component, composite, isAssetGroupRef, isAssetRef, isComponentRef, isCompositeRef, isModeRef, isStylePropRef, isTagRef, isTokenGroupRef, isTokenRef, mode, styleProp, tag, token, tokenGroup };
204
+ export { AnyRef, AssetGroupName, AssetGroupRef, AssetName, AssetRef, ComponentName, ComponentRef, CompositeName, CompositeRef, CompositeRefBuilder, ModeName, ModeRef, MotionName, RegisteredAssetGroups, RegisteredComponents, RegisteredCompositeStyles, RegisteredModes, RegisteredModifiers, RegisteredMotion, RegisteredStyleProps, RegisteredTokenGroups, StylePropName, StylePropRef, TagName, TagRef, TokenGroupName, TokenGroupRef, TokenName, TokenQualifiedName, TokenRawNameRegistry, TokenRef, asset, assetGroup, component, composite, isAssetGroupRef, isAssetRef, isComponentRef, isCompositeRef, isModeRef, isStylePropRef, isTagRef, isTokenGroupRef, isTokenRef, mode, styleProp, systemToken, tag, token, tokenGroup };
package/dist/refs.js CHANGED
@@ -5,6 +5,22 @@ function token(ref) {
5
5
  ref
6
6
  };
7
7
  }
8
+ /**
9
+ * Curried factory behind a registry barrel's `token()` utility —
10
+ * `yahooOs.token('color/red-1')` names THAT system's token, letting a
11
+ * consumer mix references across pinned systems unambiguously. The plain
12
+ * `token()` import always means the consumer's own namespace.
13
+ */
14
+ function systemToken(systemId, version) {
15
+ return (ref) => ({
16
+ __kind: "token",
17
+ ref,
18
+ system: {
19
+ id: systemId,
20
+ version
21
+ }
22
+ });
23
+ }
8
24
  function tokenGroup(ref) {
9
25
  return {
10
26
  __kind: "tokenGroup",
@@ -110,4 +126,4 @@ function isAssetRef(value) {
110
126
  return isRefOf(value, "asset");
111
127
  }
112
128
  //#endregion
113
- export { asset, assetGroup, component, composite, isAssetGroupRef, isAssetRef, isComponentRef, isCompositeRef, isModeRef, isStylePropRef, isTagRef, isTokenGroupRef, isTokenRef, mode, styleProp, tag, token, tokenGroup };
129
+ export { asset, assetGroup, component, composite, isAssetGroupRef, isAssetRef, isComponentRef, isCompositeRef, isModeRef, isStylePropRef, isTagRef, isTokenGroupRef, isTokenRef, mode, styleProp, systemToken, tag, token, tokenGroup };