From dd2c26ad768afa2ed36de438c6ccb621176fa672 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Mon, 24 Jun 2024 16:59:28 -0500 Subject: [PATCH 1/2] feat: methods for read/write sfProjectJson.plugins --- src/sfProject.ts | 56 +++++++++++++++++++++++++++--- test/unit/projectTest.ts | 75 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 4 deletions(-) diff --git a/src/sfProject.ts b/src/sfProject.ts index 3bea7a061..5675c4301 100644 --- a/src/sfProject.ts +++ b/src/sfProject.ts @@ -44,14 +44,20 @@ export type ProjectJson = ConfigContents & ProjectJsonSchema; * The sfdx-project.json config object. This file determines if a folder is a valid sfdx project. * * *Note:* Any non-standard (not owned by Salesforce) properties stored in sfdx-project.json should - * be in a top level property that represents your project or plugin. + * be in a top level property that represents your project. Plugins should store their configuration via getPluginConfiguration and setPluginConfiguration * + * @example reading a standard property * ``` * const project = await SfProject.resolve(); * const projectJson = await project.resolveProjectConfig(); - * const myPluginProperties = projectJson.get('myplugin') || {}; - * myPluginProperties.myprop = 'someValue'; - * projectJson.set('myplugin', myPluginProperties); + * const namespace = projectJson.get('namespace'); + * ``` + * + * ``` + * @example writing + * const project = await SfProject.resolve(); + * const projectJson = await project.resolveProjectConfig(); + * projectJson.set('namespace', 'new'); * await projectJson.write(); * ``` * @@ -712,6 +718,48 @@ export class SfProject { .filter(([, value]) => value?.startsWith(id)) .map(([key]) => key); } + + /** + * retrieve the configuration for a named plugin from sfdx-project.json.plugins.pluginName + * + * @example + * ``` + * const project = await SfProject.resolve(); + * const pluginConfig = await project.getPluginConfiguration('myPlugin'); + * ``` + * + * optionally pass a type parameter for your plugin configuration's schema + * */ + public async getPluginConfiguration>(pluginName: string): Promise> { + await this.retrieveSfProjectJson(); + const plugins = this.sfProjectJson.get('plugins'); + if (!plugins) { + throw new SfError('No plugins defined in sfdx-project.json', 'NoPluginsDefined'); + } + if (!plugins[pluginName]) { + throw new SfError(`No configuration defined in sfdx-project.json for plugin ${pluginName}`, 'PluginNotFound'); + } + return plugins[pluginName] as T; + } + + /** + * set the configuration for a named plugin from sfdx-project.json.plugins.pluginName, overwriting existing configuration + * + * @example + * ``` + * const project = await SfProject.resolve(); + * const pluginConfig = await project.setPluginConfiguration('myPlugin', {foo: 'bar', myLimit: 25}); + * ``` + * + * optionally pass a type parameter for your plugin configuration's schema + * */ + public async setPluginConfiguration>(pluginName: string, config: T): Promise { + await this.retrieveSfProjectJson(); + const plugins = this.getSfProjectJson().get('plugins') ?? {}; + const modified = { ...plugins, [pluginName]: config }; + this.sfProjectJson.set('plugins', modified); + this.sfProjectJson.writeSync(); + } } /** differentiate between the Base PackageDir (path, maybe default) and the Packaging version (package and maybe a LOT of other fields) by whether is has the `package` property */ diff --git a/test/unit/projectTest.ts b/test/unit/projectTest.ts index 7f4e31d02..fe87ae7d4 100644 --- a/test/unit/projectTest.ts +++ b/test/unit/projectTest.ts @@ -890,4 +890,79 @@ describe('SfProject', () => { expect(foundPkg?.default).to.be.false; }); }); + + describe('plugins', () => { + describe('read', () => { + it('throws on read when no existing plugins', async () => { + $$.setConfigStubContents('SfProjectJson', { + contents: {}, + }); + const project = SfProject.getInstance(); + try { + await project.getPluginConfiguration('fooPlugin'); + assert.fail('Expected error to be thrown'); + } catch (e) { + assert(e instanceof SfError); + expect(e.name).to.equal('NoPluginsDefined'); + } + }); + it('throws on read when no existing plugin when named', async () => { + $$.setConfigStubContents('SfProjectJson', { + contents: { + plugins: { someOtherPlugin: {} }, + }, + }); + const project = SfProject.getInstance(); + try { + await project.getPluginConfiguration('fooPlugin'); + assert.fail('Expected error to be thrown'); + } catch (e) { + assert(e instanceof SfError); + expect(e.name).to.equal('PluginNotFound'); + } + }); + it('read returns valid data', async () => { + $$.setConfigStubContents('SfProjectJson', { + contents: { + plugins: { someOtherPlugin: {}, fooPlugin: { foo: 'bar' } }, + }, + }); + const project = SfProject.getInstance(); + const config = await project.getPluginConfiguration('fooPlugin'); + expect(config).to.deep.equal({ foo: 'bar' }); + }); + }); + describe('write', () => { + it('write when no existing plugins', async () => { + $$.setConfigStubContents('SfProjectJson', { + contents: {}, + }); + const project = SfProject.getInstance(); + await project.setPluginConfiguration('fooPlugin', { foo: 'bar' }); + expect($$.getConfigStubContents('SfProjectJson').plugins).to.deep.equal({ fooPlugin: { foo: 'bar' } }); + }); + it('write new plugin', async () => { + $$.setConfigStubContents('SfProjectJson', { + contents: { plugins: { otherPlugin: {} } }, + }); + const project = SfProject.getInstance(); + await project.setPluginConfiguration('fooPlugin', { foo: 'bar' }); + expect($$.getConfigStubContents('SfProjectJson').plugins).to.deep.equal({ + otherPlugin: {}, + fooPlugin: { foo: 'bar' }, + }); + }); + it('update existing plugin', async () => { + $$.setConfigStubContents('SfProjectJson', { + contents: { plugins: { otherPlugin: {}, fooPlugin: { foo: 'bat', removeMe: 0 } } }, + }); + const project = SfProject.getInstance(); + await project.setPluginConfiguration('fooPlugin', { foo: 'bar' }); + expect($$.getConfigStubContents('SfProjectJson').plugins).to.deep.equal({ + otherPlugin: {}, + fooPlugin: { foo: 'bar' }, + }); + }); + }); + }); }); From f52699ab9d01f53aeeec86404693011659dde393 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Mon, 24 Jun 2024 17:25:05 -0500 Subject: [PATCH 2/2] chore: jsdocs links --- src/sfProject.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sfProject.ts b/src/sfProject.ts index 5675c4301..5980a2d37 100644 --- a/src/sfProject.ts +++ b/src/sfProject.ts @@ -44,7 +44,8 @@ export type ProjectJson = ConfigContents & ProjectJsonSchema; * The sfdx-project.json config object. This file determines if a folder is a valid sfdx project. * * *Note:* Any non-standard (not owned by Salesforce) properties stored in sfdx-project.json should - * be in a top level property that represents your project. Plugins should store their configuration via getPluginConfiguration and setPluginConfiguration + * be in a top level property that represents your project. + * Plugins should store their configuration @see SfProject.getPluginConfiguration and @see SfProject.setPluginConfiguration * * @example reading a standard property * ```