From 69c81f5e9992ef51cc88c13b26cdc6a2176c08e2 Mon Sep 17 00:00:00 2001 From: soridalac Date: Mon, 3 Aug 2026 13:54:56 -0700 Subject: [PATCH 1/2] fix: use execFile instead of exec for npm calls --- schemas/doctor.json | 1 + src/diagnostics.ts | 4 ++- test/diagnostics.test.ts | 54 +++++++++++++++++++++++----------------- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/schemas/doctor.json b/schemas/doctor.json index cdeb1560..ba78d297 100644 --- a/schemas/doctor.json +++ b/schemas/doctor.json @@ -56,6 +56,7 @@ }, "cliConfig": { "type": "object", + "additionalProperties": {}, "properties": { "bin": { "type": "string" diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 211b45fe..60404ff1 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -73,7 +73,9 @@ export class Diagnostics { const testName = 'using latest or latest-rc CLI version'; let status: DiagnosticStatus['status'] = 'unknown'; - childProcess.exec(`npm view ${cliName} dist-tags.latest`, {}, (error, stdout, stderr) => { + // Use execFile instead of exec to avoid shell interpretation. + // exec invokes cmd.exe on Windows, which resolves commands from CWD before PATH. + childProcess.execFile('npm', ['view', cliName, 'dist-tags.latest'], (error, stdout, stderr) => { const code = error?.code ?? 0; if (code === 0) { const latest = stdout.trim(); diff --git a/test/diagnostics.test.ts b/test/diagnostics.test.ts index 6faf3aca..b0111cd9 100644 --- a/test/diagnostics.test.ts +++ b/test/diagnostics.test.ts @@ -60,13 +60,13 @@ describe('Diagnostics', () => { const sandbox = Sinon.createSandbox(); const lifecycle = Lifecycle.getInstance(); - let childProcessExecStub: sinon.SinonStub; + let childProcessExecFileStub: sinon.SinonStub; let drAddSuggestionSpy: sinon.SinonSpy; let lifecycleEmitSpy: sinon.SinonSpy; beforeEach(() => { stubMethod(sandbox, ux, 'stdout'); - childProcessExecStub = stubMethod(sandbox, childProcess, 'exec'); + childProcessExecFileStub = sandbox.stub(childProcess, 'execFile'); drAddSuggestionSpy = spyMethod(sandbox, Doctor.prototype, 'addSuggestion'); lifecycleEmitSpy = spyMethod(sandbox, lifecycle, 'emit'); oclifConfig = { @@ -98,7 +98,7 @@ describe('Diagnostics', () => { // This will have to be updated with each new test expect(results.length).to.equal(6); - expect(childProcessExecStub.called).to.be.true; + expect(childProcessExecFileStub.called).to.be.true; expect(lifecycleEmitSpy.called).to.be.true; expect(lifecycleEmitSpy.args[0][0]).to.equal('Doctor:diagnostic'); expect(lifecycleEmitSpy.args[0][1]).to.have.property('testName'); @@ -235,11 +235,13 @@ describe('Diagnostics', () => { describe('outdatedCliVersionCheck', () => { it('passes when CLI version is equal to latest', async () => { - childProcessExecStub.callsFake((cmdString, opts, cb: (e: unknown, stdout: unknown, stderr: unknown) => void) => { - expect(cmdString).to.equal('npm view sfdx-cli dist-tags.latest'); - expect(opts).to.be.ok; - cb({}, '7.160.0', ''); - }); + childProcessExecFileStub.callsFake( + (cmd: string, args: string[], cb: (e: unknown, stdout: unknown, stderr: unknown) => void) => { + expect(cmd).to.equal('npm'); + expect(args).to.deep.equal(['view', 'sfdx-cli', 'dist-tags.latest']); + cb(null, '7.160.0', ''); + } + ); const dr = Doctor.init(oclifConfig); const diagnostics = new Diagnostics(dr, oclifConfig); @@ -254,11 +256,13 @@ describe('Diagnostics', () => { }); it('passes when CLI version is greater than latest', async () => { - childProcessExecStub.callsFake((cmdString, opts, cb: (e: unknown, stdout: unknown, stderr: unknown) => void) => { - expect(cmdString).to.equal('npm view sfdx-cli dist-tags.latest'); - expect(opts).to.be.ok; - cb({}, '7.159.0', ''); - }); + childProcessExecFileStub.callsFake( + (cmd: string, args: string[], cb: (e: unknown, stdout: unknown, stderr: unknown) => void) => { + expect(cmd).to.equal('npm'); + expect(args).to.deep.equal(['view', 'sfdx-cli', 'dist-tags.latest']); + cb(null, '7.159.0', ''); + } + ); const dr = Doctor.init(oclifConfig); const diagnostics = new Diagnostics(dr, oclifConfig); @@ -273,11 +277,13 @@ describe('Diagnostics', () => { }); it('fails when CLI version is less than latest', async () => { - childProcessExecStub.callsFake((cmdString, opts, cb: (e: unknown, stdout: unknown, stderr: unknown) => void) => { - expect(cmdString).to.equal('npm view sfdx-cli dist-tags.latest'); - expect(opts).to.be.ok; - cb({}, '7.162.0', ''); - }); + childProcessExecFileStub.callsFake( + (cmd: string, args: string[], cb: (e: unknown, stdout: unknown, stderr: unknown) => void) => { + expect(cmd).to.equal('npm'); + expect(args).to.deep.equal(['view', 'sfdx-cli', 'dist-tags.latest']); + cb(null, '7.162.0', ''); + } + ); const dr = Doctor.init(oclifConfig); const diagnostics = new Diagnostics(dr, oclifConfig); @@ -292,11 +298,13 @@ describe('Diagnostics', () => { }); it('fails when npm request fails', async () => { - childProcessExecStub.callsFake((cmdString, opts, cb: (e: unknown, stdout: unknown, stderr: unknown) => void) => { - expect(cmdString).to.equal('npm view sfdx-cli dist-tags.latest'); - expect(opts).to.be.ok; - cb({ code: 1 }, '', 'connection timeout'); - }); + childProcessExecFileStub.callsFake( + (cmd: string, args: string[], cb: (e: unknown, stdout: unknown, stderr: unknown) => void) => { + expect(cmd).to.equal('npm'); + expect(args).to.deep.equal(['view', 'sfdx-cli', 'dist-tags.latest']); + cb({ code: 1 }, '', 'connection timeout'); + } + ); const dr = Doctor.init(oclifConfig); const diagnostics = new Diagnostics(dr, oclifConfig); From 193fd9b3b83c66b20ededd67e7b0867233c7f196 Mon Sep 17 00:00:00 2001 From: soridalac Date: Mon, 3 Aug 2026 14:06:34 -0700 Subject: [PATCH 2/2] fix: reformat schemas --- schemas/doctor.json | 1 - 1 file changed, 1 deletion(-) diff --git a/schemas/doctor.json b/schemas/doctor.json index ba78d297..cdeb1560 100644 --- a/schemas/doctor.json +++ b/schemas/doctor.json @@ -56,7 +56,6 @@ }, "cliConfig": { "type": "object", - "additionalProperties": {}, "properties": { "bin": { "type": "string"