@zhin.js/cli 1.0.7 → 1.0.8
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/CHANGELOG.md +6 -0
- package/lib/cli.js +8 -0
- package/lib/cli.js.map +1 -1
- package/lib/commands/install.d.ts +4 -0
- package/lib/commands/install.d.ts.map +1 -0
- package/lib/commands/install.js +209 -0
- package/lib/commands/install.js.map +1 -0
- package/lib/commands/new.d.ts.map +1 -1
- package/lib/commands/new.js +70 -66
- package/lib/commands/new.js.map +1 -1
- package/lib/commands/pub.d.ts +3 -0
- package/lib/commands/pub.d.ts.map +1 -0
- package/lib/commands/pub.js +151 -0
- package/lib/commands/pub.js.map +1 -0
- package/lib/commands/search.d.ts +4 -0
- package/lib/commands/search.d.ts.map +1 -0
- package/lib/commands/search.js +205 -0
- package/lib/commands/search.js.map +1 -0
- package/package.json +1 -1
- package/src/cli.ts +8 -0
- package/src/commands/install.ts +242 -0
- package/src/commands/new.ts +76 -73
- package/src/commands/pub.ts +176 -0
- package/src/commands/search.ts +243 -0
- package/tsconfig.json +17 -11
package/src/commands/new.ts
CHANGED
|
@@ -7,16 +7,17 @@ import { execSync } from 'node:child_process';
|
|
|
7
7
|
|
|
8
8
|
interface NewPluginOptions {
|
|
9
9
|
skipInstall?: boolean;
|
|
10
|
+
isOfficial?: boolean;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export const newCommand = new Command('new')
|
|
13
14
|
.description('创建插件包模板')
|
|
14
15
|
.argument('[plugin-name]', '插件名称(如: my-plugin)')
|
|
16
|
+
.option('--is-official', '是否为官方插件', false)
|
|
15
17
|
.option('--skip-install', '跳过依赖安装', false)
|
|
16
18
|
.action(async (pluginName: string, options: NewPluginOptions) => {
|
|
17
19
|
try {
|
|
18
20
|
let name = pluginName;
|
|
19
|
-
|
|
20
21
|
if (!name) {
|
|
21
22
|
const { pluginName: inputName } = await inquirer.prompt([
|
|
22
23
|
{
|
|
@@ -53,7 +54,7 @@ export const newCommand = new Command('new')
|
|
|
53
54
|
await createPluginPackage(pluginDir, name, options);
|
|
54
55
|
|
|
55
56
|
// 自动添加到 app/package.json
|
|
56
|
-
await addPluginToApp(name);
|
|
57
|
+
await addPluginToApp(name, options.isOfficial);
|
|
57
58
|
|
|
58
59
|
logger.success(`✓ 插件包 ${name} 创建成功!`);
|
|
59
60
|
logger.log('');
|
|
@@ -76,11 +77,11 @@ export const newCommand = new Command('new')
|
|
|
76
77
|
|
|
77
78
|
async function createPluginPackage(pluginDir: string, pluginName: string, options: NewPluginOptions) {
|
|
78
79
|
const capitalizedName = pluginName.charAt(0).toUpperCase() + pluginName.slice(1).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
79
|
-
const packageName = `@zhin.js/${pluginName}`;
|
|
80
|
+
const packageName = options.isOfficial ? `@zhin.js/${pluginName}` : `zhin.js-${pluginName}`;
|
|
80
81
|
|
|
81
82
|
// 创建目录结构
|
|
82
83
|
await fs.ensureDir(pluginDir);
|
|
83
|
-
await fs.ensureDir(path.join(pluginDir, '
|
|
84
|
+
await fs.ensureDir(path.join(pluginDir, 'src'));
|
|
84
85
|
await fs.ensureDir(path.join(pluginDir, 'client'));
|
|
85
86
|
await fs.ensureDir(path.join(pluginDir, 'lib'));
|
|
86
87
|
await fs.ensureDir(path.join(pluginDir, 'dist'));
|
|
@@ -104,23 +105,23 @@ async function createPluginPackage(pluginDir: string, pluginName: string, option
|
|
|
104
105
|
},
|
|
105
106
|
files: [
|
|
106
107
|
'lib',
|
|
107
|
-
'
|
|
108
|
+
'src',
|
|
108
109
|
'dist',
|
|
109
110
|
'client',
|
|
110
111
|
'README.md',
|
|
111
112
|
'CHANGELOG.md'
|
|
112
113
|
],
|
|
113
114
|
scripts: {
|
|
114
|
-
build: 'pnpm build:
|
|
115
|
-
'build:
|
|
116
|
-
'build:client': '
|
|
117
|
-
dev: 'tsc --project tsconfig.
|
|
115
|
+
build: 'pnpm build:node && pnpm build:client',
|
|
116
|
+
'build:node': 'tsc --project node.tsconfig.json',
|
|
117
|
+
'build:client': 'zhin-console build',
|
|
118
|
+
dev: 'tsc --project node.tsconfig.json --watch',
|
|
118
119
|
clean: 'rm -rf lib dist',
|
|
119
120
|
prepublishOnly: 'pnpm build'
|
|
120
121
|
},
|
|
121
122
|
keywords: [
|
|
122
|
-
'zhin',
|
|
123
|
-
'
|
|
123
|
+
'zhin.js',
|
|
124
|
+
'plugin',
|
|
124
125
|
pluginName
|
|
125
126
|
],
|
|
126
127
|
author: '',
|
|
@@ -135,62 +136,77 @@ async function createPluginPackage(pluginDir: string, pluginName: string, option
|
|
|
135
136
|
'@zhin.js/types': 'workspace:*',
|
|
136
137
|
'@types/node': 'latest',
|
|
137
138
|
'@types/react': 'latest',
|
|
139
|
+
'@types/react-dom': 'latest',
|
|
138
140
|
'typescript': 'latest',
|
|
139
141
|
'react': 'latest',
|
|
140
142
|
'react-dom': 'latest',
|
|
141
|
-
|
|
143
|
+
"@zhin.js/client":"workspace:*",
|
|
144
|
+
'lucide-react': 'latest',
|
|
145
|
+
'radix-ui': 'latest',
|
|
146
|
+
'@radix-ui/themes': 'latest'
|
|
142
147
|
}
|
|
143
148
|
};
|
|
144
149
|
|
|
145
150
|
await fs.writeJson(path.join(pluginDir, 'package.json'), packageJson, { spaces: 2 });
|
|
146
151
|
|
|
147
|
-
// 创建 tsconfig.
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
// 创建 tsconfig.json (服务端主配置)
|
|
153
|
+
const tsConfig = {
|
|
154
|
+
"compilerOptions": {
|
|
155
|
+
"target": "ES2022",
|
|
156
|
+
"module": "ESNext",
|
|
157
|
+
"moduleResolution": "bundler",
|
|
158
|
+
"outDir": "./lib",
|
|
159
|
+
"rootDir": "./src",
|
|
160
|
+
"strict": true,
|
|
161
|
+
"esModuleInterop": true,
|
|
162
|
+
"skipLibCheck": true,
|
|
163
|
+
"forceConsistentCasingInFileNames": true,
|
|
164
|
+
"resolveJsonModule": true,
|
|
165
|
+
"isolatedModules": true,
|
|
166
|
+
"allowSyntheticDefaultImports": true,
|
|
167
|
+
"experimentalDecorators": true,
|
|
168
|
+
"emitDecoratorMetadata": true,
|
|
169
|
+
"declaration": true,
|
|
170
|
+
"declarationMap": true,
|
|
171
|
+
"sourceMap": true,
|
|
172
|
+
"verbatimModuleSyntax": false,
|
|
173
|
+
"types": [
|
|
174
|
+
"@zhin.js/http"
|
|
175
|
+
]
|
|
155
176
|
},
|
|
156
|
-
include: [
|
|
157
|
-
exclude: [
|
|
158
|
-
}
|
|
177
|
+
"include": ["src/**/*"],
|
|
178
|
+
"exclude": ["lib", "node_modules", "client"]
|
|
179
|
+
}
|
|
180
|
+
;
|
|
159
181
|
|
|
160
|
-
await fs.writeJson(path.join(pluginDir, 'tsconfig.
|
|
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
|
-
};
|
|
182
|
+
await fs.writeJson(path.join(pluginDir, 'tsconfig.json'), tsConfig, { spaces: 2 });
|
|
180
183
|
|
|
181
|
-
await fs.writeJson(path.join(pluginDir, 'tsconfig.client.json'), tsConfigClient, { spaces: 2 });
|
|
182
184
|
|
|
183
|
-
// 创建 tsconfig.json (
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
// 创建 client/tsconfig.json (客户端主配置)
|
|
186
|
+
const clientTsConfig = {
|
|
187
|
+
"compilerOptions": {
|
|
188
|
+
"outDir": "../dist",
|
|
189
|
+
"baseUrl": ".",
|
|
190
|
+
"declaration": true,
|
|
191
|
+
"module": "ESNext",
|
|
192
|
+
"moduleResolution": "bundler",
|
|
193
|
+
"target": "ES2022",
|
|
194
|
+
"jsx":"react-jsx",
|
|
195
|
+
"declarationMap": true,
|
|
196
|
+
"sourceMap": true,
|
|
197
|
+
"skipLibCheck": true,
|
|
198
|
+
"noEmit": false
|
|
199
|
+
},
|
|
200
|
+
"include": [
|
|
201
|
+
"./**/*"
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
;
|
|
189
205
|
|
|
190
|
-
await fs.writeJson(path.join(pluginDir, 'tsconfig.json'),
|
|
206
|
+
await fs.writeJson(path.join(pluginDir, 'client', 'tsconfig.json'), clientTsConfig, { spaces: 2 });
|
|
191
207
|
|
|
192
|
-
//
|
|
193
|
-
const
|
|
208
|
+
// 创建服务端入口文件 src/index.ts
|
|
209
|
+
const indexContent = `import {
|
|
194
210
|
useLogger,
|
|
195
211
|
useContext,
|
|
196
212
|
onDispose,
|
|
@@ -201,9 +217,10 @@ const logger = useLogger();
|
|
|
201
217
|
|
|
202
218
|
// 注册客户端入口(如果有客户端代码)
|
|
203
219
|
useContext('web', (web) => {
|
|
204
|
-
const dispose = web.addEntry(
|
|
205
|
-
path.resolve(import.meta.dirname, '../
|
|
206
|
-
|
|
220
|
+
const dispose = web.addEntry({
|
|
221
|
+
production: path.resolve(import.meta.dirname, '../dist/index.js'),
|
|
222
|
+
development: path.resolve(import.meta.dirname, '../client/index.tsx')
|
|
223
|
+
});
|
|
207
224
|
return dispose;
|
|
208
225
|
});
|
|
209
226
|
|
|
@@ -215,7 +232,7 @@ onDispose(() => {
|
|
|
215
232
|
logger.info('${capitalizedName} 插件已加载');
|
|
216
233
|
`;
|
|
217
234
|
|
|
218
|
-
await fs.writeFile(path.join(pluginDir, '
|
|
235
|
+
await fs.writeFile(path.join(pluginDir, 'src', 'index.ts'), indexContent);
|
|
219
236
|
|
|
220
237
|
// 创建客户端入口文件 client/index.tsx
|
|
221
238
|
const clientContent = `import { addPage } from '@zhin.js/client';
|
|
@@ -235,20 +252,6 @@ export { ${capitalizedName}Page };
|
|
|
235
252
|
|
|
236
253
|
await fs.writeFile(path.join(pluginDir, 'client', 'index.tsx'), clientContent);
|
|
237
254
|
|
|
238
|
-
// 创建 client/tsconfig.json
|
|
239
|
-
const clientTsConfig = {
|
|
240
|
-
extends: '@zhin.js/console/browser.tsconfig.json',
|
|
241
|
-
compilerOptions: {
|
|
242
|
-
target: 'ES2022',
|
|
243
|
-
module: 'ESNext',
|
|
244
|
-
moduleResolution: 'bundler',
|
|
245
|
-
jsx: 'react-jsx',
|
|
246
|
-
baseUrl: '.'
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
await fs.writeJson(path.join(pluginDir, 'client', 'tsconfig.json'), clientTsConfig, { spaces: 2 });
|
|
251
|
-
|
|
252
255
|
// 创建客户端页面组件
|
|
253
256
|
await fs.ensureDir(path.join(pluginDir, 'client', 'pages'));
|
|
254
257
|
const pageContent = `import { useEffect } from 'react';
|
|
@@ -349,7 +352,7 @@ dist/
|
|
|
349
352
|
}
|
|
350
353
|
}
|
|
351
354
|
|
|
352
|
-
async function addPluginToApp(pluginName: string) {
|
|
355
|
+
async function addPluginToApp(pluginName: string, isOfficial?: boolean) {
|
|
353
356
|
try {
|
|
354
357
|
const rootPackageJsonPath = path.resolve(process.cwd(), 'package.json');
|
|
355
358
|
|
|
@@ -360,7 +363,7 @@ async function addPluginToApp(pluginName: string) {
|
|
|
360
363
|
}
|
|
361
364
|
|
|
362
365
|
const packageJson = await fs.readJson(rootPackageJsonPath);
|
|
363
|
-
const packageName = `@zhin.js/${pluginName}`;
|
|
366
|
+
const packageName = isOfficial ? `@zhin.js/${pluginName}` : `zhin.js-${pluginName}`;
|
|
364
367
|
|
|
365
368
|
// 初始化 dependencies
|
|
366
369
|
if (!packageJson.dependencies) {
|
|
@@ -0,0 +1,176 @@
|
|
|
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 PublishOptions {
|
|
9
|
+
tag?: string;
|
|
10
|
+
access?: 'public' | 'restricted';
|
|
11
|
+
registry?: string;
|
|
12
|
+
dryRun?: boolean;
|
|
13
|
+
skipBuild?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const pubCommand = new Command('pub')
|
|
17
|
+
.description('发布插件到 npm')
|
|
18
|
+
.argument('[plugin-name]', '插件名称(如: my-plugin)')
|
|
19
|
+
.option('--tag <tag>', '发布标签', 'latest')
|
|
20
|
+
.option('--access <access>', '访问级别 (public|restricted)', 'public')
|
|
21
|
+
.option('--registry <url>', '自定义 npm registry')
|
|
22
|
+
.option('--dry-run', '试运行,不实际发布', false)
|
|
23
|
+
.option('--skip-build', '跳过构建步骤', false)
|
|
24
|
+
.action(async (pluginName: string, options: PublishOptions) => {
|
|
25
|
+
try {
|
|
26
|
+
const pluginsDir = path.resolve(process.cwd(), 'plugins');
|
|
27
|
+
|
|
28
|
+
// 检查 plugins 目录是否存在
|
|
29
|
+
if (!fs.existsSync(pluginsDir)) {
|
|
30
|
+
logger.error('未找到 plugins 目录,请在项目根目录运行此命令');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 获取所有插件
|
|
35
|
+
const availablePlugins = fs.readdirSync(pluginsDir)
|
|
36
|
+
.filter(name => {
|
|
37
|
+
const pluginPath = path.join(pluginsDir, name);
|
|
38
|
+
return fs.statSync(pluginPath).isDirectory() &&
|
|
39
|
+
fs.existsSync(path.join(pluginPath, 'package.json'));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (availablePlugins.length === 0) {
|
|
43
|
+
logger.error('未找到可发布的插件');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 如果没有指定插件名,让用户选择
|
|
48
|
+
let selectedPlugin = pluginName;
|
|
49
|
+
if (!selectedPlugin) {
|
|
50
|
+
if (availablePlugins.length === 1) {
|
|
51
|
+
selectedPlugin = availablePlugins[0];
|
|
52
|
+
logger.info(`自动选择插件: ${selectedPlugin}`);
|
|
53
|
+
} else {
|
|
54
|
+
const { plugin } = await inquirer.prompt([
|
|
55
|
+
{
|
|
56
|
+
type: 'list',
|
|
57
|
+
name: 'plugin',
|
|
58
|
+
message: '请选择要发布的插件:',
|
|
59
|
+
choices: availablePlugins
|
|
60
|
+
}
|
|
61
|
+
]);
|
|
62
|
+
selectedPlugin = plugin;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 验证插件是否存在
|
|
67
|
+
const pluginDir = path.join(pluginsDir, selectedPlugin);
|
|
68
|
+
if (!fs.existsSync(pluginDir)) {
|
|
69
|
+
logger.error(`插件不存在: ${selectedPlugin}`);
|
|
70
|
+
logger.log(`可用插件: ${availablePlugins.join(', ')}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const packageJsonPath = path.join(pluginDir, 'package.json');
|
|
75
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
76
|
+
logger.error(`未找到 package.json: ${packageJsonPath}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 读取 package.json
|
|
81
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
82
|
+
const packageName = packageJson.name;
|
|
83
|
+
const version = packageJson.version;
|
|
84
|
+
|
|
85
|
+
logger.info(`准备发布插件: ${packageName}@${version}`);
|
|
86
|
+
logger.log('');
|
|
87
|
+
|
|
88
|
+
// 确认发布
|
|
89
|
+
if (!options.dryRun) {
|
|
90
|
+
const { confirm } = await inquirer.prompt([
|
|
91
|
+
{
|
|
92
|
+
type: 'confirm',
|
|
93
|
+
name: 'confirm',
|
|
94
|
+
message: `确认发布 ${packageName}@${version} 到 npm?`,
|
|
95
|
+
default: false
|
|
96
|
+
}
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
if (!confirm) {
|
|
100
|
+
logger.warn('已取消发布');
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 构建插件
|
|
106
|
+
if (!options.skipBuild) {
|
|
107
|
+
logger.info('正在构建插件...');
|
|
108
|
+
try {
|
|
109
|
+
execSync('pnpm build', {
|
|
110
|
+
cwd: pluginDir,
|
|
111
|
+
stdio: 'inherit'
|
|
112
|
+
});
|
|
113
|
+
logger.success('✓ 构建完成');
|
|
114
|
+
} catch (error) {
|
|
115
|
+
logger.error('构建失败');
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 构建 npm publish 命令
|
|
121
|
+
const publishArgs = ['publish'];
|
|
122
|
+
|
|
123
|
+
if (options.access) {
|
|
124
|
+
publishArgs.push('--access', options.access);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (options.tag) {
|
|
128
|
+
publishArgs.push('--tag', options.tag);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (options.registry) {
|
|
132
|
+
publishArgs.push('--registry', options.registry);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (options.dryRun) {
|
|
136
|
+
publishArgs.push('--dry-run');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 总是添加 --no-git-checks(因为 plugins 可能不是 git 根目录)
|
|
140
|
+
publishArgs.push('--no-git-checks');
|
|
141
|
+
|
|
142
|
+
// 发布插件
|
|
143
|
+
logger.info(`正在发布${options.dryRun ? '(试运行)' : ''}...`);
|
|
144
|
+
logger.log(`命令: pnpm ${publishArgs.join(' ')}`);
|
|
145
|
+
logger.log('');
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
execSync(`pnpm ${publishArgs.join(' ')}`, {
|
|
149
|
+
cwd: pluginDir,
|
|
150
|
+
stdio: 'inherit'
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (options.dryRun) {
|
|
154
|
+
logger.success('✓ 试运行完成');
|
|
155
|
+
logger.log('');
|
|
156
|
+
logger.log('💡 提示: 移除 --dry-run 参数以实际发布');
|
|
157
|
+
} else {
|
|
158
|
+
logger.success(`✓ ${packageName}@${version} 发布成功!`);
|
|
159
|
+
logger.log('');
|
|
160
|
+
logger.log('📦 安装命令:');
|
|
161
|
+
logger.log(` pnpm add ${packageName}`);
|
|
162
|
+
logger.log('');
|
|
163
|
+
logger.log('🔗 npm 链接:');
|
|
164
|
+
logger.log(` https://www.npmjs.com/package/${packageName}`);
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
logger.error('发布失败');
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
} catch (error: any) {
|
|
172
|
+
logger.error(`发布失败: ${error.message}`);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { logger } from '../utils/logger.js';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
interface SearchOptions {
|
|
6
|
+
category?: string;
|
|
7
|
+
limit?: number;
|
|
8
|
+
official?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const searchCommand = new Command('search')
|
|
12
|
+
.description('搜索 Zhin.js 插件')
|
|
13
|
+
.argument('[keyword]', '搜索关键词')
|
|
14
|
+
.option('-c, --category <category>', '按分类搜索 (utility|service|game|adapter|admin|ai)')
|
|
15
|
+
.option('-l, --limit <number>', '限制结果数量', '20')
|
|
16
|
+
.option('--official', '仅显示官方插件', false)
|
|
17
|
+
.action(async (keyword: string, options: SearchOptions) => {
|
|
18
|
+
try {
|
|
19
|
+
logger.info('正在搜索插件...');
|
|
20
|
+
logger.log('');
|
|
21
|
+
|
|
22
|
+
// 构建搜索查询
|
|
23
|
+
let searchQuery = 'zhin';
|
|
24
|
+
|
|
25
|
+
if (options.official) {
|
|
26
|
+
searchQuery = '@zhin.js';
|
|
27
|
+
} else if (keyword) {
|
|
28
|
+
searchQuery = `zhin.js ${keyword} `;
|
|
29
|
+
} else {
|
|
30
|
+
searchQuery = 'zhin.js plugin';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 使用 npm search
|
|
34
|
+
const cmd = `npm search ${searchQuery} --json`;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const output = execSync(cmd, {
|
|
38
|
+
encoding: 'utf-8',
|
|
39
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
40
|
+
stdio: ['pipe', 'pipe', 'ignore'] // 忽略 stderr
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const results = JSON.parse(output);
|
|
44
|
+
|
|
45
|
+
// 过滤结果
|
|
46
|
+
let filteredResults = results.filter((pkg: any) => {
|
|
47
|
+
// 必须包含 zhin 关键词
|
|
48
|
+
const keywords = pkg.keywords || [];
|
|
49
|
+
const hasZhin = keywords.includes('zhin') ||
|
|
50
|
+
keywords.includes('plugin') ||
|
|
51
|
+
pkg.name.startsWith('@zhin.js/') ||
|
|
52
|
+
pkg.name.startsWith('zhin.js-');
|
|
53
|
+
|
|
54
|
+
if (!hasZhin) return false;
|
|
55
|
+
|
|
56
|
+
// 按分类过滤
|
|
57
|
+
if (options.category) {
|
|
58
|
+
const category = pkg.keywords?.find((k: string) =>
|
|
59
|
+
k.includes(options.category!)
|
|
60
|
+
);
|
|
61
|
+
if (!category) return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 按关键词过滤
|
|
65
|
+
if (keyword) {
|
|
66
|
+
const searchIn = [
|
|
67
|
+
pkg.name,
|
|
68
|
+
pkg.description || '',
|
|
69
|
+
...(pkg.keywords || [])
|
|
70
|
+
].join(' ').toLowerCase();
|
|
71
|
+
|
|
72
|
+
return searchIn.includes(keyword.toLowerCase());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return true;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// 限制结果数量
|
|
79
|
+
const limit = parseInt(String(options.limit) || '20');
|
|
80
|
+
if (filteredResults.length > limit) {
|
|
81
|
+
filteredResults = filteredResults.slice(0, limit);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 显示结果
|
|
85
|
+
if (filteredResults.length === 0) {
|
|
86
|
+
logger.warn('未找到匹配的插件');
|
|
87
|
+
logger.log('');
|
|
88
|
+
logger.log('💡 提示:');
|
|
89
|
+
logger.log(' - 尝试使用不同的关键词');
|
|
90
|
+
logger.log(' - 访问插件市场: https://zhin.pages.dev/plugins');
|
|
91
|
+
logger.log(' - 在 GitHub 搜索: https://github.com/topics/zhin.js');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
logger.success(`找到 ${filteredResults.length} 个插件:`);
|
|
96
|
+
logger.log('');
|
|
97
|
+
|
|
98
|
+
// 按下载量排序
|
|
99
|
+
filteredResults.sort((a: any, b: any) => {
|
|
100
|
+
const aDownloads = parseInt(a.downloads || '0');
|
|
101
|
+
const bDownloads = parseInt(b.downloads || '0');
|
|
102
|
+
return bDownloads - aDownloads;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// 显示插件列表
|
|
106
|
+
filteredResults.forEach((pkg: any, index: number) => {
|
|
107
|
+
const name = pkg.name;
|
|
108
|
+
const version = pkg.version;
|
|
109
|
+
const description = pkg.description || '无描述';
|
|
110
|
+
const author = pkg.publisher?.username || '未知';
|
|
111
|
+
const date = pkg.date ? new Date(pkg.date).toLocaleDateString('zh-CN') : '未知';
|
|
112
|
+
|
|
113
|
+
// 判断插件类型
|
|
114
|
+
let badge = '';
|
|
115
|
+
if (name.startsWith('@zhin.js/')) {
|
|
116
|
+
badge = '✨ [官方]';
|
|
117
|
+
} else if (name.startsWith('zhin.js-')) {
|
|
118
|
+
badge = '📦 [社区]';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
logger.log(`${index + 1}. ${badge} ${name}@${version}`);
|
|
122
|
+
logger.log(` ${description}`);
|
|
123
|
+
logger.log(` 作者: ${author} | 更新: ${date}`);
|
|
124
|
+
|
|
125
|
+
// 显示安装命令
|
|
126
|
+
logger.log(` 安装: zhin install ${name}`);
|
|
127
|
+
logger.log('');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
logger.log('💡 提示:');
|
|
131
|
+
logger.log(' - 使用 zhin info <package> 查看插件详情');
|
|
132
|
+
logger.log(' - 使用 zhin install <package> 安装插件');
|
|
133
|
+
logger.log(' - 访问 https://zhin.pages.dev/plugins 查看完整列表');
|
|
134
|
+
|
|
135
|
+
} catch (error) {
|
|
136
|
+
logger.error('搜索失败,请检查网络连接');
|
|
137
|
+
logger.log('');
|
|
138
|
+
logger.log('💡 替代方案:');
|
|
139
|
+
logger.log(' - 访问 npm: https://www.npmjs.com/search?q=zhin.js');
|
|
140
|
+
logger.log(' - 访问 GitHub: https://github.com/topics/zhin.js');
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
} catch (error: any) {
|
|
145
|
+
logger.error(`搜索插件失败: ${error.message}`);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
export const infoCommand = new Command('info')
|
|
151
|
+
.description('查看插件详细信息')
|
|
152
|
+
.argument('<package>', '插件包名')
|
|
153
|
+
.action(async (packageName: string) => {
|
|
154
|
+
try {
|
|
155
|
+
logger.info(`正在获取 ${packageName} 的信息...`);
|
|
156
|
+
logger.log('');
|
|
157
|
+
|
|
158
|
+
const cmd = `npm view ${packageName} --json`;
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const output = execSync(cmd, {
|
|
162
|
+
encoding: 'utf-8',
|
|
163
|
+
stdio: ['pipe', 'pipe', 'ignore']
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const info = JSON.parse(output);
|
|
167
|
+
|
|
168
|
+
// 显示插件信息
|
|
169
|
+
logger.success('插件信息:');
|
|
170
|
+
logger.log('');
|
|
171
|
+
|
|
172
|
+
logger.log(`📦 名称: ${info.name}`);
|
|
173
|
+
logger.log(`📝 版本: ${info.version}`);
|
|
174
|
+
logger.log(`📄 描述: ${info.description || '无'}`);
|
|
175
|
+
logger.log(`👤 作者: ${info.author?.name || info.maintainers?.[0]?.name || '未知'}`);
|
|
176
|
+
logger.log(`📅 发布时间: ${new Date(info.time?.modified || info.time?.created).toLocaleDateString('zh-CN')}`);
|
|
177
|
+
|
|
178
|
+
if (info.keywords?.length > 0) {
|
|
179
|
+
logger.log(`🏷️ 标签: ${info.keywords.join(', ')}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (info.homepage) {
|
|
183
|
+
logger.log(`🏠 主页: ${info.homepage}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (info.repository?.url) {
|
|
187
|
+
logger.log(`📂 仓库: ${info.repository.url.replace(/^git\+/, '').replace(/\.git$/, '')}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (info.bugs?.url) {
|
|
191
|
+
logger.log(`🐛 问题: ${info.bugs.url}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (info.license) {
|
|
195
|
+
logger.log(`⚖️ 许可: ${info.license}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 显示依赖
|
|
199
|
+
if (info.peerDependencies) {
|
|
200
|
+
logger.log('');
|
|
201
|
+
logger.log('🔗 对等依赖:');
|
|
202
|
+
Object.entries(info.peerDependencies).forEach(([dep, ver]) => {
|
|
203
|
+
logger.log(` ${dep}: ${ver}`);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 显示安装命令
|
|
208
|
+
logger.log('');
|
|
209
|
+
logger.log('📥 安装命令:');
|
|
210
|
+
logger.log(` zhin install ${info.name}`);
|
|
211
|
+
logger.log(` # 或`);
|
|
212
|
+
logger.log(` pnpm add ${info.name}`);
|
|
213
|
+
|
|
214
|
+
// Zhin 特定信息
|
|
215
|
+
if (info.zhin) {
|
|
216
|
+
logger.log('');
|
|
217
|
+
logger.log('🎯 Zhin 信息:');
|
|
218
|
+
if (info.zhin.displayName) {
|
|
219
|
+
logger.log(` 显示名称: ${info.zhin.displayName}`);
|
|
220
|
+
}
|
|
221
|
+
if (info.zhin.category) {
|
|
222
|
+
logger.log(` 分类: ${info.zhin.category}`);
|
|
223
|
+
}
|
|
224
|
+
if (info.zhin.features?.length > 0) {
|
|
225
|
+
logger.log(` 功能: ${info.zhin.features.join(', ')}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
} catch (error) {
|
|
230
|
+
logger.error(`未找到插件: ${packageName}`);
|
|
231
|
+
logger.log('');
|
|
232
|
+
logger.log('💡 提示:');
|
|
233
|
+
logger.log(' - 检查插件名称是否正确');
|
|
234
|
+
logger.log(' - 使用 zhin search 搜索插件');
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
} catch (error: any) {
|
|
239
|
+
logger.error(`获取插件信息失败: ${error.message}`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|