@zentauri-ui/zentauri-components 2.1.4 → 2.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 +6 -5
- package/cli/cli.integration.test.ts +44 -2
- package/cli/index.mjs +86 -23
- package/cli/props.json +14858 -0
- package/cli/props.test.ts +80 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -29,8 +29,8 @@ Generated from the component package Vitest JSON report via `pnpm --filter @zent
|
|
|
29
29
|
|
|
30
30
|
| Metric | Result |
|
|
31
31
|
| ---------- | ---------------- |
|
|
32
|
-
| Test files |
|
|
33
|
-
| Tests |
|
|
32
|
+
| Test files | 94 passed (94) |
|
|
33
|
+
| Tests | 754 passed (754) |
|
|
34
34
|
|
|
35
35
|
| Area | Test files | Tests |
|
|
36
36
|
| ------------------------------ | ---------- | ----- |
|
|
@@ -38,7 +38,7 @@ Generated from the component package Vitest JSON report via `pnpm --filter @zent
|
|
|
38
38
|
| Standalone animations | 1 | 45 |
|
|
39
39
|
| React hooks | 41 | 174 |
|
|
40
40
|
| Design system facade | 1 | 11 |
|
|
41
|
-
| CLI and import rewriting |
|
|
41
|
+
| CLI and import rewriting | 3 | 26 |
|
|
42
42
|
| Accessibility (axe + keyboard) | 2 | 42 |
|
|
43
43
|
|
|
44
44
|
### Per-suite snapshot
|
|
@@ -51,7 +51,7 @@ Generated from the component package Vitest JSON report via `pnpm --filter @zent
|
|
|
51
51
|
| `src/ui/peer-isolation.test.ts` | 29 |
|
|
52
52
|
| `src/accessibility/axe-core.test.tsx` | 24 |
|
|
53
53
|
| `src/ui/combobox/combobox.test.tsx` | 24 |
|
|
54
|
-
| `cli/cli.integration.test.ts` |
|
|
54
|
+
| `cli/cli.integration.test.ts` | 20 |
|
|
55
55
|
| `src/accessibility/keyboard-interaction.test.tsx` | 18 |
|
|
56
56
|
| `src/ui/pagination/pagination.test.tsx` | 15 |
|
|
57
57
|
| `src/ui/timeline/timeline.test.tsx` | 14 |
|
|
@@ -131,6 +131,7 @@ Generated from the component package Vitest JSON report via `pnpm --filter @zent
|
|
|
131
131
|
| `src/hooks/useMediaQuery/useMediaQuery.test.ts` | 2 |
|
|
132
132
|
| `src/hooks/useNetworkStatus/useNetworkStatus.test.ts` | 2 |
|
|
133
133
|
| `src/hooks/useResizeObserver/useResizeObserver.test.ts` | 2 |
|
|
134
|
+
| `cli/props.test.ts` | 1 |
|
|
134
135
|
| `src/hooks/useInView/useInView.test.ts` | 1 |
|
|
135
136
|
| `src/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.test.ts` | 1 |
|
|
136
137
|
| `src/hooks/usePageVisibility/usePageVisibility.test.ts` | 1 |
|
|
@@ -847,7 +848,7 @@ From this package directory in the monorepo:
|
|
|
847
848
|
|
|
848
849
|
- `pnpm build` (or `npm run build`) — production bundle via `tsup` (Rollup treeshake + `scripts/prepend-use-client.mjs` via `onSuccess` so each UI entry under `dist/ui/`, animation entry under `dist/animations/`, chart entry under `dist/charts/`, and `dist/ui/<name>/animated.*` starts with `"use client"` where needed)
|
|
849
850
|
- `pnpm dev` — `tsup` watch mode (same `onSuccess` hook after each rebuild)
|
|
850
|
-
- `pnpm test` / `pnpm test:watch` — **Vitest** and **Testing Library** unit tests // currently covered
|
|
851
|
+
- `pnpm test` / `pnpm test:watch` — **Vitest** and **Testing Library** unit tests // currently covered 754 test cases in total
|
|
851
852
|
- `pnpm test:a11y` — focused accessibility coverage for package-level UI primitives and compound components: **axe-core** audits for every interactive component plus **keyboard-interaction** tests (focus order, arrow-key nav, Home/End, Escape/Enter) for the compound components
|
|
852
853
|
- `pnpm check:tokens` — enforce the `--zui-*` token contract across design-system, variant, and local custom-property usage without generating a large checked-in token catalog
|
|
853
854
|
- **`pnpm run generate:registry`** — runs `scripts/generate-registry.mjs`, which reads **`uiComponentNames`**, **`uiAnimatedComponentNames`**, **`animationEntryNames`**, **`chartEntryNames`**, and **`hooksEntryNames`** from `tsup.config.ts`, applies fixed **`nameAliases`**, scans each component/chart source to build **`peerHints`**, and writes **`cli/registry.json`** (`components` + `animations` + `hooks` + `peerHints`). Run this after adding or renaming UI, animation, chart, or hook entries so the CLI stays in sync (the script prints counts).
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdtempSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
3
10
|
import { tmpdir } from "node:os";
|
|
4
11
|
import { dirname, join } from "node:path";
|
|
5
12
|
import { fileURLToPath } from "node:url";
|
|
@@ -106,6 +113,35 @@ describe("zentauri-ui CLI", () => {
|
|
|
106
113
|
}
|
|
107
114
|
});
|
|
108
115
|
|
|
116
|
+
it("should not use a monorepo root config when add runs inside a package", () => {
|
|
117
|
+
const dir = mkdtempSync(join(tmpdir(), "zentauri-cli-monorepo-"));
|
|
118
|
+
try {
|
|
119
|
+
const appDir = join(dir, "apps/web");
|
|
120
|
+
execFileSync(process.execPath, [
|
|
121
|
+
"-e",
|
|
122
|
+
`require("node:fs").mkdirSync(${JSON.stringify(appDir)}, { recursive: true })`,
|
|
123
|
+
]);
|
|
124
|
+
writeFileSync(
|
|
125
|
+
join(dir, "package.json"),
|
|
126
|
+
JSON.stringify({ private: true, workspaces: ["apps/*"] }),
|
|
127
|
+
);
|
|
128
|
+
writeFileSync(join(appDir, "package.json"), JSON.stringify({}));
|
|
129
|
+
|
|
130
|
+
runCli(dir, ["init"]);
|
|
131
|
+
const stderr = runCliError(appDir, ["add", "button"]);
|
|
132
|
+
|
|
133
|
+
expect(stderr).toContain("No components.json found");
|
|
134
|
+
expect(
|
|
135
|
+
existsSync(join(dir, "src/components/ui/buttons/button.tsx")),
|
|
136
|
+
).toBe(false);
|
|
137
|
+
expect(
|
|
138
|
+
existsSync(join(appDir, "src/components/ui/buttons/button.tsx")),
|
|
139
|
+
).toBe(false);
|
|
140
|
+
} finally {
|
|
141
|
+
rmSync(dir, { recursive: true, force: true });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
109
145
|
it("should add an animated component explicitly and report missing peers", () => {
|
|
110
146
|
const dir = mkdtempSync(join(tmpdir(), "zentauri-cli-add-animated-"));
|
|
111
147
|
try {
|
|
@@ -162,11 +198,17 @@ describe("zentauri-ui CLI", () => {
|
|
|
162
198
|
expect(
|
|
163
199
|
existsSync(join(dir, "src/components/design-system/button.ts")),
|
|
164
200
|
).toBe(true);
|
|
201
|
+
expect(
|
|
202
|
+
existsSync(join(dir, "src/components/design-system/tokens.ts")),
|
|
203
|
+
).toBe(true);
|
|
204
|
+
expect(
|
|
205
|
+
readdirSync(join(dir, "src/components/design-system")).sort(),
|
|
206
|
+
).toEqual(["button.ts", "tokens.ts"]);
|
|
165
207
|
const variants = readFileSync(
|
|
166
208
|
join(dir, "src/components/ui/buttons/variants.ts"),
|
|
167
209
|
"utf8",
|
|
168
210
|
);
|
|
169
|
-
expect(variants).toContain("../../design-system");
|
|
211
|
+
expect(variants).toContain("../../design-system/button");
|
|
170
212
|
} finally {
|
|
171
213
|
rmSync(dir, { recursive: true, force: true });
|
|
172
214
|
}
|
package/cli/index.mjs
CHANGED
|
@@ -238,8 +238,9 @@ async function walkFiles(dir) {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
/**
|
|
241
|
-
* Walks upward from `startDir`
|
|
242
|
-
*
|
|
241
|
+
* Walks upward from `startDir` until `components.json` exists. If the command
|
|
242
|
+
* starts inside a package, discovery stops at that package's `package.json`
|
|
243
|
+
* boundary so monorepo root config cannot capture nested package installs.
|
|
243
244
|
*
|
|
244
245
|
* @param {string} startDir — typically `process.cwd()` or `--cwd` resolved path
|
|
245
246
|
* @returns {string | undefined} — absolute path to `components.json` if found
|
|
@@ -253,11 +254,17 @@ async function walkFiles(dir) {
|
|
|
253
254
|
*/
|
|
254
255
|
async function findComponentsJson(startDir) {
|
|
255
256
|
let d = startDir;
|
|
257
|
+
const packagePath = findPackageJson(startDir);
|
|
258
|
+
const packageBoundary = packagePath ? dirname(packagePath) : undefined;
|
|
259
|
+
|
|
256
260
|
for (;;) {
|
|
257
261
|
const p = join(d, "components.json");
|
|
258
262
|
if (existsSync(p)) {
|
|
259
263
|
return p;
|
|
260
264
|
}
|
|
265
|
+
if (packageBoundary && d === packageBoundary) {
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
261
268
|
const parent = dirname(d);
|
|
262
269
|
if (parent === d) {
|
|
263
270
|
return undefined;
|
|
@@ -957,6 +964,7 @@ async function copyUiComponent(componentName, config, configDir, packageRoot) {
|
|
|
957
964
|
return true;
|
|
958
965
|
});
|
|
959
966
|
const usedHooks = new Set();
|
|
967
|
+
const designSystemImportTarget = getDesignSystemEntryName(componentName);
|
|
960
968
|
|
|
961
969
|
for (const absSrc of files) {
|
|
962
970
|
const rel = relative(srcRoot, absSrc);
|
|
@@ -972,10 +980,13 @@ async function copyUiComponent(componentName, config, configDir, packageRoot) {
|
|
|
972
980
|
hooksAlias: config.aliases.hooks,
|
|
973
981
|
uiAlias: config.aliases.ui,
|
|
974
982
|
});
|
|
983
|
+
const rewrittenCode = designSystemImportTarget
|
|
984
|
+
? rewriteDesignSystemBarrelImports(code, designSystemImportTarget)
|
|
985
|
+
: code;
|
|
975
986
|
for (const h of uh) {
|
|
976
987
|
usedHooks.add(h);
|
|
977
988
|
}
|
|
978
|
-
await writeFile(absDest,
|
|
989
|
+
await writeFile(absDest, rewrittenCode, "utf8");
|
|
979
990
|
} else {
|
|
980
991
|
await copyFile(absSrc, absDest);
|
|
981
992
|
}
|
|
@@ -1022,11 +1033,57 @@ async function copyHookFolder(hookName, config, configDir, packageRoot) {
|
|
|
1022
1033
|
}
|
|
1023
1034
|
|
|
1024
1035
|
/**
|
|
1025
|
-
*
|
|
1026
|
-
|
|
1027
|
-
|
|
1036
|
+
* Maps registry component names to their matching design-system token file.
|
|
1037
|
+
*/
|
|
1038
|
+
function getDesignSystemEntryName(componentName) {
|
|
1039
|
+
if (
|
|
1040
|
+
componentName.startsWith("charts/") ||
|
|
1041
|
+
componentName.startsWith("animations/")
|
|
1042
|
+
) {
|
|
1043
|
+
return undefined;
|
|
1044
|
+
}
|
|
1045
|
+
if (componentName === "buttons") {
|
|
1046
|
+
return "button";
|
|
1047
|
+
}
|
|
1048
|
+
return componentName;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
/**
|
|
1052
|
+
* Narrows vendored imports from the design-system barrel to the selected token
|
|
1053
|
+
* file. This lets a component like `buttons` vendor `design-system/button.ts`
|
|
1054
|
+
* without also requiring `design-system/index.ts`.
|
|
1028
1055
|
*/
|
|
1029
|
-
|
|
1056
|
+
function rewriteDesignSystemBarrelImports(source, designSystemEntryName) {
|
|
1057
|
+
return source.replace(
|
|
1058
|
+
/from\s+(["'])((?:\.\.\/)+)design-system\1/g,
|
|
1059
|
+
(_, quote, rel) =>
|
|
1060
|
+
`from ${quote}${rel}design-system/${designSystemEntryName}${quote}`,
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* Extracts local design-system dependencies from a design token file.
|
|
1066
|
+
*/
|
|
1067
|
+
function extractDesignSystemDependencies(source) {
|
|
1068
|
+
const deps = new Set();
|
|
1069
|
+
const re = /from\s+["']\.\/([^"']+)["']/g;
|
|
1070
|
+
let match;
|
|
1071
|
+
while ((match = re.exec(source)) !== null) {
|
|
1072
|
+
deps.add(match[1].replace(/\.(tsx?|jsx?)$/, ""));
|
|
1073
|
+
}
|
|
1074
|
+
return [...deps];
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
/**
|
|
1078
|
+
* Copies only the design-system token files required by selected components,
|
|
1079
|
+
* plus local token dependencies such as `tokens.ts`.
|
|
1080
|
+
*/
|
|
1081
|
+
async function copyDesignSystemFiles(
|
|
1082
|
+
componentNames,
|
|
1083
|
+
config,
|
|
1084
|
+
configDir,
|
|
1085
|
+
packageRoot,
|
|
1086
|
+
) {
|
|
1030
1087
|
const srcRoot = join(packageRoot, "src", "design-system");
|
|
1031
1088
|
if (!existsSync(srcRoot)) {
|
|
1032
1089
|
return;
|
|
@@ -1039,28 +1096,34 @@ async function copyDesignSystemFolder(config, configDir, packageRoot) {
|
|
|
1039
1096
|
dirname(config.resolvedPaths.ui),
|
|
1040
1097
|
"design-system",
|
|
1041
1098
|
);
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1099
|
+
const pending = componentNames
|
|
1100
|
+
.map((name) => getDesignSystemEntryName(name))
|
|
1101
|
+
.filter(Boolean);
|
|
1102
|
+
const copied = new Set();
|
|
1103
|
+
|
|
1104
|
+
while (pending.length > 0) {
|
|
1105
|
+
const entryName = pending.shift();
|
|
1106
|
+
if (copied.has(entryName)) {
|
|
1046
1107
|
continue;
|
|
1047
1108
|
}
|
|
1109
|
+
const absSrc = join(srcRoot, `${entryName}.ts`);
|
|
1110
|
+
if (!existsSync(absSrc)) {
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
copied.add(entryName);
|
|
1114
|
+
const rel = relative(srcRoot, absSrc);
|
|
1048
1115
|
const absDest = join(destRoot, rel);
|
|
1116
|
+
const raw = await readFile(absSrc, "utf8");
|
|
1117
|
+
for (const dep of extractDesignSystemDependencies(raw)) {
|
|
1118
|
+
if (!copied.has(dep)) {
|
|
1119
|
+
pending.push(dep);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1049
1122
|
if (existsSync(absDest)) {
|
|
1050
1123
|
continue;
|
|
1051
1124
|
}
|
|
1052
1125
|
await mkdir(dirname(absDest), { recursive: true });
|
|
1053
|
-
|
|
1054
|
-
const raw = await readFile(absSrc, "utf8");
|
|
1055
|
-
const { code } = rewriteImports(raw, {
|
|
1056
|
-
utilsAlias: config.aliases.utils,
|
|
1057
|
-
hooksAlias: config.aliases.hooks,
|
|
1058
|
-
uiAlias: config.aliases.ui,
|
|
1059
|
-
});
|
|
1060
|
-
await writeFile(absDest, code, "utf8");
|
|
1061
|
-
} else {
|
|
1062
|
-
await copyFile(absSrc, absDest);
|
|
1063
|
-
}
|
|
1126
|
+
await writeFile(absDest, raw, "utf8");
|
|
1064
1127
|
}
|
|
1065
1128
|
}
|
|
1066
1129
|
|
|
@@ -1258,7 +1321,7 @@ async function cmdAdd(names, cwd, options = {}) {
|
|
|
1258
1321
|
}
|
|
1259
1322
|
|
|
1260
1323
|
await ensureUtilsFile(config, configDir, packageRoot);
|
|
1261
|
-
await
|
|
1324
|
+
await copyDesignSystemFiles(resolvedNames, config, configDir, packageRoot);
|
|
1262
1325
|
|
|
1263
1326
|
const allHooks = new Set();
|
|
1264
1327
|
for (const name of resolvedNames) {
|