diff --git a/.github/workflows/build-actions.yml b/.github/workflows/build-actions.yml new file mode 100644 index 0000000..5095b21 --- /dev/null +++ b/.github/workflows/build-actions.yml @@ -0,0 +1,43 @@ +name: Build images for Docker container actions + +on: + push: + branches: [trunk] + pull_request: + branches: [trunk] + +permissions: + contents: read + packages: write + +jobs: + build_actions: + name: Build images for Docker container actions + runs-on: ubuntu-latest + strategy: + matrix: + action-dir: [gh-actions-docs] + env: + IMAGE_NAME: 'ghcr.io/3lvia/core-github-actions-templates/${{ matrix.action-dir }}:latest' + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: 'ghcr.io' + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build image + uses: docker/build-push-action@v5 + with: + context: '${{ matrix.action-dir }}' + push: true + tags: '${{ env.IMAGE_NAME }}' + cache-from: 'type=registry,ref=${{ env.IMAGE_NAME }}' + cache-to: 'type=inline' diff --git a/.github/workflows/build-deploy.yml b/.github/workflows/build-deploy.yml index 4407cd5..7b01b83 100644 --- a/.github/workflows/build-deploy.yml +++ b/.github/workflows/build-deploy.yml @@ -22,6 +22,7 @@ jobs: unittests: name: Unit Tests runs-on: ubuntu-latest + if: false # TODO: remove steps: - name: Checkout repository uses: actions/checkout@v4 @@ -31,6 +32,7 @@ jobs: analyze: name: Analyze runs-on: ubuntu-latest + if: false # TODO: remove steps: - name: Checkout repository uses: actions/checkout@v4 @@ -40,6 +42,7 @@ jobs: build: name: Build and Scan runs-on: ubuntu-latest + if: false # TODO: remove environment: build steps: - name: Checkout repository @@ -56,6 +59,7 @@ jobs: name: Deploy AKS needs: [build, analyze] runs-on: ubuntu-latest + if: false # TODO: remove environment: dev steps: - name: Checkout repository @@ -73,6 +77,7 @@ jobs: name: Deploy GKE needs: [build, analyze] runs-on: ubuntu-latest + if: false # TODO: remove environment: dev steps: - name: Checkout repository diff --git a/.github/workflows/generate-docs.yml b/.github/workflows/generate-docs.yml index f94955d..b9fd53b 100644 --- a/.github/workflows/generate-docs.yml +++ b/.github/workflows/generate-docs.yml @@ -3,13 +3,38 @@ name: Generate action documentation on: push: branches: [trunk] + pull_request: # TODO: remove + branches: [trunk] jobs: generate_docs: name: Generate action documentation runs-on: ubuntu-latest - env: - ACTION_DIRS: 'build,deploy,trivy-iac-scan,terraform-format' + outputs: + readme: ${{ steps.set-output.outputs.readme }} + container: + image: ghcr.io/3lvia/core-github-actions-templates/gh-actions-docs:latest + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Generate action documentation + run: gh-actions-docs + env: + IGNORE_FILES: 'my-new-action/action.yml' + IGNORE-HEADERS: '# core-github-actions-templates,## Table of Contents' + + - name: Set output + id: set-output + run: echo "readme=$(cat README.md)" >> $GITHUB_OUTPUT + + commit_docs: + name: Commit action documentation + runs-on: ubuntu-latest + needs: [generate_docs] permissions: contents: write steps: @@ -25,32 +50,24 @@ jobs: uses: actions/cache@v4 with: path: '**/node_modules' - key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + key: "${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }}" - name: Install dependencies run: yarn install --frozen-lockfile - - name: Generate action documentation - run: | - for ACTION_FILE in $(echo "$ACTION_DIRS" | tr ',' '\n'); do - yarn gen-docs "$ACTION_FILE/action.yml" - done - - - name: Generate table of contents using gh-md-toc - run: | - wget "https://raw.githubusercontent.com/ekalinin/github-markdown-toc/$GH_MD_TOC_VERSION/gh-md-toc" - chmod +x gh-md-toc - ./gh-md-toc --insert --no-backup --skip-header README.md - env: - GH_MD_TOC_VERSION: 0.10.0 + - name: Get README from output + run: echo "${{ needs.generate_docs.outputs.readme }}" > README.md - name: Format README run: yarn prettier -w --single-quote README.md + - name: Debug, remove this # TODO: remove + run: cat README.md + - name: Commit changes + if: false # TODO: remove run: | git config user.name github-actions git config user.email github-actions@github.com git add README.md - git commit -m "Update action documentation" - git push + git commit -m diff --git a/README.md b/README.md index 291f85b..71a2b39 100644 --- a/README.md +++ b/README.md @@ -4,52 +4,11 @@ GitHub Actions templates for the Elvia organization. ## Table of Contents - + + -- [Actions](#actions) - - [Build](#build) - - [Description](#description) - - [Example usage in a full workflow](#example-usage-in-a-full-workflow) - - [Inputs](#inputs) - - [Usage](#usage) - - [Deploy](#deploy) - - [Description](#description-1) - - [Inputs](#inputs-1) - - [Usage](#usage-1) - - [Trivy IaC scan](#trivy-iac-scan) - - [Description](#description-2) - - [Inputs](#inputs-2) - - [Usage](#usage-2) - - [Terraform format check](#terraform-format-check) - - [Description](#description-3) - - [Inputs](#inputs-3) - - [Usage](#usage-3) -- [Development](#development) - - [Setup](#setup) - - [Action documentation](#action-documentation) - - [Check autogenerated documentation locally](#check-autogenerated-documentation-locally) - - [Generation of table of contents](#generation-of-table-of-contents) - - [Formatting](#formatting) - - - - - - -# Actions - - - -## Build - - - - -### Description - -Builds Docker image, scans for vulnerabilities using Trivy and pushes to Azure Container Registry. To use the `Build` and `Deploy` actions, you must first add your Github repository to https://github.com/3lvia/github-repositories-terraform. - - + + ### Example usage in a full workflow @@ -146,358 +105,17 @@ jobs: helmValuesPath: '.github/deploy/values.yaml' ``` - - -### Inputs - -| name | description | required | default | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------- | -| `name` |

Name of application. Do not include namespace.

| `true` | `""` | -| `namespace` |

Namespace or system of the application.

| `true` | `""` | -| `dockerfile` |

Path to Dockerfile.

| `true` | `""` | -| `dockerBuildContext` |

Docker build context, which is the working directory needed to build the dockerfile. Defaults to the directory of the Dockerfile.

| `false` | `""` | -| `severity` |

Severity levels to scan for. See https://github.com/aquasecurity/trivy-action?tab=readme-ov-file#inputs for more information.

| `false` | `CRITICAL,HIGH` | -| `trivy-cve-ignores` |

Comma-separated list of CVEs for Trivy to ignore. See https://aquasecurity.github.io/trivy/v0.49/docs/configuration/filtering/#trivyignore for syntax.

| `false` | `""` | -| `trivy-enable-secret-scanner` |

Enable Trivy secret scanner.

| `false` | `true` | -| `trivy-skip-dirs` |

Directories/files skipped by Trivy. See https://github.com/aquasecurity/trivy-action?tab=readme-ov-file#inputs for more information.

| `false` | `""` | -| `AZURE_CLIENT_ID` |

ClientId of a service principal that can push to Container Registry.

| `true` | `""` | -| `AZURE_TENANT_ID` |

TenantId of a service principal that can push to Azure Container Registry. Default to Elvia's Tenant ID.

| `false` | `2186a6ec-c227-4291-9806-d95340bf439d` | -| `ACR_SUBSCRIPTION_ID` |

Subscription ID of the Azure Container Registry to push to. Defaults to subscription ID of Elvia's standard ACR.

| `false` | `9edbf217-b7c1-4f6a-ae76-d046cf932ff0` | -| `ACR_NAME` |

Name of the Azure Container Registry to push to. Defaults to Elvia's standard ACR.

| `false` | `containerregistryelvia` | - - - - -### Usage - -```yaml -- uses: core-github-actions-templates@trunk - with: - name: - # Name of application. Do not include namespace. - # - # Required: true - # Default: "" - - namespace: - # Namespace or system of the application. - # - # Required: true - # Default: "" - - dockerfile: - # Path to Dockerfile. - # - # Required: true - # Default: "" - - dockerBuildContext: - # Docker build context, which is the working directory needed to build the dockerfile. Defaults to the directory of the Dockerfile. - # - # Required: false - # Default: "" - - severity: - # Severity levels to scan for. See https://github.com/aquasecurity/trivy-action?tab=readme-ov-file#inputs for more information. - # - # Required: false - # Default: CRITICAL,HIGH - - trivy-cve-ignores: - # Comma-separated list of CVEs for Trivy to ignore. See https://aquasecurity.github.io/trivy/v0.49/docs/configuration/filtering/#trivyignore for syntax. - # - # Required: false - # Default: "" - - trivy-enable-secret-scanner: - # Enable Trivy secret scanner. - # - # Required: false - # Default: true - - trivy-skip-dirs: - # Directories/files skipped by Trivy. See https://github.com/aquasecurity/trivy-action?tab=readme-ov-file#inputs for more information. - # - # Required: false - # Default: "" - - AZURE_CLIENT_ID: - # ClientId of a service principal that can push to Container Registry. - # - # Required: true - # Default: "" - - AZURE_TENANT_ID: - # TenantId of a service principal that can push to Azure Container Registry. Default to Elvia's Tenant ID. - # - # Required: false - # Default: 2186a6ec-c227-4291-9806-d95340bf439d - - ACR_SUBSCRIPTION_ID: - # Subscription ID of the Azure Container Registry to push to. Defaults to subscription ID of Elvia's standard ACR. - # - # Required: false - # Default: 9edbf217-b7c1-4f6a-ae76-d046cf932ff0 - - ACR_NAME: - # Name of the Azure Container Registry to push to. Defaults to Elvia's standard ACR. - # - # Required: false - # Default: containerregistryelvia -``` - - - - - -## Deploy - - - - -### Description - -Deploys an application to Kubernetes using the Elvia Helm chart. To use the `Build` and `Deploy` actions, you must first add your Github repository to https://github.com/3lvia/github-repositories-terraform. - - - - -### Inputs - -| name | description | required | default | -| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------- | -| `name` |

Name of application. Do not include namespace.

| `true` | `""` | -| `namespace` |

Namespace or system of the application.

| `true` | `""` | -| `environment` |

Environment to deploy to.

| `true` | `""` | -| `helmValuesPath` |

Path to Helm values file, relative to the root of the repository. Defaults to .github/deploy/values.yaml.

| `false` | `.github/deploy/values.yaml` | -| `checkout` |

If "true", the action will check out the repository. If "false", the action will assume the repository has already been checked out.

| `false` | `true` | -| `runtimeCloudProvider` |

Kubernetes cloud provider to deploy to: 'AKS' or 'GKE'. Defaults to AKS.

| `false` | `AKS` | -| `AZURE_CLIENT_ID` |

Client ID of a service principal that has access to AKS. Only required for deploying to AKS.

| `false` | `""` | -| `AZURE_TENANT_ID` |

Tenant ID of a service principal that has access to AKS. Default to Elvia's Tenant ID.

| `false` | `2186a6ec-c227-4291-9806-d95340bf439d` | -| `AKS_SUBSCRIPTION_ID` |

Subscription ID of AKS to deploy to. Defaults to Elvias normal clusters.

| `false` | `""` | -| `AKS_CLUSTER_NAME` |

Name of the AKS cluster to deploy to. Defaults to Elvias normal clusters.

| `false` | `""` | -| `AKS_RESOURCE_GROUP` |

Resource group of the AKS cluster to deploy to. Defaults to Elvias normal clusters.

| `false` | `""` | -| `GC_SERVICE_ACCOUNT` |

Service account to use for deploying to GKE. Only required for deploying to GKE.

| `false` | `""` | -| `GC_WORKLOAD_IDENTITY_PROVIDER` |

Workload identity provider to use for deploying to GKE. Only required for deploying to GKE.

| `false` | `""` | -| `GC_PROJECT_ID` |

Project ID of GKE to deploy to. Defaults to Elvias normal clusters.

| `false` | `""` | -| `GC_CLUSTER_NAME` |

Name of the GKE cluster to deploy to. Defaults to Elvias normal clusters.

| `false` | `""` | -| `GC_CLUSTER_LOCATION` |

Location of the GKE cluster to deploy to. Defaults to locations of Elvias normal clusters.

| `false` | `europe-west1` | - - - - -### Usage - -```yaml -- uses: core-github-actions-templates@trunk - with: - name: - # Name of application. Do not include namespace. - # - # Required: true - # Default: "" - - namespace: - # Namespace or system of the application. - # - # Required: true - # Default: "" - - environment: - # Environment to deploy to. - # - # Required: true - # Default: "" - - helmValuesPath: - # Path to Helm values file, relative to the root of the repository. Defaults to .github/deploy/values.yaml. - # - # Required: false - # Default: .github/deploy/values.yaml - - checkout: - # If "true", the action will check out the repository. If "false", the action will assume the repository has already been checked out. - # - # Required: false - # Default: true - - runtimeCloudProvider: - # Kubernetes cloud provider to deploy to: 'AKS' or 'GKE'. Defaults to AKS. - # - # Required: false - # Default: AKS - - AZURE_CLIENT_ID: - # Client ID of a service principal that has access to AKS. Only required for deploying to AKS. - # - # Required: false - # Default: "" - - AZURE_TENANT_ID: - # Tenant ID of a service principal that has access to AKS. Default to Elvia's Tenant ID. - # - # Required: false - # Default: 2186a6ec-c227-4291-9806-d95340bf439d - - AKS_SUBSCRIPTION_ID: - # Subscription ID of AKS to deploy to. Defaults to Elvias normal clusters. - # - # Required: false - # Default: "" - - AKS_CLUSTER_NAME: - # Name of the AKS cluster to deploy to. Defaults to Elvias normal clusters. - # - # Required: false - # Default: "" - - AKS_RESOURCE_GROUP: - # Resource group of the AKS cluster to deploy to. Defaults to Elvias normal clusters. - # - # Required: false - # Default: "" - - GC_SERVICE_ACCOUNT: - # Service account to use for deploying to GKE. Only required for deploying to GKE. - # - # Required: false - # Default: "" - - GC_WORKLOAD_IDENTITY_PROVIDER: - # Workload identity provider to use for deploying to GKE. Only required for deploying to GKE. - # - # Required: false - # Default: "" - - GC_PROJECT_ID: - # Project ID of GKE to deploy to. Defaults to Elvias normal clusters. - # - # Required: false - # Default: "" - - GC_CLUSTER_NAME: - # Name of the GKE cluster to deploy to. Defaults to Elvias normal clusters. - # - # Required: false - # Default: "" - - GC_CLUSTER_LOCATION: - # Location of the GKE cluster to deploy to. Defaults to locations of Elvias normal clusters. - # - # Required: false - # Default: europe-west1 -``` - - - - - -## Trivy IaC scan - - - - -### Description - -Uses https://github.com/aquasecurity/trivy-action to scan IaC and report security issues. The action will report any vulnerabilities to GitHub Advanced Security, which will be visible in the Security tab on GitHub. - - - - -### Inputs - -| name | description | required | default | -| --------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------------------------------- | -| `path` |

Path to the directory containing the IaC files.

| `false` | `.` | -| `skip-dirs` |

Comma-separated list of directories to skip.

| `false` | `""` | -| `severity` |

Severity levels to scan for. See https://github.com/aquasecurity/trivy-action?tab=readme-ov-file#inputs for more information.

| `false` | `CRITICAL,HIGH,MEDIUM,LOW,UNKNOWN` | -| `upload-report` |

Upload Trivy report to GitHub Security tab. GitHub Advanced Security must be enabled for the repository to use this feature.

| `false` | `true` | -| `checkout` |

If true, the action will check out the repository. If false, the action will assume the repository has already been checked out.

| `false` | `true` | - - - - -### Usage - -```yaml -- uses: core-github-actions-templates@trunk - with: - path: - # Path to the directory containing the IaC files. - # - # Required: false - # Default: . - - skip-dirs: - # Comma-separated list of directories to skip. - # - # Required: false - # Default: "" - - severity: - # Severity levels to scan for. See https://github.com/aquasecurity/trivy-action?tab=readme-ov-file#inputs for more information. - # - # Required: false - # Default: CRITICAL,HIGH,MEDIUM,LOW,UNKNOWN - - upload-report: - # Upload Trivy report to GitHub Security tab. GitHub Advanced Security must be enabled for the repository to use this feature. - # - # Required: false - # Default: true - - checkout: - # If true, the action will check out the repository. If false, the action will assume the repository has already been checked out. - # - # Required: false - # Default: true -``` - - - - - -## Terraform format check - - - + + -### Description + + -Uses the built-in formatter from the Terraform CLI to check the format of Terraform code. + + - - - -### Inputs - -| name | description | required | default | -| ---------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | -| `path` |

Path to process.

| `false` | `.` | -| `checkout` |

If true, the action will check out the repository. If false, the action will assume the repository has already been checked out.

| `false` | `true` | - - - - -### Usage - -```yaml -- uses: core-github-actions-templates@trunk - with: - path: - # Path to process. - # - # Required: false - # Default: . - - checkout: - # If true, the action will check out the repository. If false, the action will assume the repository has already been checked out. - # - # Required: false - # Default: true -``` - - + + # Development @@ -509,43 +127,24 @@ Install the dependencies using [yarn](https://yarnpkg.com): yarn install ``` -## Action documentation +## Action documentation & table of contents -We use [action-docs](https://github.com/npalm/action-docs) to auto-generate the documentation for the actions. -To add documentation for your new action, add these tags to the `README.md` file: +We use the internal tool [gh-action-docs](gh-actions-docs) to auto-generate the documentation for the actions, as well as the table of contents in this README. +To add documentation for your new action, add these two tags to the `README.md` file: ```markdown - - - - + + ``` -and add the action to the comma-separated list `ACTION_DIRS` in [`.github/workflows/autogenerate-docs.yml`](.github/workflows/autogenerate-docs.yml): +Replace `path` with the path to the action yaml file from the root of the repository. +The fields `owner`, `project` and `version` are optional, but should be set to `3lvia`, `core-github-actions-templates` and `trunk` respectively. -```yaml -name: Generate action documentation for ${{ matrix.action-file }} -runs-on: ubuntu-latest -env: - ACTION_DIRS: 'build,deploy,trivy-iac-scan,terraform-format,my-new-action' # Add your action here -steps: -``` - -The documentation will then be autogenerated and commited on push to the `trunk` branch. +The documentation will then be autogenerated, added to the table of contents and commited on push to the `trunk` branch. ### Check autogenerated documentation locally -You can run [action-docs](https://github.com/npalm/action-docs) locally to check the generated documentation: - -```bash -yarn gen-docs new-action/action.yaml -cat README.md -``` - -### Generation of table of contents - -We use [github-markdown-toc](https://github.com/ekalinin/github-markdown-toc) to generate the table of contents in the README. -This process shouldn't need manual intervention, and it should automatically add your new action to the table of contents. +See the [gh-actions-docs README](gh-actions-docs/README.md) for more information. ## Formatting @@ -553,6 +152,6 @@ We use [prettier](https://prettier.io) to format the README and yaml files: ```bash yarn format -# OR +#OR yarn format --end-of-line crlf ``` diff --git a/gh-actions-docs/.gitignore b/gh-actions-docs/.gitignore new file mode 100644 index 0000000..ad2c767 --- /dev/null +++ b/gh-actions-docs/.gitignore @@ -0,0 +1,2 @@ +dist-newstyle/ +.env* diff --git a/gh-actions-docs/Dockerfile b/gh-actions-docs/Dockerfile new file mode 100644 index 0000000..0b04098 --- /dev/null +++ b/gh-actions-docs/Dockerfile @@ -0,0 +1,14 @@ +FROM haskell:9.4.8-slim AS build + +WORKDIR /opt/app + +RUN cabal update + +COPY gh-actions-docs.cabal ./ +RUN cabal build --only-dependencies + +COPY app ./app + +RUN cabal install --overwrite-policy=always + +ENTRYPOINT ["gh-actions-docs"] diff --git a/gh-actions-docs/Dockerfile.action b/gh-actions-docs/Dockerfile.action new file mode 100644 index 0000000..757c990 --- /dev/null +++ b/gh-actions-docs/Dockerfile.action @@ -0,0 +1,3 @@ +FROM ghcr.io/3lvia/core-github-actions-templates/gh-actions-docs:latest + +ENTRYPOINT ["gh-actions-docs"] diff --git a/gh-actions-docs/README.md b/gh-actions-docs/README.md new file mode 100644 index 0000000..fe16e51 --- /dev/null +++ b/gh-actions-docs/README.md @@ -0,0 +1,57 @@ +# gh-actions-docs + +Automatically generate documentation for your GitHub Actions. + +## Setup + +### Docker + +```bash +docker build . -t gh-actions-docs:latest +docker run -v $(pwd):/app gh-actions-docs:latest +``` + +### Local + +Install [GHCUp](https://www.haskell.org/ghcup), which should include Cabal. + +```bash +cabal update +cabal install +gh-actions-docs +``` + +## Usage + +The documentation will be added to the file `README.md` in the current directory. +To specify where the documentation should be added, add the following two comments to the file: + +```markdown + + +``` + +The `path` parameter is required, and the `owner`, `project`, and `version` parameters are optional. +The latter three are only used to generate the "Usage" section of the documentation. +If any of these are omitted, the "Usage" section will not be generated. + +### GitHub Actions + +```yaml +name: Generate documentation + +on: + push: + branches: [trunk] + +jobs: + generate_docs: + name: Generate documentation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Generate action documentation + uses: 3lvia/core-github-actions-templates/gh-actions-docs@trunk +``` diff --git a/gh-actions-docs/action.yml b/gh-actions-docs/action.yml new file mode 100644 index 0000000..5a29c88 --- /dev/null +++ b/gh-actions-docs/action.yml @@ -0,0 +1,27 @@ +name: 'gh-actions-docs' +description: 'Automatically generate documentation for your GitHub Actions composite actions.' + +inputs: + readme-file: + description: 'Name of README file to generate.' + required: false + default: 'README.md' + debug: + description: 'Enable debug mode.' + required: false + default: 'false' + ignore-headers: + description: 'Ignore headers in README file.' + required: false + ignore-files: + description: 'Ignore YAML files referenced by tags in README file.' + required: false + +runs: + using: 'docker' + image: 'Dockerfile.action' + args: + - README_FILE=${{ inputs.readme-file }} + - DEBUG=${{ inputs.debug }} + - IGNORE_HEADERS=${{ inputs.ingore-headers }} + - IGNORE_FILES=${{ inputs.ignore-files }} diff --git a/gh-actions-docs/app/Main.hs b/gh-actions-docs/app/Main.hs new file mode 100644 index 0000000..71242c7 --- /dev/null +++ b/gh-actions-docs/app/Main.hs @@ -0,0 +1,343 @@ +{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TemplateHaskell #-} + +module Main where + +import Control.Monad (void, when) +import Data.Aeson.TH (defaultOptions, deriveJSON, + fieldLabelModifier, omitNothingFields) +import Data.Either (fromRight, isLeft, lefts, rights) +import Data.Map (Map, toList) +import Data.Maybe (fromMaybe) +import Data.Text (Text, isPrefixOf, pack, replace, splitOn, + toLower, unpack) +import Data.Void (Void) +import Data.Yaml (ParseException, decodeFileEither) +import System.Environment (getEnvironment) +import System.Exit (exitFailure) +import Text.Megaparsec (Parsec, anySingle, eof, + errorBundlePretty, many, manyTill, + manyTill_, optional, parse, skipManyTill, + some, try, (<|>)) +import Text.Megaparsec.Char (char, latin1Char, newline, string) +import Text.Pretty.Simple (pPrint) + +-- CONFIG + +data Config + = Config + { readmeFile :: String + , debug :: Bool + , ignoreHeaders :: [String] + , ignoreFiles :: [String] + } + deriving (Show) + +getConfig :: IO Config +getConfig = do + env <- getEnvironment + let readmeFile' = fromMaybe "README.md" $ lookup "README_FILE" env + let debug' = lookup "DEBUG" env == Just "true" + let ignoreHeaders' = maybe [] (splitOn "," . pack) $ lookup "IGNORE_HEADERS" env + let ignoreFiles' = maybe [] (splitOn "," . pack) $ lookup "IGNORE_FILES" env + return $ Config readmeFile' debug' (map unpack ignoreHeaders') (map unpack ignoreFiles') + +-- ACTIONS + +data ActionInput + = ActionInput + { type' :: Maybe String + , description :: Maybe String + , required :: Maybe Bool + , default' :: Maybe String + } + deriving (Show) + +$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = filter (/= '\'')} ''ActionInput) + +data Action + = Action + { name :: String + , description :: String + , inputs :: Map String ActionInput + } + deriving (Show) + +$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = filter (/= '\'')} ''Action) + +data ActionMetadata + = ActionMetadata + { path :: String + , owner :: Maybe String + , project :: Maybe String + , version :: Maybe String + } + deriving (Show, Eq) + +toEnglishBool :: Bool -> String +toEnglishBool True = "yes" +toEnglishBool False = "no" + +actionStartTagPrefix :: String +actionStartTagPrefix = "" + +prettyPrintAction :: Action -> ActionMetadata -> String +prettyPrintAction (Action name' description' inputs') actionMetadata = + "## " + ++ name' + ++ "\n\n" + ++ "### Description\n" + ++ description' + ++ "\n\n" + ++ "### Inputs\n" + ++ prettyPrintInputs inputs' + ++ maybe "" ("### Usage\n" ++) (prettyPrintUsage inputs' actionMetadata) + +prettyPrintInputs :: Map String ActionInput -> String +prettyPrintInputs inputs' = + "|Name|Type|Description|Required|Default|\n" + ++ "|-|-|-|-|-|\n" + ++ concatMap + ( \(name', ActionInput typ des req def) -> + name' + ++ "|" + ++ fromMaybe "" typ + ++ "|" + ++ fromMaybe "" des + ++ "|" + ++ maybe "no" toEnglishBool req + ++ "|" + ++ fromMaybe "" def + ++ "|\n" + ) + (toList inputs') + ++ "\n" + +prettyPrintUsage :: Map String ActionInput -> ActionMetadata -> Maybe String +prettyPrintUsage inputs' (ActionMetadata _ (Just owner') (Just project') (Just version')) = + Just $ + "```yaml\n" + ++ "uses: " + ++ owner' + ++ "/" + ++ project' + ++ "@" + ++ version' + ++ "\n" + ++ "with:\n" + ++ concatMap (uncurry prettyPrintUsage') (toList inputs') + ++ "```\n" +prettyPrintUsage _ _ = Nothing + +prettyPrintUsage' :: String -> ActionInput -> String +prettyPrintUsage' name' (ActionInput _ des req def) = + indent + ++ name' + ++ ":\n" + ++ ( case (des, req, def) of + (Just des', Just req', Just def') -> + formatDescription des' ++ formatRequired req' ++ formatDefault def' + (Just des', Just req', _) -> + formatDescription des' ++ formatRequired req' + (Just des', _, Just def') -> + formatDescription des' ++ formatDefault def' + (Just des', _, _) -> + formatDescription des' + _ -> + "" + ) + where + indent = replicate 4 ' ' + formatDescription des' = indent ++ "# " ++ des' ++ "\n" ++ indent ++ "#\n" + formatRequired req' = indent ++ "# Required: " ++ toEnglishBool req' ++ "\n" + formatDefault def' = indent ++ "# Default: '" ++ def' ++ "'\n" + +actionMetadataToString :: ActionMetadata -> String +actionMetadataToString (ActionMetadata path' owner' project' version') = + actionStartTagPrefix + ++ " path=" + ++ path' + ++ maybe "" (" owner=" ++) owner' + ++ maybe "" (" project=" ++) project' + ++ maybe "" (" version=" ++) version' + ++ " -->" + +replaceActionTagWithDocs :: String -> (ActionMetadata, Action) -> String +replaceActionTagWithDocs readme (meta, action) = + case parse (skipManyTill latin1Char (specificActionMetadataParser_ meta)) "" (pack readme) of + Right "" -> + readme + Right match' -> + let docs = actionMetadataToString meta ++ "\n" ++ prettyPrintAction action meta ++ actionEndTag + in unpack $ replace (pack match') (pack docs) (pack readme) + Left err -> + error $ errorBundlePretty err + +-- TABLE OF CONTENTS + +data MarkdownHeader + = MarkdownHeader + { level :: Int + , text :: String + } + deriving (Show) + +markdownHeadersToTableOfContents :: [MarkdownHeader] -> String +markdownHeadersToTableOfContents = + concatMap + ( \(MarkdownHeader level' text') -> + replicate (2 * (level' - 1)) ' ' ++ "- [" ++ text' ++ "](#" ++ slugify text' ++ ")\n" + ) + where + slugify = unpack . toLower . replace " " "-" . pack . filter (`elem` ['a' .. 'z'] ++ ['A' .. 'Z'] ++ ['0' .. '9']) + +tocStartTag :: String +tocStartTag = "" + +tocEndTag :: String +tocEndTag = "" + +replaceTableOfContentsTagWithTableOfContents :: String -> String -> String +replaceTableOfContentsTagWithTableOfContents toc readme = + case parse (skipManyTill latin1Char tableOfContentsTagParser) "" (pack readme) of + Right match' -> + let toc' = tocStartTag ++ "\n" ++ toc ++ tocEndTag + in unpack $ replace (pack match') (pack toc') (pack readme) + Left err -> + error $ errorBundlePretty err + +-- PARSERS + +type Parser = Parsec Void Text + +actionMetadataParser :: Parser ActionMetadata +actionMetadataParser = do + _ <- string $ pack actionStartTagPrefix + _ <- string " path=" + path' <- manyTill anySingle (char ' ') + owner' <- optional $ do + _ <- string "owner=" + manyTill anySingle (char ' ') + project' <- optional $ do + _ <- string "project=" + manyTill anySingle (char ' ') + version' <- optional $ do + _ <- string "version=" + manyTill anySingle (char ' ') + _ <- string "-->" + _ <- skipManyTill anySingle $ string $ pack actionEndTag + return $ ActionMetadata path' owner' project' version' + +fromTextActionMetadataParser :: Parser [ActionMetadata] +fromTextActionMetadataParser = do + ps <- many $ try $ skipManyTill latin1Char actionMetadataParser + _ <- eof <|> void newline + return ps + +specificActionMetadataParser_ :: ActionMetadata -> Parser String +specificActionMetadataParser_ meta = do + meta' <- string (pack $ actionMetadataToString meta) + (between', end) <- anySingle `manyTill_` string (pack actionEndTag) + return $ unpack meta' ++ between' ++ unpack end + +markdownHeaderParser :: Parser MarkdownHeader +markdownHeaderParser = do + level' <- length <$> some (char '#') + _ <- char ' ' + text' <- latin1Char `manyTill` newline + return $ MarkdownHeader level' text' + +tableOfContentsTagParser :: Parser String +tableOfContentsTagParser = do + start <- skipManyTill anySingle $ string $ pack tocStartTag + (between', end) <- manyTill_ anySingle $ string $ pack tocEndTag + return $ unpack start ++ between' ++ unpack end + +-- MAIN + +main :: IO () +main = do + config <- getConfig + when (debug config) do + putStrLn "\nDebug mode enabled\n" + putStrLn "Config:" + pPrint config + + readme <- readFile $ readmeFile config + + -- Parse action metadata + let actionMetadataListE = parse fromTextActionMetadataParser "" $ pack readme + when (isLeft actionMetadataListE) do + putStrLn "Error parsing metadata:" + mapM_ pPrint actionMetadataListE + exitFailure + + let actionMetadataList = filter (\file -> path file `notElem` ignoreFiles config) $ fromRight [] actionMetadataListE + when (debug config) do + putStrLn "actionMetdataList:" + pPrint actionMetadataList + + -- Parse yaml in action files + parsedFilesE <- mapM (decodeFileEither . path) actionMetadataList :: IO [Either ParseException Action] + when (any isLeft parsedFilesE) do + putStrLn "Error parsing files:" + mapM_ pPrint $ lefts parsedFilesE + exitFailure + + let parsedFiles = rights parsedFilesE + when (debug config) do + putStrLn "Parsed files:\n" + mapM_ pPrint parsedFiles + + let parsedFilesWithMetadata = zip actionMetadataList parsedFiles + when (debug config) do + putStrLn "parsedFilesWithMetadata:" + pPrint parsedFilesWithMetadata + + -- Update README with action docs + let readmeWithActions = foldl replaceActionTagWithDocs readme parsedFilesWithMetadata + if readmeWithActions /= readme + then do + writeFile "README.md" readmeWithActions + putStrLn $ readmeFile config ++ " updated successfully! (actions)" + else do + putStrLn "Actions not updated." + + -- Parse markdown headers + let markdownHeaderLines = + map (++ "\n") $ + filter (`notElem` ignoreHeaders config) $ + filter (\x -> any (`isPrefixOf` pack x) ["# ", "## ", "### ", "#### "]) $ + lines readmeWithActions + when (debug config) do + putStrLn "markdownHeadersLines:" + pPrint markdownHeaderLines + + let markdownHeaders = map (parse markdownHeaderParser "" . pack) markdownHeaderLines + when (any isLeft markdownHeaders) do + putStrLn "Error parsing markdown headers:" + mapM_ pPrint markdownHeaders + exitFailure + when (debug config) do + putStrLn "markdownHeaders:" + pPrint markdownHeaders + + -- Generate table of contents + let toc = markdownHeadersToTableOfContents $ rights markdownHeaders + when (debug config) do + putStrLn "toc:" + pPrint toc + + -- Update README with table of contents + let readmeWithActionsToc = replaceTableOfContentsTagWithTableOfContents toc readmeWithActions + if readmeWithActionsToc /= readmeWithActions + then do + writeFile "README.md" readmeWithActionsToc + putStrLn $ readmeFile config ++ " updated successfully! (toc)" + else do + putStrLn "Table of contents not updated." diff --git a/gh-actions-docs/gh-actions-docs.cabal b/gh-actions-docs/gh-actions-docs.cabal new file mode 100644 index 0000000..3908d14 --- /dev/null +++ b/gh-actions-docs/gh-actions-docs.cabal @@ -0,0 +1,84 @@ +cabal-version: 3.0 +-- The cabal-version field refers to the version of the .cabal specification, +-- and can be different from the cabal-install (the tool) version and the +-- Cabal (the library) version you are using. As such, the Cabal (the library) +-- version used must be equal or greater than the version stated in this field. +-- Starting from the specification version 2.2, the cabal-version field must be +-- the first thing in the cabal file. + +-- Initial package description 'gh-actions-docs' generated by +-- 'cabal init'. For further documentation, see: +-- http://haskell.org/cabal/users-guide/ +-- +-- The name of the package. +name: gh-actions-docs + +-- The package version. +-- See the Haskell package versioning policy (PVP) for standards +-- guiding when and how versions should be incremented. +-- https://pvp.haskell.org +-- PVP summary: +-+------- breaking API changes +-- | | +----- non-breaking API additions +-- | | | +--- code changes with no API change +version: 0.1.0.0 + +-- A short (one-line) description of the package. +-- synopsis: + +-- A longer description of the package. +-- description: + +-- The license under which the package is released. +-- license: MIT + +-- The file containing the license text. +-- license-file: LICENSE + +-- The package author(s). +author: Andreas Salhus Bakseter + +-- An email address to which users can send suggestions, bug reports, and patches. +maintainer: 141913422+baksetercx@users.noreply.github.com + +-- A copyright notice. +-- copyright: +build-type: Simple + +-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README. +-- extra-doc-files: CHANGELOG.md + +-- Extra source files to be distributed with the package, such as examples, or a tutorial module. +-- extra-source-files: + +common warnings + ghc-options: -Wall + -Wunused-do-bind + +executable gh-actions-docs + -- Import common warning flags. + import: warnings + + -- .hs or .lhs file containing the Main module. + main-is: Main.hs + + -- Modules included in this executable, other than Main. + -- other-modules: + + -- LANGUAGE extensions used by modules in this package. + -- other-extensions: + + -- Other library packages from which modules are imported. + build-depends: base ^>=4.17.2.0 + , yaml + , aeson + , split + , containers + , text + , pretty-simple + , megaparsec + + -- Directories containing source files. + hs-source-dirs: app + + -- Base language which the package is written in. + default-language: Haskell2010 diff --git a/gh-actions-docs/hie.yaml b/gh-actions-docs/hie.yaml new file mode 100644 index 0000000..324872a --- /dev/null +++ b/gh-actions-docs/hie.yaml @@ -0,0 +1,4 @@ +cradle: + cabal: + - path: "app/Main.hs" + component: "gh-actions-docs:exe:gh-actions-docs" diff --git a/package.json b/package.json index 4255316..be958f6 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,10 @@ "author": "3lvia", "private": true, "scripts": { - "gen-docs": "action-docs -n -u -s", "format": "prettier --write --single-quote .github/workflows/** **/action.yml README.md", "format-check": "prettier --check --single-quote .github/workflows/** **/action.yml README.md" }, "devDependencies": { - "action-docs": "2.4.0", "prettier": "3.2.5" } } diff --git a/yarn.lock b/yarn.lock index 12455ec..744e4c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,241 +2,7 @@ # yarn lockfile v1 -action-docs@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/action-docs/-/action-docs-2.4.0.tgz#b3e3342ab75288b0a7258249423d6580a58b29f5" - integrity sha512-ALXUZDKRDfVhAvhNF9HHCRMMZXRtq00vb/rPZCJcZZOtwirch8lpWUyE727XMTjguZsK67aciV9d2VRH2opK3A== - dependencies: - chalk "^5.3.0" - figlet "^1.7.0" - replace-in-file "^7.1.0" - showdown "^2.1.0" - yaml "^2.3.4" - yargs "^17.7.2" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" - integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -commander@^9.0.0: - version "9.5.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" - integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -escalade@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" - integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== - -figlet@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.7.0.tgz#46903a04603fd19c3e380358418bb2703587a72e" - integrity sha512-gO8l3wvqo0V7wEFLXPbkX83b7MVjRrk1oRLfYlZXol8nEpb/ON9pcKLI4qpBv5YtOTfrINtqb7b40iYY2FTWFg== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -glob@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - prettier@3.2.5: version "3.2.5" resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== - -replace-in-file@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/replace-in-file/-/replace-in-file-7.1.0.tgz#ec5d50283a3ce835d62c99d90700aacbada1d2f8" - integrity sha512-1uZmJ78WtqNYCSuPC9IWbweXkGxPOtk2rKuar8diTw7naVIQZiE3Tm8ACx2PCMXDtVH6N+XxwaRY2qZ2xHPqXw== - dependencies: - chalk "^4.1.2" - glob "^8.1.0" - yargs "^17.7.2" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -showdown@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/showdown/-/showdown-2.1.0.tgz#1251f5ed8f773f0c0c7bfc8e6fd23581f9e545c5" - integrity sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ== - dependencies: - commander "^9.0.0" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yaml@^2.3.4: - version "2.4.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.1.tgz#2e57e0b5e995292c25c75d2658f0664765210eed" - integrity sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg== - -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.7.2: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1"