@cparra/apexdocs 3.0.0-alpha.9 → 3.0.0-beta.1

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 (51) hide show
  1. package/dist/cli/generate.js +294 -204
  2. package/examples/open-api/config/project-scratch-def.json +13 -0
  3. package/examples/open-api/docs/openapi.json +582 -0
  4. package/examples/{force-app → open-api/force-app}/main/default/classes/SampleClass.cls +1 -0
  5. package/examples/open-api/package-lock.json +724 -0
  6. package/examples/open-api/package.json +20 -0
  7. package/examples/open-api/sfdx-project.json +12 -0
  8. package/package.json +1 -1
  9. package/src/application/Apexdocs.ts +39 -7
  10. package/src/application/__tests__/apex-file-reader.spec.ts +0 -17
  11. package/src/application/file-writer.ts +37 -15
  12. package/src/application/generators/markdown.ts +10 -39
  13. package/src/application/generators/openapi.ts +22 -6
  14. package/src/cli/args.ts +4 -1
  15. package/src/cli/commands/openapi.ts +36 -0
  16. package/src/core/markdown/adapters/documentables.ts +0 -1
  17. package/src/core/markdown/generate-docs.ts +2 -5
  18. package/src/core/markdown/reflection/reflect-source.ts +51 -50
  19. package/src/core/openApiSettings.ts +41 -0
  20. package/src/core/openapi/__tests__/open-api-docs-processor.spec.ts +2 -2
  21. package/src/core/openapi/open-api-docs-processor.ts +8 -4
  22. package/src/core/openapi/open-api.ts +5 -1
  23. package/src/core/openapi/openapi-type-file.ts +1 -1
  24. package/src/core/openapi/parser.ts +1 -15
  25. package/src/core/openapi/transpiler.ts +0 -5
  26. package/src/core/parse-apex-metadata.ts +21 -5
  27. package/src/core/shared/types.d.ts +4 -1
  28. package/src/test-helpers/SettingsBuilder.ts +2 -6
  29. package/examples/force-app/main/default/classes/AnotherInterface.cls +0 -16
  30. package/examples/force-app/main/default/classes/EscapedAnnotations.cls +0 -5
  31. package/examples/force-app/main/default/classes/GrandparentClass.cls +0 -5
  32. package/examples/force-app/main/default/classes/GroupedClass.cls +0 -8
  33. package/examples/force-app/main/default/classes/InterfaceWithInheritance.cls +0 -1
  34. package/examples/force-app/main/default/classes/MemberGrouping.cls +0 -17
  35. package/examples/force-app/main/default/classes/ParentClass.cls +0 -16
  36. package/examples/force-app/main/default/classes/SampleClass.cls-meta.xml +0 -5
  37. package/examples/force-app/main/default/classes/SampleClassWithoutModifier.cls +0 -9
  38. package/examples/force-app/main/default/classes/SampleInterface.cls +0 -16
  39. package/src/core/settings.ts +0 -56
  40. /package/examples/{force-app → open-api/force-app}/main/default/classes/ChildClass.cls +0 -0
  41. /package/examples/{force-app → open-api/force-app}/main/default/restapi/SampleRestResource.cls +0 -0
  42. /package/examples/{force-app → open-api/force-app}/main/default/restapi/SampleRestResourceToSkip.cls +0 -0
  43. /package/examples/{force-app → open-api/force-app}/main/default/restapi/SampleRestResourceWithInnerClass.cls +0 -0
  44. /package/examples/{force-app → open-api/force-app}/main/default/restapi/SampleRestResourceWithoutApexDocs.cls +0 -0
  45. /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference1.cls +0 -0
  46. /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference2.cls +0 -0
  47. /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference3.cls +0 -0
  48. /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference4.cls +0 -0
  49. /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference5.cls +0 -0
  50. /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference6.cls +0 -0
  51. /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
+ }
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-beta.1",
4
4
  "description": "Library with CLI capabilities to generate documentation for Salesforce Apex classes.",
5
5
  "keywords": [
6
6
  "apex",
@@ -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
  }
@@ -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
+ };
@@ -98,7 +98,6 @@ export function adaptDocumentable(
98
98
  .map((currentAnnotation) => currentAnnotation.body) ?? []
99
99
  );
100
100
  }
101
-
102
101
  return {
103
102
  ...adaptDescribable(documentable.docComment?.descriptionLines, linkGenerator),
104
103
  annotations: documentable.annotations.map((annotation) => annotation.type.toUpperCase()),
@@ -1,5 +1,4 @@
1
1
  import { pipe } from 'fp-ts/function';
2
- //import * as E from 'fp-ts/Either';
3
2
  import * as TE from 'fp-ts/TaskEither';
4
3
  import yaml from 'js-yaml';
5
4
 
@@ -20,8 +19,7 @@ import {
20
19
  ParsedFile,
21
20
  } from '../shared/types';
22
21
  import { parsedFilesToRenderableBundle } from './adapters/renderable-bundle';
23
- import { myPipeSeq /*reflectSourceCode*/ } from './reflection/reflect-source';
24
- //import { checkForReflectionErrors } from './reflection/error-handling';
22
+ import { reflectBundles } from './reflection/reflect-source';
25
23
  import { addInheritanceChainToTypes } from './reflection/inheritance-chain-expanion';
26
24
  import { addInheritedMembersToTypes } from './reflection/inherited-member-expansion';
27
25
  import { convertToDocumentationBundle } from './adapters/renderable-to-page-data';
@@ -63,8 +61,7 @@ export function generateDocs(apexBundles: UnparsedSourceFile[], config: Markdown
63
61
 
64
62
  return pipe(
65
63
  apexBundles,
66
- myPipeSeq,
67
- //checkForReflectionErrors,
64
+ reflectBundles,
68
65
  TE.map(filterOutOfScope),
69
66
  TE.map(addInheritedMembersToTypes),
70
67
  TE.map(addInheritanceChainToTypes),
@@ -1,13 +1,13 @@
1
1
  import { ParsedFile, UnparsedSourceFile } from '../../shared/types';
2
- //import * as E from 'fp-ts/Either';
3
2
  import * as TE from 'fp-ts/TaskEither';
3
+ import * as E from 'fp-ts/Either';
4
4
  import * as T from 'fp-ts/Task';
5
5
  import * as A from 'fp-ts/lib/Array';
6
- import { reflect as mirrorReflection, Type } from '@cparra/apex-reflection';
6
+ import { Annotation, reflect as mirrorReflection, Type } from '@cparra/apex-reflection';
7
7
  import { pipe } from 'fp-ts/function';
8
8
  import * as O from 'fp-ts/Option';
9
9
  import { parseApexMetadata } from '../../parse-apex-metadata';
10
- import { ParsingError } from '@cparra/apex-reflection/index';
10
+ import { ParsingError } from '@cparra/apex-reflection';
11
11
  import { apply } from '#utils/fp';
12
12
  import { Semigroup } from 'fp-ts/Semigroup';
13
13
 
@@ -37,20 +37,20 @@ async function reflectAsync(rawSource: string): Promise<Type> {
37
37
  });
38
38
  }
39
39
 
40
- export function myPipeSeq(apexBundles: UnparsedSourceFile[]) {
40
+ export function reflectBundles(apexBundles: UnparsedSourceFile[]) {
41
41
  const semiGroupReflectionError: Semigroup<ReflectionErrors> = {
42
42
  concat: (x, y) => new ReflectionErrors([...x.errors, ...y.errors]),
43
43
  };
44
44
  const Ap = TE.getApplicativeTaskValidation(T.ApplyPar, semiGroupReflectionError);
45
45
 
46
- return pipe(apexBundles, A.traverse(Ap)(myPipe));
46
+ return pipe(apexBundles, A.traverse(Ap)(reflectBundle));
47
47
  }
48
48
 
49
- function myPipe(apexBundle: UnparsedSourceFile): TE.TaskEither<ReflectionErrors, ParsedFile> {
49
+ function reflectBundle(apexBundle: UnparsedSourceFile): TE.TaskEither<ReflectionErrors, ParsedFile> {
50
50
  const convertToParsedFile: (typeMirror: Type) => ParsedFile = apply(toParsedFile, apexBundle.filePath);
51
- const withMetadata: (parsedFile: ParsedFile) => ParsedFile = apply(addMetadata, apexBundle.metadataContent);
51
+ const withMetadata = apply(addMetadata, apexBundle.metadataContent);
52
52
 
53
- return pipe(apexBundle, reflectAsTask, TE.map(convertToParsedFile), TE.map(withMetadata));
53
+ return pipe(apexBundle, reflectAsTask, TE.map(convertToParsedFile), TE.flatMap(withMetadata));
54
54
  }
55
55
 
56
56
  function reflectAsTask(apexBundle: UnparsedSourceFile): TE.TaskEither<ReflectionErrors, Type> {
@@ -72,51 +72,52 @@ function toParsedFile(filePath: string, typeMirror: Type): ParsedFile {
72
72
  };
73
73
  }
74
74
 
75
- function addMetadata(rawMetadataContent: string | null, parsedFile: ParsedFile) {
76
- return {
77
- ...parsedFile,
78
- type: addFileMetadataToTypeAnnotation(parsedFile.type, rawMetadataContent),
79
- };
75
+ function addMetadata(
76
+ rawMetadataContent: string | null,
77
+ parsedFile: ParsedFile,
78
+ ): TE.TaskEither<ReflectionErrors, ParsedFile> {
79
+ return TE.fromEither(
80
+ pipe(
81
+ parsedFile.type,
82
+ (type) => addFileMetadataToTypeAnnotation(type, rawMetadataContent),
83
+ E.map((type) => ({ ...parsedFile, type })),
84
+ E.mapLeft((error) => errorToReflectionErrors(error, parsedFile.source.filePath)),
85
+ ),
86
+ );
80
87
  }
81
88
 
82
- // TODO: Not Async below
83
-
84
- // export function reflectSourceCode(apexBundles: UnparsedSourceFile[]) {
85
- // return apexBundles.map(reflectSourceBody);
86
- // }
87
- //
88
- // function reflectSourceBody(apexBundle: UnparsedSourceFile): E.Either<ReflectionError, ParsedFile> {
89
- // const { filePath, content: input, metadataContent: metadata } = apexBundle;
90
- // const result = mirrorReflection(input);
91
- // return result.error
92
- // ? E.left(new ReflectionError(filePath, result.error.message))
93
- // : E.right({
94
- // source: {
95
- // filePath,
96
- // name: result.typeMirror!.name,
97
- // type: result.typeMirror!.type_name,
98
- // },
99
- // type: addFileMetadataToTypeAnnotation(result.typeMirror!, metadata),
100
- // });
101
- // }
102
-
103
- function addFileMetadataToTypeAnnotation(type: Type, metadata: string | null): Type {
89
+ function errorToReflectionErrors(error: Error, filePath: string): ReflectionErrors {
90
+ return new ReflectionErrors([new ReflectionError(filePath, error.message)]);
91
+ }
92
+
93
+ function addFileMetadataToTypeAnnotation(type: Type, metadata: string | null): E.Either<Error, Type> {
94
+ const concatAnnotationToType = apply(concatAnnotations, type);
95
+
104
96
  return pipe(
105
97
  O.fromNullable(metadata),
106
- O.map((metadata) => {
107
- // TODO: Do we need to error check this, as it is coming from an external library?
108
- // Or maybe what we do is return the Either from the parse-apex-metadata function?
109
- const metadataParams = parseApexMetadata(metadata);
110
- metadataParams.forEach((value, key) => {
111
- const declaration = `${key}: ${value}`;
112
- type.annotations.push({
113
- rawDeclaration: declaration,
114
- name: declaration,
115
- type: declaration,
116
- });
117
- });
118
- return type;
119
- }),
120
- O.getOrElse(() => type),
98
+ O.map(concatAnnotationToType),
99
+ O.getOrElse(() => E.right(type)),
121
100
  );
122
101
  }
102
+
103
+ function concatAnnotations(type: Type, metadataInput: string): E.Either<Error, Type> {
104
+ return pipe(
105
+ metadataInput,
106
+ parseApexMetadata,
107
+ E.map((metadataMap) => ({
108
+ ...type,
109
+ annotations: [...type.annotations, ...mapToAnnotations(metadataMap)],
110
+ })),
111
+ );
112
+ }
113
+
114
+ function mapToAnnotations(metadata: Map<string, string>): Annotation[] {
115
+ return Array.from(metadata.entries()).map(([key, value]) => {
116
+ const declaration = `${key}: ${value}`;
117
+ return {
118
+ name: declaration,
119
+ type: declaration,
120
+ rawDeclaration: declaration,
121
+ };
122
+ });
123
+ }
@@ -0,0 +1,41 @@
1
+ export interface SettingsConfig {
2
+ sourceDirectory: string;
3
+ outputDir: string;
4
+ openApiFileName: string;
5
+ namespace?: string;
6
+ openApiTitle?: string;
7
+ version: string;
8
+ }
9
+
10
+ export class OpenApiSettings {
11
+ private static instance: OpenApiSettings;
12
+
13
+ private constructor(public config: SettingsConfig) {}
14
+
15
+ public static build(config: SettingsConfig) {
16
+ OpenApiSettings.instance = new OpenApiSettings(config);
17
+ }
18
+
19
+ public static getInstance(): OpenApiSettings {
20
+ if (!OpenApiSettings.instance) {
21
+ throw new Error('Settings has not been initialized');
22
+ }
23
+ return OpenApiSettings.instance;
24
+ }
25
+
26
+ public getOpenApiTitle(): string | undefined {
27
+ return this.config.openApiTitle;
28
+ }
29
+
30
+ public getNamespace(): string | undefined {
31
+ return this.config.namespace;
32
+ }
33
+
34
+ public openApiFileName(): string {
35
+ return this.config.openApiFileName;
36
+ }
37
+
38
+ public getVersion(): string {
39
+ return this.config.version;
40
+ }
41
+ }
@@ -1,12 +1,12 @@
1
1
  import { OpenApiDocsProcessor } from '../open-api-docs-processor';
2
- import { Settings } from '../../settings';
2
+ import { OpenApiSettings } from '../../openApiSettings';
3
3
  import { SettingsBuilder } from '../../../test-helpers/SettingsBuilder';
4
4
  import { DocCommentBuilder } from '../../../test-helpers/DocCommentBuilder';
5
5
  import { AnnotationBuilder } from '../../../test-helpers/AnnotationBuilder';
6
6
  import { ClassMirrorBuilder } from '../../../test-helpers/ClassMirrorBuilder';
7
7
 
8
8
  beforeEach(() => {
9
- Settings.build(new SettingsBuilder().build());
9
+ OpenApiSettings.build(new SettingsBuilder().build());
10
10
  });
11
11
 
12
12
  it('should add a path based on the @UrlResource annotation on the class', function () {
@@ -2,7 +2,7 @@ import { FileContainer } from './file-container';
2
2
  import { ClassMirror, Type } from '@cparra/apex-reflection';
3
3
  import { Logger } from '#utils/logger';
4
4
  import { OpenApi } from './open-api';
5
- import { Settings } from '../settings';
5
+ import { OpenApiSettings } from '../openApiSettings';
6
6
  import { MethodParser } from './parsers/MethodParser';
7
7
  import { camel2title } from '#utils/string-utils';
8
8
  import { createOpenApiFile } from './openapi-type-file';
@@ -13,11 +13,15 @@ export class OpenApiDocsProcessor {
13
13
 
14
14
  constructor() {
15
15
  this._fileContainer = new FileContainer();
16
- const title = Settings.getInstance().getOpenApiTitle();
16
+ const title = OpenApiSettings.getInstance().getOpenApiTitle();
17
17
  if (!title) {
18
18
  throw Error('No OpenApi title was provided.');
19
19
  }
20
- this.openApiModel = new OpenApi(title, '1.0.0', Settings.getInstance().getNamespace());
20
+ this.openApiModel = new OpenApi(
21
+ title,
22
+ OpenApiSettings.getInstance().getVersion(),
23
+ OpenApiSettings.getInstance().getNamespace(),
24
+ );
21
25
  }
22
26
 
23
27
  fileBuilder(): FileContainer {
@@ -66,7 +70,7 @@ export class OpenApiDocsProcessor {
66
70
  }
67
71
 
68
72
  onAfterProcess: ((types: Type[]) => void) | undefined = () => {
69
- const page = createOpenApiFile(Settings.getInstance().openApiFileName(), this.openApiModel);
73
+ const page = createOpenApiFile(OpenApiSettings.getInstance().openApiFileName(), this.openApiModel);
70
74
  this._fileContainer.pushFile(page);
71
75
  };
72
76
 
@@ -15,7 +15,11 @@ export class OpenApi {
15
15
  servers: ServerObject[];
16
16
  components?: ComponentsObject;
17
17
 
18
- constructor(title: string, version: string, private namespace?: string) {
18
+ constructor(
19
+ title: string,
20
+ version: string,
21
+ private namespace?: string,
22
+ ) {
19
23
  this.info = {
20
24
  title: title,
21
25
  version: version,