@zhin.js/cli 1.0.6 → 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 +14 -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 -54
- 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/lib/commands/start.d.ts.map +1 -1
- package/lib/commands/start.js +10 -4
- package/lib/commands/start.js.map +1 -1
- package/lib/utils/process.d.ts +2 -2
- package/lib/utils/process.d.ts.map +1 -1
- package/lib/utils/process.js +4 -10
- package/lib/utils/process.js.map +1 -1
- package/package.json +2 -2
- package/src/cli.ts +8 -0
- package/src/commands/install.ts +242 -0
- package/src/commands/new.ts +77 -60
- package/src/commands/pub.ts +176 -0
- package/src/commands/search.ts +243 -0
- package/src/commands/start.ts +10 -5
- package/src/utils/process.ts +5 -11
- package/tsconfig.json +17 -11
|
@@ -0,0 +1,242 @@
|
|
|
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 InstallOptions {
|
|
9
|
+
save?: boolean;
|
|
10
|
+
saveDev?: boolean;
|
|
11
|
+
global?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const installCommand = new Command('install')
|
|
15
|
+
.description('安装插件(npm 包或 git 仓库)')
|
|
16
|
+
.argument('[plugin]', '插件名称或 git 地址')
|
|
17
|
+
.option('-S, --save', '安装到 dependencies(默认)', true)
|
|
18
|
+
.option('-D, --save-dev', '安装到 devDependencies', false)
|
|
19
|
+
.option('-g, --global', '全局安装', false)
|
|
20
|
+
.action(async (plugin: string, options: InstallOptions) => {
|
|
21
|
+
try {
|
|
22
|
+
let pluginToInstall = plugin;
|
|
23
|
+
|
|
24
|
+
// 如果没有指定插件,交互式输入
|
|
25
|
+
if (!pluginToInstall) {
|
|
26
|
+
const { input } = await inquirer.prompt([
|
|
27
|
+
{
|
|
28
|
+
type: 'input',
|
|
29
|
+
name: 'input',
|
|
30
|
+
message: '请输入插件名称或 git 地址:',
|
|
31
|
+
validate: (input: string) => {
|
|
32
|
+
if (!input.trim()) {
|
|
33
|
+
return '插件名称或地址不能为空';
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
]);
|
|
39
|
+
pluginToInstall = input;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 判断插件类型
|
|
43
|
+
const pluginType = detectPluginType(pluginToInstall);
|
|
44
|
+
|
|
45
|
+
logger.info(`检测到插件类型: ${pluginType}`);
|
|
46
|
+
logger.info(`正在安装: ${pluginToInstall}`);
|
|
47
|
+
logger.log('');
|
|
48
|
+
|
|
49
|
+
// 构建安装命令
|
|
50
|
+
const installCmd = buildInstallCommand(pluginToInstall, pluginType, options);
|
|
51
|
+
|
|
52
|
+
logger.log(`执行命令: ${installCmd}`);
|
|
53
|
+
logger.log('');
|
|
54
|
+
|
|
55
|
+
// 执行安装
|
|
56
|
+
try {
|
|
57
|
+
execSync(installCmd, {
|
|
58
|
+
cwd: process.cwd(),
|
|
59
|
+
stdio: 'inherit'
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
logger.success('✓ 插件安装成功!');
|
|
63
|
+
logger.log('');
|
|
64
|
+
|
|
65
|
+
// 如果是 git 插件,提供额外说明
|
|
66
|
+
if (pluginType === 'git') {
|
|
67
|
+
logger.log('📝 Git 插件已安装到 node_modules/');
|
|
68
|
+
logger.log('');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 提示如何启用插件
|
|
72
|
+
const pluginName = extractPluginName(pluginToInstall, pluginType);
|
|
73
|
+
if (pluginName) {
|
|
74
|
+
logger.log('🔌 启用插件:');
|
|
75
|
+
logger.log(`在 zhin.config.ts 中添加:`);
|
|
76
|
+
logger.log('');
|
|
77
|
+
logger.log(' export default defineConfig({');
|
|
78
|
+
logger.log(' plugins: [');
|
|
79
|
+
logger.log(` '${pluginName}'`);
|
|
80
|
+
logger.log(' ]');
|
|
81
|
+
logger.log(' });');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
} catch (error) {
|
|
85
|
+
logger.error('安装失败');
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
} catch (error: any) {
|
|
90
|
+
logger.error(`安装插件失败: ${error.message}`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// 别名命令
|
|
96
|
+
export const addCommand = new Command('add')
|
|
97
|
+
.description('安装插件(install 的别名)')
|
|
98
|
+
.argument('[plugin]', '插件名称或 git 地址')
|
|
99
|
+
.option('-S, --save', '安装到 dependencies(默认)', true)
|
|
100
|
+
.option('-D, --save-dev', '安装到 devDependencies', false)
|
|
101
|
+
.option('-g, --global', '全局安装', false)
|
|
102
|
+
.action(async (plugin: string, options: InstallOptions) => {
|
|
103
|
+
await installCommand.parseAsync(['node', 'zhin', 'install', plugin || '', ...buildOptionsArray(options)], { from: 'user' });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 检测插件类型
|
|
108
|
+
*/
|
|
109
|
+
function detectPluginType(plugin: string): 'npm' | 'git' | 'github' | 'gitlab' | 'bitbucket' {
|
|
110
|
+
// Git 协议
|
|
111
|
+
if (plugin.startsWith('git://') || plugin.startsWith('git+')) {
|
|
112
|
+
return 'git';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// HTTPS/SSH git 地址
|
|
116
|
+
if (plugin.includes('github.com') || plugin.includes('gitlab.com') || plugin.includes('bitbucket.org')) {
|
|
117
|
+
if (plugin.includes('github.com')) return 'github';
|
|
118
|
+
if (plugin.includes('gitlab.com')) return 'gitlab';
|
|
119
|
+
if (plugin.includes('bitbucket.org')) return 'bitbucket';
|
|
120
|
+
return 'git';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// GitHub 简写 (user/repo)
|
|
124
|
+
if (/^[\w-]+\/[\w-]+$/.test(plugin)) {
|
|
125
|
+
return 'github';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 默认为 npm 包
|
|
129
|
+
return 'npm';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 构建安装命令
|
|
134
|
+
*/
|
|
135
|
+
function buildInstallCommand(plugin: string, type: string, options: InstallOptions): string {
|
|
136
|
+
const parts = ['pnpm', 'add'];
|
|
137
|
+
|
|
138
|
+
// 添加保存选项
|
|
139
|
+
if (options.saveDev) {
|
|
140
|
+
parts.push('-D');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (options.global) {
|
|
144
|
+
parts.push('-g');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 处理不同类型的插件
|
|
148
|
+
let packageSpec = plugin;
|
|
149
|
+
|
|
150
|
+
switch (type) {
|
|
151
|
+
case 'github':
|
|
152
|
+
// 如果是简写形式,转换为完整 GitHub URL
|
|
153
|
+
if (/^[\w-]+\/[\w-]+$/.test(plugin)) {
|
|
154
|
+
packageSpec = `github:${plugin}`;
|
|
155
|
+
} else if (!plugin.startsWith('git+') && !plugin.startsWith('https://')) {
|
|
156
|
+
packageSpec = `git+${plugin}`;
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
|
|
160
|
+
case 'gitlab':
|
|
161
|
+
if (!plugin.startsWith('git+') && !plugin.startsWith('https://')) {
|
|
162
|
+
packageSpec = `git+${plugin}`;
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
|
|
166
|
+
case 'bitbucket':
|
|
167
|
+
if (!plugin.startsWith('git+') && !plugin.startsWith('https://')) {
|
|
168
|
+
packageSpec = `git+${plugin}`;
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
|
|
172
|
+
case 'git':
|
|
173
|
+
// Git URL 直接使用
|
|
174
|
+
break;
|
|
175
|
+
|
|
176
|
+
case 'npm':
|
|
177
|
+
default:
|
|
178
|
+
// npm 包名直接使用
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
parts.push(packageSpec);
|
|
183
|
+
|
|
184
|
+
return parts.join(' ');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 提取插件名称
|
|
189
|
+
*/
|
|
190
|
+
function extractPluginName(plugin: string, type: string): string | null {
|
|
191
|
+
switch (type) {
|
|
192
|
+
case 'npm':
|
|
193
|
+
// npm 包名可能包含 scope 和版本号
|
|
194
|
+
// @scope/package@version -> @scope/package 或 package
|
|
195
|
+
const match = plugin.match(/^(@?[\w-]+\/)?([^@]+)/);
|
|
196
|
+
if (match) {
|
|
197
|
+
const fullName = match[0].replace(/@[\d.]+.*$/, ''); // 移除版本号
|
|
198
|
+
// 如果是 @zhin.js/ 开头的包,提取最后的名称
|
|
199
|
+
if (fullName.startsWith('@zhin.js/')) {
|
|
200
|
+
return fullName.replace('@zhin.js/', '');
|
|
201
|
+
}
|
|
202
|
+
return fullName;
|
|
203
|
+
}
|
|
204
|
+
return plugin;
|
|
205
|
+
|
|
206
|
+
case 'github':
|
|
207
|
+
case 'gitlab':
|
|
208
|
+
case 'bitbucket':
|
|
209
|
+
// 从 git URL 中提取仓库名
|
|
210
|
+
const repoMatch = plugin.match(/\/([^/]+?)(\.git)?$/);
|
|
211
|
+
if (repoMatch) {
|
|
212
|
+
return repoMatch[1];
|
|
213
|
+
}
|
|
214
|
+
// 简写形式 user/repo
|
|
215
|
+
if (/^[\w-]+\/([\w-]+)$/.test(plugin)) {
|
|
216
|
+
return plugin.split('/')[1];
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
|
|
220
|
+
case 'git':
|
|
221
|
+
// 从 git URL 中提取仓库名
|
|
222
|
+
const gitMatch = plugin.match(/\/([^/]+?)(\.git)?$/);
|
|
223
|
+
if (gitMatch) {
|
|
224
|
+
return gitMatch[1];
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
|
|
228
|
+
default:
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 构建选项数组
|
|
235
|
+
*/
|
|
236
|
+
function buildOptionsArray(options: InstallOptions): string[] {
|
|
237
|
+
const arr: string[] = [];
|
|
238
|
+
if (options.saveDev) arr.push('-D');
|
|
239
|
+
if (options.global) arr.push('-g');
|
|
240
|
+
return arr;
|
|
241
|
+
}
|
|
242
|
+
|
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
|
-
}
|
|
159
|
-
|
|
160
|
-
await fs.writeJson(path.join(pluginDir, 'tsconfig.app.json'), tsConfigApp, { spaces: 2 });
|
|
177
|
+
"include": ["src/**/*"],
|
|
178
|
+
"exclude": ["lib", "node_modules", "client"]
|
|
179
|
+
}
|
|
180
|
+
;
|
|
161
181
|
|
|
162
|
-
|
|
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';
|
|
@@ -234,7 +251,7 @@ export { ${capitalizedName}Page };
|
|
|
234
251
|
`;
|
|
235
252
|
|
|
236
253
|
await fs.writeFile(path.join(pluginDir, 'client', 'index.tsx'), clientContent);
|
|
237
|
-
|
|
254
|
+
|
|
238
255
|
// 创建客户端页面组件
|
|
239
256
|
await fs.ensureDir(path.join(pluginDir, 'client', 'pages'));
|
|
240
257
|
const pageContent = `import { useEffect } from 'react';
|
|
@@ -335,7 +352,7 @@ dist/
|
|
|
335
352
|
}
|
|
336
353
|
}
|
|
337
354
|
|
|
338
|
-
async function addPluginToApp(pluginName: string) {
|
|
355
|
+
async function addPluginToApp(pluginName: string, isOfficial?: boolean) {
|
|
339
356
|
try {
|
|
340
357
|
const rootPackageJsonPath = path.resolve(process.cwd(), 'package.json');
|
|
341
358
|
|
|
@@ -346,7 +363,7 @@ async function addPluginToApp(pluginName: string) {
|
|
|
346
363
|
}
|
|
347
364
|
|
|
348
365
|
const packageJson = await fs.readJson(rootPackageJsonPath);
|
|
349
|
-
const packageName = `@zhin.js/${pluginName}`;
|
|
366
|
+
const packageName = isOfficial ? `@zhin.js/${pluginName}` : `zhin.js-${pluginName}`;
|
|
350
367
|
|
|
351
368
|
// 初始化 dependencies
|
|
352
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
|
+
|