@zhin.js/cli 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,378 @@
1
+ import { Command } from 'commander';
2
+ import { logger } from '../utils/logger.js';
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+ import inquirer from 'inquirer';
6
+ import { execSync } from 'node:child_process';
7
+
8
+ interface NewPluginOptions {
9
+ skipInstall?: boolean;
10
+ }
11
+
12
+ export const newCommand = new Command('new')
13
+ .description('创建插件包模板')
14
+ .argument('[plugin-name]', '插件名称(如: my-plugin)')
15
+ .option('--skip-install', '跳过依赖安装', false)
16
+ .action(async (pluginName: string, options: NewPluginOptions) => {
17
+ try {
18
+ let name = pluginName;
19
+
20
+ if (!name) {
21
+ const { pluginName: inputName } = await inquirer.prompt([
22
+ {
23
+ type: 'input',
24
+ name: 'pluginName',
25
+ message: '请输入插件名称:',
26
+ default: 'my-plugin',
27
+ validate: (input: string) => {
28
+ if (!input.trim()) {
29
+ return '插件名称不能为空';
30
+ }
31
+ if (!/^[a-zA-Z0-9-_]+$/.test(input)) {
32
+ return '插件名称只能包含字母、数字、横线和下划线';
33
+ }
34
+ return true;
35
+ }
36
+ }
37
+ ]);
38
+ name = inputName;
39
+ }
40
+
41
+ // 确定插件目录
42
+ const pluginDir = path.resolve(process.cwd(), 'plugins', name);
43
+
44
+ // 检查目录是否已存在
45
+ if (fs.existsSync(pluginDir)) {
46
+ logger.error(`插件目录已存在: ${pluginDir}`);
47
+ process.exit(1);
48
+ }
49
+
50
+ logger.info(`正在创建插件包 ${name}...`);
51
+
52
+ // 创建插件包结构
53
+ await createPluginPackage(pluginDir, name, options);
54
+
55
+ // 自动添加到 app/package.json
56
+ await addPluginToApp(name);
57
+
58
+ logger.success(`✓ 插件包 ${name} 创建成功!`);
59
+ logger.log('');
60
+ logger.log('📝 下一步操作:');
61
+ logger.log(` cd plugins/${name}`);
62
+ if (options.skipInstall) {
63
+ logger.log(` pnpm install`);
64
+ }
65
+ logger.log(` pnpm build`);
66
+ logger.log(` pnpm dev # 开发模式(监听文件变化)`);
67
+ logger.log('');
68
+ logger.log('📦 发布到 npm:');
69
+ logger.log(` pnpm publish`);
70
+
71
+ } catch (error) {
72
+ logger.error(`创建插件失败: ${error}`);
73
+ process.exit(1);
74
+ }
75
+ });
76
+
77
+ async function createPluginPackage(pluginDir: string, pluginName: string, options: NewPluginOptions) {
78
+ const capitalizedName = pluginName.charAt(0).toUpperCase() + pluginName.slice(1).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
79
+ const packageName = `@zhin.js/${pluginName}`;
80
+
81
+ // 创建目录结构
82
+ await fs.ensureDir(pluginDir);
83
+ await fs.ensureDir(path.join(pluginDir, 'app'));
84
+ await fs.ensureDir(path.join(pluginDir, 'client'));
85
+ await fs.ensureDir(path.join(pluginDir, 'lib'));
86
+ await fs.ensureDir(path.join(pluginDir, 'dist'));
87
+
88
+ // 创建 package.json
89
+ const packageJson = {
90
+ name: packageName,
91
+ version: '0.1.0',
92
+ description: `Zhin.js ${capitalizedName} 插件`,
93
+ type: 'module',
94
+ main: './lib/index.js',
95
+ types: './lib/index.d.ts',
96
+ exports: {
97
+ '.': {
98
+ types: './lib/index.d.ts',
99
+ import: './lib/index.js'
100
+ },
101
+ './client': {
102
+ import: './dist/index.js'
103
+ }
104
+ },
105
+ files: [
106
+ 'lib',
107
+ 'app',
108
+ 'dist',
109
+ 'client',
110
+ 'README.md',
111
+ 'CHANGELOG.md'
112
+ ],
113
+ scripts: {
114
+ build: 'pnpm build:app && pnpm build:client',
115
+ 'build:app': 'tsc --project tsconfig.app.json',
116
+ 'build:client': 'tsc --project tsconfig.client.json',
117
+ dev: 'tsc --project tsconfig.app.json --watch',
118
+ clean: 'rm -rf lib dist',
119
+ prepublishOnly: 'pnpm build'
120
+ },
121
+ keywords: [
122
+ 'zhin',
123
+ 'zhin-plugin',
124
+ pluginName
125
+ ],
126
+ author: '',
127
+ license: 'MIT',
128
+ peerDependencies: {
129
+ 'zhin.js': 'workspace:*'
130
+ },
131
+ dependencies: {
132
+ '@zhin.js/client': 'workspace:*'
133
+ },
134
+ devDependencies: {
135
+ '@zhin.js/types': 'workspace:*',
136
+ '@types/node': 'latest',
137
+ '@types/react': 'latest',
138
+ 'typescript': 'latest',
139
+ 'react': 'latest',
140
+ 'react-dom': 'latest',
141
+ 'lucide-react': 'latest'
142
+ }
143
+ };
144
+
145
+ await fs.writeJson(path.join(pluginDir, 'package.json'), packageJson, { spaces: 2 });
146
+
147
+ // 创建 tsconfig.app.json (插件代码)
148
+ const tsConfigApp = {
149
+ extends: '../../tsconfig.json',
150
+ compilerOptions: {
151
+ rootDir: './app',
152
+ outDir: './lib',
153
+ declaration: true,
154
+ noEmit: false
155
+ },
156
+ include: ['app/**/*'],
157
+ exclude: ['node_modules', 'lib', 'dist']
158
+ };
159
+
160
+ await fs.writeJson(path.join(pluginDir, 'tsconfig.app.json'), tsConfigApp, { spaces: 2 });
161
+
162
+ // 创建 tsconfig.client.json (客户端代码)
163
+ const tsConfigClient = {
164
+ compilerOptions: {
165
+ target: 'ES2022',
166
+ module: 'ESNext',
167
+ moduleResolution: 'bundler',
168
+ rootDir: './client',
169
+ outDir: './dist',
170
+ declaration: false,
171
+ jsx: 'react-jsx',
172
+ baseUrl: '.',
173
+ skipLibCheck: true,
174
+ esModuleInterop: true,
175
+ allowSyntheticDefaultImports: true
176
+ },
177
+ include: ['client/**/*'],
178
+ exclude: ['node_modules', 'lib', 'dist']
179
+ };
180
+
181
+ await fs.writeJson(path.join(pluginDir, 'tsconfig.client.json'), tsConfigClient, { spaces: 2 });
182
+
183
+ // 创建 tsconfig.json (编辑器支持)
184
+ const tsConfig = {
185
+ extends: '../../tsconfig.json',
186
+ include: ['app/**/*', 'client/**/*'],
187
+ exclude: ['node_modules', 'lib', 'dist']
188
+ };
189
+
190
+ await fs.writeJson(path.join(pluginDir, 'tsconfig.json'), tsConfig, { spaces: 2 });
191
+
192
+ // 创建插件主入口文件 app/index.ts
193
+ const appContent = `import {
194
+ useLogger,
195
+ useContext,
196
+ onDispose,
197
+ } from 'zhin.js';
198
+ import path from 'node:path';
199
+
200
+ const logger = useLogger();
201
+
202
+ // 注册客户端入口(如果有客户端代码)
203
+ useContext('web', (web) => {
204
+ const dispose = web.addEntry(
205
+ path.resolve(import.meta.dirname, '../client/index.tsx')
206
+ );
207
+ return dispose;
208
+ });
209
+
210
+ // 插件销毁时的清理
211
+ onDispose(() => {
212
+ logger.info('${capitalizedName} 插件已销毁');
213
+ });
214
+
215
+ logger.info('${capitalizedName} 插件已加载');
216
+ `;
217
+
218
+ await fs.writeFile(path.join(pluginDir, 'app', 'index.ts'), appContent);
219
+
220
+ // 创建客户端入口文件 client/index.tsx
221
+ const clientContent = `import { addPage } from '@zhin.js/client';
222
+ import { Component } from 'lucide-react';
223
+ import ${capitalizedName}Page from './pages/${capitalizedName}Page';
224
+
225
+ addPage({
226
+ key: '${pluginName}-page',
227
+ path: '/plugins/${pluginName}',
228
+ title: '${capitalizedName}',
229
+ icon: <Component className="w-5 h-5" />,
230
+ element: <${capitalizedName}Page />
231
+ });
232
+
233
+ export { ${capitalizedName}Page };
234
+ `;
235
+
236
+ await fs.writeFile(path.join(pluginDir, 'client', 'index.tsx'), clientContent);
237
+
238
+ // 创建客户端页面组件
239
+ await fs.ensureDir(path.join(pluginDir, 'client', 'pages'));
240
+ const pageContent = `import { useEffect } from 'react';
241
+
242
+ export default function ${capitalizedName}Page() {
243
+
244
+ useEffect(() => {
245
+ console.log('${capitalizedName} 页面已挂载');
246
+ }, []);
247
+
248
+ return (
249
+ <div className="p-6">
250
+ <h1 className="text-2xl font-bold mb-4">${capitalizedName}</h1>
251
+ </div>
252
+ );
253
+ }
254
+ `;
255
+
256
+ await fs.writeFile(path.join(pluginDir, 'client', 'pages', `${capitalizedName}Page.tsx`), pageContent);
257
+
258
+ // 创建 README.md
259
+ const readmeContent = `# ${packageName}
260
+
261
+ ${capitalizedName} 插件 for Zhin.js
262
+
263
+ ## 安装
264
+
265
+ \`\`\`bash
266
+ pnpm add ${packageName}
267
+ \`\`\`
268
+
269
+ ## 使用
270
+
271
+ 在 \`zhin.config.ts\` 中添加插件:
272
+
273
+ \`\`\`typescript
274
+ export default defineConfig({
275
+ plugins: [
276
+ '${pluginName}'
277
+ ]
278
+ });
279
+ \`\`\`
280
+
281
+ ## 开发
282
+
283
+ \`\`\`bash
284
+ # 安装依赖
285
+ pnpm install
286
+
287
+ # 构建
288
+ pnpm build
289
+
290
+ # 开发模式
291
+ pnpm dev
292
+ \`\`\`
293
+
294
+ ## 许可证
295
+
296
+ MIT
297
+ `;
298
+
299
+ await fs.writeFile(path.join(pluginDir, 'README.md'), readmeContent);
300
+
301
+ // 创建 CHANGELOG.md
302
+ const changelogContent = `# ${packageName}
303
+
304
+ ## 0.1.0
305
+
306
+ ### Features
307
+
308
+ - 初始版本
309
+ `;
310
+
311
+ await fs.writeFile(path.join(pluginDir, 'CHANGELOG.md'), changelogContent);
312
+
313
+ // 创建 .gitignore
314
+ const gitignoreContent = `node_modules/
315
+ lib/
316
+ dist/
317
+ *.log
318
+ .DS_Store
319
+ `;
320
+
321
+ await fs.writeFile(path.join(pluginDir, '.gitignore'), gitignoreContent);
322
+
323
+ // 安装依赖
324
+ if (!options.skipInstall) {
325
+ logger.info('正在安装依赖...');
326
+ try {
327
+ execSync('pnpm install', {
328
+ cwd: pluginDir,
329
+ stdio: 'inherit'
330
+ });
331
+ logger.success('✓ 依赖安装成功');
332
+ } catch (error) {
333
+ logger.warn('⚠ 依赖安装失败,请手动执行 pnpm install');
334
+ }
335
+ }
336
+ }
337
+
338
+ async function addPluginToApp(pluginName: string) {
339
+ try {
340
+ const rootPackageJsonPath = path.resolve(process.cwd(), 'package.json');
341
+
342
+ // 检查根 package.json 是否存在
343
+ if (!fs.existsSync(rootPackageJsonPath)) {
344
+ logger.warn('⚠ 未找到根目录 package.json,跳过依赖添加');
345
+ return;
346
+ }
347
+
348
+ const packageJson = await fs.readJson(rootPackageJsonPath);
349
+ const packageName = `@zhin.js/${pluginName}`;
350
+
351
+ // 初始化 dependencies
352
+ if (!packageJson.dependencies) {
353
+ packageJson.dependencies = {};
354
+ }
355
+
356
+ // 添加 workspace 依赖
357
+ packageJson.dependencies[packageName] = 'workspace:*';
358
+
359
+ // 写回文件
360
+ await fs.writeJson(rootPackageJsonPath, packageJson, { spaces: 2 });
361
+
362
+ logger.success(`✓ 已将 ${packageName} 添加到 package.json`);
363
+
364
+ // 重新安装依赖
365
+ logger.info('正在更新依赖...');
366
+ try {
367
+ execSync('pnpm install', {
368
+ cwd: process.cwd(),
369
+ stdio: 'inherit'
370
+ });
371
+ logger.success('✓ 依赖更新成功');
372
+ } catch (error) {
373
+ logger.warn('⚠ 依赖更新失败,请手动执行 pnpm install');
374
+ }
375
+ } catch (error) {
376
+ logger.warn(`⚠ 添加到 package.json 失败: ${error}`);
377
+ }
378
+ }
@@ -1,3 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare const initCommand: Command;
3
- //# sourceMappingURL=init.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAcpC,eAAO,MAAM,WAAW,SA0HpB,CAAC"}