




















@@ -135,6 +135,187 @@ function listConfiguredBundledDependencyNames(packageJson) {
135135return [];
136136}
137137138+function npmInvocation() {
139+if (process.platform !== "win32") {
140+return { args: [], command: "npm" };
141+}
142+const npmCliPath = path.join(
143+path.dirname(process.execPath),
144+"node_modules",
145+"npm",
146+"bin",
147+"npm-cli.js",
148+);
149+if (fs.existsSync(npmCliPath)) {
150+return { args: [npmCliPath], command: process.execPath };
151+}
152+return { args: [], command: "npm.cmd", shell: true };
153+}
154+155+function spawnNpmSync(args, options) {
156+const invocation = npmInvocation();
157+return spawnSync(invocation.command, [...invocation.args, ...args], {
158+ ...options,
159+ ...(invocation.shell ? { shell: invocation.shell } : {}),
160+});
161+}
162+163+function spawnCommandSync(command, args, options) {
164+if (command === "npm") {
165+return spawnNpmSync(args, options);
166+}
167+return spawnSync(command, args, options);
168+}
169+170+function resolveInstalledPackageDir(packageDir, packageName) {
171+return path.join(packageDir, "node_modules", ...packageName.split("/"));
172+}
173+174+function readInstalledPackageJson(packageDir, packageName) {
175+const packageJsonPath = path.join(
176+resolveInstalledPackageDir(packageDir, packageName),
177+"package.json",
178+);
179+if (!fs.existsSync(packageJsonPath)) {
180+return undefined;
181+}
182+try {
183+return {
184+packageDir: path.dirname(packageJsonPath),
185+packageJson: readJsonFile(packageJsonPath),
186+};
187+} catch {
188+return undefined;
189+}
190+}
191+192+function hasInstalledPackage(packageDir, packageName) {
193+return fs.existsSync(
194+path.join(resolveInstalledPackageDir(packageDir, packageName), "package.json"),
195+);
196+}
197+198+function normalizeOptionalDependencySpec(packageDir, dependencyPackageDir, spec) {
199+if (typeof spec !== "string" || !spec.trim()) {
200+return undefined;
201+}
202+const trimmed = spec.trim();
203+if (!trimmed.startsWith("file:")) {
204+return trimmed;
205+}
206+const fileTarget = trimmed.slice("file:".length);
207+if (!fileTarget || path.isAbsolute(fileTarget)) {
208+return trimmed;
209+}
210+const absoluteTarget = path.resolve(dependencyPackageDir, fileTarget);
211+const packageRelativeTarget = path.relative(packageDir, absoluteTarget).replaceAll(path.sep, "/");
212+return `file:${packageRelativeTarget.startsWith(".") ? packageRelativeTarget : `./${packageRelativeTarget}`}`;
213+}
214+215+function collectMissingOptionalBundledDependencySpecs(packageDir, packageJson) {
216+const queue = listConfiguredBundledDependencyNames(packageJson);
217+const visited = new Set();
218+const missing = new Map();
219+220+while (queue.length > 0) {
221+const packageName = queue.shift();
222+if (!packageName || visited.has(packageName)) {
223+continue;
224+}
225+visited.add(packageName);
226+227+const installed = readInstalledPackageJson(packageDir, packageName);
228+if (!installed) {
229+continue;
230+}
231+const dependencyNames = [
232+ ...Object.keys(installed.packageJson.dependencies ?? {}),
233+ ...Object.keys(installed.packageJson.optionalDependencies ?? {}),
234+].toSorted((left, right) => left.localeCompare(right));
235+queue.push(...dependencyNames);
236+237+for (const [optionalName, optionalSpec] of Object.entries(
238+installed.packageJson.optionalDependencies ?? {},
239+).toSorted(([left], [right]) => left.localeCompare(right))) {
240+if (hasInstalledPackage(packageDir, optionalName)) {
241+continue;
242+}
243+const normalizedSpec = normalizeOptionalDependencySpec(
244+packageDir,
245+installed.packageDir,
246+optionalSpec,
247+);
248+if (normalizedSpec) {
249+missing.set(optionalName, normalizedSpec);
250+}
251+}
252+}
253+254+return [...missing.entries()].map(([name, spec]) => `${name}@${spec}`);
255+}
256+257+function installMissingOptionalBundledDependencies(params) {
258+const portableOptionalInstallSpecs = new Map();
259+for (let pass = 0; pass < 3; pass += 1) {
260+const installSpecs = collectMissingOptionalBundledDependencySpecs(
261+params.packageDir,
262+params.packageJson,
263+);
264+if (installSpecs.length === 0) {
265+return;
266+}
267+for (const installSpec of installSpecs) {
268+const at = installSpec.indexOf("@", installSpec.startsWith("@") ? 1 : 0);
269+const packageName = at > 0 ? installSpec.slice(0, at) : installSpec;
270+portableOptionalInstallSpecs.set(packageName, installSpec);
271+}
272+const cumulativeInstallSpecs = [...portableOptionalInstallSpecs.values()].toSorted(
273+(left, right) => left.localeCompare(right),
274+);
275+console.error(
276+`[plugin-npm-publish] installing portable optional bundled dependencies for ${params.pluginDir}: ${cumulativeInstallSpecs.join(", ")}`,
277+);
278+const result = spawnNpmSync(
279+[
280+"install",
281+"--force",
282+"--omit=dev",
283+"--omit=peer",
284+"--legacy-peer-deps",
285+"--ignore-scripts",
286+"--no-audit",
287+"--no-fund",
288+"--package-lock=false",
289+"--save=false",
290+"--loglevel=error",
291+ ...cumulativeInstallSpecs,
292+],
293+{
294+cwd: params.packageDir,
295+env: process.env,
296+stdio: ["ignore", "ignore", "inherit"],
297+},
298+);
299+if (result.error) {
300+throw result.error;
301+}
302+if ((result.status ?? 1) !== 0) {
303+throw new Error(
304+`package-local portable optional dependency install failed for ${params.pluginDir} with exit ${result.status ?? 1}`,
305+);
306+}
307+}
308+const remainingSpecs = collectMissingOptionalBundledDependencySpecs(
309+params.packageDir,
310+params.packageJson,
311+);
312+if (remainingSpecs.length > 0) {
313+throw new Error(
314+`package-local portable optional dependency install did not settle for ${params.pluginDir}: ${remainingSpecs.join(", ")}`,
315+);
316+}
317+}
318+138319function shouldBundleDependencies(value) {
139320return value === true || value === "1" || value === "true";
140321}
@@ -163,8 +344,7 @@ function installPackageLocalBundledDependencies(params) {
163344}
164345165346console.error(`[plugin-npm-publish] installing bundled dependencies for ${params.pluginDir}`);
166-const result = spawnSync(
167-"npm",
347+const result = spawnNpmSync(
168348[
169349"install",
170350"--omit=dev",
@@ -190,6 +370,7 @@ function installPackageLocalBundledDependencies(params) {
190370`package-local bundled dependency install failed for ${params.pluginDir} with exit ${result.status ?? 1}`,
191371);
192372}
373+installMissingOptionalBundledDependencies(params);
193374return () => {
194375fs.rmSync(nodeModulesPath, { recursive: true, force: true });
195376};
@@ -482,7 +663,7 @@ function main(argv = process.argv.slice(2)) {
482663bundleDependencies: process.env.OPENCLAW_PLUGIN_NPM_BUNDLE_DEPENDENCIES,
483664},
484665({ packageDir: cwd }) => {
485-const result = spawnSync(command, args, {
666+const result = spawnCommandSync(command, args, {
486667 cwd,
487668env: process.env,
488669stdio: "inherit",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。