youiyoux 0.1.13 → 0.1.15

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.
Files changed (77) hide show
  1. package/dist/chunks/{chunk-ZWVWENSB.js → chunk-A2IZOTJG.js} +1 -1
  2. package/dist/chunks/{chunk-5I2VHSEN.js → chunk-ALQVHSF2.js} +1 -1
  3. package/dist/chunks/{chunk-DX2NKY2M.js → chunk-CZJFEND6.js} +4 -4
  4. package/dist/chunks/{chunk-ZOQW6LNH.js → chunk-DAGYR4NV.js} +450 -30
  5. package/dist/chunks/{chunk-N5Z5A637.js → chunk-FTL53MU3.js} +2 -2
  6. package/dist/chunks/{chunk-PCLXMWZ2.js → chunk-HII2I2PI.js} +3 -3
  7. package/dist/chunks/{chunk-XOXF2ILO.js → chunk-HIXGUALJ.js} +1 -1
  8. package/dist/chunks/{chunk-PWGZRP3C.js → chunk-MIUG2Z43.js} +180 -8
  9. package/dist/chunks/chunk-QEUZDGIF.js +110 -0
  10. package/dist/chunks/{chunk-NF4OJYKC.js → chunk-S7UN5KQK.js} +1 -1
  11. package/dist/chunks/{chunk-EV62TUDV.js → chunk-T4EHV4XY.js} +230 -288
  12. package/dist/chunks/ingest-R2TM3QBM.js +52 -0
  13. package/dist/chunks/{src-IKDQ2BZO.js → src-4BOFDAZD.js} +1 -1
  14. package/dist/chunks/{src-4QOHR5GC.js → src-6JO73FOS.js} +3 -3
  15. package/dist/chunks/{src-PBM76LFQ.js → src-7SHBAJEO.js} +2 -2
  16. package/dist/chunks/{src-R7JGKOYI.js → src-CCGTRT5J.js} +18 -4
  17. package/dist/chunks/{src-E6ZPFKPR.js → src-IMWXYWQK.js} +23 -3
  18. package/dist/chunks/{src-624TVB6T.js → src-LUPO3O76.js} +2 -2
  19. package/dist/chunks/{src-HVYMF4S2.js → src-OHHZY3TA.js} +7 -7
  20. package/dist/chunks/{src-YC64NNQ3.js → src-WMGLZKYK.js} +2 -2
  21. package/dist/hub/assets/{CandidatePanel-DfntneYT.js → CandidatePanel-BbE2xCN2.js} +1 -1
  22. package/dist/hub/assets/CatalogScreen-CBlaW1-N.js +1 -0
  23. package/dist/hub/assets/ChangesScreen-WaA9Y7zq.js +1 -0
  24. package/dist/hub/assets/{CheatSheet-B1dXTYTW.js → CheatSheet-CGfTS7zw.js} +1 -1
  25. package/dist/hub/assets/{CommandPalette-LBZHJ659.js → CommandPalette-D4WigyuD.js} +1 -1
  26. package/dist/hub/assets/{DiscoverScreen-Yd3gVUG4.js → DiscoverScreen-CVtcQCM4.js} +2 -2
  27. package/dist/hub/assets/DraftEditor-Mp1bT8-o.js +5 -0
  28. package/dist/hub/assets/{GapsScreen-DPYGlNnA.js → GapsScreen-DzB0TfH3.js} +1 -1
  29. package/dist/hub/assets/{HomeScreen-BCPPk7mz.js → HomeScreen-BVxjYDtc.js} +1 -1
  30. package/dist/hub/assets/InventoryScreen-D33_YchG.js +1 -0
  31. package/dist/hub/assets/{LibraryScreen-BVXhOVPU.js → LibraryScreen-B8lAJBGW.js} +1 -1
  32. package/dist/hub/assets/{Opened-CJyMbxqV.js → Opened-DgNa-hGA.js} +1 -1
  33. package/dist/hub/assets/{OrgApp-Dxs_7CxZ.js → OrgApp-Q8YNFE-z.js} +1 -1
  34. package/dist/hub/assets/{Preview-DWFP0BIe.js → Preview-yu3LaZKL.js} +1 -1
  35. package/dist/hub/assets/{RuleDetail-C2fTlLJL.js → RuleDetail-BqVdc20s.js} +1 -1
  36. package/dist/hub/assets/{RulesScreen-BABeAGJg.js → RulesScreen-mb3OUlOB.js} +1 -1
  37. package/dist/hub/assets/{ScreenImage-CyhKm_nj.js → ScreenImage-DUMSgBJL.js} +1 -1
  38. package/dist/hub/assets/{ScreensScreen-8Ih8byGy.js → ScreensScreen-DjN5mwSP.js} +1 -1
  39. package/dist/hub/assets/{SearchField-hF-6-YDM.js → SearchField-ByftoJws.js} +1 -1
  40. package/dist/hub/assets/{Segmented-Dzi1x49S.js → Segmented-CTMaLPgh.js} +1 -1
  41. package/dist/hub/assets/{ServiceDown-S4ya0Cob.js → ServiceDown-BghtjpTn.js} +1 -1
  42. package/dist/hub/assets/{SpecimenCapture-e_pgT4Sp.js → SpecimenCapture-CglZ4bn-.js} +1 -1
  43. package/dist/hub/assets/{Specimens-D_9hjlJH.js → Specimens-D5ehj5-O.js} +2 -2
  44. package/dist/hub/assets/{StackScreen-J2FQilLW.js → StackScreen-w5hCLj_R.js} +1 -1
  45. package/dist/hub/assets/{StubScreens-DAkMph3i.js → StubScreens-DfF4aAbp.js} +1 -1
  46. package/dist/hub/assets/{StyleScreen-C5WytSZU.js → StyleScreen-DKkerxmC.js} +1 -1
  47. package/dist/hub/assets/{Tabs-DU-fFRDV.js → Tabs-B1IFs-wv.js} +1 -1
  48. package/dist/hub/assets/TokenDetail-BPTZ9o6J.js +1 -0
  49. package/dist/hub/assets/TokensScreen-CKnQuVaT.js +1 -0
  50. package/dist/hub/assets/{WorkspaceScreen-CzZ16fPZ.js → WorkspaceScreen-DwjGW6eY.js} +1 -1
  51. package/dist/hub/assets/{client-Bdgr12LP.js → client-uMxZXiDl.js} +1 -1
  52. package/dist/hub/assets/{discover-va2KFuK_.js → discover-Yfb0eB14.js} +1 -1
  53. package/dist/hub/assets/{dist-CC_p8DIj.js → dist-BvRlRM3Z.js} +1 -1
  54. package/dist/hub/assets/{dist-C8tf4g8c.js → dist-CL3qgCbi.js} +1 -1
  55. package/dist/hub/assets/{dist-YTWqg5sS.js → dist-tjVfXRHc.js} +1 -1
  56. package/dist/hub/assets/{family-tiles-qaK1-_c8.js → family-tiles-dZqjsxro.js} +1 -1
  57. package/dist/hub/assets/link-2-dzvhLwTC.js +1 -0
  58. package/dist/hub/assets/main-DNLr0KzQ.css +2 -0
  59. package/dist/hub/assets/main-Djqho539.js +2 -0
  60. package/dist/hub/assets/{try-live-Iys3nCBG.js → try-live-Dxsjf62v.js} +1 -1
  61. package/dist/hub/assets/{workflow-C53UBmRZ.js → workflow-Bv814-L8.js} +1 -1
  62. package/dist/hub/index.html +2 -2
  63. package/dist/hub-previews.js +6 -6
  64. package/dist/hub-server.js +3 -3
  65. package/dist/hub-snapshot.js +1 -1
  66. package/dist/schema/youi.library.schema.json +3 -1
  67. package/dist/youi.js +8 -7
  68. package/dist/youiyoux.js +8 -7
  69. package/package.json +4 -4
  70. package/dist/hub/assets/CatalogScreen-kpFYZqX3.js +0 -1
  71. package/dist/hub/assets/ChangesScreen-Chu-Ui1B.js +0 -1
  72. package/dist/hub/assets/DraftEditor-QH9Sa_Uc.js +0 -5
  73. package/dist/hub/assets/InventoryScreen-SleB733C.js +0 -1
  74. package/dist/hub/assets/TokenDetail-CDY1xxb9.js +0 -1
  75. package/dist/hub/assets/TokensScreen-Sx4aICWk.js +0 -1
  76. package/dist/hub/assets/main-cvOFSjox.js +0 -2
  77. package/dist/hub/assets/main-pNY4iNRo.css +0 -2
@@ -2,7 +2,7 @@ import { createRequire as __youiRequire } from "node:module"; import { dirname a
2
2
  import {
3
3
  SHAPE_DIMENSIONS,
4
4
  shortHash
5
- } from "./chunk-NF4OJYKC.js";
5
+ } from "./chunk-S7UN5KQK.js";
6
6
 
7
7
  // ../store/src/driver.ts
8
8
  function searchTerms(words2) {
@@ -4,7 +4,7 @@ import {
4
4
  findFiles,
5
5
  require_dist,
6
6
  writeFileAtomic
7
- } from "./chunk-NF4OJYKC.js";
7
+ } from "./chunk-S7UN5KQK.js";
8
8
  import {
9
9
  __toESM
10
10
  } from "./chunk-WNTS7ZO4.js";
@@ -2,10 +2,10 @@ import { createRequire as __youiRequire } from "node:module"; import { dirname a
2
2
 
3
3
  // src/hub-packages.js
4
4
  var PACKAGES = {
5
- core: () => import("./src-IKDQ2BZO.js"),
6
- scanner: () => import("./src-4QOHR5GC.js"),
7
- discover: () => import("./src-E6ZPFKPR.js"),
8
- tokens: () => import("./src-YC64NNQ3.js")
5
+ core: () => import("./src-4BOFDAZD.js"),
6
+ scanner: () => import("./src-6JO73FOS.js"),
7
+ discover: () => import("./src-IMWXYWQK.js"),
8
+ tokens: () => import("./src-WMGLZKYK.js")
9
9
  };
10
10
  var loadPackage = (name) => PACKAGES[name]();
11
11
 
@@ -6,15 +6,19 @@ import {
6
6
  openStore,
7
7
  pageInteger,
8
8
  searchTerms
9
- } from "./chunk-ZWVWENSB.js";
9
+ } from "./chunk-A2IZOTJG.js";
10
10
  import {
11
11
  LIBRARY_SOURCE_KINDS,
12
12
  LibraryEntry,
13
13
  copyleft,
14
14
  external_exports,
15
+ require_dist,
15
16
  sha256,
16
17
  writeFileAtomic
17
- } from "./chunk-NF4OJYKC.js";
18
+ } from "./chunk-S7UN5KQK.js";
19
+ import {
20
+ __toESM
21
+ } from "./chunk-WNTS7ZO4.js";
18
22
 
19
23
  // ../discover/src/aliases.ts
20
24
  var CONCEPTS = [
@@ -63,11 +67,11 @@ var CONCEPTS = [
63
67
  { id: "marquee", names: ["marquee", "ticker", "scrolling text"], packages: [] }
64
68
  ];
65
69
  var BY_ID = new Map(CONCEPTS.map((c) => [c.id, c]));
66
- function normalize(text) {
67
- return text.normalize("NFC").replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
70
+ function normalize(text2) {
71
+ return text2.normalize("NFC").replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
68
72
  }
69
- function hasPhrase(text, phrase) {
70
- return ` ${text} `.includes(` ${phrase} `);
73
+ function hasPhrase(text2, phrase) {
74
+ return ` ${text2} `.includes(` ${phrase} `);
71
75
  }
72
76
  var STOP = /* @__PURE__ */ new Set(["a", "an", "the", "for", "with", "and", "or", "of", "to", "in", "component", "components", "ui", "react"]);
73
77
  function readNeed(need) {
@@ -106,8 +110,8 @@ function publicHttps(value) {
106
110
  }
107
111
 
108
112
  // ../discover/src/text.ts
109
- function cleanText(text) {
110
- return text.replace(/(?:\x1b\]|\x9d)[\s\S]*?(?:\x07|\x1b\\|\x9c|$)/g, "").replace(/(?:\x1b[PX^_]|[\x90\x98\x9e\x9f])[\s\S]*?(?:\x1b\\|\x9c|$)/g, "").replace(/(?:\x1b\[|\x9b)[0-?]*[ -/]*[@-~]/g, "").replace(/\x1b[ -/]*[0-~]/g, "").replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "");
113
+ function cleanText(text2) {
114
+ return text2.replace(/(?:\x1b\]|\x9d)[\s\S]*?(?:\x07|\x1b\\|\x9c|$)/g, "").replace(/(?:\x1b[PX^_]|[\x90\x98\x9e\x9f])[\s\S]*?(?:\x1b\\|\x9c|$)/g, "").replace(/(?:\x1b\[|\x9b)[0-?]*[ -/]*[@-~]/g, "").replace(/\x1b[ -/]*[0-~]/g, "").replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "");
111
115
  }
112
116
  function cleanData(value) {
113
117
  if (typeof value === "string") return cleanText(value);
@@ -245,8 +249,8 @@ function nameSlug(name) {
245
249
  return slug || "item";
246
250
  }
247
251
  function summaryOf(...texts) {
248
- const text = texts.map((t) => cleanText(t ?? "").replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim()).find((t) => t.length > 0) ?? "";
249
- return text.length > 200 ? `${text.slice(0, 199)}\u2026` : text;
252
+ const text2 = texts.map((t) => cleanText(t ?? "").replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim()).find((t) => t.length > 0) ?? "";
253
+ return text2.length > 200 ? `${text2.slice(0, 199)}\u2026` : text2;
250
254
  }
251
255
  function categoriesOf(item, headless) {
252
256
  const own = (item.categories ?? []).map((c) => c.toLowerCase());
@@ -347,8 +351,8 @@ function githubRepo(url) {
347
351
  return match ? `${match[1]}/${match[2]}` : null;
348
352
  }
349
353
  function answers(object, concept) {
350
- const text = normalize([object.package.name, object.package.description ?? "", ...object.package.keywords ?? []].join(" "));
351
- return concept.packages.includes(object.package.name) || concept.names.some((name) => hasPhrase(text, normalize(name)));
354
+ const text2 = normalize([object.package.name, object.package.description ?? "", ...object.package.keywords ?? []].join(" "));
355
+ return concept.packages.includes(object.package.name) || concept.names.some((name) => hasPhrase(text2, normalize(name)));
352
356
  }
353
357
  var MIN_WEEKLY_DOWNLOADS = 1e3;
354
358
  function npmCandidates(concept, objects) {
@@ -570,7 +574,10 @@ var nodeFetcher = async (url, init) => {
570
574
  if (!location) throw new Error("redirect missing location");
571
575
  const next = new URL(location, url);
572
576
  if (!publicHttps(next.href)) throw new Error("redirect must use https on a public host");
573
- if (next.origin !== new URL(url).origin) delete headers.authorization;
577
+ if (next.origin !== new URL(url).origin) {
578
+ delete headers.authorization;
579
+ delete headers["x-figma-token"];
580
+ }
574
581
  url = next.href;
575
582
  }
576
583
  throw new Error("too many redirects");
@@ -624,9 +631,10 @@ var PoliteClient = class {
624
631
  return this.options.now?.() ?? /* @__PURE__ */ new Date();
625
632
  }
626
633
  /** Replace the token wherever it appears, so no message, log line or file can carry it. */
627
- redact(text) {
628
- const token = this.options.token;
629
- return cleanText(token ? text.split(token).join("***") : text);
634
+ redact(text2) {
635
+ let clean = text2;
636
+ for (const token of [this.options.token, this.options.figmaToken]) if (token) clean = clean.split(token).join("***");
637
+ return cleanText(clean);
630
638
  }
631
639
  /** True when the bucket still has room in its window; a spent bucket stays spent for this run. */
632
640
  take(bucket) {
@@ -670,7 +678,7 @@ var PoliteClient = class {
670
678
  const gate = host;
671
679
  const request = () => this.gate.run(
672
680
  () => gate.run(async () => {
673
- if (npm && this.npmStarted) await (this.options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))))(500);
681
+ if (npm && this.npmStarted) await (this.options.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms))))(500);
674
682
  if (npm) this.npmStarted = true;
675
683
  if (!this.take(bucket)) {
676
684
  this.stats.rateLimited++;
@@ -680,7 +688,7 @@ var PoliteClient = class {
680
688
  const result = await this.send(url, parsed, bucket, accept);
681
689
  if (!npm || result.ok || result.status !== 429) return result;
682
690
  if (attempt === 4) return result;
683
- await (this.options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))))(Math.max(result.retryAfterMs ?? 0, Math.min(3e4, 1e3 * 2 ** attempt * (0.5 + (this.options.random ?? Math.random)()))));
691
+ await (this.options.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms))))(Math.max(result.retryAfterMs ?? 0, Math.min(3e4, 1e3 * 2 ** attempt * (0.5 + (this.options.random ?? Math.random)()))));
684
692
  }
685
693
  })
686
694
  );
@@ -692,6 +700,7 @@ var PoliteClient = class {
692
700
  if (cached?.etag) headers["if-none-match"] = cached.etag;
693
701
  if (cached?.lastModified) headers["if-modified-since"] = cached.lastModified;
694
702
  if (this.options.token && parsed.hostname === "api.github.com") headers.authorization = `Bearer ${this.options.token}`;
703
+ if (this.options.figmaToken && parsed.hostname === "api.figma.com") headers["x-figma-token"] = this.options.figmaToken;
695
704
  this.stats.sent++;
696
705
  this.inFlight++;
697
706
  this.stats.maxInFlight = Math.max(this.stats.maxInFlight, this.inFlight);
@@ -912,10 +921,10 @@ function rankEntries(entries, need, options = {}) {
912
921
  const f = fieldsOf(entry);
913
922
  if (options.role && !playsRole(options.role, f)) continue;
914
923
  const why = [];
915
- let text = 0;
916
- for (const concept of read.concepts) text += conceptScore(concept, f, why);
917
- for (const word of read.words) text += wordScore(word, f, why);
918
- if (text <= 0) continue;
924
+ let text2 = 0;
925
+ for (const concept of read.concepts) text2 += conceptScore(concept, f, why);
926
+ for (const word of read.words) text2 += wordScore(word, f, why);
927
+ if (text2 <= 0) continue;
919
928
  let suitability = 1;
920
929
  if (isIconEntry(entry)) {
921
930
  suitability *= 0.3;
@@ -926,7 +935,7 @@ function rankEntries(entries, need, options = {}) {
926
935
  suitability *= 0.3;
927
936
  why.push(`framework ${framework}`);
928
937
  }
929
- scored.push({ entry, score: (text * KIND_WEIGHT[entry.kind] + signalBoost(entry)) * suitability, why });
938
+ scored.push({ entry, score: (text2 * KIND_WEIGHT[entry.kind] + signalBoost(entry)) * suitability, why });
930
939
  }
931
940
  scored.sort((a, b) => b.score - a.score || a.entry.id.localeCompare(b.entry.id));
932
941
  return { need, concepts: read.concepts.map((c) => c.id), scored };
@@ -1751,8 +1760,8 @@ async function preparePreview(cacheDir, id, options = {}) {
1751
1760
  const queue = [...own];
1752
1761
  while (queue.length > 0) {
1753
1762
  const file = queue.shift();
1754
- const text = texts.get(file);
1755
- for (const match of text.matchAll(SPECIFIER)) {
1763
+ const text2 = texts.get(file);
1764
+ for (const match of text2.matchAll(SPECIFIER)) {
1756
1765
  const spec = match[3];
1757
1766
  if (!spec.startsWith("@/registry/") || resolved.has(spec)) continue;
1758
1767
  const local = [...texts.keys()].find((f) => f.replace(/\.(?:tsx?|jsx?)$/, "") === spec.slice(2) || f.replace(/\/index\.(?:tsx?|jsx?)$/, "") === spec.slice(2));
@@ -1793,17 +1802,17 @@ async function preparePreview(cacheDir, id, options = {}) {
1793
1802
  }
1794
1803
  }
1795
1804
  const rewritten = /* @__PURE__ */ new Map();
1796
- for (const [file, text] of texts) {
1797
- rewritten.set(file, text.replace(SPECIFIER, (all, lead, quote, spec) => {
1805
+ for (const [file, text2] of texts) {
1806
+ rewritten.set(file, text2.replace(SPECIFIER, (all, lead, quote, spec) => {
1798
1807
  const target = resolved.get(spec);
1799
1808
  if (!target || target === spec) return all;
1800
1809
  const next = target.startsWith("/") || /^[a-z]:[\\/]/i.test(target) ? target : relativeSpec(file, target);
1801
1810
  return `${lead}${quote}${next}${quote}`;
1802
1811
  }));
1803
1812
  }
1804
- for (const [file, text] of rewritten) {
1813
+ for (const [file, text2] of rewritten) {
1805
1814
  await assertNoLinks(dir, file);
1806
- await writeFileAtomic(join5(dir, file), text);
1815
+ await writeFileAtomic(join5(dir, file), text2);
1807
1816
  }
1808
1817
  const packages = [...new Set([...rewritten.values()].flatMap((t) => importedPackages(t)))].sort();
1809
1818
  const declared = [...new Set([...source.dependencies ?? entry.install?.npm ?? [], ...partDeps].map(packageName))].sort();
@@ -1955,6 +1964,407 @@ async function buildGaps(source, store) {
1955
1964
  };
1956
1965
  }
1957
1966
 
1967
+ // ../discover/src/ingest.ts
1968
+ import { readFile as readFile3 } from "node:fs/promises";
1969
+ import { basename as basename2, resolve } from "node:path";
1970
+
1971
+ // ../discover/src/ingest-design-md.ts
1972
+ var import_yaml = __toESM(require_dist(), 1);
1973
+ import { basename } from "node:path";
1974
+ var GROUP_TITLE = { colors: "Colors", typography: "Typography", rounded: "Corner radius", spacing: "Spacing", elevation: "Elevation", shadows: "Shadows", motion: "Motion" };
1975
+ var NOT_TOKENS = /* @__PURE__ */ new Set(["name", "version", "description", "components", "$schema"]);
1976
+ var SAMPLES = 4;
1977
+ var title = (key) => key.split(/[-_\s]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
1978
+ function sample(name, value) {
1979
+ if (value && typeof value === "object") {
1980
+ const v = value;
1981
+ return `${name} ${[v.fontSize, v.lineHeight, v.fontWeight].filter((x) => x !== void 0).join("/")}`.trim();
1982
+ }
1983
+ return `${name} ${String(value)}`;
1984
+ }
1985
+ function bodyComponents(body) {
1986
+ const lines = body.split(/\r?\n/);
1987
+ const found = [];
1988
+ let level = 0;
1989
+ for (let i = 0; i < lines.length; i++) {
1990
+ const heading = /^(#{1,6})\s+(.+?)\s*#*$/.exec(lines[i]);
1991
+ if (heading) {
1992
+ const depth = heading[1].length;
1993
+ if (/\bcomponents?\b/i.test(heading[2])) {
1994
+ level = depth;
1995
+ continue;
1996
+ }
1997
+ if (level > 0 && depth <= level) level = 0;
1998
+ else if (level > 0 && depth > level) {
1999
+ const text2 = lines.slice(i + 1).find((l) => l.trim() && !l.startsWith("#") && !l.startsWith("|"));
2000
+ found.push({ name: heading[2].replace(/[`*_]/g, ""), summary: text2?.trim() ?? "" });
2001
+ }
2002
+ continue;
2003
+ }
2004
+ if (level === 0) continue;
2005
+ const row = /^\|(.+)\|\s*$/.exec(lines[i]);
2006
+ if (!row || /^\|[\s:-]+\|/.test(lines[i])) continue;
2007
+ const cells = row[1].split("|").map((c) => c.trim());
2008
+ const name = cells[0].replace(/[`*_]/g, "");
2009
+ if (!name || /^(component|name|element)s?$/i.test(name)) continue;
2010
+ found.push({ name, summary: cells.slice(1).filter((c) => c && c !== "\u2014" && !c.startsWith("`")).at(-1) ?? "" });
2011
+ }
2012
+ return found;
2013
+ }
2014
+ async function readDesignMd(target, client, fetchedAt, readLocal) {
2015
+ const text2 = target.local ? await readLocal(target.location).then((body2) => ({ body: cleanText(body2), sha256: digestOf(body2) }), () => {
2016
+ throw new IngestError(`could not read ${basename(target.location)}`);
2017
+ }) : await fetchText(client, target.location, "the DESIGN.md");
2018
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(text2.body);
2019
+ let front = {};
2020
+ if (match) {
2021
+ try {
2022
+ const parsed = (0, import_yaml.parse)(match[1]);
2023
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) front = parsed;
2024
+ } catch (error) {
2025
+ throw new IngestError(`the DESIGN.md's frontmatter is not YAML: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`);
2026
+ }
2027
+ }
2028
+ const body = match ? match[2] : text2.body;
2029
+ const heading = /^#\s+(.+)$/m.exec(body)?.[1]?.replace(/^Design System:\s*/i, "").trim();
2030
+ const name = typeof front.name === "string" && front.name.trim() || heading || (target.local ? basename(target.location, ".md") : new URL(target.location).host);
2031
+ const url = target.local ? null : target.location;
2032
+ const source = { kind: "design-md", name, url };
2033
+ const file = target.local ? basename(target.location) : new URL(target.location).pathname.split("/").at(-1) ?? "DESIGN.md";
2034
+ const inputs = [];
2035
+ for (const [group, value] of Object.entries(front)) {
2036
+ if (NOT_TOKENS.has(group) || !value || typeof value !== "object" || Array.isArray(value)) continue;
2037
+ const tokens = Object.entries(value);
2038
+ if (tokens.length === 0) continue;
2039
+ const samples = tokens.slice(0, SAMPLES).map(([k, v]) => sample(k, v)).join(", ");
2040
+ inputs.push({ kind: "token", name: GROUP_TITLE[group] ?? title(group), summary: `${tokens.length} ${group} token${tokens.length === 1 ? "" : "s"}: ${samples}${tokens.length > SAMPLES ? ", \u2026" : ""}`, ref: `${file}#${group}`, url, digest: digestOf(value), categories: [group] });
2041
+ }
2042
+ const components = /* @__PURE__ */ new Map();
2043
+ const keys = Object.keys(front.components ?? {});
2044
+ for (const key of keys) {
2045
+ const base = keys.filter((other) => other !== key && key.startsWith(`${other}-`)).sort((a, b) => a.length - b.length)[0] ?? key;
2046
+ const entry = components.get(base.toLowerCase()) ?? { name: title(base), summary: "", digest: digestOf(front.components[base]) };
2047
+ if (base !== key) entry.summary = `${entry.summary ? `${entry.summary}, ` : "Variants: "}${key.slice(base.length + 1)}`;
2048
+ components.set(base.toLowerCase(), entry);
2049
+ }
2050
+ for (const found of bodyComponents(body)) {
2051
+ const key = found.name.toLowerCase().replace(/\s+/g, "-");
2052
+ const known = components.get(key);
2053
+ components.set(key, { name: known?.name ?? found.name, summary: found.summary || known?.summary || "", digest: known?.digest ?? digestOf(found) });
2054
+ }
2055
+ for (const [key, c] of components) inputs.push({ kind: "component", name: c.name, summary: c.summary || `${c.name}, as ${name}'s DESIGN.md describes it`, ref: `${file}#${key}`, url, digest: c.digest });
2056
+ if (inputs.length === 0) throw new IngestError(`the DESIGN.md names no token groups in its frontmatter and no components: nothing to read`);
2057
+ return {
2058
+ source,
2059
+ license: "unknown",
2060
+ entries: itemsOf(source, "unknown", fetchedAt, inputs),
2061
+ skipped: [],
2062
+ notes: ["licence unknown: a DESIGN.md does not say one", ...target.local ? ["read from a local file: its path is not written into the items"] : []]
2063
+ };
2064
+ }
2065
+
2066
+ // ../discover/src/ingest-figma.ts
2067
+ var API = "https://api.figma.com/v1";
2068
+ var STYLE_GROUP = { FILL: "Color styles", TEXT: "Text styles", EFFECT: "Effect styles", GRID: "Grid styles" };
2069
+ var SAMPLES2 = 5;
2070
+ var listOf = (value, field) => {
2071
+ const list = value.meta?.[field];
2072
+ return Array.isArray(list) ? list : [];
2073
+ };
2074
+ var text = (value) => typeof value === "string" ? value.trim() : "";
2075
+ async function readFigma(target, client, fetchedAt) {
2076
+ const file = await fetchJson(client, `${API}/files/${target.key}?depth=1`, "the Figma file").catch((error) => {
2077
+ throw new IngestError(`${error instanceof Error ? error.message : String(error)}: is FIGMA_TOKEN allowed to read this file?`);
2078
+ });
2079
+ const fileName = text(file.name) || target.key;
2080
+ const [sets, components, styles] = await Promise.all([
2081
+ fetchJson(client, `${API}/files/${target.key}/component_sets`, "its component sets"),
2082
+ fetchJson(client, `${API}/files/${target.key}/components`, "its components"),
2083
+ fetchJson(client, `${API}/files/${target.key}/styles`, "its styles")
2084
+ ]);
2085
+ const at = (node) => `https://www.figma.com/design/${target.key}?node-id=${encodeURIComponent(String(node).replace(/:/g, "-"))}`;
2086
+ const source = { kind: "figma", name: `${fileName} (Figma)`, url: `https://www.figma.com/design/${target.key}` };
2087
+ const inputs = [];
2088
+ const page = (p) => [text(p.containing_frame?.pageName), text(p.containing_frame?.name)].filter(Boolean);
2089
+ for (const set of listOf(sets, "component_sets")) {
2090
+ if (!text(set.name)) continue;
2091
+ inputs.push({ kind: "component", name: text(set.name), summary: text(set.description) || `${text(set.name)}, a component set in ${fileName}`, ref: text(set.key) || String(set.node_id), url: at(set.node_id), digest: digestOf(set), categories: page(set) });
2092
+ }
2093
+ const loose = listOf(components, "components").filter((c) => !c.containing_frame?.containingComponentSet && text(c.name));
2094
+ for (const component of loose) inputs.push({ kind: "component", name: text(component.name), summary: text(component.description) || `${text(component.name)}, a component in ${fileName}`, ref: text(component.key) || String(component.node_id), url: at(component.node_id), digest: digestOf(component), categories: page(component) });
2095
+ const byType = /* @__PURE__ */ new Map();
2096
+ for (const style of listOf(styles, "styles")) {
2097
+ const type = text(style.style_type);
2098
+ if (type) byType.set(type, [...byType.get(type) ?? [], style]);
2099
+ }
2100
+ for (const [type, list] of byType) {
2101
+ const names = list.map((s) => text(s.name)).filter(Boolean);
2102
+ inputs.push({ kind: "token", name: STYLE_GROUP[type] ?? `${type.toLowerCase()} styles`, summary: `${names.length} ${(STYLE_GROUP[type] ?? type).toLowerCase()} in ${fileName}: ${names.slice(0, SAMPLES2).join(", ")}${names.length > SAMPLES2 ? ", \u2026" : ""}`, ref: `${target.key}#${type.toLowerCase()}`, url: source.url, digest: digestOf(list), categories: [type.toLowerCase()] });
2103
+ }
2104
+ if (inputs.length === 0) throw new IngestError(`${fileName} has no components or styles published to a team library: Figma lists only published ones, so publish the library (Assets > Libraries) and ingest it again`);
2105
+ return {
2106
+ source,
2107
+ license: "unknown",
2108
+ entries: itemsOf(source, "unknown", fetchedAt, inputs),
2109
+ skipped: [],
2110
+ notes: [`a Figma file: ${fileName}, ${listOf(sets, "component_sets").length} component sets, ${loose.length} other components, ${byType.size} kinds of style`, "only what is published to a team library is listed", "licence unknown: a Figma file does not say one"]
2111
+ };
2112
+ }
2113
+
2114
+ // ../discover/src/ingest-github.ts
2115
+ var API2 = "https://api.github.com";
2116
+ var COMPONENT_FILE = /\.(tsx|jsx|vue|svelte|astro)$/;
2117
+ var NOT_A_COMPONENT = /(\.(test|spec|stories|story|d)\.[^.]+$)|(^|\/)(index|types?|utils?|helpers?|constants?)\.[^.]+$|(^|\/)(__tests__|__mocks__|stories|examples?|demo|docs?)\//;
2118
+ var CONTRACTS_READ = 100;
2119
+ async function licenseOf(client, repo, fetchedAt) {
2120
+ const got = await client.json(`${API2}/repos/${repo}/license`, false);
2121
+ if (!got.ok) return "unknown";
2122
+ const value = got.value;
2123
+ const spdx = value.license?.spdx_id;
2124
+ if (typeof spdx !== "string" || spdx === "NOASSERTION" || typeof value.html_url !== "string") return "unknown";
2125
+ return { spdx, evidenceUrl: value.html_url, verifiedAt: fetchedAt };
2126
+ }
2127
+ async function readGithub(target, client, fetchedAt) {
2128
+ const repo = `${target.owner}/${target.repo}`;
2129
+ const meta = await fetchJson(client, `${API2}/repos/${repo}`, `the repository ${repo}`);
2130
+ const ref = target.ref ?? meta.default_branch ?? "main";
2131
+ const commit = (await fetchJson(client, `${API2}/repos/${repo}/commits/${encodeURIComponent(ref)}`, `${repo} at ${ref}`)).sha;
2132
+ if (typeof commit !== "string" || !/^[0-9a-f]{40}$/.test(commit)) throw new IngestError(`${repo} has no commit at ${ref}`);
2133
+ const tree = await fetchJson(client, `${API2}/repos/${repo}/git/trees/${commit}?recursive=1`, `${repo}'s files`);
2134
+ const scope = target.path ? `${target.path.replace(/\/+$/, "")}/` : "";
2135
+ const blobs = new Map((tree.tree ?? []).filter((e) => e.type === "blob" && e.path.startsWith(scope)).map((e) => [e.path, e.sha ?? null]));
2136
+ const files = [...blobs.keys()];
2137
+ const license = await licenseOf(client, repo, fetchedAt);
2138
+ const source = { kind: "github", name: repo, url: `https://github.com/${repo}${target.ref || target.path ? `/tree/${ref}${target.path ? `/${target.path}` : ""}` : ""}` };
2139
+ const blob = (path) => `https://github.com/${repo}/blob/${commit}/${path}`;
2140
+ const raw = (path) => `https://raw.githubusercontent.com/${repo}/${commit}/${path}`;
2141
+ const notes = [
2142
+ `read at ${commit.slice(0, 7)} (${ref})`,
2143
+ license === "unknown" ? "licence unknown: GitHub names none for this repository, so its items cannot be adopted until one is found" : `licence ${license.spdx}, from ${license.evidenceUrl}`,
2144
+ ...tree.truncated ? ["GitHub listed only part of this repository's files: name a folder (https://github.com/<owner>/<repo>/tree/<ref>/<folder>) to read the rest"] : []
2145
+ ];
2146
+ const byDepth = (a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b);
2147
+ const registry = files.filter((f) => /(^|\/)registry\.json$/.test(f) && !f.includes("node_modules/")).sort(byDepth)[0];
2148
+ if (registry) {
2149
+ const text2 = await fetchText(client, raw(registry), `${repo}'s ${registry}`);
2150
+ let value;
2151
+ try {
2152
+ value = JSON.parse(text2.body);
2153
+ } catch {
2154
+ throw new IngestError(`${repo}'s ${registry} is not JSON`);
2155
+ }
2156
+ const { items, invalid } = parseRegistryItems(value);
2157
+ const built = new Set(files.filter((f) => /(^|\/)public\/r\/[^/]+\.json$/.test(f) && /^[A-Za-z0-9._/-]+$/.test(f)));
2158
+ const inputs2 = items.filter(isCandidate).map((item) => {
2159
+ const servedAt = [...built].find((f) => f.endsWith(`/r/${item.name}.json`));
2160
+ return {
2161
+ kind: item.type === "registry:block" ? "pattern" : "component",
2162
+ name: item.name,
2163
+ ...item.title ? { title: item.title } : {},
2164
+ summary: item.description ?? "",
2165
+ ref: `${registry}#${item.name}`,
2166
+ url: blob(registry),
2167
+ digest: item.itemSha256 ?? digestOf(item),
2168
+ files: (item.files ?? []).map((f) => ({ path: f.path, sha256: f.sha256 ?? null })),
2169
+ categories: item.categories ?? [],
2170
+ ...servedAt ? { install: { command: `npx shadcn add ${raw(servedAt)}`, npm: [...new Set((item.dependencies ?? []).map(packageName))], registryDeps: [...new Set(item.registryDependencies ?? [])], implicitDeps: [] } } : {}
2171
+ };
2172
+ });
2173
+ const others = items.filter((i) => !isCandidate(i));
2174
+ return {
2175
+ source,
2176
+ license,
2177
+ entries: itemsOf(source, license, fetchedAt, inputs2),
2178
+ skipped: [...others.map((i) => ({ name: i.name, reason: `${i.type}: not a component or block` })), ...invalid > 0 ? [{ name: `${invalid} item${invalid === 1 ? "" : "s"}`, reason: "not valid registry items" }] : []],
2179
+ notes: [`a shadcn registry: ${registry}`, ...notes]
2180
+ };
2181
+ }
2182
+ const config = files.filter((f) => /(^|\/)youi\.config\.json$/.test(f)).sort(byDepth)[0];
2183
+ if (config) {
2184
+ const contracts = files.filter((f) => f.endsWith(".contract.json")).sort();
2185
+ const inputs2 = [];
2186
+ const skipped = [];
2187
+ for (const path of contracts.slice(0, CONTRACTS_READ)) {
2188
+ const text2 = await fetchText(client, raw(path), path).catch((error) => {
2189
+ skipped.push({ name: path, reason: error instanceof Error ? error.message : String(error) });
2190
+ return null;
2191
+ });
2192
+ if (!text2) continue;
2193
+ try {
2194
+ const contract = JSON.parse(text2.body);
2195
+ if (typeof contract.name !== "string") throw new Error("no name");
2196
+ inputs2.push({ kind: "component", name: contract.name, summary: typeof contract.summary === "string" ? contract.summary : "", ref: path, url: blob(path), digest: text2.sha256, files: [{ path, sha256: text2.sha256 }], categories: ["contract", ...typeof contract.status === "string" ? [contract.status] : []] });
2197
+ } catch (error) {
2198
+ skipped.push({ name: path, reason: `not a component contract: ${error instanceof Error ? error.message : String(error)}` });
2199
+ }
2200
+ }
2201
+ return { source, license, entries: itemsOf(source, license, fetchedAt, inputs2), skipped, notes: [`a youiyoux design repo: ${config}`, ...notes, ...contracts.length > CONTRACTS_READ ? [`${contracts.length - CONTRACTS_READ} more contracts not read: at most ${CONTRACTS_READ}`] : []] };
2202
+ }
2203
+ const components = files.filter((f) => /(^|\/)components?\//.test(f) && COMPONENT_FILE.test(f) && !NOT_A_COMPONENT.test(f) && !f.includes("node_modules/")).sort();
2204
+ if (components.length === 0) throw new IngestError(`${repo}${scope ? `/${scope}` : ""} has no registry.json, no youi.config.json and no components folder: nothing to read as Library items`);
2205
+ const inputs = components.map((path) => ({ kind: "component", name: displayName(path), summary: cleanText(`${displayName(path)}, in ${path}`), ref: path, url: blob(path), digest: digestOf({ path, blob: blobs.get(path) }), categories: [] }));
2206
+ return { source, license, entries: itemsOf(source, license, fetchedAt, inputs), skipped: [], notes: [`component files under a components folder, by name: their code was not read`, ...notes] };
2207
+ }
2208
+
2209
+ // ../discover/src/ingest-storybook.ts
2210
+ var PATTERN = /^(pages?|templates?|layouts?|patterns?|screens?|examples?)$/i;
2211
+ var STORIES_LISTED = 6;
2212
+ async function readStorybook(target, client, fetchedAt) {
2213
+ let index = null;
2214
+ let entries = {};
2215
+ for (const file of ["index.json", "stories.json"]) {
2216
+ const url = `${target.base}/${file}`;
2217
+ const got = await client.json(url, false);
2218
+ if (!got.ok) continue;
2219
+ const value = got.value;
2220
+ const found = value.entries ?? value.stories;
2221
+ if (found && typeof found === "object" && !Array.isArray(found)) {
2222
+ entries = cleanData(found);
2223
+ index = { body: got.result.body, url, sha256: got.result.sha256 };
2224
+ break;
2225
+ }
2226
+ }
2227
+ if (!index) throw new IngestError(`${target.base} is not a GitHub repository, a Figma file or a DESIGN.md, and serves no Storybook index (index.json or stories.json): nothing to read`);
2228
+ const byTitle = /* @__PURE__ */ new Map();
2229
+ for (const [key, raw] of Object.entries(entries)) {
2230
+ const title2 = typeof raw.title === "string" ? raw.title : typeof raw.kind === "string" ? raw.kind : null;
2231
+ const id = typeof raw.id === "string" ? raw.id : key;
2232
+ if (!title2) continue;
2233
+ const item = byTitle.get(title2) ?? { stories: [], first: null, docs: null };
2234
+ if (raw.type === "docs") item.docs ??= id;
2235
+ else {
2236
+ item.first ??= id;
2237
+ if (typeof raw.name === "string") item.stories.push(raw.name);
2238
+ }
2239
+ byTitle.set(title2, item);
2240
+ }
2241
+ const host = new URL(target.base).host;
2242
+ const source = { kind: "storybook", name: host, url: target.base };
2243
+ const inputs = [...byTitle].map(([title2, item]) => {
2244
+ const parts = title2.split("/").map((p) => p.trim()).filter(Boolean);
2245
+ const name = parts.at(-1) ?? title2;
2246
+ const folders = parts.slice(0, -1);
2247
+ const page = item.docs ? `?path=/docs/${item.docs}` : item.first ? `?path=/story/${item.first}` : "";
2248
+ const stories = item.stories.length === 0 ? "docs only" : `${item.stories.length} ${item.stories.length === 1 ? "story" : "stories"}: ${item.stories.slice(0, STORIES_LISTED).join(", ")}${item.stories.length > STORIES_LISTED ? ", \u2026" : ""}`;
2249
+ return {
2250
+ kind: folders.some((f) => PATTERN.test(f)) ? "pattern" : "component",
2251
+ name,
2252
+ title: title2,
2253
+ summary: `${title2} in ${host}'s Storybook, ${stories}`,
2254
+ ref: title2,
2255
+ url: `${target.base}/${page}`,
2256
+ digest: index.sha256,
2257
+ categories: folders
2258
+ };
2259
+ });
2260
+ return {
2261
+ source,
2262
+ license: "unknown",
2263
+ entries: itemsOf(source, "unknown", fetchedAt, inputs),
2264
+ skipped: [],
2265
+ notes: [`a Storybook: ${index.url}, ${byTitle.size} component title${byTitle.size === 1 ? "" : "s"}`, "licence unknown: a Storybook does not say one, so its items cannot be adopted until the code's licence is found"]
2266
+ };
2267
+ }
2268
+
2269
+ // ../discover/src/ingest.ts
2270
+ var INGEST_CAP = 300;
2271
+ var IngestError = class extends Error {
2272
+ };
2273
+ var GITHUB_SHORT = /^github:([\w.-]+)\/([\w.-]+?)(?:\.git)?(?:#([^\s]+))?$/;
2274
+ function classifyLink(link, options = {}) {
2275
+ const text2 = link.trim();
2276
+ const short = GITHUB_SHORT.exec(text2);
2277
+ if (short) return { kind: "github", owner: short[1], repo: short[2], ref: short[3] ?? null, path: null };
2278
+ let url = null;
2279
+ try {
2280
+ url = new URL(text2);
2281
+ } catch {
2282
+ url = null;
2283
+ }
2284
+ if (!url) {
2285
+ if (/(^|\/)[^/]*\.md$/i.test(text2)) {
2286
+ if (!options.allowLocal) throw new IngestError(`${text2} is a local file: only the CLI reads those (youiyoux library ingest ${text2})`);
2287
+ return { kind: "design-md", location: resolve(text2), local: true };
2288
+ }
2289
+ throw new IngestError(`"${text2}" is not a link: give a GitHub repository (github:owner/repo or its https URL), a Storybook's https URL, a Figma file's URL, or a DESIGN.md (a path or an https URL)`);
2290
+ }
2291
+ if (url.protocol !== "https:") throw new IngestError(`${url.protocol}//${url.host} is not https: ingestion reads public https links only`);
2292
+ const host = url.hostname.replace(/^www\./, "");
2293
+ const parts = url.pathname.split("/").filter(Boolean);
2294
+ if (host === "github.com") {
2295
+ if (parts.length < 2) throw new IngestError(`${text2} names no repository: expected https://github.com/<owner>/<repo>`);
2296
+ const [owner, repo, kind, ref, ...rest] = parts;
2297
+ if (kind === "blob" && ref && /\.md$/i.test(rest.at(-1) ?? "")) return { kind: "design-md", location: `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${rest.join("/")}`, local: false };
2298
+ return { kind: "github", owner, repo: repo.replace(/\.git$/, ""), ref: kind === "tree" && ref ? ref : null, path: kind === "tree" && rest.length > 0 ? rest.join("/") : null };
2299
+ }
2300
+ if (host === "figma.com" || host.endsWith(".figma.com")) {
2301
+ const at = parts.findIndex((p) => p === "design" || p === "file" || p === "proto");
2302
+ const key = at >= 0 ? parts[at + 1] : void 0;
2303
+ if (!key || !/^[A-Za-z0-9]{10,64}$/.test(key)) throw new IngestError(`${text2} names no Figma file: expected https://www.figma.com/design/<file key>/\u2026`);
2304
+ return { kind: "figma", key };
2305
+ }
2306
+ if (/\.md$/i.test(url.pathname)) return { kind: "design-md", location: url.href, local: false };
2307
+ const base = `${url.origin}${url.pathname.replace(/\/(?:index\.html|iframe\.html)?$/, "")}`.replace(/\/+$/, "");
2308
+ return { kind: "storybook", base };
2309
+ }
2310
+ function itemsOf(source, license, fetchedAt, inputs) {
2311
+ const used = /* @__PURE__ */ new Set();
2312
+ const slug = sourceSlug(source.name.replace(/\//g, "-"));
2313
+ return inputs.map((input) => {
2314
+ const base = `lib:${slug}/${input.kind}/${nameSlug(input.name)}`;
2315
+ let id = base;
2316
+ for (let n = 2; used.has(id); n++) id = `${base}-${n}`;
2317
+ used.add(id);
2318
+ const draft = {
2319
+ schema: "youi.library/v1",
2320
+ id,
2321
+ name: input.name,
2322
+ ...input.title && input.title !== input.name ? { title: input.title } : {},
2323
+ kind: input.kind,
2324
+ stage: "library",
2325
+ summary: summaryOf(input.summary, `${input.name} from ${source.name}`),
2326
+ source: { name: source.name, ...input.url ? { url: input.url } : {}, ingestedAt: fetchedAt, kind: source.kind, ref: input.ref },
2327
+ categories: [...new Set((input.categories ?? []).map((c) => c.toLowerCase()).filter(Boolean))],
2328
+ swapsWith: [],
2329
+ ...input.url ? { provenance: { fetchedAt, url: input.url, sha256: input.digest, files: [...input.files ?? []], registryHealth: null } } : {},
2330
+ license,
2331
+ ...input.install ? { install: input.install } : {}
2332
+ };
2333
+ const named = readNeed(input.name).concepts.length > 0 ? readNeed(input.name).concepts : readNeed(input.name.replace(/[^A-Za-z0-9]+/g, "").toLowerCase()).concepts;
2334
+ const roles = input.kind === "token" ? [] : (named.length > 0 ? named : input.title ? readNeed(input.title).concepts : []).map((c) => c.id);
2335
+ return LibraryEntry.parse({ ...draft, categories: [.../* @__PURE__ */ new Set([...roles, ...draft.categories])] });
2336
+ });
2337
+ }
2338
+ function displayName(file) {
2339
+ const stem = basename2(file).replace(/\.[^.]+$/, "");
2340
+ return stem.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[-_\s]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
2341
+ }
2342
+ var digestOf = (value) => sha256(typeof value === "string" ? value : JSON.stringify(value));
2343
+ async function fetchJson(client, url, what) {
2344
+ const got = await client.json(url, false);
2345
+ if (!got.ok) throw new IngestError(`could not read ${what} (${url}): ${got.status === 404 ? "not found" : got.message}`);
2346
+ return cleanData(got.value);
2347
+ }
2348
+ async function fetchText(client, url, what) {
2349
+ const got = await client.get(url, "text/plain, text/markdown, */*");
2350
+ if (!got.ok) throw new IngestError(`could not read ${what} (${url}): ${got.status === 404 ? "not found" : got.message}`);
2351
+ return { body: cleanText(got.body), sha256: got.sha256, fetchedAt: got.fetchedAt };
2352
+ }
2353
+ async function ingestLink(link, options) {
2354
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
2355
+ const target = classifyLink(link, options);
2356
+ const fetchedAt = now().toISOString();
2357
+ const result = await (target.kind === "github" ? readGithub(target, options.client, fetchedAt) : target.kind === "storybook" ? readStorybook(target, options.client, fetchedAt) : target.kind === "figma" ? options.figma ? readFigma(target, options.client, fetchedAt) : Promise.reject(new IngestError("a Figma file is read through Figma's API: set FIGMA_TOKEN to a personal access token that can read the file (Figma > Settings > Security > Personal access tokens)")) : readDesignMd(target, options.client, fetchedAt, (path) => readFile3(path, "utf8")));
2358
+ const over = result.entries.length - INGEST_CAP;
2359
+ return cleanData({
2360
+ ...result,
2361
+ link: link.trim(),
2362
+ fetchedAt,
2363
+ entries: result.entries.slice(0, INGEST_CAP),
2364
+ notes: [...result.notes, ...over > 0 ? [`${over} more item${over === 1 ? "" : "s"} not read: at most ${INGEST_CAP} come from one link`] : []]
2365
+ });
2366
+ }
2367
+
1958
2368
  export {
1959
2369
  CONCEPTS,
1960
2370
  normalize,
@@ -1977,6 +2387,7 @@ export {
1977
2387
  implicitDeps,
1978
2388
  sourceSlug,
1979
2389
  nameSlug,
2390
+ summaryOf,
1980
2391
  docsLinks,
1981
2392
  registryEntry,
1982
2393
  HEADLESS,
@@ -2072,5 +2483,14 @@ export {
2072
2483
  conceptOfName,
2073
2484
  conceptOfPackage,
2074
2485
  declaredPackages,
2075
- buildGaps
2486
+ buildGaps,
2487
+ INGEST_CAP,
2488
+ IngestError,
2489
+ classifyLink,
2490
+ itemsOf,
2491
+ displayName,
2492
+ digestOf,
2493
+ fetchJson,
2494
+ fetchText,
2495
+ ingestLink
2076
2496
  };
@@ -4,10 +4,10 @@ import {
4
4
  loadPackage,
5
5
  readTryIndex,
6
6
  send
7
- } from "./chunk-DX2NKY2M.js";
7
+ } from "./chunk-CZJFEND6.js";
8
8
  import {
9
9
  writeFileAtomic
10
- } from "./chunk-NF4OJYKC.js";
10
+ } from "./chunk-S7UN5KQK.js";
11
11
 
12
12
  // ../../apps/web/scripts/discover-thumbs.ts
13
13
  import { existsSync, readFileSync } from "node:fs";