youiyoux 0.1.6 → 0.1.7
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-XAO3LS7M.js → chunk-3SQQNQDN.js} +1 -1
- package/dist/chunks/{chunk-BHJCAZXO.js → chunk-4DVBWN7H.js} +3 -3
- package/dist/chunks/{chunk-S57F4CMN.js → chunk-7P4UXAIY.js} +1 -1
- package/dist/chunks/{chunk-5YVH6NHT.js → chunk-BITS5O6H.js} +4 -4
- package/dist/chunks/{chunk-W2KFNGQP.js → chunk-N6RVMQCV.js} +710 -211
- package/dist/chunks/{chunk-RHXBYN4W.js → chunk-O66KB7BU.js} +20 -15
- package/dist/chunks/{chunk-5Y2MVIGH.js → chunk-SIXL67TZ.js} +2 -2
- package/dist/chunks/{chunk-VOSWAWKC.js → chunk-TV5UX3FE.js} +94 -23
- package/dist/chunks/{chunk-DP54SKL4.js → chunk-YWBQO7PO.js} +13 -5
- package/dist/chunks/{src-VKQJF7KR.js → src-4JGIPP5A.js} +4 -4
- package/dist/chunks/{src-6HTTOVZ5.js → src-BKPSGYS2.js} +5 -3
- package/dist/chunks/{src-7RRKYIAS.js → src-DYZIE4XZ.js} +2 -2
- package/dist/chunks/{src-YYI52B5R.js → src-J6GPLCVO.js} +2 -2
- package/dist/chunks/{src-DZWWMUDR.js → src-NWPGCDGH.js} +6 -6
- package/dist/chunks/{src-URZDH3QH.js → src-SDJCFXRI.js} +3 -1
- package/dist/chunks/{src-4VYLEZ3Z.js → src-WP66UHF7.js} +6 -2
- package/dist/hub/assets/{CandidatePanel-CnbK4S0j.js → CandidatePanel-D6Tenu7w.js} +1 -1
- package/dist/hub/assets/{CatalogScreen-B5OVL-AR.js → CatalogScreen-C1tvk0sB.js} +1 -1
- package/dist/hub/assets/{ChangesScreen-GwGqJwxN.js → ChangesScreen-D_6yzFGP.js} +1 -1
- package/dist/hub/assets/{CheatSheet-BLSOq-xa.js → CheatSheet-Ci7mG0sm.js} +1 -1
- package/dist/hub/assets/{CommandPalette-CAWQfdXJ.js → CommandPalette-C4twwce1.js} +1 -1
- package/dist/hub/assets/{DiscoverScreen-Tl_Z6mgY.js → DiscoverScreen-SOJMVxIq.js} +2 -2
- package/dist/hub/assets/{DraftEditor-yIDapXTs.js → DraftEditor-DGK0Fbp1.js} +2 -2
- package/dist/hub/assets/{GapsScreen-DXZAlm6v.js → GapsScreen-pYuuMGEF.js} +1 -1
- package/dist/hub/assets/{HomeScreen-CEpiVhOa.js → HomeScreen-5rm4q3ZO.js} +1 -1
- package/dist/hub/assets/{InventoryScreen-CEG3Pmw_.js → InventoryScreen-CMKKrpTo.js} +1 -1
- package/dist/hub/assets/{LibraryScreen-IaVnxrX9.js → LibraryScreen-CV8lwjT6.js} +1 -1
- package/dist/hub/assets/{Opened-DJyf4Hnj.js → Opened-BvT3JnuG.js} +1 -1
- package/dist/hub/assets/{Preview-C069zVMP.js → Preview-DNOsVJ9-.js} +1 -1
- package/dist/hub/assets/{RuleDetail-B4g2-TRe.js → RuleDetail-BNQbhncD.js} +1 -1
- package/dist/hub/assets/{RulesScreen-CAaXIWjO.js → RulesScreen-ByYZB2Rs.js} +1 -1
- package/dist/hub/assets/{ScreenImage-DcI7_G3O.js → ScreenImage-Dypjm8_v.js} +1 -1
- package/dist/hub/assets/{ScreensScreen-DwczCjZD.js → ScreensScreen-bgwSlccW.js} +1 -1
- package/dist/hub/assets/{SearchField-DaCpNAj6.js → SearchField-DyTJpGOP.js} +1 -1
- package/dist/hub/assets/{ServiceDown-Bv-BPXro.js → ServiceDown-f9NYtkr_.js} +1 -1
- package/dist/hub/assets/{SpecimenCapture-DlsjshVE.js → SpecimenCapture-DFoGMhOM.js} +1 -1
- package/dist/hub/assets/{Specimens-C-lHp7sr.js → Specimens-BGwJj05V.js} +2 -2
- package/dist/hub/assets/{StackScreen-DkmELsqR.js → StackScreen-q132_QG3.js} +1 -1
- package/dist/hub/assets/{StubScreens-Bt7lWLVK.js → StubScreens-DWhYvJku.js} +1 -1
- package/dist/hub/assets/StyleScreen-CcgUv16e.js +1 -0
- package/dist/hub/assets/{Tabs-1QQDS0I6.js → Tabs-Dy1Z02s7.js} +1 -1
- package/dist/hub/assets/{TokenDetail-DvCw6GqC.js → TokenDetail-DsYgNrPw.js} +1 -1
- package/dist/hub/assets/{TokensScreen-DoENV8Xy.js → TokensScreen-CTT0GbAH.js} +1 -1
- package/dist/hub/assets/{client-D7whEyep.js → client-CIo9EOzr.js} +1 -1
- package/dist/hub/assets/color-C6bxX90C.js +1 -0
- package/dist/hub/assets/contrast-B-uhyF_b.js +1 -0
- package/dist/hub/assets/{discover-XjJEViwk.js → discover-DGlqvyIe.js} +1 -1
- package/dist/hub/assets/{dist-BYQYaPBV.js → dist-2euSoh6x.js} +1 -1
- package/dist/hub/assets/{dist-Bk4srtDn.js → dist-CpzxT6kj.js} +1 -1
- package/dist/hub/assets/{dist-D0z7Bvsy.js → dist-D8LFTd5W.js} +1 -1
- package/dist/hub/assets/{family-tiles-CyTdy_cP.js → family-tiles-Cc7tTwyr.js} +1 -1
- package/dist/hub/assets/main-Bjq_U0cU.js +2 -0
- package/dist/hub/assets/{try-live-zX3CSd3P.js → try-live-C-dwP1zF.js} +1 -1
- package/dist/hub/assets/values-cLekxBZj.js +1 -0
- package/dist/hub/assets/{workflow-jpV8wh-U.js → workflow-CnSY-W41.js} +1 -1
- package/dist/hub/index.html +1 -1
- package/dist/hub-previews.js +5 -5
- package/dist/hub-server.js +3 -3
- package/dist/hub-snapshot.js +7 -7
- package/dist/schema/youi.config.schema.json +4 -0
- package/dist/schema/youi.project.schema.json +1 -1
- package/dist/youi.js +6 -6
- package/dist/youiyoux.js +6 -6
- package/package.json +4 -4
- package/dist/hub/assets/StyleScreen-tq1tbRBo.js +0 -1
- package/dist/hub/assets/contrast-BkNHUAPa.js +0 -1
- package/dist/hub/assets/main-B9CIkk5m.js +0 -2
- package/dist/hub/assets/values-DzxR_TlI.js +0 -1
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
captureText,
|
|
14
14
|
checkCapabilities,
|
|
15
15
|
chosenSession,
|
|
16
|
+
collectComponents,
|
|
16
17
|
compareToBaseline,
|
|
17
18
|
componentCard,
|
|
18
19
|
createBaseline,
|
|
@@ -50,9 +51,11 @@ import {
|
|
|
50
51
|
shotsText,
|
|
51
52
|
substituteVars,
|
|
52
53
|
vendoredPath,
|
|
53
|
-
whereUsed
|
|
54
|
-
|
|
54
|
+
whereUsed,
|
|
55
|
+
withFile
|
|
56
|
+
} from "./chunk-O66KB7BU.js";
|
|
55
57
|
import {
|
|
58
|
+
escapingLinks,
|
|
56
59
|
within
|
|
57
60
|
} from "./chunk-42YJX3W5.js";
|
|
58
61
|
import {
|
|
@@ -64,7 +67,7 @@ import {
|
|
|
64
67
|
libraryOfPackage,
|
|
65
68
|
packageOf,
|
|
66
69
|
playwrightCli
|
|
67
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-7P4UXAIY.js";
|
|
68
71
|
import {
|
|
69
72
|
MissingIndexError,
|
|
70
73
|
buildGaps,
|
|
@@ -88,13 +91,17 @@ import {
|
|
|
88
91
|
showFromCatalog,
|
|
89
92
|
storeLocation,
|
|
90
93
|
syncIndex
|
|
91
|
-
} from "./chunk-
|
|
94
|
+
} from "./chunk-3SQQNQDN.js";
|
|
92
95
|
import {
|
|
96
|
+
DL_EXT,
|
|
93
97
|
buildTokens,
|
|
94
98
|
compareParity,
|
|
95
99
|
cssValue,
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
importTokens,
|
|
101
|
+
parseCssColor,
|
|
102
|
+
parseDimension,
|
|
103
|
+
readRules
|
|
104
|
+
} from "./chunk-TV5UX3FE.js";
|
|
98
105
|
import {
|
|
99
106
|
Baseline,
|
|
100
107
|
DESIGN_CONFIG_FILE,
|
|
@@ -113,6 +120,7 @@ import {
|
|
|
113
120
|
findFiles,
|
|
114
121
|
findUp,
|
|
115
122
|
getSystem,
|
|
123
|
+
implementationsDir,
|
|
116
124
|
loadDesignRepo,
|
|
117
125
|
loadLibrary,
|
|
118
126
|
matchesAny,
|
|
@@ -128,17 +136,17 @@ import {
|
|
|
128
136
|
shortHash,
|
|
129
137
|
toPosix,
|
|
130
138
|
writeFileAtomic
|
|
131
|
-
} from "./chunk-
|
|
139
|
+
} from "./chunk-YWBQO7PO.js";
|
|
132
140
|
import {
|
|
133
141
|
__toESM
|
|
134
142
|
} from "./chunk-WNTS7ZO4.js";
|
|
135
143
|
|
|
136
144
|
// ../cli/src/main.ts
|
|
137
|
-
var
|
|
145
|
+
var import_yaml4 = __toESM(require_dist(), 1);
|
|
138
146
|
import { execFile as execFile5 } from "node:child_process";
|
|
139
147
|
import { existsSync as existsSync5 } from "node:fs";
|
|
140
|
-
import { appendFile as appendFile2, readFile as
|
|
141
|
-
import { basename as
|
|
148
|
+
import { appendFile as appendFile2, readFile as readFile10, rm as rm6, rmdir as rmdir3 } from "node:fs/promises";
|
|
149
|
+
import { basename as basename5, dirname as dirname12, join as join24, relative as relative10, resolve as resolve14 } from "node:path";
|
|
142
150
|
import { parseArgs, promisify as promisify5 } from "node:util";
|
|
143
151
|
|
|
144
152
|
// ../cli/src/check.ts
|
|
@@ -156,7 +164,7 @@ import { join as join2, relative, resolve } from "node:path";
|
|
|
156
164
|
|
|
157
165
|
// ../cli/src/command.ts
|
|
158
166
|
var COMMAND = "youiyoux";
|
|
159
|
-
var SUBCOMMANDS = ["show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "init", "sync", "link", "unlink", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "discover", "index", "report", "render", "screens", "requests", "detect", "build", "parity", "schema", "inventory", "gaps", "help"];
|
|
167
|
+
var SUBCOMMANDS = ["show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "init", "adopt", "system", "sync", "link", "unlink", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "discover", "index", "report", "render", "screens", "requests", "detect", "build", "parity", "schema", "inventory", "gaps", "help"];
|
|
160
168
|
|
|
161
169
|
// ../cli/src/agents.ts
|
|
162
170
|
var import_yaml = __toESM(require_dist(), 1);
|
|
@@ -206,7 +214,7 @@ function enumSummary(contract) {
|
|
|
206
214
|
return Object.entries(contract.props).filter(([, spec]) => spec.type === "enum").map(([name, spec]) => `${name}: ${spec.values.join("|")}`).join("; ");
|
|
207
215
|
}
|
|
208
216
|
function importHint(config) {
|
|
209
|
-
return config.importFrom ? `\`${config.importFrom}\`` : `\`${config.componentsDir}\` (by a relative path from your file)`;
|
|
217
|
+
return config.importFrom ? `\`${withFile(config.importFrom)}\`` : `\`${config.componentsDir}\` (by a relative path from your file)`;
|
|
210
218
|
}
|
|
211
219
|
function agentsBlock(project2) {
|
|
212
220
|
const { config, system } = project2;
|
|
@@ -260,7 +268,7 @@ function frontmatter(project2, build) {
|
|
|
260
268
|
if (dark !== colors[key(t)]) colors[`${key(t)}-dark`] = dark;
|
|
261
269
|
}
|
|
262
270
|
const typography = {};
|
|
263
|
-
for (const t of byGroup("typography")) {
|
|
271
|
+
for (const t of byGroup("typography").filter((t2) => t2.type === "typography")) {
|
|
264
272
|
const v = t.light;
|
|
265
273
|
typography[key(t)] = {
|
|
266
274
|
fontFamily: cssValue("fontFamily", v.fontFamily),
|
|
@@ -466,8 +474,9 @@ async function initProject(options) {
|
|
|
466
474
|
}
|
|
467
475
|
notes.push(`owned components: ${componentsDir}`);
|
|
468
476
|
const alias = await aliasRoot(root);
|
|
469
|
-
const
|
|
470
|
-
|
|
477
|
+
const barrel = (await Promise.all(["index.ts", "index.tsx", "index.js"].map((f) => exists(join2(root, componentsDir, f))))).some(Boolean);
|
|
478
|
+
const importFrom = alias !== void 0 && componentsDir.startsWith(alias) ? `@/${componentsDir.slice(alias.length)}${barrel ? "" : "/{file}"}` : void 0;
|
|
479
|
+
if (importFrom) notes.push(`imports use the @/ alias: ${importFrom}${barrel ? "" : " (each component from its own file; no index barrel)"}`);
|
|
471
480
|
const include = (await Promise.all(
|
|
472
481
|
[
|
|
473
482
|
["app", "app/**/*.{ts,tsx,css}"],
|
|
@@ -491,7 +500,8 @@ async function initProject(options) {
|
|
|
491
500
|
componentsDir,
|
|
492
501
|
...importFrom ? { importFrom } : {},
|
|
493
502
|
routes: expo ? { strategy: "expo-router", appDir } : { strategy: "none" },
|
|
494
|
-
...prefix ? { outputs: { cssVarPrefix: prefix } } : {}
|
|
503
|
+
...options.outputs ? { outputs: options.outputs } : prefix ? { outputs: { cssVarPrefix: prefix } } : {},
|
|
504
|
+
...options.tokenSources && options.tokenSources.length > 0 ? { tokenSources: [...options.tokenSources] } : {}
|
|
495
505
|
});
|
|
496
506
|
await writeFileAtomic(file, `${JSON.stringify(config, null, 2)}
|
|
497
507
|
`);
|
|
@@ -617,7 +627,7 @@ var DRIFT_SEVERITY = { "near-duplicate": "warn", "hardcoded-value": "error", "of
|
|
|
617
627
|
async function importOf(project2) {
|
|
618
628
|
const alias = await aliasRoot(project2.root, project2.config.tsconfig);
|
|
619
629
|
return (file, fromFile) => {
|
|
620
|
-
if (project2.system && file.startsWith(`${project2.config.componentsDir}/`)) return importSpecifier(project2.config, fromFile);
|
|
630
|
+
if (project2.system && file.startsWith(`${project2.config.componentsDir}/`)) return importSpecifier(project2.config, fromFile, file);
|
|
621
631
|
if (alias !== void 0 && file.startsWith(alias)) return `@/${file.slice(alias.length).replace(/\.(tsx?|jsx?|mjs)$/, "").replace(/\/index$/, "")}`;
|
|
622
632
|
const path = posix.relative(posix.dirname(fromFile), file).replace(/\.(tsx?|jsx?|mjs|astro)$/, "").replace(/\/index$/, "");
|
|
623
633
|
return path.startsWith(".") ? path : `./${path}`;
|
|
@@ -634,7 +644,7 @@ async function chosenLibraries(project2, added, notes) {
|
|
|
634
644
|
const chosen = /* @__PURE__ */ new Map();
|
|
635
645
|
for (const { contract } of project2.system?.components ?? []) {
|
|
636
646
|
const type = EXPORT_TYPES[contract.name];
|
|
637
|
-
if (type && !chosen.has(type)) chosen.set(type, { library: "system", label: `the System's ${contract.name} (${contract.id})`, fix: (from) => `import { ${contract.name} } from "${importSpecifier(project2.config, from)}"` });
|
|
647
|
+
if (type && !chosen.has(type)) chosen.set(type, { library: "system", label: `the System's ${contract.name} (${contract.id})`, fix: (from) => `import { ${contract.name} } from "${importSpecifier(project2.config, from, contract.source.file)}"` });
|
|
638
648
|
}
|
|
639
649
|
let hits;
|
|
640
650
|
try {
|
|
@@ -917,23 +927,24 @@ var Writer = class {
|
|
|
917
927
|
}
|
|
918
928
|
async link(file, target) {
|
|
919
929
|
const path = this.path(file);
|
|
920
|
-
const
|
|
921
|
-
if (
|
|
922
|
-
if (
|
|
930
|
+
const stat4 = await lstat(path).catch(() => null);
|
|
931
|
+
if (stat4?.isSymbolicLink() && await readlink(path) === target) return this.record({ file, role: "agents", action: "unchanged" });
|
|
932
|
+
if (stat4 && !stat4.isSymbolicLink()) return this.record({ file, role: "agents", action: "skipped", detail: "a real directory is in the way" });
|
|
923
933
|
if (!this.options.dryRun) {
|
|
924
934
|
await mkdir(dirname2(path), { recursive: true });
|
|
925
|
-
if (
|
|
935
|
+
if (stat4) await rm(path);
|
|
926
936
|
await symlink(target, path);
|
|
927
937
|
}
|
|
928
|
-
this.record({ file, role: "agents", action:
|
|
938
|
+
this.record({ file, role: "agents", action: stat4 ? "updated" : "created", detail: `\u2192 ${target}` });
|
|
929
939
|
}
|
|
930
940
|
};
|
|
931
941
|
async function syncProject(project2, options = {}) {
|
|
932
942
|
const { config, system, repo } = project2;
|
|
933
943
|
const previous = await readLock(project2.root);
|
|
934
944
|
const writer = new Writer(project2, options, previous);
|
|
945
|
+
const implementations = implementationsDir(system.config);
|
|
935
946
|
for (const { contract } of system.components) {
|
|
936
|
-
if (await readTextOrNull(join6(repo.root,
|
|
947
|
+
if (await readTextOrNull(join6(repo.root, implementations, contract.source.file)) === null) throw new Error(`${implementations}/${contract.source.file} is named by ${contract.id} but does not exist; nothing was written`);
|
|
937
948
|
}
|
|
938
949
|
const build = await buildTokens(repo.root, system.config, config.outputs, { contracts: system.components.map((c) => c.contract) });
|
|
939
950
|
for (const file of build.files) await writer.generated(`${config.outputs.dir}/${file.path}`, file.content, "tokens");
|
|
@@ -952,11 +963,15 @@ async function syncProject(project2, options = {}) {
|
|
|
952
963
|
for (const support of system.config.support) if (!sources.has(support)) sources.set(support, { elements: system.components.map((c) => c.contract.id), version: system.config.version });
|
|
953
964
|
const upstreams = /* @__PURE__ */ new Map();
|
|
954
965
|
for (const source of sources.keys()) {
|
|
955
|
-
const upstream = await readTextOrNull(join6(repo.root,
|
|
956
|
-
if (upstream === null) throw new Error(`${
|
|
966
|
+
const upstream = await readTextOrNull(join6(repo.root, implementations, source));
|
|
967
|
+
if (upstream === null) throw new Error(`${implementations}/${source} is named by a contract but does not exist`);
|
|
957
968
|
upstreams.set(source, upstream);
|
|
958
969
|
}
|
|
959
|
-
for (const [source, { elements, version }] of sources)
|
|
970
|
+
for (const [source, { elements, version }] of sources) {
|
|
971
|
+
const file = vendoredPath(project2, source);
|
|
972
|
+
if (await sameFile(join6(repo.root, implementations, source), join6(project2.root, file))) writer.record({ file, role: "component", action: "unchanged", detail: "the system's own implementation, in this project" });
|
|
973
|
+
else await writer.owned(file, upstreams.get(source), elements, version);
|
|
974
|
+
}
|
|
960
975
|
await writer.retire();
|
|
961
976
|
const lock = {
|
|
962
977
|
schema: "youi.lock/v1",
|
|
@@ -970,6 +985,10 @@ async function syncProject(project2, options = {}) {
|
|
|
970
985
|
`);
|
|
971
986
|
return { actions: writer.actions, lock, unknownTools: unknownToolMentions(project2) };
|
|
972
987
|
}
|
|
988
|
+
async function sameFile(a, b) {
|
|
989
|
+
const [x, y] = await Promise.all([realpath(a).catch(() => null), realpath(b).catch(() => null)]);
|
|
990
|
+
return x !== null && x === y;
|
|
991
|
+
}
|
|
973
992
|
function mcpArgs(project2) {
|
|
974
993
|
const rel = (path) => relative3(project2.root, path) || ".";
|
|
975
994
|
return [
|
|
@@ -1760,18 +1779,18 @@ async function runClaudeHook(input, target = {}) {
|
|
|
1760
1779
|
try {
|
|
1761
1780
|
const project2 = await load(target, dirname5(file));
|
|
1762
1781
|
const real = (path) => realpath3(path).catch(() => path);
|
|
1763
|
-
const
|
|
1764
|
-
if (!isChecked(project2,
|
|
1765
|
-
const found = await changeFor(() => workingTreeChange(project2, void 0, [
|
|
1782
|
+
const relative11 = relPath(await real(project2.root), await real(file));
|
|
1783
|
+
if (!isChecked(project2, relative11)) return SILENT;
|
|
1784
|
+
const found = await changeFor(() => workingTreeChange(project2, void 0, [relative11]));
|
|
1766
1785
|
if (found.problem !== void 0) return claudeContext(found.problem, project2.root);
|
|
1767
1786
|
const { change } = found;
|
|
1768
1787
|
const result = configured(project2) ? await runCheck(project2, { files: [file], lock: false, baseline: await baselineFor(project2, target), diff: change }) : await runRepoCheck(project2, change);
|
|
1769
1788
|
const lines = findingLines(result, project2.root);
|
|
1770
|
-
const see = COMPONENT_FILE.test(
|
|
1771
|
-
if (lines.length === 0) return see ? claudeContext(`youiyoux: ${
|
|
1789
|
+
const see = COMPONENT_FILE.test(relative11) ? `See it: youiyoux render ${quoted(relative11)} (or the MCP tool ui_render), then open the images: light and dark, desktop and phone, accessibility issues and the diff against the base.` : null;
|
|
1790
|
+
if (lines.length === 0) return see ? claudeContext(`youiyoux: ${relative11} passes the design-system check. ${see}`, project2.root) : SILENT;
|
|
1772
1791
|
const older = "violations" in result ? result.violations.filter((v) => !isAdded(change.added, v.file, v.line)).length : 0;
|
|
1773
1792
|
const known = older > 0 ? ` (${older} older findings on unchanged lines omitted)` : "";
|
|
1774
|
-
return claudeContext(`youiyoux design-system check (advisory) on ${
|
|
1793
|
+
return claudeContext(`youiyoux design-system check (advisory) on ${relative11}${known}:
|
|
1775
1794
|
${lines.join("\n")}${!configured(project2) ? `
|
|
1776
1795
|
${result.diff?.notes.join("\n")}` : ""}
|
|
1777
1796
|
Fix these before finishing (each "Fix:" says what to write); \`youiyoux show <component>\` and the youiyoux MCP tools give the legal values. CI runs the authoritative check.${see ? `
|
|
@@ -1919,30 +1938,30 @@ var serialize = ({ data, indent, newline }) => `${JSON.stringify(data, null, ind
|
|
|
1919
1938
|
async function installClaudeMcp(repoRoot, server) {
|
|
1920
1939
|
const file = join11(repoRoot, CLAUDE_MCP);
|
|
1921
1940
|
const before = await readTextOrNull(file);
|
|
1922
|
-
const
|
|
1923
|
-
const servers =
|
|
1924
|
-
const next2 = { ...
|
|
1941
|
+
const json4 = readJson2(file, before);
|
|
1942
|
+
const servers = json4.data.mcpServers ?? {};
|
|
1943
|
+
const next2 = { ...json4, data: { ...json4.data, mcpServers: { ...servers, [MCP_NAME]: { command: server.command, args: server.args } } } };
|
|
1925
1944
|
return write(file, serialize(next2), before, `Claude Code starts the ${MCP_NAME} MCP server in this repo (it asks once before trusting a project .mcp.json); the command uses the installed tool or portable npx invocation; youiyoux sync writes the same entry`);
|
|
1926
1945
|
}
|
|
1927
1946
|
async function removeClaudeMcp(repoRoot) {
|
|
1928
1947
|
const file = join11(repoRoot, CLAUDE_MCP);
|
|
1929
1948
|
const before = await readTextOrNull(file);
|
|
1930
1949
|
if (before === null) return { file, action: "unchanged", guarantee: `no ${MCP_NAME} server here` };
|
|
1931
|
-
const
|
|
1932
|
-
const servers = { ...
|
|
1950
|
+
const json4 = readJson2(file, before);
|
|
1951
|
+
const servers = { ...json4.data.mcpServers ?? {} };
|
|
1933
1952
|
if (!(MCP_NAME in servers)) return { file, action: "unchanged", guarantee: `no ${MCP_NAME} server here` };
|
|
1934
1953
|
delete servers[MCP_NAME];
|
|
1935
|
-
const { mcpServers: _, ...rest } =
|
|
1936
|
-
const data = Object.keys(servers).length > 0 ? { ...
|
|
1937
|
-
return removeOrWrite(file, Object.keys(data).length > 0 ? serialize({ ...
|
|
1954
|
+
const { mcpServers: _, ...rest } = json4.data;
|
|
1955
|
+
const data = Object.keys(servers).length > 0 ? { ...json4.data, mcpServers: servers } : rest;
|
|
1956
|
+
return removeOrWrite(file, Object.keys(data).length > 0 ? serialize({ ...json4, data }) : "", `${MCP_NAME} removed; other servers kept`);
|
|
1938
1957
|
}
|
|
1939
1958
|
var tomlString = (value) => JSON.stringify(value);
|
|
1940
1959
|
function codexBlock(server) {
|
|
1941
1960
|
return [TOML_BEGIN, `[mcp_servers.${MCP_NAME}]`, `command = ${tomlString(server.command)}`, `args = [${server.args.map(tomlString).join(", ")}]`, TOML_END].join("\n");
|
|
1942
1961
|
}
|
|
1943
1962
|
function foreignTable(text) {
|
|
1944
|
-
const
|
|
1945
|
-
return new RegExp(`^\\s*\\[\\s*mcp_servers\\s*\\.\\s*"?${MCP_NAME}"?\\s*\\]`, "m").test(
|
|
1963
|
+
const inside3 = withoutBlock(text, TOML_BEGIN, TOML_END) ?? text;
|
|
1964
|
+
return new RegExp(`^\\s*\\[\\s*mcp_servers\\s*\\.\\s*"?${MCP_NAME}"?\\s*\\]`, "m").test(inside3);
|
|
1946
1965
|
}
|
|
1947
1966
|
async function installCodexMcp(repoRoot, server) {
|
|
1948
1967
|
const file = join11(repoRoot, CODEX_CONFIG);
|
|
@@ -2066,7 +2085,7 @@ import { homedir, userInfo } from "node:os";
|
|
|
2066
2085
|
import { join as join12, resolve as resolve6 } from "node:path";
|
|
2067
2086
|
async function swapImpact(candidate, family) {
|
|
2068
2087
|
try {
|
|
2069
|
-
const { discoverIndexPath: discoverIndexPath2, sharedStore: sharedStore2 } = await import("./src-
|
|
2088
|
+
const { discoverIndexPath: discoverIndexPath2, sharedStore: sharedStore2 } = await import("./src-DYZIE4XZ.js");
|
|
2070
2089
|
const path = discoverIndexPath2();
|
|
2071
2090
|
if (!process.env.DATABASE_URL && !existsSync(path)) return null;
|
|
2072
2091
|
const impact = await (await sharedStore2(path, process.env, { readOnly: true })).swapImpact(candidate, family);
|
|
@@ -2077,7 +2096,7 @@ async function swapImpact(candidate, family) {
|
|
|
2077
2096
|
}
|
|
2078
2097
|
async function libraryLicence(id) {
|
|
2079
2098
|
try {
|
|
2080
|
-
const { discoverCacheDir: discoverCacheDir2, findListing } = await import("./src-
|
|
2099
|
+
const { discoverCacheDir: discoverCacheDir2, findListing } = await import("./src-DYZIE4XZ.js");
|
|
2081
2100
|
const { entry } = await findListing(discoverCacheDir2(void 0, process.env), id);
|
|
2082
2101
|
return entry.id === id ? entry.license : null;
|
|
2083
2102
|
} catch {
|
|
@@ -2085,7 +2104,7 @@ async function libraryLicence(id) {
|
|
|
2085
2104
|
}
|
|
2086
2105
|
}
|
|
2087
2106
|
async function startRuleEditor(options) {
|
|
2088
|
-
const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-
|
|
2107
|
+
const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-4JGIPP5A.js");
|
|
2089
2108
|
const projectRoot = resolve6(options.project);
|
|
2090
2109
|
const load2 = { ...options.config ? { configPath: resolve6(options.config) } : {}, ...options.design ? { designRoot: resolve6(options.design) } : {}, ...options.root ? { productRoot: resolve6(options.root) } : {} };
|
|
2091
2110
|
const project2 = await loadProject(projectRoot, load2);
|
|
@@ -2217,15 +2236,409 @@ function renderRequests(briefs, status, unreadable) {
|
|
|
2217
2236
|
return lines.join("\n\n");
|
|
2218
2237
|
}
|
|
2219
2238
|
|
|
2239
|
+
// ../cli/src/adopt.ts
|
|
2240
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
2241
|
+
import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute5, join as join14, posix as posix3, relative as relative6, resolve as resolve8 } from "node:path";
|
|
2242
|
+
var import_yaml2 = __toESM(require_dist(), 1);
|
|
2243
|
+
var OUT_DIR = "design/build";
|
|
2244
|
+
var DARK_BLOCKS = [
|
|
2245
|
+
{ selector: ".dark" },
|
|
2246
|
+
{ selector: ":root.dark" },
|
|
2247
|
+
{ selector: '[data-theme="dark"]' },
|
|
2248
|
+
{ selector: ':root[data-theme="dark"]' },
|
|
2249
|
+
{ selector: ":root", outer: "@media (prefers-color-scheme: dark)" },
|
|
2250
|
+
{ selector: ':root:not([data-theme="light"])', outer: "@media (prefers-color-scheme: dark)" }
|
|
2251
|
+
];
|
|
2252
|
+
var kebab = (name) => name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
2253
|
+
var pascal = (file) => basename2(file).replace(/\.[^.]+$/, "").split(/[-_.]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
2254
|
+
var json = (value) => `${JSON.stringify(value, null, 2)}
|
|
2255
|
+
`;
|
|
2256
|
+
function slugOf(name) {
|
|
2257
|
+
return name.replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^[^a-z]+/, "").replace(/-+$/, "") || "app";
|
|
2258
|
+
}
|
|
2259
|
+
function inside2(root, path, what) {
|
|
2260
|
+
const normal = posix3.normalize(path.split("\\").join("/")).replace(/^\.\//, "").replace(/\/+$/, "");
|
|
2261
|
+
if (isAbsolute5(normal) || normal === ".." || normal.startsWith("../") || !within(root, resolve8(root, normal))) throw new Error(`${what} ${JSON.stringify(path)} leads outside ${root}: adopt reads and writes only inside the product`);
|
|
2262
|
+
return normal;
|
|
2263
|
+
}
|
|
2264
|
+
function exportedNames(source) {
|
|
2265
|
+
const names = /* @__PURE__ */ new Set();
|
|
2266
|
+
for (const match of source.matchAll(/^export\s+(?:declare\s+)?(?:const|let|var|function\*?|class|async\s+function)\s+([A-Za-z_$][\w$]*)/gm)) names.add(match[1]);
|
|
2267
|
+
for (const match of source.matchAll(/^export\s+\{([^}]*)\}/gm)) {
|
|
2268
|
+
for (const part of match[1].split(",")) {
|
|
2269
|
+
const spec = part.trim();
|
|
2270
|
+
if (!spec || spec.startsWith("type ")) continue;
|
|
2271
|
+
names.add(spec.split(/\s+as\s+/).at(-1).trim());
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
return [...names];
|
|
2275
|
+
}
|
|
2276
|
+
function defaultVariants(source) {
|
|
2277
|
+
const body = /defaultVariants\s*:\s*\{([^}]*)\}/.exec(source)?.[1] ?? "";
|
|
2278
|
+
return Object.fromEntries([...body.matchAll(/([A-Za-z_$][\w$]*)\s*:\s*["'`]([^"'`]+)["'`]/g)].map((m) => [m[1], m[2]]));
|
|
2279
|
+
}
|
|
2280
|
+
function propsOf(facts, defaults) {
|
|
2281
|
+
const props = {};
|
|
2282
|
+
for (const prop of facts?.props ?? []) {
|
|
2283
|
+
if (prop.values && prop.values.length > 0) props[prop.name] = { type: "enum", values: [...prop.values], required: false, ...defaults[prop.name] && prop.values.includes(defaults[prop.name]) ? { default: defaults[prop.name] } : {} };
|
|
2284
|
+
else if (prop.name === "className") props[prop.name] = { type: "string", required: false, description: "Extra classes, merged last." };
|
|
2285
|
+
else if (prop.name === "children") props[prop.name] = { type: "node", required: false };
|
|
2286
|
+
else if (/^on[A-Z]/.test(prop.name)) props[prop.name] = { type: "function", required: false };
|
|
2287
|
+
}
|
|
2288
|
+
return props;
|
|
2289
|
+
}
|
|
2290
|
+
function generatedOf(file, css) {
|
|
2291
|
+
const head = css.slice(0, 400);
|
|
2292
|
+
if (!head.includes(GENERATED_MARK)) return null;
|
|
2293
|
+
const from = /@generated by youi from the (.+?) design language v(\d+\.\d+\.\d+(?:-[\w.-]+?)?)\./.exec(head);
|
|
2294
|
+
return { file, count: rootVariables(css).length, system: from?.[1] ?? null, version: from?.[2] ?? null };
|
|
2295
|
+
}
|
|
2296
|
+
async function stylesheetOf(root, asked, fork) {
|
|
2297
|
+
const generated = [];
|
|
2298
|
+
let forked = null;
|
|
2299
|
+
const usable = async (file) => {
|
|
2300
|
+
const css = await readTextOrNull(join14(root, file));
|
|
2301
|
+
if (css === null) return false;
|
|
2302
|
+
const made = generatedOf(file, css);
|
|
2303
|
+
if (made && !generated.some((g) => g.file === file)) generated.push(made);
|
|
2304
|
+
if (made && fork) forked = made;
|
|
2305
|
+
return !made || fork;
|
|
2306
|
+
};
|
|
2307
|
+
if (asked) {
|
|
2308
|
+
if (!await usable(asked) && generated.length > 0) throw new Error(`${asked} is generated by youiyoux sync${generated[0].system ? ` from ${generated[0].system}` : ""}: name the stylesheet the product writes its own variables in, or take that System's values as the product's own with youiyoux adopt --fork`);
|
|
2309
|
+
return { file: asked, generated, forked };
|
|
2310
|
+
}
|
|
2311
|
+
const shadcn = (await readJson(join14(root, "components.json")))?.tailwind?.css;
|
|
2312
|
+
if (typeof shadcn === "string" && await usable(shadcn)) return { file: posix3.normalize(shadcn), generated, forked };
|
|
2313
|
+
let best = null;
|
|
2314
|
+
for (const file of await findFiles(root, ["**/*.css"], ["**/build/**", "**/out/**", "**/.next/**", `${OUT_DIR}/**`])) {
|
|
2315
|
+
const css = await readTextOrNull(join14(root, file)) ?? "";
|
|
2316
|
+
const count = rootVariables(css).length;
|
|
2317
|
+
if (count === 0) continue;
|
|
2318
|
+
const made = generatedOf(file, css);
|
|
2319
|
+
if (made && !generated.some((g) => g.file === file)) generated.push(made);
|
|
2320
|
+
if (made && !fork) continue;
|
|
2321
|
+
if (!best || count > best.count) best = { file, count, made };
|
|
2322
|
+
}
|
|
2323
|
+
return { file: best?.file ?? null, generated, forked: best?.made ?? forked };
|
|
2324
|
+
}
|
|
2325
|
+
function rootVariables(css) {
|
|
2326
|
+
return readRules(css).filter((r) => r.preludes.length === 1 && r.preludes[0] === ":root").flatMap((r) => r.declarations).filter((d) => d.property.startsWith("--") && !d.property.startsWith("---")).map((d) => ({ name: d.property.slice(2), value: d.value }));
|
|
2327
|
+
}
|
|
2328
|
+
var NOT_A_PREFIX = /* @__PURE__ */ new Set(["color", "colors", "bg", "fg", "radius", "rounded", "font", "text", "shadow", "space", "spacing", "size", "ease", "easing", "duration", "motion", "z", "leading", "tracking", "border", "ring", "chart", "sidebar", "background", "foreground", "primary", "secondary", "muted", "accent", "destructive", "card", "popover", "input", "typography", "brand", "gray", "grey", "neutral"]);
|
|
2329
|
+
function commonPrefix(names) {
|
|
2330
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2331
|
+
for (const name of names) if (name.includes("-")) counts.set(name.split("-")[0], (counts.get(name.split("-")[0]) ?? 0) + 1);
|
|
2332
|
+
const [word, count] = [...counts].sort((a, b) => b[1] - a[1])[0] ?? ["", 0];
|
|
2333
|
+
return count >= 3 && count >= names.length * 0.8 && /^[a-z][a-z0-9]*$/.test(word) && !NOT_A_PREFIX.has(word) ? word : "";
|
|
2334
|
+
}
|
|
2335
|
+
function lengthPath(name, value) {
|
|
2336
|
+
if (parseDimension(value) === null && !/^var\(/.test(value)) return null;
|
|
2337
|
+
if (/radius/.test(name)) return `radius.${name.replace(/^radius-(?=.)/, "")}`;
|
|
2338
|
+
if (/^typography-/.test(name)) return `typography.${name.slice("typography-".length)}`;
|
|
2339
|
+
if (/^(?:text|font)-|-(?:font-size|line-height|letter-spacing|font-weight)$/.test(name)) return `typography.${name}`;
|
|
2340
|
+
return null;
|
|
2341
|
+
}
|
|
2342
|
+
function variableNames(tree, path = []) {
|
|
2343
|
+
return Object.entries(tree).flatMap(([key2, node]) => {
|
|
2344
|
+
if (key2.startsWith("$")) return [];
|
|
2345
|
+
if (node && typeof node === "object" && "$value" in node) {
|
|
2346
|
+
const css = node.$extensions?.[DL_EXT]?.css;
|
|
2347
|
+
return [css ?? [...path, key2].join("-")];
|
|
2348
|
+
}
|
|
2349
|
+
return variableNames(node, [...path, key2]);
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
function moveDeclarations(css, blocks, moved, importPath) {
|
|
2353
|
+
const rules = readRules(css).filter((r) => blocks.some((b) => r.preludes.at(-1) === b.selector && (b.outer ? r.preludes.length === 2 && r.preludes[0] === b.outer : r.preludes.length === 1)));
|
|
2354
|
+
if (rules.length === 0) return null;
|
|
2355
|
+
const lines = css.split("\n");
|
|
2356
|
+
const drop = /* @__PURE__ */ new Set();
|
|
2357
|
+
for (const rule2 of rules) {
|
|
2358
|
+
for (const d of rule2.declarations) {
|
|
2359
|
+
const line = lines[d.line - 1] ?? "";
|
|
2360
|
+
if (moved.has(d.property.slice(2)) && new RegExp(`^\\s*${d.property.replace(/[-]/g, "\\-")}\\s*:[^;]*;?\\s*$`).test(line)) drop.add(d.line - 1);
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
if (drop.size === 0) return null;
|
|
2364
|
+
let content = lines.filter((_, i) => !drop.has(i)).join("\n");
|
|
2365
|
+
for (const block of blocks) content = content.replace(new RegExp(`(^|\\n)[ \\t]*${block.selector.replace(/[.[\]()"=:]/g, "\\$&")}\\s*\\{\\s*\\}[ \\t]*(?=\\n|$)`, "g"), "");
|
|
2366
|
+
for (const outer of new Set(blocks.flatMap((b) => b.outer ? [b.outer] : []))) content = content.replace(new RegExp(`(^|\\n)[ \\t]*${outer.replace(/[.[\]()"=:]/g, "\\$&")}\\s*\\{\\s*\\}[ \\t]*(?=\\n|$)`, "g"), "");
|
|
2367
|
+
const statement = `@import "${importPath}";`;
|
|
2368
|
+
const imports = [...content.matchAll(/^@import[^;]*;[ \t]*$/gm)];
|
|
2369
|
+
const at = imports.length > 0 ? imports.at(-1).index + imports.at(-1)[0].length : 0;
|
|
2370
|
+
content = at > 0 ? `${content.slice(0, at)}
|
|
2371
|
+
${statement}${content.slice(at)}` : `${statement}
|
|
2372
|
+
${content}`;
|
|
2373
|
+
return { content: content.replace(/\n{3,}/g, "\n\n"), moved: drop.size };
|
|
2374
|
+
}
|
|
2375
|
+
async function planAdoption(options) {
|
|
2376
|
+
const root = options.root;
|
|
2377
|
+
const pkg = await readJson(join14(root, "package.json"));
|
|
2378
|
+
const name = typeof pkg?.name === "string" ? pkg.name : basename2(root);
|
|
2379
|
+
const id = options.system ?? slugOf(name);
|
|
2380
|
+
if (!/^[a-z][a-z0-9-]*$/.test(id)) throw new Error(`"${id}" cannot be a system id: use lowercase letters, digits and hyphens, starting with a letter (--system <id>)`);
|
|
2381
|
+
const notes = [];
|
|
2382
|
+
const facts = await collectComponents(root);
|
|
2383
|
+
const chosen = options.componentsDir ?? facts.shadcnUi ?? await uiFolder(root) ?? facts.componentsDir;
|
|
2384
|
+
const componentsDir = chosen ? inside2(root, chosen, "the components folder") : null;
|
|
2385
|
+
if (!componentsDir || !await exists(join14(root, componentsDir))) throw new Error("no folder of components found: pass --components <dir> (the folder your app imports its UI components from)");
|
|
2386
|
+
const inDir = (file) => posix3.dirname(file) === componentsDir;
|
|
2387
|
+
const files = (await findFiles(root, [`${componentsDir}/*.{ts,tsx,js,jsx}`])).filter((f) => !/^index\./.test(posix3.basename(f)) && !/\.(test|spec|stories|d)\.[^.]+$/.test(f));
|
|
2388
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
2389
|
+
for (const c of facts.components) if (c.definition.kind === "react" && inDir(c.definition.file)) byFile.set(c.definition.file, [...byFile.get(c.definition.file) ?? [], c]);
|
|
2390
|
+
const out = [];
|
|
2391
|
+
const components = [];
|
|
2392
|
+
const support = [];
|
|
2393
|
+
const RULES = ["color.no-raw-color", "tokens.no-arbitrary-values", "components.canonical-import"].map((r) => `youi:${id}/rule/${r}`);
|
|
2394
|
+
for (const file of files) {
|
|
2395
|
+
const defined = (byFile.get(file) ?? []).filter((c) => /^[A-Z]/.test(c.export));
|
|
2396
|
+
const source = await readFile4(join14(root, file), "utf8");
|
|
2397
|
+
if (defined.length === 0) {
|
|
2398
|
+
support.push(posix3.basename(file));
|
|
2399
|
+
continue;
|
|
2400
|
+
}
|
|
2401
|
+
const main2 = defined.find((c) => c.export === pascal(file)) ?? defined[0];
|
|
2402
|
+
const folder = kebab(main2.export);
|
|
2403
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(folder)) {
|
|
2404
|
+
notes.push(`${file}: ${main2.export} has no contract (its name is not plain letters and digits); write one by hand`);
|
|
2405
|
+
continue;
|
|
2406
|
+
}
|
|
2407
|
+
const exportsOf = exportedNames(source);
|
|
2408
|
+
const sites = main2.sites.length;
|
|
2409
|
+
const contract = {
|
|
2410
|
+
schema: "youi.component/v1",
|
|
2411
|
+
id: `youi:${id}/component/${folder}`,
|
|
2412
|
+
name: main2.export,
|
|
2413
|
+
version: "0.1.0",
|
|
2414
|
+
status: "draft",
|
|
2415
|
+
summary: `The product's own ${main2.export} (${file}), used at ${sites} ${sites === 1 ? "site" : "sites"}.`.slice(0, 200),
|
|
2416
|
+
source: { file: posix3.basename(file), export: main2.export, also: defined.filter((c) => c !== main2).map((c) => c.export) },
|
|
2417
|
+
platforms: facts.dependencies.includes("react-native") && !facts.dependencies.includes("react-dom") ? ["ios", "android"] : ["web"],
|
|
2418
|
+
props: propsOf(main2, defaultVariants(source)),
|
|
2419
|
+
tokens: {},
|
|
2420
|
+
a11y: { notes: [] },
|
|
2421
|
+
rules: RULES,
|
|
2422
|
+
guidance: { do: [], dont: [] },
|
|
2423
|
+
examples: [],
|
|
2424
|
+
aliases: exportsOf.filter((n) => !/^[A-Z]/.test(n)),
|
|
2425
|
+
owners: []
|
|
2426
|
+
};
|
|
2427
|
+
out.push({ path: `design/components/${folder}/${folder}.contract.json`, content: json(contract) });
|
|
2428
|
+
components.push(main2.export);
|
|
2429
|
+
}
|
|
2430
|
+
if (components.length === 0) throw new Error(`${componentsDir} exports no React components: pass --components <dir> (the folder your app imports its UI components from)`);
|
|
2431
|
+
const found = await stylesheetOf(root, options.css, options.fork === true);
|
|
2432
|
+
const stylesheet = found.file ? inside2(root, found.file, "the stylesheet") : null;
|
|
2433
|
+
const css = stylesheet ? await readTextOrNull(join14(root, stylesheet)) : null;
|
|
2434
|
+
if (stylesheet && css === null) throw new Error(`${stylesheet} does not exist (--css <file>: the stylesheet that defines your :root variables)`);
|
|
2435
|
+
const darkBlocks = css ? DARK_BLOCKS.filter((b) => readRules(css).some((r) => r.preludes.at(-1) === b.selector && (b.outer ? r.preludes.length === 2 && r.preludes[0] === b.outer : r.preludes.length === 1) && r.declarations.some((d) => d.property.startsWith("--")))) : [];
|
|
2436
|
+
const darkBlock = darkBlocks[0];
|
|
2437
|
+
const variables = css ? rootVariables(css) : [];
|
|
2438
|
+
const prefix = commonPrefix(variables.map((v) => v.name));
|
|
2439
|
+
const full = (name2) => prefix ? `${prefix}-${name2}` : name2;
|
|
2440
|
+
const own = variables.filter((v) => !prefix || v.name.startsWith(`${prefix}-`)).map((v) => ({ name: prefix ? v.name.slice(prefix.length + 1) : v.name, value: v.value }));
|
|
2441
|
+
const unprefixed = variables.filter((v) => prefix && !v.name.startsWith(`${prefix}-`)).map((v) => v.name);
|
|
2442
|
+
if (unprefixed.length > 0) notes.push(`${plural(unprefixed.length, "variable")} without the --${prefix}- prefix stay in ${stylesheet}: ${unprefixed.slice(0, 4).map((n) => `--${n}`).join(", ")}${unprefixed.length > 4 ? ", \u2026" : ""}`);
|
|
2443
|
+
const paths = Object.fromEntries(own.flatMap((v) => lengthPath(v.name, v.value) ? [[v.name, lengthPath(v.name, v.value)]] : []));
|
|
2444
|
+
const imported = css ? importTokens({ css: { css, prefix, light: { selector: ":root" }, ...darkBlock ? { dark: darkBlock } : {} }, paths }) : null;
|
|
2445
|
+
const tokenCount = imported?.report.fromCss.length ?? 0;
|
|
2446
|
+
const hasDark = Boolean(imported && Object.keys(imported.dark).length > 0);
|
|
2447
|
+
out.push({ path: `design/tokens/${id}.tokens.json`, content: json(imported?.base ?? {}) });
|
|
2448
|
+
if (hasDark) out.push({ path: `design/tokens/${id}.dark.tokens.json`, content: json(imported.dark) });
|
|
2449
|
+
out.push({
|
|
2450
|
+
path: `design/tokens/${id}.resolver.json`,
|
|
2451
|
+
content: json({
|
|
2452
|
+
$schema: "https://www.designtokens.org/schemas/2025.10/resolver.json",
|
|
2453
|
+
name,
|
|
2454
|
+
version: "2025.10",
|
|
2455
|
+
description: `${name}'s tokens, adopted from ${stylesheet ?? "no stylesheet"}.`,
|
|
2456
|
+
sets: { base: { sources: [{ $ref: `${id}.tokens.json` }] } },
|
|
2457
|
+
...hasDark ? { modifiers: { theme: { contexts: { light: [], dark: [{ $ref: `${id}.dark.tokens.json` }] }, default: "light" } } } : {},
|
|
2458
|
+
resolutionOrder: [{ $ref: "#/sets/base" }, ...hasDark ? [{ $ref: "#/modifiers/theme" }] : []]
|
|
2459
|
+
})
|
|
2460
|
+
});
|
|
2461
|
+
if (!stylesheet) notes.push("no stylesheet defines :root variables, so the System has no tokens yet: add them to design/tokens");
|
|
2462
|
+
const left = (imported?.report.unparsed ?? []).map((l) => ({ ...l, name: full(l.name) }));
|
|
2463
|
+
const moved = new Set(imported ? variableNames(imported.base).map(full) : []);
|
|
2464
|
+
const importPath = stylesheet ? relative6(dirname7(join14(root, stylesheet)), join14(root, OUT_DIR, "tokens.css")).split("\\").join("/") : "";
|
|
2465
|
+
const moving = css && moved.size > 0 ? moveDeclarations(css, [{ selector: ":root" }, ...darkBlocks], moved, importPath.startsWith(".") ? importPath : `./${importPath}`) : null;
|
|
2466
|
+
const rewritten = moving && found.forked ? { ...moving, content: moving.content.split("\n").filter((line) => !line.includes(GENERATED_MARK) && !/^\/\* Built with Terrazzo/.test(line)).join("\n") } : moving;
|
|
2467
|
+
if (found.forked) notes.push(`forked from ${found.forked.system ?? "the System"}${found.forked.version ? ` v${found.forked.version}` : ""}: this product's System starts from the ${found.forked.count} variables it generated into ${found.forked.file}, and the product no longer follows ${found.forked.system ?? "it"}`);
|
|
2468
|
+
if (css && moved.size > 0 && !rewritten) notes.push(`${stylesheet}'s variables were copied, not moved (their blocks are nested or share lines): import ${OUT_DIR}/tokens.css from it and remove them by hand`);
|
|
2469
|
+
for (const [rule2, content] of starterRules(id, componentsDir)) out.push({ path: `design/rules/${rule2}.rule.yaml`, content });
|
|
2470
|
+
out.push({
|
|
2471
|
+
path: "design/design.md",
|
|
2472
|
+
content: `# ${name}
|
|
2473
|
+
|
|
2474
|
+
The design system of ${name}, adopted from its own code: the components in \`${componentsDir}\` and the variables ${stylesheet ? `\`${stylesheet}\`` : "its stylesheet"} defined. Write here what each colour and component is for and the rules people follow; agents read it in DESIGN.md.
|
|
2475
|
+
`
|
|
2476
|
+
});
|
|
2477
|
+
const system = {
|
|
2478
|
+
id,
|
|
2479
|
+
name,
|
|
2480
|
+
version: "0.1.0",
|
|
2481
|
+
description: `${name}'s own components and tokens, adopted with youiyoux adopt.`,
|
|
2482
|
+
tokens: `design/tokens/${id}.resolver.json`,
|
|
2483
|
+
...hasDark ? { themeModifier: { name: "theme", light: "light", dark: "dark" } } : {},
|
|
2484
|
+
components: "design/components",
|
|
2485
|
+
implementations: componentsDir,
|
|
2486
|
+
support,
|
|
2487
|
+
rules: "design/rules",
|
|
2488
|
+
design: "design/design.md"
|
|
2489
|
+
};
|
|
2490
|
+
out.unshift({ path: "youi.config.json", content: json({ schema: "youi.config/v1", name, systems: [system] }) });
|
|
2491
|
+
return {
|
|
2492
|
+
system,
|
|
2493
|
+
componentsDir,
|
|
2494
|
+
files: out,
|
|
2495
|
+
components,
|
|
2496
|
+
support,
|
|
2497
|
+
tokens: { stylesheet, count: tokenCount, dark: hasDark, left },
|
|
2498
|
+
stylesheet: rewritten && stylesheet ? { path: stylesheet, ...rewritten } : null,
|
|
2499
|
+
outputs: { dir: OUT_DIR, cssVarPrefix: prefix, dark: darkBlocks.map((b) => ({ ...b.outer ? { media: b.outer.replace(/^@media\s+/, "") } : {}, selector: b.selector })), targets: ["css-vars"] },
|
|
2500
|
+
tokenSources: stylesheet ? [stylesheet] : [],
|
|
2501
|
+
notes,
|
|
2502
|
+
follows: found.generated.filter((g) => g.file !== found.forked?.file),
|
|
2503
|
+
forked: found.forked
|
|
2504
|
+
};
|
|
2505
|
+
}
|
|
2506
|
+
async function uiFolder(root) {
|
|
2507
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2508
|
+
for (const file of await findFiles(root, ["**/components/ui/*.{tsx,jsx}"], ["**/build/**", "**/out/**", "**/.next/**"])) counts.set(posix3.dirname(file), (counts.get(posix3.dirname(file)) ?? 0) + 1);
|
|
2509
|
+
return [...counts].sort((a, b) => b[1] - a[1] || a[0].length - b[0].length)[0]?.[0] ?? null;
|
|
2510
|
+
}
|
|
2511
|
+
function starterRules(id, componentsDir) {
|
|
2512
|
+
const rule2 = (spec) => (0, import_yaml2.stringify)({ schema: "youi.rule/v1", ...spec });
|
|
2513
|
+
return [
|
|
2514
|
+
["color.no-raw-color", rule2({
|
|
2515
|
+
id: `youi:${id}/rule/color.no-raw-color`,
|
|
2516
|
+
title: "No raw colours",
|
|
2517
|
+
severity: "warn",
|
|
2518
|
+
statement: "Colour comes from the tokens. Never write a hex, rgb(), hsl() or oklch() literal in a component, class name, inline style or stylesheet; only the token source may define colour values.",
|
|
2519
|
+
rationale: "A literal colour cannot follow the dark scheme or a token change, and it hides from every audit.",
|
|
2520
|
+
agent: "Never hard-code a colour: use the token utilities (bg-primary, text-muted-foreground) or the variables (var(--primary)); look up the role with system_get_tokens.",
|
|
2521
|
+
check: { engine: "youi", id: "raw-color", options: {} },
|
|
2522
|
+
files: ["**/*.{ts,tsx,css}"]
|
|
2523
|
+
})],
|
|
2524
|
+
["tokens.no-arbitrary-values", rule2({
|
|
2525
|
+
id: `youi:${id}/rule/tokens.no-arbitrary-values`,
|
|
2526
|
+
title: "Use token utilities, not arbitrary values",
|
|
2527
|
+
severity: "warn",
|
|
2528
|
+
statement: "When a token holds a value, use its utility instead of a Tailwind arbitrary value.",
|
|
2529
|
+
rationale: "An arbitrary value copies a token's value once; it does not follow when the token changes.",
|
|
2530
|
+
agent: "Prefer token utilities over [arbitrary] values; youiyoux check names the utility.",
|
|
2531
|
+
check: { engine: "youi", id: "arbitrary-value", options: {} },
|
|
2532
|
+
files: ["**/*.tsx"]
|
|
2533
|
+
})],
|
|
2534
|
+
["components.canonical-import", rule2({
|
|
2535
|
+
id: `youi:${id}/rule/components.canonical-import`,
|
|
2536
|
+
title: "Render the System's component, not a lookalike",
|
|
2537
|
+
severity: "warn",
|
|
2538
|
+
statement: `A component named like one of the System's (Button, Input, Card) must be the one in ${componentsDir}. A second one with the same name splits the System in two.`,
|
|
2539
|
+
rationale: "Two Buttons drift apart: a fix to one never reaches the other.",
|
|
2540
|
+
agent: `Import components only from ${componentsDir}; never define or import another component with one of their names.`,
|
|
2541
|
+
check: { engine: "youi", id: "lookalike-component", options: {} },
|
|
2542
|
+
files: ["**/*.tsx"]
|
|
2543
|
+
})]
|
|
2544
|
+
];
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
// ../cli/src/extract.ts
|
|
2548
|
+
import { cp, mkdir as mkdir3, mkdtemp as mkdtemp3, readdir as readdir3, readFile as readFile5, realpath as realpath4, rename, rm as rm4, rmdir as rmdir2, stat } from "node:fs/promises";
|
|
2549
|
+
import { basename as basename3, dirname as dirname8, join as join15, posix as posix4, relative as relative7, resolve as resolve9, sep as sep2 } from "node:path";
|
|
2550
|
+
var json2 = (value) => `${JSON.stringify(value, null, 2)}
|
|
2551
|
+
`;
|
|
2552
|
+
var toPosix2 = (path) => path.split(sep2).join("/");
|
|
2553
|
+
async function isDirectory(path) {
|
|
2554
|
+
return (await stat(path).catch(() => null))?.isDirectory() ?? false;
|
|
2555
|
+
}
|
|
2556
|
+
async function heldInPlace(project2) {
|
|
2557
|
+
const [repo, root] = await Promise.all([realpath4(project2.repo.root), realpath4(project2.root)]);
|
|
2558
|
+
return repo === root && project2.system.config.implementations !== void 0;
|
|
2559
|
+
}
|
|
2560
|
+
function ownedPaths(project2, paths, kept) {
|
|
2561
|
+
const root = project2.repo.root;
|
|
2562
|
+
for (const path of paths) {
|
|
2563
|
+
const own = normalizeRelPath(path);
|
|
2564
|
+
if (own === "." || own.startsWith("../") || !within(root, join15(root, own)) || resolve9(root, own) === resolve9(root)) throw new Error(`the System's ${path} is not a folder of its own inside ${root}: move its files into one (design/\u2026) before extracting; nothing was changed`);
|
|
2565
|
+
for (const keep of kept.map((k) => normalizeRelPath(toPosix2(k)))) {
|
|
2566
|
+
if (keep === own || keep.startsWith(`${own}/`)) throw new Error(`the System's ${own} holds ${keep}, which stays in the product: move the System's files into a folder of their own before extracting; nothing was changed`);
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
async function extractSystem(project2, dir, options = {}) {
|
|
2571
|
+
if (!await heldInPlace(project2)) throw new Error(`${project2.root}'s System is already a design repo of its own (${project2.repo.root}): extract moves a System youiyoux adopt keeps inside the product`);
|
|
2572
|
+
const target = resolve9(dir);
|
|
2573
|
+
if (await isDirectory(target)) {
|
|
2574
|
+
if ((await readdir3(target)).length > 0) throw new Error(`${target} already exists and is not empty: extract into a new folder`);
|
|
2575
|
+
} else if (await stat(target).catch(() => null) !== null) throw new Error(`${target} already exists: extract into a new folder`);
|
|
2576
|
+
const { repo, system } = project2;
|
|
2577
|
+
const config = system.config;
|
|
2578
|
+
const from = (path) => join15(repo.root, path);
|
|
2579
|
+
const components = from(config.components);
|
|
2580
|
+
const implementations = from(implementationsDir(config));
|
|
2581
|
+
const tokensDir = posix4.dirname(config.tokens);
|
|
2582
|
+
const removed = [DESIGN_CONFIG_FILE, config.components, tokensDir, config.rules, ...config.design ? [config.design] : []];
|
|
2583
|
+
ownedPaths(project2, removed, [implementationsDir(config), relative7(project2.root, project2.configFile ?? join15(project2.root, PROJECT_FILE)), project2.config.outputs.dir, relative7(project2.root, target)]);
|
|
2584
|
+
const { implementations: _inPlace, ...rest } = config;
|
|
2585
|
+
const shared = config.support.map((file) => `_shared/${posix4.basename(file)}`);
|
|
2586
|
+
const next2 = { ...rest, tokens: `tokens/${posix4.relative(tokensDir, config.tokens)}`, components: "components", support: shared, rules: "rules", ...config.design ? { design: "design.md" } : {} };
|
|
2587
|
+
const written = [];
|
|
2588
|
+
const temp = await mkdtemp3(join15(dirname8(target), `.${basename3(target)}.`));
|
|
2589
|
+
try {
|
|
2590
|
+
const copy = async (source2, to) => {
|
|
2591
|
+
await mkdir3(dirname8(join15(temp, to)), { recursive: true });
|
|
2592
|
+
await cp(source2, join15(temp, to), { recursive: true });
|
|
2593
|
+
};
|
|
2594
|
+
await copy(components, "components");
|
|
2595
|
+
const placed = /* @__PURE__ */ new Map();
|
|
2596
|
+
for (const { contract, dir: folder } of system.components) {
|
|
2597
|
+
const contractFile = (await readdir3(folder)).find((name) => name.endsWith(".contract.json"));
|
|
2598
|
+
const at = toPosix2(relative7(components, folder));
|
|
2599
|
+
const file = placed.get(contract.source.file) ?? `${at}/${posix4.basename(contract.source.file)}`;
|
|
2600
|
+
if (!placed.has(contract.source.file)) {
|
|
2601
|
+
await copy(join15(implementations, contract.source.file), `components/${file}`);
|
|
2602
|
+
placed.set(contract.source.file, file);
|
|
2603
|
+
}
|
|
2604
|
+
const raw = JSON.parse(await readFile5(join15(folder, contractFile), "utf8"));
|
|
2605
|
+
await writeFileAtomic(join15(temp, "components", at, contractFile), json2({ ...raw, source: { ...raw.source, file } }));
|
|
2606
|
+
}
|
|
2607
|
+
for (const [i, file] of config.support.entries()) await copy(join15(implementations, file), `components/${shared[i]}`);
|
|
2608
|
+
await copy(from(tokensDir), "tokens");
|
|
2609
|
+
if (await isDirectory(from(config.rules))) await copy(from(config.rules), "rules");
|
|
2610
|
+
if (config.design) await copy(from(config.design), "design.md");
|
|
2611
|
+
await writeFileAtomic(join15(temp, DESIGN_CONFIG_FILE), json2({ schema: "youi.config/v1", name: repo.config.name, systems: [next2] }));
|
|
2612
|
+
if (options.package) await writeFileAtomic(join15(temp, "package.json"), json2({ name: options.package, version: config.version, description: config.description, files: [DESIGN_CONFIG_FILE, "components", "tokens", "rules", ...config.design ? ["design.md"] : []] }));
|
|
2613
|
+
const leaving = escapingLinks(temp);
|
|
2614
|
+
if (leaving.length > 0) throw new Error(`the System has links that lead outside it (${leaving.slice(0, 3).join(", ")}); nothing was changed`);
|
|
2615
|
+
await loadDesignRepo(temp);
|
|
2616
|
+
for (const entry of await readdir3(temp, { recursive: true })) if (!await isDirectory(join15(temp, entry))) written.push(toPosix2(entry));
|
|
2617
|
+
written.sort();
|
|
2618
|
+
const source = { path: toPosix2(relative7(project2.root, target)) || "." };
|
|
2619
|
+
if (!options.dryRun) {
|
|
2620
|
+
if (await isDirectory(target)) await rmdir2(target);
|
|
2621
|
+
await rename(temp, target);
|
|
2622
|
+
const configFile = project2.configFile ?? join15(project2.root, PROJECT_FILE);
|
|
2623
|
+
const raw = JSON.parse(await readTextOrNull(configFile) ?? "{}");
|
|
2624
|
+
await writeFileAtomic(configFile, json2({ ...raw, source }));
|
|
2625
|
+
for (const path of removed) await rm4(join15(repo.root, path), { recursive: true, force: true });
|
|
2626
|
+
}
|
|
2627
|
+
return { target, system: next2, written, removed, source };
|
|
2628
|
+
} finally {
|
|
2629
|
+
await rm4(temp, { recursive: true, force: true });
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2220
2633
|
// ../cli/src/index-store.ts
|
|
2221
|
-
import { appendFile, mkdir as
|
|
2222
|
-
import { dirname as
|
|
2634
|
+
import { appendFile, mkdir as mkdir4, readFile as readFile6, stat as stat2, rename as rename2, mkdtemp as mkdtemp4, rm as rm5 } from "node:fs/promises";
|
|
2635
|
+
import { dirname as dirname9, join as join16, resolve as resolve10 } from "node:path";
|
|
2223
2636
|
var SOURCES_FILE = "index-sources.json";
|
|
2224
2637
|
var SCANS_LOG = "scans.jsonl";
|
|
2225
2638
|
var EMPTY = { discover: null, inventories: [], projects: [] };
|
|
2226
2639
|
async function readIndexSources(home) {
|
|
2227
2640
|
try {
|
|
2228
|
-
const raw = JSON.parse(await
|
|
2641
|
+
const raw = JSON.parse(await readFile6(join16(home, SOURCES_FILE), "utf8"));
|
|
2229
2642
|
return { discover: raw.discover ?? null, inventories: raw.inventories ?? [], projects: raw.projects ?? [] };
|
|
2230
2643
|
} catch {
|
|
2231
2644
|
return EMPTY;
|
|
@@ -2238,14 +2651,14 @@ async function noteSource(home, change) {
|
|
|
2238
2651
|
inventories: change.inventory && !sources.inventories.includes(change.inventory) ? [...sources.inventories, change.inventory] : sources.inventories,
|
|
2239
2652
|
projects: change.project ? [...sources.projects.filter((p) => p.root !== change.project.root), change.project] : sources.projects
|
|
2240
2653
|
};
|
|
2241
|
-
await writeFileAtomic(
|
|
2654
|
+
await writeFileAtomic(join16(home, SOURCES_FILE), `${JSON.stringify(next2, null, 2)}
|
|
2242
2655
|
`);
|
|
2243
2656
|
}
|
|
2244
2657
|
async function recordInventory(store, home, report, file) {
|
|
2245
|
-
const run7 = inventoryRun(report,
|
|
2658
|
+
const run7 = inventoryRun(report, resolve10(file));
|
|
2246
2659
|
const logged = { run: run7.run, source: run7.source, scannedAt: run7.scannedAt, projects: run7.projects, families: run7.families };
|
|
2247
|
-
await
|
|
2248
|
-
await appendFile(
|
|
2660
|
+
await mkdir4(home, { recursive: true });
|
|
2661
|
+
await appendFile(join16(home, SCANS_LOG), `${JSON.stringify({ schema: "youi.scan-log/v1", ...logged })}
|
|
2249
2662
|
`);
|
|
2250
2663
|
await store.recordInventory(run7);
|
|
2251
2664
|
await noteSource(home, { inventory: run7.source });
|
|
@@ -2256,7 +2669,7 @@ async function recordProject(store, project2, usage) {
|
|
|
2256
2669
|
await store.replaceGraph(`project:${project2.root}`, projectGraph({ project: project2.config.id, rules: project2.system.rules, components: project2.system.components.map((c) => ({ id: c.contract.id, name: c.contract.name })), usage }));
|
|
2257
2670
|
}
|
|
2258
2671
|
async function readLog(home) {
|
|
2259
|
-
const text = await
|
|
2672
|
+
const text = await readFile6(join16(home, SCANS_LOG), "utf8").catch(() => "");
|
|
2260
2673
|
return text.split("\n").flatMap((line) => {
|
|
2261
2674
|
if (!line.trim()) return [];
|
|
2262
2675
|
try {
|
|
@@ -2271,9 +2684,9 @@ async function rebuildIndex(path, options = {}) {
|
|
|
2271
2684
|
const started = performance.now();
|
|
2272
2685
|
const env = options.env ?? process.env;
|
|
2273
2686
|
const home = indexHome(path, env);
|
|
2274
|
-
await
|
|
2275
|
-
const scratch = !env.DATABASE_URL ? await
|
|
2276
|
-
const target = scratch ?
|
|
2687
|
+
await mkdir4(dirname9(path), { recursive: true });
|
|
2688
|
+
const scratch = !env.DATABASE_URL ? await mkdtemp4(`${path}.rebuild-`) : null;
|
|
2689
|
+
const target = scratch ? join16(scratch, "index.db") : path;
|
|
2277
2690
|
const store = scratch ? await openSqlite(target) : await sharedStore(path, env);
|
|
2278
2691
|
try {
|
|
2279
2692
|
if (env.DATABASE_URL) await store.reset();
|
|
@@ -2281,7 +2694,7 @@ async function rebuildIndex(path, options = {}) {
|
|
|
2281
2694
|
const sources = await readIndexSources(home);
|
|
2282
2695
|
const cache = options.discoverCache ?? sources.discover;
|
|
2283
2696
|
let discover = { cache, chosen: 0, all: 0 };
|
|
2284
|
-
if (cache && await
|
|
2697
|
+
if (cache && await stat2(join16(cache, "index.json")).then(() => true, () => false)) {
|
|
2285
2698
|
const loaded = await ingestCatalog(store, cache);
|
|
2286
2699
|
discover = { cache, chosen: loaded.chosen, all: loaded.all };
|
|
2287
2700
|
}
|
|
@@ -2302,14 +2715,14 @@ async function rebuildIndex(path, options = {}) {
|
|
|
2302
2715
|
let projects = 0;
|
|
2303
2716
|
const gone = [];
|
|
2304
2717
|
for (const source of sources.projects) {
|
|
2305
|
-
if (!await
|
|
2718
|
+
if (!await stat2(source.root).then((s) => s.isDirectory(), () => false)) {
|
|
2306
2719
|
gone.push(source.root);
|
|
2307
2720
|
failed.push({ root: source.root, reason: "folder not found (moved or deleted), skipped" });
|
|
2308
2721
|
continue;
|
|
2309
2722
|
}
|
|
2310
2723
|
try {
|
|
2311
2724
|
const project2 = await loadProject(source.root, source.options);
|
|
2312
|
-
const usage = ScanReport.safeParse(await readJson(source.usage ??
|
|
2725
|
+
const usage = ScanReport.safeParse(await readJson(source.usage ?? join16(project2.root, ".youi/usage.json")));
|
|
2313
2726
|
await recordProject(store, project2, usage.success ? usage.data : null);
|
|
2314
2727
|
projects++;
|
|
2315
2728
|
} catch (error) {
|
|
@@ -2323,20 +2736,20 @@ async function rebuildIndex(path, options = {}) {
|
|
|
2323
2736
|
if (scratch) {
|
|
2324
2737
|
await store.close();
|
|
2325
2738
|
checkpointSqlite(target);
|
|
2326
|
-
if (await
|
|
2739
|
+
if (await stat2(path).then(() => true, () => false)) checkpointSqlite(path);
|
|
2327
2740
|
const moved = [];
|
|
2328
2741
|
try {
|
|
2329
2742
|
for (const suffix of ["-wal", "-shm"]) {
|
|
2330
2743
|
try {
|
|
2331
|
-
await
|
|
2744
|
+
await rename2(`${path}${suffix}`, `${target}.old${suffix}`);
|
|
2332
2745
|
moved.push(suffix);
|
|
2333
2746
|
} catch (error) {
|
|
2334
2747
|
if (error.code !== "ENOENT") throw error;
|
|
2335
2748
|
}
|
|
2336
2749
|
}
|
|
2337
|
-
await
|
|
2750
|
+
await rename2(target, path);
|
|
2338
2751
|
} catch (error) {
|
|
2339
|
-
for (const suffix of moved) await
|
|
2752
|
+
for (const suffix of moved) await rename2(`${target}.old${suffix}`, `${path}${suffix}`);
|
|
2340
2753
|
throw error;
|
|
2341
2754
|
}
|
|
2342
2755
|
}
|
|
@@ -2344,7 +2757,7 @@ async function rebuildIndex(path, options = {}) {
|
|
|
2344
2757
|
} finally {
|
|
2345
2758
|
if (scratch) {
|
|
2346
2759
|
await store.close();
|
|
2347
|
-
await
|
|
2760
|
+
await rm5(scratch, { recursive: true, force: true });
|
|
2348
2761
|
}
|
|
2349
2762
|
}
|
|
2350
2763
|
}
|
|
@@ -2527,8 +2940,8 @@ function renderAdoptionRequest(request) {
|
|
|
2527
2940
|
|
|
2528
2941
|
// ../cli/src/report/run.ts
|
|
2529
2942
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2530
|
-
import { mkdir as
|
|
2531
|
-
import { join as
|
|
2943
|
+
import { mkdir as mkdir5, rename as rename3, stat as stat3, writeFile as writeFile2 } from "node:fs/promises";
|
|
2944
|
+
import { join as join23, relative as relative9, resolve as resolve13 } from "node:path";
|
|
2532
2945
|
|
|
2533
2946
|
// ../cli/src/report/card.ts
|
|
2534
2947
|
import { deflateSync } from "node:zlib";
|
|
@@ -2763,8 +3176,8 @@ function drawCard(input) {
|
|
|
2763
3176
|
}
|
|
2764
3177
|
|
|
2765
3178
|
// ../cli/src/report/model.ts
|
|
2766
|
-
import { readFile as
|
|
2767
|
-
import { join as
|
|
3179
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
3180
|
+
import { join as join20 } from "node:path";
|
|
2768
3181
|
|
|
2769
3182
|
// ../cli/src/report/filters.ts
|
|
2770
3183
|
var SHOWCASE_GLOBS = ["**/{__stories__,.storybook,storybook,storybook-static,example,examples,demo,demos,playground,showcase,showcases}/**", "**/*.stories.*", "**/*.story.*"];
|
|
@@ -2891,11 +3304,11 @@ function publicLook(caption) {
|
|
|
2891
3304
|
|
|
2892
3305
|
// ../cli/src/report/groups.ts
|
|
2893
3306
|
import { existsSync as existsSync3 } from "node:fs";
|
|
2894
|
-
import { join as
|
|
3307
|
+
import { join as join18 } from "node:path";
|
|
2895
3308
|
|
|
2896
3309
|
// ../cli/src/report/imports.ts
|
|
2897
3310
|
import { realpathSync } from "node:fs";
|
|
2898
|
-
import { dirname as
|
|
3311
|
+
import { dirname as dirname10, join as join17, relative as relative8, resolve as resolve11 } from "node:path";
|
|
2899
3312
|
import ts from "typescript";
|
|
2900
3313
|
var resolvers = /* @__PURE__ */ new Map();
|
|
2901
3314
|
function resolverOf(root) {
|
|
@@ -2903,7 +3316,7 @@ function resolverOf(root) {
|
|
|
2903
3316
|
if (!found) {
|
|
2904
3317
|
const config = ts.findConfigFile(root, ts.sys.fileExists, "tsconfig.json") ?? ts.findConfigFile(root, ts.sys.fileExists, "jsconfig.json");
|
|
2905
3318
|
const read = config ? ts.readConfigFile(config, ts.sys.readFile) : null;
|
|
2906
|
-
const options = config && read && !read.error ? ts.parseJsonConfigFileContent(read.config, ts.sys,
|
|
3319
|
+
const options = config && read && !read.error ? ts.parseJsonConfigFileContent(read.config, ts.sys, dirname10(config)).options : {};
|
|
2907
3320
|
found = { options, cache: ts.createModuleResolutionCache(root, (name) => name, options) };
|
|
2908
3321
|
resolvers.set(root, found);
|
|
2909
3322
|
}
|
|
@@ -2914,13 +3327,13 @@ function clearImportCache() {
|
|
|
2914
3327
|
}
|
|
2915
3328
|
async function verifiedImport(root, keeper, from, suggested) {
|
|
2916
3329
|
const { options, cache } = resolverOf(root);
|
|
2917
|
-
let local =
|
|
3330
|
+
let local = relative8(dirname10(join17(root, from)), join17(root, keeper)).replaceAll("\\", "/").replace(/\.(tsx?|jsx?|mjs)$/, "");
|
|
2918
3331
|
if (!local.startsWith(".")) local = `./${local}`;
|
|
2919
3332
|
for (const specifier of /* @__PURE__ */ new Set([suggested, local])) {
|
|
2920
|
-
const found = ts.resolveModuleName(specifier,
|
|
3333
|
+
const found = ts.resolveModuleName(specifier, resolve11(root, from), options, ts.sys, cache).resolvedModule?.resolvedFileName;
|
|
2921
3334
|
if (found) {
|
|
2922
3335
|
try {
|
|
2923
|
-
if (realpathSync(found) === realpathSync(
|
|
3336
|
+
if (realpathSync(found) === realpathSync(join17(root, keeper))) return specifier;
|
|
2924
3337
|
} catch {
|
|
2925
3338
|
}
|
|
2926
3339
|
}
|
|
@@ -3033,7 +3446,7 @@ var NEAR_IDENTICAL = 0.5;
|
|
|
3033
3446
|
var MIN_STRUCTURE = 6;
|
|
3034
3447
|
var GENERIC_IDENTICAL = 0.85;
|
|
3035
3448
|
var repoPath = (dir, file) => dir === "." ? file : `${dir}/${file}`;
|
|
3036
|
-
var
|
|
3449
|
+
var kebab2 = (name) => name.replace(/[a-z0-9](?=[A-Z])/g, "$&-").toLowerCase();
|
|
3037
3450
|
function shapeWords(shape) {
|
|
3038
3451
|
const { height, paddingX, radius, fontSize } = shape;
|
|
3039
3452
|
return [height !== null ? `${height}px high` : null, paddingX !== null ? `${paddingX}px side padding` : null, radius !== null ? radius === 9999 ? "pill-shaped" : `${radius}px corners` : null, fontSize !== null ? `${fontSize}px text` : null].filter(Boolean).join(", ");
|
|
@@ -3134,7 +3547,7 @@ async function nearDuplicateGroups(input) {
|
|
|
3134
3547
|
const name = EXTRACT_NAMES[kind];
|
|
3135
3548
|
const fileIn = (f, dir2) => {
|
|
3136
3549
|
for (let n = 1; ; n++) {
|
|
3137
|
-
const file = `${dir2 ? `${dir2}/` : ""}${
|
|
3550
|
+
const file = `${dir2 ? `${dir2}/` : ""}${kebab2(name)}${n === 1 ? "" : `-${n}`}.tsx`;
|
|
3138
3551
|
if (![...defs.values()].some((d) => d.facts === f && d.def.file === file)) return file;
|
|
3139
3552
|
}
|
|
3140
3553
|
};
|
|
@@ -3328,22 +3741,22 @@ function factsOf(project2, id, modules, eligible, workspaceNames, dependencies,
|
|
|
3328
3741
|
if (dirs.has(file.slice(0, at))) break;
|
|
3329
3742
|
dirs.add(file.slice(0, at));
|
|
3330
3743
|
}
|
|
3331
|
-
const roots = [...dirs].filter((dir) => existsSync3(
|
|
3744
|
+
const roots = [...dirs].filter((dir) => existsSync3(join18(project2.root, dir, "package.json"))).sort((a, b) => b.length - a.length);
|
|
3332
3745
|
const nested = (file) => roots.find((dir) => file.startsWith(`${dir}/`)) ?? "";
|
|
3333
3746
|
return { project: project2, id, dependencies, packageImports, jsxCounts, eligible, twins, dirs, importer, packageOf: nested };
|
|
3334
3747
|
}
|
|
3335
3748
|
|
|
3336
3749
|
// ../cli/src/report/projects.ts
|
|
3337
|
-
var
|
|
3338
|
-
import { readFile as
|
|
3339
|
-
import { basename as
|
|
3750
|
+
var import_yaml3 = __toESM(require_dist(), 1);
|
|
3751
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
3752
|
+
import { basename as basename4, join as join19, resolve as resolve12 } from "node:path";
|
|
3340
3753
|
var UI_FILES = ["**/*.{tsx,jsx,astro,vue,svelte}"];
|
|
3341
3754
|
var strings = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
3342
3755
|
async function workspaceGlobs(root, pkg) {
|
|
3343
|
-
const pnpm = await
|
|
3756
|
+
const pnpm = await readFile7(join19(root, "pnpm-workspace.yaml"), "utf8").catch(() => null);
|
|
3344
3757
|
if (pnpm !== null) {
|
|
3345
3758
|
try {
|
|
3346
|
-
const globs2 = strings((0,
|
|
3759
|
+
const globs2 = strings((0, import_yaml3.parse)(pnpm)?.packages);
|
|
3347
3760
|
if (globs2.length > 0) return { globs: globs2, source: "pnpm-workspace.yaml" };
|
|
3348
3761
|
} catch {
|
|
3349
3762
|
}
|
|
@@ -3354,9 +3767,9 @@ async function workspaceGlobs(root, pkg) {
|
|
|
3354
3767
|
}
|
|
3355
3768
|
var hasUi = async (dir) => (await findFiles(dir, UI_FILES, DEFAULT_EXCLUDE)).length > 0;
|
|
3356
3769
|
async function findProjects(dir) {
|
|
3357
|
-
const root =
|
|
3358
|
-
const pkg = await readJson(
|
|
3359
|
-
const name = typeof pkg?.name === "string" && pkg.name.trim() ? pkg.name.trim() :
|
|
3770
|
+
const root = resolve12(dir);
|
|
3771
|
+
const pkg = await readJson(join19(root, "package.json"));
|
|
3772
|
+
const name = typeof pkg?.name === "string" && pkg.name.trim() ? pkg.name.trim() : basename4(root);
|
|
3360
3773
|
const single = { dir: ".", root, name: typeof pkg?.name === "string" ? pkg.name : null };
|
|
3361
3774
|
const workspace = await workspaceGlobs(root, pkg);
|
|
3362
3775
|
if (!workspace) return { root, name, layout: "one project", projects: [single], skipped: [] };
|
|
@@ -3366,12 +3779,12 @@ async function findProjects(dir) {
|
|
|
3366
3779
|
const projects = [];
|
|
3367
3780
|
const skipped = [];
|
|
3368
3781
|
for (const member of members) {
|
|
3369
|
-
const memberRoot =
|
|
3782
|
+
const memberRoot = join19(root, member);
|
|
3370
3783
|
if (!await hasUi(memberRoot)) {
|
|
3371
3784
|
skipped.push(member);
|
|
3372
3785
|
continue;
|
|
3373
3786
|
}
|
|
3374
|
-
const memberPkg = await readJson(
|
|
3787
|
+
const memberPkg = await readJson(join19(memberRoot, "package.json"));
|
|
3375
3788
|
projects.push({ dir: member, root: memberRoot, name: typeof memberPkg?.name === "string" ? memberPkg.name : null });
|
|
3376
3789
|
}
|
|
3377
3790
|
if (projects.length === 0) return { root, name, layout: `one project (${workspace.source} names no package with UI files)`, projects: [single], skipped };
|
|
@@ -3398,7 +3811,7 @@ var Lines = class {
|
|
|
3398
3811
|
if (!this.enabled) return { at };
|
|
3399
3812
|
let lines = this.files.get(file);
|
|
3400
3813
|
if (!lines) {
|
|
3401
|
-
lines = (await
|
|
3814
|
+
lines = (await readFile8(join20(this.root, file), "utf8").catch(() => "")).split("\n");
|
|
3402
3815
|
this.files.set(file, lines);
|
|
3403
3816
|
}
|
|
3404
3817
|
const code = lines[line - 1]?.trim();
|
|
@@ -3418,7 +3831,7 @@ async function modulesOf(project2, target, failed) {
|
|
|
3418
3831
|
for (const file of target.files) {
|
|
3419
3832
|
if (isShowcase(file)) continue;
|
|
3420
3833
|
try {
|
|
3421
|
-
const module = await parseModule(file, await
|
|
3834
|
+
const module = await parseModule(file, await readFile8(join20(project2.root, file), "utf8"));
|
|
3422
3835
|
if (module) modules.push(module);
|
|
3423
3836
|
} catch (error) {
|
|
3424
3837
|
failed.push(`${repoPath(project2.dir, file)}: ${errorText(error)}`);
|
|
@@ -3522,7 +3935,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
|
|
|
3522
3935
|
incomplete.push(`Library choices were not compared (${errorText(error)}); run ${NPX} detect ${project2.dir} to see why`);
|
|
3523
3936
|
}
|
|
3524
3937
|
}
|
|
3525
|
-
const pkg = await
|
|
3938
|
+
const pkg = await readFile8(join20(project2.root, "package.json"), "utf8").then((text) => JSON.parse(text), () => null);
|
|
3526
3939
|
const dependencies = new Set(["dependencies", "devDependencies", "peerDependencies"].flatMap((key2) => Object.keys(pkg?.[key2] ?? {})));
|
|
3527
3940
|
const importer = changeProject ? await importOf(changeProject) : () => "";
|
|
3528
3941
|
const facts = factsOf(project2, projectId ?? null, modules, eligible, workspaceNames, dependencies, target?.files ?? [], importer);
|
|
@@ -3966,36 +4379,36 @@ ${model.projects.map((p) => projectSection(p, many, looks)).join("\n")}
|
|
|
3966
4379
|
import { spawn as spawn2 } from "node:child_process";
|
|
3967
4380
|
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "node:fs";
|
|
3968
4381
|
import { homedir as homedir2 } from "node:os";
|
|
3969
|
-
import { dirname as
|
|
4382
|
+
import { dirname as dirname11, join as join21 } from "node:path";
|
|
3970
4383
|
var LOOKS_PER_PROJECT = 4;
|
|
3971
4384
|
var DOWNLOAD_SIZE = "~100 MB";
|
|
3972
4385
|
var DEPENDENCIES = "Install the repo's dependencies to see rendered looks";
|
|
3973
4386
|
var UNDRAWABLE = /not installed|loads no stylesheet|drawn from (?:its|their) (?:tag and )?classes|did not render|bundle failed|unstyled|stub fallback/i;
|
|
3974
|
-
var playwrightDir = () =>
|
|
4387
|
+
var playwrightDir = () => dirname11(playwrightCli());
|
|
3975
4388
|
function browsersPath(env) {
|
|
3976
4389
|
const set = env.PLAYWRIGHT_BROWSERS_PATH;
|
|
3977
|
-
if (set === "0") return
|
|
4390
|
+
if (set === "0") return join21(playwrightDir(), ".local-browsers");
|
|
3978
4391
|
if (set) return set;
|
|
3979
|
-
if (process.platform === "darwin") return
|
|
3980
|
-
if (process.platform === "win32") return
|
|
3981
|
-
return
|
|
4392
|
+
if (process.platform === "darwin") return join21(homedir2(), "Library/Caches/ms-playwright");
|
|
4393
|
+
if (process.platform === "win32") return join21(env.LOCALAPPDATA ?? join21(homedir2(), "AppData/Local"), "ms-playwright");
|
|
4394
|
+
return join21(env.XDG_CACHE_HOME ?? join21(homedir2(), ".cache"), "ms-playwright");
|
|
3982
4395
|
}
|
|
3983
4396
|
function chromiumStatus(env = process.env) {
|
|
3984
4397
|
if (env.YOUI_CHROMIUM) return { available: existsSync4(env.YOUI_CHROMIUM), where: env.YOUI_CHROMIUM };
|
|
3985
4398
|
try {
|
|
3986
|
-
const browsers = JSON.parse(readFileSync2(
|
|
4399
|
+
const browsers = JSON.parse(readFileSync2(join21(playwrightDir(), "browsers.json"), "utf8"));
|
|
3987
4400
|
const revision2 = browsers.browsers.find((b) => b.name === "chromium-headless-shell")?.revision;
|
|
3988
|
-
const dir =
|
|
3989
|
-
return { available: revision2 !== void 0 && existsSync4(
|
|
4401
|
+
const dir = join21(browsersPath(env), `chromium_headless_shell-${revision2}`);
|
|
4402
|
+
return { available: revision2 !== void 0 && existsSync4(join21(dir, "INSTALLATION_COMPLETE")), where: dir };
|
|
3990
4403
|
} catch {
|
|
3991
4404
|
return { available: false, where: "playwright-core is not installed" };
|
|
3992
4405
|
}
|
|
3993
4406
|
}
|
|
3994
4407
|
function downloadChromium() {
|
|
3995
|
-
return new Promise((
|
|
3996
|
-
const child = spawn2(process.execPath, [
|
|
3997
|
-
child.on("error", () =>
|
|
3998
|
-
child.on("close", (code) =>
|
|
4408
|
+
return new Promise((resolve15) => {
|
|
4409
|
+
const child = spawn2(process.execPath, [join21(playwrightDir(), "cli.js"), "install", "chromium-headless-shell"], { stdio: ["ignore", 2, 2] });
|
|
4410
|
+
child.on("error", () => resolve15(false));
|
|
4411
|
+
child.on("close", (code) => resolve15(code === 0));
|
|
3999
4412
|
});
|
|
4000
4413
|
}
|
|
4001
4414
|
var dataUri = (file) => file && existsSync4(file) ? `data:image/png;base64,${readFileSync2(file).toString("base64")}` : null;
|
|
@@ -4009,24 +4422,24 @@ async function addLooks(model, repoRoot, options = {}) {
|
|
|
4009
4422
|
const where2 = section.dir === "." ? "this repo" : section.dir;
|
|
4010
4423
|
try {
|
|
4011
4424
|
const installed = (name) => {
|
|
4012
|
-
let dir =
|
|
4425
|
+
let dir = join21(repoRoot, section.dir);
|
|
4013
4426
|
while (true) {
|
|
4014
|
-
if (existsSync4(
|
|
4015
|
-
if (dir === repoRoot ||
|
|
4016
|
-
dir =
|
|
4427
|
+
if (existsSync4(join21(dir, "node_modules", name, "package.json"))) return true;
|
|
4428
|
+
if (dir === repoRoot || dirname11(dir) === dir) return false;
|
|
4429
|
+
dir = dirname11(dir);
|
|
4017
4430
|
}
|
|
4018
4431
|
};
|
|
4019
4432
|
if (!installed("react") || !installed("react-dom")) {
|
|
4020
4433
|
missing.push(where2);
|
|
4021
4434
|
continue;
|
|
4022
4435
|
}
|
|
4023
|
-
const projectRoot =
|
|
4436
|
+
const projectRoot = join21(repoRoot, section.dir);
|
|
4024
4437
|
const target = await loadInventoryTarget(projectRoot);
|
|
4025
4438
|
if (findStylesheets(projectRoot, target.files, { tsconfig: target.tsconfig, override: target.specimenCss, templates: target.templates }).found.length === 0) {
|
|
4026
4439
|
missing.push(where2);
|
|
4027
4440
|
continue;
|
|
4028
4441
|
}
|
|
4029
|
-
const report = await renderShots(
|
|
4442
|
+
const report = await renderShots(join21(repoRoot, section.dir), names, { download: false, base: "HEAD", ...options.executablePath ? { executablePath: options.executablePath } : {}, ...options.cache ? { cache: options.cache } : {}, ...options.log ? { log: options.log } : {} });
|
|
4030
4443
|
const unavailable = report.notes.some((note) => UNDRAWABLE.test(note));
|
|
4031
4444
|
if (unavailable) {
|
|
4032
4445
|
missing.push(where2);
|
|
@@ -4096,9 +4509,9 @@ function renderMarkdown(model, reportFile) {
|
|
|
4096
4509
|
|
|
4097
4510
|
// ../cli/src/report/safe.ts
|
|
4098
4511
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
4099
|
-
import { readFile as
|
|
4512
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
4100
4513
|
import { homedir as homedir3, userInfo as userInfo2 } from "node:os";
|
|
4101
|
-
import { join as
|
|
4514
|
+
import { join as join22 } from "node:path";
|
|
4102
4515
|
var REDACTED = "[redacted]";
|
|
4103
4516
|
var OUTSIDE = "[path outside the repo]";
|
|
4104
4517
|
var MIN_VALUE = 6;
|
|
@@ -4110,7 +4523,7 @@ async function dotenvValues(root) {
|
|
|
4110
4523
|
const files = await findFiles(root, [".env", ".env.*", "**/.env", "**/.env.*"]).catch(() => []);
|
|
4111
4524
|
const values = [];
|
|
4112
4525
|
for (const file of files) {
|
|
4113
|
-
const text = await
|
|
4526
|
+
const text = await readFile9(join22(root, file), "utf8").catch(() => "");
|
|
4114
4527
|
for (const line of text.split(/\r?\n/)) {
|
|
4115
4528
|
const match = /^\s*(?:export\s+)?[A-Za-z_][A-Za-z0-9_.-]*\s*=\s*(.*)$/.exec(line);
|
|
4116
4529
|
if (!match) continue;
|
|
@@ -4239,10 +4652,10 @@ async function askTerminal(question) {
|
|
|
4239
4652
|
async function writeBinaryAtomic(file, bytes) {
|
|
4240
4653
|
const temp = `${file}.${process.pid}.${randomBytes2(6).toString("hex")}.tmp`;
|
|
4241
4654
|
await writeFile2(temp, bytes);
|
|
4242
|
-
await
|
|
4655
|
+
await rename3(temp, file);
|
|
4243
4656
|
}
|
|
4244
4657
|
var shown = (file) => {
|
|
4245
|
-
const rel =
|
|
4658
|
+
const rel = relative9(process.cwd(), file);
|
|
4246
4659
|
return rel && !rel.startsWith("..") ? rel : file;
|
|
4247
4660
|
};
|
|
4248
4661
|
async function runReport(options = {}, io = {}) {
|
|
@@ -4251,8 +4664,8 @@ async function runReport(options = {}, io = {}) {
|
|
|
4251
4664
|
const error = io.error ?? ((text) => void process.stderr.write(`${text}
|
|
4252
4665
|
`));
|
|
4253
4666
|
const env = io.env ?? process.env;
|
|
4254
|
-
const root =
|
|
4255
|
-
if (!(await
|
|
4667
|
+
const root = resolve13(options.path ?? process.cwd());
|
|
4668
|
+
if (!(await stat3(root).catch(() => null))?.isDirectory()) {
|
|
4256
4669
|
error(`\u2716 ${options.path ?? root} is not a directory: run ${NPX} in a repo, or pass its path (${NPX} path/to/repo)`);
|
|
4257
4670
|
return { code: 2, files: null, model: null };
|
|
4258
4671
|
}
|
|
@@ -4281,9 +4694,9 @@ async function runReport(options = {}, io = {}) {
|
|
|
4281
4694
|
model.durationMs += Math.round(performance.now() - started);
|
|
4282
4695
|
const redactor = await Redactor.forRepo(root, { includeCode: options.includeCode === true, env });
|
|
4283
4696
|
const clean = redactor.model(model);
|
|
4284
|
-
const outDir =
|
|
4285
|
-
await
|
|
4286
|
-
const files = { report:
|
|
4697
|
+
const outDir = resolve13(options.out ?? root);
|
|
4698
|
+
await mkdir5(outDir, { recursive: true });
|
|
4699
|
+
const files = { report: join23(outDir, REPORT_FILE), card: join23(outDir, CARD_FILE), summary: join23(outDir, SUMMARY_FILE) };
|
|
4287
4700
|
const t = clean.totals;
|
|
4288
4701
|
const card = {
|
|
4289
4702
|
repo: redactor.scrub(clean.repo),
|
|
@@ -4367,6 +4780,18 @@ ${style.bold("Anywhere")} ${style.dim("no design system needed")}
|
|
|
4367
4780
|
an unchanged component comes from the cache (YOUI_SPECIMEN_CACHE)
|
|
4368
4781
|
|
|
4369
4782
|
${style.bold("In a product repo")}
|
|
4783
|
+
adopt [--components dir] [--css file] [--system id] [--fork] [--dry-run]
|
|
4784
|
+
make this product's own UI its design system, kept in this repo: a draft
|
|
4785
|
+
contract per component in the components folder (the files stay where they
|
|
4786
|
+
are), the stylesheet's :root and dark variables as tokens (moved: the
|
|
4787
|
+
stylesheet imports the generated tokens.css, same names and values), three
|
|
4788
|
+
starter rules as warnings, then init and sync; --dry-run writes nothing.
|
|
4789
|
+
Variables youiyoux generated from another System are left to it (adopt says
|
|
4790
|
+
which); --fork takes them as the start of this product's own System
|
|
4791
|
+
system extract <dir> [--package name] [--dry-run]
|
|
4792
|
+
move the System adopt keeps here into a design repo of its own at <dir>
|
|
4793
|
+
(contracts with their implementations, tokens, rules), for a second app to
|
|
4794
|
+
follow; this product then follows it by path, and sync vendors its components
|
|
4370
4795
|
init --design <dir|package> write youi.project.json pointing at a design repo checkout, or at
|
|
4371
4796
|
the npm package that holds it (named, so no machine's folder), then sync;
|
|
4372
4797
|
a git repository goes in its source: { "git": "github:acme/design", "ref": "v3" }
|
|
@@ -4528,6 +4953,8 @@ var OPTIONS = {
|
|
|
4528
4953
|
dark: { type: "string" },
|
|
4529
4954
|
state: { type: "string" },
|
|
4530
4955
|
inventory: { type: "string" },
|
|
4956
|
+
package: { type: "string" },
|
|
4957
|
+
fork: { type: "boolean", default: false },
|
|
4531
4958
|
cache: { type: "string" },
|
|
4532
4959
|
role: { type: "string" },
|
|
4533
4960
|
license: { type: "string" },
|
|
@@ -4551,7 +4978,7 @@ var UsageError = class extends Error {
|
|
|
4551
4978
|
var run6 = promisify5(execFile5);
|
|
4552
4979
|
var print = (text) => void process.stdout.write(`${text}
|
|
4553
4980
|
`);
|
|
4554
|
-
var
|
|
4981
|
+
var json3 = (value) => print(JSON.stringify(value, null, 2));
|
|
4555
4982
|
function loadOptions(values) {
|
|
4556
4983
|
return { ...values.config ? { configPath: values.config } : {}, ...values.design ? { designRoot: values.design } : {}, ...values.root !== void 0 ? { productRoot: values.root || "." } : {} };
|
|
4557
4984
|
}
|
|
@@ -4559,17 +4986,17 @@ async function project(values) {
|
|
|
4559
4986
|
return loadProject(values.project ?? process.cwd(), loadOptions(values));
|
|
4560
4987
|
}
|
|
4561
4988
|
async function readJsonOrFile(value) {
|
|
4562
|
-
const text = value.trim().startsWith("{") ? value : await
|
|
4989
|
+
const text = value.trim().startsWith("{") ? value : await readFile10(value, "utf8");
|
|
4563
4990
|
return JSON.parse(text);
|
|
4564
4991
|
}
|
|
4565
4992
|
async function cmdScan(values) {
|
|
4566
4993
|
const p = await project(values);
|
|
4567
4994
|
const { report } = await scanProject(p);
|
|
4568
|
-
const out = values.out ??
|
|
4569
|
-
await writeFileAtomic(
|
|
4995
|
+
const out = values.out ?? join24(p.root, ".youi/usage.json");
|
|
4996
|
+
await writeFileAtomic(resolve14(out), `${JSON.stringify(report)}
|
|
4570
4997
|
`);
|
|
4571
|
-
const recorded = await recordUsage(values, p, report,
|
|
4572
|
-
if (values.json)
|
|
4998
|
+
const recorded = await recordUsage(values, p, report, resolve14(out));
|
|
4999
|
+
if (values.json) json3(report);
|
|
4573
5000
|
else print([renderScan(report, out), ...recorded ? [recorded] : []].join("\n"));
|
|
4574
5001
|
return 0;
|
|
4575
5002
|
}
|
|
@@ -4577,7 +5004,7 @@ async function recordUsage(values, p, report, usage) {
|
|
|
4577
5004
|
const { path, home } = indexOf(values);
|
|
4578
5005
|
try {
|
|
4579
5006
|
await recordProject(await sharedStore(path), p, report);
|
|
4580
|
-
await noteSource(home, { project: { root:
|
|
5007
|
+
await noteSource(home, { project: { root: resolve14(values.project ?? process.cwd()), options: Object.fromEntries(Object.entries(loadOptions(values)).map(([key2, value]) => [key2, resolve14(value)])), usage } });
|
|
4581
5008
|
return null;
|
|
4582
5009
|
} catch (error) {
|
|
4583
5010
|
return notRecorded(error);
|
|
@@ -4592,19 +5019,19 @@ function indexOf(values) {
|
|
|
4592
5019
|
}
|
|
4593
5020
|
async function cmdGaps(values) {
|
|
4594
5021
|
const dirs = inventoryDirs(values);
|
|
4595
|
-
const inventory = values.inventory ? InventoryReport.parse(JSON.parse(await
|
|
4596
|
-
const cwd = values.inventory ?
|
|
4597
|
-
const project2 = await loadProject(
|
|
5022
|
+
const inventory = values.inventory ? InventoryReport.parse(JSON.parse(await readFile10(resolve14(values.inventory), "utf8"))) : await scanInventory(values, dirs);
|
|
5023
|
+
const cwd = values.inventory ? dirname12(resolve14(values.inventory)) : process.cwd();
|
|
5024
|
+
const project2 = await loadProject(resolve14(dirs[0]), loadOptions(values)).catch(() => null);
|
|
4598
5025
|
const system = project2 ? { name: project2.system.config.name, components: project2.system.components.map((c) => c.contract.name) } : null;
|
|
4599
|
-
const store = await readCatalog(
|
|
5026
|
+
const store = await readCatalog(resolve14(discoverCacheDir(values.cache)), indexOf(values).path).catch((error) => {
|
|
4600
5027
|
if (error instanceof MissingIndexError) return null;
|
|
4601
5028
|
throw error;
|
|
4602
5029
|
});
|
|
4603
|
-
const roots = new Map(inventory.projects.map((p) => [p.id,
|
|
5030
|
+
const roots = new Map(inventory.projects.map((p) => [p.id, resolve14(cwd, p.root)]));
|
|
4604
5031
|
const report = await buildGaps({ inventory, system, dependencies: declaredPackages(roots) }, store);
|
|
4605
|
-
if (values.out) await writeFileAtomic(
|
|
5032
|
+
if (values.out) await writeFileAtomic(resolve14(values.out), `${JSON.stringify(report, null, 2)}
|
|
4606
5033
|
`);
|
|
4607
|
-
if (values.json)
|
|
5034
|
+
if (values.json) json3(report);
|
|
4608
5035
|
else print(renderGaps(report));
|
|
4609
5036
|
return 0;
|
|
4610
5037
|
}
|
|
@@ -4612,12 +5039,12 @@ async function cmdInventory(values) {
|
|
|
4612
5039
|
if (values.history) return cmdHistory(values, []);
|
|
4613
5040
|
const dirs = inventoryDirs(values);
|
|
4614
5041
|
const report = await scanInventory(values, dirs);
|
|
4615
|
-
const out =
|
|
5042
|
+
const out = resolve14(values.out ?? join24(dirs[0], ".youi/inventory.json"));
|
|
4616
5043
|
await writeFileAtomic(out, `${JSON.stringify(report, null, 2)}
|
|
4617
5044
|
`);
|
|
4618
5045
|
const recorded = await recordScan(values, report, out);
|
|
4619
|
-
if (values.json)
|
|
4620
|
-
else print([renderInventory(report,
|
|
5046
|
+
if (values.json) json3(report);
|
|
5047
|
+
else print([renderInventory(report, relative10(process.cwd(), out).startsWith("..") ? out : relative10(process.cwd(), out)), recorded].join("\n"));
|
|
4621
5048
|
return 0;
|
|
4622
5049
|
}
|
|
4623
5050
|
async function recordScan(values, report, out) {
|
|
@@ -4634,7 +5061,7 @@ async function recordScan(values, report, out) {
|
|
|
4634
5061
|
async function cmdHistory(values, positionals) {
|
|
4635
5062
|
const { path } = indexOf(values);
|
|
4636
5063
|
const scans = await (await sharedStore(path, process.env, { readOnly: true })).history({ ...positionals[0] ? { project: positionals[0] } : {}, limit: positiveInt(values.limit, "--limit") ?? 50 });
|
|
4637
|
-
if (values.json)
|
|
5064
|
+
if (values.json) json3(scans);
|
|
4638
5065
|
else print(renderHistory(scans));
|
|
4639
5066
|
return 0;
|
|
4640
5067
|
}
|
|
@@ -4642,19 +5069,19 @@ async function cmdIndex(values, positionals) {
|
|
|
4642
5069
|
const [action, ...rest] = positionals;
|
|
4643
5070
|
const { path, home } = indexOf(values);
|
|
4644
5071
|
if (action === "path") {
|
|
4645
|
-
if (values.json)
|
|
5072
|
+
if (values.json) json3({ store: storeLocation({ path }), sources: join24(home, SOURCES_FILE), scans: join24(home, SCANS_LOG) });
|
|
4646
5073
|
else print(storeLocation({ path }));
|
|
4647
5074
|
return 0;
|
|
4648
5075
|
}
|
|
4649
5076
|
if (action === "stats") {
|
|
4650
5077
|
const stats = await (await sharedStore(path, process.env, { readOnly: true })).stats();
|
|
4651
|
-
if (values.json)
|
|
5078
|
+
if (values.json) json3(stats);
|
|
4652
5079
|
else print([`${style.bold("index")} ${stats.location} ${style.dim(`\xB7 ${stats.engine} \xB7 schema ${stats.schema} \xB7 generation ${stats.generation ?? "not rebuilt"}`)}`, ...Object.entries(stats.tables).map(([table, n]) => ` ${table.padEnd(16)} ${n.toLocaleString("en-US")}`)].join("\n"));
|
|
4653
5080
|
return 0;
|
|
4654
5081
|
}
|
|
4655
5082
|
if (action === "rebuild") {
|
|
4656
|
-
const summary = await rebuildIndex(path, values.cache ? { discoverCache:
|
|
4657
|
-
if (values.json)
|
|
5083
|
+
const summary = await rebuildIndex(path, values.cache ? { discoverCache: resolve14(values.cache) } : {});
|
|
5084
|
+
if (values.json) json3(summary);
|
|
4658
5085
|
else print(renderRebuild(summary));
|
|
4659
5086
|
return 0;
|
|
4660
5087
|
}
|
|
@@ -4668,7 +5095,7 @@ async function cmdWhere(values, positionals) {
|
|
|
4668
5095
|
const { report } = await scanProject(p);
|
|
4669
5096
|
const limit = values.all ? Number.MAX_SAFE_INTEGER : Number(values.limit ?? (values.json ? 20 : 24));
|
|
4670
5097
|
const where2 = whereUsed(p, report, query, { ...values.route ? { route: values.route } : {}, limit });
|
|
4671
|
-
if (values.json)
|
|
5098
|
+
if (values.json) json3(where2);
|
|
4672
5099
|
else print(renderWhere(where2, report));
|
|
4673
5100
|
return 0;
|
|
4674
5101
|
}
|
|
@@ -4679,7 +5106,7 @@ async function cmdShow(values, positionals) {
|
|
|
4679
5106
|
const contract = findContract(p, query);
|
|
4680
5107
|
if (!contract) throw new Error(`no component "${query}"; known: ${p.system.components.map((c) => c.contract.name).join(", ")}`);
|
|
4681
5108
|
const card = componentCard(p, contract, (await scanProject(p)).report);
|
|
4682
|
-
if (values.json)
|
|
5109
|
+
if (values.json) json3(card);
|
|
4683
5110
|
else print(renderShow(card));
|
|
4684
5111
|
return 0;
|
|
4685
5112
|
}
|
|
@@ -4688,12 +5115,12 @@ async function cmdBom(values, positionals) {
|
|
|
4688
5115
|
if (!route) throw new UsageError("youiyoux bom <route>");
|
|
4689
5116
|
const p = await project(values);
|
|
4690
5117
|
const bom = screenBom(p, (await scanProject(p)).report, route);
|
|
4691
|
-
if (values.json)
|
|
5118
|
+
if (values.json) json3(bom);
|
|
4692
5119
|
else print(renderBom(bom));
|
|
4693
5120
|
return 0;
|
|
4694
5121
|
}
|
|
4695
5122
|
function baselineFile(values, p) {
|
|
4696
|
-
if (values["baseline-file"]) return
|
|
5123
|
+
if (values["baseline-file"]) return resolve14(values["baseline-file"]);
|
|
4697
5124
|
return values.baseline ? baselinePath(p, values.config) : null;
|
|
4698
5125
|
}
|
|
4699
5126
|
var checkScope = (values, positionals) => ({ ...positionals.length > 0 ? { files: positionals } : {}, ...values.rule ? { rule: values.rule } : {}, quiet: values.quiet });
|
|
@@ -4712,7 +5139,7 @@ async function cmdCheck(values, positionals) {
|
|
|
4712
5139
|
const result = await runCheck(p, { ...checkScope(values, positionals), baseline: baselineFile(values, p) });
|
|
4713
5140
|
if (format === "json") {
|
|
4714
5141
|
const { report } = result.scan;
|
|
4715
|
-
|
|
5142
|
+
json3({ errors: result.errors, incomplete: result.incomplete, identity: result.identity, unmatched: result.unmatched, notRun: result.notRun, violations: result.violations, baseline: result.baseline, lock: result.lock, suppressed: report.suppressed, coverage: report.coverage, status: report.status, checks: report.checks });
|
|
4716
5143
|
} else if (format === "github") {
|
|
4717
5144
|
for (const line of githubAnnotations(result, await repoPrefix(p.root))) print(line);
|
|
4718
5145
|
print(renderCheckText(result));
|
|
@@ -4724,7 +5151,7 @@ async function cmdCheckDiff(values, positionals, p, format) {
|
|
|
4724
5151
|
if (values.baseline) throw new UsageError("youiyoux check --diff looks only at added lines, so a baseline does not apply; drop --baseline");
|
|
4725
5152
|
const change = await workingTreeChange(p, values.base);
|
|
4726
5153
|
const result = configured(p) ? await runCheck(p, { ...checkScope(values, positionals), diff: change }) : await runRepoCheck(p, change);
|
|
4727
|
-
if (format === "json")
|
|
5154
|
+
if (format === "json") json3(diffJson(result));
|
|
4728
5155
|
else if (format === "github") {
|
|
4729
5156
|
for (const line of githubDiffAnnotations(result, await repoPrefix(p.root))) print(line);
|
|
4730
5157
|
print(renderDiffText(result));
|
|
@@ -4748,9 +5175,9 @@ async function cmdBaseline(values) {
|
|
|
4748
5175
|
const file = baselineFile(values, p) ?? baselinePath(p, values.config);
|
|
4749
5176
|
const written = await writeBaseline(p, file, { force: values.force });
|
|
4750
5177
|
const { totals } = written.baseline;
|
|
4751
|
-
if (values.json)
|
|
5178
|
+
if (values.json) json3({ file: written.file, action: written.action, totals, notAdded: written.notAdded, identity: written.baseline.identity });
|
|
4752
5179
|
else {
|
|
4753
|
-
print(`${style.ok(written.action)} ${
|
|
5180
|
+
print(`${style.ok(written.action)} ${relative10(process.cwd(), file) || file}: ${plural(totals.errors, "error")} \xB7 ${plural(totals.warnings, "warning")} accepted`);
|
|
4754
5181
|
print(style.dim(` rules ${describeIdentity(written.baseline.identity)}`));
|
|
4755
5182
|
if (written.notAdded > 0) print(style.warn(` ${plural(written.notAdded, "current violation")} not added: fix them, or accept them deliberately with --force`));
|
|
4756
5183
|
}
|
|
@@ -4764,30 +5191,30 @@ async function cmdPlan(values, positionals) {
|
|
|
4764
5191
|
else if (positionals[0] && values.patch) input = { element: positionals[0], patch: await readJsonOrFile(values.patch) };
|
|
4765
5192
|
else throw new UsageError("youiyoux plan <component> --patch <json|file> or youiyoux plan --token <id> [--value v]");
|
|
4766
5193
|
const plan = await planChange(p, scan, input);
|
|
4767
|
-
if (values.json)
|
|
5194
|
+
if (values.json) json3(plan);
|
|
4768
5195
|
else print(renderPlan(plan));
|
|
4769
5196
|
return 0;
|
|
4770
5197
|
}
|
|
4771
5198
|
async function cmdRules(values) {
|
|
4772
5199
|
const p = await project(values);
|
|
4773
5200
|
if (values.preview) {
|
|
4774
|
-
const draft = (0,
|
|
5201
|
+
const draft = (0, import_yaml4.parse)(await readFile10(values.preview, "utf8"));
|
|
4775
5202
|
const evaluation = evaluateRule(p, await scanProject(p), draft);
|
|
4776
|
-
if (values.json)
|
|
5203
|
+
if (values.json) json3(evaluation);
|
|
4777
5204
|
else print(renderRulePreview(evaluation));
|
|
4778
5205
|
return evaluation.ok && evaluation.findings !== null ? 0 : 1;
|
|
4779
5206
|
}
|
|
4780
5207
|
const rules = p.system.rules.map((rule2) => ({ rule: rule2, execution: ruleCapability(rule2) }));
|
|
4781
|
-
if (values.json)
|
|
5208
|
+
if (values.json) json3({ capabilities: checkCapabilities(), rules: rules.map(({ rule: rule2, execution }) => ({ id: rule2.id, severity: rule2.severity, ...execution })) });
|
|
4782
5209
|
else print(renderRules(rules));
|
|
4783
5210
|
return rules.some(({ execution }) => execution.state === "unsupported") ? 1 : 0;
|
|
4784
5211
|
}
|
|
4785
5212
|
async function cmdMigrate(values, positionals) {
|
|
4786
5213
|
const file = positionals[0];
|
|
4787
5214
|
if (!file) throw new UsageError("youiyoux migrate <migration.yaml> [--write] [--diff]");
|
|
4788
|
-
const spec = MigrationSpec.parse((0,
|
|
5215
|
+
const spec = MigrationSpec.parse((0, import_yaml4.parse)(await readFile10(file, "utf8")));
|
|
4789
5216
|
const result = await runMigration(await project(values), spec, { write: values.write });
|
|
4790
|
-
if (values.json)
|
|
5217
|
+
if (values.json) json3(result);
|
|
4791
5218
|
else print(renderMigration(result, values.diff || !values.write));
|
|
4792
5219
|
return result.status === "rejected" ? 1 : 0;
|
|
4793
5220
|
}
|
|
@@ -4797,14 +5224,14 @@ async function cmdLibrary(values, positionals) {
|
|
|
4797
5224
|
const p = await project(values);
|
|
4798
5225
|
const words = positionals.slice(1).join(" ");
|
|
4799
5226
|
const result = searchLibrary(await loadLibrary(p.repo.root), words, Number(values.limit ?? 20));
|
|
4800
|
-
if (values.json)
|
|
5227
|
+
if (values.json) json3(result);
|
|
4801
5228
|
else print(renderLibrarySearch(result, words));
|
|
4802
5229
|
return 0;
|
|
4803
5230
|
}
|
|
4804
5231
|
if (action === "request" && query && values.reason) {
|
|
4805
5232
|
const p = await project(values);
|
|
4806
5233
|
const request = adoptionRequest(p, await loadLibrary(p.repo.root), query, values.reason, await scanProject(p));
|
|
4807
|
-
if (values.json)
|
|
5234
|
+
if (values.json) json3(request);
|
|
4808
5235
|
else print(renderAdoptionRequest(request));
|
|
4809
5236
|
return 0;
|
|
4810
5237
|
}
|
|
@@ -4813,7 +5240,7 @@ async function cmdLibrary(values, positionals) {
|
|
|
4813
5240
|
async function cmdSync(values) {
|
|
4814
5241
|
const p = await loadProject(values.project ?? process.cwd(), { ...loadOptions(values), refreshDesign: true });
|
|
4815
5242
|
const result = await syncProject(p, { dryRun: values["dry-run"], adopt: values.adopt });
|
|
4816
|
-
if (values.json)
|
|
5243
|
+
if (values.json) json3(result);
|
|
4817
5244
|
else print([renderSync(result, values["dry-run"]), ...p.linked ? [style.warn(`! linked: the design system is your checkout ${p.linked}, not the config's source (youiyoux unlink to undo)`)] : []].join("\n"));
|
|
4818
5245
|
return result.actions.some((a) => a.action === "conflict") ? 1 : 0;
|
|
4819
5246
|
}
|
|
@@ -4821,7 +5248,7 @@ async function cmdInit(values) {
|
|
|
4821
5248
|
if (!values.design) throw new UsageError("youiyoux init --design <dir|package> (a design repo checkout, the directory holding youi.config.json, or the npm package that holds it)");
|
|
4822
5249
|
const root = values.project ?? process.cwd();
|
|
4823
5250
|
const design = await designArg(values.design, root);
|
|
4824
|
-
const result = await initProject({ root, ...design, schemaDir:
|
|
5251
|
+
const result = await initProject({ root, ...design, schemaDir: join24(TOOL_ROOT, "schema"), ...values.system ? { system: values.system } : {}, ...values.components ? { componentsDir: values.components } : {}, force: values.force });
|
|
4825
5252
|
if (!values.json) {
|
|
4826
5253
|
print(`${style.ok("created")} ${result.file}`);
|
|
4827
5254
|
for (const note of result.notes) print(style.dim(` \xB7 ${note}`));
|
|
@@ -4830,6 +5257,76 @@ async function cmdInit(values) {
|
|
|
4830
5257
|
const { design: _named, ...rest } = values;
|
|
4831
5258
|
return cmdSync({ ...design.package ? rest : values, project: root });
|
|
4832
5259
|
}
|
|
5260
|
+
async function cmdAdopt(values) {
|
|
5261
|
+
const root = resolve14(values.project ?? process.cwd());
|
|
5262
|
+
if (await exists(join24(root, PROJECT_FILE))) throw new Error(`${root} already has ${PROJECT_FILE}: it follows a design system already (youiyoux sync updates it)`);
|
|
5263
|
+
const plan = await planAdoption({ root, ...values.components ? { componentsDir: values.components } : {}, ...values.css ? { css: values.css } : {}, ...values.system ? { system: values.system } : {}, fork: values.fork });
|
|
5264
|
+
const taken = (await Promise.all(plan.files.map(async (f) => await exists(join24(root, f.path)) ? f.path : null))).filter((f) => f !== null);
|
|
5265
|
+
if (taken.length > 0) throw new Error(`nothing was written: ${taken.slice(0, 5).join(", ")}${taken.length > 5 ? ` and ${taken.length - 5} more` : ""} already exist${taken.length === 1 ? "s" : ""}`);
|
|
5266
|
+
if (values["dry-run"]) {
|
|
5267
|
+
if (values.json) json3(plan);
|
|
5268
|
+
else print([...adoptLines(plan, true), "", style.dim(`dry run: nothing written; would write ${plan.files.map((f) => f.path).join(", ")}${plan.stylesheet ? ` and change ${plan.stylesheet.path}` : ""}`)].join("\n"));
|
|
5269
|
+
return 0;
|
|
5270
|
+
}
|
|
5271
|
+
const stylesheet = plan.stylesheet ? await readFile10(join24(root, plan.stylesheet.path), "utf8") : null;
|
|
5272
|
+
try {
|
|
5273
|
+
for (const file of plan.files) await writeFileAtomic(join24(root, file.path), file.content);
|
|
5274
|
+
if (plan.stylesheet) await writeFileAtomic(join24(root, plan.stylesheet.path), plan.stylesheet.content);
|
|
5275
|
+
await initProject({ root, design: root, schemaDir: join24(TOOL_ROOT, "schema"), system: plan.system.id, componentsDir: plan.componentsDir, outputs: { ...plan.outputs, targets: [...plan.outputs.targets], dark: [...plan.outputs.dark] }, tokenSources: plan.tokenSources });
|
|
5276
|
+
await syncProject(await loadProject(root), { dryRun: true });
|
|
5277
|
+
} catch (error) {
|
|
5278
|
+
for (const file of [...plan.files.map((f) => f.path), PROJECT_FILE]) await rm6(join24(root, file), { force: true });
|
|
5279
|
+
for (const folder of [...new Set(plan.files.map((f) => dirname12(f.path)))].sort((a, b) => b.length - a.length)) await rmdir3(join24(root, folder)).catch(() => void 0);
|
|
5280
|
+
await rmdir3(join24(root, "design/components")).catch(() => void 0);
|
|
5281
|
+
await rmdir3(join24(root, "design")).catch(() => void 0);
|
|
5282
|
+
if (plan.stylesheet && stylesheet !== null) await writeFileAtomic(join24(root, plan.stylesheet.path), stylesheet);
|
|
5283
|
+
throw new Error(`adopt was undone, nothing kept: ${error instanceof Error ? error.message : String(error)}`);
|
|
5284
|
+
}
|
|
5285
|
+
if (!values.json) print([...adoptLines(plan), ""].join("\n"));
|
|
5286
|
+
const synced = await cmdSync({ ...values, project: root, components: void 0, css: void 0, system: void 0 });
|
|
5287
|
+
if (!values.json) print(`Next: describe each component in design/components/<name>/<name>.contract.json (its summary, guidance and examples; agents read them), see what is left with youiyoux check, and when a second app needs this System, youiyoux system extract <dir> moves it into its own repo`);
|
|
5288
|
+
return synced;
|
|
5289
|
+
}
|
|
5290
|
+
function adoptLines(plan, dryRun = false) {
|
|
5291
|
+
const { tokens } = plan;
|
|
5292
|
+
const shown2 = plan.components.slice(0, 6).join(", ") + (plan.components.length > 6 ? `, \u2026 ${plan.components.length - 6} more` : "");
|
|
5293
|
+
return [
|
|
5294
|
+
`${style.ok(dryRun ? "would adopt" : "adopted")} ${plan.system.name} ${style.dim(`(system ${plan.system.id}) from this product's own UI`)}`,
|
|
5295
|
+
` components ${plural(plan.components.length, "component")} in ${plan.componentsDir} (${shown2}): a draft contract each in design/components; the files stay where they are${plan.support.length > 0 ? ` (${plan.support.join(", ")} as support)` : ""}`,
|
|
5296
|
+
tokens.stylesheet ? ` tokens ${plural(tokens.count, "variable")}${tokens.dark ? ", light and dark," : ""} from ${tokens.stylesheet} \u2192 design/tokens${tokens.left.length > 0 ? `; ${tokens.left.length} left in the stylesheet, not typed: ${tokens.left.slice(0, 4).map((l) => `--${l.name} (${l.value})`).join(", ")}${tokens.left.length > 4 ? ", \u2026" : ""}` : ""}` : ` tokens none: no stylesheet defines :root variables (--css <file> names one)`,
|
|
5297
|
+
...plan.stylesheet ? [` stylesheet ${plan.stylesheet.path}: their ${plural(plan.stylesheet.moved, "declaration")}${tokens.dark ? " (light and dark)" : ""} moved into the System; it imports ${plan.outputs.dir}/tokens.css (same names, same values)`] : [],
|
|
5298
|
+
` rules 3 starter rules, as warnings: design/rules`,
|
|
5299
|
+
` config youi.config.json (the System) and youi.project.json (this product follows it)`,
|
|
5300
|
+
...plan.notes.map((n) => style.warn(` ! ${n}`)),
|
|
5301
|
+
// The product follows a System already: its values were left to it, and here is how to keep one System, or fork it.
|
|
5302
|
+
...plan.follows.map((g) => {
|
|
5303
|
+
const system = g.system ?? "a System";
|
|
5304
|
+
return style.warn(` ! ${g.file} holds ${plural(g.count, "variable")} youiyoux generated from ${system}${g.version ? ` v${g.version}` : ""}: this product already uses ${g.system ? "that System" : "one"}, so they were left to it. To manage the product with ${system}, run youiyoux init --design <${system}'s repo or package>; to make a System of the product's own from them, run youiyoux adopt --fork`);
|
|
5305
|
+
})
|
|
5306
|
+
];
|
|
5307
|
+
}
|
|
5308
|
+
async function cmdSystem(values, positionals) {
|
|
5309
|
+
const [action, dir, ...extra] = positionals;
|
|
5310
|
+
if (action !== "extract" || dir === void 0 || extra.length > 0) throw new UsageError("youiyoux system extract <dir> [--package <name>] [--dry-run] (move the System youiyoux adopt keeps in this product into a design repo of its own)");
|
|
5311
|
+
if (values.package !== void 0 && !PACKAGE_NAME.test(values.package)) throw new UsageError(`--package ${JSON.stringify(values.package)}: expected an npm package name such as @acme/design`);
|
|
5312
|
+
const p = await project(values);
|
|
5313
|
+
const result = await extractSystem(p, dir, { ...values.package ? { package: values.package } : {}, dryRun: values["dry-run"] });
|
|
5314
|
+
const contracts = result.written.filter((f) => f.endsWith(".contract.json")).length;
|
|
5315
|
+
const lines = [
|
|
5316
|
+
`${style.ok(values["dry-run"] ? "would extract" : "extracted")} ${p.system.config.name} ${style.dim(`(system ${result.system.id})`)} \u2192 ${result.target}`,
|
|
5317
|
+
` repo ${plural(result.written.length, "file")}: youi.config.json, ${plural(contracts, "contract")} with their implementations beside them, the tokens, rules${result.system.design ? " and design.md" : ""}${values.package ? `, and package.json (${values.package})` : ""}`,
|
|
5318
|
+
` product youi.project.json follows it (source: ${JSON.stringify(result.source)}); ${values["dry-run"] ? "would remove" : "removed"} its System files: ${result.removed.join(", ")}`
|
|
5319
|
+
];
|
|
5320
|
+
if (values["dry-run"]) {
|
|
5321
|
+
print([...lines, "", style.dim("dry run: nothing written or removed")].join("\n"));
|
|
5322
|
+
return 0;
|
|
5323
|
+
}
|
|
5324
|
+
print([...lines, ""].join("\n"));
|
|
5325
|
+
const synced = await cmdSync({ ...values, project: p.root, package: void 0 });
|
|
5326
|
+
const git3 = `push it and set source to { "git": "<its url>", "ref": "v${result.system.version}" }`;
|
|
5327
|
+
print(`Next: make ${relative10(process.cwd(), result.target) || "."} a repository of its own and commit it. For each app to follow it, ${values.package ? `publish ${values.package}, add it as a dependency and set source to { "package": "${values.package}" }, or ${git3}` : `${git3}, or publish it as an npm package (--package names it)`}. Change components there from now on: youiyoux sync merges them into each product`);
|
|
5328
|
+
return synced;
|
|
5329
|
+
}
|
|
4833
5330
|
async function cmdLink(values, positionals) {
|
|
4834
5331
|
const [dir] = positionals;
|
|
4835
5332
|
const p = await loadProject(values.project ?? process.cwd(), { ...loadOptions(values), ...dir !== void 0 ? { designRoot: dir } : {} });
|
|
@@ -4845,37 +5342,37 @@ async function cmdUnlink(values) {
|
|
|
4845
5342
|
const root = values.project ?? process.cwd();
|
|
4846
5343
|
const linked = await linkedDesign(root);
|
|
4847
5344
|
if (linked !== null) await setLink(root, null);
|
|
4848
|
-
print(linked ? `${style.ok("unlinked")} ${
|
|
5345
|
+
print(linked ? `${style.ok("unlinked")} ${resolve14(root)} uses its config's source again` : `${resolve14(root)} was not linked`);
|
|
4849
5346
|
return 0;
|
|
4850
5347
|
}
|
|
4851
5348
|
async function designArg(value, root) {
|
|
4852
|
-
if (existsSync5(
|
|
4853
|
-
return { design: await designRootOf({ package: value },
|
|
5349
|
+
if (existsSync5(resolve14(value)) || !PACKAGE_NAME.test(value)) return { design: value };
|
|
5350
|
+
return { design: await designRootOf({ package: value }, join24(resolve14(root), PROJECT_FILE), root), package: value };
|
|
4854
5351
|
}
|
|
4855
5352
|
async function cmdBuild(values) {
|
|
4856
|
-
const root =
|
|
5353
|
+
const root = resolve14(values.design ?? process.cwd());
|
|
4857
5354
|
const system = getSystem(await loadDesignRepo(root), values.system);
|
|
4858
5355
|
if (safeId(system.config.id) === null) throw new Error(`Cannot build system ${JSON.stringify(system.config.id)}: the id must be a plain name without ".." or path separators.`);
|
|
4859
5356
|
const output = OutputConfig.parse({ cssVarPrefix: values.prefix ?? "youi", targets: ["css-vars", "tailwind-v4", "ts", "react-native"] });
|
|
4860
5357
|
const started = performance.now();
|
|
4861
5358
|
const built = await buildTokens(root, system.config, output, { contracts: system.components.map((c) => c.contract) });
|
|
4862
|
-
const out =
|
|
4863
|
-
const boundary = values.out ? out :
|
|
4864
|
-
if (!within(boundary, out) || built.files.some((file) => !within(out,
|
|
4865
|
-
for (const file of built.files) await writeFileAtomic(
|
|
4866
|
-
if (values.json)
|
|
5359
|
+
const out = resolve14(values.out ?? join24(root, "build", system.config.id));
|
|
5360
|
+
const boundary = values.out ? out : join24(root, "build");
|
|
5361
|
+
if (!within(boundary, out) || built.files.some((file) => !within(out, join24(out, file.path)))) throw new Error(`Cannot build system ${JSON.stringify(system.config.id)}: an output path leaves the output directory.`);
|
|
5362
|
+
for (const file of built.files) await writeFileAtomic(join24(out, file.path), file.content);
|
|
5363
|
+
if (values.json) json3({ out, files: built.files.map((f) => f.path), tokens: built.tokens.length });
|
|
4867
5364
|
else print(`${style.ok("built")} ${plural(built.tokens.length, "token")} \u2192 ${built.files.map((f) => f.path).join(", ")} ${style.dim(`in ${out} (${ms(performance.now() - started)})`)}`);
|
|
4868
5365
|
return 0;
|
|
4869
5366
|
}
|
|
4870
5367
|
async function cmdParity(values) {
|
|
4871
5368
|
if (!values.css) throw new UsageError("youiyoux parity --css <file> [--prefix p]");
|
|
4872
|
-
const root =
|
|
5369
|
+
const root = resolve14(values.design ?? process.cwd());
|
|
4873
5370
|
const system = getSystem(await loadDesignRepo(root), values.system);
|
|
4874
5371
|
const prefix = values.prefix ?? "youi";
|
|
4875
5372
|
const dark = { selector: values.dark ?? ':root[data-theme="dark"]' };
|
|
4876
5373
|
const built = await buildTokens(root, system.config, OutputConfig.parse({ cssVarPrefix: prefix, dark: [dark], targets: ["css-vars"] }));
|
|
4877
|
-
const report = compareParity(await
|
|
4878
|
-
if (values.json)
|
|
5374
|
+
const report = compareParity(await readFile10(values.css, "utf8"), { light: { selector: values.light ?? ":root" }, dark }, built.files[0].content, { light: { selector: ":root" }, dark }, prefix);
|
|
5375
|
+
if (values.json) json3(report);
|
|
4879
5376
|
else {
|
|
4880
5377
|
const ok = report.different.length === 0 && report.missing.length === 0;
|
|
4881
5378
|
print(`${ok ? style.ok("\u2713 parity") : style.error("\u2716 parity")} ${report.matched} matched \xB7 ${report.different.length} different \xB7 ${report.missing.length} missing \xB7 ${style.dim(`${report.added.length} new in the build`)}`);
|
|
@@ -4887,18 +5384,18 @@ async function cmdParity(values) {
|
|
|
4887
5384
|
async function cmdSchema(values) {
|
|
4888
5385
|
const schemas = renderJsonSchemas();
|
|
4889
5386
|
if (values.check) {
|
|
4890
|
-
const dir =
|
|
5387
|
+
const dir = join24(values.design ? resolve14(values.design) : TOOL_ROOT, "schema");
|
|
4891
5388
|
const stale = [];
|
|
4892
|
-
for (const { file, content } of schemas) if (await
|
|
4893
|
-
if (values.json)
|
|
5389
|
+
for (const { file, content } of schemas) if (await readFile10(join24(dir, file), "utf8").catch(() => null) !== content) stale.push(file);
|
|
5390
|
+
if (values.json) json3({ dir, stale });
|
|
4894
5391
|
else print(stale.length === 0 ? `${style.ok("\u2713")} ${dir} matches this youi's schemas` : `${style.error("\u2716")} out of date in ${dir}: ${stale.join(", ")}; run youiyoux schema --write${values.design ? " --design <dir>" : ""}`);
|
|
4895
5392
|
return stale.length === 0 ? 0 : 1;
|
|
4896
5393
|
}
|
|
4897
5394
|
if (values.write) {
|
|
4898
|
-
const root = values.design ?
|
|
4899
|
-
for (const { file, content } of schemas) await writeFileAtomic(
|
|
5395
|
+
const root = values.design ? resolve14(values.design) : TOOL_ROOT;
|
|
5396
|
+
for (const { file, content } of schemas) await writeFileAtomic(join24(root, "schema", file), content);
|
|
4900
5397
|
print(`${style.ok("wrote")} ${schemas.map((s) => s.file).join(", ")}`);
|
|
4901
|
-
} else
|
|
5398
|
+
} else json3(Object.fromEntries(schemas.map((s) => [s.file, JSON.parse(s.content)])));
|
|
4902
5399
|
return 0;
|
|
4903
5400
|
}
|
|
4904
5401
|
function hookTarget(values) {
|
|
@@ -4919,14 +5416,14 @@ async function cmdHooks(values, positionals) {
|
|
|
4919
5416
|
if (action !== "install") throw new UsageError("youiyoux hooks install [--claude] [--git] [--ci [--diff]] [--strict] or youiyoux hooks run claude|pre-commit");
|
|
4920
5417
|
const p = await loadCheckProject(values.project ?? process.cwd(), loadOptions(values), !values.project);
|
|
4921
5418
|
if (values.ci && !configured(p)) throw new UsageError("Run youiyoux init --design <dir> before installing the pinned CI workflow; repo-mode CI can run youiyoux check --diff <base> --strict directly");
|
|
4922
|
-
const target = { ...hookTarget(values), project: p.root, ...configured(p) && p.configFile && p.configFile !==
|
|
5419
|
+
const target = { ...hookTarget(values), project: p.root, ...configured(p) && p.configFile && p.configFile !== join24(p.root, PROJECT_FILE) ? { config: p.configFile } : {}, ...values.root !== void 0 ? { root: p.root } : {} };
|
|
4923
5420
|
const all = !values.claude && !values.git && !values.ci;
|
|
4924
5421
|
const results = [];
|
|
4925
5422
|
if (values.claude || all) results.push({ kind: "Claude Code", ...await installClaudeHook(await repoRootOf(p.root), hookCommand(TOOL_ROOT, "claude", target)) });
|
|
4926
5423
|
if (values.git || all) results.push({ kind: "git pre-commit", ...await installPreCommit(p.root, hookCommand(TOOL_ROOT, "pre-commit", target, values.strict ? ["--strict"] : []), values.strict) });
|
|
4927
5424
|
if (values.git || all) results.push({ kind: "Codex", ...configured(p) ? codexFallback(p.root, p.config.agents.agentsMd) : await installRepoAgentInstructions(p.root) });
|
|
4928
5425
|
if (values.ci && configured(p)) results.push({ kind: "CI", ...await installWorkflow(p, TOOL_ROOT, target, values.force, { diff: values.diff }) });
|
|
4929
|
-
if (values.json)
|
|
5426
|
+
if (values.json) json3(results);
|
|
4930
5427
|
else for (const r of results) print(`${r.action === "unchanged" || r.action === "skipped" ? style.dim(r.action) : style.ok(r.action)} ${r.kind}: ${r.file}
|
|
4931
5428
|
${style.dim(r.guarantee)}`);
|
|
4932
5429
|
return 0;
|
|
@@ -4934,11 +5431,11 @@ async function cmdHooks(values, positionals) {
|
|
|
4934
5431
|
async function cmdAgents(values, positionals) {
|
|
4935
5432
|
const [action] = positionals;
|
|
4936
5433
|
if (action !== "install" && action !== "remove") throw new UsageError("youiyoux agents install|remove [--claude] [--codex] [--json]");
|
|
4937
|
-
const projectRoot =
|
|
5434
|
+
const projectRoot = resolve14(values.project ?? process.cwd());
|
|
4938
5435
|
const repoRoot = await repoRootOf(projectRoot);
|
|
4939
|
-
const target = { project: projectRoot, ...values.config ? { config:
|
|
5436
|
+
const target = { project: projectRoot, ...values.config ? { config: resolve14(values.config) } : {}, ...values.design ? { design: resolve14(values.design) } : {}, ...values.root !== void 0 ? { root: resolve14(values.root || ".") } : {} };
|
|
4940
5437
|
const server = mcpCommand(TOOL_ROOT, target);
|
|
4941
|
-
const system = existsSync5(
|
|
5438
|
+
const system = existsSync5(resolve14(values.config ?? join24(projectRoot, PROJECT_FILE))) ? await project(values) : null;
|
|
4942
5439
|
const claudeServer = system && system.root === repoRoot ? { command: "node", args: mcpArgs(system) } : relativeTo(repoRoot, server);
|
|
4943
5440
|
const both = !values.claude && !values.codex;
|
|
4944
5441
|
const results = [];
|
|
@@ -4946,7 +5443,7 @@ async function cmdAgents(values, positionals) {
|
|
|
4946
5443
|
results.push(...rule2.map((r) => ({ kind: "agent rule", ...r })));
|
|
4947
5444
|
if (values.claude || both) results.push({ kind: "Claude Code MCP", ...action === "install" ? await installClaudeMcp(repoRoot, claudeServer) : await removeClaudeMcp(repoRoot) });
|
|
4948
5445
|
if (values.codex || both) results.push({ kind: "Codex MCP", ...action === "install" ? await installCodexMcp(repoRoot, server) : await removeCodexMcp(repoRoot) });
|
|
4949
|
-
if (values.json)
|
|
5446
|
+
if (values.json) json3(results);
|
|
4950
5447
|
else {
|
|
4951
5448
|
for (const r of results) print(`${r.action === "unchanged" ? style.dim("unchanged") : style.ok(r.action)} ${r.kind}: ${r.file}
|
|
4952
5449
|
${style.dim(r.guarantee)}`);
|
|
@@ -5021,23 +5518,23 @@ async function cmdUi(values) {
|
|
|
5021
5518
|
async function cmdDetect(values, positionals) {
|
|
5022
5519
|
const target = detectTarget(positionals);
|
|
5023
5520
|
if (!target) throw new UsageError("youiyoux detect <repo-path> or youiyoux detect <url\u2026> [--expose] (one repo, or one or more URLs; not both)");
|
|
5024
|
-
const { repoReport, siteReport, writeReport } = await import("./src-
|
|
5025
|
-
const out =
|
|
5521
|
+
const { repoReport, siteReport, writeReport } = await import("./src-J6GPLCVO.js");
|
|
5522
|
+
const out = resolve14(values.out ?? ".youi/detect");
|
|
5026
5523
|
const report = target.kind === "repo" ? await repoReport(target.path) : await siteReport(target.urls, { out, expose: values.expose, ...process.env.YOUI_CHROMIUM ? { executablePath: process.env.YOUI_CHROMIUM } : {} });
|
|
5027
5524
|
const file = await writeReport(report, out);
|
|
5028
|
-
if (values.json)
|
|
5525
|
+
if (values.json) json3(report);
|
|
5029
5526
|
else {
|
|
5030
|
-
const shown2 =
|
|
5527
|
+
const shown2 = relative10(process.cwd(), file);
|
|
5031
5528
|
print(renderDetect(report, shown2 && !shown2.startsWith("..") ? shown2 : file));
|
|
5032
5529
|
}
|
|
5033
5530
|
return 0;
|
|
5034
5531
|
}
|
|
5035
5532
|
async function cmdRender(values, positionals, io) {
|
|
5036
|
-
const root =
|
|
5037
|
-
const targets = positionals.map((t) => existsSync5(
|
|
5533
|
+
const root = resolve14(values.root ?? values.project ?? process.cwd());
|
|
5534
|
+
const targets = positionals.map((t) => existsSync5(resolve14(t)) ? resolve14(t) : t);
|
|
5038
5535
|
try {
|
|
5039
5536
|
const report = await renderShots(root, targets, { ...values.base ? { base: values.base } : {}, ...process.env.YOUI_CHROMIUM ? { executablePath: process.env.YOUI_CHROMIUM } : {}, ...io.render });
|
|
5040
|
-
if (values.json)
|
|
5537
|
+
if (values.json) json3({ ...report, next: shotsNext(report, (t) => `run youiyoux render${t ? ` ${t}` : ""} again`) });
|
|
5041
5538
|
else print(renderShots2(report));
|
|
5042
5539
|
return targets.length > 0 && report.components.every((c) => c.shots.length === 0) ? 1 : 0;
|
|
5043
5540
|
} catch (error) {
|
|
@@ -5048,20 +5545,20 @@ async function cmdRender(values, positionals, io) {
|
|
|
5048
5545
|
}
|
|
5049
5546
|
}
|
|
5050
5547
|
async function product(values) {
|
|
5051
|
-
const project2 = await loadProject(
|
|
5052
|
-
return { project: project2, root: project2?.root ??
|
|
5548
|
+
const project2 = await loadProject(resolve14(values.project ?? process.cwd()), loadOptions(values)).catch(() => null);
|
|
5549
|
+
return { project: project2, root: project2?.root ?? resolve14(values.root ?? values.project ?? process.cwd()) };
|
|
5053
5550
|
}
|
|
5054
5551
|
async function cmdRequests(values, positionals) {
|
|
5055
5552
|
const usage = "youiyoux requests [--status open|addressed|closed|all] [--route r] or youiyoux requests show <id> | reply <id> --message <text> | addressed <id> --message <what you changed>";
|
|
5056
5553
|
const [action, id, ...rest] = positionals;
|
|
5057
5554
|
if (rest.length > 0) throw new UsageError(usage);
|
|
5058
|
-
const { RequestStore, requestBrief } = await import("./src-
|
|
5555
|
+
const { RequestStore, requestBrief } = await import("./src-4JGIPP5A.js");
|
|
5059
5556
|
const { project: project2, root } = await product(values);
|
|
5060
|
-
const store = new RequestStore(root, project2?.config.id ??
|
|
5557
|
+
const store = new RequestStore(root, project2?.config.id ?? basename5(root));
|
|
5061
5558
|
const agent = { kind: "agent", name: "youiyoux CLI" };
|
|
5062
5559
|
const one = (request) => {
|
|
5063
5560
|
const brief = requestBrief(request, root);
|
|
5064
|
-
if (values.json)
|
|
5561
|
+
if (values.json) json3(brief);
|
|
5065
5562
|
else print(renderBrief(brief, true));
|
|
5066
5563
|
return 0;
|
|
5067
5564
|
};
|
|
@@ -5071,7 +5568,7 @@ async function cmdRequests(values, positionals) {
|
|
|
5071
5568
|
if (!["open", "addressed", "closed", "all"].includes(status)) throw new UsageError("--status takes open, addressed, closed or all");
|
|
5072
5569
|
const { requests, unreadable } = await store.list();
|
|
5073
5570
|
const briefs = requests.filter((r) => (status === "all" || r.status === status) && (!values.route || r.target.route === values.route)).map((r) => requestBrief(r, root));
|
|
5074
|
-
if (values.json)
|
|
5571
|
+
if (values.json) json3({ requests: briefs, unreadable });
|
|
5075
5572
|
else print(renderRequests(briefs, status, unreadable));
|
|
5076
5573
|
return unreadable.length ? 1 : 0;
|
|
5077
5574
|
}
|
|
@@ -5087,22 +5584,22 @@ async function cmdScreens(values, positionals, io) {
|
|
|
5087
5584
|
const usage = "youiyoux screens capture --base <url of the app in development> [--route <one route>] [--project dir] [--out <screens dir>] [--storage-state <signed-in session>] [--json] or youiyoux screens login --base <url> [--project dir] [--storage-state <file>]";
|
|
5088
5585
|
if (!["capture", "login"].includes(positionals[0] ?? "") || positionals.length > 1 || !values.base) throw new UsageError(usage);
|
|
5089
5586
|
const { root } = await product(values);
|
|
5090
|
-
if (positionals[0] === "login") return screensLogin(values.base, values["storage-state"] ?
|
|
5091
|
-
const dir =
|
|
5092
|
-
if (!existsSync5(
|
|
5587
|
+
if (positionals[0] === "login") return screensLogin(values.base, values["storage-state"] ? resolve14(values["storage-state"]) : sessionFile(root), io);
|
|
5588
|
+
const dir = resolve14(values.out ?? join24(root, ".youi/screens"));
|
|
5589
|
+
if (!existsSync5(join24(dir, "screens.json"))) {
|
|
5093
5590
|
process.stderr.write(`${style.error("\u2716")} no screens.json in ${dir}: list the routes to capture there, as {"schema":"youiyoux.screens/v1","project":"<name>","source":"<what the app is>","capturedAt":"","viewport":{"width":1440,"height":900},"screens":[{"route":"/","path":"/"}]}
|
|
5094
5591
|
`);
|
|
5095
5592
|
return 1;
|
|
5096
5593
|
}
|
|
5097
|
-
const session = chosenSession(root, values["storage-state"] ?
|
|
5594
|
+
const session = chosenSession(root, values["storage-state"] ? resolve14(values["storage-state"]) : void 0);
|
|
5098
5595
|
if (session?.saved) process.stderr.write(`${style.dim(`signed in with the session youiyoux screens login saved (${session.file}); delete it to capture signed out`)}
|
|
5099
5596
|
`);
|
|
5100
|
-
const { launchBrowser } = await import("./src-
|
|
5597
|
+
const { launchBrowser } = await import("./src-J6GPLCVO.js");
|
|
5101
5598
|
const browser = await launchBrowser(process.env.YOUI_CHROMIUM, { purpose: { what: "youiyoux screens capture", command: "youiyoux screens capture" } });
|
|
5102
5599
|
try {
|
|
5103
5600
|
const report = await captureScreens(dir, { base: values.base, root, browser, ...values.route ? { routes: [values.route] } : {}, ...session ? { storageState: session.file } : {}, log: (line) => process.stderr.write(`${style.dim(line)}
|
|
5104
5601
|
`) });
|
|
5105
|
-
if (values.json)
|
|
5602
|
+
if (values.json) json3(report);
|
|
5106
5603
|
else print(captureText(report));
|
|
5107
5604
|
return report.screens.every((s) => s.error) ? 1 : 0;
|
|
5108
5605
|
} finally {
|
|
@@ -5154,7 +5651,7 @@ async function cmdReport(values, positionals, io) {
|
|
|
5154
5651
|
return result.code;
|
|
5155
5652
|
}
|
|
5156
5653
|
async function cmdMcp(values) {
|
|
5157
|
-
const { startStdioServer } = await import("./src-
|
|
5654
|
+
const { startStdioServer } = await import("./src-NWPGCDGH.js");
|
|
5158
5655
|
await startStdioServer({ projectRoot: values.project ?? process.cwd(), ...loadOptions(values), ...values.cache ? { discoverCache: values.cache } : {} });
|
|
5159
5656
|
return -1;
|
|
5160
5657
|
}
|
|
@@ -5165,13 +5662,13 @@ function positiveInt(value, flag) {
|
|
|
5165
5662
|
return n;
|
|
5166
5663
|
}
|
|
5167
5664
|
async function cmdDiscover(values, positionals, io) {
|
|
5168
|
-
const cacheDir =
|
|
5665
|
+
const cacheDir = resolve14(discoverCacheDir(values.cache));
|
|
5169
5666
|
const { path, home } = indexOf(values);
|
|
5170
5667
|
const catalog = () => readCatalog(cacheDir, path);
|
|
5171
5668
|
const [action, ...rest] = positionals;
|
|
5172
5669
|
if (action === "sources") {
|
|
5173
5670
|
const names = await selectSources(cacheDir, rest[0], rest[1]);
|
|
5174
|
-
if (values.json)
|
|
5671
|
+
if (values.json) json3(names);
|
|
5175
5672
|
else print(names.join("\n"));
|
|
5176
5673
|
return 0;
|
|
5177
5674
|
}
|
|
@@ -5181,7 +5678,7 @@ async function cmdDiscover(values, positionals, io) {
|
|
|
5181
5678
|
const summary = await syncIndex({ ...io.sleep ? { sleep: io.sleep } : {}, cacheDir, icons: values.icons ?? false, ...io.fetcher ? { fetcher: io.fetcher } : {}, ...io.timeoutMs ? { timeoutMs: io.timeoutMs } : {}, ...token ? { token } : {}, ...maxRegistries !== void 0 ? { maxRegistries } : {} });
|
|
5182
5679
|
const loaded = await ingestCatalog(await sharedStore(path), cacheDir);
|
|
5183
5680
|
await noteSource(home, { discover: cacheDir });
|
|
5184
|
-
if (values.json)
|
|
5681
|
+
if (values.json) json3(summary);
|
|
5185
5682
|
else print(`${renderDiscoverSync(summary)}
|
|
5186
5683
|
${style.dim(`store \u2192 ${storeLocation({ path })} (${plural(loaded.chosen, "listing")})`)}`);
|
|
5187
5684
|
return 0;
|
|
@@ -5194,11 +5691,11 @@ ${style.dim(`store \u2192 ${storeLocation({ path })} (${plural(loaded.chosen, "l
|
|
|
5194
5691
|
if (action === "show") {
|
|
5195
5692
|
if (!rest[0]) throw new UsageError("youiyoux discover show <id|ref>");
|
|
5196
5693
|
if (values.source) {
|
|
5197
|
-
|
|
5694
|
+
json3(await candidateSource(cacheDir, rest.join(" "), io));
|
|
5198
5695
|
return 0;
|
|
5199
5696
|
}
|
|
5200
5697
|
const shown2 = await showFromCatalog(await catalog(), rest.join(" "));
|
|
5201
|
-
if (values.json)
|
|
5698
|
+
if (values.json) json3(shown2);
|
|
5202
5699
|
else print(renderDiscoverShow(shown2));
|
|
5203
5700
|
return 0;
|
|
5204
5701
|
}
|
|
@@ -5211,11 +5708,13 @@ ${style.dim(`store \u2192 ${storeLocation({ path })} (${plural(loaded.chosen, "l
|
|
|
5211
5708
|
await noteSource(home, { discover: cacheDir });
|
|
5212
5709
|
}
|
|
5213
5710
|
const result = await searchCatalog(await catalog(), need, { ...values.role ? { role: values.role } : {}, ...licenses?.length ? { constraints: { licenses } } : {}, limit: positiveInt(values.limit, "--limit") ?? 10, offset: positiveInt(values.offset, "--offset") ?? 0, all: values.all, facets: true });
|
|
5214
|
-
if (values.json)
|
|
5711
|
+
if (values.json) json3(result);
|
|
5215
5712
|
else print(renderDiscoverSearch(result));
|
|
5216
5713
|
return 0;
|
|
5217
5714
|
}
|
|
5218
5715
|
var COMMANDS = {
|
|
5716
|
+
adopt: cmdAdopt,
|
|
5717
|
+
system: cmdSystem,
|
|
5219
5718
|
link: cmdLink,
|
|
5220
5719
|
unlink: cmdUnlink,
|
|
5221
5720
|
show: cmdShow,
|
|
@@ -5277,7 +5776,7 @@ ${HELP}
|
|
|
5277
5776
|
}
|
|
5278
5777
|
return guarded(() => handler(located(command2, parsed.values), rest, io));
|
|
5279
5778
|
}
|
|
5280
|
-
var IN_A_PRODUCT = /* @__PURE__ */ new Set(["link", "unlink", "show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "sync", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "render", "screens", "requests"]);
|
|
5779
|
+
var IN_A_PRODUCT = /* @__PURE__ */ new Set(["system", "link", "unlink", "show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "sync", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "render", "screens", "requests"]);
|
|
5281
5780
|
var IN_A_DESIGN_REPO = /* @__PURE__ */ new Set(["build", "parity"]);
|
|
5282
5781
|
function located(command2, values, cwd = process.cwd()) {
|
|
5283
5782
|
if (IN_A_PRODUCT.has(command2) && values.project === void 0 && values.config === void 0 && values.root === void 0) {
|