@expcat/tigercat-cli 1.0.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/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +790 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Yizhe Wang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# @expcat/tigercat-cli
|
|
2
|
+
|
|
3
|
+
CLI tooling for the [Tigercat](https://github.com/expcats/Tigercat) UI component library.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add -g @expcat/tigercat-cli
|
|
9
|
+
# or
|
|
10
|
+
npx @expcat/tigercat-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
### `tigercat create <name>`
|
|
16
|
+
|
|
17
|
+
Create a new project with Tigercat pre-configured.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
tigercat create my-app --template vue3
|
|
21
|
+
tigercat create my-app --template react
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### `tigercat add <component>`
|
|
25
|
+
|
|
26
|
+
Add a component to your project with import boilerplate.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
tigercat add Button
|
|
30
|
+
tigercat add Form Input Select DatePicker
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### `tigercat playground`
|
|
34
|
+
|
|
35
|
+
Launch an interactive playground for testing components.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
tigercat playground
|
|
39
|
+
tigercat playground --template react
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### `tigercat generate docs`
|
|
43
|
+
|
|
44
|
+
Generate API documentation from component type definitions.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
tigercat generate docs
|
|
48
|
+
tigercat generate docs --output ./docs/api
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import prompts from 'prompts';
|
|
4
|
+
import { existsSync, readdirSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
5
|
+
import { resolve, join, dirname, basename } from 'path';
|
|
6
|
+
import pc from 'picocolors';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
|
|
9
|
+
// src/constants.ts
|
|
10
|
+
var CLI_NAME = "tigercat";
|
|
11
|
+
var CLI_VERSION = "0.9.0";
|
|
12
|
+
var TEMPLATES = ["vue3", "react"];
|
|
13
|
+
var COMPONENT_CATEGORIES = {
|
|
14
|
+
basic: [
|
|
15
|
+
"Alert",
|
|
16
|
+
"Avatar",
|
|
17
|
+
"AvatarGroup",
|
|
18
|
+
"Badge",
|
|
19
|
+
"Button",
|
|
20
|
+
"ButtonGroup",
|
|
21
|
+
"Code",
|
|
22
|
+
"Divider",
|
|
23
|
+
"Icon",
|
|
24
|
+
"Link",
|
|
25
|
+
"Tag",
|
|
26
|
+
"Text"
|
|
27
|
+
],
|
|
28
|
+
form: [
|
|
29
|
+
"Checkbox",
|
|
30
|
+
"DatePicker",
|
|
31
|
+
"Form",
|
|
32
|
+
"Input",
|
|
33
|
+
"InputNumber",
|
|
34
|
+
"Radio",
|
|
35
|
+
"Select",
|
|
36
|
+
"Slider",
|
|
37
|
+
"Switch",
|
|
38
|
+
"Textarea",
|
|
39
|
+
"TimePicker",
|
|
40
|
+
"Upload"
|
|
41
|
+
],
|
|
42
|
+
feedback: [
|
|
43
|
+
"Drawer",
|
|
44
|
+
"Loading",
|
|
45
|
+
"Message",
|
|
46
|
+
"Modal",
|
|
47
|
+
"Notification",
|
|
48
|
+
"Popconfirm",
|
|
49
|
+
"Popover",
|
|
50
|
+
"Progress",
|
|
51
|
+
"Tooltip"
|
|
52
|
+
],
|
|
53
|
+
layout: ["Card", "Container", "Descriptions", "Grid", "Layout", "List", "Skeleton", "Space"],
|
|
54
|
+
navigation: ["Breadcrumb", "Dropdown", "Menu", "Pagination", "Steps", "Tabs", "Tree"],
|
|
55
|
+
data: ["Table", "Timeline"],
|
|
56
|
+
charts: [
|
|
57
|
+
"AreaChart",
|
|
58
|
+
"BarChart",
|
|
59
|
+
"DonutChart",
|
|
60
|
+
"LineChart",
|
|
61
|
+
"PieChart",
|
|
62
|
+
"RadarChart",
|
|
63
|
+
"ScatterChart"
|
|
64
|
+
]
|
|
65
|
+
};
|
|
66
|
+
var ALL_COMPONENTS = Object.values(COMPONENT_CATEGORIES).flat();
|
|
67
|
+
function logSuccess(msg) {
|
|
68
|
+
console.log(pc.green("\u2714") + " " + msg);
|
|
69
|
+
}
|
|
70
|
+
function logInfo(msg) {
|
|
71
|
+
console.log(pc.blue("\u2139") + " " + msg);
|
|
72
|
+
}
|
|
73
|
+
function logWarn(msg) {
|
|
74
|
+
console.log(pc.yellow("\u26A0") + " " + msg);
|
|
75
|
+
}
|
|
76
|
+
function logError(msg) {
|
|
77
|
+
console.error(pc.red("\u2716") + " " + msg);
|
|
78
|
+
}
|
|
79
|
+
function logStep(step, total, msg) {
|
|
80
|
+
console.log(pc.dim(`[${step}/${total}]`) + " " + msg);
|
|
81
|
+
}
|
|
82
|
+
function ensureDir(dir) {
|
|
83
|
+
if (!existsSync(dir)) {
|
|
84
|
+
mkdirSync(dir, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function writeFileSafe(filePath, content) {
|
|
88
|
+
ensureDir(dirname(filePath));
|
|
89
|
+
writeFileSync(filePath, content, "utf-8");
|
|
90
|
+
}
|
|
91
|
+
function isDirEmpty(dir) {
|
|
92
|
+
if (!existsSync(dir)) return true;
|
|
93
|
+
return readdirSync(dir).length === 0;
|
|
94
|
+
}
|
|
95
|
+
function readFileSafe(filePath) {
|
|
96
|
+
if (!existsSync(filePath)) return null;
|
|
97
|
+
return readFileSync(filePath, "utf-8");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/templates/vue3.ts
|
|
101
|
+
function getVue3Template(projectName) {
|
|
102
|
+
return {
|
|
103
|
+
"package.json": vue3PackageJson(projectName),
|
|
104
|
+
"tsconfig.json": vue3Tsconfig(),
|
|
105
|
+
"vite.config.ts": vue3ViteConfig(),
|
|
106
|
+
"index.html": vue3IndexHtml(projectName),
|
|
107
|
+
"src/main.ts": vue3Main(),
|
|
108
|
+
"src/App.vue": vue3App(),
|
|
109
|
+
"src/style.css": commonStyleCss(),
|
|
110
|
+
"src/env.d.ts": vue3EnvDts()
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function vue3PackageJson(name) {
|
|
114
|
+
return JSON.stringify(
|
|
115
|
+
{
|
|
116
|
+
name,
|
|
117
|
+
version: "0.0.1",
|
|
118
|
+
private: true,
|
|
119
|
+
type: "module",
|
|
120
|
+
scripts: {
|
|
121
|
+
dev: "vite",
|
|
122
|
+
build: "vue-tsc && vite build",
|
|
123
|
+
preview: "vite preview"
|
|
124
|
+
},
|
|
125
|
+
dependencies: {
|
|
126
|
+
"@expcat/tigercat-vue": "^0.9.0",
|
|
127
|
+
vue: "^3.5.26"
|
|
128
|
+
},
|
|
129
|
+
devDependencies: {
|
|
130
|
+
"@tailwindcss/vite": "^4.1.18",
|
|
131
|
+
"@vitejs/plugin-vue": "^6.0.3",
|
|
132
|
+
"@vue/tsconfig": "^0.7.0",
|
|
133
|
+
tailwindcss: "^4.1.18",
|
|
134
|
+
typescript: "^5.9.3",
|
|
135
|
+
vite: "^7.3.0",
|
|
136
|
+
"vue-tsc": "^2.2.0"
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
null,
|
|
140
|
+
2
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
function vue3Tsconfig() {
|
|
144
|
+
return JSON.stringify(
|
|
145
|
+
{
|
|
146
|
+
extends: "@vue/tsconfig/tsconfig.dom.json",
|
|
147
|
+
compilerOptions: {
|
|
148
|
+
target: "ES2020",
|
|
149
|
+
module: "ESNext",
|
|
150
|
+
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
151
|
+
skipLibCheck: true,
|
|
152
|
+
moduleResolution: "bundler",
|
|
153
|
+
resolveJsonModule: true,
|
|
154
|
+
isolatedModules: true,
|
|
155
|
+
jsx: "preserve",
|
|
156
|
+
strict: true,
|
|
157
|
+
noUnusedLocals: true,
|
|
158
|
+
noUnusedParameters: true,
|
|
159
|
+
noFallthroughCasesInSwitch: true,
|
|
160
|
+
baseUrl: ".",
|
|
161
|
+
paths: {
|
|
162
|
+
"@/*": ["./src/*"]
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
include: ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
|
166
|
+
exclude: ["node_modules"]
|
|
167
|
+
},
|
|
168
|
+
null,
|
|
169
|
+
2
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
function vue3ViteConfig() {
|
|
173
|
+
return `import { defineConfig } from 'vite'
|
|
174
|
+
import vue from '@vitejs/plugin-vue'
|
|
175
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
176
|
+
|
|
177
|
+
export default defineConfig({
|
|
178
|
+
plugins: [vue(), tailwindcss()]
|
|
179
|
+
})
|
|
180
|
+
`;
|
|
181
|
+
}
|
|
182
|
+
function vue3IndexHtml(name) {
|
|
183
|
+
return `<!DOCTYPE html>
|
|
184
|
+
<html lang="en">
|
|
185
|
+
<head>
|
|
186
|
+
<meta charset="UTF-8" />
|
|
187
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
188
|
+
<title>${name}</title>
|
|
189
|
+
</head>
|
|
190
|
+
<body>
|
|
191
|
+
<div id="app"></div>
|
|
192
|
+
<script type="module" src="/src/main.ts"></script>
|
|
193
|
+
</body>
|
|
194
|
+
</html>
|
|
195
|
+
`;
|
|
196
|
+
}
|
|
197
|
+
function vue3Main() {
|
|
198
|
+
return `import { createApp } from 'vue'
|
|
199
|
+
import App from './App.vue'
|
|
200
|
+
import './style.css'
|
|
201
|
+
|
|
202
|
+
createApp(App).mount('#app')
|
|
203
|
+
`;
|
|
204
|
+
}
|
|
205
|
+
function vue3App() {
|
|
206
|
+
return `<script setup lang="ts">
|
|
207
|
+
import { Button, Alert } from '@expcat/tigercat-vue'
|
|
208
|
+
</script>
|
|
209
|
+
|
|
210
|
+
<template>
|
|
211
|
+
<div class="min-h-screen bg-[var(--tiger-surface,#ffffff)] p-8">
|
|
212
|
+
<h1 class="text-2xl font-bold text-[var(--tiger-text,#111827)] mb-6">
|
|
213
|
+
Tigercat + Vue 3
|
|
214
|
+
</h1>
|
|
215
|
+
|
|
216
|
+
<div class="space-y-4">
|
|
217
|
+
<Alert variant="info">
|
|
218
|
+
Welcome to your Tigercat project! Edit src/App.vue to get started.
|
|
219
|
+
</Alert>
|
|
220
|
+
|
|
221
|
+
<div class="flex gap-2">
|
|
222
|
+
<Button variant="primary">Primary</Button>
|
|
223
|
+
<Button variant="secondary">Secondary</Button>
|
|
224
|
+
<Button variant="outline">Outline</Button>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
</template>
|
|
229
|
+
`;
|
|
230
|
+
}
|
|
231
|
+
function vue3EnvDts() {
|
|
232
|
+
return `/// <reference types="vite/client" />
|
|
233
|
+
|
|
234
|
+
declare module '*.vue' {
|
|
235
|
+
import type { DefineComponent } from 'vue'
|
|
236
|
+
const component: DefineComponent<{}, {}, any>
|
|
237
|
+
export default component
|
|
238
|
+
}
|
|
239
|
+
`;
|
|
240
|
+
}
|
|
241
|
+
function commonStyleCss() {
|
|
242
|
+
return `@import "tailwindcss";
|
|
243
|
+
|
|
244
|
+
:root {
|
|
245
|
+
--tiger-primary: #2563eb;
|
|
246
|
+
--tiger-primary-hover: #1d4ed8;
|
|
247
|
+
--tiger-surface: #ffffff;
|
|
248
|
+
--tiger-surface-muted: #f9fafb;
|
|
249
|
+
--tiger-text: #111827;
|
|
250
|
+
--tiger-text-muted: #6b7280;
|
|
251
|
+
--tiger-border: #e5e7eb;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@media (prefers-color-scheme: dark) {
|
|
255
|
+
:root {
|
|
256
|
+
--tiger-primary: #3b82f6;
|
|
257
|
+
--tiger-primary-hover: #2563eb;
|
|
258
|
+
--tiger-surface: #111827;
|
|
259
|
+
--tiger-surface-muted: #1f2937;
|
|
260
|
+
--tiger-text: #f9fafb;
|
|
261
|
+
--tiger-text-muted: #9ca3af;
|
|
262
|
+
--tiger-border: #374151;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
html {
|
|
266
|
+
color-scheme: dark;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/templates/react.ts
|
|
273
|
+
function getReactTemplate(projectName) {
|
|
274
|
+
return {
|
|
275
|
+
"package.json": reactPackageJson(projectName),
|
|
276
|
+
"tsconfig.json": reactTsconfig(),
|
|
277
|
+
"tsconfig.node.json": reactTsconfigNode(),
|
|
278
|
+
"vite.config.ts": reactViteConfig(),
|
|
279
|
+
"index.html": reactIndexHtml(projectName),
|
|
280
|
+
"src/main.tsx": reactMain(),
|
|
281
|
+
"src/App.tsx": reactApp(),
|
|
282
|
+
"src/style.css": commonStyleCss2()
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function reactPackageJson(name) {
|
|
286
|
+
return JSON.stringify(
|
|
287
|
+
{
|
|
288
|
+
name,
|
|
289
|
+
version: "0.0.1",
|
|
290
|
+
private: true,
|
|
291
|
+
type: "module",
|
|
292
|
+
scripts: {
|
|
293
|
+
dev: "vite",
|
|
294
|
+
build: "tsc && vite build",
|
|
295
|
+
preview: "vite preview"
|
|
296
|
+
},
|
|
297
|
+
dependencies: {
|
|
298
|
+
"@expcat/tigercat-react": "^0.9.0",
|
|
299
|
+
react: "^19.2.3",
|
|
300
|
+
"react-dom": "^19.2.3"
|
|
301
|
+
},
|
|
302
|
+
devDependencies: {
|
|
303
|
+
"@tailwindcss/vite": "^4.1.18",
|
|
304
|
+
"@types/react": "^19.2.7",
|
|
305
|
+
"@types/react-dom": "^19.2.2",
|
|
306
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
307
|
+
tailwindcss: "^4.1.18",
|
|
308
|
+
typescript: "^5.9.3",
|
|
309
|
+
vite: "^7.3.0"
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
null,
|
|
313
|
+
2
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
function reactTsconfig() {
|
|
317
|
+
return JSON.stringify(
|
|
318
|
+
{
|
|
319
|
+
compilerOptions: {
|
|
320
|
+
target: "ES2020",
|
|
321
|
+
useDefineForClassFields: true,
|
|
322
|
+
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
323
|
+
module: "ESNext",
|
|
324
|
+
skipLibCheck: true,
|
|
325
|
+
moduleResolution: "bundler",
|
|
326
|
+
allowImportingTsExtensions: true,
|
|
327
|
+
resolveJsonModule: true,
|
|
328
|
+
isolatedModules: true,
|
|
329
|
+
noEmit: true,
|
|
330
|
+
jsx: "react-jsx",
|
|
331
|
+
strict: true,
|
|
332
|
+
noUnusedLocals: true,
|
|
333
|
+
noUnusedParameters: true,
|
|
334
|
+
noFallthroughCasesInSwitch: true,
|
|
335
|
+
forceConsistentCasingInFileNames: true,
|
|
336
|
+
baseUrl: ".",
|
|
337
|
+
paths: {
|
|
338
|
+
"@/*": ["./src/*"]
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
include: ["src"],
|
|
342
|
+
references: [{ path: "./tsconfig.node.json" }]
|
|
343
|
+
},
|
|
344
|
+
null,
|
|
345
|
+
2
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
function reactTsconfigNode() {
|
|
349
|
+
return JSON.stringify(
|
|
350
|
+
{
|
|
351
|
+
compilerOptions: {
|
|
352
|
+
composite: true,
|
|
353
|
+
skipLibCheck: true,
|
|
354
|
+
module: "ESNext",
|
|
355
|
+
moduleResolution: "bundler",
|
|
356
|
+
allowSyntheticDefaultImports: true
|
|
357
|
+
},
|
|
358
|
+
include: ["vite.config.ts"]
|
|
359
|
+
},
|
|
360
|
+
null,
|
|
361
|
+
2
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
function reactViteConfig() {
|
|
365
|
+
return `import { defineConfig } from 'vite'
|
|
366
|
+
import react from '@vitejs/plugin-react'
|
|
367
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
368
|
+
|
|
369
|
+
export default defineConfig({
|
|
370
|
+
plugins: [react(), tailwindcss()]
|
|
371
|
+
})
|
|
372
|
+
`;
|
|
373
|
+
}
|
|
374
|
+
function reactIndexHtml(name) {
|
|
375
|
+
return `<!DOCTYPE html>
|
|
376
|
+
<html lang="en">
|
|
377
|
+
<head>
|
|
378
|
+
<meta charset="UTF-8" />
|
|
379
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
380
|
+
<title>${name}</title>
|
|
381
|
+
</head>
|
|
382
|
+
<body>
|
|
383
|
+
<div id="root"></div>
|
|
384
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
385
|
+
</body>
|
|
386
|
+
</html>
|
|
387
|
+
`;
|
|
388
|
+
}
|
|
389
|
+
function reactMain() {
|
|
390
|
+
return `import { StrictMode } from 'react'
|
|
391
|
+
import { createRoot } from 'react-dom/client'
|
|
392
|
+
import App from './App'
|
|
393
|
+
import './style.css'
|
|
394
|
+
|
|
395
|
+
createRoot(document.getElementById('root')!).render(
|
|
396
|
+
<StrictMode>
|
|
397
|
+
<App />
|
|
398
|
+
</StrictMode>
|
|
399
|
+
)
|
|
400
|
+
`;
|
|
401
|
+
}
|
|
402
|
+
function reactApp() {
|
|
403
|
+
return `import { Button, Alert } from '@expcat/tigercat-react'
|
|
404
|
+
|
|
405
|
+
export default function App() {
|
|
406
|
+
return (
|
|
407
|
+
<div className="min-h-screen bg-[var(--tiger-surface,#ffffff)] p-8">
|
|
408
|
+
<h1 className="text-2xl font-bold text-[var(--tiger-text,#111827)] mb-6">
|
|
409
|
+
Tigercat + React
|
|
410
|
+
</h1>
|
|
411
|
+
|
|
412
|
+
<div className="space-y-4">
|
|
413
|
+
<Alert variant="info">
|
|
414
|
+
Welcome to your Tigercat project! Edit src/App.tsx to get started.
|
|
415
|
+
</Alert>
|
|
416
|
+
|
|
417
|
+
<div className="flex gap-2">
|
|
418
|
+
<Button variant="primary">Primary</Button>
|
|
419
|
+
<Button variant="secondary">Secondary</Button>
|
|
420
|
+
<Button variant="outline">Outline</Button>
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
)
|
|
425
|
+
}
|
|
426
|
+
`;
|
|
427
|
+
}
|
|
428
|
+
function commonStyleCss2() {
|
|
429
|
+
return `@import "tailwindcss";
|
|
430
|
+
|
|
431
|
+
:root {
|
|
432
|
+
--tiger-primary: #2563eb;
|
|
433
|
+
--tiger-primary-hover: #1d4ed8;
|
|
434
|
+
--tiger-surface: #ffffff;
|
|
435
|
+
--tiger-surface-muted: #f9fafb;
|
|
436
|
+
--tiger-text: #111827;
|
|
437
|
+
--tiger-text-muted: #6b7280;
|
|
438
|
+
--tiger-border: #e5e7eb;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
@media (prefers-color-scheme: dark) {
|
|
442
|
+
:root {
|
|
443
|
+
--tiger-primary: #3b82f6;
|
|
444
|
+
--tiger-primary-hover: #2563eb;
|
|
445
|
+
--tiger-surface: #111827;
|
|
446
|
+
--tiger-surface-muted: #1f2937;
|
|
447
|
+
--tiger-text: #f9fafb;
|
|
448
|
+
--tiger-text-muted: #9ca3af;
|
|
449
|
+
--tiger-border: #374151;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
html {
|
|
453
|
+
color-scheme: dark;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
`;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/commands/create.ts
|
|
460
|
+
function createCreateCommand() {
|
|
461
|
+
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) => {
|
|
462
|
+
await runCreate(name, opts.template);
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
async function runCreate(name, templateArg) {
|
|
466
|
+
let template;
|
|
467
|
+
if (templateArg && TEMPLATES.includes(templateArg)) {
|
|
468
|
+
template = templateArg;
|
|
469
|
+
} else {
|
|
470
|
+
const response = await prompts({
|
|
471
|
+
type: "select",
|
|
472
|
+
name: "template",
|
|
473
|
+
message: "Select a framework",
|
|
474
|
+
choices: [
|
|
475
|
+
{ title: "Vue 3", value: "vue3" },
|
|
476
|
+
{ title: "React", value: "react" }
|
|
477
|
+
]
|
|
478
|
+
});
|
|
479
|
+
if (!response.template) {
|
|
480
|
+
logError("Operation cancelled");
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
template = response.template;
|
|
484
|
+
}
|
|
485
|
+
const targetDir = resolve(process.cwd(), name);
|
|
486
|
+
if (existsSync(targetDir) && !isDirEmpty(targetDir)) {
|
|
487
|
+
const { overwrite } = await prompts({
|
|
488
|
+
type: "confirm",
|
|
489
|
+
name: "overwrite",
|
|
490
|
+
message: `Directory "${name}" is not empty. Remove existing files and continue?`,
|
|
491
|
+
initial: false
|
|
492
|
+
});
|
|
493
|
+
if (!overwrite) {
|
|
494
|
+
logError("Operation cancelled");
|
|
495
|
+
process.exit(1);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
logInfo(`Creating ${template} project in ${targetDir}...`);
|
|
499
|
+
const files = template === "vue3" ? getVue3Template(name) : getReactTemplate(name);
|
|
500
|
+
const totalSteps = Object.keys(files).length;
|
|
501
|
+
let step = 0;
|
|
502
|
+
ensureDir(targetDir);
|
|
503
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
504
|
+
step++;
|
|
505
|
+
logStep(step, totalSteps, filePath);
|
|
506
|
+
writeFileSafe(resolve(targetDir, filePath), content);
|
|
507
|
+
}
|
|
508
|
+
logSuccess(`Project "${name}" created successfully!
|
|
509
|
+
`);
|
|
510
|
+
logInfo("Next steps:\n");
|
|
511
|
+
console.log(` cd ${name}`);
|
|
512
|
+
console.log(" pnpm install");
|
|
513
|
+
console.log(" pnpm dev\n");
|
|
514
|
+
}
|
|
515
|
+
function createAddCommand() {
|
|
516
|
+
return new Command("add").argument("<components...>", "Component names to add (e.g. Button Input Select)").description("Add component import boilerplate to your project").action(async (components) => {
|
|
517
|
+
await runAdd(components);
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
function detectFramework(cwd) {
|
|
521
|
+
const pkg = readFileSafe(join(cwd, "package.json"));
|
|
522
|
+
if (!pkg) return null;
|
|
523
|
+
try {
|
|
524
|
+
const parsed = JSON.parse(pkg);
|
|
525
|
+
const allDeps = { ...parsed.dependencies, ...parsed.devDependencies };
|
|
526
|
+
if ("@expcat/tigercat-vue" in allDeps || "vue" in allDeps) return "vue3";
|
|
527
|
+
if ("@expcat/tigercat-react" in allDeps || "react" in allDeps) return "react";
|
|
528
|
+
} catch {
|
|
529
|
+
}
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
function validateComponents(names) {
|
|
533
|
+
const valid = [];
|
|
534
|
+
const invalid = [];
|
|
535
|
+
for (const name of names) {
|
|
536
|
+
const match = ALL_COMPONENTS.find((c) => c.toLowerCase() === name.toLowerCase());
|
|
537
|
+
if (match) {
|
|
538
|
+
valid.push(match);
|
|
539
|
+
} else {
|
|
540
|
+
invalid.push(name);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return { valid, invalid };
|
|
544
|
+
}
|
|
545
|
+
async function runAdd(components) {
|
|
546
|
+
const cwd = process.cwd();
|
|
547
|
+
const framework = detectFramework(cwd);
|
|
548
|
+
if (!framework) {
|
|
549
|
+
logError(
|
|
550
|
+
"Could not detect framework. Make sure you are in a project with @expcat/tigercat-vue or @expcat/tigercat-react installed."
|
|
551
|
+
);
|
|
552
|
+
process.exit(1);
|
|
553
|
+
}
|
|
554
|
+
const { valid, invalid } = validateComponents(components);
|
|
555
|
+
if (invalid.length > 0) {
|
|
556
|
+
logWarn(`Unknown components: ${invalid.join(", ")}`);
|
|
557
|
+
logInfo(`Available: ${ALL_COMPONENTS.join(", ")}`);
|
|
558
|
+
}
|
|
559
|
+
if (valid.length === 0) {
|
|
560
|
+
logError("No valid components specified");
|
|
561
|
+
process.exit(1);
|
|
562
|
+
}
|
|
563
|
+
const pkgName = framework === "vue3" ? "@expcat/tigercat-vue" : "@expcat/tigercat-react";
|
|
564
|
+
const importLine = `import { ${valid.join(", ")} } from '${pkgName}'`;
|
|
565
|
+
logSuccess(`Add this import to your project:
|
|
566
|
+
`);
|
|
567
|
+
console.log(` ${importLine}
|
|
568
|
+
`);
|
|
569
|
+
if (framework === "vue3") {
|
|
570
|
+
logInfo("Vue 3 usage example:\n");
|
|
571
|
+
for (const comp of valid) {
|
|
572
|
+
console.log(` <${comp} />`);
|
|
573
|
+
}
|
|
574
|
+
} else {
|
|
575
|
+
logInfo("React usage example:\n");
|
|
576
|
+
for (const comp of valid) {
|
|
577
|
+
console.log(` <${comp} />`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
console.log();
|
|
581
|
+
const sampleDir = resolve(cwd, "src", "components");
|
|
582
|
+
if (!existsSync(sampleDir)) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
for (const comp of valid) {
|
|
586
|
+
const ext = framework === "vue3" ? "vue" : "tsx";
|
|
587
|
+
const sampleFile = join(sampleDir, `${comp}Demo.${ext}`);
|
|
588
|
+
if (existsSync(sampleFile)) {
|
|
589
|
+
logWarn(`${sampleFile} already exists, skipping`);
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
const content = framework === "vue3" ? generateVue3Demo(comp, pkgName) : generateReactDemo(comp, pkgName);
|
|
593
|
+
writeFileSafe(sampleFile, content);
|
|
594
|
+
logSuccess(`Created ${sampleFile}`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
function generateVue3Demo(component, pkg) {
|
|
598
|
+
return `<script setup lang="ts">
|
|
599
|
+
import { ${component} } from '${pkg}'
|
|
600
|
+
</script>
|
|
601
|
+
|
|
602
|
+
<template>
|
|
603
|
+
<div class="p-4">
|
|
604
|
+
<h2 class="text-lg font-semibold mb-4">${component} Demo</h2>
|
|
605
|
+
<${component} />
|
|
606
|
+
</div>
|
|
607
|
+
</template>
|
|
608
|
+
`;
|
|
609
|
+
}
|
|
610
|
+
function generateReactDemo(component, pkg) {
|
|
611
|
+
return `import { ${component} } from '${pkg}'
|
|
612
|
+
|
|
613
|
+
export default function ${component}Demo() {
|
|
614
|
+
return (
|
|
615
|
+
<div className="p-4">
|
|
616
|
+
<h2 className="text-lg font-semibold mb-4">${component} Demo</h2>
|
|
617
|
+
<${component} />
|
|
618
|
+
</div>
|
|
619
|
+
)
|
|
620
|
+
}
|
|
621
|
+
`;
|
|
622
|
+
}
|
|
623
|
+
function createPlaygroundCommand() {
|
|
624
|
+
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) => {
|
|
625
|
+
await runPlayground(opts.template, opts.port);
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
async function runPlayground(templateArg, port = "3456") {
|
|
629
|
+
let template;
|
|
630
|
+
if (templateArg && TEMPLATES.includes(templateArg)) {
|
|
631
|
+
template = templateArg;
|
|
632
|
+
} else {
|
|
633
|
+
const response = await prompts({
|
|
634
|
+
type: "select",
|
|
635
|
+
name: "template",
|
|
636
|
+
message: "Select a framework for playground",
|
|
637
|
+
choices: [
|
|
638
|
+
{ title: "Vue 3", value: "vue3" },
|
|
639
|
+
{ title: "React", value: "react" }
|
|
640
|
+
]
|
|
641
|
+
});
|
|
642
|
+
if (!response.template) {
|
|
643
|
+
logError("Operation cancelled");
|
|
644
|
+
process.exit(1);
|
|
645
|
+
}
|
|
646
|
+
template = response.template;
|
|
647
|
+
}
|
|
648
|
+
const tmpDir = resolve(process.cwd(), ".tigercat-playground");
|
|
649
|
+
const projectDir = join(tmpDir, `playground-${template}`);
|
|
650
|
+
if (!existsSync(projectDir)) {
|
|
651
|
+
logInfo(`Setting up ${template} playground...`);
|
|
652
|
+
const files = template === "vue3" ? getVue3Template("playground") : getReactTemplate("playground");
|
|
653
|
+
ensureDir(projectDir);
|
|
654
|
+
const totalSteps = Object.keys(files).length;
|
|
655
|
+
let step = 0;
|
|
656
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
657
|
+
step++;
|
|
658
|
+
logStep(step, totalSteps, filePath);
|
|
659
|
+
writeFileSafe(resolve(projectDir, filePath), content);
|
|
660
|
+
}
|
|
661
|
+
logInfo("Installing dependencies...");
|
|
662
|
+
try {
|
|
663
|
+
execSync("pnpm install", { cwd: projectDir, stdio: "inherit" });
|
|
664
|
+
} catch {
|
|
665
|
+
logError("Failed to install dependencies. Make sure pnpm is available.");
|
|
666
|
+
process.exit(1);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
logSuccess(`Starting playground on port ${port}...
|
|
670
|
+
`);
|
|
671
|
+
try {
|
|
672
|
+
const safePort = /^\d+$/.test(port) ? port : "3456";
|
|
673
|
+
execSync(`npx vite --port ${safePort}`, { cwd: projectDir, stdio: "inherit" });
|
|
674
|
+
} catch {
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
function createGenerateCommand() {
|
|
678
|
+
const cmd = new Command("generate").description("Code generation utilities");
|
|
679
|
+
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) => {
|
|
680
|
+
await runGenerateDocs(opts.input, opts.output);
|
|
681
|
+
});
|
|
682
|
+
return cmd;
|
|
683
|
+
}
|
|
684
|
+
function parseTypeFile(filePath) {
|
|
685
|
+
const content = readFileSync(filePath, "utf-8");
|
|
686
|
+
const fileName = basename(filePath, ".ts");
|
|
687
|
+
const interfaceRegex = /export\s+(?:interface|type)\s+(\w+Props)\s*(?:=\s*\{|(?:extends\s+[\w<>,\s]+)?\s*\{)/g;
|
|
688
|
+
const match = interfaceRegex.exec(content);
|
|
689
|
+
if (!match) return null;
|
|
690
|
+
const interfaceName = match[0];
|
|
691
|
+
const componentName = match[1].replace(/Props$/, "");
|
|
692
|
+
const startIdx = content.indexOf(interfaceName) + interfaceName.length;
|
|
693
|
+
let braceCount = 1;
|
|
694
|
+
let endIdx = startIdx;
|
|
695
|
+
for (let i = startIdx; i < content.length && braceCount > 0; i++) {
|
|
696
|
+
if (content[i] === "{") braceCount++;
|
|
697
|
+
if (content[i] === "}") braceCount--;
|
|
698
|
+
endIdx = i;
|
|
699
|
+
}
|
|
700
|
+
const body = content.slice(startIdx, endIdx);
|
|
701
|
+
const props = [];
|
|
702
|
+
const propRegex = /(?:\/\*\*\s*(.*?)\s*\*\/\s*)?(?:\/\/\s*(.*?)\s*\n\s*)?(\w+)(\?)?\s*:\s*([^;\n]+)/g;
|
|
703
|
+
let propMatch;
|
|
704
|
+
while ((propMatch = propRegex.exec(body)) !== null) {
|
|
705
|
+
const description = propMatch[1] || propMatch[2] || "";
|
|
706
|
+
const name = propMatch[3];
|
|
707
|
+
const required = !propMatch[4];
|
|
708
|
+
const type = propMatch[5].trim();
|
|
709
|
+
props.push({ name, type, required, description });
|
|
710
|
+
}
|
|
711
|
+
return { name: componentName, props, fileName };
|
|
712
|
+
}
|
|
713
|
+
function generateMarkdown(doc) {
|
|
714
|
+
const lines = [];
|
|
715
|
+
lines.push(`# ${doc.name}`);
|
|
716
|
+
lines.push("");
|
|
717
|
+
lines.push(`Source: \`${doc.fileName}.ts\``);
|
|
718
|
+
lines.push("");
|
|
719
|
+
if (doc.props.length > 0) {
|
|
720
|
+
lines.push("## Props");
|
|
721
|
+
lines.push("");
|
|
722
|
+
lines.push("| Prop | Type | Required | Description |");
|
|
723
|
+
lines.push("| ---- | ---- | -------- | ----------- |");
|
|
724
|
+
for (const prop of doc.props) {
|
|
725
|
+
const req = prop.required ? "\u2705" : "\u2014";
|
|
726
|
+
const desc = prop.description || "\u2014";
|
|
727
|
+
const type = prop.type.replace(/\|/g, "\\|");
|
|
728
|
+
lines.push(`| \`${prop.name}\` | \`${type}\` | ${req} | ${desc} |`);
|
|
729
|
+
}
|
|
730
|
+
lines.push("");
|
|
731
|
+
} else {
|
|
732
|
+
lines.push("_No props extracted._");
|
|
733
|
+
lines.push("");
|
|
734
|
+
}
|
|
735
|
+
return lines.join("\n");
|
|
736
|
+
}
|
|
737
|
+
async function runGenerateDocs(inputDir, outputDir) {
|
|
738
|
+
const resolvedInput = resolve(process.cwd(), inputDir);
|
|
739
|
+
const resolvedOutput = resolve(process.cwd(), outputDir);
|
|
740
|
+
if (!existsSync(resolvedInput)) {
|
|
741
|
+
logError(`Input directory not found: ${resolvedInput}`);
|
|
742
|
+
process.exit(1);
|
|
743
|
+
}
|
|
744
|
+
const files = readdirSync(resolvedInput).filter((f) => f.endsWith(".ts") && f !== "index.ts").sort();
|
|
745
|
+
logInfo(`Found ${files.length} type files in ${inputDir}`);
|
|
746
|
+
ensureDir(resolvedOutput);
|
|
747
|
+
const docs = [];
|
|
748
|
+
let step = 0;
|
|
749
|
+
for (const file of files) {
|
|
750
|
+
step++;
|
|
751
|
+
logStep(step, files.length, file);
|
|
752
|
+
const doc = parseTypeFile(join(resolvedInput, file));
|
|
753
|
+
if (doc) {
|
|
754
|
+
docs.push(doc);
|
|
755
|
+
const md = generateMarkdown(doc);
|
|
756
|
+
writeFileSafe(join(resolvedOutput, `${doc.fileName}.md`), md);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
const indexLines = [
|
|
760
|
+
"# Tigercat API Reference",
|
|
761
|
+
"",
|
|
762
|
+
`Generated from ${files.length} type files.`,
|
|
763
|
+
""
|
|
764
|
+
];
|
|
765
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
766
|
+
for (const doc of docs) {
|
|
767
|
+
const initial = doc.name[0].toUpperCase();
|
|
768
|
+
if (!grouped.has(initial)) grouped.set(initial, []);
|
|
769
|
+
grouped.get(initial).push(doc);
|
|
770
|
+
}
|
|
771
|
+
for (const [letter, comps] of [...grouped.entries()].sort()) {
|
|
772
|
+
indexLines.push(`## ${letter}`);
|
|
773
|
+
indexLines.push("");
|
|
774
|
+
for (const comp of comps) {
|
|
775
|
+
indexLines.push(`- [${comp.name}](./${comp.fileName}.md) \u2014 ${comp.props.length} props`);
|
|
776
|
+
}
|
|
777
|
+
indexLines.push("");
|
|
778
|
+
}
|
|
779
|
+
writeFileSafe(join(resolvedOutput, "index.md"), indexLines.join("\n"));
|
|
780
|
+
logSuccess(`Generated docs for ${docs.length} components in ${outputDir}`);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// src/index.ts
|
|
784
|
+
var program = new Command();
|
|
785
|
+
program.name(CLI_NAME).description("CLI tooling for Tigercat UI library").version(CLI_VERSION);
|
|
786
|
+
program.addCommand(createCreateCommand());
|
|
787
|
+
program.addCommand(createAddCommand());
|
|
788
|
+
program.addCommand(createPlaygroundCommand());
|
|
789
|
+
program.addCommand(createGenerateCommand());
|
|
790
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@expcat/tigercat-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "CLI tooling for Tigercat UI library — project scaffolding, component generation, and more",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Yizhe Wang",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/expcats/Tigercat.git",
|
|
11
|
+
"directory": "packages/cli"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/expcats/Tigercat#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/expcats/Tigercat/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"tigercat",
|
|
19
|
+
"ui",
|
|
20
|
+
"cli",
|
|
21
|
+
"scaffolding",
|
|
22
|
+
"code-generator"
|
|
23
|
+
],
|
|
24
|
+
"bin": {
|
|
25
|
+
"tigercat": "./dist/index.mjs"
|
|
26
|
+
},
|
|
27
|
+
"main": "./dist/index.mjs",
|
|
28
|
+
"module": "./dist/index.mjs",
|
|
29
|
+
"types": "./dist/index.d.mts",
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"templates"
|
|
33
|
+
],
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"commander": "^13.1.0",
|
|
39
|
+
"prompts": "^2.4.2",
|
|
40
|
+
"picocolors": "^1.1.1"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/prompts": "^2.4.9",
|
|
44
|
+
"tsup": "^8.5.1",
|
|
45
|
+
"typescript": "^5.9.3"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup --config tsup.config.ts",
|
|
49
|
+
"dev": "tsup --config tsup.config.ts --watch",
|
|
50
|
+
"clean": "node ../../scripts/rimraf.mjs dist"
|
|
51
|
+
}
|
|
52
|
+
}
|