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.
- package/dist/chunks/{chunk-ZWVWENSB.js → chunk-A2IZOTJG.js} +1 -1
- package/dist/chunks/{chunk-5I2VHSEN.js → chunk-ALQVHSF2.js} +1 -1
- package/dist/chunks/{chunk-DX2NKY2M.js → chunk-CZJFEND6.js} +4 -4
- package/dist/chunks/{chunk-ZOQW6LNH.js → chunk-DAGYR4NV.js} +450 -30
- package/dist/chunks/{chunk-N5Z5A637.js → chunk-FTL53MU3.js} +2 -2
- package/dist/chunks/{chunk-PCLXMWZ2.js → chunk-HII2I2PI.js} +3 -3
- package/dist/chunks/{chunk-XOXF2ILO.js → chunk-HIXGUALJ.js} +1 -1
- package/dist/chunks/{chunk-PWGZRP3C.js → chunk-MIUG2Z43.js} +180 -8
- package/dist/chunks/chunk-QEUZDGIF.js +110 -0
- package/dist/chunks/{chunk-NF4OJYKC.js → chunk-S7UN5KQK.js} +1 -1
- package/dist/chunks/{chunk-EV62TUDV.js → chunk-T4EHV4XY.js} +230 -288
- package/dist/chunks/ingest-R2TM3QBM.js +52 -0
- package/dist/chunks/{src-IKDQ2BZO.js → src-4BOFDAZD.js} +1 -1
- package/dist/chunks/{src-4QOHR5GC.js → src-6JO73FOS.js} +3 -3
- package/dist/chunks/{src-PBM76LFQ.js → src-7SHBAJEO.js} +2 -2
- package/dist/chunks/{src-R7JGKOYI.js → src-CCGTRT5J.js} +18 -4
- package/dist/chunks/{src-E6ZPFKPR.js → src-IMWXYWQK.js} +23 -3
- package/dist/chunks/{src-624TVB6T.js → src-LUPO3O76.js} +2 -2
- package/dist/chunks/{src-HVYMF4S2.js → src-OHHZY3TA.js} +7 -7
- package/dist/chunks/{src-YC64NNQ3.js → src-WMGLZKYK.js} +2 -2
- package/dist/hub/assets/{CandidatePanel-DfntneYT.js → CandidatePanel-BbE2xCN2.js} +1 -1
- package/dist/hub/assets/CatalogScreen-CBlaW1-N.js +1 -0
- package/dist/hub/assets/ChangesScreen-WaA9Y7zq.js +1 -0
- package/dist/hub/assets/{CheatSheet-B1dXTYTW.js → CheatSheet-CGfTS7zw.js} +1 -1
- package/dist/hub/assets/{CommandPalette-LBZHJ659.js → CommandPalette-D4WigyuD.js} +1 -1
- package/dist/hub/assets/{DiscoverScreen-Yd3gVUG4.js → DiscoverScreen-CVtcQCM4.js} +2 -2
- package/dist/hub/assets/DraftEditor-Mp1bT8-o.js +5 -0
- package/dist/hub/assets/{GapsScreen-DPYGlNnA.js → GapsScreen-DzB0TfH3.js} +1 -1
- package/dist/hub/assets/{HomeScreen-BCPPk7mz.js → HomeScreen-BVxjYDtc.js} +1 -1
- package/dist/hub/assets/InventoryScreen-D33_YchG.js +1 -0
- package/dist/hub/assets/{LibraryScreen-BVXhOVPU.js → LibraryScreen-B8lAJBGW.js} +1 -1
- package/dist/hub/assets/{Opened-CJyMbxqV.js → Opened-DgNa-hGA.js} +1 -1
- package/dist/hub/assets/{OrgApp-Dxs_7CxZ.js → OrgApp-Q8YNFE-z.js} +1 -1
- package/dist/hub/assets/{Preview-DWFP0BIe.js → Preview-yu3LaZKL.js} +1 -1
- package/dist/hub/assets/{RuleDetail-C2fTlLJL.js → RuleDetail-BqVdc20s.js} +1 -1
- package/dist/hub/assets/{RulesScreen-BABeAGJg.js → RulesScreen-mb3OUlOB.js} +1 -1
- package/dist/hub/assets/{ScreenImage-CyhKm_nj.js → ScreenImage-DUMSgBJL.js} +1 -1
- package/dist/hub/assets/{ScreensScreen-8Ih8byGy.js → ScreensScreen-DjN5mwSP.js} +1 -1
- package/dist/hub/assets/{SearchField-hF-6-YDM.js → SearchField-ByftoJws.js} +1 -1
- package/dist/hub/assets/{Segmented-Dzi1x49S.js → Segmented-CTMaLPgh.js} +1 -1
- package/dist/hub/assets/{ServiceDown-S4ya0Cob.js → ServiceDown-BghtjpTn.js} +1 -1
- package/dist/hub/assets/{SpecimenCapture-e_pgT4Sp.js → SpecimenCapture-CglZ4bn-.js} +1 -1
- package/dist/hub/assets/{Specimens-D_9hjlJH.js → Specimens-D5ehj5-O.js} +2 -2
- package/dist/hub/assets/{StackScreen-J2FQilLW.js → StackScreen-w5hCLj_R.js} +1 -1
- package/dist/hub/assets/{StubScreens-DAkMph3i.js → StubScreens-DfF4aAbp.js} +1 -1
- package/dist/hub/assets/{StyleScreen-C5WytSZU.js → StyleScreen-DKkerxmC.js} +1 -1
- package/dist/hub/assets/{Tabs-DU-fFRDV.js → Tabs-B1IFs-wv.js} +1 -1
- package/dist/hub/assets/TokenDetail-BPTZ9o6J.js +1 -0
- package/dist/hub/assets/TokensScreen-CKnQuVaT.js +1 -0
- package/dist/hub/assets/{WorkspaceScreen-CzZ16fPZ.js → WorkspaceScreen-DwjGW6eY.js} +1 -1
- package/dist/hub/assets/{client-Bdgr12LP.js → client-uMxZXiDl.js} +1 -1
- package/dist/hub/assets/{discover-va2KFuK_.js → discover-Yfb0eB14.js} +1 -1
- package/dist/hub/assets/{dist-CC_p8DIj.js → dist-BvRlRM3Z.js} +1 -1
- package/dist/hub/assets/{dist-C8tf4g8c.js → dist-CL3qgCbi.js} +1 -1
- package/dist/hub/assets/{dist-YTWqg5sS.js → dist-tjVfXRHc.js} +1 -1
- package/dist/hub/assets/{family-tiles-qaK1-_c8.js → family-tiles-dZqjsxro.js} +1 -1
- package/dist/hub/assets/link-2-dzvhLwTC.js +1 -0
- package/dist/hub/assets/main-DNLr0KzQ.css +2 -0
- package/dist/hub/assets/main-Djqho539.js +2 -0
- package/dist/hub/assets/{try-live-Iys3nCBG.js → try-live-Dxsjf62v.js} +1 -1
- package/dist/hub/assets/{workflow-C53UBmRZ.js → workflow-Bv814-L8.js} +1 -1
- package/dist/hub/index.html +2 -2
- package/dist/hub-previews.js +6 -6
- package/dist/hub-server.js +3 -3
- package/dist/hub-snapshot.js +1 -1
- package/dist/schema/youi.library.schema.json +3 -1
- package/dist/youi.js +8 -7
- package/dist/youiyoux.js +8 -7
- package/package.json +4 -4
- package/dist/hub/assets/CatalogScreen-kpFYZqX3.js +0 -1
- package/dist/hub/assets/ChangesScreen-Chu-Ui1B.js +0 -1
- package/dist/hub/assets/DraftEditor-QH9Sa_Uc.js +0 -5
- package/dist/hub/assets/InventoryScreen-SleB733C.js +0 -1
- package/dist/hub/assets/TokenDetail-CDY1xxb9.js +0 -1
- package/dist/hub/assets/TokensScreen-Sx4aICWk.js +0 -1
- package/dist/hub/assets/main-cvOFSjox.js +0 -2
- package/dist/hub/assets/main-pNY4iNRo.css +0 -2
|
@@ -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-
|
|
6
|
-
scanner: () => import("./src-
|
|
7
|
-
discover: () => import("./src-
|
|
8
|
-
tokens: () => import("./src-
|
|
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-
|
|
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-
|
|
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(
|
|
67
|
-
return
|
|
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(
|
|
70
|
-
return ` ${
|
|
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(
|
|
110
|
-
return
|
|
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
|
|
249
|
-
return
|
|
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
|
|
351
|
-
return concept.packages.includes(object.package.name) || concept.names.some((name) => hasPhrase(
|
|
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)
|
|
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(
|
|
628
|
-
|
|
629
|
-
|
|
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((
|
|
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((
|
|
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
|
|
916
|
-
for (const concept of read.concepts)
|
|
917
|
-
for (const word of read.words)
|
|
918
|
-
if (
|
|
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: (
|
|
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
|
|
1755
|
-
for (const match of
|
|
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,
|
|
1797
|
-
rewritten.set(file,
|
|
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,
|
|
1813
|
+
for (const [file, text2] of rewritten) {
|
|
1805
1814
|
await assertNoLinks(dir, file);
|
|
1806
|
-
await writeFileAtomic(join5(dir, file),
|
|
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-
|
|
7
|
+
} from "./chunk-CZJFEND6.js";
|
|
8
8
|
import {
|
|
9
9
|
writeFileAtomic
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-S7UN5KQK.js";
|
|
11
11
|
|
|
12
12
|
// ../../apps/web/scripts/discover-thumbs.ts
|
|
13
13
|
import { existsSync, readFileSync } from "node:fs";
|