youiyoux 0.1.7 → 0.1.9
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-O66KB7BU.js → chunk-3HON5Q5K.js} +3 -3
- package/dist/chunks/{chunk-3SQQNQDN.js → chunk-6GITMASZ.js} +1 -1
- package/dist/chunks/{chunk-BITS5O6H.js → chunk-DCBF7QQY.js} +4 -4
- package/dist/chunks/{chunk-YWBQO7PO.js → chunk-DWYDHVLZ.js} +74 -6
- package/dist/chunks/{chunk-N6RVMQCV.js → chunk-E5OHZJBS.js} +391 -155
- package/dist/chunks/{chunk-SIXL67TZ.js → chunk-F33PKGES.js} +2 -2
- package/dist/chunks/{chunk-TV5UX3FE.js → chunk-FLBRX6Y3.js} +1 -1
- package/dist/chunks/{chunk-7P4UXAIY.js → chunk-HDJ2TOWZ.js} +1 -1
- package/dist/chunks/{chunk-4DVBWN7H.js → chunk-Y4IBE6WS.js} +3 -3
- package/dist/chunks/{src-SDJCFXRI.js → src-BYNWPNRT.js} +11 -1
- package/dist/chunks/{src-DYZIE4XZ.js → src-DPFJ4S2N.js} +2 -2
- package/dist/chunks/{src-4JGIPP5A.js → src-EUVBYTKY.js} +4 -4
- package/dist/chunks/{src-BKPSGYS2.js → src-M3OJMCUE.js} +3 -3
- package/dist/chunks/{src-WP66UHF7.js → src-PURAHRCR.js} +2 -2
- package/dist/chunks/{src-NWPGCDGH.js → src-YKPJ3PII.js} +6 -6
- package/dist/chunks/{src-J6GPLCVO.js → src-ZG7JU6YJ.js} +2 -2
- package/dist/hub/assets/{CandidatePanel-D6Tenu7w.js → CandidatePanel-ZzbsyZo3.js} +1 -1
- package/dist/hub/assets/{CatalogScreen-C1tvk0sB.js → CatalogScreen-DDYlvOHy.js} +1 -1
- package/dist/hub/assets/{ChangesScreen-D_6yzFGP.js → ChangesScreen-C8_EyHgJ.js} +1 -1
- package/dist/hub/assets/{CheatSheet-Ci7mG0sm.js → CheatSheet-DfcPiXdS.js} +1 -1
- package/dist/hub/assets/{CommandPalette-C4twwce1.js → CommandPalette-DfFkDTVg.js} +1 -1
- package/dist/hub/assets/DiscoverScreen-D6NTgEgk.js +2 -0
- package/dist/hub/assets/{DraftEditor-DGK0Fbp1.js → DraftEditor-CUNGYbg0.js} +1 -1
- package/dist/hub/assets/{GapsScreen-pYuuMGEF.js → GapsScreen-CqedCj4z.js} +1 -1
- package/dist/hub/assets/{HomeScreen-5rm4q3ZO.js → HomeScreen-CIj1QTkw.js} +1 -1
- package/dist/hub/assets/{InventoryScreen-CMKKrpTo.js → InventoryScreen-CyvFXa2_.js} +1 -1
- package/dist/hub/assets/{LibraryScreen-CV8lwjT6.js → LibraryScreen-C4WU0f42.js} +1 -1
- package/dist/hub/assets/{Opened-BvT3JnuG.js → Opened-CeRWkA7c.js} +1 -1
- package/dist/hub/assets/{Preview-DNOsVJ9-.js → Preview-CgW-FMB7.js} +1 -1
- package/dist/hub/assets/{RuleDetail-BNQbhncD.js → RuleDetail-BDhZ1gCd.js} +1 -1
- package/dist/hub/assets/{RulesScreen-ByYZB2Rs.js → RulesScreen-D3rSo_z2.js} +1 -1
- package/dist/hub/assets/{ScreenImage-Dypjm8_v.js → ScreenImage-DLeA3nRh.js} +1 -1
- package/dist/hub/assets/{ScreensScreen-bgwSlccW.js → ScreensScreen-bGuFIyWj.js} +1 -1
- package/dist/hub/assets/{SearchField-DyTJpGOP.js → SearchField-DtEgq9-Q.js} +1 -1
- package/dist/hub/assets/{ServiceDown-f9NYtkr_.js → ServiceDown-Bn6AcA3J.js} +1 -1
- package/dist/hub/assets/{SpecimenCapture-DFoGMhOM.js → SpecimenCapture-p6YErF5y.js} +1 -1
- package/dist/hub/assets/{Specimens-BGwJj05V.js → Specimens-DuaE0WeO.js} +2 -2
- package/dist/hub/assets/{StackScreen-q132_QG3.js → StackScreen-DHDulDng.js} +1 -1
- package/dist/hub/assets/{StubScreens-DWhYvJku.js → StubScreens-CmhVNX_Z.js} +1 -1
- package/dist/hub/assets/{StyleScreen-CcgUv16e.js → StyleScreen-LwTqfGID.js} +1 -1
- package/dist/hub/assets/{Tabs-Dy1Z02s7.js → Tabs-Cbh9BCKW.js} +1 -1
- package/dist/hub/assets/{TokenDetail-DsYgNrPw.js → TokenDetail-BICobLK1.js} +1 -1
- package/dist/hub/assets/{TokensScreen-CTT0GbAH.js → TokensScreen-CyTHn55q.js} +1 -1
- package/dist/hub/assets/WorkspaceScreen-uPWqfklh.js +1 -0
- package/dist/hub/assets/{client-CIo9EOzr.js → client-BXHiuV_w.js} +1 -1
- package/dist/hub/assets/{discover-DGlqvyIe.js → discover-CFhyKler.js} +1 -1
- package/dist/hub/assets/{dist-2euSoh6x.js → dist-C6iTWL0b.js} +1 -1
- package/dist/hub/assets/{dist-CpzxT6kj.js → dist-DYNYuTjI.js} +1 -1
- package/dist/hub/assets/{dist-D8LFTd5W.js → dist-VmEpVQjP.js} +1 -1
- package/dist/hub/assets/{family-tiles-Cc7tTwyr.js → family-tiles-B8_onXUi.js} +1 -1
- package/dist/hub/assets/folder-tree-YghfTmlZ.js +1 -0
- package/dist/hub/assets/main-CfbpuT4Y.js +2 -0
- package/dist/hub/assets/{try-live-C-dwP1zF.js → try-live-CX04d2L2.js} +1 -1
- package/dist/hub/assets/workflow-DbBdiOmA.js +1 -0
- package/dist/hub/index.html +2 -1
- package/dist/hub-previews.js +5 -5
- package/dist/hub-server.js +3 -3
- package/dist/hub-snapshot.js +3 -2
- package/dist/schema/youi.status.schema.json +306 -0
- package/dist/schema/youi.workspace.schema.json +31 -0
- package/dist/youi.js +6 -6
- package/dist/youiyoux.js +6 -6
- package/package.json +4 -4
- package/dist/hub/assets/DiscoverScreen-SOJMVxIq.js +0 -2
- package/dist/hub/assets/main-Bjq_U0cU.js +0 -2
- package/dist/hub/assets/workflow-CnSY-W41.js +0 -1
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
vendoredPath,
|
|
54
54
|
whereUsed,
|
|
55
55
|
withFile
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-3HON5Q5K.js";
|
|
57
57
|
import {
|
|
58
58
|
escapingLinks,
|
|
59
59
|
within
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
libraryOfPackage,
|
|
68
68
|
packageOf,
|
|
69
69
|
playwrightCli
|
|
70
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-HDJ2TOWZ.js";
|
|
71
71
|
import {
|
|
72
72
|
MissingIndexError,
|
|
73
73
|
buildGaps,
|
|
@@ -91,7 +91,7 @@ import {
|
|
|
91
91
|
showFromCatalog,
|
|
92
92
|
storeLocation,
|
|
93
93
|
syncIndex
|
|
94
|
-
} from "./chunk-
|
|
94
|
+
} from "./chunk-6GITMASZ.js";
|
|
95
95
|
import {
|
|
96
96
|
DL_EXT,
|
|
97
97
|
buildTokens,
|
|
@@ -101,7 +101,7 @@ import {
|
|
|
101
101
|
parseCssColor,
|
|
102
102
|
parseDimension,
|
|
103
103
|
readRules
|
|
104
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-FLBRX6Y3.js";
|
|
105
105
|
import {
|
|
106
106
|
Baseline,
|
|
107
107
|
DESIGN_CONFIG_FILE,
|
|
@@ -115,6 +115,8 @@ import {
|
|
|
115
115
|
PACKAGE_NAME,
|
|
116
116
|
ProjectConfig,
|
|
117
117
|
ScanReport,
|
|
118
|
+
WORKSPACE_FILE,
|
|
119
|
+
WorkspaceConfig,
|
|
118
120
|
copyleft,
|
|
119
121
|
executionLabel,
|
|
120
122
|
findFiles,
|
|
@@ -136,7 +138,7 @@ import {
|
|
|
136
138
|
shortHash,
|
|
137
139
|
toPosix,
|
|
138
140
|
writeFileAtomic
|
|
139
|
-
} from "./chunk-
|
|
141
|
+
} from "./chunk-DWYDHVLZ.js";
|
|
140
142
|
import {
|
|
141
143
|
__toESM
|
|
142
144
|
} from "./chunk-WNTS7ZO4.js";
|
|
@@ -146,7 +148,7 @@ var import_yaml4 = __toESM(require_dist(), 1);
|
|
|
146
148
|
import { execFile as execFile5 } from "node:child_process";
|
|
147
149
|
import { existsSync as existsSync5 } from "node:fs";
|
|
148
150
|
import { appendFile as appendFile2, readFile as readFile10, rm as rm6, rmdir as rmdir3 } from "node:fs/promises";
|
|
149
|
-
import { basename as basename5, dirname as
|
|
151
|
+
import { basename as basename5, dirname as dirname13, join as join25, relative as relative11, resolve as resolve15 } from "node:path";
|
|
150
152
|
import { parseArgs, promisify as promisify5 } from "node:util";
|
|
151
153
|
|
|
152
154
|
// ../cli/src/check.ts
|
|
@@ -164,7 +166,7 @@ import { join as join2, relative, resolve } from "node:path";
|
|
|
164
166
|
|
|
165
167
|
// ../cli/src/command.ts
|
|
166
168
|
var COMMAND = "youiyoux";
|
|
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"];
|
|
169
|
+
var SUBCOMMANDS = ["show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "init", "adopt", "status", "system", "sync", "link", "unlink", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "discover", "index", "report", "render", "screens", "requests", "detect", "build", "parity", "schema", "inventory", "gaps", "help"];
|
|
168
170
|
|
|
169
171
|
// ../cli/src/agents.ts
|
|
170
172
|
var import_yaml = __toESM(require_dist(), 1);
|
|
@@ -1058,7 +1060,7 @@ async function runCheck(project2, options = {}) {
|
|
|
1058
1060
|
const { report } = scan;
|
|
1059
1061
|
const checked = new Set(scan.files);
|
|
1060
1062
|
const unmatched = scope ? [...scope].filter((f) => !checked.has(f)) : [];
|
|
1061
|
-
const
|
|
1063
|
+
const failed2 = report.coverage.failed.filter((f) => !scope || scope.has(f.file) || !/\.(tsx?|css)$/.test(f.file));
|
|
1062
1064
|
const wantedRule = (id) => !options.rule || id.endsWith(`/rule/${options.rule}`);
|
|
1063
1065
|
const violations = report.violations.filter((v) => wantedRule(v.rule) && (!options.quiet || v.severity === "error"));
|
|
1064
1066
|
const notRun = failedChecks(report.checks).filter((c) => wantedRule(c.rule));
|
|
@@ -1069,7 +1071,7 @@ async function runCheck(project2, options = {}) {
|
|
|
1069
1071
|
const failing = baseline ? [...baseline.comparison.new, ...baseline.comparison.regressed] : violations;
|
|
1070
1072
|
const errors = failing.filter((v) => v.severity === "error").length + (lock ?? []).filter((i) => i.severity === "error").length;
|
|
1071
1073
|
const drift = diff ? await analyseChange(project2, scan, diff, { violations }) : null;
|
|
1072
|
-
return { identity: scan.identity, scan, scope, unmatched, failed, notRun, violations, lock, baseline, errors, incomplete: unmatched.length > 0 ||
|
|
1074
|
+
return { identity: scan.identity, scan, scope, unmatched, failed: failed2, notRun, violations, lock, baseline, errors, incomplete: unmatched.length > 0 || failed2.length > 0 || notRun.length > 0 || (drift?.incomplete.length ?? 0) > 0, diff: drift };
|
|
1073
1075
|
}
|
|
1074
1076
|
async function lockIssues(project2) {
|
|
1075
1077
|
const lock = await verifyLock(project2);
|
|
@@ -1779,18 +1781,18 @@ async function runClaudeHook(input, target = {}) {
|
|
|
1779
1781
|
try {
|
|
1780
1782
|
const project2 = await load(target, dirname5(file));
|
|
1781
1783
|
const real = (path) => realpath3(path).catch(() => path);
|
|
1782
|
-
const
|
|
1783
|
-
if (!isChecked(project2,
|
|
1784
|
-
const found = await changeFor(() => workingTreeChange(project2, void 0, [
|
|
1784
|
+
const relative12 = relPath(await real(project2.root), await real(file));
|
|
1785
|
+
if (!isChecked(project2, relative12)) return SILENT;
|
|
1786
|
+
const found = await changeFor(() => workingTreeChange(project2, void 0, [relative12]));
|
|
1785
1787
|
if (found.problem !== void 0) return claudeContext(found.problem, project2.root);
|
|
1786
1788
|
const { change } = found;
|
|
1787
1789
|
const result = configured(project2) ? await runCheck(project2, { files: [file], lock: false, baseline: await baselineFor(project2, target), diff: change }) : await runRepoCheck(project2, change);
|
|
1788
1790
|
const lines = findingLines(result, project2.root);
|
|
1789
|
-
const see = COMPONENT_FILE.test(
|
|
1790
|
-
if (lines.length === 0) return see ? claudeContext(`youiyoux: ${
|
|
1791
|
+
const see = COMPONENT_FILE.test(relative12) ? `See it: youiyoux render ${quoted(relative12)} (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;
|
|
1792
|
+
if (lines.length === 0) return see ? claudeContext(`youiyoux: ${relative12} passes the design-system check. ${see}`, project2.root) : SILENT;
|
|
1791
1793
|
const older = "violations" in result ? result.violations.filter((v) => !isAdded(change.added, v.file, v.line)).length : 0;
|
|
1792
1794
|
const known = older > 0 ? ` (${older} older findings on unchanged lines omitted)` : "";
|
|
1793
|
-
return claudeContext(`youiyoux design-system check (advisory) on ${
|
|
1795
|
+
return claudeContext(`youiyoux design-system check (advisory) on ${relative12}${known}:
|
|
1794
1796
|
${lines.join("\n")}${!configured(project2) ? `
|
|
1795
1797
|
${result.diff?.notes.join("\n")}` : ""}
|
|
1796
1798
|
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 ? `
|
|
@@ -2085,7 +2087,7 @@ import { homedir, userInfo } from "node:os";
|
|
|
2085
2087
|
import { join as join12, resolve as resolve6 } from "node:path";
|
|
2086
2088
|
async function swapImpact(candidate, family) {
|
|
2087
2089
|
try {
|
|
2088
|
-
const { discoverIndexPath: discoverIndexPath2, sharedStore: sharedStore2 } = await import("./src-
|
|
2090
|
+
const { discoverIndexPath: discoverIndexPath2, sharedStore: sharedStore2 } = await import("./src-DPFJ4S2N.js");
|
|
2089
2091
|
const path = discoverIndexPath2();
|
|
2090
2092
|
if (!process.env.DATABASE_URL && !existsSync(path)) return null;
|
|
2091
2093
|
const impact = await (await sharedStore2(path, process.env, { readOnly: true })).swapImpact(candidate, family);
|
|
@@ -2096,7 +2098,7 @@ async function swapImpact(candidate, family) {
|
|
|
2096
2098
|
}
|
|
2097
2099
|
async function libraryLicence(id) {
|
|
2098
2100
|
try {
|
|
2099
|
-
const { discoverCacheDir: discoverCacheDir2, findListing } = await import("./src-
|
|
2101
|
+
const { discoverCacheDir: discoverCacheDir2, findListing } = await import("./src-DPFJ4S2N.js");
|
|
2100
2102
|
const { entry } = await findListing(discoverCacheDir2(void 0, process.env), id);
|
|
2101
2103
|
return entry.id === id ? entry.license : null;
|
|
2102
2104
|
} catch {
|
|
@@ -2104,7 +2106,7 @@ async function libraryLicence(id) {
|
|
|
2104
2106
|
}
|
|
2105
2107
|
}
|
|
2106
2108
|
async function startRuleEditor(options) {
|
|
2107
|
-
const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-
|
|
2109
|
+
const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-EUVBYTKY.js");
|
|
2108
2110
|
const projectRoot = resolve6(options.project);
|
|
2109
2111
|
const load2 = { ...options.config ? { configPath: resolve6(options.config) } : {}, ...options.design ? { designRoot: resolve6(options.design) } : {}, ...options.root ? { productRoot: resolve6(options.root) } : {} };
|
|
2110
2112
|
const project2 = await loadProject(projectRoot, load2);
|
|
@@ -2145,7 +2147,7 @@ function builtHubPlan(toolRoot, options, out) {
|
|
|
2145
2147
|
if (options.watch && !namesSystem(options)) throw new Error("--watch keeps a system's snapshot live: name it with --design or --project (the demo system is not watched)");
|
|
2146
2148
|
const hub = join13(toolRoot, BUILT_HUB);
|
|
2147
2149
|
const snapshot = flags.length > 0 ? [join13(toolRoot, "hub-snapshot.js"), "--out", out, ...flags] : null;
|
|
2148
|
-
const server = [join13(toolRoot, "hub-server.js"), "--dist", hub, "--kit", join13(hub, "kit"), "--snapshot", snapshot ? out : join13(toolRoot, BUILT_DEMO), ...snapshot ? ["--build-frame"] : [], ...options.port ? ["--port", options.port] : [], ...options.api ? ["--api", options.api] : [], ...options.watch ? ["--live"] : []];
|
|
2150
|
+
const server = [join13(toolRoot, "hub-server.js"), "--dist", hub, "--kit", join13(hub, "kit"), "--snapshot", snapshot ? out : join13(toolRoot, BUILT_DEMO), ...snapshot ? ["--build-frame"] : [], ...options.port ? ["--port", options.port] : [], ...options.api ? ["--api", options.api] : [], ...options.watch || options.workspace ? ["--live"] : []];
|
|
2149
2151
|
return { snapshot, server, watch: options.watch && snapshot ? [...snapshot, "--watch"] : null };
|
|
2150
2152
|
}
|
|
2151
2153
|
function run5(args, cwd, env, started) {
|
|
@@ -2170,6 +2172,7 @@ async function runBuiltHub(toolRoot, options) {
|
|
|
2170
2172
|
const code = await run5(plan.snapshot, process.cwd(), {}, (child) => children.add(child));
|
|
2171
2173
|
if (code !== 0) return code;
|
|
2172
2174
|
}
|
|
2175
|
+
options.workspace?.((project2) => run5(builtHubPlan(toolRoot, { ...options, project: project2 }, out).snapshot, process.cwd(), {}, (child) => children.add(child)));
|
|
2173
2176
|
if (plan.watch) children.add(spawn(process.execPath, plan.watch, { cwd: process.cwd(), stdio: "inherit" }));
|
|
2174
2177
|
return await run5(plan.server, process.cwd(), {}, (child) => children.add(child));
|
|
2175
2178
|
} finally {
|
|
@@ -2189,6 +2192,7 @@ async function runHub(toolRoot, options) {
|
|
|
2189
2192
|
const code = await run5(plan.snapshot, process.cwd(), {});
|
|
2190
2193
|
if (code !== 0) return code;
|
|
2191
2194
|
}
|
|
2195
|
+
options.workspace?.((project2) => run5(hubPlan(toolRoot, { ...options, project: project2 }).snapshot, process.cwd(), {}));
|
|
2192
2196
|
const watcher = plan.watch ? spawn(process.execPath, plan.watch, { cwd: process.cwd(), stdio: "inherit" }) : null;
|
|
2193
2197
|
try {
|
|
2194
2198
|
return await run5(plan.vite, plan.web, plan.env);
|
|
@@ -2353,14 +2357,26 @@ function moveDeclarations(css, blocks, moved, importPath) {
|
|
|
2353
2357
|
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
2358
|
if (rules.length === 0) return null;
|
|
2355
2359
|
const lines = css.split("\n");
|
|
2360
|
+
const byLine = /* @__PURE__ */ new Map();
|
|
2361
|
+
for (const rule2 of rules) for (const d of rule2.declarations) if (moved.has(d.property.slice(2))) byLine.set(d.line - 1, (byLine.get(d.line - 1) ?? /* @__PURE__ */ new Set()).add(d.property));
|
|
2362
|
+
let count = 0;
|
|
2363
|
+
const kept = [];
|
|
2356
2364
|
const drop = /* @__PURE__ */ new Set();
|
|
2357
|
-
for (const
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2365
|
+
for (const [index, properties] of byLine) {
|
|
2366
|
+
const segments = lines[index].split(";");
|
|
2367
|
+
const left = segments.filter((segment) => {
|
|
2368
|
+
const property = /^\s*(--[\w-]+)\s*:/.exec(segment)?.[1];
|
|
2369
|
+
if (property === void 0 || !properties.has(property)) return true;
|
|
2370
|
+
properties.delete(property);
|
|
2371
|
+
count++;
|
|
2372
|
+
return false;
|
|
2373
|
+
});
|
|
2374
|
+
kept.push(...[...properties].map((p) => p.slice(2)));
|
|
2375
|
+
const text = left.join(";");
|
|
2376
|
+
if (text.trim() === "") drop.add(index);
|
|
2377
|
+
else lines[index] = /^\s/.test(text) || index === 0 ? text : `${lines[index].match(/^\s*/)[0]}${text.trimStart()}`;
|
|
2362
2378
|
}
|
|
2363
|
-
if (
|
|
2379
|
+
if (count === 0) return null;
|
|
2364
2380
|
let content = lines.filter((_, i) => !drop.has(i)).join("\n");
|
|
2365
2381
|
for (const block of blocks) content = content.replace(new RegExp(`(^|\\n)[ \\t]*${block.selector.replace(/[.[\]()"=:]/g, "\\$&")}\\s*\\{\\s*\\}[ \\t]*(?=\\n|$)`, "g"), "");
|
|
2366
2382
|
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"), "");
|
|
@@ -2370,7 +2386,7 @@ function moveDeclarations(css, blocks, moved, importPath) {
|
|
|
2370
2386
|
content = at > 0 ? `${content.slice(0, at)}
|
|
2371
2387
|
${statement}${content.slice(at)}` : `${statement}
|
|
2372
2388
|
${content}`;
|
|
2373
|
-
return { content: content.replace(/\n{3,}/g, "\n\n"), moved:
|
|
2389
|
+
return { content: content.replace(/\n{3,}/g, "\n\n"), moved: count, kept: [...new Set(kept)].sort() };
|
|
2374
2390
|
}
|
|
2375
2391
|
async function planAdoption(options) {
|
|
2376
2392
|
const root = options.root;
|
|
@@ -2466,6 +2482,10 @@ async function planAdoption(options) {
|
|
|
2466
2482
|
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
2483
|
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
2484
|
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`);
|
|
2485
|
+
const read = [":root", ...darkBlocks.map((b) => [b.outer, b.selector].filter(Boolean).join(" > "))];
|
|
2486
|
+
const others = css ? [...new Set(readRules(css).filter((r) => !read.includes(r.preludes.join(" > ")) && !r.preludes.at(-1).startsWith("@") && r.declarations.some((d) => d.property.startsWith("--"))).map((r) => r.preludes.join(" > ").replace(/\s+/g, " ")))] : [];
|
|
2487
|
+
if (others.length > 0) notes.push(`${plural(others.length, "other block")} in ${stylesheet} set${others.length === 1 ? "s" : ""} variables too and stay${others.length === 1 ? "s" : ""} there, as ${others.length === 1 ? "it was" : "they were"} (${others.slice(0, 4).join(" \xB7 ")}${others.length > 4 ? " \xB7 \u2026" : ""}): the System holds the :root${darkBlocks.length > 0 ? " and dark" : ""} values only`);
|
|
2488
|
+
if (rewritten && rewritten.kept.length > 0) notes.push(`${plural(rewritten.kept.length, "variable")} ${rewritten.kept.length === 1 ? "stays" : "stay"} in ${stylesheet} too, written where adopt cannot take them out alone (beside a selector): ${rewritten.kept.slice(0, 4).map((n) => `--${n}`).join(", ")}${rewritten.kept.length > 4 ? ", \u2026" : ""}; they hold the same values, so remove them by hand`);
|
|
2469
2489
|
for (const [rule2, content] of starterRules(id, componentsDir)) out.push({ path: `design/rules/${rule2}.rule.yaml`, content });
|
|
2470
2490
|
out.push({
|
|
2471
2491
|
path: "design/design.md",
|
|
@@ -2630,15 +2650,193 @@ async function extractSystem(project2, dir, options = {}) {
|
|
|
2630
2650
|
}
|
|
2631
2651
|
}
|
|
2632
2652
|
|
|
2653
|
+
// ../cli/src/status.ts
|
|
2654
|
+
import { realpath as realpath5 } from "node:fs/promises";
|
|
2655
|
+
import { dirname as dirname9, join as join16, relative as relative8, resolve as resolve10 } from "node:path";
|
|
2656
|
+
var placeOf = (cwd, root) => {
|
|
2657
|
+
const rel = relative8(cwd, root) || ".";
|
|
2658
|
+
return rel.split(/[\\/]/).filter((part) => part === "..").length > 1 ? resolve10(root) : rel;
|
|
2659
|
+
};
|
|
2660
|
+
var WRITES = /* @__PURE__ */ new Set(["created", "updated", "merged", "adopted", "conflict", "removed"]);
|
|
2661
|
+
function sourceOf(project2) {
|
|
2662
|
+
if (project2.linked) return `your checkout ${project2.linked}`;
|
|
2663
|
+
const source = project2.config.source;
|
|
2664
|
+
if ("package" in source) return `package ${source.package}`;
|
|
2665
|
+
if ("git" in source) return `git ${source.git} at ${source.ref}`;
|
|
2666
|
+
return resolve10(project2.root) === resolve10(project2.repo.root) ? "this repo" : `path ${source.path}`;
|
|
2667
|
+
}
|
|
2668
|
+
function nextStep(s) {
|
|
2669
|
+
if (s.sync.state === "never") return "youiyoux sync: this product has never been synced with its System";
|
|
2670
|
+
if (s.sync.state === "conflict") return "youiyoux sync: resolve the conflicts it reports, then youiyoux sync --adopt records them";
|
|
2671
|
+
if (s.sync.state === "behind") return `youiyoux sync: ${plural(s.sync.changes.length, "file")} would change`;
|
|
2672
|
+
if (s.check.incomplete) return "youiyoux check: some files or rules could not be checked, and it says which";
|
|
2673
|
+
if (s.check.errors > 0) return `youiyoux check: fix ${plural(s.check.errors, "error")}`;
|
|
2674
|
+
if (s.requests.open > 0) return `youiyoux requests: answer ${plural(s.requests.open, "open request")}`;
|
|
2675
|
+
if (s.check.warnings > 0 && !s.check.baseline) return `youiyoux check: ${plural(s.check.warnings, "warning")} to fix, or youiyoux baseline records them as accepted debt`;
|
|
2676
|
+
return "nothing to do";
|
|
2677
|
+
}
|
|
2678
|
+
async function projectStatus(project2, cwd = process.cwd()) {
|
|
2679
|
+
const lock = await readLock(project2.root);
|
|
2680
|
+
const { actions } = await syncProject(project2, { dryRun: true });
|
|
2681
|
+
const changes = actions.filter((a) => WRITES.has(a.action)).map((a) => ({ file: a.file, action: a.action }));
|
|
2682
|
+
const baselineFile2 = baselinePath(project2);
|
|
2683
|
+
const check = await runCheck(project2, { baseline: await readTextOrNull(baselineFile2) === null ? null : baselineFile2 });
|
|
2684
|
+
const comparison = check.baseline?.comparison;
|
|
2685
|
+
const counted = comparison ? [...comparison.new, ...comparison.regressed] : check.violations;
|
|
2686
|
+
const usages = check.scan.report.usages;
|
|
2687
|
+
const { RequestStore } = await import("./src-EUVBYTKY.js");
|
|
2688
|
+
const requests = await new RequestStore(project2.root, project2.config.id).list();
|
|
2689
|
+
const status = {
|
|
2690
|
+
id: project2.config.id,
|
|
2691
|
+
root: placeOf(cwd, project2.root),
|
|
2692
|
+
system: { id: project2.system.config.id, name: project2.system.config.name, version: project2.system.config.version, source: sourceOf(project2), linked: Boolean(project2.linked), commit: project2.designCommit ?? null },
|
|
2693
|
+
lock: lock ? { systemVersion: lock.systemVersion, syncedAt: lock.syncedAt } : null,
|
|
2694
|
+
sync: { state: lock === null ? "never" : actions.some((a) => a.action === "conflict") ? "conflict" : changes.length > 0 ? "behind" : "current", changes },
|
|
2695
|
+
drift: (await verifyLock(project2) ?? []).map((i) => ({ file: i.file, severity: i.severity, message: i.message })),
|
|
2696
|
+
check: {
|
|
2697
|
+
errors: check.errors,
|
|
2698
|
+
warnings: counted.filter((v) => v.severity === "warn").length,
|
|
2699
|
+
baseline: comparison ? { known: comparison.known.length, new: comparison.new.length + comparison.regressed.length, fixed: comparison.fixed.reduce((n, f) => n + f.gone, 0) } : null,
|
|
2700
|
+
incomplete: check.incomplete
|
|
2701
|
+
},
|
|
2702
|
+
components: { system: project2.system.components.length, used: new Set(usages.map((u) => u.element)).size, sites: usages.length },
|
|
2703
|
+
requests: { open: requests.requests.filter((r) => r.status === "open").length, addressed: requests.requests.filter((r) => r.status === "addressed").length, unreadable: requests.unreadable.length }
|
|
2704
|
+
};
|
|
2705
|
+
return { ...status, next: nextStep(status) };
|
|
2706
|
+
}
|
|
2707
|
+
async function statusTargets(cwd, named2) {
|
|
2708
|
+
if (named2) return { roots: [resolve10(named2)], workspace: null };
|
|
2709
|
+
const product2 = findUp(cwd, PROJECT_FILE);
|
|
2710
|
+
if (product2) return { roots: [product2], workspace: null };
|
|
2711
|
+
const at = findUp(cwd, WORKSPACE_FILE);
|
|
2712
|
+
if (at) {
|
|
2713
|
+
const file = join16(at, WORKSPACE_FILE);
|
|
2714
|
+
const workspace = await readDefinition(file, WorkspaceConfig);
|
|
2715
|
+
return { roots: workspace.projects.map((p) => resolve10(at, p)), workspace: file };
|
|
2716
|
+
}
|
|
2717
|
+
const top = findUp(cwd, ".git") ?? null;
|
|
2718
|
+
if (top === null) return { roots: [], workspace: null };
|
|
2719
|
+
const configs = await findFiles(cwd, [`**/${PROJECT_FILE}`]);
|
|
2720
|
+
const roots = [];
|
|
2721
|
+
for (const config of configs) {
|
|
2722
|
+
const dir = resolve10(cwd, dirname9(config));
|
|
2723
|
+
if (await realpath5(findUp(dir, ".git") ?? dir) === await realpath5(top)) roots.push(dir);
|
|
2724
|
+
}
|
|
2725
|
+
return { roots, workspace: null };
|
|
2726
|
+
}
|
|
2727
|
+
async function workspaceStatus(cwd, named2) {
|
|
2728
|
+
const { roots, workspace } = await statusTargets(cwd, named2);
|
|
2729
|
+
const projects = [];
|
|
2730
|
+
for (const root of roots) {
|
|
2731
|
+
try {
|
|
2732
|
+
projects.push(await projectStatus(await loadProject(root), cwd));
|
|
2733
|
+
} catch (error) {
|
|
2734
|
+
const label = placeOf(cwd, root);
|
|
2735
|
+
projects.push({ root: label, error: error instanceof Error ? error.message : String(error), next: await readTextOrNull(join16(root, PROJECT_FILE)) === null ? `youiyoux adopt or youiyoux init --design <dir> in ${label}` : `fix ${join16(label, PROJECT_FILE)}` });
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
return { schema: "youi.status/v1", generatedAt: (/* @__PURE__ */ new Date()).toISOString(), workspace: workspace ? relative8(cwd, workspace) || WORKSPACE_FILE : null, projects };
|
|
2739
|
+
}
|
|
2740
|
+
var failed = (p) => "error" in p;
|
|
2741
|
+
function projectLines(s) {
|
|
2742
|
+
const row = (label, text) => ` ${padEnd(label, 11)} ${text}`;
|
|
2743
|
+
const synced = s.lock ? `synced ${s.lock.syncedAt.slice(0, 10)}${s.lock.systemVersion !== s.system.version ? ` at v${s.lock.systemVersion}` : ""}` : "never synced";
|
|
2744
|
+
const sync = { current: style.ok("up to date"), behind: style.warn(`behind: ${plural(s.sync.changes.length, "file")} would change`), conflict: style.error(`conflict: ${plural(s.sync.changes.filter((c) => c.action === "conflict").length, "file")}`), never: style.warn("never synced") }[s.sync.state];
|
|
2745
|
+
const edited = s.drift.filter((d) => d.severity === "warn");
|
|
2746
|
+
const broken = s.drift.filter((d) => d.severity === "error");
|
|
2747
|
+
const drift = s.drift.length === 0 ? style.ok("none") : [broken.length > 0 ? style.error(`${plural(broken.length, "file")} wrong (${broken.slice(0, 3).map((d) => d.file.split("/").at(-1)).join(", ")})`) : "", edited.length > 0 ? style.warn(`${plural(edited.length, "owned file")} edited locally, merged on sync (${edited.slice(0, 3).map((d) => d.file.split("/").at(-1)).join(", ")})`) : ""].filter(Boolean).join("; ");
|
|
2748
|
+
const { check } = s;
|
|
2749
|
+
const counts = `${check.errors > 0 ? style.error(plural(check.errors, "error")) : style.ok("0 errors")} \xB7 ${plural(check.warnings, "warning")}`;
|
|
2750
|
+
const baseline = check.baseline ? style.dim(` beyond the baseline (${check.baseline.known} accepted${check.baseline.fixed > 0 ? `, ${check.baseline.fixed} fixed` : ""})`) : "";
|
|
2751
|
+
return [
|
|
2752
|
+
`${style.bold(s.id)}${s.root === s.id ? "" : ` ${style.dim(`\xB7 ${s.root}`)}`} \xB7 ${s.system.name} v${s.system.version} ${style.dim(`(${s.system.source}${s.system.linked ? ", linked" : ""}) \xB7 ${synced}`)}`,
|
|
2753
|
+
row("sync", sync),
|
|
2754
|
+
row("drift", drift),
|
|
2755
|
+
row("check", `${counts}${baseline}${check.incomplete ? style.warn(" \xB7 incomplete: not a pass") : ""}`),
|
|
2756
|
+
row("components", `${s.components.used} of ${s.components.system} System components used, at ${plural(s.components.sites, "site")}`),
|
|
2757
|
+
row("requests", s.requests.open + s.requests.addressed + s.requests.unreadable === 0 ? style.dim("none") : [s.requests.open > 0 ? style.warn(`${s.requests.open} open`) : "0 open", `${s.requests.addressed} addressed`, ...s.requests.unreadable > 0 ? [style.error(`${s.requests.unreadable} unreadable`)] : []].join(" \xB7 ")),
|
|
2758
|
+
`${style.bold("Next:")} ${s.next}`
|
|
2759
|
+
];
|
|
2760
|
+
}
|
|
2761
|
+
function renderStatus(report) {
|
|
2762
|
+
if (report.projects.length === 0) return `${style.warn("!")} no product here: no ${PROJECT_FILE} upward, no ${WORKSPACE_FILE}, and none in this repository below here
|
|
2763
|
+
Next: youiyoux adopt makes this product's own UI its design system, or youiyoux init --design <dir> follows one`;
|
|
2764
|
+
const blocks = report.projects.map((p) => failed(p) ? [`${style.bold(p.root)} ${style.error("\u2716")} ${p.error}`, `${style.bold("Next:")} ${p.next}`] : projectLines(p));
|
|
2765
|
+
const head = report.projects.length > 1 ? [`${style.bold("workspace")} ${style.dim(`\xB7 ${plural(report.projects.length, "product")}${report.workspace ? ` from ${report.workspace}` : " in this repository"}`)}`, ""] : [];
|
|
2766
|
+
return [...head, ...blocks.flatMap((b, i) => i === 0 ? b : ["", ...b])].join("\n");
|
|
2767
|
+
}
|
|
2768
|
+
var statusExitCode = (report) => report.projects.some((p) => failed(p) || p.check.incomplete || p.check.errors > 0 || p.sync.state === "conflict") ? 1 : 0;
|
|
2769
|
+
|
|
2770
|
+
// ../cli/src/workspace-hub.ts
|
|
2771
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2772
|
+
import { createServer } from "node:http";
|
|
2773
|
+
var KEY_HEADER = "x-youi-session";
|
|
2774
|
+
function send(res, status, body) {
|
|
2775
|
+
res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
2776
|
+
res.end(JSON.stringify(body));
|
|
2777
|
+
}
|
|
2778
|
+
async function readBody(req) {
|
|
2779
|
+
let text = "";
|
|
2780
|
+
for await (const chunk2 of req) {
|
|
2781
|
+
text += String(chunk2);
|
|
2782
|
+
if (text.length > 4096) throw new Error("request too large");
|
|
2783
|
+
}
|
|
2784
|
+
return text ? JSON.parse(text) : {};
|
|
2785
|
+
}
|
|
2786
|
+
async function startWorkspaceHub(options) {
|
|
2787
|
+
const key2 = randomBytes2(18).toString("base64url");
|
|
2788
|
+
let report = await options.status();
|
|
2789
|
+
let current = options.current;
|
|
2790
|
+
let busy = false;
|
|
2791
|
+
const server = createServer((req, res) => {
|
|
2792
|
+
void (async () => {
|
|
2793
|
+
const path = (req.url ?? "").split("?")[0];
|
|
2794
|
+
if (req.method === "GET" && path === "/api/workspace") return send(res, 200, { report, current });
|
|
2795
|
+
if (req.method !== "POST" || path !== "/api/workspace/open" && path !== "/api/workspace/refresh") {
|
|
2796
|
+
return send(res, 404, { error: "Not in a workspace hub: Rules and Changes are read-only here; youiyoux ui --project <dir> opens the rule editor on one product" });
|
|
2797
|
+
}
|
|
2798
|
+
if (req.headers[KEY_HEADER] !== key2) return send(res, 401, { error: "this tab does not carry this run's key" });
|
|
2799
|
+
if (busy) return send(res, 409, { error: "another product is opening; try again in a moment" });
|
|
2800
|
+
busy = true;
|
|
2801
|
+
try {
|
|
2802
|
+
if (path === "/api/workspace/refresh") {
|
|
2803
|
+
report = await options.status();
|
|
2804
|
+
return send(res, 200, { report, current });
|
|
2805
|
+
}
|
|
2806
|
+
const { index } = await readBody(req);
|
|
2807
|
+
const entry = typeof index === "number" && Number.isInteger(index) ? report.projects[index] : void 0;
|
|
2808
|
+
if (entry === void 0 || options.roots[index] === void 0) return send(res, 400, { error: `no product ${JSON.stringify(index)} in this workspace` });
|
|
2809
|
+
if ("error" in entry) return send(res, 400, { error: `${entry.root} cannot be opened: ${entry.error}`, problems: [entry.next] });
|
|
2810
|
+
const code = await options.open(options.roots[index]);
|
|
2811
|
+
if (code !== 0) return send(res, 500, { error: `the snapshot of ${entry.root} failed (exit ${code}); the hub still shows the product it showed` });
|
|
2812
|
+
current = index;
|
|
2813
|
+
return send(res, 200, { report, current });
|
|
2814
|
+
} catch (error) {
|
|
2815
|
+
return send(res, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
2816
|
+
} finally {
|
|
2817
|
+
busy = false;
|
|
2818
|
+
}
|
|
2819
|
+
})();
|
|
2820
|
+
});
|
|
2821
|
+
await new Promise((done) => server.listen(0, "127.0.0.1", done));
|
|
2822
|
+
const address = server.address();
|
|
2823
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
2824
|
+
return {
|
|
2825
|
+
url: `http://127.0.0.1:${port}`,
|
|
2826
|
+
key: key2,
|
|
2827
|
+
close: () => new Promise((done) => server.close(() => done()))
|
|
2828
|
+
};
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2633
2831
|
// ../cli/src/index-store.ts
|
|
2634
2832
|
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
|
|
2833
|
+
import { dirname as dirname10, join as join17, resolve as resolve11 } from "node:path";
|
|
2636
2834
|
var SOURCES_FILE = "index-sources.json";
|
|
2637
2835
|
var SCANS_LOG = "scans.jsonl";
|
|
2638
2836
|
var EMPTY = { discover: null, inventories: [], projects: [] };
|
|
2639
2837
|
async function readIndexSources(home) {
|
|
2640
2838
|
try {
|
|
2641
|
-
const raw = JSON.parse(await readFile6(
|
|
2839
|
+
const raw = JSON.parse(await readFile6(join17(home, SOURCES_FILE), "utf8"));
|
|
2642
2840
|
return { discover: raw.discover ?? null, inventories: raw.inventories ?? [], projects: raw.projects ?? [] };
|
|
2643
2841
|
} catch {
|
|
2644
2842
|
return EMPTY;
|
|
@@ -2651,14 +2849,14 @@ async function noteSource(home, change) {
|
|
|
2651
2849
|
inventories: change.inventory && !sources.inventories.includes(change.inventory) ? [...sources.inventories, change.inventory] : sources.inventories,
|
|
2652
2850
|
projects: change.project ? [...sources.projects.filter((p) => p.root !== change.project.root), change.project] : sources.projects
|
|
2653
2851
|
};
|
|
2654
|
-
await writeFileAtomic(
|
|
2852
|
+
await writeFileAtomic(join17(home, SOURCES_FILE), `${JSON.stringify(next2, null, 2)}
|
|
2655
2853
|
`);
|
|
2656
2854
|
}
|
|
2657
2855
|
async function recordInventory(store, home, report, file) {
|
|
2658
|
-
const run7 = inventoryRun(report,
|
|
2856
|
+
const run7 = inventoryRun(report, resolve11(file));
|
|
2659
2857
|
const logged = { run: run7.run, source: run7.source, scannedAt: run7.scannedAt, projects: run7.projects, families: run7.families };
|
|
2660
2858
|
await mkdir4(home, { recursive: true });
|
|
2661
|
-
await appendFile(
|
|
2859
|
+
await appendFile(join17(home, SCANS_LOG), `${JSON.stringify({ schema: "youi.scan-log/v1", ...logged })}
|
|
2662
2860
|
`);
|
|
2663
2861
|
await store.recordInventory(run7);
|
|
2664
2862
|
await noteSource(home, { inventory: run7.source });
|
|
@@ -2669,7 +2867,7 @@ async function recordProject(store, project2, usage) {
|
|
|
2669
2867
|
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 }));
|
|
2670
2868
|
}
|
|
2671
2869
|
async function readLog(home) {
|
|
2672
|
-
const text = await readFile6(
|
|
2870
|
+
const text = await readFile6(join17(home, SCANS_LOG), "utf8").catch(() => "");
|
|
2673
2871
|
return text.split("\n").flatMap((line) => {
|
|
2674
2872
|
if (!line.trim()) return [];
|
|
2675
2873
|
try {
|
|
@@ -2684,9 +2882,9 @@ async function rebuildIndex(path, options = {}) {
|
|
|
2684
2882
|
const started = performance.now();
|
|
2685
2883
|
const env = options.env ?? process.env;
|
|
2686
2884
|
const home = indexHome(path, env);
|
|
2687
|
-
await mkdir4(
|
|
2885
|
+
await mkdir4(dirname10(path), { recursive: true });
|
|
2688
2886
|
const scratch = !env.DATABASE_URL ? await mkdtemp4(`${path}.rebuild-`) : null;
|
|
2689
|
-
const target = scratch ?
|
|
2887
|
+
const target = scratch ? join17(scratch, "index.db") : path;
|
|
2690
2888
|
const store = scratch ? await openSqlite(target) : await sharedStore(path, env);
|
|
2691
2889
|
try {
|
|
2692
2890
|
if (env.DATABASE_URL) await store.reset();
|
|
@@ -2694,7 +2892,7 @@ async function rebuildIndex(path, options = {}) {
|
|
|
2694
2892
|
const sources = await readIndexSources(home);
|
|
2695
2893
|
const cache = options.discoverCache ?? sources.discover;
|
|
2696
2894
|
let discover = { cache, chosen: 0, all: 0 };
|
|
2697
|
-
if (cache && await stat2(
|
|
2895
|
+
if (cache && await stat2(join17(cache, "index.json")).then(() => true, () => false)) {
|
|
2698
2896
|
const loaded = await ingestCatalog(store, cache);
|
|
2699
2897
|
discover = { cache, chosen: loaded.chosen, all: loaded.all };
|
|
2700
2898
|
}
|
|
@@ -2711,25 +2909,25 @@ async function rebuildIndex(path, options = {}) {
|
|
|
2711
2909
|
await store.recordInventory(inventoryRun(parsed.data, file));
|
|
2712
2910
|
replayed++;
|
|
2713
2911
|
}
|
|
2714
|
-
const
|
|
2912
|
+
const failed2 = [];
|
|
2715
2913
|
let projects = 0;
|
|
2716
2914
|
const gone = [];
|
|
2717
2915
|
for (const source of sources.projects) {
|
|
2718
2916
|
if (!await stat2(source.root).then((s) => s.isDirectory(), () => false)) {
|
|
2719
2917
|
gone.push(source.root);
|
|
2720
|
-
|
|
2918
|
+
failed2.push({ root: source.root, reason: "folder not found (moved or deleted), skipped" });
|
|
2721
2919
|
continue;
|
|
2722
2920
|
}
|
|
2723
2921
|
try {
|
|
2724
2922
|
const project2 = await loadProject(source.root, source.options);
|
|
2725
|
-
const usage = ScanReport.safeParse(await readJson(source.usage ??
|
|
2923
|
+
const usage = ScanReport.safeParse(await readJson(source.usage ?? join17(project2.root, ".youi/usage.json")));
|
|
2726
2924
|
await recordProject(store, project2, usage.success ? usage.data : null);
|
|
2727
2925
|
projects++;
|
|
2728
2926
|
} catch (error) {
|
|
2729
|
-
|
|
2927
|
+
failed2.push({ root: source.root, reason: error instanceof Error ? error.message.split("\n")[0] : String(error) });
|
|
2730
2928
|
}
|
|
2731
2929
|
}
|
|
2732
|
-
const broken =
|
|
2930
|
+
const broken = failed2.filter((f) => !gone.includes(f.root));
|
|
2733
2931
|
if (broken.length) throw new Error(`Index rebuild failed: ${broken.map((f) => `${f.root}: ${f.reason}`).join("; ")}`);
|
|
2734
2932
|
await linkCandidates(store);
|
|
2735
2933
|
const tables = (await store.stats()).tables;
|
|
@@ -2753,7 +2951,7 @@ async function rebuildIndex(path, options = {}) {
|
|
|
2753
2951
|
throw error;
|
|
2754
2952
|
}
|
|
2755
2953
|
}
|
|
2756
|
-
return { location: scratch ? path : store.location, discover, scans: log.length, inventories: { replayed, missing }, projects: { replayed: projects, failed }, tables, durationMs: Math.round(performance.now() - started) };
|
|
2954
|
+
return { location: scratch ? path : store.location, discover, scans: log.length, inventories: { replayed, missing }, projects: { replayed: projects, failed: failed2 }, tables, durationMs: Math.round(performance.now() - started) };
|
|
2757
2955
|
} finally {
|
|
2758
2956
|
if (scratch) {
|
|
2759
2957
|
await store.close();
|
|
@@ -2939,9 +3137,9 @@ function renderAdoptionRequest(request) {
|
|
|
2939
3137
|
}
|
|
2940
3138
|
|
|
2941
3139
|
// ../cli/src/report/run.ts
|
|
2942
|
-
import { randomBytes as
|
|
3140
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
2943
3141
|
import { mkdir as mkdir5, rename as rename3, stat as stat3, writeFile as writeFile2 } from "node:fs/promises";
|
|
2944
|
-
import { join as
|
|
3142
|
+
import { join as join24, relative as relative10, resolve as resolve14 } from "node:path";
|
|
2945
3143
|
|
|
2946
3144
|
// ../cli/src/report/card.ts
|
|
2947
3145
|
import { deflateSync } from "node:zlib";
|
|
@@ -3177,7 +3375,7 @@ function drawCard(input) {
|
|
|
3177
3375
|
|
|
3178
3376
|
// ../cli/src/report/model.ts
|
|
3179
3377
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
3180
|
-
import { join as
|
|
3378
|
+
import { join as join21 } from "node:path";
|
|
3181
3379
|
|
|
3182
3380
|
// ../cli/src/report/filters.ts
|
|
3183
3381
|
var SHOWCASE_GLOBS = ["**/{__stories__,.storybook,storybook,storybook-static,example,examples,demo,demos,playground,showcase,showcases}/**", "**/*.stories.*", "**/*.story.*"];
|
|
@@ -3304,11 +3502,11 @@ function publicLook(caption) {
|
|
|
3304
3502
|
|
|
3305
3503
|
// ../cli/src/report/groups.ts
|
|
3306
3504
|
import { existsSync as existsSync3 } from "node:fs";
|
|
3307
|
-
import { join as
|
|
3505
|
+
import { join as join19 } from "node:path";
|
|
3308
3506
|
|
|
3309
3507
|
// ../cli/src/report/imports.ts
|
|
3310
3508
|
import { realpathSync } from "node:fs";
|
|
3311
|
-
import { dirname as
|
|
3509
|
+
import { dirname as dirname11, join as join18, relative as relative9, resolve as resolve12 } from "node:path";
|
|
3312
3510
|
import ts from "typescript";
|
|
3313
3511
|
var resolvers = /* @__PURE__ */ new Map();
|
|
3314
3512
|
function resolverOf(root) {
|
|
@@ -3316,7 +3514,7 @@ function resolverOf(root) {
|
|
|
3316
3514
|
if (!found) {
|
|
3317
3515
|
const config = ts.findConfigFile(root, ts.sys.fileExists, "tsconfig.json") ?? ts.findConfigFile(root, ts.sys.fileExists, "jsconfig.json");
|
|
3318
3516
|
const read = config ? ts.readConfigFile(config, ts.sys.readFile) : null;
|
|
3319
|
-
const options = config && read && !read.error ? ts.parseJsonConfigFileContent(read.config, ts.sys,
|
|
3517
|
+
const options = config && read && !read.error ? ts.parseJsonConfigFileContent(read.config, ts.sys, dirname11(config)).options : {};
|
|
3320
3518
|
found = { options, cache: ts.createModuleResolutionCache(root, (name) => name, options) };
|
|
3321
3519
|
resolvers.set(root, found);
|
|
3322
3520
|
}
|
|
@@ -3327,13 +3525,13 @@ function clearImportCache() {
|
|
|
3327
3525
|
}
|
|
3328
3526
|
async function verifiedImport(root, keeper, from, suggested) {
|
|
3329
3527
|
const { options, cache } = resolverOf(root);
|
|
3330
|
-
let local =
|
|
3528
|
+
let local = relative9(dirname11(join18(root, from)), join18(root, keeper)).replaceAll("\\", "/").replace(/\.(tsx?|jsx?|mjs)$/, "");
|
|
3331
3529
|
if (!local.startsWith(".")) local = `./${local}`;
|
|
3332
3530
|
for (const specifier of /* @__PURE__ */ new Set([suggested, local])) {
|
|
3333
|
-
const found = ts.resolveModuleName(specifier,
|
|
3531
|
+
const found = ts.resolveModuleName(specifier, resolve12(root, from), options, ts.sys, cache).resolvedModule?.resolvedFileName;
|
|
3334
3532
|
if (found) {
|
|
3335
3533
|
try {
|
|
3336
|
-
if (realpathSync(found) === realpathSync(
|
|
3534
|
+
if (realpathSync(found) === realpathSync(join18(root, keeper))) return specifier;
|
|
3337
3535
|
} catch {
|
|
3338
3536
|
}
|
|
3339
3537
|
}
|
|
@@ -3741,7 +3939,7 @@ function factsOf(project2, id, modules, eligible, workspaceNames, dependencies,
|
|
|
3741
3939
|
if (dirs.has(file.slice(0, at))) break;
|
|
3742
3940
|
dirs.add(file.slice(0, at));
|
|
3743
3941
|
}
|
|
3744
|
-
const roots = [...dirs].filter((dir) => existsSync3(
|
|
3942
|
+
const roots = [...dirs].filter((dir) => existsSync3(join19(project2.root, dir, "package.json"))).sort((a, b) => b.length - a.length);
|
|
3745
3943
|
const nested = (file) => roots.find((dir) => file.startsWith(`${dir}/`)) ?? "";
|
|
3746
3944
|
return { project: project2, id, dependencies, packageImports, jsxCounts, eligible, twins, dirs, importer, packageOf: nested };
|
|
3747
3945
|
}
|
|
@@ -3749,11 +3947,11 @@ function factsOf(project2, id, modules, eligible, workspaceNames, dependencies,
|
|
|
3749
3947
|
// ../cli/src/report/projects.ts
|
|
3750
3948
|
var import_yaml3 = __toESM(require_dist(), 1);
|
|
3751
3949
|
import { readFile as readFile7 } from "node:fs/promises";
|
|
3752
|
-
import { basename as basename4, join as
|
|
3950
|
+
import { basename as basename4, join as join20, resolve as resolve13 } from "node:path";
|
|
3753
3951
|
var UI_FILES = ["**/*.{tsx,jsx,astro,vue,svelte}"];
|
|
3754
3952
|
var strings = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
3755
3953
|
async function workspaceGlobs(root, pkg) {
|
|
3756
|
-
const pnpm = await readFile7(
|
|
3954
|
+
const pnpm = await readFile7(join20(root, "pnpm-workspace.yaml"), "utf8").catch(() => null);
|
|
3757
3955
|
if (pnpm !== null) {
|
|
3758
3956
|
try {
|
|
3759
3957
|
const globs2 = strings((0, import_yaml3.parse)(pnpm)?.packages);
|
|
@@ -3767,8 +3965,8 @@ async function workspaceGlobs(root, pkg) {
|
|
|
3767
3965
|
}
|
|
3768
3966
|
var hasUi = async (dir) => (await findFiles(dir, UI_FILES, DEFAULT_EXCLUDE)).length > 0;
|
|
3769
3967
|
async function findProjects(dir) {
|
|
3770
|
-
const root =
|
|
3771
|
-
const pkg = await readJson(
|
|
3968
|
+
const root = resolve13(dir);
|
|
3969
|
+
const pkg = await readJson(join20(root, "package.json"));
|
|
3772
3970
|
const name = typeof pkg?.name === "string" && pkg.name.trim() ? pkg.name.trim() : basename4(root);
|
|
3773
3971
|
const single = { dir: ".", root, name: typeof pkg?.name === "string" ? pkg.name : null };
|
|
3774
3972
|
const workspace = await workspaceGlobs(root, pkg);
|
|
@@ -3779,12 +3977,12 @@ async function findProjects(dir) {
|
|
|
3779
3977
|
const projects = [];
|
|
3780
3978
|
const skipped = [];
|
|
3781
3979
|
for (const member of members) {
|
|
3782
|
-
const memberRoot =
|
|
3980
|
+
const memberRoot = join20(root, member);
|
|
3783
3981
|
if (!await hasUi(memberRoot)) {
|
|
3784
3982
|
skipped.push(member);
|
|
3785
3983
|
continue;
|
|
3786
3984
|
}
|
|
3787
|
-
const memberPkg = await readJson(
|
|
3985
|
+
const memberPkg = await readJson(join20(memberRoot, "package.json"));
|
|
3788
3986
|
projects.push({ dir: member, root: memberRoot, name: typeof memberPkg?.name === "string" ? memberPkg.name : null });
|
|
3789
3987
|
}
|
|
3790
3988
|
if (projects.length === 0) return { root, name, layout: `one project (${workspace.source} names no package with UI files)`, projects: [single], skipped };
|
|
@@ -3811,7 +4009,7 @@ var Lines = class {
|
|
|
3811
4009
|
if (!this.enabled) return { at };
|
|
3812
4010
|
let lines = this.files.get(file);
|
|
3813
4011
|
if (!lines) {
|
|
3814
|
-
lines = (await readFile8(
|
|
4012
|
+
lines = (await readFile8(join21(this.root, file), "utf8").catch(() => "")).split("\n");
|
|
3815
4013
|
this.files.set(file, lines);
|
|
3816
4014
|
}
|
|
3817
4015
|
const code = lines[line - 1]?.trim();
|
|
@@ -3826,15 +4024,15 @@ function hardcodedValue(finding3) {
|
|
|
3826
4024
|
const value = bracket ?? (declaration ? `${declaration[2]} (${declaration[1]})` : finding3.text);
|
|
3827
4025
|
return { word: word === "type" ? "text size" : word, value, tokens };
|
|
3828
4026
|
}
|
|
3829
|
-
async function modulesOf(project2, target,
|
|
4027
|
+
async function modulesOf(project2, target, failed2) {
|
|
3830
4028
|
const modules = [];
|
|
3831
4029
|
for (const file of target.files) {
|
|
3832
4030
|
if (isShowcase(file)) continue;
|
|
3833
4031
|
try {
|
|
3834
|
-
const module = await parseModule(file, await readFile8(
|
|
4032
|
+
const module = await parseModule(file, await readFile8(join21(project2.root, file), "utf8"));
|
|
3835
4033
|
if (module) modules.push(module);
|
|
3836
4034
|
} catch (error) {
|
|
3837
|
-
|
|
4035
|
+
failed2.push(`${repoPath(project2.dir, file)}: ${errorText(error)}`);
|
|
3838
4036
|
}
|
|
3839
4037
|
}
|
|
3840
4038
|
return modules;
|
|
@@ -3855,7 +4053,7 @@ async function grouped(project2, found, lines, describe) {
|
|
|
3855
4053
|
return findings2.sort((a, b) => b.count - a.count || a.title.localeCompare(b.title));
|
|
3856
4054
|
}
|
|
3857
4055
|
async function analyseProject(project2, index, inventory, inventoryError, workspaceNames, lines) {
|
|
3858
|
-
const
|
|
4056
|
+
const failed2 = [];
|
|
3859
4057
|
const incomplete = [];
|
|
3860
4058
|
const notes = [];
|
|
3861
4059
|
const projectId = inventory?.projects[index]?.id;
|
|
@@ -3863,7 +4061,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
|
|
|
3863
4061
|
let target = null;
|
|
3864
4062
|
try {
|
|
3865
4063
|
target = await loadInventoryTarget(project2.root);
|
|
3866
|
-
modules = await modulesOf(project2, target,
|
|
4064
|
+
modules = await modulesOf(project2, target, failed2);
|
|
3867
4065
|
} catch (error) {
|
|
3868
4066
|
incomplete.push(`The code could not be read (${errorText(error)})`);
|
|
3869
4067
|
}
|
|
@@ -3906,7 +4104,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
|
|
|
3906
4104
|
const components = [...byComponent.values()].map((c) => ({ name: c.name, file: c.file, uses: c.roles.length, role: [...new Set(c.roles)].sort((a, b) => c.roles.filter((r) => r === b).length - c.roles.filter((r) => r === a).length)[0] })).sort((a, b) => b.uses - a.uses || a.name.localeCompare(b.name)).slice(0, TOP_COMPONENTS);
|
|
3907
4105
|
const inventoryProject = inventory?.projects[index];
|
|
3908
4106
|
if (inventoryError) incomplete.push(`The component inventory could not be built (${inventoryError}); run ${NPX} inventory --project ${project2.dir} to see why`);
|
|
3909
|
-
for (const f of inventoryProject?.failed ?? [])
|
|
4107
|
+
for (const f of inventoryProject?.failed ?? []) failed2.push(`${repoPath(project2.dir, f.file)}: ${f.reason}`);
|
|
3910
4108
|
let hardcoded = [];
|
|
3911
4109
|
let offLibrary = [];
|
|
3912
4110
|
const everyLine = () => true;
|
|
@@ -3935,7 +4133,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
|
|
|
3935
4133
|
incomplete.push(`Library choices were not compared (${errorText(error)}); run ${NPX} detect ${project2.dir} to see why`);
|
|
3936
4134
|
}
|
|
3937
4135
|
}
|
|
3938
|
-
const pkg = await readFile8(
|
|
4136
|
+
const pkg = await readFile8(join21(project2.root, "package.json"), "utf8").then((text) => JSON.parse(text), () => null);
|
|
3939
4137
|
const dependencies = new Set(["dependencies", "devDependencies", "peerDependencies"].flatMap((key2) => Object.keys(pkg?.[key2] ?? {})));
|
|
3940
4138
|
const importer = changeProject ? await importOf(changeProject) : () => "";
|
|
3941
4139
|
const facts = factsOf(project2, projectId ?? null, modules, eligible, workspaceNames, dependencies, target?.files ?? [], importer);
|
|
@@ -3943,7 +4141,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
|
|
|
3943
4141
|
dir: project2.dir,
|
|
3944
4142
|
name: project2.name,
|
|
3945
4143
|
files: inventoryProject?.files ?? modules.length,
|
|
3946
|
-
failed,
|
|
4144
|
+
failed: failed2,
|
|
3947
4145
|
libraries,
|
|
3948
4146
|
unused,
|
|
3949
4147
|
definitions: inventory ? inventory.definitions.filter((d) => d.project === projectId && !isShowcase(d.file)).length : 0,
|
|
@@ -4379,36 +4577,36 @@ ${model.projects.map((p) => projectSection(p, many, looks)).join("\n")}
|
|
|
4379
4577
|
import { spawn as spawn2 } from "node:child_process";
|
|
4380
4578
|
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "node:fs";
|
|
4381
4579
|
import { homedir as homedir2 } from "node:os";
|
|
4382
|
-
import { dirname as
|
|
4580
|
+
import { dirname as dirname12, join as join22 } from "node:path";
|
|
4383
4581
|
var LOOKS_PER_PROJECT = 4;
|
|
4384
4582
|
var DOWNLOAD_SIZE = "~100 MB";
|
|
4385
4583
|
var DEPENDENCIES = "Install the repo's dependencies to see rendered looks";
|
|
4386
4584
|
var UNDRAWABLE = /not installed|loads no stylesheet|drawn from (?:its|their) (?:tag and )?classes|did not render|bundle failed|unstyled|stub fallback/i;
|
|
4387
|
-
var playwrightDir = () =>
|
|
4585
|
+
var playwrightDir = () => dirname12(playwrightCli());
|
|
4388
4586
|
function browsersPath(env) {
|
|
4389
4587
|
const set = env.PLAYWRIGHT_BROWSERS_PATH;
|
|
4390
|
-
if (set === "0") return
|
|
4588
|
+
if (set === "0") return join22(playwrightDir(), ".local-browsers");
|
|
4391
4589
|
if (set) return set;
|
|
4392
|
-
if (process.platform === "darwin") return
|
|
4393
|
-
if (process.platform === "win32") return
|
|
4394
|
-
return
|
|
4590
|
+
if (process.platform === "darwin") return join22(homedir2(), "Library/Caches/ms-playwright");
|
|
4591
|
+
if (process.platform === "win32") return join22(env.LOCALAPPDATA ?? join22(homedir2(), "AppData/Local"), "ms-playwright");
|
|
4592
|
+
return join22(env.XDG_CACHE_HOME ?? join22(homedir2(), ".cache"), "ms-playwright");
|
|
4395
4593
|
}
|
|
4396
4594
|
function chromiumStatus(env = process.env) {
|
|
4397
4595
|
if (env.YOUI_CHROMIUM) return { available: existsSync4(env.YOUI_CHROMIUM), where: env.YOUI_CHROMIUM };
|
|
4398
4596
|
try {
|
|
4399
|
-
const browsers = JSON.parse(readFileSync2(
|
|
4597
|
+
const browsers = JSON.parse(readFileSync2(join22(playwrightDir(), "browsers.json"), "utf8"));
|
|
4400
4598
|
const revision2 = browsers.browsers.find((b) => b.name === "chromium-headless-shell")?.revision;
|
|
4401
|
-
const dir =
|
|
4402
|
-
return { available: revision2 !== void 0 && existsSync4(
|
|
4599
|
+
const dir = join22(browsersPath(env), `chromium_headless_shell-${revision2}`);
|
|
4600
|
+
return { available: revision2 !== void 0 && existsSync4(join22(dir, "INSTALLATION_COMPLETE")), where: dir };
|
|
4403
4601
|
} catch {
|
|
4404
4602
|
return { available: false, where: "playwright-core is not installed" };
|
|
4405
4603
|
}
|
|
4406
4604
|
}
|
|
4407
4605
|
function downloadChromium() {
|
|
4408
|
-
return new Promise((
|
|
4409
|
-
const child = spawn2(process.execPath, [
|
|
4410
|
-
child.on("error", () =>
|
|
4411
|
-
child.on("close", (code) =>
|
|
4606
|
+
return new Promise((resolve16) => {
|
|
4607
|
+
const child = spawn2(process.execPath, [join22(playwrightDir(), "cli.js"), "install", "chromium-headless-shell"], { stdio: ["ignore", 2, 2] });
|
|
4608
|
+
child.on("error", () => resolve16(false));
|
|
4609
|
+
child.on("close", (code) => resolve16(code === 0));
|
|
4412
4610
|
});
|
|
4413
4611
|
}
|
|
4414
4612
|
var dataUri = (file) => file && existsSync4(file) ? `data:image/png;base64,${readFileSync2(file).toString("base64")}` : null;
|
|
@@ -4422,24 +4620,24 @@ async function addLooks(model, repoRoot, options = {}) {
|
|
|
4422
4620
|
const where2 = section.dir === "." ? "this repo" : section.dir;
|
|
4423
4621
|
try {
|
|
4424
4622
|
const installed = (name) => {
|
|
4425
|
-
let dir =
|
|
4623
|
+
let dir = join22(repoRoot, section.dir);
|
|
4426
4624
|
while (true) {
|
|
4427
|
-
if (existsSync4(
|
|
4428
|
-
if (dir === repoRoot ||
|
|
4429
|
-
dir =
|
|
4625
|
+
if (existsSync4(join22(dir, "node_modules", name, "package.json"))) return true;
|
|
4626
|
+
if (dir === repoRoot || dirname12(dir) === dir) return false;
|
|
4627
|
+
dir = dirname12(dir);
|
|
4430
4628
|
}
|
|
4431
4629
|
};
|
|
4432
4630
|
if (!installed("react") || !installed("react-dom")) {
|
|
4433
4631
|
missing.push(where2);
|
|
4434
4632
|
continue;
|
|
4435
4633
|
}
|
|
4436
|
-
const projectRoot =
|
|
4634
|
+
const projectRoot = join22(repoRoot, section.dir);
|
|
4437
4635
|
const target = await loadInventoryTarget(projectRoot);
|
|
4438
4636
|
if (findStylesheets(projectRoot, target.files, { tsconfig: target.tsconfig, override: target.specimenCss, templates: target.templates }).found.length === 0) {
|
|
4439
4637
|
missing.push(where2);
|
|
4440
4638
|
continue;
|
|
4441
4639
|
}
|
|
4442
|
-
const report = await renderShots(
|
|
4640
|
+
const report = await renderShots(join22(repoRoot, section.dir), names, { download: false, base: "HEAD", ...options.executablePath ? { executablePath: options.executablePath } : {}, ...options.cache ? { cache: options.cache } : {}, ...options.log ? { log: options.log } : {} });
|
|
4443
4641
|
const unavailable = report.notes.some((note) => UNDRAWABLE.test(note));
|
|
4444
4642
|
if (unavailable) {
|
|
4445
4643
|
missing.push(where2);
|
|
@@ -4511,7 +4709,7 @@ function renderMarkdown(model, reportFile) {
|
|
|
4511
4709
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
4512
4710
|
import { readFile as readFile9 } from "node:fs/promises";
|
|
4513
4711
|
import { homedir as homedir3, userInfo as userInfo2 } from "node:os";
|
|
4514
|
-
import { join as
|
|
4712
|
+
import { join as join23 } from "node:path";
|
|
4515
4713
|
var REDACTED = "[redacted]";
|
|
4516
4714
|
var OUTSIDE = "[path outside the repo]";
|
|
4517
4715
|
var MIN_VALUE = 6;
|
|
@@ -4523,7 +4721,7 @@ async function dotenvValues(root) {
|
|
|
4523
4721
|
const files = await findFiles(root, [".env", ".env.*", "**/.env", "**/.env.*"]).catch(() => []);
|
|
4524
4722
|
const values = [];
|
|
4525
4723
|
for (const file of files) {
|
|
4526
|
-
const text = await readFile9(
|
|
4724
|
+
const text = await readFile9(join23(root, file), "utf8").catch(() => "");
|
|
4527
4725
|
for (const line of text.split(/\r?\n/)) {
|
|
4528
4726
|
const match = /^\s*(?:export\s+)?[A-Za-z_][A-Za-z0-9_.-]*\s*=\s*(.*)$/.exec(line);
|
|
4529
4727
|
if (!match) continue;
|
|
@@ -4650,12 +4848,12 @@ async function askTerminal(question) {
|
|
|
4650
4848
|
}
|
|
4651
4849
|
}
|
|
4652
4850
|
async function writeBinaryAtomic(file, bytes) {
|
|
4653
|
-
const temp = `${file}.${process.pid}.${
|
|
4851
|
+
const temp = `${file}.${process.pid}.${randomBytes3(6).toString("hex")}.tmp`;
|
|
4654
4852
|
await writeFile2(temp, bytes);
|
|
4655
4853
|
await rename3(temp, file);
|
|
4656
4854
|
}
|
|
4657
4855
|
var shown = (file) => {
|
|
4658
|
-
const rel =
|
|
4856
|
+
const rel = relative10(process.cwd(), file);
|
|
4659
4857
|
return rel && !rel.startsWith("..") ? rel : file;
|
|
4660
4858
|
};
|
|
4661
4859
|
async function runReport(options = {}, io = {}) {
|
|
@@ -4664,7 +4862,7 @@ async function runReport(options = {}, io = {}) {
|
|
|
4664
4862
|
const error = io.error ?? ((text) => void process.stderr.write(`${text}
|
|
4665
4863
|
`));
|
|
4666
4864
|
const env = io.env ?? process.env;
|
|
4667
|
-
const root =
|
|
4865
|
+
const root = resolve14(options.path ?? process.cwd());
|
|
4668
4866
|
if (!(await stat3(root).catch(() => null))?.isDirectory()) {
|
|
4669
4867
|
error(`\u2716 ${options.path ?? root} is not a directory: run ${NPX} in a repo, or pass its path (${NPX} path/to/repo)`);
|
|
4670
4868
|
return { code: 2, files: null, model: null };
|
|
@@ -4694,9 +4892,9 @@ async function runReport(options = {}, io = {}) {
|
|
|
4694
4892
|
model.durationMs += Math.round(performance.now() - started);
|
|
4695
4893
|
const redactor = await Redactor.forRepo(root, { includeCode: options.includeCode === true, env });
|
|
4696
4894
|
const clean = redactor.model(model);
|
|
4697
|
-
const outDir =
|
|
4895
|
+
const outDir = resolve14(options.out ?? root);
|
|
4698
4896
|
await mkdir5(outDir, { recursive: true });
|
|
4699
|
-
const files = { report:
|
|
4897
|
+
const files = { report: join24(outDir, REPORT_FILE), card: join24(outDir, CARD_FILE), summary: join24(outDir, SUMMARY_FILE) };
|
|
4700
4898
|
const t = clean.totals;
|
|
4701
4899
|
const card = {
|
|
4702
4900
|
repo: redactor.scrub(clean.repo),
|
|
@@ -4780,6 +4978,11 @@ ${style.bold("Anywhere")} ${style.dim("no design system needed")}
|
|
|
4780
4978
|
an unchanged component comes from the cache (YOUI_SPECIMEN_CACHE)
|
|
4781
4979
|
|
|
4782
4980
|
${style.bold("In a product repo")}
|
|
4981
|
+
status [--json] where the product stands with its design system, in a few lines: the System
|
|
4982
|
+
and its source, what a sync would change (a dry run, offline), local drift,
|
|
4983
|
+
errors and warnings (beyond the baseline), components used, open requests,
|
|
4984
|
+
and the one thing to do next. In a workspace (youi.workspace.json, or a repo
|
|
4985
|
+
holding several products), one block per product
|
|
4783
4986
|
adopt [--components dir] [--css file] [--system id] [--fork] [--dry-run]
|
|
4784
4987
|
make this product's own UI its design system, kept in this repo: a draft
|
|
4785
4988
|
contract per component in the components folder (the files stay where they
|
|
@@ -4883,7 +5086,8 @@ ${style.bold("Detect")} ${style.dim("which UI libraries a repo or website uses
|
|
|
4883
5086
|
|
|
4884
5087
|
${style.bold("The hub")}
|
|
4885
5088
|
ui [--design dir] [--project dir] [--config file] [--root dir] [--port n] [--watch] [--state dir] [--inventory file] [--detect file]
|
|
4886
|
-
open the web hub on a design system (default: the demo system
|
|
5089
|
+
open the web hub on a design system (default: the demo system; beside a
|
|
5090
|
+
youi.workspace.json, its products, with Work > Workspace to switch);
|
|
4887
5091
|
--project adds that product's usage map (read-only scan) and the
|
|
4888
5092
|
rule editor (System > Rules, Work > Changes): drafts, impact preview,
|
|
4889
5093
|
approval, a review branch in the design repo (drafts kept in --state);
|
|
@@ -4992,10 +5196,10 @@ async function readJsonOrFile(value) {
|
|
|
4992
5196
|
async function cmdScan(values) {
|
|
4993
5197
|
const p = await project(values);
|
|
4994
5198
|
const { report } = await scanProject(p);
|
|
4995
|
-
const out = values.out ??
|
|
4996
|
-
await writeFileAtomic(
|
|
5199
|
+
const out = values.out ?? join25(p.root, ".youi/usage.json");
|
|
5200
|
+
await writeFileAtomic(resolve15(out), `${JSON.stringify(report)}
|
|
4997
5201
|
`);
|
|
4998
|
-
const recorded = await recordUsage(values, p, report,
|
|
5202
|
+
const recorded = await recordUsage(values, p, report, resolve15(out));
|
|
4999
5203
|
if (values.json) json3(report);
|
|
5000
5204
|
else print([renderScan(report, out), ...recorded ? [recorded] : []].join("\n"));
|
|
5001
5205
|
return 0;
|
|
@@ -5004,7 +5208,7 @@ async function recordUsage(values, p, report, usage) {
|
|
|
5004
5208
|
const { path, home } = indexOf(values);
|
|
5005
5209
|
try {
|
|
5006
5210
|
await recordProject(await sharedStore(path), p, report);
|
|
5007
|
-
await noteSource(home, { project: { root:
|
|
5211
|
+
await noteSource(home, { project: { root: resolve15(values.project ?? process.cwd()), options: Object.fromEntries(Object.entries(loadOptions(values)).map(([key2, value]) => [key2, resolve15(value)])), usage } });
|
|
5008
5212
|
return null;
|
|
5009
5213
|
} catch (error) {
|
|
5010
5214
|
return notRecorded(error);
|
|
@@ -5019,17 +5223,17 @@ function indexOf(values) {
|
|
|
5019
5223
|
}
|
|
5020
5224
|
async function cmdGaps(values) {
|
|
5021
5225
|
const dirs = inventoryDirs(values);
|
|
5022
|
-
const inventory = values.inventory ? InventoryReport.parse(JSON.parse(await readFile10(
|
|
5023
|
-
const cwd = values.inventory ?
|
|
5024
|
-
const project2 = await loadProject(
|
|
5226
|
+
const inventory = values.inventory ? InventoryReport.parse(JSON.parse(await readFile10(resolve15(values.inventory), "utf8"))) : await scanInventory(values, dirs);
|
|
5227
|
+
const cwd = values.inventory ? dirname13(resolve15(values.inventory)) : process.cwd();
|
|
5228
|
+
const project2 = await loadProject(resolve15(dirs[0]), loadOptions(values)).catch(() => null);
|
|
5025
5229
|
const system = project2 ? { name: project2.system.config.name, components: project2.system.components.map((c) => c.contract.name) } : null;
|
|
5026
|
-
const store = await readCatalog(
|
|
5230
|
+
const store = await readCatalog(resolve15(discoverCacheDir(values.cache)), indexOf(values).path).catch((error) => {
|
|
5027
5231
|
if (error instanceof MissingIndexError) return null;
|
|
5028
5232
|
throw error;
|
|
5029
5233
|
});
|
|
5030
|
-
const roots = new Map(inventory.projects.map((p) => [p.id,
|
|
5234
|
+
const roots = new Map(inventory.projects.map((p) => [p.id, resolve15(cwd, p.root)]));
|
|
5031
5235
|
const report = await buildGaps({ inventory, system, dependencies: declaredPackages(roots) }, store);
|
|
5032
|
-
if (values.out) await writeFileAtomic(
|
|
5236
|
+
if (values.out) await writeFileAtomic(resolve15(values.out), `${JSON.stringify(report, null, 2)}
|
|
5033
5237
|
`);
|
|
5034
5238
|
if (values.json) json3(report);
|
|
5035
5239
|
else print(renderGaps(report));
|
|
@@ -5039,12 +5243,12 @@ async function cmdInventory(values) {
|
|
|
5039
5243
|
if (values.history) return cmdHistory(values, []);
|
|
5040
5244
|
const dirs = inventoryDirs(values);
|
|
5041
5245
|
const report = await scanInventory(values, dirs);
|
|
5042
|
-
const out =
|
|
5246
|
+
const out = resolve15(values.out ?? join25(dirs[0], ".youi/inventory.json"));
|
|
5043
5247
|
await writeFileAtomic(out, `${JSON.stringify(report, null, 2)}
|
|
5044
5248
|
`);
|
|
5045
5249
|
const recorded = await recordScan(values, report, out);
|
|
5046
5250
|
if (values.json) json3(report);
|
|
5047
|
-
else print([renderInventory(report,
|
|
5251
|
+
else print([renderInventory(report, relative11(process.cwd(), out).startsWith("..") ? out : relative11(process.cwd(), out)), recorded].join("\n"));
|
|
5048
5252
|
return 0;
|
|
5049
5253
|
}
|
|
5050
5254
|
async function recordScan(values, report, out) {
|
|
@@ -5069,7 +5273,7 @@ async function cmdIndex(values, positionals) {
|
|
|
5069
5273
|
const [action, ...rest] = positionals;
|
|
5070
5274
|
const { path, home } = indexOf(values);
|
|
5071
5275
|
if (action === "path") {
|
|
5072
|
-
if (values.json) json3({ store: storeLocation({ path }), sources:
|
|
5276
|
+
if (values.json) json3({ store: storeLocation({ path }), sources: join25(home, SOURCES_FILE), scans: join25(home, SCANS_LOG) });
|
|
5073
5277
|
else print(storeLocation({ path }));
|
|
5074
5278
|
return 0;
|
|
5075
5279
|
}
|
|
@@ -5080,7 +5284,7 @@ async function cmdIndex(values, positionals) {
|
|
|
5080
5284
|
return 0;
|
|
5081
5285
|
}
|
|
5082
5286
|
if (action === "rebuild") {
|
|
5083
|
-
const summary = await rebuildIndex(path, values.cache ? { discoverCache:
|
|
5287
|
+
const summary = await rebuildIndex(path, values.cache ? { discoverCache: resolve15(values.cache) } : {});
|
|
5084
5288
|
if (values.json) json3(summary);
|
|
5085
5289
|
else print(renderRebuild(summary));
|
|
5086
5290
|
return 0;
|
|
@@ -5120,7 +5324,7 @@ async function cmdBom(values, positionals) {
|
|
|
5120
5324
|
return 0;
|
|
5121
5325
|
}
|
|
5122
5326
|
function baselineFile(values, p) {
|
|
5123
|
-
if (values["baseline-file"]) return
|
|
5327
|
+
if (values["baseline-file"]) return resolve15(values["baseline-file"]);
|
|
5124
5328
|
return values.baseline ? baselinePath(p, values.config) : null;
|
|
5125
5329
|
}
|
|
5126
5330
|
var checkScope = (values, positionals) => ({ ...positionals.length > 0 ? { files: positionals } : {}, ...values.rule ? { rule: values.rule } : {}, quiet: values.quiet });
|
|
@@ -5177,7 +5381,7 @@ async function cmdBaseline(values) {
|
|
|
5177
5381
|
const { totals } = written.baseline;
|
|
5178
5382
|
if (values.json) json3({ file: written.file, action: written.action, totals, notAdded: written.notAdded, identity: written.baseline.identity });
|
|
5179
5383
|
else {
|
|
5180
|
-
print(`${style.ok(written.action)} ${
|
|
5384
|
+
print(`${style.ok(written.action)} ${relative11(process.cwd(), file) || file}: ${plural(totals.errors, "error")} \xB7 ${plural(totals.warnings, "warning")} accepted`);
|
|
5181
5385
|
print(style.dim(` rules ${describeIdentity(written.baseline.identity)}`));
|
|
5182
5386
|
if (written.notAdded > 0) print(style.warn(` ${plural(written.notAdded, "current violation")} not added: fix them, or accept them deliberately with --force`));
|
|
5183
5387
|
}
|
|
@@ -5248,7 +5452,7 @@ async function cmdInit(values) {
|
|
|
5248
5452
|
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)");
|
|
5249
5453
|
const root = values.project ?? process.cwd();
|
|
5250
5454
|
const design = await designArg(values.design, root);
|
|
5251
|
-
const result = await initProject({ root, ...design, schemaDir:
|
|
5455
|
+
const result = await initProject({ root, ...design, schemaDir: join25(TOOL_ROOT, "schema"), ...values.system ? { system: values.system } : {}, ...values.components ? { componentsDir: values.components } : {}, force: values.force });
|
|
5252
5456
|
if (!values.json) {
|
|
5253
5457
|
print(`${style.ok("created")} ${result.file}`);
|
|
5254
5458
|
for (const note of result.notes) print(style.dim(` \xB7 ${note}`));
|
|
@@ -5258,28 +5462,28 @@ async function cmdInit(values) {
|
|
|
5258
5462
|
return cmdSync({ ...design.package ? rest : values, project: root });
|
|
5259
5463
|
}
|
|
5260
5464
|
async function cmdAdopt(values) {
|
|
5261
|
-
const root =
|
|
5262
|
-
if (await exists(
|
|
5465
|
+
const root = resolve15(values.project ?? process.cwd());
|
|
5466
|
+
if (await exists(join25(root, PROJECT_FILE))) throw new Error(`${root} already has ${PROJECT_FILE}: it follows a design system already (youiyoux sync updates it)`);
|
|
5263
5467
|
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(
|
|
5468
|
+
const taken = (await Promise.all(plan.files.map(async (f) => await exists(join25(root, f.path)) ? f.path : null))).filter((f) => f !== null);
|
|
5265
5469
|
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
5470
|
if (values["dry-run"]) {
|
|
5267
5471
|
if (values.json) json3(plan);
|
|
5268
5472
|
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
5473
|
return 0;
|
|
5270
5474
|
}
|
|
5271
|
-
const stylesheet = plan.stylesheet ? await readFile10(
|
|
5475
|
+
const stylesheet = plan.stylesheet ? await readFile10(join25(root, plan.stylesheet.path), "utf8") : null;
|
|
5272
5476
|
try {
|
|
5273
|
-
for (const file of plan.files) await writeFileAtomic(
|
|
5274
|
-
if (plan.stylesheet) await writeFileAtomic(
|
|
5275
|
-
await initProject({ root, design: root, schemaDir:
|
|
5477
|
+
for (const file of plan.files) await writeFileAtomic(join25(root, file.path), file.content);
|
|
5478
|
+
if (plan.stylesheet) await writeFileAtomic(join25(root, plan.stylesheet.path), plan.stylesheet.content);
|
|
5479
|
+
await initProject({ root, design: root, schemaDir: join25(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
5480
|
await syncProject(await loadProject(root), { dryRun: true });
|
|
5277
5481
|
} catch (error) {
|
|
5278
|
-
for (const file of [...plan.files.map((f) => f.path), PROJECT_FILE]) await rm6(
|
|
5279
|
-
for (const folder of [...new Set(plan.files.map((f) =>
|
|
5280
|
-
await rmdir3(
|
|
5281
|
-
await rmdir3(
|
|
5282
|
-
if (plan.stylesheet && stylesheet !== null) await writeFileAtomic(
|
|
5482
|
+
for (const file of [...plan.files.map((f) => f.path), PROJECT_FILE]) await rm6(join25(root, file), { force: true });
|
|
5483
|
+
for (const folder of [...new Set(plan.files.map((f) => dirname13(f.path)))].sort((a, b) => b.length - a.length)) await rmdir3(join25(root, folder)).catch(() => void 0);
|
|
5484
|
+
await rmdir3(join25(root, "design/components")).catch(() => void 0);
|
|
5485
|
+
await rmdir3(join25(root, "design")).catch(() => void 0);
|
|
5486
|
+
if (plan.stylesheet && stylesheet !== null) await writeFileAtomic(join25(root, plan.stylesheet.path), stylesheet);
|
|
5283
5487
|
throw new Error(`adopt was undone, nothing kept: ${error instanceof Error ? error.message : String(error)}`);
|
|
5284
5488
|
}
|
|
5285
5489
|
if (!values.json) print([...adoptLines(plan), ""].join("\n"));
|
|
@@ -5305,6 +5509,12 @@ function adoptLines(plan, dryRun = false) {
|
|
|
5305
5509
|
})
|
|
5306
5510
|
];
|
|
5307
5511
|
}
|
|
5512
|
+
async function cmdStatus(values) {
|
|
5513
|
+
const report = await workspaceStatus(process.cwd(), values.project);
|
|
5514
|
+
if (values.json) json3(report);
|
|
5515
|
+
else print(renderStatus(report));
|
|
5516
|
+
return report.projects.length === 0 ? 1 : statusExitCode(report);
|
|
5517
|
+
}
|
|
5308
5518
|
async function cmdSystem(values, positionals) {
|
|
5309
5519
|
const [action, dir, ...extra] = positionals;
|
|
5310
5520
|
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)");
|
|
@@ -5324,7 +5534,7 @@ async function cmdSystem(values, positionals) {
|
|
|
5324
5534
|
print([...lines, ""].join("\n"));
|
|
5325
5535
|
const synced = await cmdSync({ ...values, project: p.root, package: void 0 });
|
|
5326
5536
|
const git3 = `push it and set source to { "git": "<its url>", "ref": "v${result.system.version}" }`;
|
|
5327
|
-
print(`Next: make ${
|
|
5537
|
+
print(`Next: make ${relative11(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
5538
|
return synced;
|
|
5329
5539
|
}
|
|
5330
5540
|
async function cmdLink(values, positionals) {
|
|
@@ -5342,31 +5552,31 @@ async function cmdUnlink(values) {
|
|
|
5342
5552
|
const root = values.project ?? process.cwd();
|
|
5343
5553
|
const linked = await linkedDesign(root);
|
|
5344
5554
|
if (linked !== null) await setLink(root, null);
|
|
5345
|
-
print(linked ? `${style.ok("unlinked")} ${
|
|
5555
|
+
print(linked ? `${style.ok("unlinked")} ${resolve15(root)} uses its config's source again` : `${resolve15(root)} was not linked`);
|
|
5346
5556
|
return 0;
|
|
5347
5557
|
}
|
|
5348
5558
|
async function designArg(value, root) {
|
|
5349
|
-
if (existsSync5(
|
|
5350
|
-
return { design: await designRootOf({ package: value },
|
|
5559
|
+
if (existsSync5(resolve15(value)) || !PACKAGE_NAME.test(value)) return { design: value };
|
|
5560
|
+
return { design: await designRootOf({ package: value }, join25(resolve15(root), PROJECT_FILE), root), package: value };
|
|
5351
5561
|
}
|
|
5352
5562
|
async function cmdBuild(values) {
|
|
5353
|
-
const root =
|
|
5563
|
+
const root = resolve15(values.design ?? process.cwd());
|
|
5354
5564
|
const system = getSystem(await loadDesignRepo(root), values.system);
|
|
5355
5565
|
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.`);
|
|
5356
5566
|
const output = OutputConfig.parse({ cssVarPrefix: values.prefix ?? "youi", targets: ["css-vars", "tailwind-v4", "ts", "react-native"] });
|
|
5357
5567
|
const started = performance.now();
|
|
5358
5568
|
const built = await buildTokens(root, system.config, output, { contracts: system.components.map((c) => c.contract) });
|
|
5359
|
-
const out =
|
|
5360
|
-
const boundary = values.out ? out :
|
|
5361
|
-
if (!within(boundary, out) || built.files.some((file) => !within(out,
|
|
5362
|
-
for (const file of built.files) await writeFileAtomic(
|
|
5569
|
+
const out = resolve15(values.out ?? join25(root, "build", system.config.id));
|
|
5570
|
+
const boundary = values.out ? out : join25(root, "build");
|
|
5571
|
+
if (!within(boundary, out) || built.files.some((file) => !within(out, join25(out, file.path)))) throw new Error(`Cannot build system ${JSON.stringify(system.config.id)}: an output path leaves the output directory.`);
|
|
5572
|
+
for (const file of built.files) await writeFileAtomic(join25(out, file.path), file.content);
|
|
5363
5573
|
if (values.json) json3({ out, files: built.files.map((f) => f.path), tokens: built.tokens.length });
|
|
5364
5574
|
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)})`)}`);
|
|
5365
5575
|
return 0;
|
|
5366
5576
|
}
|
|
5367
5577
|
async function cmdParity(values) {
|
|
5368
5578
|
if (!values.css) throw new UsageError("youiyoux parity --css <file> [--prefix p]");
|
|
5369
|
-
const root =
|
|
5579
|
+
const root = resolve15(values.design ?? process.cwd());
|
|
5370
5580
|
const system = getSystem(await loadDesignRepo(root), values.system);
|
|
5371
5581
|
const prefix = values.prefix ?? "youi";
|
|
5372
5582
|
const dark = { selector: values.dark ?? ':root[data-theme="dark"]' };
|
|
@@ -5384,16 +5594,16 @@ async function cmdParity(values) {
|
|
|
5384
5594
|
async function cmdSchema(values) {
|
|
5385
5595
|
const schemas = renderJsonSchemas();
|
|
5386
5596
|
if (values.check) {
|
|
5387
|
-
const dir =
|
|
5597
|
+
const dir = join25(values.design ? resolve15(values.design) : TOOL_ROOT, "schema");
|
|
5388
5598
|
const stale = [];
|
|
5389
|
-
for (const { file, content } of schemas) if (await readFile10(
|
|
5599
|
+
for (const { file, content } of schemas) if (await readFile10(join25(dir, file), "utf8").catch(() => null) !== content) stale.push(file);
|
|
5390
5600
|
if (values.json) json3({ dir, stale });
|
|
5391
5601
|
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>" : ""}`);
|
|
5392
5602
|
return stale.length === 0 ? 0 : 1;
|
|
5393
5603
|
}
|
|
5394
5604
|
if (values.write) {
|
|
5395
|
-
const root = values.design ?
|
|
5396
|
-
for (const { file, content } of schemas) await writeFileAtomic(
|
|
5605
|
+
const root = values.design ? resolve15(values.design) : TOOL_ROOT;
|
|
5606
|
+
for (const { file, content } of schemas) await writeFileAtomic(join25(root, "schema", file), content);
|
|
5397
5607
|
print(`${style.ok("wrote")} ${schemas.map((s) => s.file).join(", ")}`);
|
|
5398
5608
|
} else json3(Object.fromEntries(schemas.map((s) => [s.file, JSON.parse(s.content)])));
|
|
5399
5609
|
return 0;
|
|
@@ -5416,7 +5626,7 @@ async function cmdHooks(values, positionals) {
|
|
|
5416
5626
|
if (action !== "install") throw new UsageError("youiyoux hooks install [--claude] [--git] [--ci [--diff]] [--strict] or youiyoux hooks run claude|pre-commit");
|
|
5417
5627
|
const p = await loadCheckProject(values.project ?? process.cwd(), loadOptions(values), !values.project);
|
|
5418
5628
|
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");
|
|
5419
|
-
const target = { ...hookTarget(values), project: p.root, ...configured(p) && p.configFile && p.configFile !==
|
|
5629
|
+
const target = { ...hookTarget(values), project: p.root, ...configured(p) && p.configFile && p.configFile !== join25(p.root, PROJECT_FILE) ? { config: p.configFile } : {}, ...values.root !== void 0 ? { root: p.root } : {} };
|
|
5420
5630
|
const all = !values.claude && !values.git && !values.ci;
|
|
5421
5631
|
const results = [];
|
|
5422
5632
|
if (values.claude || all) results.push({ kind: "Claude Code", ...await installClaudeHook(await repoRootOf(p.root), hookCommand(TOOL_ROOT, "claude", target)) });
|
|
@@ -5431,11 +5641,11 @@ async function cmdHooks(values, positionals) {
|
|
|
5431
5641
|
async function cmdAgents(values, positionals) {
|
|
5432
5642
|
const [action] = positionals;
|
|
5433
5643
|
if (action !== "install" && action !== "remove") throw new UsageError("youiyoux agents install|remove [--claude] [--codex] [--json]");
|
|
5434
|
-
const projectRoot =
|
|
5644
|
+
const projectRoot = resolve15(values.project ?? process.cwd());
|
|
5435
5645
|
const repoRoot = await repoRootOf(projectRoot);
|
|
5436
|
-
const target = { project: projectRoot, ...values.config ? { config:
|
|
5646
|
+
const target = { project: projectRoot, ...values.config ? { config: resolve15(values.config) } : {}, ...values.design ? { design: resolve15(values.design) } : {}, ...values.root !== void 0 ? { root: resolve15(values.root || ".") } : {} };
|
|
5437
5647
|
const server = mcpCommand(TOOL_ROOT, target);
|
|
5438
|
-
const system = existsSync5(
|
|
5648
|
+
const system = existsSync5(resolve15(values.config ?? join25(projectRoot, PROJECT_FILE))) ? await project(values) : null;
|
|
5439
5649
|
const claudeServer = system && system.root === repoRoot ? { command: "node", args: mcpArgs(system) } : relativeTo(repoRoot, server);
|
|
5440
5650
|
const both = !values.claude && !values.codex;
|
|
5441
5651
|
const results = [];
|
|
@@ -5506,7 +5716,32 @@ async function startEditor(values, port) {
|
|
|
5506
5716
|
return null;
|
|
5507
5717
|
}
|
|
5508
5718
|
}
|
|
5719
|
+
async function workspaceUi(values) {
|
|
5720
|
+
if (values.design || values.project || values.config || values.root) return null;
|
|
5721
|
+
const { roots, workspace } = await statusTargets(process.cwd());
|
|
5722
|
+
if (workspace === null || roots.length === 0) return null;
|
|
5723
|
+
print(style.dim(`reading the status of ${plural(roots.length, "product")}\u2026`));
|
|
5724
|
+
const report = await workspaceStatus(process.cwd());
|
|
5725
|
+
const first = report.projects.findIndex((p) => !("error" in p));
|
|
5726
|
+
if (first < 0) {
|
|
5727
|
+
print(renderStatus(report));
|
|
5728
|
+
throw new Error("no product in this workspace could be read: see each one's Next above");
|
|
5729
|
+
}
|
|
5730
|
+
const port = values.port ?? DEFAULT_HUB_PORT;
|
|
5731
|
+
let open = null;
|
|
5732
|
+
const hub = await startWorkspaceHub({ roots, current: first, status: () => workspaceStatus(process.cwd()), open: (root) => open ? open(root) : Promise.resolve(1) });
|
|
5733
|
+
print(`${style.ok("workspace hub")} http://localhost:${port}/workspace#key=${hub.key}`);
|
|
5734
|
+
print(style.dim(` ${plural(roots.length, "product")}; showing ${report.projects[first].root} first. Open the link: it carries this run's key, which switching needs`));
|
|
5735
|
+
print(style.dim(" Rules and Changes are read-only here; youiyoux ui --project <dir> opens the rule editor on one product"));
|
|
5736
|
+
try {
|
|
5737
|
+
return await runHub(TOOL_ROOT, { project: roots[first], port, api: hub.url, workspace: (snapshot) => open = snapshot, ...values.inventory ? { inventory: values.inventory } : {}, ...values.detect ? { detect: values.detect } : {} });
|
|
5738
|
+
} finally {
|
|
5739
|
+
await hub.close();
|
|
5740
|
+
}
|
|
5741
|
+
}
|
|
5509
5742
|
async function cmdUi(values) {
|
|
5743
|
+
const workspace = await workspaceUi(values);
|
|
5744
|
+
if (workspace !== null) return workspace;
|
|
5510
5745
|
const port = values.port ?? DEFAULT_HUB_PORT;
|
|
5511
5746
|
const editor = await startEditor(values, port);
|
|
5512
5747
|
try {
|
|
@@ -5518,20 +5753,20 @@ async function cmdUi(values) {
|
|
|
5518
5753
|
async function cmdDetect(values, positionals) {
|
|
5519
5754
|
const target = detectTarget(positionals);
|
|
5520
5755
|
if (!target) throw new UsageError("youiyoux detect <repo-path> or youiyoux detect <url\u2026> [--expose] (one repo, or one or more URLs; not both)");
|
|
5521
|
-
const { repoReport, siteReport, writeReport } = await import("./src-
|
|
5522
|
-
const out =
|
|
5756
|
+
const { repoReport, siteReport, writeReport } = await import("./src-ZG7JU6YJ.js");
|
|
5757
|
+
const out = resolve15(values.out ?? ".youi/detect");
|
|
5523
5758
|
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 } : {} });
|
|
5524
5759
|
const file = await writeReport(report, out);
|
|
5525
5760
|
if (values.json) json3(report);
|
|
5526
5761
|
else {
|
|
5527
|
-
const shown2 =
|
|
5762
|
+
const shown2 = relative11(process.cwd(), file);
|
|
5528
5763
|
print(renderDetect(report, shown2 && !shown2.startsWith("..") ? shown2 : file));
|
|
5529
5764
|
}
|
|
5530
5765
|
return 0;
|
|
5531
5766
|
}
|
|
5532
5767
|
async function cmdRender(values, positionals, io) {
|
|
5533
|
-
const root =
|
|
5534
|
-
const targets = positionals.map((t) => existsSync5(
|
|
5768
|
+
const root = resolve15(values.root ?? values.project ?? process.cwd());
|
|
5769
|
+
const targets = positionals.map((t) => existsSync5(resolve15(t)) ? resolve15(t) : t);
|
|
5535
5770
|
try {
|
|
5536
5771
|
const report = await renderShots(root, targets, { ...values.base ? { base: values.base } : {}, ...process.env.YOUI_CHROMIUM ? { executablePath: process.env.YOUI_CHROMIUM } : {}, ...io.render });
|
|
5537
5772
|
if (values.json) json3({ ...report, next: shotsNext(report, (t) => `run youiyoux render${t ? ` ${t}` : ""} again`) });
|
|
@@ -5545,14 +5780,14 @@ async function cmdRender(values, positionals, io) {
|
|
|
5545
5780
|
}
|
|
5546
5781
|
}
|
|
5547
5782
|
async function product(values) {
|
|
5548
|
-
const project2 = await loadProject(
|
|
5549
|
-
return { project: project2, root: project2?.root ??
|
|
5783
|
+
const project2 = await loadProject(resolve15(values.project ?? process.cwd()), loadOptions(values)).catch(() => null);
|
|
5784
|
+
return { project: project2, root: project2?.root ?? resolve15(values.root ?? values.project ?? process.cwd()) };
|
|
5550
5785
|
}
|
|
5551
5786
|
async function cmdRequests(values, positionals) {
|
|
5552
5787
|
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>";
|
|
5553
5788
|
const [action, id, ...rest] = positionals;
|
|
5554
5789
|
if (rest.length > 0) throw new UsageError(usage);
|
|
5555
|
-
const { RequestStore, requestBrief } = await import("./src-
|
|
5790
|
+
const { RequestStore, requestBrief } = await import("./src-EUVBYTKY.js");
|
|
5556
5791
|
const { project: project2, root } = await product(values);
|
|
5557
5792
|
const store = new RequestStore(root, project2?.config.id ?? basename5(root));
|
|
5558
5793
|
const agent = { kind: "agent", name: "youiyoux CLI" };
|
|
@@ -5584,17 +5819,17 @@ async function cmdScreens(values, positionals, io) {
|
|
|
5584
5819
|
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>]";
|
|
5585
5820
|
if (!["capture", "login"].includes(positionals[0] ?? "") || positionals.length > 1 || !values.base) throw new UsageError(usage);
|
|
5586
5821
|
const { root } = await product(values);
|
|
5587
|
-
if (positionals[0] === "login") return screensLogin(values.base, values["storage-state"] ?
|
|
5588
|
-
const dir =
|
|
5589
|
-
if (!existsSync5(
|
|
5822
|
+
if (positionals[0] === "login") return screensLogin(values.base, values["storage-state"] ? resolve15(values["storage-state"]) : sessionFile(root), io);
|
|
5823
|
+
const dir = resolve15(values.out ?? join25(root, ".youi/screens"));
|
|
5824
|
+
if (!existsSync5(join25(dir, "screens.json"))) {
|
|
5590
5825
|
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":"/"}]}
|
|
5591
5826
|
`);
|
|
5592
5827
|
return 1;
|
|
5593
5828
|
}
|
|
5594
|
-
const session = chosenSession(root, values["storage-state"] ?
|
|
5829
|
+
const session = chosenSession(root, values["storage-state"] ? resolve15(values["storage-state"]) : void 0);
|
|
5595
5830
|
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`)}
|
|
5596
5831
|
`);
|
|
5597
|
-
const { launchBrowser } = await import("./src-
|
|
5832
|
+
const { launchBrowser } = await import("./src-ZG7JU6YJ.js");
|
|
5598
5833
|
const browser = await launchBrowser(process.env.YOUI_CHROMIUM, { purpose: { what: "youiyoux screens capture", command: "youiyoux screens capture" } });
|
|
5599
5834
|
try {
|
|
5600
5835
|
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)}
|
|
@@ -5651,7 +5886,7 @@ async function cmdReport(values, positionals, io) {
|
|
|
5651
5886
|
return result.code;
|
|
5652
5887
|
}
|
|
5653
5888
|
async function cmdMcp(values) {
|
|
5654
|
-
const { startStdioServer } = await import("./src-
|
|
5889
|
+
const { startStdioServer } = await import("./src-YKPJ3PII.js");
|
|
5655
5890
|
await startStdioServer({ projectRoot: values.project ?? process.cwd(), ...loadOptions(values), ...values.cache ? { discoverCache: values.cache } : {} });
|
|
5656
5891
|
return -1;
|
|
5657
5892
|
}
|
|
@@ -5662,7 +5897,7 @@ function positiveInt(value, flag) {
|
|
|
5662
5897
|
return n;
|
|
5663
5898
|
}
|
|
5664
5899
|
async function cmdDiscover(values, positionals, io) {
|
|
5665
|
-
const cacheDir =
|
|
5900
|
+
const cacheDir = resolve15(discoverCacheDir(values.cache));
|
|
5666
5901
|
const { path, home } = indexOf(values);
|
|
5667
5902
|
const catalog = () => readCatalog(cacheDir, path);
|
|
5668
5903
|
const [action, ...rest] = positionals;
|
|
@@ -5714,6 +5949,7 @@ ${style.dim(`store \u2192 ${storeLocation({ path })} (${plural(loaded.chosen, "l
|
|
|
5714
5949
|
}
|
|
5715
5950
|
var COMMANDS = {
|
|
5716
5951
|
adopt: cmdAdopt,
|
|
5952
|
+
status: cmdStatus,
|
|
5717
5953
|
system: cmdSystem,
|
|
5718
5954
|
link: cmdLink,
|
|
5719
5955
|
unlink: cmdUnlink,
|