Skip to content

Commit

Permalink
Merge pull request #1094 from forcedotcom/sm/sfProject-plugin-dx
Browse files Browse the repository at this point in the history
feat: methods for read/write sfProjectJson.plugins
  • Loading branch information
shetzel authored Jun 27, 2024
2 parents cb1e66d + f52699a commit ae61aec
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 4 deletions.
57 changes: 53 additions & 4 deletions src/sfProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,21 @@ 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 @see SfProject.getPluginConfiguration and @see SfProject.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();
* ```
*
Expand Down Expand Up @@ -712,6 +719,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<T extends Record<string, unknown>>(pluginName: string): Promise<Readonly<T>> {
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<T extends Record<string, unknown>>(pluginName: string, config: T): Promise<void> {
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 */
Expand Down
75 changes: 75 additions & 0 deletions test/unit/projectTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
});
});
});
});
});

0 comments on commit ae61aec

Please sign in to comment.