@zentauri-ui/zentauri-components 2.1.4 → 2.1.6
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 +9 -6
- package/cli/cli.integration.test.ts +44 -2
- package/cli/index.mjs +134 -28
- package/cli/index.test.ts +180 -0
- package/cli/props.json +15180 -0
- package/cli/props.test.ts +80 -0
- package/cli/registry.json +2 -0
- package/dist/chunk-3W2UUKWP.js +19 -0
- package/dist/{chunk-D2GISTDL.js.map → chunk-3W2UUKWP.js.map} +1 -1
- package/dist/{chunk-BL6UVCV7.mjs → chunk-A4IB3C23.mjs} +16 -7
- package/dist/chunk-A4IB3C23.mjs.map +1 -0
- package/dist/{chunk-WBZKMSXW.mjs → chunk-CHI6MBTI.mjs} +3 -3
- package/dist/{chunk-WBZKMSXW.mjs.map → chunk-CHI6MBTI.mjs.map} +1 -1
- package/dist/chunk-COCPCZMR.mjs +77 -0
- package/dist/chunk-COCPCZMR.mjs.map +1 -0
- package/dist/chunk-PG7LQVU6.js +86 -0
- package/dist/chunk-PG7LQVU6.js.map +1 -0
- package/dist/{chunk-RENXBUZY.js → chunk-QE7OJW4J.js} +6 -6
- package/dist/{chunk-RENXBUZY.js.map → chunk-QE7OJW4J.js.map} +1 -1
- package/dist/{chunk-NZSZE36T.js → chunk-VA6SB6NN.js} +16 -7
- package/dist/{chunk-BL6UVCV7.mjs.map → chunk-VA6SB6NN.js.map} +1 -1
- package/dist/{chunk-PAG5CTLN.mjs → chunk-WWKAJHIV.mjs} +3 -3
- package/dist/{chunk-PAG5CTLN.mjs.map → chunk-WWKAJHIV.mjs.map} +1 -1
- package/dist/design-system/audio-player.d.ts +61 -0
- package/dist/design-system/audio-player.d.ts.map +1 -0
- package/dist/design-system/facade.js +8 -7
- package/dist/design-system/facade.js.map +1 -1
- package/dist/design-system/facade.mjs +7 -6
- package/dist/design-system/facade.mjs.map +1 -1
- package/dist/design-system/index.d.ts +1 -0
- package/dist/design-system/index.d.ts.map +1 -1
- package/dist/ui/audio-player/audio-player-base.d.ts +20 -0
- package/dist/ui/audio-player/audio-player-base.d.ts.map +1 -0
- package/dist/ui/audio-player/audio-player.d.ts +6 -0
- package/dist/ui/audio-player/audio-player.d.ts.map +1 -0
- package/dist/ui/audio-player/index.d.ts +5 -0
- package/dist/ui/audio-player/index.d.ts.map +1 -0
- package/dist/ui/audio-player/types.d.ts +44 -0
- package/dist/ui/audio-player/types.d.ts.map +1 -0
- package/dist/ui/audio-player/variants.d.ts +12 -0
- package/dist/ui/audio-player/variants.d.ts.map +1 -0
- package/dist/ui/audio-player.js +556 -0
- package/dist/ui/audio-player.js.map +1 -0
- package/dist/ui/audio-player.mjs +545 -0
- package/dist/ui/audio-player.mjs.map +1 -0
- package/dist/ui/buttons/animated.js +10 -9
- package/dist/ui/buttons/animated.js.map +1 -1
- package/dist/ui/buttons/animated.mjs +8 -7
- package/dist/ui/buttons/animated.mjs.map +1 -1
- package/dist/ui/buttons.js +11 -10
- package/dist/ui/buttons.mjs +9 -8
- package/dist/ui/dynamic-stepper.js +20 -19
- package/dist/ui/dynamic-stepper.js.map +1 -1
- package/dist/ui/dynamic-stepper.mjs +9 -8
- package/dist/ui/dynamic-stepper.mjs.map +1 -1
- package/dist/ui/pagination.js +16 -15
- package/dist/ui/pagination.js.map +1 -1
- package/dist/ui/pagination.mjs +8 -7
- package/dist/ui/pagination.mjs.map +1 -1
- package/package.json +5 -2
- package/src/design-system/audio-player.ts +109 -0
- package/src/design-system/index.ts +1 -0
- package/src/ui/audio-player/audio-player-base.tsx +557 -0
- package/src/ui/audio-player/audio-player.test.tsx +485 -0
- package/src/ui/audio-player/audio-player.tsx +8 -0
- package/src/ui/audio-player/index.ts +24 -0
- package/src/ui/audio-player/types.ts +57 -0
- package/src/ui/audio-player/variants.ts +43 -0
- package/dist/chunk-D2GISTDL.js +0 -19
- package/dist/chunk-NZSZE36T.js.map +0 -1
package/README.md
CHANGED
|
@@ -29,16 +29,16 @@ 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 | 96 passed (96) |
|
|
33
|
+
| Tests | 792 passed (792) |
|
|
34
34
|
|
|
35
35
|
| Area | Test files | Tests |
|
|
36
36
|
| ------------------------------ | ---------- | ----- |
|
|
37
|
-
| Components and UI utilities |
|
|
37
|
+
| Components and UI utilities | 47 | 490 |
|
|
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 | 4 | 30 |
|
|
42
42
|
| Accessibility (axe + keyboard) | 2 | 42 |
|
|
43
43
|
|
|
44
44
|
### Per-suite snapshot
|
|
@@ -48,10 +48,11 @@ Generated from the component package Vitest JSON report via `pnpm --filter @zent
|
|
|
48
48
|
| `src/animations/animations.test.tsx` | 45 |
|
|
49
49
|
| `src/ui/buttons/button.test.tsx` | 44 |
|
|
50
50
|
| `src/ui/inputs/input.test.tsx` | 40 |
|
|
51
|
+
| `src/ui/audio-player/audio-player.test.tsx` | 34 |
|
|
51
52
|
| `src/ui/peer-isolation.test.ts` | 29 |
|
|
52
53
|
| `src/accessibility/axe-core.test.tsx` | 24 |
|
|
53
54
|
| `src/ui/combobox/combobox.test.tsx` | 24 |
|
|
54
|
-
| `cli/cli.integration.test.ts` |
|
|
55
|
+
| `cli/cli.integration.test.ts` | 20 |
|
|
55
56
|
| `src/accessibility/keyboard-interaction.test.tsx` | 18 |
|
|
56
57
|
| `src/ui/pagination/pagination.test.tsx` | 15 |
|
|
57
58
|
| `src/ui/timeline/timeline.test.tsx` | 14 |
|
|
@@ -109,6 +110,7 @@ Generated from the component package Vitest JSON report via `pnpm --filter @zent
|
|
|
109
110
|
| `src/ui/popover/popover.test.tsx` | 5 |
|
|
110
111
|
| `src/ui/radio-group/radio-group.test.tsx` | 5 |
|
|
111
112
|
| `src/ui/toggle/toggle.test.tsx` | 5 |
|
|
113
|
+
| `cli/index.test.ts` | 4 |
|
|
112
114
|
| `src/hooks/useBodyScrollLock/useBodyScrollLock.test.ts` | 4 |
|
|
113
115
|
| `src/hooks/useControllableState/useControllableState.test.ts` | 4 |
|
|
114
116
|
| `src/hooks/useDebouncedValue/useDebouncedValue.test.ts` | 4 |
|
|
@@ -131,6 +133,7 @@ Generated from the component package Vitest JSON report via `pnpm --filter @zent
|
|
|
131
133
|
| `src/hooks/useMediaQuery/useMediaQuery.test.ts` | 2 |
|
|
132
134
|
| `src/hooks/useNetworkStatus/useNetworkStatus.test.ts` | 2 |
|
|
133
135
|
| `src/hooks/useResizeObserver/useResizeObserver.test.ts` | 2 |
|
|
136
|
+
| `cli/props.test.ts` | 1 |
|
|
134
137
|
| `src/hooks/useInView/useInView.test.ts` | 1 |
|
|
135
138
|
| `src/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.test.ts` | 1 |
|
|
136
139
|
| `src/hooks/usePageVisibility/usePageVisibility.test.ts` | 1 |
|
|
@@ -847,7 +850,7 @@ From this package directory in the monorepo:
|
|
|
847
850
|
|
|
848
851
|
- `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
852
|
- `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
|
|
853
|
+
- `pnpm test` / `pnpm test:watch` — **Vitest** and **Testing Library** unit tests // currently covered 792 test cases in total
|
|
851
854
|
- `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
855
|
- `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
856
|
- **`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
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
* command, or refused `init` overwrite. Successful runs leave default exit 0.
|
|
85
85
|
*/
|
|
86
86
|
|
|
87
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
87
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
88
88
|
import {
|
|
89
89
|
readFile,
|
|
90
90
|
writeFile,
|
|
@@ -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`.
|
|
1055
|
+
*/
|
|
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`.
|
|
1028
1080
|
*/
|
|
1029
|
-
async function
|
|
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)) {
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
const absSrc = join(srcRoot, `${entryName}.ts`);
|
|
1110
|
+
if (!existsSync(absSrc)) {
|
|
1046
1111
|
continue;
|
|
1047
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) {
|
|
@@ -1401,7 +1464,50 @@ async function main() {
|
|
|
1401
1464
|
process.exitCode = 1;
|
|
1402
1465
|
}
|
|
1403
1466
|
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1467
|
+
export {
|
|
1468
|
+
buildCompactThemeCss,
|
|
1469
|
+
cmdAdd,
|
|
1470
|
+
cmdInit,
|
|
1471
|
+
cmdTheme,
|
|
1472
|
+
collectHookTransitiveClosure,
|
|
1473
|
+
copyDesignSystemFiles,
|
|
1474
|
+
copyHookFolder,
|
|
1475
|
+
copyUiComponent,
|
|
1476
|
+
defaultConfig,
|
|
1477
|
+
detectFramework,
|
|
1478
|
+
findComponentsJson,
|
|
1479
|
+
getMissingDependencies,
|
|
1480
|
+
importPathFor,
|
|
1481
|
+
isTestFile,
|
|
1482
|
+
loadRegistry,
|
|
1483
|
+
main,
|
|
1484
|
+
normalizeHexColor,
|
|
1485
|
+
printAdoptionHints,
|
|
1486
|
+
printInfo,
|
|
1487
|
+
printList,
|
|
1488
|
+
resolveComponentName,
|
|
1489
|
+
resolveHookName,
|
|
1490
|
+
validateConfig,
|
|
1491
|
+
walkFiles,
|
|
1492
|
+
};
|
|
1493
|
+
|
|
1494
|
+
function isDirectCliRun() {
|
|
1495
|
+
if (!process.argv[1]) {
|
|
1496
|
+
return false;
|
|
1497
|
+
}
|
|
1498
|
+
try {
|
|
1499
|
+
return (
|
|
1500
|
+
realpathSync(process.argv[1]) ===
|
|
1501
|
+
realpathSync(fileURLToPath(import.meta.url))
|
|
1502
|
+
);
|
|
1503
|
+
} catch {
|
|
1504
|
+
return resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
if (isDirectCliRun()) {
|
|
1509
|
+
main().catch((err) => {
|
|
1510
|
+
console.error(err instanceof Error ? err.message : err);
|
|
1511
|
+
process.exitCode = 1;
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdtempSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
|
|
11
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
cmdAdd,
|
|
15
|
+
cmdInit,
|
|
16
|
+
cmdTheme,
|
|
17
|
+
defaultConfig,
|
|
18
|
+
importPathFor,
|
|
19
|
+
normalizeHexColor,
|
|
20
|
+
resolveComponentName,
|
|
21
|
+
resolveHookName,
|
|
22
|
+
validateConfig,
|
|
23
|
+
} from "./index.mjs";
|
|
24
|
+
|
|
25
|
+
type TestRegistry = Parameters<typeof resolveComponentName>[1] &
|
|
26
|
+
Parameters<typeof resolveHookName>[1];
|
|
27
|
+
|
|
28
|
+
function makeTempDir(prefix: string) {
|
|
29
|
+
return mkdtempSync(join(tmpdir(), prefix));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function silenceConsole() {
|
|
33
|
+
const logs: string[] = [];
|
|
34
|
+
const errors: string[] = [];
|
|
35
|
+
|
|
36
|
+
vi.spyOn(console, "log").mockImplementation((...args) => {
|
|
37
|
+
logs.push(args.join(" "));
|
|
38
|
+
});
|
|
39
|
+
vi.spyOn(console, "error").mockImplementation((...args) => {
|
|
40
|
+
errors.push(args.join(" "));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return { errors, logs };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("CLI module commands", () => {
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
vi.restoreAllMocks();
|
|
49
|
+
process.exitCode = undefined;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("resolves aliases, hooks, imports, hex colors, and config validation", () => {
|
|
53
|
+
const registry: TestRegistry = {
|
|
54
|
+
components: ["buttons", "card", "charts/line"],
|
|
55
|
+
hooks: ["useWindowSize"],
|
|
56
|
+
nameAliases: { button: "buttons" },
|
|
57
|
+
animatedComponents: ["buttons", "spinner"],
|
|
58
|
+
uiComponents: ["buttons", "card"],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
expect(resolveComponentName("button", registry)).toBe("buttons");
|
|
62
|
+
expect(resolveComponentName("CARD", registry)).toBe("card");
|
|
63
|
+
expect(resolveHookName("usewindowsize", registry)).toBe("useWindowSize");
|
|
64
|
+
expect(importPathFor("useWindowSize", "hook", registry)).toBe(
|
|
65
|
+
"@zentauri-ui/zentauri-components/hooks/useWindowSize",
|
|
66
|
+
);
|
|
67
|
+
expect(importPathFor("charts/line", "component", registry)).toBe(
|
|
68
|
+
"@zentauri-ui/zentauri-components/charts/line",
|
|
69
|
+
);
|
|
70
|
+
expect(importPathFor("spinner", "component", registry)).toBe(
|
|
71
|
+
"@zentauri-ui/zentauri-components/ui/spinner/animated",
|
|
72
|
+
);
|
|
73
|
+
expect(normalizeHexColor("38b")).toBe("#3388bb");
|
|
74
|
+
expect(() => validateConfig(defaultConfig())).not.toThrow();
|
|
75
|
+
expect(() => resolveComponentName("missing", registry)).toThrow(
|
|
76
|
+
/Unknown component/,
|
|
77
|
+
);
|
|
78
|
+
expect(() => normalizeHexColor("not-a-color")).toThrow(
|
|
79
|
+
/Invalid brand color/,
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("initializes components.json with framework-aware guidance", async () => {
|
|
84
|
+
const dir = makeTempDir("zentauri-cli-module-init-");
|
|
85
|
+
const { errors, logs } = silenceConsole();
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
writeFileSync(
|
|
89
|
+
join(dir, "package.json"),
|
|
90
|
+
JSON.stringify({ dependencies: { next: "16.0.0" } }),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
await cmdInit(dir);
|
|
94
|
+
|
|
95
|
+
expect(existsSync(join(dir, "components.json"))).toBe(true);
|
|
96
|
+
expect(
|
|
97
|
+
JSON.parse(readFileSync(join(dir, "components.json"), "utf8")),
|
|
98
|
+
).toEqual(defaultConfig());
|
|
99
|
+
expect(logs.join("\n")).toContain("Detected framework: Next.js");
|
|
100
|
+
expect(logs.join("\n")).toContain('@source "./src/components/ui";');
|
|
101
|
+
|
|
102
|
+
await cmdInit(dir);
|
|
103
|
+
|
|
104
|
+
expect(process.exitCode).toBe(1);
|
|
105
|
+
expect(errors.join("\n")).toContain("Refusing to overwrite existing");
|
|
106
|
+
} finally {
|
|
107
|
+
rmSync(dir, { recursive: true, force: true });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("vendors components, animated entries, design tokens, and transitive hooks", async () => {
|
|
112
|
+
const dir = makeTempDir("zentauri-cli-module-add-");
|
|
113
|
+
const { logs } = silenceConsole();
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
await cmdInit(dir);
|
|
117
|
+
await cmdAdd(["button"], dir);
|
|
118
|
+
await cmdAdd(["hook", "usePrefersReducedMotion"], dir);
|
|
119
|
+
await cmdAdd(["button"], dir, { animated: true });
|
|
120
|
+
|
|
121
|
+
expect(
|
|
122
|
+
existsSync(join(dir, "src/components/ui/buttons/button.tsx")),
|
|
123
|
+
).toBe(true);
|
|
124
|
+
expect(
|
|
125
|
+
existsSync(join(dir, "src/components/ui/buttons/animated/index.ts")),
|
|
126
|
+
).toBe(true);
|
|
127
|
+
expect(
|
|
128
|
+
existsSync(join(dir, "src/components/design-system/button.ts")),
|
|
129
|
+
).toBe(true);
|
|
130
|
+
expect(
|
|
131
|
+
existsSync(join(dir, "src/components/design-system/tokens.ts")),
|
|
132
|
+
).toBe(true);
|
|
133
|
+
expect(
|
|
134
|
+
existsSync(
|
|
135
|
+
join(
|
|
136
|
+
dir,
|
|
137
|
+
"src/hooks/usePrefersReducedMotion/usePrefersReducedMotion.ts",
|
|
138
|
+
),
|
|
139
|
+
),
|
|
140
|
+
).toBe(true);
|
|
141
|
+
expect(
|
|
142
|
+
existsSync(join(dir, "src/hooks/useMediaQuery/useMediaQuery.ts")),
|
|
143
|
+
).toBe(true);
|
|
144
|
+
expect(
|
|
145
|
+
readFileSync(
|
|
146
|
+
join(dir, "src/components/ui/buttons/button-base.tsx"),
|
|
147
|
+
"utf8",
|
|
148
|
+
),
|
|
149
|
+
).toContain('from "@/lib/utils"');
|
|
150
|
+
expect(logs.join("\n")).toContain("Including animated entry for buttons");
|
|
151
|
+
expect(logs.join("\n")).toContain("Missing peer dependencies");
|
|
152
|
+
} finally {
|
|
153
|
+
rmSync(dir, { recursive: true, force: true });
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("generates theme CSS to stdout or a requested file", async () => {
|
|
158
|
+
const dir = makeTempDir("zentauri-cli-module-theme-");
|
|
159
|
+
const { errors, logs } = silenceConsole();
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
await cmdTheme("#2563eb", { dark: "#60a5fa" }, dir);
|
|
163
|
+
expect(logs.join("\n")).toContain("--zui-brand: #2563eb;");
|
|
164
|
+
expect(logs.join("\n")).toContain("--zui-brand-dark: #60a5fa;");
|
|
165
|
+
|
|
166
|
+
await cmdTheme("38bdf8", { out: "src/styles/zentauri-theme.css" }, dir);
|
|
167
|
+
const themePath = join(dir, "src/styles/zentauri-theme.css");
|
|
168
|
+
expect(existsSync(themePath)).toBe(true);
|
|
169
|
+
expect(readFileSync(themePath, "utf8")).toContain(
|
|
170
|
+
"--zui-brand: #38bdf8;",
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
await cmdTheme("", {}, dir);
|
|
174
|
+
expect(process.exitCode).toBe(1);
|
|
175
|
+
expect(errors.join("\n")).toContain("Usage: zentauri-components theme");
|
|
176
|
+
} finally {
|
|
177
|
+
rmSync(dir, { recursive: true, force: true });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|