@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.
- package/dist/cli/generate.js +294 -204
- package/examples/open-api/config/project-scratch-def.json +13 -0
- package/examples/open-api/docs/openapi.json +582 -0
- package/examples/{force-app → open-api/force-app}/main/default/classes/SampleClass.cls +1 -0
- package/examples/open-api/package-lock.json +724 -0
- package/examples/open-api/package.json +20 -0
- package/examples/open-api/sfdx-project.json +12 -0
- package/package.json +1 -1
- package/src/application/Apexdocs.ts +39 -7
- package/src/application/__tests__/apex-file-reader.spec.ts +0 -17
- package/src/application/file-writer.ts +37 -15
- package/src/application/generators/markdown.ts +10 -39
- package/src/application/generators/openapi.ts +22 -6
- package/src/cli/args.ts +4 -1
- package/src/cli/commands/openapi.ts +36 -0
- package/src/core/markdown/adapters/documentables.ts +0 -1
- package/src/core/markdown/generate-docs.ts +2 -5
- package/src/core/markdown/reflection/reflect-source.ts +51 -50
- package/src/core/openApiSettings.ts +41 -0
- package/src/core/openapi/__tests__/open-api-docs-processor.spec.ts +2 -2
- package/src/core/openapi/open-api-docs-processor.ts +8 -4
- package/src/core/openapi/open-api.ts +5 -1
- package/src/core/openapi/openapi-type-file.ts +1 -1
- package/src/core/openapi/parser.ts +1 -15
- package/src/core/openapi/transpiler.ts +0 -5
- package/src/core/parse-apex-metadata.ts +21 -5
- package/src/core/shared/types.d.ts +4 -1
- package/src/test-helpers/SettingsBuilder.ts +2 -6
- package/examples/force-app/main/default/classes/AnotherInterface.cls +0 -16
- package/examples/force-app/main/default/classes/EscapedAnnotations.cls +0 -5
- package/examples/force-app/main/default/classes/GrandparentClass.cls +0 -5
- package/examples/force-app/main/default/classes/GroupedClass.cls +0 -8
- package/examples/force-app/main/default/classes/InterfaceWithInheritance.cls +0 -1
- package/examples/force-app/main/default/classes/MemberGrouping.cls +0 -17
- package/examples/force-app/main/default/classes/ParentClass.cls +0 -16
- package/examples/force-app/main/default/classes/SampleClass.cls-meta.xml +0 -5
- package/examples/force-app/main/default/classes/SampleClassWithoutModifier.cls +0 -9
- package/examples/force-app/main/default/classes/SampleInterface.cls +0 -16
- package/src/core/settings.ts +0 -56
- /package/examples/{force-app → open-api/force-app}/main/default/classes/ChildClass.cls +0 -0
- /package/examples/{force-app → open-api/force-app}/main/default/restapi/SampleRestResource.cls +0 -0
- /package/examples/{force-app → open-api/force-app}/main/default/restapi/SampleRestResourceToSkip.cls +0 -0
- /package/examples/{force-app → open-api/force-app}/main/default/restapi/SampleRestResourceWithInnerClass.cls +0 -0
- /package/examples/{force-app → open-api/force-app}/main/default/restapi/SampleRestResourceWithoutApexDocs.cls +0 -0
- /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference1.cls +0 -0
- /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference2.cls +0 -0
- /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference3.cls +0 -0
- /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference4.cls +0 -0
- /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference5.cls +0 -0
- /package/examples/{force-app → open-api/force-app}/main/default/restapi/references/Reference6.cls +0 -0
- /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
|
+
}
|
package/package.json
CHANGED
|
@@ -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
|
|
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(
|
|
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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
//
|
|
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.')
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
|
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)(
|
|
46
|
+
return pipe(apexBundles, A.traverse(Ap)(reflectBundle));
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function
|
|
49
|
+
function reflectBundle(apexBundle: UnparsedSourceFile): TE.TaskEither<ReflectionErrors, ParsedFile> {
|
|
50
50
|
const convertToParsedFile: (typeMirror: Type) => ParsedFile = apply(toParsedFile, apexBundle.filePath);
|
|
51
|
-
const withMetadata
|
|
51
|
+
const withMetadata = apply(addMetadata, apexBundle.metadataContent);
|
|
52
52
|
|
|
53
|
-
return pipe(apexBundle, reflectAsTask, TE.map(convertToParsedFile), TE.
|
|
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(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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(
|
|
107
|
-
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|