Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions compat/aiox-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "aiox-core",
"version": "5.2.3",
"version": "5.2.4",
"description": "Compatibility wrapper for @aiox-squads/core.",
"license": "MIT",
"bin": {
Expand All @@ -15,7 +15,7 @@
"README.md"
],
"dependencies": {
"@aiox-squads/core": "5.2.3"
"@aiox-squads/core": "5.2.4"
},
"engines": {
"node": ">=18"
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aiox-squads/core",
"version": "5.2.3",
"version": "5.2.4",
"description": "Synkra AIOX: AI-Orchestrated System for Full Stack Development - Core Framework",
"bin": {
"aiox": "bin/aiox.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/installer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aiox-squads/installer",
"version": "3.3.3",
"version": "3.3.4",
"description": "AIOX Installer - Automated setup wizard for AIOX projects (greenfield & brownfield)",
"main": "src/index.js",
"bin": {
Expand Down
49 changes: 43 additions & 6 deletions packages/installer/src/wizard/pro-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,46 @@ const { t, tf } = require('./i18n');

const execFileAsync = promisify(execFile);

function stripWrappingQuotes(value) {
return String(value || '').trim().replace(/^"(.*)"$/, '$1');
}

function resolveNpmInvocation(options = {}) {
const platform = options.platform || process.platform;
const env = options.env || process.env;
const execPath = options.execPath || process.execPath;
const fileExists = options.fileExists || fs.existsSync;
const npmExecPath = stripWrappingQuotes(env.npm_execpath);

if (npmExecPath && /\.js$/i.test(npmExecPath) && fileExists(npmExecPath)) {
return {
command: execPath,
prefixArgs: [npmExecPath],
execOptions: {},
};
}

return {
command: platform === 'win32' ? 'npm.cmd' : 'npm',
prefixArgs: [],
execOptions: platform === 'win32' ? { shell: true } : {},
};
}

async function runNpm(args, options = {}) {
const invocation = resolveNpmInvocation();

return execFileAsync(
invocation.command,
[...invocation.prefixArgs, ...args],
{
...options,
...invocation.execOptions,
windowsHide: true,
},
);
}

/**
* Gold color for Pro branding.
* Falls back gracefully if chalk hex is unavailable.
Expand Down Expand Up @@ -749,10 +789,8 @@ async function extractProArtifactToTemp(artifactPath, tempRoot) {
dependencies: {},
});

const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
try {
await execFileAsync(
npmBin,
await runNpm(
['install', artifactPath, '--ignore-scripts', '--no-audit', '--no-fund', '--no-save', '--silent'],
{
cwd: installRoot,
Expand All @@ -776,10 +814,8 @@ async function extractProArtifactToTemp(artifactPath, tempRoot) {
async function installProArtifactIntoTarget(artifactPath, targetDir) {
await fs.ensureDir(targetDir);

const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
try {
await execFileAsync(
npmBin,
await runNpm(
[
'install',
artifactPath,
Expand Down Expand Up @@ -2163,6 +2199,7 @@ module.exports = {
generateMachineId,
persistLicenseCache,
resolveLicenseServerUrl,
resolveNpmInvocation,
ensureKeyValidationParity,
acquireProArtifactSourceDir,
downloadArtifactFile,
Expand Down
47 changes: 47 additions & 0 deletions tests/installer/pro-setup-auth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,53 @@ describe('pro-setup backward compatibility (AC-7)', () => {
});
});

describe('pro-setup npm invocation', () => {
it('uses node + npm-cli.js on Windows when npm_execpath is available', () => {
const invocation = proSetup._testing.resolveNpmInvocation({
platform: 'win32',
execPath: 'C:\\Program Files\\nodejs\\node.exe',
env: {
npm_execpath: 'C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js',
},
fileExists: () => true,
});

expect(invocation).toEqual({
command: 'C:\\Program Files\\nodejs\\node.exe',
prefixArgs: ['C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js'],
execOptions: {},
});
});

it('falls back to shell execution for npm.cmd on Windows', () => {
const invocation = proSetup._testing.resolveNpmInvocation({
platform: 'win32',
env: {},
fileExists: () => false,
});

expect(invocation).toEqual({
command: 'npm.cmd',
prefixArgs: [],
execOptions: { shell: true },
});
});

it('uses npm directly on POSIX platforms', () => {
const invocation = proSetup._testing.resolveNpmInvocation({
platform: 'darwin',
env: {},
fileExists: () => false,
});

expect(invocation).toEqual({
command: 'npm',
prefixArgs: [],
execOptions: {},
});
});
});

describe('pro-setup interactive email fallback', () => {
afterEach(() => {
proSetup._testing.loadLicenseApi = undefined;
Expand Down
Loading