├── .github ├── pr-labeler.yml ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── release-drafter.yml └── workflows │ ├── advanced-1.yml │ ├── draft.yml │ ├── mock-test.yml │ ├── pr-labeler.yml │ ├── publish.yml │ ├── schema-change-check.yml │ ├── simple-1.yml │ ├── simple-2.yml │ └── test.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── commitlint.config.js ├── eslint.config.js ├── examples ├── advanced.wac.ts └── simple.wac.ts ├── jest.config.js ├── package.json ├── pnpm-lock.yaml ├── scripts ├── generateWorkflowTypes.ts └── generateZeroDependencyPackage.ts ├── src ├── cli │ ├── bin.ts │ ├── commands │ │ ├── __mocks__ │ │ │ └── test.wac.ts │ │ ├── build.spec.ts │ │ ├── build.ts │ │ ├── index.ts │ │ └── types │ │ │ ├── build.ts │ │ │ └── index.ts │ ├── index.spec.ts │ └── index.ts ├── index.spec.ts ├── index.ts └── lib │ ├── index.spec.ts │ ├── index.ts │ ├── job │ ├── index.spec.ts │ └── index.ts │ ├── step │ ├── index.spec.ts │ └── index.ts │ ├── types │ ├── githubActionsWorkflow.ts │ ├── githubActionsWorkflowExtended.ts │ └── index.ts │ ├── utils │ ├── index.spec.ts │ └── index.ts │ └── workflow │ ├── index.spec.ts │ └── index.ts ├── tsconfig.build.json ├── tsconfig.json ├── wac.config.json └── workflows ├── draft.wac.ts ├── pr-labeler.wac.ts ├── publish.wac.ts ├── schema-change-check.wac.ts └── test.wac.ts /.github/ pr-labeler.yml: -------------------------------------------------------------------------------- 1 | chore: ["chore/*"] 2 | ci: ["ci/*"] 3 | docs: ["docs/*"] 4 | feature: ["feature/*", "feat/*"] 5 | bug: ["fix/*"] 6 | refactor: ["refactor/*"] 7 | test: ["test/*"] 8 | 9 | 10 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | First off, thank you for considering contributing to github-actions-workflow-ts. It's people 4 | like you that make it such a great tool. 5 | 6 | ### Where do I go from here? 7 | 8 | If you've noticed a bug or have a feature request, [make one][new issue]! It's 9 | generally best if you get confirmation of your bug or approval for your feature 10 | request this way before starting to code. 11 | 12 | If you have a general question about github-actions-workflow-ts, you can post it in [Discussions](https://github.com/emmanuelnk/github-actions-workflow-ts/discussions), the issue tracker is only for bugs and feature requests. 13 | 14 | ### Fork & create a branch 15 | 16 | If this is something you think you can fix, then [fork github-actions-workflow-ts](https://github.com/emmanuelnk/github-actions-workflow-ts/fork) and create a branch with a descriptive name. 17 | 18 | A good branch name would be (where issue #325 is the ticket you're working on): 19 | 20 | ```sh 21 | git checkout -b feat/325-add-some-new-feature 22 | ``` 23 | 24 | ### Get the test suite running 25 | 26 | Make sure you're using Node 18 and above. 27 | 28 | Now install the development dependencies. This project uses [pnpm](https://github.com/pnpm/pnpm) 29 | 30 | ```bash 31 | pnpm install 32 | ``` 33 | 34 | ### Implement your fix or feature 35 | 36 | At this point, you're ready to make your changes! Feel free to ask for help; 37 | everyone is a beginner at first :smile_cat: 38 | 39 | ### Make a Pull Request 40 | 41 | At this point, you should switch back to your main branch and make sure it's 42 | up to date with github-actions-workflow-ts's main branch: 43 | 44 | ```sh 45 | git remote add upstream git@github.com:emmanuelnk/github-actions-workflow-ts.git 46 | git checkout main 47 | git pull upstream main 48 | ``` 49 | 50 | Then update your feature branch from your local copy of main, and push it! 51 | 52 | ```sh 53 | git checkout feat/325-add-some-new-feature 54 | git rebase main 55 | git push --set-upstream origin feat/325-add-some-new-feature 56 | ``` 57 | 58 | Finally, go to GitHub and [make a Pull Request][] :D 59 | 60 | ### Keeping your Pull Request updated 61 | 62 | If a maintainer asks you to "rebase" your PR, they're saying that a lot of code 63 | has changed, and that you need to update your branch so it's easier to merge. 64 | 65 | To learn more about rebasing in Git, there are a lot of [good][git rebasing] 66 | [resources][interactive rebase] but here's the suggested workflow: 67 | 68 | ```sh 69 | git checkout feat/325-add-some-new-feature 70 | git pull --rebase upstream main 71 | git push --force-with-lease feat/325-add-some-new-feature 72 | ``` 73 | 74 | ### Merging a PR (maintainers only) 75 | 76 | A PR can only be merged into main by a maintainer if: 77 | 78 | * It is passing CI. 79 | * It has been approved by at least two maintainers. If it was a maintainer who 80 | opened the PR, only one extra approval is needed. 81 | * It has no requested changes. 82 | * It is up to date with current main. 83 | 84 | Any maintainer is allowed to merge a PR if all of these conditions are 85 | met. 86 | 87 | ### Shipping a release (maintainers only) 88 | 89 | ### References 90 | 91 | [new issue]: https://github.com/emmanuelnk/github-actions-workflow-ts/issues/new 92 | [fork github-actions-workflow-ts]: https://help.github.com/articles/fork-a-repo 93 | [make a pull request]: https://help.github.com/articles/creating-a-pull-request 94 | [git rebasing]: https://git-scm.com/book/en/Git-Branching-Rebasing 95 | [interactive rebase]: https://help.github.com/en/github/using-git/about-git-rebase 96 | 97 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | 4 | ## Actual Behavior 5 | 6 | 7 | ## Steps to Reproduce the Problem 8 | 1. 9 | 2. 10 | 3. 11 | 12 | ## Specifications 13 | - Version: 14 | - Platform: 15 | - Subsystem: -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: "v$RESOLVED_VERSION" 2 | tag-template: "v$RESOLVED_VERSION" 3 | template: | 4 | # What's Changed 5 | 6 | $CHANGES 7 | 8 | **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION 9 | 10 | categories: 11 | - title: "New" 12 | label: "type: feature" 13 | - title: "Chore" 14 | label: "type: chore" 15 | - title: "Refactor" 16 | label: "type: refactor" 17 | - title: "CI" 18 | label: "type: ci" 19 | - title: "Breaking" 20 | label: "type: breaking" 21 | - title: "Bug Fixes" 22 | label: "type: bug" 23 | - title: "Documentation" 24 | label: "type: docs" 25 | - title: "Tests" 26 | label: "type: test" 27 | - title: "Other" 28 | - title: "Dependency Updates" 29 | label: "type: dependencies" 30 | collapse-after: 5 31 | 32 | version-resolver: 33 | major: 34 | labels: 35 | - "type: breaking" 36 | minor: 37 | labels: 38 | - "type: feature" 39 | patch: 40 | labels: 41 | - "type: bug" 42 | - "type: maintenance" 43 | - "type: docs" 44 | - "type: dependencies" 45 | - "type: security" 46 | 47 | exclude-labels: 48 | - "skip-changelog" 49 | -------------------------------------------------------------------------------- /.github/workflows/advanced-1.yml: -------------------------------------------------------------------------------- 1 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 2 | # This file was automatically generated by github-actions-workflow-ts. 3 | # Instead, modify examples/advanced.wac.ts 4 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 5 | 6 | name: ExampleAdvanced 7 | 'on': 8 | workflow_dispatch: {} 9 | env: 10 | SENTRY_APP_NAME: ExampleAdvanced 11 | SENTRY_ORG: example-org 12 | jobs: 13 | Test: 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 10 16 | strategy: 17 | matrix: 18 | node: 19 | - 12.x 20 | - 14.x 21 | - 16.x 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v3 25 | - name: Install pnpm 26 | uses: pnpm/action-setup@v4 27 | id: pnpm-install 28 | with: 29 | version: 8 30 | run_install: 'false' 31 | - name: Get pnpm store directory 32 | id: pnpm-cache 33 | shell: bash 34 | run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 35 | - name: Setup pnpm cache 36 | uses: actions/cache@v3 37 | with: 38 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 39 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles("**/pnpm-lock.yaml") }} 40 | restore-keys: ${{ runner.os }}-pnpm-store- 41 | - name: Setup npmrc for Private Packages 42 | run: |- 43 | echo "@your-org:registry=https://npm.pkg.github.com" >> .npmrc 44 | echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc 45 | - name: Install Dependencies 46 | run: pnpm install --frozen-lockfile 47 | - name: Run Tests 48 | run: pnpm run test 49 | Build: 50 | runs-on: ubuntu-latest 51 | timeout-minutes: 10 52 | needs: 53 | - Test 54 | steps: 55 | - name: Checkout 56 | uses: actions/checkout@v3 57 | - name: Install pnpm 58 | uses: pnpm/action-setup@v4 59 | id: pnpm-install 60 | with: 61 | version: 8 62 | run_install: 'false' 63 | - name: Get pnpm store directory 64 | id: pnpm-cache 65 | shell: bash 66 | run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 67 | - name: Setup pnpm cache 68 | uses: actions/cache@v3 69 | with: 70 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 71 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles("**/pnpm-lock.yaml") }} 72 | restore-keys: ${{ runner.os }}-pnpm-store- 73 | - name: Setup npmrc for Private Packages 74 | run: |- 75 | echo "@your-org:registry=https://npm.pkg.github.com" >> .npmrc 76 | echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc 77 | - name: Install Dependencies 78 | run: pnpm install --frozen-lockfile 79 | - name: Build 80 | run: pnpm run build 81 | env: 82 | VITE_APP_VERSION: ${{ env.GITHUB_SHA }} 83 | Deploy: 84 | runs-on: ubuntu-latest 85 | timeout-minutes: 10 86 | environment: ${{ github.event_name == 'release' && prod || dev }} 87 | permissions: 88 | contents: read 89 | id-token: write 90 | needs: 91 | - Build 92 | steps: 93 | - name: Checkout 94 | uses: actions/checkout@v3 95 | - name: Install pnpm 96 | uses: pnpm/action-setup@v4 97 | id: pnpm-install 98 | with: 99 | version: 8 100 | run_install: 'false' 101 | - name: Get pnpm store directory 102 | id: pnpm-cache 103 | shell: bash 104 | run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 105 | - name: Setup pnpm cache 106 | uses: actions/cache@v3 107 | with: 108 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 109 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles("**/pnpm-lock.yaml") }} 110 | restore-keys: ${{ runner.os }}-pnpm-store- 111 | - name: Setup npmrc for Private Packages 112 | run: |- 113 | echo "@your-org:registry=https://npm.pkg.github.com" >> .npmrc 114 | echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc 115 | - name: Install Dependencies 116 | run: pnpm install --frozen-lockfile 117 | - name: Deploy 118 | run: pnpm run deploy 119 | - name: Multi line yaml with special characters 120 | run: |- 121 | content="${content//$'\n'/'%0A'}" 122 | content="${content//$'\r'/'%0D'}" 123 | ReleaseJob: 124 | uses: your-org/your-repo/.github/workflows/reusable-workflow.yml@main 125 | secrets: inherit 126 | -------------------------------------------------------------------------------- /.github/workflows/draft.yml: -------------------------------------------------------------------------------- 1 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 2 | # This file was automatically generated by github-actions-workflow-ts. 3 | # Instead, modify workflows/draft.wac.ts 4 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 5 | 6 | name: Draft Release 7 | 'on': 8 | push: 9 | branches: 10 | - main 11 | pull_request: 12 | types: 13 | - opened 14 | - reopened 15 | - synchronize 16 | permissions: 17 | contents: read 18 | jobs: 19 | UpdateReleaseDraft: 20 | runs-on: ubuntu-latest 21 | timeout-minutes: 20 22 | permissions: 23 | contents: write 24 | steps: 25 | - name: Draft next release 26 | uses: release-drafter/release-drafter@v5 27 | with: 28 | commitish: main 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | -------------------------------------------------------------------------------- /.github/workflows/mock-test.yml: -------------------------------------------------------------------------------- 1 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 2 | # This file was automatically generated by github-actions-workflow-ts. 3 | # Instead, modify src/cli/commands/__mocks__/test.wac.ts 4 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 5 | 6 | name: ExampleMockTests 7 | 'on': 8 | workflow_dispatch: {} 9 | jobs: 10 | Test: 11 | runs-on: 12 | - self-hosted 13 | - linux 14 | - x64 15 | - gpu 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | - name: Install Node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 18 23 | - name: Install pnpm 24 | uses: pnpm/action-setup@v4 25 | with: 26 | version: 8 27 | - name: Install Dependencies 28 | run: pnpm install --no-frozen-lockfile 29 | - name: Run Tests 30 | run: pnpm test 31 | -------------------------------------------------------------------------------- /.github/workflows/pr-labeler.yml: -------------------------------------------------------------------------------- 1 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 2 | # This file was automatically generated by github-actions-workflow-ts. 3 | # Instead, modify workflows/pr-labeler.wac.ts 4 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 5 | 6 | name: PR Labeler 7 | 'on': 8 | pull_request: 9 | types: 10 | - opened 11 | - reopened 12 | - synchronize 13 | permissions: 14 | contents: read 15 | jobs: 16 | PrLabeler: 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 20 19 | permissions: 20 | contents: read 21 | pull-requests: write 22 | steps: 23 | - name: Apply label to a PR 24 | uses: TimonVS/pr-labeler-action@v4 25 | with: 26 | repo-token: ${{ secrets.GITHUB_TOKEN }} 27 | configuration-path: .github/pr-labeler.yml 28 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 2 | # This file was automatically generated by github-actions-workflow-ts. 3 | # Instead, modify workflows/publish.wac.ts 4 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 5 | 6 | name: Publish Release 7 | 'on': 8 | release: 9 | types: 10 | - published 11 | jobs: 12 | PublishZeroDependencyPackage: 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 20 15 | permissions: 16 | contents: write 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | with: 21 | ref: ${{ github.event.release.target_commitish }} 22 | - name: Install Node 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: 18 26 | - name: Install pnpm 27 | uses: pnpm/action-setup@v4 28 | with: 29 | version: 8 30 | - name: Install tsx 31 | run: npm install -g tsx 32 | - name: Bump Version 33 | run: |- 34 | git config user.name github-actions 35 | git config user.email github-actions@github.com 36 | echo version: ${{ github.event.release.tag_name }} 37 | npm version --no-git-tag-version ${{ github.event.release.tag_name }} 38 | - name: Create Zero Dependency Package 39 | run: tsx scripts/generateZeroDependencyPackage.ts 40 | - name: Build Zero Dependency Package 41 | working-directory: github-actions-workflow-ts-lib 42 | run: npm run build 43 | - name: Publish to npm 44 | working-directory: github-actions-workflow-ts-lib 45 | run: |- 46 | echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> .npmrc 47 | npm publish 48 | env: 49 | NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 50 | PublishPackage: 51 | runs-on: ubuntu-latest 52 | timeout-minutes: 20 53 | permissions: 54 | contents: write 55 | steps: 56 | - name: Checkout 57 | uses: actions/checkout@v4 58 | with: 59 | ref: ${{ github.event.release.target_commitish }} 60 | - name: Install Node 61 | uses: actions/setup-node@v4 62 | with: 63 | node-version: 18 64 | - name: Install pnpm 65 | uses: pnpm/action-setup@v4 66 | with: 67 | version: 8 68 | - name: Install tsx 69 | run: npm install -g tsx 70 | - name: Install Dependencies 71 | run: pnpm install --no-frozen-lockfile 72 | - name: Run Build 73 | run: pnpm build 74 | - name: Bump Version 75 | run: |- 76 | git config user.name github-actions 77 | git config user.email github-actions@github.com 78 | echo version: ${{ github.event.release.tag_name }} 79 | npm version --no-git-tag-version ${{ github.event.release.tag_name }} 80 | - name: Publish to npm 81 | run: |- 82 | echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> .npmrc 83 | npm publish 84 | env: 85 | NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 86 | CommitVersionBump: 87 | runs-on: ubuntu-latest 88 | timeout-minutes: 20 89 | needs: 90 | - PublishPackage 91 | - PublishZeroDependencyPackage 92 | permissions: 93 | contents: write 94 | steps: 95 | - name: Checkout 96 | uses: actions/checkout@v4 97 | with: 98 | ref: main 99 | - name: Push updates to main branch 100 | shell: bash 101 | run: |- 102 | git config user.name github-actions 103 | git config user.email github-actions@github.com 104 | echo version: ${{ github.event.release.tag_name }} 105 | npm version --no-git-tag-version ${{ github.event.release.tag_name }} 106 | git add . 107 | git commit -m "new release: ${{ github.event.release.tag_name }} 🚀 [skip ci]" --no-verify 108 | git push origin HEAD:main 109 | -------------------------------------------------------------------------------- /.github/workflows/schema-change-check.yml: -------------------------------------------------------------------------------- 1 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 2 | # This file was automatically generated by github-actions-workflow-ts. 3 | # Instead, modify workflows/schema-change-check.wac.ts 4 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 5 | 6 | name: Schema Change Check 7 | 'on': 8 | pull_request: 9 | types: 10 | - opened 11 | - reopened 12 | - synchronize 13 | schedule: 14 | - cron: 0 0 * * * 15 | jobs: 16 | SchemaChangeCheck: 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: write 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | - name: Install Node 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: 20 27 | - name: Install tsx 28 | run: npm install -g tsx 29 | - name: Install pnpm 30 | uses: pnpm/action-setup@v4 31 | with: 32 | version: 8 33 | - name: Install Dependencies 34 | run: pnpm install --no-frozen-lockfile 35 | - name: Generate Workflow Types 36 | run: pnpm generate-workflow-types 37 | - name: Get git diff 38 | run: git diff -- ':!pnpm-lock.yaml' 39 | - name: Fail if git diff is not empty 40 | run: |- 41 | if test -z "$(git diff --name-only -- ':!pnpm-lock.yaml')"; then 42 | echo "No file changes detected." 43 | exit 0 44 | else 45 | echo "File changes detected." 46 | git diff -- ':!pnpm-lock.yaml' 47 | exit 1 48 | fi 49 | -------------------------------------------------------------------------------- /.github/workflows/simple-1.yml: -------------------------------------------------------------------------------- 1 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 2 | # This file was automatically generated by github-actions-workflow-ts. 3 | # Instead, modify examples/simple.wac.ts 4 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 5 | 6 | name: ExampleSimpleWorkflow 7 | 'on': 8 | workflow_dispatch: {} 9 | jobs: 10 | firstJob: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 5 13 | steps: 14 | - name: Setup Node 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: 18.x 18 | - name: Echo 19 | run: echo "Hello, World!" 20 | -------------------------------------------------------------------------------- /.github/workflows/simple-2.yml: -------------------------------------------------------------------------------- 1 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 2 | # This file was automatically generated by github-actions-workflow-ts. 3 | # Instead, modify examples/simple.wac.ts 4 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 5 | 6 | name: ExampleSimpleWorkflowJSON 7 | 'on': 8 | workflow_dispatch: {} 9 | jobs: 10 | firstJob: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 5 13 | steps: 14 | - name: Echo 15 | run: echo "Hello, World!" 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | with: 19 | ref: dev 20 | secondJob: 21 | runs-on: ubuntu-latest 22 | timeout-minutes: 5 23 | steps: 24 | - name: Echo 25 | run: echo "Hello, World!" 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | with: 29 | ref: dev 30 | - name: Echo Event Name 31 | run: echo ${{ github.event_name }} 32 | - name: Comment on Issue 33 | run: gh issue comment $ISSUE --body "Thank you for opening this issue!" 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | ISSUE: ${{ env.ISSUE }} 37 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 2 | # This file was automatically generated by github-actions-workflow-ts. 3 | # Instead, modify workflows/test.wac.ts 4 | # ------------DO-NOT-MODIFY-THIS-FILE------------ 5 | 6 | name: Tests 7 | 'on': 8 | pull_request: 9 | branches: 10 | - main 11 | push: 12 | branches: 13 | - main 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: write 19 | strategy: 20 | matrix: 21 | node: 22 | - 16 23 | - 18 24 | - 20 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | - name: Install Node 29 | uses: actions/setup-node@v3 30 | with: 31 | node-version: ${{ matrix.node }} 32 | - name: Install pnpm 33 | uses: pnpm/action-setup@v4 34 | with: 35 | version: 8 36 | - name: Install Dependencies 37 | run: pnpm install --no-frozen-lockfile 38 | - name: Run Tests 39 | run: pnpm test 40 | - name: Update Code Coverage Badge 41 | if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && matrix.node == 20 42 | uses: we-cli/coverage-badge-action@48a2699b2e537c7519bdc970fb0ecd75c80a698e 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | coverage 4 | .npmrc 5 | TODO.md 6 | github-actions-workflow-ts-lib -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm dlx commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm run test && pnpm run gwf build && git add .github/workflows/*.yml && pnpm dlx lint-staged --allow-empty 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "useTabs": false, 5 | "tabWidth": 2 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Adrian Smijulj 4 | Copyright (c) 2023 Emmanuel N Kyeyune 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # github-actions-workflow-ts 2 | Stop writing workflows in YAML and use TypeScript instead! 3 | 4 |

github-actions-workflow-ts-logo

5 | 6 |

7 | 8 | 9 | love opensource 10 | 11 | 12 | license 13 | 14 | 15 | npm version 16 | 17 | 18 | Tests 19 | 20 | 21 | coverage 22 | 23 | 24 | issues 25 | 26 |

27 | 28 | ## Table of Contents 29 | - [github-actions-workflow-ts](#github-actions-workflow-ts) 30 | - [Table of Contents](#table-of-contents) 31 | - [Installation](#installation) 32 | - [Overview](#overview) 33 | - [Key Benefits:](#key-benefits) 34 | - [Getting Started:](#getting-started) 35 | - [Examples](#examples) 36 | - [Try it out on Replit](#try-it-out-on-replit) 37 | - [More Examples](#more-examples) 38 | - [Generating Workflow YAML](#generating-workflow-yaml) 39 | - [Using the CLI](#using-the-cli) 40 | - [Integration with Husky (recommended)](#integration-with-husky-recommended) 41 | - [Config file](#config-file) 42 | - [Workflow Classes](#workflow-classes) 43 | - [`new Step()`](#new-step) 44 | - [`.addEnvs()`](#addenvs) 45 | - [`new NormalJob()`](#new-normaljob) 46 | - [`.addEnvs()`](#addenvs-1) 47 | - [`.addStep()`](#addstep) 48 | - [`.addSteps()`](#addsteps) 49 | - [`.needs()`](#needs) 50 | - [`new ReusableWorkflowCallJob()`](#new-reusableworkflowcalljob) 51 | - [`.needs()`](#needs-1) 52 | - [`new Workflow()`](#new-workflow) 53 | - [`.addEnvs()`](#addenvs-2) 54 | - [`.addJob()`](#addjob) 55 | - [`.addJobs()`](#addjobs) 56 | - [Workflow Types](#workflow-types) 57 | - [GeneratedWorkflowTypes](#generatedworkflowtypes) 58 | - [ExtendedWorkflowTypes](#extendedworkflowtypes) 59 | - [Helpers](#helpers) 60 | - [`multilineString()`](#multilinestring) 61 | - [`expressions`](#expressions) 62 | - [`.expn()`](#expn) 63 | - [`.env()`](#env) 64 | - [`.secret()`](#secret) 65 | - [`.var()`](#var) 66 | - [`.ternary()`](#ternary) 67 | - [`echoKeyValue`](#echokeyvalue) 68 | - [`.to()`](#to) 69 | - [`.toGithubEnv()`](#togithubenv) 70 | - [`.toGithubOutput()`](#togithuboutput) 71 | - [Contributing](#contributing) 72 | - [Credits](#credits) 73 | 74 | ## Installation 75 | 76 | ``` 77 | npm install --save-dev github-actions-workflow-ts 78 | ``` 79 | 80 | Or to use the [zero dependency no-cli package](https://www.npmjs.com/package/github-actions-workflow-ts-lib) (if you only want to generate a workflow JSON object and use it in something like [projen](https://github.com/projen/projen)): 81 | 82 | ``` 83 | npm install --save-dev github-actions-workflow-ts-lib 84 | ``` 85 | 86 | ## Overview 87 | 88 | Introducing `github-actions-workflow-ts`: A seamless integration allowing developers to author GitHub Actions workflows with the power and flexibility of TypeScript. 89 | 90 | ### Key Benefits: 91 | 92 | 1. **Type Safety**: Elevate the confidence in your workflows with the robust type-checking capabilities of TypeScript. 93 | 2. **Modularity**: Efficiently package and reuse common jobs and steps across various workflows, promoting the DRY (Don't Repeat Yourself) principle. 94 | 3. **Control Flow**: Harness the inherent control flow mechanisms, like conditionals, available in imperative languages. This empowers developers to craft intricate workflows beyond the constraints of YAML. 95 | 96 | ### Getting Started: 97 | To embark on this efficient journey, create a new `*.wac.ts` file, for instance, `deploy.wac.ts`, in your project directory. Then, dive into authoring your enhanced GitHub Actions workflows! 98 | 99 | ## Examples 100 | ### Try it out on Replit 101 | Want to quickly see it in action? Explore these Replit examples (create a free account to fork and modify my examples): 102 | - [Simple Example](https://replit.com/@EmmanuelKyeyune/github-actions-workflow-ts-example#workflows/simple.example.wac.ts) 103 | - [Advanced Example](https://replit.com/@EmmanuelKyeyune/github-actions-workflow-ts-example#src/workflows/advanced.example.wac.ts) 104 | 105 | ### More Examples 106 | Check the [examples folder](./examples/) and the [workflows folder](./workflows/) for more advanced examples. 107 | 108 | Below is a simple example: 109 | ```ts 110 | // example.wac.ts 111 | 112 | import { Workflow, NormalJob, Step } from 'github-actions-workflow-ts' 113 | 114 | const checkoutStep = new Step({ 115 | name: 'Checkout', 116 | uses: 'actions/checkout@v3', 117 | }) 118 | 119 | const testJob = new NormalJob('Test', { 120 | 'runs-on': 'ubuntu-latest', 121 | 'timeout-minutes': 2 122 | }) 123 | 124 | // IMPORTANT - the instance of Workflow MUST be exported with `export` 125 | export const exampleWorkflow = new Workflow('example-filename', { 126 | name: 'Example', 127 | on: { 128 | workflow_dispatch: {} 129 | } 130 | }) 131 | 132 | // add the defined step to the defined job 133 | testJob.addStep(checkoutStep) 134 | 135 | // add the defined job to the defined workflow 136 | exampleWorkflow.addJob(testJob) 137 | ``` 138 | If you want to use the zero dependency package, you can do this: 139 | ```ts 140 | import { Workflow, NormalJob, Step } from 'github-actions-workflow-ts-lib' 141 | 142 | const checkoutStep = new Step({ 143 | name: 'Checkout', 144 | uses: 'actions/checkout@v3', 145 | }) 146 | 147 | const testJob = new NormalJob('Test', { 148 | 'runs-on': 'ubuntu-latest', 149 | 'timeout-minutes': 2 150 | }) 151 | 152 | const exampleWorkflow = new Workflow('example-filename', { 153 | name: 'Example', 154 | on: { 155 | workflow_dispatch: {} 156 | } 157 | }) 158 | 159 | // the generated workflow object to be used in other parts of your project 160 | // without conversion to YAML 161 | console.log(exampleWorkflow.workflow) 162 | ``` 163 | 164 | ## Generating Workflow YAML 165 | ### Using the CLI 166 | When you have written your `*.wac.ts` file, you use the `github-actions-workflow-ts` CLI to generate the yaml files. 167 | 168 | **Don't forget** to **export** the workflows that you want to generate in your `*.wac.ts` files i.e. 169 | ```ts 170 | // exporting `exampleWorkflow` will generate example-filename.yml 171 | export const exampleWorkflow = new Workflow('example-filename', { /***/ }) 172 | ``` 173 | Then, from project root, run: 174 | ```bash 175 | npx generate-workflow-files build 176 | 177 | # OR 178 | 179 | npx gwf build 180 | ``` 181 | 182 | ### Integration with Husky (recommended) 183 | For seamless automation and to eliminate the possibility of overlooking updates in `*.wac.ts` files, integrating with a pre-commit tool is recommended. We recommend [husky](https://github.com/typicode/husky). With Husky, each commit triggers the `npx github-actions-workflow-ts build` command, ensuring that your GitHub Actions YAML files consistently reflect the latest modifications. 184 | 185 |
See more 186 | 187 | - Install Husky: 188 | ```bash 189 | npm install --save-dev husky 190 | npx husky-init 191 | ``` 192 | - In `package.json`, add the following script: 193 | ```json 194 | "scripts": { 195 | "build:workflows": "npx gwf build && git add .github/workflows/*.yml", 196 | } 197 | ``` 198 | - Install the `pre-commit` command to Husky and add our npm command to build the `*.wac.ts` files 199 | ```bash 200 | npx husky add .husky/pre-commit "npm run build:workflows" 201 | ``` 202 | - Now every time you make a change to `*.wac.ts`, Husky will run the `npx gwf build` command and add the generated `.github/workflows/*.yml` to your commit 203 |
204 | 205 | ### Config file 206 | If you want to change how github-actions-workflow-ts generates the yaml files, you can create a `wac.config.json` file in your project root. See the [example config file](./wac.config.json) 207 | 208 |
See config options 209 | 210 | | Property | Description | Type | Default Value | 211 | |------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 212 | | refs | If true, convert duplicate objects into references in YAML | `Boolean` | false | 213 | | headerText | Replace the header text in generated YAML files with your own text.
If you want the source filename and path in the text, use `` in
the text and it will be replaced with the path to the source-file. | `Array` | # ----DO-NOT-MODIFY-THIS-FILE----
# This file was automatically generated by github-actions-workflow-ts.
# Instead, modify ``
# ----DO-NOT-MODIFY-THIS-FILE---- | 214 | | dumpOptions | Options for the dump function of js-yaml. See [all the options here](https://github.com/nodeca/js-yaml#dump-object---options-) | Record | Uses the default options | 215 | 216 |
217 | 218 | ## Workflow Classes 219 | ### `new Step()` 220 | The building block of every `NormalJob`. Contains instructions on what to run in your Github Actions Runner in each job. 221 |
Example 222 | 223 | ```ts 224 | import { Step } from 'github-actions-workflow-ts' 225 | 226 | const checkoutStep = new Step({ 227 | name: 'Checkout', 228 | uses: 'actions/checkout@v3', 229 | }) 230 | ``` 231 |
232 | 233 | #### `.addEnvs()` 234 | This adds environment variables to a step. 235 |
Example 236 | 237 | ```ts 238 | import { Step } from 'github-actions-workflow-ts' 239 | 240 | const checkoutStep = new Step({ 241 | name: 'Checkout', 242 | uses: 'actions/checkout@v3', 243 | }).addEnvs({ 244 | SOME_KEY: 'some-value', 245 | SOME_OTHER_KEY: 'some-other-value' 246 | }) 247 | ``` 248 |
249 | 250 | ### `new NormalJob()` 251 | The most typical job that contains steps. 252 | 253 | #### `.addEnvs()` 254 | This adds environment variables to a job. 255 |
Example 256 | 257 | ```ts 258 | import { NormalJob } from 'github-actions-workflow-ts' 259 | 260 | const testJob = new NormalJob('Test', { 261 | 'runs-on': 'ubuntu-latest', 262 | 'timeout-minutes': 2 263 | }).addEnvs({ 264 | SOME_KEY: 'some-value', 265 | SOME_OTHER_KEY: 'some-other-value' 266 | }) 267 | ``` 268 |
269 | 270 | #### `.addStep()` 271 | This adds a single step to a normal Job 272 | 273 |
Example 274 | 275 | ```ts 276 | import { Workflow, NormalJob, Step } from 'github-actions-workflow-ts' 277 | 278 | const checkoutStep = new Step({ 279 | name: 'Checkout', 280 | uses: 'actions/checkout@v3', 281 | }) 282 | 283 | const testJob = new NormalJob('Test', { 284 | 'runs-on': 'ubuntu-latest', 285 | 'timeout-minutes': 2 286 | }) 287 | 288 | testJob.addStep(checkoutStep) 289 | ``` 290 |
291 | 292 | #### `.addSteps()` 293 | This adds multiple steps to a normal Job 294 | 295 |
Example 296 | 297 | ```ts 298 | import { Workflow, NormalJob, Step } from 'github-actions-workflow-ts' 299 | 300 | const checkoutStep = new Step({ 301 | name: 'Checkout', 302 | uses: 'actions/checkout@v3', 303 | }) 304 | 305 | const installNodeStep = new Step({ 306 | name: 'Install Node', 307 | uses: 'actions/setup-node@v3', 308 | with: { 309 | 'node-version': 18 310 | } 311 | }) 312 | 313 | const testJob = new NormalJob('Test', { 314 | 'runs-on': 'ubuntu-latest', 315 | 'timeout-minutes': 2 316 | }) 317 | 318 | testJob.addSteps([ 319 | checkoutStep, 320 | installNodeStep 321 | ]) 322 | ``` 323 |
324 | 325 | #### `.needs()` 326 | This adds any jobs that the current job depends on to the current job's `needs` property 327 | 328 |
Example 329 | 330 | ```ts 331 | import { Workflow, NormalJob, Step } from 'github-actions-workflow-ts' 332 | 333 | const checkoutStep = new Step({ 334 | name: 'Checkout', 335 | uses: 'actions/checkout@v3', 336 | }) 337 | 338 | const testJob = new NormalJob('Test', { 339 | 'runs-on': 'ubuntu-latest', 340 | 'timeout-minutes': 2 341 | }) 342 | 343 | const buildJob = new NormalJob('Build', { 344 | 'runs-on': 'ubuntu-latest', 345 | 'timeout-minutes': 2 346 | }) 347 | 348 | 349 | testJob.addStep(checkoutStep) 350 | buildJob 351 | .needs([testJob]) 352 | .addStep(checkoutStep) 353 | 354 | export const exampleWorkflow = new Workflow('example-filename', { 355 | name: 'Example', 356 | on: { 357 | workflow_dispatch: {} 358 | } 359 | }) 360 | 361 | exampleWorkflow.addJobs([ 362 | testJob, 363 | buildJob 364 | ]) 365 | ``` 366 |
367 | 368 | ### `new ReusableWorkflowCallJob()` 369 | A job that allows you to call another workflow and use it in the same run. 370 | 371 |
Example 372 | 373 | ```ts 374 | import { Workflow, ReusableWorkflowCallJob } from 'github-actions-workflow-ts' 375 | 376 | const releaseJob = new ReusableWorkflowCallJob('ReleaseJob', { 377 | uses: 'your-org/your-repo/.github/workflows/reusable-workflow.yml@main', 378 | secrets: 'inherit', 379 | }) 380 | 381 | export const exampleWorkflow = new Workflow('example-filename', { 382 | name: 'Example', 383 | on: { 384 | workflow_dispatch: {} 385 | } 386 | }).addJob(releaseJob) 387 | ``` 388 |
389 | 390 | #### `.needs()` 391 | Same as [NormalJob.needs()](#needs) 392 | 393 | ### `new Workflow()` 394 | #### `.addEnvs()` 395 | This adds environment variables to a workflow. 396 |
Example 397 | 398 | ```ts 399 | import { Workflow } from 'github-actions-workflow-ts' 400 | 401 | export const exampleWorkflow = new Workflow('example-filename', { 402 | name: 'Example', 403 | on: { 404 | workflow_dispatch: {} 405 | } 406 | }).addEnvs({ 407 | SOME_KEY: 'some-value', 408 | SOME_OTHER_KEY: 'some-other-value' 409 | }) 410 | ``` 411 |
412 | 413 | #### `.addJob()` 414 | This adds a single job to a Workflow 415 | 416 |
Example 417 | 418 | ```ts 419 | import { Workflow, NormalJob, Step } from 'github-actions-workflow-ts' 420 | 421 | const checkoutStep = new Step({ 422 | name: 'Checkout', 423 | uses: 'actions/checkout@v3', 424 | }) 425 | 426 | const testJob = new NormalJob('Test', { 427 | 'runs-on': 'ubuntu-latest', 428 | 'timeout-minutes': 2 429 | }) 430 | 431 | testJob.addStep([checkoutStep]) 432 | 433 | export const exampleWorkflow = new Workflow('example-filename', { 434 | name: 'Example', 435 | on: { 436 | workflow_dispatch: {} 437 | } 438 | }) 439 | 440 | exampleWorkflow.addJob(testJob) 441 | ``` 442 |
443 | 444 | #### `.addJobs()` 445 | This adds multiple jobs to a Workflow 446 | 447 |
Example 448 | 449 | ```ts 450 | import { Workflow, NormalJob, Step } from 'github-actions-workflow-ts' 451 | 452 | const checkoutStep = new Step({ 453 | name: 'Checkout', 454 | uses: 'actions/checkout@v3', 455 | }) 456 | 457 | const testJob = new NormalJob('Test', { 458 | 'runs-on': 'ubuntu-latest', 459 | 'timeout-minutes': 2 460 | }) 461 | 462 | const buildJob = new NormalJob('Build', { 463 | 'runs-on': 'ubuntu-latest', 464 | 'timeout-minutes': 2 465 | }) 466 | 467 | 468 | testJob.addStep(checkoutStep) 469 | buildJob.addStep(checkoutStep) 470 | 471 | export const exampleWorkflow = new Workflow('example-filename', { 472 | name: 'Example', 473 | on: { 474 | workflow_dispatch: {} 475 | } 476 | }) 477 | 478 | exampleWorkflow.addJobs([ 479 | testJob, 480 | buildJob 481 | ]) 482 | ``` 483 |
484 | 485 | ## Workflow Types 486 | You can also choose not to use the workflow helpers and just use plain old JSON. You get type safety by importing the types. The only exception is the `Workflow` class. You must export an instance of this class in order to generate your workflow files. 487 | 488 | ### GeneratedWorkflowTypes 489 | These are types generated right out of the [Github Actions Workflow JSON Schema](https://json.schemastore.org/github-workflow.json) 490 | 491 | ### ExtendedWorkflowTypes 492 | These are types that I extended myself because they weren't autogenerated from the JSON Schema. 493 | 494 |
Example 495 | 496 | ```ts 497 | import { 498 | Workflow, 499 | NormalJob, 500 | Step, 501 | expressions as ex, 502 | GeneratedWorkflowTypes as GWT, // all types generated from the official Github Actions Workflow JSON Schema 503 | } from '../src' 504 | 505 | const nodeSetupStep: GWT.Step = { 506 | name: 'Setup Node', 507 | uses: 'actions/setup-node@v3', 508 | with: { 509 | 'node-version': '18.x', 510 | }, 511 | } 512 | 513 | const firstNormalJob: GWT.NormalJob = { 514 | 'runs-on': 'ubuntu-latest', 515 | 'timeout-minutes': 5, 516 | steps: [ 517 | nodeSetupStep, 518 | { 519 | name: 'Echo', 520 | run: 'echo "Hello, World!"', 521 | }, 522 | ], 523 | } 524 | 525 | export const simpleWorkflowOne = new Workflow('simple-1', { 526 | name: 'ExampleSimpleWorkflow', 527 | on: { 528 | workflow_dispatch: {}, 529 | }, 530 | jobs: { 531 | firstJob: firstNormalJob, 532 | }, 533 | }) 534 | ``` 535 |
536 | 537 | ## Helpers 538 | ### `multilineString()` 539 | This is a useful function that aids in writing multiline yaml like this: 540 | ```yaml 541 | name: Run something 542 | run: |- 543 | command exec line 1 544 | command exec line 2 545 | ``` 546 | 547 |
Examples 548 | 549 | Example 1 550 | ```ts 551 | import { multilineString } from 'github-actions-workflow-ts' 552 | 553 | // multilineString(...strings) joins all strings with a newline 554 | // character '\n' which is interpreted as separate lines in YAML 555 | console.log(multilineString('This is sentence 1', 'This is sentence 2')) 556 | // 'This is sentence 1\nThis is sentence 2' 557 | 558 | // it also has the ability to escape special characters 559 | console.log( 560 | multilineString( 561 | `content="\${content//$'\n'/'%0A'}"`, 562 | `content="\${content//$'\r'/'%0D'}"` 563 | ) 564 | ) 565 | // `content="${content//$'\n'/'%0A'}"` 566 | // `content="${content//$'\r'/'%0D'}"`` 567 | ``` 568 | Example 2 - handling multiline string indentation 569 | If you want to do something like this 570 | ```yaml 571 | - name: Check for build directory 572 | run: |- 573 | #!/bin/bash 574 | ls /tmp 575 | if [ ! -d "/tmp/build" ]; then 576 | mv /tmp/build . 577 | ls 578 | fi 579 | ``` 580 | then you just add the same indentation in the string: 581 | ```ts 582 | // If you want indentation then you can do this: 583 | new Step({ 584 | name: 'Check for build directory', 585 | run: multilineString( 586 | `#!/bin/bash`, 587 | `ls /tmp`, 588 | `if [ ! -d "/tmp/build" ]; then`, 589 | ` mv /tmp/build .`, // notice the two spaces before 'mv ..' 590 | ` ls`, // notice the two spaces before 'ls ..' 591 | `fi`, 592 | ), 593 | }); 594 | ``` 595 |
596 | 597 | ### `expressions` 598 | #### `.expn()` 599 | Returns the expression string `${{ }}` 600 |
Example 601 | 602 | ```ts 603 | import { expressions } from 'github-actions-workflow-ts' 604 | 605 | console.log(expressions.expn('hashFiles("**/pnpm-lock.yaml")')) 606 | // '${{ hashFiles("**/pnpm-lock.yaml") }}' 607 | ``` 608 |
609 | 610 | #### `.env()` 611 | Returns the expression string `${{ env.SOMETHING }}` 612 |
Example 613 | 614 | ```ts 615 | import { expressions } from 'github-actions-workflow-ts' 616 | 617 | console.log(expressions.env('GITHUB_SHA')) 618 | // '${{ env.GITHUB_SHA }}' 619 | ``` 620 |
621 | 622 | #### `.secret()` 623 | Returns the expression string `${{ secrets.SOMETHING }}` 624 |
Example 625 | 626 | ```ts 627 | import { expressions } from 'github-actions-workflow-ts' 628 | 629 | console.log(expressions.secret('GITHUB_TOKEN')) 630 | // '${{ secrets.GITHUB_TOKEN }}' 631 | ``` 632 |
633 | 634 | #### `.var()` 635 | Returns the expression string `${{ vars.SOMETHING }}` 636 |
Example 637 | 638 | ```ts 639 | import { expressions } from 'github-actions-workflow-ts' 640 | 641 | console.log(expressions.var('SENTRY_APP_ID')) 642 | // '${{ vars.SENTRY_APP_ID }}' 643 | ``` 644 |
645 | 646 | #### `.ternary()` 647 |
Example 648 | 649 | ```ts 650 | import { expressions } from 'github-actions-workflow-ts' 651 | 652 | // ternary(condition, ifTrue, ifFalse) 653 | console.log(expressions.ternary("github.event_name == 'release'", 'prod', 'dev')) 654 | // '${{ github.event_name == 'release' && 'prod' || 'dev' }}' 655 | ``` 656 |
657 | 658 | ### `echoKeyValue` 659 | #### `.to()` 660 | Returns the string `echo "key=value" >> ` 661 |
Example 662 | 663 | ```ts 664 | import { echoKeyValue } from 'github-actions-workflow-ts' 665 | 666 | // echoKeyValue.to(key, value, to) returns 'echo "key=value" >> ' 667 | echoKeyValue.to('@your-org:registry', 'https://npm.pkg.github.com', '.npmrc') 668 | // 'echo "@your-org:registry=https://npm.pkg.github.com" >> .npmrc' 669 | ``` 670 |
671 | 672 | #### `.toGithubEnv()` 673 | Returns the string `echo "key=value" >> $GITHUB_ENV` 674 |
Example 675 | 676 | ```ts 677 | import { echoKeyValue } from 'github-actions-workflow-ts' 678 | 679 | // echoKeyValue.toGithubEnv(key, value, to) returns 'echo "key=value" >> $GITHUB_ENV' 680 | echoKeyValue.toGithubEnv('NODE_VERSION', '18') 681 | // 'echo "NODE_VERSION=18" >> $GITHUB_ENV' 682 | ``` 683 |
684 | 685 | #### `.toGithubOutput()` 686 | Returns the string `echo "key=value" >> $GITHUB_OUTPUT` 687 |
Example 688 | 689 | ```ts 690 | import { echoKeyValue } from 'github-actions-workflow-ts' 691 | 692 | // echoKeyValue.toGithubOutput(key, value, to) returns 'echo "key=value" >> $GITHUB_OUTPUT' 693 | echoKeyValue.toGithubOutput('NODE_VERSION', '18') 694 | // 'echo "NODE_VERSION=18" >> $GITHUB_OUTPUT' 695 | ``` 696 |
697 | 698 | ## Contributing 699 | See the [Contributing Guide](./.github/CONTRIBUTING.md) 700 | 701 | ## Credits 702 | Inspired by [webiny/github-actions-wac](https://github.com/webiny/github-actions-wac) which is also the original source of the filename extension (`.wac.ts`) used to distinguish the Github Actions YAML workflow TypeScript files. When I hit too many limitations with `github-actions-wac`, I decided to create `github-actions-workflow-ts` to address those limitations and add a lot more functionality. 703 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | ignores: [(commit) => commit.includes('new release:')], 4 | } 5 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | const globals = require('globals'); 2 | const eslint = require('@eslint/js'); 3 | const tseslint = require('typescript-eslint'); 4 | const prettierPlugin = require('eslint-plugin-prettier'); 5 | const eslintPluginJest = require('eslint-plugin-jest'); 6 | const prettier = require('eslint-config-prettier'); 7 | 8 | module.exports = [ 9 | eslint.configs.recommended, 10 | ...tseslint.configs.recommended, 11 | prettier, 12 | { 13 | plugins: { 14 | prettier: prettierPlugin, 15 | jest: eslintPluginJest, 16 | }, 17 | rules: { 18 | 'no-console': 'warn', 19 | 'prettier/prettier': 'error', 20 | }, 21 | languageOptions: { 22 | parserOptions: { 23 | ecmaVersion: 'latest', 24 | sourceType: 'module', 25 | }, 26 | globals: { 27 | ...eslintPluginJest.environments.globals.globals, 28 | ...globals.browser, 29 | ...globals.node, 30 | }, 31 | }, 32 | }, 33 | ]; 34 | -------------------------------------------------------------------------------- /examples/advanced.wac.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Advanced example 3 | * 4 | * This example shows the usage of: 5 | * - expressions helper 6 | * - multilineString helper 7 | * - echoKeyValue helper 8 | * 9 | * IMPORTANT: You have to export the workflow from this file for it to be generated. 10 | */ 11 | import { 12 | Workflow, 13 | NormalJob, 14 | Step, 15 | expressions as ex, 16 | echoKeyValue as echo, 17 | multilineString, 18 | ReusableWorkflowCallJob, 19 | } from '../src' 20 | 21 | // Like the experienced engineer you are, let us start by first defining some common reusable steps 22 | const checkoutStep = new Step({ 23 | name: 'Checkout', 24 | uses: 'actions/checkout@v3', 25 | }) 26 | 27 | const installPnpm = new Step({ 28 | name: 'Install pnpm', 29 | uses: 'pnpm/action-setup@v4', 30 | id: 'pnpm-install', 31 | with: { 32 | version: 8, 33 | run_install: 'false', 34 | }, 35 | }) 36 | 37 | const getPnpmStoreDirectory = new Step({ 38 | name: 'Get pnpm store directory', 39 | id: 'pnpm-cache', 40 | shell: 'bash', 41 | run: echo.toGithubOutput('STORE_PATH', '$(pnpm store path)'), 42 | }) 43 | 44 | const setupPnpmCache = new Step({ 45 | name: 'Setup pnpm cache', 46 | uses: 'actions/cache@v3', 47 | with: { 48 | path: ex.expn('steps.pnpm-cache.outputs.STORE_PATH'), 49 | key: `${ex.expn('runner.os')}-pnpm-store-${ex.expn( 50 | 'hashFiles("**/pnpm-lock.yaml")', 51 | )}`, 52 | 'restore-keys': `${ex.expn('runner.os')}-pnpm-store-`, 53 | }, 54 | }) 55 | 56 | const setupNpmrc = new Step({ 57 | name: 'Setup npmrc for Private Packages', 58 | run: multilineString( 59 | echo.to('@your-org:registry', 'https://npm.pkg.github.com', '.npmrc'), 60 | echo.to( 61 | '//npm.pkg.github.com/:_authToken', 62 | ex.secret('GITHUB_TOKEN'), 63 | '.npmrc', 64 | ), 65 | ), 66 | }) 67 | 68 | const installDependencies = new Step({ 69 | name: 'Install Dependencies', 70 | run: 'pnpm install --frozen-lockfile', 71 | }) 72 | 73 | const sharedSteps = [ 74 | checkoutStep, 75 | installPnpm, 76 | getPnpmStoreDirectory, 77 | setupPnpmCache, 78 | setupNpmrc, 79 | installDependencies, 80 | ] 81 | 82 | // Now that we have some steps, let's define some jobs 83 | const testJob = new NormalJob('Test', { 84 | 'runs-on': 'ubuntu-latest', 85 | 'timeout-minutes': 10, 86 | strategy: { 87 | matrix: { 88 | node: ['12.x', '14.x', '16.x'], 89 | }, 90 | }, 91 | }) 92 | .addSteps(sharedSteps) 93 | .addStep( 94 | new Step({ 95 | name: 'Run Tests', 96 | run: 'pnpm run test', 97 | }), 98 | ) 99 | 100 | const buildJob = new NormalJob('Build', { 101 | 'runs-on': 'ubuntu-latest', 102 | 'timeout-minutes': 10, 103 | needs: [testJob.name], 104 | }) 105 | .addSteps(sharedSteps) 106 | .addStep( 107 | new Step({ 108 | name: 'Build', 109 | run: 'pnpm run build', 110 | env: { 111 | VITE_APP_VERSION: ex.env('GITHUB_SHA'), 112 | }, 113 | }), 114 | ) 115 | 116 | const deployJob = new NormalJob('Deploy', { 117 | 'runs-on': 'ubuntu-latest', 118 | 'timeout-minutes': 10, 119 | environment: ex.ternary("github.event_name == 'release'", 'prod', 'dev'), 120 | permissions: { 121 | contents: 'read', 122 | 'id-token': 'write', 123 | }, 124 | }) 125 | .needs([buildJob]) 126 | .addSteps(sharedSteps) 127 | .addStep( 128 | new Step({ 129 | name: 'Deploy', 130 | run: 'pnpm run deploy', 131 | }), 132 | ) 133 | .addStep( 134 | new Step({ 135 | name: 'Multi line yaml with special characters', 136 | run: multilineString( 137 | `content="\${content//$'\n'/'%0A'}"`, 138 | `content="\${content//$'\r'/'%0D'}"`, 139 | ), 140 | }), 141 | ) 142 | 143 | const releaseJob = new ReusableWorkflowCallJob('ReleaseJob', { 144 | uses: 'your-org/your-repo/.github/workflows/reusable-workflow.yml@main', 145 | secrets: 'inherit', 146 | }) 147 | 148 | export const advancedWorkflow = new Workflow('advanced-1', { 149 | name: 'ExampleAdvanced', 150 | on: { 151 | workflow_dispatch: {}, 152 | }, 153 | }) 154 | .addEnvs({ 155 | SENTRY_APP_NAME: 'ExampleAdvanced', 156 | SENTRY_ORG: 'example-org', 157 | }) 158 | .addJob(testJob) 159 | .addJob(buildJob) 160 | .addJob(deployJob) 161 | .addJob(releaseJob) 162 | -------------------------------------------------------------------------------- /examples/simple.wac.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple Examples 3 | * 4 | * This file will create two workflow files in the .github/workflows directory: 5 | * - simple-1.yml 6 | * - simple-2.yml 7 | * 8 | * IMPORTANT: You have to export the workflow from this file for it to be generated. 9 | */ 10 | 11 | import { 12 | Workflow, 13 | NormalJob, 14 | Step, 15 | expressions as ex, 16 | GeneratedWorkflowTypes as GWT, 17 | } from '../src' 18 | 19 | /** 20 | * Example 1 21 | * 22 | * You can use just objects to define the entire workflow and type safety with 23 | * the help of the GeneratedWorkflowTypes types 24 | */ 25 | const nodeSetupStep: GWT.Step = { 26 | name: 'Setup Node', 27 | uses: 'actions/setup-node@v3', 28 | with: { 29 | 'node-version': '18.x', 30 | }, 31 | } 32 | 33 | const firstNormalJob: GWT.NormalJob = { 34 | 'runs-on': 'ubuntu-latest', 35 | 'timeout-minutes': 5, 36 | steps: [ 37 | nodeSetupStep, 38 | { 39 | name: 'Echo', 40 | run: 'echo "Hello, World!"', 41 | }, 42 | ], 43 | } 44 | 45 | export const simpleWorkflowOne = new Workflow('simple-1', { 46 | name: 'ExampleSimpleWorkflow', 47 | on: { 48 | workflow_dispatch: {}, 49 | }, 50 | jobs: { 51 | firstJob: firstNormalJob, 52 | }, 53 | }) 54 | 55 | /** 56 | * Example 2 57 | * 58 | * You can use helper classes too i.e, Step, NormalJob, ReusableWorkflowCallJob etc so 59 | * you don't have to write the entire workflow in one go and make pieces of code more reusable with type 60 | * safety without having to import workflow types 61 | */ 62 | 63 | const echoStep = new Step({ 64 | name: 'Echo', 65 | run: 'echo "Hello, World!"', 66 | }) 67 | 68 | const checkoutStep = new Step({ 69 | name: 'Checkout', 70 | uses: 'actions/checkout@v3', 71 | with: { 72 | ref: 'dev', 73 | }, 74 | }) 75 | 76 | const stepWithVariable = new Step({ 77 | name: 'Echo Event Name', 78 | run: 'echo ${{ github.event_name }}', 79 | }) 80 | 81 | const stepWithOutputHelpers = new Step({ 82 | name: 'Comment on Issue', 83 | run: 'gh issue comment $ISSUE --body "Thank you for opening this issue!"', 84 | env: { 85 | GITHUB_TOKEN: ex.secret('GITHUB_TOKEN'), 86 | ISSUE: ex.env('ISSUE'), 87 | }, 88 | }) 89 | 90 | const firstJob = new NormalJob('firstJob', { 91 | 'runs-on': 'ubuntu-latest', 92 | 'timeout-minutes': 5, 93 | }).addSteps([echoStep, checkoutStep]) 94 | 95 | // Export it to generate it 96 | export const simpleWorkflowTwo = new Workflow('simple-2', { 97 | name: 'ExampleSimpleWorkflowJSON', 98 | on: { workflow_dispatch: {} }, 99 | jobs: { 100 | firstJob: firstJob.job, 101 | }, 102 | }) 103 | 104 | /** 105 | * Example 3 106 | */ 107 | 108 | const secondJob = new NormalJob('secondJob', { 109 | 'runs-on': 'ubuntu-latest', 110 | 'timeout-minutes': 5, 111 | }) 112 | 113 | secondJob 114 | .addStep(echoStep) 115 | .addStep(checkoutStep) 116 | .addStep(stepWithVariable) 117 | .addStep(stepWithOutputHelpers) 118 | 119 | simpleWorkflowTwo.addJob(firstJob).addJob(secondJob) 120 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | collectCoverage: true, 5 | coverageReporters: ['text', 'json-summary'], 6 | collectCoverageFrom: ['src/**/*.{ts,tsx}'], 7 | testPathIgnorePatterns: ['/node_modules/', '/build/'], 8 | modulePathIgnorePatterns: ['.*__mocks__.*'], 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-actions-workflow-ts", 3 | "version": "1.2.1", 4 | "author": "Emmanuel N Kyeyune", 5 | "description": "Generate GitHub Actions workflow files using TypeScript (compiles to YAML)", 6 | "repository": "https://github.com/emmanuelnk/github-actions-workflow-ts", 7 | "main": "build/index", 8 | "types": "build/index", 9 | "files": [ 10 | "build" 11 | ], 12 | "license": "MIT", 13 | "scripts": { 14 | "tsc": "tsc", 15 | "watch": "tsc --watch", 16 | "test": "jest --coverage", 17 | "lint": "pnpm exec eslint src --fix && pnpm exec prettier --config .prettierrc.json --write \"src/**/*.{ts,tsx}\"", 18 | "build": "pnpm run clean && pnpm run compile", 19 | "clean": "rm -rf ./build", 20 | "compile": "tsc -p tsconfig.build.json", 21 | "generate-workflow-types": "pnpm exec tsx scripts/generateWorkflowTypes.ts && pnpm lint", 22 | "generate-workflow-files": "node build/cli/bin.js build", 23 | "generate-zero-dependency-package": "pnpm exec tsx scripts/generateZeroDependencyPackage.ts", 24 | "gwf": "node build/cli/bin.js", 25 | "prepare": "husky install" 26 | }, 27 | "bin": { 28 | "generate-workflow-files": "./build/cli/bin.js", 29 | "gwf": "./build/cli/bin.js" 30 | }, 31 | "husky": { 32 | "hooks": { 33 | "pre-commit": "lint-staged" 34 | } 35 | }, 36 | "lint-staged": { 37 | "*.{ts,tsx}": [ 38 | "pnpm dlx prettier --config .prettierrc.json --write" 39 | ] 40 | }, 41 | "dependencies": { 42 | "fast-glob": "^3.3.2", 43 | "js-yaml": "^4.1.0", 44 | "ts-node": "^10.9.2", 45 | "yargs": "^17.7.2" 46 | }, 47 | "devDependencies": { 48 | "@commitlint/cli": "^19.8.0", 49 | "@commitlint/config-conventional": "^19.8.0", 50 | "@types/jest": "^29.5.14", 51 | "@types/js-yaml": "^4.0.9", 52 | "@types/node-fetch": "^2.6.12", 53 | "@types/yargs": "^17.0.33", 54 | "@typescript-eslint/eslint-plugin": "^8.28.0", 55 | "@typescript-eslint/parser": "^8.28.0", 56 | "eslint": "^9.23.0", 57 | "eslint-config-prettier": "^10.1.1", 58 | "eslint-plugin-jest": "^28.11.0", 59 | "eslint-plugin-prettier": "^5.2.4", 60 | "husky": "^9.1.7", 61 | "jest": "^29.7.0", 62 | "json-schema-to-typescript": "^15.0.4", 63 | "lint-staged": "^15.5.0", 64 | "node-fetch": "^3.3.2", 65 | "prettier": "3.5.3", 66 | "ts-jest": "^29.3.0", 67 | "tsx": "^4.19.3", 68 | "typescript": "^4.9.5", 69 | "typescript-eslint": "^8.28.0" 70 | }, 71 | "pnpm": { 72 | "overrides": { 73 | "micromatch@<4.0.8": ">=4.0.8" 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /scripts/generateWorkflowTypes.ts: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs' 2 | import * as path from 'path' 3 | import fetch from 'node-fetch' 4 | import { compile, JSONSchema } from 'json-schema-to-typescript' 5 | 6 | const GITHUB_ACTIONS_WORKFLOW_JSON_SCHEMA_URL = 7 | 'https://raw.githubusercontent.com/SchemaStore/schemastore/refs/heads/master/src/schemas/json/github-workflow.json' 8 | 9 | ;(async () => { 10 | const jsonSchema = await fetch(GITHUB_ACTIONS_WORKFLOW_JSON_SCHEMA_URL).then( 11 | (response) => response.json(), 12 | ) 13 | 14 | const outputPath = path.join( 15 | process.cwd(), 16 | 'src', 17 | 'lib', 18 | 'types', 19 | 'githubActionsWorkflow.ts', 20 | ) 21 | 22 | const workflowTypes = await compile(jsonSchema as JSONSchema, 'Workflow', { 23 | bannerComment: ` 24 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 25 | // @ts-nocheck 26 | /* eslint-disable */ 27 | /** 28 | * This file was automatically generated by json-schema-to-typescript. 29 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 30 | * and run json-schema-to-typescript to regenerate this file. 31 | */ 32 | `, 33 | customName: (schema, keyNameFromDefinition) => { 34 | if (schema.$id === 'https://json.schemastore.org/github-workflow.json') 35 | return 'Workflow' 36 | return keyNameFromDefinition 37 | }, 38 | }) 39 | 40 | await fs.writeFile(outputPath, workflowTypes) 41 | })() 42 | -------------------------------------------------------------------------------- /scripts/generateZeroDependencyPackage.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | 4 | const packageJson = JSON.parse( 5 | fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf8'), 6 | ) 7 | 8 | const newPackageJson = { 9 | name: `${packageJson.name}-lib`, 10 | version: `${packageJson.version}`, 11 | author: packageJson.author, 12 | description: packageJson.description, 13 | repository: packageJson.repository, 14 | license: packageJson.license, 15 | main: 'build/index', 16 | types: 'build/index', 17 | files: ['build'], 18 | scripts: { 19 | build: 'tsc -p tsconfig.build.json', 20 | }, 21 | } 22 | 23 | fs.mkdirSync(path.resolve(__dirname, '../github-actions-workflow-ts-lib/src'), { 24 | recursive: true, 25 | }) 26 | 27 | // copy needed files 28 | ;[ 29 | { 30 | from: '../src/lib', 31 | to: '../github-actions-workflow-ts-lib/src', 32 | }, 33 | { 34 | from: '../tsconfig.json', 35 | to: '../github-actions-workflow-ts-lib/tsconfig.json', 36 | }, 37 | { 38 | from: '../tsconfig.build.json', 39 | to: '../github-actions-workflow-ts-lib/tsconfig.build.json', 40 | }, 41 | { 42 | from: '../LICENSE', 43 | to: '../github-actions-workflow-ts-lib/LICENSE', 44 | }, 45 | ].forEach(({ from, to }) => { 46 | fs.cpSync(path.resolve(__dirname, from), path.resolve(__dirname, to), { 47 | recursive: true, 48 | }) 49 | }) 50 | 51 | // write new package.json 52 | fs.writeFileSync( 53 | path.resolve(__dirname, '../github-actions-workflow-ts-lib/package.json'), 54 | JSON.stringify(newPackageJson, null, 2), 55 | ) 56 | 57 | // write new README.md content 58 | fs.writeFileSync( 59 | path.resolve(__dirname, '../github-actions-workflow-ts-lib/README.md'), 60 | `# github-actions-workflow-ts-lib 61 | 62 | This is a zero-dependency version of [github-actions-workflow-ts](https://github.com/emmanuelnk/github-actions-workflow-ts). 63 | `, 64 | ) 65 | -------------------------------------------------------------------------------- /src/cli/bin.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* istanbul ignore file */ 3 | /* eslint-disable @typescript-eslint/no-unused-expressions */ 4 | import * as yargs from 'yargs' 5 | import { registerTsNode, generateWorkflowFiles } from './commands/build' 6 | 7 | yargs 8 | .scriptName('github-actions-wac') 9 | .usage('$0 [args]') 10 | .command( 11 | 'build', 12 | `Builds YAML from detected TypeScript ("*.wac.ts") workflow files.`, 13 | {}, 14 | async (argv) => { 15 | registerTsNode() 16 | generateWorkflowFiles(argv) 17 | }, 18 | ) 19 | .help().argv 20 | -------------------------------------------------------------------------------- /src/cli/commands/__mocks__/test.wac.ts: -------------------------------------------------------------------------------- 1 | import { Workflow, NormalJob, Step } from '../../../lib' 2 | 3 | const checkout = new Step({ 4 | name: 'Checkout', 5 | uses: 'actions/checkout@v3', 6 | }) 7 | 8 | const installNode = new Step({ 9 | name: 'Install Node', 10 | uses: 'actions/setup-node@v3', 11 | with: { 'node-version': 18 }, 12 | }) 13 | 14 | const installPnpm = new Step({ 15 | name: 'Install pnpm', 16 | uses: 'pnpm/action-setup@v4', 17 | with: { version: 8 }, 18 | }) 19 | 20 | const installDependencies = new Step({ 21 | name: 'Install Dependencies', 22 | run: 'pnpm install --no-frozen-lockfile', 23 | }) 24 | 25 | const runTests = new Step({ 26 | name: 'Run Tests', 27 | run: 'pnpm test', 28 | }) 29 | 30 | const testJob = new NormalJob('Test', { 31 | 'runs-on': ['self-hosted', 'linux', 'x64', 'gpu'], 32 | }).addSteps([checkout, installNode, installPnpm, installDependencies, runTests]) 33 | 34 | export const test = new Workflow('mock-test', { 35 | name: 'ExampleMockTests', 36 | on: { 37 | workflow_dispatch: {}, 38 | }, 39 | }).addJob(testJob) 40 | -------------------------------------------------------------------------------- /src/cli/commands/build.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-require-imports */ 2 | 3 | import * as tsNode from 'ts-node' 4 | import * as build from './build' 5 | import * as fs from 'fs' 6 | import * as path from 'path' 7 | import * as jsYaml from 'js-yaml' 8 | import { Workflow } from '../../lib' 9 | import { BuildTypes } from '../../cli' 10 | 11 | jest.mock('fs') 12 | jest.mock('path') 13 | jest.mock('js-yaml') 14 | jest.mock('ts-node') 15 | 16 | describe('build', () => { 17 | let consoleLogSpy: jest.SpyInstance 18 | let consoleErrorSpy: jest.SpyInstance 19 | 20 | beforeEach(() => { 21 | consoleLogSpy = jest.spyOn(console, 'log').mockImplementation() 22 | consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation() 23 | }) 24 | 25 | afterEach(() => { 26 | consoleLogSpy.mockRestore() 27 | consoleErrorSpy.mockRestore() 28 | }) 29 | 30 | describe('relativePath', () => { 31 | beforeEach(() => { 32 | jest.resetAllMocks() 33 | }) 34 | 35 | it('calls path.relative with correct arguments', () => { 36 | const mockCwd = '/mock/absolute/path' 37 | const mockAbsolutePath = '/mock/absolute/path/to/file.txt' 38 | const mockRelativePath = 'to/file.txt' 39 | const relativePathSpy = jest 40 | .spyOn(path, 'relative') 41 | .mockReturnValue(mockRelativePath) 42 | jest.spyOn(process, 'cwd').mockReturnValue(mockCwd) 43 | 44 | const result = build.relativePath(mockAbsolutePath) 45 | 46 | expect(relativePathSpy).toHaveBeenCalledWith(mockCwd, mockAbsolutePath) 47 | expect(result).toBe(mockRelativePath) 48 | }) 49 | }) 50 | 51 | describe('getConfig', () => { 52 | afterEach(() => { 53 | jest.clearAllMocks() 54 | }) 55 | 56 | it('should return undefined if wac.config.json does not exist', () => { 57 | jest.spyOn(fs, 'existsSync').mockReturnValue(false) 58 | const result = build.getConfig() 59 | 60 | expect(result).toBeUndefined() 61 | expect(consoleLogSpy).toHaveBeenCalledWith( 62 | '[github-actions-workflow-ts] No config (wac.config.json) file found in root dir. Using default config.', 63 | ) 64 | }) 65 | 66 | it('should return the config object if wac.config.json exists', () => { 67 | const mockConfig = JSON.stringify({ key: 'value' }) 68 | jest.spyOn(fs, 'existsSync').mockReturnValue(true) 69 | jest.spyOn(fs, 'readFileSync').mockReturnValue(mockConfig) 70 | jest.spyOn(path, 'join').mockReturnValue('some/fake/path/wac.config.json') 71 | 72 | const result = build.getConfig() 73 | 74 | expect(result).toEqual({ key: 'value' }) 75 | expect(consoleLogSpy).toHaveBeenCalledWith( 76 | '[github-actions-workflow-ts] wac.config.json config file found in root dir', 77 | ) 78 | }) 79 | 80 | it('should throw an error if the config file contains invalid JSON', () => { 81 | jest.spyOn(fs, 'existsSync').mockReturnValue(true) 82 | jest.spyOn(fs, 'readFileSync').mockReturnValue('invalid JSON') 83 | jest.spyOn(path, 'join').mockReturnValue('some/fake/path/wac.config.json') 84 | 85 | // Run getConfig and test that it throws an error 86 | expect(() => build.getConfig()).toThrow() 87 | }) 88 | }) 89 | 90 | describe('registerTsNode', () => { 91 | let registerSpy: jest.SpyInstance 92 | 93 | beforeEach(() => { 94 | registerSpy = jest.spyOn(tsNode, 'register').mockImplementation() 95 | }) 96 | 97 | afterEach(() => { 98 | registerSpy.mockRestore() 99 | }) 100 | 101 | it('should register ts-node with options', () => { 102 | const options = { transpileOnly: true } 103 | build.registerTsNode(options) 104 | expect(registerSpy).toHaveBeenCalledWith(options) 105 | }) 106 | 107 | it('should not register ts-node if it has been already registered', () => { 108 | build.registerTsNode() 109 | expect(registerSpy).toHaveBeenCalledTimes(0) 110 | }) 111 | }) 112 | 113 | describe('getWorkflowPaths', () => { 114 | afterEach(() => { 115 | jest.clearAllMocks() 116 | }) 117 | 118 | it('should return undefined and log message if no workflow files are found', () => { 119 | const fg = require('fast-glob') 120 | jest.doMock('fast-glob') 121 | jest.spyOn(fg, 'sync').mockReturnValue([]) 122 | const result = build.getWorkflowFilePaths() 123 | 124 | expect(result).toBeUndefined() 125 | expect(consoleLogSpy).toHaveBeenCalledWith( 126 | '[github-actions-workflow-ts] No workflow files found. Please create at least one *.wac.ts file in your project', 127 | ) 128 | 129 | jest.dontMock('fast-glob') 130 | }) 131 | 132 | it('should return workflow files paths and log message', () => { 133 | const fg = require('fast-glob') 134 | jest.doMock('fast-glob') 135 | jest.spyOn(fg, 'sync').mockReturnValue(['.github/workflows/test.wac.ts']) 136 | jest 137 | .spyOn(build, 'relativePath') 138 | .mockReturnValue('.github/workflows/test.wac.ts') 139 | const result = build.getWorkflowFilePaths() 140 | 141 | expect(result).toEqual(['.github/workflows/test.wac.ts']) 142 | expect(consoleLogSpy).toHaveBeenCalledWith( 143 | '[github-actions-workflow-ts] Detected following .wac.ts files:\n[github-actions-workflow-ts] --> .github/workflows/test.wac.ts', 144 | ) 145 | 146 | jest.dontMock('fast-glob') 147 | }) 148 | }) 149 | 150 | describe('createWorkflowDirectory', () => { 151 | afterEach(() => { 152 | jest.clearAllMocks() 153 | }) 154 | 155 | it('should create .github/workflows directory if it does not exist', () => { 156 | const fsMkdirSyncSpy = jest 157 | .spyOn(fs, 'mkdirSync') 158 | .mockReturnValue(undefined) 159 | jest.spyOn(fs, 'existsSync').mockReturnValue(false) 160 | jest.spyOn(path, 'join').mockReturnValue('some/fake/path') 161 | jest.spyOn(path, 'relative').mockReturnValue('some/fake/path') 162 | 163 | build.createWorkflowDirectory() 164 | 165 | expect(fsMkdirSyncSpy).toHaveBeenCalledWith(expect.any(String), { 166 | recursive: true, 167 | }) 168 | expect(consoleLogSpy).toHaveBeenCalledWith( 169 | '[github-actions-workflow-ts] .github/workflows directory not found. Creating it.', 170 | ) 171 | }) 172 | 173 | it('should not create .github/workflows directory if it already exists', () => { 174 | const fsMkdirSyncSpy = jest 175 | .spyOn(fs, 'mkdirSync') 176 | .mockReturnValue(undefined) 177 | jest.spyOn(fs, 'existsSync').mockReturnValue(true) 178 | jest.spyOn(path, 'join').mockReturnValue('some/fake/path') 179 | jest.spyOn(path, 'relative').mockReturnValue('some/fake/path') 180 | 181 | build.createWorkflowDirectory() 182 | 183 | expect(fsMkdirSyncSpy).not.toHaveBeenCalled() 184 | expect(consoleLogSpy).not.toHaveBeenCalled() 185 | }) 186 | }) 187 | 188 | describe('importing of *.wac.ts files', () => { 189 | it('should import TS file and return its exported workflows', async () => { 190 | const fg = require('fast-glob') 191 | jest.doMock('fast-glob') 192 | 193 | const fgSyncSpy = jest 194 | .spyOn(fg, 'sync') 195 | .mockReturnValue(['./__mocks__/test.wac.ts']) 196 | 197 | build.registerTsNode() 198 | const workflowFilePaths = (await build.getWorkflowFilePaths()) as string[] 199 | 200 | const jsonToConvertToYaml = await import(workflowFilePaths[0]) 201 | 202 | expect(jsonToConvertToYaml).toEqual({ 203 | test: new Workflow('mock-test', { 204 | name: 'ExampleMockTests', 205 | on: { 206 | workflow_dispatch: {}, 207 | }, 208 | jobs: { 209 | Test: { 210 | 'runs-on': ['self-hosted', 'linux', 'x64', 'gpu'], 211 | steps: [ 212 | { 213 | name: 'Checkout', 214 | uses: 'actions/checkout@v3', 215 | }, 216 | { 217 | name: 'Install Node', 218 | uses: 'actions/setup-node@v3', 219 | with: { 'node-version': 18 }, 220 | }, 221 | { 222 | name: 'Install pnpm', 223 | uses: 'pnpm/action-setup@v4', 224 | with: { 225 | version: 8, 226 | }, 227 | }, 228 | { 229 | name: 'Install Dependencies', 230 | run: 'pnpm install --no-frozen-lockfile', 231 | }, 232 | { 233 | name: 'Run Tests', 234 | run: 'pnpm test', 235 | }, 236 | ], 237 | }, 238 | }, 239 | }), 240 | }) 241 | 242 | fgSyncSpy.mockRestore() 243 | jest.dontMock('fast-glob') 244 | }) 245 | }) 246 | 247 | describe('createYamlFiles', () => { 248 | it('should write a yaml file for each exported workflow', () => { 249 | const workflows: Record = { 250 | myWorkflow1: new Workflow('some-name-1', { 251 | on: 'push', 252 | jobs: { 253 | myJob: { 254 | 'runs-on': 'ubuntu-latest', 255 | steps: [{ uses: 'actions/checkout@v3' }], 256 | }, 257 | }, 258 | }), 259 | myWorkflow2: new Workflow('some-name-2', { 260 | on: 'pull_request', 261 | jobs: { 262 | myJob: { 263 | 'runs-on': 'ubuntu-latest', 264 | steps: [{ uses: 'actions/checkout@v3' }], 265 | }, 266 | }, 267 | }), 268 | } 269 | 270 | const fsWriteFileSyncSpy = jest.spyOn(fs, 'writeFileSync') 271 | 272 | build.writeWorkflowJSONToYamlFiles(workflows, 'some-name-1.wac.ts', {}) 273 | 274 | expect(fsWriteFileSyncSpy).toHaveBeenCalledTimes(2) 275 | }) 276 | }) 277 | 278 | describe('writeWorkflowJSONToYamlFiles', () => { 279 | it('should write the converted YAML with correct DEFAULT_HEADER_TEXT', () => { 280 | const mockWorkflow = new Workflow('sample-filename', { 281 | name: 'Sample Workflow', 282 | on: { 283 | workflow_dispatch: {}, 284 | }, 285 | }) 286 | 287 | const mockYamlString = 'name: Sample Workflow' 288 | 289 | // Mocking function implementations 290 | jest.spyOn(jsYaml, 'dump').mockReturnValue(mockYamlString) 291 | jest 292 | .spyOn(path, 'join') 293 | .mockReturnValue('.github/workflows/sample-filename.yml') 294 | 295 | build.writeWorkflowJSONToYamlFiles( 296 | { 297 | 'sample-filename': mockWorkflow, 298 | }, 299 | 'sample-filename.wac.ts', 300 | { refs: true }, 301 | ) 302 | 303 | expect(fs.writeFileSync).toHaveBeenCalledWith( 304 | '.github/workflows/sample-filename.yml', 305 | `${build.DEFAULT_HEADER_TEXT.join('\n').replace( 306 | '', 307 | 'sample-filename.wac.ts', 308 | )}\n${mockYamlString}`, 309 | ) 310 | }) 311 | 312 | it('should handle cases where filename property is not present and default to workflow name', () => { 313 | const mockWorkflow = new Workflow('sample-filename', { 314 | name: 'Sample Workflow', 315 | on: { 316 | workflow_dispatch: {}, 317 | }, 318 | }) 319 | 320 | const mockYamlString = 'name: Sample Workflow' 321 | 322 | jest.spyOn(jsYaml, 'dump').mockReturnValue(mockYamlString) 323 | jest 324 | .spyOn(path, 'join') 325 | .mockReturnValue('.github/workflows/exampleWorkflow.yml') 326 | 327 | build.writeWorkflowJSONToYamlFiles( 328 | { 329 | 'sample-filename': mockWorkflow, 330 | }, 331 | 'sample-filename.wac.ts', 332 | { refs: true }, 333 | ) 334 | 335 | expect(fs.writeFileSync).toHaveBeenCalledWith( 336 | '.github/workflows/exampleWorkflow.yml', 337 | `${build.DEFAULT_HEADER_TEXT.join('\n').replace( 338 | '', 339 | 'sample-filename.wac.ts', 340 | )}\n${mockYamlString}`, 341 | ) 342 | }) 343 | }) 344 | 345 | describe('generateWorkflowFiles', () => { 346 | afterEach(() => { 347 | jest.clearAllMocks() 348 | }) 349 | 350 | it('should generate expected number of files', async () => { 351 | const mockWorkflowFilePaths = ['./__mocks__/test.wac.ts'] 352 | const mockConfig = { refs: false } 353 | 354 | jest.spyOn(fs, 'writeFileSync').mockImplementation() 355 | jest.spyOn(jsYaml, 'dump').mockReturnValue('sampleYamlString') 356 | jest.spyOn(build, 'getConfig').mockReturnValue(mockConfig) 357 | jest 358 | .spyOn(build, 'getWorkflowFilePaths') 359 | .mockReturnValue(mockWorkflowFilePaths) 360 | 361 | await build.generateWorkflowFiles({ refs: true }) 362 | 363 | expect(consoleLogSpy).toHaveBeenCalledWith( 364 | '[github-actions-workflow-ts] Successfully generated 1 workflow file(s)', 365 | ) 366 | }) 367 | 368 | it('should generate no files if no .wac.ts files are found', async () => { 369 | const mockWorkflowFilePaths: string[] | undefined = undefined 370 | const mockConfig = { refs: false } 371 | 372 | jest.spyOn(fs, 'writeFileSync').mockImplementation() 373 | jest.spyOn(jsYaml, 'dump').mockReturnValue('sampleYamlString') 374 | jest.spyOn(build, 'getConfig').mockReturnValue(mockConfig) 375 | jest 376 | .spyOn(build, 'getWorkflowFilePaths') 377 | .mockReturnValue(mockWorkflowFilePaths) 378 | 379 | await build.generateWorkflowFiles({ refs: true }) 380 | 381 | expect(consoleLogSpy).toHaveBeenCalledWith( 382 | '[github-actions-workflow-ts] Successfully generated 0 workflow file(s)', 383 | ) 384 | }) 385 | 386 | it('should write YAML files to the correct paths', async () => { 387 | const mockWorkflowFilePaths = ['./__mocks__/test.wac.ts'] 388 | const mockConfig = { refs: false } 389 | 390 | jest.spyOn(fs, 'writeFileSync').mockImplementation() 391 | jest.spyOn(jsYaml, 'dump').mockReturnValue('sampleYamlString') 392 | jest.spyOn(build, 'getConfig').mockReturnValue(mockConfig) 393 | jest 394 | .spyOn(path, 'join') 395 | .mockReturnValue('.github/workflows/sample-filename.yml') 396 | jest 397 | .spyOn(build, 'getWorkflowFilePaths') 398 | .mockReturnValue(mockWorkflowFilePaths) 399 | 400 | await build.generateWorkflowFiles({ refs: true }) 401 | 402 | expect(fs.writeFileSync).toHaveBeenCalledWith( 403 | '.github/workflows/sample-filename.yml', 404 | expect.stringContaining('sampleYamlString'), 405 | ) 406 | }) 407 | 408 | it('should use the config from wac.config.json if available', async () => { 409 | const mockWorkflowFilePaths = ['./__mocks__/test.wac.ts'] 410 | const mockConfig = { refs: false } 411 | 412 | jest.spyOn(fs, 'writeFileSync').mockImplementation() 413 | jest.spyOn(jsYaml, 'dump').mockReturnValue('sampleYamlString') 414 | jest.spyOn(build, 'getConfig').mockReturnValue(mockConfig) 415 | jest 416 | .spyOn(build, 'getWorkflowFilePaths') 417 | .mockReturnValue(mockWorkflowFilePaths) 418 | jest.spyOn(build, 'writeWorkflowJSONToYamlFiles') 419 | 420 | // Even though we pass in { refs: true }, the config from wac.config.json 421 | // should override it and set refs to false 422 | await build.generateWorkflowFiles({ refs: true }) 423 | 424 | const expectedConfig: BuildTypes.WacConfig = { 425 | refs: false, 426 | } 427 | 428 | expect(build.writeWorkflowJSONToYamlFiles).toHaveBeenCalledWith( 429 | expect.anything(), 430 | expect.anything(), 431 | expectedConfig, 432 | ) 433 | }) 434 | 435 | it('should use default options if no wac.config.json is found', async () => { 436 | const mockWorkflowFilePaths = ['./__mocks__/test.wac.ts'] 437 | 438 | jest.spyOn(fs, 'writeFileSync').mockImplementation() 439 | jest.spyOn(jsYaml, 'dump').mockReturnValue('sampleYamlString') 440 | jest.spyOn(build, 'getConfig').mockReturnValue(undefined) 441 | jest 442 | .spyOn(build, 'getWorkflowFilePaths') 443 | .mockReturnValue(mockWorkflowFilePaths) 444 | 445 | await build.generateWorkflowFiles({ refs: true }) 446 | 447 | const expectedConfig: BuildTypes.WacConfig = { 448 | refs: true, 449 | } 450 | 451 | expect(build.writeWorkflowJSONToYamlFiles).toHaveBeenCalledWith( 452 | expect.anything(), 453 | expect.anything(), 454 | expectedConfig, 455 | ) 456 | }) 457 | }) 458 | }) 459 | -------------------------------------------------------------------------------- /src/cli/commands/build.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import * as fs from 'fs' 3 | import * as path from 'path' 4 | import * as jsYaml from 'js-yaml' 5 | import * as fg from 'fast-glob' 6 | import * as tsNode from 'ts-node' 7 | import { Workflow } from '../../lib' 8 | import { BuildTypes } from '../commands/types' 9 | 10 | /** 11 | * Comment indicating the file should not be modified. 12 | * @type {string} 13 | */ 14 | export const DEFAULT_HEADER_TEXT = [ 15 | '# ------------DO-NOT-MODIFY-THIS-FILE------------', 16 | '# This file was automatically generated by github-actions-workflow-ts.', 17 | '# Instead, modify ', 18 | '# ------------DO-NOT-MODIFY-THIS-FILE------------', 19 | ] 20 | 21 | /** 22 | * Flag to track if tsNode is registered. 23 | * @type {boolean} 24 | */ 25 | let tsNodeRegistered = false 26 | 27 | /** 28 | * Convert an absolute path to a relative path from the current working directory. 29 | * 30 | * @param {string} p - The absolute path. 31 | * @returns {string} - Relative path from the current working directory. 32 | */ 33 | export const relativePath = (p: string): string => 34 | path.relative(process.cwd(), p) 35 | 36 | /** 37 | * Registers ts-node if it hasn't been registered. 38 | * 39 | * @param {object} [options={}] - Options for ts-node registration. 40 | */ 41 | export const registerTsNode = (options = {}): void => { 42 | if (tsNodeRegistered) return 43 | 44 | tsNode.register({ ...options }) 45 | tsNodeRegistered = true 46 | } 47 | 48 | /** 49 | * Returns the config file 50 | * @returns { Record | undefined} - The config file as an object 51 | */ 52 | export const getConfig = (): BuildTypes.WacConfig | undefined => { 53 | const configFilePath = path.join(process.cwd(), 'wac.config.json') 54 | 55 | if (!fs.existsSync(configFilePath)) { 56 | console.log( 57 | '[github-actions-workflow-ts] No config (wac.config.json) file found in root dir. Using default config.', 58 | ) 59 | 60 | return undefined 61 | } 62 | 63 | console.log( 64 | '[github-actions-workflow-ts] wac.config.json config file found in root dir', 65 | ) 66 | 67 | return JSON.parse(fs.readFileSync(configFilePath, 'utf-8')) 68 | } 69 | 70 | /** 71 | * Retrieves the file paths of all workflow files in the project. 72 | * 73 | * @returns {string[] | undefined} - Array of paths to *.wac.ts files or undefined if none are found. 74 | */ 75 | export const getWorkflowFilePaths = (): string[] | undefined => { 76 | const workflowFilesPaths = fg.sync( 77 | fg.convertPathToPattern(process.cwd()) + '/**/*.wac.ts', 78 | { 79 | onlyFiles: true, 80 | dot: true, 81 | }, 82 | ) 83 | 84 | if (!workflowFilesPaths || !workflowFilesPaths.length) { 85 | console.log( 86 | '[github-actions-workflow-ts] No workflow files found. Please create at least one *.wac.ts file in your project', 87 | ) 88 | 89 | return 90 | } 91 | 92 | const workflowFileList = workflowFilesPaths 93 | .map((item) => `[github-actions-workflow-ts] --> ${relativePath(item)}`) 94 | .join('\n') 95 | 96 | console.log( 97 | `[github-actions-workflow-ts] Detected following .wac.ts files:\n${workflowFileList}`, 98 | ) 99 | 100 | return workflowFilesPaths 101 | } 102 | 103 | /** 104 | * Writes the provided workflow JSON data to corresponding YAML files. 105 | * 106 | * @param {Record} workflowJSON - The workflow data in JSON format. 107 | * @param {string} workflowFilePath - The path to the workflow file. 108 | * @param {BuildTypes.WacConfig} config - Command line arguments. 109 | * @returns {number} - The number of workflows written. 110 | */ 111 | export const writeWorkflowJSONToYamlFiles = ( 112 | workflowJSON: Record, 113 | workflowFilePath: string, 114 | config: BuildTypes.WacConfig, 115 | ): number => { 116 | let workflowCount: number = 0 117 | 118 | for (const workflowName in workflowJSON) { 119 | const workflowYaml = jsYaml.dump(workflowJSON[workflowName].workflow, { 120 | noRefs: !config.refs, 121 | ...(config.dumpOptions || {}), 122 | }) 123 | 124 | const yamlWorkflowPath = path.join( 125 | '.github', 126 | 'workflows', 127 | `${workflowJSON[workflowName].filename}.yml`, 128 | ) 129 | 130 | console.log( 131 | `[github-actions-workflow-ts] Writing to ${relativePath(yamlWorkflowPath)}:`, 132 | ) 133 | 134 | const headerText = (config.headerText || DEFAULT_HEADER_TEXT) 135 | .join('\n') 136 | .replace('', workflowFilePath) 137 | 138 | fs.writeFileSync(yamlWorkflowPath, [headerText, workflowYaml].join('\n')) 139 | 140 | workflowCount++ 141 | } 142 | 143 | return workflowCount 144 | } 145 | 146 | /** 147 | * Creates the .github/workflows directory if it doesn't exist. 148 | */ 149 | export const createWorkflowDirectory = (): void => { 150 | const workflowsDir = relativePath(path.join('.github', 'workflows')) 151 | 152 | if (!fs.existsSync(workflowsDir)) { 153 | console.log( 154 | '[github-actions-workflow-ts] .github/workflows directory not found. Creating it.', 155 | ) 156 | fs.mkdirSync(workflowsDir, { recursive: true }) 157 | } 158 | } 159 | 160 | /** 161 | * Generates workflow files based on the provided command line arguments. 162 | * 163 | * @param {Record} argv - Command line arguments. 164 | * @returns {Promise} - A promise that resolves when the generation is completed. 165 | */ 166 | export const generateWorkflowFiles = async ( 167 | argv: Record, 168 | ): Promise => { 169 | const config = getConfig() || {} 170 | const workflowFilePaths = getWorkflowFilePaths() || [] 171 | let workflowCount = 0 172 | 173 | createWorkflowDirectory() 174 | 175 | for (const idx in workflowFilePaths) { 176 | workflowCount += writeWorkflowJSONToYamlFiles( 177 | await import(workflowFilePaths[idx]), 178 | relativePath(workflowFilePaths[idx]), 179 | { 180 | ...argv, 181 | ...config, 182 | } as BuildTypes.WacConfig, 183 | ) 184 | } 185 | 186 | console.log( 187 | `[github-actions-workflow-ts] Successfully generated ${workflowCount} workflow file(s)`, 188 | ) 189 | } 190 | -------------------------------------------------------------------------------- /src/cli/commands/index.ts: -------------------------------------------------------------------------------- 1 | export * from './build' 2 | export * from './types' 3 | -------------------------------------------------------------------------------- /src/cli/commands/types/build.ts: -------------------------------------------------------------------------------- 1 | export type WacConfig = { 2 | /** if true, convert duplicate objects into references (default: false) */ 3 | refs?: boolean 4 | /** 5 | * An array of string to override the default text that appears at the top of 6 | * the generated workflow files. This is useful for adding comments to the 7 | * generated workflow files. 8 | */ 9 | headerText?: string[] 10 | /** 11 | * Options for js-yaml dump funtion 12 | * Ref: https://github.com/nodeca/js-yaml#dump-object---options- 13 | */ 14 | dumpOptions?: Record 15 | } 16 | -------------------------------------------------------------------------------- /src/cli/commands/types/index.ts: -------------------------------------------------------------------------------- 1 | export * as BuildTypes from './build' 2 | -------------------------------------------------------------------------------- /src/cli/index.spec.ts: -------------------------------------------------------------------------------- 1 | import * as index from './index' 2 | 3 | describe('index', () => { 4 | it('should export the correct functions', () => { 5 | expect(index).toBeDefined() 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /src/cli/index.ts: -------------------------------------------------------------------------------- 1 | export * from './commands' 2 | -------------------------------------------------------------------------------- /src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import * as index from './index' 2 | 3 | describe('index', () => { 4 | it('should export the correct functions', () => { 5 | expect(index).toBeDefined() 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib' 2 | -------------------------------------------------------------------------------- /src/lib/index.spec.ts: -------------------------------------------------------------------------------- 1 | import * as index from './index' 2 | 3 | describe('index', () => { 4 | it('should export the correct functions', () => { 5 | expect(index).toBeDefined() 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './workflow' 2 | export * from './job' 3 | export * from './step' 4 | export * from './utils' 5 | export * from './types' 6 | -------------------------------------------------------------------------------- /src/lib/job/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { NormalJob, ReusableWorkflowCallJob } from './' 2 | import { Step } from '../step' 3 | 4 | describe('NormalJob', () => { 5 | describe('addSteps', () => { 6 | it('should add steps to an existing steps array', () => { 7 | const stepInstance = new Step({ 8 | name: 'Echo', 9 | run: 'echo "Hello World"', 10 | }) 11 | 12 | const job = new NormalJob('testJob', { 13 | 'runs-on': 'ubuntu-latest', 14 | steps: [ 15 | { 16 | name: 'Checkout', 17 | uses: 'actions/checkout@v3', 18 | }, 19 | ], 20 | }) 21 | 22 | job.addSteps([stepInstance, stepInstance]) 23 | 24 | expect(job.job.steps?.length).toBe(3) 25 | }) 26 | 27 | it('should initialize steps if it does not exist', () => { 28 | const stepInstance = new Step({ 29 | name: 'Echo', 30 | run: 'echo "Hello World"', 31 | }) 32 | 33 | const job = new NormalJob('testJob', { 34 | 'runs-on': 'ubuntu-latest', 35 | }) 36 | 37 | job.addSteps([stepInstance]) 38 | 39 | expect(job.job.steps?.length).toBe(1) 40 | }) 41 | }) 42 | 43 | describe('addStep', () => { 44 | it('should add a step to an existing steps array', () => { 45 | const stepInstance = new Step({ 46 | name: 'Echo', 47 | run: 'echo "Hello World"', 48 | }) 49 | 50 | const job = new NormalJob('testJob', { 51 | 'runs-on': 'ubuntu-latest', 52 | steps: [ 53 | { 54 | name: 'Checkout', 55 | uses: 'actions/checkout@v3', 56 | }, 57 | ], 58 | }) 59 | 60 | job.addStep(stepInstance) 61 | 62 | expect(job.job.steps?.length).toBe(2) 63 | }) 64 | 65 | it('should initialize steps with a single step if it does not exist', () => { 66 | const stepInstance = new Step({ 67 | name: 'Echo', 68 | run: 'echo "Hello World"', 69 | }) 70 | 71 | const job = new NormalJob('testJob', { 72 | 'runs-on': 'ubuntu-latest', 73 | }) 74 | 75 | job.addStep(stepInstance) 76 | 77 | expect(job.job.steps?.length).toBe(1) 78 | }) 79 | }) 80 | 81 | describe('addEnvs', () => { 82 | it('should add environment variables to an existing env object', () => { 83 | const job = new NormalJob('testJob', { 84 | 'runs-on': 'ubuntu-latest', 85 | env: { existingKey: 'existingValue' }, 86 | }) 87 | 88 | job.addEnvs({ newKey: 'newValue' }) 89 | 90 | expect(job.job.env).toEqual({ 91 | existingKey: 'existingValue', 92 | newKey: 'newValue', 93 | }) 94 | }) 95 | 96 | it('should initialize env object if it does not exist', () => { 97 | const job = new NormalJob('testJob', { 98 | 'runs-on': 'ubuntu-latest', 99 | }) 100 | 101 | job.addEnvs({ newKey: 'newValue' }) 102 | 103 | expect(job.job.env).toEqual({ newKey: 'newValue' }) 104 | }) 105 | }) 106 | 107 | describe('needs', () => { 108 | it("should add jobs to an empty job's needs", () => { 109 | const job1 = new NormalJob('job1', { 110 | 'runs-on': 'ubuntu-latest', 111 | }) 112 | 113 | const job2 = new ReusableWorkflowCallJob('job2', { 114 | uses: 'your-org/your-repo/.github/workflows/reusable-workflow.yml@main', 115 | }) 116 | 117 | const targetJob = new NormalJob('target', { 118 | 'runs-on': 'ubuntu-latest', 119 | }) 120 | 121 | targetJob.needs([job1, job2]) 122 | 123 | expect(targetJob.job.needs).toEqual(['job1', 'job2']) 124 | }) 125 | 126 | it("should add jobs to an existing job's needs", () => { 127 | const job1 = new NormalJob('job1', { 128 | 'runs-on': 'ubuntu-latest', 129 | }) 130 | 131 | const job2 = new ReusableWorkflowCallJob('job2', { 132 | uses: 'your-org/your-repo/.github/workflows/reusable-workflow.yml@main', 133 | }) 134 | 135 | const targetJob = new NormalJob('target', { 136 | 'runs-on': 'ubuntu-latest', 137 | needs: ['initialJob'], 138 | }) 139 | 140 | targetJob.needs([job1, job2]) 141 | 142 | expect(targetJob.job.needs).toEqual(['initialJob', 'job1', 'job2']) 143 | }) 144 | 145 | it('should handle a mix of NormalJob and ReusableWorkflowCallJob types', () => { 146 | const job1 = new NormalJob('job1', { 147 | 'runs-on': 'ubuntu-latest', 148 | }) 149 | 150 | const job2 = new ReusableWorkflowCallJob('job2', { 151 | uses: 'your-org/your-repo/.github/workflows/reusable-workflow.yml@main', 152 | }) 153 | 154 | const job3 = new NormalJob('job3', { 155 | 'runs-on': 'ubuntu-latest', 156 | }) 157 | const targetJob = new NormalJob('target', { 158 | 'runs-on': 'ubuntu-latest', 159 | }) 160 | 161 | targetJob.needs([job1, job2, job3]) 162 | 163 | expect(targetJob.job.needs).toEqual(['job1', 'job2', 'job3']) 164 | }) 165 | }) 166 | }) 167 | 168 | describe('ReusableWorkflowCallJob', () => { 169 | describe('constructor', () => { 170 | it('should construct properly', () => { 171 | const job = new ReusableWorkflowCallJob('testReusableJob', { 172 | uses: 'your-org/your-repo/.github/workflows/reusable-workflow.yml@main', 173 | secrets: 'inherit', 174 | }) 175 | 176 | expect(job.name).toBe('testReusableJob') 177 | expect(job.job).toEqual({ 178 | uses: 'your-org/your-repo/.github/workflows/reusable-workflow.yml@main', 179 | secrets: 'inherit', 180 | }) 181 | }) 182 | }) 183 | 184 | describe('needs', () => { 185 | it("should add jobs to an empty job's needs", () => { 186 | const job1 = new NormalJob('job1', { 187 | 'runs-on': 'ubuntu-latest', 188 | }) 189 | 190 | const job2 = new NormalJob('job2', { 191 | 'runs-on': 'ubuntu-latest', 192 | }) 193 | 194 | const targetJob = new ReusableWorkflowCallJob('target', { 195 | uses: 'your-org/your-repo/.github/workflows/reusable-workflow.yml@main', 196 | }) 197 | 198 | targetJob.needs([job1, job2]) 199 | 200 | expect(targetJob.job.needs).toEqual(['job1', 'job2']) 201 | }) 202 | 203 | it("should add jobs to an existing job's needs", () => { 204 | const job1 = new NormalJob('job1', { 205 | 'runs-on': 'ubuntu-latest', 206 | }) 207 | 208 | const job2 = new NormalJob('job2', { 209 | 'runs-on': 'ubuntu-latest', 210 | }) 211 | 212 | const targetJob = new ReusableWorkflowCallJob('targetJob', { 213 | uses: 'your-org/your-repo/.github/workflows/reusable-workflow.yml@main', 214 | needs: ['initialJob'], 215 | }) 216 | 217 | targetJob.needs([job1, job2]) 218 | 219 | expect(targetJob.job.needs).toEqual(['initialJob', 'job1', 'job2']) 220 | }) 221 | 222 | it('should handle a mix of NormalJob and ReusableWorkflowCallJob types', () => { 223 | const job1 = new NormalJob('job1', { 224 | 'runs-on': 'ubuntu-latest', 225 | }) 226 | 227 | const job2 = new ReusableWorkflowCallJob('job2', { 228 | uses: 'your-org/your-repo/.github/workflows/reusable-workflow.yml@main', 229 | }) 230 | 231 | const job3 = new NormalJob('job3', { 232 | 'runs-on': 'ubuntu-latest', 233 | }) 234 | 235 | const targetJob = new ReusableWorkflowCallJob('targetJob', { 236 | uses: 'your-org/your-repo/.github/workflows/reusable-workflow.yml@main', 237 | }) 238 | 239 | targetJob.needs([job1, job2, job3]) 240 | 241 | expect(targetJob.job.needs).toEqual(['job1', 'job2', 'job3']) 242 | }) 243 | }) 244 | }) 245 | -------------------------------------------------------------------------------- /src/lib/job/index.ts: -------------------------------------------------------------------------------- 1 | import { GeneratedWorkflowTypes } from '../types' 2 | import { Step } from '..' 3 | 4 | export class NormalJob { 5 | public name: string 6 | public job: GeneratedWorkflowTypes.NormalJob 7 | public steps: GeneratedWorkflowTypes.Step[] = [] 8 | 9 | addEnvs(envs: GeneratedWorkflowTypes.NormalJob['env']): this { 10 | if (this.job.env && typeof this.job.env === 'object') 11 | this.job.env = { 12 | ...(this.job.env as { [k: string]: string | number | boolean }), 13 | ...(envs as { [k: string]: string | number | boolean }), 14 | } 15 | else this.job.env = envs 16 | 17 | return this 18 | } 19 | 20 | addSteps(steps: Step[]): this { 21 | if (this.job.steps?.length) 22 | this.job.steps = [...this.job.steps, ...steps.map((step) => step.step)] 23 | else (this.job.steps as unknown[]) = steps.map((step) => step.step) 24 | 25 | return this 26 | } 27 | 28 | addStep(step: Step): this { 29 | if (this.job.steps?.length) this.job.steps = [...this.job.steps, step.step] 30 | else (this.job.steps as unknown[]) = [step.step] 31 | 32 | return this 33 | } 34 | 35 | needs(jobs: (NormalJob | ReusableWorkflowCallJob)[]): this { 36 | if (this.job.needs?.length) 37 | this.job.needs = [ 38 | ...this.job.needs, 39 | ...jobs.map((job) => job.name as GeneratedWorkflowTypes.Name), 40 | ] as GeneratedWorkflowTypes.JobNeeds 41 | else 42 | (this.job.needs as GeneratedWorkflowTypes.JobNeeds) = jobs.map( 43 | (job) => job.name as GeneratedWorkflowTypes.Name, 44 | ) as GeneratedWorkflowTypes.JobNeeds 45 | 46 | return this 47 | } 48 | 49 | constructor(name: string, jobProps: GeneratedWorkflowTypes.NormalJob) { 50 | this.name = name 51 | this.job = jobProps 52 | } 53 | } 54 | 55 | export class ReusableWorkflowCallJob { 56 | public name: string 57 | public job: GeneratedWorkflowTypes.ReusableWorkflowCallJob 58 | 59 | needs(jobs: (NormalJob | ReusableWorkflowCallJob)[]): this { 60 | if (this.job.needs?.length) 61 | this.job.needs = [ 62 | ...this.job.needs, 63 | ...jobs.map((job) => job.name as GeneratedWorkflowTypes.Name), 64 | ] as GeneratedWorkflowTypes.JobNeeds 65 | else 66 | (this.job.needs as GeneratedWorkflowTypes.JobNeeds) = jobs.map( 67 | (job) => job.name as GeneratedWorkflowTypes.Name, 68 | ) as GeneratedWorkflowTypes.JobNeeds 69 | 70 | return this 71 | } 72 | 73 | constructor( 74 | name: string, 75 | jobProps: GeneratedWorkflowTypes.ReusableWorkflowCallJob, 76 | ) { 77 | this.name = name 78 | this.job = jobProps 79 | 80 | return this 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/lib/step/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { Step } from './' 2 | 3 | describe('Step', () => { 4 | describe('addEnvs', () => { 5 | it('should add environment variables to an existing env object', () => { 6 | const stepInstance = new Step({ 7 | name: 'Checkout', 8 | uses: 'actions/checkout@v3', 9 | env: { 10 | INITIAL_KEY: 'initial_value', 11 | }, 12 | }) 13 | 14 | stepInstance.addEnvs({ 15 | ADDITIONAL_KEY: 'additional_value', 16 | }) 17 | 18 | expect(stepInstance.step.env).toEqual({ 19 | INITIAL_KEY: 'initial_value', 20 | ADDITIONAL_KEY: 'additional_value', 21 | }) 22 | }) 23 | 24 | it('should initialize env object if it does not exist', () => { 25 | const stepInstance = new Step({ 26 | name: 'Checkout', 27 | uses: 'actions/checkout@v3', 28 | }) 29 | 30 | stepInstance.addEnvs({ 31 | NEW_KEY: 'new_value', 32 | }) 33 | 34 | expect(stepInstance.step.env).toEqual({ 35 | NEW_KEY: 'new_value', 36 | }) 37 | }) 38 | 39 | it('should handle different types of values', () => { 40 | const stepInstance = new Step({ 41 | name: 'Checkout', 42 | uses: 'actions/checkout@v3', 43 | env: { 44 | INITIAL_KEY: 'initial_value', 45 | INITIAL_NUM: 1, 46 | INITIAL_BOOL: true, 47 | }, 48 | }) 49 | 50 | stepInstance.addEnvs({ 51 | ADDITIONAL_KEY: 'additional_value', 52 | ADDITIONAL_NUM: 2, 53 | ADDITIONAL_BOOL: false, 54 | }) 55 | 56 | expect(stepInstance.step.env).toEqual({ 57 | INITIAL_KEY: 'initial_value', 58 | INITIAL_NUM: 1, 59 | INITIAL_BOOL: true, 60 | ADDITIONAL_KEY: 'additional_value', 61 | ADDITIONAL_NUM: 2, 62 | ADDITIONAL_BOOL: false, 63 | }) 64 | }) 65 | }) 66 | 67 | describe('constructor', () => { 68 | it('should construct the Step instance correctly', () => { 69 | const stepInstance = new Step({ 70 | name: 'Checkout', 71 | uses: 'actions/checkout@v3', 72 | env: { 73 | INITIAL_KEY: 'initial_value', 74 | }, 75 | }) 76 | 77 | expect(stepInstance.step).toEqual({ 78 | name: 'Checkout', 79 | uses: 'actions/checkout@v3', 80 | env: { 81 | INITIAL_KEY: 'initial_value', 82 | }, 83 | }) 84 | }) 85 | }) 86 | }) 87 | -------------------------------------------------------------------------------- /src/lib/step/index.ts: -------------------------------------------------------------------------------- 1 | import { GeneratedWorkflowTypes } from '../types' 2 | 3 | export class Step { 4 | public step: GeneratedWorkflowTypes.Step 5 | public id: string | undefined 6 | 7 | addEnvs(envs: GeneratedWorkflowTypes.Step['env']): this { 8 | if (this.step.env && typeof this.step.env === 'object') 9 | this.step.env = { 10 | ...(this.step.env as { 11 | [k: string]: string | number | boolean 12 | }), 13 | ...(envs as { [k: string]: string | number | boolean }), 14 | } 15 | else this.step.env = envs 16 | 17 | return this 18 | } 19 | 20 | constructor(stepProps: GeneratedWorkflowTypes.Step) { 21 | this.step = { 22 | ...stepProps, 23 | } 24 | this.id = stepProps.id 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/types/githubActionsWorkflow.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 2 | // @ts-nocheck 3 | /* eslint-disable */ 4 | /** 5 | * This file was automatically generated by json-schema-to-typescript. 6 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 7 | * and run json-schema-to-typescript to regenerate this file. 8 | */ 9 | 10 | export type Event = 11 | | 'branch_protection_rule' 12 | | 'check_run' 13 | | 'check_suite' 14 | | 'create' 15 | | 'delete' 16 | | 'deployment' 17 | | 'deployment_status' 18 | | 'discussion' 19 | | 'discussion_comment' 20 | | 'fork' 21 | | 'gollum' 22 | | 'issue_comment' 23 | | 'issues' 24 | | 'label' 25 | | 'merge_group' 26 | | 'milestone' 27 | | 'page_build' 28 | | 'project' 29 | | 'project_card' 30 | | 'project_column' 31 | | 'public' 32 | | 'pull_request' 33 | | 'pull_request_review' 34 | | 'pull_request_review_comment' 35 | | 'pull_request_target' 36 | | 'push' 37 | | 'registry_package' 38 | | 'release' 39 | | 'status' 40 | | 'watch' 41 | | 'workflow_call' 42 | | 'workflow_dispatch' 43 | | 'workflow_run' 44 | | 'repository_dispatch' 45 | /** 46 | * Runs your workflow anytime the branch_protection_rule event occurs. More than one activity type triggers this event. 47 | */ 48 | export type EventObject = { 49 | types?: Types 50 | [k: string]: unknown 51 | } & ({ 52 | [k: string]: unknown 53 | } | null) 54 | /** 55 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 56 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 57 | */ 58 | export type Types = ([unknown, ...unknown[]] | string) & 59 | ('created' | 'edited' | 'deleted')[] 60 | /** 61 | * Runs your workflow anytime the check_run event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/checks/runs. 62 | */ 63 | export type EventObject1 = { 64 | types?: Types1 65 | [k: string]: unknown 66 | } & ({ 67 | [k: string]: unknown 68 | } | null) 69 | /** 70 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 71 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 72 | */ 73 | export type Types1 = ([unknown, ...unknown[]] | string) & 74 | ('created' | 'rerequested' | 'completed' | 'requested_action')[] 75 | /** 76 | * Runs your workflow anytime the check_suite event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/checks/suites/. 77 | */ 78 | export type EventObject2 = { 79 | types?: Types2 80 | [k: string]: unknown 81 | } & ({ 82 | [k: string]: unknown 83 | } | null) 84 | /** 85 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 86 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 87 | */ 88 | export type Types2 = ([unknown, ...unknown[]] | string) & 89 | ('completed' | 'requested' | 'rerequested')[] 90 | /** 91 | * Runs your workflow anytime the discussion event occurs. More than one activity type triggers this event. For information about the GraphQL API, see https://docs.github.com/en/graphql/guides/using-the-graphql-api-for-discussions 92 | */ 93 | export type EventObject3 = { 94 | types?: Types3 95 | [k: string]: unknown 96 | } & ({ 97 | [k: string]: unknown 98 | } | null) 99 | /** 100 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 101 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 102 | */ 103 | export type Types3 = ([unknown, ...unknown[]] | string) & 104 | ( 105 | | 'created' 106 | | 'edited' 107 | | 'deleted' 108 | | 'transferred' 109 | | 'pinned' 110 | | 'unpinned' 111 | | 'labeled' 112 | | 'unlabeled' 113 | | 'locked' 114 | | 'unlocked' 115 | | 'category_changed' 116 | | 'answered' 117 | | 'unanswered' 118 | )[] 119 | /** 120 | * Runs your workflow anytime the discussion_comment event occurs. More than one activity type triggers this event. For information about the GraphQL API, see https://docs.github.com/en/graphql/guides/using-the-graphql-api-for-discussions 121 | */ 122 | export type EventObject4 = { 123 | types?: Types4 124 | [k: string]: unknown 125 | } & ({ 126 | [k: string]: unknown 127 | } | null) 128 | /** 129 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 130 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 131 | */ 132 | export type Types4 = ([unknown, ...unknown[]] | string) & 133 | ('created' | 'edited' | 'deleted')[] 134 | /** 135 | * Runs your workflow anytime the issue_comment event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/issues/comments/. 136 | */ 137 | export type EventObject5 = { 138 | types?: Types5 139 | [k: string]: unknown 140 | } & ({ 141 | [k: string]: unknown 142 | } | null) 143 | /** 144 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 145 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 146 | */ 147 | export type Types5 = ([unknown, ...unknown[]] | string) & 148 | ('created' | 'edited' | 'deleted')[] 149 | /** 150 | * Runs your workflow anytime the issues event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/issues. 151 | */ 152 | export type EventObject6 = { 153 | types?: Types6 154 | [k: string]: unknown 155 | } & ({ 156 | [k: string]: unknown 157 | } | null) 158 | /** 159 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 160 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 161 | */ 162 | export type Types6 = ([unknown, ...unknown[]] | string) & 163 | ( 164 | | 'opened' 165 | | 'edited' 166 | | 'deleted' 167 | | 'transferred' 168 | | 'pinned' 169 | | 'unpinned' 170 | | 'closed' 171 | | 'reopened' 172 | | 'assigned' 173 | | 'unassigned' 174 | | 'labeled' 175 | | 'unlabeled' 176 | | 'locked' 177 | | 'unlocked' 178 | | 'milestoned' 179 | | 'demilestoned' 180 | )[] 181 | /** 182 | * Runs your workflow anytime the label event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/issues/labels/. 183 | */ 184 | export type EventObject7 = { 185 | types?: Types7 186 | [k: string]: unknown 187 | } & ({ 188 | [k: string]: unknown 189 | } | null) 190 | /** 191 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 192 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 193 | */ 194 | export type Types7 = ([unknown, ...unknown[]] | string) & 195 | ('created' | 'edited' | 'deleted')[] 196 | /** 197 | * Runs your workflow when a pull request is added to a merge queue, which adds the pull request to a merge group. For information about the merge queue, see https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/merging-a-pull-request-with-a-merge-queue . 198 | */ 199 | export type EventObject8 = { 200 | types?: Types8 201 | [k: string]: unknown 202 | } & ({ 203 | [k: string]: unknown 204 | } | null) 205 | /** 206 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 207 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 208 | */ 209 | export type Types8 = ([unknown, ...unknown[]] | string) & 'checks_requested'[] 210 | /** 211 | * Runs your workflow anytime the milestone event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/issues/milestones/. 212 | */ 213 | export type EventObject9 = { 214 | types?: Types9 215 | [k: string]: unknown 216 | } & ({ 217 | [k: string]: unknown 218 | } | null) 219 | /** 220 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 221 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 222 | */ 223 | export type Types9 = ([unknown, ...unknown[]] | string) & 224 | ('created' | 'closed' | 'opened' | 'edited' | 'deleted')[] 225 | /** 226 | * Runs your workflow anytime the project event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/projects/. 227 | */ 228 | export type EventObject10 = { 229 | types?: Types10 230 | [k: string]: unknown 231 | } & ({ 232 | [k: string]: unknown 233 | } | null) 234 | /** 235 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 236 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 237 | */ 238 | export type Types10 = ([unknown, ...unknown[]] | string) & 239 | ('created' | 'updated' | 'closed' | 'reopened' | 'edited' | 'deleted')[] 240 | /** 241 | * Runs your workflow anytime the project_card event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/projects/cards. 242 | */ 243 | export type EventObject11 = { 244 | types?: Types11 245 | [k: string]: unknown 246 | } & ({ 247 | [k: string]: unknown 248 | } | null) 249 | /** 250 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 251 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 252 | */ 253 | export type Types11 = ([unknown, ...unknown[]] | string) & 254 | ('created' | 'moved' | 'converted' | 'edited' | 'deleted')[] 255 | /** 256 | * Runs your workflow anytime the project_column event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/projects/columns. 257 | */ 258 | export type EventObject12 = { 259 | types?: Types12 260 | [k: string]: unknown 261 | } & ({ 262 | [k: string]: unknown 263 | } | null) 264 | /** 265 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 266 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 267 | */ 268 | export type Types12 = ([unknown, ...unknown[]] | string) & 269 | ('created' | 'updated' | 'moved' | 'deleted')[] 270 | /** 271 | * Runs your workflow anytime the pull_request event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/pulls. 272 | * Note: Workflows do not run on private base repositories when you open a pull request from a forked repository. 273 | * When you create a pull request from a forked repository to the base repository, GitHub sends the pull_request event to the base repository and no pull request events occur on the forked repository. 274 | * Workflows don't run on forked repositories by default. You must enable GitHub Actions in the Actions tab of the forked repository. 275 | * The permissions for the GITHUB_TOKEN in forked repositories is read-only. For more information about the GITHUB_TOKEN, see https://help.github.com/en/articles/virtual-environments-for-github-actions. 276 | */ 277 | export type Ref = { 278 | types?: Types13 279 | /** 280 | * This interface was referenced by `undefined`'s JSON-Schema definition 281 | * via the `patternProperty` "^(branche|tag|path)s(-ignore)?$". 282 | */ 283 | [k: string]: unknown[] 284 | } & ({ 285 | [k: string]: unknown 286 | } | null) 287 | /** 288 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 289 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 290 | */ 291 | export type Types13 = ([unknown, ...unknown[]] | string) & 292 | ( 293 | | 'assigned' 294 | | 'unassigned' 295 | | 'labeled' 296 | | 'unlabeled' 297 | | 'opened' 298 | | 'edited' 299 | | 'closed' 300 | | 'reopened' 301 | | 'synchronize' 302 | | 'converted_to_draft' 303 | | 'ready_for_review' 304 | | 'locked' 305 | | 'unlocked' 306 | | 'milestoned' 307 | | 'demilestoned' 308 | | 'review_requested' 309 | | 'review_request_removed' 310 | | 'auto_merge_enabled' 311 | | 'auto_merge_disabled' 312 | | 'enqueued' 313 | | 'dequeued' 314 | )[] 315 | /** 316 | * Runs your workflow anytime the pull_request_review event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/pulls/reviews. 317 | * Note: Workflows do not run on private base repositories when you open a pull request from a forked repository. 318 | * When you create a pull request from a forked repository to the base repository, GitHub sends the pull_request event to the base repository and no pull request events occur on the forked repository. 319 | * Workflows don't run on forked repositories by default. You must enable GitHub Actions in the Actions tab of the forked repository. 320 | * The permissions for the GITHUB_TOKEN in forked repositories is read-only. For more information about the GITHUB_TOKEN, see https://help.github.com/en/articles/virtual-environments-for-github-actions. 321 | */ 322 | export type EventObject13 = { 323 | types?: Types14 324 | [k: string]: unknown 325 | } & ({ 326 | [k: string]: unknown 327 | } | null) 328 | /** 329 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 330 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 331 | */ 332 | export type Types14 = ([unknown, ...unknown[]] | string) & 333 | ('submitted' | 'edited' | 'dismissed')[] 334 | /** 335 | * Runs your workflow anytime a comment on a pull request's unified diff is modified, which triggers the pull_request_review_comment event. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/pulls/comments. 336 | * Note: Workflows do not run on private base repositories when you open a pull request from a forked repository. 337 | * When you create a pull request from a forked repository to the base repository, GitHub sends the pull_request event to the base repository and no pull request events occur on the forked repository. 338 | * Workflows don't run on forked repositories by default. You must enable GitHub Actions in the Actions tab of the forked repository. 339 | * The permissions for the GITHUB_TOKEN in forked repositories is read-only. For more information about the GITHUB_TOKEN, see https://help.github.com/en/articles/virtual-environments-for-github-actions. 340 | */ 341 | export type EventObject14 = { 342 | types?: Types15 343 | [k: string]: unknown 344 | } & ({ 345 | [k: string]: unknown 346 | } | null) 347 | /** 348 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 349 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 350 | */ 351 | export type Types15 = ([unknown, ...unknown[]] | string) & 352 | ('created' | 'edited' | 'deleted')[] 353 | /** 354 | * This event is similar to pull_request, except that it runs in the context of the base repository of the pull request, rather than in the merge commit. This means that you can more safely make your secrets available to the workflows triggered by the pull request, because only workflows defined in the commit on the base repository are run. For example, this event allows you to create workflows that label and comment on pull requests, based on the contents of the event payload. 355 | */ 356 | export type Ref1 = { 357 | types?: Types16 358 | /** 359 | * This interface was referenced by `undefined`'s JSON-Schema definition 360 | * via the `patternProperty` "^(branche|tag|path)s(-ignore)?$". 361 | */ 362 | [k: string]: unknown 363 | } & ({ 364 | [k: string]: unknown 365 | } | null) 366 | /** 367 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 368 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 369 | */ 370 | export type Types16 = ([unknown, ...unknown[]] | string) & 371 | ( 372 | | 'assigned' 373 | | 'unassigned' 374 | | 'labeled' 375 | | 'unlabeled' 376 | | 'opened' 377 | | 'edited' 378 | | 'closed' 379 | | 'reopened' 380 | | 'synchronize' 381 | | 'converted_to_draft' 382 | | 'ready_for_review' 383 | | 'locked' 384 | | 'unlocked' 385 | | 'review_requested' 386 | | 'review_request_removed' 387 | | 'auto_merge_enabled' 388 | | 'auto_merge_disabled' 389 | )[] 390 | /** 391 | * Runs your workflow when someone pushes to a repository branch, which triggers the push event. 392 | * Note: The webhook payload available to GitHub Actions does not include the added, removed, and modified attributes in the commit object. You can retrieve the full commit object using the REST API. For more information, see https://developer.github.com/v3/repos/commits/#get-a-single-commit. 393 | */ 394 | export type Ref2 = { 395 | branches?: Branch 396 | 'branches-ignore'?: Branch 397 | tags?: Branch 398 | 'tags-ignore'?: Branch 399 | paths?: Path 400 | 'paths-ignore'?: Path 401 | /** 402 | * This interface was referenced by `undefined`'s JSON-Schema definition 403 | * via the `patternProperty` "^(branche|tag|path)s(-ignore)?$". 404 | */ 405 | [k: string]: string[] 406 | } & ({ 407 | [k: string]: unknown 408 | } | null) 409 | /** 410 | * When using the push and pull_request events, you can configure a workflow to run on specific branches or tags. If you only define only tags or only branches, the workflow won't run for events affecting the undefined Git ref. 411 | * The branches, branches-ignore, tags, and tags-ignore keywords accept glob patterns that use the * and ** wildcard characters to match more than one branch or tag name. For more information, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet. 412 | * The patterns defined in branches and tags are evaluated against the Git ref's name. For example, defining the pattern mona/octocat in branches will match the refs/heads/mona/octocat Git ref. The pattern releases/** will match the refs/heads/releases/10 Git ref. 413 | * You can use two types of filters to prevent a workflow from running on pushes and pull requests to tags and branches: 414 | * - branches or branches-ignore - You cannot use both the branches and branches-ignore filters for the same event in a workflow. Use the branches filter when you need to filter branches for positive matches and exclude branches. Use the branches-ignore filter when you only need to exclude branch names. 415 | * - tags or tags-ignore - You cannot use both the tags and tags-ignore filters for the same event in a workflow. Use the tags filter when you need to filter tags for positive matches and exclude tags. Use the tags-ignore filter when you only need to exclude tag names. 416 | * You can exclude tags and branches using the ! character. The order that you define patterns matters. 417 | * - A matching negative pattern (prefixed with !) after a positive match will exclude the Git ref. 418 | * - A matching positive pattern after a negative match will include the Git ref again. 419 | * 420 | * @minItems 1 421 | */ 422 | export type Branch = [string, ...string[]] 423 | /** 424 | * When using the push and pull_request events, you can configure a workflow to run when at least one file does not match paths-ignore or at least one modified file matches the configured paths. Path filters are not evaluated for pushes to tags. 425 | * The paths-ignore and paths keywords accept glob patterns that use the * and ** wildcard characters to match more than one path name. For more information, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet. 426 | * You can exclude paths using two types of filters. You cannot use both of these filters for the same event in a workflow. 427 | * - paths-ignore - Use the paths-ignore filter when you only need to exclude path names. 428 | * - paths - Use the paths filter when you need to filter paths for positive matches and exclude paths. 429 | * 430 | * @minItems 1 431 | */ 432 | export type Path = [string, ...string[]] 433 | /** 434 | * Runs your workflow anytime a package is published or updated. For more information, see https://help.github.com/en/github/managing-packages-with-github-packages. 435 | */ 436 | export type EventObject15 = { 437 | types?: Types17 438 | [k: string]: unknown 439 | } & ({ 440 | [k: string]: unknown 441 | } | null) 442 | /** 443 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 444 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 445 | */ 446 | export type Types17 = ([unknown, ...unknown[]] | string) & 447 | ('published' | 'updated')[] 448 | /** 449 | * Runs your workflow anytime the release event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/repos/releases/ in the GitHub Developer documentation. 450 | */ 451 | export type EventObject16 = { 452 | types?: Types18 453 | [k: string]: unknown 454 | } & ({ 455 | [k: string]: unknown 456 | } | null) 457 | /** 458 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 459 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 460 | */ 461 | export type Types18 = ([unknown, ...unknown[]] | string) & 462 | ( 463 | | 'published' 464 | | 'unpublished' 465 | | 'created' 466 | | 'edited' 467 | | 'deleted' 468 | | 'prereleased' 469 | | 'released' 470 | )[] 471 | /** 472 | * A string identifier to associate with the input. The value of is a map of the input's metadata. The must be a unique identifier within the inputs object. The must start with a letter or _ and contain only alphanumeric characters, -, or _. 473 | * 474 | * This interface was referenced by `undefined`'s JSON-Schema definition 475 | * via the `patternProperty` "^[_a-zA-Z][a-zA-Z0-9_-]*$". 476 | */ 477 | export type WorkflowDispatchInput = { 478 | [k: string]: unknown 479 | } & { 480 | /** 481 | * A string description of the input parameter. 482 | */ 483 | description: string 484 | /** 485 | * A string shown to users using the deprecated input. 486 | */ 487 | deprecationMessage?: string 488 | /** 489 | * A boolean to indicate whether the action requires the input parameter. Set to true when the parameter is required. 490 | */ 491 | required?: boolean 492 | /** 493 | * A string representing the default value. The default value is used when an input parameter isn't specified in a workflow file. 494 | */ 495 | default?: { 496 | [k: string]: unknown 497 | } 498 | /** 499 | * A string representing the type of the input. 500 | */ 501 | type?: 'string' | 'choice' | 'boolean' | 'number' | 'environment' 502 | /** 503 | * The options of the dropdown list, if the type is a choice. 504 | * 505 | * @minItems 1 506 | */ 507 | options?: [string, ...string[]] 508 | } 509 | /** 510 | * This event occurs when a workflow run is requested or completed, and allows you to execute a workflow based on the finished result of another workflow. For example, if your pull_request workflow generates build artifacts, you can create a new workflow that uses workflow_run to analyze the results and add a comment to the original pull request. 511 | */ 512 | export type EventObject17 = { 513 | types?: Types19 514 | /** 515 | * @minItems 1 516 | */ 517 | workflows?: [string, ...string[]] 518 | [k: string]: unknown 519 | } & ({ 520 | [k: string]: unknown 521 | } | null) 522 | /** 523 | * Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary. 524 | * You can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events. 525 | */ 526 | export type Types19 = ([unknown, ...unknown[]] | string) & 527 | ('requested' | 'completed' | 'in_progress')[] 528 | export type StringContainingExpressionSyntax = string 529 | /** 530 | * You can override the default shell settings in the runner's operating system using the shell keyword. You can use built-in shell keywords, or you can define a custom set of shell options. 531 | */ 532 | export type Shell = 533 | | string 534 | | ('bash' | 'pwsh' | 'python' | 'sh' | 'cmd' | 'powershell') 535 | /** 536 | * Using the working-directory keyword, you can specify the working directory of where to run the command. 537 | */ 538 | export type WorkingDirectory = string 539 | export type ExpressionSyntax = string 540 | /** 541 | * Identifies any jobs that must complete successfully before this job will run. It can be a string or array of strings. If a job fails, all jobs that need it are skipped unless the jobs use a conditional statement that causes the job to continue. 542 | */ 543 | export type JobNeeds = [Name, ...Name[]] | Name 544 | export type Name = string 545 | /** 546 | * You can modify the default permissions granted to the GITHUB_TOKEN, adding or removing access as required, so that you only allow the minimum required access. 547 | */ 548 | export type Permissions = ('read-all' | 'write-all') | PermissionsEvent 549 | export type PermissionsLevel = 'read' | 'write' | 'none' 550 | export type Step = { 551 | /** 552 | * A unique identifier for the step. You can use the id to reference the step in contexts. For more information, see https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. 553 | */ 554 | id?: string 555 | /** 556 | * You can use the if conditional to prevent a step from running unless a condition is met. You can use any supported context and expression to create a conditional. 557 | * Expressions in an if conditional do not require the ${{ }} syntax. For more information, see https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. 558 | */ 559 | if?: boolean | number | string 560 | /** 561 | * A name for your step to display on GitHub. 562 | */ 563 | name?: string 564 | /** 565 | * Selects an action to run as part of a step in your job. An action is a reusable unit of code. You can use an action defined in the same repository as the workflow, a public repository, or in a published Docker container image (https://hub.docker.com/). 566 | * We strongly recommend that you include the version of the action you are using by specifying a Git ref, SHA, or Docker tag number. If you don't specify a version, it could break your workflows or cause unexpected behavior when the action owner publishes an update. 567 | * - Using the commit SHA of a released action version is the safest for stability and security. 568 | * - Using the specific major action version allows you to receive critical fixes and security patches while still maintaining compatibility. It also assures that your workflow should still work. 569 | * - Using the master branch of an action may be convenient, but if someone releases a new major version with a breaking change, your workflow could break. 570 | * Some actions require inputs that you must set using the with keyword. Review the action's README file to determine the inputs required. 571 | * Actions are either JavaScript files or Docker containers. If the action you're using is a Docker container you must run the job in a Linux virtual environment. For more details, see https://help.github.com/en/articles/virtual-environments-for-github-actions. 572 | */ 573 | uses?: string 574 | /** 575 | * Runs command-line programs using the operating system's shell. If you do not provide a name, the step name will default to the text specified in the run command. 576 | * Commands run using non-login shells by default. You can choose a different shell and customize the shell used to run commands. For more information, see https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#using-a-specific-shell. 577 | * Each run keyword represents a new process and shell in the virtual environment. When you provide multi-line commands, each line runs in the same shell. 578 | */ 579 | run?: string 580 | 'working-directory'?: WorkingDirectory 581 | shell?: Shell 582 | with?: Env 583 | /** 584 | * Sets environment variables for steps to use in the virtual environment. You can also set environment variables for the entire workflow or a job. 585 | */ 586 | env?: 587 | | { 588 | [k: string]: string | number | boolean 589 | } 590 | | StringContainingExpressionSyntax 591 | /** 592 | * Prevents a job from failing when a step fails. Set to true to allow a job to pass when this step fails. 593 | */ 594 | 'continue-on-error'?: boolean | ExpressionSyntax 595 | /** 596 | * The maximum number of minutes to run the step before killing the process. 597 | */ 598 | 'timeout-minutes'?: number | ExpressionSyntax 599 | } & Step1 & { 600 | /** 601 | * A unique identifier for the step. You can use the id to reference the step in contexts. For more information, see https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. 602 | */ 603 | id?: string 604 | /** 605 | * You can use the if conditional to prevent a step from running unless a condition is met. You can use any supported context and expression to create a conditional. 606 | * Expressions in an if conditional do not require the ${{ }} syntax. For more information, see https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. 607 | */ 608 | if?: boolean | number | string 609 | /** 610 | * A name for your step to display on GitHub. 611 | */ 612 | name?: string 613 | /** 614 | * Selects an action to run as part of a step in your job. An action is a reusable unit of code. You can use an action defined in the same repository as the workflow, a public repository, or in a published Docker container image (https://hub.docker.com/). 615 | * We strongly recommend that you include the version of the action you are using by specifying a Git ref, SHA, or Docker tag number. If you don't specify a version, it could break your workflows or cause unexpected behavior when the action owner publishes an update. 616 | * - Using the commit SHA of a released action version is the safest for stability and security. 617 | * - Using the specific major action version allows you to receive critical fixes and security patches while still maintaining compatibility. It also assures that your workflow should still work. 618 | * - Using the master branch of an action may be convenient, but if someone releases a new major version with a breaking change, your workflow could break. 619 | * Some actions require inputs that you must set using the with keyword. Review the action's README file to determine the inputs required. 620 | * Actions are either JavaScript files or Docker containers. If the action you're using is a Docker container you must run the job in a Linux virtual environment. For more details, see https://help.github.com/en/articles/virtual-environments-for-github-actions. 621 | */ 622 | uses?: string 623 | /** 624 | * Runs command-line programs using the operating system's shell. If you do not provide a name, the step name will default to the text specified in the run command. 625 | * Commands run using non-login shells by default. You can choose a different shell and customize the shell used to run commands. For more information, see https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#using-a-specific-shell. 626 | * Each run keyword represents a new process and shell in the virtual environment. When you provide multi-line commands, each line runs in the same shell. 627 | */ 628 | run?: string 629 | 'working-directory'?: WorkingDirectory 630 | shell?: Shell 631 | with?: Env 632 | /** 633 | * Sets environment variables for steps to use in the virtual environment. You can also set environment variables for the entire workflow or a job. 634 | */ 635 | env?: 636 | | { 637 | [k: string]: string | number | boolean 638 | } 639 | | StringContainingExpressionSyntax 640 | /** 641 | * Prevents a job from failing when a step fails. Set to true to allow a job to pass when this step fails. 642 | */ 643 | 'continue-on-error'?: boolean | ExpressionSyntax 644 | /** 645 | * The maximum number of minutes to run the step before killing the process. 646 | */ 647 | 'timeout-minutes'?: number | ExpressionSyntax 648 | } & Step1 649 | /** 650 | * A map of the input parameters defined by the action. Each input parameter is a key/value pair. Input parameters are set as environment variables. The variable is prefixed with INPUT_ and converted to upper case. 651 | */ 652 | export type Env = { 653 | args?: string 654 | entrypoint?: string 655 | [k: string]: unknown 656 | } & ( 657 | | { 658 | [k: string]: string | number | boolean 659 | } 660 | | StringContainingExpressionSyntax 661 | ) 662 | export type Step1 = 663 | | { 664 | [k: string]: unknown 665 | } 666 | | { 667 | [k: string]: unknown 668 | } 669 | /** 670 | * A build matrix is a set of different configurations of the virtual environment. For example you might run a job against more than one supported version of a language, operating system, or tool. Each configuration is a copy of the job that runs and reports a status. 671 | * You can specify a matrix by supplying an array for the configuration options. For example, if the GitHub virtual environment supports Node.js versions 6, 8, and 10 you could specify an array of those versions in the matrix. 672 | * When you define a matrix of operating systems, you must set the required runs-on keyword to the operating system of the current job, rather than hard-coding the operating system name. To access the operating system name, you can use the matrix.os context parameter to set runs-on. For more information, see https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. 673 | */ 674 | export type Matrix = 675 | | { 676 | [k: string]: [Configuration, ...Configuration[]] | ExpressionSyntax 677 | } 678 | | ExpressionSyntax 679 | export type Configuration = 680 | | string 681 | | number 682 | | boolean 683 | | { 684 | [k: string]: Configuration 685 | } 686 | | Configuration[] 687 | /** 688 | * To set custom environment variables, you need to specify the variables in the workflow file. You can define environment variables for a step, job, or entire workflow using the jobs..steps[*].env, jobs..env, and env keywords. For more information, see https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsenv 689 | */ 690 | export type Env1 = 691 | | { 692 | [k: string]: string | number | boolean 693 | } 694 | | StringContainingExpressionSyntax 695 | 696 | export interface Workflow { 697 | /** 698 | * The name of your workflow. GitHub displays the names of your workflows on your repository's actions page. If you omit this field, GitHub sets the name to the workflow's filename. 699 | */ 700 | name?: string 701 | /** 702 | * The name of the GitHub event that triggers the workflow. You can provide a single event string, array of events, array of event types, or an event configuration map that schedules a workflow or restricts the execution of a workflow to specific files, tags, or branch changes. For a list of available events, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows. 703 | */ 704 | on: 705 | | Event 706 | | [Event, ...Event[]] 707 | | { 708 | branch_protection_rule?: EventObject 709 | check_run?: EventObject1 710 | check_suite?: EventObject2 711 | /** 712 | * Runs your workflow anytime someone creates a branch or tag, which triggers the create event. For information about the REST API, see https://developer.github.com/v3/git/refs/#create-a-reference. 713 | */ 714 | create?: { 715 | [k: string]: unknown 716 | } | null 717 | /** 718 | * Runs your workflow anytime someone deletes a branch or tag, which triggers the delete event. For information about the REST API, see https://developer.github.com/v3/git/refs/#delete-a-reference. 719 | */ 720 | delete?: { 721 | [k: string]: unknown 722 | } | null 723 | /** 724 | * Runs your workflow anytime someone creates a deployment, which triggers the deployment event. Deployments created with a commit SHA may not have a Git ref. For information about the REST API, see https://developer.github.com/v3/repos/deployments/. 725 | */ 726 | deployment?: { 727 | [k: string]: unknown 728 | } | null 729 | /** 730 | * Runs your workflow anytime a third party provides a deployment status, which triggers the deployment_status event. Deployments created with a commit SHA may not have a Git ref. For information about the REST API, see https://developer.github.com/v3/repos/deployments/#create-a-deployment-status. 731 | */ 732 | deployment_status?: { 733 | [k: string]: unknown 734 | } | null 735 | discussion?: EventObject3 736 | discussion_comment?: EventObject4 737 | /** 738 | * Runs your workflow anytime when someone forks a repository, which triggers the fork event. For information about the REST API, see https://developer.github.com/v3/repos/forks/#create-a-fork. 739 | */ 740 | fork?: { 741 | [k: string]: unknown 742 | } | null 743 | /** 744 | * Runs your workflow when someone creates or updates a Wiki page, which triggers the gollum event. 745 | */ 746 | gollum?: { 747 | [k: string]: unknown 748 | } | null 749 | issue_comment?: EventObject5 750 | issues?: EventObject6 751 | label?: EventObject7 752 | merge_group?: EventObject8 753 | milestone?: EventObject9 754 | /** 755 | * Runs your workflow anytime someone pushes to a GitHub Pages-enabled branch, which triggers the page_build event. For information about the REST API, see https://developer.github.com/v3/repos/pages/. 756 | */ 757 | page_build?: { 758 | [k: string]: unknown 759 | } | null 760 | project?: EventObject10 761 | project_card?: EventObject11 762 | project_column?: EventObject12 763 | /** 764 | * Runs your workflow anytime someone makes a private repository public, which triggers the public event. For information about the REST API, see https://developer.github.com/v3/repos/#edit. 765 | */ 766 | public?: { 767 | [k: string]: unknown 768 | } | null 769 | pull_request?: Ref 770 | pull_request_review?: EventObject13 771 | pull_request_review_comment?: EventObject14 772 | pull_request_target?: Ref1 773 | push?: Ref2 774 | registry_package?: EventObject15 775 | release?: EventObject16 776 | /** 777 | * Runs your workflow anytime the status of a Git commit changes, which triggers the status event. For information about the REST API, see https://developer.github.com/v3/repos/statuses/. 778 | */ 779 | status?: { 780 | [k: string]: unknown 781 | } | null 782 | /** 783 | * Runs your workflow anytime the watch event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/activity/starring/. 784 | */ 785 | watch?: { 786 | [k: string]: unknown 787 | } | null 788 | /** 789 | * Allows workflows to be reused by other workflows. 790 | */ 791 | workflow_call?: { 792 | /** 793 | * When using the workflow_call keyword, you can optionally specify inputs that are passed to the called workflow from the caller workflow. 794 | */ 795 | inputs?: { 796 | /** 797 | * A string identifier to associate with the input. The value of is a map of the input's metadata. The must be a unique identifier within the inputs object. The must start with a letter or _ and contain only alphanumeric characters, -, or _. 798 | * 799 | * This interface was referenced by `undefined`'s JSON-Schema definition 800 | * via the `patternProperty` "^[_a-zA-Z][a-zA-Z0-9_-]*$". 801 | */ 802 | [k: string]: { 803 | /** 804 | * A string description of the input parameter. 805 | */ 806 | description?: string 807 | /** 808 | * A boolean to indicate whether the action requires the input parameter. Set to true when the parameter is required. 809 | */ 810 | required?: boolean 811 | /** 812 | * Required if input is defined for the on.workflow_call keyword. The value of this parameter is a string specifying the data type of the input. This must be one of: boolean, number, or string. 813 | */ 814 | type: 'boolean' | 'number' | 'string' 815 | /** 816 | * The default value is used when an input parameter isn't specified in a workflow file. 817 | */ 818 | default?: boolean | number | string 819 | } 820 | } 821 | /** 822 | * A map of the secrets that can be used in the called workflow. Within the called workflow, you can use the secrets context to refer to a secret. 823 | */ 824 | secrets?: { 825 | /** 826 | * A string identifier to associate with the secret. 827 | * 828 | * This interface was referenced by `undefined`'s JSON-Schema definition 829 | * via the `patternProperty` "^[_a-zA-Z][a-zA-Z0-9_-]*$". 830 | */ 831 | [k: string]: { 832 | /** 833 | * A string description of the secret parameter. 834 | */ 835 | description?: string 836 | /** 837 | * A boolean specifying whether the secret must be supplied. 838 | */ 839 | required?: boolean 840 | } 841 | } 842 | [k: string]: unknown 843 | } 844 | /** 845 | * You can now create workflows that are manually triggered with the new workflow_dispatch event. You will then see a 'Run workflow' button on the Actions tab, enabling you to easily trigger a run. 846 | */ 847 | workflow_dispatch?: { 848 | /** 849 | * Input parameters allow you to specify data that the action expects to use during runtime. GitHub stores input parameters as environment variables. Input ids with uppercase letters are converted to lowercase during runtime. We recommended using lowercase input ids. 850 | */ 851 | inputs?: { 852 | [k: string]: WorkflowDispatchInput 853 | } 854 | } 855 | workflow_run?: EventObject17 856 | /** 857 | * You can use the GitHub API to trigger a webhook event called repository_dispatch when you want to trigger a workflow for activity that happens outside of GitHub. For more information, see https://developer.github.com/v3/repos/#create-a-repository-dispatch-event. 858 | * To trigger the custom repository_dispatch webhook event, you must send a POST request to a GitHub API endpoint and provide an event_type name to describe the activity type. To trigger a workflow run, you must also configure your workflow to use the repository_dispatch event. 859 | */ 860 | repository_dispatch?: { 861 | [k: string]: unknown 862 | } | null 863 | /** 864 | * You can schedule a workflow to run at specific UTC times using POSIX cron syntax (https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html#tag_20_25_07). Scheduled workflows run on the latest commit on the default or base branch. The shortest interval you can run scheduled workflows is once every 5 minutes. 865 | * Note: GitHub Actions does not support the non-standard syntax @yearly, @monthly, @weekly, @daily, @hourly, and @reboot. 866 | * You can use crontab guru (https://crontab.guru/). to help generate your cron syntax and confirm what time it will run. To help you get started, there is also a list of crontab guru examples (https://crontab.guru/examples.html). 867 | * 868 | * @minItems 1 869 | */ 870 | schedule?: [ 871 | { 872 | cron?: string 873 | }, 874 | ...{ 875 | cron?: string 876 | }[], 877 | ] 878 | } 879 | /** 880 | * To set custom environment variables, you need to specify the variables in the workflow file. You can define environment variables for a step, job, or entire workflow using the jobs..steps[*].env, jobs..env, and env keywords. For more information, see https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsenv 881 | */ 882 | env?: 883 | | { 884 | [k: string]: string | number | boolean 885 | } 886 | | StringContainingExpressionSyntax 887 | defaults?: Defaults 888 | /** 889 | * Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. A concurrency group can be any string or expression. The expression can use any context except for the secrets context. 890 | * You can also specify concurrency at the workflow level. 891 | * When a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. Any previously pending job or workflow in the concurrency group will be canceled. To also cancel any currently running job or workflow in the same concurrency group, specify cancel-in-progress: true. 892 | */ 893 | concurrency?: string | Concurrency 894 | /** 895 | * A workflow run is made up of one or more jobs. Jobs run in parallel by default. To run jobs sequentially, you can define dependencies on other jobs using the jobs..needs keyword. 896 | * Each job runs in a fresh instance of the virtual environment specified by runs-on. 897 | * You can run an unlimited number of jobs as long as you are within the workflow usage limits. For more information, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#usage-limits. 898 | */ 899 | jobs: { 900 | /** 901 | * This interface was referenced by `undefined`'s JSON-Schema definition 902 | * via the `patternProperty` "^[_a-zA-Z][a-zA-Z0-9_-]*$". 903 | */ 904 | [k: string]: NormalJob | ReusableWorkflowCallJob 905 | } 906 | /** 907 | * The name for workflow runs generated from the workflow. GitHub displays the workflow run name in the list of workflow runs on your repository's 'Actions' tab. 908 | */ 909 | 'run-name'?: string 910 | permissions?: Permissions 911 | } 912 | /** 913 | * A map of default settings that will apply to all jobs in the workflow. 914 | */ 915 | export interface Defaults { 916 | run?: { 917 | shell?: Shell 918 | 'working-directory'?: WorkingDirectory 919 | } 920 | } 921 | export interface Concurrency { 922 | /** 923 | * When a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. Any previously pending job or workflow in the concurrency group will be canceled. 924 | */ 925 | group: string 926 | /** 927 | * To cancel any currently running job or workflow in the same concurrency group, specify cancel-in-progress: true. 928 | */ 929 | 'cancel-in-progress'?: boolean | ExpressionSyntax 930 | } 931 | /** 932 | * Each job must have an id to associate with the job. The key job_id is a string and its value is a map of the job's configuration data. You must replace with a string that is unique to the jobs object. The must start with a letter or _ and contain only alphanumeric characters, -, or _. 933 | */ 934 | export interface NormalJob { 935 | /** 936 | * The name of the job displayed on GitHub. 937 | */ 938 | name?: string 939 | needs?: JobNeeds 940 | permissions?: Permissions 941 | /** 942 | * The type of machine to run the job on. The machine can be either a GitHub-hosted runner, or a self-hosted runner. 943 | */ 944 | 'runs-on': 945 | | string 946 | | ([string, ...string[]] & unknown[]) 947 | | { 948 | group?: string 949 | labels?: string | string[] 950 | [k: string]: unknown 951 | } 952 | | StringContainingExpressionSyntax 953 | | ExpressionSyntax 954 | /** 955 | * The environment that the job references. 956 | */ 957 | environment?: string | Environment 958 | /** 959 | * A map of outputs for a job. Job outputs are available to all downstream jobs that depend on this job. 960 | */ 961 | outputs?: { 962 | [k: string]: string 963 | } 964 | /** 965 | * To set custom environment variables, you need to specify the variables in the workflow file. You can define environment variables for a step, job, or entire workflow using the jobs..steps[*].env, jobs..env, and env keywords. For more information, see https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsenv 966 | */ 967 | env?: 968 | | { 969 | [k: string]: string | number | boolean 970 | } 971 | | StringContainingExpressionSyntax 972 | defaults?: Defaults1 973 | /** 974 | * You can use the if conditional to prevent a job from running unless a condition is met. You can use any supported context and expression to create a conditional. 975 | * Expressions in an if conditional do not require the ${{ }} syntax. For more information, see https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. 976 | */ 977 | if?: boolean | number | string 978 | /** 979 | * A job contains a sequence of tasks called steps. Steps can run commands, run setup tasks, or run an action in your repository, a public repository, or an action published in a Docker registry. Not all steps run actions, but all actions run as a step. Each step runs in its own process in the virtual environment and has access to the workspace and filesystem. Because steps run in their own process, changes to environment variables are not preserved between steps. GitHub provides built-in steps to set up and complete a job. 980 | * Must contain either `uses` or `run` 981 | * 982 | * 983 | * @minItems 1 984 | */ 985 | steps?: [Step, ...Step[]] 986 | /** 987 | * The maximum number of minutes to let a workflow run before GitHub automatically cancels it. Default: 360 988 | */ 989 | 'timeout-minutes'?: number | ExpressionSyntax 990 | /** 991 | * A strategy creates a build matrix for your jobs. You can define different variations of an environment to run each job in. 992 | */ 993 | strategy?: { 994 | matrix: Matrix 995 | /** 996 | * When set to true, GitHub cancels all in-progress jobs if any matrix job fails. Default: true 997 | */ 998 | 'fail-fast'?: boolean | string 999 | /** 1000 | * The maximum number of jobs that can run simultaneously when using a matrix job strategy. By default, GitHub will maximize the number of jobs run in parallel depending on the available runners on GitHub-hosted virtual machines. 1001 | */ 1002 | 'max-parallel'?: number | string 1003 | } 1004 | /** 1005 | * Prevents a workflow run from failing when a job fails. Set to true to allow a workflow run to pass when this job fails. 1006 | */ 1007 | 'continue-on-error'?: boolean | ExpressionSyntax 1008 | /** 1009 | * A container to run any steps in a job that don't already specify a container. If you have steps that use both script and container actions, the container actions will run as sibling containers on the same network with the same volume mounts. 1010 | * If you do not set a container, all steps will run directly on the host specified by runs-on unless a step refers to an action configured to run in a container. 1011 | */ 1012 | container?: string | Container 1013 | /** 1014 | * Additional containers to host services for a job in a workflow. These are useful for creating databases or cache services like redis. The runner on the virtual machine will automatically create a network and manage the life cycle of the service containers. 1015 | * When you use a service container for a job or your step uses container actions, you don't need to set port information to access the service. Docker automatically exposes all ports between containers on the same network. 1016 | * When both the job and the action run in a container, you can directly reference the container by its hostname. The hostname is automatically mapped to the service name. 1017 | * When a step does not use a container action, you must access the service using localhost and bind the ports. 1018 | */ 1019 | services?: { 1020 | [k: string]: Container 1021 | } 1022 | /** 1023 | * Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. A concurrency group can be any string or expression. The expression can use any context except for the secrets context. 1024 | * You can also specify concurrency at the workflow level. 1025 | * When a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. Any previously pending job or workflow in the concurrency group will be canceled. To also cancel any currently running job or workflow in the same concurrency group, specify cancel-in-progress: true. 1026 | */ 1027 | concurrency?: string | Concurrency 1028 | } 1029 | export interface PermissionsEvent { 1030 | actions?: PermissionsLevel 1031 | attestations?: PermissionsLevel 1032 | checks?: PermissionsLevel 1033 | contents?: PermissionsLevel 1034 | deployments?: PermissionsLevel 1035 | discussions?: PermissionsLevel 1036 | 'id-token'?: PermissionsLevel 1037 | issues?: PermissionsLevel 1038 | packages?: PermissionsLevel 1039 | pages?: PermissionsLevel 1040 | 'pull-requests'?: PermissionsLevel 1041 | 'repository-projects'?: PermissionsLevel 1042 | 'security-events'?: PermissionsLevel 1043 | statuses?: PermissionsLevel 1044 | } 1045 | /** 1046 | * The environment that the job references 1047 | */ 1048 | export interface Environment { 1049 | /** 1050 | * The name of the environment configured in the repo. 1051 | */ 1052 | name: string 1053 | /** 1054 | * A deployment URL 1055 | */ 1056 | url?: string 1057 | } 1058 | /** 1059 | * A map of default settings that will apply to all steps in the job. 1060 | */ 1061 | export interface Defaults1 { 1062 | run?: { 1063 | shell?: Shell 1064 | 'working-directory'?: WorkingDirectory 1065 | } 1066 | } 1067 | export interface Container { 1068 | /** 1069 | * The Docker image to use as the container to run the action. The value can be the Docker Hub image name or a registry name. 1070 | */ 1071 | image: string 1072 | /** 1073 | * If the image's container registry requires authentication to pull the image, you can use credentials to set a map of the username and password. The credentials are the same values that you would provide to the `docker login` command. 1074 | */ 1075 | credentials?: { 1076 | username?: string 1077 | password?: string 1078 | [k: string]: unknown 1079 | } 1080 | /** 1081 | * Sets an array of environment variables in the container. 1082 | */ 1083 | env?: 1084 | | { 1085 | [k: string]: string | number | boolean 1086 | } 1087 | | StringContainingExpressionSyntax 1088 | /** 1089 | * Sets an array of ports to expose on the container. 1090 | * 1091 | * @minItems 1 1092 | */ 1093 | ports?: [number | string, ...(number | string)[]] 1094 | /** 1095 | * Sets an array of volumes for the container to use. You can use volumes to share data between services or other steps in a job. You can specify named Docker volumes, anonymous Docker volumes, or bind mounts on the host. 1096 | * To specify a volume, you specify the source and destination path: : 1097 | * The is a volume name or an absolute path on the host machine, and is an absolute path in the container. 1098 | * 1099 | * @minItems 1 1100 | */ 1101 | volumes?: [string, ...string[]] 1102 | /** 1103 | * Additional Docker container resource options. For a list of options, see https://docs.docker.com/engine/reference/commandline/create/#options. 1104 | */ 1105 | options?: string 1106 | } 1107 | /** 1108 | * Each job must have an id to associate with the job. The key job_id is a string and its value is a map of the job's configuration data. You must replace with a string that is unique to the jobs object. The must start with a letter or _ and contain only alphanumeric characters, -, or _. 1109 | */ 1110 | export interface ReusableWorkflowCallJob { 1111 | /** 1112 | * The name of the job displayed on GitHub. 1113 | */ 1114 | name?: string 1115 | needs?: JobNeeds 1116 | permissions?: Permissions 1117 | /** 1118 | * You can use the if conditional to prevent a job from running unless a condition is met. You can use any supported context and expression to create a conditional. 1119 | * Expressions in an if conditional do not require the ${{ }} syntax. For more information, see https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. 1120 | */ 1121 | if?: boolean | number | string 1122 | /** 1123 | * The location and version of a reusable workflow file to run as a job, of the form './{path/to}/{localfile}.yml' or '{owner}/{repo}/{path}/{filename}@{ref}'. {ref} can be a SHA, a release tag, or a branch name. Using the commit SHA is the safest for stability and security. 1124 | */ 1125 | uses: string 1126 | /** 1127 | * A map of inputs that are passed to the called workflow. Any inputs that you pass must match the input specifications defined in the called workflow. Unlike 'jobs..steps[*].with', the inputs you pass with 'jobs..with' are not be available as environment variables in the called workflow. Instead, you can reference the inputs by using the inputs context. 1128 | */ 1129 | with?: 1130 | | { 1131 | [k: string]: string | number | boolean 1132 | } 1133 | | StringContainingExpressionSyntax 1134 | /** 1135 | * When a job is used to call a reusable workflow, you can use 'secrets' to provide a map of secrets that are passed to the called workflow. Any secrets that you pass must match the names defined in the called workflow. 1136 | */ 1137 | secrets?: Env1 | 'inherit' 1138 | /** 1139 | * A strategy creates a build matrix for your jobs. You can define different variations of an environment to run each job in. 1140 | */ 1141 | strategy?: { 1142 | matrix: Matrix 1143 | /** 1144 | * When set to true, GitHub cancels all in-progress jobs if any matrix job fails. Default: true 1145 | */ 1146 | 'fail-fast'?: boolean | string 1147 | /** 1148 | * The maximum number of jobs that can run simultaneously when using a matrix job strategy. By default, GitHub will maximize the number of jobs run in parallel depending on the available runners on GitHub-hosted virtual machines. 1149 | */ 1150 | 'max-parallel'?: number | string 1151 | } 1152 | /** 1153 | * Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. A concurrency group can be any string or expression. The expression can use any context except for the secrets context. 1154 | * You can also specify concurrency at the workflow level. 1155 | * When a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. Any previously pending job or workflow in the concurrency group will be canceled. To also cancel any currently running job or workflow in the same concurrency group, specify cancel-in-progress: true. 1156 | */ 1157 | concurrency?: string | Concurrency 1158 | } 1159 | -------------------------------------------------------------------------------- /src/lib/types/githubActionsWorkflowExtended.ts: -------------------------------------------------------------------------------- 1 | import { Configuration } from './githubActionsWorkflow' 2 | 3 | export type MatrixConfiguration = string | [Configuration, ...Configuration[]] 4 | -------------------------------------------------------------------------------- /src/lib/types/index.ts: -------------------------------------------------------------------------------- 1 | export * as GeneratedWorkflowTypes from './githubActionsWorkflow' 2 | export * as ExtendedWorkflowTypes from './githubActionsWorkflowExtended' 3 | -------------------------------------------------------------------------------- /src/lib/utils/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { expressions, echoKeyValue, multilineString, workflowOps } from '..' 2 | 3 | describe('expressions', () => { 4 | describe('expression', () => { 5 | it('should wrap the given expression in ${{ }}', () => { 6 | expect(expressions.expn('test.expression')).toBe('${{ test.expression }}') 7 | }) 8 | }) 9 | 10 | describe('env', () => { 11 | it('should wrap the given envName in ${{ env. }}', () => { 12 | expect(expressions.env('MY_ENV')).toBe('${{ env.MY_ENV }}') 13 | }) 14 | }) 15 | 16 | describe('secret', () => { 17 | it('should wrap the given secretName in ${{ secrets. }}', () => { 18 | expect(expressions.secret('MY_SECRET')).toBe('${{ secrets.MY_SECRET }}') 19 | }) 20 | }) 21 | 22 | describe('var', () => { 23 | it('should wrap the given varName in ${{ vars. }}', () => { 24 | expect(expressions.var('MY_VAR')).toBe('${{ vars.MY_VAR }}') 25 | }) 26 | }) 27 | 28 | describe('outputTernary', () => { 29 | it('should format the provided condition, ifTrue, and ifFalse values into a ternary expression', () => { 30 | expect(expressions.ternary('condition', 'trueValue', 'falseValue')).toBe( 31 | '${{ condition && trueValue || falseValue }}', 32 | ) 33 | }) 34 | }) 35 | }) 36 | 37 | describe('echoKeyValue', () => { 38 | describe('githubEnv', () => { 39 | it('should echo the given key and value to $GITHUB_ENV', () => { 40 | expect(echoKeyValue.toGithubEnv('key', 'value')).toBe( 41 | 'echo "key=value" >> $GITHUB_ENV', 42 | ) 43 | }) 44 | }) 45 | 46 | describe('githubOutput', () => { 47 | it('should echo the given key and value to $GITHUB_OUTPUT', () => { 48 | expect(echoKeyValue.toGithubOutput('key', 'value')).toBe( 49 | 'echo "key=value" >> $GITHUB_OUTPUT', 50 | ) 51 | }) 52 | }) 53 | 54 | describe('location', () => { 55 | it('should echo the given key and value to the specified location', () => { 56 | expect(echoKeyValue.to('key', 'value', '/path/to/location')).toBe( 57 | 'echo "key=value" >> /path/to/location', 58 | ) 59 | }) 60 | }) 61 | }) 62 | 63 | describe('multilineString', () => { 64 | it('should join the given strings with newline characters', () => { 65 | expect(multilineString('line1', 'line2', 'line3')).toBe( 66 | 'line1\nline2\nline3', 67 | ) 68 | }) 69 | 70 | it('escapes backspace characters', () => { 71 | const input = '\b' 72 | const expected = '\\b' 73 | expect(multilineString(input)).toBe(expected) 74 | }) 75 | 76 | it('escapes form feed characters', () => { 77 | const input = '\f' 78 | const expected = '\\f' 79 | expect(multilineString(input)).toBe(expected) 80 | }) 81 | 82 | it('escapes newline characters', () => { 83 | const input = '\n' 84 | const expected = '\\n' 85 | expect(multilineString(input)).toBe(expected) 86 | }) 87 | 88 | it('escapes carriage return characters', () => { 89 | const input = '\r' 90 | const expected = '\\r' 91 | expect(multilineString(input)).toBe(expected) 92 | }) 93 | 94 | it('escapes tab characters', () => { 95 | const input = '\t' 96 | const expected = '\\t' 97 | expect(multilineString(input)).toBe(expected) 98 | }) 99 | 100 | it('escapes vertical tab characters', () => { 101 | const input = '\v' 102 | const expected = '\\v' 103 | expect(multilineString(input)).toBe(expected) 104 | }) 105 | 106 | it('escapes null characters', () => { 107 | const input = '\0' 108 | const expected = '\\0' 109 | expect(multilineString(input)).toBe(expected) 110 | }) 111 | 112 | it('escapes backslash characters', () => { 113 | const input = '\\' 114 | const expected = '\\\\' 115 | expect(multilineString(input)).toBe(expected) 116 | }) 117 | 118 | it('escapes multiple escape sequences', () => { 119 | const input = '\b\f\n\r\t\v\0\\' 120 | const expected = '\\b\\f\\n\\r\\t\\v\\0\\\\' 121 | expect(multilineString(input)).toBe(expected) 122 | }) 123 | 124 | it('returns the original string if there are no characters to escape', () => { 125 | const input = 'Hello World!' 126 | expect(multilineString(input)).toBe(input) 127 | }) 128 | 129 | it('handles multiple strings', () => { 130 | const inputs = ['Hello', 'World\n', '\\Hello\\'] 131 | const expected = 'Hello\nWorld\\n\n\\\\Hello\\\\' 132 | expect(multilineString(...inputs)).toBe(expected) 133 | }) 134 | 135 | it('handles empty strings', () => { 136 | const input = '' 137 | expect(multilineString(input)).toBe('') 138 | }) 139 | }) 140 | 141 | describe('outputTernary', () => { 142 | it('should format the provided condition, ifTrue, and ifFalse values into a ternary expression', () => { 143 | expect(workflowOps.ternary('condition', 'trueValue', 'falseValue')).toBe( 144 | '${{ condition && trueValue || falseValue }}', 145 | ) 146 | }) 147 | }) 148 | -------------------------------------------------------------------------------- /src/lib/utils/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility methods to generate specific Github Actions workflow expressions. 3 | */ 4 | export const expressions = { 5 | /** 6 | * Wraps the provided expression inside a special format understood by Github Actions 7 | * i.e. `${{ }}` 8 | * 9 | * Ref: https://docs.github.com/en/actions/learn-github-actions/expressions 10 | * 11 | * @param {string} expression - The raw expression to wrap. 12 | * @returns {string} The wrapped expression. 13 | */ 14 | expn(expression: string): string { 15 | return `\${{ ${expression} }}` 16 | }, 17 | 18 | /** 19 | * Generates an environment variable expression 20 | * i.e. `${{ env. }}` 21 | * 22 | * Ref: https://docs.github.com/en/actions/learn-github-actions/expressions 23 | * 24 | * @param {string} envName - The name of the environment variable. 25 | * @returns {string} The formatted environment variable reference. 26 | */ 27 | env(envName: string): string { 28 | return this.expn(`env.${envName}`) 29 | }, 30 | 31 | /** 32 | * Generates a secrets expression 33 | * i.e. `${{ secrets. }}` 34 | * 35 | * Ref: https://docs.github.com/en/actions/learn-github-actions/expressions 36 | * 37 | * @param {string} secretName - The name of the secret. 38 | * @returns {string} The formatted secret reference. 39 | */ 40 | secret(secretName: string): string { 41 | return this.expn(`secrets.${secretName}`) 42 | }, 43 | 44 | /** 45 | * Generates a variables expression 46 | * i.e. `${{ vars. }}` 47 | * 48 | * Ref: https://docs.github.com/en/actions/learn-github-actions/expressions 49 | * 50 | * @param {string} varName - The name of the variable. 51 | * @returns {string} The formatted variable reference. 52 | */ 53 | var(varName: string): string { 54 | return this.expn(`vars.${varName}`) 55 | }, 56 | 57 | /** 58 | * Generates a YAML compatible ternary operation 59 | * i.e. `${{ && || }}` 60 | * 61 | * @param {string} condition - The condition to evaluate. 62 | * @param {string} ifTrue - The value to return if the condition is true. 63 | * @param {string} ifFalse - The value to return if the condition is false. 64 | * @returns {string} The formatted ternary operation. 65 | */ 66 | ternary(condition: string, ifTrue: string, ifFalse: string): string { 67 | return `\${{ ${condition} && ${ifTrue} || ${ifFalse} }}` 68 | }, 69 | } 70 | 71 | /** 72 | * Utility methods to echo key-value pairs to various targets. 73 | */ 74 | export const echoKeyValue = { 75 | /** 76 | * Outputs a key-value pair to a specified target. 77 | * 78 | * @param {string} key - The key to output. 79 | * @param {string} value - The value corresponding to the key. 80 | * @param {string} to - The target to output to. 81 | * @returns {string} The command string to output the key-value pair. 82 | */ 83 | to(key: string, value: string, to: string): string { 84 | return `echo "${key}=${value}" >> ${to}` 85 | }, 86 | 87 | /** 88 | * Outputs a key-value pair to the GitHub Actions workflow environment. 89 | * i.e. `echo "KEY=VALUE" >> $GITHUB_ENV` 90 | * 91 | * @param {string} key - The key to output. 92 | * @param {string} value - The value corresponding to the key. 93 | * @returns {string} The command string to output the key-value pair to GitHub Actions workflow environment. 94 | */ 95 | toGithubEnv(key: string, value: string): string { 96 | return `echo "${key}=${value}" >> $GITHUB_ENV` 97 | }, 98 | 99 | /** 100 | * Outputs a key-value pair to the GitHub Actions workflow output. 101 | * i.e. `echo "KEY=VALUE" >> $GITHUB_OUTPUT` 102 | * 103 | * @param {string} key - The key to output. 104 | * @param {string} value - The value corresponding to the key. 105 | * @returns {string} The command string to output the key-value pair to GitHub output. 106 | */ 107 | toGithubOutput(key: string, value: string): string { 108 | return `echo "${key}=${value}" >> $GITHUB_OUTPUT` 109 | }, 110 | } 111 | 112 | /** 113 | * Joins multiple strings with newline characters to create a multi-line YAML string. 114 | * 115 | * @param {...string[]} strings - An array of strings to join. 116 | * @returns {string} A multi-line YAML string. 117 | * 118 | * @example 119 | * ```yaml 120 | * some-field: >- 121 | * This is the first line in a multi-line YAML string. 122 | * 123 | * This is the second line. 124 | * ``` 125 | */ 126 | export const multilineString = (...strings: string[]): string => { 127 | return strings 128 | .map((str) => { 129 | const escapedStr = str.replace( 130 | /[\b\f\n\r\t\v\0\\]/g, 131 | (match: string): string => { 132 | return { 133 | '\b': '\\b', 134 | '\f': '\\f', 135 | '\n': '\\n', 136 | '\r': '\\r', 137 | '\t': '\\t', 138 | '\v': '\\v', 139 | '\0': '\\0', 140 | '\\': '\\\\', 141 | }[match] as string 142 | }, 143 | ) 144 | 145 | return escapedStr 146 | }) 147 | .join('\n') 148 | } 149 | 150 | /** 151 | * Utility methods related to GitHub workflow operations. 152 | */ 153 | export const workflowOps = { 154 | /** 155 | * @deprecated since version 0.2.0 use `expressions.ternary` instead. 156 | * 157 | * Generates a YAML compatible ternary operation 158 | * i.e. `${{ && || }}` 159 | * 160 | * @param {string} condition - The condition to evaluate. 161 | * @param {string} ifTrue - The value to return if the condition is true. 162 | * @param {string} ifFalse - The value to return if the condition is false. 163 | * @returns {string} The formatted ternary operation. 164 | */ 165 | ternary(condition: string, ifTrue: string, ifFalse: string): string { 166 | return `\${{ ${condition} && ${ifTrue} || ${ifFalse} }}` 167 | }, 168 | } 169 | -------------------------------------------------------------------------------- /src/lib/workflow/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { Workflow } from './' 2 | import { NormalJob, ReusableWorkflowCallJob } from '../job' 3 | 4 | describe('Workflow', () => { 5 | describe('addJobs', () => { 6 | it('should add multiple jobs to the workflow', () => { 7 | const mockJob1 = new NormalJob('job1', { 8 | 'runs-on': 'ubuntu-latest', 9 | steps: [ 10 | { 11 | name: 'Checkout', 12 | uses: 'actions/checkout@v3', 13 | }, 14 | ], 15 | }) 16 | 17 | const mockJob2 = new ReusableWorkflowCallJob('job2', { 18 | uses: 'your-org/your-repo/.github/workflows/reusable-workflow.yml@main', 19 | }) 20 | 21 | const workflow = new Workflow('testWorkflow', { 22 | name: 'testWorkflow', 23 | on: { 24 | workflow_dispatch: {}, 25 | }, 26 | }) 27 | 28 | workflow.addJobs([mockJob1, mockJob2]) 29 | 30 | expect(workflow.workflow).toEqual({ 31 | name: 'testWorkflow', 32 | on: { 33 | workflow_dispatch: {}, 34 | }, 35 | jobs: { 36 | job1: { 37 | 'runs-on': 'ubuntu-latest', 38 | steps: [ 39 | { 40 | name: 'Checkout', 41 | uses: 'actions/checkout@v3', 42 | }, 43 | ], 44 | }, 45 | job2: { 46 | uses: 'your-org/your-repo/.github/workflows/reusable-workflow.yml@main', 47 | }, 48 | }, 49 | }) 50 | }) 51 | 52 | it('should merge new jobs with existing jobs', () => { 53 | const mockJob1 = new NormalJob('job1', { 54 | 'runs-on': 'ubuntu-latest', 55 | steps: [ 56 | { 57 | name: 'Install Node', 58 | uses: 'actions/node@v3', 59 | }, 60 | ], 61 | }) 62 | 63 | const mockJob2 = new ReusableWorkflowCallJob('job2', { 64 | uses: 'your-org/your-repo/.github/workflows/reusable-workflow.yml@main', 65 | }) 66 | 67 | const workflow = new Workflow('testWorkflow', { 68 | name: 'testWorkflow', 69 | on: { 70 | workflow_dispatch: {}, 71 | }, 72 | jobs: { 73 | job0: { 74 | 'runs-on': 'ubuntu-latest', 75 | steps: [ 76 | { 77 | name: 'Checkout', 78 | uses: 'actions/checkout@v3', 79 | }, 80 | ], 81 | }, 82 | }, 83 | }) 84 | 85 | workflow.addJobs([mockJob1, mockJob2]) 86 | 87 | expect(workflow.workflow).toEqual({ 88 | name: 'testWorkflow', 89 | on: { 90 | workflow_dispatch: {}, 91 | }, 92 | jobs: { 93 | job0: { 94 | 'runs-on': 'ubuntu-latest', 95 | steps: [ 96 | { 97 | name: 'Checkout', 98 | uses: 'actions/checkout@v3', 99 | }, 100 | ], 101 | }, 102 | job1: { 103 | 'runs-on': 'ubuntu-latest', 104 | steps: [ 105 | { 106 | name: 'Install Node', 107 | uses: 'actions/node@v3', 108 | }, 109 | ], 110 | }, 111 | job2: { 112 | uses: 'your-org/your-repo/.github/workflows/reusable-workflow.yml@main', 113 | }, 114 | }, 115 | }) 116 | }) 117 | }) 118 | 119 | describe('addJob', () => { 120 | it('should add a single job to the workflow', () => { 121 | const mockJob1 = new NormalJob('job1', { 122 | 'runs-on': 'ubuntu-latest', 123 | steps: [ 124 | { 125 | name: 'Checkout', 126 | uses: 'actions/checkout@v3', 127 | }, 128 | ], 129 | }) 130 | 131 | const workflow = new Workflow('testWorkflow', { 132 | name: 'testWorkflow', 133 | on: { 134 | workflow_dispatch: {}, 135 | }, 136 | }) 137 | 138 | workflow.addJob(mockJob1) 139 | 140 | expect(workflow.workflow).toEqual({ 141 | name: 'testWorkflow', 142 | on: { 143 | workflow_dispatch: {}, 144 | }, 145 | jobs: { 146 | job1: { 147 | 'runs-on': 'ubuntu-latest', 148 | steps: [ 149 | { 150 | name: 'Checkout', 151 | uses: 'actions/checkout@v3', 152 | }, 153 | ], 154 | }, 155 | }, 156 | }) 157 | }) 158 | 159 | it('should merge the new job with existing jobs', () => { 160 | const mockJob1 = new NormalJob('job1', { 161 | 'runs-on': 'ubuntu-latest', 162 | steps: [ 163 | { 164 | name: 'Checkout', 165 | uses: 'actions/checkout@v3', 166 | }, 167 | ], 168 | }) 169 | 170 | const workflow = new Workflow('testWorkflow', { 171 | name: 'testWorkflow', 172 | on: { 173 | workflow_dispatch: {}, 174 | }, 175 | jobs: { 176 | job0: { 177 | 'runs-on': 'ubuntu-latest', 178 | steps: [ 179 | { 180 | name: 'Install Node', 181 | uses: 'actions/node@v3', 182 | }, 183 | ], 184 | }, 185 | }, 186 | }) 187 | 188 | workflow.addJob(mockJob1) 189 | 190 | expect(workflow.workflow).toEqual({ 191 | name: 'testWorkflow', 192 | on: { 193 | workflow_dispatch: {}, 194 | }, 195 | jobs: { 196 | job0: { 197 | 'runs-on': 'ubuntu-latest', 198 | steps: [ 199 | { 200 | name: 'Install Node', 201 | uses: 'actions/node@v3', 202 | }, 203 | ], 204 | }, 205 | job1: { 206 | 'runs-on': 'ubuntu-latest', 207 | steps: [ 208 | { 209 | name: 'Checkout', 210 | uses: 'actions/checkout@v3', 211 | }, 212 | ], 213 | }, 214 | }, 215 | }) 216 | }) 217 | }) 218 | 219 | describe('addEnvs', () => { 220 | it('should add environment variables to the workflow', () => { 221 | const workflow = new Workflow('testWorkflow', { 222 | name: 'testWorkflow', 223 | on: { 224 | workflow_dispatch: {}, 225 | }, 226 | }) 227 | 228 | workflow.addEnvs({ 229 | NODE_ENV: 'production', 230 | API_KEY: '12345', 231 | }) 232 | 233 | expect(workflow.workflow).toEqual({ 234 | name: 'testWorkflow', 235 | on: { 236 | workflow_dispatch: {}, 237 | }, 238 | env: { 239 | NODE_ENV: 'production', 240 | API_KEY: '12345', 241 | }, 242 | }) 243 | }) 244 | 245 | it('should merge new environment variables with existing ones', () => { 246 | const workflow = new Workflow('testWorkflow', { 247 | name: 'testWorkflow', 248 | on: { 249 | workflow_dispatch: {}, 250 | }, 251 | env: { 252 | OLD_ENV: 'oldValue', 253 | }, 254 | }) 255 | 256 | workflow.addEnvs({ 257 | NEW_ENV: 'newValue', 258 | }) 259 | 260 | expect(workflow.workflow).toEqual({ 261 | name: 'testWorkflow', 262 | on: { 263 | workflow_dispatch: {}, 264 | }, 265 | env: { 266 | OLD_ENV: 'oldValue', 267 | NEW_ENV: 'newValue', 268 | }, 269 | }) 270 | }) 271 | }) 272 | 273 | describe('constructor', () => { 274 | it('should construct the workflow with given filename and properties', () => { 275 | const workflow = new Workflow('testWorkflow', { 276 | name: 'testWorkflow', 277 | on: { 278 | workflow_dispatch: {}, 279 | }, 280 | }) 281 | 282 | expect(workflow.filename).toBe('testWorkflow') 283 | expect(workflow.workflow).toEqual({ 284 | name: 'testWorkflow', 285 | on: { 286 | workflow_dispatch: {}, 287 | }, 288 | }) 289 | }) 290 | }) 291 | }) 292 | -------------------------------------------------------------------------------- /src/lib/workflow/index.ts: -------------------------------------------------------------------------------- 1 | import { GeneratedWorkflowTypes } from '../types' 2 | import * as Jobs from '../job' 3 | 4 | export class Workflow { 5 | public workflow: Partial 6 | 7 | /** 8 | * The filename of the workflow e.g. `main.yml` 9 | */ 10 | public filename?: string 11 | 12 | addEnvs(envs: GeneratedWorkflowTypes.Workflow['env']): this { 13 | if (this.workflow.env && typeof this.workflow.env === 'object') 14 | this.workflow.env = { 15 | ...(this.workflow.env as { 16 | [k: string]: string | number | boolean 17 | }), 18 | ...(envs as { [k: string]: string | number | boolean }), 19 | } 20 | else this.workflow.env = envs 21 | 22 | return this 23 | } 24 | 25 | addJobs(jobs: (Jobs.NormalJob | Jobs.ReusableWorkflowCallJob)[]): this { 26 | this.workflow.jobs = { 27 | ...(this.workflow.jobs || {}), 28 | ...jobs.reduce((acc, job) => ({ ...acc, [`${job.name}`]: job.job }), {}), 29 | } 30 | 31 | return this 32 | } 33 | 34 | addJob(job: Jobs.NormalJob | Jobs.ReusableWorkflowCallJob): this { 35 | this.workflow.jobs = { 36 | ...(this.workflow.jobs || {}), 37 | [`${job.name}`]: job.job, 38 | } 39 | 40 | return this 41 | } 42 | 43 | constructor( 44 | filename: string, 45 | workflowProps: Partial, 46 | ) { 47 | this.filename = filename 48 | this.workflow = { 49 | ...workflowProps, 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./build", 5 | "resolveJsonModule": true 6 | }, 7 | "include": ["src/**/*"], 8 | "exclude": ["**/*.spec.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src"], 3 | "compilerOptions": { 4 | "outDir": "./build", 5 | "isolatedModules": true, 6 | "resolveJsonModule": true, 7 | "target": "ES2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, 8 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 9 | "declaration": true /* Generates corresponding '.d.ts' file. */, 10 | "sourceMap": true /* Generates corresponding '.map' file. */, 11 | "removeComments": false /* Do not emit comments to output. */, 12 | "downlevelIteration": true /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */, 13 | "strict": true /* Enable all strict type-checking options. */, 14 | "strictPropertyInitialization": false /* Enable strict checking of property initialization in classes. */, 15 | "noUnusedLocals": true /* Report errors on unused locals. */, 16 | "noUnusedParameters": true /* Report errors on unused parameters. */, 17 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 18 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 19 | "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 20 | "skipLibCheck": true /* Skip type checking of declaration files. */, 21 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /wac.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "refs": false, 3 | "headerText": [ 4 | "# ------------DO-NOT-MODIFY-THIS-FILE------------", 5 | "# This file was automatically generated by github-actions-workflow-ts.", 6 | "# Instead, modify ", 7 | "# ------------DO-NOT-MODIFY-THIS-FILE------------\n" 8 | ], 9 | "dumpOptions": { 10 | "lineWidth": -1 11 | } 12 | } -------------------------------------------------------------------------------- /workflows/draft.wac.ts: -------------------------------------------------------------------------------- 1 | import { Workflow, NormalJob, Step, expressions as ex } from '../src' 2 | 3 | const draftStep = new Step({ 4 | name: 'Draft next release', 5 | uses: 'release-drafter/release-drafter@v5', 6 | with: { 7 | commitish: 'main', 8 | }, 9 | env: { 10 | GITHUB_TOKEN: ex.secret('GITHUB_TOKEN'), 11 | }, 12 | }) 13 | 14 | const draftJob = new NormalJob('UpdateReleaseDraft', { 15 | 'runs-on': 'ubuntu-latest', 16 | 'timeout-minutes': 20, 17 | permissions: { 18 | contents: 'write', 19 | }, 20 | }).addStep(draftStep) 21 | 22 | export const draftWorkflow = new Workflow('draft', { 23 | name: 'Draft Release', 24 | on: { 25 | push: { 26 | branches: ['main'], 27 | }, 28 | pull_request: { 29 | types: ['opened', 'reopened', 'synchronize'], 30 | }, 31 | }, 32 | permissions: { 33 | contents: 'read', 34 | }, 35 | }).addJob(draftJob) 36 | -------------------------------------------------------------------------------- /workflows/pr-labeler.wac.ts: -------------------------------------------------------------------------------- 1 | import { Workflow, NormalJob, Step, expressions as ex } from '../src' 2 | 3 | const prLabelerStep = new Step({ 4 | name: 'Apply label to a PR', 5 | uses: 'TimonVS/pr-labeler-action@v4', 6 | with: { 7 | 'repo-token': ex.secret('GITHUB_TOKEN'), 8 | 'configuration-path': '.github/pr-labeler.yml', 9 | }, 10 | }) 11 | 12 | const draftJob = new NormalJob('PrLabeler', { 13 | 'runs-on': 'ubuntu-latest', 14 | 'timeout-minutes': 20, 15 | permissions: { 16 | contents: 'read', 17 | 'pull-requests': 'write', 18 | }, 19 | }).addStep(prLabelerStep) 20 | 21 | export const draftWorkflow = new Workflow('pr-labeler', { 22 | name: 'PR Labeler', 23 | on: { 24 | pull_request: { 25 | types: ['opened', 'reopened', 'synchronize'], 26 | }, 27 | }, 28 | permissions: { 29 | contents: 'read', 30 | }, 31 | }).addJob(draftJob) 32 | -------------------------------------------------------------------------------- /workflows/publish.wac.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Workflow, 3 | NormalJob, 4 | Step, 5 | expressions as ex, 6 | multilineString, 7 | echoKeyValue, 8 | } from '../src' 9 | 10 | const targetCommitish = ex.expn('github.event.release.target_commitish') 11 | const tagName = ex.expn('github.event.release.tag_name') 12 | 13 | const checkout = new Step({ 14 | name: 'Checkout', 15 | uses: 'actions/checkout@v4', 16 | with: { 17 | ref: targetCommitish, 18 | }, 19 | }) 20 | 21 | const installNode = new Step({ 22 | name: 'Install Node', 23 | uses: 'actions/setup-node@v4', 24 | with: { 'node-version': 18 }, 25 | }) 26 | 27 | const installGlobalTsx = new Step({ 28 | name: 'Install tsx', 29 | run: 'npm install -g tsx', 30 | }) 31 | 32 | const installPnpm = new Step({ 33 | name: 'Install pnpm', 34 | uses: 'pnpm/action-setup@v4', 35 | with: { version: 8 }, 36 | }) 37 | 38 | const installDependencies = new Step({ 39 | name: 'Install Dependencies', 40 | run: 'pnpm install --no-frozen-lockfile', 41 | }) 42 | 43 | const bumpVersion = new Step({ 44 | name: 'Bump Version', 45 | run: multilineString( 46 | `git config user.name github-actions`, 47 | `git config user.email github-actions@github.com`, 48 | `echo version: ${tagName}`, 49 | `npm version --no-git-tag-version ${tagName}`, 50 | ), 51 | }) 52 | 53 | const build = new Step({ 54 | name: 'Run Build', 55 | run: 'pnpm build', 56 | }) 57 | 58 | const npmPublish = new Step({ 59 | name: 'Publish to npm', 60 | run: multilineString( 61 | echoKeyValue.to( 62 | '//registry.npmjs.org/:_authToken', 63 | ex.secret('NPM_TOKEN'), 64 | '.npmrc', 65 | ), 66 | 'npm publish', 67 | ), 68 | env: { 69 | NPM_AUTH_TOKEN: ex.secret('NPM_TOKEN'), 70 | }, 71 | }) 72 | 73 | const createZDP = new Step({ 74 | name: 'Create Zero Dependency Package', 75 | run: 'tsx scripts/generateZeroDependencyPackage.ts', 76 | }) 77 | 78 | const buildZDP = new Step({ 79 | name: 'Build Zero Dependency Package', 80 | 'working-directory': 'github-actions-workflow-ts-lib', 81 | run: 'npm run build', 82 | }) 83 | 84 | const npmPublishZDP = new Step({ 85 | name: 'Publish to npm', 86 | 'working-directory': 'github-actions-workflow-ts-lib', 87 | run: multilineString( 88 | echoKeyValue.to( 89 | '//registry.npmjs.org/:_authToken', 90 | ex.secret('NPM_TOKEN'), 91 | '.npmrc', 92 | ), 93 | 'npm publish', 94 | ), 95 | env: { 96 | NPM_AUTH_TOKEN: ex.secret('NPM_TOKEN'), 97 | }, 98 | }) 99 | 100 | const publishZeroDependencyPackageJob = new NormalJob( 101 | 'PublishZeroDependencyPackage', 102 | { 103 | 'runs-on': 'ubuntu-latest', 104 | 'timeout-minutes': 20, 105 | permissions: { 106 | contents: 'write', 107 | }, 108 | }, 109 | ).addSteps([ 110 | checkout, 111 | installNode, 112 | installPnpm, 113 | installGlobalTsx, 114 | bumpVersion, 115 | createZDP, 116 | buildZDP, 117 | npmPublishZDP, 118 | ]) 119 | 120 | const publishPackageJob = new NormalJob('PublishPackage', { 121 | 'runs-on': 'ubuntu-latest', 122 | 'timeout-minutes': 20, 123 | permissions: { 124 | contents: 'write', 125 | }, 126 | }).addSteps([ 127 | checkout, 128 | installNode, 129 | installPnpm, 130 | installGlobalTsx, 131 | installDependencies, 132 | build, 133 | bumpVersion, 134 | npmPublish, 135 | ]) 136 | 137 | const commitVersionBumpJob = new NormalJob('CommitVersionBump', { 138 | 'runs-on': 'ubuntu-latest', 139 | 'timeout-minutes': 20, 140 | needs: [publishPackageJob.name, publishZeroDependencyPackageJob.name], 141 | permissions: { 142 | contents: 'write', 143 | }, 144 | }).addSteps([ 145 | new Step({ 146 | name: 'Checkout', 147 | uses: 'actions/checkout@v4', 148 | with: { 149 | ref: 'main', 150 | }, 151 | }), 152 | new Step({ 153 | name: 'Push updates to main branch', 154 | shell: 'bash', 155 | run: multilineString( 156 | `git config user.name github-actions`, 157 | `git config user.email github-actions@github.com`, 158 | `echo version: ${tagName}`, 159 | `npm version --no-git-tag-version ${tagName}`, 160 | `git add .`, 161 | `git commit -m "new release: ${tagName} 🚀 [skip ci]" --no-verify`, 162 | `git push origin HEAD:main`, 163 | ), 164 | }), 165 | ]) 166 | 167 | export const publishWorkflow = new Workflow('publish', { 168 | name: 'Publish Release', 169 | on: { 170 | release: { 171 | types: ['published'], 172 | }, 173 | }, 174 | }).addJobs([ 175 | publishZeroDependencyPackageJob, 176 | publishPackageJob, 177 | commitVersionBumpJob, 178 | ]) 179 | -------------------------------------------------------------------------------- /workflows/schema-change-check.wac.ts: -------------------------------------------------------------------------------- 1 | import { Workflow, NormalJob, Step, multilineString } from '../src' 2 | 3 | const checkout = new Step({ 4 | name: 'Checkout', 5 | uses: 'actions/checkout@v4', 6 | }) 7 | 8 | const installNode = new Step({ 9 | name: 'Install Node', 10 | uses: 'actions/setup-node@v4', 11 | with: { 'node-version': 20 }, 12 | }) 13 | 14 | const installGlobalTsx = new Step({ 15 | name: 'Install tsx', 16 | run: 'npm install -g tsx', 17 | }) 18 | 19 | const installPnpm = new Step({ 20 | name: 'Install pnpm', 21 | uses: 'pnpm/action-setup@v4', 22 | with: { version: 8 }, 23 | }) 24 | 25 | const installDependencies = new Step({ 26 | name: 'Install Dependencies', 27 | run: 'pnpm install --no-frozen-lockfile', 28 | }) 29 | 30 | const generateWorkflowTypes = new Step({ 31 | name: 'Generate Workflow Types', 32 | run: 'pnpm generate-workflow-types', 33 | }) 34 | 35 | const gitDiff = new Step({ 36 | name: 'Get git diff', 37 | run: `git diff -- ':!pnpm-lock.yaml'`, 38 | }) 39 | 40 | const isGitDiffEmpty = new Step({ 41 | name: 'Fail if git diff is not empty', 42 | run: multilineString( 43 | `if test -z "$(git diff --name-only -- ':!pnpm-lock.yaml')"; then`, 44 | ` echo "No file changes detected."`, 45 | ` exit 0`, 46 | `else`, 47 | ` echo "File changes detected."`, 48 | ` git diff -- ':!pnpm-lock.yaml'`, 49 | ` exit 1`, 50 | `fi`, 51 | ), 52 | }) 53 | 54 | const schemaChangeCheck = new NormalJob('SchemaChangeCheck', { 55 | 'runs-on': 'ubuntu-latest', 56 | permissions: { 57 | contents: 'write', 58 | }, 59 | }).addSteps([ 60 | checkout, 61 | installNode, 62 | installGlobalTsx, 63 | installPnpm, 64 | installDependencies, 65 | generateWorkflowTypes, 66 | gitDiff, 67 | isGitDiffEmpty, 68 | ]) 69 | 70 | export const schemaChangeCheckWorkflow = new Workflow('schema-change-check', { 71 | name: 'Schema Change Check', 72 | on: { 73 | pull_request: { 74 | types: ['opened', 'reopened', 'synchronize'], 75 | }, 76 | schedule: [{ cron: '0 0 * * *' }], 77 | }, 78 | }).addJob(schemaChangeCheck) 79 | -------------------------------------------------------------------------------- /workflows/test.wac.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Workflow, 3 | NormalJob, 4 | Step, 5 | expressions as ex, 6 | ExtendedWorkflowTypes, 7 | } from '../' 8 | 9 | const nodeVersions: ExtendedWorkflowTypes.MatrixConfiguration = [16, 18, 20] 10 | 11 | const checkout = new Step({ 12 | name: 'Checkout', 13 | uses: 'actions/checkout@v3', 14 | }) 15 | 16 | const installNode = new Step({ 17 | name: 'Install Node', 18 | uses: 'actions/setup-node@v3', 19 | with: { 'node-version': ex.expn('matrix.node') }, 20 | }) 21 | 22 | const installPnpm = new Step({ 23 | name: 'Install pnpm', 24 | uses: 'pnpm/action-setup@v4', 25 | with: { version: 8 }, 26 | }) 27 | 28 | const installDependencies = new Step({ 29 | name: 'Install Dependencies', 30 | run: 'pnpm install --no-frozen-lockfile', 31 | }) 32 | 33 | const runTests = new Step({ 34 | name: 'Run Tests', 35 | run: 'pnpm test', 36 | }) 37 | 38 | const updateCodeCoverageBadge = new Step({ 39 | name: 'Update Code Coverage Badge', 40 | if: `github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && matrix.node == ${ 41 | nodeVersions.slice(-1)[0] 42 | }`, 43 | uses: 'we-cli/coverage-badge-action@48a2699b2e537c7519bdc970fb0ecd75c80a698e', 44 | }) 45 | 46 | const testJob = new NormalJob('Tests', { 47 | 'runs-on': 'ubuntu-latest', 48 | permissions: { 49 | contents: 'write', 50 | }, 51 | strategy: { 52 | matrix: { 53 | node: nodeVersions, 54 | }, 55 | }, 56 | }).addSteps([ 57 | checkout, 58 | installNode, 59 | installPnpm, 60 | installDependencies, 61 | runTests, 62 | updateCodeCoverageBadge, 63 | ]) 64 | 65 | export const test = new Workflow('test', { 66 | name: 'Tests', 67 | on: { 68 | pull_request: { 69 | branches: ['main'], 70 | }, 71 | push: { 72 | branches: ['main'], 73 | }, 74 | }, 75 | }).addJob(testJob) 76 | --------------------------------------------------------------------------------