UNPKG

github-action-readme-generator

Version:

The docs generator for GitHub Actions. Auto-syncs action.yml to README.md with 8 sections: inputs, outputs, usage, badges, branding & more. Works as CLI or GitHub Action.

494 lines 19.8 kB
/** * This class handles input configuration and manipulation. * It imports various modules and packages for file operations, configuration parsing, and logging. * The class has methods for initializing the input configuration, setting default values, and converting the configuration to a string. * It also has properties for storing the configuration values, sections, readme path, action instance, and readme editor instance. */ import * as fs from 'node:fs'; import * as path from 'node:path'; import * as core from '@actions/core'; import { context as githubContext } from '@actions/github'; import nconf from 'nconf'; import YAML from 'yaml'; const Context = githubContext.constructor; import Action from './Action.js'; import { ConfigKeys, configFileName, README_SECTIONS } from './constants.js'; import { repositoryFinder } from './helpers.js'; import LogTask from './logtask/index.js'; import ReadmeEditor from './readme-editor.js'; const { Provider } = nconf; /** * Change working directory to output of workingDirectory() */ // process.chdir(workingDirectory()); export const metaActionPath = '../../action.yml'; /** * Represents the command line argument options for the application. */ const argvOptions = {}; /** * Save option configuration. * @property {string} alias - Alias for the save option. * @property {string} describe - Description for the save option. * @property {boolean} parseValues - Specifies whether to parse values for the save option. * @property {string} type - Type of the save option. */ argvOptions[ConfigKeys.Save] = { alias: 'save', describe: `Save this config to ${configFileName}`, parseValues: true, type: 'boolean', }; /** * Paths action option configuration. * @property {string | string[]} alias - Alias(es) for the pathsAction option. * @property {string} type - Type of the pathsAction option. * @property {string} describe - Description for the pathsAction option. */ argvOptions[ConfigKeys.pathsAction] = { alias: ['pathsAction', 'action'], type: 'string', describe: 'Path to the action.yml', }; /** * Paths readme option configuration. * @property {string | string[]} alias - Alias(es) for the pathsReadme option. * @property {string} type - Type of the pathsReadme option. * @property {string} describe - Description for the pathsReadme option. */ argvOptions[ConfigKeys.pathsReadme] = { alias: ['pathsReadme', 'readme'], type: 'string', describe: 'Path to the README file', }; /** * Branding SVG path option configuration. * @property {string} alias - Alias for the svg option. * @property {string} type - Type of the svg option. * @property {string} describe - Description for the svg option. */ argvOptions[ConfigKeys.BrandingSvgPath] = { alias: 'svg', type: 'string', describe: 'Save and load the branding svg image in the README from this path', }; /** * Branding as title prefix option configuration. * @property {string} alias - Alias for the branding_prefix option. * @property {string} type - Type of the branding_prefix option. * @property {boolean} parseValues - Specifies whether to parse values for the branding_prefix option. * @property {string} describe - Description for the branding_prefix option. */ argvOptions[ConfigKeys.BrandingAsTitlePrefix] = { alias: 'branding_prefix', type: 'boolean', parseValues: true, describe: 'Use the branding svg as a prefix for the README title', }; /** * Owner option configuration. * @property {string} alias - Alias for the owner option. * @property {string} type - Type of the owner option. * @property {string} describe - Description for the owner option. */ argvOptions[ConfigKeys.Owner] = { alias: 'owner', type: 'string', describe: 'The GitHub Action repository owner. i.e: `bitflight-devops`', }; /** * Repo option configuration. * @property {string} alias - Alias for the repo option. * @property {string} type - Type of the repo option. * @property {string} describe - Description for the repo option. */ argvOptions[ConfigKeys.Repo] = { alias: 'repo', type: 'string', describe: 'The GitHub Action repository name. i.e: `github-action-readme-generator`', }; /** * Prettier option configuration. * @property {string | string[]} alias - Alias(es) for the prettier option. * @property {string} type - Type of the prettier option. * @property {boolean} parseValues - Specifies whether to parse values for the prettier option. * @property {string} describe - Description for the prettier option. */ argvOptions[ConfigKeys.Prettier] = { alias: ['pretty', 'prettier'], type: 'boolean', parseValues: true, describe: 'Format the markdown using prettier formatter', }; /** * Versioning enabled option configuration. * @property {string | string[]} alias - Alias(es) for the versioning_enabled option. * @property {string} describe - Description for the versioning_enabled option. * @property {boolean} parseValues - Specifies whether to parse values for the versioning_enabled option. * @property {string} type - Type of the versioning_enabled option. */ argvOptions[ConfigKeys.VersioningEnabled] = { alias: ['versioning', 'versioning_enabled'], describe: 'Enable the update of the usage version to match the latest version in the package.json file', parseValues: true, type: 'boolean', }; /** * Versioning override option configuration. * @property {string | string[]} alias - Alias(es) for the versioning_override option. * @property {string} describe - Description for the versioning_override option. * @property {boolean} parseValues - Specifies whether to parse values for the versioning_override option. */ argvOptions[ConfigKeys.VersioningOverride] = { alias: ['setversion', 'versioning_override', 'version_override'], describe: 'Set a specific version to display in the README.md', parseValues: true, }; /** * Versioning prefix option configuration. * @property {string | string[]} alias - Alias(es) for the version_prefix option. * @property {string} describe - Description for the version_prefix option. * @property {boolean} parseValues - Specifies whether to parse values for the version_prefix option. */ argvOptions[ConfigKeys.VersioningPrefix] = { alias: ['vp', 'version_prefix'], describe: "Prefix the version with this value (if it isn't already prefixed)", parseValues: true, }; /** * Versioning branch option configuration. * @property {string | string[]} alias - Alias(es) for the versioning_default_branch option. * @property {string} describe - Description for the versioning_default_branch option. * @property {boolean} parseValues - Specifies whether to parse values for the versioning_default_branch option. */ argvOptions[ConfigKeys.VersioningBranch] = { alias: ['branch', 'versioning_default_branch'], describe: 'If versioning is disabled show this branch instead', parseValues: true, }; /** * Versioning source option configuration. * @property {string | string[]} alias - Alias(es) for the version_source option. * @property {string} describe - Description for the version_source option. * @property {boolean} parseValues - Specifies whether to parse values for the version_source option. * @property {string} type - Type of the version_source option. */ argvOptions[ConfigKeys.VersioningSource] = { alias: ['version-source', 'version_source', 'versioning_source'], describe: 'How to detect the action version (git-tag, git-branch, git-sha, package-json, explicit)', parseValues: true, type: 'string', }; /** * Include GitHub version badge option configuration. * @property {string | string[]} alias - Alias(es) for the include_github_version_badge option. * @property {string} describe - Description for the include_github_version_badge option. * @property {boolean} parseValues - Specifies whether to parse values for the include_github_version_badge option. * @property {string} type - Type of the include_github_version_badge option. */ argvOptions[ConfigKeys.IncludeGithubVersionBadge] = { alias: ['version-badge', 'versioning_badge', 'include_github_version_badge'], describe: 'Display the current version as a badge', parseValues: true, type: 'boolean', }; /** * Title prefix option configuration. * @property {string | string[]} alias - Alias(es) for the title_prefix option. * @property {string} describe - Description for the title_prefix option. * @property {boolean} parseValues - Specifies whether to parse values for the title_prefix option. */ argvOptions[ConfigKeys.TitlePrefix] = { alias: ['prefix', 'title_prefix'], describe: 'Add a prefix to the README title', parseValues: true, }; /** * Debug Nconf option configuration. * @property {string} describe - Description for the debugNconf option. * @property {boolean} parseValues - Specifies whether to parse values for the debugNconf option. * @property {string} type - Type of the debugNconf option. */ argvOptions[ConfigKeys.DebugNconf] = { alias: ['debug_nconf'], describe: 'Print out the resolved nconf object with all values', parseValues: true, type: 'boolean', }; /** * Debug Config option configuration. * @property {string} describe - Description for the debugConfig option. * @property {boolean} parseValues - Specifies whether to parse values for the debugConfig option. * @property {string} type - Type of the debugConfig option. */ argvOptions[ConfigKeys.DebugConfig] = { alias: ['debug_config'], describe: 'Print out the resolved nconf object with all values', parseValues: true, type: 'boolean', }; /** * Configuration inputs from the github action don't * all match the input names when running on cli. * This maps the action inputs to the cli. */ const ConfigKeysInputsMap = { save: ConfigKeys.Save, action: ConfigKeys.pathsAction, readme: ConfigKeys.pathsReadme, branding_svg_path: ConfigKeys.BrandingSvgPath, branding_as_title_prefix: ConfigKeys.BrandingAsTitlePrefix, versioning_enabled: ConfigKeys.VersioningEnabled, version_prefix: ConfigKeys.VersioningPrefix, versioning_default_branch: ConfigKeys.VersioningBranch, version_override: ConfigKeys.VersioningOverride, version_source: ConfigKeys.VersioningSource, include_github_version_badge: ConfigKeys.IncludeGithubVersionBadge, owner: ConfigKeys.Owner, repo: ConfigKeys.Repo, title_prefix: ConfigKeys.TitlePrefix, pretty: ConfigKeys.Prettier, }; export function transformGitHubInputsToArgv(log, _config, obj) { /** The obj.key is always in lowercase, but it checks for it without case sensitivity */ if (/^(INPUT|input)_[A-Z_a-z]\w*$/.test(obj.key)) { log.debug(`Parsing input: ${obj.key} with ith value: ${obj.value}`); const keyParsed = obj.key.replace(/^(INPUT|input)_/, '').toLocaleLowerCase(); const key = ConfigKeysInputsMap[keyParsed] || keyParsed; // Skip empty values for owner/repo to allow fallback detection from .git/config or GITHUB_REPOSITORY if ((key === 'owner' || key === 'repo') && (!obj.value || obj.value === '')) { log.debug(`Ignoring empty ${key} input to allow auto-detection`); return undefined; } log.debug(`New input is ${key} with the value ${obj.value}`); return { key, value: obj.value }; } log.debug(`Ignoring input: ${obj.key} with ith value: ${obj.value}`); return undefined; } /** * Sets config value from action file default * * @param {Action} actionInstance - The action instance * @param {string} inputName - The input name * @returns {string | boolean | undefined} The default value */ export function setConfigValueFromActionFileDefault(log, actionInstance, inputName) { if (ConfigKeysInputsMap[inputName] === undefined) { log.error(`${inputName} from ${actionInstance.path} does not match a known input. Known inputs are: ${Object.keys(ConfigKeysInputsMap)}`); return; } const configName = ConfigKeysInputsMap[inputName]; const defaultValue = actionInstance.inputDefault(inputName); log.debug(`Default Value for action.yml: ${inputName} CLI: ${configName} = ${defaultValue}`); return defaultValue; } /** * Collects all default values from action file * * @returns {IOptions} The default values object */ export function collectAllDefaultValuesFromAction(log, providedMetaActionPath, providedDefaults = {}) { log.debug('Collecting default values from action.yml'); // This loads the defaults from THIS action's own action.yml file (github-action-readme-generator's action.yml) // NOT the user's action.yml file (which is loaded separately via the 'action' input parameter) // Therefore, we use import.meta.dirname to find this package's action.yml regardless of where it's installed const thisActionPath = path.join(import.meta.dirname, providedMetaActionPath ?? metaActionPath); try { const defaultValues = {}; const thisAction = new Action(thisActionPath); const defaults = { ...thisAction.inputs, ...providedDefaults }; // Collect all of the default values from the action.yml file if (defaults) { for (const key of Object.keys(defaults)) { const mappedKey = ConfigKeysInputsMap[key] ?? key; defaultValues[mappedKey] = setConfigValueFromActionFileDefault(log, thisAction, key); } } log.debug(JSON.stringify(defaultValues, null, 2)); return defaultValues; } catch (error) { // When running as a CLI tool (e.g., via npx or yarn dlx), the tool's own action.yml // may not be present in the node_modules. This is expected behavior, as the tool // should still work to generate documentation for other actions. log.debug(`Could not load defaults from this tool's action.yml at ${thisActionPath}: ${error}`); log.debug('Continuing without default values from action.yml'); return {}; } } /** * Loads the configuration * * @returns {ProviderInstance} The configuration instance */ export function loadConfig(log, providedConfig, configFilePath) { log.debug('Loading config from env and argv'); const config = providedConfig ?? new Provider(); if (process.env.GITHUB_ACTION === 'true') { log.info('Running in GitHub action'); } if (configFilePath) { if (fs.existsSync(configFilePath)) { log.info(`Config file found: ${configFilePath}`); config.file(configFilePath); } else { log.debug(`Config file not found: ${configFilePath}`); } } config .env({ lowerCase: true, parseValues: true, transform: (obj) => { return transformGitHubInputsToArgv(log, config, obj); }, }) .argv(argvOptions); return config; } /** * Loads the default configuration * * @param {ProviderInstance} config - The config instance * @returns {ProviderInstance} The updated config instance */ export function loadDefaultConfig(log, config, providedContext) { log.debug('Loading default config'); const defaultValues = collectAllDefaultValuesFromAction(log); const context = providedContext ?? new Context(); // Get owner/repo from config (which includes CLI args), falling back to env vars for GitHub Actions const ownerFromConfig = config.get('owner'); const repoFromConfig = config.get('repo'); const ownerInput = ownerFromConfig ?? process.env.INPUT_OWNER ?? ''; const repoInput = repoFromConfig ?? process.env.INPUT_REPO ?? ''; // Get the action path to derive the target repo directory for .git/config lookup const actionPath = config.get(ConfigKeys.pathsAction); const actionDir = actionPath ? path.dirname(path.resolve(actionPath)) : undefined; log.debug(`Action directory for repository detection: ${actionDir ?? 'not specified'}`); const repositoryDetail = repositoryFinder(`${ownerInput}/${repoInput}`, context, actionDir); log.debug(`repositoryDetail: ${repositoryDetail}`); // Apply the default values from the action.yml file return config.defaults({ ...defaultValues, owner: repositoryDetail?.owner, repo: repositoryDetail?.repo, sections: [...README_SECTIONS], }); } /** * Represents the required inputs for the action. */ const RequiredInputs = [ ConfigKeys.pathsAction, ConfigKeys.pathsReadme, ConfigKeys.Owner, ConfigKeys.Repo, ]; /** * Loads the required configuration * * @param {ProviderInstance} config - The config instance * @returns {ProviderInstance} The updated config instance */ export function loadRequiredConfig(log, config, requiredInputs = RequiredInputs) { log.debug('Loading required config'); return config.required([...requiredInputs]); } /** * */ export function loadAction(log, actionPath) { log.debug(`Loading action from: ${actionPath}`); if (actionPath) { return new Action(path.resolve(actionPath)); } throw new Error(`Action path not found: ${actionPath}`); } /** * Main Inputs class that handles configuration */ export default class Inputs { /** * The configuration instance */ config; /** * The readme sections */ sections; /** * The readme file path */ readmePath; /** * The config file path */ configPath; /** * The action instance */ action; /** * The readme editor instance */ readmeEditor; /** * The repository owner */ owner; /** * The repository name */ repo; /** The logger for this instance */ log; /** * Initializes a new instance of the Inputs class. */ constructor(providedInputContext = {}, log = new LogTask('inputs')) { this.log = log ?? new LogTask('inputs'); this.log.debug('Initializing Inputs'); const inputContext = providedInputContext ?? {}; this.configPath = inputContext.configPath ?? path.resolve(configFileName); this.config = inputContext.config ?? new Provider(); loadConfig(log, this.config, this.configPath); loadDefaultConfig(log, this.config); loadRequiredConfig(log, this.config); this.action = inputContext.action ?? loadAction(log, this.config.get(ConfigKeys.pathsAction)); this.config.set('sections', inputContext.sections ?? this.config.get('sections')); this.sections = this.config.get('sections'); this.readmePath = inputContext.readmePath ?? path.resolve(this.config.get(ConfigKeys.pathsReadme)); this.readmeEditor = inputContext.readmeEditor ?? new ReadmeEditor(this.readmePath); /** * Output the readme path that is being parsed */ if (process.env.GITHUB_ACTIONS) { core.setOutput('readme', this.readmePath); } /** * owner is required, and if it doesn't exist it is handled by nconf which throws an error */ this.owner = inputContext.owner ?? this.config.get('owner'); /** * repo is required, and if it doesn't exist it is handled by nconf which throws an error */ this.repo = inputContext.repo ?? this.config.get('repo'); } stringify() { if (this?.config) { try { return YAML.stringify(this.config.get()); } catch (error) { this.log.error(`${error}`); // continue } } // this is just for debug, no need to stop the process if it fails return ''; } } //# sourceMappingURL=inputs.js.map