@mindbase/node-tools 1.1.0 → 1.3.7
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/README.md +170 -0
- package/bin/clear.js +9 -18
- package/bin/publish.js +236 -0
- package/package.json +8 -4
- package/src/clear/ui.js +183 -204
- package/src/common/ui/screen.js +26 -2
- package/src/common/ui/utils.js +31 -0
- package/src/git-log/ui.js +3 -10
- package/src/publish/core/builder.js +61 -0
- package/src/publish/core/dependency.js +137 -0
- package/src/publish/core/detector.js +76 -0
- package/src/publish/core/scanner.js +91 -0
- package/src/publish/core/version.js +80 -0
- package/src/publish/publisher.js +157 -0
- package/src/publish/registry/adapters/base.js +51 -0
- package/src/publish/registry/adapters/codeup.js +97 -0
- package/src/publish/registry/adapters/npm.js +120 -0
- package/src/publish/registry/global-config.js +275 -0
- package/src/publish/registry/project-config.js +172 -0
- package/src/publish/registry/registry-manager.js +118 -0
- package/src/publish/ui.js +464 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm 适配器
|
|
3
|
+
* 支持发布到 npm 兼容的注册源
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { execaCommand } = require('execa');
|
|
7
|
+
const { writeFileSync, unlinkSync, existsSync } = require('fs');
|
|
8
|
+
const { join } = require('path');
|
|
9
|
+
const { BaseAdapter } = require('./base.js');
|
|
10
|
+
|
|
11
|
+
class NpmAdapter extends BaseAdapter {
|
|
12
|
+
/**
|
|
13
|
+
* 发布包到 npm
|
|
14
|
+
*/
|
|
15
|
+
async publish (pkg, options) {
|
|
16
|
+
const { tag = 'latest', dryRun = false } = options;
|
|
17
|
+
|
|
18
|
+
// 创建临时 .npmrc,包含 token 认证
|
|
19
|
+
const npmrcPath = join(pkg.path, '.npmrc');
|
|
20
|
+
// 提取 registry 主机名,不使用 origin(避免带协议)
|
|
21
|
+
const registryHost = new URL(this.registry).host;
|
|
22
|
+
const authTokenLine = `//${registryHost}/:_authToken=${this.token}\n`;
|
|
23
|
+
const npmrcContent = `registry=${this.registry}\n${authTokenLine}`;
|
|
24
|
+
writeFileSync(npmrcPath, npmrcContent, 'utf-8');
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const args = ['publish'];
|
|
28
|
+
if (tag) args.push('--tag', tag);
|
|
29
|
+
if (dryRun) args.push('--dry-run');
|
|
30
|
+
|
|
31
|
+
await execaCommand('npm ' + args.join(' '), {
|
|
32
|
+
cwd: pkg.path,
|
|
33
|
+
stdout: 'inherit',
|
|
34
|
+
stderr: 'inherit'
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return { success: true };
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return {
|
|
40
|
+
success: false,
|
|
41
|
+
error: error.message || error.stderr || '发布失败'
|
|
42
|
+
};
|
|
43
|
+
} finally {
|
|
44
|
+
// 清理临时 .npmrc
|
|
45
|
+
if (existsSync(npmrcPath)) {
|
|
46
|
+
// unlinkSync(npmrcPath);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 验证 token 有效性
|
|
52
|
+
*/
|
|
53
|
+
async validateToken () {
|
|
54
|
+
const { tmpdir } = require('os');
|
|
55
|
+
const { join } = require('path');
|
|
56
|
+
|
|
57
|
+
// 创建临时 .npmrc
|
|
58
|
+
const tmpNpmrcPath = join(tmpdir(), '.npmrc-' + Date.now());
|
|
59
|
+
const registryHost = new URL(this.registry).host;
|
|
60
|
+
const authTokenLine = `//${registryHost}/:_authToken=${this.token}\n`;
|
|
61
|
+
const npmrcContent = `registry=${this.registry}\n${authTokenLine}`;
|
|
62
|
+
writeFileSync(tmpNpmrcPath, npmrcContent, 'utf-8');
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const result = await execaCommand('npm whoami', {
|
|
66
|
+
env: {
|
|
67
|
+
...process.env,
|
|
68
|
+
npm_config_userconfig: tmpNpmrcPath
|
|
69
|
+
},
|
|
70
|
+
shell: true
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// 清理临时文件
|
|
74
|
+
unlinkSync(tmpNpmrcPath);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
valid: true,
|
|
78
|
+
username: result.stdout?.trim()
|
|
79
|
+
};
|
|
80
|
+
} catch (error) {
|
|
81
|
+
// 清理临时文件
|
|
82
|
+
try {
|
|
83
|
+
unlinkSync(tmpNpmrcPath);
|
|
84
|
+
} catch (e) {}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
valid: false,
|
|
88
|
+
error: error.message || 'Token 验证失败'
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 检查包是否存在
|
|
95
|
+
*/
|
|
96
|
+
async checkPackage (name, version) {
|
|
97
|
+
try {
|
|
98
|
+
const args = ['view', `${name}@${version}`];
|
|
99
|
+
if (this.registry && this.registry !== 'https://registry.npmjs.org') {
|
|
100
|
+
args.push('--registry', this.registry);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
await execaCommand('npm ' + args.join(' '), {
|
|
104
|
+
env: {
|
|
105
|
+
...process.env,
|
|
106
|
+
npm_config_registry: this.registry
|
|
107
|
+
},
|
|
108
|
+
shell: true
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return { exists: true };
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return { exists: false };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
NpmAdapter
|
|
120
|
+
};
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全局配置管理模块
|
|
3
|
+
* 统一管理所有发布源和 token
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { existsSync, readFileSync, writeFileSync, mkdirSync } = require('fs');
|
|
7
|
+
const { join, dirname } = require('path');
|
|
8
|
+
const { homedir } = 'os';
|
|
9
|
+
const os = require('os');
|
|
10
|
+
|
|
11
|
+
// 获取配置文件路径
|
|
12
|
+
function getGlobalConfigPath() {
|
|
13
|
+
const appData = process.env.APPDATA || join(homedir(), '.config');
|
|
14
|
+
return join(appData, 'node-tools', 'publish-config.json');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 默认配置
|
|
18
|
+
const DEFAULT_GLOBAL_CONFIG = {
|
|
19
|
+
registries: [
|
|
20
|
+
{
|
|
21
|
+
id: 'npm-official',
|
|
22
|
+
name: 'npm 官方',
|
|
23
|
+
type: 'npm',
|
|
24
|
+
url: 'https://registry.npmjs.org'
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
tokens: []
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// 确保配置目录存在
|
|
31
|
+
function ensureConfigDir(configPath) {
|
|
32
|
+
const dir = dirname(configPath);
|
|
33
|
+
if (!existsSync(dir)) {
|
|
34
|
+
mkdirSync(dir, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 全局配置管理类
|
|
40
|
+
*/
|
|
41
|
+
class GlobalConfig {
|
|
42
|
+
constructor() {
|
|
43
|
+
this.configPath = getGlobalConfigPath();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 读取全局配置
|
|
48
|
+
*/
|
|
49
|
+
getGlobalConfig() {
|
|
50
|
+
if (!existsSync(this.configPath)) {
|
|
51
|
+
// 首次运行,创建默认配置
|
|
52
|
+
ensureConfigDir(this.configPath);
|
|
53
|
+
this.setGlobalConfig(DEFAULT_GLOBAL_CONFIG);
|
|
54
|
+
return JSON.parse(JSON.stringify(DEFAULT_GLOBAL_CONFIG));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const content = readFileSync(this.configPath, 'utf-8');
|
|
59
|
+
return JSON.parse(content);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('配置文件读取失败,使用默认配置');
|
|
62
|
+
return JSON.parse(JSON.stringify(DEFAULT_GLOBAL_CONFIG));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 保存全局配置
|
|
68
|
+
*/
|
|
69
|
+
setGlobalConfig(config) {
|
|
70
|
+
ensureConfigDir(this.configPath);
|
|
71
|
+
writeFileSync(this.configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 获取所有注册源
|
|
76
|
+
*/
|
|
77
|
+
getRegistries() {
|
|
78
|
+
const config = this.getGlobalConfig();
|
|
79
|
+
return config.registries || [];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 根据 ID 获取注册源
|
|
84
|
+
*/
|
|
85
|
+
getRegistry(registryId) {
|
|
86
|
+
const registries = this.getRegistries();
|
|
87
|
+
return registries.find(r => r.id === registryId);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 添加注册源
|
|
92
|
+
*/
|
|
93
|
+
addRegistry(id, name, type, url) {
|
|
94
|
+
const config = this.getGlobalConfig();
|
|
95
|
+
|
|
96
|
+
// 检查 ID 是否已存在
|
|
97
|
+
if (config.registries.find(r => r.id === id)) {
|
|
98
|
+
throw new Error(`注册源 ID 已存在: ${id}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
config.registries.push({ id, name, type, url });
|
|
102
|
+
this.setGlobalConfig(config);
|
|
103
|
+
|
|
104
|
+
return { id, name, type, url };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 删除注册源
|
|
109
|
+
*/
|
|
110
|
+
removeRegistry(registryId) {
|
|
111
|
+
const config = this.getGlobalConfig();
|
|
112
|
+
|
|
113
|
+
// 删除关联的 tokens
|
|
114
|
+
config.tokens = config.tokens.filter(t => t.registryId !== registryId);
|
|
115
|
+
|
|
116
|
+
// 删除注册源
|
|
117
|
+
config.registries = config.registries.filter(r => r.id !== registryId);
|
|
118
|
+
|
|
119
|
+
this.setGlobalConfig(config);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 获取所有 token
|
|
124
|
+
*/
|
|
125
|
+
getTokens() {
|
|
126
|
+
const config = this.getGlobalConfig();
|
|
127
|
+
return config.tokens || [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 根据 ID 获取 token
|
|
132
|
+
*/
|
|
133
|
+
getToken(tokenId) {
|
|
134
|
+
const tokens = this.getTokens();
|
|
135
|
+
return tokens.find(t => t.id === tokenId);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 获取指定注册源的 tokens
|
|
140
|
+
*/
|
|
141
|
+
getTokensByRegistry(registryId) {
|
|
142
|
+
const tokens = this.getTokens();
|
|
143
|
+
return tokens.filter(t => t.registryId === registryId);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 添加 token
|
|
148
|
+
*/
|
|
149
|
+
async addToken(registryId, name, token) {
|
|
150
|
+
const config = this.getGlobalConfig();
|
|
151
|
+
|
|
152
|
+
// 检查注册源是否存在
|
|
153
|
+
const registry = this.getRegistry(registryId);
|
|
154
|
+
if (!registry) {
|
|
155
|
+
throw new Error(`注册源不存在: ${registryId}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const tokenId = `${registryId}-${Date.now()}`;
|
|
159
|
+
config.tokens.push({
|
|
160
|
+
id: tokenId,
|
|
161
|
+
registryId,
|
|
162
|
+
name,
|
|
163
|
+
token,
|
|
164
|
+
createdAt: new Date().toISOString()
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
this.setGlobalConfig(config);
|
|
168
|
+
|
|
169
|
+
return tokenId;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 删除 token
|
|
174
|
+
*/
|
|
175
|
+
async removeToken(tokenId) {
|
|
176
|
+
const config = this.getGlobalConfig();
|
|
177
|
+
config.tokens = config.tokens.filter(t => t.id !== tokenId);
|
|
178
|
+
this.setGlobalConfig(config);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 更新 token
|
|
183
|
+
*/
|
|
184
|
+
async updateToken(tokenId, newToken) {
|
|
185
|
+
const config = this.getGlobalConfig();
|
|
186
|
+
const tokenData = config.tokens.find(t => t.id === tokenId);
|
|
187
|
+
|
|
188
|
+
if (!tokenData) {
|
|
189
|
+
throw new Error(`Token 不存在: ${tokenId}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
tokenData.token = newToken;
|
|
193
|
+
this.setGlobalConfig(config);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 显示配置
|
|
198
|
+
*/
|
|
199
|
+
showConfig() {
|
|
200
|
+
const config = this.getGlobalConfig();
|
|
201
|
+
|
|
202
|
+
console.log('全局配置:');
|
|
203
|
+
console.log('\n注册源:');
|
|
204
|
+
for (const registry of config.registries) {
|
|
205
|
+
console.log(` ${registry.id} - ${registry.name} (${registry.type})`);
|
|
206
|
+
console.log(` URL: ${registry.url}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.log('\nTokens:');
|
|
210
|
+
for (const token of config.tokens) {
|
|
211
|
+
console.log(` ${token.id} - ${token.name}`);
|
|
212
|
+
console.log(` 注册源: ${token.registryId}`);
|
|
213
|
+
console.log(` 创建时间: ${token.createdAt}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 编辑配置
|
|
219
|
+
*/
|
|
220
|
+
editConfig() {
|
|
221
|
+
const edit = require('editor');
|
|
222
|
+
const { spawn } = require('child_process');
|
|
223
|
+
const { platform } = process;
|
|
224
|
+
|
|
225
|
+
// 确保配置文件存在
|
|
226
|
+
if (!existsSync(this.configPath)) {
|
|
227
|
+
ensureConfigDir(this.configPath);
|
|
228
|
+
this.setGlobalConfig(DEFAULT_GLOBAL_CONFIG);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
console.log(`打开配置文件: ${this.configPath}`);
|
|
232
|
+
|
|
233
|
+
// 优先使用 code(VS Code)
|
|
234
|
+
const editorCmd = process.env.EDITOR || (platform === 'win32' ? 'code' : 'vim');
|
|
235
|
+
|
|
236
|
+
if (editorCmd === 'code') {
|
|
237
|
+
const spawnOptions = platform === 'win32'
|
|
238
|
+
? { shell: true, stdio: 'inherit' }
|
|
239
|
+
: { stdio: 'inherit' };
|
|
240
|
+
|
|
241
|
+
spawn('code', [this.configPath, '--wait'], spawnOptions)
|
|
242
|
+
.on('exit', (code) => {
|
|
243
|
+
if (code === 0) {
|
|
244
|
+
console.log('\n配置已保存');
|
|
245
|
+
} else {
|
|
246
|
+
console.log(`\n编辑器退出,代码: ${code}`);
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
.on('error', () => {
|
|
250
|
+
console.log('VS Code 不可用,使用默认编辑器...');
|
|
251
|
+
edit(this.configPath, (code) => {
|
|
252
|
+
console.log(code === 0 ? '\n配置已保存' : `\n编辑器退出,代码: ${code}`);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
} else {
|
|
256
|
+
edit(this.configPath, (code) => {
|
|
257
|
+
console.log(code === 0 ? '\n配置已保存' : `\n编辑器退出,代码: ${code}`);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 重置为默认配置
|
|
264
|
+
*/
|
|
265
|
+
resetConfig() {
|
|
266
|
+
this.setGlobalConfig(DEFAULT_GLOBAL_CONFIG);
|
|
267
|
+
return DEFAULT_GLOBAL_CONFIG;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
module.exports = {
|
|
272
|
+
GlobalConfig,
|
|
273
|
+
getGlobalConfigPath,
|
|
274
|
+
DEFAULT_GLOBAL_CONFIG
|
|
275
|
+
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 项目配置管理模块
|
|
3
|
+
* 每个项目记录自己默认的源和 token ID
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { existsSync, readFileSync, writeFileSync } = require('fs');
|
|
7
|
+
const { join } = require('path');
|
|
8
|
+
|
|
9
|
+
const PROJECT_CONFIG_FILENAME = '.node-tools-publish.json';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_PROJECT_CONFIG = {
|
|
12
|
+
defaults: {
|
|
13
|
+
registries: [],
|
|
14
|
+
tokens: {} // { registryId: tokenId }
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 项目配置管理类
|
|
20
|
+
*/
|
|
21
|
+
class ProjectConfig {
|
|
22
|
+
constructor(projectPath) {
|
|
23
|
+
this.projectPath = projectPath;
|
|
24
|
+
this.configPath = join(projectPath, PROJECT_CONFIG_FILENAME);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 读取项目配置
|
|
29
|
+
*/
|
|
30
|
+
getProjectConfig() {
|
|
31
|
+
if (!existsSync(this.configPath)) {
|
|
32
|
+
return JSON.parse(JSON.stringify(DEFAULT_PROJECT_CONFIG));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const content = readFileSync(this.configPath, 'utf-8');
|
|
37
|
+
return JSON.parse(content);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('项目配置文件读取失败');
|
|
40
|
+
return JSON.parse(JSON.stringify(DEFAULT_PROJECT_CONFIG));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 保存项目配置
|
|
46
|
+
*/
|
|
47
|
+
setProjectConfig(config) {
|
|
48
|
+
writeFileSync(this.configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 获取默认源列表
|
|
53
|
+
*/
|
|
54
|
+
getDefaultRegistries() {
|
|
55
|
+
const config = this.getProjectConfig();
|
|
56
|
+
return config.defaults.registries || [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 设置默认源列表
|
|
61
|
+
*/
|
|
62
|
+
setDefaultRegistries(registryIds) {
|
|
63
|
+
const config = this.getProjectConfig();
|
|
64
|
+
config.defaults.registries = registryIds;
|
|
65
|
+
|
|
66
|
+
// 清理无效的 token 映射
|
|
67
|
+
const newTokens = {};
|
|
68
|
+
for (const registryId of registryIds) {
|
|
69
|
+
if (config.defaults.tokens[registryId]) {
|
|
70
|
+
newTokens[registryId] = config.defaults.tokens[registryId];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
config.defaults.tokens = newTokens;
|
|
74
|
+
|
|
75
|
+
this.setProjectConfig(config);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 获取指定源的默认 token ID
|
|
80
|
+
*/
|
|
81
|
+
getDefaultTokenId(registryId) {
|
|
82
|
+
const config = this.getProjectConfig();
|
|
83
|
+
return config.defaults.tokens[registryId];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 设置指定源的默认 token ID
|
|
88
|
+
*/
|
|
89
|
+
setDefaultToken(registryId, tokenId) {
|
|
90
|
+
const config = this.getProjectConfig();
|
|
91
|
+
config.defaults.tokens[registryId] = tokenId;
|
|
92
|
+
this.setProjectConfig(config);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 初始化项目配置
|
|
97
|
+
*/
|
|
98
|
+
async initConfig(defaultRegistryId, defaultTokenId) {
|
|
99
|
+
const config = this.getProjectConfig();
|
|
100
|
+
config.defaults.registries = [defaultRegistryId];
|
|
101
|
+
config.defaults.tokens = {
|
|
102
|
+
[defaultRegistryId]: defaultTokenId
|
|
103
|
+
};
|
|
104
|
+
this.setProjectConfig(config);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 显示配置
|
|
109
|
+
*/
|
|
110
|
+
showConfig() {
|
|
111
|
+
const config = this.getProjectConfig();
|
|
112
|
+
|
|
113
|
+
console.log('项目配置:');
|
|
114
|
+
console.log(` 配置文件: ${this.configPath}`);
|
|
115
|
+
console.log(`\n默认源: ${config.defaults.registries.join(', ') || '未设置'}`);
|
|
116
|
+
|
|
117
|
+
console.log('\n默认 Tokens:');
|
|
118
|
+
for (const [registryId, tokenId] of Object.entries(config.defaults.tokens)) {
|
|
119
|
+
console.log(` ${registryId}: ${tokenId}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 编辑配置
|
|
125
|
+
*/
|
|
126
|
+
editConfig() {
|
|
127
|
+
const edit = require('editor');
|
|
128
|
+
const { spawn } = require('child_process');
|
|
129
|
+
const { platform } = process;
|
|
130
|
+
|
|
131
|
+
// 确保配置文件存在
|
|
132
|
+
if (!existsSync(this.configPath)) {
|
|
133
|
+
this.setProjectConfig(DEFAULT_PROJECT_CONFIG);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log(`打开项目配置文件: ${this.configPath}`);
|
|
137
|
+
|
|
138
|
+
// 优先使用 code(VS Code)
|
|
139
|
+
const editorCmd = process.env.EDITOR || (platform === 'win32' ? 'code' : 'vim');
|
|
140
|
+
|
|
141
|
+
if (editorCmd === 'code') {
|
|
142
|
+
const spawnOptions = platform === 'win32'
|
|
143
|
+
? { shell: true, stdio: 'inherit' }
|
|
144
|
+
: { stdio: 'inherit' };
|
|
145
|
+
|
|
146
|
+
spawn('code', [this.configPath, '--wait'], spawnOptions)
|
|
147
|
+
.on('exit', (code) => {
|
|
148
|
+
if (code === 0) {
|
|
149
|
+
console.log('\n配置已保存');
|
|
150
|
+
} else {
|
|
151
|
+
console.log(`\n编辑器退出,代码: ${code}`);
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
.on('error', () => {
|
|
155
|
+
console.log('VS Code 不可用,使用默认编辑器...');
|
|
156
|
+
edit(this.configPath, (code) => {
|
|
157
|
+
console.log(code === 0 ? '\n配置已保存' : `\n编辑器退出,代码: ${code}`);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
} else {
|
|
161
|
+
edit(this.configPath, (code) => {
|
|
162
|
+
console.log(code === 0 ? '\n配置已保存' : `\n编辑器退出,代码: ${code}`);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = {
|
|
169
|
+
ProjectConfig,
|
|
170
|
+
PROJECT_CONFIG_FILENAME,
|
|
171
|
+
DEFAULT_PROJECT_CONFIG
|
|
172
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 注册源管理器
|
|
3
|
+
* 协调全局配置、项目配置和适配器
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { GlobalConfig } = require('./global-config.js');
|
|
7
|
+
const { ProjectConfig } = require('./project-config.js');
|
|
8
|
+
const { NpmAdapter } = require('./adapters/npm.js');
|
|
9
|
+
const { CodeupAdapter } = require('./adapters/codeup.js');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 注册源管理器类
|
|
13
|
+
*/
|
|
14
|
+
class RegistryManager {
|
|
15
|
+
constructor(globalConfig, projectConfig) {
|
|
16
|
+
this.globalConfig = globalConfig;
|
|
17
|
+
this.projectConfig = projectConfig;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 创建实例(工厂方法)
|
|
22
|
+
*/
|
|
23
|
+
static async create(projectPath) {
|
|
24
|
+
const globalConfig = new GlobalConfig();
|
|
25
|
+
const projectConfig = new ProjectConfig(projectPath);
|
|
26
|
+
|
|
27
|
+
return new RegistryManager(globalConfig, projectConfig);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 获取项目默认的源和 token
|
|
32
|
+
*/
|
|
33
|
+
async getDefaultRegistriesWithTokens() {
|
|
34
|
+
const registryIds = this.projectConfig.getDefaultRegistries();
|
|
35
|
+
|
|
36
|
+
// 如果项目配置为空,返回全局第一个源
|
|
37
|
+
if (registryIds.length === 0) {
|
|
38
|
+
const registries = this.globalConfig.getRegistries();
|
|
39
|
+
if (registries.length > 0) {
|
|
40
|
+
const firstRegistry = registries[0];
|
|
41
|
+
const tokens = this.globalConfig.getTokensByRegistry(firstRegistry.id);
|
|
42
|
+
if (tokens.length > 0) {
|
|
43
|
+
return [{
|
|
44
|
+
registry: firstRegistry,
|
|
45
|
+
tokenId: tokens[0].id,
|
|
46
|
+
token: tokens[0].token
|
|
47
|
+
}];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result = [];
|
|
54
|
+
|
|
55
|
+
for (const registryId of registryIds) {
|
|
56
|
+
const registry = this.globalConfig.getRegistry(registryId);
|
|
57
|
+
if (!registry) {
|
|
58
|
+
console.warn(`注册源不存在: ${registryId},跳过`);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const tokenId = this.projectConfig.getDefaultTokenId(registryId);
|
|
63
|
+
if (!tokenId) {
|
|
64
|
+
console.warn(`注册源 ${registryId} 未配置默认 token,跳过`);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const tokenData = this.globalConfig.getToken(tokenId);
|
|
69
|
+
if (!tokenData) {
|
|
70
|
+
console.warn(`Token 不存在: ${tokenId},跳过`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
result.push({
|
|
75
|
+
registry,
|
|
76
|
+
tokenId,
|
|
77
|
+
token: tokenData.token
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 创建适配器实例
|
|
86
|
+
*/
|
|
87
|
+
createAdapter(registry, token) {
|
|
88
|
+
const AdapterClass = {
|
|
89
|
+
'npm': NpmAdapter,
|
|
90
|
+
'codeup': CodeupAdapter
|
|
91
|
+
}[registry.type];
|
|
92
|
+
|
|
93
|
+
if (!AdapterClass) {
|
|
94
|
+
throw new Error(`不支持的注册源类型: ${registry.type}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return new AdapterClass(registry, token);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 验证 token
|
|
102
|
+
*/
|
|
103
|
+
async validateToken(registryId, tokenId) {
|
|
104
|
+
const registry = this.globalConfig.getRegistry(registryId);
|
|
105
|
+
const tokenData = this.globalConfig.getToken(tokenId);
|
|
106
|
+
|
|
107
|
+
if (!registry || !tokenData) {
|
|
108
|
+
return { valid: false, error: '注册源或 token 不存在' };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const adapter = this.createAdapter(registry, tokenData.token);
|
|
112
|
+
return await adapter.validateToken();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
RegistryManager
|
|
118
|
+
};
|