@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,464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 发布 UI 交互流程
|
|
3
|
+
* 遵循备用屏幕模式规范
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const prompts = require('prompts');
|
|
8
|
+
const treeify = require('treeify');
|
|
9
|
+
const { withScreenSession } = require('../common/ui/screen.js');
|
|
10
|
+
const { waitForAnyKey, waitForConfirm } = require('../common/ui/utils.js');
|
|
11
|
+
const { detectWorkspace } = require('./core/detector.js');
|
|
12
|
+
const { scanPackages } = require('./core/scanner.js');
|
|
13
|
+
const { analyzeDependencies } = require('./core/dependency.js');
|
|
14
|
+
const { bumpVersion } = require('./core/version.js');
|
|
15
|
+
const { detectBuildScripts } = require('./core/builder.js');
|
|
16
|
+
const { RegistryManager } = require('./registry/registry-manager.js');
|
|
17
|
+
const { Publisher } = require('./publisher.js');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 显示扫描预览页
|
|
21
|
+
*/
|
|
22
|
+
async function showScanPreview(renderer, targetPath) {
|
|
23
|
+
// 检测工作空间
|
|
24
|
+
const workspace = await detectWorkspace(targetPath);
|
|
25
|
+
|
|
26
|
+
let content = chalk.bold.white(`扫描目录: ${targetPath}\n\n`);
|
|
27
|
+
content += `模式: ${workspace.type === 'single' ? '单项目' : '工作空间'}\n`;
|
|
28
|
+
|
|
29
|
+
// 扫描包
|
|
30
|
+
const packages = await scanPackages(targetPath, workspace.patterns);
|
|
31
|
+
content += `找到 ${chalk.bold(packages.length)} 个包\n\n`;
|
|
32
|
+
|
|
33
|
+
// 工作空间模式显示依赖树
|
|
34
|
+
if (workspace.type !== 'single' && packages.length > 0) {
|
|
35
|
+
// 依赖分析
|
|
36
|
+
const depAnalysis = analyzeDependencies(packages);
|
|
37
|
+
|
|
38
|
+
if (depAnalysis.hasCycle) {
|
|
39
|
+
content += chalk.red('警告: 检测到循环依赖\n');
|
|
40
|
+
content += chalk.red(depAnalysis.cycles.map(c => c.join(' -> ')).join('\n') + '\n\n');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 构建依赖树
|
|
44
|
+
content += '依赖关系树:\n';
|
|
45
|
+
const tree = {};
|
|
46
|
+
for (const pkg of depAnalysis.order) {
|
|
47
|
+
tree[pkg.name] = buildDepTree(pkg.name, packages, depAnalysis.graph, new Set());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
content += chalk.cyan(treeify.asTree(tree, true)) + '\n';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
content += '\n' + chalk.dim('按任意键继续...');
|
|
54
|
+
renderer.render(content);
|
|
55
|
+
|
|
56
|
+
await waitForAnyKey();
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
workspace,
|
|
60
|
+
packages,
|
|
61
|
+
depAnalysis: workspace.type !== 'single' ? analyzeDependencies(packages) : null
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 构建依赖树
|
|
67
|
+
*/
|
|
68
|
+
function buildDepTree(pkgName, packages, graph, visited) {
|
|
69
|
+
if (visited.has(pkgName)) {
|
|
70
|
+
return '(循环依赖)';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
visited.add(pkgName);
|
|
74
|
+
const pkg = packages.find(p => p.name === pkgName);
|
|
75
|
+
if (!pkg) return {};
|
|
76
|
+
|
|
77
|
+
const neighbors = graph.get(pkgName);
|
|
78
|
+
if (!neighbors || neighbors.size === 0) return {};
|
|
79
|
+
|
|
80
|
+
const tree = {};
|
|
81
|
+
for (const neighbor of neighbors) {
|
|
82
|
+
tree[neighbor] = buildDepTree(neighbor, packages, graph, new Set([...visited]));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return tree;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 显示源和 Token 选择页
|
|
90
|
+
*/
|
|
91
|
+
async function showRegistrySelection(renderer, projectPath) {
|
|
92
|
+
const registryManager = await RegistryManager.create(projectPath);
|
|
93
|
+
|
|
94
|
+
// 获取项目默认的源和 token
|
|
95
|
+
let registries = await registryManager.getDefaultRegistriesWithTokens();
|
|
96
|
+
|
|
97
|
+
// 如果没有配置默认源,显示选择界面
|
|
98
|
+
if (registries.length === 0) {
|
|
99
|
+
renderer.render(chalk.yellow('\n未配置默认发布源\n\n按任意键继续...'));
|
|
100
|
+
await waitForAnyKey();
|
|
101
|
+
|
|
102
|
+
const allRegistries = registryManager.globalConfig.getRegistries();
|
|
103
|
+
if (allRegistries.length === 0) {
|
|
104
|
+
throw new Error('请先使用 "node-tools-publish config --add-registry" 添加发布源');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const selected = await selectRegistriesInteractive(allRegistries, registryManager);
|
|
108
|
+
registries = selected;
|
|
109
|
+
} else {
|
|
110
|
+
// 显示当前配置并询问是否重新选择
|
|
111
|
+
let content = chalk.bold.cyan('\n发布源配置\n\n');
|
|
112
|
+
content += '当前项目默认:\n';
|
|
113
|
+
|
|
114
|
+
for (const { registry, tokenId } of registries) {
|
|
115
|
+
const tokenInfo = registryManager.globalConfig.getToken(tokenId);
|
|
116
|
+
const tokenName = tokenInfo ? tokenInfo.name : tokenId;
|
|
117
|
+
content += ` ✓ ${chalk.green(registry.name)} - Token: ${chalk.yellow(tokenName)}\n`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
content += '\n';
|
|
121
|
+
renderer.render(content);
|
|
122
|
+
|
|
123
|
+
const { action } = await prompts({
|
|
124
|
+
type: 'select',
|
|
125
|
+
name: 'action',
|
|
126
|
+
message: '是否使用默认配置?',
|
|
127
|
+
choices: [
|
|
128
|
+
{ title: '使用默认配置', value: 'use' },
|
|
129
|
+
{ title: '重新选择源和 Token', value: 'reselect' }
|
|
130
|
+
],
|
|
131
|
+
initial: 0
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (action === 'reselect') {
|
|
135
|
+
const allRegistries = registryManager.globalConfig.getRegistries();
|
|
136
|
+
if (allRegistries.length === 0) {
|
|
137
|
+
throw new Error('请先使用 "node-tools-publish config --add-registry" 添加发布源');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const selected = await selectRegistriesInteractive(allRegistries, registryManager);
|
|
141
|
+
registries = selected;
|
|
142
|
+
|
|
143
|
+
// 更新项目默认配置
|
|
144
|
+
if (selected.length > 0) {
|
|
145
|
+
const projectConfig = registryManager.projectConfig;
|
|
146
|
+
const registryIds = selected.map(r => r.registry.id);
|
|
147
|
+
projectConfig.setDefaultRegistries(registryIds);
|
|
148
|
+
|
|
149
|
+
// 设置每个源的默认 token
|
|
150
|
+
for (const { registry, tokenId } of selected) {
|
|
151
|
+
projectConfig.setDefaultToken(registry.id, tokenId);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return registries;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 交互式选择源和 token
|
|
162
|
+
*/
|
|
163
|
+
async function selectRegistriesInteractive(allRegistries, registryManager) {
|
|
164
|
+
const { selectedRegistries } = await prompts({
|
|
165
|
+
type: 'multiselect',
|
|
166
|
+
name: 'selectedRegistries',
|
|
167
|
+
message: '选择要发布的源',
|
|
168
|
+
choices: allRegistries.map(r => ({
|
|
169
|
+
title: `${r.name} (${r.type})`,
|
|
170
|
+
value: r.id
|
|
171
|
+
}))
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const result = [];
|
|
175
|
+
|
|
176
|
+
for (const registryId of selectedRegistries) {
|
|
177
|
+
// 选择该源的 token
|
|
178
|
+
const tokens = registryManager.globalConfig.getTokensByRegistry(registryId);
|
|
179
|
+
|
|
180
|
+
if (tokens.length === 0) {
|
|
181
|
+
console.log(chalk.yellow(`\n警告: 注册源 ${registryId} 没有配置 token`));
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const { tokenId } = await prompts({
|
|
186
|
+
type: 'select',
|
|
187
|
+
name: 'tokenId',
|
|
188
|
+
message: `选择 ${registryId} 的 token`,
|
|
189
|
+
choices: tokens.map(t => ({
|
|
190
|
+
title: `${t.name}`,
|
|
191
|
+
value: t.id
|
|
192
|
+
}))
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const registry = allRegistries.find(r => r.id === registryId);
|
|
196
|
+
const tokenData = registryManager.globalConfig.getToken(tokenId);
|
|
197
|
+
|
|
198
|
+
result.push({ registry, tokenId, token: tokenData.token });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 配置包版本
|
|
206
|
+
*/
|
|
207
|
+
async function configurePackages(renderer, packages, registries) {
|
|
208
|
+
const configuredPackages = [];
|
|
209
|
+
|
|
210
|
+
for (const pkg of packages) {
|
|
211
|
+
let content = chalk.bold.cyan(`\n配置: ${pkg.name}\n`);
|
|
212
|
+
content += `当前版本: ${chalk.yellow(pkg.version)}\n`;
|
|
213
|
+
|
|
214
|
+
const buildScript = detectBuildScripts(pkg.scripts);
|
|
215
|
+
content += `构建脚本: ${buildScript ? chalk.green(buildScript) : chalk.red('无')}\n`;
|
|
216
|
+
|
|
217
|
+
content += `发布到: ${registries.map(r => chalk.green(r.registry.name)).join(' + ')}\n`;
|
|
218
|
+
|
|
219
|
+
renderer.render(content);
|
|
220
|
+
|
|
221
|
+
// 版本升级选择
|
|
222
|
+
const { increment } = await prompts({
|
|
223
|
+
type: 'select',
|
|
224
|
+
name: 'increment',
|
|
225
|
+
message: `选择 ${pkg.name} 的版本升级类型`,
|
|
226
|
+
choices: [
|
|
227
|
+
{ title: `major - 主版本 (${bumpVersion(pkg.version, 'major')})`, value: 'major' },
|
|
228
|
+
{ title: `minor - 小版本 (${bumpVersion(pkg.version, 'minor')})`, value: 'minor' },
|
|
229
|
+
{ title: `patch - 修复 (${bumpVersion(pkg.version, 'patch')})`, value: 'patch' },
|
|
230
|
+
{ title: 'custom - 自定义版本', value: 'custom' },
|
|
231
|
+
{ title: 'skip - 跳过此包', value: 'skip' }
|
|
232
|
+
],
|
|
233
|
+
initial: 2
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
if (increment === 'skip') {
|
|
237
|
+
console.log(chalk.yellow(`跳过: ${pkg.name}`));
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let newVersion;
|
|
242
|
+
if (increment === 'custom') {
|
|
243
|
+
const { version } = await prompts({
|
|
244
|
+
type: 'text',
|
|
245
|
+
name: 'version',
|
|
246
|
+
message: '输入新版本号',
|
|
247
|
+
initial: pkg.version,
|
|
248
|
+
validate: value => {
|
|
249
|
+
const semver = require('semver');
|
|
250
|
+
return semver.valid(value) ? true : '无效的版本号格式';
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
newVersion = version;
|
|
254
|
+
} else {
|
|
255
|
+
newVersion = bumpVersion(pkg.version, increment);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// 发布标签选择
|
|
259
|
+
const { tag } = await prompts({
|
|
260
|
+
type: 'select',
|
|
261
|
+
name: 'tag',
|
|
262
|
+
message: '选择发布标签',
|
|
263
|
+
choices: [
|
|
264
|
+
{ title: 'latest - 最新稳定版', value: 'latest' },
|
|
265
|
+
{ title: 'beta - 测试版', value: 'beta' },
|
|
266
|
+
{ title: 'rc - 候选发布版', value: 'rc' },
|
|
267
|
+
{ title: 'canary - 金丝雀版本', value: 'canary' },
|
|
268
|
+
{ title: 'next - 下个版本预览', value: 'next' }
|
|
269
|
+
],
|
|
270
|
+
initial: 0
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
configuredPackages.push({
|
|
274
|
+
...pkg,
|
|
275
|
+
newVersion,
|
|
276
|
+
tag
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return configuredPackages;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 显示发布确认页
|
|
285
|
+
*/
|
|
286
|
+
async function showPublishConfirmation(renderer, packages, registries) {
|
|
287
|
+
const Table = require('cli-table3');
|
|
288
|
+
|
|
289
|
+
const table = new Table({
|
|
290
|
+
head: ['包名', '版本变化', '标签'],
|
|
291
|
+
colWidths: [40, 20, 15]
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
for (const pkg of packages) {
|
|
295
|
+
table.push([
|
|
296
|
+
pkg.name,
|
|
297
|
+
`${pkg.version} → ${chalk.green(pkg.newVersion)}`,
|
|
298
|
+
pkg.tag
|
|
299
|
+
]);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
let content = chalk.bold.cyan('\n发布计划确认\n\n');
|
|
303
|
+
content += '发布目标:\n';
|
|
304
|
+
|
|
305
|
+
for (const { registry, tokenId } of registries) {
|
|
306
|
+
content += ` • ${chalk.green(registry.name)} (${chalk.yellow(tokenId)})\n`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
content += '\n' + table.toString();
|
|
310
|
+
content += '\n' + chalk.dim('按 y/Enter 确认发布,Esc 取消...');
|
|
311
|
+
|
|
312
|
+
renderer.render(content);
|
|
313
|
+
|
|
314
|
+
return await waitForConfirm();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* 显示发布进度
|
|
319
|
+
*/
|
|
320
|
+
async function showPublishProgress(renderer, packages, registries, options) {
|
|
321
|
+
const publisher = new Publisher(await RegistryManager.create(process.cwd()));
|
|
322
|
+
const results = [];
|
|
323
|
+
|
|
324
|
+
for (let i = 0; i < packages.length; i++) {
|
|
325
|
+
const pkg = packages[i];
|
|
326
|
+
|
|
327
|
+
let content = chalk.bold.cyan(`\n发布中...\n\n`);
|
|
328
|
+
content += `[${i + 1}/${packages.length}] ${chalk.bold(pkg.name)}\n`;
|
|
329
|
+
|
|
330
|
+
// 显示之前的包结果
|
|
331
|
+
for (const prevResult of results) {
|
|
332
|
+
if (prevResult.skipped) {
|
|
333
|
+
content += ` ${chalk.yellow('○')} ${prevResult.package}: 跳过\n`;
|
|
334
|
+
} else if (prevResult.publishResults) {
|
|
335
|
+
const successCount = prevResult.publishResults.filter(r => r.success).length;
|
|
336
|
+
const totalCount = prevResult.publishResults.length;
|
|
337
|
+
const icon = successCount === totalCount ? chalk.green('✓') : chalk.red('✗');
|
|
338
|
+
content += ` ${icon} ${prevResult.package}: ${successCount}/${totalCount}\n`;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
content += `\n → 正在处理...`;
|
|
343
|
+
renderer.render(content);
|
|
344
|
+
|
|
345
|
+
const result = await publisher.publishPackage(pkg, registries, options);
|
|
346
|
+
results.push(result);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return results;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* 显示发布结果
|
|
354
|
+
*/
|
|
355
|
+
async function showPublishResults(renderer, results, registries) {
|
|
356
|
+
let successCount = 0;
|
|
357
|
+
let failedCount = 0;
|
|
358
|
+
|
|
359
|
+
// 统计每个源的结果
|
|
360
|
+
const registryStats = {};
|
|
361
|
+
for (const { registry } of registries) {
|
|
362
|
+
registryStats[registry.name] = { success: 0, failed: 0 };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
for (const result of results) {
|
|
366
|
+
if (result.skipped) continue;
|
|
367
|
+
|
|
368
|
+
for (let i = 0; i < result.publishResults.length; i++) {
|
|
369
|
+
const pubResult = result.publishResults[i];
|
|
370
|
+
const regName = registries[i].registry.name;
|
|
371
|
+
|
|
372
|
+
if (pubResult.success) {
|
|
373
|
+
successCount++;
|
|
374
|
+
registryStats[regName].success++;
|
|
375
|
+
} else {
|
|
376
|
+
failedCount++;
|
|
377
|
+
registryStats[regName].failed++;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const total = successCount + failedCount;
|
|
383
|
+
|
|
384
|
+
let content = chalk.bold.white('\n发布完成\n\n');
|
|
385
|
+
content += `总计: ${chalk.bold(total)} 次发布\n`;
|
|
386
|
+
content += ` ${chalk.green('成功')}: ${chalk.bold.green(successCount)}\n`;
|
|
387
|
+
content += ` ${chalk.red('失败')}: ${chalk.bold.red(failedCount)}\n`;
|
|
388
|
+
|
|
389
|
+
content += '\n分源统计:\n';
|
|
390
|
+
for (const [name, stats] of Object.entries(registryStats)) {
|
|
391
|
+
const icon = stats.failed === 0 ? chalk.green('✓') : chalk.yellow('⚠');
|
|
392
|
+
content += ` ${icon} ${name}: ${stats.success}/${stats.success + stats.failed}`;
|
|
393
|
+
if (stats.failed > 0) {
|
|
394
|
+
content += ` (${chalk.red(stats.failed)} 失败)`;
|
|
395
|
+
}
|
|
396
|
+
content += '\n';
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// 失败详情
|
|
400
|
+
if (failedCount > 0) {
|
|
401
|
+
content += '\n' + chalk.bold.red('失败详情:\n');
|
|
402
|
+
|
|
403
|
+
for (const result of results) {
|
|
404
|
+
if (result.skipped) continue;
|
|
405
|
+
|
|
406
|
+
for (let i = 0; i < result.publishResults.length; i++) {
|
|
407
|
+
const pubResult = result.publishResults[i];
|
|
408
|
+
if (!pubResult.success) {
|
|
409
|
+
content += `\n${chalk.red(result.package)} → ${registries[i].registry.name}\n`;
|
|
410
|
+
content += ` ${chalk.red(pubResult.error)}\n`;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
content += '\n' + chalk.dim('按任意键退出...');
|
|
417
|
+
renderer.render(content);
|
|
418
|
+
|
|
419
|
+
await waitForAnyKey();
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* 运行发布流程
|
|
424
|
+
*/
|
|
425
|
+
async function runPublishFlow(targetPath, options) {
|
|
426
|
+
return withScreenSession(async (renderer) => {
|
|
427
|
+
// 1. 扫描预览页
|
|
428
|
+
const scanResult = await showScanPreview(renderer, targetPath);
|
|
429
|
+
|
|
430
|
+
// 2. 源和 Token 选择页
|
|
431
|
+
const registries = await showRegistrySelection(renderer, targetPath);
|
|
432
|
+
|
|
433
|
+
if (registries.length === 0) {
|
|
434
|
+
renderer.render(chalk.red('\n未选择任何发布源\n按任意键退出...'));
|
|
435
|
+
await waitForAnyKey();
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// 3. 版本配置页
|
|
440
|
+
const packages = await configurePackages(renderer, scanResult.packages, registries);
|
|
441
|
+
|
|
442
|
+
if (packages.length === 0) {
|
|
443
|
+
renderer.render(chalk.yellow('\n未选择任何包\n按任意键退出...'));
|
|
444
|
+
await waitForAnyKey();
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// 4. 确认页
|
|
449
|
+
const confirmed = await showPublishConfirmation(renderer, packages, registries);
|
|
450
|
+
if (!confirmed) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// 5. 执行页
|
|
455
|
+
const results = await showPublishProgress(renderer, packages, registries, options);
|
|
456
|
+
|
|
457
|
+
// 6. 结果页
|
|
458
|
+
await showPublishResults(renderer, results, registries);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
module.exports = {
|
|
463
|
+
runPublishFlow
|
|
464
|
+
};
|