├── .node-version ├── .ncurc.yml ├── dist └── mjs │ ├── package.json │ ├── unicode-word-match.d.ts │ ├── util.d.ts │ ├── index.d.ts │ ├── util.js │ ├── errors │ ├── error-type.d.ts │ ├── is-error.d.ts │ ├── error-type.js │ ├── error-type.js.map │ ├── is-error.js │ └── is-error.js.map │ ├── sections │ ├── update-usage.d.ts │ ├── update-badges.d.ts │ ├── update-description.d.ts │ ├── update-title.d.ts │ ├── index.d.ts │ ├── update-inputs.d.ts │ ├── update-outputs.d.ts │ ├── update-description.js │ ├── update-title.js │ ├── index.js │ ├── update-outputs.js │ ├── update-inputs.js │ ├── update-description.js.map │ ├── update-usage.js │ ├── update-title.js.map │ ├── update-branding.d.ts │ ├── update-badges.js │ └── index.js.map │ ├── working-directory.d.ts │ ├── util.js.map │ ├── index.js │ ├── working-directory.js │ ├── save.d.ts │ ├── working-directory.js.map │ ├── index.js.map │ ├── save.js │ ├── save.js.map │ ├── config.d.ts │ ├── readme-editor.d.ts │ ├── prettier.d.ts │ ├── markdowner │ ├── index.d.ts │ └── index.js │ ├── readme-generator.d.ts │ ├── svg-editor.d.mts │ ├── config.js │ ├── readme-generator.js │ ├── logtask │ └── index.d.ts │ ├── prettier.js │ ├── constants.d.ts │ ├── constants.js │ ├── config.js.map │ ├── helpers.d.ts │ ├── readme-editor.js │ ├── prettier.js.map │ └── readme-generator.js.map ├── __tests__ ├── package.mock.json ├── payload.json ├── env.test.ts ├── action.constants.ts ├── markdowner.test.ts └── prettier.test.ts ├── .lintstagedrc ├── .dockerignore ├── .markdownlintignore ├── .husky ├── pre-commit ├── pre-push └── commit-msg ├── scripts ├── set_package_type.sh ├── latest_valid_node_version.sh ├── formatter.ts ├── esbuild.mjs ├── release.sh └── editorconfig.ts ├── .eslintignore ├── src ├── errors │ ├── error-type.ts │ └── is-error.ts ├── util.ts ├── index.ts ├── working-directory.ts ├── logtask │ └── README.md ├── save.ts ├── sections │ ├── update-description.ts │ ├── update-title.ts │ ├── index.ts │ ├── update-outputs.ts │ ├── update-inputs.ts │ ├── update-usage.ts │ └── update-badges.ts ├── config.ts ├── prettier.ts ├── readme-generator.ts ├── constants.ts ├── readme-editor.ts └── markdowner │ └── index.ts ├── .gitconfig ├── .shellcheckrc ├── tsconfig-mjs.json ├── .babelrc.cjs ├── .prettierignore ├── Dockerfile ├── .npmrc ├── CLAUDE.md ├── .github ├── workflows │ ├── assign.yml │ ├── release_new_action_version.yml │ ├── pull_request_cleanup_tags_and_releases.yml │ ├── github_actions_version_updater.yml │ ├── stale.yml │ ├── push_code_linting.yml │ ├── test.yml │ └── deploy.yml ├── ghadocs │ ├── examples │ │ └── 1_dotghdocsjson.md │ └── branding.svg ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 02_feature_request.yml │ └── 01_bug_report.yml ├── dependabot.yml ├── FUNDING.yml ├── CODEOWNERS ├── actions │ └── setup-node │ │ └── action.yml └── PULL_REQUEST_TEMPLATE.md ├── .ghadocs.json ├── vitest.config.ts ├── .all-contributorsrc ├── README.example.md ├── .vscode ├── launch.json └── settings.json ├── .coderabbit.yaml ├── Makefile ├── __mocks__ └── node:fs.ts ├── .editorconfig ├── .prettierrc.cjs ├── .cspell.json ├── SECURITY.md ├── SUPPORT.md └── action.yml /.node-version: -------------------------------------------------------------------------------- 1 | 20.x 2 | -------------------------------------------------------------------------------- /.ncurc.yml: -------------------------------------------------------------------------------- 1 | upgrade: true 2 | -------------------------------------------------------------------------------- /dist/mjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /__tests__/package.mock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "10.5.1" 3 | } 4 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{ts,js,json,md}": ["prettier --write"] 3 | } 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cache 3 | .github 4 | .git 5 | .vscode 6 | -------------------------------------------------------------------------------- /dist/mjs/unicode-word-match.d.ts: -------------------------------------------------------------------------------- 1 | export declare const unicodeWordMatch: RegExp; 2 | -------------------------------------------------------------------------------- /.markdownlintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | CHANGELOG.md 3 | tests/integration/fixtures/markdown 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck source=./_/husky.sh 3 | . "$(dirname "$0")/_/husky.sh" 4 | 5 | npm run pre-commit 6 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck source=./_/husky.sh 3 | . "$(dirname "$0")/_/husky.sh" 4 | 5 | # npm run pre-push 6 | -------------------------------------------------------------------------------- /scripts/set_package_type.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat >dist/mjs/package.json < = T | null | undefined; 2 | export declare function notEmpty(str: Nullable): str is string; 3 | -------------------------------------------------------------------------------- /dist/mjs/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates a ReadmeGenerator instance and generates docs. 3 | */ 4 | export declare function generateReadme(): Promise; 5 | -------------------------------------------------------------------------------- /dist/mjs/util.js: -------------------------------------------------------------------------------- 1 | export function notEmpty(str) { 2 | return typeof str === 'string' ? str.trim().length > 0 : false; 3 | } 4 | //# sourceMappingURL=util.js.map -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | lib/ 3 | node_modules/ 4 | .gulpfile.js 5 | esbuild.mjs 6 | package-lock.json 7 | __tests__/package.mock.json 8 | __tests__/payload.json 9 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # shellcheck source=./_/husky.sh 3 | . "$(dirname "$0")/_/husky.sh" 4 | # --no-install 5 | npx --yes commitlint@latest --edit "$1" 6 | -------------------------------------------------------------------------------- /src/errors/error-type.ts: -------------------------------------------------------------------------------- 1 | export enum ErrorType { 2 | FILE = 'file', 3 | SCHEMA = 'schema', 4 | VALIDATION = 'validation', 5 | INPUTS = 'inputs', 6 | URL = 'url', 7 | } 8 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [core] 2 | autocrlf = false 3 | ignorecase = false 4 | whitespace = cr-at-eol 5 | [help] 6 | autocorrect = 1 7 | 8 | [rebase] 9 | autosquash = true 10 | -------------------------------------------------------------------------------- /.shellcheckrc: -------------------------------------------------------------------------------- 1 | enable=all 2 | source-path=SCRIPTDIR 3 | disable=SC2154 4 | 5 | # If you're having issues with shellcheck following source, disable the errors via: 6 | # disable=SC1090 7 | # disable=SC1091 8 | -------------------------------------------------------------------------------- /dist/mjs/errors/error-type.d.ts: -------------------------------------------------------------------------------- 1 | export declare enum ErrorType { 2 | FILE = "file", 3 | SCHEMA = "schema", 4 | VALIDATION = "validation", 5 | INPUTS = "inputs", 6 | URL = "url" 7 | } 8 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | export type Nullable = T | null | undefined; 2 | export function notEmpty(str: Nullable): str is string { 3 | return typeof str === 'string' ? str.trim().length > 0 : false; 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig-mjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/mjs/", 5 | "sourceMap": true, 6 | "inlineSourceMap": false 7 | }, 8 | "include": ["src/"] 9 | } 10 | -------------------------------------------------------------------------------- /dist/mjs/sections/update-usage.d.ts: -------------------------------------------------------------------------------- 1 | import { ReadmeSection } from '../constants.js'; 2 | import type Inputs from '../inputs.js'; 3 | export default function updateUsage(sectionToken: ReadmeSection, inputs: Inputs): Promise>; 4 | -------------------------------------------------------------------------------- /.babelrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | sourceMaps: 'inline', 3 | presets: [ 4 | [ 5 | '@babel/preset-env', 6 | { 7 | targets: 'node 16', 8 | }, 9 | ], 10 | ], 11 | plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]], 12 | }; 13 | -------------------------------------------------------------------------------- /__tests__/payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "opened", 3 | "repository": { 4 | "owner": { 5 | "login": "userpayload" 6 | }, 7 | "name": "testpayload" 8 | }, 9 | "issue": { 10 | "number": 1 11 | }, 12 | "sender": { 13 | "type": "User" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | lib/ 3 | out/ 4 | node_modules/ 5 | **/.vscode 6 | **/.vs 7 | /.yarn/ 8 | **/yarn.lock 9 | /pnp* 10 | # Ignore CHANGELOG.md files to avoid issues with automated release job 11 | CHANGELOG.md 12 | **/.trunk/** 13 | **/.husky 14 | package.json 15 | .ghadocs.json 16 | package-lock.json 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Node.js runtime as a parent image 2 | FROM node:20.7.0 3 | 4 | RUN \ 5 | --mount=type=cache,target=/root/.npm \ 6 | npm install -g npm@latest 7 | 8 | # Set the working directory to /app 9 | WORKDIR /app 10 | 11 | # Start the application 12 | CMD ["tail", "-f", "/dev/null"] -------------------------------------------------------------------------------- /dist/mjs/errors/is-error.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type guard to check if an `unknown` value is an `Error` object. 3 | * 4 | * @param value - The value to check. 5 | * 6 | * @returns `true` if the value is an `Error` object, otherwise `false`. 7 | */ 8 | export declare const isError: (value: unknown) => value is Error; 9 | -------------------------------------------------------------------------------- /scripts/latest_valid_node_version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e -o pipefail 3 | 4 | # Check if the node version is compatible with the engine 5 | # 6 | ENGINE_RANGE="$(jq -r '.engines.node' package.json)" 7 | npm view node@"${ENGINE_RANGE}" version | sort | tail -1 | awk '{gsub(/'\''/,"", $2);print $2;}' 8 | 9 | -------------------------------------------------------------------------------- /dist/mjs/working-directory.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the working directory path based on the environment variables. 3 | * The order of preference is GITHUB_WORKSPACE, INIT_CWD, and then the current working directory. 4 | * @returns The working directory path. 5 | */ 6 | export default function workingDirectory(): string; 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # package-lock=false 2 | scope=@bitflight-devops 3 | access=public 4 | bitflight-devops:registry=https://registry.npmjs.org/ 5 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 6 | # always-auth=true 7 | merge-git-branch-lockfiles-branch-pattern[]=main 8 | merge-git-branch-lockfiles-branch-pattern[]=release* 9 | progress=false 10 | -------------------------------------------------------------------------------- /dist/mjs/errors/error-type.js: -------------------------------------------------------------------------------- 1 | export var ErrorType; 2 | (function (ErrorType) { 3 | ErrorType["FILE"] = "file"; 4 | ErrorType["SCHEMA"] = "schema"; 5 | ErrorType["VALIDATION"] = "validation"; 6 | ErrorType["INPUTS"] = "inputs"; 7 | ErrorType["URL"] = "url"; 8 | })(ErrorType || (ErrorType = {})); 9 | //# sourceMappingURL=error-type.js.map -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # Claude AI Instructions 2 | 3 | For detailed coding guidelines and project standards, please read: 4 | 5 | @.github/copilot-instructions.md 6 | 7 | This file contains comprehensive instructions for: 8 | 9 | - Commit message format requirements 10 | - Code style and linting rules 11 | - Testing expectations 12 | - Git workflow standards 13 | - Pre-commit hook behavior 14 | - And more... 15 | -------------------------------------------------------------------------------- /.github/workflows/assign.yml: -------------------------------------------------------------------------------- 1 | name: Issue assignment 2 | on: 3 | issues: 4 | types: [opened] 5 | 6 | permissions: 7 | issues: write 8 | 9 | jobs: 10 | auto-assign: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: pozil/auto-assign-issue@v2.2.0 14 | if: github.actor != 'dependabot[bot]' 15 | with: 16 | repo-token: ${{ secrets.GITHUB_TOKEN }} 17 | assignees: "Jamie-BitFlight" 18 | -------------------------------------------------------------------------------- /.ghadocs.json: -------------------------------------------------------------------------------- 1 | { 2 | "owner": "bitflight-devops", 3 | "repo": "github-action-readme-generator", 4 | "title_prefix": "GitHub Action: ", 5 | "paths": { 6 | "action": "action.yml", 7 | "readme": "README.md" 8 | }, 9 | "branding_svg_path": ".github/ghadocs/branding.svg", 10 | "versioning": { 11 | "enabled": true, 12 | "prefix": "v", 13 | "override": "", 14 | "branch": "main" 15 | }, 16 | "prettier": true 17 | } 18 | -------------------------------------------------------------------------------- /dist/mjs/errors/error-type.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"error-type.js","sourceRoot":"","sources":["../../../src/errors/error-type.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,SAMX;AAND,WAAY,SAAS;IACnB,0BAAa,CAAA;IACb,8BAAiB,CAAA;IACjB,sCAAyB,CAAA;IACzB,8BAAiB,CAAA;IACjB,wBAAW,CAAA;AACb,CAAC,EANW,SAAS,KAAT,SAAS,QAMpB","sourcesContent":["export enum ErrorType {\n FILE = 'file',\n SCHEMA = 'schema',\n VALIDATION = 'validation',\n INPUTS = 'inputs',\n URL = 'url',\n}\n"]} -------------------------------------------------------------------------------- /dist/mjs/util.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/util.ts"],"names":[],"mappings":"AACA,MAAM,UAAU,QAAQ,CAAC,GAAqB;IAC5C,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACjE,CAAC","sourcesContent":["export type Nullable = T | null | undefined;\nexport function notEmpty(str: Nullable): str is string {\n return typeof str === 'string' ? str.trim().length > 0 : false;\n}\n"]} -------------------------------------------------------------------------------- /src/errors/is-error.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type guard to check if an `unknown` value is an `Error` object. 3 | * 4 | * @param value - The value to check. 5 | * 6 | * @returns `true` if the value is an `Error` object, otherwise `false`. 7 | */ 8 | export const isError = (value: unknown): value is Error => 9 | !!value && 10 | typeof value === 'object' && 11 | 'message' in value && 12 | typeof value.message === 'string' && 13 | 'stack' in value && 14 | typeof value.stack === 'string'; 15 | -------------------------------------------------------------------------------- /dist/mjs/errors/is-error.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Type guard to check if an `unknown` value is an `Error` object. 3 | * 4 | * @param value - The value to check. 5 | * 6 | * @returns `true` if the value is an `Error` object, otherwise `false`. 7 | */ 8 | export const isError = (value) => !!value && 9 | typeof value === 'object' && 10 | 'message' in value && 11 | typeof value.message === 'string' && 12 | 'stack' in value && 13 | typeof value.stack === 'string'; 14 | //# sourceMappingURL=is-error.js.map -------------------------------------------------------------------------------- /.github/ghadocs/examples/1_dotghdocsjson.md: -------------------------------------------------------------------------------- 1 | ### Example `.ghadocs.json` with all possible values 2 | 3 | ```json 4 | { 5 | "paths": { 6 | "action": "action.yml", 7 | "readme": "README.md" 8 | }, 9 | "show_logo": true, 10 | "versioning": { 11 | "enabled": true, 12 | "override": "", 13 | "prefix": "v", 14 | "branch": "main" 15 | }, 16 | "owner": "bitflight-devops", 17 | "repo": "github-action-readme-generator", 18 | "title_prefix": "GitHub Action: ", 19 | "pretty": true 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | // import { resolve } from 'node:path'; 3 | 4 | import { defineConfig } from 'vitest/config'; 5 | 6 | export default defineConfig({ 7 | test: { 8 | globals: true, 9 | setupFiles: ['dotenv/config'], 10 | environment: 'node', 11 | root: './', 12 | deps: { 13 | interopDefault: true, 14 | }, 15 | coverage: { 16 | provider: 'v8', 17 | reportsDirectory: './out', 18 | reporter: ['text', 'json-summary', 'json'], 19 | include: ['src/'], 20 | }, 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Inputs from './inputs.js'; 2 | import LogTask from './logtask/index.js'; 3 | import { ReadmeGenerator } from './readme-generator.js'; 4 | import save from './save.js'; 5 | /** 6 | * Creates a ReadmeGenerator instance and generates docs. 7 | */ 8 | export async function generateReadme(): Promise { 9 | const log = new LogTask('Generate Documentation'); 10 | const inputs = new Inputs(); 11 | const generator = new ReadmeGenerator(inputs, log); 12 | await generator.generate(); 13 | return save(inputs, log); 14 | } 15 | 16 | await generateReadme(); 17 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "github-action-readme-generator", 3 | "projectOwner": "bitflight-devops", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": ["README.md"], 7 | "imageSize": 100, 8 | "commit": true, 9 | "contributors": [ 10 | { 11 | "login": "Jamie-BitFlight", 12 | "name": "Jamie Nelson", 13 | "avatar_url": "https://avatars.githubusercontent.com/u/25075504?v=4", 14 | "profile": "https://bitflight.io", 15 | "contributions": ["code", "doc", "ideas", "test", "vibes"] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /README.example.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | --- 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "type": "node", 7 | "request": "launch", 8 | "name": "Debug Current Test File", 9 | "autoAttachChildProcesses": true, 10 | "skipFiles": ["/**", "**/node_modules/**"], 11 | "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs", 12 | "args": ["run", "${relativeFile}"], 13 | "smartStep": true, 14 | "console": "integratedTerminal" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /__tests__/env.test.ts: -------------------------------------------------------------------------------- 1 | import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; 2 | 3 | describe('test required environment variables', () => { 4 | beforeEach(() => { 5 | vi.stubEnv('GHADOCS_OWNER', 'bitflight-devops'); 6 | vi.stubEnv('GHADOCS_REPOSITORY', 'github-action-readme-generator'); 7 | }); 8 | afterEach(() => { 9 | vi.unstubAllEnvs(); 10 | }); 11 | test('Check Env Vars', () => { 12 | expect(process.env.GHADOCS_OWNER).toBe('bitflight-devops'); 13 | expect(process.env.GHADOCS_REPOSITORY).toBe('github-action-readme-generator'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /dist/mjs/index.js: -------------------------------------------------------------------------------- 1 | import Inputs from './inputs.js'; 2 | import LogTask from './logtask/index.js'; 3 | import { ReadmeGenerator } from './readme-generator.js'; 4 | import save from './save.js'; 5 | /** 6 | * Creates a ReadmeGenerator instance and generates docs. 7 | */ 8 | export async function generateReadme() { 9 | const log = new LogTask('Generate Documentation'); 10 | const inputs = new Inputs(); 11 | const generator = new ReadmeGenerator(inputs, log); 12 | await generator.generate(); 13 | return save(inputs, log); 14 | } 15 | await generateReadme(); 16 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 💬 GitHub Discussions 4 | url: https://github.com/bitflight-devops/github-action-readme-generator/discussions 5 | about: Ask questions and discuss with other community members 6 | - name: 📖 Documentation 7 | url: https://github.com/bitflight-devops/github-action-readme-generator#readme 8 | about: Read the documentation for usage examples and configuration options 9 | - name: 🔒 Security Issues 10 | url: https://github.com/bitflight-devops/github-action-readme-generator/security/advisories/new 11 | about: Report security vulnerabilities privately 12 | -------------------------------------------------------------------------------- /src/working-directory.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the working directory path based on the environment variables. 3 | * The order of preference is GITHUB_WORKSPACE, INIT_CWD, and then the current working directory. 4 | * @returns The working directory path. 5 | */ 6 | export default function workingDirectory(): string { 7 | // Use the GITHUB_WORKSPACE environment variable if available 8 | const githubWorkspace = process.env.GITHUB_WORKSPACE; 9 | 10 | // Use the INIT_CWD environment variable if available 11 | const initCwd = process.env.INIT_CWD; 12 | 13 | // If neither GITHUB_WORKSPACE nor INIT_CWD is available, use the current working directory 14 | 15 | return githubWorkspace ?? initCwd ?? process.cwd(); 16 | } 17 | -------------------------------------------------------------------------------- /dist/mjs/working-directory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the working directory path based on the environment variables. 3 | * The order of preference is GITHUB_WORKSPACE, INIT_CWD, and then the current working directory. 4 | * @returns The working directory path. 5 | */ 6 | export default function workingDirectory() { 7 | // Use the GITHUB_WORKSPACE environment variable if available 8 | const githubWorkspace = process.env.GITHUB_WORKSPACE; 9 | // Use the INIT_CWD environment variable if available 10 | const initCwd = process.env.INIT_CWD; 11 | // If neither GITHUB_WORKSPACE nor INIT_CWD is available, use the current working directory 12 | return githubWorkspace ?? initCwd ?? process.cwd(); 13 | } 14 | //# sourceMappingURL=working-directory.js.map -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | assignees: 9 | - jamie-bitflight 10 | labels: 11 | - "github-actions" 12 | - "dependencies" 13 | - package-ecosystem: "docker" 14 | directory: "/" 15 | schedule: 16 | interval: "weekly" 17 | assignees: 18 | - jamie-bitflight 19 | labels: 20 | - "docker" 21 | - "dependencies" 22 | - package-ecosystem: "npm" 23 | directory: "/" 24 | schedule: 25 | interval: "weekly" 26 | assignees: 27 | - jamie-bitflight 28 | labels: 29 | - "npm" 30 | - "dependencies" 31 | -------------------------------------------------------------------------------- /dist/mjs/sections/update-badges.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code imports necessary modules and defines a function named 'updateBadges' which takes a sectionToken (ReadmeSection) and an instance of the 'Inputs' class as its parameters. 3 | * The function is responsible for updating the badges section in the README.md file based on the provided inputs. 4 | * It utilizes the 'LogTask' class for logging purposes. 5 | */ 6 | import { ReadmeSection } from '../constants.js'; 7 | import type Inputs from '../inputs.js'; 8 | /** 9 | * Interface for a badge. 10 | */ 11 | export interface IBadge { 12 | alt: string; 13 | img: string; 14 | url?: string; 15 | } 16 | export default function updateBadges(sectionToken: ReadmeSection, inputs: Inputs): Record; 17 | -------------------------------------------------------------------------------- /dist/mjs/sections/update-description.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code exports a function named 'updateDescription' which takes a sectionToken (ReadmeSection) and an instance of the 'Inputs' class as its parameters. 3 | * The function is responsible for updating the description section in the README.md file based on the provided inputs. 4 | * It utilizes the 'LogTask' class for logging purposes. 5 | * @param {ReadmeSection} sectionToken - The sectionToken representing the section of the README to update. 6 | * @param {Inputs} inputs - The Inputs class instance. 7 | */ 8 | import { ReadmeSection } from '../constants.js'; 9 | import type Inputs from '../inputs.js'; 10 | export default function updateDescription(sectionToken: ReadmeSection, inputs: Inputs): Record; 11 | -------------------------------------------------------------------------------- /.github/ghadocs/branding.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /dist/mjs/errors/is-error.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"is-error.js","sourceRoot":"","sources":["../../../src/errors/is-error.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,KAAc,EAAkB,EAAE,CACxD,CAAC,CAAC,KAAK;IACP,OAAO,KAAK,KAAK,QAAQ;IACzB,SAAS,IAAI,KAAK;IAClB,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ;IACjC,OAAO,IAAI,KAAK;IAChB,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC","sourcesContent":["/**\n * Type guard to check if an `unknown` value is an `Error` object.\n *\n * @param value - The value to check.\n *\n * @returns `true` if the value is an `Error` object, otherwise `false`.\n */\nexport const isError = (value: unknown): value is Error =>\n !!value &&\n typeof value === 'object' &&\n 'message' in value &&\n typeof value.message === 'string' &&\n 'stack' in value &&\n typeof value.stack === 'string';\n"]} -------------------------------------------------------------------------------- /dist/mjs/sections/update-title.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code exports a function named 'updateTitle' which takes a sectionToken (ReadmeSection) and an instance of the 'Inputs' class as its parameters. 3 | * The function is responsible for updating the title section in the README.md file based on the provided inputs. 4 | * It utilizes the 'LogTask' class for logging purposes, the 'generateImgMarkup' function from './update-branding.js' for generating image markup. 5 | * @param {ReadmeSection} sectionToken - The sectionToken representing the section of the README to update. 6 | * @param {Inputs} inputs - The Inputs class instance. 7 | */ 8 | import { ReadmeSection } from '../constants.js'; 9 | import type Inputs from '../inputs.js'; 10 | export default function updateTitle(sectionToken: ReadmeSection, inputs: Inputs): Record; 11 | -------------------------------------------------------------------------------- /.github/workflows/release_new_action_version.yml: -------------------------------------------------------------------------------- 1 | name: Release new action version 2 | 3 | on: 4 | release: 5 | types: [released] 6 | workflow_dispatch: 7 | inputs: 8 | TAG_NAME: 9 | description: "Tag name that the major tag will point to" 10 | required: true 11 | 12 | env: 13 | TAG_NAME: ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }} 14 | permissions: 15 | contents: write 16 | 17 | jobs: 18 | update_tag: 19 | name: Update the major tag to include the ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }} changes 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Update the ${{ env.TAG_NAME }} tag 23 | uses: actions/publish-action@v0.2.2 24 | with: 25 | source-tag: ${{ env.TAG_NAME }} 26 | # slack-webhook: ${{ secrets.SLACK_WEBHOOK }} 27 | -------------------------------------------------------------------------------- /dist/mjs/sections/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code exports a function named 'updateSection' which takes a section (ReadmeSection) and an instance of the 'Inputs' class as its parameters. 3 | * The function is responsible for updating different sections of the README.md file based on the provided section input. 4 | * It utilizes various update functions (e.g., updateBranding, updateBadges) to update specific sections. 5 | * @param {ReadmeSection} section - The section of the README to update. 6 | * @param {Inputs} inputs - The Inputs class instance. 7 | * @returns {Promise} A promise that resolves once the section is updated. 8 | */ 9 | import { ReadmeSection } from '../constants.js'; 10 | import type Inputs from '../inputs.js'; 11 | export default function updateSection(section: ReadmeSection, inputs: Inputs): Promise>; 12 | -------------------------------------------------------------------------------- /.coderabbit.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json 2 | language: "en" 3 | early_access: true 4 | reviews: 5 | high_level_summary: true 6 | poem: false 7 | review_status: true 8 | collapse_walkthrough: false 9 | path_filters: 10 | - "!dist/**" 11 | path_instructions: 12 | - path: "__tests__/**.*" 13 | instructions: | 14 | "Assess the unit test code employing the Vitest testing framework. Confirm that: 15 | - The tests adhere to Vitest's established best practices. 16 | - Test descriptions are sufficiently detailed to clarify the purpose of each test." 17 | auto_review: 18 | enabled: true 19 | ignore_title_keywords: 20 | - "WIP" 21 | - "DO NOT MERGE" 22 | drafts: false 23 | base_branches: 24 | - "main" 25 | - "next" 26 | chat: 27 | auto_reply: true 28 | -------------------------------------------------------------------------------- /.github/workflows/pull_request_cleanup_tags_and_releases.yml: -------------------------------------------------------------------------------- 1 | name: 🧼 Pull Request - Cleanup Tags and Releases 2 | 3 | on: 4 | delete: 5 | pull_request: 6 | types: [closed] 7 | workflow_dispatch: 8 | inputs: 9 | regex: 10 | description: "Regex to search by" 11 | required: false 12 | pr_number: 13 | description: "PR Number to search by" 14 | required: false 15 | 16 | permissions: 17 | contents: write 18 | 19 | jobs: 20 | test: 21 | name: Cleanup Tags and Releases 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Remove unused releases and tags 25 | uses: bitflight-devops/github-action-cleanup-releases-and-tags@v1 26 | env: 27 | GITHUB_TOKEN: ${{ github.token }} 28 | with: 29 | regex: ${{ github.event.inputs.regex }} 30 | pr_number: ${{ github.event.inputs.pr_number }} 31 | -------------------------------------------------------------------------------- /dist/mjs/save.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code exports a function named 'save' which takes an instance of the 'Inputs' class as its parameter. 3 | * The function reads the configuration inputs from the 'inputs' parameter and uses them to create a new instance of the 'GHActionDocsConfig' class. 4 | * If the 'save' property is set to true in the configuration inputs, the function saves the configuration to the file specified in the 'configPath' property of the 'inputs' parameter. 5 | * This script is used to update the usage section in the README.md file to match the contents of the action.yml file. 6 | */ 7 | import Inputs from './inputs.js'; 8 | import LogTask from './logtask/index.js'; 9 | /** 10 | * This script rebuilds the usage section in the README.md to be consistent with the action.yml 11 | * @param {Inputs} inputs - the inputs class 12 | */ 13 | export default function save(inputs: Inputs, log: LogTask): void; 14 | -------------------------------------------------------------------------------- /dist/mjs/sections/update-inputs.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code exports a function named 'updateInputs' which takes a sectionToken (ReadmeSection) and an instance of the 'Inputs' class as its parameters. 3 | * The function is responsible for updating the inputs section in the README.md file based on the provided inputs. 4 | * It utilizes the 'LogTask' class for logging purposes, 'columnHeader' and 'rowHeader' functions from '../helpers.js' for formatting table headers, and 'markdowner' function from '../markdowner/index.js' for generating markdown content. 5 | * @param {ReadmeSection} sectionToken - The sectionToken representing the section of the README to update. 6 | * @param {Inputs} inputs - The Inputs class instance. 7 | */ 8 | import { ReadmeSection } from '../constants.js'; 9 | import type Inputs from '../inputs.js'; 10 | export default function updateInputs(sectionToken: ReadmeSection, inputs: Inputs): Record; 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02_feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Wouldn't it be nice if `github-action-readme-generator` could ... 3 | labels: ["enhancement", "needs-triage"] 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: New feature motivation 8 | description: Describe the context, the use-case and the advantages of the feature request. 9 | validations: 10 | required: true 11 | 12 | - type: textarea 13 | attributes: 14 | label: New feature description 15 | description: Describe the functional changes that would have to be made in `github-action-readme-generator`. 16 | validations: 17 | required: true 18 | 19 | - type: textarea 20 | attributes: 21 | label: New feature implementation 22 | description: Optionally describe the technical changes to be made in `github-action-readme-generator` or its plugins. 23 | validations: 24 | required: false 25 | -------------------------------------------------------------------------------- /src/logtask/README.md: -------------------------------------------------------------------------------- 1 | The file [`index.ts`](./index.ts) defines a class called `LogTask` that is used for logging and displaying different steps and messages in a consistent format. The class has various methods for logging different types of messages such as `debug` , `start` , `info` , `warn` , `success` , `fail` , `error` , and `title` . These methods take a description as an argument and display the message with an appropriate emoji and formatting. 2 | 3 | The `LogTask` class keeps track of whether a log message belongs to a group or not using the `ingroup` property. It also keeps track of the maximum width of the step string to ensure consistent indentation. 4 | 5 | The class uses the `chalk` library to add colors to the log messages and the `node-emoji` library to add emojis. It also uses the `@actions/core` library for logging messages in GitHub Actions. 6 | 7 | Overall, the `LogTask` class provides a convenient way to log and display messages with consistent formatting and styling. 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | # patreon: # Replace with a single Patreon username 5 | # open_collective: # Replace with a single Open Collective username 6 | # ko_fi: # Replace with a single Ko-fi username 7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | # liberapay: # Replace with a single Liberapay username 10 | # issuehunt: # Replace with a single IssueHunt username 11 | # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | # polar: # Replace with a single Polar username 13 | # buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | # thanks_dev: # Replace with a single thanks.dev username 15 | custom: ["https://github.com/sponsors/bitflight-devops"] 16 | -------------------------------------------------------------------------------- /dist/mjs/sections/update-outputs.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code exports a function named 'updateOutputs' which takes a sectionToken (string) and an instance of the 'Inputs' class as its parameters. 3 | * The function is responsible for updating the outputs section in the README.md file based on the provided inputs. 4 | * It generates a table with three columns: Output name, Description, and Value (for composite actions). 5 | * It utilizes the 'LogTask' class for logging purposes, 'columnHeader' and 'rowHeader' functions from '../helpers.js' for formatting table headers, and 'markdowner' function from '../markdowner/index.js' for generating markdown content. 6 | * @param {ReadmeSection} sectionToken - The sectionToken used for identifying the section. 7 | * @param {Inputs} inputs - The Inputs class instance. 8 | */ 9 | import { ReadmeSection } from '../constants.js'; 10 | import type Inputs from '../inputs.js'; 11 | export default function updateOutputs(sectionToken: ReadmeSection, inputs: Inputs): Record; 12 | -------------------------------------------------------------------------------- /dist/mjs/working-directory.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"working-directory.js","sourceRoot":"","sources":["../../src/working-directory.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB;IACtC,6DAA6D;IAC7D,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAErD,qDAAqD;IACrD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;IAErC,2FAA2F;IAE3F,OAAO,eAAe,IAAI,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;AACrD,CAAC","sourcesContent":["/**\n * Returns the working directory path based on the environment variables.\n * The order of preference is GITHUB_WORKSPACE, INIT_CWD, and then the current working directory.\n * @returns The working directory path.\n */\nexport default function workingDirectory(): string {\n // Use the GITHUB_WORKSPACE environment variable if available\n const githubWorkspace = process.env.GITHUB_WORKSPACE;\n\n // Use the INIT_CWD environment variable if available\n const initCwd = process.env.INIT_CWD;\n\n // If neither GITHUB_WORKSPACE nor INIT_CWD is available, use the current working directory\n\n return githubWorkspace ?? initCwd ?? process.cwd();\n}\n"]} -------------------------------------------------------------------------------- /dist/mjs/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,OAAO,MAAM,oBAAoB,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;IAC5B,MAAM,SAAS,GAAG,IAAI,eAAe,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnD,MAAM,SAAS,CAAC,QAAQ,EAAE,CAAC;IAC3B,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,cAAc,EAAE,CAAC","sourcesContent":["import Inputs from './inputs.js';\nimport LogTask from './logtask/index.js';\nimport { ReadmeGenerator } from './readme-generator.js';\nimport save from './save.js';\n/**\n * Creates a ReadmeGenerator instance and generates docs.\n */\nexport async function generateReadme(): Promise {\n const log = new LogTask('Generate Documentation');\n const inputs = new Inputs();\n const generator = new ReadmeGenerator(inputs, log);\n await generator.generate();\n return save(inputs, log);\n}\n\nawait generateReadme();\n"]} -------------------------------------------------------------------------------- /dist/mjs/sections/update-description.js: -------------------------------------------------------------------------------- 1 | import LogTask from '../logtask/index.js'; 2 | export default function updateDescription(sectionToken, inputs) { 3 | const log = new LogTask(sectionToken); 4 | // Build the new README 5 | const content = []; 6 | // Build the new description section 7 | if (inputs?.action?.description) { 8 | log.start(); 9 | const desc = inputs.action.description 10 | .trim() 11 | .replaceAll('\r\n', '\n') // Convert CR to LF 12 | .replaceAll(/ +/g, ' ') // Squash consecutive spaces 13 | .replaceAll(' \n', '\n') // Squash space followed by newline 14 | .replaceAll('\n\n', '
'); // Convert double return to a break 15 | log.info(`Writing ${desc.length} characters to the description section`); 16 | content.push(desc); 17 | inputs.readmeEditor.updateSection(sectionToken, content); 18 | log.success(); 19 | } 20 | const ret = {}; 21 | ret[sectionToken] = content.join('\n'); 22 | return ret; 23 | } 24 | //# sourceMappingURL=update-description.js.map -------------------------------------------------------------------------------- /.github/workflows/github_actions_version_updater.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions Version Updater 2 | 3 | # Controls when the action will run. 4 | on: 5 | schedule: 6 | # Automatically run on every Sunday 7 | - cron: "0 0 * * 0" 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: write 12 | pull-requests: write 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v6.0.1 20 | with: 21 | # [Required] Access token with `workflow` scope. 22 | token: ${{ secrets.RELEASE_TOKEN }} 23 | 24 | - name: Run GitHub Actions Version Updater 25 | uses: saadmk11/github-actions-version-updater@v0.9.0 26 | with: 27 | # [Required] Access token with `workflow` scope. 28 | token: ${{ secrets.RELEASE_TOKEN }} 29 | pull_request_branch: "github-actions-updates" 30 | pull_request_labels: "dependencies, automated" 31 | commit_message: "fix(deps): update github action versions" 32 | pull_request_title: "fix(deps): update github action versions" 33 | -------------------------------------------------------------------------------- /dist/mjs/sections/update-title.js: -------------------------------------------------------------------------------- 1 | import LogTask from '../logtask/index.js'; 2 | import { generateImgMarkup } from './update-branding.js'; 3 | export default function updateTitle(sectionToken, inputs) { 4 | const log = new LogTask(sectionToken); 5 | // Build the new README 6 | const content = []; 7 | let name = ''; 8 | let svgInline = ''; 9 | if (inputs.action.name) { 10 | log.start(); 11 | name = inputs.action.name; 12 | if (inputs.config.get('branding_as_title_prefix')) { 13 | svgInline = `${generateImgMarkup(inputs, '60px')} `; 14 | } 15 | log.info(`Writing ${name.length} characters to the title`); 16 | const title = `# ${svgInline}${inputs.config.get('title_prefix')}${inputs.action.name}`; 17 | log.info(`Title: ${title}`); 18 | // Build the new usage section 19 | content.push(title); 20 | inputs.readmeEditor.updateSection(sectionToken, content, true); 21 | log.success(); 22 | } 23 | const ret = {}; 24 | ret[sectionToken] = content.join('\n'); 25 | return ret; 26 | } 27 | //# sourceMappingURL=update-title.js.map -------------------------------------------------------------------------------- /scripts/formatter.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable promise/no-nesting */ 2 | import * as prettier from 'prettier'; 3 | 4 | class Formatter { 5 | max_line_length = 80; 6 | 7 | constructor(readmePath: string) { 8 | prettier 9 | .resolveConfigFile() 10 | .then(async (cwd) => { 11 | const opts: prettier.ResolveConfigOptions = cwd ? { config: cwd } : {}; 12 | opts.editorconfig = true; 13 | return prettier 14 | .resolveConfig(readmePath, opts) 15 | .then((config) => config || {}) 16 | .then((config: prettier.Options) => { 17 | if ('max_line_length' in config && config.max_line_length) { 18 | const val: string | number = config.max_line_length as any; 19 | if (typeof val === 'number') { 20 | this.max_line_length = val; 21 | return val; 22 | } 23 | } 24 | return -1; 25 | }) 26 | .catch(() => { 27 | // ignore 28 | }); 29 | }) 30 | .catch(() => { 31 | // ignore 32 | }); 33 | } 34 | } 35 | 36 | export default new Formatter(''); 37 | -------------------------------------------------------------------------------- /dist/mjs/save.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This code exports a function named 'save' which takes an instance of the 'Inputs' class as its parameter. 3 | * The function reads the configuration inputs from the 'inputs' parameter and uses them to create a new instance of the 'GHActionDocsConfig' class. 4 | * If the 'save' property is set to true in the configuration inputs, the function saves the configuration to the file specified in the 'configPath' property of the 'inputs' parameter. 5 | * This script is used to update the usage section in the README.md file to match the contents of the action.yml file. 6 | */ 7 | import { GHActionDocsConfig } from './config.js'; 8 | /** 9 | * This script rebuilds the usage section in the README.md to be consistent with the action.yml 10 | * @param {Inputs} inputs - the inputs class 11 | */ 12 | export default function save(inputs, log) { 13 | const docsConfig = new GHActionDocsConfig(); 14 | docsConfig.loadInputs(inputs); 15 | if (inputs.config.get().save === true) { 16 | try { 17 | docsConfig.save(inputs.configPath); 18 | } 19 | catch (error) { 20 | log.error(`${error}`); 21 | } 22 | } 23 | } 24 | //# sourceMappingURL=save.js.map -------------------------------------------------------------------------------- /src/save.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code exports a function named 'save' which takes an instance of the 'Inputs' class as its parameter. 3 | * The function reads the configuration inputs from the 'inputs' parameter and uses them to create a new instance of the 'GHActionDocsConfig' class. 4 | * If the 'save' property is set to true in the configuration inputs, the function saves the configuration to the file specified in the 'configPath' property of the 'inputs' parameter. 5 | * This script is used to update the usage section in the README.md file to match the contents of the action.yml file. 6 | */ 7 | 8 | import { GHActionDocsConfig } from './config.js'; 9 | import Inputs from './inputs.js'; 10 | import LogTask from './logtask/index.js'; 11 | 12 | /** 13 | * This script rebuilds the usage section in the README.md to be consistent with the action.yml 14 | * @param {Inputs} inputs - the inputs class 15 | */ 16 | export default function save(inputs: Inputs, log: LogTask): void { 17 | const docsConfig = new GHActionDocsConfig(); 18 | docsConfig.loadInputs(inputs); 19 | if (inputs.config.get().save === true) { 20 | try { 21 | docsConfig.save(inputs.configPath); 22 | } catch (error) { 23 | log.error(`${error}`); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This file defines the code owners for this repository. 2 | # Code owners are automatically requested for review when someone opens a pull request 3 | # that modifies code that they own. 4 | # 5 | # See https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 6 | 7 | # Default owners for everything in the repo 8 | * @bitflight-devops @Jamie-BitFlight 9 | 10 | # Action definition and metadata 11 | /action.yml @bitflight-devops @Jamie-BitFlight 12 | 13 | # GitHub workflows and CI/CD 14 | /.github/workflows/ @bitflight-devops @Jamie-BitFlight 15 | 16 | # Core source code 17 | /src/ @bitflight-devops @Jamie-BitFlight 18 | 19 | # Tests 20 | /__tests__/ @bitflight-devops @Jamie-BitFlight 21 | 22 | # Documentation 23 | /README.md @bitflight-devops @Jamie-BitFlight 24 | /CONTRIBUTING.md @bitflight-devops @Jamie-BitFlight 25 | /CODE_OF_CONDUCT.md @bitflight-devops @Jamie-BitFlight 26 | /SECURITY.md @bitflight-devops @Jamie-BitFlight 27 | /SUPPORT.md @bitflight-devops @Jamie-BitFlight 28 | 29 | # Package configuration 30 | /package.json @bitflight-devops @Jamie-BitFlight 31 | /package-lock.json @bitflight-devops @Jamie-BitFlight 32 | 33 | # TypeScript configuration 34 | /tsconfig*.json @bitflight-devops @Jamie-BitFlight 35 | -------------------------------------------------------------------------------- /scripts/esbuild.mjs: -------------------------------------------------------------------------------- 1 | import * as esbuild from 'esbuild'; 2 | import { writeFileSync } from 'node:fs'; 3 | // import { nodeExternalsPlugin } from 'esbuild-node-externals'; 4 | 5 | const ESM_REQUIRE_SHIM = `#!/usr/bin/env node 6 | 7 | await(async()=>{let{dirname:e}=await import("path"),{fileURLToPath:i}=await import("url");if(typeof globalThis.__filename>"u"&&(globalThis.__filename=i(import.meta.url)),typeof globalThis.__dirname>"u"&&(globalThis.__dirname=e(globalThis.__filename)),typeof globalThis.require>"u"){let{default:a}=await import("module");globalThis.require=a.createRequire(import.meta.url)}})(); 8 | `; 9 | await esbuild 10 | .build({ 11 | entryPoints: ['./src/index.ts'], 12 | outdir: './dist/bin/', 13 | bundle: true, 14 | minify: false, 15 | treeShaking: true, 16 | sourcemap: 'inline', 17 | platform: 'node', 18 | format: 'esm', 19 | target: 'node20', 20 | metafile: true, 21 | external: ['prettier'], 22 | banner: { 23 | js: ESM_REQUIRE_SHIM, 24 | }, 25 | plugins: [ 26 | // nodeExternalsPlugin({ 27 | // dependencies: false, 28 | // }), 29 | ], 30 | }) 31 | .then((result) => { 32 | if (result.metafile) { 33 | writeFileSync('meta.json', JSON.stringify(result.metafile, null, 2)); 34 | console.log('✓ Metafile written to meta.json'); 35 | } 36 | }) 37 | .catch(() => process.exit(1)); 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01_bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Something not working as expected 3 | labels: ["bug", "needs-triage"] 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: Current behavior 8 | description: Describe how the issue manifests. 9 | validations: 10 | required: true 11 | 12 | - type: textarea 13 | attributes: 14 | label: Expected behavior 15 | description: Describe what the desired behavior would be. 16 | validations: 17 | required: true 18 | 19 | - type: input 20 | attributes: 21 | label: "`github-action-readme-generator` version" 22 | description: Version set in `package.json` `devDependencies`. 23 | validations: 24 | required: true 25 | 26 | - type: input 27 | attributes: 28 | label: CI environment 29 | description: CI service name. 30 | validations: 31 | required: true 32 | 33 | - type: textarea 34 | attributes: 35 | label: "`github-action-readme-generator` configuration i.e: `.ghadocs.json`" 36 | description: Link to your repository or relevant part of the `github-action-readme-generator` config. 37 | validations: 38 | required: true 39 | 40 | - type: textarea 41 | attributes: 42 | label: CI logs 43 | description: Link to your CI logs or `github-action-readme-generator` logs. 44 | validations: 45 | required: true 46 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: help 2 | 3 | MAKEFLAGS += --silent 4 | .DEFAULT_GOAL := help 5 | 6 | help: ## Show help message 7 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[$$()% a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 8 | 9 | setup: ## Setup development environment 10 | docker build -t github-action-readme-generator:latest . 11 | npm install 12 | 13 | lint: ## Run linter 14 | $(call docker-run,npm run prelint $(filter-out $@,$(MAKECMDGOALS))) 15 | $(call docker-run,npm run lint $(filter-out $@,$(MAKECMDGOALS))) 16 | $(call docker-run,npm run lint:markdown $(filter-out $@,$(MAKECMDGOALS))) 17 | 18 | lint-fix: ## Fix lint errors 19 | $(call docker-run,npm run format $(filter-out $@,$(MAKECMDGOALS))) 20 | $(call docker-run,npm run lint:fix $(filter-out $@,$(MAKECMDGOALS))) 21 | $(call docker-run,npm run lint:markdown:fix $(filter-out $@,$(MAKECMDGOALS))) 22 | 23 | test: ## Run tests 24 | $(call docker-run,npm run test $(filter-out $@,$(MAKECMDGOALS))) 25 | 26 | npm: ## Exec npm in application container 27 | $(call docker-run,npm $(filter-out $@,$(MAKECMDGOALS))) 28 | 29 | ############################# 30 | # Argument fix workaround 31 | ############################# 32 | %: 33 | @: 34 | 35 | define docker-run 36 | docker run --rm -it -v $(PWD):/app -w /app github-action-readme-generator:latest $(1) 37 | endef 38 | -------------------------------------------------------------------------------- /__tests__/action.constants.ts: -------------------------------------------------------------------------------- 1 | export const actTestYmlPath = './action.test.yml'; 2 | 3 | export const actionTestString = `name: Test Action 4 | author: Test Author 5 | description: Test Description 6 | branding: 7 | color: white 8 | icon: activity 9 | inputs: 10 | input1: 11 | description: Test Input 1 12 | required: true 13 | default: default1 14 | input2: 15 | description: Test Input 2 16 | outputs: 17 | output1: 18 | description: Test Output 1 19 | runs: 20 | using: container 21 | image: test-image 22 | main: test-main 23 | pre: test-pre 24 | `; 25 | 26 | export const ghadocsTestString = `{ 27 | "owner": "user-from-config", 28 | "repo": "repo-from-config", 29 | "paths": { 30 | "action": "action.test-config.yml", 31 | "readme": "README.test-config.md" 32 | }, 33 | "branding_svg_path": ".github/ghadocs/branding-config.svg", 34 | "versioning": { 35 | "enabled": true, 36 | "prefix": "config", 37 | "override": "", 38 | "branch": "config" 39 | } 40 | } 41 | `; 42 | export const gitConfigTestString = `[remote "origin"] 43 | url = https://github.com/ownergit/repogit.git 44 | `; 45 | 46 | export const payloadTestString = `{ 47 | "action": "opened", 48 | "repository": { 49 | "owner": { 50 | "login": "userpayload" 51 | }, 52 | "name": "testpayload" 53 | }, 54 | "issue": { 55 | "number": 1 56 | }, 57 | "sender": { 58 | "type": "User" 59 | } 60 | }`; 61 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | git pull 3 | git fetch --tags 4 | git push --tags 5 | git push 6 | 7 | RELEASE_BRANCH="${RELEASE_BRANCH:-main}" 8 | current_branch="$(git branch --show-current)" 9 | 10 | if [[ ${current_branch} != "${RELEASE_BRANCH}" ]]; then 11 | # we want to create a canary release, where we can leave all the files in place. 12 | true 13 | else 14 | # we want to create a release branch, where we can delete all the unneeded files 15 | true 16 | fi 17 | bump="${1:-bui}" 18 | # newtag="$(git semver "${bump}" --dryrun)" 19 | yarntag="$(jq -r '.version' package.json)" 20 | if [[ ${yarntag} != "${newtag#v}" ]]; then 21 | yarn version -i "${newtag#v}" || true 22 | fi 23 | if yarn build; then 24 | git add -f dist/ package.json yarn.lock .yarn README.md 25 | git commit -m "build(release): bump version to ${newtag}" --no-verify 26 | git semver "${bump}" 27 | else 28 | echo "build failed" 29 | exit 1 30 | fi 31 | # newtag2="$(git semver get)" 32 | # stub_major="${newtag%%\.*}" 33 | # stub_major_minor="${newtag%\.*}" 34 | 35 | # git tag -d "${stub_major}" 2>/dev/null || true 36 | # git tag -d "${stub_major_minor}" 2>/dev/null || true 37 | # git tag -a "${stub_major}" -m "Release ${newtag}" 38 | # git tag -a "${stub_major_minor}" -m "Release ${newtag}" 39 | 40 | # git push origin ":${stub_major}" 2>/dev/null || true 41 | # git push origin ":${stub_major_minor}" 2>/dev/null || true 42 | yarn postversion 43 | yarn release:post 44 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues and PRs" 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" 5 | workflow_dispatch: 6 | 7 | permissions: 8 | issues: write 9 | pull-requests: write 10 | 11 | jobs: 12 | stale: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/stale@v10.1.1 16 | with: 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | stale-issue-message: "This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions." 19 | stale-pr-message: "This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions." 20 | close-issue-message: "This issue has been automatically closed due to inactivity. Please feel free to reopen if you have more information to share." 21 | close-pr-message: "This pull request has been automatically closed due to inactivity. Please feel free to reopen if you would like to continue working on it." 22 | days-before-stale: 60 23 | days-before-close: 14 24 | stale-issue-label: "stale" 25 | stale-pr-label: "stale" 26 | exempt-issue-labels: "pinned,security,help-wanted,good-first-issue" 27 | exempt-pr-labels: "pinned,security" 28 | operations-per-run: 30 29 | -------------------------------------------------------------------------------- /__mocks__/node:fs.ts: -------------------------------------------------------------------------------- 1 | import type { BigIntStats, PathLike, PathOrFileDescriptor, Stats, StatSyncOptions } from 'node:fs'; 2 | 3 | import { vi } from 'vitest'; 4 | 5 | import { 6 | actionTestString, 7 | ghadocsTestString, 8 | gitConfigTestString, 9 | payloadTestString, 10 | } from '../__tests__/action.constants.js'; 11 | 12 | export type { BigIntStats, PathLike, PathOrFileDescriptor, Stats, StatSyncOptions } from 'node:fs'; 13 | 14 | export const statSync = vi.fn( 15 | (path: PathLike, options?: StatSyncOptions | undefined): Stats | BigIntStats | undefined => { 16 | if (typeof path === 'string' && options === undefined) { 17 | return { 18 | isFile: () => true, 19 | } as Stats; 20 | } 21 | return { 22 | isFile: () => false, 23 | } as Stats; 24 | }, 25 | ); 26 | 27 | export const existsSync = vi.fn((filename: PathLike): boolean => typeof filename === 'string'); 28 | export const readFileSync = vi.fn((filename: PathOrFileDescriptor): string | Buffer => { 29 | if (typeof filename === 'string' && filename.endsWith('payload.json')) { 30 | return payloadTestString; 31 | } 32 | if (typeof filename === 'string' && filename.endsWith('.ghadocs.json')) { 33 | return ghadocsTestString; 34 | } 35 | if (typeof filename === 'string' && filename.endsWith('config')) { 36 | return gitConfigTestString; 37 | } 38 | if (typeof filename === 'string' && filename.endsWith('yml')) { 39 | return actionTestString; 40 | } 41 | return ''; 42 | }); 43 | -------------------------------------------------------------------------------- /.github/workflows/push_code_linting.yml: -------------------------------------------------------------------------------- 1 | name: Code Linting Annotation 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | - next 7 | - beta 8 | - "*.x" 9 | push: 10 | branches: 11 | - main 12 | - next 13 | - beta 14 | - "*.x" 15 | 16 | concurrency: 17 | group: ci-linting-${{ github.event.pull_request.number || github.ref }} 18 | cancel-in-progress: true 19 | jobs: 20 | eslint_annotation: 21 | name: runner / eslint 22 | if: (!contains(github.event.head_commit.message, 'ci skip') && !contains(github.event.head_commit.message, 'skip ci')) 23 | runs-on: ubuntu-latest 24 | permissions: 25 | issues: write 26 | pull-requests: write 27 | actions: write 28 | statuses: write 29 | checks: write 30 | env: 31 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 32 | NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 33 | GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} 34 | steps: 35 | - uses: actions/checkout@v6.0.1 36 | 37 | - name: Install compatible Nodejs version 38 | id: setup-node 39 | uses: ./.github/actions/setup-node 40 | 41 | - name: Install Deps 42 | run: npm install 43 | - uses: xt0rted/markdownlint-problem-matcher@v3.0.0 44 | - run: npm run lint:markdown 45 | continue-on-error: true 46 | - name: eslint 47 | uses: reviewdog/action-eslint@v1.34.0 48 | with: 49 | reporter: github-pr-review # Change reporter. 50 | eslint_flags: src/ 51 | github_token: ${{ secrets.GITHUB_TOKEN }} 52 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vitest.enable":true, 3 | "vitest.commandLine": "npm run test -- ", 4 | "git.ignoreLimitWarning": true, 5 | "eslint.validate": [ 6 | "javascript", 7 | "typescript", 8 | "javascriptreact", 9 | "typescriptreact", 10 | "html" 11 | ], 12 | "eslint.options": { 13 | "extensions": [ 14 | ".js", 15 | ".ts", 16 | ".mts", 17 | ".mjs", 18 | ".cjs", 19 | ".cts", 20 | "html" 21 | ] 22 | }, 23 | "editor.codeActionsOnSave": { 24 | "source.fixAll.eslint": true, 25 | }, 26 | "github.copilot.advanced": {}, 27 | "eslint.debug": true, 28 | "json.schemas": [ 29 | { 30 | "fileMatch": [ 31 | "/.github/workflows/*.yml", 32 | "/.github/workflows/*.yaml" 33 | ], 34 | "url": "https://json.schemastore.org/github-workflow.json" 35 | }, 36 | { 37 | "fileMatch": [ 38 | "/package.json" 39 | ], 40 | "url": "https://json.schemastore.org/package.json" 41 | }, 42 | { 43 | "fileMatch": [ 44 | ".ncurc", 45 | ".ncurc.json", 46 | ], 47 | "url": "https://raw.githubusercontent.com/raineorshine/npm-check-updates/main/src/types/RunOptions.json" 48 | } 49 | ], 50 | "yaml.schemas": { 51 | "https://raw.githubusercontent.com/raineorshine/npm-check-updates/main/src/types/RunOptions.json": [ 52 | ".ncurc.yml", 53 | ] 54 | }, 55 | "typescript.tsdk": "./node_modules/typescript/lib", 56 | "editor.inlineSuggest.showToolbar": "always" 57 | } 58 | -------------------------------------------------------------------------------- /dist/mjs/save.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"save.js","sourceRoot":"","sources":["../../src/save.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAIjD;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,MAAc,EAAE,GAAY;IACvD,MAAM,UAAU,GAAG,IAAI,kBAAkB,EAAE,CAAC;IAC5C,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAC9B,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,KAAK,IAAI,EAAE;QACrC,IAAI;YACF,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;SACpC;QAAC,OAAO,KAAK,EAAE;YACd,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC;SACvB;KACF;AACH,CAAC","sourcesContent":["/**\n * This code exports a function named 'save' which takes an instance of the 'Inputs' class as its parameter.\n * The function reads the configuration inputs from the 'inputs' parameter and uses them to create a new instance of the 'GHActionDocsConfig' class.\n * If the 'save' property is set to true in the configuration inputs, the function saves the configuration to the file specified in the 'configPath' property of the 'inputs' parameter.\n * This script is used to update the usage section in the README.md file to match the contents of the action.yml file.\n */\n\nimport { GHActionDocsConfig } from './config.js';\nimport Inputs from './inputs.js';\nimport LogTask from './logtask/index.js';\n\n/**\n * This script rebuilds the usage section in the README.md to be consistent with the action.yml\n * @param {Inputs} inputs - the inputs class\n */\nexport default function save(inputs: Inputs, log: LogTask): void {\n const docsConfig = new GHActionDocsConfig();\n docsConfig.loadInputs(inputs);\n if (inputs.config.get().save === true) {\n try {\n docsConfig.save(inputs.configPath);\n } catch (error) {\n log.error(`${error}`);\n }\n }\n}\n"]} -------------------------------------------------------------------------------- /src/sections/update-description.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code exports a function named 'updateDescription' which takes a sectionToken (ReadmeSection) and an instance of the 'Inputs' class as its parameters. 3 | * The function is responsible for updating the description section in the README.md file based on the provided inputs. 4 | * It utilizes the 'LogTask' class for logging purposes. 5 | * @param {ReadmeSection} sectionToken - The sectionToken representing the section of the README to update. 6 | * @param {Inputs} inputs - The Inputs class instance. 7 | */ 8 | import { ReadmeSection } from '../constants.js'; 9 | import type Inputs from '../inputs.js'; 10 | import LogTask from '../logtask/index.js'; 11 | 12 | export default function updateDescription( 13 | sectionToken: ReadmeSection, 14 | inputs: Inputs, 15 | ): Record { 16 | const log = new LogTask(sectionToken); 17 | 18 | // Build the new README 19 | const content: string[] = []; 20 | 21 | // Build the new description section 22 | if (inputs?.action?.description) { 23 | log.start(); 24 | const desc: string = inputs.action.description 25 | .trim() 26 | .replaceAll('\r\n', '\n') // Convert CR to LF 27 | .replaceAll(/ +/g, ' ') // Squash consecutive spaces 28 | .replaceAll(' \n', '\n') // Squash space followed by newline 29 | .replaceAll('\n\n', '
'); // Convert double return to a break 30 | 31 | log.info(`Writing ${desc.length} characters to the description section`); 32 | content.push(desc); 33 | inputs.readmeEditor.updateSection(sectionToken, content); 34 | log.success(); 35 | } 36 | const ret: Record = {}; 37 | ret[sectionToken] = content.join('\n'); 38 | return ret; 39 | } 40 | -------------------------------------------------------------------------------- /dist/mjs/sections/index.js: -------------------------------------------------------------------------------- 1 | import LogTask from '../logtask/index.js'; 2 | import updateBadges from './update-badges.js'; 3 | import updateBranding from './update-branding.js'; 4 | import updateDescription from './update-description.js'; 5 | import updateInputs from './update-inputs.js'; 6 | import updateOutputs from './update-outputs.js'; 7 | import updateTitle from './update-title.js'; 8 | import updateUsage from './update-usage.js'; 9 | const log = new LogTask('updateSection'); 10 | export default async function updateSection(section, inputs) { 11 | const [startToken, stopToken] = inputs.readmeEditor.getTokenIndexes(section); 12 | // && 13 | // ['branding', 'title'].includes(section) && 14 | // inputs.config.get('branding_as_title_prefix') !== true 15 | if (startToken === -1 || stopToken === -1) { 16 | return {}; 17 | } 18 | switch (section) { 19 | case 'branding': { 20 | return updateBranding(section, inputs); 21 | } 22 | case 'badges': { 23 | return updateBadges(section, inputs); 24 | } 25 | case 'usage': { 26 | return updateUsage(section, inputs); 27 | } 28 | case 'title': { 29 | return updateTitle(section, inputs); 30 | } 31 | case 'description': { 32 | return updateDescription(section, inputs); 33 | } 34 | case 'inputs': { 35 | return updateInputs(section, inputs); 36 | } 37 | case 'outputs': { 38 | return updateOutputs(section, inputs); 39 | } 40 | default: { 41 | log.debug(`unknown section found . No updates were made.`); 42 | return {}; 43 | } 44 | } 45 | } 46 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /dist/mjs/config.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code imports the necessary modules and defines two interfaces: `Versioning` and `Paths`. 3 | * It also defines a class named `GHActionDocsConfig` that represents the configuration for generating GitHub Actions documentation. 4 | * The class has properties that correspond to the configuration options and a method `loadInputs` to load the configuration from the provided `Inputs` object. 5 | * The class also has a method `save` to save the configuration to a file. 6 | */ 7 | import type Inputs from './inputs.js'; 8 | /** 9 | * Represents the versioning configuration for GitHub Actions documentation. 10 | */ 11 | export interface Versioning { 12 | enabled?: boolean; 13 | prefix?: string; 14 | override?: string; 15 | branch?: string; 16 | badge?: string; 17 | } 18 | /** 19 | * Represents the paths configuration for GitHub Actions documentation. 20 | */ 21 | export interface Paths { 22 | action: string; 23 | readme: string; 24 | } 25 | /** 26 | * Represents the configuration for generating GitHub Actions documentation. 27 | */ 28 | export declare class GHActionDocsConfig { 29 | owner?: string; 30 | repo?: string; 31 | title_prefix?: string; 32 | title?: string; 33 | paths?: Paths; 34 | branding_svg_path?: string; 35 | versioning?: Versioning; 36 | prettier?: boolean; 37 | /** 38 | * Loads the configuration from the provided `Inputs` object. 39 | * @param {Inputs} inputs - The `Inputs` object containing the configuration values. 40 | */ 41 | loadInputs(inputs: Inputs): void; 42 | /** 43 | * Saves the configuration to a file. If the file exists, it will be overwritten. 44 | * @param {string} configPath - The path to the configuration file. 45 | */ 46 | save(configPath: string): Promise; 47 | } 48 | -------------------------------------------------------------------------------- /src/sections/update-title.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code exports a function named 'updateTitle' which takes a sectionToken (ReadmeSection) and an instance of the 'Inputs' class as its parameters. 3 | * The function is responsible for updating the title section in the README.md file based on the provided inputs. 4 | * It utilizes the 'LogTask' class for logging purposes, the 'generateImgMarkup' function from './update-branding.js' for generating image markup. 5 | * @param {ReadmeSection} sectionToken - The sectionToken representing the section of the README to update. 6 | * @param {Inputs} inputs - The Inputs class instance. 7 | */ 8 | import { ReadmeSection } from '../constants.js'; 9 | import type Inputs from '../inputs.js'; 10 | import LogTask from '../logtask/index.js'; 11 | import { generateImgMarkup } from './update-branding.js'; 12 | 13 | export default function updateTitle( 14 | sectionToken: ReadmeSection, 15 | inputs: Inputs, 16 | ): Record { 17 | const log = new LogTask(sectionToken); 18 | 19 | // Build the new README 20 | const content: string[] = []; 21 | let name = ''; 22 | let svgInline = ''; 23 | 24 | if (inputs.action.name) { 25 | log.start(); 26 | name = inputs.action.name; 27 | if (inputs.config.get('branding_as_title_prefix') as boolean) { 28 | svgInline = `${generateImgMarkup(inputs, '60px')} `; 29 | } 30 | log.info(`Writing ${name.length} characters to the title`); 31 | const title = `# ${svgInline}${inputs.config.get('title_prefix') as string}${ 32 | inputs.action.name 33 | }`; 34 | log.info(`Title: ${title}`); 35 | // Build the new usage section 36 | content.push(title); 37 | inputs.readmeEditor.updateSection(sectionToken, content, true); 38 | 39 | log.success(); 40 | } 41 | const ret: Record = {}; 42 | ret[sectionToken] = content.join('\n'); 43 | return ret; 44 | } 45 | -------------------------------------------------------------------------------- /dist/mjs/readme-editor.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code imports the necessary modules and defines a class named `ReadmeEditor`. 3 | * The class represents an editor for modifying a README file. 4 | * It has methods to update specific sections within the file and dump the modified content back to the file. 5 | */ 6 | import LogTask from './logtask/index.js'; 7 | /** 8 | * The format for the start token of a section. 9 | */ 10 | export declare const startTokenFormat = "(^|[^`\\\\])"; 11 | /** 12 | * The format for the end token of a section. 13 | */ 14 | export declare const endTokenFormat = "(^|[^`\\\\])"; 15 | export default class ReadmeEditor { 16 | private log; 17 | /** 18 | * The path to the README file. 19 | */ 20 | private readonly filePath; 21 | private fileContent; 22 | /** 23 | * Creates a new instance of `ReadmeEditor`. 24 | * @param {string} filePath - The path to the README file. 25 | */ 26 | constructor(filePath: string); 27 | /** 28 | * Gets the indexes of the start and end tokens for a given section. 29 | * @param {string} token - The section token. 30 | * @returns {number[]} - The indexes of the start and end tokens. 31 | */ 32 | getTokenIndexes(token: string, logTask?: LogTask): number[]; 33 | /** 34 | * Updates a specific section in the README file with the provided content. 35 | * @param {string} name - The name of the section. 36 | * @param {string | string[]} providedContent - The content to update the section with. 37 | * @param {boolean} addNewlines - Whether to add newlines before and after the content. 38 | */ 39 | updateSection(name: string, providedContent: string | string[], addNewlines?: boolean): void; 40 | /** 41 | * Dumps the modified content back to the README file. 42 | * @returns {Promise} 43 | */ 44 | dumpToFile(): Promise; 45 | } 46 | -------------------------------------------------------------------------------- /__tests__/markdowner.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest'; 2 | 3 | import { 4 | ArrayOfArraysToMarkdownTable, 5 | cloneArray, 6 | getColumnCounts, 7 | markdownEscapeInlineCode, 8 | markdownEscapeTableCell, 9 | padArrayRows, 10 | padString, 11 | } from '../src/markdowner/index.js'; 12 | 13 | const testArray = [ 14 | ['Header1', 'Header2'], 15 | ['Cell1', 'Cell2'], 16 | ]; 17 | 18 | test('padString pads text to width with spaces', () => { 19 | expect(padString('Hi', 5, 0)).toBe('Hi '); 20 | expect(padString('Hello', 8, 3)).toBe(' Hello '); 21 | }); 22 | 23 | test('markdownEscapeTableCell escapes newlines and pipes', () => { 24 | expect(markdownEscapeTableCell('Hello\nWorld|Foo')).toBe('Hello
World\\|Foo'); 25 | }); 26 | 27 | test('markdownEscapeInlineCode escapes inline code', () => { 28 | expect(markdownEscapeInlineCode('Hello `World`')).toBe('Hello World'); 29 | expect(markdownEscapeInlineCode('Hello `World`|`Code`')).toBe( 30 | 'Hello World|Code', 31 | ); 32 | }); 33 | 34 | test('cloneArray clones 2D array', () => { 35 | const cloned = cloneArray(testArray); 36 | expect(cloned).toEqual(testArray); 37 | expect(cloned).not.toBe(testArray); 38 | }); 39 | 40 | test('getColumnCounts gets max and min columns', () => { 41 | const { maxCols, minCols } = getColumnCounts(testArray); 42 | expect(maxCols).toBe(2); 43 | expect(minCols).toBe(2); 44 | }); 45 | 46 | test('padArrayRows pads rows to match max cols', () => { 47 | const padded = padArrayRows(testArray, 4); 48 | expect(padded[0]).toEqual(['Header1', 'Header2', '', '']); 49 | expect(padded[1]).toEqual(['Cell1', 'Cell2', '', '']); 50 | }); 51 | 52 | test('ArrayOfArraysToMarkdownTable converts array to markdown table', () => { 53 | const expected = `| **Header1** | **Header2** | 54 | |---|---| 55 | | Cell1 | Cell2 | 56 | `; 57 | 58 | expect(ArrayOfArraysToMarkdownTable(testArray)).toBe(expected); 59 | }); 60 | -------------------------------------------------------------------------------- /dist/mjs/sections/update-outputs.js: -------------------------------------------------------------------------------- 1 | import { columnHeader, rowHeader } from '../helpers.js'; 2 | import LogTask from '../logtask/index.js'; 3 | import markdowner from '../markdowner/index.js'; 4 | export default function updateOutputs(sectionToken, inputs) { 5 | const log = new LogTask(sectionToken); 6 | // Build the new README 7 | const content = []; 8 | const markdownArray = []; 9 | const titleArray = ['Output', 'Description', 'Value']; 10 | const titles = []; 11 | for (const t of titleArray) { 12 | titles.push(columnHeader(t)); 13 | } 14 | markdownArray.push(titles); 15 | const vars = inputs.action.outputs; 16 | const tI = vars ? Object.keys(vars).length : 0; 17 | if (vars && tI > 0) { 18 | log.start(); 19 | for (const key of Object.keys(vars)) { 20 | const values = vars[key]; 21 | let description = values?.description ?? ''; 22 | // Check if only first line should be added (only subject without body) 23 | const matches = /(.*?)\n\n([Ss]*)/.exec(description); 24 | if (matches && matches.length >= 2) { 25 | description = matches[1] || description; 26 | } 27 | description = description.trim().replace('\n', '
'); 28 | const value = values?.value ? `\`${values.value}\`` : ''; 29 | const row = [rowHeader(key), description, value]; 30 | log.debug(JSON.stringify(row)); 31 | markdownArray.push(row); 32 | } 33 | content.push(markdowner(markdownArray)); 34 | log.info(`Action has ${tI} total ${sectionToken}`); 35 | inputs.readmeEditor.updateSection(sectionToken, content); 36 | log.success(); 37 | } 38 | else { 39 | log.debug(`Action has no ${sectionToken}`); 40 | } 41 | const ret = {}; 42 | ret[sectionToken] = content.join('\n'); 43 | return ret; 44 | } 45 | //# sourceMappingURL=update-outputs.js.map -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | end_of_line = lf 11 | 12 | [{*.ts,*.js}] 13 | quote_type = single 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [{Dockerfile,Makefile,*.go,script/add-grammar}] 18 | indent_style = tab 19 | indent_size = 4 20 | 21 | # md (GitHub shows code fences with 8 spaces) 22 | [*.md] 23 | indent_size = 2 24 | trim_trailing_whitespace = false 25 | x-soft-wrap-text = true 26 | max_line_length = off 27 | 28 | # Ignore fixtures and vendored files 29 | [{grammars,test/fixtures,samples,vendor,dist,build,node_modules,target}/**] 30 | charset = unset 31 | end_of_line = unset 32 | indent_size = unset 33 | indent_style = unset 34 | insert_final_newline = unset 35 | trim_trailing_spaces = unset 36 | 37 | 38 | [{**/dist/**,**/build/**,**/node_modules/**,**/vendor/**,**/target/**}] 39 | indent_style = ignore 40 | indent_size = ignore 41 | insert_final_newline = ignore 42 | trim_trailing_whitespace = ignore 43 | 44 | # Makefiles always use tabs for indentation 45 | [**/Makefile*] 46 | indent_style = tab 47 | indent_size = 4 48 | 49 | # Minified JavaScript files shouldn't be changed 50 | [**.min.js] 51 | indent_style = ignore 52 | insert_final_newline = ignore 53 | 54 | # Batch files use tabs for indentation 55 | [*.bat] 56 | indent_style = tab 57 | 58 | [docs/**.txt] 59 | max_line_length = 79 60 | 61 | # Docstrings and comments use max_line_length = 79 62 | [*.py] 63 | max_line_length = 88 64 | 65 | # Use 2 spaces for the HTML files 66 | [*.html] 67 | indent_size = 2 68 | 69 | # The JSON files contain newlines inconsistently 70 | [*.json] 71 | indent_size = 2 72 | insert_final_newline = ignore 73 | 74 | # The indent size used in the `package.json` file cannot be changed 75 | # https://github.com/npm/npm/pull/3180#issuecomment-16336516 76 | [{*.yml,*.yaml,package.json}] 77 | indent_style = space 78 | indent_size = 2 79 | trim_trailing_whitespace = false 80 | -------------------------------------------------------------------------------- /dist/mjs/sections/update-inputs.js: -------------------------------------------------------------------------------- 1 | import { columnHeader, rowHeader } from '../helpers.js'; 2 | import LogTask from '../logtask/index.js'; 3 | import markdowner from '../markdowner/index.js'; 4 | export default function updateInputs(sectionToken, inputs) { 5 | const log = new LogTask(sectionToken); 6 | // Build the new README 7 | const content = []; 8 | const markdownArray = []; 9 | const titleArray = ['Input', 'Description', 'Default', 'Required']; 10 | const titles = []; 11 | for (const t of titleArray) { 12 | titles.push(columnHeader(t)); 13 | } 14 | markdownArray.push(titles); 15 | const vars = inputs.action.inputs; 16 | const tI = vars ? Object.keys(vars).length : 0; 17 | if (vars && tI > 0) { 18 | log.start(); 19 | for (const key of Object.keys(vars)) { 20 | const values = vars[key]; 21 | let description = values?.description ?? ''; 22 | // Check if only the first line should be added (only subject without body) 23 | const matches = /(.*?)\n\n([Ss]*)/.exec(description); 24 | if (matches && matches.length >= 2) { 25 | description = matches[1] || description; 26 | } 27 | description = description.trim().replace('\n', '
'); 28 | const row = [ 29 | rowHeader(key), 30 | description, 31 | values?.default ? `${values.default}` : '', 32 | values?.required ? '**true**' : '__false__', 33 | ]; 34 | log.debug(JSON.stringify(row)); 35 | markdownArray.push(row); 36 | } 37 | content.push(markdowner(markdownArray)); 38 | log.info(`Action has ${tI} total ${sectionToken}`); 39 | inputs.readmeEditor.updateSection(sectionToken, content); 40 | log.success(); 41 | } 42 | else { 43 | log.debug(`Action has no ${sectionToken}`); 44 | } 45 | const ret = {}; 46 | ret[sectionToken] = content.join('\n'); 47 | return ret; 48 | } 49 | //# sourceMappingURL=update-inputs.js.map -------------------------------------------------------------------------------- /dist/mjs/prettier.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code exports three functions: `formatYaml`, `formatMarkdown`, and `wrapDescription`. 3 | * 4 | * - `formatYaml` takes a YAML string and an optional filepath as parameters and uses the `prettier` library to format the YAML code. It returns the formatted YAML string. 5 | * - `formatMarkdown` takes a Markdown string and an optional filepath as parameters and uses the `prettier` library to format the Markdown code. It returns the formatted Markdown string. 6 | * - `wrapDescription` takes a string value, an array of content, and an optional prefix as parameters. It wraps the description text with the specified prefix and formats it using `prettier`. It returns the updated content array with the formatted description lines. 7 | * 8 | * The code utilizes the `prettier` library for code formatting and the `LogTask` class for logging purposes. 9 | */ 10 | /** 11 | * Formats a YAML string using `prettier`. 12 | * @param {string} value - The YAML string to format. 13 | * @param {string} [filepath] - The optional filepath. 14 | * @returns {Promise} A promise that resolves with the formatted YAML string. 15 | */ 16 | export declare function formatYaml(value: string, filepath?: string): Promise; 17 | /** 18 | * Formats a Markdown string using `prettier`. 19 | * @param {string} value - The Markdown string to format. 20 | * @param {string} [filepath] - The optional filepath. 21 | * @returns {Promise} A promise that resolves with the formatted Markdown string. 22 | */ 23 | export declare function formatMarkdown(value: string, filepath?: string): Promise; 24 | /** 25 | * Wraps a description text with a prefix and formats it using `prettier`. 26 | * @param {string | undefined} value - The description text to wrap and format. 27 | * @param {string[]} content - The array of content to update. 28 | * @param {string} [prefix=' # '] - The optional prefix to wrap the description lines. 29 | * @returns {Promise} A promise that resolves with the updated content array. 30 | */ 31 | export declare function wrapDescription(value: string | undefined, content: string[], prefix?: string): Promise; 32 | -------------------------------------------------------------------------------- /dist/mjs/markdowner/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Types representing a 2D array of strings for a Markdown table. 3 | */ 4 | export type MarkdownArrayRowType = string[][]; 5 | export type MarkdownArrayItemType = string; 6 | /** 7 | * Fills a string to a desired width by padding with spaces. 8 | * 9 | * @param text - The text to pad. 10 | * @param width - The desired total width. 11 | * @param paddingStart - Number of spaces to pad at the start. 12 | * @returns The padded string. 13 | */ 14 | export declare function padString(text: string, width: number, paddingStart: number): string; 15 | /** 16 | * Escapes special Markdown characters in a string. 17 | * 18 | * @param text - The text to escape. 19 | * @returns The escaped text. 20 | */ 21 | export declare function markdownEscapeTableCell(text: string): string; 22 | /** 23 | * Escapes inline code blocks in a Markdown string. 24 | * 25 | * @param content - Markdown string. 26 | * @returns String with escaped inline code blocks. 27 | */ 28 | export declare function markdownEscapeInlineCode(content: string): string; 29 | /** 30 | * Clones a 2D array. 31 | * 32 | * @param arr - Array to clone. 33 | * @returns Cloned array. 34 | */ 35 | export declare function cloneArray(arr: MarkdownArrayRowType): MarkdownArrayRowType; 36 | /** 37 | * Gets max and min column counts from 2D array. 38 | * 39 | * @param data - 2D string array. 40 | * @returns Object with max and min cols. 41 | */ 42 | export declare function getColumnCounts(data: MarkdownArrayRowType): { 43 | maxCols: number; 44 | minCols: number; 45 | }; 46 | /** 47 | * Pads 2D array rows to equal length. 48 | * 49 | * @param data - 2D array to pad. 50 | * @param maxCols - Number of columns to pad to. 51 | * @returns Padded 2D array. 52 | */ 53 | export declare function padArrayRows(data: MarkdownArrayRowType, maxCols: number): MarkdownArrayRowType; 54 | /** 55 | * Converts a 2D array of strings to a Markdown table. 56 | * 57 | * @param data - 2D string array. 58 | * @returns Markdown table string. 59 | */ 60 | export declare function ArrayOfArraysToMarkdownTable(providedTableContent: MarkdownArrayRowType): string; 61 | export default ArrayOfArraysToMarkdownTable; 62 | -------------------------------------------------------------------------------- /dist/mjs/readme-generator.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code imports various modules and defines a function named 'generateDocs'. 3 | * The function is responsible for generating documentation for the README.md file based on the provided inputs. 4 | * It iterates through each section defined in the 'inputs.sections' array and calls the 'updateSection' function to update the corresponding section in the README.md file. 5 | * If an error occurs during the update of a section, it logs the error message and stops the process. 6 | * Finally, it saves the updated README.md file and calls the 'save' function. 7 | */ 8 | import { ReadmeSection } from './constants.js'; 9 | import Inputs from './inputs.js'; 10 | import LogTask from './logtask/index.js'; 11 | export type SectionKV = Record; 12 | /** 13 | * Class for managing README generation. 14 | */ 15 | export declare class ReadmeGenerator { 16 | /** 17 | * The Inputs instance. 18 | */ 19 | private inputs; 20 | /** 21 | * The Logger instance. 22 | */ 23 | private log; 24 | /** 25 | * Initializes the ReadmeGenerator. 26 | * 27 | * @param inputs - The Inputs instance 28 | * @param log - The Logger instance 29 | */ 30 | constructor(inputs: Inputs, log: LogTask); 31 | /** 32 | * Updates the README sections. 33 | * 34 | * @param sections - The sections array 35 | * @returns Promise array of section KV objects 36 | */ 37 | updateSections(sections: ReadmeSection[]): Promise[]; 38 | /** 39 | * Resolves the section update promises. 40 | * 41 | * @param promises - The promise array 42 | * @returns Promise resolving to combined sections KV 43 | */ 44 | resolveUpdates(promises: Promise[]): Promise; 45 | /** 46 | * Outputs the sections KV to GitHub output. 47 | * 48 | * @param sections - The sections KV 49 | */ 50 | outputSections(sections: SectionKV): void; 51 | /** 52 | * Generates the README documentation. 53 | * 54 | * @returns Promise resolving when done 55 | */ 56 | generate(providedSections?: ReadmeSection[]): Promise; 57 | } 58 | -------------------------------------------------------------------------------- /scripts/editorconfig.ts: -------------------------------------------------------------------------------- 1 | import * as editorconfig from 'editorconfig'; 2 | 3 | import LogTask from '../src/logtask'; 4 | 5 | const log = new LogTask('Editorconfig'); 6 | export const DEFAULT_EDITORCONFIG_MAX_LINE_LENGTH = 80; 7 | export interface IProperties extends editorconfig.KnownProps { 8 | /** 9 | * Set to latin1, utf-8, utf-8-bom, utf-16be or utf-16le to control the 10 | * character set. 11 | */ 12 | charset?: string; 13 | /** 14 | * Set to tab or space to use hard tabs or soft tabs respectively. 15 | */ 16 | indent_style?: 'unset' | 'tab' | 'space'; 17 | /** 18 | * The number of columns used for each indentation level and the width 19 | * of soft tabs (when supported). When set to tab, the value of 20 | * tab_width (if specified) will be used. 21 | */ 22 | indent_size?: number | 'unset' | 'tab'; 23 | /** 24 | * Number of columns used to represent a tab character. This defaults 25 | * to the value of indent_size and doesn't usually need to be specified. 26 | */ 27 | tab_width?: number | 'unset'; 28 | /** 29 | * Removes any whitespace characters preceding newline characters. 30 | */ 31 | trim_trailing_whitespace?: boolean | 'unset'; 32 | /** 33 | * Set to lf, cr, or crlf to control how line breaks are represented. 34 | */ 35 | end_of_line?: 'lf' | 'crlf' | 'unset'; 36 | /** 37 | * Ensures files ends with a newline. 38 | */ 39 | insert_final_newline?: boolean | 'unset'; 40 | /** 41 | * Enforces the maximum number of columns you can have in a line. 42 | */ 43 | max_line_length: number; 44 | block_comment?: string; 45 | block_comment_start?: string; 46 | block_comment_end?: string; 47 | } 48 | class Editorconfig { 49 | props: IProperties; 50 | 51 | constructor() { 52 | const defaults = { max_line_length: DEFAULT_EDITORCONFIG_MAX_LINE_LENGTH }; 53 | try { 54 | this.props = { ...defaults, ...editorconfig.parseSync(process.cwd()) }; 55 | log.debug('Editor config: JSON.stringify(this.props)'); 56 | } catch (error) { 57 | log.error(`Error parsing editorconfig: ${JSON.stringify(error)}`); 58 | this.props = defaults; 59 | } 60 | } 61 | } 62 | 63 | export default new Editorconfig(); 64 | -------------------------------------------------------------------------------- /dist/mjs/svg-editor.d.mts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code imports necessary modules and defines a class named 'SVGEditor' for generating SVG images. 3 | * The class has methods for initializing the SVG window, generating SVG content, and writing SVG files. 4 | * It utilizes various packages such as 'fs', 'path', '@svgdotjs/svg.js', 'feather-icons', and 'svgdom' for SVG manipulation and file operations. 5 | * The class also defines interfaces for badges and brand colors. 6 | */ 7 | import type { FeatherIconNames } from 'feather-icons'; 8 | import type { BrandColors } from './constants.js'; 9 | /** 10 | * Utility class for generating SVG images. 11 | */ 12 | export default class SVGEditor { 13 | private log; 14 | private window?; 15 | private canvas?; 16 | private document?; 17 | /** 18 | * Initializes a new SVGEditor instance. 19 | */ 20 | constructor(); 21 | /** 22 | * Initializes the SVG window, document, and canvas if not already set up. 23 | */ 24 | initSVG(): Promise; 25 | /** 26 | * Generates a branded SVG image. 27 | * @param {string | undefined} svgPath - Path to write the generated SVG file to. 28 | * @param {Partial} icon - Name of the icon to use. 29 | * @param {Partial} bgcolor - Background color for the image. 30 | * @returns {Promise} A promise that resolves when the image is generated. 31 | */ 32 | generateSvgImage(svgPath: string | undefined, icon?: Partial, bgcolor?: Partial): Promise; 33 | /** 34 | * Writes the SVG xml to disk. 35 | * @param {string} svgPath - File path to save the SVG to. 36 | * @param {string} svgContent - The XML for the SVG file. 37 | */ 38 | writeSVGFile(svgPath: string, svgContent: string): void; 39 | /** 40 | * Generates the SVG content for the branding image. 41 | * @param {FeatherIconNames} icon - Name of the icon to use. 42 | * @param {BrandColors} color - Background color for the image. 43 | * @param {number} outerViewBox - Size of the canvas for the image. 44 | * @returns {string} The generated SVG content. 45 | */ 46 | generateSVGContent(icon: FeatherIconNames, color: BrandColors, outerViewBox?: number): string; 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tag and Release Updated NPM Package 2 | 3 | on: 4 | pull_request_target: 5 | push: 6 | branches: 7 | - main 8 | - next 9 | - beta 10 | - "*.x" 11 | repository_dispatch: 12 | types: [semantic-release] 13 | 14 | concurrency: 15 | group: ci-${{ github.event.pull_request.number }}${{ github.ref }}${{ github.workflow }} 16 | cancel-in-progress: true 17 | 18 | permissions: 19 | issues: write 20 | pull-requests: write 21 | statuses: write 22 | checks: write 23 | actions: write 24 | id-token: write 25 | contents: write 26 | 27 | jobs: 28 | run-tests: 29 | name: Run unit tests (Node ${{ matrix.node-version }}) 30 | runs-on: ubuntu-latest 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | node-version: ["20.0.0", "20.17.0", "24.x"] 35 | env: 36 | SKIP_PREFLIGHT_CHECK: true 37 | steps: 38 | - uses: actions/checkout@v6.0.1 39 | with: 40 | ref: ${{ github.head_ref || github.ref }} 41 | 42 | - name: Install compatible Nodejs version 43 | id: setup-node 44 | uses: ./.github/actions/setup-node 45 | with: 46 | version: ${{ matrix.node-version }} 47 | 48 | - name: Configure PATH 49 | run: | 50 | mkdir -p "$HOME/.local/bin" 51 | echo "$HOME/.local/bin" >> "${GITHUB_PATH}" 52 | echo "HOME=$HOME" >> "${GITHUB_ENV}" 53 | 54 | - run: npm install 55 | - run: npm run test 56 | - run: npm run coverage 57 | - name: "Report Coverage" 58 | if: always() 59 | continue-on-error: true 60 | uses: davelosert/vitest-coverage-report-action@v2.9.0 61 | with: 62 | json-summary-path: "./out/coverage-summary.json" 63 | json-final-path: ./out/coverage-final.json 64 | - run: npm run build 65 | - run: npm run generate-docs 66 | call-workflow-passing-data: 67 | needs: run-tests 68 | if: ${{ github.event_name == 'push' }} 69 | permissions: 70 | issues: write 71 | pull-requests: write 72 | statuses: write 73 | checks: write 74 | actions: write 75 | id-token: write 76 | contents: write 77 | uses: ./.github/workflows/deploy.yml 78 | with: 79 | ref: ${{ github.head_ref || github.ref }} 80 | secrets: inherit 81 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tabWidth: 2, 3 | useTabs: false, 4 | printWidth: 100, 5 | bracketSameLine: true, 6 | proseWrap: 'preserve', 7 | endOfLine: 'lf', 8 | embeddedLanguageFormatting: 'auto', 9 | trailingComma: 'es5', 10 | bracketSpacing: true, 11 | semi: true, 12 | arrowParens: 'always', 13 | singleQuote: true, 14 | quoteProps: 'consistent', 15 | 16 | overrides: [ 17 | { 18 | files: ['**/*.yml', '**/*.yaml'], 19 | options: { 20 | singleQuote: false, 21 | printWidth: 100, 22 | parser: 'yaml', 23 | }, 24 | }, 25 | { 26 | files: 'package*.json', 27 | options: { 28 | printWidth: 1000, 29 | }, 30 | }, 31 | { 32 | files: ['**/*.ts', '**/*.tsx'], 33 | options: { 34 | parser: 'typescript', 35 | useTabs: false, 36 | tabWidth: 2, 37 | trailingComma: 'all', 38 | semi: true, 39 | }, 40 | }, 41 | { 42 | files: ['**/*.js', '**/*.jsx'], 43 | options: { 44 | parser: 'babel', 45 | useTabs: false, 46 | tabWidth: 2, 47 | trailingComma: 'all', 48 | semi: true, 49 | }, 50 | }, 51 | { 52 | files: ['**/*.json'], 53 | options: { 54 | singleQuote: false, 55 | quoteProps: 'preserve', 56 | parser: 'json', 57 | }, 58 | }, 59 | { 60 | files: ['**/*.json5'], 61 | options: { 62 | singleQuote: false, 63 | quoteProps: 'preserve', 64 | parser: 'json5', 65 | }, 66 | }, 67 | { 68 | files: ['**/*.md'], 69 | options: { 70 | parser: 'markdown', 71 | proseWrap: 'preserve', 72 | }, 73 | }, 74 | { 75 | files: ['**/*.css'], 76 | options: { 77 | parser: 'css', 78 | }, 79 | }, 80 | { 81 | files: ['**/*.scss'], 82 | options: { 83 | parser: 'scss', 84 | }, 85 | }, 86 | { 87 | files: ['**/*.less'], 88 | options: { 89 | parser: 'less', 90 | }, 91 | }, 92 | { 93 | files: ['**/*.sh'], 94 | options: { 95 | parser: 'sh', 96 | tabWidth: 2, 97 | useTabs: false, 98 | }, 99 | }, 100 | ], 101 | }; 102 | -------------------------------------------------------------------------------- /.github/actions/setup-node/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup Node 2 | description: Sets up Node.js environment 3 | 4 | inputs: 5 | version: 6 | description: "The version of Node.js to use" 7 | default: "20.x" 8 | required: false 9 | type: string 10 | 11 | outputs: 12 | version: 13 | description: "The version of Node.js that was set up" 14 | value: ${{ steps.node.outputs.version }} 15 | 16 | runs: 17 | using: "composite" 18 | steps: 19 | - name: Check if Node Version change is required 20 | working-directory: ${{ github.workspace }} 21 | id: node 22 | shell: bash 23 | env: 24 | npm_config_engine_strict: true 25 | run: | 26 | # check if the installed node version works with the engines field in package.json 27 | if npm ln --dry-run --ignore-scripts &>/dev/null; then 28 | NODE_VERSION="$(node -v)" 29 | 30 | # check if .npmrc specifies a node version 31 | elif [ -f .npmrc ] && grep -qP '^node-version\s*=' .npmrc ; then 32 | NODE_VERSION="$(grep -oP '^node-version\s*=\s*\K.*' .npmrc | cut -d '.' -f 1-3)" 33 | 34 | # check if .nvmrc or .node-version specify a node version 35 | elif [ -f .node-version ] && grep -qP '^\d+\.\d+\.\d+$' .node-version ; then 36 | NODE_VERSION="$(cat .node-version)" 37 | elif [ -f .nvmrc ] && grep -qP '^\d+\.\d+\.\d+$' .nvmrc ; then 38 | NODE_VERSION="$(cat .nvmrc)" 39 | 40 | # get the latest version of node that is compatible with the engines field in package.json 41 | elif [ -f package.json ] && jq --exit-status -r '.engines.node' package.json 2>&1 >/dev/null; then 42 | NODE_VERSION="$(bash ./scripts/latest_valid_node_version.sh)" 43 | fi 44 | 45 | # check that we now have a node version, if not, use the default 46 | if ! grep -qoP '^v?\d+\.(x|\d+\.)?(\d+|x)?' <<<"${NODE_VERSION}"; then 47 | NODE_VERSION="${{ inputs.version || '20.x' }}" 48 | echo "::warning::Unable to determine Node.js version from project files, using default version ${NODE_VERSION}" 49 | fi 50 | 51 | echo "version=${NODE_VERSION}" | tee -a "$GITHUB_OUTPUT" 52 | echo "NODE_VERSION=${NODE_VERSION}" >> "$GITHUB_ENV" 53 | 54 | - uses: actions/setup-node@v4 55 | with: 56 | node-version: "${{ steps.node.outputs.version }}" 57 | cache: npm 58 | -------------------------------------------------------------------------------- /src/sections/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code exports a function named 'updateSection' which takes a section (ReadmeSection) and an instance of the 'Inputs' class as its parameters. 3 | * The function is responsible for updating different sections of the README.md file based on the provided section input. 4 | * It utilizes various update functions (e.g., updateBranding, updateBadges) to update specific sections. 5 | * @param {ReadmeSection} section - The section of the README to update. 6 | * @param {Inputs} inputs - The Inputs class instance. 7 | * @returns {Promise} A promise that resolves once the section is updated. 8 | */ 9 | import { ReadmeSection } from '../constants.js'; 10 | import type Inputs from '../inputs.js'; 11 | import LogTask from '../logtask/index.js'; 12 | import updateBadges from './update-badges.js'; 13 | import updateBranding from './update-branding.js'; 14 | import updateDescription from './update-description.js'; 15 | import updateInputs from './update-inputs.js'; 16 | import updateOutputs from './update-outputs.js'; 17 | import updateTitle from './update-title.js'; 18 | import updateUsage from './update-usage.js'; 19 | 20 | const log = new LogTask('updateSection'); 21 | 22 | export default async function updateSection( 23 | section: ReadmeSection, 24 | inputs: Inputs, 25 | ): Promise> { 26 | const [startToken, stopToken] = inputs.readmeEditor.getTokenIndexes(section); 27 | // && 28 | // ['branding', 'title'].includes(section) && 29 | // inputs.config.get('branding_as_title_prefix') !== true 30 | if (startToken === -1 || stopToken === -1) { 31 | return {}; 32 | } 33 | switch (section) { 34 | case 'branding': { 35 | return updateBranding(section, inputs); 36 | } 37 | case 'badges': { 38 | return updateBadges(section, inputs); 39 | } 40 | case 'usage': { 41 | return updateUsage(section, inputs); 42 | } 43 | case 'title': { 44 | return updateTitle(section, inputs); 45 | } 46 | case 'description': { 47 | return updateDescription(section, inputs); 48 | } 49 | case 'inputs': { 50 | return updateInputs(section, inputs); 51 | } 52 | case 'outputs': { 53 | return updateOutputs(section, inputs); 54 | } 55 | default: { 56 | log.debug(`unknown section found . No updates were made.`); 57 | return {}; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | ## Description 15 | 16 | 17 | 18 | ## Type of Change 19 | 20 | 21 | 22 | - [ ] Bug fix (non-breaking change which fixes an issue) 23 | - [ ] New feature (non-breaking change which adds functionality) 24 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 25 | - [ ] Documentation update 26 | - [ ] Code refactoring 27 | - [ ] Performance improvement 28 | - [ ] Test update 29 | - [ ] Build/CI update 30 | - [ ] Other (please describe): 31 | 32 | ## Related Issues 33 | 34 | 35 | 36 | - Fixes # 37 | - Related to # 38 | 39 | ## Changes Made 40 | 41 | 42 | 43 | - 44 | - 45 | - 46 | 47 | ## Testing 48 | 49 | 50 | 51 | - [ ] All existing tests pass 52 | - [ ] Added new tests for new functionality 53 | - [ ] Manually tested the changes 54 | - [ ] Updated documentation 55 | 56 | ## Checklist 57 | 58 | 59 | 60 | - [ ] My code follows the project's code style 61 | - [ ] I have performed a self-review of my own code 62 | - [ ] I have commented my code, particularly in hard-to-understand areas 63 | - [ ] I have made corresponding changes to the documentation 64 | - [ ] My changes generate no new warnings or errors 65 | - [ ] I have added tests that prove my fix is effective or that my feature works 66 | - [ ] New and existing unit tests pass locally with my changes 67 | - [ ] Any dependent changes have been merged and published 68 | 69 | ## Screenshots (if applicable) 70 | 71 | 72 | 73 | ## Additional Notes 74 | 75 | 76 | -------------------------------------------------------------------------------- /dist/mjs/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code imports the necessary modules and defines two interfaces: `Versioning` and `Paths`. 3 | * It also defines a class named `GHActionDocsConfig` that represents the configuration for generating GitHub Actions documentation. 4 | * The class has properties that correspond to the configuration options and a method `loadInputs` to load the configuration from the provided `Inputs` object. 5 | * The class also has a method `save` to save the configuration to a file. 6 | */ 7 | import { promises as fsPromises } from 'node:fs'; 8 | import path from 'node:path'; 9 | import LogTask from './logtask/index.js'; 10 | /** 11 | * Represents the configuration for generating GitHub Actions documentation. 12 | */ 13 | export class GHActionDocsConfig { 14 | owner; 15 | repo; 16 | title_prefix; 17 | title; 18 | paths; 19 | branding_svg_path; 20 | versioning; 21 | prettier; 22 | /** 23 | * Loads the configuration from the provided `Inputs` object. 24 | * @param {Inputs} inputs - The `Inputs` object containing the configuration values. 25 | */ 26 | loadInputs(inputs) { 27 | const config = inputs.config.get(); 28 | this.owner = config.owner; 29 | this.repo = config.repo; 30 | this.title_prefix = config.title_prefix; 31 | this.title = config.title; 32 | this.paths = config.paths; 33 | this.branding_svg_path = config.branding_svg_path; 34 | this.versioning = config.versioning; 35 | this.prettier = config.prettier; 36 | } 37 | /** 38 | * Saves the configuration to a file. If the file exists, it will be overwritten. 39 | * @param {string} configPath - The path to the configuration file. 40 | */ 41 | async save(configPath) { 42 | const log = new LogTask('config:save'); 43 | const directory = path.dirname(configPath); 44 | try { 45 | await fsPromises.mkdir(directory, { recursive: true }); 46 | } 47 | catch (error) { 48 | log.error(`Error creating directory: ${directory}.`); 49 | throw error; 50 | } 51 | try { 52 | await fsPromises.writeFile(configPath, JSON.stringify(this, null, 2)); 53 | log.info(`Config file written to: ${configPath}`); 54 | } 55 | catch (error) { 56 | log.error(`Error writing config file: ${configPath}.`); 57 | throw error; 58 | } 59 | } 60 | } 61 | //# sourceMappingURL=config.js.map -------------------------------------------------------------------------------- /dist/mjs/sections/update-description.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"update-description.js","sourceRoot":"","sources":["../../../src/sections/update-description.ts"],"names":[],"mappings":"AASA,OAAO,OAAO,MAAM,qBAAqB,CAAC;AAE1C,MAAM,CAAC,OAAO,UAAU,iBAAiB,CACvC,YAA2B,EAC3B,MAAc;IAEd,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;IAEtC,uBAAuB;IACvB,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,oCAAoC;IACpC,IAAI,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE;QAC/B,GAAG,CAAC,KAAK,EAAE,CAAC;QACZ,MAAM,IAAI,GAAW,MAAM,CAAC,MAAM,CAAC,WAAW;aAC3C,IAAI,EAAE;aACN,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,mBAAmB;aAC5C,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,4BAA4B;aACnD,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,mCAAmC;aAC3D,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,mCAAmC;QAEpE,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,MAAM,wCAAwC,CAAC,CAAC;QACzE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,MAAM,CAAC,YAAY,CAAC,aAAa,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACzD,GAAG,CAAC,OAAO,EAAE,CAAC;KACf;IACD,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,GAAG,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["/**\n * This TypeScript code exports a function named 'updateDescription' which takes a sectionToken (ReadmeSection) and an instance of the 'Inputs' class as its parameters.\n * The function is responsible for updating the description section in the README.md file based on the provided inputs.\n * It utilizes the 'LogTask' class for logging purposes.\n * @param {ReadmeSection} sectionToken - The sectionToken representing the section of the README to update.\n * @param {Inputs} inputs - The Inputs class instance.\n */\nimport { ReadmeSection } from '../constants.js';\nimport type Inputs from '../inputs.js';\nimport LogTask from '../logtask/index.js';\n\nexport default function updateDescription(\n sectionToken: ReadmeSection,\n inputs: Inputs,\n): Record {\n const log = new LogTask(sectionToken);\n\n // Build the new README\n const content: string[] = [];\n\n // Build the new description section\n if (inputs?.action?.description) {\n log.start();\n const desc: string = inputs.action.description\n .trim()\n .replaceAll('\\r\\n', '\\n') // Convert CR to LF\n .replaceAll(/ +/g, ' ') // Squash consecutive spaces\n .replaceAll(' \\n', '\\n') // Squash space followed by newline\n .replaceAll('\\n\\n', '
'); // Convert double return to a break\n\n log.info(`Writing ${desc.length} characters to the description section`);\n content.push(desc);\n inputs.readmeEditor.updateSection(sectionToken, content);\n log.success();\n }\n const ret: Record = {};\n ret[sectionToken] = content.join('\\n');\n return ret;\n}\n"]} -------------------------------------------------------------------------------- /.cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "language": "en", 4 | "ignorePaths": [ 5 | "**/coverage/**", 6 | "**/node_modules/**", 7 | "**/dist/**", 8 | "**/fixtures/**", 9 | "**/**/CHANGELOG.md", 10 | "**/**/CONTRIBUTORS.md", 11 | "**/**/ROADMAP.md", 12 | "**/*.{json,snap}", 13 | ".cspell.json", 14 | "yarn.lock", 15 | ".github/workflows/**", 16 | ".vscode/*.json" 17 | ], 18 | "dictionaries": ["typescript", "softwareTerms", "node", "en_US", "npm", "misc", "filetypes"], 19 | "ignoreRegExpList": [ 20 | "/```[\\w\\W]*?```/", 21 | "/~~~[\\w\\W]*?~~~/", 22 | "/``[\\w\\W]*?``/", 23 | "/`[^`]*`/", 24 | "/\\.\\/docs\\/rules\\/[^.]*.md/", 25 | "/@typescript-eslint\\/[a-z-]+/", 26 | "/\\.all-contributorsrc/", 27 | "/TS[^\\s]+/", 28 | "\\(#.+?\\)" 29 | ], 30 | "words": [ 31 | "Airbnb", 32 | "Airbnb's", 33 | "ambiently", 34 | "ASTs", 35 | "autofix", 36 | "autofixers", 37 | "autofixes", 38 | "backticks", 39 | "bigint", 40 | "bivariant", 41 | "blockless", 42 | "btob", 43 | "camelcase", 44 | "codebases", 45 | "Codecov", 46 | "contravariant", 47 | "Crockford", 48 | "declarators", 49 | "destructure", 50 | "destructured", 51 | "errored", 52 | "erroring", 53 | "ESLint", 54 | "ESLint's", 55 | "espree", 56 | "esrecurse", 57 | "estree", 58 | "exnext", 59 | "hardlinks", 60 | "IDE's", 61 | "IIFE", 62 | "IIFEs", 63 | "linebreaks", 64 | "necroing", 65 | "nocheck", 66 | "nullish", 67 | "oclif", 68 | "OOM", 69 | "OOMs", 70 | "parameterised", 71 | "performant", 72 | "pluggable", 73 | "plusplus", 74 | "postprocess", 75 | "postprocessor", 76 | "preact", 77 | "Premade", 78 | "prettier's", 79 | "recurse", 80 | "redeclaration", 81 | "redeclarations", 82 | "redeclared", 83 | "reimplement", 84 | "resync", 85 | "ROADMAP", 86 | "ruleset", 87 | "rulesets", 88 | "serializers", 89 | "superset", 90 | "thenables", 91 | "transpiled", 92 | "transpiles", 93 | "transpiling", 94 | "tsconfigs", 95 | "tsutils", 96 | "typedef", 97 | "typedefs", 98 | "unfixable", 99 | "unprefixed", 100 | "Zacher" 101 | ], 102 | "overrides": [ 103 | { 104 | "filename": "**/*.{ts,js}", 105 | "ignoreRegExpList": ["/@[a-z]+/", "/#(end)?region/"], 106 | "includeRegExpList": [ 107 | "/\\/\\*[\\s\\S]*?\\*\\/|([^\\\\:]|^)\\/\\/.*$/", 108 | "/(\\/\\/[^\\n\\r]*[\\n\\r]+)/" 109 | ] 110 | } 111 | ] 112 | } 113 | -------------------------------------------------------------------------------- /src/sections/update-outputs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code exports a function named 'updateOutputs' which takes a sectionToken (string) and an instance of the 'Inputs' class as its parameters. 3 | * The function is responsible for updating the outputs section in the README.md file based on the provided inputs. 4 | * It generates a table with three columns: Output name, Description, and Value (for composite actions). 5 | * It utilizes the 'LogTask' class for logging purposes, 'columnHeader' and 'rowHeader' functions from '../helpers.js' for formatting table headers, and 'markdowner' function from '../markdowner/index.js' for generating markdown content. 6 | * @param {ReadmeSection} sectionToken - The sectionToken used for identifying the section. 7 | * @param {Inputs} inputs - The Inputs class instance. 8 | */ 9 | import { ReadmeSection } from '../constants.js'; 10 | import { columnHeader, rowHeader } from '../helpers.js'; 11 | import type Inputs from '../inputs.js'; 12 | import LogTask from '../logtask/index.js'; 13 | import markdowner from '../markdowner/index.js'; 14 | 15 | export default function updateOutputs( 16 | sectionToken: ReadmeSection, 17 | inputs: Inputs, 18 | ): Record { 19 | const log = new LogTask(sectionToken); 20 | 21 | // Build the new README 22 | const content: string[] = []; 23 | 24 | const markdownArray: string[][] = []; 25 | const titleArray = ['Output', 'Description', 'Value']; 26 | const titles: string[] = []; 27 | for (const t of titleArray) { 28 | titles.push(columnHeader(t)); 29 | } 30 | markdownArray.push(titles); 31 | const vars = inputs.action.outputs; 32 | const tI = vars ? Object.keys(vars).length : 0; 33 | if (vars && tI > 0) { 34 | log.start(); 35 | for (const key of Object.keys(vars)) { 36 | const values = vars[key]; 37 | 38 | let description = values?.description ?? ''; 39 | 40 | // Check if only first line should be added (only subject without body) 41 | 42 | const matches = /(.*?)\n\n([Ss]*)/.exec(description); 43 | if (matches && matches.length >= 2) { 44 | description = matches[1] || description; 45 | } 46 | 47 | description = description.trim().replace('\n', '
'); 48 | const value = values?.value ? `\`${values.value}\`` : ''; 49 | const row: string[] = [rowHeader(key), description, value]; 50 | 51 | log.debug(JSON.stringify(row)); 52 | markdownArray.push(row); 53 | } 54 | content.push(markdowner(markdownArray)); 55 | log.info(`Action has ${tI} total ${sectionToken}`); 56 | inputs.readmeEditor.updateSection(sectionToken, content); 57 | log.success(); 58 | } else { 59 | log.debug(`Action has no ${sectionToken}`); 60 | } 61 | const ret: Record = {}; 62 | ret[sectionToken] = content.join('\n'); 63 | return ret; 64 | } 65 | -------------------------------------------------------------------------------- /src/sections/update-inputs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code exports a function named 'updateInputs' which takes a sectionToken (ReadmeSection) and an instance of the 'Inputs' class as its parameters. 3 | * The function is responsible for updating the inputs section in the README.md file based on the provided inputs. 4 | * It utilizes the 'LogTask' class for logging purposes, 'columnHeader' and 'rowHeader' functions from '../helpers.js' for formatting table headers, and 'markdowner' function from '../markdowner/index.js' for generating markdown content. 5 | * @param {ReadmeSection} sectionToken - The sectionToken representing the section of the README to update. 6 | * @param {Inputs} inputs - The Inputs class instance. 7 | */ 8 | import { ReadmeSection } from '../constants.js'; 9 | import { columnHeader, rowHeader } from '../helpers.js'; 10 | import type Inputs from '../inputs.js'; 11 | import LogTask from '../logtask/index.js'; 12 | import markdowner from '../markdowner/index.js'; 13 | 14 | export default function updateInputs( 15 | sectionToken: ReadmeSection, 16 | inputs: Inputs, 17 | ): Record { 18 | const log = new LogTask(sectionToken); 19 | 20 | // Build the new README 21 | const content: string[] = []; 22 | const markdownArray: string[][] = []; 23 | const titleArray = ['Input', 'Description', 'Default', 'Required']; 24 | const titles: string[] = []; 25 | 26 | for (const t of titleArray) { 27 | titles.push(columnHeader(t)); 28 | } 29 | 30 | markdownArray.push(titles); 31 | 32 | const vars = inputs.action.inputs; 33 | const tI = vars ? Object.keys(vars).length : 0; 34 | 35 | if (vars && tI > 0) { 36 | log.start(); 37 | 38 | for (const key of Object.keys(vars)) { 39 | const values = vars[key]; 40 | let description = values?.description ?? ''; 41 | 42 | // Check if only the first line should be added (only subject without body) 43 | const matches = /(.*?)\n\n([Ss]*)/.exec(description); 44 | 45 | if (matches && matches.length >= 2) { 46 | description = matches[1] || description; 47 | } 48 | 49 | description = description.trim().replace('\n', '
'); 50 | 51 | const row: string[] = [ 52 | rowHeader(key), 53 | description, 54 | values?.default ? `${values.default}` : '', 55 | values?.required ? '**true**' : '__false__', 56 | ]; 57 | log.debug(JSON.stringify(row)); 58 | markdownArray.push(row); 59 | } 60 | content.push(markdowner(markdownArray)); 61 | log.info(`Action has ${tI} total ${sectionToken}`); 62 | inputs.readmeEditor.updateSection(sectionToken, content); 63 | log.success(); 64 | } else { 65 | log.debug(`Action has no ${sectionToken}`); 66 | } 67 | const ret: Record = {}; 68 | ret[sectionToken] = content.join('\n'); 69 | return ret; 70 | } 71 | -------------------------------------------------------------------------------- /dist/mjs/sections/update-usage.js: -------------------------------------------------------------------------------- 1 | import { getCurrentVersionString } from '../helpers.js'; 2 | import LogTask from '../logtask/index.js'; 3 | import { wrapDescription } from '../prettier.js'; 4 | export default async function updateUsage(sectionToken, inputs) { 5 | const log = new LogTask(sectionToken); 6 | log.start(); 7 | const actionName = `${inputs.owner}/${inputs.repo}`; 8 | log.info(`Action name: ${actionName}`); 9 | const versionString = getCurrentVersionString(inputs); 10 | log.info(`Version string: ${versionString}`); 11 | const actionReference = `${actionName}@${versionString}`; 12 | const indent = ' # '; 13 | // Build the new README 14 | const content = []; 15 | // Build the new usage section 16 | content.push('```yaml', `- uses: ${actionReference}`, ' with:'); 17 | const inp = inputs.action.inputs; 18 | let firstInput = true; 19 | const descriptionPromises = {}; 20 | if (inp) { 21 | for (const key of Object.keys(inp)) { 22 | const input = inp[key]; 23 | if (input !== undefined) { 24 | descriptionPromises[key] = wrapDescription(`Description: ${input.description}`, [], indent); 25 | } 26 | } 27 | const descriptions = {}; 28 | const kvArray = await Promise.all(Object.keys(descriptionPromises).map(async (key) => { 29 | return { key, value: await descriptionPromises[key] }; 30 | })); 31 | for (const e of kvArray) { 32 | descriptions[e.key] = e.value; 33 | log.debug(`${e.key}: ${descriptions[e.key].join('\n')}`); 34 | } 35 | for (const key of Object.keys(inp)) { 36 | const input = inp[key]; 37 | if (input !== undefined) { 38 | // Line break between inputs 39 | if (!firstInput) { 40 | content.push(''); 41 | } 42 | // Constrain the width of the description, and append it 43 | content.push(...descriptions[key]); 44 | if (input.default !== undefined) { 45 | // Append blank line if description had paragraphs 46 | // if (input.description?.trimEnd().match(/\n *\r?\n/)) { 47 | // content.push(' #'); 48 | // } 49 | // Default 50 | content.push(`${indent}Default: ${input.default}`); 51 | } 52 | // Input name 53 | content.push(` ${key}: ''`); 54 | firstInput = false; 55 | } 56 | } 57 | } 58 | content.push('```\n'); 59 | inputs.readmeEditor.updateSection(sectionToken, content); 60 | log.success(); 61 | const ret = {}; 62 | ret[sectionToken] = content.join('\n'); 63 | return ret; 64 | } 65 | //# sourceMappingURL=update-usage.js.map -------------------------------------------------------------------------------- /dist/mjs/sections/update-title.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"update-title.js","sourceRoot":"","sources":["../../../src/sections/update-title.ts"],"names":[],"mappings":"AASA,OAAO,OAAO,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,CAAC,OAAO,UAAU,WAAW,CACjC,YAA2B,EAC3B,MAAc;IAEd,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;IAEtC,uBAAuB;IACvB,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,SAAS,GAAG,EAAE,CAAC;IAEnB,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;QACtB,GAAG,CAAC,KAAK,EAAE,CAAC;QACZ,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;QAC1B,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAY,EAAE;YAC5D,SAAS,GAAG,GAAG,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC;SACrD;QACD,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,MAAM,0BAA0B,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAW,GACxE,MAAM,CAAC,MAAM,CAAC,IAChB,EAAE,CAAC;QACH,GAAG,CAAC,IAAI,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC;QAC5B,8BAA8B;QAC9B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpB,MAAM,CAAC,YAAY,CAAC,aAAa,CAAC,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QAE/D,GAAG,CAAC,OAAO,EAAE,CAAC;KACf;IACD,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,GAAG,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["/**\n * This TypeScript code exports a function named 'updateTitle' which takes a sectionToken (ReadmeSection) and an instance of the 'Inputs' class as its parameters.\n * The function is responsible for updating the title section in the README.md file based on the provided inputs.\n * It utilizes the 'LogTask' class for logging purposes, the 'generateImgMarkup' function from './update-branding.js' for generating image markup.\n * @param {ReadmeSection} sectionToken - The sectionToken representing the section of the README to update.\n * @param {Inputs} inputs - The Inputs class instance.\n */\nimport { ReadmeSection } from '../constants.js';\nimport type Inputs from '../inputs.js';\nimport LogTask from '../logtask/index.js';\nimport { generateImgMarkup } from './update-branding.js';\n\nexport default function updateTitle(\n sectionToken: ReadmeSection,\n inputs: Inputs,\n): Record {\n const log = new LogTask(sectionToken);\n\n // Build the new README\n const content: string[] = [];\n let name = '';\n let svgInline = '';\n\n if (inputs.action.name) {\n log.start();\n name = inputs.action.name;\n if (inputs.config.get('branding_as_title_prefix') as boolean) {\n svgInline = `${generateImgMarkup(inputs, '60px')} `;\n }\n log.info(`Writing ${name.length} characters to the title`);\n const title = `# ${svgInline}${inputs.config.get('title_prefix') as string}${\n inputs.action.name\n }`;\n log.info(`Title: ${title}`);\n // Build the new usage section\n content.push(title);\n inputs.readmeEditor.updateSection(sectionToken, content, true);\n\n log.success();\n }\n const ret: Record = {};\n ret[sectionToken] = content.join('\\n');\n return ret;\n}\n"]} -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code imports the necessary modules and defines two interfaces: `Versioning` and `Paths`. 3 | * It also defines a class named `GHActionDocsConfig` that represents the configuration for generating GitHub Actions documentation. 4 | * The class has properties that correspond to the configuration options and a method `loadInputs` to load the configuration from the provided `Inputs` object. 5 | * The class also has a method `save` to save the configuration to a file. 6 | */ 7 | 8 | import { promises as fsPromises } from 'node:fs'; 9 | import path from 'node:path'; 10 | 11 | import type Inputs from './inputs.js'; 12 | import LogTask from './logtask/index.js'; 13 | 14 | /** 15 | * Represents the versioning configuration for GitHub Actions documentation. 16 | */ 17 | export interface Versioning { 18 | enabled?: boolean; 19 | prefix?: string; 20 | override?: string; 21 | branch?: string; 22 | badge?: string; 23 | } 24 | 25 | /** 26 | * Represents the paths configuration for GitHub Actions documentation. 27 | */ 28 | export interface Paths { 29 | action: string; 30 | readme: string; 31 | } 32 | 33 | /** 34 | * Represents the configuration for generating GitHub Actions documentation. 35 | */ 36 | export class GHActionDocsConfig { 37 | owner?: string; 38 | 39 | repo?: string; 40 | 41 | title_prefix?: string; 42 | 43 | title?: string; 44 | 45 | paths?: Paths; 46 | 47 | branding_svg_path?: string; 48 | 49 | versioning?: Versioning; 50 | 51 | prettier?: boolean; 52 | 53 | /** 54 | * Loads the configuration from the provided `Inputs` object. 55 | * @param {Inputs} inputs - The `Inputs` object containing the configuration values. 56 | */ 57 | loadInputs(inputs: Inputs): void { 58 | const config = inputs.config.get(); 59 | this.owner = config.owner; 60 | this.repo = config.repo; 61 | this.title_prefix = config.title_prefix; 62 | this.title = config.title; 63 | this.paths = config.paths; 64 | this.branding_svg_path = config.branding_svg_path; 65 | this.versioning = config.versioning; 66 | this.prettier = config.prettier; 67 | } 68 | 69 | /** 70 | * Saves the configuration to a file. If the file exists, it will be overwritten. 71 | * @param {string} configPath - The path to the configuration file. 72 | */ 73 | async save(configPath: string): Promise { 74 | const log = new LogTask('config:save'); 75 | const directory = path.dirname(configPath); 76 | 77 | try { 78 | await fsPromises.mkdir(directory, { recursive: true }); 79 | } catch (error) { 80 | log.error(`Error creating directory: ${directory}.`); 81 | throw error; 82 | } 83 | 84 | try { 85 | await fsPromises.writeFile(configPath, JSON.stringify(this, null, 2)); 86 | log.info(`Config file written to: ${configPath}`); 87 | } catch (error) { 88 | log.error(`Error writing config file: ${configPath}.`); 89 | throw error; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/sections/update-usage.ts: -------------------------------------------------------------------------------- 1 | import { ReadmeSection } from '../constants.js'; 2 | import { getCurrentVersionString } from '../helpers.js'; 3 | import type Inputs from '../inputs.js'; 4 | import LogTask from '../logtask/index.js'; 5 | import { wrapDescription } from '../prettier.js'; 6 | 7 | type DescriptionType = Record; 8 | export default async function updateUsage( 9 | sectionToken: ReadmeSection, 10 | inputs: Inputs, 11 | ): Promise> { 12 | const log = new LogTask(sectionToken); 13 | log.start(); 14 | 15 | const actionName = `${inputs.owner}/${inputs.repo}`; 16 | log.info(`Action name: ${actionName}`); 17 | const versionString: string = getCurrentVersionString(inputs); 18 | 19 | log.info(`Version string: ${versionString}`); 20 | 21 | const actionReference = `${actionName}@${versionString}`; 22 | 23 | const indent = ' # '; 24 | // Build the new README 25 | const content: string[] = []; 26 | // Build the new usage section 27 | content.push('```yaml', `- uses: ${actionReference}`, ' with:'); 28 | 29 | const inp = inputs.action.inputs; 30 | let firstInput = true; 31 | const descriptionPromises: Record> = {}; 32 | if (inp) { 33 | for (const key of Object.keys(inp)) { 34 | const input = inp[key]; 35 | if (input !== undefined) { 36 | descriptionPromises[key] = wrapDescription(`Description: ${input.description}`, [], indent); 37 | } 38 | } 39 | 40 | const descriptions: DescriptionType = {}; 41 | const kvArray = await Promise.all( 42 | Object.keys(descriptionPromises).map(async (key) => { 43 | return { key, value: await descriptionPromises[key] }; 44 | }), 45 | ); 46 | for (const e of kvArray) { 47 | descriptions[e.key] = e.value; 48 | log.debug(`${e.key}: ${descriptions[e.key].join('\n')}`); 49 | } 50 | 51 | for (const key of Object.keys(inp)) { 52 | const input = inp[key]; 53 | if (input !== undefined) { 54 | // Line break between inputs 55 | if (!firstInput) { 56 | content.push(''); 57 | } 58 | 59 | // Constrain the width of the description, and append it 60 | content.push(...descriptions[key]); 61 | 62 | if (input.default !== undefined) { 63 | // Append blank line if description had paragraphs 64 | // if (input.description?.trimEnd().match(/\n *\r?\n/)) { 65 | // content.push(' #'); 66 | // } 67 | 68 | // Default 69 | content.push(`${indent}Default: ${input.default}`); 70 | } 71 | 72 | // Input name 73 | content.push(` ${key}: ''`); 74 | 75 | firstInput = false; 76 | } 77 | } 78 | } 79 | 80 | content.push('```\n'); 81 | 82 | inputs.readmeEditor.updateSection(sectionToken, content); 83 | log.success(); 84 | const ret: Record = {}; 85 | ret[sectionToken] = content.join('\n'); 86 | return ret; 87 | } 88 | -------------------------------------------------------------------------------- /dist/mjs/sections/update-branding.d.ts: -------------------------------------------------------------------------------- 1 | import type { FeatherIconNames } from 'feather-icons'; 2 | import type { BrandColors } from '../constants.js'; 3 | import { ReadmeSection } from '../constants.js'; 4 | import type Inputs from '../inputs.js'; 5 | export interface IBranding { 6 | alt: string; 7 | img: string; 8 | url?: string; 9 | } 10 | /** 11 | * Generates a svg branding image. 12 | * example: 13 | * ```ts 14 | * generateSvgImage('/path/to/file.svg', 'home', 'red') 15 | * ``` 16 | * 17 | * @param svgPath - The path to where the svg file will be saved 18 | * @param icon - The icon name from the feather-icons list 19 | * @param bgcolor - The background color of the circle behind the icon 20 | */ 21 | export declare function generateSvgImage(svgPath: string, icon: Partial, bgcolor: Partial): void; 22 | /** 23 | * This function returns a valid icon name based on the provided branding. 24 | * If the branding is undefined or not a valid icon name, an error is thrown. 25 | * It checks if the branding icon is present in the GITHUB_ACTIONS_BRANDING_ICONS set, 26 | * and if so, returns the corresponding feather icon key array. 27 | * If the branding icon is present in the GITHUB_ACTIONS_OMITTED_ICONS set, 28 | * an error is thrown specifying that the icon is part of the omitted icons list. 29 | * If the branding icon is not a valid icon from the feather-icons list, an error is thrown. 30 | * @param brand - The branding object 31 | * @returns The corresponding feather icon key array 32 | * @throws Error if the branding icon is undefined, not a valid icon name, or part of the omitted icons list 33 | */ 34 | export declare function getValidIconName(icon?: Partial): FeatherIconNames; 35 | /** 36 | * This function generates an HTML image markup with branding information. 37 | * It takes inputs and an optional width parameter. 38 | * If the branding_svg_path is provided, it generates an action.yml branding image for the specified icon and color. 39 | * Otherwise, it returns an error message. 40 | * 41 | * @param inputs - The inputs instance with data for the function. 42 | * @param width - The width of the image (default is '15%'). 43 | * @returns The HTML image markup with branding information or an error message. 44 | */ 45 | export declare function generateImgMarkup(inputs: Inputs, width?: string): string; 46 | /** 47 | * This is a TypeScript function named "updateBranding" that takes in a sectionToken string and an object of inputs. 48 | * It exports the function as the default export. 49 | * The function logs the brand details from the inputs, starts a log task, generates image markup, 50 | * updates a section in the readme editor using the sectionToken and content, and logs success or failure messages. 51 | * 52 | * @param sectionToken - The sectionToken string that is used to identify the section in the readme editor. 53 | * @param inputs - The inputs object that contains data for the function. 54 | */ 55 | export default function updateBranding(sectionToken: ReadmeSection, inputs: Inputs): Record; 56 | -------------------------------------------------------------------------------- /dist/mjs/readme-generator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code imports various modules and defines a function named 'generateDocs'. 3 | * The function is responsible for generating documentation for the README.md file based on the provided inputs. 4 | * It iterates through each section defined in the 'inputs.sections' array and calls the 'updateSection' function to update the corresponding section in the README.md file. 5 | * If an error occurs during the update of a section, it logs the error message and stops the process. 6 | * Finally, it saves the updated README.md file and calls the 'save' function. 7 | */ 8 | import * as core from '@actions/core'; 9 | import updateSection from './sections/index.js'; 10 | /** 11 | * Class for managing README generation. 12 | */ 13 | export class ReadmeGenerator { 14 | /** 15 | * The Inputs instance. 16 | */ 17 | inputs; 18 | /** 19 | * The Logger instance. 20 | */ 21 | log; 22 | /** 23 | * Initializes the ReadmeGenerator. 24 | * 25 | * @param inputs - The Inputs instance 26 | * @param log - The Logger instance 27 | */ 28 | constructor(inputs, log) { 29 | this.inputs = inputs; 30 | this.log = log; 31 | } 32 | /** 33 | * Updates the README sections. 34 | * 35 | * @param sections - The sections array 36 | * @returns Promise array of section KV objects 37 | */ 38 | updateSections(sections) { 39 | const promises = []; 40 | for (const section of sections) { 41 | promises.push(updateSection(section, this.inputs)); 42 | } 43 | return promises; 44 | } 45 | /** 46 | * Resolves the section update promises. 47 | * 48 | * @param promises - The promise array 49 | * @returns Promise resolving to combined sections KV 50 | */ 51 | async resolveUpdates(promises) { 52 | this.log.debug('Resolving updates'); 53 | const results = await Promise.all(promises); 54 | const sections = {}; 55 | for (const result of results) { 56 | Object.assign(sections, result); 57 | } 58 | return sections; 59 | } 60 | /** 61 | * Outputs the sections KV to GitHub output. 62 | * 63 | * @param sections - The sections KV 64 | */ 65 | outputSections(sections) { 66 | if (process.env.GITHUB_ACTIONS) { 67 | this.log.debug('Outputting sections'); 68 | core.setOutput('sections', JSON.stringify(sections, null, 2)); 69 | } 70 | else { 71 | this.log.debug('Not outputting sections'); 72 | } 73 | } 74 | /** 75 | * Generates the README documentation. 76 | * 77 | * @returns Promise resolving when done 78 | */ 79 | async generate(providedSections = this.inputs.sections) { 80 | const sectionPromises = this.updateSections(providedSections); 81 | const sections = await this.resolveUpdates(sectionPromises); 82 | this.outputSections(sections); 83 | return this.inputs.readmeEditor.dumpToFile(); 84 | } 85 | } 86 | //# sourceMappingURL=readme-generator.js.map -------------------------------------------------------------------------------- /dist/mjs/logtask/index.d.ts: -------------------------------------------------------------------------------- 1 | declare enum LogGroup { 2 | NO_GROUP = 0, 3 | START_GROUP = 1, 4 | END_GROUP = 2, 5 | IS_ERROR = 3, 6 | IS_FAILED = 4, 7 | IS_TITLE = 5 8 | } 9 | /** 10 | * Represents a logging task with various log step methods. 11 | */ 12 | export default class LogTask { 13 | /** 14 | * Map of ingroup settings per task name. 15 | */ 16 | private static ingroupSettings; 17 | /** 18 | * The width of the indentation for log messages. 19 | */ 20 | private static indentWidth; 21 | /** 22 | * Checks if debug mode is enabled. 23 | * @returns A boolean indicating if debug mode is enabled. 24 | */ 25 | static isDebug(): boolean; 26 | /** 27 | * The name of the task. 28 | */ 29 | private name; 30 | /** 31 | * Creates a new instance of the LogTask class. 32 | * @param name - The name of the task. 33 | */ 34 | constructor(name: string); 35 | /** 36 | * Gets the ingroup setting for the task. 37 | */ 38 | get ingroup(): boolean; 39 | /** 40 | * Sets the ingroup setting for this task. 41 | */ 42 | set ingroup(value: boolean); 43 | getMessageString(step: string, desc: string, emojiStr: string): string; 44 | /** 45 | * Logs a step with the given emoji, type, message and group. 46 | * @param emojiStr - The emoji string to display. 47 | * @param step - The step type. 48 | * @param message - The message of the step. 49 | * @param startGroup - The start group type. 50 | */ 51 | logStep(emojiStr: string, step: string, message: string, startGroup?: LogGroup): void; 52 | /** 53 | * Logs a debug message. 54 | * @param message - The message of the debug message. 55 | */ 56 | debug(message?: string): void; 57 | /** 58 | * Logs a start message. 59 | * @param message - The message of the start message. 60 | */ 61 | start(message?: string): void; 62 | /** 63 | * Logs an info message. 64 | * @param message - The message of the info message. 65 | */ 66 | info(message?: string): void; 67 | /** 68 | * Logs a warning message. 69 | * @param message - The message of the warning message. 70 | */ 71 | warn(message?: string): void; 72 | /** 73 | * Logs a success message. 74 | * @param message - The message of the success message. 75 | * @param ingroup - Indicates whether the success message is in a group. 76 | */ 77 | success(message?: string, ingroup?: boolean): void; 78 | /** 79 | * Logs a failure message. 80 | * @param message - The message of the failure message. 81 | * @param ingroup - Indicates whether the failure message is in a group. 82 | */ 83 | fail(message?: string, ingroup?: boolean): void; 84 | /** 85 | * Logs an error message. 86 | * @param message - The message of the error message. 87 | */ 88 | error(message?: string): void; 89 | /** 90 | * Logs a title message. 91 | * @param message - The message of the title message. 92 | */ 93 | title(message?: string): void; 94 | } 95 | export {}; 96 | -------------------------------------------------------------------------------- /dist/mjs/prettier.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code exports three functions: `formatYaml`, `formatMarkdown`, and `wrapDescription`. 3 | * 4 | * - `formatYaml` takes a YAML string and an optional filepath as parameters and uses the `prettier` library to format the YAML code. It returns the formatted YAML string. 5 | * - `formatMarkdown` takes a Markdown string and an optional filepath as parameters and uses the `prettier` library to format the Markdown code. It returns the formatted Markdown string. 6 | * - `wrapDescription` takes a string value, an array of content, and an optional prefix as parameters. It wraps the description text with the specified prefix and formats it using `prettier`. It returns the updated content array with the formatted description lines. 7 | * 8 | * The code utilizes the `prettier` library for code formatting and the `LogTask` class for logging purposes. 9 | */ 10 | import { format } from 'prettier'; 11 | import LogTask from './logtask/index.js'; 12 | const log = new LogTask('prettier'); 13 | /** 14 | * Formats a YAML string using `prettier`. 15 | * @param {string} value - The YAML string to format. 16 | * @param {string} [filepath] - The optional filepath. 17 | * @returns {Promise} A promise that resolves with the formatted YAML string. 18 | */ 19 | export async function formatYaml(value, filepath) { 20 | const fp = filepath ? { filepath } : {}; 21 | return format(value, { 22 | semi: false, 23 | parser: 'yaml', 24 | embeddedLanguageFormatting: 'auto', 25 | ...fp, 26 | }); 27 | } 28 | /** 29 | * Formats a Markdown string using `prettier`. 30 | * @param {string} value - The Markdown string to format. 31 | * @param {string} [filepath] - The optional filepath. 32 | * @returns {Promise} A promise that resolves with the formatted Markdown string. 33 | */ 34 | export async function formatMarkdown(value, filepath) { 35 | const fp = filepath ? { filepath } : {}; 36 | return format(value, { 37 | semi: false, 38 | parser: 'markdown', 39 | embeddedLanguageFormatting: 'auto', 40 | ...fp, 41 | }); 42 | } 43 | /** 44 | * Wraps a description text with a prefix and formats it using `prettier`. 45 | * @param {string | undefined} value - The description text to wrap and format. 46 | * @param {string[]} content - The array of content to update. 47 | * @param {string} [prefix=' # '] - The optional prefix to wrap the description lines. 48 | * @returns {Promise} A promise that resolves with the updated content array. 49 | */ 50 | export async function wrapDescription(value, content, prefix = ' # ') { 51 | if (!value) 52 | return content ?? []; 53 | // const valueWithoutPrefix = prefix && prefix.length > 0 ? value.replace(prefix, '') : value; 54 | let formattedString = ''; 55 | try { 56 | formattedString = await format(value, { 57 | semi: false, 58 | parser: 'markdown', 59 | proseWrap: 'always', 60 | }); 61 | } 62 | catch (error) { 63 | log.error(`${error}`); 64 | } 65 | content.push(...formattedString.split('\n').map((line) => prefix + line.replace(prefix, ''))); 66 | return content; 67 | } 68 | //# sourceMappingURL=prettier.js.map -------------------------------------------------------------------------------- /src/prettier.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code exports three functions: `formatYaml`, `formatMarkdown`, and `wrapDescription`. 3 | * 4 | * - `formatYaml` takes a YAML string and an optional filepath as parameters and uses the `prettier` library to format the YAML code. It returns the formatted YAML string. 5 | * - `formatMarkdown` takes a Markdown string and an optional filepath as parameters and uses the `prettier` library to format the Markdown code. It returns the formatted Markdown string. 6 | * - `wrapDescription` takes a string value, an array of content, and an optional prefix as parameters. It wraps the description text with the specified prefix and formats it using `prettier`. It returns the updated content array with the formatted description lines. 7 | * 8 | * The code utilizes the `prettier` library for code formatting and the `LogTask` class for logging purposes. 9 | */ 10 | 11 | import { format } from 'prettier'; 12 | 13 | import LogTask from './logtask/index.js'; 14 | 15 | const log = new LogTask('prettier'); 16 | 17 | /** 18 | * Formats a YAML string using `prettier`. 19 | * @param {string} value - The YAML string to format. 20 | * @param {string} [filepath] - The optional filepath. 21 | * @returns {Promise} A promise that resolves with the formatted YAML string. 22 | */ 23 | export async function formatYaml(value: string, filepath?: string): Promise { 24 | const fp = filepath ? { filepath } : {}; 25 | return format(value, { 26 | semi: false, 27 | parser: 'yaml', 28 | embeddedLanguageFormatting: 'auto', 29 | ...fp, 30 | }); 31 | } 32 | 33 | /** 34 | * Formats a Markdown string using `prettier`. 35 | * @param {string} value - The Markdown string to format. 36 | * @param {string} [filepath] - The optional filepath. 37 | * @returns {Promise} A promise that resolves with the formatted Markdown string. 38 | */ 39 | export async function formatMarkdown(value: string, filepath?: string): Promise { 40 | const fp = filepath ? { filepath } : {}; 41 | return format(value, { 42 | semi: false, 43 | parser: 'markdown', 44 | embeddedLanguageFormatting: 'auto', 45 | ...fp, 46 | }); 47 | } 48 | 49 | /** 50 | * Wraps a description text with a prefix and formats it using `prettier`. 51 | * @param {string | undefined} value - The description text to wrap and format. 52 | * @param {string[]} content - The array of content to update. 53 | * @param {string} [prefix=' # '] - The optional prefix to wrap the description lines. 54 | * @returns {Promise} A promise that resolves with the updated content array. 55 | */ 56 | export async function wrapDescription( 57 | value: string | undefined, 58 | content: string[], 59 | prefix = ' # ', 60 | ): Promise { 61 | if (!value) return content ?? []; 62 | // const valueWithoutPrefix = prefix && prefix.length > 0 ? value.replace(prefix, '') : value; 63 | let formattedString = ''; 64 | try { 65 | formattedString = await format(value, { 66 | semi: false, 67 | parser: 'markdown', 68 | proseWrap: 'always', 69 | }); 70 | } catch (error) { 71 | log.error(`${error}`); 72 | } 73 | 74 | content.push(...formattedString.split('\n').map((line) => prefix + line.replace(prefix, ''))); 75 | return content; 76 | } 77 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: NPM Release Workflow 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | ref: 7 | description: "The branch or tag ref to checkout. Defaults to the default branch." 8 | required: false 9 | default: ${{ github.ref }} 10 | type: string 11 | sha: 12 | description: "The commit SHA to checkout. Defaults to the SHA of the ref." 13 | required: false 14 | default: ${{ github.sha }} 15 | type: string 16 | repository: 17 | description: "The repository to checkout. Defaults to the current repository." 18 | required: false 19 | default: ${{ github.repository }} 20 | type: string 21 | token: 22 | description: "The token to use for authentication. Defaults to the token of the current workflow run." 23 | required: false 24 | default: ${{ github.token }} 25 | type: string 26 | secrets: 27 | RELEASE_TOKEN: 28 | required: true 29 | repository_dispatch: 30 | types: [semantic-release] 31 | 32 | concurrency: 33 | group: ci-${{ inputs.sha }} 34 | cancel-in-progress: true 35 | 36 | permissions: 37 | actions: write 38 | contents: write # to be able to publish a GitHub release 39 | issues: write # to be able to comment on released issues 40 | pull-requests: write # to be able to comment on released pull requests 41 | id-token: write # to enable use of OIDC for npm provenance 42 | 43 | jobs: 44 | deploy: 45 | name: Deploy NPM build 46 | runs-on: ubuntu-latest 47 | env: 48 | GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} 49 | GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} 50 | SKIP_PREFLIGHT_CHECK: true 51 | steps: 52 | - uses: actions/checkout@v6.0.1 53 | with: 54 | ref: ${{ inputs.sha }} 55 | token: ${{ secrets.RELEASE_TOKEN }} 56 | 57 | - name: Install compatible Nodejs version 58 | id: setup-node 59 | uses: ./.github/actions/setup-node 60 | 61 | - name: Configure PATH 62 | run: | 63 | mkdir -p "$HOME/.local/bin" 64 | echo "$HOME/.local/bin" >> "${GITHUB_PATH}" 65 | echo "HOME=$HOME" >> "${GITHUB_ENV}" 66 | 67 | - name: Configure Git 68 | run: | 69 | git config --global user.email "${{ github.event.pusher.email || 'stack@bitflight.io' }}" 70 | git config --global user.name "${{ github.event.pusher.name || 'GitHub[bot]' }}" 71 | git fetch --tags 72 | git status --porcelain -u 73 | 74 | - name: Install Deps 75 | id: deps 76 | run: | 77 | npm ci 78 | 79 | - name: Ensure dependencies are compatible with the version of node 80 | run: npx --yes ls-engines 81 | 82 | - name: Verify the integrity of provenance attestations and registry signatures for installed dependencies 83 | run: npm audit signatures 84 | 85 | - run: npm run build --if-present 86 | 87 | - run: | 88 | git add -f dist 89 | npm run generate-docs 90 | git commit -n -m 'build(release): bundle distribution files' 91 | 92 | - name: Setup Node 24 for semantic-release 93 | uses: actions/setup-node@v6.1.0 94 | with: 95 | node-version: "24.x" 96 | 97 | - run: npx --yes semantic-release@latest 98 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We release patches for security vulnerabilities for the following versions: 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 1.8.x | :white_check_mark: | 10 | | 1.7.x | :white_check_mark: | 11 | | < 1.7 | :x: | 12 | 13 | ## Reporting a Vulnerability 14 | 15 | We take the security of our GitHub Action seriously. If you believe you have found a security vulnerability, please report it to us as described below. 16 | 17 | ### How to Report a Security Vulnerability 18 | 19 | **Please do not report security vulnerabilities through public GitHub issues.** 20 | 21 | Instead, please report them via one of the following methods: 22 | 23 | 1. **Email**: Send an email to [jamie@bitflight.io](mailto:jamie@bitflight.io) with the subject line "SECURITY: GitHub Action Readme Generator" 24 | 2. **GitHub Security Advisories**: Use [GitHub's security advisory feature](https://github.com/bitflight-devops/github-action-readme-generator/security/advisories/new) to privately report vulnerabilities 25 | 26 | ### What to Include in Your Report 27 | 28 | Please include the following information in your report: 29 | 30 | - Type of issue (e.g., buffer overflow, SQL injection, cross-site scripting, etc.) 31 | - Full paths of source file(s) related to the manifestation of the issue 32 | - The location of the affected source code (tag/branch/commit or direct URL) 33 | - Any special configuration required to reproduce the issue 34 | - Step-by-step instructions to reproduce the issue 35 | - Proof-of-concept or exploit code (if possible) 36 | - Impact of the issue, including how an attacker might exploit it 37 | 38 | ### Response Timeline 39 | 40 | - We will acknowledge receipt of your vulnerability report within 3 business days 41 | - We will send you a more detailed response within 7 days indicating the next steps 42 | - We will keep you informed about the progress towards a fix and full announcement 43 | - We may ask for additional information or guidance during our investigation 44 | 45 | ### Disclosure Policy 46 | 47 | - We will credit you in the release notes when the vulnerability is fixed (unless you prefer to remain anonymous) 48 | - We ask that you do not publicly disclose the vulnerability until we have had a chance to address it 49 | - We will make a public disclosure once a fix is available, typically within 90 days of the initial report 50 | 51 | ## Security Best Practices for Users 52 | 53 | When using this GitHub Action: 54 | 55 | 1. **Pin to a specific version**: Always use a specific version tag (e.g., `v1.8.0`) rather than `@main` or `@latest` in production workflows 56 | 2. **Review permissions**: Ensure your workflow only grants the minimum necessary permissions 57 | 3. **Audit dependencies**: Regularly check for security advisories affecting this action's dependencies 58 | 4. **Keep updated**: Stay informed about new releases and security patches 59 | 60 | ## Security Update Notifications 61 | 62 | To receive security updates: 63 | 64 | - Watch this repository for releases 65 | - Subscribe to GitHub Security Advisories for this repository 66 | - Follow our [releases page](https://github.com/bitflight-devops/github-action-readme-generator/releases) 67 | 68 | Thank you for helping to keep GitHub Action Readme Generator and our users safe! 69 | -------------------------------------------------------------------------------- /src/readme-generator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code imports various modules and defines a function named 'generateDocs'. 3 | * The function is responsible for generating documentation for the README.md file based on the provided inputs. 4 | * It iterates through each section defined in the 'inputs.sections' array and calls the 'updateSection' function to update the corresponding section in the README.md file. 5 | * If an error occurs during the update of a section, it logs the error message and stops the process. 6 | * Finally, it saves the updated README.md file and calls the 'save' function. 7 | */ 8 | 9 | import * as core from '@actions/core'; 10 | 11 | import { ReadmeSection } from './constants.js'; 12 | import Inputs from './inputs.js'; 13 | import LogTask from './logtask/index.js'; 14 | import updateSection from './sections/index.js'; 15 | 16 | export type SectionKV = Record; 17 | /** 18 | * Class for managing README generation. 19 | */ 20 | export class ReadmeGenerator { 21 | /** 22 | * The Inputs instance. 23 | */ 24 | private inputs: Inputs; 25 | 26 | /** 27 | * The Logger instance. 28 | */ 29 | private log: LogTask; 30 | 31 | /** 32 | * Initializes the ReadmeGenerator. 33 | * 34 | * @param inputs - The Inputs instance 35 | * @param log - The Logger instance 36 | */ 37 | constructor(inputs: Inputs, log: LogTask) { 38 | this.inputs = inputs; 39 | this.log = log; 40 | } 41 | 42 | /** 43 | * Updates the README sections. 44 | * 45 | * @param sections - The sections array 46 | * @returns Promise array of section KV objects 47 | */ 48 | updateSections(sections: ReadmeSection[]): Promise[] { 49 | const promises: Promise[] = []; 50 | 51 | for (const section of sections) { 52 | promises.push(updateSection(section, this.inputs)); 53 | } 54 | 55 | return promises; 56 | } 57 | 58 | /** 59 | * Resolves the section update promises. 60 | * 61 | * @param promises - The promise array 62 | * @returns Promise resolving to combined sections KV 63 | */ 64 | async resolveUpdates(promises: Promise[]): Promise { 65 | this.log.debug('Resolving updates'); 66 | const results = await Promise.all(promises); 67 | const sections: SectionKV = {}; 68 | 69 | for (const result of results) { 70 | Object.assign(sections, result); 71 | } 72 | 73 | return sections; 74 | } 75 | 76 | /** 77 | * Outputs the sections KV to GitHub output. 78 | * 79 | * @param sections - The sections KV 80 | */ 81 | outputSections(sections: SectionKV): void { 82 | if (process.env.GITHUB_ACTIONS) { 83 | this.log.debug('Outputting sections'); 84 | core.setOutput('sections', JSON.stringify(sections, null, 2)); 85 | } else { 86 | this.log.debug('Not outputting sections'); 87 | } 88 | } 89 | 90 | /** 91 | * Generates the README documentation. 92 | * 93 | * @returns Promise resolving when done 94 | */ 95 | async generate(providedSections: ReadmeSection[] = this.inputs.sections): Promise { 96 | const sectionPromises = this.updateSections(providedSections); 97 | const sections = await this.resolveUpdates(sectionPromises); 98 | 99 | this.outputSections(sections); 100 | 101 | return this.inputs.readmeEditor.dumpToFile(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /dist/mjs/sections/update-badges.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code imports necessary modules and defines a function named 'updateBadges' which takes a sectionToken (ReadmeSection) and an instance of the 'Inputs' class as its parameters. 3 | * The function is responsible for updating the badges section in the README.md file based on the provided inputs. 4 | * It utilizes the 'LogTask' class for logging purposes. 5 | */ 6 | import LogTask from '../logtask/index.js'; 7 | /** 8 | * Generate GitHub badges. 9 | * @returns {IBadge[]} - The array of GitHub badges. 10 | */ 11 | function githubBadges(owner, repo) { 12 | const repoUrl = `https://github.com/${owner}/${repo}`; 13 | return [ 14 | { 15 | img: `https://img.shields.io/github/v/release/${owner}/${repo}?display_name=tag&sort=semver&logo=github&style=flat-square`, 16 | alt: 'Release by tag', 17 | url: `${repoUrl}/releases/latest`, 18 | }, 19 | { 20 | img: `https://img.shields.io/github/release-date/${owner}/${repo}?display_name=tag&sort=semver&logo=github&style=flat-square`, 21 | alt: 'Release by date', 22 | url: `${repoUrl}/releases/latest`, 23 | }, 24 | { 25 | img: `https://img.shields.io/github/last-commit/${owner}/${repo}?logo=github&style=flat-square`, 26 | alt: 'Commit', 27 | }, 28 | { 29 | img: `https://img.shields.io/github/issues/${owner}/${repo}?logo=github&style=flat-square`, 30 | alt: 'Open Issues', 31 | url: `${repoUrl}/issues`, 32 | }, 33 | { 34 | img: `https://img.shields.io/github/downloads/${owner}/${repo}/total?logo=github&style=flat-square`, 35 | alt: 'Downloads', 36 | }, 37 | ]; 38 | } 39 | /** 40 | * Generates a badge HTML markup. 41 | * @param {IBadge} item - The badge object. 42 | * @returns {string} - The HTML markup for the badge. 43 | */ 44 | function generateBadge(item, log) { 45 | const badgeTemplate = `${encodeURIComponent(item.alt) || ''}`; 46 | log.info(`Generating badge ${item.alt}`); 47 | if (item.url) { 48 | return `${badgeTemplate}`; 49 | } 50 | return badgeTemplate; 51 | } 52 | /** 53 | * Generates all badges HTML markup. 54 | * @param {IBadge} badges - The array of badge objects 55 | * @param log - A LogTask instance 56 | * @returns {string[]} - The array of HTML markup for all badges. 57 | */ 58 | function generateBadges(badges, log) { 59 | const badgeArray = []; 60 | for (const b of badges) { 61 | badgeArray.push(generateBadge(b, log)); 62 | } 63 | log.debug(`Total badges: ${badgeArray.length}`); 64 | return badgeArray; 65 | } 66 | export default function updateBadges(sectionToken, inputs) { 67 | const log = new LogTask(sectionToken); 68 | const config = inputs.config.get(); 69 | const enableVersioning = config ? config.versioning?.badge : false; 70 | log.info(`Versioning badge: ${enableVersioning}`); 71 | log.start(); 72 | let content = ''; 73 | // Add GitHub badges 74 | if (enableVersioning) { 75 | const badges = githubBadges(inputs.owner, inputs.repo); 76 | content = generateBadges(badges, log).join(''); 77 | inputs.readmeEditor.updateSection(sectionToken, content); 78 | } 79 | log.success(); 80 | const ret = {}; 81 | ret[sectionToken] = content; 82 | return ret; 83 | } 84 | //# sourceMappingURL=update-badges.js.map -------------------------------------------------------------------------------- /dist/mjs/constants.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the Feather icon names. 3 | */ 4 | import type { FeatherIconNames } from 'feather-icons'; 5 | /** 6 | * Represents the sections of the README. 7 | */ 8 | export declare const README_SECTIONS: readonly ["title", "branding", "description", "usage", "inputs", "outputs", "contents", "badges"]; 9 | /** 10 | * Represents a single section of the README. 11 | */ 12 | export type ReadmeSection = (typeof README_SECTIONS)[number]; 13 | /** 14 | * Represents the file name for the configuration file. 15 | */ 16 | export declare const configFileName = ".ghadocs.json"; 17 | /** 18 | * Enumerates the keys for the configuration options. 19 | */ 20 | export declare enum ConfigKeys { 21 | Owner = "owner", 22 | Repo = "repo", 23 | TitlePrefix = "title_prefix", 24 | Prettier = "prettier", 25 | Save = "save", 26 | pathsAction = "paths:action", 27 | pathsReadme = "paths:readme", 28 | BrandingSvgPath = "branding_svg_path", 29 | BrandingAsTitlePrefix = "branding_as_title_prefix", 30 | VersioningEnabled = "versioning:enabled", 31 | VersioningOverride = "versioning:override", 32 | VersioningPrefix = "versioning:prefix", 33 | VersioningBranch = "versioning:branch", 34 | IncludeGithubVersionBadge = "versioning:badge", 35 | DebugNconf = "debug:nconf", 36 | DebugReadme = "debug:readme", 37 | DebugConfig = "debug:config", 38 | DebugAction = "debug:action", 39 | DebugGithub = "debug:github" 40 | } 41 | /** 42 | * Represents the edge length (in pixels) for the branding square. 43 | */ 44 | export declare const brandingSquareEdgeLengthInPixels = 50; 45 | /** 46 | * Represents the default brand color. 47 | */ 48 | export declare const DEFAULT_BRAND_COLOR = "blue"; 49 | /** 50 | * Represents the default brand icon. 51 | */ 52 | export declare const DEFAULT_BRAND_ICON = "activity"; 53 | /** 54 | * Represents the markup for center alignment. 55 | */ 56 | export declare const ALIGNMENT_MARKUP = "
"; 57 | /** 58 | * Represents the set of icons that are omitted in GitHub Actions branding. 59 | */ 60 | export declare const GITHUB_ACTIONS_OMITTED_ICONS: Set; 61 | /** 62 | * Represents the set of icons available for GitHub Actions branding. 63 | */ 64 | export declare const GITHUB_ACTIONS_BRANDING_ICONS: Set; 65 | /** 66 | * Represents the available colors for GitHub Actions branding. 67 | */ 68 | export declare const GITHUB_ACTIONS_BRANDING_COLORS: readonly ["white", "yellow", "blue", "green", "orange", "red", "purple", "gray-dark"]; 69 | /** 70 | * Represents the available brand colors. 71 | */ 72 | export type BrandColors = (typeof GITHUB_ACTIONS_BRANDING_COLORS)[number]; 73 | /** 74 | * Checks if the given icon is valid for GitHub Actions branding. 75 | * @param {Partial} icon - The icon to validate. 76 | * @returns A boolean indicating if the icon is valid. 77 | */ 78 | export declare function isValidIcon(icon: Partial): icon is FeatherIconNames; 79 | /** 80 | * Checks if the given color is valid for GitHub Actions branding. 81 | * @param {Partial} color - The color to validate. 82 | * @returns A boolean indicating if the color is valid. 83 | */ 84 | export declare function isValidColor(color: Partial): color is BrandColors; 85 | /** 86 | * Represents the branding information for the action. 87 | */ 88 | export interface Branding { 89 | /** Color for the action branding */ 90 | color: Partial; 91 | icon: Partial; 92 | } 93 | -------------------------------------------------------------------------------- /dist/mjs/sections/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/sections/index.ts"],"names":[],"mappings":"AAUA,OAAO,OAAO,MAAM,qBAAqB,CAAC;AAC1C,OAAO,YAAY,MAAM,oBAAoB,CAAC;AAC9C,OAAO,cAAc,MAAM,sBAAsB,CAAC;AAClD,OAAO,iBAAiB,MAAM,yBAAyB,CAAC;AACxD,OAAO,YAAY,MAAM,oBAAoB,CAAC;AAC9C,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAChD,OAAO,WAAW,MAAM,mBAAmB,CAAC;AAC5C,OAAO,WAAW,MAAM,mBAAmB,CAAC;AAE5C,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;AAEzC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,aAAa,CACzC,OAAsB,EACtB,MAAc;IAEd,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAC7E,KAAK;IACL,6CAA6C;IAC7C,yDAAyD;IACzD,IAAI,UAAU,KAAK,CAAC,CAAC,IAAI,SAAS,KAAK,CAAC,CAAC,EAAE;QACzC,OAAO,EAAE,CAAC;KACX;IACD,QAAQ,OAAO,EAAE;QACf,KAAK,UAAU,CAAC,CAAC;YACf,OAAO,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;SACxC;QACD,KAAK,QAAQ,CAAC,CAAC;YACb,OAAO,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;SACtC;QACD,KAAK,OAAO,CAAC,CAAC;YACZ,OAAO,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;SACrC;QACD,KAAK,OAAO,CAAC,CAAC;YACZ,OAAO,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;SACrC;QACD,KAAK,aAAa,CAAC,CAAC;YAClB,OAAO,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;SAC3C;QACD,KAAK,QAAQ,CAAC,CAAC;YACb,OAAO,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;SACtC;QACD,KAAK,SAAS,CAAC,CAAC;YACd,OAAO,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;SACvC;QACD,OAAO,CAAC,CAAC;YACP,GAAG,CAAC,KAAK,CAAC,oCAAoC,OAAO,6BAA6B,CAAC,CAAC;YACpF,OAAO,EAAE,CAAC;SACX;KACF;AACH,CAAC","sourcesContent":["/**\n * This TypeScript code exports a function named 'updateSection' which takes a section (ReadmeSection) and an instance of the 'Inputs' class as its parameters.\n * The function is responsible for updating different sections of the README.md file based on the provided section input.\n * It utilizes various update functions (e.g., updateBranding, updateBadges) to update specific sections.\n * @param {ReadmeSection} section - The section of the README to update.\n * @param {Inputs} inputs - The Inputs class instance.\n * @returns {Promise} A promise that resolves once the section is updated.\n */\nimport { ReadmeSection } from '../constants.js';\nimport type Inputs from '../inputs.js';\nimport LogTask from '../logtask/index.js';\nimport updateBadges from './update-badges.js';\nimport updateBranding from './update-branding.js';\nimport updateDescription from './update-description.js';\nimport updateInputs from './update-inputs.js';\nimport updateOutputs from './update-outputs.js';\nimport updateTitle from './update-title.js';\nimport updateUsage from './update-usage.js';\n\nconst log = new LogTask('updateSection');\n\nexport default async function updateSection(\n section: ReadmeSection,\n inputs: Inputs,\n): Promise> {\n const [startToken, stopToken] = inputs.readmeEditor.getTokenIndexes(section);\n // &&\n // ['branding', 'title'].includes(section) &&\n // inputs.config.get('branding_as_title_prefix') !== true\n if (startToken === -1 || stopToken === -1) {\n return {};\n }\n switch (section) {\n case 'branding': {\n return updateBranding(section, inputs);\n }\n case 'badges': {\n return updateBadges(section, inputs);\n }\n case 'usage': {\n return updateUsage(section, inputs);\n }\n case 'title': {\n return updateTitle(section, inputs);\n }\n case 'description': {\n return updateDescription(section, inputs);\n }\n case 'inputs': {\n return updateInputs(section, inputs);\n }\n case 'outputs': {\n return updateOutputs(section, inputs);\n }\n default: {\n log.debug(`unknown section found . No updates were made.`);\n return {};\n }\n }\n}\n"]} -------------------------------------------------------------------------------- /src/sections/update-badges.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code imports necessary modules and defines a function named 'updateBadges' which takes a sectionToken (ReadmeSection) and an instance of the 'Inputs' class as its parameters. 3 | * The function is responsible for updating the badges section in the README.md file based on the provided inputs. 4 | * It utilizes the 'LogTask' class for logging purposes. 5 | */ 6 | 7 | import { ReadmeSection } from '../constants.js'; 8 | import type Inputs from '../inputs.js'; 9 | import LogTask from '../logtask/index.js'; 10 | 11 | /** 12 | * Interface for a badge. 13 | */ 14 | export interface IBadge { 15 | alt: string; 16 | img: string; 17 | url?: string; 18 | } 19 | 20 | /** 21 | * Generate GitHub badges. 22 | * @returns {IBadge[]} - The array of GitHub badges. 23 | */ 24 | function githubBadges(owner: string, repo: string): IBadge[] { 25 | const repoUrl = `https://github.com/${owner}/${repo}`; 26 | return [ 27 | { 28 | img: `https://img.shields.io/github/v/release/${owner}/${repo}?display_name=tag&sort=semver&logo=github&style=flat-square`, 29 | alt: 'Release by tag', 30 | url: `${repoUrl}/releases/latest`, 31 | }, 32 | { 33 | img: `https://img.shields.io/github/release-date/${owner}/${repo}?display_name=tag&sort=semver&logo=github&style=flat-square`, 34 | alt: 'Release by date', 35 | url: `${repoUrl}/releases/latest`, 36 | }, 37 | { 38 | img: `https://img.shields.io/github/last-commit/${owner}/${repo}?logo=github&style=flat-square`, 39 | alt: 'Commit', 40 | }, 41 | { 42 | img: `https://img.shields.io/github/issues/${owner}/${repo}?logo=github&style=flat-square`, 43 | alt: 'Open Issues', 44 | url: `${repoUrl}/issues`, 45 | }, 46 | { 47 | img: `https://img.shields.io/github/downloads/${owner}/${repo}/total?logo=github&style=flat-square`, 48 | alt: 'Downloads', 49 | }, 50 | ]; 51 | } 52 | 53 | /** 54 | * Generates a badge HTML markup. 55 | * @param {IBadge} item - The badge object. 56 | * @returns {string} - The HTML markup for the badge. 57 | */ 58 | function generateBadge(item: IBadge, log: LogTask): string { 59 | const badgeTemplate = `${encodeURIComponent(item.alt) || ''}`; 60 | log.info(`Generating badge ${item.alt}`); 61 | if (item.url) { 62 | return `${badgeTemplate}`; 63 | } 64 | return badgeTemplate; 65 | } 66 | 67 | /** 68 | * Generates all badges HTML markup. 69 | * @param {IBadge} badges - The array of badge objects 70 | * @param log - A LogTask instance 71 | * @returns {string[]} - The array of HTML markup for all badges. 72 | */ 73 | function generateBadges(badges: IBadge[], log: LogTask): string[] { 74 | const badgeArray: string[] = []; 75 | for (const b of badges) { 76 | badgeArray.push(generateBadge(b, log)); 77 | } 78 | log.debug(`Total badges: ${badgeArray.length}`); 79 | return badgeArray; 80 | } 81 | export default function updateBadges( 82 | sectionToken: ReadmeSection, 83 | inputs: Inputs, 84 | ): Record { 85 | const log = new LogTask(sectionToken); 86 | const config = inputs.config.get(); 87 | 88 | const enableVersioning = config ? config.versioning?.badge : false; 89 | log.info(`Versioning badge: ${enableVersioning}`); 90 | 91 | log.start(); 92 | let content = ''; 93 | // Add GitHub badges 94 | if (enableVersioning) { 95 | const badges: IBadge[] = githubBadges(inputs.owner, inputs.repo); 96 | content = generateBadges(badges, log).join(''); 97 | inputs.readmeEditor.updateSection(sectionToken, content); 98 | } 99 | log.success(); 100 | const ret: Record = {}; 101 | ret[sectionToken] = content; 102 | return ret; 103 | } 104 | -------------------------------------------------------------------------------- /dist/mjs/constants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the icons object from 'feather-icons' library. 3 | */ 4 | import { icons } from 'feather-icons'; 5 | /** 6 | * Represents the sections of the README. 7 | */ 8 | export const README_SECTIONS = [ 9 | 'title', 10 | 'branding', 11 | 'description', 12 | 'usage', 13 | 'inputs', 14 | 'outputs', 15 | 'contents', 16 | 'badges', 17 | ]; 18 | /** 19 | * Represents the file name for the configuration file. 20 | */ 21 | export const configFileName = '.ghadocs.json'; 22 | /** 23 | * Enumerates the keys for the configuration options. 24 | */ 25 | export var ConfigKeys; 26 | (function (ConfigKeys) { 27 | ConfigKeys["Owner"] = "owner"; 28 | ConfigKeys["Repo"] = "repo"; 29 | ConfigKeys["TitlePrefix"] = "title_prefix"; 30 | ConfigKeys["Prettier"] = "prettier"; 31 | ConfigKeys["Save"] = "save"; 32 | ConfigKeys["pathsAction"] = "paths:action"; 33 | ConfigKeys["pathsReadme"] = "paths:readme"; 34 | ConfigKeys["BrandingSvgPath"] = "branding_svg_path"; 35 | ConfigKeys["BrandingAsTitlePrefix"] = "branding_as_title_prefix"; 36 | ConfigKeys["VersioningEnabled"] = "versioning:enabled"; 37 | ConfigKeys["VersioningOverride"] = "versioning:override"; 38 | ConfigKeys["VersioningPrefix"] = "versioning:prefix"; 39 | ConfigKeys["VersioningBranch"] = "versioning:branch"; 40 | ConfigKeys["IncludeGithubVersionBadge"] = "versioning:badge"; 41 | ConfigKeys["DebugNconf"] = "debug:nconf"; 42 | ConfigKeys["DebugReadme"] = "debug:readme"; 43 | ConfigKeys["DebugConfig"] = "debug:config"; 44 | ConfigKeys["DebugAction"] = "debug:action"; 45 | ConfigKeys["DebugGithub"] = "debug:github"; 46 | })(ConfigKeys || (ConfigKeys = {})); 47 | /** 48 | * Represents the edge length (in pixels) for the branding square. 49 | */ 50 | export const brandingSquareEdgeLengthInPixels = 50; 51 | /** 52 | * Represents the default brand color. 53 | */ 54 | export const DEFAULT_BRAND_COLOR = 'blue'; 55 | /** 56 | * Represents the default brand icon. 57 | */ 58 | export const DEFAULT_BRAND_ICON = 'activity'; 59 | /** 60 | * Represents the markup for center alignment. 61 | */ 62 | export const ALIGNMENT_MARKUP = '
'; 63 | /** 64 | * Represents the set of icons that are omitted in GitHub Actions branding. 65 | */ 66 | export const GITHUB_ACTIONS_OMITTED_ICONS = new Set([ 67 | 'coffee', 68 | 'columns', 69 | 'divide-circle', 70 | 'divide-square', 71 | 'divide', 72 | 'frown', 73 | 'hexagon', 74 | 'key', 75 | 'meh', 76 | 'mouse-pointer', 77 | 'smile', 78 | 'tool', 79 | 'x-octagon', 80 | ]); 81 | /** 82 | * Represents the set of icons available for GitHub Actions branding. 83 | */ 84 | export const GITHUB_ACTIONS_BRANDING_ICONS = new Set(Object.keys(icons).filter((item) => !GITHUB_ACTIONS_OMITTED_ICONS.has(item))); 85 | /** 86 | * Represents the available colors for GitHub Actions branding. 87 | */ 88 | export const GITHUB_ACTIONS_BRANDING_COLORS = [ 89 | 'white', 90 | 'yellow', 91 | 'blue', 92 | 'green', 93 | 'orange', 94 | 'red', 95 | 'purple', 96 | 'gray-dark', 97 | ]; 98 | /** 99 | * Checks if the given icon is valid for GitHub Actions branding. 100 | * @param {Partial} icon - The icon to validate. 101 | * @returns A boolean indicating if the icon is valid. 102 | */ 103 | export function isValidIcon(icon) { 104 | return GITHUB_ACTIONS_BRANDING_ICONS.has(icon); 105 | } 106 | /** 107 | * Checks if the given color is valid for GitHub Actions branding. 108 | * @param {Partial} color - The color to validate. 109 | * @returns A boolean indicating if the color is valid. 110 | */ 111 | export function isValidColor(color) { 112 | return GITHUB_ACTIONS_BRANDING_COLORS.includes(color); 113 | } 114 | //# sourceMappingURL=constants.js.map -------------------------------------------------------------------------------- /dist/mjs/markdowner/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Fills a string to a desired width by padding with spaces. 3 | * 4 | * @param text - The text to pad. 5 | * @param width - The desired total width. 6 | * @param paddingStart - Number of spaces to pad at the start. 7 | * @returns The padded string. 8 | */ 9 | export function padString(text, width, paddingStart) { 10 | return ' '.repeat(paddingStart) + text.padEnd(width); 11 | } 12 | /** 13 | * Escapes special Markdown characters in a string. 14 | * 15 | * @param text - The text to escape. 16 | * @returns The escaped text. 17 | */ 18 | export function markdownEscapeTableCell(text) { 19 | return text.replaceAll('\n', '
').replaceAll('|', '\\|'); 20 | } 21 | /** 22 | * Escapes inline code blocks in a Markdown string. 23 | * 24 | * @param content - Markdown string. 25 | * @returns String with escaped inline code blocks. 26 | */ 27 | export function markdownEscapeInlineCode(content) { 28 | return content.replaceAll(/`([^`]*)`/g, '$1').replaceAll('>'; 16 | /** 17 | * The format for the end token of a section. 18 | */ 19 | export const endTokenFormat = '(^|[^`\\\\])'; 20 | export default class ReadmeEditor { 21 | log = new LogTask('ReadmeEditor'); 22 | /** 23 | * The path to the README file. 24 | */ 25 | filePath; 26 | fileContent; 27 | /** 28 | * Creates a new instance of `ReadmeEditor`. 29 | * @param {string} filePath - The path to the README file. 30 | */ 31 | constructor(filePath) { 32 | this.filePath = filePath; 33 | try { 34 | fs.accessSync(filePath); 35 | this.fileContent = fs.readFileSync(filePath, 'utf8'); 36 | if (process.env.GITHUB_ACTIONS) { 37 | core.setOutput('readme_before', this.fileContent); 38 | } 39 | } 40 | catch (error) { 41 | this.log.fail(`Readme at '${filePath}' does not exist.`); 42 | throw error; 43 | } 44 | } 45 | /** 46 | * Gets the indexes of the start and end tokens for a given section. 47 | * @param {string} token - The section token. 48 | * @returns {number[]} - The indexes of the start and end tokens. 49 | */ 50 | getTokenIndexes(token, logTask) { 51 | const log = logTask ?? new LogTask('getTokenIndexes'); 52 | const startRegExp = new RegExp(startTokenFormat.replace('%s', token)); 53 | const stopRegExp = new RegExp(endTokenFormat.replace('%s', token)); 54 | const startIndex = lastIndexOfRegex(this.fileContent, startRegExp); 55 | if (startIndex === -1) { 56 | log.debug(`No start token found for section '${token}'. Skipping`); 57 | return []; 58 | } 59 | const stopIndex = indexOfRegex(this.fileContent, stopRegExp); 60 | if (stopIndex === -1) { 61 | log.debug(`No start or end token found for section '${token}'. Skipping`); 62 | return []; 63 | } 64 | return [startIndex, stopIndex]; 65 | } 66 | /** 67 | * Updates a specific section in the README file with the provided content. 68 | * @param {string} name - The name of the section. 69 | * @param {string | string[]} providedContent - The content to update the section with. 70 | * @param {boolean} addNewlines - Whether to add newlines before and after the content. 71 | */ 72 | updateSection(name, providedContent, addNewlines = true) { 73 | const log = new LogTask(name); 74 | const content = (Array.isArray(providedContent) ? providedContent.join(EOL) : providedContent ?? '').trim(); 75 | log.info(`Looking for the ${name} token in ${this.filePath}`); 76 | const [startIndex, stopIndex] = this.getTokenIndexes(name, log); 77 | if (startIndex && stopIndex) { 78 | const beforeContent = this.fileContent.slice(0, startIndex); 79 | const afterContent = this.fileContent.slice(stopIndex); 80 | this.fileContent = addNewlines 81 | ? `${beforeContent}\n\n${content}\n${afterContent}` 82 | : `${beforeContent}${content}${afterContent}`; 83 | } 84 | } 85 | /** 86 | * Dumps the modified content back to the README file. 87 | * @returns {Promise} 88 | */ 89 | async dumpToFile() { 90 | const content = await formatMarkdown(this.fileContent); 91 | if (process.env.GITHUB_ACTIONS) { 92 | core.setOutput('readme_after', content); 93 | } 94 | return fs.promises.writeFile(this.filePath, content, 'utf8'); 95 | } 96 | } 97 | //# sourceMappingURL=readme-editor.js.map -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the Feather icon names. 3 | */ 4 | import type { FeatherIconNames } from 'feather-icons'; 5 | /** 6 | * Represents the icons object from 'feather-icons' library. 7 | */ 8 | import { icons } from 'feather-icons'; 9 | 10 | /** 11 | * Represents the sections of the README. 12 | */ 13 | export const README_SECTIONS = [ 14 | 'title', 15 | 'branding', 16 | 'description', 17 | 'usage', 18 | 'inputs', 19 | 'outputs', 20 | 'contents', 21 | 'badges', 22 | ] as const; 23 | 24 | /** 25 | * Represents a single section of the README. 26 | */ 27 | export type ReadmeSection = (typeof README_SECTIONS)[number]; 28 | 29 | /** 30 | * Represents the file name for the configuration file. 31 | */ 32 | export const configFileName = '.ghadocs.json'; 33 | 34 | /** 35 | * Enumerates the keys for the configuration options. 36 | */ 37 | export enum ConfigKeys { 38 | Owner = 'owner', 39 | Repo = 'repo', 40 | TitlePrefix = 'title_prefix', 41 | Prettier = 'prettier', 42 | Save = 'save', 43 | pathsAction = 'paths:action', 44 | pathsReadme = 'paths:readme', 45 | BrandingSvgPath = 'branding_svg_path', 46 | BrandingAsTitlePrefix = 'branding_as_title_prefix', 47 | VersioningEnabled = 'versioning:enabled', 48 | VersioningOverride = 'versioning:override', 49 | VersioningPrefix = 'versioning:prefix', 50 | VersioningBranch = 'versioning:branch', 51 | IncludeGithubVersionBadge = 'versioning:badge', 52 | DebugNconf = 'debug:nconf', 53 | DebugReadme = 'debug:readme', 54 | DebugConfig = 'debug:config', 55 | DebugAction = 'debug:action', 56 | DebugGithub = 'debug:github', 57 | } 58 | 59 | /** 60 | * Represents the edge length (in pixels) for the branding square. 61 | */ 62 | export const brandingSquareEdgeLengthInPixels = 50; 63 | 64 | /** 65 | * Represents the default brand color. 66 | */ 67 | export const DEFAULT_BRAND_COLOR = 'blue'; 68 | 69 | /** 70 | * Represents the default brand icon. 71 | */ 72 | export const DEFAULT_BRAND_ICON = 'activity'; 73 | 74 | /** 75 | * Represents the markup for center alignment. 76 | */ 77 | export const ALIGNMENT_MARKUP = '
'; 78 | 79 | /** 80 | * Represents the set of icons that are omitted in GitHub Actions branding. 81 | */ 82 | export const GITHUB_ACTIONS_OMITTED_ICONS = new Set([ 83 | 'coffee', 84 | 'columns', 85 | 'divide-circle', 86 | 'divide-square', 87 | 'divide', 88 | 'frown', 89 | 'hexagon', 90 | 'key', 91 | 'meh', 92 | 'mouse-pointer', 93 | 'smile', 94 | 'tool', 95 | 'x-octagon', 96 | ]); 97 | 98 | /** 99 | * Represents the set of icons available for GitHub Actions branding. 100 | */ 101 | export const GITHUB_ACTIONS_BRANDING_ICONS = new Set( 102 | Object.keys(icons).filter((item) => !GITHUB_ACTIONS_OMITTED_ICONS.has(item)), 103 | ); 104 | 105 | /** 106 | * Represents the available colors for GitHub Actions branding. 107 | */ 108 | export const GITHUB_ACTIONS_BRANDING_COLORS = [ 109 | 'white', 110 | 'yellow', 111 | 'blue', 112 | 'green', 113 | 'orange', 114 | 'red', 115 | 'purple', 116 | 'gray-dark', 117 | ] as const; 118 | 119 | /** 120 | * Represents the available brand colors. 121 | */ 122 | export type BrandColors = (typeof GITHUB_ACTIONS_BRANDING_COLORS)[number]; 123 | 124 | /** 125 | * Checks if the given icon is valid for GitHub Actions branding. 126 | * @param {Partial} icon - The icon to validate. 127 | * @returns A boolean indicating if the icon is valid. 128 | */ 129 | export function isValidIcon(icon: Partial): icon is FeatherIconNames { 130 | return GITHUB_ACTIONS_BRANDING_ICONS.has(icon); 131 | } 132 | 133 | /** 134 | * Checks if the given color is valid for GitHub Actions branding. 135 | * @param {Partial} color - The color to validate. 136 | * @returns A boolean indicating if the color is valid. 137 | */ 138 | export function isValidColor(color: Partial): color is BrandColors { 139 | return GITHUB_ACTIONS_BRANDING_COLORS.includes(color); 140 | } 141 | 142 | /** 143 | * Represents the branding information for the action. 144 | */ 145 | export interface Branding { 146 | /** Color for the action branding */ 147 | color: Partial; 148 | icon: Partial; 149 | } 150 | -------------------------------------------------------------------------------- /dist/mjs/prettier.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"prettier.js","sourceRoot":"","sources":["../../src/prettier.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,OAAO,OAAO,MAAM,oBAAoB,CAAC;AAEzC,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;AAEpC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAa,EAAE,QAAiB;IAC/D,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACxC,OAAO,MAAM,CAAC,KAAK,EAAE;QACnB,IAAI,EAAE,KAAK;QACX,MAAM,EAAE,MAAM;QACd,0BAA0B,EAAE,MAAM;QAClC,GAAG,EAAE;KACN,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAAa,EAAE,QAAiB;IACnE,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACxC,OAAO,MAAM,CAAC,KAAK,EAAE;QACnB,IAAI,EAAE,KAAK;QACX,MAAM,EAAE,UAAU;QAClB,0BAA0B,EAAE,MAAM;QAClC,GAAG,EAAE;KACN,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,KAAyB,EACzB,OAAiB,EACjB,MAAM,GAAG,QAAQ;IAEjB,IAAI,CAAC,KAAK;QAAE,OAAO,OAAO,IAAI,EAAE,CAAC;IACjC,8FAA8F;IAC9F,IAAI,eAAe,GAAG,EAAE,CAAC;IACzB,IAAI;QACF,eAAe,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE;YACpC,IAAI,EAAE,KAAK;YACX,MAAM,EAAE,UAAU;YAClB,SAAS,EAAE,QAAQ;SACpB,CAAC,CAAC;KACJ;IAAC,OAAO,KAAK,EAAE;QACd,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC;KACvB;IAED,OAAO,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9F,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["/**\n * This TypeScript code exports three functions: `formatYaml`, `formatMarkdown`, and `wrapDescription`.\n *\n * - `formatYaml` takes a YAML string and an optional filepath as parameters and uses the `prettier` library to format the YAML code. It returns the formatted YAML string.\n * - `formatMarkdown` takes a Markdown string and an optional filepath as parameters and uses the `prettier` library to format the Markdown code. It returns the formatted Markdown string.\n * - `wrapDescription` takes a string value, an array of content, and an optional prefix as parameters. It wraps the description text with the specified prefix and formats it using `prettier`. It returns the updated content array with the formatted description lines.\n *\n * The code utilizes the `prettier` library for code formatting and the `LogTask` class for logging purposes.\n */\n\nimport { format } from 'prettier';\n\nimport LogTask from './logtask/index.js';\n\nconst log = new LogTask('prettier');\n\n/**\n * Formats a YAML string using `prettier`.\n * @param {string} value - The YAML string to format.\n * @param {string} [filepath] - The optional filepath.\n * @returns {Promise} A promise that resolves with the formatted YAML string.\n */\nexport async function formatYaml(value: string, filepath?: string): Promise {\n const fp = filepath ? { filepath } : {};\n return format(value, {\n semi: false,\n parser: 'yaml',\n embeddedLanguageFormatting: 'auto',\n ...fp,\n });\n}\n\n/**\n * Formats a Markdown string using `prettier`.\n * @param {string} value - The Markdown string to format.\n * @param {string} [filepath] - The optional filepath.\n * @returns {Promise} A promise that resolves with the formatted Markdown string.\n */\nexport async function formatMarkdown(value: string, filepath?: string): Promise {\n const fp = filepath ? { filepath } : {};\n return format(value, {\n semi: false,\n parser: 'markdown',\n embeddedLanguageFormatting: 'auto',\n ...fp,\n });\n}\n\n/**\n * Wraps a description text with a prefix and formats it using `prettier`.\n * @param {string | undefined} value - The description text to wrap and format.\n * @param {string[]} content - The array of content to update.\n * @param {string} [prefix=' # '] - The optional prefix to wrap the description lines.\n * @returns {Promise} A promise that resolves with the updated content array.\n */\nexport async function wrapDescription(\n value: string | undefined,\n content: string[],\n prefix = ' # ',\n): Promise {\n if (!value) return content ?? [];\n // const valueWithoutPrefix = prefix && prefix.length > 0 ? value.replace(prefix, '') : value;\n let formattedString = '';\n try {\n formattedString = await format(value, {\n semi: false,\n parser: 'markdown',\n proseWrap: 'always',\n });\n } catch (error) {\n log.error(`${error}`);\n }\n\n content.push(...formattedString.split('\\n').map((line) => prefix + line.replace(prefix, '')));\n return content;\n}\n"]} -------------------------------------------------------------------------------- /src/readme-editor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This TypeScript code imports the necessary modules and defines a class named `ReadmeEditor`. 3 | * The class represents an editor for modifying a README file. 4 | * It has methods to update specific sections within the file and dump the modified content back to the file. 5 | */ 6 | 7 | import * as fs from 'node:fs'; 8 | import { EOL } from 'node:os'; 9 | 10 | import * as core from '@actions/core'; 11 | 12 | import { indexOfRegex, lastIndexOfRegex } from './helpers.js'; 13 | import LogTask from './logtask/index.js'; 14 | import { formatMarkdown } from './prettier.js'; 15 | 16 | /** 17 | * The format for the start token of a section. 18 | */ 19 | 20 | export const startTokenFormat = '(^|[^`\\\\])'; 21 | 22 | /** 23 | * The format for the end token of a section. 24 | */ 25 | export const endTokenFormat = '(^|[^`\\\\])'; 26 | 27 | export default class ReadmeEditor { 28 | private log = new LogTask('ReadmeEditor'); 29 | 30 | /** 31 | * The path to the README file. 32 | */ 33 | private readonly filePath: string; 34 | 35 | private fileContent: string; 36 | 37 | /** 38 | * Creates a new instance of `ReadmeEditor`. 39 | * @param {string} filePath - The path to the README file. 40 | */ 41 | constructor(filePath: string) { 42 | this.filePath = filePath; 43 | try { 44 | fs.accessSync(filePath); 45 | this.fileContent = fs.readFileSync(filePath, 'utf8'); 46 | if (process.env.GITHUB_ACTIONS) { 47 | core.setOutput('readme_before', this.fileContent); 48 | } 49 | } catch (error) { 50 | this.log.fail(`Readme at '${filePath}' does not exist.`); 51 | throw error; 52 | } 53 | } 54 | 55 | /** 56 | * Gets the indexes of the start and end tokens for a given section. 57 | * @param {string} token - The section token. 58 | * @returns {number[]} - The indexes of the start and end tokens. 59 | */ 60 | getTokenIndexes(token: string, logTask?: LogTask): number[] { 61 | const log = logTask ?? new LogTask('getTokenIndexes'); 62 | const startRegExp = new RegExp(startTokenFormat.replace('%s', token)); 63 | const stopRegExp = new RegExp(endTokenFormat.replace('%s', token)); 64 | const startIndex = lastIndexOfRegex(this.fileContent, startRegExp); 65 | if (startIndex === -1) { 66 | log.debug(`No start token found for section '${token}'. Skipping`); 67 | return []; 68 | } 69 | 70 | const stopIndex = indexOfRegex(this.fileContent, stopRegExp); 71 | if (stopIndex === -1) { 72 | log.debug(`No start or end token found for section '${token}'. Skipping`); 73 | return []; 74 | } 75 | 76 | return [startIndex, stopIndex]; 77 | } 78 | 79 | /** 80 | * Updates a specific section in the README file with the provided content. 81 | * @param {string} name - The name of the section. 82 | * @param {string | string[]} providedContent - The content to update the section with. 83 | * @param {boolean} addNewlines - Whether to add newlines before and after the content. 84 | */ 85 | updateSection(name: string, providedContent: string | string[], addNewlines = true): void { 86 | const log = new LogTask(name); 87 | const content = ( 88 | Array.isArray(providedContent) ? providedContent.join(EOL) : providedContent ?? '' 89 | ).trim(); 90 | log.info(`Looking for the ${name} token in ${this.filePath}`); 91 | 92 | const [startIndex, stopIndex] = this.getTokenIndexes(name, log); 93 | if (startIndex && stopIndex) { 94 | const beforeContent = this.fileContent.slice(0, startIndex); 95 | const afterContent = this.fileContent.slice(stopIndex); 96 | 97 | this.fileContent = addNewlines 98 | ? `${beforeContent}\n\n${content}\n${afterContent}` 99 | : `${beforeContent}${content}${afterContent}`; 100 | } 101 | } 102 | 103 | /** 104 | * Dumps the modified content back to the README file. 105 | * @returns {Promise} 106 | */ 107 | async dumpToFile(): Promise { 108 | const content = await formatMarkdown(this.fileContent); 109 | if (process.env.GITHUB_ACTIONS) { 110 | core.setOutput('readme_after', content); 111 | } 112 | return fs.promises.writeFile(this.filePath, content, 'utf8'); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/markdowner/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Types representing a 2D array of strings for a Markdown table. 3 | */ 4 | export type MarkdownArrayRowType = string[][]; 5 | export type MarkdownArrayItemType = string; 6 | 7 | /** 8 | * Fills a string to a desired width by padding with spaces. 9 | * 10 | * @param text - The text to pad. 11 | * @param width - The desired total width. 12 | * @param paddingStart - Number of spaces to pad at the start. 13 | * @returns The padded string. 14 | */ 15 | export function padString(text: string, width: number, paddingStart: number): string { 16 | return ' '.repeat(paddingStart) + text.padEnd(width); 17 | } 18 | 19 | /** 20 | * Escapes special Markdown characters in a string. 21 | * 22 | * @param text - The text to escape. 23 | * @returns The escaped text. 24 | */ 25 | export function markdownEscapeTableCell(text: string): string { 26 | return text.replaceAll('\n', '
').replaceAll('|', '\\|'); 27 | } 28 | 29 | /** 30 | * Escapes inline code blocks in a Markdown string. 31 | * 32 | * @param content - Markdown string. 33 | * @returns String with escaped inline code blocks. 34 | */ 35 | export function markdownEscapeInlineCode(content: string): string { 36 | return content.replaceAll(/`([^`]*)`/g, '$1').replaceAll('>` 96 | 97 | or use the action input: 98 | 99 | `branding_as_title_prefix: true` 100 | 101 | to prefix the 'title' section with the image. 102 | 103 | The title template looks like this: 104 | 105 | # {brand}{prefix}{title} 106 | required: false 107 | default: .github/ghadocs/branding.svg 108 | branding_as_title_prefix: 109 | default: true 110 | type: boolean 111 | description: > 112 | Prefix the title in the `` section with the svg 113 | branding image 114 | 115 | The title template looks like this: 116 | 117 | # {brand}{prefix}{title} 118 | outputs: 119 | sections: 120 | description: | 121 | A json object containing a map of section names to their new content 122 | readme: 123 | description: | 124 | The path to the generated README.md file 125 | readme_before: 126 | description: | 127 | The content of the readme file before the changes were made 128 | readme_after: 129 | description: | 130 | The content of the readme file after the changes were made 131 | runs: 132 | using: "node20" 133 | main: "./dist/bin/index.js" 134 | --------------------------------------------------------------------------------