youiyoux 0.1.3 → 0.1.5
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/README.md +10 -0
- package/dist/chunks/{chunk-S7UV34H5.js → chunk-42YJX3W5.js} +1 -3
- package/dist/chunks/{chunk-74KBRI2H.js → chunk-4BBYLTZX.js} +10 -10
- package/dist/chunks/{chunk-RTOTNSOV.js → chunk-5QRDHWK5.js} +600 -506
- package/dist/chunks/{chunk-OIHNEGWH.js → chunk-BSJLO2LJ.js} +96 -142
- package/dist/chunks/chunk-FVVL5JCY.js +24 -0
- package/dist/chunks/{chunk-UEB6MJDX.js → chunk-M5XZWCPS.js} +176 -61
- package/dist/chunks/{chunk-C3TSTYP7.js → chunk-NYDJYMID.js} +212 -26
- package/dist/chunks/chunk-O6QTQCTC.js +129 -0
- package/dist/chunks/chunk-VG3ZCSST.js +8720 -0
- package/dist/chunks/chunk-VM6BGPRI.js +188 -0
- package/dist/chunks/{chunk-E4HNK7VF.js → chunk-WNTS7ZO4.js} +1 -1
- package/dist/chunks/chunk-YJ7WMBTT.js +13761 -0
- package/dist/chunks/{src-U5NTDPUX.js → src-34R7QMJC.js} +11 -35
- package/dist/chunks/src-5IP4NGKT.js +115 -0
- package/dist/chunks/{src-YGEZCUAO.js → src-E25GHWWT.js} +134 -77
- package/dist/chunks/src-RHNZELGE.js +306 -0
- package/dist/chunks/{src-YUC5PIDK.js → src-SFKFERZ2.js} +14 -8
- package/dist/chunks/{src-ZS76UD7E.js → src-SZHEV4YC.js} +27 -16
- package/dist/chunks/{src-DS43LCRA.js → src-VL2ZYO6X.js} +4 -4
- package/dist/hub/assets/Badge-CUJUit4d.js +1 -0
- package/dist/hub/assets/CandidatePanel-CnbK4S0j.js +1 -0
- package/dist/hub/assets/CatalogScreen-B5OVL-AR.js +1 -0
- package/dist/hub/assets/ChangesScreen-GwGqJwxN.js +1 -0
- package/dist/hub/assets/CheatSheet-BLSOq-xa.js +1 -0
- package/dist/hub/assets/CodeBlock-CsiGFgvI.js +1 -0
- package/dist/hub/assets/CommandPalette-CAWQfdXJ.js +1 -0
- package/dist/hub/assets/CopyButton-DE8drl4P.js +1 -0
- package/dist/hub/assets/Disclosure-B1kTgPgk.js +1 -0
- package/dist/hub/assets/DiscoverScreen-Tl_Z6mgY.js +2 -0
- package/dist/hub/assets/DraftEditor-yIDapXTs.js +5 -0
- package/dist/hub/assets/Field-LwrJXoOs.js +1 -0
- package/dist/hub/assets/GapsScreen-DXZAlm6v.js +1 -0
- package/dist/hub/assets/HomeScreen-CEpiVhOa.js +1 -0
- package/dist/hub/assets/InventoryScreen-CEG3Pmw_.js +1 -0
- package/dist/hub/assets/KindIcon-BJ6bz-ny.js +1 -0
- package/dist/hub/assets/LibraryScreen-IaVnxrX9.js +1 -0
- package/dist/hub/assets/LicenceBadge-CLCUMMHE.js +1 -0
- package/dist/hub/assets/Opened-DJyf4Hnj.js +1 -0
- package/dist/hub/assets/Preview-C069zVMP.js +1 -0
- package/dist/hub/assets/RuleDetail-B4g2-TRe.js +3 -0
- package/dist/hub/assets/RulesScreen-CAaXIWjO.js +3 -0
- package/dist/hub/assets/ScreenImage-DcI7_G3O.js +1 -0
- package/dist/hub/assets/ScreensScreen-DwczCjZD.js +1 -0
- package/dist/hub/assets/SearchField-DaCpNAj6.js +1 -0
- package/dist/hub/assets/Section-BTnvUq_W.js +1 -0
- package/dist/hub/assets/Segmented-Dzi1x49S.js +1 -0
- package/dist/hub/assets/ServiceDown-Bv-BPXro.js +1 -0
- package/dist/hub/assets/SpecimenCapture-DlsjshVE.js +1 -0
- package/dist/hub/assets/Specimens-C-lHp7sr.js +2 -0
- package/dist/hub/assets/StackScreen-DkmELsqR.js +1 -0
- package/dist/hub/assets/StubScreens-Bt7lWLVK.js +1 -0
- package/dist/hub/assets/StyleScreen-tq1tbRBo.js +1 -0
- package/dist/hub/assets/Swatch-KDz4qxdM.js +1 -0
- package/dist/hub/assets/Tabs-1QQDS0I6.js +1 -0
- package/dist/hub/assets/TokenDetail-DvCw6GqC.js +1 -0
- package/dist/hub/assets/TokenValue-CsPRc2ai.js +1 -0
- package/dist/hub/assets/TokensScreen-DoENV8Xy.js +1 -0
- package/dist/hub/assets/arrow-right-DcbNDzqs.js +1 -0
- package/dist/hub/assets/bot-B3gC9oUR.js +1 -0
- package/dist/hub/assets/button-6mvMVtpb.js +1 -0
- package/dist/hub/assets/button.demo-DuU0DQbv.js +1 -0
- package/dist/hub/assets/card.demo-CufAxMAA.js +1 -0
- package/dist/hub/assets/chevron-down-CzN07vMi.js +1 -0
- package/dist/hub/assets/circle-alert-DT2LC4yf.js +1 -0
- package/dist/hub/assets/circle-check-DkwBMT62.js +1 -0
- package/dist/hub/assets/client-D7whEyep.js +1 -0
- package/dist/hub/assets/clsx-DB0hHKMi.js +1 -0
- package/dist/hub/assets/compass-BkKOZPYe.js +1 -0
- package/dist/hub/assets/contrast-BkNHUAPa.js +1 -0
- package/dist/hub/assets/createLucideIcon-CGDJWtKO.js +1 -0
- package/dist/hub/assets/discover-XjJEViwk.js +1 -0
- package/dist/hub/assets/dist-BYQYaPBV.js +1 -0
- package/dist/hub/assets/dist-Bk4srtDn.js +41 -0
- package/dist/hub/assets/dist-BogNzeK9.js +1 -0
- package/dist/hub/assets/dist-D0z7Bvsy.js +1 -0
- package/dist/hub/assets/download-BQI-Si9R.js +1 -0
- package/dist/hub/assets/engine-CMmt9yEz.js +1 -0
- package/dist/hub/assets/esm-NPpg23M8.js +1 -0
- package/dist/hub/assets/family-tiles-CyTdy_cP.js +1 -0
- package/dist/hub/assets/format-DxAYNTN_.js +1 -0
- package/dist/hub/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dist/hub/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dist/hub/assets/geist-latin-core-vkBVTr4s.woff2 +0 -0
- package/dist/hub/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dist/hub/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dist/hub/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
- package/dist/hub/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
- package/dist/hub/assets/geist-mono-latin-core-DccGGzmg.woff2 +0 -0
- package/dist/hub/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
- package/dist/hub/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
- package/dist/hub/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
- package/dist/hub/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
- package/dist/hub/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dist/hub/assets/git-pull-request-arrow-RqUUIRCU.js +1 -0
- package/dist/hub/assets/input.demo-238gqWpz.js +1 -0
- package/dist/hub/assets/inventory-BI8QBdar.js +1 -0
- package/dist/hub/assets/inventory-source-lZYzcA8O.js +1 -0
- package/dist/hub/assets/jsx-runtime-D6zNwnaE.js +9 -0
- package/dist/hub/assets/layers-DfzdK_wG.js +1 -0
- package/dist/hub/assets/layout-grid-xFY9L9Oc.js +1 -0
- package/dist/hub/assets/library-W5R8GwvG.js +1 -0
- package/dist/hub/assets/main-7snyIdoi.css +2 -0
- package/dist/hub/assets/main-B9CIkk5m.js +2 -0
- package/dist/hub/assets/minus-BDifFhOo.js +1 -0
- package/dist/hub/assets/monitor-X_lELkcU.js +1 -0
- package/dist/hub/assets/mouse-pointer-click-CVJ6hWg8.js +1 -0
- package/dist/hub/assets/paintbrush-DOkEgSTO.js +1 -0
- package/dist/hub/assets/plural-BbGQPQKA.js +1 -0
- package/dist/hub/assets/preview-BA8CPsKb.css +2 -0
- package/dist/hub/assets/preview-DUm13vr1.js +3 -0
- package/dist/hub/assets/protocol-caP6umNL.js +1 -0
- package/dist/hub/assets/react-DB-4Zxce.js +1 -0
- package/dist/hub/assets/shield-check-0ka0kQct.js +1 -0
- package/dist/hub/assets/trash-B7aEfJps.js +1 -0
- package/dist/hub/assets/triangle-alert-Dkdi9eDR.js +1 -0
- package/dist/hub/assets/try-live-zX3CSd3P.js +1 -0
- package/dist/hub/assets/utils-BoaXfs8y.js +1 -0
- package/dist/hub/assets/values-DzxR_TlI.js +1 -0
- package/dist/hub/assets/worker-Bh-wSp42.js +1 -0
- package/dist/hub/assets/workflow-jpV8wh-U.js +1 -0
- package/dist/hub/assets/x-MLbFVa4v.js +1 -0
- package/dist/hub/favicon.svg +1 -0
- package/dist/hub/index.html +35 -0
- package/dist/hub/kit/candidates.json +1 -0
- package/dist/hub/kit/chunks/chunk-2NYFUOIV.js +14 -0
- package/dist/hub/kit/chunks/chunk-DCANE7XH.js +1 -0
- package/dist/hub/kit/chunks/chunk-FTGHCY4I.js +14 -0
- package/dist/hub/kit/chunks/chunk-IBF3UTJF.js +33 -0
- package/dist/hub/kit/chunks/chunk-LLXY4VSH.js +1 -0
- package/dist/hub/kit/chunks/chunk-PUPZMW4P.js +14 -0
- package/dist/hub/kit/chunks/chunk-PWLNZQET.js +1 -0
- package/dist/hub/kit/chunks/chunk-ULXWVOKJ.js +1 -0
- package/dist/hub/kit/chunks/chunk-WM4BGGMK.js +1 -0
- package/dist/hub/kit/chunks/chunk-ZQUNRK72.js +1 -0
- package/dist/hub/kit/class-variance-authority.js +1 -0
- package/dist/hub/kit/clsx.js +1 -0
- package/dist/hub/kit/fonts/geist-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/hub/kit/fonts/geist-cyrillic-wght-normal.woff2 +0 -0
- package/dist/hub/kit/fonts/geist-latin-ext-wght-normal.woff2 +0 -0
- package/dist/hub/kit/fonts/geist-latin-wght-normal.woff2 +0 -0
- package/dist/hub/kit/fonts/geist-vietnamese-wght-normal.woff2 +0 -0
- package/dist/hub/kit/frame.css.src +73 -0
- package/dist/hub/kit/frame.js +2 -0
- package/dist/hub/kit/lucide-react.js +1878 -0
- package/dist/hub/kit/preview.html +13 -0
- package/dist/hub/kit/radix-ui-react-dialog.js +41 -0
- package/dist/hub/kit/radix-ui-react-label.js +1 -0
- package/dist/hub/kit/radix-ui-react-separator.js +1 -0
- package/dist/hub/kit/radix-ui-react-slot.js +1 -0
- package/dist/hub/kit/radix-ui-react-switch.js +1 -0
- package/dist/hub/kit/radix-ui-react-tabs.js +1 -0
- package/dist/hub/kit/react-dom-client.js +1 -0
- package/dist/hub/kit/react-dom.js +1 -0
- package/dist/hub/kit/react-jsx-runtime.js +1 -0
- package/dist/hub/kit/react.js +1 -0
- package/dist/hub/kit/tailwind-merge.js +1 -0
- package/dist/hub/kit/tailwindcss/index.css +950 -0
- package/dist/hub/kit/tailwindcss/preflight.css +398 -0
- package/dist/hub/kit/tailwindcss/theme.css +510 -0
- package/dist/hub/kit/tailwindcss/utilities.css +1 -0
- package/dist/hub-demo/frame/button.demo.js +1 -0
- package/dist/hub-demo/frame/card.demo.js +1 -0
- package/dist/hub-demo/frame/demos.js +6 -0
- package/dist/hub-demo/frame/frame.css +770 -0
- package/dist/hub-demo/frame/input.demo.js +1 -0
- package/dist/hub-demo/preview/components/button.demo.tsx +47 -0
- package/dist/hub-demo/preview/components/button.tsx +31 -0
- package/dist/hub-demo/preview/components/card.demo.tsx +26 -0
- package/dist/hub-demo/preview/components/card.tsx +20 -0
- package/dist/hub-demo/preview/components/input.demo.tsx +39 -0
- package/dist/hub-demo/preview/components/input.tsx +19 -0
- package/dist/hub-demo/preview/components/utils.ts +5 -0
- package/dist/hub-demo/preview/system.css +161 -0
- package/dist/hub-demo/public/data/demo-app.usage.bedd9c82fe.json +1 -0
- package/dist/hub-demo/public/data/demo.catalog.a605de2281.json +1 -0
- package/dist/hub-demo/public/data/gaps.129a93538d.json +1 -0
- package/dist/hub-demo/public/data/inventory.165a4f6408.json +1 -0
- package/dist/hub-demo/public/data/inventory.console.badge.ad0238784e.json +1 -0
- package/dist/hub-demo/public/data/inventory.console.button.3249daff35.json +1 -0
- package/dist/hub-demo/public/data/inventory.console.card.9ea88708ac.json +1 -0
- package/dist/hub-demo/public/data/inventory.console.dialog.8b6574d93a.json +1 -0
- package/dist/hub-demo/public/data/inventory.console.input.bdffb756a7.json +1 -0
- package/dist/hub-demo/public/data/inventory.console.link-as-button.1ce7faa1f3.json +1 -0
- package/dist/hub-demo/public/data/inventory.console.tabs.813e694c44.json +1 -0
- package/dist/hub-demo/public/data/inventory.extras.e5d06c6ff4.json +1 -0
- package/dist/hub-demo/public/data/inventory.site.badge.047a68f644.json +1 -0
- package/dist/hub-demo/public/data/inventory.site.button.720938384e.json +1 -0
- package/dist/hub-demo/public/data/inventory.site.card.d8df5f6873.json +1 -0
- package/dist/hub-demo/public/data/inventory.site.link-as-button.9c1f8a55bf.json +1 -0
- package/dist/hub-demo/public/data/inventory.site.tabs.59bd0200b9.json +1 -0
- package/dist/hub-demo/public/data/manifest.json +39 -0
- package/dist/hub-demo/public/data/specimens.console.dark.046471907d.css +786 -0
- package/dist/hub-demo/public/data/specimens.console.light.aaffa2d213.css +786 -0
- package/dist/hub-demo/public/data/specimens.site.dark.94a6706908.css +92 -0
- package/dist/hub-demo/public/data/specimens.site.light.3ddbc8d28d.css +92 -0
- package/dist/hub-demo/public/previews/button.dark.webp +0 -0
- package/dist/hub-demo/public/previews/button.light.webp +0 -0
- package/dist/hub-demo/public/previews/card.dark.webp +0 -0
- package/dist/hub-demo/public/previews/card.light.webp +0 -0
- package/dist/hub-demo/public/previews/input.dark.webp +0 -0
- package/dist/hub-demo/public/previews/input.light.webp +0 -0
- package/dist/hub-previews.js +173 -0
- package/dist/hub-server.js +6329 -0
- package/dist/hub-snapshot.js +573 -0
- package/dist/schema/youi.gaps.schema.json +235 -0
- package/dist/tailwind-worker.js +3 -3
- package/dist/youi.js +10 -8
- package/dist/youiyoux.js +10 -8
- package/package.json +4 -4
- package/dist/chunks/chunk-L5BV4PI7.js +0 -22159
|
@@ -1,35 +1,4 @@
|
|
|
1
|
-
import { createRequire as __youiRequire } from "node:module"; const require = __youiRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
EXPORT_TYPES,
|
|
4
|
-
LIBRARY_BY_ID,
|
|
5
|
-
buildReport,
|
|
6
|
-
detectRepo,
|
|
7
|
-
elementTypes,
|
|
8
|
-
libraryOfPackage,
|
|
9
|
-
packageOf,
|
|
10
|
-
playwrightCli
|
|
11
|
-
} from "./chunk-74KBRI2H.js";
|
|
12
|
-
import {
|
|
13
|
-
candidateSource,
|
|
14
|
-
checkpointSqlite,
|
|
15
|
-
cleanData,
|
|
16
|
-
discoverCacheDir,
|
|
17
|
-
discoverIndexPath,
|
|
18
|
-
expandAll,
|
|
19
|
-
indexHome,
|
|
20
|
-
ingestCatalog,
|
|
21
|
-
inventoryRun,
|
|
22
|
-
linkCandidates,
|
|
23
|
-
openSqlite,
|
|
24
|
-
projectGraph,
|
|
25
|
-
readCatalog,
|
|
26
|
-
searchCatalog,
|
|
27
|
-
selectSources,
|
|
28
|
-
sharedStore,
|
|
29
|
-
showFromCatalog,
|
|
30
|
-
storeLocation,
|
|
31
|
-
syncIndex
|
|
32
|
-
} from "./chunk-UEB6MJDX.js";
|
|
1
|
+
import { createRequire as __youiRequire } from "node:module"; import { dirname as __youiDir } from "node:path"; import { fileURLToPath as __youiPath } from "node:url"; const require = __youiRequire(import.meta.url); const __youiDirname = __youiDir(__youiPath(import.meta.url));
|
|
33
2
|
import {
|
|
34
3
|
BASELINE_FILE,
|
|
35
4
|
DEFAULT_EXCLUDE,
|
|
@@ -40,15 +9,13 @@ import {
|
|
|
40
9
|
adoptionRequest,
|
|
41
10
|
buildInventory,
|
|
42
11
|
buildTheme,
|
|
43
|
-
buildTokens,
|
|
44
12
|
captureScreens,
|
|
45
13
|
captureText,
|
|
46
14
|
checkCapabilities,
|
|
47
|
-
|
|
15
|
+
chosenSession,
|
|
48
16
|
compareToBaseline,
|
|
49
17
|
componentCard,
|
|
50
18
|
createBaseline,
|
|
51
|
-
cssValue,
|
|
52
19
|
describeIdentity,
|
|
53
20
|
evaluateRule,
|
|
54
21
|
failedChecks,
|
|
@@ -62,26 +29,68 @@ import {
|
|
|
62
29
|
loadProject,
|
|
63
30
|
nearDuplicates,
|
|
64
31
|
offLibraryImports,
|
|
65
|
-
|
|
32
|
+
openLoginBrowser,
|
|
66
33
|
parseModule,
|
|
67
34
|
planChange,
|
|
68
35
|
ratchetBaseline,
|
|
69
36
|
readStylesheet,
|
|
37
|
+
recordSession,
|
|
70
38
|
renderShots,
|
|
71
39
|
ruleCapability,
|
|
72
40
|
runMigration,
|
|
73
41
|
scanProject,
|
|
74
42
|
screenBom,
|
|
75
43
|
searchLibrary,
|
|
44
|
+
sessionFile,
|
|
76
45
|
shotsNext,
|
|
77
46
|
shotsText,
|
|
78
47
|
substituteVars,
|
|
79
48
|
vendoredPath,
|
|
80
49
|
whereUsed
|
|
81
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-VG3ZCSST.js";
|
|
82
51
|
import {
|
|
83
52
|
within
|
|
84
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-42YJX3W5.js";
|
|
54
|
+
import {
|
|
55
|
+
EXPORT_TYPES,
|
|
56
|
+
LIBRARY_BY_ID,
|
|
57
|
+
buildReport,
|
|
58
|
+
detectRepo,
|
|
59
|
+
elementTypes,
|
|
60
|
+
libraryOfPackage,
|
|
61
|
+
packageOf,
|
|
62
|
+
playwrightCli
|
|
63
|
+
} from "./chunk-4BBYLTZX.js";
|
|
64
|
+
import {
|
|
65
|
+
MissingIndexError,
|
|
66
|
+
buildGaps,
|
|
67
|
+
candidateSource,
|
|
68
|
+
checkpointSqlite,
|
|
69
|
+
cleanData,
|
|
70
|
+
declaredPackages,
|
|
71
|
+
discoverCacheDir,
|
|
72
|
+
discoverIndexPath,
|
|
73
|
+
expandAll,
|
|
74
|
+
indexHome,
|
|
75
|
+
ingestCatalog,
|
|
76
|
+
inventoryRun,
|
|
77
|
+
linkCandidates,
|
|
78
|
+
openSqlite,
|
|
79
|
+
projectGraph,
|
|
80
|
+
readCatalog,
|
|
81
|
+
searchCatalog,
|
|
82
|
+
selectSources,
|
|
83
|
+
sharedStore,
|
|
84
|
+
showFromCatalog,
|
|
85
|
+
storeLocation,
|
|
86
|
+
syncIndex
|
|
87
|
+
} from "./chunk-M5XZWCPS.js";
|
|
88
|
+
import {
|
|
89
|
+
buildTokens,
|
|
90
|
+
compareParity,
|
|
91
|
+
cssValue,
|
|
92
|
+
parseCssColor
|
|
93
|
+
} from "./chunk-YJ7WMBTT.js";
|
|
85
94
|
import {
|
|
86
95
|
Baseline,
|
|
87
96
|
DefinitionError,
|
|
@@ -106,48 +115,27 @@ import {
|
|
|
106
115
|
relPath,
|
|
107
116
|
renderJsonSchemas,
|
|
108
117
|
require_dist,
|
|
118
|
+
ruleSlug,
|
|
109
119
|
safeId,
|
|
110
120
|
sha256,
|
|
111
121
|
shortHash,
|
|
122
|
+
toPosix,
|
|
112
123
|
writeFileAtomic
|
|
113
|
-
} from "./chunk-
|
|
124
|
+
} from "./chunk-NYDJYMID.js";
|
|
114
125
|
import {
|
|
115
126
|
__toESM
|
|
116
|
-
} from "./chunk-
|
|
117
|
-
|
|
118
|
-
// src/node20.js
|
|
119
|
-
if (!Map.groupBy) {
|
|
120
|
-
Map.groupBy = (items, key2) => {
|
|
121
|
-
const groups = /* @__PURE__ */ new Map();
|
|
122
|
-
let i = 0;
|
|
123
|
-
for (const item of items) {
|
|
124
|
-
const k = key2(item, i++);
|
|
125
|
-
const list = groups.get(k);
|
|
126
|
-
if (list) list.push(item);
|
|
127
|
-
else groups.set(k, [item]);
|
|
128
|
-
}
|
|
129
|
-
return groups;
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
if (!Object.groupBy) Object.groupBy = (items, key2) => Object.fromEntries(Map.groupBy(items, key2));
|
|
133
|
-
if (!Promise.withResolvers) {
|
|
134
|
-
Promise.withResolvers = () => {
|
|
135
|
-
let resolve13, reject;
|
|
136
|
-
const promise = new Promise((yes, no) => (resolve13 = yes, reject = no));
|
|
137
|
-
return { promise, resolve: resolve13, reject };
|
|
138
|
-
};
|
|
139
|
-
}
|
|
127
|
+
} from "./chunk-WNTS7ZO4.js";
|
|
140
128
|
|
|
141
129
|
// ../cli/src/main.ts
|
|
142
130
|
var import_yaml3 = __toESM(require_dist(), 1);
|
|
143
|
-
import { execFile as
|
|
144
|
-
import { existsSync as
|
|
131
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
132
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
145
133
|
import { appendFile as appendFile2, readFile as readFile8 } from "node:fs/promises";
|
|
146
|
-
import { basename as basename3, join as join22, relative as
|
|
147
|
-
import { parseArgs, promisify as
|
|
134
|
+
import { basename as basename3, dirname as dirname11, join as join22, relative as relative8, resolve as resolve12 } from "node:path";
|
|
135
|
+
import { parseArgs, promisify as promisify5 } from "node:util";
|
|
148
136
|
|
|
149
137
|
// ../cli/src/check.ts
|
|
150
|
-
import { dirname as dirname3, join as join7,
|
|
138
|
+
import { dirname as dirname3, join as join7, resolve as resolve2 } from "node:path";
|
|
151
139
|
|
|
152
140
|
// ../cli/src/check-diff.ts
|
|
153
141
|
import { execFile } from "node:child_process";
|
|
@@ -161,7 +149,7 @@ import { join as join2, relative, resolve } from "node:path";
|
|
|
161
149
|
|
|
162
150
|
// ../cli/src/command.ts
|
|
163
151
|
var COMMAND = "youiyoux";
|
|
164
|
-
var SUBCOMMANDS = ["show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "init", "sync", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "discover", "index", "report", "render", "screens", "requests", "detect", "build", "parity", "schema", "inventory", "help"];
|
|
152
|
+
var SUBCOMMANDS = ["show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "init", "sync", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "discover", "index", "report", "render", "screens", "requests", "detect", "build", "parity", "schema", "inventory", "gaps", "help"];
|
|
165
153
|
|
|
166
154
|
// ../cli/src/agents.ts
|
|
167
155
|
var import_yaml = __toESM(require_dist(), 1);
|
|
@@ -191,10 +179,9 @@ function viaNpx(text, root) {
|
|
|
191
179
|
return text.replace(BARE, `${portableYoui(root).run} `);
|
|
192
180
|
}
|
|
193
181
|
function diffInstruction(root) {
|
|
194
|
-
const { run:
|
|
195
|
-
return `- ${setup} After UI edits, run \`${
|
|
182
|
+
const { run: run7, setup } = portableYoui(root);
|
|
183
|
+
return `- ${setup} After UI edits, run \`${run7} check --diff\` (\`--json\` for data): it lists what your change adds that near-duplicates an existing component, hardcodes a value a token holds, or imports an element from a library other than the project's choice, each with its fix. Apply each fix.`;
|
|
196
184
|
}
|
|
197
|
-
var DIFF_INSTRUCTION = diffInstruction();
|
|
198
185
|
function repoAgentsBlock(root) {
|
|
199
186
|
return viaNpx([BLOCK_BEGIN, REPO_BLOCK_HEADING, "", diffInstruction(root), "- Run `youiyoux init --design <dir>` to connect design-system rules; repository inference works without it.", BLOCK_END].join("\n"), root);
|
|
200
187
|
}
|
|
@@ -234,7 +221,7 @@ function agentsBlock(project2) {
|
|
|
234
221
|
`- Never edit files marked \`${GENERATED_MARK}\` (\`${config.outputs.dir}/*\`, ${config.agents.designMd}, the youiyoux skill). Change the design language and run \`youiyoux sync\`.`,
|
|
235
222
|
`- Owned component files in \`${config.componentsDir}\` may be edited; \`youiyoux sync\` 3-way merges upstream changes into them and reports drift.`,
|
|
236
223
|
"- After changing a component, call `ui_render` with its file (or run `youiyoux render <file>`) and look at the images: light and dark, desktop and phone, its accessibility issues and the diff against the base. Fix what it reports.",
|
|
237
|
-
"- People pin comments and change requests to components on captured screens. Before UI work, call `requests_list` (or run `youiyoux requests`); after making a requested change, capture its screen again (`youiyoux screens capture --base <app> --route <route>`) and call `request_addressed` with what you changed (file:line). Ask with `request_reply` when one is unclear. A person closes requests, never an agent.",
|
|
224
|
+
"- People pin comments and change requests to components on captured screens. Before UI work, call `requests_list` (or run `youiyoux requests`); after making a requested change, capture its screen again (`screens_capture` with its route, or `youiyoux screens capture --base <app> --route <route>`) and call `request_addressed` with what you changed (file:line). Ask with `request_reply` when one is unclear. A person closes requests, never an agent.",
|
|
238
225
|
"- Before you finish, run `youiyoux check`; it must report 0 errors.",
|
|
239
226
|
diffInstruction(project2.root),
|
|
240
227
|
"- To change the System itself (a token, a prop, a rule), run `youiyoux plan` (or `system_plan`) and propose it. Agents never approve, publish or merge design changes.",
|
|
@@ -302,7 +289,7 @@ function frontmatter(project2, build) {
|
|
|
302
289
|
colors,
|
|
303
290
|
typography,
|
|
304
291
|
rounded: dims("radius"),
|
|
305
|
-
spacing:
|
|
292
|
+
spacing: dims("space"),
|
|
306
293
|
components
|
|
307
294
|
};
|
|
308
295
|
}
|
|
@@ -346,7 +333,7 @@ function skillMd(project2, uncontracted = []) {
|
|
|
346
333
|
const from = importHint(config);
|
|
347
334
|
const description = `Build and fix UI with the ${system.config.name} design language (YouIyouX): System components, tokens, rules, where-used lookups, change proposals and Library adoption requests. Use for any UI, styling, colour or component work in this project.`;
|
|
348
335
|
const components = system.components.map(({ contract }) => `- **${contract.name}**: ${contract.summary}${enumSummary(contract) ? ` (${enumSummary(contract)})` : ""}`);
|
|
349
|
-
const fixes = enforced(system.rules).map((r) => `| \`${r.id
|
|
336
|
+
const fixes = enforced(system.rules).map((r) => `| \`${ruleSlug(r.id)}\` | ${r.severity} | ${r.agent} |`);
|
|
350
337
|
return viaNpx([
|
|
351
338
|
"---",
|
|
352
339
|
"name: youiyoux",
|
|
@@ -412,22 +399,15 @@ function astGrepRules(project2) {
|
|
|
412
399
|
# Portable subset: youiyoux check also resolves imports; these run with plain \`ast-grep scan -r <file>\`.
|
|
413
400
|
`;
|
|
414
401
|
for (const rule2 of enforced(project2.system.rules)) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
402
|
+
const entry = (language, rest) => ({ file: `${ruleSlug(rule2.id)}.yml`, content: header + (0, import_yaml.stringify)({ id: rule2.id, language, severity: rule2.severity === "error" ? "error" : "warning", message: rule2.title, note: rule2.agent, ...rest }) });
|
|
403
|
+
if (rule2.check.engine === "ast-grep") out.push(entry(rule2.check.language, { rule: rule2.check.rule }));
|
|
418
404
|
if (rule2.check.engine === "youi" && rule2.check.id === "native-element") {
|
|
419
405
|
const tags = Object.keys(rule2.check.options.elements ?? {});
|
|
420
406
|
const regex = `^(${tags.join("|")})$`;
|
|
421
|
-
out.push({
|
|
422
|
-
file: `${rule2.id.split("/rule/")[1]}.yml`,
|
|
423
|
-
content: header + (0, import_yaml.stringify)({ id: rule2.id, language: "tsx", severity: rule2.severity === "error" ? "error" : "warning", message: rule2.title, note: rule2.agent, ignores: [`${project2.config.componentsDir}/**`], rule: { any: [{ kind: "jsx_opening_element", has: { field: "name", regex } }, { kind: "jsx_self_closing_element", has: { field: "name", regex } }] } })
|
|
424
|
-
});
|
|
407
|
+
out.push(entry("tsx", { ignores: [`${project2.config.componentsDir}/**`], rule: { any: [{ kind: "jsx_opening_element", has: { field: "name", regex } }, { kind: "jsx_self_closing_element", has: { field: "name", regex } }] } }));
|
|
425
408
|
}
|
|
426
409
|
if (rule2.check.engine === "youi" && rule2.check.id === "raw-color") {
|
|
427
|
-
out.push({
|
|
428
|
-
file: `${rule2.id.split("/rule/")[1]}.yml`,
|
|
429
|
-
content: header + (0, import_yaml.stringify)({ id: rule2.id, language: "tsx", severity: rule2.severity === "error" ? "error" : "warning", message: rule2.title, note: rule2.agent, rule: { kind: "string_fragment", regex: "(\\[#[0-9a-fA-F]{3,8}\\]|^#[0-9a-fA-F]{3,8}$|rgba?\\()", inside: { kind: "jsx_attribute", stopBy: "end" } } })
|
|
430
|
-
});
|
|
410
|
+
out.push(entry("tsx", { rule: { kind: "string_fragment", regex: "(\\[#[0-9a-fA-F]{3,8}\\]|^#[0-9a-fA-F]{3,8}$|rgba?\\()", inside: { kind: "jsx_attribute", stopBy: "end" } } }));
|
|
431
411
|
}
|
|
432
412
|
}
|
|
433
413
|
return out;
|
|
@@ -478,8 +458,7 @@ async function initProject(options) {
|
|
|
478
458
|
}
|
|
479
459
|
}
|
|
480
460
|
notes.push(`owned components: ${componentsDir}`);
|
|
481
|
-
const
|
|
482
|
-
const alias = await aliasRoot(root, tsconfig);
|
|
461
|
+
const alias = await aliasRoot(root);
|
|
483
462
|
const importFrom = alias !== void 0 && componentsDir.startsWith(alias) ? `@/${componentsDir.slice(alias.length)}` : void 0;
|
|
484
463
|
if (importFrom) notes.push(`imports use the @/ alias: ${importFrom}`);
|
|
485
464
|
const include = (await Promise.all(
|
|
@@ -627,7 +606,6 @@ async function stagedChange(project2) {
|
|
|
627
606
|
for (const file of added.keys()) sources[file] = await git(root, ["show", `:${prefix}${file}`]);
|
|
628
607
|
return { base: "HEAD (staged changes)", commit: head, added, sources };
|
|
629
608
|
}
|
|
630
|
-
var slugOf = (rule2) => rule2.slice(rule2.indexOf("/rule/") + 6);
|
|
631
609
|
var DRIFT_SEVERITY = { "near-duplicate": "warn", "hardcoded-value": "error", "off-library": "error" };
|
|
632
610
|
async function importOf(project2) {
|
|
633
611
|
const alias = await aliasRoot(project2.root, project2.config.tsconfig);
|
|
@@ -658,9 +636,9 @@ async function chosenLibraries(project2, added, notes) {
|
|
|
658
636
|
notes.push(`imports were compared only with the System's components (${error instanceof Error ? error.message : String(error)})`);
|
|
659
637
|
return chosen;
|
|
660
638
|
}
|
|
661
|
-
const outside = hits.filter((hit) => (hit.where ?? []).some((
|
|
662
|
-
const at =
|
|
663
|
-
return !isAdded(added,
|
|
639
|
+
const outside = hits.filter((hit) => (hit.where ?? []).some((where2) => {
|
|
640
|
+
const at = where2.lastIndexOf(":");
|
|
641
|
+
return !isAdded(added, where2.slice(0, at), Number(where2.slice(at + 1)));
|
|
664
642
|
}));
|
|
665
643
|
const byType = /* @__PURE__ */ new Map();
|
|
666
644
|
for (const hit of outside) {
|
|
@@ -730,11 +708,11 @@ async function analyseChange(project2, scan, change, options) {
|
|
|
730
708
|
const merged = findings2.findIndex((f, i) => f.kind !== "rule" && samePlace(f, violation) && sameText(drift[i]?.text ?? "", violation.text));
|
|
731
709
|
if (merged >= 0) {
|
|
732
710
|
const into = findings2[merged];
|
|
733
|
-
findings2[merged] = { ...into, rule:
|
|
711
|
+
findings2[merged] = { ...into, rule: ruleSlug(violation.rule), severity: violation.severity === "error" ? "error" : into.severity };
|
|
734
712
|
continue;
|
|
735
713
|
}
|
|
736
|
-
const fix = violation.suggestion ?? rules.get(violation.rule)?.agent ?? `see \`youiyoux rules\` for ${
|
|
737
|
-
findings2.push({ kind: "rule", file: violation.file, line: violation.line, column: violation.column, message: violation.message, fix, severity: violation.severity, rule:
|
|
714
|
+
const fix = violation.suggestion ?? rules.get(violation.rule)?.agent ?? `see \`youiyoux rules\` for ${ruleSlug(violation.rule)}`;
|
|
715
|
+
findings2.push({ kind: "rule", file: violation.file, line: violation.line, column: violation.column, message: violation.message, fix, severity: violation.severity, rule: ruleSlug(violation.rule) });
|
|
738
716
|
}
|
|
739
717
|
findings2.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.column - b.column);
|
|
740
718
|
return { base: change.base, commit: change.commit, files, findings: findings2, notes, incomplete };
|
|
@@ -751,7 +729,6 @@ import { promisify as promisify2 } from "node:util";
|
|
|
751
729
|
import { dirname, join as join4 } from "node:path";
|
|
752
730
|
import { createRequire } from "node:module";
|
|
753
731
|
var TOOL_ROOT = join4(dirname(createRequire(import.meta.url).resolve("youiyoux/package.json")), "dist");
|
|
754
|
-
var CLI_BIN = join4(TOOL_ROOT, "packages/cli/bin/youi.js");
|
|
755
732
|
|
|
756
733
|
// ../cli/src/tool-command.ts
|
|
757
734
|
import { join as join5, relative as relative2, isAbsolute as isAbsolute2 } from "node:path";
|
|
@@ -1039,7 +1016,7 @@ async function verifyWiring(project2) {
|
|
|
1039
1016
|
}
|
|
1040
1017
|
|
|
1041
1018
|
// ../cli/src/check.ts
|
|
1042
|
-
var toProjectPath = (project2, file) =>
|
|
1019
|
+
var toProjectPath = (project2, file) => relPath(project2.root, resolve2(file));
|
|
1043
1020
|
function baselinePath(project2, configPath) {
|
|
1044
1021
|
return join7(dirname3(resolve2(configPath ?? project2.configFile ?? join7(project2.root, PROJECT_FILE))), BASELINE_FILE);
|
|
1045
1022
|
}
|
|
@@ -1059,7 +1036,7 @@ async function runCheck(project2, options = {}) {
|
|
|
1059
1036
|
const wantedRule = (id) => !options.rule || id.endsWith(`/rule/${options.rule}`);
|
|
1060
1037
|
const violations = report.violations.filter((v) => wantedRule(v.rule) && (!options.quiet || v.severity === "error"));
|
|
1061
1038
|
const notRun = failedChecks(report.checks).filter((c) => wantedRule(c.rule));
|
|
1062
|
-
const lock = options.lock === false || diff
|
|
1039
|
+
const lock = options.lock === false || diff ? null : await lockIssues(project2);
|
|
1063
1040
|
const recorded = options.baseline ? await readBaseline(options.baseline) : null;
|
|
1064
1041
|
if (options.baseline && !recorded) throw new Error(`no baseline at ${options.baseline}; record one with \`youiyoux baseline\``);
|
|
1065
1042
|
const baseline = recorded && options.baseline ? { file: options.baseline, recorded: recorded.identity, comparison: compareToBaseline(recorded, violations, scope ?? void 0) } : null;
|
|
@@ -1077,7 +1054,7 @@ var diffExitCodeOf = (result, strict) => strict && ((result.diff?.findings.lengt
|
|
|
1077
1054
|
async function writeBaseline(project2, file, options = {}) {
|
|
1078
1055
|
const scan = await scanProject(project2, options.cache ? { cache: options.cache } : {});
|
|
1079
1056
|
if (scan.report.status !== "complete" && !options.force) {
|
|
1080
|
-
const gaps = [...scan.report.coverage.failed.map((f) => f.file), ...failedChecks(scan.report.checks).map((c) => `rule ${c.rule
|
|
1057
|
+
const gaps = [...scan.report.coverage.failed.map((f) => f.file), ...failedChecks(scan.report.checks).map((c) => `rule ${ruleSlug(c.rule)}`)];
|
|
1081
1058
|
throw new Error(`the scan is partial (${gaps.join(", ")}): findings there would later look new. Fix what could not be analysed or run, or record anyway with --force`);
|
|
1082
1059
|
}
|
|
1083
1060
|
const existing = await readBaseline(file);
|
|
@@ -1091,23 +1068,208 @@ async function writeBaseline(project2, file, options = {}) {
|
|
|
1091
1068
|
}
|
|
1092
1069
|
|
|
1093
1070
|
// ../cli/src/check-repo.ts
|
|
1071
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
1072
|
+
import { dirname as dirname5, join as join9, resolve as resolve4 } from "node:path";
|
|
1073
|
+
|
|
1074
|
+
// ../cli/src/hooks-install.ts
|
|
1094
1075
|
import { execFile as execFile3 } from "node:child_process";
|
|
1095
|
-
import {
|
|
1096
|
-
import { dirname as dirname4, join as join8, resolve as resolve3 } from "node:path";
|
|
1076
|
+
import { chmod, mkdir as mkdir2, realpath as realpath2 } from "node:fs/promises";
|
|
1077
|
+
import { dirname as dirname4, join as join8, relative as relative4, resolve as resolve3 } from "node:path";
|
|
1097
1078
|
import { promisify as promisify3 } from "node:util";
|
|
1098
1079
|
var run3 = promisify3(execFile3);
|
|
1099
|
-
var
|
|
1080
|
+
var CLAUDE_MARKER = "hooks run claude";
|
|
1081
|
+
var BLOCK_START = "# >>> youi pre-commit >>>";
|
|
1082
|
+
var BLOCK_END2 = "# <<< youi pre-commit <<<";
|
|
1083
|
+
var WORKFLOW = ".github/workflows/youi-check.yml";
|
|
1084
|
+
var WORKFLOW_HEADER = "# Generated by `youiyoux hooks install --ci`.";
|
|
1085
|
+
var LEGACY_WORKFLOW_HEADER = WORKFLOW_HEADER.replace("youiyoux", "youi");
|
|
1086
|
+
var managedWorkflow = (text) => text.startsWith(WORKFLOW_HEADER) || text.startsWith(LEGACY_WORKFLOW_HEADER);
|
|
1087
|
+
var DIFF_STEP = "name: youiyoux check --diff";
|
|
1088
|
+
var CLAUDE_MATCHER = "Edit|Write|MultiEdit";
|
|
1089
|
+
var CLAUDE_TIMEOUT_S = 60;
|
|
1090
|
+
var shellQuote = (arg) => /^[\w@%+=:,./-]+$/.test(arg) ? arg : `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
1091
|
+
async function gitOut(cwd, args) {
|
|
1092
|
+
return (await run3("git", [...args], { cwd })).stdout.trim();
|
|
1093
|
+
}
|
|
1094
|
+
async function repoRootOf(dir) {
|
|
1095
|
+
return gitOut(dir, ["rev-parse", "--show-toplevel"]).catch(() => dir);
|
|
1096
|
+
}
|
|
1097
|
+
function hookCommand(toolRoot, kind, target, extra = []) {
|
|
1098
|
+
const server = toolCommand(toolRoot);
|
|
1099
|
+
const args = [server.command, ...server.args, "hooks", "run", kind];
|
|
1100
|
+
for (const key2 of ["project", "config", "design", "root"]) {
|
|
1101
|
+
const value = target[key2];
|
|
1102
|
+
if (value) args.push(`--${key2}`, resolve3(value));
|
|
1103
|
+
}
|
|
1104
|
+
return [...args, ...extra].map(shellQuote).join(" ");
|
|
1105
|
+
}
|
|
1106
|
+
async function write(file, content, before, guarantee, mode) {
|
|
1107
|
+
if (before === content) return { file, action: "unchanged", guarantee };
|
|
1108
|
+
await mkdir2(dirname4(file), { recursive: true });
|
|
1109
|
+
await writeFileAtomic(file, content);
|
|
1110
|
+
if (mode !== void 0) await chmod(file, mode);
|
|
1111
|
+
return { file, action: before === null ? "created" : "updated", guarantee };
|
|
1112
|
+
}
|
|
1113
|
+
var isYouiEntry = (entry) => (entry.hooks ?? []).some((h) => typeof h.command === "string" && h.command.includes(CLAUDE_MARKER));
|
|
1114
|
+
async function installClaudeHook(repoRoot, command2) {
|
|
1115
|
+
const file = join8(repoRoot, ".claude/settings.local.json");
|
|
1116
|
+
const before = await readTextOrNull(file);
|
|
1117
|
+
let settings;
|
|
1118
|
+
try {
|
|
1119
|
+
settings = before === null ? {} : JSON.parse(before);
|
|
1120
|
+
} catch {
|
|
1121
|
+
throw new Error(`${file} is not valid JSON; fix it first (youi will not overwrite it)`);
|
|
1122
|
+
}
|
|
1123
|
+
const hooks = settings.hooks ?? {};
|
|
1124
|
+
const existing = hooks.PostToolUse ?? [];
|
|
1125
|
+
const entry = { matcher: CLAUDE_MATCHER, hooks: [{ type: "command", command: command2, timeout: CLAUDE_TIMEOUT_S }] };
|
|
1126
|
+
const postToolUse = existing.some(isYouiEntry) ? existing.map((e) => isYouiEntry(e) ? entry : e) : [...existing, entry];
|
|
1127
|
+
const next2 = { ...settings, hooks: { ...hooks, PostToolUse: postToolUse } };
|
|
1128
|
+
return write(file, `${JSON.stringify(next2, null, 2)}
|
|
1129
|
+
`, before, "advisory: after each Claude Code Edit/Write/MultiEdit, that file's new violations go back to Claude; never blocks; edits made any other way are not seen");
|
|
1130
|
+
}
|
|
1131
|
+
function withBlock(script, block) {
|
|
1132
|
+
if (script === null) return `#!/bin/sh
|
|
1133
|
+
${block}
|
|
1134
|
+
`;
|
|
1135
|
+
const start = script.indexOf(BLOCK_START);
|
|
1136
|
+
const end = script.indexOf(BLOCK_END2);
|
|
1137
|
+
if (start >= 0 && end > start) return `${script.slice(0, start)}${block}${script.slice(end + BLOCK_END2.length)}`;
|
|
1138
|
+
return `${script.endsWith("\n") ? script : `${script}
|
|
1139
|
+
`}
|
|
1140
|
+
${block}
|
|
1141
|
+
`;
|
|
1142
|
+
}
|
|
1143
|
+
async function installPreCommit(projectRoot, command2, strict) {
|
|
1144
|
+
const hooksDir = resolve3(projectRoot, await gitOut(projectRoot, ["rev-parse", "--git-path", "hooks"]));
|
|
1145
|
+
const file = join8(hooksDir, "pre-commit");
|
|
1146
|
+
const before = await readTextOrNull(file);
|
|
1147
|
+
const shebang = before?.split("\n", 1)[0] ?? "";
|
|
1148
|
+
if (before !== null && shebang.startsWith("#!") && !/\b(?:sh|bash|zsh|dash)\b/.test(shebang)) {
|
|
1149
|
+
throw new Error(`${file} is not a shell script (${shebang}); add this line to it yourself: ${command2}`);
|
|
1150
|
+
}
|
|
1151
|
+
if (before !== null && /^\s*exec\s/m.test(before) && !before.includes(BLOCK_START)) {
|
|
1152
|
+
throw new Error(`${file} hands off with exec, so an appended check would never run; add this line before the exec yourself: ${command2}`);
|
|
1153
|
+
}
|
|
1154
|
+
const what = strict ? "Blocks commits with new design-system errors" : "Advisory, never blocks";
|
|
1155
|
+
const block = `${BLOCK_START}
|
|
1156
|
+
# ${what}: checks the staged content of staged files only; CI is authoritative.
|
|
1157
|
+
${command2} || exit $?
|
|
1158
|
+
${BLOCK_END2}`;
|
|
1159
|
+
const guarantee = strict ? "blocks commits with new errors in staged files (any agent or person that commits; --no-verify skips it)" : "advisory at commit time, staged files only (any agent or person that commits; not every edit)";
|
|
1160
|
+
return write(file, withBlock(before, block), before, guarantee, 493);
|
|
1161
|
+
}
|
|
1162
|
+
async function installRepoAgentInstructions(root) {
|
|
1163
|
+
const file = join8(root, "AGENTS.md");
|
|
1164
|
+
const before = await readTextOrNull(file);
|
|
1165
|
+
const guarantee = "agents run youiyoux check --diff after UI edits; pre-commit also checks staged edits";
|
|
1166
|
+
const ruled = before !== null && before.indexOf(RULE_END) > before.indexOf(RULE_BEGIN) && before.includes(RULE_BEGIN);
|
|
1167
|
+
return write(file, ruled ? before : upsertBlock(before, repoAgentsBlock(root)), before, guarantee);
|
|
1168
|
+
}
|
|
1169
|
+
function codexFallback(projectRoot, agentsFile) {
|
|
1170
|
+
return {
|
|
1171
|
+
file: join8(projectRoot, agentsFile),
|
|
1172
|
+
action: "skipped",
|
|
1173
|
+
guarantee: "no post-edit hook installed (Codex documents none): Codex follows AGENTS.md, where `youiyoux sync` tells it to run `youiyoux check --diff` after UI edits, and the git pre-commit hook checks what it commits"
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
function githubSlug(url) {
|
|
1177
|
+
const match = /github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/.exec(url.trim());
|
|
1178
|
+
return match ? `${match[1]}/${match[2]}` : null;
|
|
1179
|
+
}
|
|
1180
|
+
async function repoAt(root) {
|
|
1181
|
+
const slug = githubSlug(await gitOut(root, ["remote", "get-url", "origin"]).catch(() => ""));
|
|
1182
|
+
if (!slug) throw new Error(`${root} has no GitHub origin remote; the workflow needs one to check it out`);
|
|
1183
|
+
return { slug, commit: await gitOut(root, ["rev-parse", "HEAD"]) };
|
|
1184
|
+
}
|
|
1185
|
+
var posix2 = (path) => toPosix(path) || ".";
|
|
1186
|
+
var inside = (root, path) => !relative4(root, path).startsWith("..");
|
|
1187
|
+
function workflowText(input) {
|
|
1188
|
+
const dir = input.projectDir || ".";
|
|
1189
|
+
const target = ["--project", dir, "--design", ".youi-ci/design", ...input.configPath ? ["--config", input.configPath, "--root", dir] : []];
|
|
1190
|
+
const checkArgs = ["--baseline", "--format", "github", ...target].map(shellQuote).join(" ");
|
|
1191
|
+
const diffArgs2 = ["--strict", "--format", "github", ...target].map(shellQuote).join(" ");
|
|
1192
|
+
const diffStep = input.diff ? ` - ${DIFF_STEP} (drift this pull request adds)
|
|
1193
|
+
if: \${{ !cancelled() && github.event_name == 'pull_request' }}
|
|
1194
|
+
env:
|
|
1195
|
+
YOUI_BASE: \${{ github.base_ref }}
|
|
1196
|
+
run: node .youi-ci/tool/packages/cli/bin/youi.js check --diff "origin/$YOUI_BASE" ${diffArgs2}
|
|
1197
|
+
` : "";
|
|
1198
|
+
return `${WORKFLOW_HEADER}
|
|
1199
|
+
# The authoritative design-system check: it fails on violations the baseline does not
|
|
1200
|
+
# accept, annotates them on the PR's lines, and states the rules version (design commit
|
|
1201
|
+
# and definitions digest) in the job summary. Raise the rules version by changing the
|
|
1202
|
+
# design ref below; YOUI_REPO_TOKEN must be able to read both repositories.
|
|
1203
|
+
name: youiyoux check
|
|
1204
|
+
on:
|
|
1205
|
+
pull_request:
|
|
1206
|
+
push:
|
|
1207
|
+
branches: [main]
|
|
1208
|
+
permissions:
|
|
1209
|
+
contents: read
|
|
1210
|
+
jobs:
|
|
1211
|
+
youi-check:
|
|
1212
|
+
runs-on: ubuntu-latest
|
|
1213
|
+
timeout-minutes: 10
|
|
1214
|
+
steps:
|
|
1215
|
+
- uses: actions/checkout@v4${input.diff ? "\n with:\n fetch-depth: 0" : ""}
|
|
1216
|
+
- name: Check out youi
|
|
1217
|
+
uses: actions/checkout@v4
|
|
1218
|
+
with:
|
|
1219
|
+
repository: ${input.tool.slug}
|
|
1220
|
+
ref: ${input.tool.commit}
|
|
1221
|
+
path: .youi-ci/tool
|
|
1222
|
+
token: \${{ secrets.YOUI_REPO_TOKEN }}
|
|
1223
|
+
- name: Check out the design rules (the rules version)
|
|
1224
|
+
uses: actions/checkout@v4
|
|
1225
|
+
with:
|
|
1226
|
+
repository: ${input.design.slug}
|
|
1227
|
+
ref: ${input.design.commit}
|
|
1228
|
+
path: .youi-ci/design
|
|
1229
|
+
token: \${{ secrets.YOUI_REPO_TOKEN }}
|
|
1230
|
+
- uses: pnpm/action-setup@v4
|
|
1231
|
+
with:
|
|
1232
|
+
version: 10.13.1
|
|
1233
|
+
- uses: actions/setup-node@v4
|
|
1234
|
+
with:
|
|
1235
|
+
node-version: 24
|
|
1236
|
+
- run: pnpm install --frozen-lockfile --ignore-scripts
|
|
1237
|
+
working-directory: .youi-ci/tool
|
|
1238
|
+
- name: youiyoux check
|
|
1239
|
+
run: node .youi-ci/tool/packages/cli/bin/youi.js check ${checkArgs}
|
|
1240
|
+
${diffStep}`;
|
|
1241
|
+
}
|
|
1242
|
+
async function installWorkflow(project2, toolRoot, target, force, options = {}) {
|
|
1243
|
+
const top = await gitOut(project2.root, ["rev-parse", "--show-toplevel"]);
|
|
1244
|
+
const published = ephemeralTool(toolRoot);
|
|
1245
|
+
const [tool, design] = await Promise.all([published ? Promise.resolve({ slug: "npm/youiyoux", commit: "published" }) : repoAt(toolRoot), repoAt(project2.repo.root)]);
|
|
1246
|
+
const configFile = resolve3(target.config ?? project2.configFile ?? join8(project2.root, PROJECT_FILE));
|
|
1247
|
+
const configPath = !target.config ? null : inside(project2.repo.root, configFile) ? `.youi-ci/design/${posix2(relative4(project2.repo.root, configFile))}` : inside(top, configFile) ? posix2(relative4(top, configFile)) : null;
|
|
1248
|
+
if (target.config && !configPath) throw new Error(`${configFile} is in neither this repo nor the design repo, so CI cannot read it`);
|
|
1249
|
+
const file = join8(top, WORKFLOW);
|
|
1250
|
+
const before = await readTextOrNull(file);
|
|
1251
|
+
if (before !== null && !managedWorkflow(before) && !force) throw new Error(`${file} exists and was not written by youi; pass --force to replace it`);
|
|
1252
|
+
const diff = options.diff === true || before !== null && managedWorkflow(before) && (before.includes(DIFF_STEP) || before.includes(DIFF_STEP.replace("youiyoux", "youi")));
|
|
1253
|
+
const projectDir = posix2(relative4(top, await realpath2(project2.root).catch(() => project2.root)));
|
|
1254
|
+
let text = workflowText({ tool, design, projectDir, configPath, diff });
|
|
1255
|
+
if (published) {
|
|
1256
|
+
text = text.replace(/ - name: Check out youi\n[\s\S]*?(?= - name: Check out the design)/, "").replace(/ - run: pnpm install --frozen-lockfile --ignore-scripts\n working-directory: .youi-ci\/tool\n/, "").replaceAll("node .youi-ci/tool/packages/cli/bin/youi.js", "npx -y youiyoux");
|
|
1257
|
+
}
|
|
1258
|
+
return write(file, text, before, `authoritative: ${published ? "runs the published youiyoux package; pinned to" : `pinned to ${tool.slug}@${tool.commit.slice(0, 7)} and`} rules ${design.slug}@${design.commit.slice(0, 7)}${diff ? "; pull requests also fail on drift they add (youiyoux check --diff --strict)" : ""}`);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// ../cli/src/check-repo.ts
|
|
1100
1262
|
async function loadCheckProject(root, options = {}, inferTop = false) {
|
|
1101
|
-
let dir =
|
|
1263
|
+
let dir = resolve4(root);
|
|
1102
1264
|
if (options.configPath) return loadProject(dir, options);
|
|
1103
1265
|
if (inferTop) {
|
|
1104
|
-
for (let ancestor = dir; ; ancestor =
|
|
1105
|
-
if (await
|
|
1106
|
-
if (
|
|
1266
|
+
for (let ancestor = dir; ; ancestor = dirname5(ancestor)) {
|
|
1267
|
+
if (await exists(join9(ancestor, PROJECT_FILE))) return loadProject(ancestor, options);
|
|
1268
|
+
if (dirname5(ancestor) === ancestor) break;
|
|
1107
1269
|
}
|
|
1108
|
-
dir = await
|
|
1270
|
+
dir = await repoRootOf(dir);
|
|
1109
1271
|
}
|
|
1110
|
-
if (await
|
|
1272
|
+
if (await exists(join9(dir, PROJECT_FILE))) return loadProject(dir, options);
|
|
1111
1273
|
return inferProject(options.productRoot ?? dir);
|
|
1112
1274
|
}
|
|
1113
1275
|
async function inferProject(dir, loaded) {
|
|
@@ -1143,7 +1305,7 @@ async function runRepoCheck(project2, change) {
|
|
|
1143
1305
|
const changedUi = (file) => change.added.has(file) && (/\.(tsx|jsx|astro|css)$/.test(file) || modules.some((m) => m.file === file && m.jsx.length > 0));
|
|
1144
1306
|
for (const file of /* @__PURE__ */ new Set([...target.files, ...change.added.keys()])) {
|
|
1145
1307
|
try {
|
|
1146
|
-
const module = await parseModule(file, change.sources?.[file] ?? await readFile3(
|
|
1308
|
+
const module = await parseModule(file, change.sources?.[file] ?? await readFile3(join9(project2.root, file), "utf8"));
|
|
1147
1309
|
if (module) {
|
|
1148
1310
|
modules.push(module);
|
|
1149
1311
|
if (changedUi(file) && module.parseErrors > 0) incomplete.push(`${file}: fix the syntax errors, then run youiyoux check --diff again`);
|
|
@@ -1168,7 +1330,7 @@ async function runRepoCheck(project2, change) {
|
|
|
1168
1330
|
} catch (error) {
|
|
1169
1331
|
incomplete.push(`Components could not be compared (${String(error)}); run youiyoux inventory --project ${project2.root} to diagnose`);
|
|
1170
1332
|
}
|
|
1171
|
-
const findings2 = drift.map((d) => ({ ...d, severity: d.kind
|
|
1333
|
+
const findings2 = drift.map((d) => ({ ...d, severity: DRIFT_SEVERITY[d.kind] }));
|
|
1172
1334
|
findings2.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.column - b.column);
|
|
1173
1335
|
return { identity: null, unmatched: [], failed: [], notRun: [], incomplete: incomplete.length > 0, diff: { base: change.base, commit: change.commit, files: [...change.added.keys()].sort(), findings: findings2, notes, incomplete } };
|
|
1174
1336
|
}
|
|
@@ -1247,25 +1409,26 @@ function renderScan(report, outFile) {
|
|
|
1247
1409
|
coverageLine(report),
|
|
1248
1410
|
...report.coverage.failed.map((f) => style.warn(` ! ${f.file}: ${f.reason}`)),
|
|
1249
1411
|
...ruleProblemLines(report),
|
|
1250
|
-
|
|
1412
|
+
style.dim(`usage map \u2192 ${outFile}`)
|
|
1251
1413
|
];
|
|
1252
1414
|
return lines.join("\n");
|
|
1253
1415
|
}
|
|
1254
|
-
function renderWhere(
|
|
1255
|
-
const
|
|
1256
|
-
const
|
|
1257
|
-
const
|
|
1258
|
-
const
|
|
1416
|
+
function renderWhere(where2, report) {
|
|
1417
|
+
const all = where2.sites;
|
|
1418
|
+
const max = Math.max(...where2.byRoute.map((r) => r.sites), 1);
|
|
1419
|
+
const routeWidth = Math.max(...where2.byRoute.map((r) => r.route.length), 6);
|
|
1420
|
+
const unknown = where2.unknown.length > 0 ? style.warn(plural(where2.unknown.length, "unknown")) : style.dim("0 unknown");
|
|
1421
|
+
const lookalikes = where2.lookalikes.reduce((n, l) => n + l.sites, 0);
|
|
1259
1422
|
const lines = [
|
|
1260
|
-
`${style.accent(
|
|
1261
|
-
style.dim(
|
|
1423
|
+
`${style.accent(where2.name)} ${style.dim(`${where2.element} \xB7 v${where2.version} \xB7 ${where2.status}`)}`,
|
|
1424
|
+
style.dim(where2.import),
|
|
1262
1425
|
"",
|
|
1263
|
-
`${style.bold(plural(
|
|
1426
|
+
`${style.bold(plural(where2.total, "site"))} in ${plural(where2.files, "file")} across ${plural(where2.screens, "screen")} ${unknown} ${lookalikes > 0 ? style.warn(`${plural(lookalikes, "lookalike")} not counted`) : style.dim("0 lookalikes")}`,
|
|
1264
1427
|
rule(),
|
|
1265
|
-
...
|
|
1266
|
-
...
|
|
1428
|
+
...where2.byRoute.map((r) => `${padEnd(r.route, routeWidth)} ${padStart(String(r.sites), 4)} ${bar(r.sites, max)}${r.route === "*" ? ` ${SHELL_NOTE}` : ""}`),
|
|
1429
|
+
...where2.unrouted > 0 ? [`${padEnd(style.warn("(unrouted)"), routeWidth)} ${padStart(String(where2.unrouted), 4)} ${style.dim("no screen imports these files")}`] : [],
|
|
1267
1430
|
"",
|
|
1268
|
-
...Object.entries(
|
|
1431
|
+
...Object.entries(where2.props).map(([prop, counts]) => `${style.dim(padEnd(prop, 9))}${Object.entries(counts).map(([value, n]) => `${value.startsWith("(") ? style.dim(value) : value} ${style.dim(String(n))}`).join(style.dim(" \xB7 "))}`),
|
|
1269
1432
|
""
|
|
1270
1433
|
];
|
|
1271
1434
|
const byRoute = Map.groupBy(all, (s) => s.routes[0] ?? "(unrouted)");
|
|
@@ -1273,16 +1436,14 @@ function renderWhere(where, report, all) {
|
|
|
1273
1436
|
lines.push(style.bold(route));
|
|
1274
1437
|
for (const site of sites) lines.push(` ${padEnd(style.dim(site.at), 46)} ${padEnd(site.use, 14)} ${site.props}`);
|
|
1275
1438
|
}
|
|
1276
|
-
if (
|
|
1277
|
-
for (const l of
|
|
1278
|
-
for (const u of
|
|
1439
|
+
if (where2.nextOffset !== null) lines.push(style.dim(` \u2026 ${where2.total - all.length} more (--all, or --route <route>)`));
|
|
1440
|
+
for (const l of where2.lookalikes) lines.push("", style.warn(`lookalike: ${plural(l.sites, "site")} render ${l.resolved}, a different ${where2.name}`), style.dim(" not counted above \xB7 youiyoux check --rule components.canonical-import lists them"));
|
|
1441
|
+
for (const u of where2.unknown) lines.push(style.warn(`unknown: ${u.at} <${u.name}> (${u.reason})`));
|
|
1279
1442
|
lines.push("", coverageLine(report));
|
|
1280
1443
|
return lines.join("\n");
|
|
1281
1444
|
}
|
|
1282
|
-
function
|
|
1283
|
-
|
|
1284
|
-
}
|
|
1285
|
-
function renderCheck(violations, lockIssues2, report, suppressed) {
|
|
1445
|
+
function renderCheck(violations, lockIssues2, report) {
|
|
1446
|
+
const { suppressed } = report;
|
|
1286
1447
|
const lines = [];
|
|
1287
1448
|
for (const [file, list] of Map.groupBy(violations, (v) => v.file)) {
|
|
1288
1449
|
lines.push(style.bold(file));
|
|
@@ -1302,7 +1463,7 @@ function renderCheck(violations, lockIssues2, report, suppressed) {
|
|
|
1302
1463
|
lines.push(
|
|
1303
1464
|
"",
|
|
1304
1465
|
errors > 0 ? style.error(`\u2716 ${plural(errors, "error")} \xB7 ${plural(warnings, "warning")} in ${plural(files, "file")}`) : warnings > 0 ? style.warn(`\u2713 0 errors \xB7 ${plural(warnings, "warning")} in ${plural(files, "file")}`) : style.ok("\u2713 0 errors \xB7 0 warnings"),
|
|
1305
|
-
...[...byRule].sort((a, b) => b[1].length - a[1].length).map(([
|
|
1466
|
+
...[...byRule].sort((a, b) => b[1].length - a[1].length).map(([slug, list]) => style.dim(` ${padStart(String(list.length), 4)} ${slug}`)),
|
|
1306
1467
|
...suppressed > 0 ? [style.dim(` ${padStart(String(suppressed), 4)} suppressed by youi-ignore`)] : [],
|
|
1307
1468
|
...lockIssues2 === null ? [style.dim(" no youi.lock.json yet: generated-file and drift checks start after `youiyoux sync`")] : [],
|
|
1308
1469
|
coverageLine(report),
|
|
@@ -1429,14 +1590,13 @@ function renderShots2(report) {
|
|
|
1429
1590
|
}
|
|
1430
1591
|
|
|
1431
1592
|
// ../cli/src/check-render.ts
|
|
1432
|
-
var slug = (rule2) => rule2.slice(rule2.indexOf("/rule/") + 6);
|
|
1433
1593
|
function actionable(result) {
|
|
1434
1594
|
if (!result.baseline) return result.violations;
|
|
1435
1595
|
const { comparison } = result.baseline;
|
|
1436
1596
|
return [...comparison.new, ...comparison.regressed].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
1437
1597
|
}
|
|
1438
1598
|
function renderCheckText(result) {
|
|
1439
|
-
const lines = [renderCheck(actionable(result), result.lock, result.scan.report
|
|
1599
|
+
const lines = [renderCheck(actionable(result), result.lock, result.scan.report)];
|
|
1440
1600
|
if (result.baseline) {
|
|
1441
1601
|
const { comparison, file, recorded } = result.baseline;
|
|
1442
1602
|
const gone = comparison.fixed.reduce((n, f) => n + f.gone, 0);
|
|
@@ -1464,13 +1624,13 @@ function githubAnnotations(result, prefix) {
|
|
|
1464
1624
|
file: `${prefix}${v.file}`,
|
|
1465
1625
|
line: v.line,
|
|
1466
1626
|
col: v.column,
|
|
1467
|
-
title: `youi ${
|
|
1627
|
+
title: `youi ${ruleSlug(v.rule)}`
|
|
1468
1628
|
})
|
|
1469
1629
|
);
|
|
1470
1630
|
for (const issue of result.lock ?? []) lines.push(command(issue.severity === "error" ? "error" : "warning", issue.message, { file: `${prefix}${issue.file}`, title: "youi setup" }));
|
|
1471
1631
|
for (const file of result.unmatched) lines.push(command("error", `${file} was not checked: not in this project's include globs`, { title: "youi incomplete" }));
|
|
1472
1632
|
for (const failure of result.failed) lines.push(command("error", `could not be fully analysed (${failure.reason}); a clean result here is not a pass`, { file: `${prefix}${failure.file}`, title: "youi incomplete" }));
|
|
1473
|
-
for (const
|
|
1633
|
+
for (const run7 of result.notRun) lines.push(command("error", `rule ${ruleSlug(run7.rule)} did not run (${run7.reason ?? run7.state}); its findings are unknown`, { title: "youi incomplete" }));
|
|
1474
1634
|
return lines;
|
|
1475
1635
|
}
|
|
1476
1636
|
var revision = (state) => state.commit ? `\`${state.commit}\`${state.dirty ? " (dirty)" : ""}` : "not a git checkout";
|
|
@@ -1492,7 +1652,7 @@ function diffGaps(result) {
|
|
|
1492
1652
|
return [
|
|
1493
1653
|
...result.unmatched.map((f) => `${f} was not checked: it is not in this project's include globs`),
|
|
1494
1654
|
...result.failed.map((f) => `${f.file} could not be fully analysed (${f.reason})`),
|
|
1495
|
-
...result.notRun.map((r) => `rule ${
|
|
1655
|
+
...result.notRun.map((r) => `rule ${ruleSlug(r.rule)} did not run (${r.reason ?? r.state})`),
|
|
1496
1656
|
...result.diff?.incomplete ?? []
|
|
1497
1657
|
];
|
|
1498
1658
|
}
|
|
@@ -1546,33 +1706,27 @@ function githubDiffSummary(result) {
|
|
|
1546
1706
|
|
|
1547
1707
|
// ../cli/src/hooks.ts
|
|
1548
1708
|
import { execFile as execFile4 } from "node:child_process";
|
|
1549
|
-
import {
|
|
1550
|
-
import { dirname as
|
|
1709
|
+
import { realpath as realpath3 } from "node:fs/promises";
|
|
1710
|
+
import { dirname as dirname6, join as join10, resolve as resolve5 } from "node:path";
|
|
1551
1711
|
import { promisify as promisify4 } from "node:util";
|
|
1552
1712
|
var run4 = promisify4(execFile4);
|
|
1553
1713
|
var MAX_LISTED = 20;
|
|
1554
1714
|
var COMPONENT_FILE = /\.(?:tsx|jsx)$/;
|
|
1555
1715
|
var quoted = (path) => /^[\w./-]+$/.test(path) ? path : `'${path.replace(/'/g, "'\\''")}'`;
|
|
1556
1716
|
var SILENT = { stdout: "", exitCode: 0 };
|
|
1557
|
-
async function exists3(file) {
|
|
1558
|
-
return access3(file).then(
|
|
1559
|
-
() => true,
|
|
1560
|
-
() => false
|
|
1561
|
-
);
|
|
1562
|
-
}
|
|
1563
1717
|
async function load(target, near) {
|
|
1564
1718
|
return loadCheckProject(target.project ?? near, { ...target.config ? { configPath: target.config } : {}, ...target.design ? { designRoot: target.design } : {}, ...target.root ? { productRoot: target.root } : {} }, !target.project);
|
|
1565
1719
|
}
|
|
1566
1720
|
async function baselineFor(project2, target) {
|
|
1567
1721
|
const file = baselinePath(project2, target.config);
|
|
1568
|
-
return await
|
|
1722
|
+
return await exists(file) ? file : null;
|
|
1569
1723
|
}
|
|
1570
1724
|
function findingLines(result, root) {
|
|
1571
1725
|
const findings2 = result.diff?.findings ?? [];
|
|
1572
1726
|
const lines = findings2.slice(0, MAX_LISTED).map((f) => `- ${diffFindingLine(f)}`);
|
|
1573
1727
|
if (findings2.length > MAX_LISTED) {
|
|
1574
|
-
const { run:
|
|
1575
|
-
lines.push(`- \u2026and ${findings2.length - MAX_LISTED} more. ${setup} Run \`${
|
|
1728
|
+
const { run: run7, setup } = portableYoui(root);
|
|
1729
|
+
lines.push(`- \u2026and ${findings2.length - MAX_LISTED} more. ${setup} Run \`${run7} check --diff\`.`);
|
|
1576
1730
|
}
|
|
1577
1731
|
for (const gap of diffGaps(result)) lines.push(`- incomplete: ${gap}; 0 findings there is not a pass`);
|
|
1578
1732
|
return lines;
|
|
@@ -1597,21 +1751,22 @@ async function runClaudeHook(input, target = {}) {
|
|
|
1597
1751
|
}
|
|
1598
1752
|
const raw = payload.tool_input?.file_path;
|
|
1599
1753
|
if (typeof raw !== "string" || raw.length === 0) return SILENT;
|
|
1600
|
-
const file =
|
|
1754
|
+
const file = resolve5(payload.cwd ?? process.cwd(), raw);
|
|
1601
1755
|
try {
|
|
1602
|
-
const project2 = await load(target,
|
|
1603
|
-
const real = (path) =>
|
|
1604
|
-
const
|
|
1605
|
-
if (!isChecked(project2,
|
|
1606
|
-
const
|
|
1607
|
-
if (problem) return claudeContext(problem, project2.root);
|
|
1608
|
-
const
|
|
1756
|
+
const project2 = await load(target, dirname6(file));
|
|
1757
|
+
const real = (path) => realpath3(path).catch(() => path);
|
|
1758
|
+
const relative9 = relPath(await real(project2.root), await real(file));
|
|
1759
|
+
if (!isChecked(project2, relative9)) return SILENT;
|
|
1760
|
+
const found = await changeFor(() => workingTreeChange(project2, void 0, [relative9]));
|
|
1761
|
+
if (found.problem !== void 0) return claudeContext(found.problem, project2.root);
|
|
1762
|
+
const { change } = found;
|
|
1763
|
+
const result = configured(project2) ? await runCheck(project2, { files: [file], lock: false, baseline: await baselineFor(project2, target), diff: change }) : await runRepoCheck(project2, change);
|
|
1609
1764
|
const lines = findingLines(result, project2.root);
|
|
1610
|
-
const see = COMPONENT_FILE.test(
|
|
1611
|
-
if (lines.length === 0) return see ? claudeContext(`youiyoux: ${
|
|
1612
|
-
const older =
|
|
1765
|
+
const see = COMPONENT_FILE.test(relative9) ? `See it: youiyoux render ${quoted(relative9)} (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;
|
|
1766
|
+
if (lines.length === 0) return see ? claudeContext(`youiyoux: ${relative9} passes the design-system check. ${see}`, project2.root) : SILENT;
|
|
1767
|
+
const older = "violations" in result ? result.violations.filter((v) => !isAdded(change.added, v.file, v.line)).length : 0;
|
|
1613
1768
|
const known = older > 0 ? ` (${older} older findings on unchanged lines omitted)` : "";
|
|
1614
|
-
return claudeContext(`youiyoux design-system check (advisory) on ${
|
|
1769
|
+
return claudeContext(`youiyoux design-system check (advisory) on ${relative9}${known}:
|
|
1615
1770
|
${lines.join("\n")}${!configured(project2) ? `
|
|
1616
1771
|
${result.diff?.notes.join("\n")}` : ""}
|
|
1617
1772
|
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 ? `
|
|
@@ -1639,10 +1794,11 @@ async function runPreCommitHook(target = {}, options = {}) {
|
|
|
1639
1794
|
const staged = await stagedSources(project2);
|
|
1640
1795
|
const files = Object.keys(staged);
|
|
1641
1796
|
if (files.length === 0) return SILENT;
|
|
1642
|
-
const
|
|
1643
|
-
if (problem) return { stdout: `${viaNpx(problem, project2.root)}
|
|
1797
|
+
const found = await changeFor(() => stagedChange(project2));
|
|
1798
|
+
if (found.problem !== void 0) return { stdout: `${viaNpx(found.problem, project2.root)}
|
|
1644
1799
|
`, exitCode: options.strict ? 1 : 0 };
|
|
1645
|
-
const
|
|
1800
|
+
const { change } = found;
|
|
1801
|
+
const result = configured(project2) ? await runCheck(project2, { files: files.map((f) => join10(project2.root, f)), overrides: staged, lock: false, baseline: await baselineFor(project2, target), diff: change }) : await runRepoCheck(project2, change);
|
|
1646
1802
|
const lines = findingLines(result, project2.root);
|
|
1647
1803
|
const failing = result.incomplete || (result.diff?.findings.length ?? 0) > 0;
|
|
1648
1804
|
if (lines.length === 0) return { stdout: `${label}: ${files.length} staged file(s) clean, no drift
|
|
@@ -1657,195 +1813,9 @@ ${viaNpx(lines.join("\n"), project2.root)}
|
|
|
1657
1813
|
}
|
|
1658
1814
|
}
|
|
1659
1815
|
|
|
1660
|
-
// ../cli/src/hooks-install.ts
|
|
1661
|
-
import { execFile as execFile5 } from "node:child_process";
|
|
1662
|
-
import { chmod, mkdir as mkdir2, realpath as realpath3 } from "node:fs/promises";
|
|
1663
|
-
import { dirname as dirname6, join as join10, relative as relative5, resolve as resolve5, sep as sep3 } from "node:path";
|
|
1664
|
-
import { promisify as promisify5 } from "node:util";
|
|
1665
|
-
var run5 = promisify5(execFile5);
|
|
1666
|
-
var CLAUDE_MARKER = "hooks run claude";
|
|
1667
|
-
var BLOCK_START = "# >>> youi pre-commit >>>";
|
|
1668
|
-
var BLOCK_END2 = "# <<< youi pre-commit <<<";
|
|
1669
|
-
var WORKFLOW = ".github/workflows/youi-check.yml";
|
|
1670
|
-
var WORKFLOW_HEADER = "# Generated by `youiyoux hooks install --ci`.";
|
|
1671
|
-
var LEGACY_WORKFLOW_HEADER = WORKFLOW_HEADER.replace("youiyoux", "youi");
|
|
1672
|
-
var managedWorkflow = (text) => text.startsWith(WORKFLOW_HEADER) || text.startsWith(LEGACY_WORKFLOW_HEADER);
|
|
1673
|
-
var DIFF_STEP = "name: youiyoux check --diff";
|
|
1674
|
-
var CLAUDE_MATCHER = "Edit|Write|MultiEdit";
|
|
1675
|
-
var CLAUDE_TIMEOUT_S = 60;
|
|
1676
|
-
var shellQuote = (arg) => /^[\w@%+=:,./-]+$/.test(arg) ? arg : `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
1677
|
-
async function gitOut(cwd, args) {
|
|
1678
|
-
return (await run5("git", [...args], { cwd })).stdout.trim();
|
|
1679
|
-
}
|
|
1680
|
-
async function repoRootOf(dir) {
|
|
1681
|
-
return gitOut(dir, ["rev-parse", "--show-toplevel"]).catch(() => dir);
|
|
1682
|
-
}
|
|
1683
|
-
function hookCommand(toolRoot, kind, target, extra = []) {
|
|
1684
|
-
const server = toolCommand(toolRoot);
|
|
1685
|
-
const args = [server.command, ...server.args, "hooks", "run", kind];
|
|
1686
|
-
for (const key2 of ["project", "config", "design", "root"]) {
|
|
1687
|
-
const value = target[key2];
|
|
1688
|
-
if (value) args.push(`--${key2}`, resolve5(value));
|
|
1689
|
-
}
|
|
1690
|
-
return [...args, ...extra].map(shellQuote).join(" ");
|
|
1691
|
-
}
|
|
1692
|
-
async function write(file, content, before, guarantee, mode) {
|
|
1693
|
-
if (before === content) return { file, action: "unchanged", guarantee };
|
|
1694
|
-
await mkdir2(dirname6(file), { recursive: true });
|
|
1695
|
-
await writeFileAtomic(file, content);
|
|
1696
|
-
if (mode !== void 0) await chmod(file, mode);
|
|
1697
|
-
return { file, action: before === null ? "created" : "updated", guarantee };
|
|
1698
|
-
}
|
|
1699
|
-
var isYouiEntry = (entry) => (entry.hooks ?? []).some((h) => typeof h.command === "string" && h.command.includes(CLAUDE_MARKER));
|
|
1700
|
-
async function installClaudeHook(repoRoot, command2) {
|
|
1701
|
-
const file = join10(repoRoot, ".claude/settings.local.json");
|
|
1702
|
-
const before = await readTextOrNull(file);
|
|
1703
|
-
let settings;
|
|
1704
|
-
try {
|
|
1705
|
-
settings = before === null ? {} : JSON.parse(before);
|
|
1706
|
-
} catch {
|
|
1707
|
-
throw new Error(`${file} is not valid JSON; fix it first (youi will not overwrite it)`);
|
|
1708
|
-
}
|
|
1709
|
-
const hooks = settings.hooks ?? {};
|
|
1710
|
-
const existing = hooks.PostToolUse ?? [];
|
|
1711
|
-
const entry = { matcher: CLAUDE_MATCHER, hooks: [{ type: "command", command: command2, timeout: CLAUDE_TIMEOUT_S }] };
|
|
1712
|
-
const postToolUse = existing.some(isYouiEntry) ? existing.map((e) => isYouiEntry(e) ? entry : e) : [...existing, entry];
|
|
1713
|
-
const next2 = { ...settings, hooks: { ...hooks, PostToolUse: postToolUse } };
|
|
1714
|
-
return write(file, `${JSON.stringify(next2, null, 2)}
|
|
1715
|
-
`, before, "advisory: after each Claude Code Edit/Write/MultiEdit, that file's new violations go back to Claude; never blocks; edits made any other way are not seen");
|
|
1716
|
-
}
|
|
1717
|
-
function withBlock(script, block) {
|
|
1718
|
-
if (script === null) return `#!/bin/sh
|
|
1719
|
-
${block}
|
|
1720
|
-
`;
|
|
1721
|
-
const start = script.indexOf(BLOCK_START);
|
|
1722
|
-
const end = script.indexOf(BLOCK_END2);
|
|
1723
|
-
if (start >= 0 && end > start) return `${script.slice(0, start)}${block}${script.slice(end + BLOCK_END2.length)}`;
|
|
1724
|
-
return `${script.endsWith("\n") ? script : `${script}
|
|
1725
|
-
`}
|
|
1726
|
-
${block}
|
|
1727
|
-
`;
|
|
1728
|
-
}
|
|
1729
|
-
async function installPreCommit(projectRoot, command2, strict) {
|
|
1730
|
-
const hooksDir = resolve5(projectRoot, await gitOut(projectRoot, ["rev-parse", "--git-path", "hooks"]));
|
|
1731
|
-
const file = join10(hooksDir, "pre-commit");
|
|
1732
|
-
const before = await readTextOrNull(file);
|
|
1733
|
-
const shebang = before?.split("\n", 1)[0] ?? "";
|
|
1734
|
-
if (before !== null && shebang.startsWith("#!") && !/\b(?:sh|bash|zsh|dash)\b/.test(shebang)) {
|
|
1735
|
-
throw new Error(`${file} is not a shell script (${shebang}); add this line to it yourself: ${command2}`);
|
|
1736
|
-
}
|
|
1737
|
-
if (before !== null && /^\s*exec\s/m.test(before) && !before.includes(BLOCK_START)) {
|
|
1738
|
-
throw new Error(`${file} hands off with exec, so an appended check would never run; add this line before the exec yourself: ${command2}`);
|
|
1739
|
-
}
|
|
1740
|
-
const what = strict ? "Blocks commits with new design-system errors" : "Advisory, never blocks";
|
|
1741
|
-
const block = `${BLOCK_START}
|
|
1742
|
-
# ${what}: checks the staged content of staged files only; CI is authoritative.
|
|
1743
|
-
${command2} || exit $?
|
|
1744
|
-
${BLOCK_END2}`;
|
|
1745
|
-
const guarantee = strict ? "blocks commits with new errors in staged files (any agent or person that commits; --no-verify skips it)" : "advisory at commit time, staged files only (any agent or person that commits; not every edit)";
|
|
1746
|
-
return write(file, withBlock(before, block), before, guarantee, 493);
|
|
1747
|
-
}
|
|
1748
|
-
async function installRepoAgentInstructions(root) {
|
|
1749
|
-
const file = join10(root, "AGENTS.md");
|
|
1750
|
-
const before = await readTextOrNull(file);
|
|
1751
|
-
const guarantee = "agents run youiyoux check --diff after UI edits; pre-commit also checks staged edits";
|
|
1752
|
-
const ruled = before !== null && before.indexOf(RULE_END) > before.indexOf(RULE_BEGIN) && before.includes(RULE_BEGIN);
|
|
1753
|
-
return write(file, ruled ? before : upsertBlock(before, repoAgentsBlock(root)), before, guarantee);
|
|
1754
|
-
}
|
|
1755
|
-
function codexFallback(projectRoot, agentsFile) {
|
|
1756
|
-
return {
|
|
1757
|
-
file: join10(projectRoot, agentsFile),
|
|
1758
|
-
action: "skipped",
|
|
1759
|
-
guarantee: "no post-edit hook installed (Codex documents none): Codex follows AGENTS.md, where `youiyoux sync` tells it to run `youiyoux check --diff` after UI edits, and the git pre-commit hook checks what it commits"
|
|
1760
|
-
};
|
|
1761
|
-
}
|
|
1762
|
-
function githubSlug(url) {
|
|
1763
|
-
const match = /github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/.exec(url.trim());
|
|
1764
|
-
return match ? `${match[1]}/${match[2]}` : null;
|
|
1765
|
-
}
|
|
1766
|
-
async function repoAt(root) {
|
|
1767
|
-
const slug2 = githubSlug(await gitOut(root, ["remote", "get-url", "origin"]).catch(() => ""));
|
|
1768
|
-
if (!slug2) throw new Error(`${root} has no GitHub origin remote; the workflow needs one to check it out`);
|
|
1769
|
-
return { slug: slug2, commit: await gitOut(root, ["rev-parse", "HEAD"]) };
|
|
1770
|
-
}
|
|
1771
|
-
var posix2 = (path) => path.split(sep3).join("/") || ".";
|
|
1772
|
-
var inside = (root, path) => !relative5(root, path).startsWith("..");
|
|
1773
|
-
function workflowText(input) {
|
|
1774
|
-
const dir = input.projectDir || ".";
|
|
1775
|
-
const target = ["--project", dir, "--design", ".youi-ci/design", ...input.configPath ? ["--config", input.configPath, "--root", dir] : []];
|
|
1776
|
-
const checkArgs = ["--baseline", "--format", "github", ...target].map(shellQuote).join(" ");
|
|
1777
|
-
const diffArgs2 = ["--strict", "--format", "github", ...target].map(shellQuote).join(" ");
|
|
1778
|
-
const diffStep = input.diff ? ` - ${DIFF_STEP} (drift this pull request adds)
|
|
1779
|
-
if: \${{ !cancelled() && github.event_name == 'pull_request' }}
|
|
1780
|
-
env:
|
|
1781
|
-
YOUI_BASE: \${{ github.base_ref }}
|
|
1782
|
-
run: node .youi-ci/tool/packages/cli/bin/youi.js check --diff "origin/$YOUI_BASE" ${diffArgs2}
|
|
1783
|
-
` : "";
|
|
1784
|
-
return `${WORKFLOW_HEADER}
|
|
1785
|
-
# The authoritative design-system check: it fails on violations the baseline does not
|
|
1786
|
-
# accept, annotates them on the PR's lines, and states the rules version (design commit
|
|
1787
|
-
# and definitions digest) in the job summary. Raise the rules version by changing the
|
|
1788
|
-
# design ref below; YOUI_REPO_TOKEN must be able to read both repositories.
|
|
1789
|
-
name: youiyoux check
|
|
1790
|
-
on:
|
|
1791
|
-
pull_request:
|
|
1792
|
-
push:
|
|
1793
|
-
branches: [main]
|
|
1794
|
-
permissions:
|
|
1795
|
-
contents: read
|
|
1796
|
-
jobs:
|
|
1797
|
-
youi-check:
|
|
1798
|
-
runs-on: ubuntu-latest
|
|
1799
|
-
timeout-minutes: 10
|
|
1800
|
-
steps:
|
|
1801
|
-
- uses: actions/checkout@v4${input.diff ? "\n with:\n fetch-depth: 0" : ""}
|
|
1802
|
-
- name: Check out youi
|
|
1803
|
-
uses: actions/checkout@v4
|
|
1804
|
-
with:
|
|
1805
|
-
repository: ${input.tool.slug}
|
|
1806
|
-
ref: ${input.tool.commit}
|
|
1807
|
-
path: .youi-ci/tool
|
|
1808
|
-
token: \${{ secrets.YOUI_REPO_TOKEN }}
|
|
1809
|
-
- name: Check out the design rules (the rules version)
|
|
1810
|
-
uses: actions/checkout@v4
|
|
1811
|
-
with:
|
|
1812
|
-
repository: ${input.design.slug}
|
|
1813
|
-
ref: ${input.design.commit}
|
|
1814
|
-
path: .youi-ci/design
|
|
1815
|
-
token: \${{ secrets.YOUI_REPO_TOKEN }}
|
|
1816
|
-
- uses: pnpm/action-setup@v4
|
|
1817
|
-
with:
|
|
1818
|
-
version: 10.13.1
|
|
1819
|
-
- uses: actions/setup-node@v4
|
|
1820
|
-
with:
|
|
1821
|
-
node-version: 24
|
|
1822
|
-
- run: pnpm install --frozen-lockfile --ignore-scripts
|
|
1823
|
-
working-directory: .youi-ci/tool
|
|
1824
|
-
- name: youiyoux check
|
|
1825
|
-
run: node .youi-ci/tool/packages/cli/bin/youi.js check ${checkArgs}
|
|
1826
|
-
${diffStep}`;
|
|
1827
|
-
}
|
|
1828
|
-
async function installWorkflow(project2, toolRoot, target, force, options = {}) {
|
|
1829
|
-
const top = await gitOut(project2.root, ["rev-parse", "--show-toplevel"]);
|
|
1830
|
-
const [tool, design] = await Promise.all([ephemeralTool(toolRoot) ? Promise.resolve({ slug: "npm/youiyoux", commit: "published" }) : repoAt(toolRoot), repoAt(project2.repo.root)]);
|
|
1831
|
-
const configFile = resolve5(target.config ?? project2.configFile ?? join10(project2.root, PROJECT_FILE));
|
|
1832
|
-
const configPath = !target.config ? null : inside(project2.repo.root, configFile) ? `.youi-ci/design/${posix2(relative5(project2.repo.root, configFile))}` : inside(top, configFile) ? posix2(relative5(top, configFile)) : null;
|
|
1833
|
-
if (target.config && !configPath) throw new Error(`${configFile} is in neither this repo nor the design repo, so CI cannot read it`);
|
|
1834
|
-
const file = join10(top, WORKFLOW);
|
|
1835
|
-
const before = await readTextOrNull(file);
|
|
1836
|
-
if (before !== null && !managedWorkflow(before) && !force) throw new Error(`${file} exists and was not written by youi; pass --force to replace it`);
|
|
1837
|
-
const diff = options.diff === true || before !== null && managedWorkflow(before) && (before.includes(DIFF_STEP) || before.includes(DIFF_STEP.replace("youiyoux", "youi")));
|
|
1838
|
-
const projectDir = posix2(relative5(top, await realpath3(project2.root).catch(() => project2.root)));
|
|
1839
|
-
let text = workflowText({ tool, design, projectDir, configPath, diff });
|
|
1840
|
-
if (ephemeralTool(toolRoot)) {
|
|
1841
|
-
text = text.replace(/ - name: Check out youi\n[\s\S]*?(?= - name: Check out the design)/, "").replace(/ - run: pnpm install --frozen-lockfile --ignore-scripts\n working-directory: .youi-ci\/tool\n/, "").replaceAll("node .youi-ci/tool/packages/cli/bin/youi.js", "npx -y youiyoux");
|
|
1842
|
-
}
|
|
1843
|
-
return write(file, text, before, `authoritative: ${ephemeralTool(toolRoot) ? "runs the published youiyoux package; pinned to" : `pinned to ${tool.slug}@${tool.commit.slice(0, 7)} and`} rules ${design.slug}@${design.commit.slice(0, 7)}${diff ? "; pull requests also fail on drift they add (youiyoux check --diff --strict)" : ""}`);
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
1816
|
// ../cli/src/agents-install.ts
|
|
1847
1817
|
import { readdir as readdir2, rm as rm2, rmdir } from "node:fs/promises";
|
|
1848
|
-
import { dirname as dirname7, isAbsolute as isAbsolute4, join as join11, relative as
|
|
1818
|
+
import { dirname as dirname7, isAbsolute as isAbsolute4, join as join11, relative as relative5 } from "node:path";
|
|
1849
1819
|
var TOML_BEGIN = "# >>> youi mcp >>>";
|
|
1850
1820
|
var TOML_END = "# <<< youi mcp <<<";
|
|
1851
1821
|
var MCP_NAME = "youiyoux";
|
|
@@ -1862,7 +1832,7 @@ function mcpCommand(toolRoot, target) {
|
|
|
1862
1832
|
return { command: server.command, args };
|
|
1863
1833
|
}
|
|
1864
1834
|
function relativeTo(repoRoot, server) {
|
|
1865
|
-
return { command: server.command, args: server.args.map((arg) => isAbsolute4(arg) ?
|
|
1835
|
+
return { command: server.command, args: server.args.map((arg) => isAbsolute4(arg) ? relative5(repoRoot, arg) || "." : arg) };
|
|
1866
1836
|
}
|
|
1867
1837
|
var shellLine = ({ command: command2, args }) => [command2, ...args].map(shellQuote).join(" ");
|
|
1868
1838
|
function withBlock2(text, block, begin, end) {
|
|
@@ -2091,7 +2061,7 @@ import { homedir, userInfo } from "node:os";
|
|
|
2091
2061
|
import { join as join12, resolve as resolve6 } from "node:path";
|
|
2092
2062
|
async function swapImpact(candidate, family) {
|
|
2093
2063
|
try {
|
|
2094
|
-
const { discoverIndexPath: discoverIndexPath2, sharedStore: sharedStore2 } = await import("./src-
|
|
2064
|
+
const { discoverIndexPath: discoverIndexPath2, sharedStore: sharedStore2 } = await import("./src-SFKFERZ2.js");
|
|
2095
2065
|
const path = discoverIndexPath2();
|
|
2096
2066
|
if (!process.env.DATABASE_URL && !existsSync(path)) return null;
|
|
2097
2067
|
const impact = await (await sharedStore2(path, process.env, { readOnly: true })).swapImpact(candidate, family);
|
|
@@ -2102,7 +2072,7 @@ async function swapImpact(candidate, family) {
|
|
|
2102
2072
|
}
|
|
2103
2073
|
async function libraryLicence(id) {
|
|
2104
2074
|
try {
|
|
2105
|
-
const { discoverCacheDir: discoverCacheDir2, findListing } = await import("./src-
|
|
2075
|
+
const { discoverCacheDir: discoverCacheDir2, findListing } = await import("./src-SFKFERZ2.js");
|
|
2106
2076
|
const { entry } = await findListing(discoverCacheDir2(void 0, process.env), id);
|
|
2107
2077
|
return entry.id === id ? entry.license : null;
|
|
2108
2078
|
} catch {
|
|
@@ -2110,7 +2080,7 @@ async function libraryLicence(id) {
|
|
|
2110
2080
|
}
|
|
2111
2081
|
}
|
|
2112
2082
|
async function startRuleEditor(options) {
|
|
2113
|
-
const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-
|
|
2083
|
+
const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-34R7QMJC.js");
|
|
2114
2084
|
const projectRoot = resolve6(options.project);
|
|
2115
2085
|
const load2 = { ...options.config ? { configPath: resolve6(options.config) } : {}, ...options.design ? { designRoot: resolve6(options.design) } : {}, ...options.root ? { productRoot: resolve6(options.root) } : {} };
|
|
2116
2086
|
const project2 = await loadProject(projectRoot, load2);
|
|
@@ -2125,41 +2095,79 @@ async function startRuleEditor(options) {
|
|
|
2125
2095
|
|
|
2126
2096
|
// ../cli/src/hub.ts
|
|
2127
2097
|
import { spawn } from "node:child_process";
|
|
2128
|
-
import {
|
|
2098
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
2099
|
+
import { access as access2, mkdtemp as mkdtemp2, rm as rm3 } from "node:fs/promises";
|
|
2100
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
2129
2101
|
import { join as join13, resolve as resolve7 } from "node:path";
|
|
2130
2102
|
var HUB_SNAPSHOT_DIR = ".hub";
|
|
2131
2103
|
var DEFAULT_HUB_PORT = "5173";
|
|
2104
|
+
var named = (options) => ["design", "project", "config", "root", "inventory", "detect"].flatMap((key2) => options[key2] ? [`--${key2}`, resolve7(options[key2])] : []);
|
|
2105
|
+
var namesSystem = (options) => ["design", "project", "config", "root"].some((key2) => options[key2]);
|
|
2132
2106
|
function hubPlan(toolRoot, options) {
|
|
2133
2107
|
const web = join13(toolRoot, "apps/web");
|
|
2134
2108
|
const vite = [join13(web, "node_modules/vite/bin/vite.js"), ...options.port ? ["--port", options.port, "--strictPort"] : []];
|
|
2135
|
-
const
|
|
2136
|
-
const
|
|
2137
|
-
const flags = watchesDemo ? [...named, "--project", join13(toolRoot, "examples/demo-app")] : named;
|
|
2109
|
+
const watchesDemo = options.watch && !namesSystem(options);
|
|
2110
|
+
const flags = watchesDemo ? [...named(options), "--project", join13(toolRoot, "examples/demo-app")] : named(options);
|
|
2138
2111
|
const api = options.api ? { YOUI_API: options.api } : {};
|
|
2139
2112
|
if (flags.length === 0) return { web, snapshot: null, env: api, vite, watch: null };
|
|
2140
2113
|
const out = join13(web, HUB_SNAPSHOT_DIR);
|
|
2141
2114
|
const snapshot = [join13(web, "scripts/snapshot.ts"), "--out", out, ...flags];
|
|
2142
2115
|
return { web, snapshot, env: { YOUI_HUB_SNAPSHOT: out, ...api }, vite, watch: options.watch ? [...snapshot, "--watch"] : null };
|
|
2143
2116
|
}
|
|
2144
|
-
|
|
2117
|
+
var BUILT_HUB = "hub";
|
|
2118
|
+
var BUILT_DEMO = "hub-demo";
|
|
2119
|
+
function builtHubPlan(toolRoot, options, out) {
|
|
2120
|
+
const flags = named(options);
|
|
2121
|
+
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)");
|
|
2122
|
+
const hub = join13(toolRoot, BUILT_HUB);
|
|
2123
|
+
const snapshot = flags.length > 0 ? [join13(toolRoot, "hub-snapshot.js"), "--out", out, ...flags] : null;
|
|
2124
|
+
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"] : []];
|
|
2125
|
+
return { snapshot, server, watch: options.watch && snapshot ? [...snapshot, "--watch"] : null };
|
|
2126
|
+
}
|
|
2127
|
+
function run5(args, cwd, env, started) {
|
|
2145
2128
|
return new Promise((done, fail) => {
|
|
2146
2129
|
const child = spawn(process.execPath, args, { cwd, env: { ...process.env, ...env }, stdio: "inherit" });
|
|
2130
|
+
started?.(child);
|
|
2147
2131
|
child.on("error", fail);
|
|
2148
2132
|
child.on("exit", (code, signal) => done(code ?? (signal === "SIGINT" || signal === "SIGTERM" ? 0 : 1)));
|
|
2149
2133
|
});
|
|
2150
2134
|
}
|
|
2135
|
+
async function runBuiltHub(toolRoot, options) {
|
|
2136
|
+
const out = await mkdtemp2(join13(tmpdir2(), "youiyoux-hub-"));
|
|
2137
|
+
const children = /* @__PURE__ */ new Set();
|
|
2138
|
+
const stop = () => {
|
|
2139
|
+
for (const child of children) child.kill("SIGTERM");
|
|
2140
|
+
};
|
|
2141
|
+
process.on("SIGINT", stop);
|
|
2142
|
+
process.on("SIGTERM", stop);
|
|
2143
|
+
try {
|
|
2144
|
+
const plan = builtHubPlan(toolRoot, options, out);
|
|
2145
|
+
if (plan.snapshot) {
|
|
2146
|
+
const code = await run5(plan.snapshot, process.cwd(), {}, (child) => children.add(child));
|
|
2147
|
+
if (code !== 0) return code;
|
|
2148
|
+
}
|
|
2149
|
+
if (plan.watch) children.add(spawn(process.execPath, plan.watch, { cwd: process.cwd(), stdio: "inherit" }));
|
|
2150
|
+
return await run5(plan.server, process.cwd(), {}, (child) => children.add(child));
|
|
2151
|
+
} finally {
|
|
2152
|
+
stop();
|
|
2153
|
+
process.off("SIGINT", stop);
|
|
2154
|
+
process.off("SIGTERM", stop);
|
|
2155
|
+
await rm3(out, { recursive: true, force: true });
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2151
2158
|
async function runHub(toolRoot, options) {
|
|
2159
|
+
if (existsSync2(join13(toolRoot, BUILT_HUB, "index.html"))) return runBuiltHub(toolRoot, options);
|
|
2152
2160
|
const plan = hubPlan(toolRoot, options);
|
|
2153
|
-
await
|
|
2161
|
+
await access2(plan.vite[0]).catch(() => {
|
|
2154
2162
|
throw new Error(`the hub's dependencies are not installed: run \`pnpm install\` in ${toolRoot}`);
|
|
2155
2163
|
});
|
|
2156
2164
|
if (plan.snapshot) {
|
|
2157
|
-
const code = await
|
|
2165
|
+
const code = await run5(plan.snapshot, process.cwd(), {});
|
|
2158
2166
|
if (code !== 0) return code;
|
|
2159
2167
|
}
|
|
2160
2168
|
const watcher = plan.watch ? spawn(process.execPath, plan.watch, { cwd: process.cwd(), stdio: "inherit" }) : null;
|
|
2161
2169
|
try {
|
|
2162
|
-
return await
|
|
2170
|
+
return await run5(plan.vite, plan.web, plan.env);
|
|
2163
2171
|
} finally {
|
|
2164
2172
|
watcher?.kill("SIGTERM");
|
|
2165
2173
|
}
|
|
@@ -2176,10 +2184,11 @@ function previewsPlan(toolRoot, options) {
|
|
|
2176
2184
|
}
|
|
2177
2185
|
async function runPreviews(toolRoot, options) {
|
|
2178
2186
|
const plan = previewsPlan(toolRoot, options);
|
|
2179
|
-
|
|
2187
|
+
if (existsSync2(join13(toolRoot, BUILT_HUB, "index.html"))) return run5([join13(toolRoot, "hub-previews.js"), ...plan.args.slice(1)], process.cwd(), {});
|
|
2188
|
+
await access2(join13(plan.web, "node_modules/playwright-core")).catch(() => {
|
|
2180
2189
|
throw new Error(`the hub's dependencies are not installed: run \`pnpm install\` in ${toolRoot}`);
|
|
2181
2190
|
});
|
|
2182
|
-
return
|
|
2191
|
+
return run5(plan.args, plan.web, {});
|
|
2183
2192
|
}
|
|
2184
2193
|
|
|
2185
2194
|
// ../cli/src/requests.ts
|
|
@@ -2204,7 +2213,7 @@ function renderRequests(briefs, status, unreadable) {
|
|
|
2204
2213
|
}
|
|
2205
2214
|
|
|
2206
2215
|
// ../cli/src/index-store.ts
|
|
2207
|
-
import { appendFile, mkdir as mkdir3, readFile as readFile4, stat, rename, mkdtemp as
|
|
2216
|
+
import { appendFile, mkdir as mkdir3, readFile as readFile4, stat, rename, mkdtemp as mkdtemp3, rm as rm4 } from "node:fs/promises";
|
|
2208
2217
|
import { dirname as dirname8, join as join14, resolve as resolve8 } from "node:path";
|
|
2209
2218
|
var SOURCES_FILE = "index-sources.json";
|
|
2210
2219
|
var SCANS_LOG = "scans.jsonl";
|
|
@@ -2228,26 +2237,19 @@ async function noteSource(home, change) {
|
|
|
2228
2237
|
`);
|
|
2229
2238
|
}
|
|
2230
2239
|
async function recordInventory(store, home, report, file) {
|
|
2231
|
-
const
|
|
2232
|
-
const logged = { run:
|
|
2240
|
+
const run7 = inventoryRun(report, resolve8(file));
|
|
2241
|
+
const logged = { run: run7.run, source: run7.source, scannedAt: run7.scannedAt, projects: run7.projects, families: run7.families };
|
|
2233
2242
|
await mkdir3(home, { recursive: true });
|
|
2234
2243
|
await appendFile(join14(home, SCANS_LOG), `${JSON.stringify({ schema: "youi.scan-log/v1", ...logged })}
|
|
2235
2244
|
`);
|
|
2236
|
-
await store.recordInventory(
|
|
2237
|
-
await noteSource(home, { inventory:
|
|
2245
|
+
await store.recordInventory(run7);
|
|
2246
|
+
await noteSource(home, { inventory: run7.source });
|
|
2238
2247
|
await linkCandidates(store);
|
|
2239
|
-
return
|
|
2248
|
+
return run7;
|
|
2240
2249
|
}
|
|
2241
2250
|
async function recordProject(store, project2, usage) {
|
|
2242
2251
|
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 }));
|
|
2243
2252
|
}
|
|
2244
|
-
async function readJsonFile(file) {
|
|
2245
|
-
try {
|
|
2246
|
-
return JSON.parse(await readFile4(file, "utf8"));
|
|
2247
|
-
} catch {
|
|
2248
|
-
return null;
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
2253
|
async function readLog(home) {
|
|
2252
2254
|
const text = await readFile4(join14(home, SCANS_LOG), "utf8").catch(() => "");
|
|
2253
2255
|
return text.split("\n").flatMap((line) => {
|
|
@@ -2265,7 +2267,7 @@ async function rebuildIndex(path, options = {}) {
|
|
|
2265
2267
|
const env = options.env ?? process.env;
|
|
2266
2268
|
const home = indexHome(path, env);
|
|
2267
2269
|
await mkdir3(dirname8(path), { recursive: true });
|
|
2268
|
-
const scratch = !env.DATABASE_URL ? await
|
|
2270
|
+
const scratch = !env.DATABASE_URL ? await mkdtemp3(`${path}.rebuild-`) : null;
|
|
2269
2271
|
const target = scratch ? join14(scratch, "index.db") : path;
|
|
2270
2272
|
const store = scratch ? await openSqlite(target) : await sharedStore(path, env);
|
|
2271
2273
|
try {
|
|
@@ -2283,7 +2285,7 @@ async function rebuildIndex(path, options = {}) {
|
|
|
2283
2285
|
const missing = [];
|
|
2284
2286
|
let replayed = 0;
|
|
2285
2287
|
for (const file of sources.inventories) {
|
|
2286
|
-
const parsed = InventoryReport.safeParse(await
|
|
2288
|
+
const parsed = InventoryReport.safeParse(await readJson(file));
|
|
2287
2289
|
if (!parsed.success) {
|
|
2288
2290
|
missing.push(file);
|
|
2289
2291
|
continue;
|
|
@@ -2302,7 +2304,7 @@ async function rebuildIndex(path, options = {}) {
|
|
|
2302
2304
|
}
|
|
2303
2305
|
try {
|
|
2304
2306
|
const project2 = await loadProject(source.root, source.options);
|
|
2305
|
-
const usage = ScanReport.safeParse(await
|
|
2307
|
+
const usage = ScanReport.safeParse(await readJson(source.usage ?? join14(project2.root, ".youi/usage.json")));
|
|
2306
2308
|
await recordProject(store, project2, usage.success ? usage.data : null);
|
|
2307
2309
|
projects++;
|
|
2308
2310
|
} catch (error) {
|
|
@@ -2337,7 +2339,7 @@ async function rebuildIndex(path, options = {}) {
|
|
|
2337
2339
|
} finally {
|
|
2338
2340
|
if (scratch) {
|
|
2339
2341
|
await store.close();
|
|
2340
|
-
await
|
|
2342
|
+
await rm4(scratch, { recursive: true, force: true });
|
|
2341
2343
|
}
|
|
2342
2344
|
}
|
|
2343
2345
|
}
|
|
@@ -2369,6 +2371,40 @@ function renderHistory(scans) {
|
|
|
2369
2371
|
return lines.join("\n");
|
|
2370
2372
|
}
|
|
2371
2373
|
|
|
2374
|
+
// ../cli/src/gaps.ts
|
|
2375
|
+
var where = (c) => {
|
|
2376
|
+
const first = c.definitions.map((d) => `${d.file}:${d.line}`).slice(0, 2).join(", ");
|
|
2377
|
+
const parts = [c.count ? `${c.count} ${c.count === 1 ? "component" : "components"} named for it` : null, c.uses ? `${c.uses} ${c.uses === 1 ? "use" : "uses"}` : null, `in ${c.files} ${c.files === 1 ? "file" : "files"}`].filter(Boolean);
|
|
2378
|
+
return `${parts.join(", ")}${first ? ` (${first}${c.definitions.length > 2 ? ", \u2026" : ""})` : ""}`;
|
|
2379
|
+
};
|
|
2380
|
+
var offered = (c) => c.candidates === null ? style.dim("no candidates: the Discover index is not synced") : c.candidates.length === 0 ? style.dim("no licensed candidate in the index") : c.candidates.map((x) => `${x.name} ${style.dim(`(${x.source}, ${x.licence}${x.copyleft ? ", copyleft" : ""})`)}`).join(" \xB7 ");
|
|
2381
|
+
function renderGaps(report) {
|
|
2382
|
+
const lines = [];
|
|
2383
|
+
for (const project2 of report.projects) {
|
|
2384
|
+
const by = (status) => project2.concepts.filter((c) => c.status === status);
|
|
2385
|
+
const hand = by("hand-built");
|
|
2386
|
+
const missing = by("missing");
|
|
2387
|
+
lines.push(`${style.bold(project2.id)}${project2.complete ? "" : style.warn(" (some files could not be read: counts are a lower bound)")}${project2.dependencies === "unread" ? style.dim(" (package.json not read: libraries only from imports)") : ""}`);
|
|
2388
|
+
if (hand.length) {
|
|
2389
|
+
lines.push(" Built by hand, with what could stand in for it:");
|
|
2390
|
+
for (const c of hand) lines.push(` ${c.name.padEnd(16)} ${where(c)}`, ` ${"".padEnd(16)} ${offered(c)}`);
|
|
2391
|
+
}
|
|
2392
|
+
if (missing.length) lines.push(` Not in it, from the standard kit (${missing.length}): ${missing.map((c) => c.name).join(", ")}`);
|
|
2393
|
+
const library = by("library");
|
|
2394
|
+
if (library.length) lines.push(` From libraries: ${library.map((c) => `${c.name} (${c.packages.join(", ")})`).join(", ")}`);
|
|
2395
|
+
const system = by("system");
|
|
2396
|
+
if (system.length) lines.push(` In the System${report.system ? ` (${report.system})` : ""}: ${system.map((c) => c.name).join(", ")}`);
|
|
2397
|
+
lines.push("");
|
|
2398
|
+
}
|
|
2399
|
+
if (report.across.length) {
|
|
2400
|
+
lines.push("Built by hand in more than one project, worth adding to the System once:");
|
|
2401
|
+
for (const a of report.across) lines.push(` ${a.name}: ${a.projects.join(", ")} (${a.uses} uses)`);
|
|
2402
|
+
lines.push("");
|
|
2403
|
+
}
|
|
2404
|
+
lines.push(report.discover === "synced" ? "Next: candidates for every gap are in youiyoux gaps --json and on the Gaps page of youiyoux ui; youiyoux discover show <id> for one. Nothing is installed." : "Next: youiyoux discover sync, then run youiyoux gaps again for candidates.");
|
|
2405
|
+
return lines.join("\n");
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2372
2408
|
// ../cli/src/inventory.ts
|
|
2373
2409
|
var FAMILIES_PER_ROLE = 6;
|
|
2374
2410
|
function shapeLabel(shape) {
|
|
@@ -2405,7 +2441,7 @@ function storyLines(report) {
|
|
|
2405
2441
|
function storyNext(report, outFile) {
|
|
2406
2442
|
const stories = report.projects.flatMap((p) => p.stories ? [p.stories] : []);
|
|
2407
2443
|
if (stories.length === 0) return null;
|
|
2408
|
-
const open = `youiyoux ui --inventory ${outFile
|
|
2444
|
+
const open = `youiyoux ui --inventory ${outFile}`;
|
|
2409
2445
|
const skipped = stories.reduce((n, s) => n + s.skipped.length, 0);
|
|
2410
2446
|
if (skipped > 0) return `Next: rewrite the skipped stories as CSF 3 args stories of a component in this project (or fix the file), run youiyoux inventory again, then see them beside their real uses with ${open}`;
|
|
2411
2447
|
return `Next: see the stories beside their real uses with ${open}`;
|
|
@@ -2453,7 +2489,7 @@ function renderInventory(report, outFile) {
|
|
|
2453
2489
|
}
|
|
2454
2490
|
const stories = storyLines(report);
|
|
2455
2491
|
if (stories.length > 0) lines.push("", ...stories);
|
|
2456
|
-
|
|
2492
|
+
lines.push(style.dim(`inventory \u2192 ${outFile}`));
|
|
2457
2493
|
const next2 = storyNext(report, outFile);
|
|
2458
2494
|
if (next2) lines.push(next2);
|
|
2459
2495
|
return lines.join("\n");
|
|
@@ -2487,7 +2523,7 @@ function renderAdoptionRequest(request) {
|
|
|
2487
2523
|
// ../cli/src/report/run.ts
|
|
2488
2524
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2489
2525
|
import { mkdir as mkdir4, rename as rename2, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
|
|
2490
|
-
import { join as join21, relative as
|
|
2526
|
+
import { join as join21, relative as relative7, resolve as resolve11 } from "node:path";
|
|
2491
2527
|
|
|
2492
2528
|
// ../cli/src/report/card.ts
|
|
2493
2529
|
import { deflateSync } from "node:zlib";
|
|
@@ -2801,7 +2837,7 @@ function prefixOf(tokens) {
|
|
|
2801
2837
|
const [best, n] = [...counts].sort((a, b) => b[1] - a[1])[0] ?? [null, 0];
|
|
2802
2838
|
return best && n >= tokens.length / 4 ? best : null;
|
|
2803
2839
|
}
|
|
2804
|
-
function scaleTokens(index, file, usage
|
|
2840
|
+
function scaleTokens(index, file, usage) {
|
|
2805
2841
|
const value = (t) => `${t.id.split(".")[0]}\0${JSON.stringify(t.light)}`;
|
|
2806
2842
|
const prefix = prefixOf(index.tokens);
|
|
2807
2843
|
const rank = new Map(index.tokens.map((t) => [t, genericity(t, prefix)]));
|
|
@@ -2815,7 +2851,7 @@ function scaleTokens(index, file, usage = { count: /* @__PURE__ */ new Map(), by
|
|
|
2815
2851
|
const component = componentOf(t);
|
|
2816
2852
|
return component !== null && !general.has(value(t)) && new RegExp(`(?:^|[/._-])${component}s?(?:[/._-]|$)`).test(path);
|
|
2817
2853
|
});
|
|
2818
|
-
const score = (t) => [rank.get(t), Number(local.has(cssName(t))), Number(!usage.aliases
|
|
2854
|
+
const score = (t) => [rank.get(t), Number(local.has(cssName(t))), Number(!usage.aliases.has(cssName(t))), usage.count.get(cssName(t)) ?? 0, -t.cssName.length];
|
|
2819
2855
|
const higher = (a, b) => {
|
|
2820
2856
|
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return b[i] - a[i];
|
|
2821
2857
|
return 0;
|
|
@@ -2826,21 +2862,20 @@ function scaleTokens(index, file, usage = { count: /* @__PURE__ */ new Map(), by
|
|
|
2826
2862
|
var escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2827
2863
|
async function reportTokens(module, tokens, usage) {
|
|
2828
2864
|
if (isShowcase(module.file) || module.lang !== "css" && module.jsx.length === 0) return [];
|
|
2829
|
-
const { parseModule: parseModule2 } = await import("./src-ZS76UD7E.js");
|
|
2830
2865
|
let source = withoutVariables(module.source);
|
|
2831
2866
|
if (module.lang !== "css") {
|
|
2832
2867
|
source = maskFunctions(source, /(?:repeating-)?(?:linear|radial|conic)-gradient\s*\(/gi);
|
|
2833
2868
|
source = source.replace(/(?<=[[,]\s*)(["'`])(?:#[0-9a-fA-F]{3,8}|(?:rgba?|hsla?)\([^)\n]*\))\1(?=\s*[,\]])/g, (literal) => " ".repeat(literal.length));
|
|
2834
2869
|
}
|
|
2835
|
-
const clean = source === module.source ? module : await
|
|
2870
|
+
const clean = source === module.source ? module : await parseModule(module.file, source);
|
|
2836
2871
|
if (!clean) return [];
|
|
2837
2872
|
const index = scaleTokens(tokens, module.file, usage);
|
|
2838
2873
|
return hardcodedValues(clean, index, { isTokenSource: module.lang === "css", added: () => true }).filter((f) => {
|
|
2839
2874
|
if (/\bvar\(/.test(f.text)) return false;
|
|
2840
2875
|
const kind = /^Hardcoded (colour|radius|spacing|type)\b/.exec(f.message)?.[1];
|
|
2841
2876
|
const groups = kind === "colour" ? /^(color|colour)\./ : kind === "radius" ? /^(radius|radii|rounded)\./ : kind === "spacing" ? /^(space|spacing)\./ : /^(type|typography|fontSize)\./;
|
|
2842
|
-
const
|
|
2843
|
-
return
|
|
2877
|
+
const named2 = index.tokens.filter((t) => new RegExp(`[\\s(]${escape(t.id)}(?=[\\s,)]|$)`).test(f.fix));
|
|
2878
|
+
return named2.length > 0 && named2.every((t) => groups.test(t.id));
|
|
2844
2879
|
});
|
|
2845
2880
|
}
|
|
2846
2881
|
function publicLook(caption) {
|
|
@@ -2850,12 +2885,12 @@ function publicLook(caption) {
|
|
|
2850
2885
|
}
|
|
2851
2886
|
|
|
2852
2887
|
// ../cli/src/report/groups.ts
|
|
2853
|
-
import { existsSync as
|
|
2888
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
2854
2889
|
import { join as join16 } from "node:path";
|
|
2855
2890
|
|
|
2856
2891
|
// ../cli/src/report/imports.ts
|
|
2857
2892
|
import { realpathSync } from "node:fs";
|
|
2858
|
-
import { dirname as dirname9, join as join15, relative as
|
|
2893
|
+
import { dirname as dirname9, join as join15, relative as relative6, resolve as resolve9 } from "node:path";
|
|
2859
2894
|
import ts from "typescript";
|
|
2860
2895
|
var resolvers = /* @__PURE__ */ new Map();
|
|
2861
2896
|
function resolverOf(root) {
|
|
@@ -2874,7 +2909,7 @@ function clearImportCache() {
|
|
|
2874
2909
|
}
|
|
2875
2910
|
async function verifiedImport(root, keeper, from, suggested) {
|
|
2876
2911
|
const { options, cache } = resolverOf(root);
|
|
2877
|
-
let local =
|
|
2912
|
+
let local = relative6(dirname9(join15(root, from)), join15(root, keeper)).replaceAll("\\", "/").replace(/\.(tsx?|jsx?|mjs)$/, "");
|
|
2878
2913
|
if (!local.startsWith(".")) local = `./${local}`;
|
|
2879
2914
|
for (const specifier of /* @__PURE__ */ new Set([suggested, local])) {
|
|
2880
2915
|
const found = ts.resolveModuleName(specifier, resolve9(root, from), options, ts.sys, cache).resolvedModule?.resolvedFileName;
|
|
@@ -2992,7 +3027,6 @@ var SVG_TAGS = /* @__PURE__ */ new Set(["svg", "path", "g", "circle", "rect", "l
|
|
|
2992
3027
|
var NEAR_IDENTICAL = 0.5;
|
|
2993
3028
|
var MIN_STRUCTURE = 6;
|
|
2994
3029
|
var GENERIC_IDENTICAL = 0.85;
|
|
2995
|
-
var plural2 = (count, word, many = `${word}s`) => `${count} ${count === 1 ? word : many}`;
|
|
2996
3030
|
var repoPath = (dir, file) => dir === "." ? file : `${dir}/${file}`;
|
|
2997
3031
|
var kebab = (name) => name.replace(/[a-z0-9](?=[A-Z])/g, "$&-").toLowerCase();
|
|
2998
3032
|
function shapeWords(shape) {
|
|
@@ -3158,8 +3192,8 @@ async function nearDuplicateGroups(input) {
|
|
|
3158
3192
|
actions.push(`replace the hand-styled ${tag}${at.length > 1 ? "s" : ""} at line${at.length > 1 ? "s" : ""} ${at.join(", ")} with ${keeperName}`);
|
|
3159
3193
|
}
|
|
3160
3194
|
for (const v of list.filter((c) => c.kind === "component")) {
|
|
3161
|
-
const
|
|
3162
|
-
actions.push(v.name === keeperName ? `remove its own ${v.name} and use the kept one` : `build ${
|
|
3195
|
+
const named2 = v.name === "default" ? "its default export" : v.name;
|
|
3196
|
+
actions.push(v.name === keeperName ? `remove its own ${v.name} and use the kept one` : `build ${named2} on ${keeperName} instead of restyling its own element`);
|
|
3163
3197
|
}
|
|
3164
3198
|
if (actions.length > 0) steps.push(`In ${file}, ${actions.join("; ")} (${use})`);
|
|
3165
3199
|
for (const v of list.filter((c) => c.kind === "class")) steps.push(`In ${file}, ${v.name} copies ${keeperName}'s shape: use ${keeperName} where ${v.name} is used`);
|
|
@@ -3168,13 +3202,13 @@ async function nearDuplicateGroups(input) {
|
|
|
3168
3202
|
const copyIds = new Set(copies.map((v) => v.id));
|
|
3169
3203
|
const counted = members.filter((m) => copyIds.has(m.version.id));
|
|
3170
3204
|
const home = [...counted.reduce((n, m) => n.set(m.version.facts, (n.get(m.version.facts) ?? 0) + 1), /* @__PURE__ */ new Map())].sort((a, b) => b[1] - a[1])[0]?.[0] ?? keeperFacts;
|
|
3171
|
-
const
|
|
3172
|
-
const lead = kept ? `Keep ${keeperName} (${
|
|
3205
|
+
const where2 = keeperFacts.project.name && importedBy(keeperFacts) > 0 ? `${keeperFacts.project.name}, ${keeperFile}` : keeperFile;
|
|
3206
|
+
const lead = kept ? `Keep ${keeperName} (${where2})` : `Create ${keeperName} in ${keeperFile} (a new file in ${extract.where}) with this shape`;
|
|
3173
3207
|
const [one, many] = KIND_WORDS[kind];
|
|
3174
3208
|
const components = new Set([kept, ...copies].flatMap((v) => v?.kind === "component" ? [v.id] : [])).size;
|
|
3175
3209
|
findings2.push(finding(
|
|
3176
3210
|
home.project.dir,
|
|
3177
|
-
`${
|
|
3211
|
+
`${plural(members.length, one, many)} look the same (${shapeWords(family.shape)}) but are built ${sides.length} ways`,
|
|
3178
3212
|
counted.length,
|
|
3179
3213
|
[...copies].sort((a, b) => path(a).localeCompare(path(b)) || a.line - b.line).slice(0, SITES_PER_FINDING).map((v) => ({ at: `${path(v)}:${v.line}`, file: path(v), line: v.line })),
|
|
3180
3214
|
lead,
|
|
@@ -3187,7 +3221,7 @@ async function nearDuplicateGroups(input) {
|
|
|
3187
3221
|
}
|
|
3188
3222
|
function finding(project2, title, count, sites, lead, steps, exported) {
|
|
3189
3223
|
const shown2 = steps.slice(0, STEPS_SHOWN);
|
|
3190
|
-
const more = steps.length > shown2.length ? `; and ${
|
|
3224
|
+
const more = steps.length > shown2.length ? `; and ${plural(steps.length - shown2.length, "more file")} (listed in the report)` : "";
|
|
3191
3225
|
return { kind: "near-duplicate", project: project2, title, detail: null, count, sites, lead, steps, next: `${lead}. ${shown2.join(". ")}${more}`, exported };
|
|
3192
3226
|
}
|
|
3193
3227
|
function commonDir(files) {
|
|
@@ -3289,7 +3323,7 @@ function factsOf(project2, id, modules, eligible, workspaceNames, dependencies,
|
|
|
3289
3323
|
if (dirs.has(file.slice(0, at))) break;
|
|
3290
3324
|
dirs.add(file.slice(0, at));
|
|
3291
3325
|
}
|
|
3292
|
-
const roots = [...dirs].filter((dir) =>
|
|
3326
|
+
const roots = [...dirs].filter((dir) => existsSync3(join16(project2.root, dir, "package.json"))).sort((a, b) => b.length - a.length);
|
|
3293
3327
|
const nested = (file) => roots.find((dir) => file.startsWith(`${dir}/`)) ?? "";
|
|
3294
3328
|
return { project: project2, id, dependencies, packageImports, jsxCounts, eligible, twins, dirs, importer, packageOf: nested };
|
|
3295
3329
|
}
|
|
@@ -3299,13 +3333,6 @@ var import_yaml2 = __toESM(require_dist(), 1);
|
|
|
3299
3333
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
3300
3334
|
import { basename as basename2, join as join17, resolve as resolve10 } from "node:path";
|
|
3301
3335
|
var UI_FILES = ["**/*.{tsx,jsx,astro,vue,svelte}"];
|
|
3302
|
-
async function readJson3(file) {
|
|
3303
|
-
try {
|
|
3304
|
-
return JSON.parse(await readFile5(file, "utf8"));
|
|
3305
|
-
} catch {
|
|
3306
|
-
return null;
|
|
3307
|
-
}
|
|
3308
|
-
}
|
|
3309
3336
|
var strings = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
3310
3337
|
async function workspaceGlobs(root, pkg) {
|
|
3311
3338
|
const pnpm = await readFile5(join17(root, "pnpm-workspace.yaml"), "utf8").catch(() => null);
|
|
@@ -3323,7 +3350,7 @@ async function workspaceGlobs(root, pkg) {
|
|
|
3323
3350
|
var hasUi = async (dir) => (await findFiles(dir, UI_FILES, DEFAULT_EXCLUDE)).length > 0;
|
|
3324
3351
|
async function findProjects(dir) {
|
|
3325
3352
|
const root = resolve10(dir);
|
|
3326
|
-
const pkg = await
|
|
3353
|
+
const pkg = await readJson(join17(root, "package.json"));
|
|
3327
3354
|
const name = typeof pkg?.name === "string" && pkg.name.trim() ? pkg.name.trim() : basename2(root);
|
|
3328
3355
|
const single = { dir: ".", root, name: typeof pkg?.name === "string" ? pkg.name : null };
|
|
3329
3356
|
const workspace = await workspaceGlobs(root, pkg);
|
|
@@ -3339,7 +3366,7 @@ async function findProjects(dir) {
|
|
|
3339
3366
|
skipped.push(member);
|
|
3340
3367
|
continue;
|
|
3341
3368
|
}
|
|
3342
|
-
const memberPkg = await
|
|
3369
|
+
const memberPkg = await readJson(join17(memberRoot, "package.json"));
|
|
3343
3370
|
projects.push({ dir: member, root: memberRoot, name: typeof memberPkg?.name === "string" ? memberPkg.name : null });
|
|
3344
3371
|
}
|
|
3345
3372
|
if (projects.length === 0) return { root, name, layout: `one project (${workspace.source} names no package with UI files)`, projects: [single], skipped };
|
|
@@ -3349,13 +3376,10 @@ async function findProjects(dir) {
|
|
|
3349
3376
|
// ../cli/src/report/model.ts
|
|
3350
3377
|
var REPORT_SCHEMA = "youiyoux.report/1";
|
|
3351
3378
|
var NPX = `npx ${COMMAND}`;
|
|
3352
|
-
var SITES_PER_FINDING2 = 6;
|
|
3353
3379
|
var TOP_COMPONENTS = 10;
|
|
3354
3380
|
var TOP_FIXES = 5;
|
|
3355
3381
|
var AGENT_LINE = `Set up youiyoux for your agent: run ${NPX} agents install, and your coding agent looks up these components before it writes a new one.`;
|
|
3356
|
-
var plural3 = (count, word, many = `${word}s`) => `${count} ${count === 1 ? word : many}`;
|
|
3357
3382
|
var sentence = (text) => text.charAt(0).toUpperCase() + text.slice(1);
|
|
3358
|
-
var repoPath2 = (dir, file) => dir === "." ? file : `${dir}/${file}`;
|
|
3359
3383
|
var errorText = (error) => error instanceof Error ? error.message : String(error);
|
|
3360
3384
|
var Lines = class {
|
|
3361
3385
|
files = /* @__PURE__ */ new Map();
|
|
@@ -3392,7 +3416,7 @@ async function modulesOf(project2, target, failed) {
|
|
|
3392
3416
|
const module = await parseModule(file, await readFile6(join18(project2.root, file), "utf8"));
|
|
3393
3417
|
if (module) modules.push(module);
|
|
3394
3418
|
} catch (error) {
|
|
3395
|
-
failed.push(`${
|
|
3419
|
+
failed.push(`${repoPath(project2.dir, file)}: ${errorText(error)}`);
|
|
3396
3420
|
}
|
|
3397
3421
|
}
|
|
3398
3422
|
return modules;
|
|
@@ -3406,8 +3430,9 @@ async function grouped(project2, found, lines, describe) {
|
|
|
3406
3430
|
const findings2 = [];
|
|
3407
3431
|
for (const list of groups.values()) {
|
|
3408
3432
|
const first = list[0];
|
|
3409
|
-
const sites = await Promise.all(list.slice(0,
|
|
3410
|
-
|
|
3433
|
+
const sites = await Promise.all(list.slice(0, SITES_PER_FINDING).map((f) => lines.site(`${repoPath(project2.dir, f.file)}:${f.line}`, repoPath(project2.dir, f.file), f.line)));
|
|
3434
|
+
const { title, next: next2 } = describe(first, list.length);
|
|
3435
|
+
findings2.push({ kind: first.kind, project: project2.dir, count: list.length, sites, title, detail: null, next: next2 });
|
|
3411
3436
|
}
|
|
3412
3437
|
return findings2.sort((a, b) => b.count - a.count || a.title.localeCompare(b.title));
|
|
3413
3438
|
}
|
|
@@ -3437,8 +3462,8 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
|
|
|
3437
3462
|
if (isShowcase(at)) showcaseFiles.add(at.replace(/:\d+(?::\d+)?$/, ""));
|
|
3438
3463
|
}
|
|
3439
3464
|
const live = (items) => items.flatMap((item) => {
|
|
3440
|
-
const
|
|
3441
|
-
return item.where?.length && !
|
|
3465
|
+
const where2 = item.where?.filter((at) => !isShowcase(at));
|
|
3466
|
+
return item.where?.length && !where2?.length ? [] : [{ ...item, ...where2 ? { where: where2, count: Math.min(item.count, where2.length) } : {} }];
|
|
3442
3467
|
});
|
|
3443
3468
|
const signals = live(scan.signals);
|
|
3444
3469
|
const used = new Set(signals.filter((s) => s.kind !== "package" && s.kind !== "lockfile").map((s) => s.library));
|
|
@@ -3456,14 +3481,14 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
|
|
|
3456
3481
|
for (const look of looks) {
|
|
3457
3482
|
if (look.recipe?.kind !== "component") continue;
|
|
3458
3483
|
const key2 = `${look.recipe.file}#${look.recipe.export}`;
|
|
3459
|
-
const entry = byComponent.get(key2) ?? { name: look.recipe.export, file:
|
|
3484
|
+
const entry = byComponent.get(key2) ?? { name: look.recipe.export, file: repoPath(project2.dir, look.recipe.file), roles: [] };
|
|
3460
3485
|
entry.roles.push(look.role);
|
|
3461
3486
|
byComponent.set(key2, entry);
|
|
3462
3487
|
}
|
|
3463
3488
|
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);
|
|
3464
3489
|
const inventoryProject = inventory?.projects[index];
|
|
3465
3490
|
if (inventoryError) incomplete.push(`The component inventory could not be built (${inventoryError}); run ${NPX} inventory --project ${project2.dir} to see why`);
|
|
3466
|
-
for (const f of inventoryProject?.failed ?? []) failed.push(`${
|
|
3491
|
+
for (const f of inventoryProject?.failed ?? []) failed.push(`${repoPath(project2.dir, f.file)}: ${f.reason}`);
|
|
3467
3492
|
let hardcoded = [];
|
|
3468
3493
|
let offLibrary = [];
|
|
3469
3494
|
const everyLine = () => true;
|
|
@@ -3475,7 +3500,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
|
|
|
3475
3500
|
const found = (await Promise.all(modules.map((module) => reportTokens(module, tokens, usage)))).flat();
|
|
3476
3501
|
hardcoded = await grouped(project2, found, lines, (first, count) => {
|
|
3477
3502
|
const { word, value, tokens: names } = hardcodedValue(first);
|
|
3478
|
-
return { title: `${sentence(word)} ${value} is hardcoded in ${
|
|
3503
|
+
return { title: `${sentence(word)} ${value} is hardcoded in ${plural(count, "place")}; the theme already has it as ${names}`, next: `${sentence(first.fix)} in each place` };
|
|
3479
3504
|
});
|
|
3480
3505
|
} catch (error) {
|
|
3481
3506
|
incomplete.push(`Hardcoded values were not checked (${errorText(error)}); run ${NPX} check --diff to see why`);
|
|
@@ -3487,7 +3512,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
|
|
|
3487
3512
|
const kitDirs = [changeProject.config.componentsDir, ...inventory?.kits.filter((k) => k.project === projectId && k.kind === "cva-folder").map((k) => k.path) ?? []];
|
|
3488
3513
|
const exempt = (file) => kitDirs.some((dir) => file === dir || file.startsWith(`${dir}/`));
|
|
3489
3514
|
const found = modules.flatMap((module) => offLibraryImports(module, { added: everyLine, classify, chosen, exempt }));
|
|
3490
|
-
offLibrary = await grouped(project2, found, lines, (first) => ({ title: first.message.replace(/^This imports/, "Imports"),
|
|
3515
|
+
offLibrary = await grouped(project2, found, lines, (first) => ({ title: first.message.replace(/^This imports/, "Imports"), next: sentence(first.fix) }));
|
|
3491
3516
|
} catch (error) {
|
|
3492
3517
|
incomplete.push(`Library choices were not compared (${errorText(error)}); run ${NPX} detect ${project2.dir} to see why`);
|
|
3493
3518
|
}
|
|
@@ -3504,7 +3529,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
|
|
|
3504
3529
|
libraries,
|
|
3505
3530
|
unused,
|
|
3506
3531
|
definitions: inventory ? inventory.definitions.filter((d) => d.project === projectId && !isShowcase(d.file)).length : 0,
|
|
3507
|
-
kits: inventory ? inventory.kits.filter((k) => k.project === projectId).map((k) => ({ path:
|
|
3532
|
+
kits: inventory ? inventory.kits.filter((k) => k.project === projectId).map((k) => ({ path: repoPath(project2.dir, k.path), kind: k.kind === "cva-folder" ? "CVA components" : "CSS classes", members: k.members.length })) : [],
|
|
3508
3533
|
roles,
|
|
3509
3534
|
uses: looks.length,
|
|
3510
3535
|
components,
|
|
@@ -3517,7 +3542,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
|
|
|
3517
3542
|
return { section, facts };
|
|
3518
3543
|
}
|
|
3519
3544
|
function topNext(f) {
|
|
3520
|
-
const text = f.lead && f.steps ? `${f.lead}${f.steps.length > 1 ? `, then change ${
|
|
3545
|
+
const text = f.lead && f.steps ? `${f.lead}${f.steps.length > 1 ? `, then change ${plural(f.steps.length, "file")} (each listed in the report)` : `; ${f.steps[0].charAt(0).toLowerCase()}${f.steps[0].slice(1)}`}` : f.next;
|
|
3521
3546
|
return `${text.charAt(0).toLowerCase()}${text.slice(1).replace(/\.$/, "")}`;
|
|
3522
3547
|
}
|
|
3523
3548
|
var DUPLICATE_RULE = "Duplicated components: groups of 2+ exported components that look alike or share a name.";
|
|
@@ -3527,10 +3552,10 @@ function summaryLines(model) {
|
|
|
3527
3552
|
const top = model.projects.flatMap((p) => p.components).sort((a, b) => b.uses - a.uses)[0];
|
|
3528
3553
|
const first = model.topFixes[0];
|
|
3529
3554
|
return [
|
|
3530
|
-
`${model.repo}: ${
|
|
3531
|
-
`Components: ${t.definitions} defined, ${
|
|
3532
|
-
`Near-duplicates: ${
|
|
3533
|
-
`Hardcoded values: ${
|
|
3555
|
+
`${model.repo}: ${plural(t.projects, "project")}, ${plural(t.files, "file")} scanned; UI libraries: ${t.libraries.length > 0 ? t.libraries.join(", ") : "none found"}.`,
|
|
3556
|
+
`Components: ${t.definitions} defined, ${plural(t.uses, "button, input, card or other UI element", "buttons, inputs, cards and other UI elements")} in use${top ? `; most used: ${top.name} (${plural(top.uses, "use")})` : ""}.`,
|
|
3557
|
+
`Near-duplicates: ${plural(t.nearDuplicates, "group")} of UI that looks the same but is built more than once; ${t.componentDuplicates} of them between exported components.`,
|
|
3558
|
+
`Hardcoded values: ${plural(t.hardcoded, "colour, spacing or radius", "colours, spacings and radii")} a theme token already holds; off-library imports: ${t.offLibrary}.`,
|
|
3534
3559
|
first ? `Top fix: ${first.title.replace(/\.$/, "")}; ${topNext(first)}.` : `Nothing to fix first; next: ${NPX} agents install, so your agent keeps it that way.`
|
|
3535
3560
|
];
|
|
3536
3561
|
}
|
|
@@ -3565,7 +3590,7 @@ async function buildReportModel(dir, options = {}) {
|
|
|
3565
3590
|
offLibrary: projects.reduce((n, p) => n + p.offLibrary.reduce((m, f) => m + f.count, 0), 0)
|
|
3566
3591
|
};
|
|
3567
3592
|
const topFixes = [...near, ...projects.flatMap((p) => [...p.hardcoded, ...p.offLibrary])].sort((a, b) => b.count - a.count || a.kind.localeCompare(b.kind) || a.title.localeCompare(b.title)).slice(0, TOP_FIXES);
|
|
3568
|
-
const layout = repo.skipped.length > 0 ? `${repo.layout}; ${
|
|
3593
|
+
const layout = repo.skipped.length > 0 ? `${repo.layout}; ${plural(repo.skipped.length, "package")} without UI files left out (${repo.skipped.join(", ")})` : repo.layout;
|
|
3569
3594
|
const model = {
|
|
3570
3595
|
schema: REPORT_SCHEMA,
|
|
3571
3596
|
repo: repo.name,
|
|
@@ -3587,7 +3612,6 @@ async function buildReportModel(dir, options = {}) {
|
|
|
3587
3612
|
|
|
3588
3613
|
// ../cli/src/report/html.ts
|
|
3589
3614
|
var escape2 = (text) => String(text).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3590
|
-
var plural4 = (count, word, many = `${word}s`) => `${count} ${count === 1 ? word : many}`;
|
|
3591
3615
|
var anchor = (dir) => `p-${dir === "." ? "root" : dir.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "")}`;
|
|
3592
3616
|
var CSS = `
|
|
3593
3617
|
:root{color-scheme:light dark;--canvas:#f4f5f7;--surface:#fcfcfd;--raised:#f6f7f9;--sunken:#f8f9fa;--line:#e3e4e8;--line-strong:#d0d2d8;--ink:#16171a;--ink-2:#3d4049;--muted:#6b6f78;--faint:#9ea2ab;--accent:#5e6ad2;--accent-soft:#eceefa;--success:#4f7a69;--success-soft:#eaf1ee;--danger:#ad5a43;--danger-soft:#f7ece8;--neutral-soft:#eef0f3;--k-dup:var(--accent);--k-hard:color-mix(in oklch,var(--accent) 55%,var(--surface));--k-off:color-mix(in oklch,var(--accent) 25%,var(--surface));--shadow:0 1px 2px rgba(16,17,20,.04)}
|
|
@@ -3765,7 +3789,6 @@ var rich = (text) => escape2(text).replace(FACT, (fact) => `<span class="mono">$
|
|
|
3765
3789
|
function next(text) {
|
|
3766
3790
|
return `<p class="next"><strong>Next:</strong> ${rich(text.replace(/\.$/, ""))}.</p>`;
|
|
3767
3791
|
}
|
|
3768
|
-
var STEPS_SHOWN2 = 5;
|
|
3769
3792
|
var KIND = { "near-duplicate": "Duplicate", "hardcoded-value": "Hardcoded", "off-library": "Off-library" };
|
|
3770
3793
|
function splitMeasures(title) {
|
|
3771
3794
|
const m = /\s*\(([^()]*\d[^()]*)\)/.exec(title);
|
|
@@ -3789,18 +3812,18 @@ function shots(f, looks) {
|
|
|
3789
3812
|
}
|
|
3790
3813
|
function finding2(f, showProject, looks) {
|
|
3791
3814
|
const { text, measures } = f.kind === "near-duplicate" ? splitMeasures(f.title) : { text: f.title, measures: [] };
|
|
3792
|
-
const
|
|
3793
|
-
const head = `<div class="head"><span class="count">${f.count}<small>${f.count === 1 ? "place" : "places"}</small></span><div class="what"><p class="title">${rich(text)}</p>${
|
|
3815
|
+
const where2 = showProject && f.project !== "." ? `<p class="muted">in <span class="mono">${escape2(f.project)}</span></p>` : "";
|
|
3816
|
+
const head = `<div class="head"><span class="count">${f.count}<small>${f.count === 1 ? "place" : "places"}</small></span><div class="what"><p class="title">${rich(text)}</p>${where2}</div><span class="chip">${f.kind === "hardcoded-value" ? swatch(f.title) : ""}${KIND[f.kind]}</span></div>`;
|
|
3794
3817
|
const specs = measures.length > 0 ? `<ul class="specs">${measures.map((m) => `<li>${escape2(m)}</li>`).join("")}</ul>` : "";
|
|
3795
3818
|
if (f.lead && f.steps && f.steps.length > 0) {
|
|
3796
3819
|
const item = (step) => `<li>${rich(step.replace(/\.$/, ""))}</li>`;
|
|
3797
|
-
const rest = f.steps.slice(
|
|
3798
|
-
const more2 = rest.length > 0 ? `<details class="more"><summary>and ${
|
|
3799
|
-
return `<li class="finding" data-kind="${f.kind}">${head}${specs}<div class="compare">${next(f.lead)}<ol class="steps">${f.steps.slice(0,
|
|
3820
|
+
const rest = f.steps.slice(STEPS_SHOWN);
|
|
3821
|
+
const more2 = rest.length > 0 ? `<details class="more"><summary>and ${plural(rest.length, "more file")}</summary><ol class="steps" start="${STEPS_SHOWN + 1}">${rest.map(item).join("")}</ol></details>` : "";
|
|
3822
|
+
return `<li class="finding" data-kind="${f.kind}">${head}${specs}<div class="compare">${next(f.lead)}<ol class="steps">${f.steps.slice(0, STEPS_SHOWN).map(item).join("")}</ol>${more2}${shots(f, looks)}</div></li>`;
|
|
3800
3823
|
}
|
|
3801
3824
|
const code = f.sites.some((s) => s.code);
|
|
3802
3825
|
const sites = f.sites.map((s) => `<li><code>${escape2(s.at)}</code>${s.code ? `<pre>${escape2(s.code)}</pre>` : ""}</li>`).join("");
|
|
3803
|
-
const more = f.count > f.sites.length ? `<li>+ ${
|
|
3826
|
+
const more = f.count > f.sites.length ? `<li>+ ${plural(f.count - f.sites.length, "more place")}</li>` : "";
|
|
3804
3827
|
return `<li class="finding" data-kind="${f.kind}">${head}${specs}<ul class="sites${code ? " code" : ""}">${sites}${more}</ul>${next(f.next)}</li>`;
|
|
3805
3828
|
}
|
|
3806
3829
|
function findings(list, none, after, looks) {
|
|
@@ -3812,29 +3835,29 @@ function panel(name, id, title, aside, body, level = "h3") {
|
|
|
3812
3835
|
}
|
|
3813
3836
|
function projectSection(p, many, looks) {
|
|
3814
3837
|
const id = anchor(p.dir);
|
|
3815
|
-
const
|
|
3838
|
+
const where2 = p.dir === "." ? "" : ` --project ${p.dir}`;
|
|
3816
3839
|
const libraries = p.libraries.length === 0 ? `<div class="body"><p class="none">No UI library found.</p>${next(`if it uses one, run ${NPX} detect ${p.dir} to see what was read`)}</div>` : `<div class="body"><ul class="rows">${p.libraries.map((l) => `<li><span>${escape2(l.name)}${l.for.length > 0 ? ` <small>${escape2(l.for.join(", "))}</small>` : ""}</span><span>${escape2(l.confidence)} confidence</span></li>`).join("")}</ul>${p.unused.length > 0 ? `<p class="muted">Declared but never imported (not counted): ${escape2(p.unused.join(", "))}</p>` : ""}${next(`build new UI with these, not a new library: ${NPX} agents install tells your agent so`)}</div>`;
|
|
3817
|
-
const kits = p.kits.length > 0 ? `<p class="muted">Kits: ${p.kits.map((k) => `<code>${escape2(k.path)}</code> (${escape2(k.kind)}, ${
|
|
3818
|
-
const components = p.components.length > 0 ? `<h4>Most used</h4><ul class="rows">${p.components.map((c) => `<li><span>${escape2(c.name)}<small>${escape2(c.file)}</small></span><span>${
|
|
3819
|
-
const inventory = `<div class="body"><p>${
|
|
3820
|
-
const unread = p.failed.length > 0 ? `<details class="incomplete more"><summary>${
|
|
3821
|
-
const count = (list) => list.length === 0 ? "None" : `${
|
|
3840
|
+
const kits = p.kits.length > 0 ? `<p class="muted">Kits: ${p.kits.map((k) => `<code>${escape2(k.path)}</code> (${escape2(k.kind)}, ${plural(k.members, "member")})`).join(", ")}</p>` : "";
|
|
3841
|
+
const components = p.components.length > 0 ? `<h4>Most used</h4><ul class="rows">${p.components.map((c) => `<li><span>${escape2(c.name)}<small>${escape2(c.file)}</small></span><span>${plural(c.uses, "use")}</span></li>`).join("")}</ul>` : "";
|
|
3842
|
+
const inventory = `<div class="body"><p>${plural(p.definitions, "component")} defined in ${plural(p.files, "file")}; ${plural(p.uses, "UI element")} in use (buttons, links styled as buttons, inputs, cards, badges, dialogs, tabs).</p>${p.roles.length > 0 ? `<ul class="rows">${p.roles.map((r) => `<li><span class="mono">${escape2(r.role)}</span><span>${plural(r.uses, "use")} \xB7 ${plural(r.families, "shape")}</span></li>`).join("")}</ul>` : ""}${kits}${components}${next(`see every look grouped by shape with ${NPX} inventory${where2}`)}</div>`;
|
|
3843
|
+
const unread = p.failed.length > 0 ? `<details class="incomplete more"><summary>${plural(p.failed.length, "file")} not fully read; what is in them is unknown</summary><ul class="unread">${p.failed.map((f) => `<li>${rich(f)}</li>`).join("")}</ul></details>` : "";
|
|
3844
|
+
const count = (list) => list.length === 0 ? "None" : `${plural(list.length, "finding")} \xB7 ${plural(list.reduce((n, f) => n + f.count, 0), "place")}`;
|
|
3822
3845
|
return `<section class="project" data-section="project" data-project="${escape2(p.dir)}" id="${id}">
|
|
3823
3846
|
<h2>${escape2(many || p.dir !== "." ? p.dir : "This repo")}${p.name && p.name !== p.dir ? ` <small>${escape2(p.name)}</small>` : ""}</h2>
|
|
3824
3847
|
${p.incomplete.length > 0 || p.failed.length > 0 ? `<div class="unread-at" id="${id}-unread">${p.incomplete.map((i) => `<p class="incomplete">${escape2(i)}</p>`).join("")}${unread}</div>` : ""}
|
|
3825
3848
|
${panel("near-duplicates", `${id}-near`, "Near-duplicate groups", count(p.nearDuplicates), `<div class="body"><p class="muted">Components that look the same (height, padding, corners, text size and weight) but are built more than once.</p></div>${findings(p.nearDuplicates, "None found: each look is built once.", `keep it that way: ${NPX} agents install has your agent reuse these components`, looks)}`)}
|
|
3826
3849
|
${panel("hardcoded", `${id}-hardcoded`, "Hardcoded values that should be tokens", count(p.hardcoded), `<div class="body"><p class="muted">Colours, spacing and radii written as literals although a theme variable holds the same value.</p></div>${findings(p.hardcoded, "None found.", `run ${NPX} check --diff on each change to keep new literals out`, looks)}`)}
|
|
3827
3850
|
${panel("off-library", `${id}-off`, "Off-library imports", count(p.offLibrary), `<div class="body"><p class="muted">Imports of an element from a library other than the one the rest of the code uses for it.</p></div>${findings(p.offLibrary, "None found.", `run ${NPX} check --diff on each change to catch a second library early`, looks)}`)}
|
|
3828
|
-
${panel("inventory", `${id}-inventory`, "Component inventory", `${
|
|
3829
|
-
${panel("libraries", `${id}-libraries`, "UI libraries", p.libraries.length > 0 ?
|
|
3851
|
+
${panel("inventory", `${id}-inventory`, "Component inventory", `${plural(p.definitions, "component")} \xB7 ${plural(p.uses, "use")}`, inventory)}
|
|
3852
|
+
${panel("libraries", `${id}-libraries`, "UI libraries", p.libraries.length > 0 ? plural(p.libraries.length, "library", "libraries") : "", libraries)}
|
|
3830
3853
|
${p.notes.map((n) => `<p class="muted">${escape2(n.replace(/\.?$/, "."))}</p>`).join("")}
|
|
3831
3854
|
</section>`;
|
|
3832
3855
|
}
|
|
3833
3856
|
function looksSection(model) {
|
|
3834
3857
|
const drawn = model.projects.flatMap((p) => p.rendered.map((r) => ({ ...r, project: p.dir })));
|
|
3835
|
-
const body = drawn.length > 0 ? `<div class="looks">${drawn.map((r) => `<figure data-component="${escape2(r.component)}"><figcaption>${escape2(r.component)} <small>${escape2(r.file)}</small></figcaption><div class="pair">${r.light ? `<img alt="${escape2(r.component)}, light" src="${r.light}">` : ""}${r.dark ? `<img alt="${escape2(r.component)}, dark" src="${r.dark}">` : ""}</div>${r.looks.length > 0 ? `<p class="muted">${
|
|
3858
|
+
const body = drawn.length > 0 ? `<div class="looks">${drawn.map((r) => `<figure data-component="${escape2(r.component)}"><figcaption>${escape2(r.component)} <small>${escape2(r.file)}</small></figcaption><div class="pair">${r.light ? `<img alt="${escape2(r.component)}, light" src="${r.light}">` : ""}${r.dark ? `<img alt="${escape2(r.component)}, dark" src="${r.dark}">` : ""}</div>${r.looks.length > 0 ? `<p class="muted">${plural(r.looks.length, "look")} drawn, light and dark</p>` : ""}</figure>`).join("")}</div>` : "";
|
|
3836
3859
|
const after = model.render.next ? next(model.render.next) : drawn.length > 0 ? next(`see a component after you change it with ${NPX} render <file>`) : next(model.render.state === "off" ? `run ${NPX} --render to draw the most-used components (it asks before downloading the ~100 MB headless Chromium, once)` : `run ${NPX} render <file> to see why a component could not be drawn`);
|
|
3837
|
-
return `<section class="panel" data-section="looks" data-render="${model.render.state}" id="looks"><header><h2>Rendered looks</h2><span class="aside">${drawn.length > 0 ?
|
|
3860
|
+
return `<section class="panel" data-section="looks" data-render="${model.render.state}" id="looks"><header><h2>Rendered looks</h2><span class="aside">${drawn.length > 0 ? plural(drawn.length, "component") : ""}</span></header><div class="body">${body}<p class="${drawn.length > 0 ? "muted" : ""}">${escape2(model.render.note)}</p>${after}</div></section>`;
|
|
3838
3861
|
}
|
|
3839
3862
|
function placesByKind(model) {
|
|
3840
3863
|
const places = (pick) => model.projects.reduce((n, p) => n + pick(p).reduce((m, f) => m + f.count, 0), 0);
|
|
@@ -3846,25 +3869,21 @@ function placesByKind(model) {
|
|
|
3846
3869
|
}
|
|
3847
3870
|
function headline(model) {
|
|
3848
3871
|
const t = model.totals;
|
|
3849
|
-
const [near,
|
|
3850
|
-
const parts = [
|
|
3851
|
-
{ label: "Near-duplicates", places: near.places, href: model.projects.length > 1 ? "top-fixes" : `${anchor(model.projects[0]?.dir ?? ".")}-near` },
|
|
3852
|
-
{ label: "Hardcoded values", places: hard.places, href: model.projects.length > 1 ? "top-fixes" : `${anchor(model.projects[0]?.dir ?? ".")}-hardcoded` },
|
|
3853
|
-
{ label: "Off-library imports", places: off.places, href: model.projects.length > 1 ? "top-fixes" : `${anchor(model.projects[0]?.dir ?? ".")}-off` }
|
|
3854
|
-
];
|
|
3872
|
+
const panels = ["near", "hardcoded", "off"];
|
|
3873
|
+
const parts = placesByKind(model).map((part, i) => ({ ...part, href: model.projects.length > 1 ? "top-fixes" : `${anchor(model.projects[0]?.dir ?? ".")}-${panels[i]}` }));
|
|
3855
3874
|
const total = parts.reduce((n2, part) => n2 + part.places, 0);
|
|
3856
3875
|
const n = (value) => `<b class="${value > 0 ? "warn" : "ok"}">${value}</b>`;
|
|
3857
3876
|
const unknown = model.projects.some((p) => p.incomplete.length > 0 || p.failed.length > 0);
|
|
3858
3877
|
const analyses = model.projects.reduce((n2, p) => n2 + p.incomplete.length, 0);
|
|
3859
3878
|
const unread = model.projects.reduce((n2, p) => n2 + p.failed.length, 0);
|
|
3860
|
-
const caveat = [analyses > 0 ? `${
|
|
3879
|
+
const caveat = [analyses > 0 ? `${plural(analyses, "analysis", "analyses")} could not run` : "", unread > 0 ? `${plural(unread, "file")} could not be fully read` : ""].filter(Boolean).join(" and ");
|
|
3861
3880
|
const said = [];
|
|
3862
3881
|
if (t.nearDuplicates > 0) said.push(`${n(t.nearDuplicates)} ${t.nearDuplicates === 1 ? "look is" : "looks are"} built more than once`);
|
|
3863
3882
|
if (t.hardcoded > 0) said.push(`${n(t.hardcoded)} ${t.hardcoded === 1 ? "value skips" : "values skip"} the theme`);
|
|
3864
3883
|
if (t.offLibrary > 0) said.push(`${n(t.offLibrary)} ${t.offLibrary === 1 ? "import comes" : "imports come"} from a second library`);
|
|
3865
3884
|
const sentence2 = said.length === 0 ? `Every look is built once and every value comes from the theme${unknown ? ", in what could be read" : ""}.` : `${said.length > 1 ? `${said.slice(0, -1).join(", ")} and ${said.at(-1)}` : said[0]}: <b class="warn">${total}</b> ${total === 1 ? "place" : "places"} to fix.`;
|
|
3866
3885
|
const bar2 = total > 0 ? `<div class="split" role="img" aria-label="${parts.map((part) => `${part.label} ${part.places}`).join(", ")}">${parts.map((part, i) => part.places > 0 ? `<span class="seg s${i}" style="width:${(part.places / total * 100).toFixed(2)}%"></span>` : "").join("")}</div>` : "";
|
|
3867
|
-
const legend = `<ul class="legend">${parts.map((part, i) => `<li><a href="#${part.href}"><span class="key s${i}${part.places === 0 ? " zero" : ""}"></span>${escape2(part.label)} <b>${part.places}</b>${part.places === 0 ? ` <span class="clean">Clean</span>` : ""}</a></li>`).join("")}${unknown ? `<li class="alert"><a href="#${anchor(model.projects.find((p) => p.failed.length > 0 || p.incomplete.length > 0)?.dir ?? ".")}-unread" aria-describedby="unread-tip"><i aria-hidden="true">!</i>${analyses + unread} not read</a><span class="tip" id="unread-tip" role="tooltip">${escape2(caveat.charAt(0).toUpperCase() + caveat.slice(1))}: what is in them is unknown, not zero.</span></li>` : ""}<li class="of">${
|
|
3886
|
+
const legend = `<ul class="legend">${parts.map((part, i) => `<li><a href="#${part.href}"><span class="key s${i}${part.places === 0 ? " zero" : ""}"></span>${escape2(part.label)} <b>${part.places}</b>${part.places === 0 ? ` <span class="clean">Clean</span>` : ""}</a></li>`).join("")}${unknown ? `<li class="alert"><a href="#${anchor(model.projects.find((p) => p.failed.length > 0 || p.incomplete.length > 0)?.dir ?? ".")}-unread" aria-describedby="unread-tip"><i aria-hidden="true">!</i>${analyses + unread} not read</a><span class="tip" id="unread-tip" role="tooltip">${escape2(caveat.charAt(0).toUpperCase() + caveat.slice(1))}: what is in them is unknown, not zero.</span></li>` : ""}<li class="of">${plural(t.definitions, "component")} \xB7 ${plural(t.uses, "UI element")} in use \xB7 ${plural(t.files, "file")}</li></ul>`;
|
|
3868
3887
|
return `<section class="headline" data-section="headline"><p class="say">${sentence2}</p>${bar2}${legend}</section>`;
|
|
3869
3888
|
}
|
|
3870
3889
|
var SQUARES = 40;
|
|
@@ -3878,7 +3897,7 @@ function shapeMap(model) {
|
|
|
3878
3897
|
const rows = [...roles].map(([role, { uses, families }]) => {
|
|
3879
3898
|
const shown2 = Math.min(families, SQUARES);
|
|
3880
3899
|
const squares = families === 0 ? `<span class="unmeasured">shape not measured</span>` : `${Array.from({ length: shown2 }, (_, i) => `<i class="${i === 0 ? "one" : "more"}"></i>`).join("")}${families > shown2 ? `<span class="over">+${families - shown2}</span>` : ""}`;
|
|
3881
|
-
return `<li class="${families > 1 ? "drift" : families === 1 ? "even" : ""}"><span class="role mono">${escape2(role)}</span><span class="sq">${squares}</span><span class="val"><b>${families}</b> ${families === 1 ? "shape" : "shapes"} \xB7 ${
|
|
3900
|
+
return `<li class="${families > 1 ? "drift" : families === 1 ? "even" : ""}"><span class="role mono">${escape2(role)}</span><span class="sq">${squares}</span><span class="val"><b>${families}</b> ${families === 1 ? "shape" : "shapes"} \xB7 ${plural(uses, "use")}</span></li>`;
|
|
3882
3901
|
}).join("");
|
|
3883
3902
|
const many = model.projects.length > 1 ? ", summed over the projects" : "";
|
|
3884
3903
|
return `<section class="panel" data-section="shapes" id="shapes"><header><h2>Shapes per element</h2><span class="aside"><i class="k one"></i>first shape <i class="k more"></i>each extra shape</span></header><div class="body"><p class="muted">One square per distinct look of each kind of element${many}. One square is consistent; every extra square is a copy someone built.</p><ul class="shapes">${rows}</ul></div></section>`;
|
|
@@ -3925,7 +3944,7 @@ ${shapeMap(model)}
|
|
|
3925
3944
|
${panel("top-fixes", "top-fixes", "Top fixes", "Ranked by how many places each one touches", top, "h2")}
|
|
3926
3945
|
<aside class="agent" data-section="agent"><p><strong>${escape2(model.agent)}</strong></p>${next(`${NPX} agents install, then restart your agent`)}</aside>
|
|
3927
3946
|
${looksSection(model)}
|
|
3928
|
-
<section class="panel projects" data-section="projects" id="projects"><header><h2>Projects</h2><span class="aside">${
|
|
3947
|
+
<section class="panel projects" data-section="projects" id="projects"><header><h2>Projects</h2><span class="aside">${plural(model.projects.length, "project")}</span></header><div class="body"><p>${escape2(model.layout.charAt(0).toUpperCase() + model.layout.slice(1))}.</p>${many ? `<nav><ul>${model.projects.map((p) => `<li><a href="#${anchor(p.dir)}">${escape2(p.dir)}</a>${p.name && p.name !== p.dir ? ` <small>${escape2(p.name)}</small>` : ""}</li>`).join("")}</ul></nav>` : ""}${next(many ? "open each project below; each has its own fixes" : "read this repo's section below")}</div></section>
|
|
3929
3948
|
${model.projects.map((p) => projectSection(p, many, looks)).join("\n")}
|
|
3930
3949
|
<footer>
|
|
3931
3950
|
<p>Made with ${escape2(NPX)} on ${escape2(date)} in ${(model.durationMs / 1e3).toFixed(1)} s. This file is self-contained: it loads nothing from the network, and nothing was uploaded.</p>
|
|
@@ -3940,12 +3959,13 @@ ${model.projects.map((p) => projectSection(p, many, looks)).join("\n")}
|
|
|
3940
3959
|
|
|
3941
3960
|
// ../cli/src/report/looks.ts
|
|
3942
3961
|
import { spawn as spawn2 } from "node:child_process";
|
|
3943
|
-
import { existsSync as
|
|
3962
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "node:fs";
|
|
3944
3963
|
import { homedir as homedir2 } from "node:os";
|
|
3945
3964
|
import { dirname as dirname10, join as join19 } from "node:path";
|
|
3946
3965
|
var LOOKS_PER_PROJECT = 4;
|
|
3947
3966
|
var DOWNLOAD_SIZE = "~100 MB";
|
|
3948
3967
|
var DEPENDENCIES = "Install the repo's dependencies to see rendered looks";
|
|
3968
|
+
var UNDRAWABLE = /not installed|loads no stylesheet|drawn from (?:its|their) (?:tag and )?classes|did not render|bundle failed|unstyled|stub fallback/i;
|
|
3949
3969
|
var playwrightDir = () => dirname10(playwrightCli());
|
|
3950
3970
|
function browsersPath(env) {
|
|
3951
3971
|
const set = env.PLAYWRIGHT_BROWSERS_PATH;
|
|
@@ -3956,12 +3976,12 @@ function browsersPath(env) {
|
|
|
3956
3976
|
return join19(env.XDG_CACHE_HOME ?? join19(homedir2(), ".cache"), "ms-playwright");
|
|
3957
3977
|
}
|
|
3958
3978
|
function chromiumStatus(env = process.env) {
|
|
3959
|
-
if (env.YOUI_CHROMIUM) return { available:
|
|
3979
|
+
if (env.YOUI_CHROMIUM) return { available: existsSync4(env.YOUI_CHROMIUM), where: env.YOUI_CHROMIUM };
|
|
3960
3980
|
try {
|
|
3961
3981
|
const browsers = JSON.parse(readFileSync2(join19(playwrightDir(), "browsers.json"), "utf8"));
|
|
3962
3982
|
const revision2 = browsers.browsers.find((b) => b.name === "chromium-headless-shell")?.revision;
|
|
3963
3983
|
const dir = join19(browsersPath(env), `chromium_headless_shell-${revision2}`);
|
|
3964
|
-
return { available: revision2 !== void 0 &&
|
|
3984
|
+
return { available: revision2 !== void 0 && existsSync4(join19(dir, "INSTALLATION_COMPLETE")), where: dir };
|
|
3965
3985
|
} catch {
|
|
3966
3986
|
return { available: false, where: "playwright-core is not installed" };
|
|
3967
3987
|
}
|
|
@@ -3973,7 +3993,7 @@ function downloadChromium() {
|
|
|
3973
3993
|
child.on("close", (code) => resolve13(code === 0));
|
|
3974
3994
|
});
|
|
3975
3995
|
}
|
|
3976
|
-
var dataUri = (file) => file &&
|
|
3996
|
+
var dataUri = (file) => file && existsSync4(file) ? `data:image/png;base64,${readFileSync2(file).toString("base64")}` : null;
|
|
3977
3997
|
async function addLooks(model, repoRoot, options = {}) {
|
|
3978
3998
|
const drawn = [];
|
|
3979
3999
|
const problems = [];
|
|
@@ -3981,34 +4001,34 @@ async function addLooks(model, repoRoot, options = {}) {
|
|
|
3981
4001
|
for (const section of model.projects) {
|
|
3982
4002
|
const names = [...new Set(section.components.map((c) => c.name))].slice(0, LOOKS_PER_PROJECT);
|
|
3983
4003
|
if (names.length === 0) continue;
|
|
3984
|
-
const
|
|
4004
|
+
const where2 = section.dir === "." ? "this repo" : section.dir;
|
|
3985
4005
|
try {
|
|
3986
4006
|
const installed = (name) => {
|
|
3987
4007
|
let dir = join19(repoRoot, section.dir);
|
|
3988
4008
|
while (true) {
|
|
3989
|
-
if (
|
|
4009
|
+
if (existsSync4(join19(dir, "node_modules", name, "package.json"))) return true;
|
|
3990
4010
|
if (dir === repoRoot || dirname10(dir) === dir) return false;
|
|
3991
4011
|
dir = dirname10(dir);
|
|
3992
4012
|
}
|
|
3993
4013
|
};
|
|
3994
4014
|
if (!installed("react") || !installed("react-dom")) {
|
|
3995
|
-
missing.push(
|
|
4015
|
+
missing.push(where2);
|
|
3996
4016
|
continue;
|
|
3997
4017
|
}
|
|
3998
4018
|
const projectRoot = join19(repoRoot, section.dir);
|
|
3999
4019
|
const target = await loadInventoryTarget(projectRoot);
|
|
4000
4020
|
if (findStylesheets(projectRoot, target.files, { tsconfig: target.tsconfig, override: target.specimenCss, templates: target.templates }).found.length === 0) {
|
|
4001
|
-
missing.push(
|
|
4021
|
+
missing.push(where2);
|
|
4002
4022
|
continue;
|
|
4003
4023
|
}
|
|
4004
4024
|
const report = await renderShots(join19(repoRoot, section.dir), names, { download: false, base: "HEAD", ...options.executablePath ? { executablePath: options.executablePath } : {}, ...options.cache ? { cache: options.cache } : {}, ...options.log ? { log: options.log } : {} });
|
|
4005
|
-
const unavailable = report.notes.some((note) =>
|
|
4025
|
+
const unavailable = report.notes.some((note) => UNDRAWABLE.test(note));
|
|
4006
4026
|
if (unavailable) {
|
|
4007
|
-
missing.push(
|
|
4027
|
+
missing.push(where2);
|
|
4008
4028
|
continue;
|
|
4009
4029
|
}
|
|
4010
4030
|
for (const component of report.components) {
|
|
4011
|
-
if (component.notes.some((note) =>
|
|
4031
|
+
if (component.notes.some((note) => UNDRAWABLE.test(note))) {
|
|
4012
4032
|
missing.push(component.component);
|
|
4013
4033
|
continue;
|
|
4014
4034
|
}
|
|
@@ -4024,7 +4044,7 @@ async function addLooks(model, repoRoot, options = {}) {
|
|
|
4024
4044
|
drawn.push(component.component);
|
|
4025
4045
|
}
|
|
4026
4046
|
} catch (error) {
|
|
4027
|
-
problems.push(`${
|
|
4047
|
+
problems.push(`${where2}: ${error instanceof Error ? error.message : String(error)}`);
|
|
4028
4048
|
if (error instanceof RenderUnavailableError) break;
|
|
4029
4049
|
}
|
|
4030
4050
|
}
|
|
@@ -4038,7 +4058,6 @@ function looksOff(model) {
|
|
|
4038
4058
|
}
|
|
4039
4059
|
|
|
4040
4060
|
// ../cli/src/report/markdown.ts
|
|
4041
|
-
var STEPS_SHOWN3 = 5;
|
|
4042
4061
|
var md = (text) => text.replace(/([\\`*_[\]<>|#])/g, "\\$1");
|
|
4043
4062
|
function renderMarkdown(model, reportFile) {
|
|
4044
4063
|
const t = model.totals;
|
|
@@ -4059,8 +4078,8 @@ function renderMarkdown(model, reportFile) {
|
|
|
4059
4078
|
model.topFixes.forEach((fix, i) => {
|
|
4060
4079
|
lines.push(`${i + 1}. ${md(fix.title)}${model.projects.length > 1 && fix.project !== "." ? ` (${md(fix.project)})` : ""} `);
|
|
4061
4080
|
if (fix.lead && fix.steps && fix.steps.length > 0) {
|
|
4062
|
-
lines.push(` Next: ${md(fix.lead)}.`, ...fix.steps.slice(0,
|
|
4063
|
-
if (fix.steps.length >
|
|
4081
|
+
lines.push(` Next: ${md(fix.lead)}.`, ...fix.steps.slice(0, STEPS_SHOWN).map((step) => ` - ${md(step.replace(/\.$/, ""))}`));
|
|
4082
|
+
if (fix.steps.length > STEPS_SHOWN) lines.push(` - and ${fix.steps.length - STEPS_SHOWN} more ${fix.steps.length - STEPS_SHOWN === 1 ? "file" : "files"}, listed in the report`);
|
|
4064
4083
|
} else lines.push(` Next: ${md(fix.next.replace(/\.$/, ""))}.`);
|
|
4065
4084
|
});
|
|
4066
4085
|
lines.push("");
|
|
@@ -4218,7 +4237,7 @@ async function writeBinaryAtomic(file, bytes) {
|
|
|
4218
4237
|
await rename2(temp, file);
|
|
4219
4238
|
}
|
|
4220
4239
|
var shown = (file) => {
|
|
4221
|
-
const rel =
|
|
4240
|
+
const rel = relative7(process.cwd(), file);
|
|
4222
4241
|
return rel && !rel.startsWith("..") ? rel : file;
|
|
4223
4242
|
};
|
|
4224
4243
|
async function runReport(options = {}, io = {}) {
|
|
@@ -4275,8 +4294,8 @@ async function runReport(options = {}, io = {}) {
|
|
|
4275
4294
|
print2(JSON.stringify({ ...clean, projects: clean.projects.map((p) => ({ ...p, rendered: p.rendered.map(({ light: _l, dark: _d, ...r }) => r) })), files }, null, 2));
|
|
4276
4295
|
return { code: 0, files, model: clean };
|
|
4277
4296
|
}
|
|
4278
|
-
const width =
|
|
4279
|
-
const looks = clean.render.state === "
|
|
4297
|
+
const width = 9;
|
|
4298
|
+
const looks = clean.render.state === "off" && !renderNote ? `; to add rendered looks, run ${NPX} --render` : "";
|
|
4280
4299
|
print2([
|
|
4281
4300
|
...clean.summary,
|
|
4282
4301
|
"",
|
|
@@ -4318,6 +4337,10 @@ ${style.bold("Anywhere")} ${style.dim("no design system needed")}
|
|
|
4318
4337
|
<first project>/.youi/inventory.json unless --out, and records the scan
|
|
4319
4338
|
(project, commit, time, counts, families) in the local index for trends
|
|
4320
4339
|
inventory --history every recorded scan per project, newest first, with what changed
|
|
4340
|
+
gaps [--project <dir>\u2026] [--inventory <file>] [--json] [--out <file>]
|
|
4341
|
+
per project, each kind of element: in the System, from a library, built
|
|
4342
|
+
by hand or missing from the standard kit, with licensed candidates from
|
|
4343
|
+
the Discover index, and what is built by hand in more than one project
|
|
4321
4344
|
requests [--status open|addressed|closed|all] [--route r] [--json]
|
|
4322
4345
|
comments and change requests people pinned to components on captured
|
|
4323
4346
|
screens: what is asked, the file:line, the picture, and what to do next
|
|
@@ -4327,6 +4350,9 @@ ${style.bold("Anywhere")} ${style.dim("no design system needed")}
|
|
|
4327
4350
|
photograph the routes in <project>/.youi/screens/screens.json, light and
|
|
4328
4351
|
dark, from the app running in development at <url>, and place each
|
|
4329
4352
|
component on them with the line that rendered it (for youiyoux ui)
|
|
4353
|
+
screens login --base <url> [--project dir] [--storage-state file]
|
|
4354
|
+
open the app in a browser window to sign in once; capture then uses the
|
|
4355
|
+
session (kept outside the product, readable only by you)
|
|
4330
4356
|
render [file|component\u2026] [--base ref] [--json]
|
|
4331
4357
|
see a component you changed: its looks drawn with the project's own code
|
|
4332
4358
|
and CSS in headless Chromium, light and dark at 1280 and 390 px, axe's
|
|
@@ -4508,7 +4534,7 @@ var OPTIONS = {
|
|
|
4508
4534
|
var INVENTORY_OPTIONS = { ...OPTIONS, project: { type: "string", multiple: true }, include: { type: "string", multiple: true }, exclude: { type: "string", multiple: true } };
|
|
4509
4535
|
var UsageError = class extends Error {
|
|
4510
4536
|
};
|
|
4511
|
-
var
|
|
4537
|
+
var run6 = promisify5(execFile5);
|
|
4512
4538
|
var print = (text) => void process.stdout.write(`${text}
|
|
4513
4539
|
`);
|
|
4514
4540
|
var json = (value) => print(JSON.stringify(value, null, 2));
|
|
@@ -4540,23 +4566,44 @@ async function recordUsage(values, p, report, usage) {
|
|
|
4540
4566
|
await noteSource(home, { project: { root: resolve12(values.project ?? process.cwd()), options: Object.fromEntries(Object.entries(loadOptions(values)).map(([key2, value]) => [key2, resolve12(value)])), usage } });
|
|
4541
4567
|
return null;
|
|
4542
4568
|
} catch (error) {
|
|
4543
|
-
return
|
|
4569
|
+
return notRecorded(error);
|
|
4544
4570
|
}
|
|
4545
4571
|
}
|
|
4572
|
+
var notRecorded = (error) => style.warn(`! not recorded in the local index: ${error instanceof Error ? error.message : String(error)}`);
|
|
4573
|
+
var inventoryDirs = (values) => values.project?.length ? values.project : [process.cwd()];
|
|
4574
|
+
var scanInventory = (values, dirs) => buildInventory(dirs, { ...values.include ? { include: values.include } : {}, ...values.exclude ? { exclude: values.exclude } : {} });
|
|
4546
4575
|
function indexOf(values) {
|
|
4547
4576
|
const path = discoverIndexPath(values.cache);
|
|
4548
4577
|
return { path, home: indexHome(path) };
|
|
4549
4578
|
}
|
|
4579
|
+
async function cmdGaps(values) {
|
|
4580
|
+
const dirs = inventoryDirs(values);
|
|
4581
|
+
const inventory = values.inventory ? InventoryReport.parse(JSON.parse(await readFile8(resolve12(values.inventory), "utf8"))) : await scanInventory(values, dirs);
|
|
4582
|
+
const cwd = values.inventory ? dirname11(resolve12(values.inventory)) : process.cwd();
|
|
4583
|
+
const project2 = await loadProject(resolve12(dirs[0]), loadOptions(values)).catch(() => null);
|
|
4584
|
+
const system = project2 ? { name: project2.system.config.name, components: project2.system.components.map((c) => c.contract.name) } : null;
|
|
4585
|
+
const store = await readCatalog(resolve12(discoverCacheDir(values.cache)), indexOf(values).path).catch((error) => {
|
|
4586
|
+
if (error instanceof MissingIndexError) return null;
|
|
4587
|
+
throw error;
|
|
4588
|
+
});
|
|
4589
|
+
const roots = new Map(inventory.projects.map((p) => [p.id, resolve12(cwd, p.root)]));
|
|
4590
|
+
const report = await buildGaps({ inventory, system, dependencies: declaredPackages(roots) }, store);
|
|
4591
|
+
if (values.out) await writeFileAtomic(resolve12(values.out), `${JSON.stringify(report, null, 2)}
|
|
4592
|
+
`);
|
|
4593
|
+
if (values.json) json(report);
|
|
4594
|
+
else print(renderGaps(report));
|
|
4595
|
+
return 0;
|
|
4596
|
+
}
|
|
4550
4597
|
async function cmdInventory(values) {
|
|
4551
4598
|
if (values.history) return cmdHistory(values, []);
|
|
4552
|
-
const dirs = values
|
|
4553
|
-
const report = await
|
|
4599
|
+
const dirs = inventoryDirs(values);
|
|
4600
|
+
const report = await scanInventory(values, dirs);
|
|
4554
4601
|
const out = resolve12(values.out ?? join22(dirs[0], ".youi/inventory.json"));
|
|
4555
4602
|
await writeFileAtomic(out, `${JSON.stringify(report, null, 2)}
|
|
4556
4603
|
`);
|
|
4557
4604
|
const recorded = await recordScan(values, report, out);
|
|
4558
4605
|
if (values.json) json(report);
|
|
4559
|
-
else print([renderInventory(report,
|
|
4606
|
+
else print([renderInventory(report, relative8(process.cwd(), out).startsWith("..") ? out : relative8(process.cwd(), out)), recorded].join("\n"));
|
|
4560
4607
|
return 0;
|
|
4561
4608
|
}
|
|
4562
4609
|
async function recordScan(values, report, out) {
|
|
@@ -4567,7 +4614,7 @@ async function recordScan(values, report, out) {
|
|
|
4567
4614
|
const scans = (await store.history({ limit: 500 })).filter((s) => report.projects.some((p) => p.id === s.project)).length;
|
|
4568
4615
|
return style.dim(`history \u2192 ${store.location} (${plural(scans, "scan")} of these projects; youiyoux inventory --history)`);
|
|
4569
4616
|
} catch (error) {
|
|
4570
|
-
return
|
|
4617
|
+
return notRecorded(error);
|
|
4571
4618
|
}
|
|
4572
4619
|
}
|
|
4573
4620
|
async function cmdHistory(values, positionals) {
|
|
@@ -4606,9 +4653,9 @@ async function cmdWhere(values, positionals) {
|
|
|
4606
4653
|
const p = await project(values);
|
|
4607
4654
|
const { report } = await scanProject(p);
|
|
4608
4655
|
const limit = values.all ? Number.MAX_SAFE_INTEGER : Number(values.limit ?? (values.json ? 20 : 24));
|
|
4609
|
-
const
|
|
4610
|
-
if (values.json) json(
|
|
4611
|
-
else print(renderWhere(
|
|
4656
|
+
const where2 = whereUsed(p, report, query, { ...values.route ? { route: values.route } : {}, limit });
|
|
4657
|
+
if (values.json) json(where2);
|
|
4658
|
+
else print(renderWhere(where2, report));
|
|
4612
4659
|
return 0;
|
|
4613
4660
|
}
|
|
4614
4661
|
async function cmdShow(values, positionals) {
|
|
@@ -4635,9 +4682,10 @@ function baselineFile(values, p) {
|
|
|
4635
4682
|
if (values["baseline-file"]) return resolve12(values["baseline-file"]);
|
|
4636
4683
|
return values.baseline ? baselinePath(p, values.config) : null;
|
|
4637
4684
|
}
|
|
4685
|
+
var checkScope = (values, positionals) => ({ ...positionals.length > 0 ? { files: positionals } : {}, ...values.rule ? { rule: values.rule } : {}, quiet: values.quiet });
|
|
4638
4686
|
async function repoPrefix(root) {
|
|
4639
4687
|
try {
|
|
4640
|
-
return (await
|
|
4688
|
+
return (await run6("git", ["rev-parse", "--show-prefix"], { cwd: root })).stdout.trim();
|
|
4641
4689
|
} catch {
|
|
4642
4690
|
return "";
|
|
4643
4691
|
}
|
|
@@ -4647,7 +4695,7 @@ async function cmdCheck(values, positionals) {
|
|
|
4647
4695
|
if (!["text", "json", "github"].includes(format)) throw new UsageError("youiyoux check --format text|json|github");
|
|
4648
4696
|
if (values.diff || values.base !== void 0) return cmdCheckDiff(values, positionals, await loadCheckProject(values.project ?? process.cwd(), loadOptions(values), !values.project), format);
|
|
4649
4697
|
const p = await project(values);
|
|
4650
|
-
const result = await runCheck(p, { ...
|
|
4698
|
+
const result = await runCheck(p, { ...checkScope(values, positionals), baseline: baselineFile(values, p) });
|
|
4651
4699
|
if (format === "json") {
|
|
4652
4700
|
const { report } = result.scan;
|
|
4653
4701
|
json({ errors: result.errors, incomplete: result.incomplete, identity: result.identity, unmatched: result.unmatched, notRun: result.notRun, violations: result.violations, baseline: result.baseline, lock: result.lock, suppressed: report.suppressed, coverage: report.coverage, status: report.status, checks: report.checks });
|
|
@@ -4661,7 +4709,7 @@ async function cmdCheck(values, positionals) {
|
|
|
4661
4709
|
async function cmdCheckDiff(values, positionals, p, format) {
|
|
4662
4710
|
if (values.baseline) throw new UsageError("youiyoux check --diff looks only at added lines, so a baseline does not apply; drop --baseline");
|
|
4663
4711
|
const change = await workingTreeChange(p, values.base);
|
|
4664
|
-
const result = configured(p) ? await runCheck(p, { ...
|
|
4712
|
+
const result = configured(p) ? await runCheck(p, { ...checkScope(values, positionals), diff: change }) : await runRepoCheck(p, change);
|
|
4665
4713
|
if (format === "json") json(diffJson(result));
|
|
4666
4714
|
else if (format === "github") {
|
|
4667
4715
|
for (const line of githubDiffAnnotations(result, await repoPrefix(p.root))) print(line);
|
|
@@ -4688,7 +4736,7 @@ async function cmdBaseline(values) {
|
|
|
4688
4736
|
const { totals } = written.baseline;
|
|
4689
4737
|
if (values.json) json({ file: written.file, action: written.action, totals, notAdded: written.notAdded, identity: written.baseline.identity });
|
|
4690
4738
|
else {
|
|
4691
|
-
print(`${style.ok(written.action)} ${
|
|
4739
|
+
print(`${style.ok(written.action)} ${relative8(process.cwd(), file) || file}: ${plural(totals.errors, "error")} \xB7 ${plural(totals.warnings, "warning")} accepted`);
|
|
4692
4740
|
print(style.dim(` rules ${describeIdentity(written.baseline.identity)}`));
|
|
4693
4741
|
if (written.notAdded > 0) print(style.warn(` ${plural(written.notAdded, "current violation")} not added: fix them, or accept them deliberately with --force`));
|
|
4694
4742
|
}
|
|
@@ -4852,7 +4900,7 @@ async function cmdAgents(values, positionals) {
|
|
|
4852
4900
|
const repoRoot = await repoRootOf(projectRoot);
|
|
4853
4901
|
const target = { project: projectRoot, ...values.config ? { config: resolve12(values.config) } : {}, ...values.design ? { design: resolve12(values.design) } : {}, ...values.root !== void 0 ? { root: resolve12(values.root || ".") } : {} };
|
|
4854
4902
|
const server = mcpCommand(TOOL_ROOT, target);
|
|
4855
|
-
const system =
|
|
4903
|
+
const system = existsSync5(resolve12(values.config ?? join22(projectRoot, PROJECT_FILE))) ? await project(values) : null;
|
|
4856
4904
|
const claudeServer = system && system.root === repoRoot ? { command: "node", args: mcpArgs(system) } : relativeTo(repoRoot, server);
|
|
4857
4905
|
const both = !values.claude && !values.codex;
|
|
4858
4906
|
const results = [];
|
|
@@ -4910,10 +4958,10 @@ async function startEditor(values, port) {
|
|
|
4910
4958
|
if (!values.project) return null;
|
|
4911
4959
|
try {
|
|
4912
4960
|
const editor = await startRuleEditor({ project: values.project, ...values.config ? { config: values.config } : {}, ...values.design ? { design: values.design } : {}, ...values.root ? { root: values.root } : {}, ...values.state ? { state: values.state } : {} });
|
|
4913
|
-
const { design, project:
|
|
4961
|
+
const { design, project: product2 } = editor.info;
|
|
4914
4962
|
print(`${style.ok("rule editor")} http://localhost:${port}/#key=${editor.sessionKey}`);
|
|
4915
4963
|
print(style.dim(` design ${design.root} (${design.branch} @ ${design.head.slice(0, 7)}${design.dirty ? ", working tree differs" : ""})`));
|
|
4916
|
-
print(style.dim(` project ${
|
|
4964
|
+
print(style.dim(` project ${product2.id} at ${product2.root}`));
|
|
4917
4965
|
print(style.dim(` drafts ${editor.stateDir}`));
|
|
4918
4966
|
print(style.dim(" open the link above: it carries this run's key, which every change needs (nothing else can approve)"));
|
|
4919
4967
|
print(style.dim(" approved changes become a local review branch; nothing is pushed"));
|
|
@@ -4935,20 +4983,20 @@ async function cmdUi(values) {
|
|
|
4935
4983
|
async function cmdDetect(values, positionals) {
|
|
4936
4984
|
const target = detectTarget(positionals);
|
|
4937
4985
|
if (!target) throw new UsageError("youiyoux detect <repo-path> or youiyoux detect <url\u2026> [--expose] (one repo, or one or more URLs; not both)");
|
|
4938
|
-
const { repoReport, siteReport, writeReport } = await import("./src-
|
|
4986
|
+
const { repoReport, siteReport, writeReport } = await import("./src-VL2ZYO6X.js");
|
|
4939
4987
|
const out = resolve12(values.out ?? ".youi/detect");
|
|
4940
4988
|
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 } : {} });
|
|
4941
4989
|
const file = await writeReport(report, out);
|
|
4942
4990
|
if (values.json) json(report);
|
|
4943
4991
|
else {
|
|
4944
|
-
const shown2 =
|
|
4992
|
+
const shown2 = relative8(process.cwd(), file);
|
|
4945
4993
|
print(renderDetect(report, shown2 && !shown2.startsWith("..") ? shown2 : file));
|
|
4946
4994
|
}
|
|
4947
4995
|
return 0;
|
|
4948
4996
|
}
|
|
4949
4997
|
async function cmdRender(values, positionals, io) {
|
|
4950
4998
|
const root = resolve12(values.root ?? values.project ?? process.cwd());
|
|
4951
|
-
const targets = positionals.map((t) =>
|
|
4999
|
+
const targets = positionals.map((t) => existsSync5(resolve12(t)) ? resolve12(t) : t);
|
|
4952
5000
|
try {
|
|
4953
5001
|
const report = await renderShots(root, targets, { ...values.base ? { base: values.base } : {}, ...process.env.YOUI_CHROMIUM ? { executablePath: process.env.YOUI_CHROMIUM } : {}, ...io.render });
|
|
4954
5002
|
if (values.json) json({ ...report, next: shotsNext(report, (t) => `run youiyoux render${t ? ` ${t}` : ""} again`) });
|
|
@@ -4961,13 +5009,16 @@ async function cmdRender(values, positionals, io) {
|
|
|
4961
5009
|
return 1;
|
|
4962
5010
|
}
|
|
4963
5011
|
}
|
|
5012
|
+
async function product(values) {
|
|
5013
|
+
const project2 = await loadProject(resolve12(values.project ?? process.cwd()), loadOptions(values)).catch(() => null);
|
|
5014
|
+
return { project: project2, root: project2?.root ?? resolve12(values.root ?? values.project ?? process.cwd()) };
|
|
5015
|
+
}
|
|
4964
5016
|
async function cmdRequests(values, positionals) {
|
|
4965
5017
|
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>";
|
|
4966
5018
|
const [action, id, ...rest] = positionals;
|
|
4967
5019
|
if (rest.length > 0) throw new UsageError(usage);
|
|
4968
|
-
const { RequestStore, requestBrief } = await import("./src-
|
|
4969
|
-
const project2 = await
|
|
4970
|
-
const root = project2?.root ?? resolve12(values.root ?? values.project ?? process.cwd());
|
|
5020
|
+
const { RequestStore, requestBrief } = await import("./src-34R7QMJC.js");
|
|
5021
|
+
const { project: project2, root } = await product(values);
|
|
4971
5022
|
const store = new RequestStore(root, project2?.config.id ?? basename3(root));
|
|
4972
5023
|
const agent = { kind: "agent", name: "youiyoux CLI" };
|
|
4973
5024
|
const one = (request) => {
|
|
@@ -4994,20 +5045,24 @@ async function cmdRequests(values, positionals) {
|
|
|
4994
5045
|
}
|
|
4995
5046
|
throw new UsageError(usage);
|
|
4996
5047
|
}
|
|
4997
|
-
async function cmdScreens(values, positionals) {
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
const root =
|
|
5048
|
+
async function cmdScreens(values, positionals, io) {
|
|
5049
|
+
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>]";
|
|
5050
|
+
if (!["capture", "login"].includes(positionals[0] ?? "") || positionals.length > 1 || !values.base) throw new UsageError(usage);
|
|
5051
|
+
const { root } = await product(values);
|
|
5052
|
+
if (positionals[0] === "login") return screensLogin(values.base, values["storage-state"] ? resolve12(values["storage-state"]) : sessionFile(root), io);
|
|
5001
5053
|
const dir = resolve12(values.out ?? join22(root, ".youi/screens"));
|
|
5002
|
-
if (!
|
|
5054
|
+
if (!existsSync5(join22(dir, "screens.json"))) {
|
|
5003
5055
|
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":"/"}]}
|
|
5004
5056
|
`);
|
|
5005
5057
|
return 1;
|
|
5006
5058
|
}
|
|
5007
|
-
const
|
|
5059
|
+
const session = chosenSession(root, values["storage-state"] ? resolve12(values["storage-state"]) : void 0);
|
|
5060
|
+
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`)}
|
|
5061
|
+
`);
|
|
5062
|
+
const { launchBrowser } = await import("./src-VL2ZYO6X.js");
|
|
5008
5063
|
const browser = await launchBrowser(process.env.YOUI_CHROMIUM, { purpose: { what: "youiyoux screens capture", command: "youiyoux screens capture" } });
|
|
5009
5064
|
try {
|
|
5010
|
-
const report = await captureScreens(dir, { base: values.base, root, browser, ...values.route ? { routes: [values.route] } : {}, ...
|
|
5065
|
+
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)}
|
|
5011
5066
|
`) });
|
|
5012
5067
|
if (values.json) json(report);
|
|
5013
5068
|
else print(captureText(report));
|
|
@@ -5016,13 +5071,52 @@ async function cmdScreens(values, positionals) {
|
|
|
5016
5071
|
await browser.close();
|
|
5017
5072
|
}
|
|
5018
5073
|
}
|
|
5074
|
+
async function screensLogin(base, file, io) {
|
|
5075
|
+
const login = io.login ?? {};
|
|
5076
|
+
if (!login.browser && !process.stdin.isTTY) {
|
|
5077
|
+
process.stderr.write(`${style.error("\u2716")} youiyoux screens login waits for you to sign in and press Enter: run it in a terminal
|
|
5078
|
+
`);
|
|
5079
|
+
return 1;
|
|
5080
|
+
}
|
|
5081
|
+
let browser;
|
|
5082
|
+
try {
|
|
5083
|
+
browser = login.browser ?? await openLoginBrowser(process.env.YOUI_CHROMIUM);
|
|
5084
|
+
} catch (error) {
|
|
5085
|
+
process.stderr.write(`${style.error("\u2716")} youiyoux screens login opens a browser window to sign in with, and could not: ${(error instanceof Error ? error.message : String(error)).split("\n")[0]}. It needs Google Chrome installed, or YOUI_CHROMIUM set to a Chromium.
|
|
5086
|
+
`);
|
|
5087
|
+
return 1;
|
|
5088
|
+
}
|
|
5089
|
+
try {
|
|
5090
|
+
print(`Sign in to ${base} in the window that opened, then press Enter here (or type q and Enter to give up).`);
|
|
5091
|
+
const signedIn = login.signedIn ?? (async (page) => {
|
|
5092
|
+
const { createInterface } = await import("node:readline/promises");
|
|
5093
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
5094
|
+
const closed = new Promise((done) => page.once("close", () => done("closed")));
|
|
5095
|
+
try {
|
|
5096
|
+
return (await Promise.race([prompt.question(""), closed])).trim().toLowerCase() !== "q";
|
|
5097
|
+
} finally {
|
|
5098
|
+
prompt.close();
|
|
5099
|
+
}
|
|
5100
|
+
});
|
|
5101
|
+
const result = await recordSession({ base, file, browser, signedIn });
|
|
5102
|
+
if (!result.file) {
|
|
5103
|
+
print("Nothing was saved: the window was closed, or you gave up. Run youiyoux screens login again to sign in.");
|
|
5104
|
+
return 1;
|
|
5105
|
+
}
|
|
5106
|
+
print(`Saved the session (${result.cookies} cookies, storage for ${result.origins} sites) to ${result.file}, readable only by you: it holds your sign-in, so it is kept outside the product.`);
|
|
5107
|
+
print("Next: youiyoux screens capture --base <url> uses it from now on; delete the file to capture signed out.");
|
|
5108
|
+
return 0;
|
|
5109
|
+
} finally {
|
|
5110
|
+
if (!login.browser) await browser.close();
|
|
5111
|
+
}
|
|
5112
|
+
}
|
|
5019
5113
|
async function cmdReport(values, positionals, io) {
|
|
5020
5114
|
if (positionals.length > 1) throw new UsageError("youiyoux report [path] [--out dir] [--render [--yes]] [--include-code] [--json] (one repo)");
|
|
5021
5115
|
const result = await runReport({ ...positionals[0] ? { path: positionals[0] } : {}, ...values.out ? { out: values.out } : {}, render: values.render, yes: values.yes, includeCode: values["include-code"], json: values.json }, io.report ?? {});
|
|
5022
5116
|
return result.code;
|
|
5023
5117
|
}
|
|
5024
5118
|
async function cmdMcp(values) {
|
|
5025
|
-
const { startStdioServer } = await import("./src-
|
|
5119
|
+
const { startStdioServer } = await import("./src-E25GHWWT.js");
|
|
5026
5120
|
await startStdioServer({ projectRoot: values.project ?? process.cwd(), ...loadOptions(values), ...values.cache ? { discoverCache: values.cache } : {} });
|
|
5027
5121
|
return -1;
|
|
5028
5122
|
}
|
|
@@ -5112,13 +5206,13 @@ var COMMANDS = {
|
|
|
5112
5206
|
parity: cmdParity,
|
|
5113
5207
|
schema: cmdSchema
|
|
5114
5208
|
};
|
|
5115
|
-
var COMMAND_NAMES = [...Object.keys(COMMANDS), "inventory", "help"];
|
|
5209
|
+
var COMMAND_NAMES = [...Object.keys(COMMANDS), "inventory", "gaps", "help"];
|
|
5116
5210
|
async function main(argv, io = {}) {
|
|
5117
5211
|
let parsed;
|
|
5118
5212
|
try {
|
|
5119
|
-
if (argv[0] === "inventory") {
|
|
5213
|
+
if (argv[0] === "inventory" || argv[0] === "gaps") {
|
|
5120
5214
|
const inventory = parseArgs({ args: argv, options: INVENTORY_OPTIONS, allowPositionals: true });
|
|
5121
|
-
if (!inventory.values.help) return await guarded(() => cmdInventory(inventory.values));
|
|
5215
|
+
if (!inventory.values.help) return await guarded(() => argv[0] === "gaps" ? cmdGaps(inventory.values) : cmdInventory(inventory.values));
|
|
5122
5216
|
}
|
|
5123
5217
|
parsed = parseArgs({ args: diffArgs(argv), options: OPTIONS, allowPositionals: true });
|
|
5124
5218
|
} catch (error) {
|