@expcat/tigercat-cli 1.1.0 → 1.2.0
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 +110 -6
- package/dist/index.js +417 -59
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @expcat/tigercat-cli
|
|
2
2
|
|
|
3
|
-
CLI tooling for the [Tigercat](https://github.com/
|
|
3
|
+
CLI tooling for the [Tigercat](https://github.com/expcat/Tigercat) UI component library.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -10,6 +10,84 @@ pnpm add -g @expcat/tigercat-cli
|
|
|
10
10
|
npx @expcat/tigercat-cli
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
## Usage Examples
|
|
14
|
+
|
|
15
|
+
### Start a Vue 3 project
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
tigercat create admin-console --template vue3
|
|
19
|
+
cd admin-console
|
|
20
|
+
pnpm install
|
|
21
|
+
pnpm dev
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Preview the generated file list without writing files:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
tigercat create admin-console --template vue3 --dry-run
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Start a React project
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
tigercat create design-lab --template react
|
|
34
|
+
cd design-lab
|
|
35
|
+
pnpm install
|
|
36
|
+
pnpm dev
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Add component boilerplate to an existing project
|
|
40
|
+
|
|
41
|
+
Run from a project that already depends on `@expcat/tigercat-vue` or `@expcat/tigercat-react`:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
tigercat add Button Form Input Select
|
|
45
|
+
tigercat add --framework vue3 --install --snippet src/tigercat-components.ts
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
When no component names are provided, `add` opens an interactive multi-select prompt.
|
|
49
|
+
The command prints the correct package import, detects missing peer dependencies,
|
|
50
|
+
can install them with `--install`, and creates `src/components/*Demo.vue` or
|
|
51
|
+
`src/components/*Demo.tsx` files when a `src/components` directory exists.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
tigercat add Button Form Input Select --dry-run
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Open a temporary playground
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
tigercat playground --template vue3 --port 3456
|
|
61
|
+
tigercat playground --template react --port 3457
|
|
62
|
+
tigercat playground --template react --no-open
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Playground files are created under `.tigercat-playground/` in the current working directory.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
tigercat playground --template react --port 3457 --dry-run
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Generate API docs from type definitions
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
tigercat generate docs --input packages/core/src/types --output docs/api
|
|
75
|
+
tigercat generate docs --input packages/core/src/types --output docs/api --dry-run
|
|
76
|
+
tigercat generate test Button --framework both
|
|
77
|
+
tigercat generate doc-template Button --output docs/components
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Check project compatibility
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
tigercat doctor
|
|
84
|
+
tigercat doctor --json
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`doctor` verifies `package.json`, Node.js, pnpm, Tailwind CSS, Tigercat peer dependencies,
|
|
88
|
+
template dependency compatibility, and the supported version compatibility matrix. JSON output is
|
|
89
|
+
designed for CI and automated diagnostics.
|
|
90
|
+
|
|
13
91
|
## Commands
|
|
14
92
|
|
|
15
93
|
### `tigercat create <name>`
|
|
@@ -19,6 +97,7 @@ Create a new project with Tigercat pre-configured.
|
|
|
19
97
|
```bash
|
|
20
98
|
tigercat create my-app --template vue3
|
|
21
99
|
tigercat create my-app --template react
|
|
100
|
+
tigercat create my-app --template vue3 --dry-run
|
|
22
101
|
```
|
|
23
102
|
|
|
24
103
|
### `tigercat add <component>`
|
|
@@ -28,6 +107,8 @@ Add a component to your project with import boilerplate.
|
|
|
28
107
|
```bash
|
|
29
108
|
tigercat add Button
|
|
30
109
|
tigercat add Form Input Select DatePicker
|
|
110
|
+
tigercat add --framework react --install --snippet src/tigercat-components.ts
|
|
111
|
+
tigercat add Button --dry-run
|
|
31
112
|
```
|
|
32
113
|
|
|
33
114
|
### `tigercat playground`
|
|
@@ -37,6 +118,8 @@ Launch an interactive playground for testing components.
|
|
|
37
118
|
```bash
|
|
38
119
|
tigercat playground
|
|
39
120
|
tigercat playground --template react
|
|
121
|
+
tigercat playground --template react --no-open
|
|
122
|
+
tigercat playground --template react --dry-run
|
|
40
123
|
```
|
|
41
124
|
|
|
42
125
|
### `tigercat generate docs`
|
|
@@ -46,6 +129,26 @@ Generate API documentation from component type definitions.
|
|
|
46
129
|
```bash
|
|
47
130
|
tigercat generate docs
|
|
48
131
|
tigercat generate docs --output ./docs/api
|
|
132
|
+
tigercat generate docs --output ./docs/api --dry-run
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `tigercat generate test`
|
|
136
|
+
|
|
137
|
+
Generate Vue and/or React starter test templates for a component.
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
tigercat generate test Button --framework both
|
|
141
|
+
tigercat generate test Button --framework vue3
|
|
142
|
+
tigercat generate test Button --framework react --dry-run
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### `tigercat generate doc-template`
|
|
146
|
+
|
|
147
|
+
Generate a component documentation page template.
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
tigercat generate doc-template Button
|
|
151
|
+
tigercat generate doc-template Button --output docs/components --dry-run
|
|
49
152
|
```
|
|
50
153
|
|
|
51
154
|
### `tigercat doctor`
|
|
@@ -54,6 +157,7 @@ Check whether the current project has compatible Node, pnpm, Tailwind CSS, Tiger
|
|
|
54
157
|
|
|
55
158
|
```bash
|
|
56
159
|
tigercat doctor
|
|
160
|
+
tigercat doctor --json
|
|
57
161
|
```
|
|
58
162
|
|
|
59
163
|
## Windows Support
|
|
@@ -67,11 +171,11 @@ with backslash paths, paths containing spaces, and UNC paths.
|
|
|
67
171
|
When installed globally or locally, each package manager creates platform-specific shims
|
|
68
172
|
so that `tigercat` can be invoked directly from PowerShell, CMD, or Git Bash:
|
|
69
173
|
|
|
70
|
-
| Package manager | Global install
|
|
71
|
-
| --------------- |
|
|
72
|
-
| **pnpm** | `pnpm add -g @expcat/tigercat-cli`
|
|
73
|
-
| **npm** | `npm i -g @expcat/tigercat-cli`
|
|
74
|
-
| **bun** | `bun add -g @expcat/tigercat-cli`
|
|
174
|
+
| Package manager | Global install | Shim files created |
|
|
175
|
+
| --------------- | ---------------------------------- | ----------------------------------------------- |
|
|
176
|
+
| **pnpm** | `pnpm add -g @expcat/tigercat-cli` | `tigercat.cmd`, `tigercat` (sh) |
|
|
177
|
+
| **npm** | `npm i -g @expcat/tigercat-cli` | `tigercat.cmd`, `tigercat` (sh), `tigercat.ps1` |
|
|
178
|
+
| **bun** | `bun add -g @expcat/tigercat-cli` | `tigercat.cmd`, `tigercat` (sh) |
|
|
75
179
|
|
|
76
180
|
For local (non-global) installs, run via `npx tigercat`, `pnpm exec tigercat`, or
|
|
77
181
|
`bunx tigercat`. The `#!/usr/bin/env node` shebang in the built output is used by all
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Command } from 'commander';
|
|
|
3
3
|
import prompts from 'prompts';
|
|
4
4
|
import { existsSync, readdirSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
5
5
|
import { resolve, join, dirname, basename } from 'path';
|
|
6
|
-
import
|
|
6
|
+
import pc2 from 'picocolors';
|
|
7
7
|
import { execSync } from 'child_process';
|
|
8
8
|
|
|
9
9
|
// src/constants.ts
|
|
@@ -87,19 +87,19 @@ var COMPONENT_CATEGORIES = {
|
|
|
87
87
|
};
|
|
88
88
|
var ALL_COMPONENTS = Object.values(COMPONENT_CATEGORIES).flat();
|
|
89
89
|
function logSuccess(msg) {
|
|
90
|
-
console.log(
|
|
90
|
+
console.log(pc2.green("\u2714") + " " + msg);
|
|
91
91
|
}
|
|
92
92
|
function logInfo(msg) {
|
|
93
|
-
console.log(
|
|
93
|
+
console.log(pc2.blue("\u2139") + " " + msg);
|
|
94
94
|
}
|
|
95
|
-
function
|
|
96
|
-
console.log(
|
|
95
|
+
function logWarn2(msg) {
|
|
96
|
+
console.log(pc2.yellow("\u26A0") + " " + msg);
|
|
97
97
|
}
|
|
98
98
|
function logError(msg) {
|
|
99
|
-
console.error(
|
|
99
|
+
console.error(pc2.red("\u2716") + " " + msg);
|
|
100
100
|
}
|
|
101
101
|
function logStep(step, total, msg) {
|
|
102
|
-
console.log(
|
|
102
|
+
console.log(pc2.dim(`[${step}/${total}]`) + " " + msg);
|
|
103
103
|
}
|
|
104
104
|
function ensureDir(dir) {
|
|
105
105
|
if (!existsSync(dir)) {
|
|
@@ -296,7 +296,7 @@ function vue3EnvDts() {
|
|
|
296
296
|
|
|
297
297
|
declare module '*.vue' {
|
|
298
298
|
import type { DefineComponent } from 'vue'
|
|
299
|
-
const component: DefineComponent<{}, {},
|
|
299
|
+
const component: DefineComponent<{}, {}, unknown>
|
|
300
300
|
export default component
|
|
301
301
|
}
|
|
302
302
|
`;
|
|
@@ -529,11 +529,11 @@ html {
|
|
|
529
529
|
|
|
530
530
|
// src/commands/create.ts
|
|
531
531
|
function createCreateCommand() {
|
|
532
|
-
return new Command("create").argument("<name>", "Project name").option("-t, --template <template>", "Project template (vue3 | react)").description("Create a new project with Tigercat pre-configured").action(async (name, opts) => {
|
|
533
|
-
await runCreate(name, opts.template);
|
|
532
|
+
return new Command("create").argument("<name>", "Project name").option("-t, --template <template>", "Project template (vue3 | react)").option("--dry-run", "Preview files without writing them").description("Create a new project with Tigercat pre-configured").action(async (name, opts) => {
|
|
533
|
+
await runCreate(name, opts.template, Boolean(opts.dryRun));
|
|
534
534
|
});
|
|
535
535
|
}
|
|
536
|
-
async function runCreate(name, templateArg) {
|
|
536
|
+
async function runCreate(name, templateArg, dryRun = false) {
|
|
537
537
|
let template;
|
|
538
538
|
if (templateArg && TEMPLATES.includes(templateArg)) {
|
|
539
539
|
template = templateArg;
|
|
@@ -554,7 +554,7 @@ async function runCreate(name, templateArg) {
|
|
|
554
554
|
template = response.template;
|
|
555
555
|
}
|
|
556
556
|
const targetDir = resolve(process.cwd(), name);
|
|
557
|
-
if (existsSync(targetDir) && !isDirEmpty(targetDir)) {
|
|
557
|
+
if (!dryRun && existsSync(targetDir) && !isDirEmpty(targetDir)) {
|
|
558
558
|
const { overwrite } = await prompts({
|
|
559
559
|
type: "confirm",
|
|
560
560
|
name: "overwrite",
|
|
@@ -568,6 +568,13 @@ async function runCreate(name, templateArg) {
|
|
|
568
568
|
}
|
|
569
569
|
logInfo(`Creating ${template} project in ${targetDir}...`);
|
|
570
570
|
const files = template === "vue3" ? getVue3Template(name) : getReactTemplate(name);
|
|
571
|
+
if (dryRun) {
|
|
572
|
+
logInfo("Dry run: no files will be written.");
|
|
573
|
+
for (const filePath of Object.keys(files)) {
|
|
574
|
+
console.log(` ${filePath}`);
|
|
575
|
+
}
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
571
578
|
const totalSteps = Object.keys(files).length;
|
|
572
579
|
let step = 0;
|
|
573
580
|
ensureDir(targetDir);
|
|
@@ -584,8 +591,8 @@ async function runCreate(name, templateArg) {
|
|
|
584
591
|
console.log(" pnpm dev\n");
|
|
585
592
|
}
|
|
586
593
|
function createAddCommand() {
|
|
587
|
-
return new Command("add").argument("
|
|
588
|
-
await runAdd(components);
|
|
594
|
+
return new Command("add").argument("[components...]", "Component names to add (e.g. Button Input Select)").option("-f, --framework <framework>", "Framework override (vue3 | react)").option("--install", "Install missing Tigercat dependencies before generating snippets").option("--snippet <file>", "Generate a reusable import snippet file").option("--dry-run", "Preview generated demo files without writing them").description("Add component import boilerplate to your project").action(async (components, opts) => {
|
|
595
|
+
await runAdd(components ?? [], opts);
|
|
589
596
|
});
|
|
590
597
|
}
|
|
591
598
|
function detectFramework(cwd) {
|
|
@@ -600,6 +607,45 @@ function detectFramework(cwd) {
|
|
|
600
607
|
}
|
|
601
608
|
return null;
|
|
602
609
|
}
|
|
610
|
+
function normalizeFramework(value) {
|
|
611
|
+
if (value === "vue3" || value === "react") return value;
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
async function resolveComponents(components) {
|
|
615
|
+
if (components.length > 0) return components;
|
|
616
|
+
const response = await prompts({
|
|
617
|
+
type: "multiselect",
|
|
618
|
+
name: "components",
|
|
619
|
+
message: "Select components to add",
|
|
620
|
+
choices: ALL_COMPONENTS.map((component) => ({ title: component, value: component })),
|
|
621
|
+
min: 1
|
|
622
|
+
});
|
|
623
|
+
return response.components ?? [];
|
|
624
|
+
}
|
|
625
|
+
function collectDependencies(framework) {
|
|
626
|
+
return framework === "vue3" ? ["@expcat/tigercat-vue", "@expcat/tigercat-core", "vue"] : ["@expcat/tigercat-react", "@expcat/tigercat-core", "react", "react-dom"];
|
|
627
|
+
}
|
|
628
|
+
function readPackageDeps(cwd) {
|
|
629
|
+
const pkg = readFileSafe(join(cwd, "package.json"));
|
|
630
|
+
if (!pkg) return {};
|
|
631
|
+
try {
|
|
632
|
+
const parsed = JSON.parse(pkg);
|
|
633
|
+
return { ...parsed.dependencies, ...parsed.devDependencies, ...parsed.peerDependencies };
|
|
634
|
+
} catch {
|
|
635
|
+
return {};
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function detectPackageManager(cwd) {
|
|
639
|
+
if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
640
|
+
if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
|
|
641
|
+
return "npm";
|
|
642
|
+
}
|
|
643
|
+
function formatAddCommand(packageManager, dependencies) {
|
|
644
|
+
const deps = dependencies.join(" ");
|
|
645
|
+
if (packageManager === "yarn") return `yarn add ${deps}`;
|
|
646
|
+
if (packageManager === "npm") return `npm install ${deps}`;
|
|
647
|
+
return `pnpm add ${deps}`;
|
|
648
|
+
}
|
|
603
649
|
function validateComponents(names) {
|
|
604
650
|
const valid = [];
|
|
605
651
|
const invalid = [];
|
|
@@ -613,18 +659,20 @@ function validateComponents(names) {
|
|
|
613
659
|
}
|
|
614
660
|
return { valid, invalid };
|
|
615
661
|
}
|
|
616
|
-
async function runAdd(components) {
|
|
662
|
+
async function runAdd(components, options = {}) {
|
|
617
663
|
const cwd = process.cwd();
|
|
618
|
-
const framework = detectFramework(cwd);
|
|
664
|
+
const framework = normalizeFramework(options.framework) ?? detectFramework(cwd);
|
|
665
|
+
const dryRun = Boolean(options.dryRun);
|
|
619
666
|
if (!framework) {
|
|
620
667
|
logError(
|
|
621
668
|
"Could not detect framework. Make sure you are in a project with @expcat/tigercat-vue or @expcat/tigercat-react installed."
|
|
622
669
|
);
|
|
623
670
|
process.exit(1);
|
|
624
671
|
}
|
|
625
|
-
const
|
|
672
|
+
const selectedComponents = await resolveComponents(components);
|
|
673
|
+
const { valid, invalid } = validateComponents(selectedComponents);
|
|
626
674
|
if (invalid.length > 0) {
|
|
627
|
-
|
|
675
|
+
logWarn2(`Unknown components: ${invalid.join(", ")}`);
|
|
628
676
|
logInfo(`Available: ${ALL_COMPONENTS.join(", ")}`);
|
|
629
677
|
}
|
|
630
678
|
if (valid.length === 0) {
|
|
@@ -632,11 +680,34 @@ async function runAdd(components) {
|
|
|
632
680
|
process.exit(1);
|
|
633
681
|
}
|
|
634
682
|
const pkgName = framework === "vue3" ? "@expcat/tigercat-vue" : "@expcat/tigercat-react";
|
|
683
|
+
const requiredDeps = collectDependencies(framework);
|
|
684
|
+
const installedDeps = readPackageDeps(cwd);
|
|
685
|
+
const missingDeps = requiredDeps.filter((dependency) => !installedDeps[dependency]);
|
|
686
|
+
if (missingDeps.length > 0) {
|
|
687
|
+
const packageManager = detectPackageManager(cwd);
|
|
688
|
+
const installCommand = formatAddCommand(packageManager, missingDeps);
|
|
689
|
+
if (options.install && !dryRun) {
|
|
690
|
+
logInfo(`Installing missing dependencies: ${missingDeps.join(", ")}`);
|
|
691
|
+
execSync(installCommand, { cwd, stdio: "inherit" });
|
|
692
|
+
} else {
|
|
693
|
+
logInfo(`Missing dependencies detected. Run: ${installCommand}`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
635
696
|
const importLine = `import { ${valid.join(", ")} } from '${pkgName}'`;
|
|
636
697
|
logSuccess(`Add this import to your project:
|
|
637
698
|
`);
|
|
638
699
|
console.log(` ${importLine}
|
|
639
700
|
`);
|
|
701
|
+
if (options.snippet) {
|
|
702
|
+
const snippetFile = resolve(cwd, options.snippet);
|
|
703
|
+
const snippet = generateImportSnippet(valid, pkgName);
|
|
704
|
+
if (dryRun) {
|
|
705
|
+
logInfo(`Would create import snippet ${snippetFile}`);
|
|
706
|
+
} else {
|
|
707
|
+
writeFileSafe(snippetFile, snippet);
|
|
708
|
+
logSuccess(`Created import snippet ${snippetFile}`);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
640
711
|
if (framework === "vue3") {
|
|
641
712
|
logInfo("Vue 3 usage example:\n");
|
|
642
713
|
for (const comp of valid) {
|
|
@@ -653,11 +724,18 @@ async function runAdd(components) {
|
|
|
653
724
|
if (!existsSync(sampleDir)) {
|
|
654
725
|
return;
|
|
655
726
|
}
|
|
727
|
+
if (dryRun) {
|
|
728
|
+
logInfo("Dry run: no demo files will be written.");
|
|
729
|
+
}
|
|
656
730
|
for (const comp of valid) {
|
|
657
731
|
const ext = framework === "vue3" ? "vue" : "tsx";
|
|
658
732
|
const sampleFile = join(sampleDir, `${comp}Demo.${ext}`);
|
|
659
733
|
if (existsSync(sampleFile)) {
|
|
660
|
-
|
|
734
|
+
logWarn2(`${sampleFile} already exists, skipping`);
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
if (dryRun) {
|
|
738
|
+
logInfo(`Would create ${sampleFile}`);
|
|
661
739
|
continue;
|
|
662
740
|
}
|
|
663
741
|
const content = framework === "vue3" ? generateVue3Demo(comp, pkgName) : generateReactDemo(comp, pkgName);
|
|
@@ -665,6 +743,14 @@ async function runAdd(components) {
|
|
|
665
743
|
logSuccess(`Created ${sampleFile}`);
|
|
666
744
|
}
|
|
667
745
|
}
|
|
746
|
+
function generateImportSnippet(components, pkg) {
|
|
747
|
+
return `import { ${components.join(", ")} } from '${pkg}'
|
|
748
|
+
|
|
749
|
+
export const tigercatComponents = {
|
|
750
|
+
${components.map((component) => ` ${component}`).join(",\n")}
|
|
751
|
+
}
|
|
752
|
+
`;
|
|
753
|
+
}
|
|
668
754
|
function generateVue3Demo(component, pkg) {
|
|
669
755
|
return `<script setup lang="ts">
|
|
670
756
|
import { ${component} } from '${pkg}'
|
|
@@ -692,11 +778,11 @@ export default function ${component}Demo() {
|
|
|
692
778
|
`;
|
|
693
779
|
}
|
|
694
780
|
function createPlaygroundCommand() {
|
|
695
|
-
return new Command("playground").option("-t, --template <template>", "Framework template (vue3 | react)").option("-p, --port <port>", "Dev server port", "3456").description("Launch an interactive playground for testing components").action(async (opts) => {
|
|
696
|
-
await runPlayground(opts.template, opts.port);
|
|
781
|
+
return new Command("playground").option("-t, --template <template>", "Framework template (vue3 | react)").option("-p, --port <port>", "Dev server port", "3456").option("--no-open", "Do not open the playground in the default browser").option("--dry-run", "Preview playground setup without writing files or starting Vite").description("Launch an interactive playground for testing components").action(async (opts) => {
|
|
782
|
+
await runPlayground(opts.template, opts.port, opts.open !== false, Boolean(opts.dryRun));
|
|
697
783
|
});
|
|
698
784
|
}
|
|
699
|
-
async function runPlayground(templateArg, port = "3456") {
|
|
785
|
+
async function runPlayground(templateArg, port = "3456", open = true, dryRun = false) {
|
|
700
786
|
let template;
|
|
701
787
|
if (templateArg && TEMPLATES.includes(templateArg)) {
|
|
702
788
|
template = templateArg;
|
|
@@ -718,6 +804,19 @@ async function runPlayground(templateArg, port = "3456") {
|
|
|
718
804
|
}
|
|
719
805
|
const tmpDir = resolve(process.cwd(), ".tigercat-playground");
|
|
720
806
|
const projectDir = join(tmpDir, `playground-${template}`);
|
|
807
|
+
if (dryRun) {
|
|
808
|
+
const safePort = /^\d+$/.test(port) ? port : "3456";
|
|
809
|
+
logInfo(`Dry run: would prepare ${template} playground in ${projectDir}.`);
|
|
810
|
+
if (!existsSync(projectDir)) {
|
|
811
|
+
const files = template === "vue3" ? getVue3Template("playground") : getReactTemplate("playground");
|
|
812
|
+
for (const filePath of Object.keys(files)) {
|
|
813
|
+
console.log(` ${filePath}`);
|
|
814
|
+
}
|
|
815
|
+
logInfo("Would run pnpm install");
|
|
816
|
+
}
|
|
817
|
+
logInfo(`Would start Vite on port ${safePort}${open ? " and open the browser" : ""}`);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
721
820
|
if (!existsSync(projectDir)) {
|
|
722
821
|
logInfo(`Setting up ${template} playground...`);
|
|
723
822
|
const files = template === "vue3" ? getVue3Template("playground") : getReactTemplate("playground");
|
|
@@ -741,14 +840,23 @@ async function runPlayground(templateArg, port = "3456") {
|
|
|
741
840
|
`);
|
|
742
841
|
try {
|
|
743
842
|
const safePort = /^\d+$/.test(port) ? port : "3456";
|
|
744
|
-
|
|
843
|
+
const openFlag = open ? " --open" : "";
|
|
844
|
+
execSync(`npx vite --port ${safePort}${openFlag}`, { cwd: projectDir, stdio: "inherit" });
|
|
745
845
|
} catch {
|
|
746
846
|
}
|
|
747
847
|
}
|
|
748
848
|
function createGenerateCommand() {
|
|
749
849
|
const cmd = new Command("generate").description("Code generation utilities");
|
|
750
|
-
cmd.command("docs").option("-i, --input <dir>", "Types directory", "packages/core/src/types").option("-o, --output <dir>", "Output directory", "docs/api").description("Generate API documentation from component type definitions").action(async (opts) => {
|
|
751
|
-
await runGenerateDocs(opts.input, opts.output);
|
|
850
|
+
cmd.command("docs").option("-i, --input <dir>", "Types directory", "packages/core/src/types").option("-o, --output <dir>", "Output directory", "docs/api").option("--dry-run", "Preview generated docs without writing files").description("Generate API documentation from component type definitions").action(async (opts) => {
|
|
851
|
+
await runGenerateDocs(opts.input, opts.output, Boolean(opts.dryRun));
|
|
852
|
+
});
|
|
853
|
+
cmd.command("test <component>").option("-f, --framework <framework>", "Target framework (vue3 | react | both)", "both").option("-o, --output <dir>", "Tests root directory", "tests").option("--dry-run", "Preview generated test files without writing files").description("Generate starter test templates for a component").action(
|
|
854
|
+
async (component, opts) => {
|
|
855
|
+
await runGenerateTest(component, opts.framework, opts.output, Boolean(opts.dryRun));
|
|
856
|
+
}
|
|
857
|
+
);
|
|
858
|
+
cmd.command("doc-template <component>").option("-o, --output <dir>", "Documentation output directory", "docs/components").option("--dry-run", "Preview generated documentation without writing files").description("Generate a component documentation page template").action(async (component, opts) => {
|
|
859
|
+
await runGenerateDocTemplate(component, opts.output, Boolean(opts.dryRun));
|
|
752
860
|
});
|
|
753
861
|
return cmd;
|
|
754
862
|
}
|
|
@@ -805,7 +913,7 @@ function generateMarkdown(doc) {
|
|
|
805
913
|
}
|
|
806
914
|
return lines.join("\n");
|
|
807
915
|
}
|
|
808
|
-
async function runGenerateDocs(inputDir, outputDir) {
|
|
916
|
+
async function runGenerateDocs(inputDir, outputDir, dryRun = false) {
|
|
809
917
|
const resolvedInput = resolve(process.cwd(), inputDir);
|
|
810
918
|
const resolvedOutput = resolve(process.cwd(), outputDir);
|
|
811
919
|
if (!existsSync(resolvedInput)) {
|
|
@@ -814,7 +922,11 @@ async function runGenerateDocs(inputDir, outputDir) {
|
|
|
814
922
|
}
|
|
815
923
|
const files = readdirSync(resolvedInput).filter((f) => f.endsWith(".ts") && f !== "index.ts").sort();
|
|
816
924
|
logInfo(`Found ${files.length} type files in ${inputDir}`);
|
|
817
|
-
|
|
925
|
+
if (dryRun) {
|
|
926
|
+
logInfo("Dry run: no documentation files will be written.");
|
|
927
|
+
} else {
|
|
928
|
+
ensureDir(resolvedOutput);
|
|
929
|
+
}
|
|
818
930
|
const docs = [];
|
|
819
931
|
let step = 0;
|
|
820
932
|
for (const file of files) {
|
|
@@ -824,7 +936,12 @@ async function runGenerateDocs(inputDir, outputDir) {
|
|
|
824
936
|
if (doc) {
|
|
825
937
|
docs.push(doc);
|
|
826
938
|
const md = generateMarkdown(doc);
|
|
827
|
-
|
|
939
|
+
const outputPath = join(resolvedOutput, `${doc.fileName}.md`);
|
|
940
|
+
if (dryRun) {
|
|
941
|
+
logInfo(`Would generate ${outputPath}`);
|
|
942
|
+
} else {
|
|
943
|
+
writeFileSafe(outputPath, md);
|
|
944
|
+
}
|
|
828
945
|
}
|
|
829
946
|
}
|
|
830
947
|
const indexLines = [
|
|
@@ -847,13 +964,184 @@ async function runGenerateDocs(inputDir, outputDir) {
|
|
|
847
964
|
}
|
|
848
965
|
indexLines.push("");
|
|
849
966
|
}
|
|
850
|
-
|
|
967
|
+
const indexPath = join(resolvedOutput, "index.md");
|
|
968
|
+
if (dryRun) {
|
|
969
|
+
logInfo(`Would generate ${indexPath}`);
|
|
970
|
+
logSuccess(`Dry run completed for ${docs.length} component docs in ${outputDir}`);
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
writeFileSafe(indexPath, indexLines.join("\n"));
|
|
851
974
|
logSuccess(`Generated docs for ${docs.length} components in ${outputDir}`);
|
|
852
975
|
}
|
|
976
|
+
async function runGenerateTest(component, frameworkArg, outputDir, dryRun = false) {
|
|
977
|
+
const componentName = normalizeComponentName(component);
|
|
978
|
+
if (!componentName) {
|
|
979
|
+
logError(`Unknown component: ${component}`);
|
|
980
|
+
process.exit(1);
|
|
981
|
+
}
|
|
982
|
+
const framework = normalizeFrameworkTarget(frameworkArg);
|
|
983
|
+
if (!framework) {
|
|
984
|
+
logError(`Unknown framework target: ${frameworkArg}. Use vue3, react, or both.`);
|
|
985
|
+
process.exit(1);
|
|
986
|
+
}
|
|
987
|
+
const targets = framework === "both" ? ["vue3", "react"] : [framework];
|
|
988
|
+
for (const target of targets) {
|
|
989
|
+
const ext = target === "vue3" ? "ts" : "tsx";
|
|
990
|
+
const folder = target === "vue3" ? "vue" : "react";
|
|
991
|
+
const filePath = resolve(process.cwd(), outputDir, folder, `${componentName}.spec.${ext}`);
|
|
992
|
+
const content = target === "vue3" ? generateVueTest(componentName) : generateReactTest(componentName);
|
|
993
|
+
if (existsSync(filePath)) {
|
|
994
|
+
logWarn(`${filePath} already exists, skipping`);
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
if (dryRun) {
|
|
998
|
+
logInfo(`Would generate ${filePath}`);
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
writeFileSafe(filePath, content);
|
|
1002
|
+
logSuccess(`Generated ${filePath}`);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
async function runGenerateDocTemplate(component, outputDir, dryRun = false) {
|
|
1006
|
+
const componentName = normalizeComponentName(component);
|
|
1007
|
+
if (!componentName) {
|
|
1008
|
+
logError(`Unknown component: ${component}`);
|
|
1009
|
+
process.exit(1);
|
|
1010
|
+
}
|
|
1011
|
+
const fileName = `${componentName.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()}.md`;
|
|
1012
|
+
const filePath = resolve(process.cwd(), outputDir, fileName);
|
|
1013
|
+
const content = generateComponentDocTemplate(componentName);
|
|
1014
|
+
if (existsSync(filePath)) {
|
|
1015
|
+
logWarn(`${filePath} already exists, skipping`);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
if (dryRun) {
|
|
1019
|
+
logInfo(`Would generate ${filePath}`);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
writeFileSafe(filePath, content);
|
|
1023
|
+
logSuccess(`Generated ${filePath}`);
|
|
1024
|
+
}
|
|
1025
|
+
function normalizeComponentName(component) {
|
|
1026
|
+
return ALL_COMPONENTS.find((name) => name.toLowerCase() === component.toLowerCase()) ?? null;
|
|
1027
|
+
}
|
|
1028
|
+
function normalizeFrameworkTarget(value) {
|
|
1029
|
+
if (value === "vue3" || value === "react" || value === "both") return value;
|
|
1030
|
+
return null;
|
|
1031
|
+
}
|
|
1032
|
+
function generateVueTest(component) {
|
|
1033
|
+
return `/**
|
|
1034
|
+
* @vitest-environment happy-dom
|
|
1035
|
+
*/
|
|
1036
|
+
|
|
1037
|
+
import { describe, it, expect } from 'vitest'
|
|
1038
|
+
import { render, screen } from '@testing-library/vue'
|
|
1039
|
+
import { ${component} } from '@expcat/tigercat-vue'
|
|
1040
|
+
import { expectNoA11yViolationsIsolated } from '../utils'
|
|
1041
|
+
|
|
1042
|
+
describe('${component}', () => {
|
|
1043
|
+
it('renders without crashing', () => {
|
|
1044
|
+
const { container } = render(${component}, {
|
|
1045
|
+
attrs: { 'data-testid': '${component.toLowerCase()}' }
|
|
1046
|
+
})
|
|
1047
|
+
|
|
1048
|
+
expect(screen.getByTestId('${component.toLowerCase()}')).toBeInTheDocument()
|
|
1049
|
+
expect(container.firstElementChild).toBeTruthy()
|
|
1050
|
+
})
|
|
1051
|
+
|
|
1052
|
+
describe('a11y', () => {
|
|
1053
|
+
it('has no accessibility violations', async () => {
|
|
1054
|
+
const { container } = render(${component})
|
|
1055
|
+
await expectNoA11yViolationsIsolated(container)
|
|
1056
|
+
})
|
|
1057
|
+
})
|
|
1058
|
+
|
|
1059
|
+
describe('Edge Cases', () => {
|
|
1060
|
+
it('keeps rendering with empty props', () => {
|
|
1061
|
+
const { container } = render(${component})
|
|
1062
|
+
expect(container.firstElementChild).toBeTruthy()
|
|
1063
|
+
})
|
|
1064
|
+
})
|
|
1065
|
+
})
|
|
1066
|
+
`;
|
|
1067
|
+
}
|
|
1068
|
+
function generateReactTest(component) {
|
|
1069
|
+
return `/**
|
|
1070
|
+
* @vitest-environment happy-dom
|
|
1071
|
+
*/
|
|
1072
|
+
|
|
1073
|
+
import { describe, it, expect } from 'vitest'
|
|
1074
|
+
import { render, screen } from '@testing-library/react'
|
|
1075
|
+
import React from 'react'
|
|
1076
|
+
import { ${component} } from '@expcat/tigercat-react'
|
|
1077
|
+
import { expectNoA11yViolationsIsolated } from '../utils/react'
|
|
1078
|
+
|
|
1079
|
+
describe('${component}', () => {
|
|
1080
|
+
it('renders without crashing', () => {
|
|
1081
|
+
const { container } = render(<${component} data-testid="${component.toLowerCase()}" />)
|
|
1082
|
+
|
|
1083
|
+
expect(screen.getByTestId('${component.toLowerCase()}')).toBeInTheDocument()
|
|
1084
|
+
expect(container.firstElementChild).toBeTruthy()
|
|
1085
|
+
})
|
|
1086
|
+
|
|
1087
|
+
describe('a11y', () => {
|
|
1088
|
+
it('has no accessibility violations', async () => {
|
|
1089
|
+
const { container } = render(<${component} />)
|
|
1090
|
+
await expectNoA11yViolationsIsolated(container)
|
|
1091
|
+
})
|
|
1092
|
+
})
|
|
1093
|
+
|
|
1094
|
+
describe('Edge Cases', () => {
|
|
1095
|
+
it('keeps rendering with empty props', () => {
|
|
1096
|
+
const { container } = render(<${component} />)
|
|
1097
|
+
expect(container.firstElementChild).toBeTruthy()
|
|
1098
|
+
})
|
|
1099
|
+
})
|
|
1100
|
+
})
|
|
1101
|
+
`;
|
|
1102
|
+
}
|
|
1103
|
+
function generateComponentDocTemplate(component) {
|
|
1104
|
+
return `# ${component}
|
|
1105
|
+
|
|
1106
|
+
## Overview
|
|
1107
|
+
|
|
1108
|
+
Describe the user-facing purpose and primary workflow for ${component}.
|
|
1109
|
+
|
|
1110
|
+
## Import
|
|
1111
|
+
|
|
1112
|
+
\`\`\`ts
|
|
1113
|
+
import { ${component} } from '@expcat/tigercat-vue'
|
|
1114
|
+
import { ${component} } from '@expcat/tigercat-react'
|
|
1115
|
+
\`\`\`
|
|
1116
|
+
|
|
1117
|
+
## Basic Usage
|
|
1118
|
+
|
|
1119
|
+
Add one minimal Vue example and one minimal React example from \`skills/tigercat/references\`.
|
|
1120
|
+
|
|
1121
|
+
## Props
|
|
1122
|
+
|
|
1123
|
+
Keep this section aligned with \`packages/core/src/types\` and regenerate API docs after changes.
|
|
1124
|
+
|
|
1125
|
+
## Accessibility
|
|
1126
|
+
|
|
1127
|
+
Document keyboard behavior, roles, labels, and focus management.
|
|
1128
|
+
|
|
1129
|
+
## Edge Cases
|
|
1130
|
+
|
|
1131
|
+
List boundary states, empty states, loading states, and controlled/uncontrolled behavior.
|
|
1132
|
+
`;
|
|
1133
|
+
}
|
|
853
1134
|
var MIN_NODE_MAJOR = 20;
|
|
854
1135
|
var MIN_PNPM_MAJOR = 8;
|
|
855
1136
|
var REQUIRED_TAILWIND_MAJOR = 4;
|
|
856
1137
|
var REQUIRED_TIGERCAT_MAJOR = 1;
|
|
1138
|
+
var VERSION_COMPATIBILITY_MATRIX = [
|
|
1139
|
+
{ name: "Node.js", range: ">=20.11.0", reason: "Matches workspace engines and CLI templates" },
|
|
1140
|
+
{ name: "pnpm", range: ">=8.0.0", reason: "Required by workspace package management" },
|
|
1141
|
+
{ name: "Tailwind CSS", range: ">=4.0.0", reason: "Required by Tigercat theme utilities" },
|
|
1142
|
+
{ name: "Vue", range: "^3.0.0", reason: "Peer range for @expcat/tigercat-vue" },
|
|
1143
|
+
{ name: "React", range: "^19.0.0", reason: "Peer range for @expcat/tigercat-react" }
|
|
1144
|
+
];
|
|
857
1145
|
var FRAMEWORK_REQUIREMENTS = {
|
|
858
1146
|
vue3: {
|
|
859
1147
|
peers: ["@expcat/tigercat-vue", "@expcat/tigercat-core", "vue"],
|
|
@@ -865,8 +1153,8 @@ var FRAMEWORK_REQUIREMENTS = {
|
|
|
865
1153
|
}
|
|
866
1154
|
};
|
|
867
1155
|
function createDoctorCommand() {
|
|
868
|
-
return new Command("doctor").description("Check whether the current project is compatible with Tigercat").action(() => {
|
|
869
|
-
runDoctor();
|
|
1156
|
+
return new Command("doctor").option("--json", "Print structured JSON output").description("Check whether the current project is compatible with Tigercat").action((opts) => {
|
|
1157
|
+
runDoctor(Boolean(opts.json));
|
|
870
1158
|
});
|
|
871
1159
|
}
|
|
872
1160
|
function collectDoctorChecks(options = {}) {
|
|
@@ -883,10 +1171,28 @@ function collectDoctorChecks(options = {}) {
|
|
|
883
1171
|
checks.push(createTailwindCheck(packageResult.packageJson));
|
|
884
1172
|
checks.push(createPeerDepsCheck(packageResult.packageJson));
|
|
885
1173
|
checks.push(createTemplateCompatibilityCheck(packageResult.packageJson));
|
|
1174
|
+
checks.push(createCompatibilityMatrixCheck(packageResult.packageJson));
|
|
886
1175
|
return checks;
|
|
887
1176
|
}
|
|
888
|
-
function runDoctor() {
|
|
1177
|
+
function runDoctor(json = false) {
|
|
889
1178
|
const checks = collectDoctorChecks();
|
|
1179
|
+
if (json) {
|
|
1180
|
+
const failures2 = checks.filter((check) => check.status === "fail");
|
|
1181
|
+
const warnings2 = checks.filter((check) => check.status === "warn");
|
|
1182
|
+
console.log(
|
|
1183
|
+
JSON.stringify(
|
|
1184
|
+
{
|
|
1185
|
+
status: failures2.length > 0 ? "fail" : warnings2.length > 0 ? "warn" : "pass",
|
|
1186
|
+
checks,
|
|
1187
|
+
compatibilityMatrix: VERSION_COMPATIBILITY_MATRIX
|
|
1188
|
+
},
|
|
1189
|
+
null,
|
|
1190
|
+
2
|
|
1191
|
+
)
|
|
1192
|
+
);
|
|
1193
|
+
if (failures2.length > 0) process.exit(1);
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
890
1196
|
logInfo("Running Tigercat project checks...");
|
|
891
1197
|
console.log();
|
|
892
1198
|
for (const check of checks) {
|
|
@@ -900,7 +1206,7 @@ function runDoctor() {
|
|
|
900
1206
|
process.exit(1);
|
|
901
1207
|
}
|
|
902
1208
|
if (warnings.length > 0) {
|
|
903
|
-
|
|
1209
|
+
logWarn2(`${warnings.length} warning${warnings.length === 1 ? "" : "s"} found`);
|
|
904
1210
|
return;
|
|
905
1211
|
}
|
|
906
1212
|
logSuccess("All checks passed");
|
|
@@ -922,7 +1228,8 @@ function createPackageCheck(result) {
|
|
|
922
1228
|
return {
|
|
923
1229
|
name: "Project package",
|
|
924
1230
|
status: "fail",
|
|
925
|
-
message: result.error ?? "package.json could not be read"
|
|
1231
|
+
message: result.error ?? "package.json could not be read",
|
|
1232
|
+
suggestions: ["Run this command from a project root that contains package.json"]
|
|
926
1233
|
};
|
|
927
1234
|
}
|
|
928
1235
|
return {
|
|
@@ -937,7 +1244,8 @@ function createNodeCheck(version) {
|
|
|
937
1244
|
return {
|
|
938
1245
|
name: "Node.js",
|
|
939
1246
|
status: "fail",
|
|
940
|
-
message: `Node ${MIN_NODE_MAJOR}+ is required, current version is ${version}
|
|
1247
|
+
message: `Node ${MIN_NODE_MAJOR}+ is required, current version is ${version}`,
|
|
1248
|
+
suggestions: [`Install Node ${MIN_NODE_MAJOR}+ and rerun tigercat doctor`]
|
|
941
1249
|
};
|
|
942
1250
|
}
|
|
943
1251
|
return {
|
|
@@ -952,7 +1260,8 @@ function createPnpmCheck(packageJson, env) {
|
|
|
952
1260
|
return {
|
|
953
1261
|
name: "pnpm",
|
|
954
1262
|
status: "warn",
|
|
955
|
-
message: `Could not detect pnpm version; Tigercat templates expect pnpm ${MIN_PNPM_MAJOR}
|
|
1263
|
+
message: `Could not detect pnpm version; Tigercat templates expect pnpm ${MIN_PNPM_MAJOR}+`,
|
|
1264
|
+
suggestions: ["Add packageManager: pnpm@10.26.2 to package.json or run through pnpm"]
|
|
956
1265
|
};
|
|
957
1266
|
}
|
|
958
1267
|
const parsed = parseVersion(version);
|
|
@@ -960,7 +1269,8 @@ function createPnpmCheck(packageJson, env) {
|
|
|
960
1269
|
return {
|
|
961
1270
|
name: "pnpm",
|
|
962
1271
|
status: "fail",
|
|
963
|
-
message: `pnpm ${MIN_PNPM_MAJOR}+ is required, detected ${version}
|
|
1272
|
+
message: `pnpm ${MIN_PNPM_MAJOR}+ is required, detected ${version}`,
|
|
1273
|
+
suggestions: [`Upgrade pnpm to ${MIN_PNPM_MAJOR}+`]
|
|
964
1274
|
};
|
|
965
1275
|
}
|
|
966
1276
|
return {
|
|
@@ -970,55 +1280,75 @@ function createPnpmCheck(packageJson, env) {
|
|
|
970
1280
|
};
|
|
971
1281
|
}
|
|
972
1282
|
function createTailwindCheck(packageJson) {
|
|
973
|
-
const allDeps =
|
|
1283
|
+
const allDeps = collectDependencies2(packageJson);
|
|
974
1284
|
const tailwindRange = allDeps.tailwindcss;
|
|
975
1285
|
const vitePluginRange = allDeps["@tailwindcss/vite"];
|
|
976
1286
|
if (!tailwindRange) {
|
|
977
1287
|
return {
|
|
978
1288
|
name: "Tailwind CSS",
|
|
979
1289
|
status: "fail",
|
|
980
|
-
message: "tailwindcss is missing; Tigercat requires Tailwind CSS 4+"
|
|
1290
|
+
message: "tailwindcss is missing; Tigercat requires Tailwind CSS 4+",
|
|
1291
|
+
suggestions: ["Install tailwindcss and @tailwindcss/vite"]
|
|
981
1292
|
};
|
|
982
1293
|
}
|
|
983
1294
|
const tailwindMajor = getRangeMajor(tailwindRange);
|
|
984
|
-
const pluginMajor = vitePluginRange ? getRangeMajor(vitePluginRange) : null;
|
|
985
1295
|
if (tailwindMajor !== null && tailwindMajor < REQUIRED_TAILWIND_MAJOR) {
|
|
986
1296
|
return {
|
|
987
1297
|
name: "Tailwind CSS",
|
|
988
1298
|
status: "fail",
|
|
989
|
-
message: `tailwindcss ${tailwindRange} is not compatible; use Tailwind CSS ${REQUIRED_TAILWIND_MAJOR}
|
|
1299
|
+
message: `tailwindcss ${tailwindRange} is not compatible; use Tailwind CSS ${REQUIRED_TAILWIND_MAJOR}+`,
|
|
1300
|
+
suggestions: [`Upgrade tailwindcss to ${REQUIRED_TAILWIND_MAJOR}+`]
|
|
990
1301
|
};
|
|
991
1302
|
}
|
|
992
1303
|
if (tailwindMajor === null) {
|
|
993
1304
|
return {
|
|
994
1305
|
name: "Tailwind CSS",
|
|
995
|
-
status: "
|
|
996
|
-
message: `Could not verify tailwindcss range ${tailwindRange};
|
|
1306
|
+
status: "fail",
|
|
1307
|
+
message: `Could not verify tailwindcss range ${tailwindRange}; Tigercat builds with Tailwind CSS ${REQUIRED_TAILWIND_MAJOR}+ only`,
|
|
1308
|
+
suggestions: [`Use an explicit Tailwind CSS ${REQUIRED_TAILWIND_MAJOR}+ semver range`]
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
if (!vitePluginRange) {
|
|
1312
|
+
return {
|
|
1313
|
+
name: "Tailwind CSS",
|
|
1314
|
+
status: "fail",
|
|
1315
|
+
message: "@tailwindcss/vite is required; Tigercat builds with Tailwind CSS 4 only",
|
|
1316
|
+
suggestions: ["Install @tailwindcss/vite 4+"]
|
|
997
1317
|
};
|
|
998
1318
|
}
|
|
999
|
-
|
|
1319
|
+
const pluginMajor = getRangeMajor(vitePluginRange);
|
|
1320
|
+
if (pluginMajor !== null && pluginMajor < REQUIRED_TAILWIND_MAJOR) {
|
|
1000
1321
|
return {
|
|
1001
1322
|
name: "Tailwind CSS",
|
|
1002
1323
|
status: "fail",
|
|
1003
|
-
message: `@tailwindcss/vite ${vitePluginRange} is not compatible; use ${REQUIRED_TAILWIND_MAJOR}
|
|
1324
|
+
message: `@tailwindcss/vite ${vitePluginRange} is not compatible; use ${REQUIRED_TAILWIND_MAJOR}+`,
|
|
1325
|
+
suggestions: [`Upgrade @tailwindcss/vite to ${REQUIRED_TAILWIND_MAJOR}+`]
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
if (pluginMajor === null) {
|
|
1329
|
+
return {
|
|
1330
|
+
name: "Tailwind CSS",
|
|
1331
|
+
status: "fail",
|
|
1332
|
+
message: `Could not verify @tailwindcss/vite range ${vitePluginRange}; Tigercat builds with Tailwind CSS ${REQUIRED_TAILWIND_MAJOR}+ only`,
|
|
1333
|
+
suggestions: [`Use an explicit @tailwindcss/vite ${REQUIRED_TAILWIND_MAJOR}+ semver range`]
|
|
1004
1334
|
};
|
|
1005
1335
|
}
|
|
1006
|
-
const details = vitePluginRange ? [`@tailwindcss/vite ${vitePluginRange}`] : ["@tailwindcss/vite is recommended for Vite templates"];
|
|
1007
1336
|
return {
|
|
1008
1337
|
name: "Tailwind CSS",
|
|
1009
|
-
status:
|
|
1010
|
-
message: `tailwindcss ${tailwindRange}
|
|
1011
|
-
details
|
|
1338
|
+
status: "pass",
|
|
1339
|
+
message: `tailwindcss ${tailwindRange} uses the Tailwind CSS ${REQUIRED_TAILWIND_MAJOR} build pipeline`,
|
|
1340
|
+
details: [`@tailwindcss/vite ${vitePluginRange}`]
|
|
1012
1341
|
};
|
|
1013
1342
|
}
|
|
1014
1343
|
function createPeerDepsCheck(packageJson) {
|
|
1015
|
-
const allDeps =
|
|
1344
|
+
const allDeps = collectDependencies2(packageJson);
|
|
1016
1345
|
const frameworks = detectTigercatFrameworks(allDeps);
|
|
1017
1346
|
if (frameworks.length === 0) {
|
|
1018
1347
|
return {
|
|
1019
1348
|
name: "Peer dependencies",
|
|
1020
1349
|
status: "warn",
|
|
1021
|
-
message: "No Tigercat Vue or React package was detected"
|
|
1350
|
+
message: "No Tigercat Vue or React package was detected",
|
|
1351
|
+
suggestions: ["Install @expcat/tigercat-vue or @expcat/tigercat-react"]
|
|
1022
1352
|
};
|
|
1023
1353
|
}
|
|
1024
1354
|
const missing = [
|
|
@@ -1036,7 +1366,8 @@ function createPeerDepsCheck(packageJson) {
|
|
|
1036
1366
|
name: "Peer dependencies",
|
|
1037
1367
|
status: "fail",
|
|
1038
1368
|
message: "Tigercat peer dependencies are incomplete or incompatible",
|
|
1039
|
-
details: [...missing.map((dependency) => `Missing ${dependency}`), ...incompatible]
|
|
1369
|
+
details: [...missing.map((dependency) => `Missing ${dependency}`), ...incompatible],
|
|
1370
|
+
suggestions: ["Run tigercat add --install or install the listed dependencies manually"]
|
|
1040
1371
|
};
|
|
1041
1372
|
}
|
|
1042
1373
|
return {
|
|
@@ -1046,13 +1377,14 @@ function createPeerDepsCheck(packageJson) {
|
|
|
1046
1377
|
};
|
|
1047
1378
|
}
|
|
1048
1379
|
function createTemplateCompatibilityCheck(packageJson) {
|
|
1049
|
-
const allDeps =
|
|
1380
|
+
const allDeps = collectDependencies2(packageJson);
|
|
1050
1381
|
const frameworks = detectTigercatFrameworks(allDeps);
|
|
1051
1382
|
if (frameworks.length === 0) {
|
|
1052
1383
|
return {
|
|
1053
1384
|
name: "Template compatibility",
|
|
1054
1385
|
status: "warn",
|
|
1055
|
-
message: "Skipped because no supported Tigercat framework package was detected"
|
|
1386
|
+
message: "Skipped because no supported Tigercat framework package was detected",
|
|
1387
|
+
suggestions: ["Install a Tigercat framework package to enable template compatibility checks"]
|
|
1056
1388
|
};
|
|
1057
1389
|
}
|
|
1058
1390
|
const missing = [
|
|
@@ -1067,7 +1399,8 @@ function createTemplateCompatibilityCheck(packageJson) {
|
|
|
1067
1399
|
name: "Template compatibility",
|
|
1068
1400
|
status: "warn",
|
|
1069
1401
|
message: "Project differs from current CLI template dependencies",
|
|
1070
|
-
details: missing.map((dependency) => `Template dependency not found: ${dependency}`)
|
|
1402
|
+
details: missing.map((dependency) => `Template dependency not found: ${dependency}`),
|
|
1403
|
+
suggestions: ["Compare your project dependencies with the latest tigercat create template"]
|
|
1071
1404
|
};
|
|
1072
1405
|
}
|
|
1073
1406
|
return {
|
|
@@ -1076,7 +1409,29 @@ function createTemplateCompatibilityCheck(packageJson) {
|
|
|
1076
1409
|
message: `${frameworks.map(formatFramework).join(" + ")} template dependencies are present`
|
|
1077
1410
|
};
|
|
1078
1411
|
}
|
|
1079
|
-
function
|
|
1412
|
+
function createCompatibilityMatrixCheck(packageJson) {
|
|
1413
|
+
const dependencies = collectDependencies2(packageJson);
|
|
1414
|
+
const details = VERSION_COMPATIBILITY_MATRIX.map(
|
|
1415
|
+
(item) => `${item.name} ${item.range} - ${item.reason}`
|
|
1416
|
+
);
|
|
1417
|
+
const frameworks = detectTigercatFrameworks(dependencies);
|
|
1418
|
+
if (frameworks.length === 0) {
|
|
1419
|
+
return {
|
|
1420
|
+
name: "Version compatibility matrix",
|
|
1421
|
+
status: "warn",
|
|
1422
|
+
message: "Framework-specific matrix checks were skipped",
|
|
1423
|
+
details,
|
|
1424
|
+
suggestions: ["Install a Tigercat Vue or React package to validate framework peer ranges"]
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
return {
|
|
1428
|
+
name: "Version compatibility matrix",
|
|
1429
|
+
status: "pass",
|
|
1430
|
+
message: `${frameworks.map(formatFramework).join(" + ")} compatibility matrix is available`,
|
|
1431
|
+
details
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
function collectDependencies2(packageJson) {
|
|
1080
1435
|
return {
|
|
1081
1436
|
...packageJson.peerDependencies,
|
|
1082
1437
|
...packageJson.dependencies,
|
|
@@ -1126,11 +1481,14 @@ function formatFramework(framework) {
|
|
|
1126
1481
|
return framework === "vue3" ? "Vue 3" : "React";
|
|
1127
1482
|
}
|
|
1128
1483
|
function printCheck(check) {
|
|
1129
|
-
const symbol = check.status === "pass" ?
|
|
1130
|
-
const name =
|
|
1484
|
+
const symbol = check.status === "pass" ? pc2.green("\u2714") : check.status === "warn" ? pc2.yellow("\u26A0") : pc2.red("\u2716");
|
|
1485
|
+
const name = pc2.bold(check.name);
|
|
1131
1486
|
console.log(`${symbol} ${name}: ${check.message}`);
|
|
1132
1487
|
for (const detail of check.details ?? []) {
|
|
1133
|
-
console.log(` ${
|
|
1488
|
+
console.log(` ${pc2.dim("-")} ${detail}`);
|
|
1489
|
+
}
|
|
1490
|
+
for (const suggestion of check.suggestions ?? []) {
|
|
1491
|
+
console.log(` ${pc2.dim("fix:")} ${suggestion}`);
|
|
1134
1492
|
}
|
|
1135
1493
|
}
|
|
1136
1494
|
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expcat/tigercat-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI tooling for Tigercat UI library — project scaffolding, component generation, and more",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Yizhe Wang",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/
|
|
10
|
+
"url": "https://github.com/expcat/Tigercat",
|
|
11
11
|
"directory": "packages/cli"
|
|
12
12
|
},
|
|
13
|
-
"homepage": "https://github.com/
|
|
13
|
+
"homepage": "https://github.com/expcat/Tigercat#readme",
|
|
14
14
|
"bugs": {
|
|
15
|
-
"url": "https://github.com/
|
|
15
|
+
"url": "https://github.com/expcat/Tigercat/issues"
|
|
16
16
|
},
|
|
17
17
|
"keywords": [
|
|
18
18
|
"tigercat",
|