youiyoux 0.1.13 → 0.1.14
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-EV62TUDV.js → chunk-EN64OSUT.js} +230 -288
- package/dist/chunks/{chunk-DX2NKY2M.js → chunk-F3KCL226.js} +4 -4
- package/dist/chunks/chunk-FTF4MBV5.js +110 -0
- 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-N5Z5A637.js → chunk-JGEVNQ5R.js} +2 -2
- package/dist/chunks/{chunk-PWGZRP3C.js → chunk-MIUG2Z43.js} +180 -8
- package/dist/chunks/{chunk-NF4OJYKC.js → chunk-S7UN5KQK.js} +1 -1
- package/dist/chunks/{chunk-ZOQW6LNH.js → chunk-TPP4HLDB.js} +446 -29
- package/dist/chunks/ingest-OF4Z33L2.js +52 -0
- package/dist/chunks/{src-HVYMF4S2.js → src-3LEBH7E3.js} +7 -7
- 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-E6ZPFKPR.js → src-7LETTRRF.js} +23 -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-624TVB6T.js → src-LUPO3O76.js} +2 -2
- 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
|
@@ -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) {
|
|
@@ -624,9 +628,10 @@ var PoliteClient = class {
|
|
|
624
628
|
return this.options.now?.() ?? /* @__PURE__ */ new Date();
|
|
625
629
|
}
|
|
626
630
|
/** Replace the token wherever it appears, so no message, log line or file can carry it. */
|
|
627
|
-
redact(
|
|
628
|
-
|
|
629
|
-
|
|
631
|
+
redact(text2) {
|
|
632
|
+
let clean = text2;
|
|
633
|
+
for (const token of [this.options.token, this.options.figmaToken]) if (token) clean = clean.split(token).join("***");
|
|
634
|
+
return cleanText(clean);
|
|
630
635
|
}
|
|
631
636
|
/** True when the bucket still has room in its window; a spent bucket stays spent for this run. */
|
|
632
637
|
take(bucket) {
|
|
@@ -670,7 +675,7 @@ var PoliteClient = class {
|
|
|
670
675
|
const gate = host;
|
|
671
676
|
const request = () => this.gate.run(
|
|
672
677
|
() => gate.run(async () => {
|
|
673
|
-
if (npm && this.npmStarted) await (this.options.sleep ?? ((ms) => new Promise((
|
|
678
|
+
if (npm && this.npmStarted) await (this.options.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms))))(500);
|
|
674
679
|
if (npm) this.npmStarted = true;
|
|
675
680
|
if (!this.take(bucket)) {
|
|
676
681
|
this.stats.rateLimited++;
|
|
@@ -680,7 +685,7 @@ var PoliteClient = class {
|
|
|
680
685
|
const result = await this.send(url, parsed, bucket, accept);
|
|
681
686
|
if (!npm || result.ok || result.status !== 429) return result;
|
|
682
687
|
if (attempt === 4) return result;
|
|
683
|
-
await (this.options.sleep ?? ((ms) => new Promise((
|
|
688
|
+
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
689
|
}
|
|
685
690
|
})
|
|
686
691
|
);
|
|
@@ -692,6 +697,7 @@ var PoliteClient = class {
|
|
|
692
697
|
if (cached?.etag) headers["if-none-match"] = cached.etag;
|
|
693
698
|
if (cached?.lastModified) headers["if-modified-since"] = cached.lastModified;
|
|
694
699
|
if (this.options.token && parsed.hostname === "api.github.com") headers.authorization = `Bearer ${this.options.token}`;
|
|
700
|
+
if (this.options.figmaToken && parsed.hostname === "api.figma.com") headers["x-figma-token"] = this.options.figmaToken;
|
|
695
701
|
this.stats.sent++;
|
|
696
702
|
this.inFlight++;
|
|
697
703
|
this.stats.maxInFlight = Math.max(this.stats.maxInFlight, this.inFlight);
|
|
@@ -912,10 +918,10 @@ function rankEntries(entries, need, options = {}) {
|
|
|
912
918
|
const f = fieldsOf(entry);
|
|
913
919
|
if (options.role && !playsRole(options.role, f)) continue;
|
|
914
920
|
const why = [];
|
|
915
|
-
let
|
|
916
|
-
for (const concept of read.concepts)
|
|
917
|
-
for (const word of read.words)
|
|
918
|
-
if (
|
|
921
|
+
let text2 = 0;
|
|
922
|
+
for (const concept of read.concepts) text2 += conceptScore(concept, f, why);
|
|
923
|
+
for (const word of read.words) text2 += wordScore(word, f, why);
|
|
924
|
+
if (text2 <= 0) continue;
|
|
919
925
|
let suitability = 1;
|
|
920
926
|
if (isIconEntry(entry)) {
|
|
921
927
|
suitability *= 0.3;
|
|
@@ -926,7 +932,7 @@ function rankEntries(entries, need, options = {}) {
|
|
|
926
932
|
suitability *= 0.3;
|
|
927
933
|
why.push(`framework ${framework}`);
|
|
928
934
|
}
|
|
929
|
-
scored.push({ entry, score: (
|
|
935
|
+
scored.push({ entry, score: (text2 * KIND_WEIGHT[entry.kind] + signalBoost(entry)) * suitability, why });
|
|
930
936
|
}
|
|
931
937
|
scored.sort((a, b) => b.score - a.score || a.entry.id.localeCompare(b.entry.id));
|
|
932
938
|
return { need, concepts: read.concepts.map((c) => c.id), scored };
|
|
@@ -1751,8 +1757,8 @@ async function preparePreview(cacheDir, id, options = {}) {
|
|
|
1751
1757
|
const queue = [...own];
|
|
1752
1758
|
while (queue.length > 0) {
|
|
1753
1759
|
const file = queue.shift();
|
|
1754
|
-
const
|
|
1755
|
-
for (const match of
|
|
1760
|
+
const text2 = texts.get(file);
|
|
1761
|
+
for (const match of text2.matchAll(SPECIFIER)) {
|
|
1756
1762
|
const spec = match[3];
|
|
1757
1763
|
if (!spec.startsWith("@/registry/") || resolved.has(spec)) continue;
|
|
1758
1764
|
const local = [...texts.keys()].find((f) => f.replace(/\.(?:tsx?|jsx?)$/, "") === spec.slice(2) || f.replace(/\/index\.(?:tsx?|jsx?)$/, "") === spec.slice(2));
|
|
@@ -1793,17 +1799,17 @@ async function preparePreview(cacheDir, id, options = {}) {
|
|
|
1793
1799
|
}
|
|
1794
1800
|
}
|
|
1795
1801
|
const rewritten = /* @__PURE__ */ new Map();
|
|
1796
|
-
for (const [file,
|
|
1797
|
-
rewritten.set(file,
|
|
1802
|
+
for (const [file, text2] of texts) {
|
|
1803
|
+
rewritten.set(file, text2.replace(SPECIFIER, (all, lead, quote, spec) => {
|
|
1798
1804
|
const target = resolved.get(spec);
|
|
1799
1805
|
if (!target || target === spec) return all;
|
|
1800
1806
|
const next = target.startsWith("/") || /^[a-z]:[\\/]/i.test(target) ? target : relativeSpec(file, target);
|
|
1801
1807
|
return `${lead}${quote}${next}${quote}`;
|
|
1802
1808
|
}));
|
|
1803
1809
|
}
|
|
1804
|
-
for (const [file,
|
|
1810
|
+
for (const [file, text2] of rewritten) {
|
|
1805
1811
|
await assertNoLinks(dir, file);
|
|
1806
|
-
await writeFileAtomic(join5(dir, file),
|
|
1812
|
+
await writeFileAtomic(join5(dir, file), text2);
|
|
1807
1813
|
}
|
|
1808
1814
|
const packages = [...new Set([...rewritten.values()].flatMap((t) => importedPackages(t)))].sort();
|
|
1809
1815
|
const declared = [...new Set([...source.dependencies ?? entry.install?.npm ?? [], ...partDeps].map(packageName))].sort();
|
|
@@ -1955,6 +1961,407 @@ async function buildGaps(source, store) {
|
|
|
1955
1961
|
};
|
|
1956
1962
|
}
|
|
1957
1963
|
|
|
1964
|
+
// ../discover/src/ingest.ts
|
|
1965
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
1966
|
+
import { basename as basename2, resolve } from "node:path";
|
|
1967
|
+
|
|
1968
|
+
// ../discover/src/ingest-design-md.ts
|
|
1969
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
1970
|
+
import { basename } from "node:path";
|
|
1971
|
+
var GROUP_TITLE = { colors: "Colors", typography: "Typography", rounded: "Corner radius", spacing: "Spacing", elevation: "Elevation", shadows: "Shadows", motion: "Motion" };
|
|
1972
|
+
var NOT_TOKENS = /* @__PURE__ */ new Set(["name", "version", "description", "components", "$schema"]);
|
|
1973
|
+
var SAMPLES = 4;
|
|
1974
|
+
var title = (key) => key.split(/[-_\s]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
|
|
1975
|
+
function sample(name, value) {
|
|
1976
|
+
if (value && typeof value === "object") {
|
|
1977
|
+
const v = value;
|
|
1978
|
+
return `${name} ${[v.fontSize, v.lineHeight, v.fontWeight].filter((x) => x !== void 0).join("/")}`.trim();
|
|
1979
|
+
}
|
|
1980
|
+
return `${name} ${String(value)}`;
|
|
1981
|
+
}
|
|
1982
|
+
function bodyComponents(body) {
|
|
1983
|
+
const lines = body.split(/\r?\n/);
|
|
1984
|
+
const found = [];
|
|
1985
|
+
let level = 0;
|
|
1986
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1987
|
+
const heading = /^(#{1,6})\s+(.+?)\s*#*$/.exec(lines[i]);
|
|
1988
|
+
if (heading) {
|
|
1989
|
+
const depth = heading[1].length;
|
|
1990
|
+
if (/\bcomponents?\b/i.test(heading[2])) {
|
|
1991
|
+
level = depth;
|
|
1992
|
+
continue;
|
|
1993
|
+
}
|
|
1994
|
+
if (level > 0 && depth <= level) level = 0;
|
|
1995
|
+
else if (level > 0 && depth > level) {
|
|
1996
|
+
const text2 = lines.slice(i + 1).find((l) => l.trim() && !l.startsWith("#") && !l.startsWith("|"));
|
|
1997
|
+
found.push({ name: heading[2].replace(/[`*_]/g, ""), summary: text2?.trim() ?? "" });
|
|
1998
|
+
}
|
|
1999
|
+
continue;
|
|
2000
|
+
}
|
|
2001
|
+
if (level === 0) continue;
|
|
2002
|
+
const row = /^\|(.+)\|\s*$/.exec(lines[i]);
|
|
2003
|
+
if (!row || /^\|[\s:-]+\|/.test(lines[i])) continue;
|
|
2004
|
+
const cells = row[1].split("|").map((c) => c.trim());
|
|
2005
|
+
const name = cells[0].replace(/[`*_]/g, "");
|
|
2006
|
+
if (!name || /^(component|name|element)s?$/i.test(name)) continue;
|
|
2007
|
+
found.push({ name, summary: cells.slice(1).filter((c) => c && c !== "\u2014" && !c.startsWith("`")).at(-1) ?? "" });
|
|
2008
|
+
}
|
|
2009
|
+
return found;
|
|
2010
|
+
}
|
|
2011
|
+
async function readDesignMd(target, client, fetchedAt, readLocal) {
|
|
2012
|
+
const text2 = target.local ? await readLocal(target.location).then((body2) => ({ body: body2, sha256: digestOf(body2) }), () => {
|
|
2013
|
+
throw new IngestError(`could not read ${basename(target.location)}`);
|
|
2014
|
+
}) : await fetchText(client, target.location, "the DESIGN.md");
|
|
2015
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(text2.body);
|
|
2016
|
+
let front = {};
|
|
2017
|
+
if (match) {
|
|
2018
|
+
try {
|
|
2019
|
+
const parsed = (0, import_yaml.parse)(match[1]);
|
|
2020
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) front = parsed;
|
|
2021
|
+
} catch (error) {
|
|
2022
|
+
throw new IngestError(`the DESIGN.md's frontmatter is not YAML: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`);
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
const body = match ? match[2] : text2.body;
|
|
2026
|
+
const heading = /^#\s+(.+)$/m.exec(body)?.[1]?.replace(/^Design System:\s*/i, "").trim();
|
|
2027
|
+
const name = typeof front.name === "string" && front.name.trim() || heading || (target.local ? basename(target.location, ".md") : new URL(target.location).host);
|
|
2028
|
+
const url = target.local ? null : target.location;
|
|
2029
|
+
const source = { kind: "design-md", name, url };
|
|
2030
|
+
const file = target.local ? basename(target.location) : new URL(target.location).pathname.split("/").at(-1) ?? "DESIGN.md";
|
|
2031
|
+
const inputs = [];
|
|
2032
|
+
for (const [group, value] of Object.entries(front)) {
|
|
2033
|
+
if (NOT_TOKENS.has(group) || !value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
2034
|
+
const tokens = Object.entries(value);
|
|
2035
|
+
if (tokens.length === 0) continue;
|
|
2036
|
+
const samples = tokens.slice(0, SAMPLES).map(([k, v]) => sample(k, v)).join(", ");
|
|
2037
|
+
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] });
|
|
2038
|
+
}
|
|
2039
|
+
const components = /* @__PURE__ */ new Map();
|
|
2040
|
+
const keys = Object.keys(front.components ?? {});
|
|
2041
|
+
for (const key of keys) {
|
|
2042
|
+
const base = keys.filter((other) => other !== key && key.startsWith(`${other}-`)).sort((a, b) => a.length - b.length)[0] ?? key;
|
|
2043
|
+
const entry = components.get(base.toLowerCase()) ?? { name: title(base), summary: "", digest: digestOf(front.components[base]) };
|
|
2044
|
+
if (base !== key) entry.summary = `${entry.summary ? `${entry.summary}, ` : "Variants: "}${key.slice(base.length + 1)}`;
|
|
2045
|
+
components.set(base.toLowerCase(), entry);
|
|
2046
|
+
}
|
|
2047
|
+
for (const found of bodyComponents(body)) {
|
|
2048
|
+
const key = found.name.toLowerCase().replace(/\s+/g, "-");
|
|
2049
|
+
const known = components.get(key);
|
|
2050
|
+
components.set(key, { name: known?.name ?? found.name, summary: found.summary || known?.summary || "", digest: known?.digest ?? digestOf(found) });
|
|
2051
|
+
}
|
|
2052
|
+
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 });
|
|
2053
|
+
if (inputs.length === 0) throw new IngestError(`the DESIGN.md names no token groups in its frontmatter and no components: nothing to read`);
|
|
2054
|
+
return {
|
|
2055
|
+
source,
|
|
2056
|
+
license: "unknown",
|
|
2057
|
+
entries: itemsOf(source, "unknown", fetchedAt, inputs),
|
|
2058
|
+
skipped: [],
|
|
2059
|
+
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"] : []]
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
// ../discover/src/ingest-figma.ts
|
|
2064
|
+
var API = "https://api.figma.com/v1";
|
|
2065
|
+
var STYLE_GROUP = { FILL: "Color styles", TEXT: "Text styles", EFFECT: "Effect styles", GRID: "Grid styles" };
|
|
2066
|
+
var SAMPLES2 = 5;
|
|
2067
|
+
var listOf = (value, field) => {
|
|
2068
|
+
const list = value.meta?.[field];
|
|
2069
|
+
return Array.isArray(list) ? list : [];
|
|
2070
|
+
};
|
|
2071
|
+
var text = (value) => typeof value === "string" ? value.trim() : "";
|
|
2072
|
+
async function readFigma(target, client, fetchedAt) {
|
|
2073
|
+
const file = await fetchJson(client, `${API}/files/${target.key}?depth=1`, "the Figma file").catch((error) => {
|
|
2074
|
+
throw new IngestError(`${error instanceof Error ? error.message : String(error)}: is FIGMA_TOKEN allowed to read this file?`);
|
|
2075
|
+
});
|
|
2076
|
+
const fileName = text(file.name) || target.key;
|
|
2077
|
+
const [sets, components, styles] = await Promise.all([
|
|
2078
|
+
fetchJson(client, `${API}/files/${target.key}/component_sets`, "its component sets"),
|
|
2079
|
+
fetchJson(client, `${API}/files/${target.key}/components`, "its components"),
|
|
2080
|
+
fetchJson(client, `${API}/files/${target.key}/styles`, "its styles")
|
|
2081
|
+
]);
|
|
2082
|
+
const at = (node) => `https://www.figma.com/design/${target.key}?node-id=${encodeURIComponent(String(node).replace(/:/g, "-"))}`;
|
|
2083
|
+
const source = { kind: "figma", name: `${fileName} (Figma)`, url: `https://www.figma.com/design/${target.key}` };
|
|
2084
|
+
const inputs = [];
|
|
2085
|
+
const page = (p) => [text(p.containing_frame?.pageName), text(p.containing_frame?.name)].filter(Boolean);
|
|
2086
|
+
for (const set of listOf(sets, "component_sets")) {
|
|
2087
|
+
if (!text(set.name)) continue;
|
|
2088
|
+
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) });
|
|
2089
|
+
}
|
|
2090
|
+
const loose = listOf(components, "components").filter((c) => !c.containing_frame?.containingComponentSet && text(c.name));
|
|
2091
|
+
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) });
|
|
2092
|
+
const byType = /* @__PURE__ */ new Map();
|
|
2093
|
+
for (const style of listOf(styles, "styles")) {
|
|
2094
|
+
const type = text(style.style_type);
|
|
2095
|
+
if (type) byType.set(type, [...byType.get(type) ?? [], style]);
|
|
2096
|
+
}
|
|
2097
|
+
for (const [type, list] of byType) {
|
|
2098
|
+
const names = list.map((s) => text(s.name)).filter(Boolean);
|
|
2099
|
+
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()] });
|
|
2100
|
+
}
|
|
2101
|
+
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`);
|
|
2102
|
+
return {
|
|
2103
|
+
source,
|
|
2104
|
+
license: "unknown",
|
|
2105
|
+
entries: itemsOf(source, "unknown", fetchedAt, inputs),
|
|
2106
|
+
skipped: [],
|
|
2107
|
+
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"]
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
// ../discover/src/ingest-github.ts
|
|
2112
|
+
var API2 = "https://api.github.com";
|
|
2113
|
+
var COMPONENT_FILE = /\.(tsx|jsx|vue|svelte|astro)$/;
|
|
2114
|
+
var NOT_A_COMPONENT = /(\.(test|spec|stories|story|d)\.[^.]+$)|(^|\/)(index|types?|utils?|helpers?|constants?)\.[^.]+$|(^|\/)(__tests__|__mocks__|stories|examples?|demo|docs?)\//;
|
|
2115
|
+
var CONTRACTS_READ = 100;
|
|
2116
|
+
async function licenseOf(client, repo, fetchedAt) {
|
|
2117
|
+
const got = await client.json(`${API2}/repos/${repo}/license`, false);
|
|
2118
|
+
if (!got.ok) return "unknown";
|
|
2119
|
+
const value = got.value;
|
|
2120
|
+
const spdx = value.license?.spdx_id;
|
|
2121
|
+
if (typeof spdx !== "string" || spdx === "NOASSERTION" || typeof value.html_url !== "string") return "unknown";
|
|
2122
|
+
return { spdx, evidenceUrl: value.html_url, verifiedAt: fetchedAt };
|
|
2123
|
+
}
|
|
2124
|
+
async function readGithub(target, client, fetchedAt) {
|
|
2125
|
+
const repo = `${target.owner}/${target.repo}`;
|
|
2126
|
+
const meta = await fetchJson(client, `${API2}/repos/${repo}`, `the repository ${repo}`);
|
|
2127
|
+
const ref = target.ref ?? meta.default_branch ?? "main";
|
|
2128
|
+
const commit = (await fetchJson(client, `${API2}/repos/${repo}/commits/${encodeURIComponent(ref)}`, `${repo} at ${ref}`)).sha;
|
|
2129
|
+
if (typeof commit !== "string" || !/^[0-9a-f]{40}$/.test(commit)) throw new IngestError(`${repo} has no commit at ${ref}`);
|
|
2130
|
+
const tree = await fetchJson(client, `${API2}/repos/${repo}/git/trees/${commit}?recursive=1`, `${repo}'s files`);
|
|
2131
|
+
const scope = target.path ? `${target.path.replace(/\/+$/, "")}/` : "";
|
|
2132
|
+
const blobs = new Map((tree.tree ?? []).filter((e) => e.type === "blob" && e.path.startsWith(scope)).map((e) => [e.path, e.sha ?? null]));
|
|
2133
|
+
const files = [...blobs.keys()];
|
|
2134
|
+
const license = await licenseOf(client, repo, fetchedAt);
|
|
2135
|
+
const source = { kind: "github", name: repo, url: `https://github.com/${repo}${target.ref || target.path ? `/tree/${ref}${target.path ? `/${target.path}` : ""}` : ""}` };
|
|
2136
|
+
const blob = (path) => `https://github.com/${repo}/blob/${commit}/${path}`;
|
|
2137
|
+
const raw = (path) => `https://raw.githubusercontent.com/${repo}/${commit}/${path}`;
|
|
2138
|
+
const notes = [
|
|
2139
|
+
`read at ${commit.slice(0, 7)} (${ref})`,
|
|
2140
|
+
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}`,
|
|
2141
|
+
...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"] : []
|
|
2142
|
+
];
|
|
2143
|
+
const byDepth = (a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b);
|
|
2144
|
+
const registry = files.filter((f) => /(^|\/)registry\.json$/.test(f) && !f.includes("node_modules/")).sort(byDepth)[0];
|
|
2145
|
+
if (registry) {
|
|
2146
|
+
const text2 = await fetchText(client, raw(registry), `${repo}'s ${registry}`);
|
|
2147
|
+
let value;
|
|
2148
|
+
try {
|
|
2149
|
+
value = JSON.parse(text2.body);
|
|
2150
|
+
} catch {
|
|
2151
|
+
throw new IngestError(`${repo}'s ${registry} is not JSON`);
|
|
2152
|
+
}
|
|
2153
|
+
const { items, invalid } = parseRegistryItems(value);
|
|
2154
|
+
const built = new Set(files.filter((f) => /(^|\/)public\/r\/[^/]+\.json$/.test(f)));
|
|
2155
|
+
const inputs2 = items.filter(isCandidate).map((item) => {
|
|
2156
|
+
const servedAt = [...built].find((f) => f.endsWith(`/r/${item.name}.json`));
|
|
2157
|
+
return {
|
|
2158
|
+
kind: item.type === "registry:block" ? "pattern" : "component",
|
|
2159
|
+
name: item.name,
|
|
2160
|
+
...item.title ? { title: item.title } : {},
|
|
2161
|
+
summary: item.description ?? "",
|
|
2162
|
+
ref: `${registry}#${item.name}`,
|
|
2163
|
+
url: blob(registry),
|
|
2164
|
+
digest: item.itemSha256 ?? digestOf(item),
|
|
2165
|
+
files: (item.files ?? []).map((f) => ({ path: f.path, sha256: f.sha256 ?? null })),
|
|
2166
|
+
categories: item.categories ?? [],
|
|
2167
|
+
...servedAt ? { install: { command: `npx shadcn add ${raw(servedAt)}`, npm: [...new Set((item.dependencies ?? []).map(packageName))], registryDeps: [...new Set(item.registryDependencies ?? [])], implicitDeps: [] } } : {}
|
|
2168
|
+
};
|
|
2169
|
+
});
|
|
2170
|
+
const others = items.filter((i) => !isCandidate(i));
|
|
2171
|
+
return {
|
|
2172
|
+
source,
|
|
2173
|
+
license,
|
|
2174
|
+
entries: itemsOf(source, license, fetchedAt, inputs2),
|
|
2175
|
+
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" }] : []],
|
|
2176
|
+
notes: [`a shadcn registry: ${registry}`, ...notes]
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2179
|
+
const config = files.filter((f) => /(^|\/)youi\.config\.json$/.test(f)).sort(byDepth)[0];
|
|
2180
|
+
if (config) {
|
|
2181
|
+
const contracts = files.filter((f) => f.endsWith(".contract.json")).sort();
|
|
2182
|
+
const inputs2 = [];
|
|
2183
|
+
const skipped = [];
|
|
2184
|
+
for (const path of contracts.slice(0, CONTRACTS_READ)) {
|
|
2185
|
+
const text2 = await fetchText(client, raw(path), path).catch((error) => {
|
|
2186
|
+
skipped.push({ name: path, reason: error instanceof Error ? error.message : String(error) });
|
|
2187
|
+
return null;
|
|
2188
|
+
});
|
|
2189
|
+
if (!text2) continue;
|
|
2190
|
+
try {
|
|
2191
|
+
const contract = JSON.parse(text2.body);
|
|
2192
|
+
if (typeof contract.name !== "string") throw new Error("no name");
|
|
2193
|
+
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] : []] });
|
|
2194
|
+
} catch (error) {
|
|
2195
|
+
skipped.push({ name: path, reason: `not a component contract: ${error instanceof Error ? error.message : String(error)}` });
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
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}`] : []] };
|
|
2199
|
+
}
|
|
2200
|
+
const components = files.filter((f) => /(^|\/)components?\//.test(f) && COMPONENT_FILE.test(f) && !NOT_A_COMPONENT.test(f) && !f.includes("node_modules/")).sort();
|
|
2201
|
+
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`);
|
|
2202
|
+
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: [] }));
|
|
2203
|
+
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] };
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
// ../discover/src/ingest-storybook.ts
|
|
2207
|
+
var PATTERN = /^(pages?|templates?|layouts?|patterns?|screens?|examples?)$/i;
|
|
2208
|
+
var STORIES_LISTED = 6;
|
|
2209
|
+
async function readStorybook(target, client, fetchedAt) {
|
|
2210
|
+
let index = null;
|
|
2211
|
+
let entries = {};
|
|
2212
|
+
for (const file of ["index.json", "stories.json"]) {
|
|
2213
|
+
const url = `${target.base}/${file}`;
|
|
2214
|
+
const got = await client.json(url, false);
|
|
2215
|
+
if (!got.ok) continue;
|
|
2216
|
+
const value = got.value;
|
|
2217
|
+
const found = value.entries ?? value.stories;
|
|
2218
|
+
if (found && typeof found === "object" && !Array.isArray(found)) {
|
|
2219
|
+
entries = found;
|
|
2220
|
+
index = { body: got.result.body, url, sha256: got.result.sha256 };
|
|
2221
|
+
break;
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
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`);
|
|
2225
|
+
const byTitle = /* @__PURE__ */ new Map();
|
|
2226
|
+
for (const [key, raw] of Object.entries(entries)) {
|
|
2227
|
+
const title2 = typeof raw.title === "string" ? raw.title : typeof raw.kind === "string" ? raw.kind : null;
|
|
2228
|
+
const id = typeof raw.id === "string" ? raw.id : key;
|
|
2229
|
+
if (!title2) continue;
|
|
2230
|
+
const item = byTitle.get(title2) ?? { stories: [], first: null, docs: null };
|
|
2231
|
+
if (raw.type === "docs") item.docs ??= id;
|
|
2232
|
+
else {
|
|
2233
|
+
item.first ??= id;
|
|
2234
|
+
if (typeof raw.name === "string") item.stories.push(raw.name);
|
|
2235
|
+
}
|
|
2236
|
+
byTitle.set(title2, item);
|
|
2237
|
+
}
|
|
2238
|
+
const host = new URL(target.base).host;
|
|
2239
|
+
const source = { kind: "storybook", name: host, url: target.base };
|
|
2240
|
+
const inputs = [...byTitle].map(([title2, item]) => {
|
|
2241
|
+
const parts = title2.split("/").map((p) => p.trim()).filter(Boolean);
|
|
2242
|
+
const name = parts.at(-1) ?? title2;
|
|
2243
|
+
const folders = parts.slice(0, -1);
|
|
2244
|
+
const page = item.docs ? `?path=/docs/${item.docs}` : item.first ? `?path=/story/${item.first}` : "";
|
|
2245
|
+
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" : ""}`;
|
|
2246
|
+
return {
|
|
2247
|
+
kind: folders.some((f) => PATTERN.test(f)) ? "pattern" : "component",
|
|
2248
|
+
name,
|
|
2249
|
+
title: title2,
|
|
2250
|
+
summary: `${title2} in ${host}'s Storybook, ${stories}`,
|
|
2251
|
+
ref: title2,
|
|
2252
|
+
url: `${target.base}/${page}`,
|
|
2253
|
+
digest: index.sha256,
|
|
2254
|
+
categories: folders
|
|
2255
|
+
};
|
|
2256
|
+
});
|
|
2257
|
+
return {
|
|
2258
|
+
source,
|
|
2259
|
+
license: "unknown",
|
|
2260
|
+
entries: itemsOf(source, "unknown", fetchedAt, inputs),
|
|
2261
|
+
skipped: [],
|
|
2262
|
+
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"]
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
// ../discover/src/ingest.ts
|
|
2267
|
+
var INGEST_CAP = 300;
|
|
2268
|
+
var IngestError = class extends Error {
|
|
2269
|
+
};
|
|
2270
|
+
var GITHUB_SHORT = /^github:([\w.-]+)\/([\w.-]+?)(?:\.git)?(?:#([^\s]+))?$/;
|
|
2271
|
+
function classifyLink(link, options = {}) {
|
|
2272
|
+
const text2 = link.trim();
|
|
2273
|
+
const short = GITHUB_SHORT.exec(text2);
|
|
2274
|
+
if (short) return { kind: "github", owner: short[1], repo: short[2], ref: short[3] ?? null, path: null };
|
|
2275
|
+
let url = null;
|
|
2276
|
+
try {
|
|
2277
|
+
url = new URL(text2);
|
|
2278
|
+
} catch {
|
|
2279
|
+
url = null;
|
|
2280
|
+
}
|
|
2281
|
+
if (!url) {
|
|
2282
|
+
if (/(^|\/)[^/]*\.md$/i.test(text2)) {
|
|
2283
|
+
if (!options.allowLocal) throw new IngestError(`${text2} is a local file: only the CLI reads those (youiyoux library ingest ${text2})`);
|
|
2284
|
+
return { kind: "design-md", location: resolve(text2), local: true };
|
|
2285
|
+
}
|
|
2286
|
+
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)`);
|
|
2287
|
+
}
|
|
2288
|
+
if (url.protocol !== "https:") throw new IngestError(`${url.protocol}//${url.host} is not https: ingestion reads public https links only`);
|
|
2289
|
+
const host = url.hostname.replace(/^www\./, "");
|
|
2290
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
2291
|
+
if (host === "github.com") {
|
|
2292
|
+
if (parts.length < 2) throw new IngestError(`${text2} names no repository: expected https://github.com/<owner>/<repo>`);
|
|
2293
|
+
const [owner, repo, kind, ref, ...rest] = parts;
|
|
2294
|
+
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 };
|
|
2295
|
+
return { kind: "github", owner, repo: repo.replace(/\.git$/, ""), ref: kind === "tree" && ref ? ref : null, path: kind === "tree" && rest.length > 0 ? rest.join("/") : null };
|
|
2296
|
+
}
|
|
2297
|
+
if (host === "figma.com" || host.endsWith(".figma.com")) {
|
|
2298
|
+
const at = parts.findIndex((p) => p === "design" || p === "file" || p === "proto");
|
|
2299
|
+
const key = at >= 0 ? parts[at + 1] : void 0;
|
|
2300
|
+
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`);
|
|
2301
|
+
return { kind: "figma", key };
|
|
2302
|
+
}
|
|
2303
|
+
if (/\.md$/i.test(url.pathname)) return { kind: "design-md", location: url.href, local: false };
|
|
2304
|
+
const base = `${url.origin}${url.pathname.replace(/\/(?:index\.html|iframe\.html)?$/, "")}`.replace(/\/+$/, "");
|
|
2305
|
+
return { kind: "storybook", base };
|
|
2306
|
+
}
|
|
2307
|
+
function itemsOf(source, license, fetchedAt, inputs) {
|
|
2308
|
+
const used = /* @__PURE__ */ new Set();
|
|
2309
|
+
const slug = sourceSlug(source.name.replace(/\//g, "-"));
|
|
2310
|
+
return inputs.map((input) => {
|
|
2311
|
+
const base = `lib:${slug}/${input.kind}/${nameSlug(input.name)}`;
|
|
2312
|
+
let id = base;
|
|
2313
|
+
for (let n = 2; used.has(id); n++) id = `${base}-${n}`;
|
|
2314
|
+
used.add(id);
|
|
2315
|
+
const draft = {
|
|
2316
|
+
schema: "youi.library/v1",
|
|
2317
|
+
id,
|
|
2318
|
+
name: input.name,
|
|
2319
|
+
...input.title && input.title !== input.name ? { title: input.title } : {},
|
|
2320
|
+
kind: input.kind,
|
|
2321
|
+
stage: "library",
|
|
2322
|
+
summary: summaryOf(input.summary, `${input.name} from ${source.name}`),
|
|
2323
|
+
source: { name: source.name, ...input.url ? { url: input.url } : {}, ingestedAt: fetchedAt, kind: source.kind, ref: input.ref },
|
|
2324
|
+
categories: [...new Set((input.categories ?? []).map((c) => c.toLowerCase()).filter(Boolean))],
|
|
2325
|
+
swapsWith: [],
|
|
2326
|
+
...input.url ? { provenance: { fetchedAt, url: input.url, sha256: input.digest, files: [...input.files ?? []], registryHealth: null } } : {},
|
|
2327
|
+
license,
|
|
2328
|
+
...input.install ? { install: input.install } : {}
|
|
2329
|
+
};
|
|
2330
|
+
const named = readNeed(input.name).concepts.length > 0 ? readNeed(input.name).concepts : readNeed(input.name.replace(/[^A-Za-z0-9]+/g, "").toLowerCase()).concepts;
|
|
2331
|
+
const roles = input.kind === "token" ? [] : (named.length > 0 ? named : input.title ? readNeed(input.title).concepts : []).map((c) => c.id);
|
|
2332
|
+
return LibraryEntry.parse({ ...draft, categories: [.../* @__PURE__ */ new Set([...roles, ...draft.categories])] });
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
function displayName(file) {
|
|
2336
|
+
const stem = basename2(file).replace(/\.[^.]+$/, "");
|
|
2337
|
+
return stem.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[-_\s]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
|
|
2338
|
+
}
|
|
2339
|
+
var digestOf = (value) => sha256(typeof value === "string" ? value : JSON.stringify(value));
|
|
2340
|
+
async function fetchJson(client, url, what) {
|
|
2341
|
+
const got = await client.json(url, false);
|
|
2342
|
+
if (!got.ok) throw new IngestError(`could not read ${what} (${url}): ${got.status === 404 ? "not found" : got.message}`);
|
|
2343
|
+
return got.value;
|
|
2344
|
+
}
|
|
2345
|
+
async function fetchText(client, url, what) {
|
|
2346
|
+
const got = await client.get(url, "text/plain, text/markdown, */*");
|
|
2347
|
+
if (!got.ok) throw new IngestError(`could not read ${what} (${url}): ${got.status === 404 ? "not found" : got.message}`);
|
|
2348
|
+
return { body: got.body, sha256: got.sha256, fetchedAt: got.fetchedAt };
|
|
2349
|
+
}
|
|
2350
|
+
async function ingestLink(link, options) {
|
|
2351
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
2352
|
+
const target = classifyLink(link, options);
|
|
2353
|
+
const fetchedAt = now().toISOString();
|
|
2354
|
+
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")));
|
|
2355
|
+
const over = result.entries.length - INGEST_CAP;
|
|
2356
|
+
return {
|
|
2357
|
+
...result,
|
|
2358
|
+
link: link.trim(),
|
|
2359
|
+
fetchedAt,
|
|
2360
|
+
entries: result.entries.slice(0, INGEST_CAP),
|
|
2361
|
+
notes: [...result.notes, ...over > 0 ? [`${over} more item${over === 1 ? "" : "s"} not read: at most ${INGEST_CAP} come from one link`] : []]
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
|
|
1958
2365
|
export {
|
|
1959
2366
|
CONCEPTS,
|
|
1960
2367
|
normalize,
|
|
@@ -1977,6 +2384,7 @@ export {
|
|
|
1977
2384
|
implicitDeps,
|
|
1978
2385
|
sourceSlug,
|
|
1979
2386
|
nameSlug,
|
|
2387
|
+
summaryOf,
|
|
1980
2388
|
docsLinks,
|
|
1981
2389
|
registryEntry,
|
|
1982
2390
|
HEADLESS,
|
|
@@ -2072,5 +2480,14 @@ export {
|
|
|
2072
2480
|
conceptOfName,
|
|
2073
2481
|
conceptOfPackage,
|
|
2074
2482
|
declaredPackages,
|
|
2075
|
-
buildGaps
|
|
2483
|
+
buildGaps,
|
|
2484
|
+
INGEST_CAP,
|
|
2485
|
+
IngestError,
|
|
2486
|
+
classifyLink,
|
|
2487
|
+
itemsOf,
|
|
2488
|
+
displayName,
|
|
2489
|
+
digestOf,
|
|
2490
|
+
fetchJson,
|
|
2491
|
+
fetchText,
|
|
2492
|
+
ingestLink
|
|
2076
2493
|
};
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
padEnd,
|
|
4
|
+
plural,
|
|
5
|
+
style,
|
|
6
|
+
workflowStateDir
|
|
7
|
+
} from "./chunk-FTF4MBV5.js";
|
|
8
|
+
import "./chunk-HII2I2PI.js";
|
|
9
|
+
import "./chunk-42YJX3W5.js";
|
|
10
|
+
import "./chunk-HIXGUALJ.js";
|
|
11
|
+
import "./chunk-S7UN5KQK.js";
|
|
12
|
+
import "./chunk-WNTS7ZO4.js";
|
|
13
|
+
|
|
14
|
+
// ../cli/src/ingest.ts
|
|
15
|
+
import { userInfo } from "node:os";
|
|
16
|
+
async function readLink(link, options) {
|
|
17
|
+
const { githubBuckets, ingestLink, PoliteClient } = await import("./src-7LETTRRF.js");
|
|
18
|
+
const env = options.env ?? process.env;
|
|
19
|
+
const token = env.GITHUB_TOKEN || void 0;
|
|
20
|
+
const figmaToken = env.FIGMA_TOKEN || void 0;
|
|
21
|
+
const client = new PoliteClient({ buckets: githubBuckets(token), ...token ? { token } : {}, ...figmaToken ? { figmaToken } : {} });
|
|
22
|
+
const result = await ingestLink(link, { client, allowLocal: options.allowLocal, figma: Boolean(figmaToken) });
|
|
23
|
+
return { link: result.link, source: result.source, license: result.license, fetchedAt: result.fetchedAt, entries: result.entries, skipped: result.skipped, notes: result.notes };
|
|
24
|
+
}
|
|
25
|
+
async function fileIngestDraft(project, reading, state) {
|
|
26
|
+
const { FileStore, RuleWorkflow } = await import("./src-CCGTRT5J.js");
|
|
27
|
+
const workflow = new RuleWorkflow({ projectRoot: project.root, store: new FileStore(workflowStateDir(project, state)), ...project.configFile ? { configPath: project.configFile } : {}, ...project.designOverride ? { designRoot: project.repo.root } : {}, ...project.rootOverride ? { productRoot: project.root } : {}, ingest: async () => reading });
|
|
28
|
+
const draft = await workflow.create({ kind: "person", name: userInfo().username }, { kind: "ingest", link: reading.link });
|
|
29
|
+
return { id: draft.id, problems: draft.problems };
|
|
30
|
+
}
|
|
31
|
+
function renderIngest(reading, filed) {
|
|
32
|
+
const licence = reading.license === "unknown" ? style.warn("licence unknown") : `licence ${reading.license.spdx}`;
|
|
33
|
+
const lines = [`${style.bold(reading.source.name)} ${style.dim(`\xB7 ${reading.source.kind}${reading.source.url ? ` \xB7 ${reading.source.url}` : ""}`)} \xB7 ${plural(reading.entries.length, "item")} \xB7 ${licence}`, ""];
|
|
34
|
+
const width = Math.min(28, Math.max(0, ...reading.entries.map((e) => e.name.length)));
|
|
35
|
+
for (const kind of ["component", "pattern", "token"]) {
|
|
36
|
+
const items = reading.entries.filter((e) => e.kind === kind);
|
|
37
|
+
if (items.length === 0) continue;
|
|
38
|
+
lines.push(style.bold(`${kind}s`));
|
|
39
|
+
for (const e of items) lines.push(` ${padEnd(e.name.length > width ? `${e.name.slice(0, width - 1)}\u2026` : e.name, width)} ${style.dim(e.categories.length ? e.categories.join(", ") : "no role")} ${e.summary}`);
|
|
40
|
+
lines.push("");
|
|
41
|
+
}
|
|
42
|
+
const SKIPPED_SHOWN = 10;
|
|
43
|
+
if (reading.skipped.length > 0) lines.push(style.bold(`not read (${reading.skipped.length})`), ...reading.skipped.slice(0, SKIPPED_SHOWN).map((s) => ` ${s.name} ${style.dim(s.reason)}`), ...reading.skipped.length > SKIPPED_SHOWN ? [style.dim(` \u2026 ${reading.skipped.length - SKIPPED_SHOWN} more (--json lists them all)`)] : [], "");
|
|
44
|
+
lines.push(...reading.notes.map((n) => style.dim(`\xB7 ${n}`)), "");
|
|
45
|
+
lines.push(filed ? `${style.ok("filed")} as draft ${filed.id}: keep or drop each item, then approve, in youiyoux ui > Changes` : `Next: youiyoux library ingest ${reading.link} --draft files it for review (nothing is written to the Library until a person approves)`);
|
|
46
|
+
return lines.join("\n");
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
fileIngestDraft,
|
|
50
|
+
readLink,
|
|
51
|
+
renderIngest
|
|
52
|
+
};
|