@cparra/apexdocs 3.0.0-alpha.9 → 3.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +442 -325
  3. package/dist/cli/generate.js +295 -205
  4. package/dist/index.d.ts +15 -17
  5. package/examples/markdown/docs/miscellaneous/Url.md +10 -8
  6. package/examples/markdown/force-app/classes/Url.cls +3 -1
  7. package/examples/markdown-jsconfig/.forceignore +12 -0
  8. package/examples/markdown-jsconfig/apexdocs.config.mjs +21 -0
  9. package/examples/markdown-jsconfig/config/project-scratch-def.json +5 -0
  10. package/examples/markdown-jsconfig/docs/index.md +12 -0
  11. package/examples/markdown-jsconfig/docs/miscellaneous/Url.md +315 -0
  12. package/examples/markdown-jsconfig/force-app/classes/Url.cls +196 -0
  13. package/examples/markdown-jsconfig/package-lock.json +665 -0
  14. package/examples/markdown-jsconfig/package.json +15 -0
  15. package/examples/markdown-jsconfig/sfdx-project.json +12 -0
  16. package/examples/open-api/config/project-scratch-def.json +13 -0
  17. package/examples/open-api/docs/openapi.json +582 -0
  18. package/examples/{force-app → open-api/force-app}/main/default/classes/SampleClass.cls +1 -0
  19. package/examples/open-api/package-lock.json +724 -0
  20. package/examples/open-api/package.json +20 -0
  21. package/examples/open-api/sfdx-project.json +12 -0
  22. package/examples/vitepress/apexdocs.config.ts +7 -2
  23. package/examples/vitepress/docs/index.md +11 -11
  24. package/examples/vitepress/docs/miscellaneous/BaseClass.md +1 -1
  25. package/examples/vitepress/docs/miscellaneous/MultiInheritanceClass.md +2 -2
  26. package/examples/vitepress/docs/miscellaneous/SampleException.md +1 -1
  27. package/examples/vitepress/docs/miscellaneous/SampleInterface.md +6 -6
  28. package/examples/vitepress/docs/miscellaneous/Url.md +3 -3
  29. package/examples/vitepress/docs/sample-enums/SampleEnum.md +3 -3
  30. package/examples/vitepress/docs/samplegroup/SampleClass.md +5 -5
  31. package/examples/vitepress/force-app/main/default/classes/SampleClass.cls +1 -1
  32. package/package.json +2 -2
  33. package/src/application/Apexdocs.ts +39 -7
  34. package/src/application/__tests__/apex-file-reader.spec.ts +0 -17
  35. package/src/application/file-writer.ts +37 -15
  36. package/src/application/generators/markdown.ts +10 -39
  37. package/src/application/generators/openapi.ts +22 -6
  38. package/src/cli/args.ts +4 -1
  39. package/src/cli/commands/markdown.ts +1 -3
  40. package/src/cli/commands/openapi.ts +36 -0
  41. package/src/core/markdown/__test__/generating-class-docs.spec.ts +1 -129
  42. package/src/core/markdown/__test__/generating-docs.spec.ts +111 -0
  43. package/src/core/markdown/__test__/generating-enum-docs.spec.ts +0 -64
  44. package/src/core/markdown/__test__/generating-interface-docs.spec.ts +0 -64
  45. package/src/core/markdown/adapters/documentables.ts +0 -1
  46. package/src/core/markdown/generate-docs.ts +2 -5
  47. package/src/core/markdown/reflection/__test__/filter-scope.spec.ts +306 -0
  48. package/src/core/markdown/reflection/reflect-source.ts +51 -50
  49. package/src/core/openApiSettings.ts +41 -0
  50. package/src/core/openapi/__tests__/open-api-docs-processor.spec.ts +2 -2
  51. package/src/core/openapi/open-api-docs-processor.ts +8 -4
  52. package/src/core/openapi/open-api.ts +5 -1
  53. package/src/core/openapi/openapi-type-file.ts +1 -1
  54. package/src/core/openapi/parser.ts +1 -15
  55. package/src/core/openapi/transpiler.ts +0 -5
  56. package/src/core/parse-apex-metadata.ts +21 -5
  57. package/src/core/shared/types.d.ts +18 -17
  58. package/src/index.ts +23 -10
  59. package/src/test-helpers/SettingsBuilder.ts +2 -6
  60. package/examples/force-app/main/default/classes/AnotherInterface.cls +0 -16
  61. package/examples/force-app/main/default/classes/EscapedAnnotations.cls +0 -5
  62. package/examples/force-app/main/default/classes/GrandparentClass.cls +0 -5
  63. package/examples/force-app/main/default/classes/GroupedClass.cls +0 -8
  64. package/examples/force-app/main/default/classes/InterfaceWithInheritance.cls +0 -1
  65. package/examples/force-app/main/default/classes/MemberGrouping.cls +0 -17
  66. package/examples/force-app/main/default/classes/ParentClass.cls +0 -16
  67. package/examples/force-app/main/default/classes/SampleClass.cls-meta.xml +0 -5
  68. package/examples/force-app/main/default/classes/SampleClassWithoutModifier.cls +0 -9
  69. package/examples/force-app/main/default/classes/SampleInterface.cls +0 -16
  70. package/src/core/settings.ts +0 -56
  71. /package/examples/{force-app → open-api/force-app}/main/default/classes/ChildClass.cls +0 -0
  72. /package/examples/{force-app → open-api/force-app}/main/default/restapi/SampleRestResource.cls +0 -0
  73. /package/examples/{force-app → open-api/force-app}/main/default/restapi/SampleRestResourceToSkip.cls +0 -0
  74. /package/examples/{force-app → open-api/force-app}/main/default/restapi/SampleRestResourceWithInnerClass.cls +0 -0
  75. /package/examples/{force-app → open-api/force-app}/main/default/restapi/SampleRestResourceWithoutApexDocs.cls +0 -0
  76. /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference1.cls +0 -0
  77. /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference2.cls +0 -0
  78. /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference3.cls +0 -0
  79. /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference4.cls +0 -0
  80. /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference5.cls +0 -0
  81. /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference6.cls +0 -0
  82. /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference7.cls +0 -0
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "openapi-example",
3
+ "scripts": {
4
+ "docs:clean": "rimraf docs",
5
+ "docs:gen": "ts-node ../../src/cli/generate.ts openapi",
6
+ "apexdocs:build": "npm run docs:clean && npm run docs:gen"
7
+ },
8
+ "devDependencies": {
9
+ "ts-node": "^10.9.2"
10
+ },
11
+ "dependencies": {
12
+ "rimraf": "^5.0.7"
13
+ },
14
+ "apexdocs": {
15
+ "sourceDir": "force-app",
16
+ "title": "Open API Example",
17
+ "namespace": "openapi",
18
+ "apiVersion": "3.0.0"
19
+ }
20
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "packageDirectories": [
3
+ {
4
+ "path": "force-app",
5
+ "default": true
6
+ }
7
+ ],
8
+ "name": "openapi",
9
+ "namespace": "",
10
+ "sfdcLoginUrl": "https://login.salesforce.com",
11
+ "sourceApiVersion": "61.0"
12
+ }
@@ -29,10 +29,15 @@ export default defineMarkdownConfig({
29
29
  sourceDir: 'force-app',
30
30
  scope: ['global', 'public', 'protected', 'private', 'namespaceaccessible'],
31
31
  namespace: 'apexdocs',
32
- transformReferenceGuide: async (referenceGuide) => {
32
+ transformReference: (reference) => {
33
+ return {
34
+ // remove the trailing .md
35
+ referencePath: reference.referencePath.replace(/\.md$/, ''),
36
+ };
37
+ },
38
+ transformReferenceGuide: async () => {
33
39
  const frontMatter = await loadFileAsync('./docs/index-frontmatter.md');
34
40
  return {
35
- ...referenceGuide,
36
41
  frontmatter: frontMatter,
37
42
  };
38
43
  },
@@ -19,38 +19,38 @@ hero:
19
19
 
20
20
  ## Miscellaneous
21
21
 
22
- ### [BaseClass](miscellaneous/BaseClass.md)
22
+ ### [BaseClass](miscellaneous/BaseClass)
23
23
 
24
- ### [MultiInheritanceClass](miscellaneous/MultiInheritanceClass.md)
24
+ ### [MultiInheritanceClass](miscellaneous/MultiInheritanceClass)
25
25
 
26
- ### [ParentInterface](miscellaneous/ParentInterface.md)
26
+ ### [ParentInterface](miscellaneous/ParentInterface)
27
27
 
28
- ### [ReferencedEnum](miscellaneous/ReferencedEnum.md)
28
+ ### [ReferencedEnum](miscellaneous/ReferencedEnum)
29
29
 
30
- ### [SampleException](miscellaneous/SampleException.md)
30
+ ### [SampleException](miscellaneous/SampleException)
31
31
 
32
32
  This is a sample exception.
33
33
 
34
- ### [SampleInterface](miscellaneous/SampleInterface.md)
34
+ ### [SampleInterface](miscellaneous/SampleInterface)
35
35
 
36
36
  This is a sample interface
37
37
 
38
- ### [Url](miscellaneous/Url.md)
38
+ ### [Url](miscellaneous/Url)
39
39
 
40
40
  Represents a uniform resource locator (URL) and provides access to parts of the URL.
41
41
  Enables access to the base URL used to access your Salesforce org.
42
42
 
43
43
  ## Sample Enums
44
44
 
45
- ### [SampleEnum](sample-enums/SampleEnum.md)
45
+ ### [SampleEnum](sample-enums/SampleEnum)
46
46
 
47
- This is a sample enum. This references [ReferencedEnum](miscellaneous/ReferencedEnum.md) .
47
+ This is a sample enum. This references [ReferencedEnum](miscellaneous/ReferencedEnum) .
48
48
 
49
49
  This description has several lines
50
50
 
51
51
  ## SampleGroup
52
52
 
53
- ### [SampleClass](samplegroup/SampleClass.md)
53
+ ### [SampleClass](samplegroup/SampleClass)
54
54
 
55
55
  aliquip ex sunt officia ullamco anim deserunt magna aliquip nisi eiusmod in sit officia veniam ex
56
- deserunt ea officia exercitation laboris enim in duis quis enim eiusmod eu amet cupidatat.
56
+ **deserunt** ea officia exercitation laboris enim in duis quis enim eiusmod eu amet cupidatat.
@@ -17,4 +17,4 @@ public sampleEnumFromBase
17
17
  ```
18
18
 
19
19
  #### Type
20
- [SampleEnum](../sample-enums/SampleEnum.md)
20
+ [SampleEnum](../sample-enums/SampleEnum)
@@ -9,7 +9,7 @@ apexdocs
9
9
 
10
10
  **Inheritance**
11
11
 
12
- [SampleClass](../samplegroup/SampleClass.md) < [BaseClass](BaseClass.md)
12
+ [SampleClass](../samplegroup/SampleClass) < [BaseClass](BaseClass)
13
13
 
14
14
  ## Fields
15
15
  ### `sampleEnumFromBase`
@@ -22,7 +22,7 @@ public sampleEnumFromBase
22
22
  ```
23
23
 
24
24
  #### Type
25
- [SampleEnum](../sample-enums/SampleEnum.md)
25
+ [SampleEnum](../sample-enums/SampleEnum)
26
26
 
27
27
  ## Properties
28
28
  ### Group Name
@@ -9,7 +9,7 @@ This is a sample exception.
9
9
  **Usage**
10
10
 
11
11
  You can use the exception the following way.
12
- You can also take a look at [SampleClass](../samplegroup/SampleClass.md) to see how it is used.
12
+ You can also take a look at [SampleClass](../samplegroup/SampleClass) to see how it is used.
13
13
  This is a dangerous HTML tag: &lt;script&gt;alert(&#x27;Hello&#x27;);&lt;/script&gt;
14
14
 
15
15
  ```apex
@@ -19,9 +19,9 @@ C --&gt;|extends| D[GreatGrandParentInterface]
19
19
 
20
20
  **Date** 2020-01-01
21
21
 
22
- **See** [SampleEnum](../sample-enums/SampleEnum.md)
22
+ **See** [SampleEnum](../sample-enums/SampleEnum)
23
23
 
24
- **See** [ReferencedEnum](ReferencedEnum.md)
24
+ **See** [ReferencedEnum](ReferencedEnum)
25
25
 
26
26
  ## Namespace
27
27
  apexdocs
@@ -31,7 +31,7 @@ SampleInterface sampleInterface &#x3D; new SampleInterface();
31
31
  sampleInterface.sampleMethod();
32
32
 
33
33
  **Extends**
34
- [ParentInterface](ParentInterface.md)
34
+ [ParentInterface](ParentInterface)
35
35
 
36
36
  ## Methods
37
37
  ### `sampleMethod()`
@@ -66,7 +66,7 @@ public String sampleMethod()
66
66
  Some return value
67
67
 
68
68
  #### Throws
69
- [SampleException](SampleException.md): This is a sample exception
69
+ [SampleException](SampleException): This is a sample exception
70
70
 
71
71
  AnotherSampleException: This is another sample exception
72
72
 
@@ -94,10 +94,10 @@ public SampleEnum sampleMethodWithParams(String param1, Integer param2, SampleEn
94
94
  |------|------|-------------|
95
95
  | param1 | String | This is the first parameter |
96
96
  | param2 | Integer | This is the second parameter |
97
- | theEnum | [SampleEnum](../sample-enums/SampleEnum.md) | This is an enum parameter |
97
+ | theEnum | [SampleEnum](../sample-enums/SampleEnum) | This is an enum parameter |
98
98
 
99
99
  #### Return Type
100
- **[SampleEnum](../sample-enums/SampleEnum.md)**
100
+ **[SampleEnum](../sample-enums/SampleEnum)**
101
101
 
102
102
  Some return value
103
103
 
@@ -120,7 +120,7 @@ global Url(Url context, String spec)
120
120
  #### Parameters
121
121
  | Name | Type | Description |
122
122
  |------|------|-------------|
123
- | context | [Url](Url.md) | The context in which to parse the specification. |
123
+ | context | [Url](Url) | The context in which to parse the specification. |
124
124
  | spec | String | The string to parse as a URL. |
125
125
 
126
126
  ---
@@ -191,7 +191,7 @@ global static Url getCurrentRequestUrl()
191
191
  ```
192
192
 
193
193
  #### Return Type
194
- **[Url](Url.md)**
194
+ **[Url](Url)**
195
195
 
196
196
  The URL of the entire request.
197
197
 
@@ -299,7 +299,7 @@ global static Url getOrgDomainUrl()
299
299
  ```
300
300
 
301
301
  #### Return Type
302
- **[Url](Url.md)**
302
+ **[Url](Url)**
303
303
 
304
304
  getOrgDomainUrl() always returns the login URL for your org, regardless of context. Use that URL when making API calls to your org.
305
305
 
@@ -6,13 +6,13 @@ title: SampleEnum
6
6
 
7
7
  `NAMESPACEACCESSIBLE`
8
8
 
9
- This is a sample enum. This references [ReferencedEnum](../miscellaneous/ReferencedEnum.md) .
9
+ This is a sample enum. This references [ReferencedEnum](../miscellaneous/ReferencedEnum) .
10
10
 
11
11
  This description has several lines
12
12
 
13
13
  **Some Custom**
14
14
 
15
- Test. I can also have a [ReferencedEnum](../miscellaneous/ReferencedEnum.md) here.
15
+ Test. I can also have a [ReferencedEnum](../miscellaneous/ReferencedEnum) here.
16
16
  And it can be multiline.
17
17
 
18
18
  **Mermaid**
@@ -27,7 +27,7 @@ B --&gt;|referenced by| A
27
27
 
28
28
  **Date** 2022-01-01
29
29
 
30
- **See** [ReferencedEnum](../miscellaneous/ReferencedEnum.md)
30
+ **See** [ReferencedEnum](../miscellaneous/ReferencedEnum)
31
31
 
32
32
  ## Namespace
33
33
  apexdocs
@@ -6,7 +6,7 @@ title: SampleClass
6
6
  `virtual`
7
7
 
8
8
  aliquip ex sunt officia ullamco anim deserunt magna aliquip nisi eiusmod in sit officia veniam ex
9
- deserunt ea officia exercitation laboris enim in duis quis enim eiusmod eu amet cupidatat.
9
+ **deserunt** ea officia exercitation laboris enim in duis quis enim eiusmod eu amet cupidatat.
10
10
 
11
11
  **Group** SampleGroup
12
12
 
@@ -19,12 +19,12 @@ sample.doSomething();
19
19
 
20
20
  **Inheritance**
21
21
 
22
- [BaseClass](../miscellaneous/BaseClass.md)
22
+ [BaseClass](../miscellaneous/BaseClass)
23
23
 
24
24
  **Implements**
25
25
 
26
- [SampleInterface](../miscellaneous/SampleInterface.md),
27
- [ParentInterface](../miscellaneous/ParentInterface.md)
26
+ [SampleInterface](../miscellaneous/SampleInterface),
27
+ [ParentInterface](../miscellaneous/ParentInterface)
28
28
 
29
29
  ## Fields
30
30
  ### Group Name
@@ -51,7 +51,7 @@ public sampleEnumFromBase
51
51
  ```
52
52
 
53
53
  ##### Type
54
- [SampleEnum](../sample-enums/SampleEnum.md)
54
+ [SampleEnum](../sample-enums/SampleEnum)
55
55
 
56
56
  ## Properties
57
57
  ### Group Name
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @description aliquip ex sunt officia ullamco anim deserunt magna aliquip nisi eiusmod in sit officia veniam ex
3
- * deserunt ea officia exercitation laboris enim in duis quis enim eiusmod eu amet cupidatat.
3
+ * **deserunt** ea officia exercitation laboris enim in duis quis enim eiusmod eu amet cupidatat.
4
4
  * @group SampleGroup
5
5
  * @example
6
6
  * SampleClass sample = new SampleClass();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cparra/apexdocs",
3
- "version": "3.0.0-alpha.9",
3
+ "version": "3.0.0-rc.0",
4
4
  "description": "Library with CLI capabilities to generate documentation for Salesforce Apex classes.",
5
5
  "keywords": [
6
6
  "apex",
@@ -17,6 +17,7 @@
17
17
  },
18
18
  "scripts": {
19
19
  "test": "npm run build && jest",
20
+ "test:cov": "npm run build && jest --coverage",
20
21
  "build": "rimraf ./lib && npm run lint && tsc --noEmit && pkgroll",
21
22
  "lint": "eslint \"./src/**/*.{js,ts}\" --quiet --fix",
22
23
  "prepare": "npm run build",
@@ -68,7 +69,6 @@
68
69
  "fp-ts": "^2.16.8",
69
70
  "handlebars": "^4.7.8",
70
71
  "js-yaml": "^4.1.0",
71
- "type-fest": "^4.23.0",
72
72
  "yargs": "^17.7.2"
73
73
  },
74
74
  "imports": {
@@ -4,7 +4,10 @@ import openApi from './generators/openapi';
4
4
  import { ApexFileReader } from './apex-file-reader';
5
5
  import { DefaultFileSystem } from './file-system';
6
6
  import { Logger } from '#utils/logger';
7
- import { UserDefinedConfig } from '../core/shared/types';
7
+ import { UnparsedSourceFile, UserDefinedConfig, UserDefinedMarkdownConfig } from '../core/shared/types';
8
+ import { pipe } from 'fp-ts/function';
9
+ import * as TE from 'fp-ts/TaskEither';
10
+ import { ReflectionError } from '../core/markdown/reflection/reflect-source';
8
11
 
9
12
  /**
10
13
  * Application entry-point to generate documentation out of Apex source files.
@@ -20,21 +23,50 @@ export class Apexdocs {
20
23
  const fileBodies = await ApexFileReader.processFiles(
21
24
  new DefaultFileSystem(),
22
25
  config.sourceDir,
23
- config.includeMetadata,
26
+ config.targetGenerator === 'markdown' ? config.includeMetadata : false,
24
27
  );
25
28
 
26
29
  switch (config.targetGenerator) {
27
30
  case 'markdown':
28
- await markdown(fileBodies, config);
31
+ await generateMarkdownDocumentation(fileBodies, config)();
29
32
  break;
30
33
  case 'openapi':
31
- openApi(fileBodies, config);
34
+ await openApi(fileBodies, config);
32
35
  break;
33
36
  }
34
-
35
- Logger.logSingle('✔️ Documentation generated successfully!');
36
37
  } catch (error) {
37
- Logger.logSingle('❌ An error occurred while generating the documentation.', 'red');
38
+ Logger.logSingle(`❌ An error occurred while generating the documentation: ${error}`, 'red');
38
39
  }
39
40
  }
40
41
  }
42
+
43
+ function generateMarkdownDocumentation(fileBodies: UnparsedSourceFile[], config: UserDefinedMarkdownConfig) {
44
+ return pipe(
45
+ markdown(fileBodies, config),
46
+ TE.map(() => Logger.logSingle('✔️ Documentation generated successfully!')),
47
+ TE.mapLeft((error) => {
48
+ if (error._tag === 'HookError') {
49
+ Logger.error('Error(s) occurred while processing hooks. Please review the following issues:');
50
+ Logger.error(error.error);
51
+ return;
52
+ }
53
+
54
+ if (error._tag === 'FileWritingError') {
55
+ Logger.error(error.message);
56
+ Logger.error(error.error);
57
+ return;
58
+ }
59
+
60
+ const errorMessages = [
61
+ 'Error(s) occurred while parsing files. Please review the following issues:',
62
+ ...error.errors.map(formatReflectionError),
63
+ ].join('\n');
64
+
65
+ Logger.error(errorMessages);
66
+ }),
67
+ );
68
+ }
69
+
70
+ function formatReflectionError(error: ReflectionError) {
71
+ return `Source file: ${error.file}\n${error.message}\n`;
72
+ }
@@ -1,23 +1,6 @@
1
- import { Settings, SettingsConfig } from '../../core/settings';
2
1
  import { ApexFileReader } from '../apex-file-reader';
3
2
 
4
3
  describe('File Reader', () => {
5
- beforeEach(() => {
6
- Settings.build({
7
- sourceDirectory: '',
8
- recursive: true,
9
- scope: [],
10
- outputDir: '',
11
- targetGenerator: 'markdown',
12
- indexOnly: false,
13
- defaultGroupName: 'Misc',
14
- sanitizeHtml: true,
15
- openApiFileName: 'openapi',
16
- title: 'Classes',
17
- includeMetadata: false,
18
- } as SettingsConfig);
19
- });
20
-
21
4
  it('returns an empty list when there are no files in the directory', async () => {
22
5
  const result = await ApexFileReader.processFiles(
23
6
  {
@@ -1,21 +1,43 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
+ import * as TE from 'fp-ts/lib/TaskEither';
3
4
  import { PageData } from '../core/shared/types';
5
+ import { pipe } from 'fp-ts/function';
4
6
 
5
- export class FileWriter {
6
- static write(files: PageData[], outputDir: string, onWriteCallback?: (file: PageData) => void) {
7
- files.forEach((file) => {
8
- const { outputDocPath, content } = this.getTargetLocation(file, outputDir);
9
- fs.mkdirSync(path.dirname(outputDocPath), { recursive: true });
10
- fs.writeFileSync(outputDocPath, content, 'utf8');
11
- onWriteCallback?.(file);
12
- });
13
- }
7
+ const mkdir: (path: fs.PathLike, options: fs.MakeDirectoryOptions) => TE.TaskEither<NodeJS.ErrnoException, void> =
8
+ TE.taskify(fs.mkdir);
14
9
 
15
- private static getTargetLocation(file: PageData, outputDir: string): PageData {
16
- return {
17
- ...file,
18
- outputDocPath: path.join(outputDir, file.outputDocPath),
19
- };
20
- }
10
+ const writeFile: (
11
+ path: fs.PathOrFileDescriptor,
12
+ data: string,
13
+ options?: fs.WriteFileOptions,
14
+ ) => TE.TaskEither<NodeJS.ErrnoException, void> = TE.taskify(fs.writeFile);
15
+
16
+ export function writeFiles(files: PageData[], outputDir: string, onWriteCallback?: (file: PageData) => void) {
17
+ return pipe(
18
+ files,
19
+ TE.traverseArray((file) => writeSingle(file, outputDir, onWriteCallback)),
20
+ );
21
+ }
22
+
23
+ function writeSingle(file: PageData, outputDir: string, onWriteCallback?: (file: PageData) => void) {
24
+ const ensureDirectoryExists = ({ outputDocPath }: PageData) =>
25
+ mkdir(path.dirname(outputDocPath), { recursive: true });
26
+
27
+ const writeContents = (file: PageData) => writeFile(file.outputDocPath, file.content, 'utf8');
28
+
29
+ return pipe(
30
+ resolveTargetLocation(file, outputDir),
31
+ (file) => TE.right(file),
32
+ TE.tapIO(ensureDirectoryExists),
33
+ TE.flatMap(writeContents),
34
+ TE.map(() => onWriteCallback?.(file)),
35
+ );
36
+ }
37
+
38
+ function resolveTargetLocation(file: PageData, outputDir: string): PageData {
39
+ return {
40
+ ...file,
41
+ outputDocPath: path.join(outputDir, file.outputDocPath),
42
+ };
21
43
  }
@@ -1,6 +1,4 @@
1
1
  import { generateDocs } from '../../core/markdown/generate-docs';
2
- import { FileWriter } from '../file-writer';
3
- import { Logger } from '#utils/logger';
4
2
  import { pipe } from 'fp-ts/function';
5
3
  import {
6
4
  PageData,
@@ -11,10 +9,11 @@ import {
11
9
  import { referenceGuideTemplate } from '../../core/markdown/templates/reference-guide';
12
10
  import * as TE from 'fp-ts/TaskEither';
13
11
  import { isSkip } from '../../core/shared/utils';
14
- import { ReflectionError } from '../../core/markdown/reflection/reflect-source';
12
+ import { writeFiles } from '../file-writer';
15
13
 
16
14
  class FileWritingError {
17
15
  readonly _tag = 'FileWritingError';
16
+
18
17
  constructor(
19
18
  public message: string,
20
19
  public error: unknown,
@@ -25,27 +24,7 @@ export default function generate(bundles: UnparsedSourceFile[], config: UserDefi
25
24
  return pipe(
26
25
  generateDocumentationBundle(bundles, config),
27
26
  TE.flatMap((files) => writeFilesToSystem(files, config.targetDir)),
28
- TE.mapLeft((error) => {
29
- if (error._tag === 'HookError') {
30
- Logger.error('Error(s) occurred while processing hooks. Please review the following issues:');
31
- Logger.error(error.error);
32
- return;
33
- }
34
-
35
- if (error._tag === 'FileWritingError') {
36
- Logger.error(error.message);
37
- Logger.error(error.error);
38
- return;
39
- }
40
-
41
- const errorMessages = [
42
- 'Error(s) occurred while parsing files. Please review the following issues:',
43
- ...error.errors.map(formatReflectionError),
44
- ].join('\n');
45
-
46
- Logger.error(errorMessages);
47
- }),
48
- )();
27
+ );
49
28
  }
50
29
 
51
30
  function generateDocumentationBundle(bundles: UnparsedSourceFile[], config: UserDefinedMarkdownConfig) {
@@ -56,19 +35,11 @@ function generateDocumentationBundle(bundles: UnparsedSourceFile[], config: User
56
35
  }
57
36
 
58
37
  function writeFilesToSystem(files: PostHookDocumentationBundle, outputDir: string) {
59
- try {
60
- FileWriter.write(
61
- [files.referenceGuide, ...files.docs]
62
- // Filter out any files that should be skipped
63
- .filter((file) => !isSkip(file)) as PageData[],
64
- outputDir,
65
- );
66
- return TE.right(undefined);
67
- } catch (error) {
68
- return TE.left(new FileWritingError('An error occurred while writing files to the system.', error));
69
- }
70
- }
71
-
72
- function formatReflectionError(error: ReflectionError) {
73
- return `Source file: ${error.file}\n${error.message}\n`;
38
+ return pipe(
39
+ [files.referenceGuide, ...files.docs].filter((file) => !isSkip(file)),
40
+ (files) => writeFiles(files as PageData[], outputDir),
41
+ TE.mapLeft((error) => {
42
+ return new FileWritingError('An error occurred while writing files to the system.', error);
43
+ }),
44
+ );
74
45
  }
@@ -2,15 +2,27 @@ import { createManifest } from '../../core/openapi/manifest-factory';
2
2
  import { RawBodyParser } from '../../core/openapi/parser';
3
3
  import { TypesRepository } from '../../core/openapi/types-repository';
4
4
  import Transpiler from '../../core/openapi/transpiler';
5
- import { FileWriter } from '../file-writer';
6
5
  import { Logger } from '#utils/logger';
7
6
  import ErrorLogger from '#utils/error-logger';
8
7
  import { reflect, ReflectionResult } from '@cparra/apex-reflection';
9
8
  import Manifest from '../../core/manifest';
10
9
  import { PageData, UnparsedSourceFile, UserDefinedOpenApiConfig } from '../../core/shared/types';
11
10
  import { OpenApiDocsProcessor } from '../../core/openapi/open-api-docs-processor';
11
+ import { writeFiles } from '../file-writer';
12
+ import { pipe } from 'fp-ts/function';
13
+ import * as TE from 'fp-ts/TaskEither';
14
+ import { OpenApiSettings } from '../../core/openApiSettings';
15
+
16
+ export default async function openApi(fileBodies: UnparsedSourceFile[], config: UserDefinedOpenApiConfig) {
17
+ OpenApiSettings.build({
18
+ sourceDirectory: config.sourceDir,
19
+ outputDir: config.targetDir,
20
+ openApiFileName: config.fileName,
21
+ openApiTitle: config.title,
22
+ namespace: config.namespace,
23
+ version: config.apiVersion,
24
+ });
12
25
 
13
- export default function openApi(fileBodies: UnparsedSourceFile[], config: UserDefinedOpenApiConfig) {
14
26
  const manifest = createManifest(new RawBodyParser(fileBodies), reflectionWithLogger);
15
27
  TypesRepository.getInstance().populateAll(manifest.types);
16
28
  const filteredTypes = filterByScopes(manifest);
@@ -18,11 +30,15 @@ export default function openApi(fileBodies: UnparsedSourceFile[], config: UserDe
18
30
  Transpiler.generate(filteredTypes, processor);
19
31
  const generatedFiles = processor.fileBuilder().files();
20
32
 
21
- FileWriter.write(generatedFiles, config.targetDir, (file: PageData) => {
22
- Logger.logSingle(`${file.outputDocPath} processed.`, 'green');
23
- });
33
+ await pipe(
34
+ writeFiles(generatedFiles, config.targetDir, (file: PageData) => {
35
+ Logger.logSingle(`${file.outputDocPath} processed.`, 'green');
36
+ }),
37
+ TE.map(() => Logger.logSingle('✔️ Documentation generated successfully!')),
38
+ TE.mapError((error) => Logger.error(error)),
39
+ )();
24
40
 
25
- // Error logging
41
+ // Logs any errors that the types might have in their doc comment's error field
26
42
  ErrorLogger.logErrors(filteredTypes);
27
43
  }
28
44
 
package/src/cli/args.ts CHANGED
@@ -3,6 +3,7 @@ import * as yargs from 'yargs';
3
3
  import { UserDefinedMarkdownConfig } from '../core/shared/types';
4
4
  import { TypeScriptLoader } from 'cosmiconfig-typescript-loader';
5
5
  import { markdownOptions } from './commands/markdown';
6
+ import { openApiOptions } from './commands/openapi';
6
7
 
7
8
  /**
8
9
  * Extracts configuration from a configuration file or the package.json
@@ -26,7 +27,9 @@ function _extractYargs(config?: CosmiconfigResult) {
26
27
  .command('markdown', 'Generate documentation from Apex classes as a Markdown site.', (yargs) =>
27
28
  yargs.options(markdownOptions),
28
29
  )
29
- .command('openapi', 'Generate an OpenApi REST specification from Apex classes.') // TODO: Add OpenApi specific options
30
+ .command('openapi', 'Generate an OpenApi REST specification from Apex classes.', () =>
31
+ yargs.options(openApiOptions),
32
+ )
30
33
  .demandCommand()
31
34
  .parseSync();
32
35
  }
@@ -30,9 +30,7 @@ export const markdownOptions: { [key: string]: Options } = {
30
30
  },
31
31
  namespace: {
32
32
  type: 'string',
33
- describe:
34
- 'The package namespace, if any. If this value is provided the namespace will be added as a prefix to all of the parsed files. ' +
35
- "If generating an OpenApi definition, it will be added to the file's Server Url.",
33
+ describe: 'The package namespace, if any. If provided, it will be added to the generated files.',
36
34
  },
37
35
  sortMembersAlphabetically: {
38
36
  type: 'boolean',
@@ -0,0 +1,36 @@
1
+ import { Options } from 'yargs';
2
+ import { defaults } from '../../defaults';
3
+
4
+ export const openApiOptions: { [key: string]: Options } = {
5
+ sourceDir: {
6
+ type: 'string',
7
+ alias: 's',
8
+ demandOption: true,
9
+ describe: 'The directory location which contains your apex .cls classes.',
10
+ },
11
+ targetDir: {
12
+ type: 'string',
13
+ alias: 't',
14
+ default: defaults.targetDir,
15
+ describe: 'The directory location where the OpenApi file will be generated.',
16
+ },
17
+ fileName: {
18
+ type: 'string',
19
+ default: 'openapi',
20
+ describe: 'The name of the OpenApi file to be generated.',
21
+ },
22
+ namespace: {
23
+ type: 'string',
24
+ describe: 'The package namespace, if any. This will be added to the API file Server Url.',
25
+ },
26
+ title: {
27
+ type: 'string',
28
+ default: 'Apex REST API',
29
+ describe: 'The title of the OpenApi file.',
30
+ },
31
+ apiVersion: {
32
+ type: 'string',
33
+ default: '1.0.0',
34
+ describe: 'The version of the OpenApi file.',
35
+ },
36
+ };