diff --git a/package.json b/package.json index 344dc9f0..8726181b 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "bugs": "https://github.com/forcedotcom/cli/issues", "dependencies": { "@inquirer/input": "^2.1.0", - "@oclif/core": "^3.23.0", + "@oclif/core": "^3.26.0", "@salesforce/core": "^6.7.1", "@salesforce/kit": "^3.0.15", "@salesforce/sf-plugins-core": "^8.0.1", diff --git a/src/commands/doctor.ts b/src/commands/doctor.ts index e243a5cc..4f5a45ab 100644 --- a/src/commands/doctor.ts +++ b/src/commands/doctor.ts @@ -81,7 +81,7 @@ export default class Doctor extends SfCommand { const hasDoctorHook = plugin.hooks && Object.keys(plugin.hooks).some((hook) => hook === eventName); if (hasDoctorHook) { this.styledHeader(`Running diagnostics for plugin: ${flags.plugin}`); - this.tasks.push(this.config.runHook(eventName, { doctor: this.doctor })); + this.tasks.push(this.runDoctorHook(eventName)); } else { this.log(`${flags.plugin} doesn't have diagnostic tests to run.`); } @@ -94,7 +94,7 @@ export default class Doctor extends SfCommand { this.config.getPluginsList().forEach((plugin) => { const eventName = `sf-doctor-${plugin.name}`; if (plugin.hooks && Object.keys(plugin.hooks).find((hook) => hook === eventName)) { - this.tasks.push(this.config.runHook(eventName, { doctor: this.doctor })); + this.tasks.push(this.runDoctorHook(eventName)); } }); this.doctor.diagnose().map((p) => this.tasks.push(p)); @@ -137,6 +137,10 @@ export default class Doctor extends SfCommand { return diagnosis; } + private runDoctorHook(event: string): Promise { + return this.config.runHook(event, { doctor: this.doctor }); + } + /** * Only made into its own method for unit testing purposes * diff --git a/test/commands/doctor.test.ts b/test/commands/doctor.test.ts index 2ad0bbac..46e38286 100644 --- a/test/commands/doctor.test.ts +++ b/test/commands/doctor.test.ts @@ -11,12 +11,12 @@ import childProcess from 'node:child_process'; import fs from 'node:fs'; import Sinon from 'sinon'; import { expect } from 'chai'; -import { stubMethod } from '@salesforce/ts-sinon'; +import { fromStub, stubInterface, stubMethod } from '@salesforce/ts-sinon'; import { Lifecycle, Messages } from '@salesforce/core'; import { Config, Interfaces } from '@oclif/core'; import { SfCommand } from '@salesforce/sf-plugins-core'; import DoctorCmd from '../../src/commands/doctor.js'; -import { Diagnostics, DiagnosticStatus, Doctor, SfDoctorDiagnosis } from '../../src/index.js'; +import { Diagnostics, Doctor, SfDoctorDiagnosis } from '../../src/index.js'; import { formatPlugins } from '../../src/doctor.js'; import { prompts } from '../../src/shared/prompts.js'; @@ -81,57 +81,67 @@ describe('Doctor Command', () => { let childProcessExecStub: sinon.SinonStub; let promptStub: sinon.SinonStub; let openStub: sinon.SinonStub; + let runHookStub: sinon.SinonStub; - oclifConfig = { - runHook: sandbox.stub(), + const plugins = [ + { + name: '@salesforce/plugin-org', + hooks: { 'sf-doctor-@salesforce/plugin-org': ['./lib/hooks/diagnostics'] }, + }, + { + name: '@salesforce/plugin-source', + hooks: { 'sf-doctor-@salesforce/plugin-source': ['./lib/hooks/diagnostics'] }, + }, + { + name: 'salesforce-alm', + hooks: {}, + }, + { + name: '@salesforce/plugin-data', + hooks: {}, + }, + ].map((p) => ({ + ...p, + commands: [], + topics: [], pjson: { - engines: { - node: 'node-v16.17.0', + name: p.name, + oclif: { + hooks: p.hooks, }, - oclif: {}, }, - plugins: [ - { - name: '@salesforce/plugin-org', - hooks: { 'sf-doctor-@salesforce/plugin-org': './lib/hooks/diagnostics' }, - }, - { - name: '@salesforce/plugin-source', - hooks: { 'sf-doctor-@salesforce/plugin-source': './lib/hooks/diagnostics' }, - }, - { - name: 'salesforce-alm', - }, - { - name: '@salesforce/plugin-data', + })); + + oclifConfig = fromStub( + stubInterface(sandbox, { + runHook: async () => ({ + successes: [], + failures: [], + }), + pjson: { + engines: { + node: 'node-v16.17.0', + }, + oclif: {}, }, - ], - getPluginsList: () => oclifConfig.plugins, - bin: 'sfdx', - versionDetails: {}, - } as unknown as Config; - - // eslint-disable-next-line @typescript-eslint/unbound-method - const runHookStub = oclifConfig.runHook as sinon.SinonStub; - - class TestDoctor extends DoctorCmd { - public async runIt() { - await this.init(); - return this.run(); - } - } + plugins: new Map(plugins.map((p) => [p.name, p])), + getPluginsList: () => plugins, + bin: 'sfdx', + versionDetails: getVersionDetailStub(), + }) + ); const runDoctorCmd = async (params: string[]) => { - const cmd = new TestDoctor(params, oclifConfig); + const cmd = new DoctorCmd(params, oclifConfig); uxLogStub = stubMethod(sandbox, SfCommand.prototype, 'log'); promptStub = sandbox.stub(prompts, 'titleInput').resolves('my new and crazy issue'); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore + // @ts-expect-error because private method openStub = sandbox.stub(cmd, 'openUrl').resolves(); uxStyledHeaderStub = stubMethod(sandbox, SfCommand.prototype, 'styledHeader'); - - return cmd.runIt(); + // @ts-expect-error because private method + runHookStub = sandbox.stub(DoctorCmd.prototype, 'runDoctorHook'); + return cmd.run(); }; beforeEach(async () => { @@ -146,7 +156,6 @@ describe('Doctor Command', () => { stubMethod(sandbox, Doctor.prototype, 'createStderrWriteStream'); stubMethod(sandbox, Doctor.prototype, 'closeStdout'); stubMethod(sandbox, Doctor.prototype, 'closeStderr'); - runHookStub.reset(); }); afterEach(() => { @@ -186,10 +195,7 @@ describe('Doctor Command', () => { it('runs doctor command with no flags', async () => { const suggestion = 'work smarter, not faster'; fsExistsSyncStub.returns(true); - const versionDetail = getVersionDetailStub(); - const diagnosticStatus: DiagnosticStatus = { testName: 'doctor test', status: 'pass' }; - // @ts-expect-error: stubbing a private property - oclifConfig.versionDetails = versionDetail; + const diagnosticStatus = { testName: 'doctor test', status: 'pass' }; Doctor.init(oclifConfig); diagnosticsRunStub.callsFake(() => { const dr = Doctor.getInstance(); @@ -215,18 +221,15 @@ describe('Doctor Command', () => { expect(drWriteStderr.called).to.be.false; expect(fsWriteFileSyncStub.calledOnce).to.be.true; expect(runHookStub.calledTwice).to.be.true; - expect(runHookStub.args[0][0]).to.equal('sf-doctor-@salesforce/plugin-org'); - expect(runHookStub.args[0][1]).to.deep.equal({ doctor: Doctor.getInstance() }); - expect(runHookStub.args[1][0]).to.equal('sf-doctor-@salesforce/plugin-source'); - expect(runHookStub.args[1][1]).to.deep.equal({ doctor: Doctor.getInstance() }); + expect(runHookStub.args).to.deep.equal([ + ['sf-doctor-@salesforce/plugin-org'], + ['sf-doctor-@salesforce/plugin-source'], + ]); }); it('runs doctor command with outputdir flag (existing dir)', async () => { const outputdir = path.resolve('foo', 'bar', 'test'); fsExistsSyncStub.returns(true); - const versionDetail = getVersionDetailStub(); - // @ts-expect-error: stubbing a private property - oclifConfig.versionDetails = versionDetail; Doctor.init(oclifConfig); diagnosticsRunStub.callsFake(() => [Promise.resolve()]); @@ -247,18 +250,15 @@ describe('Doctor Command', () => { expect(drWriteStdout.called).to.be.false; expect(drWriteStderr.called).to.be.false; expect(runHookStub.callCount, 'Expected runHooks to be called twice').to.equal(2); - expect(runHookStub.args[0][0]).to.equal('sf-doctor-@salesforce/plugin-org'); - expect(runHookStub.args[0][1]).to.deep.equal({ doctor: Doctor.getInstance() }); - expect(runHookStub.args[1][0]).to.equal('sf-doctor-@salesforce/plugin-source'); - expect(runHookStub.args[1][1]).to.deep.equal({ doctor: Doctor.getInstance() }); + expect(runHookStub.args).to.deep.equal([ + ['sf-doctor-@salesforce/plugin-org'], + ['sf-doctor-@salesforce/plugin-source'], + ]); }); it('runs doctor command with outputdir flag (non-existing dir)', async () => { const outputdir = path.resolve('foo', 'bar', 'test'); fsExistsSyncStub.returns(false); - const versionDetail = getVersionDetailStub(); - // @ts-expect-error: stubbing a private property - oclifConfig.versionDetails = versionDetail; Doctor.init(oclifConfig); diagnosticsRunStub.callsFake(() => [Promise.resolve()]); @@ -279,19 +279,16 @@ describe('Doctor Command', () => { expect(drWriteStderr.called).to.be.false; expect(fsWriteFileSyncStub.calledOnce).to.be.true; expect(runHookStub.calledTwice).to.be.true; - expect(runHookStub.args[0][0]).to.equal('sf-doctor-@salesforce/plugin-org'); - expect(runHookStub.args[0][1]).to.deep.equal({ doctor: Doctor.getInstance() }); - expect(runHookStub.args[1][0]).to.equal('sf-doctor-@salesforce/plugin-source'); - expect(runHookStub.args[1][1]).to.deep.equal({ doctor: Doctor.getInstance() }); + expect(runHookStub.args).to.deep.equal([ + ['sf-doctor-@salesforce/plugin-org'], + ['sf-doctor-@salesforce/plugin-source'], + ]); }); it('runs doctor command with command flag (minimal)', async () => { const cmd = 'force:org:list --all'; const expectedCmd = `${oclifConfig.bin} ${cmd} --dev-debug`; fsExistsSyncStub.returns(true); - const versionDetail = getVersionDetailStub(); - // @ts-expect-error: stubbing a private property - oclifConfig.versionDetails = versionDetail; Doctor.init(oclifConfig); diagnosticsRunStub.callsFake(() => [Promise.resolve()]); childProcessExecStub.callsFake((cmdString, opts, cb: () => void) => { @@ -316,19 +313,16 @@ describe('Doctor Command', () => { expect(drWriteStdout.called).to.be.true; expect(drWriteStderr.called).to.be.true; expect(runHookStub.calledTwice).to.be.true; - expect(runHookStub.args[0][0]).to.equal('sf-doctor-@salesforce/plugin-org'); - expect(runHookStub.args[0][1]).to.deep.equal({ doctor: Doctor.getInstance() }); - expect(runHookStub.args[1][0]).to.equal('sf-doctor-@salesforce/plugin-source'); - expect(runHookStub.args[1][1]).to.deep.equal({ doctor: Doctor.getInstance() }); + expect(runHookStub.args).to.deep.equal([ + ['sf-doctor-@salesforce/plugin-org'], + ['sf-doctor-@salesforce/plugin-source'], + ]); expect(result).to.have.property('commandName', expectedCmd); }); it('runs doctor command with command flag (full)', async () => { const cmd = `${oclifConfig.bin} force:org:list --all --dev-debug`; fsExistsSyncStub.returns(true); - const versionDetail = getVersionDetailStub(); - // @ts-expect-error: stubbing a private property - oclifConfig.versionDetails = versionDetail; Doctor.init(oclifConfig); diagnosticsRunStub.callsFake(() => [Promise.resolve()]); childProcessExecStub.callsFake((cmdString, opts, cb: () => void) => { @@ -354,18 +348,15 @@ describe('Doctor Command', () => { expect(drWriteStdout.called).to.be.true; expect(drWriteStderr.called).to.be.true; expect(runHookStub.calledTwice).to.be.true; - expect(runHookStub.args[0][0]).to.equal('sf-doctor-@salesforce/plugin-org'); - expect(runHookStub.args[0][1]).to.deep.equal({ doctor: Doctor.getInstance() }); - expect(runHookStub.args[1][0]).to.equal('sf-doctor-@salesforce/plugin-source'); - expect(runHookStub.args[1][1]).to.deep.equal({ doctor: Doctor.getInstance() }); + expect(runHookStub.args).to.deep.equal([ + ['sf-doctor-@salesforce/plugin-org'], + ['sf-doctor-@salesforce/plugin-source'], + ]); expect(result).to.have.property('commandName', cmd); }); it('runs doctor command with plugin flag', async () => { fsExistsSyncStub.returns(true); - const versionDetail = getVersionDetailStub(); - // @ts-expect-error: stubbing a private property - oclifConfig.versionDetails = versionDetail; Doctor.init(oclifConfig); diagnosticsRunStub.callsFake(() => [Promise.resolve()]); @@ -386,8 +377,7 @@ describe('Doctor Command', () => { expect(drWriteStdout.called).to.be.false; expect(drWriteStderr.called).to.be.false; expect(runHookStub.calledOnce).to.be.true; - expect(runHookStub.args[0][0]).to.equal('sf-doctor-@salesforce/plugin-org'); - expect(runHookStub.args[0][1]).to.deep.equal({ doctor: Doctor.getInstance() }); + expect(runHookStub.args).to.deep.equal([['sf-doctor-@salesforce/plugin-org']]); }); it('runs doctor command with plugin flag (no plugin tests)', async () => { diff --git a/test/commands/info/releasenotes/display.test.ts b/test/commands/info/releasenotes/display.test.ts index 5c4628bd..4862f571 100644 --- a/test/commands/info/releasenotes/display.test.ts +++ b/test/commands/info/releasenotes/display.test.ts @@ -35,24 +35,23 @@ describe('sfdx info:releasenotes:display', () => { let parseReleaseNotesSpy: Sinon.SinonSpy; let markedParserSpy: Sinon.SinonSpy; - const oclifConfigStub = fromStub(stubInterface(sandbox)); - - class TestDisplay extends Display { - public async runIt() { - await this.init(); - return this.run(); - } - } + const oclifConfigStub = fromStub( + stubInterface(sandbox, { + runHook: async () => ({ + successes: [], + failures: [], + }), + bin: 'sfdx', + }) + ); const runDisplayCmd = async (params: string[]) => { - oclifConfigStub.bin = 'sfdx'; - - const cmd = new TestDisplay(params, oclifConfigStub); + const cmd = new Display(params, oclifConfigStub); uxLogStub = stubMethod(sandbox, SfCommand.prototype, 'log'); uxWarnStub = stubMethod(sandbox, SfCommand.prototype, 'warn'); - return cmd.runIt(); + return cmd.run(); }; beforeEach(() => { @@ -261,6 +260,7 @@ describe('sfdx info:releasenotes:display', () => { expect(json).to.deep.equal(expected); }); }); + describe('sf info:releasenotes:display', () => { const sandbox = Sinon.createSandbox(); @@ -274,24 +274,23 @@ describe('sf info:releasenotes:display', () => { let parseReleaseNotesSpy: Sinon.SinonSpy; let markedParserSpy: Sinon.SinonSpy; - const oclifConfigStub = fromStub(stubInterface(sandbox)); - - class TestDisplay extends Display { - public async runIt() { - await this.init(); - return this.run(); - } - } + const oclifConfigStub = fromStub( + stubInterface(sandbox, { + runHook: async () => ({ + successes: [], + failures: [], + }), + bin: 'sf', + }) + ); const runDisplayCmd = async (params: string[]) => { - oclifConfigStub.bin = 'sf'; - - const cmd = new TestDisplay(params, oclifConfigStub); + const cmd = new Display(params, oclifConfigStub); uxLogStub = stubMethod(sandbox, SfCommand.prototype, 'log'); uxWarnStub = stubMethod(sandbox, SfCommand.prototype, 'warn'); - return cmd.runIt(); + return cmd.run(); }; beforeEach(() => { diff --git a/yarn.lock b/yarn.lock index 1bec9bd3..e15a7953 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1319,10 +1319,10 @@ wordwrap "^1.0.0" wrap-ansi "^7.0.0" -"@oclif/core@^3.15.1", "@oclif/core@^3.19.2", "@oclif/core@^3.20.0", "@oclif/core@^3.21.0", "@oclif/core@^3.23.0": - version "3.25.2" - resolved "https://registry.yarnpkg.com/@oclif/core/-/core-3.25.2.tgz#a26d56abe5686c57c1e973957777bd2ae324e973" - integrity sha512-OkW/cNa/3DhoCz2YlSpymVe8DXqkoRaLY4SPTVqNVzR4R1dFBE5KoCtuwKwnhxYLCRCqaViPgRnB5K26f0MnjA== +"@oclif/core@^3.15.1", "@oclif/core@^3.19.2", "@oclif/core@^3.20.0", "@oclif/core@^3.21.0", "@oclif/core@^3.23.0", "@oclif/core@^3.26.0": + version "3.26.0" + resolved "https://registry.yarnpkg.com/@oclif/core/-/core-3.26.0.tgz#959d5e9f13f4ad6a4e98235ad125189df9ee4279" + integrity sha512-TpMdfD4tfA2tVVbd4l0PrP02o5KoUXYmudBbTC7CeguDo/GLoprw4uL8cMsaVA26+cbcy7WYtOEydQiHVtJixA== dependencies: "@types/cli-progress" "^3.11.5" ansi-escapes "^4.3.2"