Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ apexdocs openapi -s force-app -t docs -n MyNamespace --title "My Custom OpenApi
| `--targetDir` | `-t` | The directory location where the changelog file will be generated. | `./docs/` | No |
| `--fileName` | N/A | The name of the changelog file to be generated. | `changelog` | No |
| `--scope` | N/A | The list of scope to respect when generating the changelog. | ['global'] | No |
| `--skipIfNoChanges` | N/A | Whether to skip generating the changelog if there are no changes. | `true` | No |

#### Sample Usage

Expand Down Expand Up @@ -263,9 +264,8 @@ export default {

Then you only need to run the top level `apexdocs` command, and it will generate both types of documentation.

```bash
apexdocs
```
Note that you can still run the individual commands if you only want to generate one type of documentation by
providing the subcommand, e.g `apexdocs markdown` or `apexdocs changelog`.

### Excluding Tags from Appearing in the Documentation

Expand Down
63 changes: 63 additions & 0 deletions examples/changelog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Changelog Example

This project contains an example on how to generate a changelog using ApexDocs.'

It has 2 directories: `previous`, which contains the previous version of the project, and `current`, which contains the current version of the project.

By running the following command, you can generate a changelog between the two versions:

```bash
apexdocs changelog --previousVersionDir previous --currentVersionDir current
```

---

## Generating PR Comments with Changelog Information

One example of how to use the changelog feature is to generate a changelog for a pull request.

You can achieve this through Github Actions by using a workflow that looks as follows:

```yaml
on:
pull_request:
branches: [ main ]
types: [ opened, reopened, synchronize, closed ]

jobs:
comment-pr:
runs-on: ubuntu-latest
name: Comment PR
permissions:
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Checkout previous version
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.base.ref }}
path: previous

- uses: actions/setup-node@v2
with:
node-version: "20"

- name: Install dependencies
run: npm install

- name: Install ApexDocs globally
run: npm install @cparra/apexdocs --global

- name: Generate changelog
# Change the previousVersionDir and currentVersionDir to match your project structure
run: apexdocs changelog --currentVersionDir force-app --previousVersionDir './previous/force-app' --targetDir './changelog'

- name: Comment PR
uses: thollander/actions-comment-pull-request@v2
with:
# Providing a comment_tag will update the comment if it already exists
comment_tag: 'changelog'
filePath: './changelog/changelog.md'
```
14 changes: 7 additions & 7 deletions examples/vitepress/docs/.vitepress/cache/deps/_metadata.json
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
{
"hash": "98853dc2",
"hash": "f2216e85",
"configHash": "7f7b0dad",
"lockfileHash": "2250ed1c",
"browserHash": "0b73d2e4",
"lockfileHash": "76121266",
"browserHash": "441a8d6a",
"optimized": {
"vue": {
"src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "dd9e77dd",
"fileHash": "885cbaa9",
"needsInterop": false
},
"vitepress > @vue/devtools-api": {
"src": "../../../../node_modules/@vue/devtools-api/dist/index.js",
"file": "vitepress___@vue_devtools-api.js",
"fileHash": "522b9fac",
"fileHash": "ff3ba36c",
"needsInterop": false
},
"vitepress > @vueuse/core": {
"src": "../../../../node_modules/@vueuse/core/index.mjs",
"file": "vitepress___@vueuse_core.js",
"fileHash": "5b6312b7",
"fileHash": "b6cc6d79",
"needsInterop": false
},
"@theme/index": {
"src": "../../../../node_modules/vitepress/dist/client/theme-default/index.js",
"file": "@theme_index.js",
"fileHash": "8adc2f2d",
"fileHash": "6b17bcd7",
"needsInterop": false
}
},
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cparra/apexdocs",
"version": "3.2.2",
"version": "3.3.0",
"description": "Library with CLI capabilities to generate documentation for Salesforce Apex classes.",
"keywords": [
"apex",
Expand Down Expand Up @@ -61,7 +61,7 @@
]
},
"dependencies": {
"@cparra/apex-reflection": "2.13.1",
"@cparra/apex-reflection": "2.15.0",
"@types/js-yaml": "^4.0.9",
"@types/yargs": "^17.0.32",
"chalk": "^4.1.2",
Expand Down
1 change: 0 additions & 1 deletion src/application/Apexdocs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ async function processChangeLog(config: UserDefinedChangelogConfig) {
return pipe(
TE.tryCatch(loadFiles, (e) => new FileReadingError('An error occurred while reading files.', e)),
TE.flatMap(([previous, current]) => changelog(previous, current, config)),
TE.map(() => '✔️ Changelog generated successfully!'),
TE.mapLeft(toErrors),
);
}
Expand Down
17 changes: 12 additions & 5 deletions src/application/generators/changelog.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import { pipe } from 'fp-ts/function';
import { PageData, UnparsedSourceFile, UserDefinedChangelogConfig } from '../../core/shared/types';
import { PageData, Skip, UnparsedSourceFile, UserDefinedChangelogConfig } from '../../core/shared/types';
import * as TE from 'fp-ts/TaskEither';
import { writeFiles } from '../file-writer';
import { ChangeLogPageData, generateChangeLog } from '../../core/changelog/generate-change-log';
import { FileWritingError } from '../errors';
import { isSkip } from '../../core/shared/utils';

export default function generate(
oldBundles: UnparsedSourceFile[],
newBundles: UnparsedSourceFile[],
config: UserDefinedChangelogConfig,
) {
return pipe(
generateChangeLog(oldBundles, newBundles, config),
TE.flatMap((files) => writeFilesToSystem(files, config.targetDir)),
);
function handleFile(file: ChangeLogPageData | Skip) {
if (isSkip(file)) {
return TE.right('✔️ Done! Skipped writing files to the system.');
}

return writeFilesToSystem(file, config.targetDir);
}

return pipe(generateChangeLog(oldBundles, newBundles, config), TE.flatMap(handleFile));
}

function writeFilesToSystem(pageData: ChangeLogPageData, outputDir: string) {
return pipe(
[pageData],
(files) => writeFiles(files as PageData[], outputDir),
TE.map(() => '✔️ Changelog generated successfully!'),
TE.mapLeft((error) => {
return new FileWritingError('An error occurred while writing files to the system.', error);
}),
Expand Down
13 changes: 11 additions & 2 deletions src/cli/__tests__/args/multiple-command-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {

describe('when extracting arguments', () => {
describe('and a configuration is provided for multiple commands', () => {
it('errors when a command was still passed through the cli', async () => {
it('if a subcommand was specified through the cli, it only extract the specified subcommand', async () => {
function getFromProcess() {
return ['markdown'];
}
Expand All @@ -20,13 +20,22 @@ describe('when extracting arguments', () => {
markdown: {
sourceDir: 'force-app',
},
openapi: {
sourceDir: 'force-app',
},
},
});
}

const result = await extractArgs(getFromProcess, extractConfig);

expect(E.isLeft(result)).toBeTruthy();
assertEither(result, (configs) => {
expect(configs).toHaveLength(1);
expect(configs[0].targetGenerator).toEqual('markdown');

const markdownConfig = configs[0] as UserDefinedMarkdownConfig;
expect(markdownConfig.sourceDir).toEqual('force-app');
});
});

it('extracts multiple configurations', async () => {
Expand Down
76 changes: 40 additions & 36 deletions src/cli/args.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { cosmiconfig } from 'cosmiconfig';
import * as yargs from 'yargs';
import yargs = require('yargs');
import * as E from 'fp-ts/Either';
import {
Generators,
Expand Down Expand Up @@ -108,26 +108,41 @@ type ConfigByGenerator = {
function extractArgsForCommandsProvidedInConfig(
extractFromProcessFn: ExtractArgsFromProcess,
config: ConfigByGenerator,
) {
const configs = Object.entries(config).map(([generator, generatorConfig]) => {
switch (generator as Generators) {
case 'markdown':
return pipe(
validateMultiCommandConfig(extractFromProcessFn, 'markdown', generatorConfig),
E.map(() => ({ ...configOnlyMarkdownDefaults, ...generatorConfig })),
);
case 'openapi':
return pipe(
validateMultiCommandConfig(extractFromProcessFn, 'openapi', generatorConfig),
E.map(() => ({ ...configOnlyOpenApiDefaults, ...generatorConfig })),
);
case 'changelog':
return pipe(
validateMultiCommandConfig(extractFromProcessFn, 'changelog', generatorConfig),
E.map(() => ({ ...configOnlyChangelogDefaults, ...generatorConfig })),
);
}
});
): E.Either<Error, readonly UserDefinedConfig[]> {
const providedThroughCli = yargs.parseSync(extractFromProcessFn());
const hasACommandBeenProvided = providedThroughCli._.length > 0;

const configs = Object.entries(config)
// If no specific command was provided through the CLI, we will generate all the commands.
// Otherwise, we only want to generate the command that was provided.
.filter(([generator]) => (hasACommandBeenProvided ? providedThroughCli._[0] === generator : true))
.map(([generator, generatorConfig]) => {
switch (generator as Generators) {
case 'markdown':
return pipe(
extractMultiCommandConfig(extractFromProcessFn, 'markdown', generatorConfig),
E.map((cliArgs) => {
console.log('markdown', cliArgs);
return cliArgs;
}),
E.map((cliArgs) => ({ ...configOnlyMarkdownDefaults, ...generatorConfig, ...cliArgs })),
);
case 'openapi':
return pipe(
extractMultiCommandConfig(extractFromProcessFn, 'openapi', generatorConfig),
E.map((cliArgs) => ({ ...configOnlyOpenApiDefaults, ...generatorConfig, ...cliArgs })),
);
case 'changelog':
return pipe(
extractMultiCommandConfig(extractFromProcessFn, 'changelog', generatorConfig),
E.map((cliArgs) => {
console.log('changelog', cliArgs);
return cliArgs;
}),
E.map((cliArgs) => ({ ...configOnlyChangelogDefaults, ...generatorConfig, ...cliArgs })),
);
}
});

return E.sequenceArray(configs);
}
Expand Down Expand Up @@ -192,7 +207,7 @@ function extractYargsDemandingCommand(extractFromProcessFn: ExtractArgsFromProce
.parseSync(extractFromProcessFn());
}

function validateMultiCommandConfig(
function extractMultiCommandConfig(
extractFromProcessFn: ExtractArgsFromProcess,
command: Generators,
config: UserDefinedConfig,
Expand All @@ -209,25 +224,14 @@ function validateMultiCommandConfig(
}

const options = getOptions(command);
console.log('config', config);
return E.tryCatch(() => {
yargs
return yargs(extractFromProcessFn())
.config(config)
.options(options)
.check((argv) => {
// we should not be receiving a command here
// since this is a multi-command config
if (argv._.length > 0) {
throw new Error(
`Unexpected command "${argv._[0]}".
The command name should be provided in the configuration when using the current configuration format.`,
);
} else {
return true;
}
})
.fail((msg) => {
throw new Error(`Invalid configuration for command "${command}": ${msg}`);
})
.parse(extractFromProcessFn());
.parseSync();
}, E.toError);
}
5 changes: 5 additions & 0 deletions src/cli/commands/changelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,9 @@ export const changeLogOptions: { [key: string]: Options } = {
'Values should be separated by a space, e.g --scope global public namespaceaccessible. ' +
'Annotations are supported and should be passed lowercased and without the @ symbol, e.g. namespaceaccessible auraenabled.',
},
skipIfNoChanges: {
type: 'boolean',
default: changeLogDefaults.skipIfNoChanges,
describe: 'Skip the changelog generation if there are no changes between the previous and current version.',
},
};
Loading
Loading