cus-base-ui 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +146 -55
  2. package/package.json +1 -1
  3. package/scripts/ui-cli.ts +170 -2
package/README.md CHANGED
@@ -1,55 +1,146 @@
1
- # CUS-UI CLI (Remote/NPM Ready)
2
-
3
- Bộ công cụ CLI giúp bạn cài đặt các components từ thư viện UI này vào bất kỳ dự án React nào khác thông qua `npx`.
4
-
5
- ## 🚀 Cách sử dụng từ dự án khác
6
-
7
- Bạn không cần cài đặt gì cả, chỉ cần đứng tại thư mục dự án của bạn và gõ:
8
-
9
- ### 1. Khởi tạo dự án (Lần đầu)
10
- ```bash
11
- npx cus-base-ui init
12
- ```
13
- Lệnh này sẽ tự động cài các gói cần thiết (`clsx`, `tailwind-merge`) và tạo file `src/lib/utils/cn.ts`.
14
-
15
- ### 2. Thêm Component
16
- ```bash
17
- npx cus-base-ui add button input switch
18
- ```
19
- *Lưu ý: CLI sẽ tự động nhận diện và tải thêm các component phụ thuộc nếu cần.*
20
-
21
- ### 3. Cấu hình Tailwind
22
- Chạy lệnh sau để nhận hướng dẫn copy-paste cấu hình theme:
23
- ```bash
24
- npx cus-base-ui tailwind
25
- ```
26
-
27
- ---
28
- ### 4. Liệt Component
29
- ```bash
30
- npx cus-base-ui list
31
- ```
32
-
33
- ## 🛠 Cách để tự bạn quản phát hành
34
-
35
- ### 1. Cập nhật Registry (Khi thêm component mới)
36
- ```bash
37
- npm run registry:build
38
- ```
39
- Sau đó hãy `git commit` và `git push` lên GitHub để CLI trên máy người dùng có thể thấy update mới.
40
-
41
- ### 2. Biên dịch CLI
42
- ```bash
43
- npm run build:cli
44
- ```
45
-
46
- ### 3. Phát hành lên NPM
47
- Để mọi người có thể gõ `npx cus-base-ui`, bạn cần đưa nó lên NPM:
48
- 1. Đăng nhập: `npm login`
49
- 2. Phát hành: `npm publish` (Nếu tên `cus-base-ui` đã bị trùng trên NPM, hãy đổi tên trong `package.json`).
50
-
51
- ---
52
-
53
- ## 📂 Cơ chế hoạt động
54
- - **Local Mode**: Nếu bạn chạy `npx cus-base-ui add --local`, nó sẽ tìm file `registry.json` ngay tại thư mục hiện tại.
55
- - **Remote Mode (Mặc định)**: CLI sẽ tải dữ liệu trực tiếp từ: `https://raw.githubusercontent.com/huy14032003/ui-component/main/registry.json`.
1
+ # cus-base-ui
2
+
3
+ Bộ component UI cho React, phân phối qua CLI copy trực tiếp vào dự án của bạn, không cần cài như package dependency (tương tự shadcn/ui).
4
+
5
+ ---
6
+
7
+ ## Yêu cầu
8
+
9
+ - Node.js 18+
10
+ - Dự án React + Vite + TypeScript
11
+
12
+ ---
13
+
14
+ ## Bắt đầu nhanh
15
+
16
+ ### 1. Khởi tạo dự án
17
+
18
+ Đứng tại thư mục gốc dự án của bạn, chạy:
19
+
20
+ ```bash
21
+ npx cus-base-ui init
22
+ ```
23
+
24
+ Lệnh này tự động thực hiện:
25
+
26
+ | Bước | Nội dung |
27
+ |------|----------|
28
+ | Cài dev packages | `tailwindcss`, `@tailwindcss/vite`, `@vitejs/plugin-react`, `vite-plugin-babel`, `babel-plugin-react-compiler`, `@types/node` |
29
+ | Cài runtime packages | `@base-ui/react`, `tailwind-variants`, `clsx`, `tailwind-merge`, `tailwindcss-animate` |
30
+ | Tạo / cập nhật `vite.config.ts` | Thêm plugin Tailwind, React, React Compiler + alias `@`, `@lib`, `@components`, `@assets`, `@pages`, `@styles` |
31
+ | Cập nhật `tsconfig.json` | Thêm `baseUrl` + `paths` tương ứng với alias trên |
32
+ | Setup Tailwind CSS | Thêm `@import "tailwindcss";` vào `src/index.css` (tạo mới nếu chưa có) |
33
+ | Cài core utilities | `clsx`, `tailwind-merge` + tạo `src/lib/utils/cn.ts` |
34
+
35
+ > Nếu `vite.config.ts` hoặc `tsconfig.json` đã tồn tại đã có cấu hình, CLI sẽ bỏ qua bước đó — không ghi đè.
36
+
37
+ **Kết quả `vite.config.ts` sau init:**
38
+
39
+ ```ts
40
+ import { defineConfig } from 'vite';
41
+ import tailwindcss from '@tailwindcss/vite';
42
+ import react from '@vitejs/plugin-react';
43
+ import babel from 'vite-plugin-babel';
44
+ import { reactCompilerPreset } from 'babel-plugin-react-compiler';
45
+ import path from 'path';
46
+
47
+ export default defineConfig({
48
+ plugins: [
49
+ tailwindcss(),
50
+ react(),
51
+ babel({ presets: [reactCompilerPreset()] }),
52
+ ],
53
+ resolve: {
54
+ alias: {
55
+ '@': path.resolve(__dirname, './src'),
56
+ '@lib': path.resolve(__dirname, './src/lib'),
57
+ '@components': path.resolve(__dirname, './src/components'),
58
+ '@assets': path.resolve(__dirname, './src/assets'),
59
+ '@pages': path.resolve(__dirname, './src/pages'),
60
+ '@styles': path.resolve(__dirname, './src/styles'),
61
+ },
62
+ },
63
+ });
64
+ ```
65
+
66
+ ---
67
+
68
+ ### 2. Thêm component
69
+
70
+ ```bash
71
+ npx cus-base-ui add button
72
+ npx cus-base-ui add button input switch # nhiều component cùng lúc
73
+ npx cus-base-ui add button --force # ghi đè nếu đã tồn tại
74
+ ```
75
+
76
+ Component được copy thẳng vào `src/components/ui/<name>/`. Các component phụ thuộc lẫn nhau sẽ tự động được kéo theo.
77
+
78
+ ---
79
+
80
+ ### 3. Xóa component
81
+
82
+ ```bash
83
+ npx cus-base-ui remove button
84
+ ```
85
+
86
+ ---
87
+
88
+ ### 4. Liệt kê tất cả component
89
+
90
+ ```bash
91
+ npx cus-base-ui list
92
+ ```
93
+
94
+ ---
95
+
96
+ ### 5. Hướng dẫn cấu hình Tailwind theme
97
+
98
+ ```bash
99
+ npx cus-base-ui tailwind
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Tùy chọn
105
+
106
+ | Flag | Mô tả |
107
+ |------|-------|
108
+ | `--local` | Dùng `registry.json` cục bộ thay vì tải từ remote |
109
+ | `--force` | Ghi đè file đã tồn tại khi `add` |
110
+
111
+ ---
112
+
113
+ ## Dành cho maintainer
114
+
115
+ ### Cập nhật registry (sau khi thêm/sửa component)
116
+
117
+ ```bash
118
+ npm run registry:build
119
+ ```
120
+
121
+ Sau đó commit + push lên GitHub để người dùng nhận được bản mới nhất.
122
+
123
+ ### Build CLI
124
+
125
+ ```bash
126
+ npm run build:cli
127
+ ```
128
+
129
+ Output: `dist/ui-cli.js`
130
+
131
+ ### Phát hành lên NPM
132
+
133
+ ```bash
134
+ npm login
135
+ npm publish
136
+ ```
137
+
138
+ > Nếu tên package bị trùng, đổi `name` trong `package.json` trước khi publish.
139
+
140
+ ---
141
+
142
+ ## Cơ chế hoạt động
143
+
144
+ - **Remote mode (mặc định):** Tải `registry.json` từ `https://raw.githubusercontent.com/huy14032003/ui-component/main/registry.json`
145
+ - **Local mode (`--local`):** Đọc `registry.json` ngay tại thư mục hiện tại
146
+ - Registry chứa source code + danh sách npm dependencies của từng component — CLI đọc rồi copy/install vào dự án target
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cus-base-ui",
3
3
  "private": false,
4
- "version": "0.2.3",
4
+ "version": "0.2.5",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "cus-base-ui": "./dist/ui-cli.cjs"
package/scripts/ui-cli.ts CHANGED
@@ -56,7 +56,7 @@ const getRegistry = async (isLocal: boolean): Promise<Registry> => {
56
56
  }
57
57
  };
58
58
 
59
- const installNpmPackages = (packages: string[], cwd: string) => {
59
+ const installNpmPackages = (packages: string[], cwd: string, dev = false) => {
60
60
  if (packages.length === 0) return;
61
61
 
62
62
  const pkgJsonPath = path.join(cwd, 'package.json');
@@ -71,13 +71,177 @@ const installNpmPackages = (packages: string[], cwd: string) => {
71
71
  if (toInstall.length === 0) return;
72
72
 
73
73
  log(`Installing: ${toInstall.join(', ')}...`);
74
+ const flag = dev ? '--save-dev' : '--save';
74
75
  try {
75
- execSync(`npm install ${toInstall.join(' ')} --save`, { stdio: 'inherit', cwd });
76
+ execSync(`npm install ${toInstall.join(' ')} ${flag}`, { stdio: 'inherit', cwd });
76
77
  } catch (err) {
77
78
  error(`Failed to install packages: ${toInstall.join(', ')}. ${err instanceof Error ? err.message : ''}`);
78
79
  }
79
80
  };
80
81
 
82
+ const VITE_DEV_PACKAGES = ['tailwindcss', '@tailwindcss/vite', '@vitejs/plugin-react', 'vite-plugin-babel', 'babel-plugin-react-compiler', '@types/node'];
83
+
84
+ // Runtime packages always required — installed regardless of registry content
85
+ const RUNTIME_PACKAGES = ['@base-ui/react', 'tailwind-variants', 'clsx', 'tailwind-merge', 'tailwindcss-animate'];
86
+
87
+ const VITE_CONFIG_TEMPLATE = `import { defineConfig } from 'vite';
88
+ import tailwindcss from '@tailwindcss/vite';
89
+ import react from '@vitejs/plugin-react';
90
+ import babel from 'vite-plugin-babel';
91
+ import { reactCompilerPreset } from 'babel-plugin-react-compiler';
92
+ import path from 'path';
93
+
94
+ // https://vite.dev/config/
95
+ export default defineConfig({
96
+ plugins: [
97
+ tailwindcss(),
98
+ react(),
99
+ babel({ presets: [reactCompilerPreset()] }),
100
+ ],
101
+ resolve: {
102
+ alias: {
103
+ '@': path.resolve(__dirname, './src'),
104
+ '@lib': path.resolve(__dirname, './src/lib'),
105
+ '@components': path.resolve(__dirname, './src/components'),
106
+ '@assets': path.resolve(__dirname, './src/assets'),
107
+ '@pages': path.resolve(__dirname, './src/pages'),
108
+ '@styles': path.resolve(__dirname, './src/styles'),
109
+ },
110
+ },
111
+ });
112
+ `;
113
+
114
+ const TSCONFIG_PATHS = {
115
+ '@/*': ['./src/*'],
116
+ '@lib/*': ['./src/lib/*'],
117
+ '@components/*': ['./src/components/*'],
118
+ '@assets/*': ['./src/assets/*'],
119
+ '@pages/*': ['./src/pages/*'],
120
+ '@styles/*': ['./src/styles/*'],
121
+ };
122
+
123
+ const setupViteConfig = (cwd: string) => {
124
+ installNpmPackages(VITE_DEV_PACKAGES, cwd, true);
125
+
126
+ const configTs = path.join(cwd, 'vite.config.ts');
127
+ const configJs = path.join(cwd, 'vite.config.js');
128
+
129
+ if (!fs.existsSync(configTs) && !fs.existsSync(configJs)) {
130
+ fs.writeFileSync(configTs, VITE_CONFIG_TEMPLATE);
131
+ log('Created vite.config.ts with Tailwind + React Compiler setup.');
132
+ return;
133
+ }
134
+
135
+ const existingPath = fs.existsSync(configTs) ? configTs : configJs;
136
+ const content = fs.readFileSync(existingPath, 'utf-8');
137
+
138
+ const missingImports: string[] = [];
139
+ if (!content.includes('@tailwindcss/vite')) missingImports.push("import tailwindcss from '@tailwindcss/vite';");
140
+ if (!content.includes('@vitejs/plugin-react')) missingImports.push("import react from '@vitejs/plugin-react';");
141
+ if (!content.includes('vite-plugin-babel')) missingImports.push("import babel from 'vite-plugin-babel';");
142
+ if (!content.includes('babel-plugin-react-compiler')) missingImports.push("import { reactCompilerPreset } from 'babel-plugin-react-compiler';");
143
+
144
+ const missingPlugins: string[] = [];
145
+ if (!content.includes('tailwindcss()')) missingPlugins.push('tailwindcss()');
146
+ if (!content.includes('react()') && !content.includes('react({')) missingPlugins.push('react()');
147
+ if (!content.includes('reactCompilerPreset')) missingPlugins.push('babel({ presets: [reactCompilerPreset()] })');
148
+
149
+ const hasAlias = content.includes('alias:') || content.includes("'@'") || content.includes('"@"');
150
+
151
+ if (missingImports.length === 0 && missingPlugins.length === 0 && hasAlias) {
152
+ log('vite.config already configured — skipping.');
153
+ return;
154
+ }
155
+
156
+ warn(`${path.basename(existingPath)} exists but is missing required setup. Add the following manually:`);
157
+ if (missingImports.length > 0) {
158
+ console.log('\n // Imports to add:');
159
+ for (const imp of missingImports) console.log(` ${imp}`);
160
+ }
161
+ if (missingPlugins.length > 0) {
162
+ console.log('\n // Plugins to add inside defineConfig({ plugins: [...] }):');
163
+ for (const plugin of missingPlugins) console.log(` ${plugin},`);
164
+ }
165
+ if (!hasAlias) {
166
+ console.log('\n // resolve.alias to add inside defineConfig({}):');
167
+ console.log(" resolve: {");
168
+ console.log(" alias: {");
169
+ console.log(" '@': path.resolve(__dirname, './src'),");
170
+ console.log(" '@lib': path.resolve(__dirname, './src/lib'),");
171
+ console.log(" '@components': path.resolve(__dirname, './src/components'),");
172
+ console.log(" '@assets': path.resolve(__dirname, './src/assets'),");
173
+ console.log(" '@pages': path.resolve(__dirname, './src/pages'),");
174
+ console.log(" '@styles': path.resolve(__dirname, './src/styles'),");
175
+ console.log(" },");
176
+ console.log(" },");
177
+ }
178
+ console.log('');
179
+ };
180
+
181
+ const ensureTailwindCss = (cwd: string) => {
182
+ const candidates = ['src/index.css', 'src/App.css', 'src/main.css'];
183
+ for (const cssFile of candidates) {
184
+ const cssPath = path.join(cwd, cssFile);
185
+ if (fs.existsSync(cssPath)) {
186
+ const content = fs.readFileSync(cssPath, 'utf-8');
187
+ if (!content.includes('@import "tailwindcss"') && !content.includes("@import 'tailwindcss'")) {
188
+ fs.writeFileSync(cssPath, `@import "tailwindcss";\n\n${content}`);
189
+ log(`Added @import "tailwindcss" to ${cssFile}`);
190
+ } else {
191
+ log(`${cssFile} already imports Tailwind — skipping.`);
192
+ }
193
+ return;
194
+ }
195
+ }
196
+ // No CSS file found — create src/index.css
197
+ const srcDir = path.join(cwd, 'src');
198
+ if (!fs.existsSync(srcDir)) fs.mkdirSync(srcDir, { recursive: true });
199
+ fs.writeFileSync(path.join(srcDir, 'index.css'), '@import "tailwindcss";\n');
200
+ log('Created src/index.css with @import "tailwindcss"');
201
+ };
202
+
203
+ const setupTsConfig = (cwd: string) => {
204
+ const candidates = ['tsconfig.app.json', 'tsconfig.json'];
205
+
206
+ for (const candidate of candidates) {
207
+ const configPath = path.join(cwd, candidate);
208
+ if (!fs.existsSync(configPath)) continue;
209
+
210
+ const raw = fs.readFileSync(configPath, 'utf-8');
211
+
212
+ if (raw.includes('"@/*"') || raw.includes("'@/*'")) {
213
+ log(`${candidate} already has path aliases — skipping.`);
214
+ return;
215
+ }
216
+
217
+ try {
218
+ // Strip single-line and block comments before parsing
219
+ const stripped = raw.replace(/\/\/[^\n]*/g, '').replace(/\/\*[\s\S]*?\*\//g, '');
220
+ const parsed = JSON.parse(stripped) as { compilerOptions?: Record<string, unknown> };
221
+ if (!parsed.compilerOptions) parsed.compilerOptions = {};
222
+ parsed.compilerOptions.baseUrl = '.';
223
+ parsed.compilerOptions.paths = TSCONFIG_PATHS;
224
+ fs.writeFileSync(configPath, JSON.stringify(parsed, null, 2));
225
+ log(`Added path aliases to ${candidate}.`);
226
+ } catch {
227
+ warn(`Could not auto-patch ${candidate}. Add these to compilerOptions manually:`);
228
+ console.log('\n "baseUrl": ".",');
229
+ console.log(' "paths": {');
230
+ for (const [alias, targets] of Object.entries(TSCONFIG_PATHS)) {
231
+ console.log(` "${alias}": ["${targets[0]}"],`);
232
+ }
233
+ console.log(' }');
234
+ console.log('');
235
+ }
236
+ return;
237
+ }
238
+
239
+ // No tsconfig found — create a minimal one
240
+ const newConfig = { compilerOptions: { baseUrl: '.', paths: TSCONFIG_PATHS } };
241
+ fs.writeFileSync(path.join(cwd, 'tsconfig.json'), JSON.stringify(newConfig, null, 2));
242
+ log('Created tsconfig.json with path aliases.');
243
+ };
244
+
81
245
  const ensureCore = (registry: { core?: { dependencies: string[]; files: { path: string; content: string }[] } }, cwd: string) => {
82
246
  const core = registry.core;
83
247
  if (!core) return;
@@ -226,6 +390,10 @@ const main = async () => {
226
390
  }
227
391
 
228
392
  case 'init': {
393
+ setupViteConfig(cwd);
394
+ setupTsConfig(cwd);
395
+ ensureTailwindCss(cwd);
396
+ installNpmPackages(RUNTIME_PACKAGES, cwd);
229
397
  ensureCore(registry, cwd);
230
398
  log('Initialization complete.');
231
399
  break;