@cparra/apexdocs
Version:
Library with CLI capabilities to generate documentation for Salesforce Apex classes.
555 lines (546 loc) • 21.1 kB
JavaScript
var logger$1 = require('../logger-D4Q3KA6D.js');
var require$$0 = require('yargs');
var cosmiconfig = require('cosmiconfig');
var E = require('fp-ts/Either');
var cosmiconfigTypescriptLoader = require('cosmiconfig-typescript-loader');
var _function = require('fp-ts/function');
require('fp-ts/TaskEither');
require('js-yaml');
require('path');
require('fp-ts/Task');
require('fp-ts/lib/Array');
require('@cparra/apex-reflection');
require('fp-ts/Option');
require('fast-xml-parser');
require('handlebars');
require('fp-ts/boolean');
require('fp-ts/Array');
require('fs');
require('fp-ts/lib/TaskEither');
require('minimatch');
require('@salesforce/source-deploy-retrieve');
require('chalk');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var E__namespace = /*#__PURE__*/_interopNamespaceDefault(E);
function validateMarkdownArgs(argv) {
const hasSourceDir = argv.sourceDir && (typeof argv.sourceDir === "string" || Array.isArray(argv.sourceDir) && argv.sourceDir.length > 0);
const hasUseSfdxProjectJson = argv.useSfdxProjectJson;
if (!hasSourceDir && !hasUseSfdxProjectJson) {
throw new Error("Must specify one of: --sourceDir or --useSfdxProjectJson");
}
return true;
}
const markdownOptions = {
sourceDir: {
type: "string",
array: true,
alias: "s",
demandOption: false,
describe: "The directory location(s) which contain your apex .cls classes. Can specify a single directory or multiple directories. Cannot be used with useSfdxProjectJson.",
conflicts: ["useSfdxProjectJson"]
},
useSfdxProjectJson: {
type: "boolean",
demandOption: false,
describe: "Read source directories from sfdx-project.json packageDirectories. Cannot be used with sourceDir.",
conflicts: ["sourceDir"]
},
sfdxProjectPath: {
type: "string",
demandOption: false,
describe: "Path to the directory containing sfdx-project.json (defaults to current working directory). Only used with useSfdxProjectJson.",
implies: "useSfdxProjectJson"
},
targetDir: {
type: "string",
alias: "t",
default: logger$1.markdownDefaults.targetDir,
describe: "The directory location where documentation will be generated to."
},
scope: {
type: "string",
array: true,
alias: "p",
default: logger$1.markdownDefaults.scope,
describe: "A list of scopes to document. Values should be separated by a space, e.g --scope global public namespaceaccessible. Annotations are supported and should be passed lowercased and without the @ symbol, e.g. namespaceaccessible auraenabled."
},
customObjectVisibility: {
type: "string",
array: true,
alias: "v",
default: logger$1.markdownDefaults.customObjectVisibility,
choices: ["public", "protected", "packageprotected"],
describe: "Controls which custom objects are documented. Values should be separated by a space."
},
defaultGroupName: {
type: "string",
default: logger$1.markdownDefaults.defaultGroupName,
describe: "Defines the @group name to be used when a file does not specify it."
},
customObjectsGroupName: {
type: "string",
default: logger$1.markdownDefaults.customObjectsGroupName,
describe: "The name under which custom objects will be grouped in the Reference Guide"
},
triggersGroupName: {
type: "string",
default: logger$1.markdownDefaults.triggersGroupName,
describe: "The name under which triggers will be grouped in the Reference Guide"
},
namespace: {
type: "string",
describe: "The package namespace, if any. If provided, it will be added to the generated files."
},
sortAlphabetically: {
type: "boolean",
describe: "Whether to sort files and members alphabetically.",
default: logger$1.markdownDefaults.sortAlphabetically
},
includeMetadata: {
type: "boolean",
describe: "Whether to include the file's meta.xml information: Whether it is active and and the API version",
default: logger$1.markdownDefaults.includeMetadata
},
linkingStrategy: {
type: "string",
describe: "The strategy to use when linking to other documentation pages.",
choices: ["relative", "no-link", "none"],
default: logger$1.markdownDefaults.linkingStrategy
},
referenceGuideTitle: {
type: "string",
describe: "The title of the reference guide.",
default: logger$1.markdownDefaults.referenceGuideTitle
},
includeFieldSecurityMetadata: {
type: "boolean",
describe: "Whether to include the compliance category and security classification for fields in the generated files.",
default: logger$1.markdownDefaults.includeFieldSecurityMetadata
},
includeInlineHelpTextMetadata: {
type: "boolean",
describe: "Whether to include the inline help text for fields in the generated files."
}
};
function validateOpenApiArgs(argv) {
const hasSourceDir = argv.sourceDir && (typeof argv.sourceDir === "string" || Array.isArray(argv.sourceDir) && argv.sourceDir.length > 0);
const hasUseSfdxProjectJson = argv.useSfdxProjectJson;
if (!hasSourceDir && !hasUseSfdxProjectJson) {
throw new Error("Must specify one of: --sourceDir or --useSfdxProjectJson");
}
return true;
}
const openApiOptions = {
sourceDir: {
type: "string",
array: true,
alias: "s",
demandOption: false,
describe: "The directory location(s) which contain your apex .cls classes. Can specify a single directory or multiple directories. Cannot be used with useSfdxProjectJson.",
conflicts: ["useSfdxProjectJson"]
},
useSfdxProjectJson: {
type: "boolean",
demandOption: false,
describe: "Read source directories from sfdx-project.json packageDirectories. Cannot be used with sourceDir.",
conflicts: ["sourceDir"]
},
sfdxProjectPath: {
type: "string",
demandOption: false,
describe: "Path to the directory containing sfdx-project.json (defaults to current working directory). Only used with useSfdxProjectJson.",
implies: "useSfdxProjectJson"
},
targetDir: {
type: "string",
alias: "t",
default: logger$1.openApiDefaults.targetDir,
describe: "The directory location where the OpenApi file will be generated."
},
fileName: {
type: "string",
default: logger$1.openApiDefaults.fileName,
describe: "The name of the OpenApi file to be generated."
},
namespace: {
type: "string",
describe: "The package namespace, if any. This will be added to the API file Server Url."
},
title: {
type: "string",
default: logger$1.openApiDefaults.title,
describe: "The title of the OpenApi file."
},
apiVersion: {
type: "string",
default: logger$1.openApiDefaults.apiVersion,
describe: "The version of the OpenApi file."
}
};
function validateChangelogArgs(argv) {
const hasPreviousVersionDir = argv.previousVersionDir && (typeof argv.previousVersionDir === "string" || Array.isArray(argv.previousVersionDir) && argv.previousVersionDir.length > 0);
const hasCurrentVersionDir = argv.currentVersionDir && (typeof argv.currentVersionDir === "string" || Array.isArray(argv.currentVersionDir) && argv.currentVersionDir.length > 0);
if (!hasPreviousVersionDir) {
throw new Error("Must specify --previousVersionDir");
}
if (!hasCurrentVersionDir) {
throw new Error("Must specify --currentVersionDir");
}
return true;
}
const changeLogOptions = {
previousVersionDir: {
type: "string",
array: true,
alias: "p",
demandOption: false,
describe: "The directory location(s) of the previous version of the source code. Can specify a single directory or multiple directories."
},
currentVersionDir: {
type: "string",
array: true,
alias: "c",
demandOption: false,
describe: "The directory location(s) of the current version of the source code. Can specify a single directory or multiple directories."
},
targetDir: {
type: "string",
alias: "t",
default: logger$1.changeLogDefaults.targetDir,
describe: "The directory location where the changelog file will be generated."
},
fileName: {
type: "string",
default: logger$1.changeLogDefaults.fileName,
describe: "The name of the changelog file to be generated."
},
scope: {
type: "string",
array: true,
alias: "s",
default: logger$1.changeLogDefaults.scope,
describe: "The list of scope to respect when generating the changelog. Values should be separated by a space, e.g --scope global public namespaceaccessible. Annotations are supported and should be passed lowercased and without the @ symbol, e.g. namespaceaccessible auraenabled."
},
customObjectVisibility: {
type: "string",
array: true,
alias: "v",
default: logger$1.changeLogDefaults.customObjectVisibility,
choices: ["public", "protected", "packageprotected"],
describe: "Controls which custom objects are documented. Values should be separated by a space."
},
skipIfNoChanges: {
type: "boolean",
default: logger$1.changeLogDefaults.skipIfNoChanges,
describe: "Skip the changelog generation if there are no changes between the previous and current version."
}
};
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __async$1 = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
const yargs = require$$0;
const configOnlyMarkdownDefaults = {
targetGenerator: "markdown",
excludeTags: [],
exclude: []
};
const configOnlyOpenApiDefaults = {
targetGenerator: "openapi",
exclude: []
};
const configOnlyChangelogDefaults = {
targetGenerator: "changelog",
exclude: []
};
function getArgumentsFromProcess() {
return process.argv.slice(2);
}
function extractConfigFromPackageJsonOrFile() {
return __async$1(this, null, function* () {
var _a;
const result = yield cosmiconfig.cosmiconfig("apexdocs", {
loaders: {
".ts": cosmiconfigTypescriptLoader.TypeScriptLoader()
}
}).search();
return { config: (_a = result == null ? void 0 : result.config) != null ? _a : {} };
});
}
function extractArgs() {
return __async$1(this, arguments, function* (extractFromProcessFn = getArgumentsFromProcess, extractConfigFn = extractConfigFromPackageJsonOrFile) {
const config = yield extractConfigFn();
function handle(configType) {
switch (configType._type) {
case "no-config":
case "single-command-config":
return handleSingleCommand(extractFromProcessFn, config);
case "multi-command-config":
return extractArgsForCommandsProvidedInConfig(extractFromProcessFn, config.config);
}
}
return _function.pipe(getConfigType(config), E__namespace.flatMap(handle));
});
}
function handleSingleCommand(extractFromProcessFn, config) {
return _function.pipe(
E__namespace.right(config),
E__namespace.flatMap((config2) => extractArgsForCommandProvidedThroughCli(extractFromProcessFn, config2)),
E__namespace.map((config2) => [config2])
);
}
function extractArgsForCommandProvidedThroughCli(extractFromProcessFn, config) {
const cliArgs = extractYargsDemandingCommand(extractFromProcessFn, config);
const commandName = cliArgs._[0];
const mergedConfig = __spreadProps(__spreadValues(__spreadValues({}, config.config), cliArgs), { targetGenerator: commandName });
switch (mergedConfig.targetGenerator) {
case "markdown":
return _function.pipe(
logger$1.validateSourceDirectoryConfig(extractSourceDirectoryConfig(mergedConfig)),
E__namespace.mapLeft((error) => new Error(`Invalid markdown configuration: ${error.message}`)),
E__namespace.map(() => __spreadValues(__spreadValues({}, configOnlyMarkdownDefaults), mergedConfig))
);
case "openapi":
return _function.pipe(
logger$1.validateSourceDirectoryConfig(extractSourceDirectoryConfig(mergedConfig)),
E__namespace.mapLeft((error) => new Error(`Invalid openapi configuration: ${error.message}`)),
E__namespace.map(() => __spreadValues(__spreadValues({}, configOnlyOpenApiDefaults), mergedConfig))
);
case "changelog":
return _function.pipe(
validateChangelogConfig(mergedConfig),
E__namespace.mapLeft((error) => new Error(`Invalid changelog configuration: ${error.message}`)),
E__namespace.map(() => __spreadValues(__spreadValues({}, configOnlyChangelogDefaults), mergedConfig))
);
default:
return E__namespace.left(new Error(`Invalid command provided: ${mergedConfig.targetGenerator}`));
}
}
function extractArgsForCommandsProvidedInConfig(extractFromProcessFn, config) {
const providedThroughCli = yargs.parseSync(extractFromProcessFn());
const hasACommandBeenProvided = providedThroughCli._.length > 0;
const configs = Object.entries(config).filter(([generator]) => hasACommandBeenProvided ? providedThroughCli._[0] === generator : true).map(([generator, generatorConfig]) => {
switch (generator) {
case "markdown":
return _function.pipe(
extractMultiCommandConfig(extractFromProcessFn, "markdown", generatorConfig),
E__namespace.map((cliArgs) => {
return cliArgs;
}),
E__namespace.flatMap((cliArgs) => {
const mergedConfig = __spreadValues(__spreadValues(__spreadValues({}, configOnlyMarkdownDefaults), generatorConfig), cliArgs);
return _function.pipe(
logger$1.validateSourceDirectoryConfig(extractSourceDirectoryConfig(mergedConfig)),
E__namespace.mapLeft((error) => new Error(`Invalid markdown configuration: ${error.message}`)),
E__namespace.map(() => mergedConfig)
);
})
);
case "openapi":
return _function.pipe(
extractMultiCommandConfig(extractFromProcessFn, "openapi", generatorConfig),
E__namespace.flatMap((cliArgs) => {
const mergedConfig = __spreadValues(__spreadValues(__spreadValues({}, configOnlyOpenApiDefaults), generatorConfig), cliArgs);
return _function.pipe(
logger$1.validateSourceDirectoryConfig(extractSourceDirectoryConfig(mergedConfig)),
E__namespace.mapLeft((error) => new Error(`Invalid openapi configuration: ${error.message}`)),
E__namespace.map(() => mergedConfig)
);
})
);
case "changelog":
return _function.pipe(
extractMultiCommandConfig(extractFromProcessFn, "changelog", generatorConfig),
E__namespace.map((cliArgs) => {
return cliArgs;
}),
E__namespace.flatMap((cliArgs) => {
const mergedConfig = __spreadValues(__spreadValues(__spreadValues({}, configOnlyChangelogDefaults), generatorConfig), cliArgs);
return _function.pipe(
validateChangelogConfig(mergedConfig),
E__namespace.mapLeft((error) => new Error(`Invalid changelog configuration: ${error.message}`)),
E__namespace.map(() => mergedConfig)
);
})
);
}
});
return E__namespace.sequenceArray(configs);
}
function getConfigType(config) {
if (!config) {
return E__namespace.right({ _type: "no-config" });
}
const rootKeys = Object.keys(config.config);
const validRootKeys = ["markdown", "openapi", "changelog"];
const containsAnyValidRootKey = rootKeys.some((key) => validRootKeys.includes(key));
if (containsAnyValidRootKey) {
const commands = rootKeys.filter((key) => validRootKeys.includes(key));
const hasInvalidCommands = rootKeys.some((key) => !validRootKeys.includes(key));
if (hasInvalidCommands) {
return E__namespace.left(new Error(`Invalid command(s) provided in the configuration file: ${rootKeys}`));
}
return E__namespace.right({
_type: "multi-command-config",
commands
});
}
return E__namespace.right({ _type: "single-command-config" });
}
function extractYargsDemandingCommand(extractFromProcessFn, config) {
return yargs.config(config.config).command(
"markdown",
"Generate documentation from Apex classes as a Markdown site.",
(yargs2) => yargs2.options(markdownOptions).check(validateMarkdownArgs)
).command(
"openapi",
"Generate an OpenApi REST specification from Apex classes.",
(yargs2) => yargs2.options(openApiOptions).check(validateOpenApiArgs)
).command(
"changelog",
"Generate a changelog from 2 versions of the source code.",
(yargs2) => yargs2.options(changeLogOptions).check(validateChangelogArgs)
).demandCommand().parseSync(extractFromProcessFn());
}
function extractMultiCommandConfig(extractFromProcessFn, command, config) {
function getOptions(generator) {
switch (generator) {
case "markdown":
return markdownOptions;
case "openapi":
return openApiOptions;
case "changelog":
return changeLogOptions;
}
}
function getValidationFunction(generator) {
switch (generator) {
case "markdown":
return validateMarkdownArgs;
case "openapi":
return validateOpenApiArgs;
case "changelog":
return validateChangelogArgs;
}
}
const options = getOptions(command);
const validator = getValidationFunction(command);
return E__namespace.tryCatch(() => {
return yargs(extractFromProcessFn()).config(config).options(options).check(validator).fail((msg) => {
throw new Error(`Invalid configuration for command "${command}": ${msg}`);
}).parseSync();
}, E__namespace.toError);
}
function extractSourceDirectoryConfig(config) {
return {
sourceDir: config.sourceDir,
useSfdxProjectJson: config.useSfdxProjectJson,
sfdxProjectPath: config.sfdxProjectPath
};
}
function validateChangelogConfig(config) {
const previousVersionConfig = {
sourceDir: config.previousVersionDir
};
const currentVersionConfig = {
sourceDir: config.currentVersionDir
};
return _function.pipe(
E__namespace.Do,
E__namespace.bind("previousValid", () => logger$1.validateSourceDirectoryConfig(previousVersionConfig)),
E__namespace.bind("currentValid", () => logger$1.validateSourceDirectoryConfig(currentVersionConfig)),
E__namespace.map(() => config)
);
}
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
const logger = new logger$1.StdOutLogger();
function main() {
function parseResult(result) {
E__namespace.match(catchUnexpectedError, (successMessage) => {
logger.logSingle(successMessage);
})(result);
}
function catchUnexpectedError(error) {
logger.error(`\u274C An error occurred while processing the request: ${error}`);
process.exit(1);
}
extractArgs().then((maybeConfigs) => __async(null, null, function* () {
E__namespace.match(catchUnexpectedError, (configs) => __async(null, null, function* () {
for (const config of configs) {
yield logger$1.Apexdocs.generate(config, logger).then(parseResult);
}
}))(maybeConfigs);
})).catch(catchUnexpectedError);
}
main();
;