Skip to content

Commit bf0be55

Browse files
committed
Adding to support for providing multiple source directories or the sfdx-project.json as the source of directories.
1 parent 5a79777 commit bf0be55

File tree

8 files changed

+357
-45
lines changed

8 files changed

+357
-45
lines changed

src/application/Apexdocs.ts

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import { DefaultFileSystem } from './file-system';
1111
import { Logger } from '#utils/logger';
1212
import {
1313
UnparsedApexBundle,
14-
UnparsedSourceBundle,
1514
UserDefinedChangelogConfig,
1615
UserDefinedConfig,
1716
UserDefinedMarkdownConfig,
1817
UserDefinedOpenApiConfig,
1918
} from '../core/shared/types';
19+
import { resolveAndValidateSourceDirectories } from '../util/source-directory-resolver';
2020
import { ReflectionError, ReflectionErrors, HookError } from '../core/errors/errors';
2121
import { FileReadingError, FileWritingError } from './errors';
2222

@@ -50,12 +50,16 @@ const readFiles = processFiles(new DefaultFileSystem());
5050

5151
async function processMarkdown(config: UserDefinedMarkdownConfig) {
5252
return pipe(
53-
E.tryCatch(
54-
() =>
55-
readFiles(allComponentTypes, {
56-
includeMetadata: config.includeMetadata,
57-
})(config.sourceDir, config.exclude),
58-
(e) => new FileReadingError('An error occurred while reading files.', e),
53+
resolveAndValidateSourceDirectories(config),
54+
E.mapLeft((error) => new FileReadingError(`Failed to resolve source directories: ${error.message}`, error)),
55+
E.flatMap((sourceDirs) =>
56+
E.tryCatch(
57+
() =>
58+
readFiles(allComponentTypes, {
59+
includeMetadata: config.includeMetadata,
60+
})(sourceDirs, config.exclude),
61+
(e) => new FileReadingError('An error occurred while reading files.', e),
62+
),
5963
),
6064
TE.fromEither,
6165
TE.flatMap((fileBodies) => markdown(fileBodies, config)),
@@ -65,20 +69,67 @@ async function processMarkdown(config: UserDefinedMarkdownConfig) {
6569
}
6670

6771
async function processOpenApi(config: UserDefinedOpenApiConfig, logger: Logger) {
68-
const fileBodies = readFiles(['ApexClass'])(config.sourceDir, config.exclude) as UnparsedApexBundle[];
69-
return openApi(logger, fileBodies, config);
72+
return pipe(
73+
resolveAndValidateSourceDirectories(config),
74+
E.mapLeft((error) => new FileReadingError(`Failed to resolve source directories: ${error.message}`, error)),
75+
TE.fromEither,
76+
TE.flatMap((sourceDirs) =>
77+
TE.tryCatch(
78+
() => {
79+
const fileBodies = readFiles(['ApexClass'])(sourceDirs, config.exclude) as UnparsedApexBundle[];
80+
return openApi(logger, fileBodies, config);
81+
},
82+
(e) => new FileReadingError('An error occurred while generating OpenAPI documentation.', e),
83+
),
84+
),
85+
);
7086
}
7187

7288
async function processChangeLog(config: UserDefinedChangelogConfig) {
73-
function loadFiles(): [UnparsedSourceBundle[], UnparsedSourceBundle[]] {
74-
return [
75-
readFiles(allComponentTypes)(config.previousVersionDir, config.exclude),
76-
readFiles(allComponentTypes)(config.currentVersionDir, config.exclude),
77-
];
89+
function loadFiles() {
90+
const previousVersionConfig = {
91+
sourceDir: config.previousVersionDir,
92+
sourceDirs: config.previousVersionDirs,
93+
useSfdxProjectJson: config.useSfdxProjectJsonForPrevious,
94+
sfdxProjectPath: config.sfdxProjectPathForPrevious,
95+
};
96+
97+
const currentVersionConfig = {
98+
sourceDir: config.currentVersionDir,
99+
sourceDirs: config.currentVersionDirs,
100+
useSfdxProjectJson: config.useSfdxProjectJsonForCurrent,
101+
sfdxProjectPath: config.sfdxProjectPathForCurrent,
102+
};
103+
104+
return pipe(
105+
E.Do,
106+
E.bind('previousVersionDirs', () =>
107+
pipe(
108+
resolveAndValidateSourceDirectories(previousVersionConfig),
109+
E.mapLeft(
110+
(error) =>
111+
new FileReadingError(`Failed to resolve previous version source directories: ${error.message}`, error),
112+
),
113+
),
114+
),
115+
E.bind('currentVersionDirs', () =>
116+
pipe(
117+
resolveAndValidateSourceDirectories(currentVersionConfig),
118+
E.mapLeft(
119+
(error) =>
120+
new FileReadingError(`Failed to resolve current version source directories: ${error.message}`, error),
121+
),
122+
),
123+
),
124+
E.map(({ previousVersionDirs, currentVersionDirs }) => [
125+
readFiles(allComponentTypes)(previousVersionDirs, config.exclude),
126+
readFiles(allComponentTypes)(currentVersionDirs, config.exclude),
127+
]),
128+
);
78129
}
79130

80131
return pipe(
81-
E.tryCatch(loadFiles, (e) => new FileReadingError('An error occurred while reading files.', e)),
132+
loadFiles(),
82133
TE.fromEither,
83134
TE.flatMap(([previous, current]) => changelog(previous, current, config)),
84135
TE.mapLeft(toErrors),

src/application/generators/openapi.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@ export default async function openApi(
1919
fileBodies: UnparsedApexBundle[],
2020
config: UserDefinedOpenApiConfig,
2121
) {
22+
// For backwards compatibility, use sourceDir if provided, otherwise derive from file paths or use current directory
23+
const sourceDirectory =
24+
config.sourceDir ||
25+
(fileBodies.length > 0 ? fileBodies[0].filePath.split('/').slice(0, -1).join('/') : process.cwd());
26+
2227
OpenApiSettings.build({
23-
sourceDirectory: config.sourceDir,
28+
sourceDirectory,
2429
outputDir: config.targetDir,
2530
openApiFileName: config.fileName,
2631
openApiTitle: config.title,

src/application/source-code-file-reader.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,13 @@ export function processFiles(fileSystem: FileSystem) {
248248

249249
const convertersToUse = componentTypesToRetrieve.map((componentType) => converters[componentType]);
250250

251-
return (rootPath: string, exclude: string[]) => {
251+
return (rootPaths: string | string[], exclude: string[]) => {
252+
const paths = Array.isArray(rootPaths) ? rootPaths : [rootPaths];
253+
254+
const allComponents = paths.flatMap((path) => fileSystem.getComponents(path));
255+
252256
return pipe(
253-
fileSystem.getComponents(rootPath),
257+
allComponents,
254258
(components) => {
255259
return components.map((component) => {
256260
const pathLocation = component.type.name === 'ApexClass' ? component.content : component.xml;

src/cli/args.ts

Lines changed: 100 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import {
99
UserDefinedOpenApiConfig,
1010
} from '../core/shared/types';
1111
import { TypeScriptLoader } from 'cosmiconfig-typescript-loader';
12-
import { markdownOptions } from './commands/markdown';
13-
import { openApiOptions } from './commands/openapi';
14-
import { changeLogOptions } from './commands/changelog';
12+
import { markdownOptions, validateMarkdownArgs } from './commands/markdown';
13+
import { openApiOptions, validateOpenApiArgs } from './commands/openapi';
14+
import { changeLogOptions, validateChangelogArgs } from './commands/changelog';
1515
import { pipe } from 'fp-ts/function';
16+
import { validateSourceDirectoryConfig } from '../util/source-directory-resolver';
1617

1718
const configOnlyMarkdownDefaults: Partial<UserDefinedMarkdownConfig> = {
1819
targetGenerator: 'markdown',
@@ -91,11 +92,23 @@ function extractArgsForCommandProvidedThroughCli(
9192

9293
switch (mergedConfig.targetGenerator) {
9394
case 'markdown':
94-
return E.right({ ...configOnlyMarkdownDefaults, ...mergedConfig } as UserDefinedMarkdownConfig);
95+
return pipe(
96+
validateSourceDirectoryConfig(extractSourceDirectoryConfig(mergedConfig)),
97+
E.mapLeft((error) => new Error(`Invalid markdown configuration: ${error.message}`)),
98+
E.map(() => ({ ...configOnlyMarkdownDefaults, ...mergedConfig }) as UserDefinedMarkdownConfig),
99+
);
95100
case 'openapi':
96-
return E.right({ ...configOnlyOpenApiDefaults, ...mergedConfig } as unknown as UserDefinedOpenApiConfig);
101+
return pipe(
102+
validateSourceDirectoryConfig(extractSourceDirectoryConfig(mergedConfig)),
103+
E.mapLeft((error) => new Error(`Invalid openapi configuration: ${error.message}`)),
104+
E.map(() => ({ ...configOnlyOpenApiDefaults, ...mergedConfig }) as unknown as UserDefinedOpenApiConfig),
105+
);
97106
case 'changelog':
98-
return E.right({ ...configOnlyChangelogDefaults, ...mergedConfig } as unknown as UserDefinedChangelogConfig);
107+
return pipe(
108+
validateChangelogConfig(mergedConfig as unknown as UserDefinedChangelogConfig),
109+
E.mapLeft((error) => new Error(`Invalid changelog configuration: ${error.message}`)),
110+
E.map(() => ({ ...configOnlyChangelogDefaults, ...mergedConfig }) as unknown as UserDefinedChangelogConfig),
111+
);
99112
default:
100113
return E.left(new Error(`Invalid command provided: ${mergedConfig.targetGenerator}`));
101114
}
@@ -124,20 +137,41 @@ function extractArgsForCommandsProvidedInConfig(
124137
E.map((cliArgs) => {
125138
return cliArgs;
126139
}),
127-
E.map((cliArgs) => ({ ...configOnlyMarkdownDefaults, ...generatorConfig, ...cliArgs })),
140+
E.flatMap((cliArgs) => {
141+
const mergedConfig = { ...configOnlyMarkdownDefaults, ...generatorConfig, ...cliArgs };
142+
return pipe(
143+
validateSourceDirectoryConfig(extractSourceDirectoryConfig(mergedConfig)),
144+
E.mapLeft((error) => new Error(`Invalid markdown configuration: ${error.message}`)),
145+
E.map(() => mergedConfig),
146+
);
147+
}),
128148
);
129149
case 'openapi':
130150
return pipe(
131151
extractMultiCommandConfig(extractFromProcessFn, 'openapi', generatorConfig),
132-
E.map((cliArgs) => ({ ...configOnlyOpenApiDefaults, ...generatorConfig, ...cliArgs })),
152+
E.flatMap((cliArgs) => {
153+
const mergedConfig = { ...configOnlyOpenApiDefaults, ...generatorConfig, ...cliArgs };
154+
return pipe(
155+
validateSourceDirectoryConfig(extractSourceDirectoryConfig(mergedConfig)),
156+
E.mapLeft((error) => new Error(`Invalid openapi configuration: ${error.message}`)),
157+
E.map(() => mergedConfig),
158+
);
159+
}),
133160
);
134161
case 'changelog':
135162
return pipe(
136163
extractMultiCommandConfig(extractFromProcessFn, 'changelog', generatorConfig),
137164
E.map((cliArgs) => {
138165
return cliArgs;
139166
}),
140-
E.map((cliArgs) => ({ ...configOnlyChangelogDefaults, ...generatorConfig, ...cliArgs })),
167+
E.flatMap((cliArgs) => {
168+
const mergedConfig = { ...configOnlyChangelogDefaults, ...generatorConfig, ...cliArgs };
169+
return pipe(
170+
validateChangelogConfig(mergedConfig as unknown as UserDefinedChangelogConfig),
171+
E.mapLeft((error) => new Error(`Invalid changelog configuration: ${error.message}`)),
172+
E.map(() => mergedConfig),
173+
);
174+
}),
141175
);
142176
}
143177
});
@@ -193,13 +227,13 @@ function extractYargsDemandingCommand(extractFromProcessFn: ExtractArgsFromProce
193227
return yargs
194228
.config(config.config as Record<string, unknown>)
195229
.command('markdown', 'Generate documentation from Apex classes as a Markdown site.', (yargs) =>
196-
yargs.options(markdownOptions),
230+
yargs.options(markdownOptions).check(validateMarkdownArgs),
197231
)
198-
.command('openapi', 'Generate an OpenApi REST specification from Apex classes.', () =>
199-
yargs.options(openApiOptions),
232+
.command('openapi', 'Generate an OpenApi REST specification from Apex classes.', (yargs) =>
233+
yargs.options(openApiOptions).check(validateOpenApiArgs),
200234
)
201-
.command('changelog', 'Generate a changelog from 2 versions of the source code.', () =>
202-
yargs.options(changeLogOptions),
235+
.command('changelog', 'Generate a changelog from 2 versions of the source code.', (yargs) =>
236+
yargs.options(changeLogOptions).check(validateChangelogArgs),
203237
)
204238
.demandCommand()
205239
.parseSync(extractFromProcessFn());
@@ -221,14 +255,66 @@ function extractMultiCommandConfig(
221255
}
222256
}
223257

258+
function getValidationFunction(generator: Generators) {
259+
switch (generator) {
260+
case 'markdown':
261+
return validateMarkdownArgs;
262+
case 'openapi':
263+
return validateOpenApiArgs;
264+
case 'changelog':
265+
return validateChangelogArgs;
266+
}
267+
}
268+
224269
const options = getOptions(command);
270+
const validator = getValidationFunction(command);
225271
return E.tryCatch(() => {
226272
return yargs(extractFromProcessFn())
227273
.config(config)
228274
.options(options)
275+
.check(validator)
229276
.fail((msg) => {
230277
throw new Error(`Invalid configuration for command "${command}": ${msg}`);
231278
})
232279
.parseSync();
233280
}, E.toError);
234281
}
282+
283+
function extractSourceDirectoryConfig(config: Record<string, unknown>): {
284+
sourceDir?: string;
285+
sourceDirs?: string[];
286+
useSfdxProjectJson?: boolean;
287+
sfdxProjectPath?: string;
288+
} {
289+
return {
290+
sourceDir: config.sourceDir as string | undefined,
291+
sourceDirs: config.sourceDirs as string[] | undefined,
292+
useSfdxProjectJson: config.useSfdxProjectJson as boolean | undefined,
293+
sfdxProjectPath: config.sfdxProjectPath as string | undefined,
294+
};
295+
}
296+
297+
function validateChangelogConfig(
298+
config: UserDefinedChangelogConfig,
299+
): E.Either<{ message: string }, UserDefinedChangelogConfig> {
300+
const previousVersionConfig = {
301+
sourceDir: config.previousVersionDir,
302+
sourceDirs: config.previousVersionDirs,
303+
useSfdxProjectJson: config.useSfdxProjectJsonForPrevious,
304+
sfdxProjectPath: config.sfdxProjectPathForPrevious,
305+
};
306+
307+
const currentVersionConfig = {
308+
sourceDir: config.currentVersionDir,
309+
sourceDirs: config.currentVersionDirs,
310+
useSfdxProjectJson: config.useSfdxProjectJsonForCurrent,
311+
sfdxProjectPath: config.sfdxProjectPathForCurrent,
312+
};
313+
314+
return pipe(
315+
E.Do,
316+
E.bind('previousValid', () => validateSourceDirectoryConfig(previousVersionConfig)),
317+
E.bind('currentValid', () => validateSourceDirectoryConfig(currentVersionConfig)),
318+
E.map(() => config),
319+
);
320+
}

0 commit comments

Comments
 (0)