Skip to content

Commit b48c475

Browse files
authored
fix: avoid Windows npm spawn failure in Pro install (#735)
1 parent e5b7cf5 commit b48c475

6 files changed

Lines changed: 97 additions & 13 deletions

File tree

compat/aiox-core/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "aiox-core",
3-
"version": "5.2.3",
3+
"version": "5.2.4",
44
"description": "Compatibility wrapper for @aiox-squads/core.",
55
"license": "MIT",
66
"bin": {
@@ -15,7 +15,7 @@
1515
"README.md"
1616
],
1717
"dependencies": {
18-
"@aiox-squads/core": "5.2.3"
18+
"@aiox-squads/core": "5.2.4"
1919
},
2020
"engines": {
2121
"node": ">=18"

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@aiox-squads/core",
3-
"version": "5.2.3",
3+
"version": "5.2.4",
44
"description": "Synkra AIOX: AI-Orchestrated System for Full Stack Development - Core Framework",
55
"bin": {
66
"aiox": "bin/aiox.js",

packages/installer/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@aiox-squads/installer",
3-
"version": "3.3.3",
3+
"version": "3.3.4",
44
"description": "AIOX Installer - Automated setup wizard for AIOX projects (greenfield & brownfield)",
55
"main": "src/index.js",
66
"bin": {

packages/installer/src/wizard/pro-setup.js

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,46 @@ const { t, tf } = require('./i18n');
2828

2929
const execFileAsync = promisify(execFile);
3030

31+
function stripWrappingQuotes(value) {
32+
return String(value || '').trim().replace(/^"(.*)"$/, '$1');
33+
}
34+
35+
function resolveNpmInvocation(options = {}) {
36+
const platform = options.platform || process.platform;
37+
const env = options.env || process.env;
38+
const execPath = options.execPath || process.execPath;
39+
const fileExists = options.fileExists || fs.existsSync;
40+
const npmExecPath = stripWrappingQuotes(env.npm_execpath);
41+
42+
if (npmExecPath && /\.js$/i.test(npmExecPath) && fileExists(npmExecPath)) {
43+
return {
44+
command: execPath,
45+
prefixArgs: [npmExecPath],
46+
execOptions: {},
47+
};
48+
}
49+
50+
return {
51+
command: platform === 'win32' ? 'npm.cmd' : 'npm',
52+
prefixArgs: [],
53+
execOptions: platform === 'win32' ? { shell: true } : {},
54+
};
55+
}
56+
57+
async function runNpm(args, options = {}) {
58+
const invocation = resolveNpmInvocation();
59+
60+
return execFileAsync(
61+
invocation.command,
62+
[...invocation.prefixArgs, ...args],
63+
{
64+
...options,
65+
...invocation.execOptions,
66+
windowsHide: true,
67+
},
68+
);
69+
}
70+
3171
/**
3272
* Gold color for Pro branding.
3373
* Falls back gracefully if chalk hex is unavailable.
@@ -749,10 +789,8 @@ async function extractProArtifactToTemp(artifactPath, tempRoot) {
749789
dependencies: {},
750790
});
751791

752-
const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
753792
try {
754-
await execFileAsync(
755-
npmBin,
793+
await runNpm(
756794
['install', artifactPath, '--ignore-scripts', '--no-audit', '--no-fund', '--no-save', '--silent'],
757795
{
758796
cwd: installRoot,
@@ -776,10 +814,8 @@ async function extractProArtifactToTemp(artifactPath, tempRoot) {
776814
async function installProArtifactIntoTarget(artifactPath, targetDir) {
777815
await fs.ensureDir(targetDir);
778816

779-
const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
780817
try {
781-
await execFileAsync(
782-
npmBin,
818+
await runNpm(
783819
[
784820
'install',
785821
artifactPath,
@@ -2163,6 +2199,7 @@ module.exports = {
21632199
generateMachineId,
21642200
persistLicenseCache,
21652201
resolveLicenseServerUrl,
2202+
resolveNpmInvocation,
21662203
ensureKeyValidationParity,
21672204
acquireProArtifactSourceDir,
21682205
downloadArtifactFile,

tests/installer/pro-setup-auth.test.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,53 @@ describe('pro-setup backward compatibility (AC-7)', () => {
169169
});
170170
});
171171

172+
describe('pro-setup npm invocation', () => {
173+
it('uses node + npm-cli.js on Windows when npm_execpath is available', () => {
174+
const invocation = proSetup._testing.resolveNpmInvocation({
175+
platform: 'win32',
176+
execPath: 'C:\\Program Files\\nodejs\\node.exe',
177+
env: {
178+
npm_execpath: 'C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js',
179+
},
180+
fileExists: () => true,
181+
});
182+
183+
expect(invocation).toEqual({
184+
command: 'C:\\Program Files\\nodejs\\node.exe',
185+
prefixArgs: ['C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js'],
186+
execOptions: {},
187+
});
188+
});
189+
190+
it('falls back to shell execution for npm.cmd on Windows', () => {
191+
const invocation = proSetup._testing.resolveNpmInvocation({
192+
platform: 'win32',
193+
env: {},
194+
fileExists: () => false,
195+
});
196+
197+
expect(invocation).toEqual({
198+
command: 'npm.cmd',
199+
prefixArgs: [],
200+
execOptions: { shell: true },
201+
});
202+
});
203+
204+
it('uses npm directly on POSIX platforms', () => {
205+
const invocation = proSetup._testing.resolveNpmInvocation({
206+
platform: 'darwin',
207+
env: {},
208+
fileExists: () => false,
209+
});
210+
211+
expect(invocation).toEqual({
212+
command: 'npm',
213+
prefixArgs: [],
214+
execOptions: {},
215+
});
216+
});
217+
});
218+
172219
describe('pro-setup interactive email fallback', () => {
173220
afterEach(() => {
174221
proSetup._testing.loadLicenseApi = undefined;

0 commit comments

Comments
 (0)