@infly/libs 2.0.48 → 2.0.50

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.
@@ -1,198 +1,199 @@
1
- const { execFileSync } = require("node:child_process");
2
- const fs = require("node:fs");
3
- const path = require("node:path");
4
-
5
- const {
6
- EXIT_SELECTION,
7
- selectOption: defaultSelectOption,
8
- } = require("./select-option");
9
-
10
- function loadConfig(cwd = process.cwd()) {
11
- const packagePath = path.resolve(cwd, "package.json");
12
- if (!fs.existsSync(packagePath)) {
13
- throw new Error(`Project package not found: ${packagePath}.`);
14
- }
15
- delete require.cache[require.resolve(packagePath)];
16
- const inflyConfig = require(packagePath).infly;
17
- if (!inflyConfig?.dev && !inflyConfig?.build) {
18
- throw new Error(`Project config is missing in ${packagePath}. Add infly.dev or infly.build.`);
19
- }
20
- return {
21
- config: {
22
- targets: inflyConfig.targets,
23
- dev: inflyConfig.dev,
24
- build: inflyConfig.build,
25
- },
26
- configDir: path.dirname(packagePath),
27
- };
28
- }
29
-
30
- function normalizeTargets(targets) {
31
- if (Array.isArray(targets)) {
32
- return Object.fromEntries(targets.map((target) => [target, {}]));
33
- }
34
- if (targets && typeof targets === "object") return targets;
35
- throw new Error("Project targets must be a non-empty array or object.");
36
- }
37
-
38
- function normalizeEnvironments(environments) {
39
- if (Array.isArray(environments)) {
40
- return Object.fromEntries(environments.map((entry) => {
41
- const separatorIndex = entry.indexOf(":");
42
- const environment = separatorIndex < 0 ? entry : entry.slice(0, separatorIndex);
43
- const mode = separatorIndex < 0 ? entry : entry.slice(separatorIndex + 1);
44
- if (!environment || !mode) {
45
- throw new Error(`Invalid build environment mapping: ${entry}`);
46
- }
47
- return [environment, { mode, args: ["--mode", mode] }];
48
- }));
49
- }
50
- if (environments && typeof environments === "object") return environments;
51
- throw new Error("Build environments must be an array or object.");
52
- }
53
-
54
- function resolveBuildPlatforms(targets, buildConfig, targetName) {
55
- const normalizedTargets = normalizeTargets(targets);
56
- if (normalizedTargets[targetName]) return [targetName];
57
-
58
- const preset = buildConfig.presets?.[targetName];
59
- if (!preset) throw new Error(`Unknown build target: ${targetName}`);
60
-
61
- return preset.targets.map((name) => {
62
- if (!normalizedTargets[name]) {
63
- throw new Error(`Unknown build target in preset ${targetName}: ${name}`);
64
- }
65
- return name;
66
- });
67
- }
68
-
69
- function commandParts(command) {
70
- const parts = typeof command === "string"
71
- ? command.trim().split(/\s+/)
72
- : command;
73
- if (!Array.isArray(parts) || parts.length === 0 || !parts[0]) {
74
- throw new Error("Command configuration must be a non-empty string or array.");
75
- }
76
- return { command: parts[0], args: parts.slice(1) };
77
- }
78
-
79
- function defaultRunCommand(command, args, options) {
80
- execFileSync(command, args, {
81
- cwd: options.cwd,
82
- env: options.env,
83
- stdio: "inherit",
84
- });
85
- }
86
-
87
- function printDryRun(command, args, env) {
88
- const platform = env.VUE_APP_PLATFORM ? `VUE_APP_PLATFORM=${env.VUE_APP_PLATFORM} ` : "";
89
- console.log(`[dry-run] ${platform}${command} ${args.join(" ")}`.trim());
90
- }
91
-
92
- async function choose(selectOption, message, entries) {
93
- return selectOption(
94
- message,
95
- entries.map(([value, item]) => ({ value, label: item.label || value })),
96
- );
97
- }
98
-
99
- async function runProjectCommand(options = {}, dependencies = {}) {
100
- if (typeof options.config === "string") {
101
- throw new Error("--config is not supported for project commands; use package.json infly config.");
102
- }
103
-
104
- const configObject = options.configObject
105
- || (options.config && typeof options.config === "object" ? options.config : undefined);
106
- const loaded = configObject
107
- ? { config: configObject, configDir: options.configDir }
108
- : loadConfig(options.cwd || process.cwd());
109
- const config = loaded.config;
110
- const configDir = options.configDir || loaded.configDir || process.cwd();
111
- if (!config) throw new Error("Project command config is required.");
112
- const targets = normalizeTargets(config.targets);
113
-
114
- const selectOption = dependencies.selectOption || defaultSelectOption;
115
- const runCommand = dependencies.runCommand || defaultRunCommand;
116
- const log = dependencies.log || console.log;
117
- const exitIfSelected = (selection) => {
118
- if (selection !== EXIT_SELECTION) return false;
119
- log("已退出,未执行任何操作。");
120
- return true;
121
- };
122
- const action = options.action || await selectOption("请选择操作", [
123
- { value: "dev", label: "启动开发环境" },
124
- { value: "build", label: "构建项目" },
125
- ]);
126
- if (exitIfSelected(action)) return { cancelled: true };
127
-
128
- if (action === "dev") {
129
- const devConfig = config.dev;
130
- if (!devConfig) throw new Error("Dev configuration is missing.");
131
- const targetName = options.target || await choose(
132
- selectOption,
133
- "请选择目标平台",
134
- Object.entries(targets),
135
- );
136
- if (exitIfSelected(targetName)) return { cancelled: true };
137
- const target = targets[targetName];
138
- if (!target) throw new Error(`Unknown dev target: ${targetName}`);
139
- const parts = commandParts(devConfig.command);
140
- const env = { ...process.env, VUE_APP_PLATFORM: targetName };
141
- if (options.dryRun) printDryRun(parts.command, parts.args, env);
142
- else runCommand(parts.command, parts.args, { cwd: configDir, env });
143
- return { action, target: targetName };
144
- }
145
-
146
- if (action !== "build") throw new Error(`Unknown project action: ${action}`);
147
- const buildConfig = config.build;
148
- if (!buildConfig) throw new Error("Build configuration is missing.");
149
- const environments = normalizeEnvironments(buildConfig.environments);
150
- const environment = options.env || await choose(
151
- selectOption,
152
- "请选择构建环境",
153
- Object.entries(environments),
154
- );
155
- if (exitIfSelected(environment)) return { cancelled: true };
156
- const environmentConfig = environments[environment];
157
- if (!environmentConfig) throw new Error(`Unknown build environment: ${environment}`);
158
- const targetEntries = [
159
- ...Object.entries(targets),
160
- ...Object.entries(buildConfig.presets || {}),
161
- ];
162
- const targetName = options.target || await choose(
163
- selectOption,
164
- "请选择目标平台",
165
- targetEntries,
166
- );
167
- if (exitIfSelected(targetName)) return { cancelled: true };
168
- const platforms = resolveBuildPlatforms(targets, buildConfig, targetName);
169
-
170
- for (const platform of platforms) {
171
- const env = { ...process.env, VUE_APP_PLATFORM: platform };
172
- const lifecycleArgs = environmentConfig.mode ? ["--mode", environmentConfig.mode] : [];
173
- // staging 构建禁用 Vue CLI 5 默认的 modern mode 双构建(测试环境无需 legacy 兼容)
174
- const buildArgs = environment === "stage"
175
- ? [...(environmentConfig.args || []), "--no-module"]
176
- : (environmentConfig.args || []);
177
- const steps = [
178
- { command: buildConfig.before, extraArgs: lifecycleArgs },
179
- { command: buildConfig.command, extraArgs: buildArgs },
180
- { command: buildConfig.after, extraArgs: lifecycleArgs },
181
- ];
182
-
183
- for (const step of steps) {
184
- const parts = commandParts(step.command);
185
- const args = [...parts.args, ...step.extraArgs];
186
- if (options.dryRun) printDryRun(parts.command, args, env);
187
- else runCommand(parts.command, args, { cwd: configDir, env });
188
- }
189
- }
190
-
191
- return { action, environment, target: targetName, platforms };
192
- }
193
-
194
- module.exports = {
195
- loadConfig,
196
- resolveBuildPlatforms,
197
- runProjectCommand,
198
- };
1
+ // 配置驱动的公共项目命令编排。
2
+ const { execFileSync } = require("node:child_process");
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+
6
+ const {
7
+ EXIT_SELECTION,
8
+ selectOption: defaultSelectOption,
9
+ } = require("./select-option");
10
+
11
+ function loadConfig(cwd = process.cwd()) {
12
+ const packagePath = path.resolve(cwd, "package.json");
13
+ if (!fs.existsSync(packagePath)) {
14
+ throw new Error(`Project package not found: ${packagePath}.`);
15
+ }
16
+ delete require.cache[require.resolve(packagePath)];
17
+ const inflyConfig = require(packagePath).infly;
18
+ if (!inflyConfig?.dev && !inflyConfig?.build) {
19
+ throw new Error(`Project config is missing in ${packagePath}. Add infly.dev or infly.build.`);
20
+ }
21
+ return {
22
+ config: {
23
+ targets: inflyConfig.targets,
24
+ dev: inflyConfig.dev,
25
+ build: inflyConfig.build,
26
+ },
27
+ configDir: path.dirname(packagePath),
28
+ };
29
+ }
30
+
31
+ function normalizeTargets(targets) {
32
+ if (Array.isArray(targets)) {
33
+ return Object.fromEntries(targets.map((target) => [target, {}]));
34
+ }
35
+ if (targets && typeof targets === "object") return targets;
36
+ throw new Error("Project targets must be a non-empty array or object.");
37
+ }
38
+
39
+ function normalizeEnvironments(environments) {
40
+ if (Array.isArray(environments)) {
41
+ return Object.fromEntries(environments.map((entry) => {
42
+ const separatorIndex = entry.indexOf(":");
43
+ const environment = separatorIndex < 0 ? entry : entry.slice(0, separatorIndex);
44
+ const mode = separatorIndex < 0 ? entry : entry.slice(separatorIndex + 1);
45
+ if (!environment || !mode) {
46
+ throw new Error(`Invalid build environment mapping: ${entry}`);
47
+ }
48
+ return [environment, { mode, args: ["--mode", mode] }];
49
+ }));
50
+ }
51
+ if (environments && typeof environments === "object") return environments;
52
+ throw new Error("Build environments must be an array or object.");
53
+ }
54
+
55
+ function resolveBuildPlatforms(targets, buildConfig, targetName) {
56
+ const normalizedTargets = normalizeTargets(targets);
57
+ if (normalizedTargets[targetName]) return [targetName];
58
+
59
+ const preset = buildConfig.presets?.[targetName];
60
+ if (!preset) throw new Error(`Unknown build target: ${targetName}`);
61
+
62
+ return preset.targets.map((name) => {
63
+ if (!normalizedTargets[name]) {
64
+ throw new Error(`Unknown build target in preset ${targetName}: ${name}`);
65
+ }
66
+ return name;
67
+ });
68
+ }
69
+
70
+ function commandParts(command) {
71
+ const parts = typeof command === "string"
72
+ ? command.trim().split(/\s+/)
73
+ : command;
74
+ if (!Array.isArray(parts) || parts.length === 0 || !parts[0]) {
75
+ throw new Error("Command configuration must be a non-empty string or array.");
76
+ }
77
+ return { command: parts[0], args: parts.slice(1) };
78
+ }
79
+
80
+ function defaultRunCommand(command, args, options) {
81
+ execFileSync(command, args, {
82
+ cwd: options.cwd,
83
+ env: options.env,
84
+ stdio: "inherit",
85
+ });
86
+ }
87
+
88
+ function printDryRun(command, args, env) {
89
+ const platform = env.VUE_APP_PLATFORM ? `VUE_APP_PLATFORM=${env.VUE_APP_PLATFORM} ` : "";
90
+ console.log(`[dry-run] ${platform}${command} ${args.join(" ")}`.trim());
91
+ }
92
+
93
+ async function choose(selectOption, message, entries) {
94
+ return selectOption(
95
+ message,
96
+ entries.map(([value, item]) => ({ value, label: item.label || value })),
97
+ );
98
+ }
99
+
100
+ async function runProjectCommand(options = {}, dependencies = {}) {
101
+ if (typeof options.config === "string") {
102
+ throw new Error("--config is not supported for project commands; use package.json infly config.");
103
+ }
104
+
105
+ const configObject = options.configObject
106
+ || (options.config && typeof options.config === "object" ? options.config : undefined);
107
+ const loaded = configObject
108
+ ? { config: configObject, configDir: options.configDir }
109
+ : loadConfig(options.cwd || process.cwd());
110
+ const config = loaded.config;
111
+ const configDir = options.configDir || loaded.configDir || process.cwd();
112
+ if (!config) throw new Error("Project command config is required.");
113
+ const targets = normalizeTargets(config.targets);
114
+
115
+ const selectOption = dependencies.selectOption || defaultSelectOption;
116
+ const runCommand = dependencies.runCommand || defaultRunCommand;
117
+ const log = dependencies.log || console.log;
118
+ const exitIfSelected = (selection) => {
119
+ if (selection !== EXIT_SELECTION) return false;
120
+ log("已退出,未执行任何操作。");
121
+ return true;
122
+ };
123
+ const action = options.action || await selectOption("请选择操作", [
124
+ { value: "dev", label: "启动开发环境" },
125
+ { value: "build", label: "构建项目" },
126
+ ]);
127
+ if (exitIfSelected(action)) return { cancelled: true };
128
+
129
+ if (action === "dev") {
130
+ const devConfig = config.dev;
131
+ if (!devConfig) throw new Error("Dev configuration is missing.");
132
+ const targetName = options.target || await choose(
133
+ selectOption,
134
+ "请选择目标平台",
135
+ Object.entries(targets),
136
+ );
137
+ if (exitIfSelected(targetName)) return { cancelled: true };
138
+ const target = targets[targetName];
139
+ if (!target) throw new Error(`Unknown dev target: ${targetName}`);
140
+ const parts = commandParts(devConfig.command);
141
+ const env = { ...process.env, VUE_APP_PLATFORM: targetName };
142
+ if (options.dryRun) printDryRun(parts.command, parts.args, env);
143
+ else runCommand(parts.command, parts.args, { cwd: configDir, env });
144
+ return { action, target: targetName };
145
+ }
146
+
147
+ if (action !== "build") throw new Error(`Unknown project action: ${action}`);
148
+ const buildConfig = config.build;
149
+ if (!buildConfig) throw new Error("Build configuration is missing.");
150
+ const environments = normalizeEnvironments(buildConfig.environments);
151
+ const environment = options.env || await choose(
152
+ selectOption,
153
+ "请选择构建环境",
154
+ Object.entries(environments),
155
+ );
156
+ if (exitIfSelected(environment)) return { cancelled: true };
157
+ const environmentConfig = environments[environment];
158
+ if (!environmentConfig) throw new Error(`Unknown build environment: ${environment}`);
159
+ const targetEntries = [
160
+ ...Object.entries(targets),
161
+ ...Object.entries(buildConfig.presets || {}),
162
+ ];
163
+ const targetName = options.target || await choose(
164
+ selectOption,
165
+ "请选择目标平台",
166
+ targetEntries,
167
+ );
168
+ if (exitIfSelected(targetName)) return { cancelled: true };
169
+ const platforms = resolveBuildPlatforms(targets, buildConfig, targetName);
170
+
171
+ for (const platform of platforms) {
172
+ const env = { ...process.env, VUE_APP_PLATFORM: platform };
173
+ const lifecycleArgs = environmentConfig.mode ? ["--mode", environmentConfig.mode] : [];
174
+ // staging 构建禁用 Vue CLI 5 默认的 modern mode 双构建(测试环境无需 legacy 兼容)
175
+ const buildArgs = environment === "stage"
176
+ ? [...(environmentConfig.args || []), "--no-module"]
177
+ : (environmentConfig.args || []);
178
+ const steps = [
179
+ { command: buildConfig.before, extraArgs: lifecycleArgs },
180
+ { command: buildConfig.command, extraArgs: buildArgs },
181
+ { command: buildConfig.after, extraArgs: lifecycleArgs },
182
+ ];
183
+
184
+ for (const step of steps) {
185
+ const parts = commandParts(step.command);
186
+ const args = [...parts.args, ...step.extraArgs];
187
+ if (options.dryRun) printDryRun(parts.command, args, env);
188
+ else runCommand(parts.command, args, { cwd: configDir, env });
189
+ }
190
+ }
191
+
192
+ return { action, environment, target: targetName, platforms };
193
+ }
194
+
195
+ module.exports = {
196
+ loadConfig,
197
+ resolveBuildPlatforms,
198
+ runProjectCommand,
199
+ };
@@ -1,60 +1,61 @@
1
- const EXIT_SELECTION = "__exit__";
2
-
3
- function isReadlineClosedError(error) {
4
- return error?.code === "ERR_USE_AFTER_CLOSE";
5
- }
6
-
7
- function makePromptCancellationSafe(prompt) {
8
- const unsafeStop = prompt.stop;
9
- if (typeof unsafeStop !== "function") return;
10
-
11
- prompt.removeListener("close", unsafeStop);
12
- const safeStop = () => {
13
- try {
14
- unsafeStop();
15
- } catch (error) {
16
- if (!isReadlineClosedError(error)) throw error;
17
- }
18
- };
19
- prompt.stop = safeStop;
20
- prompt.once("close", safeStop);
21
- }
22
-
23
- async function selectOption(message, choices, dependencies = {}) {
24
- const stdin = dependencies.stdin || process.stdin;
25
- const stdout = dependencies.stdout || process.stdout;
26
- if (!stdin.isTTY || !stdout.isTTY) {
27
- throw new Error(`${message}: interactive selection requires a TTY; pass an explicit option.`);
28
- }
29
-
30
- const Select = dependencies.Select || require("enquirer").Select;
31
- const prompt = new Select({
32
- name: "selection",
33
- message,
34
- stdin,
35
- stdout,
36
- choices: [
37
- ...choices.map((choice) => ({
38
- name: choice.value,
39
- message: choice.label,
40
- })),
41
- { name: EXIT_SELECTION, message: "退出" },
42
- ],
43
- });
44
-
45
- prompt.once("start", makePromptCancellationSafe);
46
- try {
47
- return await prompt.run();
48
- } catch (error) {
49
- if (!error || error.name === "CancelPromptError" || isReadlineClosedError(error)) {
50
- return EXIT_SELECTION;
51
- }
52
- throw error;
53
- }
54
- }
55
-
56
- module.exports = {
57
- EXIT_SELECTION,
58
- makePromptCancellationSafe,
59
- selectOption,
60
- };
1
+ // CLI 交互选择适配,业务选项由调用方注入。
2
+ const EXIT_SELECTION = "__exit__";
3
+
4
+ function isReadlineClosedError(error) {
5
+ return error?.code === "ERR_USE_AFTER_CLOSE";
6
+ }
7
+
8
+ function makePromptCancellationSafe(prompt) {
9
+ const unsafeStop = prompt.stop;
10
+ if (typeof unsafeStop !== "function") return;
11
+
12
+ prompt.removeListener("close", unsafeStop);
13
+ const safeStop = () => {
14
+ try {
15
+ unsafeStop();
16
+ } catch (error) {
17
+ if (!isReadlineClosedError(error)) throw error;
18
+ }
19
+ };
20
+ prompt.stop = safeStop;
21
+ prompt.once("close", safeStop);
22
+ }
23
+
24
+ async function selectOption(message, choices, dependencies = {}) {
25
+ const stdin = dependencies.stdin || process.stdin;
26
+ const stdout = dependencies.stdout || process.stdout;
27
+ if (!stdin.isTTY || !stdout.isTTY) {
28
+ throw new Error(`${message}: interactive selection requires a TTY; pass an explicit option.`);
29
+ }
30
+
31
+ const Select = dependencies.Select || require("enquirer").Select;
32
+ const prompt = new Select({
33
+ name: "selection",
34
+ message,
35
+ stdin,
36
+ stdout,
37
+ choices: [
38
+ ...choices.map((choice) => ({
39
+ name: choice.value,
40
+ message: choice.label,
41
+ })),
42
+ { name: EXIT_SELECTION, message: "退出" },
43
+ ],
44
+ });
45
+
46
+ prompt.once("start", makePromptCancellationSafe);
47
+ try {
48
+ return await prompt.run();
49
+ } catch (error) {
50
+ if (!error || error.name === "CancelPromptError" || isReadlineClosedError(error)) {
51
+ return EXIT_SELECTION;
52
+ }
53
+ throw error;
54
+ }
55
+ }
56
+
57
+ module.exports = {
58
+ EXIT_SELECTION,
59
+ makePromptCancellationSafe,
60
+ selectOption,
61
+ };
@@ -1,30 +1,31 @@
1
- /**
2
- * 异步并发限流器。
3
- * 对 items 数组中的每一项执行 worker,同时最多运行 concurrency 个 worker。
4
- *
5
- * @param {Array} items - 待处理项
6
- * @param {number} concurrency - 最大并发数
7
- * @param {Function} worker - 处理函数 (item, index) => Promise
8
- * @returns {Promise<Array>} 结果数组,索引与 items 一一对应
9
- */
10
- async function runWithConcurrency(items, concurrency, worker) {
11
- if (!Number.isInteger(concurrency) || concurrency < 1) {
12
- throw new TypeError("concurrency must be a positive integer");
13
- }
14
-
15
- const results = new Array(items.length);
16
- let nextIndex = 0;
17
- const runWorker = async () => {
18
- while (nextIndex < items.length) {
19
- const index = nextIndex;
20
- nextIndex += 1;
21
- results[index] = await worker(items[index], index);
22
- }
23
- };
24
-
25
- const workerCount = Math.min(concurrency, items.length);
26
- await Promise.all(Array.from({ length: workerCount }, runWorker));
27
- return results;
28
- }
29
-
30
- module.exports = { runWithConcurrency };
1
+ /**
2
+ * 框架无关的异步并发控制。
3
+ * 异步并发限流器。
4
+ * 对 items 数组中的每一项执行 worker,同时最多运行 concurrency 个 worker。
5
+ *
6
+ * @param {Array} items - 待处理项
7
+ * @param {number} concurrency - 最大并发数
8
+ * @param {Function} worker - 处理函数 (item, index) => Promise
9
+ * @returns {Promise<Array>} 结果数组,索引与 items 一一对应
10
+ */
11
+ async function runWithConcurrency(items, concurrency, worker) {
12
+ if (!Number.isInteger(concurrency) || concurrency < 1) {
13
+ throw new TypeError("concurrency must be a positive integer");
14
+ }
15
+
16
+ const results = new Array(items.length);
17
+ let nextIndex = 0;
18
+ const runWorker = async () => {
19
+ while (nextIndex < items.length) {
20
+ const index = nextIndex;
21
+ nextIndex += 1;
22
+ results[index] = await worker(items[index], index);
23
+ }
24
+ };
25
+
26
+ const workerCount = Math.min(concurrency, items.length);
27
+ await Promise.all(Array.from({ length: workerCount }, runWorker));
28
+ return results;
29
+ }
30
+
31
+ module.exports = { runWithConcurrency };