youiyoux 0.1.3 → 0.1.4

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 (211) hide show
  1. package/README.md +10 -0
  2. package/dist/chunks/{chunk-S7UV34H5.js → chunk-42YJX3W5.js} +1 -3
  3. package/dist/chunks/{chunk-74KBRI2H.js → chunk-4BBYLTZX.js} +10 -10
  4. package/dist/chunks/chunk-52KOKYY6.js +8710 -0
  5. package/dist/chunks/chunk-FJZFCAL5.js +129 -0
  6. package/dist/chunks/chunk-FVVL5JCY.js +24 -0
  7. package/dist/chunks/{chunk-UEB6MJDX.js → chunk-M5XZWCPS.js} +176 -61
  8. package/dist/chunks/{chunk-C3TSTYP7.js → chunk-NYDJYMID.js} +212 -26
  9. package/dist/chunks/{chunk-OIHNEGWH.js → chunk-RDTHHRZN.js} +96 -142
  10. package/dist/chunks/{chunk-E4HNK7VF.js → chunk-WNTS7ZO4.js} +1 -1
  11. package/dist/chunks/chunk-XAEMVEYX.js +188 -0
  12. package/dist/chunks/{chunk-RTOTNSOV.js → chunk-YFCMTTXS.js} +600 -506
  13. package/dist/chunks/chunk-YJ7WMBTT.js +13761 -0
  14. package/dist/chunks/src-5IP4NGKT.js +115 -0
  15. package/dist/chunks/{src-U5NTDPUX.js → src-6CJR547Y.js} +11 -35
  16. package/dist/chunks/{src-YGEZCUAO.js → src-BPETAFLW.js} +134 -77
  17. package/dist/chunks/{src-ZS76UD7E.js → src-LLV7IY6W.js} +27 -16
  18. package/dist/chunks/src-RHNZELGE.js +306 -0
  19. package/dist/chunks/{src-YUC5PIDK.js → src-SFKFERZ2.js} +14 -8
  20. package/dist/chunks/{src-DS43LCRA.js → src-VL2ZYO6X.js} +4 -4
  21. package/dist/hub/assets/Badge-CUJUit4d.js +1 -0
  22. package/dist/hub/assets/CandidatePanel-CnbK4S0j.js +1 -0
  23. package/dist/hub/assets/CatalogScreen-B5OVL-AR.js +1 -0
  24. package/dist/hub/assets/ChangesScreen-GwGqJwxN.js +1 -0
  25. package/dist/hub/assets/CheatSheet-BLSOq-xa.js +1 -0
  26. package/dist/hub/assets/CodeBlock-CsiGFgvI.js +1 -0
  27. package/dist/hub/assets/CommandPalette-CAWQfdXJ.js +1 -0
  28. package/dist/hub/assets/CopyButton-DE8drl4P.js +1 -0
  29. package/dist/hub/assets/Disclosure-B1kTgPgk.js +1 -0
  30. package/dist/hub/assets/DiscoverScreen-Tl_Z6mgY.js +2 -0
  31. package/dist/hub/assets/DraftEditor-yIDapXTs.js +5 -0
  32. package/dist/hub/assets/Field-LwrJXoOs.js +1 -0
  33. package/dist/hub/assets/GapsScreen-DXZAlm6v.js +1 -0
  34. package/dist/hub/assets/HomeScreen-CEpiVhOa.js +1 -0
  35. package/dist/hub/assets/InventoryScreen-CEG3Pmw_.js +1 -0
  36. package/dist/hub/assets/KindIcon-BJ6bz-ny.js +1 -0
  37. package/dist/hub/assets/LibraryScreen-IaVnxrX9.js +1 -0
  38. package/dist/hub/assets/LicenceBadge-CLCUMMHE.js +1 -0
  39. package/dist/hub/assets/Opened-DJyf4Hnj.js +1 -0
  40. package/dist/hub/assets/Preview-C069zVMP.js +1 -0
  41. package/dist/hub/assets/RuleDetail-B4g2-TRe.js +3 -0
  42. package/dist/hub/assets/RulesScreen-CAaXIWjO.js +3 -0
  43. package/dist/hub/assets/ScreenImage-DcI7_G3O.js +1 -0
  44. package/dist/hub/assets/ScreensScreen-DwczCjZD.js +1 -0
  45. package/dist/hub/assets/SearchField-DaCpNAj6.js +1 -0
  46. package/dist/hub/assets/Section-BTnvUq_W.js +1 -0
  47. package/dist/hub/assets/Segmented-Dzi1x49S.js +1 -0
  48. package/dist/hub/assets/ServiceDown-Bv-BPXro.js +1 -0
  49. package/dist/hub/assets/SpecimenCapture-DlsjshVE.js +1 -0
  50. package/dist/hub/assets/Specimens-C-lHp7sr.js +2 -0
  51. package/dist/hub/assets/StackScreen-DkmELsqR.js +1 -0
  52. package/dist/hub/assets/StubScreens-Bt7lWLVK.js +1 -0
  53. package/dist/hub/assets/StyleScreen-tq1tbRBo.js +1 -0
  54. package/dist/hub/assets/Swatch-KDz4qxdM.js +1 -0
  55. package/dist/hub/assets/Tabs-1QQDS0I6.js +1 -0
  56. package/dist/hub/assets/TokenDetail-DvCw6GqC.js +1 -0
  57. package/dist/hub/assets/TokenValue-CsPRc2ai.js +1 -0
  58. package/dist/hub/assets/TokensScreen-DoENV8Xy.js +1 -0
  59. package/dist/hub/assets/arrow-right-DcbNDzqs.js +1 -0
  60. package/dist/hub/assets/bot-B3gC9oUR.js +1 -0
  61. package/dist/hub/assets/button-6mvMVtpb.js +1 -0
  62. package/dist/hub/assets/button.demo-DuU0DQbv.js +1 -0
  63. package/dist/hub/assets/card.demo-CufAxMAA.js +1 -0
  64. package/dist/hub/assets/chevron-down-CzN07vMi.js +1 -0
  65. package/dist/hub/assets/circle-alert-DT2LC4yf.js +1 -0
  66. package/dist/hub/assets/circle-check-DkwBMT62.js +1 -0
  67. package/dist/hub/assets/client-D7whEyep.js +1 -0
  68. package/dist/hub/assets/clsx-DB0hHKMi.js +1 -0
  69. package/dist/hub/assets/compass-BkKOZPYe.js +1 -0
  70. package/dist/hub/assets/contrast-BkNHUAPa.js +1 -0
  71. package/dist/hub/assets/createLucideIcon-CGDJWtKO.js +1 -0
  72. package/dist/hub/assets/discover-XjJEViwk.js +1 -0
  73. package/dist/hub/assets/dist-BYQYaPBV.js +1 -0
  74. package/dist/hub/assets/dist-Bk4srtDn.js +41 -0
  75. package/dist/hub/assets/dist-BogNzeK9.js +1 -0
  76. package/dist/hub/assets/dist-D0z7Bvsy.js +1 -0
  77. package/dist/hub/assets/download-BQI-Si9R.js +1 -0
  78. package/dist/hub/assets/engine-CMmt9yEz.js +1 -0
  79. package/dist/hub/assets/esm-NPpg23M8.js +1 -0
  80. package/dist/hub/assets/family-tiles-CyTdy_cP.js +1 -0
  81. package/dist/hub/assets/format-DxAYNTN_.js +1 -0
  82. package/dist/hub/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  83. package/dist/hub/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  84. package/dist/hub/assets/geist-latin-core-vkBVTr4s.woff2 +0 -0
  85. package/dist/hub/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  86. package/dist/hub/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  87. package/dist/hub/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  88. package/dist/hub/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  89. package/dist/hub/assets/geist-mono-latin-core-DccGGzmg.woff2 +0 -0
  90. package/dist/hub/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  91. package/dist/hub/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  92. package/dist/hub/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  93. package/dist/hub/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  94. package/dist/hub/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  95. package/dist/hub/assets/git-pull-request-arrow-RqUUIRCU.js +1 -0
  96. package/dist/hub/assets/input.demo-238gqWpz.js +1 -0
  97. package/dist/hub/assets/inventory-BI8QBdar.js +1 -0
  98. package/dist/hub/assets/inventory-source-lZYzcA8O.js +1 -0
  99. package/dist/hub/assets/jsx-runtime-D6zNwnaE.js +9 -0
  100. package/dist/hub/assets/layers-DfzdK_wG.js +1 -0
  101. package/dist/hub/assets/layout-grid-xFY9L9Oc.js +1 -0
  102. package/dist/hub/assets/library-W5R8GwvG.js +1 -0
  103. package/dist/hub/assets/main-7snyIdoi.css +2 -0
  104. package/dist/hub/assets/main-B9CIkk5m.js +2 -0
  105. package/dist/hub/assets/minus-BDifFhOo.js +1 -0
  106. package/dist/hub/assets/monitor-X_lELkcU.js +1 -0
  107. package/dist/hub/assets/mouse-pointer-click-CVJ6hWg8.js +1 -0
  108. package/dist/hub/assets/paintbrush-DOkEgSTO.js +1 -0
  109. package/dist/hub/assets/plural-BbGQPQKA.js +1 -0
  110. package/dist/hub/assets/preview-BA8CPsKb.css +2 -0
  111. package/dist/hub/assets/preview-DUm13vr1.js +3 -0
  112. package/dist/hub/assets/protocol-caP6umNL.js +1 -0
  113. package/dist/hub/assets/react-DB-4Zxce.js +1 -0
  114. package/dist/hub/assets/shield-check-0ka0kQct.js +1 -0
  115. package/dist/hub/assets/trash-B7aEfJps.js +1 -0
  116. package/dist/hub/assets/triangle-alert-Dkdi9eDR.js +1 -0
  117. package/dist/hub/assets/try-live-zX3CSd3P.js +1 -0
  118. package/dist/hub/assets/utils-BoaXfs8y.js +1 -0
  119. package/dist/hub/assets/values-DzxR_TlI.js +1 -0
  120. package/dist/hub/assets/worker-Bh-wSp42.js +1 -0
  121. package/dist/hub/assets/workflow-jpV8wh-U.js +1 -0
  122. package/dist/hub/assets/x-MLbFVa4v.js +1 -0
  123. package/dist/hub/favicon.svg +1 -0
  124. package/dist/hub/index.html +35 -0
  125. package/dist/hub/kit/candidates.json +1 -0
  126. package/dist/hub/kit/chunks/chunk-2NYFUOIV.js +14 -0
  127. package/dist/hub/kit/chunks/chunk-DCANE7XH.js +1 -0
  128. package/dist/hub/kit/chunks/chunk-FTGHCY4I.js +14 -0
  129. package/dist/hub/kit/chunks/chunk-IBF3UTJF.js +33 -0
  130. package/dist/hub/kit/chunks/chunk-LLXY4VSH.js +1 -0
  131. package/dist/hub/kit/chunks/chunk-PUPZMW4P.js +14 -0
  132. package/dist/hub/kit/chunks/chunk-PWLNZQET.js +1 -0
  133. package/dist/hub/kit/chunks/chunk-ULXWVOKJ.js +1 -0
  134. package/dist/hub/kit/chunks/chunk-WM4BGGMK.js +1 -0
  135. package/dist/hub/kit/chunks/chunk-ZQUNRK72.js +1 -0
  136. package/dist/hub/kit/class-variance-authority.js +1 -0
  137. package/dist/hub/kit/clsx.js +1 -0
  138. package/dist/hub/kit/fonts/geist-cyrillic-ext-wght-normal.woff2 +0 -0
  139. package/dist/hub/kit/fonts/geist-cyrillic-wght-normal.woff2 +0 -0
  140. package/dist/hub/kit/fonts/geist-latin-ext-wght-normal.woff2 +0 -0
  141. package/dist/hub/kit/fonts/geist-latin-wght-normal.woff2 +0 -0
  142. package/dist/hub/kit/fonts/geist-vietnamese-wght-normal.woff2 +0 -0
  143. package/dist/hub/kit/frame.css.src +73 -0
  144. package/dist/hub/kit/frame.js +2 -0
  145. package/dist/hub/kit/lucide-react.js +1878 -0
  146. package/dist/hub/kit/preview.html +13 -0
  147. package/dist/hub/kit/radix-ui-react-dialog.js +41 -0
  148. package/dist/hub/kit/radix-ui-react-label.js +1 -0
  149. package/dist/hub/kit/radix-ui-react-separator.js +1 -0
  150. package/dist/hub/kit/radix-ui-react-slot.js +1 -0
  151. package/dist/hub/kit/radix-ui-react-switch.js +1 -0
  152. package/dist/hub/kit/radix-ui-react-tabs.js +1 -0
  153. package/dist/hub/kit/react-dom-client.js +1 -0
  154. package/dist/hub/kit/react-dom.js +1 -0
  155. package/dist/hub/kit/react-jsx-runtime.js +1 -0
  156. package/dist/hub/kit/react.js +1 -0
  157. package/dist/hub/kit/tailwind-merge.js +1 -0
  158. package/dist/hub/kit/tailwindcss/index.css +950 -0
  159. package/dist/hub/kit/tailwindcss/preflight.css +398 -0
  160. package/dist/hub/kit/tailwindcss/theme.css +510 -0
  161. package/dist/hub/kit/tailwindcss/utilities.css +1 -0
  162. package/dist/hub-demo/frame/button.demo.js +1 -0
  163. package/dist/hub-demo/frame/card.demo.js +1 -0
  164. package/dist/hub-demo/frame/demos.js +6 -0
  165. package/dist/hub-demo/frame/frame.css +770 -0
  166. package/dist/hub-demo/frame/input.demo.js +1 -0
  167. package/dist/hub-demo/preview/components/button.demo.tsx +47 -0
  168. package/dist/hub-demo/preview/components/button.tsx +31 -0
  169. package/dist/hub-demo/preview/components/card.demo.tsx +26 -0
  170. package/dist/hub-demo/preview/components/card.tsx +20 -0
  171. package/dist/hub-demo/preview/components/input.demo.tsx +39 -0
  172. package/dist/hub-demo/preview/components/input.tsx +19 -0
  173. package/dist/hub-demo/preview/components/utils.ts +5 -0
  174. package/dist/hub-demo/preview/system.css +161 -0
  175. package/dist/hub-demo/public/data/demo-app.usage.bedd9c82fe.json +1 -0
  176. package/dist/hub-demo/public/data/demo.catalog.a605de2281.json +1 -0
  177. package/dist/hub-demo/public/data/gaps.129a93538d.json +1 -0
  178. package/dist/hub-demo/public/data/inventory.165a4f6408.json +1 -0
  179. package/dist/hub-demo/public/data/inventory.console.badge.ad0238784e.json +1 -0
  180. package/dist/hub-demo/public/data/inventory.console.button.3249daff35.json +1 -0
  181. package/dist/hub-demo/public/data/inventory.console.card.9ea88708ac.json +1 -0
  182. package/dist/hub-demo/public/data/inventory.console.dialog.8b6574d93a.json +1 -0
  183. package/dist/hub-demo/public/data/inventory.console.input.bdffb756a7.json +1 -0
  184. package/dist/hub-demo/public/data/inventory.console.link-as-button.1ce7faa1f3.json +1 -0
  185. package/dist/hub-demo/public/data/inventory.console.tabs.813e694c44.json +1 -0
  186. package/dist/hub-demo/public/data/inventory.extras.e5d06c6ff4.json +1 -0
  187. package/dist/hub-demo/public/data/inventory.site.badge.047a68f644.json +1 -0
  188. package/dist/hub-demo/public/data/inventory.site.button.720938384e.json +1 -0
  189. package/dist/hub-demo/public/data/inventory.site.card.d8df5f6873.json +1 -0
  190. package/dist/hub-demo/public/data/inventory.site.link-as-button.9c1f8a55bf.json +1 -0
  191. package/dist/hub-demo/public/data/inventory.site.tabs.59bd0200b9.json +1 -0
  192. package/dist/hub-demo/public/data/manifest.json +39 -0
  193. package/dist/hub-demo/public/data/specimens.console.dark.046471907d.css +786 -0
  194. package/dist/hub-demo/public/data/specimens.console.light.aaffa2d213.css +786 -0
  195. package/dist/hub-demo/public/data/specimens.site.dark.94a6706908.css +92 -0
  196. package/dist/hub-demo/public/data/specimens.site.light.3ddbc8d28d.css +92 -0
  197. package/dist/hub-demo/public/previews/button.dark.webp +0 -0
  198. package/dist/hub-demo/public/previews/button.light.webp +0 -0
  199. package/dist/hub-demo/public/previews/card.dark.webp +0 -0
  200. package/dist/hub-demo/public/previews/card.light.webp +0 -0
  201. package/dist/hub-demo/public/previews/input.dark.webp +0 -0
  202. package/dist/hub-demo/public/previews/input.light.webp +0 -0
  203. package/dist/hub-previews.js +173 -0
  204. package/dist/hub-server.js +6329 -0
  205. package/dist/hub-snapshot.js +573 -0
  206. package/dist/schema/youi.gaps.schema.json +235 -0
  207. package/dist/tailwind-worker.js +3 -3
  208. package/dist/youi.js +10 -8
  209. package/dist/youiyoux.js +10 -8
  210. package/package.json +4 -4
  211. package/dist/chunks/chunk-L5BV4PI7.js +0 -22159
@@ -0,0 +1,129 @@
1
+ import { createRequire as __youiRequire } from "node:module"; import { dirname as __youiDir } from "node:path"; import { fileURLToPath as __youiPath } from "node:url"; const require = __youiRequire(import.meta.url); const __youiDirname = __youiDir(__youiPath(import.meta.url));
2
+ import {
3
+ isLocalHost,
4
+ loadPackage,
5
+ readTryIndex,
6
+ send
7
+ } from "./chunk-XAEMVEYX.js";
8
+ import {
9
+ writeFileAtomic
10
+ } from "./chunk-NYDJYMID.js";
11
+
12
+ // ../../apps/web/scripts/discover-thumbs.ts
13
+ import { existsSync, readFileSync } from "node:fs";
14
+ import { readdir, rm } from "node:fs/promises";
15
+ import { join, resolve } from "node:path";
16
+ var THUMBS_PATH = "/__youi/discover-thumbs";
17
+ var THUMBS_DIR = "thumbs";
18
+ var THUMBS_INDEX = "index.json";
19
+ var THUMBS_SCHEMA = "youiyoux.discover-thumbs/v1";
20
+ var THUMB_SCHEMES = ["light", "dark"];
21
+ var PROJECT = /^[a-z0-9][a-z0-9._-]{0,80}$/i;
22
+ var PICTURE = /^([0-9a-f]{16})\.(light|dark)\.webp$/;
23
+ var safeProject = (project) => PROJECT.test(project) && project !== "." && project !== ".." ? project : null;
24
+ function thumbsDir(cache, project) {
25
+ const safe = safeProject(project);
26
+ if (!safe) throw new Error(`refused: "${project.slice(0, 60)}" is not a project id`);
27
+ return join(resolve(cache), THUMBS_DIR, safe);
28
+ }
29
+ var emptyIndex = (project) => ({ schema: THUMBS_SCHEMA, project, items: {}, failed: {} });
30
+ function readThumbIndex(cache, project) {
31
+ const file = join(thumbsDir(cache, project), THUMBS_INDEX);
32
+ if (!existsSync(file)) return emptyIndex(project);
33
+ try {
34
+ const value = JSON.parse(readFileSync(file, "utf8"));
35
+ if (value.schema !== THUMBS_SCHEMA) return emptyIndex(project);
36
+ return { schema: THUMBS_SCHEMA, project, items: { ...value.items }, failed: { ...value.failed } };
37
+ } catch {
38
+ return emptyIndex(project);
39
+ }
40
+ }
41
+ async function writeThumbIndex(cache, index) {
42
+ await writeFileAtomic(join(thumbsDir(cache, index.project), THUMBS_INDEX), `${JSON.stringify(index, null, 1)}
43
+ `);
44
+ }
45
+ function needsCapture(index, id, sha, force = false) {
46
+ return force || index.items[id]?.sha !== sha && index.failed[id]?.sha !== sha;
47
+ }
48
+ function currentThumbs(index, current, url) {
49
+ const items = {};
50
+ for (const [id, thumb] of Object.entries(index.items)) {
51
+ if (current.get(id) !== thumb.sha || !/^[0-9a-f]{16}$/.test(thumb.key)) continue;
52
+ items[id] = { light: url(`${thumb.key}.light.webp`), dark: url(`${thumb.key}.dark.webp`), width: thumb.width, height: thumb.height, capturedAt: thumb.capturedAt };
53
+ }
54
+ const failed = {};
55
+ for (const [id, failure] of Object.entries(index.failed)) if (current.get(id) === failure.sha && !items[id]) failed[id] = failure.reason;
56
+ return { project: index.project, items, failed };
57
+ }
58
+ async function prunePictures(cache, index) {
59
+ const dir = thumbsDir(cache, index.project);
60
+ const keep = new Set(Object.values(index.items).map((t) => t.key));
61
+ let removed = 0;
62
+ for (const name of await readdir(dir).catch(() => [])) {
63
+ const match = PICTURE.exec(name);
64
+ if (!match || keep.has(match[1])) continue;
65
+ await rm(join(dir, name), { force: true });
66
+ removed++;
67
+ }
68
+ return removed;
69
+ }
70
+ function selectCandidates(entries, selection) {
71
+ const chosen = entries.filter((e) => {
72
+ if (e.source.kind !== "shadcn-registry" || !e.source.ref || typeof e.provenance?.sha256 !== "string") return false;
73
+ if (selection.registry && !e.source.ref.startsWith(`${selection.registry}/`)) return false;
74
+ return !selection.role || (e.categories ?? []).includes(selection.role) || e.source.ref.endsWith(`/${selection.role}`);
75
+ });
76
+ return selection.limit === null ? chosen : chosen.slice(0, selection.limit);
77
+ }
78
+ function listingDigests(cache) {
79
+ const digests = /* @__PURE__ */ new Map();
80
+ for (const name of ["index.json", "index-all.json"]) {
81
+ const file = join(cache, name);
82
+ if (!existsSync(file)) continue;
83
+ try {
84
+ const value = JSON.parse(readFileSync(file, "utf8"));
85
+ for (const entry of value.entries ?? []) if (typeof entry.id === "string" && typeof entry.provenance?.sha256 === "string") digests.set(entry.id, entry.provenance.sha256);
86
+ } catch {
87
+ }
88
+ }
89
+ return digests;
90
+ }
91
+ function discoverThumbsMiddleware(snapshot, base = "/", options = {}, cacheDir = async () => (await loadPackage("discover")).discoverCacheDir(void 0, options.env ?? process.env)) {
92
+ const projects = () => Object.keys(readTryIndex(snapshot)?.projects ?? {});
93
+ return async (req, res, next) => {
94
+ const url = new URL((req.url ?? "/").replace(base, "/"), "http://hub.local");
95
+ if (!url.pathname.startsWith(THUMBS_PATH) || req.method !== "GET") return next();
96
+ if (!isLocalHost(req.headers.host)) return send(res, 403, { error: "Thumbnails answer this machine only (Host must be localhost)." });
97
+ const cache = resolve(options.cache ?? await cacheDir());
98
+ const picture = /^\/__youi\/discover-thumbs\/([^/]+)\/([^/]+)$/.exec(url.pathname);
99
+ if (picture) {
100
+ const project2 = safeProject(decodeURIComponent(picture[1]));
101
+ if (!project2 || !PICTURE.test(picture[2])) return send(res, 400, { error: "not a thumbnail" });
102
+ const file = join(thumbsDir(cache, project2), picture[2]);
103
+ if (!existsSync(file)) return send(res, 404, { error: "no such thumbnail" });
104
+ res.setHeader("Content-Type", "image/webp");
105
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
106
+ res.end(readFileSync(file));
107
+ return;
108
+ }
109
+ if (url.pathname !== THUMBS_PATH) return send(res, 404, { error: "not found" });
110
+ const known = projects();
111
+ const wanted = url.searchParams.get("project");
112
+ const project = wanted ? known.includes(wanted) ? wanted : null : known[0] ?? null;
113
+ if (!project || !safeProject(project)) return send(res, 404, { error: wanted ? `The hub has no project "${wanted.slice(0, 60)}".` : "The hub has no project: open it with youiyoux ui --project." });
114
+ const index = readThumbIndex(cache, project);
115
+ send(res, 200, currentThumbs(index, listingDigests(cache), (file) => `${base}${THUMBS_PATH.slice(1)}/${encodeURIComponent(project)}/${file}`));
116
+ };
117
+ }
118
+
119
+ export {
120
+ THUMBS_PATH,
121
+ THUMB_SCHEMES,
122
+ thumbsDir,
123
+ readThumbIndex,
124
+ writeThumbIndex,
125
+ needsCapture,
126
+ prunePictures,
127
+ selectCandidates,
128
+ discoverThumbsMiddleware
129
+ };
@@ -0,0 +1,24 @@
1
+ import { createRequire as __youiRequire } from "node:module"; import { dirname as __youiDir } from "node:path"; import { fileURLToPath as __youiPath } from "node:url"; const require = __youiRequire(import.meta.url); const __youiDirname = __youiDir(__youiPath(import.meta.url));
2
+
3
+ // src/node20.js
4
+ if (!Map.groupBy) {
5
+ Map.groupBy = (items, key) => {
6
+ const groups = /* @__PURE__ */ new Map();
7
+ let i = 0;
8
+ for (const item of items) {
9
+ const k = key(item, i++);
10
+ const list = groups.get(k);
11
+ if (list) list.push(item);
12
+ else groups.set(k, [item]);
13
+ }
14
+ return groups;
15
+ };
16
+ }
17
+ if (!Object.groupBy) Object.groupBy = (items, key) => Object.fromEntries(Map.groupBy(items, key));
18
+ if (!Promise.withResolvers) {
19
+ Promise.withResolvers = () => {
20
+ let resolve, reject;
21
+ const promise = new Promise((yes, no) => (resolve = yes, reject = no));
22
+ return { promise, resolve, reject };
23
+ };
24
+ }
@@ -1,12 +1,14 @@
1
- import { createRequire as __youiRequire } from "node:module"; const require = __youiRequire(import.meta.url);
1
+ import { createRequire as __youiRequire } from "node:module"; import { dirname as __youiDir } from "node:path"; import { fileURLToPath as __youiPath } from "node:url"; const require = __youiRequire(import.meta.url); const __youiDirname = __youiDir(__youiPath(import.meta.url));
2
2
  import {
3
3
  LIBRARY_SOURCE_KINDS,
4
4
  LibraryEntry,
5
5
  SHAPE_DIMENSIONS,
6
+ copyleft,
6
7
  external_exports,
7
8
  sha256,
9
+ shortHash,
8
10
  writeFileAtomic
9
- } from "./chunk-C3TSTYP7.js";
11
+ } from "./chunk-NYDJYMID.js";
10
12
 
11
13
  // ../discover/src/aliases.ts
12
14
  var CONCEPTS = [
@@ -55,9 +57,6 @@ var CONCEPTS = [
55
57
  { id: "marquee", names: ["marquee", "ticker", "scrolling text"], packages: [] }
56
58
  ];
57
59
  var BY_ID = new Map(CONCEPTS.map((c) => [c.id, c]));
58
- function conceptById(id) {
59
- return BY_ID.get(id);
60
- }
61
60
  function normalize(text) {
62
61
  return text.normalize("NFC").replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
63
62
  }
@@ -215,7 +214,7 @@ function importedPackages(source) {
215
214
  const found = /* @__PURE__ */ new Set();
216
215
  for (const match of source.matchAll(IMPORT)) {
217
216
  const spec = match[1];
218
- if (/^(\.|\/|@\/|~\/|#|node:)/.test(spec) || spec.startsWith("@/")) continue;
217
+ if (/^(\.|\/|@\/|~\/|#|node:)/.test(spec)) continue;
219
218
  const parts = spec.split("/");
220
219
  const name = spec.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
221
220
  if (name && !AMBIENT.has(name)) found.add(name);
@@ -508,7 +507,7 @@ var json = (value) => JSON.stringify(value);
508
507
  var parse = (value) => typeof value === "string" ? JSON.parse(value) : value;
509
508
  var int = (value) => Number(value ?? 0);
510
509
  var marks = (n) => Array.from({ length: n }, () => "?").join(", ");
511
- var clampLimit = pageInteger;
510
+ var batches = (items) => Array.from({ length: Math.ceil(items.length / MAX_PARAMS) }, (_, i) => items.slice(i * MAX_PARAMS, (i + 1) * MAX_PARAMS));
512
511
  var SqlStore = class {
513
512
  db;
514
513
  lock = new Lock();
@@ -603,10 +602,7 @@ var SqlStore = class {
603
602
  const update = columns.slice(1).map((c) => `${c} = excluded.${c}`).join(", ");
604
603
  await this.insert("discover_items", columns, items.map((i) => [i.id, i.scope, i.ref, i.name, i.title, i.summary, i.kind, i.sourceKind, i.sourceClass, i.registry, i.licence, i.style, i.downloads, i.stars, i.archived ? 1 : 0, i.searchText, json(i.entry), i.builtAt]), `ON CONFLICT (id) DO UPDATE SET ${update}`);
605
604
  const ids = items.map((i) => i.id);
606
- for (let i = 0; i < ids.length; i += MAX_PARAMS) {
607
- const batch = ids.slice(i, i + MAX_PARAMS);
608
- await this.db.run(`DELETE FROM discover_roles WHERE item_id IN (${marks(batch.length)})`, batch);
609
- }
605
+ for (const batch of batches(ids)) await this.db.run(`DELETE FROM discover_roles WHERE item_id IN (${marks(batch.length)})`, batch);
610
606
  await this.insert("discover_roles", ["item_id", "role"], items.flatMap((i) => [...new Set(i.roles)].map((role) => [i.id, role])), "ON CONFLICT (item_id, role) DO NOTHING");
611
607
  }
612
608
  async replaceDiscoverItems(scope, items) {
@@ -659,15 +655,14 @@ var SqlStore = class {
659
655
  );
660
656
  const entries = /* @__PURE__ */ new Map();
661
657
  const ids = ranked.map((r) => r.id);
662
- for (let i = 0; i < ids.length; i += MAX_PARAMS) {
663
- const batch = ids.slice(i, i + MAX_PARAMS);
658
+ for (const batch of batches(ids)) {
664
659
  for (const row of await this.db.all(`SELECT id, entry FROM discover_items WHERE id IN (${marks(batch.length)})`, batch)) entries.set(row.id, row.entry);
665
660
  }
666
661
  return ranked.map((r) => ({ id: r.id, sourceClass: r.source_class, entry: parse(entries.get(r.id)) }));
667
662
  }
668
663
  async listDiscover(filter, page = {}) {
669
664
  const where = this.filterSql(filter);
670
- const limit = clampLimit(page.limit, 20);
665
+ const limit = pageInteger(page.limit, 20);
671
666
  const offset = pageInteger(page.offset, 0, 2147483647);
672
667
  const rows = await this.db.all(
673
668
  `SELECT d.id, d.source_class, d.entry FROM discover_items d WHERE ${where.sql} ORDER BY CASE d.source_class WHEN 'official' THEN 0 ELSE 1 END, COALESCE(d.downloads, -1) DESC, d.id LIMIT ? OFFSET ?`,
@@ -698,8 +693,7 @@ var SqlStore = class {
698
693
  }
699
694
  async discoverRoles(itemIds) {
700
695
  const out = {};
701
- for (let i = 0; i < itemIds.length; i += MAX_PARAMS) {
702
- const batch = itemIds.slice(i, i + MAX_PARAMS);
696
+ for (const batch of batches(itemIds)) {
703
697
  for (const row of await this.db.all(`SELECT item_id, role FROM discover_roles WHERE item_id IN (${marks(batch.length)}) ORDER BY item_id, role`, batch)) (out[row.item_id] ??= []).push(row.role);
704
698
  }
705
699
  return out;
@@ -745,7 +739,7 @@ var SqlStore = class {
745
739
  }
746
740
  async history(filter = {}) {
747
741
  const where = filter.project ? `WHERE project = ?` : "";
748
- const rows = await this.db.all(`SELECT * FROM scans ${where} ORDER BY scanned_at DESC, project LIMIT ? OFFSET ?`, [...filter.project ? [filter.project] : [], clampLimit(filter.limit, 20), pageInteger(filter.offset, 0, 2147483647)]);
742
+ const rows = await this.db.all(`SELECT * FROM scans ${where} ORDER BY scanned_at DESC, project LIMIT ? OFFSET ?`, [...filter.project ? [filter.project] : [], pageInteger(filter.limit, 20), pageInteger(filter.offset, 0, 2147483647)]);
749
743
  return rows.map((r) => ({ id: String(r.id), run: String(r.run), source: String(r.source), project: String(r.project), root: String(r.root), commit: r.commit_sha ?? null, scannedAt: String(r.scanned_at), status: String(r.status), files: int(r.files), looks: int(r.looks), families: int(r.families), nearDuplicates: int(r.near_duplicates), roles: parse(r.roles) }));
750
744
  }
751
745
  async latestRun(source) {
@@ -758,7 +752,7 @@ var SqlStore = class {
758
752
  const where = `run = ?${filter.role ? " AND role = ?" : ""}`;
759
753
  const params = [run, ...filter.role ? [filter.role] : []];
760
754
  const total = int((await this.db.all(`SELECT CAST(COUNT(*) AS INTEGER) AS n FROM families WHERE ${where}`, params))[0]?.n);
761
- const rows = await this.db.all(`SELECT * FROM families WHERE ${where} ORDER BY role, looks DESC, id LIMIT ? OFFSET ?`, [...params, clampLimit(filter.limit, 50), pageInteger(filter.offset, 0, 2147483647)]);
755
+ const rows = await this.db.all(`SELECT * FROM families WHERE ${where} ORDER BY role, looks DESC, id LIMIT ? OFFSET ?`, [...params, pageInteger(filter.limit, 50), pageInteger(filter.offset, 0, 2147483647)]);
762
756
  return { run, total, rows: rows.map((r) => ({ id: String(r.id), role: String(r.role), looks: int(r.looks), nearDuplicate: int(r.near_duplicate) === 1, projects: parse(r.projects), kits: parse(r.kits), shape: parse(r.shape) })) };
763
757
  }
764
758
  async looks(filter) {
@@ -767,7 +761,7 @@ var SqlStore = class {
767
761
  const where = `run = ?${filter.family ? " AND family = ?" : ""}${filter.role ? " AND role = ?" : ""}`;
768
762
  const params = [run, ...filter.family ? [filter.family] : [], ...filter.role ? [filter.role] : []];
769
763
  const total = int((await this.db.all(`SELECT CAST(COUNT(*) AS INTEGER) AS n FROM looks WHERE ${where}`, params))[0]?.n);
770
- const rows = await this.db.all(`SELECT * FROM looks WHERE ${where} ORDER BY project, file, line, col, id LIMIT ? OFFSET ?`, [...params, clampLimit(filter.limit, 50), pageInteger(filter.offset, 0, 2147483647)]);
764
+ const rows = await this.db.all(`SELECT * FROM looks WHERE ${where} ORDER BY project, file, line, col, id LIMIT ? OFFSET ?`, [...params, pageInteger(filter.limit, 50), pageInteger(filter.offset, 0, 2147483647)]);
771
765
  return { run, total, rows: rows.map((r) => ({ id: String(r.id), project: String(r.project), role: String(r.role), file: String(r.file), line: int(r.line), column: int(r.col), tag: String(r.tag), kit: r.kit ?? null, family: r.family ?? null, routes: parse(r.routes), shape: parse(r.shape) })) };
772
766
  }
773
767
  /* --------------------------------------------------------------------- graph */
@@ -1108,10 +1102,8 @@ async function openStore(options = {}) {
1108
1102
  }
1109
1103
 
1110
1104
  // ../store/src/derive.ts
1111
- import { createHash } from "node:crypto";
1112
- var short = (text) => createHash("sha256").update(text).digest("hex").slice(0, 12);
1113
1105
  var nodeId = {
1114
- family: (inventory, id, project = "") => `family:${short(inventory)}/${encodeURIComponent(project)}/${id}`,
1106
+ family: (inventory, id, project = "") => `family:${shortHash(inventory)}/${encodeURIComponent(project)}/${id}`,
1115
1107
  file: (project, file) => `file:${project}:${file}`,
1116
1108
  route: (project, route) => `route:${project}:${route}`,
1117
1109
  project: (id) => `project:${id}`,
@@ -1158,7 +1150,7 @@ function nearDuplicatePairs(families, tolerance) {
1158
1150
  return pairs;
1159
1151
  }
1160
1152
  function inventoryRun(report, source) {
1161
- const run = `${report.generatedAt}#${short(`${source}\0${report.generatedAt}`).slice(0, 8)}`;
1153
+ const run = `${report.generatedAt}#${shortHash(`${source}\0${report.generatedAt}`).slice(0, 8)}`;
1162
1154
  const g = new GraphBuilder();
1163
1155
  const familyNode = (id, project) => g.node({ id: nodeId.family(source, id, project), kind: "family", ref: id, project });
1164
1156
  for (const family of report.families) for (const project of family.projects) familyNode(family.id, project);
@@ -1266,7 +1258,6 @@ var DiscoverIndex = external_exports.object({
1266
1258
  categories: external_exports.record(external_exports.string(), external_exports.array(external_exports.string())),
1267
1259
  entries: external_exports.array(LibraryEntry)
1268
1260
  });
1269
- var EMPTY_SIGNALS = { npm: {}, github: {}, registryRepos: {} };
1270
1261
  async function readJson(file) {
1271
1262
  try {
1272
1263
  return JSON.parse(await readFile(file, "utf8"));
@@ -1373,7 +1364,7 @@ function slimResponse(url, body) {
1373
1364
  }))
1374
1365
  })) };
1375
1366
  }
1376
- async function writeHttpCache(cacheDir, cache, _previous) {
1367
+ async function writeHttpCache(cacheDir, cache) {
1377
1368
  const keep = /* @__PURE__ */ new Set();
1378
1369
  for (const [url, entry] of cache) {
1379
1370
  let data;
@@ -1511,7 +1502,7 @@ var PoliteClient = class {
1511
1502
  this.stats.cached++;
1512
1503
  return { ok: true, url, status: 200, body: cached.body, sha256: cached.sha256, fetchedAt: cached.fetchedAt, revalidated: true, headers: new Headers() };
1513
1504
  }
1514
- const bucket = npm ? "npm" : this.options.bucketFor?.(parsed) ?? githubBucketFor(parsed);
1505
+ const bucket = npm ? "npm" : githubBucketFor(parsed);
1515
1506
  let host = this.hosts.get(parsed.host);
1516
1507
  if (!host) this.hosts.set(parsed.host, host = new Gate(this.options.perHost ?? 2));
1517
1508
  const gate = host;
@@ -1720,6 +1711,9 @@ function playsRole(role, f) {
1720
1711
  const n = normalize(role);
1721
1712
  return hasPhrase(f.name, n) || f.categories.some((c) => hasPhrase(c, n));
1722
1713
  }
1714
+ function licenceOf(entry) {
1715
+ return entry.license && entry.license !== "unknown" ? entry.license.spdx : "unknown";
1716
+ }
1723
1717
  function hitOf(entry, score, why) {
1724
1718
  return {
1725
1719
  id: entry.id,
@@ -1728,7 +1722,7 @@ function hitOf(entry, score, why) {
1728
1722
  kind: entry.kind,
1729
1723
  summary: entry.summary,
1730
1724
  source: { kind: entry.source.kind ?? null, name: entry.source.name, ref: entry.source.ref ?? null, url: entry.source.url ?? null },
1731
- license: entry.license && entry.license !== "unknown" ? entry.license.spdx : "unknown",
1725
+ license: licenceOf(entry),
1732
1726
  signals: { ...entry.signals?.via ? { via: entry.signals.via } : {}, weeklyDownloads: entry.signals?.weeklyDownloads ?? null, stars: entry.signals?.stars ?? null, pushedAt: entry.signals?.pushedAt ?? null, archived: entry.signals?.archived ?? null },
1733
1727
  install: entry.install ? { command: entry.install.command, npm: entry.install.npm, registryDeps: entry.install.registryDeps, implicitDeps: entry.install.implicitDeps } : null,
1734
1728
  score: Math.round(score * 100) / 100,
@@ -1810,32 +1804,25 @@ function registryOf(entry) {
1810
1804
  return entry.source.ref.slice(0, entry.source.ref.indexOf("/"));
1811
1805
  }
1812
1806
  var fresh = (at, ttl, now) => Boolean(at) && now.getTime() - Date.parse(at) < ttl;
1807
+ function homeParts(value) {
1808
+ try {
1809
+ const url = new URL(value.includes("://") ? value : `https://${value}`);
1810
+ return { host: url.hostname.replace(/^www\./, "").toLowerCase(), path: url.pathname.replace(/\/+$/, "").toLowerCase() };
1811
+ } catch {
1812
+ return null;
1813
+ }
1814
+ }
1813
1815
  function sameHomepage(a, b) {
1814
- const key = (value) => {
1815
- try {
1816
- const url = new URL(value.includes("://") ? value : `https://${value}`);
1817
- return `${url.hostname.replace(/^www\./, "")}${url.pathname.replace(/\/+$/, "")}`.toLowerCase();
1818
- } catch {
1819
- return null;
1820
- }
1821
- };
1822
1816
  if (!a || !b) return false;
1823
- const ka = key(a);
1824
- return ka !== null && ka === key(b);
1817
+ const pa = homeParts(a);
1818
+ const pb = homeParts(b);
1819
+ return pa !== null && pb !== null && `${pa.host}${pa.path}` === `${pb.host}${pb.path}`;
1825
1820
  }
1826
1821
  var PATH_TENANTS = /* @__PURE__ */ new Set(["github.com", "gitlab.com", "bitbucket.org", "codeberg.org", "npmjs.com", "codesandbox.io", "stackblitz.com", "gitbook.io", "medium.com", "notion.site"]);
1827
1822
  function sameSite(repository, registry) {
1828
- const parts = (value) => {
1829
- try {
1830
- const url = new URL(value.includes("://") ? value : `https://${value}`);
1831
- return { host: url.hostname.replace(/^www\./, "").toLowerCase(), path: url.pathname.replace(/\/+$/, "").toLowerCase() };
1832
- } catch {
1833
- return null;
1834
- }
1835
- };
1836
1823
  if (!repository || !registry) return false;
1837
- const repo = parts(repository);
1838
- const home = parts(registry);
1824
+ const repo = homeParts(repository);
1825
+ const home = homeParts(registry);
1839
1826
  if (!repo || !home || repo.host !== home.host) return false;
1840
1827
  if (PATH_TENANTS.has(repo.host)) return repo.path === home.path && repo.path !== "";
1841
1828
  return repo.path === "" || home.path === repo.path || home.path.startsWith(`${repo.path}/`);
@@ -2082,7 +2069,6 @@ async function syncIndex(options) {
2082
2069
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
2083
2070
  const builtAt = now().toISOString();
2084
2071
  const cache = await readHttpCache(options.cacheDir);
2085
- const previousBodies = new Map([...cache].map(([url, c]) => [url, c.sha256]));
2086
2072
  const client = new PoliteClient({
2087
2073
  ...options.fetcher ? { fetcher: options.fetcher } : {},
2088
2074
  ...options.token ? { token: options.token } : {},
@@ -2090,7 +2076,6 @@ async function syncIndex(options) {
2090
2076
  perHost: options.perHost ?? 2,
2091
2077
  timeoutMs: options.timeoutMs ?? 15e3,
2092
2078
  buckets: githubBuckets(options.token),
2093
- bucketFor: githubBucketFor,
2094
2079
  cache,
2095
2080
  now,
2096
2081
  ...options.sleep ? { sleep: options.sleep } : {}
@@ -2116,7 +2101,7 @@ async function syncIndex(options) {
2116
2101
  failure = "could not be parsed";
2117
2102
  }
2118
2103
  }
2119
- sources.push(failure === null && result.ok ? { id: `headless:${catalog.id}`, url: catalog.url, outcome: result.revalidated ? "revalidated" : "fetched", reason: null, httpStatus: result.status, items: names.length, health: null } : { id: `headless:${catalog.id}`, url: catalog.url, outcome: "failed", reason: failure, httpStatus: result.ok ? result.status : result.status, items: 0, health: null });
2104
+ sources.push(failure === null && result.ok ? { id: `headless:${catalog.id}`, url: catalog.url, outcome: result.revalidated ? "revalidated" : "fetched", reason: null, httpStatus: result.status, items: names.length, health: null } : { id: `headless:${catalog.id}`, url: catalog.url, outcome: "failed", reason: failure, httpStatus: result.status, items: 0, health: null });
2120
2105
  return { id: catalog.id, names };
2121
2106
  })
2122
2107
  );
@@ -2201,7 +2186,7 @@ async function syncIndex(options) {
2201
2186
  const index = cleanData({ schema: "youi.discover-index/v1", format: 2, builtAt, sources, categories: Object.fromEntries(categories), entries: final });
2202
2187
  await writeJson(join3(options.cacheDir, SIGNALS_FILE), signals);
2203
2188
  await writeJson(join3(options.cacheDir, HEALTH_FILE), cleanData({ checkedAt: builtAt, sources }));
2204
- await writeHttpCache(options.cacheDir, client.cache, previousBodies);
2189
+ await writeHttpCache(options.cacheDir, client.cache);
2205
2190
  await writeJson(join3(options.cacheDir, INDEX_FILE2), index);
2206
2191
  const registryStatuses = sources.filter((s) => s.health !== null);
2207
2192
  const known = final.filter((e) => e.license && e.license !== "unknown").length;
@@ -2260,7 +2245,7 @@ async function expandAll(cacheDir, options = {}) {
2260
2245
  const entries2 = parseRegistryItems(response.value).items.filter(isCandidate).map((item) => registryEntry({ name: r.name, homepage: r.homepage, url, fetchedAt: response.result.fetchedAt, official: false, health: r.health }, item, categories));
2261
2246
  return !options.icons && entries2.length && entries2.filter(isIconEntry).length / entries2.length >= 0.8 ? [] : entries2.map((entry) => applySignals(entry, signals));
2262
2247
  }));
2263
- await writeHttpCache(cacheDir, cache, /* @__PURE__ */ new Map());
2248
+ await writeHttpCache(cacheDir, cache);
2264
2249
  const chosen = new Set(index.entries.map((e) => e.id));
2265
2250
  const entries = dedupe([...index.entries, ...extra.flat()]).entries.filter((e) => !chosen.has(e.id));
2266
2251
  const next = `${JSON.stringify(cleanData({ schema: index.schema, format: 2, builtAt: index.builtAt, sources: [], categories: index.categories, entries }))}
@@ -2322,9 +2307,6 @@ function rolesOf(entry) {
2322
2307
  const f = fieldsOf(entry);
2323
2308
  return CONCEPTS.filter((concept) => conceptScore(concept, f, []) > 0).map((c) => c.id);
2324
2309
  }
2325
- function licenceOf(entry) {
2326
- return entry.license && entry.license !== "unknown" ? entry.license.spdx : "unknown";
2327
- }
2328
2310
  function searchTextOf(entry) {
2329
2311
  const f = fieldsOf(entry);
2330
2312
  return [f.name, f.name, f.name, f.title, f.title, f.description, ...f.categories, ...f.packages.map(normalize)].filter(Boolean).join(" ");
@@ -2451,7 +2433,7 @@ function needTerms(need) {
2451
2433
  }
2452
2434
  async function searchCatalog(store, need, options = {}) {
2453
2435
  const filter = filterOf(options);
2454
- const rows = await store.matchDiscover({ ...filter, terms: needTerms(need), perClass: options.perClass ?? CANDIDATES_PER_CLASS });
2436
+ const rows = await store.matchDiscover({ ...filter, terms: needTerms(need), perClass: CANDIDATES_PER_CLASS });
2455
2437
  const ranked = rankEntries(rows.map((r) => r.entry), need, options);
2456
2438
  const offset = pageInteger(options.offset, 0, 2147483647);
2457
2439
  const page = ranked.scored.slice(offset, offset + pageInteger(options.limit, 10));
@@ -2661,7 +2643,7 @@ async function preparePreview(cacheDir, id, options = {}) {
2661
2643
  await assertNoLinks(dir, file);
2662
2644
  await writeFileAtomic(join6(dir, file), text);
2663
2645
  }
2664
- const packages = [...new Set([...rewritten.values()].flatMap((t) => importedPackages(t)))].filter((p) => !p.startsWith("/")).sort();
2646
+ const packages = [...new Set([...rewritten.values()].flatMap((t) => importedPackages(t)))].sort();
2665
2647
  const declared = [...new Set([...source.dependencies ?? entry.install?.npm ?? [], ...partDeps].map(packageName))].sort();
2666
2648
  const sources = [...rewritten.entries()].filter(([f]) => /\.(?:tsx|jsx|ts|js)$/.test(f)).map(([, t]) => t);
2667
2649
  return {
@@ -2681,9 +2663,138 @@ async function preparePreview(cacheDir, id, options = {}) {
2681
2663
  };
2682
2664
  }
2683
2665
 
2666
+ // ../discover/src/gaps.ts
2667
+ import { existsSync, readFileSync } from "node:fs";
2668
+ import { join as join7 } from "node:path";
2669
+ var STANDARD_KIT = ["button", "input", "card", "badge", "dialog", "alert", "drawer", "menu", "popover", "tooltip", "tabs", "avatar", "accordion", "table", "carousel", "slider", "switch", "checkbox", "radio", "select", "otp", "progress", "skeleton", "spinner", "sidebar", "navigation", "breadcrumb", "pagination", "command", "toast", "date-picker", "combobox", "chart"];
2670
+ var ONLY_EXACT = /* @__PURE__ */ new Set(["hint", "tile", "range", "loading", "picker", "tree", "graph", "plot", "editor", "cta", "notice", "steps", "tag", "toggle", "meter", "command", "banner", "popup", "pager", "loader", "navigation", "chip", "pill"]);
2671
+ var ROLE_CONCEPT2 = { button: "button", "link-as-button": "button", input: "input", card: "card", badge: "badge", dialog: "dialog", tabs: "tabs" };
2672
+ var CANDIDATES = 3;
2673
+ var DEFINITIONS_SHOWN = 5;
2674
+ function conceptOfName(name) {
2675
+ const words2 = normalize(name.replace(/\.[a-z]+$/, ""));
2676
+ if (!words2) return void 0;
2677
+ const phrases = CONCEPTS.flatMap((concept) => concept.names.map((n) => ({ concept, name: normalize(n) }))).sort((a, b) => b.name.length - a.name.length);
2678
+ return phrases.find((p) => words2 === p.name || !ONLY_EXACT.has(p.name) && ` ${words2}`.endsWith(` ${p.name}`))?.concept;
2679
+ }
2680
+ function conceptOfPackage(pkg) {
2681
+ const listed = CONCEPTS.find((c) => c.packages.includes(pkg));
2682
+ if (listed) return listed;
2683
+ const last = pkg.split("/").at(-1).replace(/^react-/, "");
2684
+ return conceptForRole(last);
2685
+ }
2686
+ function declaredPackages(roots) {
2687
+ const found = {};
2688
+ for (const [id, root] of roots) {
2689
+ const file = join7(root, "package.json");
2690
+ if (!existsSync(file)) continue;
2691
+ try {
2692
+ const pkg = JSON.parse(readFileSync(file, "utf8"));
2693
+ found[id] = [...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})].sort();
2694
+ } catch {
2695
+ }
2696
+ }
2697
+ return found;
2698
+ }
2699
+ var fresh2 = () => ({ system: /* @__PURE__ */ new Set(), packages: /* @__PURE__ */ new Set(), definitions: [], uses: 0, files: /* @__PURE__ */ new Set() });
2700
+ async function candidatesFor(store, concept) {
2701
+ const { hits } = await searchCatalog(store, concept.names[0], { role: concept.id, limit: 12 });
2702
+ return hits.filter((hit) => hit.license !== "unknown").map((hit) => ({ id: hit.id, name: hit.name, source: hit.source.name, licence: hit.license, copyleft: copyleft(hit.license), install: hit.install?.command ?? null })).sort((a, b) => Number(a.copyleft) - Number(b.copyleft)).slice(0, CANDIDATES);
2703
+ }
2704
+ var ORDER = { "hand-built": 0, missing: 1, library: 2, system: 3 };
2705
+ var KIT_ORDER = new Map(STANDARD_KIT.map((id, i) => [id, i]));
2706
+ async function buildGaps(source, store) {
2707
+ const { inventory } = source;
2708
+ const systemByConcept = /* @__PURE__ */ new Map();
2709
+ for (const name of source.system?.components ?? []) {
2710
+ const concept = conceptOfName(name);
2711
+ if (concept) systemByConcept.set(concept.id, [...systemByConcept.get(concept.id) ?? [], name]);
2712
+ }
2713
+ const candidates = /* @__PURE__ */ new Map();
2714
+ const offer = (concept) => {
2715
+ if (!store) return null;
2716
+ let found = candidates.get(concept.id);
2717
+ if (!found) candidates.set(concept.id, found = candidatesFor(store, concept));
2718
+ return found;
2719
+ };
2720
+ const projects = [];
2721
+ for (const project of inventory.projects) {
2722
+ const tallies = /* @__PURE__ */ new Map();
2723
+ const tally2 = (id) => {
2724
+ let t = tallies.get(id);
2725
+ if (!t) tallies.set(id, t = fresh2());
2726
+ return t;
2727
+ };
2728
+ for (const [id, names] of systemByConcept) for (const name of names) tally2(id).system.add(name);
2729
+ for (const primitive of inventory.primitives.filter((p) => p.project === project.id)) {
2730
+ const concept = conceptOfPackage(primitive.package);
2731
+ if (concept) tally2(concept.id).packages.add(primitive.package);
2732
+ }
2733
+ for (const pkg of source.dependencies[project.id] ?? []) {
2734
+ const concept = CONCEPTS.find((c) => c.packages.includes(pkg));
2735
+ if (concept) tally2(concept.id).packages.add(pkg);
2736
+ }
2737
+ const ownConcept = /* @__PURE__ */ new Map();
2738
+ for (const definition of inventory.definitions.filter((d) => d.project === project.id)) {
2739
+ const concept = conceptOfName(definition.name);
2740
+ if (!concept) continue;
2741
+ ownConcept.set(definition.id, concept.id);
2742
+ const t = tally2(concept.id);
2743
+ t.definitions.push({ name: definition.name, file: definition.file, line: definition.line });
2744
+ t.files.add(definition.file);
2745
+ }
2746
+ for (const look of inventory.looks.filter((l) => l.project === project.id && !l.story)) {
2747
+ const id = look.definition ? ownConcept.get(look.definition) : look.source === "intrinsic" ? ROLE_CONCEPT2[look.role] : void 0;
2748
+ if (!id) continue;
2749
+ const t = tally2(id);
2750
+ t.uses += 1;
2751
+ t.files.add(look.file);
2752
+ }
2753
+ const concepts = [];
2754
+ for (const concept of CONCEPTS) {
2755
+ const t = tallies.get(concept.id) ?? fresh2();
2756
+ const hand = t.definitions.length > 0 || t.uses > 0;
2757
+ const status2 = t.system.size ? "system" : t.packages.size ? "library" : hand ? "hand-built" : KIT_ORDER.has(concept.id) ? "missing" : null;
2758
+ if (!status2) continue;
2759
+ const offered = status2 === "hand-built" || status2 === "missing" ? offer(concept) : null;
2760
+ concepts.push({
2761
+ concept: concept.id,
2762
+ name: concept.names[0],
2763
+ status: status2,
2764
+ system: [...t.system].sort(),
2765
+ packages: [...t.packages].sort(),
2766
+ definitions: t.definitions.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line).slice(0, DEFINITIONS_SHOWN),
2767
+ count: t.definitions.length,
2768
+ uses: t.uses,
2769
+ files: t.files.size,
2770
+ candidates: offered ? await offered : status2 === "hand-built" || status2 === "missing" ? null : []
2771
+ });
2772
+ }
2773
+ concepts.sort((a, b) => ORDER[a.status] - ORDER[b.status] || (a.status === "hand-built" ? b.uses - a.uses || b.count - a.count : 0) || (KIT_ORDER.get(a.concept) ?? 99) - (KIT_ORDER.get(b.concept) ?? 99) || a.concept.localeCompare(b.concept));
2774
+ projects.push({ id: project.id, complete: project.status === "complete", dependencies: source.dependencies[project.id] ? "read" : "unread", concepts });
2775
+ }
2776
+ const across = /* @__PURE__ */ new Map();
2777
+ for (const project of projects) {
2778
+ for (const c of project.concepts.filter((c2) => c2.status === "hand-built")) {
2779
+ const entry = across.get(c.concept) ?? { name: c.name, projects: [], uses: 0 };
2780
+ entry.projects.push(project.id);
2781
+ entry.uses += c.uses;
2782
+ across.set(c.concept, entry);
2783
+ }
2784
+ }
2785
+ return {
2786
+ schema: "youi.gaps/v1",
2787
+ generatedAt: (source.now ?? /* @__PURE__ */ new Date()).toISOString(),
2788
+ system: source.system?.name ?? null,
2789
+ discover: store ? "synced" : "not-synced",
2790
+ kit: [...STANDARD_KIT],
2791
+ projects,
2792
+ across: [...across].filter(([, e]) => e.projects.length > 1).map(([concept, e]) => ({ concept, ...e })).sort((a, b) => b.uses - a.uses || a.concept.localeCompare(b.concept))
2793
+ };
2794
+ }
2795
+
2684
2796
  export {
2685
2797
  CONCEPTS,
2686
- conceptById,
2687
2798
  normalize,
2688
2799
  hasPhrase,
2689
2800
  readNeed,
@@ -2732,7 +2843,6 @@ export {
2732
2843
  discoverIndexPath,
2733
2844
  SourceStatus,
2734
2845
  DiscoverIndex,
2735
- EMPTY_SIGNALS,
2736
2846
  writeJson,
2737
2847
  MissingIndexError,
2738
2848
  readIndex,
@@ -2759,6 +2869,7 @@ export {
2759
2869
  registryName,
2760
2870
  passes,
2761
2871
  playsRole,
2872
+ licenceOf,
2762
2873
  hitOf,
2763
2874
  isIconEntry,
2764
2875
  rankEntries,
@@ -2783,7 +2894,6 @@ export {
2783
2894
  LINKS_PER_ROLE,
2784
2895
  sourceClassOf,
2785
2896
  rolesOf,
2786
- licenceOf,
2787
2897
  searchTextOf,
2788
2898
  itemOf,
2789
2899
  ingestCatalog,
@@ -2801,5 +2911,10 @@ export {
2801
2911
  safeRelative,
2802
2912
  exportNames,
2803
2913
  importedNames,
2804
- preparePreview
2914
+ preparePreview,
2915
+ STANDARD_KIT,
2916
+ conceptOfName,
2917
+ conceptOfPackage,
2918
+ declaredPackages,
2919
+ buildGaps
2805
2920
  };