Skip to content

Commit 75f47cf

Browse files
authored
Merge pull request #305 from cesarParra/best-effort
Best effort and debug flag support.
2 parents e1ff354 + 35ea6b2 commit 75f47cf

19 files changed

Lines changed: 600 additions & 196 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ apexdocs changelog --previousVersionDir force-app-previous --currentVersionDir f
159159
| `--includeInlineHelpTextMetadata` | N/A | Whether to include the inline help text for fields in the generated files. | `false` | No |
160160
| `--parallelReflection` | N/A | Parallelize CPU-heavy reflection via worker threads. | `true` | No |
161161
| `--parallelReflectionMaxWorkers` | N/A | Maximum number of worker threads to use for parallel reflection. Defaults to a reasonable value based on CPU count. | N/A | No |
162+
| `--debug` | N/A | Enable debug logging. Prints file-by-file parsing progress and success/failure. | `false` | No |
162163

163164
> **Note:** The `*` in the Required column indicates that **one** of the source directory options must be specified:
164165
> - `--sourceDir` (single directory or array of directories)
@@ -216,6 +217,7 @@ apexdocs markdown -s force-app -t docs -p global public namespaceaccessible -n M
216217
| `--apiVersion` | N/A | The version of the API. | `1.0.0` | No |
217218
| `--parallelReflection` | N/A | Parallelize CPU-heavy reflection via worker threads. | `true` | No |
218219
| `--parallelReflectionMaxWorkers` | N/A | Maximum number of worker threads to use for parallel reflection. Defaults to a reasonable value. | N/A | No |
220+
| `--debug` | N/A | Enable debug logging. Prints file-by-file parsing progress and success/failure. | `false` | No |
219221

220222
#### Sample Usage
221223

@@ -240,6 +242,7 @@ apexdocs openapi -s force-app -t docs -n MyNamespace --title "My Custom OpenApi
240242
| `--skipIfNoChanges` | N/A | Whether to skip generating the changelog if there are no changes. | `true` | No |
241243
| `--parallelReflection` | N/A | Parallelize CPU-heavy reflection via worker threads. | `true` | No |
242244
| `--parallelReflectionMaxWorkers` | N/A | Maximum number of worker threads to use for parallel reflection. Defaults to a reasonable value. | N/A | No |
245+
| `--debug` | N/A | Enable debug logging. Prints file-by-file parsing progress and success/failure. | `false` | No |
243246

244247
#### Sample Usage
245248

examples/vitepress/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "vitepress-example",
33
"scripts": {
44
"docs:clean": "rimraf --glob 'docs/!(.vitepress|index-frontmatter.md|api-examples.md|markdown-examples.md)'",
5-
"apexdocs:build": "npm run docs:clean && ts-node ../../src/cli/generate.ts",
5+
"apexdocs:build": "npm run docs:clean && ts-node ../../src/cli/generate.ts --parallelReflection=false --debug",
66
"docs:dev": "vitepress dev docs"
77
},
88
"devDependencies": {

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cparra/apexdocs",
3-
"version": "3.17.0",
3+
"version": "3.17.1-beta.5",
44
"description": "Library with CLI capabilities to generate documentation for Salesforce Apex classes.",
55
"engines": {
66
"node": ">=18"

src/application/Apexdocs.ts

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import changelog from './generators/changelog';
99
import { allComponentTypes, processFiles } from './source-code-file-reader';
1010
import { DefaultFileSystem } from './file-system';
1111
import { Logger } from '#utils/logger';
12+
import type { ReflectionDebugLogger } from '../core/reflection/apex/reflect-apex-source';
1213
import {
1314
UnparsedApexBundle,
1415
UserDefinedChangelogConfig,
@@ -28,18 +29,34 @@ export class Apexdocs {
2829
/**
2930
* Generates documentation out of Apex source files.
3031
*/
31-
static async generate(config: UserDefinedConfig, logger: Logger): Promise<E.Either<unknown[], string>> {
32+
static async generate(
33+
config: UserDefinedConfig,
34+
deps: {
35+
logger: Logger;
36+
reflectionDebugLogger: ReflectionDebugLogger;
37+
},
38+
): Promise<E.Either<unknown[], string>> {
39+
const { logger, reflectionDebugLogger } = deps;
3240
logger.logSingle(`Generating ${config.targetGenerator} documentation...`);
3341

3442
try {
3543
switch (config.targetGenerator) {
36-
case 'markdown':
37-
return (await processMarkdown(config))();
38-
case 'openapi':
39-
await processOpenApi(config, logger);
44+
case 'markdown': {
45+
return (await processMarkdown(config, logger, reflectionDebugLogger))();
46+
}
47+
case 'openapi': {
48+
const task = await processOpenApi(config, logger);
49+
const openApiResult = await task();
50+
51+
if (E.isLeft(openApiResult)) {
52+
return E.left([openApiResult.left]);
53+
}
54+
4055
return E.right('✔️ Documentation generated successfully!');
41-
case 'changelog':
42-
return (await processChangeLog(config))();
56+
}
57+
case 'changelog': {
58+
return (await processChangeLog(config, logger, reflectionDebugLogger))();
59+
}
4360
}
4461
} catch (error) {
4562
return E.left([error]);
@@ -49,7 +66,13 @@ export class Apexdocs {
4966

5067
const readFiles = apply(processFiles, new DefaultFileSystem());
5168

52-
async function processMarkdown(config: UserDefinedMarkdownConfig) {
69+
async function processMarkdown(
70+
config: UserDefinedMarkdownConfig,
71+
logger: Logger,
72+
reflectionDebugLogger: ReflectionDebugLogger,
73+
) {
74+
const debugLogger = reflectionDebugLogger;
75+
5376
return pipe(
5477
resolveAndValidateSourceDirectories(config),
5578
E.mapLeft((error) => new FileReadingError(`Failed to resolve source directories: ${error.message}`, error)),
@@ -63,9 +86,15 @@ async function processMarkdown(config: UserDefinedMarkdownConfig) {
6386
),
6487
),
6588
TE.fromEither,
66-
TE.flatMap((fileBodies) => markdown(fileBodies, config)),
89+
TE.flatMap((fileBodies) => markdown(fileBodies, config, debugLogger)),
6790
TE.map(() => '✔️ Documentation generated successfully!'),
68-
TE.mapLeft(toErrors),
91+
TE.mapLeft((err) => {
92+
const errors = toErrors(err);
93+
if (logger.isDebugEnabled()) {
94+
logger.debug(`markdown generator finished with ${Array.isArray(errors) ? errors.length : 1} error item(s)`);
95+
}
96+
return errors;
97+
}),
6998
);
7099
}
71100

@@ -89,7 +118,11 @@ async function processOpenApi(config: UserDefinedOpenApiConfig, logger: Logger)
89118
);
90119
}
91120

92-
async function processChangeLog(config: UserDefinedChangelogConfig) {
121+
async function processChangeLog(
122+
config: UserDefinedChangelogConfig,
123+
logger: Logger,
124+
reflectionDebugLogger: ReflectionDebugLogger,
125+
) {
93126
function loadFiles() {
94127
const previousVersionConfig = {
95128
sourceDir: config.previousVersionDir,
@@ -129,8 +162,14 @@ async function processChangeLog(config: UserDefinedChangelogConfig) {
129162
return pipe(
130163
loadFiles(),
131164
TE.fromEither,
132-
TE.flatMap(([previous, current]) => changelog(previous, current, config)),
133-
TE.mapLeft(toErrors),
165+
TE.flatMap(([previous, current]) => changelog(previous, current, config, reflectionDebugLogger)),
166+
TE.mapLeft((err) => {
167+
const errors = toErrors(err);
168+
if (logger.isDebugEnabled()) {
169+
logger.debug(`changelog generator finished with ${Array.isArray(errors) ? errors.length : 1} error item(s)`);
170+
}
171+
return errors;
172+
}),
134173
);
135174
}
136175

src/application/generators/changelog.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ import { writeFiles } from '../file-writer';
1111
import { generateChangeLog } from '../../core/changelog/generate-change-log';
1212
import { FileWritingError } from '../errors';
1313
import { isSkip } from '../../core/shared/utils';
14+
import type { ReflectionDebugLogger } from '../../core/reflection/apex/reflect-apex-source';
1415

1516
export default function generate(
1617
oldBundles: UnparsedSourceBundle[],
1718
newBundles: UnparsedSourceBundle[],
1819
config: UserDefinedChangelogConfig,
20+
debugLogger: ReflectionDebugLogger,
1921
) {
2022
function handleFile(file: ChangeLogPageData | Skip) {
2123
if (isSkip(file)) {
@@ -25,7 +27,7 @@ export default function generate(
2527
return writeFilesToSystem(file, config.targetDir);
2628
}
2729

28-
return pipe(generateChangeLog(oldBundles, newBundles, config), TE.flatMap(handleFile));
30+
return pipe(generateChangeLog(oldBundles, newBundles, config, debugLogger), TE.flatMap(handleFile));
2931
}
3032

3133
function writeFilesToSystem(pageData: ChangeLogPageData, outputDir: string) {

src/application/generators/markdown.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,32 @@ import * as TE from 'fp-ts/TaskEither';
1111
import { isSkip } from '../../core/shared/utils';
1212
import { writeFiles } from '../file-writer';
1313
import { FileWritingError } from '../errors';
14+
import type { ReflectionDebugLogger } from '../../core/reflection/apex/reflect-apex-source';
1415

15-
export default function generate(bundles: UnparsedSourceBundle[], config: UserDefinedMarkdownConfig) {
16+
export default function generate(
17+
bundles: UnparsedSourceBundle[],
18+
config: UserDefinedMarkdownConfig,
19+
debugLogger: ReflectionDebugLogger,
20+
) {
1621
return pipe(
17-
generateDocumentationBundle(bundles, config),
22+
generateDocumentationBundle(bundles, config, debugLogger),
1823
TE.flatMap((files) => writeFilesToSystem(files, config.targetDir)),
1924
);
2025
}
2126

22-
function generateDocumentationBundle(bundles: UnparsedSourceBundle[], config: UserDefinedMarkdownConfig) {
23-
return generateDocs(bundles, {
24-
...config,
25-
referenceGuideTemplate: referenceGuideTemplate,
26-
});
27+
function generateDocumentationBundle(
28+
bundles: UnparsedSourceBundle[],
29+
config: UserDefinedMarkdownConfig,
30+
debugLogger: ReflectionDebugLogger,
31+
) {
32+
return generateDocs(
33+
bundles,
34+
{
35+
...config,
36+
referenceGuideTemplate: referenceGuideTemplate,
37+
},
38+
debugLogger,
39+
);
2740
}
2841

2942
function writeFilesToSystem(files: PostHookDocumentationBundle, outputDir: string) {

src/application/generators/openapi.ts

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ import { writeFiles } from '../file-writer';
1212
import { pipe } from 'fp-ts/function';
1313
import * as TE from 'fp-ts/TaskEither';
1414
import { OpenApiSettings } from '../../core/openapi/openApiSettings';
15-
import { reflectApexSource } from '../../core/reflection/apex/reflect-apex-source';
15+
import { reflectApexSourceBestEffort } from '../../core/reflection/apex/reflect-apex-source';
16+
import { ReflectionErrors } from '../../core/errors/errors';
1617
import * as E from 'fp-ts/Either';
18+
import { FileWritingError } from '../errors';
19+
import { createReflectionDebugLogger } from '#utils/reflection-debug-logger';
1720

1821
export default async function openApi(
1922
logger: Logger,
@@ -35,18 +38,40 @@ export default async function openApi(
3538
version: config.apiVersion,
3639
});
3740

38-
const parsedFilesEither = await reflectApexSource(fileBodies, {
39-
parallelReflection: config.parallelReflection,
40-
parallelReflectionMaxWorkers: config.parallelReflectionMaxWorkers,
41-
})();
41+
const debugLogger = createReflectionDebugLogger(logger);
4242

43-
if (E.isLeft(parsedFilesEither)) {
44-
const errors = parsedFilesEither.left;
45-
logger.error(errors);
46-
return;
43+
const reflectedEither = await reflectApexSourceBestEffort(
44+
fileBodies,
45+
{
46+
parallelReflection: config.parallelReflection,
47+
parallelReflectionMaxWorkers: config.parallelReflectionMaxWorkers,
48+
},
49+
debugLogger,
50+
)();
51+
52+
if (E.isLeft(reflectedEither)) {
53+
// Propagate the failure to the caller instead of swallowing it.
54+
// The CLI/application layer is responsible for end-of-run aggregation.
55+
return E.left(reflectedEither.left);
56+
}
57+
58+
const { successes: parsedFiles, errors: recoverableErrors } = reflectedEither.right;
59+
60+
if (recoverableErrors.errors.length > 0) {
61+
logger.logSingle(
62+
`⚠️ ${recoverableErrors.errors.length} file(s) failed to parse/reflect. Continuing with successfully reflected files.`,
63+
'red',
64+
);
65+
66+
logger.error(
67+
new ReflectionErrors(
68+
recoverableErrors.errors.map((e) => ({
69+
...e,
70+
})),
71+
),
72+
);
4773
}
4874

49-
const parsedFiles = parsedFilesEither.right;
5075
const reflectionByPath = new Map<string, ReflectionResult>();
5176

5277
for (const parsed of parsedFiles) {
@@ -74,15 +99,32 @@ export default async function openApi(
7499
Transpiler.generate(filteredTypes, processor);
75100
const generatedFiles = processor.fileBuilder().files();
76101

77-
await pipe(
102+
const writeResult = await pipe(
78103
writeFiles(generatedFiles, config.targetDir, (file: PageData) => {
79104
logger.logSingle(`${file.outputDocPath} processed.`, 'green');
80105
}),
81-
TE.mapError((error) => logger.error(error)),
106+
TE.mapLeft((error) => new FileWritingError('An error occurred while writing files to the system.', error)),
82107
)();
83108

109+
if (E.isLeft(writeResult)) {
110+
return E.left(writeResult.left);
111+
}
112+
84113
// Logs any errors that the types might have in their doc comment's error field
85114
ErrorLogger.logErrors(logger, filteredTypes);
115+
116+
// If there were recoverable reflection failures, propagate them to the caller for aggregation.
117+
if (recoverableErrors.errors.length > 0) {
118+
return E.left(
119+
new ReflectionErrors(
120+
recoverableErrors.errors.map((e) => ({
121+
...e,
122+
})),
123+
),
124+
);
125+
}
126+
127+
return E.right(undefined);
86128
}
87129

88130
function filterByScopes(logger: Logger, manifest: Manifest) {

src/cli/args.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ import { changeLogOptions, validateChangelogArgs } from './commands/changelog';
1515
import { pipe } from 'fp-ts/function';
1616
import { validateSourceDirectoryConfig } from '#utils/source-directory-resolver';
1717

18+
const globalOptions = {
19+
debug: {
20+
type: 'boolean',
21+
demandOption: false,
22+
default: false,
23+
describe: 'Enable debug logging.',
24+
},
25+
} as const;
26+
1827
const configOnlyMarkdownDefaults: Partial<UserDefinedMarkdownConfig> = {
1928
targetGenerator: 'markdown',
2029
excludeTags: [],
@@ -226,6 +235,7 @@ function getConfigType(config: ConfigResult): E.Either<Error, NoConfig | SingleC
226235
function extractYargsDemandingCommand(extractFromProcessFn: ExtractArgsFromProcess, config: ConfigResult) {
227236
return yargs
228237
.config(config.config as Record<string, unknown>)
238+
.options(globalOptions)
229239
.command('markdown', 'Generate documentation from Apex classes as a Markdown site.', (yargs) =>
230240
yargs.options(markdownOptions).check(validateMarkdownArgs),
231241
)
@@ -271,6 +281,7 @@ function extractMultiCommandConfig(
271281
return E.tryCatch(() => {
272282
return yargs(extractFromProcessFn())
273283
.config(config)
284+
.options(globalOptions)
274285
.options(options)
275286
.check(validator)
276287
.fail((msg) => {

0 commit comments

Comments
 (0)