Skip to content

Commit

Permalink
add tests, format code
Browse files Browse the repository at this point in the history
  • Loading branch information
k-capehart committed Oct 4, 2024
1 parent 4ccfff0 commit d9dc112
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 128 deletions.
35 changes: 8 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

This plugin is bundled with the [Salesforce CLI](https://developer.salesforce.com/tools/sfdxcli). For more information on the CLI, read the [getting started guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_intro.htm).

## Contents

- [Install](#install)
- [Trigger-Framework](#trigger-framework)
- [List of Commands](#list-of-commands)
- [Build](#build)

## Install

```bash
Expand Down Expand Up @@ -90,33 +97,7 @@ Running the command: `sf kc trigger-framework --custom-template templates/ --sob

For more template examples: https://github.com/k-capehart/kc-sf-plugin/tree/main/src/templates/

## API Version Management

Automatically update the API version for Salesforce components such as Apex classes, triggers, and flows. Specify a component type (or multiple) and a minimum API version that should be satisfied.

For example, if you want to update all classes, triggers, and flows to be at least version 61.0, then run the following command within a SFDX directory.

`sf kc update-api --type classes --type triggers --type flows --api-version 61.0`

Specifying deprecated API versions will throw an error.

## Preview Project Diff

Combine the output of a retrieval preview and deploy preview into one command. The org must have source tracking enabled. Source tracking isn’t supported and can’t be enabled for Partial Copy sandboxes, Full sandboxes, or Developer Edition orgs.

https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_setup_enable_source_tracking_sandboxes.htm

Instead of running both of these commands:

`sf project deploy preview`

`sf project retrieve preview`

You can now combine the output into one command:

`sf kc diff`

## Commands
## List of Commands

<!-- commands -->

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
]
},
"compile": {
"command": "tsc -p . --pretty --incremental && cp -r src/templates/ lib/templates/",
"command": "tsc -p . --pretty --incremental",
"files": [
"src/**/*.ts",
"**/tsconfig.json",
Expand Down
9 changes: 4 additions & 5 deletions src/commands/kc/update-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,33 +38,32 @@ export default class KcUpdateApi extends SfCommand<KcUpdateApiResult> {

public async run(): Promise<KcUpdateApiResult> {
const { flags } = await this.parse(KcUpdateApi);
let updatedNumber = 0;

const targetDir = flags['target-dir'];
if (!fs.existsSync(targetDir)) {
this.warn(targetDir + ' does not exist');
return {
updatedNumber: 0,
updatedNumber,
};
}

const apiVersion = flags['api-version'];
let updatedNumber = 0;

// multiple types can be specified, so loop through all of them
flags['type'].forEach((type) => {
const componentDir = targetDir.concat('/' + type.toString());
if (!fs.existsSync(componentDir)) {
this.warn(componentDir + ' does not exist');
return {
updatedNumber: 0,
updatedNumber,
};
}

updatedNumber = updatedNumber + updateAPIVersion(componentDir, apiVersion);
});

return {
updatedNumber: 0,
updatedNumber,
};
}
}
92 changes: 46 additions & 46 deletions src/utils/xmlParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ type ApexClassXml = {
ApexClass: {
apiVersion: string;
};
}
};

type ApexTriggerXml = {
ApexTrigger: {
apiVersion: string;
};
}
};

type FlowXml = {
Flow: {
apiVersion: string;
};
}
};

type XmlData = ApexClassXml | ApexTriggerXml | FlowXml;

Expand All @@ -35,55 +35,55 @@ const builderOptions = {
};

export const updateAPIVersion = (targetDir: string, version: string): number => {
fs.readdir(targetDir, (err, files) => {
if (err) {
ux.error('Error reading directory:', err);
}
let files: string[];
try {
files = fs.readdirSync(targetDir);
} catch (error: unknown) {
ux.error('Error reading directory:', error as Error);
}

const parser = new XMLParser(parserOptions);
const builder = new XMLBuilder(builderOptions);
const updatedNumber = files.length;
const parser = new XMLParser(parserOptions);
const builder = new XMLBuilder(builderOptions);
let updatedNumber = 0;

try {
for (const file of files) {
if (path.extname(file) === '.xml') {
const filePath = path.join(targetDir, file);
const data = fs.readFileSync(filePath, 'utf-8');
const component = parser.parse(data) as XmlData;
let isUpdated = false;
try {
for (const file of files) {
if (path.extname(file) === '.xml') {
const filePath = path.join(targetDir, file);
const data = fs.readFileSync(filePath, 'utf-8');
const component = parser.parse(data) as XmlData;
let isUpdated = false;

if ('ApexClass' in component) {
if (parseFloat(component.ApexClass.apiVersion) < parseFloat(version)) {
component.ApexClass.apiVersion = version;
isUpdated = true;
}
} else if ('ApexTrigger' in component) {
if (parseFloat(component.ApexTrigger.apiVersion) < parseFloat(version)) {
ux.stdout(parseFloat(component.ApexTrigger.apiVersion).toString());
component.ApexTrigger.apiVersion = version;
isUpdated = true;
}
} else if ('Flow' in component) {
if (parseFloat(component.Flow.apiVersion) < parseFloat(version)) {
component.Flow.apiVersion = version;
isUpdated = true;
}
if ('ApexClass' in component) {
if (parseFloat(component.ApexClass.apiVersion) < parseFloat(version)) {
component.ApexClass.apiVersion = version;
isUpdated = true;
}

if (isUpdated) {
const updatedXml = builder.build(component) as string;
// remove blank lines
const cleanedXml = (updatedXml.replace(/\n\s*\n/g, '\n'));
fs.writeFileSync(filePath, cleanedXml);
} else if ('ApexTrigger' in component) {
if (parseFloat(component.ApexTrigger.apiVersion) < parseFloat(version)) {
ux.stdout(parseFloat(component.ApexTrigger.apiVersion).toString());
component.ApexTrigger.apiVersion = version;
isUpdated = true;
}
} else if ('Flow' in component) {
if (parseFloat(component.Flow.apiVersion) < parseFloat(version)) {
component.Flow.apiVersion = version;
isUpdated = true;
}
}

if (isUpdated) {
const updatedXml = builder.build(component) as string;
// remove blank lines
const cleanedXml = updatedXml.replace(/\n\s*\n/g, '\n');
fs.writeFileSync(filePath, cleanedXml);
updatedNumber++;
}
}
} catch (error: unknown) {
ux.error(error as Error);
}
} catch (error: unknown) {
ux.error(error as Error);
}

return updatedNumber;
});

return 0;
};
return updatedNumber;
};
20 changes: 0 additions & 20 deletions test/commands/kc/update-api.nut.ts

This file was deleted.

117 changes: 88 additions & 29 deletions test/commands/kc/update-api.test.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,99 @@
import { TestContext } from '@salesforce/core/testSetup';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import * as fs from 'node:fs';
import { expect } from 'chai';
import { stubSfCommandUx } from '@salesforce/sf-plugins-core';
import { ux } from '@oclif/core';
import KcUpdateApi from '../../../src/commands/kc/update-api.js';

describe('kc update-api', () => {
const $$ = new TestContext();
let sfCommandStubs: ReturnType<typeof stubSfCommandUx>;

beforeEach(() => {
sfCommandStubs = stubSfCommandUx($$.SANDBOX);
it('runs update-api and throws error if no api-version is provided', async () => {
try {
const output = await KcUpdateApi.run(['--type', 'classes']);
expect.fail(`Should throw an error. Response: ${JSON.stringify(output)}`);
} catch (e) {
expect((e as Error).message).to.include('Missing required flag api-version');
}
});

afterEach(() => {
$$.restore();
it('runs update-api and throws error if no type is provided', async () => {
try {
const output = await KcUpdateApi.run(['--api-version', '61.0']);
expect.fail(`Should throw an error. Response: ${JSON.stringify(output)}`);
} catch (e) {
expect((e as Error).message).to.include('Missing required flag type');
}
});

it('runs update-api', async () => {
await KcUpdateApi.run(['--type', 'classes', '--api-version', '61.0']);
const output = sfCommandStubs.log
.getCalls()
.flatMap((c) => c.args)
.join('\n');
expect(output).to.include('');
it('runs update-api and throws error if invalid api version given', async () => {
try {
const output = await KcUpdateApi.run(['--type', 'classes', '--api-version', '10.0']);
expect.fail(`Should throw an error. Response: ${JSON.stringify(output)}`);
} catch (e) {
expect((e as Error).message).to.include('The API version must be greater than');
}
});

// it('runs hello with --json and no provided name', async () => {
// const result = await KcUpdateApi.run([]);
// expect(result.updatedNumber).to.equal(0);
// });

// it('runs hello world --name Astro', async () => {
// await KcUpdateApi.run(['--name', 'Astro']);
// const output = sfCommandStubs.log
// .getCalls()
// .flatMap((c) => c.args)
// .join('\n');
// expect(output).to.include('hello Astro');
// });
describe('update xml files', () => {
const filename = fileURLToPath(import.meta.url);
const fileDirName = dirname(filename);
const xmlFilesPath = join(fileDirName, '../../xml');
const xmlBackupPath = join(fileDirName, '../../xmlBackup');

beforeEach(async () => {
try {
fs.cpSync(xmlFilesPath, xmlBackupPath, { recursive: true });
if (!fs.existsSync(xmlBackupPath)) {
throw new Error(`Backup XML directory does not exist: ${xmlFilesPath}`);
}
} catch (error: unknown) {
ux.error(error as Error);
}
});

afterEach(async () => {
try {
fs.rmSync(xmlFilesPath, { recursive: true, force: true });
fs.cpSync(xmlBackupPath, xmlFilesPath, { recursive: true });
fs.rmSync(xmlBackupPath, { recursive: true, force: true });
} catch (error: unknown) {
ux.error(error as Error);
}
});

it('runs update-api to update 2 apex classes', async () => {
const result = await KcUpdateApi.run(['-d', xmlFilesPath, '--type', 'classes', '--api-version', '61.0']);
expect(result.updatedNumber).to.equal(2);
});

it('runs update-api to update 1 apex class', async () => {
const result = await KcUpdateApi.run(['-d', xmlFilesPath, '--type', 'classes', '--api-version', '58.0']);
expect(result.updatedNumber).to.equal(1);
});

it('runs update-api to update 1 trigger', async () => {
const result = await KcUpdateApi.run(['-d', xmlFilesPath, '--type', 'triggers', '--api-version', '60.0']);
expect(result.updatedNumber).to.equal(1);
});

it('runs update-api to update 1 flow', async () => {
const result = await KcUpdateApi.run(['-d', xmlFilesPath, '--type', 'flows', '--api-version', '60.0']);
expect(result.updatedNumber).to.equal(1);
});

it('runs update-api to update classes, triggers, and flows', async () => {
const result = await KcUpdateApi.run([
'-d',
xmlFilesPath,
'--type',
'classes',
'--type',
'triggers',
'--type',
'flows',
'--api-version',
'60.0',
]);
expect(result.updatedNumber).to.equal(4);
});
});
});
5 changes: 5 additions & 0 deletions test/xml/classes/AccountHelper.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>57.0</apiVersion>
<status>Active</status>
</ApexClass>
5 changes: 5 additions & 0 deletions test/xml/classes/AccountHelper_Test.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>58.0</apiVersion>
<status>Active</status>
</ApexClass>
5 changes: 5 additions & 0 deletions test/xml/flows/Test_Opp_Flow.flow-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>58.0</apiVersion>
<status>Active</status>
</Flow>
5 changes: 5 additions & 0 deletions test/xml/triggers/AccountTrigger.trigger-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexTrigger xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>58.0</apiVersion>
<status>Active</status>
</ApexTrigger>

0 comments on commit d9dc112

Please sign in to comment.