@infly/libs 2.0.49 → 2.0.51
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 +45 -5
- package/adapters/vue2/project-preview.js +152 -152
- package/{tools → adapters/webpack}/auto-export.js +56 -55
- package/bin/cli.js +1 -1
- package/build/build-dist/index.js +866 -866
- package/build/docker/compose-command.js +74 -74
- package/build/docker/release-docker.js +70 -6
- package/build/index.js +34 -0
- package/{tools/file-process.js → build/project-files.js} +55 -52
- package/{tools → cli}/progress.js +67 -63
- package/{tools → cli}/project-command.js +206 -198
- package/{tools → cli}/select-option.js +61 -60
- package/{tools → core/async}/concurrency.js +31 -30
- package/package.json +19 -5
- package/release/npm-plan.js +51 -0
- package/script/git-automation/submodule-utils.js +22 -18
- package/{tools/workspace-config.js → workspace/config.js} +146 -145
- package/workspace/packages.js +91 -0
- package/adapters/vue2/vite/index.js +0 -95
- package/adapters/vue2/vite/jest.config.js +0 -11
- package/tools/format.js +0 -31
- package/tools/project-preview.js +0 -1
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
const fs = require("node:fs");
|
|
2
|
-
const path = require("node:path");
|
|
3
|
-
|
|
4
|
-
const {
|
|
5
|
-
EXIT_SELECTION,
|
|
6
|
-
selectOption: defaultSelectOption,
|
|
7
|
-
} = require("../../
|
|
8
|
-
const { composeRelease: defaultComposeRelease } = require("./compose-release");
|
|
9
|
-
|
|
10
|
-
function loadTargetConfig(configPath) {
|
|
11
|
-
const resolvedPath = path.resolve(configPath);
|
|
12
|
-
delete require.cache[require.resolve(resolvedPath)];
|
|
13
|
-
return {
|
|
14
|
-
config: require(resolvedPath),
|
|
15
|
-
configDir: path.dirname(resolvedPath),
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function loadPackageTargetConfig(cwd = process.cwd()) {
|
|
20
|
-
const packagePath = path.resolve(cwd, "package.json");
|
|
21
|
-
if (!fs.existsSync(packagePath)) {
|
|
22
|
-
throw new Error(`Package config not found: ${packagePath}`);
|
|
23
|
-
}
|
|
24
|
-
delete require.cache[require.resolve(packagePath)];
|
|
25
|
-
const packageJson = require(packagePath);
|
|
26
|
-
return {
|
|
27
|
-
config: packageJson.infly?.docker,
|
|
28
|
-
configDir: path.dirname(packagePath),
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function runComposeCommand(options = {}, dependencies = {}) {
|
|
33
|
-
const composeRelease = dependencies.composeRelease || defaultComposeRelease;
|
|
34
|
-
const usesDirectConfig = !options.config
|
|
35
|
-
&& !options.configObject
|
|
36
|
-
&& (options.projectDir || options.file);
|
|
37
|
-
if (usesDirectConfig) return composeRelease(options);
|
|
38
|
-
|
|
39
|
-
const loaded = options.config
|
|
40
|
-
? loadTargetConfig(options.config)
|
|
41
|
-
: options.configObject
|
|
42
|
-
? { config: options.configObject, configDir: options.configDir || process.cwd() }
|
|
43
|
-
: loadPackageTargetConfig(options.cwd || process.cwd());
|
|
44
|
-
const targets = loaded.config?.targets;
|
|
45
|
-
if (!targets || Object.keys(targets).length === 0) {
|
|
46
|
-
throw new Error("Docker target config must define at least one target.");
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const selectOption = dependencies.selectOption || defaultSelectOption;
|
|
50
|
-
const log = dependencies.log || console.log;
|
|
51
|
-
const targetName = options.target || await selectOption(
|
|
52
|
-
"请选择 Docker 发布目标",
|
|
53
|
-
Object.entries(targets).map(([value, target]) => ({
|
|
54
|
-
value,
|
|
55
|
-
label: target.label || value,
|
|
56
|
-
})),
|
|
57
|
-
);
|
|
58
|
-
if (targetName === EXIT_SELECTION) {
|
|
59
|
-
log("已退出,未执行任何操作。");
|
|
60
|
-
return { cancelled: true };
|
|
61
|
-
}
|
|
62
|
-
const target = targets[targetName];
|
|
63
|
-
if (!target) throw new Error(`Unknown Docker target: ${targetName}`);
|
|
64
|
-
|
|
65
|
-
return composeRelease({
|
|
66
|
-
projectDir: loaded.configDir,
|
|
67
|
-
file: target.file,
|
|
68
|
-
bump: options.bump || "patch",
|
|
69
|
-
push: Boolean(options.push),
|
|
70
|
-
dryRun: Boolean(options.dryRun),
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
module.exports = { loadPackageTargetConfig, loadTargetConfig, runComposeCommand };
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
EXIT_SELECTION,
|
|
6
|
+
selectOption: defaultSelectOption,
|
|
7
|
+
} = require("../../cli/select-option");
|
|
8
|
+
const { composeRelease: defaultComposeRelease } = require("./compose-release");
|
|
9
|
+
|
|
10
|
+
function loadTargetConfig(configPath) {
|
|
11
|
+
const resolvedPath = path.resolve(configPath);
|
|
12
|
+
delete require.cache[require.resolve(resolvedPath)];
|
|
13
|
+
return {
|
|
14
|
+
config: require(resolvedPath),
|
|
15
|
+
configDir: path.dirname(resolvedPath),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function loadPackageTargetConfig(cwd = process.cwd()) {
|
|
20
|
+
const packagePath = path.resolve(cwd, "package.json");
|
|
21
|
+
if (!fs.existsSync(packagePath)) {
|
|
22
|
+
throw new Error(`Package config not found: ${packagePath}`);
|
|
23
|
+
}
|
|
24
|
+
delete require.cache[require.resolve(packagePath)];
|
|
25
|
+
const packageJson = require(packagePath);
|
|
26
|
+
return {
|
|
27
|
+
config: packageJson.infly?.docker,
|
|
28
|
+
configDir: path.dirname(packagePath),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function runComposeCommand(options = {}, dependencies = {}) {
|
|
33
|
+
const composeRelease = dependencies.composeRelease || defaultComposeRelease;
|
|
34
|
+
const usesDirectConfig = !options.config
|
|
35
|
+
&& !options.configObject
|
|
36
|
+
&& (options.projectDir || options.file);
|
|
37
|
+
if (usesDirectConfig) return composeRelease(options);
|
|
38
|
+
|
|
39
|
+
const loaded = options.config
|
|
40
|
+
? loadTargetConfig(options.config)
|
|
41
|
+
: options.configObject
|
|
42
|
+
? { config: options.configObject, configDir: options.configDir || process.cwd() }
|
|
43
|
+
: loadPackageTargetConfig(options.cwd || process.cwd());
|
|
44
|
+
const targets = loaded.config?.targets;
|
|
45
|
+
if (!targets || Object.keys(targets).length === 0) {
|
|
46
|
+
throw new Error("Docker target config must define at least one target.");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const selectOption = dependencies.selectOption || defaultSelectOption;
|
|
50
|
+
const log = dependencies.log || console.log;
|
|
51
|
+
const targetName = options.target || await selectOption(
|
|
52
|
+
"请选择 Docker 发布目标",
|
|
53
|
+
Object.entries(targets).map(([value, target]) => ({
|
|
54
|
+
value,
|
|
55
|
+
label: target.label || value,
|
|
56
|
+
})),
|
|
57
|
+
);
|
|
58
|
+
if (targetName === EXIT_SELECTION) {
|
|
59
|
+
log("已退出,未执行任何操作。");
|
|
60
|
+
return { cancelled: true };
|
|
61
|
+
}
|
|
62
|
+
const target = targets[targetName];
|
|
63
|
+
if (!target) throw new Error(`Unknown Docker target: ${targetName}`);
|
|
64
|
+
|
|
65
|
+
return composeRelease({
|
|
66
|
+
projectDir: loaded.configDir,
|
|
67
|
+
file: target.file,
|
|
68
|
+
bump: options.bump || "patch",
|
|
69
|
+
push: Boolean(options.push),
|
|
70
|
+
dryRun: Boolean(options.dryRun),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { loadPackageTargetConfig, loadTargetConfig, runComposeCommand };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const fs = require("node:fs");
|
|
2
2
|
const path = require("node:path");
|
|
3
|
-
const { execFileSync, spawnSync } = require("node:child_process");
|
|
3
|
+
const { execFileSync, spawn, spawnSync } = require("node:child_process");
|
|
4
4
|
const dotenv = require("dotenv");
|
|
5
5
|
const { dockerBuildPush } = require("./docker-build-push");
|
|
6
6
|
|
|
@@ -45,6 +45,25 @@ function run(command, args, label, cwd) {
|
|
|
45
45
|
if (result.status !== 0) throw new Error(`${label}失败,退出码: ${result.status}`);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function runAsync(command, args, label, cwd) {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
console.log(`\n>>> ${label}`);
|
|
51
|
+
const child = spawn(command, args, {
|
|
52
|
+
cwd,
|
|
53
|
+
env: process.env,
|
|
54
|
+
stdio: "inherit",
|
|
55
|
+
shell: false,
|
|
56
|
+
});
|
|
57
|
+
child.once("error", (error) => {
|
|
58
|
+
reject(new Error(`${label}无法启动: ${error.message}`));
|
|
59
|
+
});
|
|
60
|
+
child.once("close", (code) => {
|
|
61
|
+
if (code === 0) resolve();
|
|
62
|
+
else reject(new Error(`${label}失败,退出码: ${code}`));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
48
67
|
function runShellCommand(command, label, cwd) {
|
|
49
68
|
if (/[\r\n&|<>^]/.test(command)) {
|
|
50
69
|
throw new Error(`${label}包含不安全的 Windows shell 字符`);
|
|
@@ -52,16 +71,59 @@ function runShellCommand(command, label, cwd) {
|
|
|
52
71
|
run(process.env.ComSpec || "cmd.exe", ["/d", "/s", "/c", command], label, cwd);
|
|
53
72
|
}
|
|
54
73
|
|
|
55
|
-
function
|
|
74
|
+
function runShellCommandAsync(command, label, cwd) {
|
|
75
|
+
if (/[\r\n&|<>^]/.test(command)) {
|
|
76
|
+
throw new Error(`${label}包含不安全的 Windows shell 字符`);
|
|
77
|
+
}
|
|
78
|
+
return runAsync(
|
|
79
|
+
process.env.ComSpec || "cmd.exe",
|
|
80
|
+
["/d", "/s", "/c", command],
|
|
81
|
+
label,
|
|
82
|
+
cwd
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function buildPnpmScriptSpawn(scriptName) {
|
|
56
87
|
if (!SAFE_SCRIPT_NAME.test(scriptName)) {
|
|
57
88
|
throw new Error(`dockerRelease.checks 包含非法脚本名: ${scriptName}`);
|
|
58
89
|
}
|
|
59
90
|
const label = `项目检查: pnpm run ${scriptName}`;
|
|
60
91
|
if (process.platform === "win32") {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
92
|
+
const command = `pnpm run ${scriptName}`;
|
|
93
|
+
if (/[\r\n&|<>^]/.test(command)) {
|
|
94
|
+
throw new Error(`${label}包含不安全的 Windows shell 字符`);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
command: process.env.ComSpec || "cmd.exe",
|
|
98
|
+
args: ["/d", "/s", "/c", command],
|
|
99
|
+
label,
|
|
100
|
+
};
|
|
64
101
|
}
|
|
102
|
+
return { command: "pnpm", args: ["run", scriptName], label };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function runPnpmScript(scriptName, cwd) {
|
|
106
|
+
const { command, args, label } = buildPnpmScriptSpawn(scriptName);
|
|
107
|
+
run(command, args, label, cwd);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function runPnpmScriptAsync(scriptName, cwd) {
|
|
111
|
+
const { command, args, label } = buildPnpmScriptSpawn(scriptName);
|
|
112
|
+
return runAsync(command, args, label, cwd);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function runProjectChecks(scriptNames, cwd) {
|
|
116
|
+
const results = await Promise.allSettled(
|
|
117
|
+
scriptNames.map((scriptName) => runPnpmScriptAsync(scriptName, cwd))
|
|
118
|
+
);
|
|
119
|
+
const failures = results
|
|
120
|
+
.map((result, index) => ({ result, scriptName: scriptNames[index] }))
|
|
121
|
+
.filter(({ result }) => result.status === "rejected");
|
|
122
|
+
if (failures.length === 0) return;
|
|
123
|
+
const lines = failures.map(({ result }) => ` - ${result.reason.message}`);
|
|
124
|
+
throw new Error(
|
|
125
|
+
`项目检查未全部通过(失败 ${failures.length}/${scriptNames.length}):\n${lines.join("\n")}`
|
|
126
|
+
);
|
|
65
127
|
}
|
|
66
128
|
|
|
67
129
|
function runSonarScanner(cwd, projectVersion) {
|
|
@@ -168,7 +230,7 @@ async function releaseDocker(options = {}) {
|
|
|
168
230
|
console.warn(`紧急原因: ${emergency.emergencyReason}`);
|
|
169
231
|
console.warn(`目标 commit: ${projectVersion.fullCommit}`);
|
|
170
232
|
} else {
|
|
171
|
-
|
|
233
|
+
await runProjectChecks(config.checks, projectRoot);
|
|
172
234
|
}
|
|
173
235
|
|
|
174
236
|
if (!emergency.emergency && config.coverageReport) {
|
|
@@ -190,10 +252,12 @@ async function releaseDocker(options = {}) {
|
|
|
190
252
|
|
|
191
253
|
module.exports = {
|
|
192
254
|
assertCleanProject,
|
|
255
|
+
buildPnpmScriptSpawn,
|
|
193
256
|
loadLocalReleaseEnv,
|
|
194
257
|
normalizeEmergencyOptions,
|
|
195
258
|
readReleaseConfig,
|
|
196
259
|
releaseDocker,
|
|
197
260
|
resolveInsideProject,
|
|
198
261
|
resolveProjectVersion,
|
|
262
|
+
runProjectChecks,
|
|
199
263
|
};
|
package/build/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const { run, spawnBackground } = require("./build-utils/command.js");
|
|
2
|
+
const { createBuildContext, parseBuildContext, ENV } = require("./build-utils/env.js");
|
|
3
|
+
const {
|
|
4
|
+
getTargetBranch,
|
|
5
|
+
updatePackageSources,
|
|
6
|
+
updateAppAndDistSources,
|
|
7
|
+
updateAllSources,
|
|
8
|
+
getDistRepoTasks,
|
|
9
|
+
printDryRunSourceUpdatePlan,
|
|
10
|
+
} = require("./build-utils/git.js");
|
|
11
|
+
const { postBatchWebhook } = require("./build-utils/webhook.js");
|
|
12
|
+
const { startBatchPreviews } = require("./build-utils/preview.js");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Stable public entry for repository-level build orchestration.
|
|
16
|
+
*
|
|
17
|
+
* Keep application selection and build ordering in the caller. This module only
|
|
18
|
+
* exposes framework-neutral lifecycle primitives owned by @infly/libs.
|
|
19
|
+
*/
|
|
20
|
+
module.exports = {
|
|
21
|
+
ENV,
|
|
22
|
+
createBuildContext,
|
|
23
|
+
getDistRepoTasks,
|
|
24
|
+
getTargetBranch,
|
|
25
|
+
parseBuildContext,
|
|
26
|
+
postBatchWebhook,
|
|
27
|
+
printDryRunSourceUpdatePlan,
|
|
28
|
+
run,
|
|
29
|
+
spawnBackground,
|
|
30
|
+
startBatchPreviews,
|
|
31
|
+
updateAllSources,
|
|
32
|
+
updateAppAndDistSources,
|
|
33
|
+
updatePackageSources,
|
|
34
|
+
};
|
|
@@ -1,52 +1,55 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
pkg[updateKey][item.key]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
// 兼容旧 tools/file-process.js 的 __dirname 路径语义。
|
|
5
|
+
const legacyToolsDir = path.resolve(__dirname, "../tools");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 覆盖写入构建脚本
|
|
9
|
+
* @param {String} filePath - 覆盖文件路径
|
|
10
|
+
* @param {Array | Object} scriptsList - 写入脚本配置
|
|
11
|
+
* @param {String} 更新字段值
|
|
12
|
+
*/
|
|
13
|
+
function scriptWrite(filePath, scriptsList, updateKey = "scripts") {
|
|
14
|
+
const pkg = JSON.parse(fs.readFileSync(filePath));
|
|
15
|
+
pkg[updateKey] = pkg[updateKey] || {};
|
|
16
|
+
if (Array.isArray(scriptsList)) {
|
|
17
|
+
scriptsList.forEach(item => {
|
|
18
|
+
if (pkg[updateKey][item.key]) {
|
|
19
|
+
pkg[updateKey][`origin:${item.key}`] = pkg[updateKey][item.key];
|
|
20
|
+
}
|
|
21
|
+
pkg[updateKey][item.key] = item.value;
|
|
22
|
+
});
|
|
23
|
+
} else if (typeof scriptsList === "object") {
|
|
24
|
+
pkg[updateKey] = scriptsList;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fs.writeFileSync(filePath, JSON.stringify(pkg, null, 2));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 获取文件绝对路径
|
|
32
|
+
* @param {String} filePath - 文件路径
|
|
33
|
+
* @returns
|
|
34
|
+
*/
|
|
35
|
+
function targetProjectFileResolve(filePath) {
|
|
36
|
+
// 初始脚本自动执行路径会自动读取node_modules
|
|
37
|
+
const projectRoot = process.cwd();
|
|
38
|
+
const absoluteProjectRoot = projectRoot.replace(/\\node_modules.*$/g, "");
|
|
39
|
+
return path.resolve(absoluteProjectRoot, filePath);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 获取文件绝对路径
|
|
44
|
+
* @param {String} filePath - 文件路径
|
|
45
|
+
* @returns
|
|
46
|
+
*/
|
|
47
|
+
function currentProjectFileResolve(filePath) {
|
|
48
|
+
return path.join(legacyToolsDir, filePath);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
scriptWrite,
|
|
53
|
+
targetProjectFileResolve,
|
|
54
|
+
currentProjectFileResolve
|
|
55
|
+
};
|
|
@@ -1,63 +1,67 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
1
|
+
// CLI 与终端流程的进度呈现能力。
|
|
2
|
+
function formatProgressLine({ label, completed, total, path = "" }) {
|
|
3
|
+
const safeTotal = Math.max(total, 1);
|
|
4
|
+
const percentage = total === 0 ? 100 : Math.round((completed / safeTotal) * 100);
|
|
5
|
+
const filled = Math.round(percentage / 5);
|
|
6
|
+
const bar = `${"█".repeat(filled)}${"░".repeat(20 - filled)}`;
|
|
7
|
+
return `${label} [${bar}] ${percentage}% (${completed}/${total})${path ? ` ${path}` : ""}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function createProgressReporter(options = {}) {
|
|
11
|
+
const stream = options.stream || process.stdout;
|
|
12
|
+
const quiet = Boolean(options.quiet);
|
|
13
|
+
const defaultLabels = {
|
|
14
|
+
status: "检查状态",
|
|
15
|
+
deinit: "停用模块",
|
|
16
|
+
init: "模块加载",
|
|
17
|
+
};
|
|
18
|
+
const labels = { ...defaultLabels, ...options.labels };
|
|
19
|
+
return (event) => {
|
|
20
|
+
if (quiet || !labels[event.phase] || event.total === 0) return;
|
|
21
|
+
const line = formatProgressLine({
|
|
22
|
+
...event,
|
|
23
|
+
label: labels[event.phase],
|
|
24
|
+
});
|
|
25
|
+
if (stream.isTTY) {
|
|
26
|
+
stream.write(`\r${line}\x1b[K${event.done || event.completed >= event.total ? "\n" : ""}`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (event.completed === 0 || event.done || event.completed >= event.total) stream.write(`${line}\n`);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function formatSwitchSummary(name, totalMs, dependencyResult, timings) {
|
|
34
|
+
const seconds = (totalMs / 1000).toFixed(1);
|
|
35
|
+
const dependencyLabel = dependencyResult.installed ? ",依赖已更新" : "";
|
|
36
|
+
const skipWarning = dependencyResult.reason === "skipped"
|
|
37
|
+
? "\n⚠️ 已跳过依赖安装,后续 dev 缺依赖时请手动 pnpm install"
|
|
38
|
+
: "";
|
|
39
|
+
const summary = `\n✅ ${name}(${seconds}s${dependencyLabel})${skipWarning}`;
|
|
40
|
+
if (!timings) return summary;
|
|
41
|
+
const labels = {
|
|
42
|
+
analyze: "分析",
|
|
43
|
+
precheck: "预检",
|
|
44
|
+
status: "状态",
|
|
45
|
+
deinit: "停用",
|
|
46
|
+
init: "加载",
|
|
47
|
+
install: "依赖",
|
|
48
|
+
};
|
|
49
|
+
const phases = Object.entries(labels)
|
|
50
|
+
.filter(([phase]) => Number.isFinite(timings[phase]))
|
|
51
|
+
.map(([phase, label]) => `${label} ${(timings[phase] / 1000).toFixed(1)}s`);
|
|
52
|
+
return phases.length > 0 ? `${summary}\n 阶段: ${phases.join(" | ")}` : summary;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function formatSlowStatusDetails(details, thresholdMs = 2000) {
|
|
56
|
+
return details
|
|
57
|
+
.filter((detail) => detail.durationMs > thresholdMs)
|
|
58
|
+
.sort((left, right) => right.durationMs - left.durationMs)
|
|
59
|
+
.map((detail) => `${detail.path} ${detail.durationMs}ms`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
createProgressReporter,
|
|
64
|
+
formatProgressLine,
|
|
65
|
+
formatSlowStatusDetails,
|
|
66
|
+
formatSwitchSummary,
|
|
67
|
+
};
|