├── .devcontainer └── devcontainer.json ├── .env.example ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── check-dist.yml │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── licensed.yml │ └── linter.yml ├── .gitignore ├── .licensed.yml ├── .licenses └── npm │ ├── @actions │ ├── core.dep.yml │ ├── exec.dep.yml │ ├── http-client.dep.yml │ └── io.dep.yml │ ├── @fastify │ └── busboy.dep.yml │ ├── tunnel.dep.yml │ └── undici.dep.yml ├── .markdown-lint.yml ├── .node-version ├── .prettierignore ├── .prettierrc.yml ├── .vscode └── launch.json ├── .yaml-lint.yml ├── CODEOWNERS ├── LICENSE ├── README.md ├── __fixtures__ ├── core.ts └── wait.ts ├── __tests__ ├── main.test.ts └── wait.test.ts ├── action.yml ├── badges └── coverage.svg ├── dist ├── index.js └── index.js.map ├── eslint.config.mjs ├── jest.config.js ├── package-lock.json ├── package.json ├── rollup.config.ts ├── script └── release ├── src ├── index.ts ├── main.ts └── wait.ts ├── tsconfig.base.json ├── tsconfig.eslint.json └── tsconfig.json /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GitHub Actions (TypeScript)", 3 | "image": "mcr.microsoft.com/devcontainers/typescript-node:20", 4 | "postCreateCommand": "npm install", 5 | "customizations": { 6 | "codespaces": { 7 | "openFiles": ["README.md"] 8 | }, 9 | "vscode": { 10 | "extensions": [ 11 | "bierner.markdown-preview-github-styles", 12 | "davidanson.vscode-markdownlint", 13 | "dbaeumer.vscode-eslint", 14 | "esbenp.prettier-vscode", 15 | "github.copilot", 16 | "github.copilot-chat", 17 | "github.vscode-github-actions", 18 | "github.vscode-pull-request-github", 19 | "me-dutour-mathieu.vscode-github-actions", 20 | "redhat.vscode-yaml", 21 | "rvest.vs-code-prettier-eslint", 22 | "yzhang.markdown-all-in-one" 23 | ], 24 | "settings": { 25 | "editor.defaultFormatter": "esbenp.prettier-vscode", 26 | "editor.tabSize": 2, 27 | "editor.formatOnSave": true, 28 | "markdown.extension.list.indentationSize": "adaptive", 29 | "markdown.extension.italic.indicator": "_", 30 | "markdown.extension.orderedList.marker": "one" 31 | } 32 | } 33 | }, 34 | "remoteEnv": { 35 | "GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}" 36 | }, 37 | "features": { 38 | "ghcr.io/devcontainers/features/github-cli:1": {}, 39 | "ghcr.io/devcontainers-contrib/features/prettier:1": {} 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # dotenv-linter:off IncorrectDelimiter 2 | 3 | # Do not commit your actual .env file to Git! This may contain secrets or other 4 | # private information. 5 | 6 | # Enable/disable step debug logging (default: `false`). For local debugging, it 7 | # may be useful to set it to `true`. 8 | ACTIONS_STEP_DEBUG=true 9 | 10 | # GitHub Actions inputs should follow `INPUT_` format (case-sensitive). 11 | # Hyphens should not be converted to underscores! 12 | INPUT_MILLISECONDS=2400 13 | 14 | # GitHub Actions default environment variables. These are set for every run of a 15 | # workflow and can be used in your actions. Setting the value here will override 16 | # any value set by the local-action tool. 17 | # https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables 18 | 19 | # CI="true" 20 | # GITHUB_ACTION="" 21 | # GITHUB_ACTION_PATH="" 22 | # GITHUB_ACTION_REPOSITORY="" 23 | # GITHUB_ACTIONS="" 24 | # GITHUB_ACTOR="" 25 | # GITHUB_ACTOR_ID="" 26 | # GITHUB_API_URL="" 27 | # GITHUB_BASE_REF="" 28 | # GITHUB_ENV="" 29 | # GITHUB_EVENT_NAME="" 30 | # GITHUB_EVENT_PATH="" 31 | # GITHUB_GRAPHQL_URL="" 32 | # GITHUB_HEAD_REF="" 33 | # GITHUB_JOB="" 34 | # GITHUB_OUTPUT="" 35 | # GITHUB_PATH="" 36 | # GITHUB_REF="" 37 | # GITHUB_REF_NAME="" 38 | # GITHUB_REF_PROTECTED="" 39 | # GITHUB_REF_TYPE="" 40 | # GITHUB_REPOSITORY="" 41 | # GITHUB_REPOSITORY_ID="" 42 | # GITHUB_REPOSITORY_OWNER="" 43 | # GITHUB_REPOSITORY_OWNER_ID="" 44 | # GITHUB_RETENTION_DAYS="" 45 | # GITHUB_RUN_ATTEMPT="" 46 | # GITHUB_RUN_ID="" 47 | # GITHUB_RUN_NUMBER="" 48 | # GITHUB_SERVER_URL="" 49 | # GITHUB_SHA="" 50 | # GITHUB_STEP_SUMMARY="" 51 | # GITHUB_TRIGGERING_ACTOR="" 52 | # GITHUB_WORKFLOW="" 53 | # GITHUB_WORKFLOW_REF="" 54 | # GITHUB_WORKFLOW_SHA="" 55 | # GITHUB_WORKSPACE="" 56 | # RUNNER_ARCH="" 57 | # RUNNER_DEBUG="" 58 | # RUNNER_NAME="" 59 | # RUNNER_OS="" 60 | # RUNNER_TEMP="" 61 | # RUNNER_TOOL_CACHE="" 62 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | dist/** -diff linguist-generated=true 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | groups: 8 | actions-minor: 9 | update-types: 10 | - minor 11 | - patch 12 | 13 | - package-ecosystem: npm 14 | directory: / 15 | schedule: 16 | interval: weekly 17 | ignore: 18 | - dependency-name: '@types/node' 19 | update-types: 20 | - 'version-update:semver-major' 21 | groups: 22 | npm-development: 23 | dependency-type: development 24 | update-types: 25 | - minor 26 | - patch 27 | npm-production: 28 | dependency-type: production 29 | update-types: 30 | - patch 31 | -------------------------------------------------------------------------------- /.github/workflows/check-dist.yml: -------------------------------------------------------------------------------- 1 | # In TypeScript actions, `dist/` is a special directory. When you reference 2 | # an action with the `uses:` property, `dist/index.js` is the code that will be 3 | # run. For this project, the `dist/index.js` file is transpiled from other 4 | # source files. This workflow ensures the `dist/` directory contains the 5 | # expected transpiled code. 6 | # 7 | # If this workflow is run from a feature branch, it will act as an additional CI 8 | # check and fail if the checked-in `dist/` directory does not match what is 9 | # expected from the build. 10 | name: Check Transpiled JavaScript 11 | 12 | on: 13 | pull_request: 14 | branches: 15 | - main 16 | push: 17 | branches: 18 | - main 19 | 20 | permissions: 21 | contents: read 22 | 23 | jobs: 24 | check-dist: 25 | name: Check dist/ 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - name: Checkout 30 | id: checkout 31 | uses: actions/checkout@v4 32 | 33 | - name: Setup Node.js 34 | id: setup-node 35 | uses: actions/setup-node@v4 36 | with: 37 | node-version-file: .node-version 38 | cache: npm 39 | 40 | - name: Install Dependencies 41 | id: install 42 | run: npm ci 43 | 44 | - name: Build dist/ Directory 45 | id: build 46 | run: npm run bundle 47 | 48 | # This will fail the workflow if the `dist/` directory is different than 49 | # expected. 50 | - name: Compare Directories 51 | id: diff 52 | run: | 53 | if [ ! -d dist/ ]; then 54 | echo "Expected dist/ directory does not exist. See status below:" 55 | ls -la ./ 56 | exit 1 57 | fi 58 | if [ "$(git diff --ignore-space-at-eol --text dist/ | wc -l)" -gt "0" ]; then 59 | echo "Detected uncommitted changes after build. See status below:" 60 | git diff --ignore-space-at-eol --text dist/ 61 | exit 1 62 | fi 63 | 64 | # If `dist/` was different than expected, upload the expected version as a 65 | # workflow artifact. 66 | - if: ${{ failure() && steps.diff.outcome == 'failure' }} 67 | name: Upload Artifact 68 | id: upload 69 | uses: actions/upload-artifact@v4 70 | with: 71 | name: dist 72 | path: dist/ 73 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | test-typescript: 16 | name: TypeScript Tests 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: Checkout 21 | id: checkout 22 | uses: actions/checkout@v4 23 | 24 | - name: Setup Node.js 25 | id: setup-node 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version-file: .node-version 29 | cache: npm 30 | 31 | - name: Install Dependencies 32 | id: npm-ci 33 | run: npm ci 34 | 35 | - name: Check Format 36 | id: npm-format-check 37 | run: npm run format:check 38 | 39 | - name: Lint 40 | id: npm-lint 41 | run: npm run lint 42 | 43 | - name: Test 44 | id: npm-ci-test 45 | run: npm run ci-test 46 | 47 | test-action: 48 | name: GitHub Actions Test 49 | runs-on: ubuntu-latest 50 | 51 | steps: 52 | - name: Checkout 53 | id: checkout 54 | uses: actions/checkout@v4 55 | 56 | - name: Test Local Action 57 | id: test-action 58 | uses: ./ 59 | with: 60 | milliseconds: 2000 61 | 62 | - name: Print Output 63 | id: output 64 | run: echo "${{ steps.test-action.outputs.time }}" 65 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: CodeQL 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | schedule: 11 | - cron: '31 7 * * 3' 12 | 13 | permissions: 14 | actions: read 15 | checks: write 16 | contents: read 17 | security-events: write 18 | 19 | jobs: 20 | analyze: 21 | name: Analyze 22 | runs-on: ubuntu-latest 23 | 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | language: 28 | - TypeScript 29 | 30 | steps: 31 | - name: Checkout 32 | id: checkout 33 | uses: actions/checkout@v4 34 | 35 | - name: Initialize CodeQL 36 | id: initialize 37 | uses: github/codeql-action/init@v3 38 | with: 39 | languages: ${{ matrix.language }} 40 | source-root: src 41 | 42 | - name: Autobuild 43 | id: autobuild 44 | uses: github/codeql-action/autobuild@v3 45 | 46 | - name: Perform CodeQL Analysis 47 | id: analyze 48 | uses: github/codeql-action/analyze@v3 49 | -------------------------------------------------------------------------------- /.github/workflows/licensed.yml: -------------------------------------------------------------------------------- 1 | # This workflow checks the statuses of cached dependencies used in this action 2 | # with the help of the Licensed tool. If any licenses are invalid or missing, 3 | # this workflow will fail. See: https://github.com/licensee/licensed 4 | 5 | name: Licensed 6 | 7 | on: 8 | # Uncomment the below lines to run this workflow on pull requests and pushes 9 | # to the default branch. This is useful for checking licenses before merging 10 | # changes into the default branch. 11 | # pull_request: 12 | # branches: 13 | # - main 14 | # push: 15 | # branches: 16 | # - main 17 | workflow_dispatch: 18 | 19 | permissions: 20 | contents: write 21 | 22 | jobs: 23 | licensed: 24 | name: Check Licenses 25 | runs-on: ubuntu-latest 26 | 27 | steps: 28 | - name: Checkout 29 | id: checkout 30 | uses: actions/checkout@v4 31 | 32 | - name: Setup Node.js 33 | id: setup-node 34 | uses: actions/setup-node@v4 35 | with: 36 | node-version-file: .node-version 37 | cache: npm 38 | 39 | - name: Install Dependencies 40 | id: npm-ci 41 | run: npm ci 42 | 43 | - name: Setup Ruby 44 | id: setup-ruby 45 | uses: ruby/setup-ruby@v1 46 | with: 47 | ruby-version: ruby 48 | 49 | - uses: licensee/setup-licensed@v1.3.2 50 | with: 51 | version: 4.x 52 | github_token: ${{ secrets.GITHUB_TOKEN }} 53 | 54 | # If this is a workflow_dispatch event, update the cached licenses. 55 | - if: ${{ github.event_name == 'workflow_dispatch' }} 56 | name: Update Licenses 57 | id: update-licenses 58 | run: licensed cache 59 | 60 | # Then, commit the updated licenses to the repository. 61 | - if: ${{ github.event_name == 'workflow_dispatch' }} 62 | name: Commit Licenses 63 | id: commit-licenses 64 | run: | 65 | git config --local user.email "licensed-ci@users.noreply.github.com" 66 | git config --local user.name "licensed-ci" 67 | git add . 68 | git commit -m "Auto-update license files" 69 | git push 70 | 71 | # Last, check the status of the cached licenses. 72 | - name: Check Licenses 73 | id: check-licenses 74 | run: licensed status 75 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: Lint Codebase 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | contents: read 13 | packages: read 14 | statuses: write 15 | 16 | jobs: 17 | lint: 18 | name: Lint Codebase 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - name: Checkout 23 | id: checkout 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | 28 | - name: Setup Node.js 29 | id: setup-node 30 | uses: actions/setup-node@v4 31 | with: 32 | node-version-file: .node-version 33 | cache: npm 34 | 35 | - name: Install Dependencies 36 | id: install 37 | run: npm ci 38 | 39 | - name: Lint Codebase 40 | id: super-linter 41 | uses: super-linter/super-linter/slim@v7 42 | env: 43 | DEFAULT_BRANCH: main 44 | FILTER_REGEX_EXCLUDE: dist/**/* 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | LINTER_RULES_PATH: ${{ github.workspace }} 47 | VALIDATE_ALL_CODEBASE: true 48 | VALIDATE_JAVASCRIPT_ES: false 49 | VALIDATE_JAVASCRIPT_STANDARD: false 50 | VALIDATE_JSCPD: false 51 | VALIDATE_TYPESCRIPT_ES: false 52 | VALIDATE_JSON: false 53 | VALIDATE_TYPESCRIPT_STANDARD: false 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | node_modules 3 | 4 | # Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | .env.test 71 | 72 | # parcel-bundler cache (https://parceljs.org/) 73 | .cache 74 | 75 | # next.js build output 76 | .next 77 | 78 | # nuxt.js build output 79 | .nuxt 80 | 81 | # vuepress build output 82 | .vuepress/dist 83 | 84 | # Serverless directories 85 | .serverless/ 86 | 87 | # FuseBox cache 88 | .fusebox/ 89 | 90 | # DynamoDB Local files 91 | .dynamodb/ 92 | 93 | # OS metadata 94 | .DS_Store 95 | Thumbs.db 96 | 97 | # Ignore built ts files 98 | __tests__/runner/* 99 | 100 | # IDE files 101 | .idea 102 | *.code-workspace 103 | -------------------------------------------------------------------------------- /.licensed.yml: -------------------------------------------------------------------------------- 1 | # See: https://github.com/licensee/licensed/blob/main/docs/configuration.md 2 | 3 | sources: 4 | npm: true 5 | 6 | allowed: 7 | - apache-2.0 8 | - bsd-2-clause 9 | - bsd-3-clause 10 | - isc 11 | - mit 12 | - cc0-1.0 13 | - other 14 | 15 | ignored: 16 | npm: 17 | # Used by Rollup.js when building in GitHub Actions 18 | - '@rollup/rollup-linux-x64-gnu' 19 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/core.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/core" 3 | version: 1.11.1 4 | type: npm 5 | summary: Actions core lib 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/core 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: |- 11 | The MIT License (MIT) 12 | 13 | Copyright 2019 GitHub 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/exec.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/exec" 3 | version: 1.1.1 4 | type: npm 5 | summary: Actions exec lib 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/exec 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: |- 11 | The MIT License (MIT) 12 | 13 | Copyright 2019 GitHub 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/http-client.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/http-client" 3 | version: 2.2.3 4 | type: npm 5 | summary: Actions Http Client 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/http-client 7 | license: other 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | Actions Http Client for Node.js 12 | 13 | Copyright (c) GitHub, Inc. 14 | 15 | All rights reserved. 16 | 17 | MIT License 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 20 | associated documentation files (the "Software"), to deal in the Software without restriction, 21 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 22 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 23 | subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 28 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 29 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 30 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 31 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/io.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/io" 3 | version: 1.1.3 4 | type: npm 5 | summary: Actions io lib 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/io 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: |- 11 | The MIT License (MIT) 12 | 13 | Copyright 2019 GitHub 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/@fastify/busboy.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@fastify/busboy" 3 | version: 2.1.1 4 | type: npm 5 | summary: A streaming parser for HTML form data for node.js 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: |- 11 | Copyright Brian White. All rights reserved. 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to 15 | deal in the Software without restriction, including without limitation the 16 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 17 | sell copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in 21 | all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 29 | IN THE SOFTWARE. 30 | notices: [] 31 | -------------------------------------------------------------------------------- /.licenses/npm/tunnel.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: tunnel 3 | version: 0.0.6 4 | type: npm 5 | summary: Node HTTP/HTTPS Agents for tunneling proxies 6 | homepage: https://github.com/koichik/node-tunnel/ 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The MIT License (MIT) 12 | 13 | Copyright (c) 2012 Koichi Kobayashi 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | THE SOFTWARE. 32 | - sources: README.md 33 | text: Licensed under the [MIT](https://github.com/koichik/node-tunnel/blob/master/LICENSE) 34 | license. 35 | notices: [] 36 | -------------------------------------------------------------------------------- /.licenses/npm/undici.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: undici 3 | version: 5.28.5 4 | type: npm 5 | summary: An HTTP/1.1 client, written from scratch for Node.js 6 | homepage: https://undici.nodejs.org 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | MIT License 12 | 13 | Copyright (c) Matteo Collina and Undici contributors 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | - sources: README.md 33 | text: MIT 34 | notices: [] 35 | -------------------------------------------------------------------------------- /.markdown-lint.yml: -------------------------------------------------------------------------------- 1 | # See: https://github.com/DavidAnson/markdownlint 2 | 3 | # Unordered list style 4 | MD004: 5 | style: dash 6 | 7 | # Disable line length for tables 8 | MD013: 9 | tables: false 10 | 11 | # Ordered list item prefix 12 | MD029: 13 | style: one 14 | 15 | # Spaces after list markers 16 | MD030: 17 | ul_single: 1 18 | ol_single: 1 19 | ul_multi: 1 20 | ol_multi: 1 21 | 22 | # Code block style 23 | MD046: 24 | style: fenced 25 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 20.18.1 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .licenses/ 3 | dist/ 4 | node_modules/ 5 | coverage/ 6 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | # See: https://prettier.io/docs/en/configuration 2 | 3 | printWidth: 80 4 | tabWidth: 2 5 | useTabs: false 6 | semi: false 7 | singleQuote: true 8 | quoteProps: as-needed 9 | jsxSingleQuote: false 10 | trailingComma: none 11 | bracketSpacing: true 12 | bracketSameLine: true 13 | arrowParens: always 14 | proseWrap: always 15 | htmlWhitespaceSensitivity: css 16 | endOfLine: lf 17 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Action", 6 | "type": "node", 7 | "request": "launch", 8 | "runtimeExecutable": "npx", 9 | "cwd": "${workspaceRoot}", 10 | "args": ["@github/local-action", ".", "src/main.ts", ".env"], 11 | "console": "integratedTerminal", 12 | "skipFiles": ["/**", "node_modules/**"] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.yaml-lint.yml: -------------------------------------------------------------------------------- 1 | # See: https://yamllint.readthedocs.io/en/stable/ 2 | 3 | rules: 4 | document-end: disable 5 | document-start: 6 | level: warning 7 | present: false 8 | line-length: 9 | level: warning 10 | max: 80 11 | allow-non-breakable-words: true 12 | allow-non-breakable-inline-mappings: true 13 | ignore: 14 | - .licenses/ 15 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Repository CODEOWNERS # 3 | # Order is important! The last matching pattern takes the most precedence. # 4 | ############################################################################ 5 | 6 | # Default owners, unless a later match takes precedence. 7 | * @actions/actions-oss-maintainers 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright GitHub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Create a GitHub Action Using TypeScript 2 | 3 | [![GitHub Super-Linter](https://github.com/actions/typescript-action/actions/workflows/linter.yml/badge.svg)](https://github.com/super-linter/super-linter) 4 | ![CI](https://github.com/actions/typescript-action/actions/workflows/ci.yml/badge.svg) 5 | [![Check dist/](https://github.com/actions/typescript-action/actions/workflows/check-dist.yml/badge.svg)](https://github.com/actions/typescript-action/actions/workflows/check-dist.yml) 6 | [![CodeQL](https://github.com/actions/typescript-action/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/actions/typescript-action/actions/workflows/codeql-analysis.yml) 7 | [![Coverage](./badges/coverage.svg)](./badges/coverage.svg) 8 | 9 | Use this template to bootstrap the creation of a TypeScript action. :rocket: 10 | 11 | This template includes compilation support, tests, a validation workflow, 12 | publishing, and versioning guidance. 13 | 14 | If you are new, there's also a simpler introduction in the 15 | [Hello world JavaScript action repository](https://github.com/actions/hello-world-javascript-action). 16 | 17 | ## Create Your Own Action 18 | 19 | To create your own action, you can use this repository as a template! Just 20 | follow the below instructions: 21 | 22 | 1. Click the **Use this template** button at the top of the repository 23 | 1. Select **Create a new repository** 24 | 1. Select an owner and name for your new repository 25 | 1. Click **Create repository** 26 | 1. Clone your new repository 27 | 28 | > [!IMPORTANT] 29 | > 30 | > Make sure to remove or update the [`CODEOWNERS`](./CODEOWNERS) file! For 31 | > details on how to use this file, see 32 | > [About code owners](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners). 33 | 34 | ## Initial Setup 35 | 36 | After you've cloned the repository to your local machine or codespace, you'll 37 | need to perform some initial setup steps before you can develop your action. 38 | 39 | > [!NOTE] 40 | > 41 | > You'll need to have a reasonably modern version of 42 | > [Node.js](https://nodejs.org) handy (20.x or later should work!). If you are 43 | > using a version manager like [`nodenv`](https://github.com/nodenv/nodenv) or 44 | > [`fnm`](https://github.com/Schniz/fnm), this template has a `.node-version` 45 | > file at the root of the repository that can be used to automatically switch to 46 | > the correct version when you `cd` into the repository. Additionally, this 47 | > `.node-version` file is used by GitHub Actions in any `actions/setup-node` 48 | > actions. 49 | 50 | 1. :hammer_and_wrench: Install the dependencies 51 | 52 | ```bash 53 | npm install 54 | ``` 55 | 56 | 1. :building_construction: Package the TypeScript for distribution 57 | 58 | ```bash 59 | npm run bundle 60 | ``` 61 | 62 | 1. :white_check_mark: Run the tests 63 | 64 | ```bash 65 | $ npm test 66 | 67 | PASS ./index.test.js 68 | ✓ throws invalid number (3ms) 69 | ✓ wait 500 ms (504ms) 70 | ✓ test runs (95ms) 71 | 72 | ... 73 | ``` 74 | 75 | ## Update the Action Metadata 76 | 77 | The [`action.yml`](action.yml) file defines metadata about your action, such as 78 | input(s) and output(s). For details about this file, see 79 | [Metadata syntax for GitHub Actions](https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions). 80 | 81 | When you copy this repository, update `action.yml` with the name, description, 82 | inputs, and outputs for your action. 83 | 84 | ## Update the Action Code 85 | 86 | The [`src/`](./src/) directory is the heart of your action! This contains the 87 | source code that will be run when your action is invoked. You can replace the 88 | contents of this directory with your own code. 89 | 90 | There are a few things to keep in mind when writing your action code: 91 | 92 | - Most GitHub Actions toolkit and CI/CD operations are processed asynchronously. 93 | In `main.ts`, you will see that the action is run in an `async` function. 94 | 95 | ```javascript 96 | import * as core from '@actions/core' 97 | //... 98 | 99 | async function run() { 100 | try { 101 | //... 102 | } catch (error) { 103 | core.setFailed(error.message) 104 | } 105 | } 106 | ``` 107 | 108 | For more information about the GitHub Actions toolkit, see the 109 | [documentation](https://github.com/actions/toolkit/blob/master/README.md). 110 | 111 | So, what are you waiting for? Go ahead and start customizing your action! 112 | 113 | 1. Create a new branch 114 | 115 | ```bash 116 | git checkout -b releases/v1 117 | ``` 118 | 119 | 1. Replace the contents of `src/` with your action code 120 | 1. Add tests to `__tests__/` for your source code 121 | 1. Format, test, and build the action 122 | 123 | ```bash 124 | npm run all 125 | ``` 126 | 127 | > This step is important! It will run [`rollup`](https://rollupjs.org/) to 128 | > build the final JavaScript action code with all dependencies included. If 129 | > you do not run this step, your action will not work correctly when it is 130 | > used in a workflow. 131 | 132 | 1. (Optional) Test your action locally 133 | 134 | The [`@github/local-action`](https://github.com/github/local-action) utility 135 | can be used to test your action locally. It is a simple command-line tool 136 | that "stubs" (or simulates) the GitHub Actions Toolkit. This way, you can run 137 | your TypeScript action locally without having to commit and push your changes 138 | to a repository. 139 | 140 | The `local-action` utility can be run in the following ways: 141 | 142 | - Visual Studio Code Debugger 143 | 144 | Make sure to review and, if needed, update 145 | [`.vscode/launch.json`](./.vscode/launch.json) 146 | 147 | - Terminal/Command Prompt 148 | 149 | ```bash 150 | # npx @github/local action 151 | npx @github/local-action . src/main.ts .env 152 | ``` 153 | 154 | You can provide a `.env` file to the `local-action` CLI to set environment 155 | variables used by the GitHub Actions Toolkit. For example, setting inputs and 156 | event payload data used by your action. For more information, see the example 157 | file, [`.env.example`](./.env.example), and the 158 | [GitHub Actions Documentation](https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables). 159 | 160 | 1. Commit your changes 161 | 162 | ```bash 163 | git add . 164 | git commit -m "My first action is ready!" 165 | ``` 166 | 167 | 1. Push them to your repository 168 | 169 | ```bash 170 | git push -u origin releases/v1 171 | ``` 172 | 173 | 1. Create a pull request and get feedback on your action 174 | 1. Merge the pull request into the `main` branch 175 | 176 | Your action is now published! :rocket: 177 | 178 | For information about versioning your action, see 179 | [Versioning](https://github.com/actions/toolkit/blob/master/docs/action-versioning.md) 180 | in the GitHub Actions toolkit. 181 | 182 | ## Validate the Action 183 | 184 | You can now validate the action by referencing it in a workflow file. For 185 | example, [`ci.yml`](./.github/workflows/ci.yml) demonstrates how to reference an 186 | action in the same repository. 187 | 188 | ```yaml 189 | steps: 190 | - name: Checkout 191 | id: checkout 192 | uses: actions/checkout@v4 193 | 194 | - name: Test Local Action 195 | id: test-action 196 | uses: ./ 197 | with: 198 | milliseconds: 1000 199 | 200 | - name: Print Output 201 | id: output 202 | run: echo "${{ steps.test-action.outputs.time }}" 203 | ``` 204 | 205 | For example workflow runs, check out the 206 | [Actions tab](https://github.com/actions/typescript-action/actions)! :rocket: 207 | 208 | ## Usage 209 | 210 | After testing, you can create version tag(s) that developers can use to 211 | reference different stable versions of your action. For more information, see 212 | [Versioning](https://github.com/actions/toolkit/blob/master/docs/action-versioning.md) 213 | in the GitHub Actions toolkit. 214 | 215 | To include the action in a workflow in another repository, you can use the 216 | `uses` syntax with the `@` symbol to reference a specific branch, tag, or commit 217 | hash. 218 | 219 | ```yaml 220 | steps: 221 | - name: Checkout 222 | id: checkout 223 | uses: actions/checkout@v4 224 | 225 | - name: Test Local Action 226 | id: test-action 227 | uses: actions/typescript-action@v1 # Commit with the `v1` tag 228 | with: 229 | milliseconds: 1000 230 | 231 | - name: Print Output 232 | id: output 233 | run: echo "${{ steps.test-action.outputs.time }}" 234 | ``` 235 | 236 | ## Publishing a New Release 237 | 238 | This project includes a helper script, [`script/release`](./script/release) 239 | designed to streamline the process of tagging and pushing new releases for 240 | GitHub Actions. 241 | 242 | GitHub Actions allows users to select a specific version of the action to use, 243 | based on release tags. This script simplifies this process by performing the 244 | following steps: 245 | 246 | 1. **Retrieving the latest release tag:** The script starts by fetching the most 247 | recent SemVer release tag of the current branch, by looking at the local data 248 | available in your repository. 249 | 1. **Prompting for a new release tag:** The user is then prompted to enter a new 250 | release tag. To assist with this, the script displays the tag retrieved in 251 | the previous step, and validates the format of the inputted tag (vX.X.X). The 252 | user is also reminded to update the version field in package.json. 253 | 1. **Tagging the new release:** The script then tags a new release and syncs the 254 | separate major tag (e.g. v1, v2) with the new release tag (e.g. v1.0.0, 255 | v2.1.2). When the user is creating a new major release, the script 256 | auto-detects this and creates a `releases/v#` branch for the previous major 257 | version. 258 | 1. **Pushing changes to remote:** Finally, the script pushes the necessary 259 | commits, tags and branches to the remote repository. From here, you will need 260 | to create a new release in GitHub so users can easily reference the new tags 261 | in their workflows. 262 | 263 | ## Dependency License Management 264 | 265 | This template includes a GitHub Actions workflow, 266 | [`licensed.yml`](./.github/workflows/licensed.yml), that uses 267 | [Licensed](https://github.com/licensee/licensed) to check for dependencies with 268 | missing or non-compliant licenses. This workflow is initially disabled. To 269 | enable the workflow, follow the below steps. 270 | 271 | 1. Open [`licensed.yml`](./.github/workflows/licensed.yml) 272 | 1. Uncomment the following lines: 273 | 274 | ```yaml 275 | # pull_request: 276 | # branches: 277 | # - main 278 | # push: 279 | # branches: 280 | # - main 281 | ``` 282 | 283 | 1. Save and commit the changes 284 | 285 | Once complete, this workflow will run any time a pull request is created or 286 | changes pushed directly to `main`. If the workflow detects any dependencies with 287 | missing or non-compliant licenses, it will fail the workflow and provide details 288 | on the issue(s) found. 289 | 290 | ### Updating Licenses 291 | 292 | Whenever you install or update dependencies, you can use the Licensed CLI to 293 | update the licenses database. To install Licensed, see the project's 294 | [Readme](https://github.com/licensee/licensed?tab=readme-ov-file#installation). 295 | 296 | To update the cached licenses, run the following command: 297 | 298 | ```bash 299 | licensed cache 300 | ``` 301 | 302 | To check the status of cached licenses, run the following command: 303 | 304 | ```bash 305 | licensed status 306 | ``` 307 | -------------------------------------------------------------------------------- /__fixtures__/core.ts: -------------------------------------------------------------------------------- 1 | import type * as core from '@actions/core' 2 | import { jest } from '@jest/globals' 3 | 4 | export const debug = jest.fn() 5 | export const error = jest.fn() 6 | export const info = jest.fn() 7 | export const getInput = jest.fn() 8 | export const setOutput = jest.fn() 9 | export const setFailed = jest.fn() 10 | export const warning = jest.fn() 11 | -------------------------------------------------------------------------------- /__fixtures__/wait.ts: -------------------------------------------------------------------------------- 1 | import { jest } from '@jest/globals' 2 | 3 | export const wait = jest.fn() 4 | -------------------------------------------------------------------------------- /__tests__/main.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Unit tests for the action's main functionality, src/main.ts 3 | * 4 | * To mock dependencies in ESM, you can create fixtures that export mock 5 | * functions and objects. For example, the core module is mocked in this test, 6 | * so that the actual '@actions/core' module is not imported. 7 | */ 8 | import { jest } from '@jest/globals' 9 | import * as core from '../__fixtures__/core.js' 10 | import { wait } from '../__fixtures__/wait.js' 11 | 12 | // Mocks should be declared before the module being tested is imported. 13 | jest.unstable_mockModule('@actions/core', () => core) 14 | jest.unstable_mockModule('../src/wait.js', () => ({ wait })) 15 | 16 | // The module being tested should be imported dynamically. This ensures that the 17 | // mocks are used in place of any actual dependencies. 18 | const { run } = await import('../src/main.js') 19 | 20 | describe('main.ts', () => { 21 | beforeEach(() => { 22 | // Set the action's inputs as return values from core.getInput(). 23 | core.getInput.mockImplementation(() => '500') 24 | 25 | // Mock the wait function so that it does not actually wait. 26 | wait.mockImplementation(() => Promise.resolve('done!')) 27 | }) 28 | 29 | afterEach(() => { 30 | jest.resetAllMocks() 31 | }) 32 | 33 | it('Sets the time output', async () => { 34 | await run() 35 | 36 | // Verify the time output was set. 37 | expect(core.setOutput).toHaveBeenNthCalledWith( 38 | 1, 39 | 'time', 40 | // Simple regex to match a time string in the format HH:MM:SS. 41 | expect.stringMatching(/^\d{2}:\d{2}:\d{2}/) 42 | ) 43 | }) 44 | 45 | it('Sets a failed status', async () => { 46 | // Clear the getInput mock and return an invalid value. 47 | core.getInput.mockClear().mockReturnValueOnce('this is not a number') 48 | 49 | // Clear the wait mock and return a rejected promise. 50 | wait 51 | .mockClear() 52 | .mockRejectedValueOnce(new Error('milliseconds is not a number')) 53 | 54 | await run() 55 | 56 | // Verify that the action was marked as failed. 57 | expect(core.setFailed).toHaveBeenNthCalledWith( 58 | 1, 59 | 'milliseconds is not a number' 60 | ) 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /__tests__/wait.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Unit tests for src/wait.ts 3 | */ 4 | import { wait } from '../src/wait.js' 5 | 6 | describe('wait.ts', () => { 7 | it('Throws an invalid number', async () => { 8 | const input = parseInt('foo', 10) 9 | 10 | expect(isNaN(input)).toBe(true) 11 | 12 | await expect(wait(input)).rejects.toThrow('milliseconds is not a number') 13 | }) 14 | 15 | it('Waits with a valid number', async () => { 16 | const start = new Date() 17 | await wait(500) 18 | const end = new Date() 19 | 20 | const delta = Math.abs(end.getTime() - start.getTime()) 21 | 22 | expect(delta).toBeGreaterThan(450) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: The name of your action here 2 | description: Provide a description here 3 | author: Your name or organization here 4 | 5 | # Add your action's branding here. This will appear on the GitHub Marketplace. 6 | branding: 7 | icon: heart 8 | color: red 9 | 10 | # Define your inputs here. 11 | inputs: 12 | milliseconds: 13 | description: Your input description here 14 | required: true 15 | default: '1000' 16 | 17 | # Define your outputs here. 18 | outputs: 19 | time: 20 | description: Your output description here 21 | 22 | runs: 23 | using: node20 24 | main: dist/index.js 25 | -------------------------------------------------------------------------------- /badges/coverage.svg: -------------------------------------------------------------------------------- 1 | Coverage: 100%Coverage100% -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // See: https://eslint.org/docs/latest/use/configure/configuration-files 2 | 3 | import { fixupPluginRules } from '@eslint/compat' 4 | import { FlatCompat } from '@eslint/eslintrc' 5 | import js from '@eslint/js' 6 | import typescriptEslint from '@typescript-eslint/eslint-plugin' 7 | import tsParser from '@typescript-eslint/parser' 8 | import _import from 'eslint-plugin-import' 9 | import jest from 'eslint-plugin-jest' 10 | import prettier from 'eslint-plugin-prettier' 11 | import globals from 'globals' 12 | import path from 'node:path' 13 | import { fileURLToPath } from 'node:url' 14 | 15 | const __filename = fileURLToPath(import.meta.url) 16 | const __dirname = path.dirname(__filename) 17 | const compat = new FlatCompat({ 18 | baseDirectory: __dirname, 19 | recommendedConfig: js.configs.recommended, 20 | allConfig: js.configs.all 21 | }) 22 | 23 | export default [ 24 | { 25 | ignores: ['**/coverage', '**/dist', '**/linter', '**/node_modules'] 26 | }, 27 | ...compat.extends( 28 | 'eslint:recommended', 29 | 'plugin:@typescript-eslint/eslint-recommended', 30 | 'plugin:@typescript-eslint/recommended', 31 | 'plugin:jest/recommended', 32 | 'plugin:prettier/recommended' 33 | ), 34 | { 35 | plugins: { 36 | import: fixupPluginRules(_import), 37 | jest, 38 | prettier, 39 | '@typescript-eslint': typescriptEslint 40 | }, 41 | 42 | languageOptions: { 43 | globals: { 44 | ...globals.node, 45 | ...globals.jest, 46 | Atomics: 'readonly', 47 | SharedArrayBuffer: 'readonly' 48 | }, 49 | 50 | parser: tsParser, 51 | ecmaVersion: 2023, 52 | sourceType: 'module', 53 | 54 | parserOptions: { 55 | project: ['tsconfig.eslint.json'], 56 | tsconfigRootDir: '.' 57 | } 58 | }, 59 | 60 | settings: { 61 | 'import/resolver': { 62 | typescript: { 63 | alwaysTryTypes: true, 64 | project: 'tsconfig.eslint.json' 65 | } 66 | } 67 | }, 68 | 69 | rules: { 70 | camelcase: 'off', 71 | 'eslint-comments/no-use': 'off', 72 | 'eslint-comments/no-unused-disable': 'off', 73 | 'i18n-text/no-en': 'off', 74 | 'import/no-namespace': 'off', 75 | 'no-console': 'off', 76 | 'no-shadow': 'off', 77 | 'no-unused-vars': 'off', 78 | 'prettier/prettier': 'error' 79 | } 80 | } 81 | ] 82 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // See: https://jestjs.io/docs/configuration 2 | 3 | /** @type {import('ts-jest').JestConfigWithTsJest} **/ 4 | export default { 5 | clearMocks: true, 6 | collectCoverage: true, 7 | collectCoverageFrom: ['./src/**'], 8 | coverageDirectory: './coverage', 9 | coveragePathIgnorePatterns: ['/node_modules/', '/dist/'], 10 | coverageReporters: ['json-summary', 'text', 'lcov'], 11 | // Uncomment the below lines if you would like to enforce a coverage threshold 12 | // for your action. This will fail the build if the coverage is below the 13 | // specified thresholds. 14 | // coverageThreshold: { 15 | // global: { 16 | // branches: 100, 17 | // functions: 100, 18 | // lines: 100, 19 | // statements: 100 20 | // } 21 | // }, 22 | extensionsToTreatAsEsm: ['.ts'], 23 | moduleFileExtensions: ['ts', 'js'], 24 | preset: 'ts-jest', 25 | reporters: ['default'], 26 | resolver: 'ts-jest-resolver', 27 | testEnvironment: 'node', 28 | testMatch: ['**/*.test.ts'], 29 | testPathIgnorePatterns: ['/dist/', '/node_modules/'], 30 | transform: { 31 | '^.+\\.ts$': [ 32 | 'ts-jest', 33 | { 34 | tsconfig: 'tsconfig.eslint.json', 35 | useESM: true 36 | } 37 | ] 38 | }, 39 | verbose: true 40 | } 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-action", 3 | "description": "GitHub Actions TypeScript template", 4 | "version": "0.0.0", 5 | "author": "", 6 | "type": "module", 7 | "private": true, 8 | "homepage": "https://github.com/actions/typescript-action", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/actions/typescript-action.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/actions/typescript-action/issues" 15 | }, 16 | "keywords": [ 17 | "actions" 18 | ], 19 | "exports": { 20 | ".": "./dist/index.js" 21 | }, 22 | "engines": { 23 | "node": ">=20" 24 | }, 25 | "scripts": { 26 | "bundle": "npm run format:write && npm run package", 27 | "ci-test": "NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 npx jest", 28 | "coverage": "npx make-coverage-badge --output-path ./badges/coverage.svg", 29 | "format:write": "npx prettier --write .", 30 | "format:check": "npx prettier --check .", 31 | "lint": "npx eslint .", 32 | "local-action": "npx @github/local-action . src/main.ts .env", 33 | "package": "npx rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript", 34 | "package:watch": "npm run package -- --watch", 35 | "test": "NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 npx jest", 36 | "all": "npm run format:write && npm run lint && npm run test && npm run coverage && npm run package" 37 | }, 38 | "license": "MIT", 39 | "dependencies": { 40 | "@actions/core": "^1.11.1" 41 | }, 42 | "devDependencies": { 43 | "@eslint/compat": "^1.2.9", 44 | "@github/local-action": "^3.2.1", 45 | "@jest/globals": "^29.7.0", 46 | "@rollup/plugin-commonjs": "^28.0.1", 47 | "@rollup/plugin-node-resolve": "^16.0.1", 48 | "@rollup/plugin-typescript": "^12.1.1", 49 | "@types/jest": "^29.5.14", 50 | "@types/node": "^20.17.48", 51 | "@typescript-eslint/eslint-plugin": "^8.32.1", 52 | "@typescript-eslint/parser": "^8.32.1", 53 | "eslint": "^9.27.0", 54 | "eslint-config-prettier": "^10.1.5", 55 | "eslint-import-resolver-typescript": "^4.3.5", 56 | "eslint-plugin-import": "^2.31.0", 57 | "eslint-plugin-jest": "^28.11.0", 58 | "eslint-plugin-prettier": "^5.4.0", 59 | "jest": "^29.7.0", 60 | "make-coverage-badge": "^1.2.0", 61 | "prettier": "^3.5.3", 62 | "prettier-eslint": "^16.4.2", 63 | "rollup": "^4.41.0", 64 | "ts-jest": "^29.3.4", 65 | "ts-jest-resolver": "^2.0.1", 66 | "typescript": "^5.8.3" 67 | }, 68 | "optionalDependencies": { 69 | "@rollup/rollup-linux-x64-gnu": "*" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /rollup.config.ts: -------------------------------------------------------------------------------- 1 | // See: https://rollupjs.org/introduction/ 2 | 3 | import commonjs from '@rollup/plugin-commonjs' 4 | import nodeResolve from '@rollup/plugin-node-resolve' 5 | import typescript from '@rollup/plugin-typescript' 6 | 7 | const config = { 8 | input: 'src/index.ts', 9 | output: { 10 | esModule: true, 11 | file: 'dist/index.js', 12 | format: 'es', 13 | sourcemap: true 14 | }, 15 | plugins: [typescript(), nodeResolve({ preferBuiltins: true }), commonjs()] 16 | } 17 | 18 | export default config 19 | -------------------------------------------------------------------------------- /script/release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit early 4 | # See: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#The-Set-Builtin 5 | set -e 6 | 7 | # About: 8 | # 9 | # This is a helper script to tag and push a new release. GitHub Actions use 10 | # release tags to allow users to select a specific version of the action to use. 11 | # 12 | # See: https://github.com/actions/typescript-action#publishing-a-new-release 13 | # See: https://github.com/actions/toolkit/blob/master/docs/action-versioning.md#recommendations 14 | # 15 | # This script will do the following: 16 | # 17 | # 1. Retrieve the latest release tag 18 | # 2. Display the latest release tag 19 | # 3. Prompt the user for a new release tag 20 | # 4. Validate the new release tag 21 | # 5. Remind user to update the version field in package.json 22 | # 6. Tag a new release 23 | # 7. Set 'is_major_release' variable 24 | # 8. Point separate major release tag (e.g. v1, v2) to the new release 25 | # 9. Push the new tags (with commits, if any) to remote 26 | # 10. If this is a major release, create a 'releases/v#' branch and push 27 | # 28 | # Usage: 29 | # 30 | # script/release 31 | 32 | # Variables 33 | semver_tag_regex='v[0-9]+\.[0-9]+\.[0-9]+$' 34 | semver_tag_glob='v[0-9].[0-9].[0-9]*' 35 | git_remote='origin' 36 | major_semver_tag_regex='\(v[0-9]*\)' 37 | 38 | # Terminal colors 39 | OFF='\033[0m' 40 | BOLD_RED='\033[1;31m' 41 | BOLD_GREEN='\033[1;32m' 42 | BOLD_BLUE='\033[1;34m' 43 | BOLD_PURPLE='\033[1;35m' 44 | BOLD_UNDERLINED='\033[1;4m' 45 | BOLD='\033[1m' 46 | 47 | # 1. Retrieve the latest release tag 48 | if ! latest_tag=$(git describe --abbrev=0 --match="$semver_tag_glob"); then 49 | # There are no existing release tags 50 | echo -e "No tags found (yet) - Continue to create and push your first tag" 51 | latest_tag="[unknown]" 52 | fi 53 | 54 | # 2. Display the latest release tag 55 | echo -e "The latest release tag is: ${BOLD_BLUE}${latest_tag}${OFF}" 56 | 57 | # 3. Prompt the user for a new release tag 58 | read -r -p 'Enter a new release tag (vX.X.X format): ' new_tag 59 | 60 | # 4. Validate the new release tag 61 | if echo "$new_tag" | grep -q -E "$semver_tag_regex"; then 62 | # Release tag is valid 63 | echo -e "Tag: ${BOLD_BLUE}$new_tag${OFF} is valid syntax" 64 | else 65 | # Release tag is not in `vX.X.X` format 66 | echo -e "Tag: ${BOLD_BLUE}$new_tag${OFF} is ${BOLD_RED}not valid${OFF} (must be in ${BOLD}vX.X.X${OFF} format)" 67 | exit 1 68 | fi 69 | 70 | # 5. Remind user to update the version field in package.json 71 | echo -e -n "Make sure the version field in package.json is ${BOLD_BLUE}$new_tag${OFF}. Yes? [Y/${BOLD_UNDERLINED}n${OFF}] " 72 | read -r YN 73 | 74 | if [[ ! ($YN == "y" || $YN == "Y") ]]; then 75 | # Package.json version field is not up to date 76 | echo -e "Please update the package.json version to ${BOLD_PURPLE}$new_tag${OFF} and commit your changes" 77 | exit 1 78 | fi 79 | 80 | # 6. Tag a new release 81 | git tag "$new_tag" --annotate --message "$new_tag Release" 82 | echo -e "Tagged: ${BOLD_GREEN}$new_tag${OFF}" 83 | 84 | # 7. Set 'is_major_release' variable 85 | new_major_release_tag=$(expr "$new_tag" : "$major_semver_tag_regex") 86 | 87 | if [[ "$latest_tag" = "[unknown]" ]]; then 88 | # This is the first major release 89 | is_major_release='yes' 90 | else 91 | # Compare the major version of the latest tag with the new tag 92 | latest_major_release_tag=$(expr "$latest_tag" : "$major_semver_tag_regex") 93 | 94 | if ! [[ "$new_major_release_tag" = "$latest_major_release_tag" ]]; then 95 | is_major_release='yes' 96 | else 97 | is_major_release='no' 98 | fi 99 | fi 100 | 101 | # 8. Point separate major release tag (e.g. v1, v2) to the new release 102 | if [ $is_major_release = 'yes' ]; then 103 | # Create a new major version tag and point it to this release 104 | git tag "$new_major_release_tag" --annotate --message "$new_major_release_tag Release" 105 | echo -e "New major version tag: ${BOLD_GREEN}$new_major_release_tag${OFF}" 106 | else 107 | # Update the major version tag to point it to this release 108 | git tag "$latest_major_release_tag" --force --annotate --message "Sync $latest_major_release_tag tag with $new_tag" 109 | echo -e "Synced ${BOLD_GREEN}$latest_major_release_tag${OFF} with ${BOLD_GREEN}$new_tag${OFF}" 110 | fi 111 | 112 | # 9. Push the new tags (with commits, if any) to remote 113 | git push --follow-tags 114 | 115 | if [ $is_major_release = 'yes' ]; then 116 | # New major version tag is pushed with the '--follow-tags' flags 117 | echo -e "Tags: ${BOLD_GREEN}$new_major_release_tag${OFF} and ${BOLD_GREEN}$new_tag${OFF} pushed to remote" 118 | else 119 | # Force push the updated major version tag 120 | git push $git_remote "$latest_major_release_tag" --force 121 | echo -e "Tags: ${BOLD_GREEN}$latest_major_release_tag${OFF} and ${BOLD_GREEN}$new_tag${OFF} pushed to remote" 122 | fi 123 | 124 | # 10. If this is a major release, create a 'releases/v#' branch and push 125 | if [ $is_major_release = 'yes' ]; then 126 | git branch "releases/$new_major_release_tag" "$new_major_release_tag" 127 | echo -e "Branch: ${BOLD_BLUE}releases/$new_major_release_tag${OFF} created from ${BOLD_BLUE}$new_major_release_tag${OFF} tag" 128 | git push --set-upstream $git_remote "releases/$new_major_release_tag" 129 | echo -e "Branch: ${BOLD_GREEN}releases/$new_major_release_tag${OFF} pushed to remote" 130 | fi 131 | 132 | # Completed 133 | echo -e "${BOLD_GREEN}Done!${OFF}" 134 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The entrypoint for the action. This file simply imports and runs the action's 3 | * main logic. 4 | */ 5 | import { run } from './main.js' 6 | 7 | /* istanbul ignore next */ 8 | run() 9 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | import { wait } from './wait.js' 3 | 4 | /** 5 | * The main function for the action. 6 | * 7 | * @returns Resolves when the action is complete. 8 | */ 9 | export async function run(): Promise { 10 | try { 11 | const ms: string = core.getInput('milliseconds') 12 | 13 | // Debug logs are only output if the `ACTIONS_STEP_DEBUG` secret is true 14 | core.debug(`Waiting ${ms} milliseconds ...`) 15 | 16 | // Log the current timestamp, wait, then log the new timestamp 17 | core.debug(new Date().toTimeString()) 18 | await wait(parseInt(ms, 10)) 19 | core.debug(new Date().toTimeString()) 20 | 21 | // Set outputs for other workflow steps to use 22 | core.setOutput('time', new Date().toTimeString()) 23 | } catch (error) { 24 | // Fail the workflow run if an error occurs 25 | if (error instanceof Error) core.setFailed(error.message) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/wait.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Waits for a number of milliseconds. 3 | * 4 | * @param milliseconds The number of milliseconds to wait. 5 | * @returns Resolves with 'done!' after the wait is over. 6 | */ 7 | export async function wait(milliseconds: number): Promise { 8 | return new Promise((resolve) => { 9 | if (isNaN(milliseconds)) throw new Error('milliseconds is not a number') 10 | 11 | setTimeout(() => resolve('done!'), milliseconds) 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "declaration": false, 6 | "declarationMap": false, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "lib": ["ES2022"], 10 | "module": "NodeNext", 11 | "moduleResolution": "NodeNext", 12 | "newLine": "lf", 13 | "noImplicitAny": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": false, 16 | "pretty": true, 17 | "resolveJsonModule": true, 18 | "strict": true, 19 | "strictNullChecks": true, 20 | "target": "ES2022" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.base.json", 4 | "compilerOptions": { 5 | "allowJs": true, 6 | "noEmit": true 7 | }, 8 | "exclude": ["dist", "node_modules"], 9 | "include": [ 10 | "__fixtures__", 11 | "__tests__", 12 | "src", 13 | "eslint.config.mjs", 14 | "jest.config.js", 15 | "rollup.config.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.base.json", 4 | "compilerOptions": { 5 | "module": "NodeNext", 6 | "moduleResolution": "NodeNext", 7 | "outDir": "./dist" 8 | }, 9 | "exclude": ["__fixtures__", "__tests__", "coverage", "dist", "node_modules"], 10 | "include": ["src"] 11 | } 12 | --------------------------------------------------------------------------------