├── packages ├── cache │ ├── __tests__ │ │ ├── __fixtures__ │ │ │ ├── helloWorld.txt │ │ │ ├── action.yml │ │ │ └── index.js │ │ ├── create-cache-files.sh │ │ ├── verify-cache-files.sh │ │ ├── config.test.ts │ │ ├── cacheUtils.test.ts │ │ ├── uploadUtils.test.ts │ │ └── cache.test.ts │ ├── tsconfig.json │ ├── src │ │ └── internal │ │ │ ├── shared │ │ │ ├── user-agent.ts │ │ │ ├── errors.ts │ │ │ └── util.ts │ │ │ ├── contracts.d.ts │ │ │ ├── config.ts │ │ │ └── constants.ts │ ├── LICENSE.md │ └── package.json ├── http-client │ ├── .gitignore │ ├── tsconfig.json │ ├── LICENSE │ ├── package.json │ ├── README.md │ ├── src │ │ ├── auth.ts │ │ ├── interfaces.ts │ │ └── proxy.ts │ └── __tests__ │ │ ├── auth.test.ts │ │ └── keepalive.test.ts ├── tool-cache │ ├── __tests__ │ │ └── data │ │ │ ├── archive-content │ │ │ ├── file.txt │ │ │ ├── folder │ │ │ │ └── nested-file.txt │ │ │ └── file-with-ç-character.txt │ │ │ ├── test.7z │ │ │ └── test.tar.gz │ ├── scripts │ │ ├── externals │ │ │ └── 7zdec.exe │ │ └── Invoke-7zdec.ps1 │ ├── tsconfig.json │ ├── LICENSE.md │ ├── package.json │ ├── src │ │ └── retry-helper.ts │ └── README.md ├── exec │ ├── __tests__ │ │ └── scripts │ │ │ ├── stderroutput.js │ │ │ ├── stdoutoutput.js │ │ │ ├── wait-for-input.cmd │ │ │ ├── wait-for-input.sh │ │ │ ├── wait-for-input.js │ │ │ ├── stdoutoutputlarge.js │ │ │ ├── stdlineoutput.js │ │ │ ├── print-args-cmd.cmd │ │ │ ├── print args cmd with spaces.cmd │ │ │ ├── print-args-sh.sh │ │ │ ├── stdoutputspecial.js │ │ │ ├── print-args-exe.cs │ │ │ ├── wait-for-file.js │ │ │ └── spawn-wait-for-file.js │ ├── tsconfig.json │ ├── package-lock.json │ ├── RELEASES.md │ ├── LICENSE.md │ ├── package.json │ ├── README.md │ └── src │ │ ├── interfaces.ts │ │ └── exec.ts ├── artifact │ ├── __tests__ │ │ ├── fixtures │ │ │ └── evil.zip │ │ ├── common.ts │ │ ├── path-and-artifact-name-validation.test.ts │ │ └── retention.test.ts │ ├── src │ │ ├── generated │ │ │ └── index.ts │ │ ├── artifact.ts │ │ └── internal │ │ │ ├── shared │ │ │ ├── user-agent.ts │ │ │ └── errors.ts │ │ │ ├── upload │ │ │ └── retention.ts │ │ │ └── find │ │ │ └── retry-options.ts │ ├── tsconfig.json │ ├── docs │ │ ├── generated │ │ │ ├── interfaces │ │ │ │ ├── DeleteArtifactResponse.md │ │ │ │ ├── GetArtifactResponse.md │ │ │ │ ├── ListArtifactsResponse.md │ │ │ │ ├── DownloadArtifactResponse.md │ │ │ │ ├── DownloadArtifactOptions.md │ │ │ │ ├── ListArtifactsOptions.md │ │ │ │ ├── FindOptions.md │ │ │ │ ├── Artifact.md │ │ │ │ ├── UploadArtifactResponse.md │ │ │ │ └── UploadArtifactOptions.md │ │ │ └── README.md │ │ └── faq.md │ ├── LICENSE.md │ ├── package.json │ └── CONTRIBUTIONS.md ├── attest │ ├── __tests__ │ │ ├── index.test.ts │ │ ├── attest.test.ts │ │ ├── __snapshots__ │ │ │ ├── intoto.test.ts.snap │ │ │ └── provenance.test.ts.snap │ │ ├── intoto.test.ts │ │ ├── endpoints.test.ts │ │ └── store.test.ts │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ ├── intoto.ts │ │ ├── shared.types.ts │ │ ├── store.ts │ │ ├── endpoints.ts │ │ ├── sign.ts │ │ └── artifactMetadata.ts │ ├── LICENSE.md │ └── package.json ├── glob │ ├── tsconfig.json │ ├── src │ │ ├── internal-search-state.ts │ │ ├── internal-hash-file-options.ts │ │ ├── internal-match-kind.ts │ │ ├── glob.ts │ │ ├── internal-glob-options.ts │ │ ├── internal-glob-options-helper.ts │ │ ├── internal-hash-files.ts │ │ └── internal-pattern-helper.ts │ ├── RELEASES.md │ ├── LICENSE.md │ └── package.json ├── io │ ├── tsconfig.json │ ├── package-lock.json │ ├── RELEASES.md │ ├── package.json │ ├── LICENSE.md │ └── README.md ├── github │ ├── tsconfig.json │ ├── jest.config.js │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── lib.test.ts.snap │ │ ├── payload.json │ │ └── lib.test.ts │ ├── src │ │ ├── github.ts │ │ ├── interfaces.ts │ │ ├── utils.ts │ │ ├── internal │ │ │ └── utils.ts │ │ └── context.ts │ ├── LICENSE.md │ ├── package.json │ └── RELEASES.md └── core │ ├── tsconfig.json │ ├── __tests__ │ └── platform.test.ts │ ├── LICENSE.md │ ├── src │ ├── path-utils.ts │ ├── utils.ts │ ├── file-command.ts │ ├── platform.ts │ └── oidc-utils.ts │ ├── package.json │ └── package-lock.json ├── res └── at-logo.png ├── docs ├── container-action-toolkit.md ├── assets │ ├── annotations.png │ ├── action-releases.png │ ├── node12-template.png │ └── action-releases.drawio ├── proxy-support.md ├── adrs │ └── README.md ├── container-action.md ├── action-types.md ├── action-debugging.md └── specs │ └── github-package.md ├── lerna.json ├── tsconfig.eslint.json ├── .eslintignore ├── .prettierignore ├── .gitignore ├── CODEOWNERS ├── SECURITY.md ├── .prettierrc.json ├── scripts └── create-package ├── jest.config.js ├── nx.json ├── tsconfig.json ├── .github ├── workflows │ ├── audit.yml │ ├── codeql.yml │ ├── unit-tests.yml │ ├── update-github.yaml │ └── releases.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── enhancement_request.md │ └── bug_report.md └── CONTRIBUTING.md ├── LICENSE.md └── package.json /packages/cache/__tests__/__fixtures__/helloWorld.txt: -------------------------------------------------------------------------------- 1 | hello world -------------------------------------------------------------------------------- /packages/http-client/.gitignore: -------------------------------------------------------------------------------- 1 | testoutput.txt 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /packages/tool-cache/__tests__/data/archive-content/file.txt: -------------------------------------------------------------------------------- 1 | file.txt contents -------------------------------------------------------------------------------- /res/at-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actions/toolkit/HEAD/res/at-logo.png -------------------------------------------------------------------------------- /packages/exec/__tests__/scripts/stderroutput.js: -------------------------------------------------------------------------------- 1 | process.stderr.write('this is output to stderr'); -------------------------------------------------------------------------------- /packages/exec/__tests__/scripts/stdoutoutput.js: -------------------------------------------------------------------------------- 1 | process.stdout.write('this is output to stdout'); -------------------------------------------------------------------------------- /packages/exec/__tests__/scripts/wait-for-input.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | set /p var= 3 | echo %var% 4 | -------------------------------------------------------------------------------- /docs/container-action-toolkit.md: -------------------------------------------------------------------------------- 1 | # Creating a Container Action Using the Toolkit 2 | 3 | In progress. -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/**/*" 4 | ], 5 | "version": "independent" 6 | } -------------------------------------------------------------------------------- /docs/assets/annotations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actions/toolkit/HEAD/docs/assets/annotations.png -------------------------------------------------------------------------------- /packages/exec/__tests__/scripts/wait-for-input.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | read var 4 | echo $var 5 | -------------------------------------------------------------------------------- /packages/tool-cache/__tests__/data/archive-content/folder/nested-file.txt: -------------------------------------------------------------------------------- 1 | folder/nested-file.txt contents -------------------------------------------------------------------------------- /docs/assets/action-releases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actions/toolkit/HEAD/docs/assets/action-releases.png -------------------------------------------------------------------------------- /docs/assets/node12-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actions/toolkit/HEAD/docs/assets/node12-template.png -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules" 5 | ] 6 | } -------------------------------------------------------------------------------- /packages/tool-cache/__tests__/data/test.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actions/toolkit/HEAD/packages/tool-cache/__tests__/data/test.7z -------------------------------------------------------------------------------- /packages/artifact/__tests__/fixtures/evil.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actions/toolkit/HEAD/packages/artifact/__tests__/fixtures/evil.zip -------------------------------------------------------------------------------- /packages/tool-cache/__tests__/data/test.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actions/toolkit/HEAD/packages/tool-cache/__tests__/data/test.tar.gz -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | packages/*/node_modules/ 3 | packages/*/lib/ 4 | packages/glob/__tests__/_temp 5 | packages/*/src/generated/*/ 6 | -------------------------------------------------------------------------------- /packages/tool-cache/scripts/externals/7zdec.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actions/toolkit/HEAD/packages/tool-cache/scripts/externals/7zdec.exe -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | packages/*/node_modules/ 3 | packages/*/lib/ 4 | packages/glob/__tests__/_temp/**/ 5 | packages/*/src/generated/*/ 6 | -------------------------------------------------------------------------------- /packages/exec/__tests__/scripts/wait-for-input.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var data = fs.readFileSync(0, 'utf-8') 3 | process.stdout.write(data) 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | packages/*/node_modules/ 3 | packages/*/lib/ 4 | packages/*/__tests__/_temp/ 5 | .DS_Store 6 | *.xar 7 | packages/*/audit.json 8 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @actions/actions-runtime 2 | 3 | /packages/artifact/ @actions/artifacts-actions 4 | /packages/cache/ @actions/actions-cache 5 | /packages/attest/ @actions/package-security 6 | -------------------------------------------------------------------------------- /packages/tool-cache/__tests__/data/archive-content/file-with-ç-character.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actions/toolkit/HEAD/packages/tool-cache/__tests__/data/archive-content/file-with-ç-character.txt -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | If you discover a security issue in this repo, please submit it through the [GitHub Security Bug Bounty](https://hackerone.com/github) 2 | 3 | Thanks for helping make GitHub Actions safe for everyone. -------------------------------------------------------------------------------- /packages/exec/__tests__/scripts/stdoutoutputlarge.js: -------------------------------------------------------------------------------- 1 | //Default highWaterMark for readable stream buffers us 64K (2^16) 2 | //so we go over that to get more than a buffer's worth 3 | process.stdout.write('a\n'.repeat(2**24)); 4 | -------------------------------------------------------------------------------- /packages/cache/__tests__/__fixtures__/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Set env variables' 2 | description: 'Sets certain env variables so that e2e restore and save cache can be tested in a shell' 3 | runs: 4 | using: 'node12' 5 | main: 'index.js' -------------------------------------------------------------------------------- /packages/artifact/src/generated/index.ts: -------------------------------------------------------------------------------- 1 | export * from './google/protobuf/timestamp' 2 | export * from './google/protobuf/wrappers' 3 | export * from './results/api/v1/artifact' 4 | export * from './results/api/v1/artifact.twirp-client' 5 | -------------------------------------------------------------------------------- /packages/attest/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import {attest, attestProvenance} from '../src' 2 | 3 | it('exports functions', () => { 4 | expect(attestProvenance).toBeInstanceOf(Function) 5 | expect(attest).toBeInstanceOf(Function) 6 | }) 7 | -------------------------------------------------------------------------------- /packages/exec/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./lib", 6 | "rootDir": "./src" 7 | }, 8 | "include": [ 9 | "./src" 10 | ] 11 | } -------------------------------------------------------------------------------- /packages/glob/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./lib", 6 | "rootDir": "./src" 7 | }, 8 | "include": [ 9 | "./src" 10 | ] 11 | } -------------------------------------------------------------------------------- /packages/io/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./lib", 6 | "rootDir": "./src" 7 | }, 8 | "include": [ 9 | "./src" 10 | ] 11 | } -------------------------------------------------------------------------------- /packages/github/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./lib", 6 | "rootDir": "./src" 7 | }, 8 | "include": [ 9 | "./src" 10 | ] 11 | } -------------------------------------------------------------------------------- /packages/tool-cache/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./lib", 6 | "rootDir": "./src" 7 | }, 8 | "include": [ 9 | "./src" 10 | ] 11 | } -------------------------------------------------------------------------------- /packages/glob/src/internal-search-state.ts: -------------------------------------------------------------------------------- 1 | export class SearchState { 2 | readonly path: string 3 | readonly level: number 4 | 5 | constructor(path: string, level: number) { 6 | this.path = path 7 | this.level = level 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./lib", 6 | "declaration": true, 7 | "rootDir": "./src" 8 | }, 9 | "include": [ 10 | "./src" 11 | ] 12 | } -------------------------------------------------------------------------------- /packages/exec/__tests__/scripts/stdlineoutput.js: -------------------------------------------------------------------------------- 1 | //Default highWaterMark for readable stream buffers us 64K (2^16) 2 | //so we go over that to get more than a buffer's worth 3 | const os = require('os') 4 | 5 | process.stdout.write('a'.repeat(2**16 + 1) + os.EOL); 6 | -------------------------------------------------------------------------------- /packages/github/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | moduleFileExtensions: ['js', 'ts'], 4 | testEnvironment: 'node', 5 | testMatch: ['**/*.test.ts'], 6 | transform: { 7 | '^.+\\.ts$': 'ts-jest' 8 | }, 9 | verbose: true 10 | } -------------------------------------------------------------------------------- /packages/attest/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./lib", 6 | "declaration": true, 7 | "rootDir": "./src" 8 | }, 9 | "include": [ 10 | "./src" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/exec/__tests__/scripts/print-args-cmd.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | set index=0 4 | 5 | :check_arg 6 | set arg=%1 7 | if not defined arg goto :eof 8 | set "arg=%arg:"=%" 9 | echo args[%index%]: "%arg%" 10 | set /a index=%index%+1 11 | shift 12 | goto check_arg -------------------------------------------------------------------------------- /packages/github/__tests__/__snapshots__/lib.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`@actions/context return error for context.repo when repository doesn't exist 1`] = `"context.repo requires a GITHUB_REPOSITORY environment variable like 'owner/repo'"`; 4 | -------------------------------------------------------------------------------- /packages/github/__tests__/payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "opened", 3 | "repository": { 4 | "owner": { 5 | "login": "user" 6 | }, 7 | "name": "test" 8 | }, 9 | "issue": { 10 | "number": 1 11 | }, 12 | "sender": { 13 | "type": "User" 14 | } 15 | } -------------------------------------------------------------------------------- /packages/http-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src", 6 | "moduleResolution": "node", 7 | "declaration": true, 8 | }, 9 | "include": [ 10 | "./src" 11 | ] 12 | } -------------------------------------------------------------------------------- /packages/exec/__tests__/scripts/print args cmd with spaces.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | set index=0 4 | 5 | :check_arg 6 | set arg=%1 7 | if not defined arg goto :eof 8 | set "arg=%arg:"=%" 9 | echo args[%index%]: "%arg%" 10 | set /a index=%index%+1 11 | shift 12 | goto check_arg -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": false, 9 | "arrowParens": "avoid", 10 | "parser": "typescript", 11 | "endOfLine": "auto" 12 | } -------------------------------------------------------------------------------- /packages/io/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@actions/io", 3 | "version": "2.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@actions/io", 9 | "version": "2.0.0", 10 | "license": "MIT" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /scripts/create-package: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | name=$1 6 | 7 | if [[ -z "$name" ]]; then 8 | >&2 echo "Usage: npm run new-package [name]" 9 | exit 1 10 | fi 11 | 12 | npx lerna create @actions/$name 13 | cp packages/core/tsconfig.json packages/$name/tsconfig.json -------------------------------------------------------------------------------- /packages/exec/__tests__/scripts/print-args-sh.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # store arguments in a special array 4 | args=("$@") 5 | # get number of elements 6 | ELEMENTS=${#args[@]} 7 | 8 | # echo each element 9 | for (( i=0;i<$ELEMENTS;i++)); do 10 | echo "args[$i]: \"${args[${i}]}\"" 11 | done -------------------------------------------------------------------------------- /packages/artifact/src/artifact.ts: -------------------------------------------------------------------------------- 1 | import {ArtifactClient, DefaultArtifactClient} from './internal/client' 2 | 3 | export * from './internal/shared/interfaces' 4 | export * from './internal/shared/errors' 5 | export * from './internal/client' 6 | 7 | const client: ArtifactClient = new DefaultArtifactClient() 8 | export default client 9 | -------------------------------------------------------------------------------- /packages/cache/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./lib", 6 | "rootDir": "./src", 7 | "lib": [ 8 | "es6", 9 | "dom" 10 | ], 11 | "useUnknownInCatchVariables": false 12 | }, 13 | "include": [ 14 | "./src" 15 | ] 16 | } -------------------------------------------------------------------------------- /packages/exec/__tests__/scripts/stdoutputspecial.js: -------------------------------------------------------------------------------- 1 | //first half of © character 2 | process.stdout.write(Buffer.from([0xC2]), (err) => { 3 | //write in the callback so that the second byte is sent separately 4 | setTimeout(() => { 5 | process.stdout.write(Buffer.from([0xA9])) //second half of © character 6 | }, 5000) 7 | 8 | }) 9 | -------------------------------------------------------------------------------- /packages/glob/src/internal-hash-file-options.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Options to control globbing behavior 3 | */ 4 | export interface HashFileOptions { 5 | /** 6 | * Indicates whether to follow symbolic links. Generally should set to false 7 | * when deleting files. 8 | * 9 | * @default true 10 | */ 11 | followSymbolicLinks?: boolean 12 | } 13 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | moduleFileExtensions: ['js', 'ts'], 4 | roots: ['/packages'], 5 | testEnvironment: 'node', 6 | testMatch: ['**/__tests__/*.test.ts'], 7 | transform: { 8 | '^.+\\.ts$': ['ts-jest', {isolatedModules: true, diagnostics: {warnOnly: true}}] 9 | }, 10 | verbose: true 11 | } 12 | -------------------------------------------------------------------------------- /packages/cache/__tests__/create-cache-files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Validate args 4 | prefix="$1" 5 | if [ -z "$prefix" ]; then 6 | echo "Must supply prefix argument" 7 | exit 1 8 | fi 9 | 10 | path="$2" 11 | if [ -z "$path" ]; then 12 | echo "Must supply path argument" 13 | exit 1 14 | fi 15 | 16 | mkdir -p $path 17 | echo "$prefix $GITHUB_RUN_ID" > $path/test-file.txt 18 | -------------------------------------------------------------------------------- /packages/glob/src/internal-match-kind.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Indicates whether a pattern matches a path 3 | */ 4 | export enum MatchKind { 5 | /** Not matched */ 6 | None = 0, 7 | 8 | /** Matched if the path is a directory */ 9 | Directory = 1, 10 | 11 | /** Matched if the path is a regular file */ 12 | File = 2, 13 | 14 | /** Matched */ 15 | All = Directory | File 16 | } 17 | -------------------------------------------------------------------------------- /packages/attest/src/index.ts: -------------------------------------------------------------------------------- 1 | export {createStorageRecord} from './artifactMetadata' 2 | export {AttestOptions, attest} from './attest' 3 | export { 4 | AttestProvenanceOptions, 5 | attestProvenance, 6 | buildSLSAProvenancePredicate 7 | } from './provenance' 8 | 9 | export type {SerializedBundle} from '@sigstore/bundle' 10 | export type {Attestation, Predicate, Subject} from './shared.types' 11 | -------------------------------------------------------------------------------- /packages/exec/__tests__/scripts/print-args-exe.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace PrintArgs 3 | { 4 | public static class Program 5 | { 6 | public static void Main(string[] args) 7 | { 8 | for (int i = 0 ; i < args.Length ; i++) 9 | { 10 | Console.WriteLine("args[{0}]: '{1}'", i, args[i]); 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/artifact/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./lib", 6 | "rootDir": "./src", 7 | "paths": { 8 | "@actions/core": [ 9 | "../core" 10 | ], 11 | "@actions/http-client": [ 12 | "../http-client" 13 | ] 14 | }, 15 | "useUnknownInCatchVariables": false, 16 | }, 17 | "include": [ 18 | "./src" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/artifact/__tests__/common.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | 3 | // noopLogs mocks the console.log and core.* functions to prevent output in the console while testing 4 | export const noopLogs = (): void => { 5 | jest.spyOn(console, 'log').mockImplementation(() => {}) 6 | jest.spyOn(core, 'debug').mockImplementation(() => {}) 7 | jest.spyOn(core, 'info').mockImplementation(() => {}) 8 | jest.spyOn(core, 'warning').mockImplementation(() => {}) 9 | } 10 | -------------------------------------------------------------------------------- /packages/cache/src/internal/shared/user-agent.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports 2 | const packageJson = require('../../../package.json') 3 | 4 | /** 5 | * Ensure that this User Agent String is used in all HTTP calls so that we can monitor telemetry between different versions of this package 6 | */ 7 | export function getUserAgentString(): string { 8 | return `@actions/cache-${packageJson.version}` 9 | } 10 | -------------------------------------------------------------------------------- /packages/artifact/src/internal/shared/user-agent.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports 2 | const packageJson = require('../../../package.json') 3 | 4 | /** 5 | * Ensure that this User Agent String is used in all HTTP calls so that we can monitor telemetry between different versions of this package 6 | */ 7 | export function getUserAgentString(): string { 8 | return `@actions/artifact-${packageJson.version}` 9 | } 10 | -------------------------------------------------------------------------------- /packages/attest/__tests__/attest.test.ts: -------------------------------------------------------------------------------- 1 | import {attest} from '../src/attest' 2 | 3 | describe('attest', () => { 4 | describe('when no subject information is provided', () => { 5 | it('throws an error', async () => { 6 | const options = { 7 | predicateType: 'foo', 8 | predicate: {bar: 'baz'}, 9 | token: 'token' 10 | } 11 | expect(attest(options)).rejects.toThrowError( 12 | 'Must provide either subjectName and subjectDigest or subjects' 13 | ) 14 | }) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasksRunnerOptions": { 3 | "default": { 4 | "runner": "nx/tasks-runners/default", 5 | "options": { 6 | "cacheableOperations": [] 7 | } 8 | } 9 | }, 10 | "affected": { 11 | "defaultBase": "master" 12 | }, 13 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 14 | "namedInputs": { 15 | "default": [ 16 | "{projectRoot}/**/*", 17 | "sharedGlobals" 18 | ], 19 | "sharedGlobals": [], 20 | "production": [ 21 | "default" 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /packages/attest/__tests__/__snapshots__/intoto.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`buildIntotoStatement returns an intoto statement 1`] = ` 4 | { 5 | "_type": "https://in-toto.io/Statement/v1", 6 | "predicate": { 7 | "key": "value", 8 | }, 9 | "predicateType": "predicatey", 10 | "subject": [ 11 | { 12 | "digest": { 13 | "sha256": "7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32", 14 | }, 15 | "name": "subjecty", 16 | }, 17 | ], 18 | } 19 | `; 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "module": "commonjs", 5 | "strict": true, 6 | "declaration": true, 7 | "target": "es6", 8 | "sourceMap": true, 9 | "noImplicitAny": false, 10 | "baseUrl": "./", 11 | "paths": { 12 | "@actions/core": [ 13 | "packages/core" 14 | ], 15 | "@actions/http-client": [ 16 | "packages/http-client" 17 | ], 18 | }, 19 | "useUnknownInCatchVariables": false 20 | }, 21 | "exclude": [ 22 | "node_modules", 23 | "**/*.test.ts", 24 | "**/__mocks__/*" 25 | ] 26 | } -------------------------------------------------------------------------------- /packages/artifact/docs/generated/interfaces/DeleteArtifactResponse.md: -------------------------------------------------------------------------------- 1 | [@actions/artifact](../README.md) / DeleteArtifactResponse 2 | 3 | # Interface: DeleteArtifactResponse 4 | 5 | Response from the server when deleting an artifact 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [id](DeleteArtifactResponse.md#id) 12 | 13 | ## Properties 14 | 15 | ### id 16 | 17 | • **id**: `number` 18 | 19 | The id of the artifact that was deleted 20 | 21 | #### Defined in 22 | 23 | [src/internal/shared/interfaces.ts:163](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L163) 24 | -------------------------------------------------------------------------------- /docs/proxy-support.md: -------------------------------------------------------------------------------- 1 | # Proxy Server Support 2 | 3 | Self-hosted runners [can be configured](https://help.github.com/en/actions/hosting-your-own-runners/using-a-proxy-server-with-self-hosted-runners) to run behind a proxy server in enterprises. 4 | 5 | For actions to **just work** behind a proxy server: 6 | 7 | 1. Use [tool-cache](/packages/tool-cache) version >= 1.3.1 8 | 2. Optionally use [actions/http-client](/packages/http-client) 9 | 10 | If you are using other http clients, refer to the [environment variables set by the runner](https://help.github.com/en/actions/hosting-your-own-runners/using-a-proxy-server-with-self-hosted-runners). 11 | -------------------------------------------------------------------------------- /packages/artifact/docs/generated/interfaces/GetArtifactResponse.md: -------------------------------------------------------------------------------- 1 | [@actions/artifact](../README.md) / GetArtifactResponse 2 | 3 | # Interface: GetArtifactResponse 4 | 5 | Response from the server when getting an artifact 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [artifact](GetArtifactResponse.md#artifact) 12 | 13 | ## Properties 14 | 15 | ### artifact 16 | 17 | • **artifact**: [`Artifact`](Artifact.md) 18 | 19 | Metadata about the artifact that was found 20 | 21 | #### Defined in 22 | 23 | [src/internal/shared/interfaces.ts:62](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L62) 24 | -------------------------------------------------------------------------------- /packages/artifact/docs/generated/interfaces/ListArtifactsResponse.md: -------------------------------------------------------------------------------- 1 | [@actions/artifact](../README.md) / ListArtifactsResponse 2 | 3 | # Interface: ListArtifactsResponse 4 | 5 | Response from the server when listing artifacts 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [artifacts](ListArtifactsResponse.md#artifacts) 12 | 13 | ## Properties 14 | 15 | ### artifacts 16 | 17 | • **artifacts**: [`Artifact`](Artifact.md)[] 18 | 19 | A list of artifacts that were found 20 | 21 | #### Defined in 22 | 23 | [src/internal/shared/interfaces.ts:83](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L83) 24 | -------------------------------------------------------------------------------- /packages/exec/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@actions/exec", 3 | "version": "2.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@actions/exec", 9 | "version": "2.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@actions/io": "^2.0.0" 13 | } 14 | }, 15 | "node_modules/@actions/io": { 16 | "version": "2.0.0", 17 | "resolved": "https://registry.npmjs.org/@actions/io/-/io-2.0.0.tgz", 18 | "integrity": "sha512-Jv33IN09XLO+0HS79aaODsvIRyduiF7NY/F6LYeK5oeUmrsz7aFdRphQjFoESF4jS7lMauDOttKALcpapVDIAg==", 19 | "license": "MIT" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/artifact/docs/generated/interfaces/DownloadArtifactResponse.md: -------------------------------------------------------------------------------- 1 | [@actions/artifact](../README.md) / DownloadArtifactResponse 2 | 3 | # Interface: DownloadArtifactResponse 4 | 5 | Response from the server when downloading an artifact 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [downloadPath](DownloadArtifactResponse.md#downloadpath) 12 | 13 | ## Properties 14 | 15 | ### downloadPath 16 | 17 | • `Optional` **downloadPath**: `string` 18 | 19 | The path where the artifact was downloaded to 20 | 21 | #### Defined in 22 | 23 | [src/internal/shared/interfaces.ts:93](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L93) 24 | -------------------------------------------------------------------------------- /packages/attest/__tests__/intoto.test.ts: -------------------------------------------------------------------------------- 1 | import {buildIntotoStatement} from '../src/intoto' 2 | import type {Predicate, Subject} from '../src/shared.types' 3 | 4 | describe('buildIntotoStatement', () => { 5 | const subject: Subject = { 6 | name: 'subjecty', 7 | digest: { 8 | sha256: '7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32' 9 | } 10 | } 11 | 12 | const predicate: Predicate = { 13 | type: 'predicatey', 14 | params: { 15 | key: 'value' 16 | } 17 | } 18 | 19 | it('returns an intoto statement', () => { 20 | const statement = buildIntotoStatement([subject], predicate) 21 | expect(statement).toMatchSnapshot() 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /packages/artifact/docs/generated/interfaces/DownloadArtifactOptions.md: -------------------------------------------------------------------------------- 1 | [@actions/artifact](../README.md) / DownloadArtifactOptions 2 | 3 | # Interface: DownloadArtifactOptions 4 | 5 | Options for downloading an artifact 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [path](DownloadArtifactOptions.md#path) 12 | 13 | ## Properties 14 | 15 | ### path 16 | 17 | • `Optional` **path**: `string` 18 | 19 | Denotes where the artifact will be downloaded to. If not specified then the artifact is download to GITHUB_WORKSPACE 20 | 21 | #### Defined in 22 | 23 | [src/internal/shared/interfaces.ts:103](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L103) 24 | -------------------------------------------------------------------------------- /packages/artifact/docs/generated/interfaces/ListArtifactsOptions.md: -------------------------------------------------------------------------------- 1 | [@actions/artifact](../README.md) / ListArtifactsOptions 2 | 3 | # Interface: ListArtifactsOptions 4 | 5 | Options for listing artifacts 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [latest](ListArtifactsOptions.md#latest) 12 | 13 | ## Properties 14 | 15 | ### latest 16 | 17 | • `Optional` **latest**: `boolean` 18 | 19 | Filter the workflow run's artifacts to the latest by name 20 | In the case of reruns, this can be useful to avoid duplicates 21 | 22 | #### Defined in 23 | 24 | [src/internal/shared/interfaces.ts:73](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L73) 25 | -------------------------------------------------------------------------------- /packages/github/src/github.ts: -------------------------------------------------------------------------------- 1 | import * as Context from './context' 2 | import {GitHub, getOctokitOptions} from './utils' 3 | 4 | // octokit + plugins 5 | import {OctokitOptions, OctokitPlugin} from '@octokit/core/dist-types/types' 6 | 7 | export const context = new Context.Context() 8 | 9 | /** 10 | * Returns a hydrated octokit ready to use for GitHub Actions 11 | * 12 | * @param token the repo PAT or GITHUB_TOKEN 13 | * @param options other options to set 14 | */ 15 | export function getOctokit( 16 | token: string, 17 | options?: OctokitOptions, 18 | ...additionalPlugins: OctokitPlugin[] 19 | ): InstanceType { 20 | const GitHubWithPlugins = GitHub.plugin(...additionalPlugins) 21 | return new GitHubWithPlugins(getOctokitOptions(token, options)) 22 | } 23 | -------------------------------------------------------------------------------- /packages/exec/__tests__/scripts/wait-for-file.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | // get the command line args that use the format someArg=someValue 4 | var args = {}; 5 | process.argv.forEach(function (arg) { 6 | var match = arg.match(/^(.+)=(.*)$/); 7 | if (match) { 8 | args[match[1]] = match[2]; 9 | } 10 | }); 11 | 12 | var state = { 13 | file: args.file 14 | }; 15 | 16 | if (!state.file) { 17 | throw new Error('file is not specified'); 18 | } 19 | 20 | state.checkFile = function (s) { 21 | try { 22 | fs.statSync(s.file); 23 | } 24 | catch (err) { 25 | if (err.code == 'ENOENT') { 26 | return; 27 | } 28 | 29 | throw err; 30 | } 31 | 32 | setTimeout(s.checkFile, 100, s); 33 | }; 34 | 35 | state.checkFile(state); -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: toolkit-audit 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths-ignore: 7 | - '**.md' 8 | pull_request: 9 | paths-ignore: 10 | - '**.md' 11 | 12 | jobs: 13 | 14 | build: 15 | name: Audit 16 | 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v5 22 | 23 | - name: Set Node.js 24.x 24 | uses: actions/setup-node@v5 25 | with: 26 | node-version: 24.x 27 | 28 | - name: npm install 29 | run: npm install 30 | 31 | - name: Bootstrap 32 | run: npm run bootstrap 33 | 34 | - name: audit tools (without allow-list) 35 | run: npm audit --audit-level=moderate --omit dev 36 | 37 | - name: audit packages 38 | run: npm run audit-all 39 | -------------------------------------------------------------------------------- /packages/cache/__tests__/verify-cache-files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Validate args 4 | prefix="$1" 5 | if [ -z "$prefix" ]; then 6 | echo "Must supply prefix argument" 7 | exit 1 8 | fi 9 | 10 | path="$2" 11 | if [ -z "$path" ]; then 12 | echo "Must specify path argument" 13 | exit 1 14 | fi 15 | 16 | # Sanity check GITHUB_RUN_ID defined 17 | if [ -z "$GITHUB_RUN_ID" ]; then 18 | echo "GITHUB_RUN_ID not defined" 19 | exit 1 20 | fi 21 | 22 | # Verify file exists 23 | file="$path/test-file.txt" 24 | echo "Checking for $file" 25 | if [ ! -e $file ]; then 26 | echo "File does not exist" 27 | exit 1 28 | fi 29 | 30 | # Verify file content 31 | content="$(cat $file)" 32 | echo "File content:\n$content" 33 | if [ -z "$(echo $content | grep --fixed-strings "$prefix $GITHUB_RUN_ID")" ]; then 34 | echo "Unexpected file content" 35 | exit 1 36 | fi 37 | -------------------------------------------------------------------------------- /packages/exec/RELEASES.md: -------------------------------------------------------------------------------- 1 | # @actions/exec Releases 2 | 3 | ### 2.0.0 4 | - Add support for Node 24 [#2110](https://github.com/actions/toolkit/pull/2110) 5 | - Bump @actions/io dependency from ^1.0.1 to ^2.0.0 6 | 7 | ### 1.1.1 8 | - Update `lockfileVersion` to `v2` in `package-lock.json [#1024](https://github.com/actions/toolkit/pull/1024) 9 | 10 | ### 1.1.0 11 | 12 | - [Fix stdline dropping large output](https://github.com/actions/toolkit/pull/773) 13 | - [Add getExecOutput function](https://github.com/actions/toolkit/pull/814) 14 | - [Better error for bad cwd](https://github.com/actions/toolkit/pull/793) 15 | 16 | ### 1.0.3 17 | 18 | - [Add \"types\" to package.json](https://github.com/actions/toolkit/pull/221) 19 | 20 | ### 1.0.2 21 | 22 | - [Which before invoking tool](https://github.com/actions/toolkit/pull/220) 23 | 24 | ### 1.0.0 25 | 26 | - Initial release -------------------------------------------------------------------------------- /packages/cache/__tests__/config.test.ts: -------------------------------------------------------------------------------- 1 | import * as config from '../src/internal/config' 2 | 3 | beforeEach(() => { 4 | jest.resetModules() 5 | }) 6 | 7 | test('isGhes returns false for github.com', async () => { 8 | process.env.GITHUB_SERVER_URL = 'https://github.com' 9 | expect(config.isGhes()).toBe(false) 10 | }) 11 | 12 | test('isGhes returns false for ghe.com', async () => { 13 | process.env.GITHUB_SERVER_URL = 'https://somedomain.ghe.com' 14 | expect(config.isGhes()).toBe(false) 15 | }) 16 | 17 | test('isGhes returns true for enterprise URL', async () => { 18 | process.env.GITHUB_SERVER_URL = 'https://my-enterprise.github.com' 19 | expect(config.isGhes()).toBe(true) 20 | }) 21 | 22 | test('isGhes returns false for ghe.localhost', () => { 23 | process.env.GITHUB_SERVER_URL = 'https://my.domain.ghe.localhost' 24 | expect(config.isGhes()).toBe(false) 25 | }) 26 | -------------------------------------------------------------------------------- /packages/cache/__tests__/__fixtures__/index.js: -------------------------------------------------------------------------------- 1 | // Certain env variables are not set by default in a shell context and are only available in a node context from a running action 2 | // In order to be able to restore and save cache e2e in a shell when running CI tests, we need these env variables set 3 | const fs = require('fs'); 4 | const os = require('os'); 5 | const filePath = process.env[`GITHUB_ENV`] 6 | fs.appendFileSync(filePath, `ACTIONS_CACHE_SERVICE_V2=true${os.EOL}`, { 7 | encoding: 'utf8' 8 | }) 9 | fs.appendFileSync(filePath, `ACTIONS_RESULTS_URL=${process.env.ACTIONS_RESULTS_URL}${os.EOL}`, { 10 | encoding: 'utf8' 11 | }) 12 | fs.appendFileSync(filePath, `ACTIONS_RUNTIME_TOKEN=${process.env.ACTIONS_RUNTIME_TOKEN}${os.EOL}`, { 13 | encoding: 'utf8' 14 | }) 15 | fs.appendFileSync(filePath, `GITHUB_RUN_ID=${process.env.GITHUB_RUN_ID}${os.EOL}`, { 16 | encoding: 'utf8' 17 | }) -------------------------------------------------------------------------------- /packages/io/RELEASES.md: -------------------------------------------------------------------------------- 1 | # @actions/io Releases 2 | 3 | ### 2.0.0 4 | - Add support for Node 24 [#2110](https://github.com/actions/toolkit/pull/2110) 5 | - Ensures consistent behavior for paths on Node 24 with Windows 6 | 7 | ### 1.1.3 8 | - Replace `child_process.exec` with `fs.rm` in `rmRF` for all OS implementations [#1373](https://github.com/actions/toolkit/pull/1373) 9 | 10 | ### 1.1.2 11 | - Update `lockfileVersion` to `v2` in `package-lock.json [#1020](https://github.com/actions/toolkit/pull/1020) 12 | 13 | ### 1.1.1 14 | - [Fixed a bug where we incorrectly escaped paths for rmrf](https://github.com/actions/toolkit/pull/828) 15 | 16 | ### 1.1.0 17 | 18 | - Add `findInPath` method to locate all matching executables in the system path 19 | 20 | ### 1.0.2 21 | 22 | - [Add \"types\" to package.json](https://github.com/actions/toolkit/pull/221) 23 | 24 | ### 1.0.0 25 | 26 | - Initial release 27 | -------------------------------------------------------------------------------- /docs/adrs/README.md: -------------------------------------------------------------------------------- 1 | # ADRs 2 | 3 | ADR, short for "Architecture Decision Record" is a way of capturing important architectural decisions, along with their context and consequences. 4 | 5 | This folder includes ADRs for the actions toolkit. ADRs are proposed in the form of a pull request, and they commonly follow this format: 6 | 7 | * **Title**: short present tense imperative phrase, less than 50 characters, like a git commit message. 8 | 9 | * **Status**: proposed, accepted, rejected, deprecated, superseded, etc. 10 | 11 | * **Context**: what is the issue that we're seeing that is motivating this decision or change. 12 | 13 | * **Decision**: what is the change that we're actually proposing or doing. 14 | 15 | * **Consequences**: what becomes easier or more difficult to do because of this change. 16 | 17 | --- 18 | 19 | - More information about ADRs can be found [here](https://github.com/joelparkerhenderson/architecture_decision_record). -------------------------------------------------------------------------------- /packages/github/src/interfaces.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | 3 | export interface PayloadRepository { 4 | [key: string]: any 5 | full_name?: string 6 | name: string 7 | owner: { 8 | [key: string]: any 9 | login: string 10 | name?: string 11 | } 12 | html_url?: string 13 | } 14 | 15 | export interface WebhookPayload { 16 | [key: string]: any 17 | repository?: PayloadRepository 18 | issue?: { 19 | [key: string]: any 20 | number: number 21 | html_url?: string 22 | body?: string 23 | } 24 | pull_request?: { 25 | [key: string]: any 26 | number: number 27 | html_url?: string 28 | body?: string 29 | } 30 | sender?: { 31 | [key: string]: any 32 | type: string 33 | } 34 | action?: string 35 | installation?: { 36 | id: number 37 | [key: string]: any 38 | } 39 | comment?: { 40 | id: number 41 | [key: string]: any 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/artifact/docs/generated/interfaces/FindOptions.md: -------------------------------------------------------------------------------- 1 | [@actions/artifact](../README.md) / FindOptions 2 | 3 | # Interface: FindOptions 4 | 5 | ## Table of contents 6 | 7 | ### Properties 8 | 9 | - [findBy](FindOptions.md#findby) 10 | 11 | ## Properties 12 | 13 | ### findBy 14 | 15 | • `Optional` **findBy**: `Object` 16 | 17 | The criteria for finding Artifact(s) out of the scope of the current run. 18 | 19 | #### Type declaration 20 | 21 | | Name | Type | Description | 22 | | :------ | :------ | :------ | 23 | | `repositoryName` | `string` | Repository owner (eg. 'toolkit') | 24 | | `repositoryOwner` | `string` | Repository owner (eg. 'actions') | 25 | | `token` | `string` | Token with actions:read permissions | 26 | | `workflowRunId` | `number` | WorkflowRun of the artifact(s) to lookup | 27 | 28 | #### Defined in 29 | 30 | [src/internal/shared/interfaces.ts:136](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L136) 31 | -------------------------------------------------------------------------------- /packages/attest/src/intoto.ts: -------------------------------------------------------------------------------- 1 | import {Predicate, Subject} from './shared.types' 2 | 3 | const INTOTO_STATEMENT_V1_TYPE = 'https://in-toto.io/Statement/v1' 4 | 5 | /** 6 | * An in-toto statement. 7 | * https://github.com/in-toto/attestation/blob/main/spec/v1/statement.md 8 | */ 9 | export type InTotoStatement = { 10 | _type: string 11 | subject: Subject[] 12 | predicateType: string 13 | predicate: object 14 | } 15 | 16 | /** 17 | * Assembles the given subject and predicate into an in-toto statement. 18 | * @param subject - The subject of the statement. 19 | * @param predicate - The predicate of the statement. 20 | * @returns The constructed in-toto statement. 21 | */ 22 | export const buildIntotoStatement = ( 23 | subjects: Subject[], 24 | predicate: Predicate 25 | ): InTotoStatement => { 26 | return { 27 | _type: INTOTO_STATEMENT_V1_TYPE, 28 | subject: subjects, 29 | predicateType: predicate.type, 30 | predicate: predicate.params 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/io/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@actions/io", 3 | "version": "2.0.0", 4 | "description": "Actions io lib", 5 | "keywords": [ 6 | "github", 7 | "actions", 8 | "io" 9 | ], 10 | "homepage": "https://github.com/actions/toolkit/tree/main/packages/io", 11 | "license": "MIT", 12 | "main": "lib/io.js", 13 | "types": "lib/io.d.ts", 14 | "directories": { 15 | "lib": "lib", 16 | "test": "__tests__" 17 | }, 18 | "files": [ 19 | "lib" 20 | ], 21 | "publishConfig": { 22 | "access": "public" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/actions/toolkit.git", 27 | "directory": "packages/io" 28 | }, 29 | "scripts": { 30 | "audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json", 31 | "test": "echo \"Error: run tests from root\" && exit 1", 32 | "tsc": "tsc" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/actions/toolkit/issues" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" 9 | directory: "/packages/artifact" 10 | schedule: 11 | interval: "daily" 12 | groups: 13 | # Group minor and patch updates together but keep major separate 14 | artifact-minor-patch: 15 | update-types: 16 | - "minor" 17 | - "patch" 18 | - package-ecosystem: "npm" 19 | directory: "/packages/cache" 20 | schedule: 21 | interval: "daily" 22 | groups: 23 | # Group minor and patch updates together but keep major separate 24 | cache-minor-patch: 25 | update-types: 26 | - "minor" 27 | - "patch" -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "Code Scanning - Action" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | schedule: 9 | - cron: '0 0 * * 0' 10 | 11 | jobs: 12 | CodeQL-Build: 13 | 14 | strategy: 15 | fail-fast: false 16 | 17 | 18 | # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v5 24 | 25 | # Initializes the CodeQL tools for scanning. 26 | - name: Initialize CodeQL 27 | uses: github/codeql-action/init@v2 28 | with: 29 | languages: javascript 30 | 31 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 32 | # If this step fails, then you should remove it and run the build manually (see below) 33 | - name: Autobuild 34 | uses: github/codeql-action/autobuild@v2 35 | 36 | - name: Perform CodeQL Analysis 37 | uses: github/codeql-action/analyze@v2 38 | -------------------------------------------------------------------------------- /packages/core/__tests__/platform.test.ts: -------------------------------------------------------------------------------- 1 | import os from 'os' 2 | import {platform} from '../src/core' 3 | 4 | describe('getInfo', () => { 5 | it('returns the platform info', async () => { 6 | const info = await platform.getDetails() 7 | expect(info).toEqual({ 8 | name: expect.any(String), 9 | platform: expect.any(String), 10 | arch: expect.any(String), 11 | version: expect.any(String), 12 | isWindows: expect.any(Boolean), 13 | isMacOS: expect.any(Boolean), 14 | isLinux: expect.any(Boolean) 15 | }) 16 | }) 17 | 18 | it('returns the platform info with the correct name', async () => { 19 | const isWindows = os.platform() === 'win32' 20 | const isMacOS = os.platform() === 'darwin' 21 | const isLinux = os.platform() === 'linux' 22 | 23 | const info = await platform.getDetails() 24 | expect(info.platform).toEqual(os.platform()) 25 | expect(info.isWindows).toEqual(isWindows) 26 | expect(info.isMacOS).toEqual(isMacOS) 27 | expect(info.isLinux).toEqual(isLinux) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2019 GitHub 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /packages/glob/RELEASES.md: -------------------------------------------------------------------------------- 1 | # @actions/glob Releases 2 | 3 | ### 0.5.0 4 | - Added `excludeHiddenFiles` option, which is disabled by default to preserve existing behavior [#1791: Add glob option to ignore hidden files](https://github.com/actions/toolkit/pull/1791) 5 | 6 | ### 0.4.0 7 | - Pass in the current workspace as a parameter to HashFiles [#1318](https://github.com/actions/toolkit/pull/1318) 8 | 9 | ### 0.3.0 10 | - Added a `verbose` option to HashFiles [#1052](https://github.com/actions/toolkit/pull/1052/files) 11 | 12 | ### 0.2.1 13 | - Update `lockfileVersion` to `v2` in `package-lock.json [#1023](https://github.com/actions/toolkit/pull/1023) 14 | 15 | ### 0.2.0 16 | - [Added the hashFiles function to Glob](https://github.com/actions/toolkit/pull/830) 17 | - [Added an option to filter out directories](https://github.com/actions/toolkit/pull/728) 18 | 19 | ### 0.1.2 20 | 21 | - [Fix bug where files were matched incorrectly](https://github.com/actions/toolkit/pull/805) 22 | 23 | ### 0.1.1 24 | 25 | - Update @actions/core version 26 | ### 0.1.0 27 | 28 | - Initial release 29 | 30 | -------------------------------------------------------------------------------- /packages/io/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2019 GitHub 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /packages/artifact/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2019 GitHub 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /packages/cache/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2019 GitHub 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /packages/core/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2019 GitHub 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /packages/core/src/path-utils.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | 3 | /** 4 | * toPosixPath converts the given path to the posix form. On Windows, \\ will be 5 | * replaced with /. 6 | * 7 | * @param pth. Path to transform. 8 | * @return string Posix path. 9 | */ 10 | export function toPosixPath(pth: string): string { 11 | return pth.replace(/[\\]/g, '/') 12 | } 13 | 14 | /** 15 | * toWin32Path converts the given path to the win32 form. On Linux, / will be 16 | * replaced with \\. 17 | * 18 | * @param pth. Path to transform. 19 | * @return string Win32 path. 20 | */ 21 | export function toWin32Path(pth: string): string { 22 | return pth.replace(/[/]/g, '\\') 23 | } 24 | 25 | /** 26 | * toPlatformPath converts the given path to a platform-specific path. It does 27 | * this by replacing instances of / and \ with the platform-specific path 28 | * separator. 29 | * 30 | * @param pth The path to platformize. 31 | * @return string The platform-specific path. 32 | */ 33 | export function toPlatformPath(pth: string): string { 34 | return pth.replace(/[/\\]/g, path.sep) 35 | } 36 | -------------------------------------------------------------------------------- /packages/exec/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2019 GitHub 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /packages/github/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2019 GitHub 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /packages/glob/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2019 GitHub 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /packages/tool-cache/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2019 GitHub 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /packages/attest/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2024 GitHub 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /packages/exec/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@actions/exec", 3 | "version": "2.0.0", 4 | "description": "Actions exec lib", 5 | "keywords": [ 6 | "github", 7 | "actions", 8 | "exec" 9 | ], 10 | "homepage": "https://github.com/actions/toolkit/tree/main/packages/exec", 11 | "license": "MIT", 12 | "main": "lib/exec.js", 13 | "types": "lib/exec.d.ts", 14 | "directories": { 15 | "lib": "lib", 16 | "test": "__tests__" 17 | }, 18 | "files": [ 19 | "lib", 20 | "!.DS_Store" 21 | ], 22 | "publishConfig": { 23 | "access": "public" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/actions/toolkit.git", 28 | "directory": "packages/exec" 29 | }, 30 | "scripts": { 31 | "audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json", 32 | "test": "echo \"Error: run tests from root\" && exit 1", 33 | "tsc": "tsc" 34 | }, 35 | "bugs": { 36 | "url": "https://github.com/actions/toolkit/issues" 37 | }, 38 | "dependencies": { 39 | "@actions/io": "^2.0.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/artifact/src/internal/upload/retention.ts: -------------------------------------------------------------------------------- 1 | import {Timestamp} from '../../generated' 2 | import * as core from '@actions/core' 3 | 4 | export function getExpiration(retentionDays?: number): Timestamp | undefined { 5 | if (!retentionDays) { 6 | return undefined 7 | } 8 | 9 | const maxRetentionDays = getRetentionDays() 10 | if (maxRetentionDays && maxRetentionDays < retentionDays) { 11 | core.warning( 12 | `Retention days cannot be greater than the maximum allowed retention set within the repository. Using ${maxRetentionDays} instead.` 13 | ) 14 | retentionDays = maxRetentionDays 15 | } 16 | 17 | const expirationDate = new Date() 18 | expirationDate.setDate(expirationDate.getDate() + retentionDays) 19 | 20 | return Timestamp.fromDate(expirationDate) 21 | } 22 | 23 | function getRetentionDays(): number | undefined { 24 | const retentionDays = process.env['GITHUB_RETENTION_DAYS'] 25 | if (!retentionDays) { 26 | return undefined 27 | } 28 | const days = parseInt(retentionDays) 29 | if (isNaN(days)) { 30 | return undefined 31 | } 32 | 33 | return days 34 | } 35 | -------------------------------------------------------------------------------- /packages/glob/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@actions/glob", 3 | "version": "0.5.0", 4 | "preview": true, 5 | "description": "Actions glob lib", 6 | "keywords": [ 7 | "github", 8 | "actions", 9 | "glob" 10 | ], 11 | "homepage": "https://github.com/actions/toolkit/tree/main/packages/glob", 12 | "license": "MIT", 13 | "main": "lib/glob.js", 14 | "types": "lib/glob.d.ts", 15 | "directories": { 16 | "lib": "lib", 17 | "test": "__tests__" 18 | }, 19 | "files": [ 20 | "lib", 21 | "!.DS_Store" 22 | ], 23 | "publishConfig": { 24 | "access": "public" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/actions/toolkit.git", 29 | "directory": "packages/glob" 30 | }, 31 | "scripts": { 32 | "audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json", 33 | "test": "echo \"Error: run tests from root\" && exit 1", 34 | "tsc": "tsc" 35 | }, 36 | "bugs": { 37 | "url": "https://github.com/actions/toolkit/issues" 38 | }, 39 | "dependencies": { 40 | "@actions/core": "^1.9.1", 41 | "minimatch": "^3.0.4" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/cache/src/internal/contracts.d.ts: -------------------------------------------------------------------------------- 1 | import {CompressionMethod} from './constants' 2 | import {TypedResponse} from '@actions/http-client/lib/interfaces' 3 | import {HttpClientError} from '@actions/http-client' 4 | 5 | export interface ITypedResponseWithError extends TypedResponse { 6 | error?: HttpClientError 7 | } 8 | 9 | export interface ArtifactCacheEntry { 10 | cacheKey?: string 11 | scope?: string 12 | cacheVersion?: string 13 | creationTime?: string 14 | archiveLocation?: string 15 | } 16 | 17 | export interface ArtifactCacheList { 18 | totalCount: number 19 | artifactCaches?: ArtifactCacheEntry[] 20 | } 21 | 22 | export interface CommitCacheRequest { 23 | size: number 24 | } 25 | 26 | export interface ReserveCacheRequest { 27 | key: string 28 | version?: string 29 | cacheSize?: number 30 | } 31 | 32 | export interface ReserveCacheResponse { 33 | cacheId: number 34 | } 35 | 36 | export interface InternalCacheOptions { 37 | compressionMethod?: CompressionMethod 38 | enableCrossOsArchive?: boolean 39 | cacheSize?: number 40 | } 41 | 42 | export interface ArchiveTool { 43 | path: string 44 | type: string 45 | } 46 | -------------------------------------------------------------------------------- /packages/http-client/LICENSE: -------------------------------------------------------------------------------- 1 | Actions Http Client for Node.js 2 | 3 | Copyright (c) GitHub, Inc. 4 | 5 | All rights reserved. 6 | 7 | MIT License 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 10 | associated documentation files (the "Software"), to deal in the Software without restriction, 11 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 13 | subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 18 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 19 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@actions/core", 3 | "version": "2.0.1", 4 | "description": "Actions core lib", 5 | "keywords": [ 6 | "github", 7 | "actions", 8 | "core" 9 | ], 10 | "homepage": "https://github.com/actions/toolkit/tree/main/packages/core", 11 | "license": "MIT", 12 | "main": "lib/core.js", 13 | "types": "lib/core.d.ts", 14 | "directories": { 15 | "lib": "lib", 16 | "test": "__tests__" 17 | }, 18 | "files": [ 19 | "lib", 20 | "!.DS_Store" 21 | ], 22 | "publishConfig": { 23 | "access": "public" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/actions/toolkit.git", 28 | "directory": "packages/core" 29 | }, 30 | "scripts": { 31 | "audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json", 32 | "test": "echo \"Error: run tests from root\" && exit 1", 33 | "tsc": "tsc -p tsconfig.json" 34 | }, 35 | "bugs": { 36 | "url": "https://github.com/actions/toolkit/issues" 37 | }, 38 | "dependencies": { 39 | "@actions/exec": "^2.0.0", 40 | "@actions/http-client": "^3.0.0" 41 | }, 42 | "devDependencies": { 43 | "@types/node": "^16.18.112" 44 | } 45 | } -------------------------------------------------------------------------------- /packages/attest/__tests__/endpoints.test.ts: -------------------------------------------------------------------------------- 1 | import {signingEndpoints} from '../src/endpoints' 2 | 3 | describe('signingEndpoints', () => { 4 | const originalEnv = process.env 5 | 6 | afterEach(() => { 7 | process.env = originalEnv 8 | }) 9 | 10 | describe('when using github.com', () => { 11 | beforeEach(async () => { 12 | process.env = { 13 | ...originalEnv, 14 | GITHUB_SERVER_URL: 'https://github.com' 15 | } 16 | }) 17 | 18 | it('returns expected endpoints', async () => { 19 | const endpoints = signingEndpoints('github') 20 | 21 | expect(endpoints.fulcioURL).toEqual('https://fulcio.githubapp.com') 22 | expect(endpoints.tsaServerURL).toEqual('https://timestamp.githubapp.com') 23 | }) 24 | }) 25 | 26 | describe('when using custom domain', () => { 27 | beforeEach(async () => { 28 | process.env = { 29 | ...originalEnv, 30 | GITHUB_SERVER_URL: 'https://foo.bar.com' 31 | } 32 | }) 33 | 34 | it('returns a expected endpoints', async () => { 35 | const endpoints = signingEndpoints('github') 36 | 37 | expect(endpoints.fulcioURL).toEqual('https://fulcio.foo.bar.com') 38 | expect(endpoints.tsaServerURL).toEqual('https://timestamp.foo.bar.com') 39 | }) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /packages/attest/src/shared.types.ts: -------------------------------------------------------------------------------- 1 | import type {SerializedBundle} from '@sigstore/bundle' 2 | 3 | /* 4 | * The subject of an attestation. 5 | */ 6 | export type Subject = { 7 | /* 8 | * Name of the subject. 9 | */ 10 | name: string 11 | /* 12 | * Digests of the subject. Should be a map of digest algorithms to their hex-encoded values. 13 | */ 14 | digest: Record 15 | } 16 | 17 | /* 18 | * The predicate of an attestation. 19 | */ 20 | export type Predicate = { 21 | /* 22 | * URI identifying the content type of the predicate. 23 | */ 24 | type: string 25 | /* 26 | * Predicate parameters. 27 | */ 28 | params: object 29 | } 30 | 31 | /* 32 | * Artifact attestation. 33 | */ 34 | export type Attestation = { 35 | /* 36 | * Serialized Sigstore bundle containing the provenance attestation, 37 | * signature, signing certificate and witnessed timestamp. 38 | */ 39 | bundle: SerializedBundle 40 | /* 41 | * PEM-encoded signing certificate used to sign the attestation. 42 | */ 43 | certificate: string 44 | /* 45 | * ID of Rekor transparency log entry created for the attestation. 46 | */ 47 | tlogID?: string 48 | /* 49 | * ID of the persisted attestation (accessible via the GH API). 50 | */ 51 | attestationID?: string 52 | } 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Create a request to help us improve 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | Thank you 🙇‍♀ for wanting to create an issue in this repository. Before you do, please ensure you are filing the issue in the right place. Issues should only be opened on if the issue **relates to code in this repository**. 11 | 12 | * If you have found a security issue [please submit it here](https://hackerone.com/github) 13 | * If you have questions about writing workflows or action files, then please [visit the GitHub Community Forum's Actions Board](https://github.community/t5/GitHub-Actions/bd-p/actions) 14 | * If you are having an issue or question about GitHub Actions then please [contact customer support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-github-actions#contacting-support) 15 | 16 | If your issue is relevant to this repository, please include the information below: 17 | 18 | **Describe the enhancement** 19 | A clear and concise description of what the features or enhancement you need. 20 | 21 | **Code Snippet** 22 | If applicable, add a code snippet to show the api enhancement. 23 | 24 | **Additional information** 25 | Add any other context about the feature here. 26 | -------------------------------------------------------------------------------- /packages/exec/README.md: -------------------------------------------------------------------------------- 1 | # `@actions/exec` 2 | 3 | ## Usage 4 | 5 | #### Basic 6 | 7 | You can use this package to execute tools in a cross platform way: 8 | 9 | ```js 10 | const exec = require('@actions/exec'); 11 | 12 | await exec.exec('node index.js'); 13 | ``` 14 | 15 | #### Args 16 | 17 | You can also pass in arg arrays: 18 | 19 | ```js 20 | const exec = require('@actions/exec'); 21 | 22 | await exec.exec('node', ['index.js', 'foo=bar']); 23 | ``` 24 | 25 | #### Output/options 26 | 27 | Capture output or specify [other options](https://github.com/actions/toolkit/blob/d9347d4ab99fd507c0b9104b2cf79fb44fcc827d/packages/exec/src/interfaces.ts#L5): 28 | 29 | ```js 30 | const exec = require('@actions/exec'); 31 | 32 | let myOutput = ''; 33 | let myError = ''; 34 | 35 | const options = {}; 36 | options.listeners = { 37 | stdout: (data: Buffer) => { 38 | myOutput += data.toString(); 39 | }, 40 | stderr: (data: Buffer) => { 41 | myError += data.toString(); 42 | } 43 | }; 44 | options.cwd = './lib'; 45 | 46 | await exec.exec('node', ['index.js', 'foo=bar'], options); 47 | ``` 48 | 49 | #### Exec tools not in the PATH 50 | 51 | You can specify the full path for tools not in the PATH: 52 | 53 | ```js 54 | const exec = require('@actions/exec'); 55 | 56 | await exec.exec('"/path/to/my-tool"', ['arg1']); 57 | ``` 58 | -------------------------------------------------------------------------------- /packages/cache/src/internal/config.ts: -------------------------------------------------------------------------------- 1 | export function isGhes(): boolean { 2 | const ghUrl = new URL( 3 | process.env['GITHUB_SERVER_URL'] || 'https://github.com' 4 | ) 5 | 6 | const hostname = ghUrl.hostname.trimEnd().toUpperCase() 7 | const isGitHubHost = hostname === 'GITHUB.COM' 8 | const isGheHost = hostname.endsWith('.GHE.COM') 9 | const isLocalHost = hostname.endsWith('.LOCALHOST') 10 | 11 | return !isGitHubHost && !isGheHost && !isLocalHost 12 | } 13 | 14 | export function getCacheServiceVersion(): string { 15 | // Cache service v2 is not supported on GHES. We will default to 16 | // cache service v1 even if the feature flag was enabled by user. 17 | if (isGhes()) return 'v1' 18 | 19 | return process.env['ACTIONS_CACHE_SERVICE_V2'] ? 'v2' : 'v1' 20 | } 21 | 22 | export function getCacheServiceURL(): string { 23 | const version = getCacheServiceVersion() 24 | 25 | // Based on the version of the cache service, we will determine which 26 | // URL to use. 27 | switch (version) { 28 | case 'v1': 29 | return ( 30 | process.env['ACTIONS_CACHE_URL'] || 31 | process.env['ACTIONS_RESULTS_URL'] || 32 | '' 33 | ) 34 | case 'v2': 35 | return process.env['ACTIONS_RESULTS_URL'] || '' 36 | default: 37 | throw new Error(`Unsupported cache service version: ${version}`) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/github/src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as Context from './context' 2 | import * as Utils from './internal/utils' 3 | 4 | // octokit + plugins 5 | import {Octokit} from '@octokit/core' 6 | import {OctokitOptions} from '@octokit/core/dist-types/types' 7 | import {restEndpointMethods} from '@octokit/plugin-rest-endpoint-methods' 8 | import {paginateRest} from '@octokit/plugin-paginate-rest' 9 | 10 | export const context = new Context.Context() 11 | 12 | const baseUrl = Utils.getApiBaseUrl() 13 | export const defaults: OctokitOptions = { 14 | baseUrl, 15 | request: { 16 | agent: Utils.getProxyAgent(baseUrl), 17 | fetch: Utils.getProxyFetch(baseUrl) 18 | } 19 | } 20 | 21 | export const GitHub = Octokit.plugin( 22 | restEndpointMethods, 23 | paginateRest 24 | ).defaults(defaults) 25 | 26 | /** 27 | * Convience function to correctly format Octokit Options to pass into the constructor. 28 | * 29 | * @param token the repo PAT or GITHUB_TOKEN 30 | * @param options other options to set 31 | */ 32 | export function getOctokitOptions( 33 | token: string, 34 | options?: OctokitOptions 35 | ): OctokitOptions { 36 | const opts = Object.assign({}, options || {}) // Shallow clone - don't mutate the object provided by the caller 37 | 38 | // Auth 39 | const auth = Utils.getAuthString(token, opts) 40 | if (auth) { 41 | opts.auth = auth 42 | } 43 | 44 | return opts 45 | } 46 | -------------------------------------------------------------------------------- /packages/glob/src/glob.ts: -------------------------------------------------------------------------------- 1 | import {Globber, DefaultGlobber} from './internal-globber' 2 | import {GlobOptions} from './internal-glob-options' 3 | import {HashFileOptions} from './internal-hash-file-options' 4 | import {hashFiles as _hashFiles} from './internal-hash-files' 5 | 6 | export {Globber, GlobOptions} 7 | 8 | /** 9 | * Constructs a globber 10 | * 11 | * @param patterns Patterns separated by newlines 12 | * @param options Glob options 13 | */ 14 | export async function create( 15 | patterns: string, 16 | options?: GlobOptions 17 | ): Promise { 18 | return await DefaultGlobber.create(patterns, options) 19 | } 20 | 21 | /** 22 | * Computes the sha256 hash of a glob 23 | * 24 | * @param patterns Patterns separated by newlines 25 | * @param currentWorkspace Workspace used when matching files 26 | * @param options Glob options 27 | * @param verbose Enables verbose logging 28 | */ 29 | export async function hashFiles( 30 | patterns: string, 31 | currentWorkspace = '', 32 | options?: HashFileOptions, 33 | verbose: Boolean = false 34 | ): Promise { 35 | let followSymbolicLinks = true 36 | if (options && typeof options.followSymbolicLinks === 'boolean') { 37 | followSymbolicLinks = options.followSymbolicLinks 38 | } 39 | const globber = await create(patterns, {followSymbolicLinks}) 40 | return _hashFiles(globber, currentWorkspace, verbose) 41 | } 42 | -------------------------------------------------------------------------------- /packages/tool-cache/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@actions/tool-cache", 3 | "version": "2.0.2", 4 | "description": "Actions tool-cache lib", 5 | "keywords": [ 6 | "github", 7 | "actions", 8 | "exec" 9 | ], 10 | "homepage": "https://github.com/actions/toolkit/tree/main/packages/tool-cache", 11 | "license": "MIT", 12 | "main": "lib/tool-cache.js", 13 | "types": "lib/tool-cache.d.ts", 14 | "directories": { 15 | "lib": "lib", 16 | "test": "__tests__" 17 | }, 18 | "files": [ 19 | "lib", 20 | "scripts" 21 | ], 22 | "publishConfig": { 23 | "access": "public" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/actions/toolkit.git", 28 | "directory": "packages/tool-cache" 29 | }, 30 | "scripts": { 31 | "audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json", 32 | "test": "echo \"Error: run tests from root\" && exit 1", 33 | "tsc": "tsc" 34 | }, 35 | "bugs": { 36 | "url": "https://github.com/actions/toolkit/issues" 37 | }, 38 | "dependencies": { 39 | "@actions/core": "^1.11.1", 40 | "@actions/exec": "^1.0.0", 41 | "@actions/http-client": "^2.0.1", 42 | "@actions/io": "^1.1.1", 43 | "semver": "^6.1.0" 44 | }, 45 | "devDependencies": { 46 | "@types/nock": "^11.1.0", 47 | "@types/semver": "^6.0.0", 48 | "nock": "^13.2.9" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/cache/src/internal/constants.ts: -------------------------------------------------------------------------------- 1 | export enum CacheFilename { 2 | Gzip = 'cache.tgz', 3 | Zstd = 'cache.tzst' 4 | } 5 | 6 | export enum CompressionMethod { 7 | Gzip = 'gzip', 8 | // Long range mode was added to zstd in v1.3.2. 9 | // This enum is for earlier version of zstd that does not have --long support 10 | ZstdWithoutLong = 'zstd-without-long', 11 | Zstd = 'zstd' 12 | } 13 | 14 | export enum ArchiveToolType { 15 | GNU = 'gnu', 16 | BSD = 'bsd' 17 | } 18 | 19 | // The default number of retry attempts. 20 | export const DefaultRetryAttempts = 2 21 | 22 | // The default delay in milliseconds between retry attempts. 23 | export const DefaultRetryDelay = 5000 24 | 25 | // Socket timeout in milliseconds during download. If no traffic is received 26 | // over the socket during this period, the socket is destroyed and the download 27 | // is aborted. 28 | export const SocketTimeout = 5000 29 | 30 | // The default path of GNUtar on hosted Windows runners 31 | export const GnuTarPathOnWindows = `${process.env['PROGRAMFILES']}\\Git\\usr\\bin\\tar.exe` 32 | 33 | // The default path of BSDtar on hosted Windows runners 34 | export const SystemTarPathOnWindows = `${process.env['SYSTEMDRIVE']}\\Windows\\System32\\tar.exe` 35 | 36 | export const TarFilename = 'cache.tar' 37 | 38 | export const ManifestFilename = 'manifest.txt' 39 | 40 | export const CacheFileSizeLimit = 10 * Math.pow(1024, 3) // 10GiB per repository 41 | -------------------------------------------------------------------------------- /packages/core/src/utils.ts: -------------------------------------------------------------------------------- 1 | // We use any as a valid input type 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | 4 | import {AnnotationProperties} from './core' 5 | import {CommandProperties} from './command' 6 | 7 | /** 8 | * Sanitizes an input into a string so it can be passed into issueCommand safely 9 | * @param input input to sanitize into a string 10 | */ 11 | export function toCommandValue(input: any): string { 12 | if (input === null || input === undefined) { 13 | return '' 14 | } else if (typeof input === 'string' || input instanceof String) { 15 | return input as string 16 | } 17 | return JSON.stringify(input) 18 | } 19 | 20 | /** 21 | * 22 | * @param annotationProperties 23 | * @returns The command properties to send with the actual annotation command 24 | * See IssueCommandProperties: https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionCommandManager.cs#L646 25 | */ 26 | export function toCommandProperties( 27 | annotationProperties: AnnotationProperties 28 | ): CommandProperties { 29 | if (!Object.keys(annotationProperties).length) { 30 | return {} 31 | } 32 | 33 | return { 34 | title: annotationProperties.title, 35 | file: annotationProperties.file, 36 | line: annotationProperties.startLine, 37 | endLine: annotationProperties.endLine, 38 | col: annotationProperties.startColumn, 39 | endColumn: annotationProperties.endColumn 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/glob/src/internal-glob-options.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Options to control globbing behavior 3 | */ 4 | export interface GlobOptions { 5 | /** 6 | * Indicates whether to follow symbolic links. Generally should set to false 7 | * when deleting files. 8 | * 9 | * @default true 10 | */ 11 | followSymbolicLinks?: boolean 12 | 13 | /** 14 | * Indicates whether directories that match a glob pattern, should implicitly 15 | * cause all descendant paths to be matched. 16 | * 17 | * For example, given the directory `my-dir`, the following glob patterns 18 | * would produce the same results: `my-dir/**`, `my-dir/`, `my-dir` 19 | * 20 | * @default true 21 | */ 22 | implicitDescendants?: boolean 23 | 24 | /** 25 | * Indicates whether matching directories should be included in the 26 | * result set. 27 | * 28 | * @default true 29 | */ 30 | matchDirectories?: boolean 31 | 32 | /** 33 | * Indicates whether broken symbolic should be ignored and omitted from the 34 | * result set. Otherwise an error will be thrown. 35 | * 36 | * @default true 37 | */ 38 | omitBrokenSymbolicLinks?: boolean 39 | 40 | /** 41 | * Indicates whether to exclude hidden files (files and directories starting with a `.`). 42 | * This does not apply to Windows files and directories with the hidden attribute unless 43 | * they are also prefixed with a `.`. 44 | * 45 | * @default false 46 | */ 47 | excludeHiddenFiles?: boolean 48 | } 49 | -------------------------------------------------------------------------------- /packages/exec/__tests__/scripts/spawn-wait-for-file.js: -------------------------------------------------------------------------------- 1 | const childProcess = require('child_process') 2 | const path = require('path') 3 | 4 | // Parse args 5 | const args = {} 6 | for (const arg of process.argv.slice(2)) { 7 | const idx = arg.indexOf('=') 8 | if (idx !== -1) { 9 | args[arg.slice(0, idx)] = arg.slice(idx + 1) 10 | } 11 | } 12 | 13 | const filePath = args.file 14 | if (!filePath) { 15 | throw new Error('file is not specified') 16 | } 17 | 18 | // Spawn wait-for-file.js with inherited stdio 19 | // This creates a grandchild process that holds the stdio handles open 20 | // after this process (the child) exits 21 | const waitScript = path.join(__dirname, 'wait-for-file.js') 22 | const isWindows = process.platform === 'win32' 23 | 24 | // On Windows, use detached:true to properly keep streams open 25 | // On Unix, detached:true also works and creates a new process group 26 | const child = childProcess.spawn(process.execPath, [waitScript, `file=${filePath}`], { 27 | stdio: ['ignore', 'inherit', 'inherit'], 28 | detached: true, 29 | windowsHide: true 30 | }) 31 | 32 | // Don't wait for child to exit 33 | child.unref() 34 | 35 | // Handle optional stderr output (must happen BEFORE we exit) 36 | if (args.stderr === 'true') { 37 | process.stderr.write('hi') 38 | } 39 | 40 | // Small delay to ensure child has started and inherited handles 41 | setTimeout(() => { 42 | const exitCode = args.exitCode ? parseInt(args.exitCode, 10) : 0 43 | process.exit(exitCode) 44 | }, 50) 45 | -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: toolkit-unit-tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths-ignore: 7 | - '**.md' 8 | pull_request: 9 | paths-ignore: 10 | - '**.md' 11 | 12 | jobs: 13 | 14 | build: 15 | name: Build 16 | 17 | strategy: 18 | matrix: 19 | runs-on: [ubuntu-latest, macos-latest-large, windows-latest] 20 | 21 | # Node 20 is the currently supported stable Node version for actions - https://docs.github.com/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#runsusing-for-javascript-actions 22 | # Node 24 is the new version being added with support in actions runners 23 | node-version: [20.x, 24.x] 24 | fail-fast: false 25 | 26 | runs-on: ${{ matrix.runs-on }} 27 | 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v5 31 | 32 | - name: Set up Node ${{ matrix.node-version }} 33 | uses: actions/setup-node@v5 34 | with: 35 | node-version: ${{ matrix.node-version }} 36 | 37 | - name: npm install 38 | run: npm install 39 | 40 | - name: Bootstrap 41 | run: npm run bootstrap 42 | 43 | - name: Compile 44 | run: npm run build 45 | 46 | - name: npm test 47 | run: npm test -- --runInBand --forceExit 48 | env: 49 | GITHUB_TOKEN: ${{ github.token }} 50 | 51 | - name: Lint 52 | run: npm run lint 53 | 54 | - name: Format 55 | run: npm run format-check 56 | -------------------------------------------------------------------------------- /packages/github/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@actions/github", 3 | "version": "6.0.1", 4 | "description": "Actions github lib", 5 | "keywords": [ 6 | "github", 7 | "actions" 8 | ], 9 | "homepage": "https://github.com/actions/toolkit/tree/main/packages/github", 10 | "license": "MIT", 11 | "main": "lib/github.js", 12 | "types": "lib/github.d.ts", 13 | "directories": { 14 | "lib": "lib", 15 | "test": "__tests__" 16 | }, 17 | "files": [ 18 | "lib", 19 | "!.DS_Store" 20 | ], 21 | "publishConfig": { 22 | "access": "public" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/actions/toolkit.git", 27 | "directory": "packages/github" 28 | }, 29 | "scripts": { 30 | "audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json", 31 | "test": "jest", 32 | "build": "tsc", 33 | "format": "prettier --write **/*.ts", 34 | "format-check": "prettier --check **/*.ts", 35 | "tsc": "tsc" 36 | }, 37 | "bugs": { 38 | "url": "https://github.com/actions/toolkit/issues" 39 | }, 40 | "dependencies": { 41 | "@actions/http-client": "^2.2.0", 42 | "@octokit/core": "^5.0.1", 43 | "@octokit/plugin-paginate-rest": "^9.2.2", 44 | "@octokit/plugin-rest-endpoint-methods": "^10.4.0", 45 | "@octokit/request": "^8.4.1", 46 | "@octokit/request-error": "^5.1.1", 47 | "undici": "^5.28.5" 48 | }, 49 | "devDependencies": { 50 | "proxy": "^2.1.1" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/artifact/docs/generated/interfaces/Artifact.md: -------------------------------------------------------------------------------- 1 | [@actions/artifact](../README.md) / Artifact 2 | 3 | # Interface: Artifact 4 | 5 | An Actions Artifact 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [createdAt](Artifact.md#createdat) 12 | - [id](Artifact.md#id) 13 | - [name](Artifact.md#name) 14 | - [size](Artifact.md#size) 15 | 16 | ## Properties 17 | 18 | ### createdAt 19 | 20 | • `Optional` **createdAt**: `Date` 21 | 22 | The time when the artifact was created 23 | 24 | #### Defined in 25 | 26 | [src/internal/shared/interfaces.ts:128](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L128) 27 | 28 | ___ 29 | 30 | ### id 31 | 32 | • **id**: `number` 33 | 34 | The ID of the artifact 35 | 36 | #### Defined in 37 | 38 | [src/internal/shared/interfaces.ts:118](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L118) 39 | 40 | ___ 41 | 42 | ### name 43 | 44 | • **name**: `string` 45 | 46 | The name of the artifact 47 | 48 | #### Defined in 49 | 50 | [src/internal/shared/interfaces.ts:113](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L113) 51 | 52 | ___ 53 | 54 | ### size 55 | 56 | • **size**: `number` 57 | 58 | The size of the artifact in bytes 59 | 60 | #### Defined in 61 | 62 | [src/internal/shared/interfaces.ts:123](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L123) 63 | -------------------------------------------------------------------------------- /packages/attest/__tests__/__snapshots__/provenance.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`provenance functions buildSLSAProvenancePredicate returns a provenance hydrated from an OIDC token 1`] = ` 4 | { 5 | "params": { 6 | "buildDefinition": { 7 | "buildType": "https://actions.github.io/buildtypes/workflow/v1", 8 | "externalParameters": { 9 | "workflow": { 10 | "path": ".github/workflows/main.yml", 11 | "ref": "refs/heads/main", 12 | "repository": "https://foo.ghe.com/owner/repo", 13 | }, 14 | }, 15 | "internalParameters": { 16 | "github": { 17 | "event_name": "push", 18 | "repository_id": "repo-id", 19 | "repository_owner_id": "owner-id", 20 | "runner_environment": "github-hosted", 21 | }, 22 | }, 23 | "resolvedDependencies": [ 24 | { 25 | "digest": { 26 | "gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b", 27 | }, 28 | "uri": "git+https://foo.ghe.com/owner/repo@refs/heads/main", 29 | }, 30 | ], 31 | }, 32 | "runDetails": { 33 | "builder": { 34 | "id": "https://foo.ghe.com/owner/workflows/.github/workflows/publish.yml@main", 35 | }, 36 | "metadata": { 37 | "invocationId": "https://foo.ghe.com/owner/repo/actions/runs/run-id/attempts/run-attempt", 38 | }, 39 | }, 40 | }, 41 | "type": "https://slsa.dev/provenance/v1", 42 | } 43 | `; 44 | -------------------------------------------------------------------------------- /packages/cache/__tests__/cacheUtils.test.ts: -------------------------------------------------------------------------------- 1 | import {promises as fs} from 'fs' 2 | import * as path from 'path' 3 | import * as cacheUtils from '../src/internal/cacheUtils' 4 | 5 | beforeEach(() => { 6 | jest.resetModules() 7 | }) 8 | 9 | test('getArchiveFileSizeInBytes returns file size', () => { 10 | const filePath = path.join(__dirname, '__fixtures__', 'helloWorld.txt') 11 | 12 | const size = cacheUtils.getArchiveFileSizeInBytes(filePath) 13 | 14 | expect(size).toBe(11) 15 | }) 16 | 17 | test('unlinkFile unlinks file', async () => { 18 | const testDirectory = await fs.mkdtemp('unlinkFileTest') 19 | const testFile = path.join(testDirectory, 'test.txt') 20 | await fs.writeFile(testFile, 'hello world') 21 | 22 | await expect(fs.stat(testFile)).resolves.not.toThrow() 23 | 24 | await cacheUtils.unlinkFile(testFile) 25 | 26 | // This should throw as testFile should not exist 27 | await expect(fs.stat(testFile)).rejects.toThrow() 28 | 29 | await fs.rmdir(testDirectory) 30 | }) 31 | 32 | test('assertDefined throws if undefined', () => { 33 | expect(() => cacheUtils.assertDefined('test', undefined)).toThrowError() 34 | }) 35 | 36 | test('assertDefined returns value', () => { 37 | expect(cacheUtils.assertDefined('test', 5)).toBe(5) 38 | }) 39 | 40 | test('resolvePaths works on github workspace directory', async () => { 41 | const workspace = process.env['GITHUB_WORKSPACE'] ?? '.' 42 | const paths = await cacheUtils.resolvePaths([workspace]) 43 | expect(paths.length).toBeGreaterThan(0) 44 | }) 45 | -------------------------------------------------------------------------------- /packages/http-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@actions/http-client", 3 | "version": "3.0.0", 4 | "description": "Actions Http Client", 5 | "keywords": [ 6 | "github", 7 | "actions", 8 | "http" 9 | ], 10 | "homepage": "https://github.com/actions/toolkit/tree/main/packages/http-client", 11 | "license": "MIT", 12 | "main": "lib/index.js", 13 | "types": "lib/index.d.ts", 14 | "directories": { 15 | "lib": "lib", 16 | "test": "__tests__" 17 | }, 18 | "files": [ 19 | "lib", 20 | "!.DS_Store" 21 | ], 22 | "publishConfig": { 23 | "access": "public" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/actions/toolkit.git", 28 | "directory": "packages/http-client" 29 | }, 30 | "scripts": { 31 | "audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json", 32 | "test": "echo \"Error: run tests from root\" && exit 1", 33 | "build": "tsc", 34 | "format": "prettier --write **/*.ts", 35 | "format-check": "prettier --check **/*.ts", 36 | "tsc": "tsc" 37 | }, 38 | "bugs": { 39 | "url": "https://github.com/actions/toolkit/issues" 40 | }, 41 | "devDependencies": { 42 | "@types/node": "24.1.0", 43 | "@types/tunnel": "0.0.3", 44 | "proxy": "^2.1.1", 45 | "@types/proxy": "^1.0.1" 46 | }, 47 | "dependencies": { 48 | "tunnel": "^0.0.6", 49 | "undici": "^5.28.5" 50 | }, 51 | "overrides": { 52 | "uri-js": "npm:uri-js-replace@^1.0.1" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/github/src/internal/utils.ts: -------------------------------------------------------------------------------- 1 | import * as http from 'http' 2 | import * as httpClient from '@actions/http-client' 3 | import {OctokitOptions} from '@octokit/core/dist-types/types' 4 | import {ProxyAgent, fetch} from 'undici' 5 | 6 | export function getAuthString( 7 | token: string, 8 | options: OctokitOptions 9 | ): string | undefined { 10 | if (!token && !options.auth) { 11 | throw new Error('Parameter token or opts.auth is required') 12 | } else if (token && options.auth) { 13 | throw new Error('Parameters token and opts.auth may not both be specified') 14 | } 15 | 16 | return typeof options.auth === 'string' ? options.auth : `token ${token}` 17 | } 18 | 19 | export function getProxyAgent(destinationUrl: string): http.Agent { 20 | const hc = new httpClient.HttpClient() 21 | return hc.getAgent(destinationUrl) 22 | } 23 | 24 | export function getProxyAgentDispatcher( 25 | destinationUrl: string 26 | ): ProxyAgent | undefined { 27 | const hc = new httpClient.HttpClient() 28 | return hc.getAgentDispatcher(destinationUrl) 29 | } 30 | 31 | export function getProxyFetch(destinationUrl): typeof fetch { 32 | const httpDispatcher = getProxyAgentDispatcher(destinationUrl) 33 | const proxyFetch: typeof fetch = async (url, opts) => { 34 | return fetch(url, { 35 | ...opts, 36 | dispatcher: httpDispatcher 37 | }) 38 | } 39 | return proxyFetch 40 | } 41 | 42 | export function getApiBaseUrl(): string { 43 | return process.env['GITHUB_API_URL'] || 'https://api.github.com' 44 | } 45 | -------------------------------------------------------------------------------- /packages/io/README.md: -------------------------------------------------------------------------------- 1 | # `@actions/io` 2 | 3 | > Core functions for cli filesystem scenarios 4 | 5 | ## Usage 6 | 7 | #### mkdir -p 8 | 9 | Recursively make a directory. Follows rules specified in [man mkdir](https://linux.die.net/man/1/mkdir) with the `-p` option specified: 10 | 11 | ```js 12 | const io = require('@actions/io'); 13 | 14 | await io.mkdirP('path/to/make'); 15 | ``` 16 | 17 | #### cp/mv 18 | 19 | Copy or move files or folders. Follows rules specified in [man cp](https://linux.die.net/man/1/cp) and [man mv](https://linux.die.net/man/1/mv): 20 | 21 | ```js 22 | const io = require('@actions/io'); 23 | 24 | // Recursive must be true for directories 25 | const options = { recursive: true, force: false } 26 | 27 | await io.cp('path/to/directory', 'path/to/dest', options); 28 | await io.mv('path/to/file', 'path/to/dest'); 29 | ``` 30 | 31 | #### rm -rf 32 | 33 | Remove a file or folder recursively. Follows rules specified in [man rm](https://linux.die.net/man/1/rm) with the `-r` and `-f` rules specified. 34 | 35 | ```js 36 | const io = require('@actions/io'); 37 | 38 | await io.rmRF('path/to/directory'); 39 | await io.rmRF('path/to/file'); 40 | ``` 41 | 42 | #### which 43 | 44 | Get the path to a tool and resolves via paths. Follows the rules specified in [man which](https://linux.die.net/man/1/which). 45 | 46 | ```js 47 | const exec = require('@actions/exec'); 48 | const io = require('@actions/io'); 49 | 50 | const pythonPath: string = await io.which('python', true) 51 | 52 | await exec.exec(`"${pythonPath}"`, ['main.py']); 53 | ``` 54 | -------------------------------------------------------------------------------- /packages/artifact/docs/generated/README.md: -------------------------------------------------------------------------------- 1 | @actions/artifact 2 | 3 | # @actions/artifact 4 | 5 | ## Table of contents 6 | 7 | ### Classes 8 | 9 | - [ArtifactNotFoundError](classes/ArtifactNotFoundError.md) 10 | - [DefaultArtifactClient](classes/DefaultArtifactClient.md) 11 | - [FilesNotFoundError](classes/FilesNotFoundError.md) 12 | - [GHESNotSupportedError](classes/GHESNotSupportedError.md) 13 | - [InvalidResponseError](classes/InvalidResponseError.md) 14 | - [NetworkError](classes/NetworkError.md) 15 | - [UsageError](classes/UsageError.md) 16 | 17 | ### Interfaces 18 | 19 | - [Artifact](interfaces/Artifact.md) 20 | - [ArtifactClient](interfaces/ArtifactClient.md) 21 | - [DeleteArtifactResponse](interfaces/DeleteArtifactResponse.md) 22 | - [DownloadArtifactOptions](interfaces/DownloadArtifactOptions.md) 23 | - [DownloadArtifactResponse](interfaces/DownloadArtifactResponse.md) 24 | - [FindOptions](interfaces/FindOptions.md) 25 | - [GetArtifactResponse](interfaces/GetArtifactResponse.md) 26 | - [ListArtifactsOptions](interfaces/ListArtifactsOptions.md) 27 | - [ListArtifactsResponse](interfaces/ListArtifactsResponse.md) 28 | - [UploadArtifactOptions](interfaces/UploadArtifactOptions.md) 29 | - [UploadArtifactResponse](interfaces/UploadArtifactResponse.md) 30 | 31 | ### Variables 32 | 33 | - [default](README.md#default) 34 | 35 | ## Variables 36 | 37 | ### default 38 | 39 | • `Const` **default**: [`ArtifactClient`](interfaces/ArtifactClient.md) 40 | 41 | #### Defined in 42 | 43 | [src/artifact.ts:7](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/artifact.ts#L7) 44 | -------------------------------------------------------------------------------- /packages/attest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@actions/attest", 3 | "version": "2.1.0", 4 | "description": "Actions attestation lib", 5 | "keywords": [ 6 | "github", 7 | "actions", 8 | "attestation" 9 | ], 10 | "homepage": "https://github.com/actions/toolkit/tree/main/packages/attest", 11 | "license": "MIT", 12 | "main": "lib/index.js", 13 | "types": "lib/index.d.ts", 14 | "directories": { 15 | "lib": "lib", 16 | "test": "__tests__" 17 | }, 18 | "files": [ 19 | "lib" 20 | ], 21 | "publishConfig": { 22 | "access": "public", 23 | "provenance": true 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/actions/toolkit.git", 28 | "directory": "packages/attest" 29 | }, 30 | "scripts": { 31 | "test": "echo \"Error: run tests from root\" && exit 1", 32 | "tsc": "tsc" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/actions/toolkit/issues" 36 | }, 37 | "devDependencies": { 38 | "@sigstore/mock": "^0.10.0", 39 | "@sigstore/rekor-types": "^3.0.0", 40 | "@types/jsonwebtoken": "^9.0.6", 41 | "nock": "^13.5.1", 42 | "undici": "^6.20.0" 43 | }, 44 | "dependencies": { 45 | "@actions/core": "^1.11.1", 46 | "@actions/github": "^6.0.0", 47 | "@actions/http-client": "^2.2.3", 48 | "@octokit/plugin-retry": "^6.0.1", 49 | "@sigstore/bundle": "^3.1.0", 50 | "@sigstore/sign": "^3.1.0", 51 | "jose": "^5.10.0" 52 | }, 53 | "overrides": { 54 | "@octokit/plugin-retry": { 55 | "@octokit/core": "^5.2.0" 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/artifact/docs/generated/interfaces/UploadArtifactResponse.md: -------------------------------------------------------------------------------- 1 | [@actions/artifact](../README.md) / UploadArtifactResponse 2 | 3 | # Interface: UploadArtifactResponse 4 | 5 | Response from the server when an artifact is uploaded 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [digest](UploadArtifactResponse.md#digest) 12 | - [id](UploadArtifactResponse.md#id) 13 | - [size](UploadArtifactResponse.md#size) 14 | 15 | ## Properties 16 | 17 | ### digest 18 | 19 | • `Optional` **digest**: `string` 20 | 21 | The SHA256 digest of the artifact that was created. Not provided if no artifact was uploaded 22 | 23 | #### Defined in 24 | 25 | [src/internal/shared/interfaces.ts:19](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L19) 26 | 27 | ___ 28 | 29 | ### id 30 | 31 | • `Optional` **id**: `number` 32 | 33 | The id of the artifact that was created. Not provided if no artifact was uploaded 34 | This ID can be used as input to other APIs to download, delete or get more information about an artifact: https://docs.github.com/en/rest/actions/artifacts 35 | 36 | #### Defined in 37 | 38 | [src/internal/shared/interfaces.ts:14](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L14) 39 | 40 | ___ 41 | 42 | ### size 43 | 44 | • `Optional` **size**: `number` 45 | 46 | Total size of the artifact in bytes. Not provided if no artifact was uploaded 47 | 48 | #### Defined in 49 | 50 | [src/internal/shared/interfaces.ts:8](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L8) 51 | -------------------------------------------------------------------------------- /packages/glob/src/internal-glob-options-helper.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | import {GlobOptions} from './internal-glob-options' 3 | 4 | /** 5 | * Returns a copy with defaults filled in. 6 | */ 7 | export function getOptions(copy?: GlobOptions): GlobOptions { 8 | const result: GlobOptions = { 9 | followSymbolicLinks: true, 10 | implicitDescendants: true, 11 | matchDirectories: true, 12 | omitBrokenSymbolicLinks: true, 13 | excludeHiddenFiles: false 14 | } 15 | 16 | if (copy) { 17 | if (typeof copy.followSymbolicLinks === 'boolean') { 18 | result.followSymbolicLinks = copy.followSymbolicLinks 19 | core.debug(`followSymbolicLinks '${result.followSymbolicLinks}'`) 20 | } 21 | 22 | if (typeof copy.implicitDescendants === 'boolean') { 23 | result.implicitDescendants = copy.implicitDescendants 24 | core.debug(`implicitDescendants '${result.implicitDescendants}'`) 25 | } 26 | 27 | if (typeof copy.matchDirectories === 'boolean') { 28 | result.matchDirectories = copy.matchDirectories 29 | core.debug(`matchDirectories '${result.matchDirectories}'`) 30 | } 31 | 32 | if (typeof copy.omitBrokenSymbolicLinks === 'boolean') { 33 | result.omitBrokenSymbolicLinks = copy.omitBrokenSymbolicLinks 34 | core.debug(`omitBrokenSymbolicLinks '${result.omitBrokenSymbolicLinks}'`) 35 | } 36 | 37 | if (typeof copy.excludeHiddenFiles === 'boolean') { 38 | result.excludeHiddenFiles = copy.excludeHiddenFiles 39 | core.debug(`excludeHiddenFiles '${result.excludeHiddenFiles}'`) 40 | } 41 | } 42 | 43 | return result 44 | } 45 | -------------------------------------------------------------------------------- /packages/glob/src/internal-hash-files.ts: -------------------------------------------------------------------------------- 1 | import * as crypto from 'crypto' 2 | import * as core from '@actions/core' 3 | import * as fs from 'fs' 4 | import * as stream from 'stream' 5 | import * as util from 'util' 6 | import * as path from 'path' 7 | import {Globber} from './glob' 8 | 9 | export async function hashFiles( 10 | globber: Globber, 11 | currentWorkspace: string, 12 | verbose: Boolean = false 13 | ): Promise { 14 | const writeDelegate = verbose ? core.info : core.debug 15 | let hasMatch = false 16 | const githubWorkspace = currentWorkspace 17 | ? currentWorkspace 18 | : (process.env['GITHUB_WORKSPACE'] ?? process.cwd()) 19 | const result = crypto.createHash('sha256') 20 | let count = 0 21 | for await (const file of globber.globGenerator()) { 22 | writeDelegate(file) 23 | if (!file.startsWith(`${githubWorkspace}${path.sep}`)) { 24 | writeDelegate(`Ignore '${file}' since it is not under GITHUB_WORKSPACE.`) 25 | continue 26 | } 27 | if (fs.statSync(file).isDirectory()) { 28 | writeDelegate(`Skip directory '${file}'.`) 29 | continue 30 | } 31 | const hash = crypto.createHash('sha256') 32 | const pipeline = util.promisify(stream.pipeline) 33 | await pipeline(fs.createReadStream(file), hash) 34 | result.write(hash.digest()) 35 | count++ 36 | if (!hasMatch) { 37 | hasMatch = true 38 | } 39 | } 40 | result.end() 41 | 42 | if (hasMatch) { 43 | writeDelegate(`Found ${count} files to hash.`) 44 | return result.digest('hex') 45 | } else { 46 | writeDelegate(`No matches found for glob`) 47 | return '' 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/core/src/file-command.ts: -------------------------------------------------------------------------------- 1 | // For internal use, subject to change. 2 | 3 | // We use any as a valid input type 4 | /* eslint-disable @typescript-eslint/no-explicit-any */ 5 | 6 | import * as crypto from 'crypto' 7 | import * as fs from 'fs' 8 | import * as os from 'os' 9 | import {toCommandValue} from './utils' 10 | 11 | export function issueFileCommand(command: string, message: any): void { 12 | const filePath = process.env[`GITHUB_${command}`] 13 | if (!filePath) { 14 | throw new Error( 15 | `Unable to find environment variable for file command ${command}` 16 | ) 17 | } 18 | if (!fs.existsSync(filePath)) { 19 | throw new Error(`Missing file at path: ${filePath}`) 20 | } 21 | 22 | fs.appendFileSync(filePath, `${toCommandValue(message)}${os.EOL}`, { 23 | encoding: 'utf8' 24 | }) 25 | } 26 | 27 | export function prepareKeyValueMessage(key: string, value: any): string { 28 | const delimiter = `ghadelimiter_${crypto.randomUUID()}` 29 | const convertedValue = toCommandValue(value) 30 | 31 | // These should realistically never happen, but just in case someone finds a 32 | // way to exploit uuid generation let's not allow keys or values that contain 33 | // the delimiter. 34 | if (key.includes(delimiter)) { 35 | throw new Error( 36 | `Unexpected input: name should not contain the delimiter "${delimiter}"` 37 | ) 38 | } 39 | 40 | if (convertedValue.includes(delimiter)) { 41 | throw new Error( 42 | `Unexpected input: value should not contain the delimiter "${delimiter}"` 43 | ) 44 | } 45 | 46 | return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}` 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/update-github.yaml: -------------------------------------------------------------------------------- 1 | name: "UpdateOctokit" 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | UpdateOctokit: 8 | runs-on: ubuntu-latest 9 | if: ${{ github.repository_owner == 'actions' }} 10 | steps: 11 | - name: Checkout repository 12 | uses: actions/checkout@v5 13 | - name: Update Octokit 14 | working-directory: packages/github 15 | run: | 16 | npx npm-check-updates -u --dep prod 17 | npm install 18 | - name: Check Status 19 | id: status 20 | working-directory: packages/github 21 | run: | 22 | if [[ "$(git status --porcelain)" != "" ]]; then 23 | echo "::set-output name=createPR::true" 24 | git config --global user.email "github-actions@github.com" 25 | git config --global user.name "github-actions[bot]" 26 | git checkout -b bots/updateGitHubDependencies-${{github.run_number}} 27 | git add . 28 | git commit -m "Update Dependencies" 29 | git push --set-upstream origin bots/updateGitHubDependencies-${{github.run_number}} 30 | fi 31 | - name: Create PR 32 | if: ${{steps.status.outputs.createPR}} 33 | uses: actions/github-script@v6 34 | with: 35 | github-token: ${{secrets.GITHUB_TOKEN}} 36 | script: | 37 | github.pulls.create( 38 | { 39 | base: "main", 40 | owner: "${{github.repository_owner}}", 41 | repo: "toolkit", 42 | title: "Update Octokit dependencies", 43 | body: "Update Octokit dependencies", 44 | head: "bots/updateGitHubDependencies-${{github.run_number}}" 45 | }) 46 | -------------------------------------------------------------------------------- /packages/attest/src/store.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github' 2 | import {retry} from '@octokit/plugin-retry' 3 | import {RequestHeaders} from '@octokit/types' 4 | 5 | const CREATE_ATTESTATION_REQUEST = 'POST /repos/{owner}/{repo}/attestations' 6 | const DEFAULT_RETRY_COUNT = 5 7 | 8 | export type WriteOptions = { 9 | retry?: number 10 | headers?: RequestHeaders 11 | } 12 | /** 13 | * Writes an attestation to the repository's attestations endpoint. 14 | * @param attestation - The attestation to write. 15 | * @param token - The GitHub token for authentication. 16 | * @returns The ID of the attestation. 17 | * @throws Error if the attestation fails to persist. 18 | */ 19 | export const writeAttestation = async ( 20 | attestation: unknown, 21 | token: string, 22 | options: WriteOptions = {} 23 | ): Promise => { 24 | const retries = options.retry ?? DEFAULT_RETRY_COUNT 25 | const octokit = github.getOctokit(token, {retry: {retries}}, retry) 26 | 27 | try { 28 | const response = await octokit.request(CREATE_ATTESTATION_REQUEST, { 29 | owner: github.context.repo.owner, 30 | repo: github.context.repo.repo, 31 | headers: options.headers, 32 | bundle: attestation as { 33 | mediaType?: string 34 | verificationMaterial?: {[key: string]: unknown} 35 | dsseEnvelope?: {[key: string]: unknown} 36 | } 37 | }) 38 | 39 | const data = 40 | typeof response.data == 'string' 41 | ? JSON.parse(response.data) 42 | : response.data 43 | return data?.id 44 | } catch (err) { 45 | const message = err instanceof Error ? err.message : err 46 | throw new Error(`Failed to persist attestation: ${message}`) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/attest/src/endpoints.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github' 2 | 3 | const PUBLIC_GOOD_ID = 'public-good' 4 | const GITHUB_ID = 'github' 5 | 6 | const FULCIO_PUBLIC_GOOD_URL = 'https://fulcio.sigstore.dev' 7 | const REKOR_PUBLIC_GOOD_URL = 'https://rekor.sigstore.dev' 8 | 9 | export type SigstoreInstance = typeof PUBLIC_GOOD_ID | typeof GITHUB_ID 10 | 11 | export type Endpoints = { 12 | fulcioURL: string 13 | rekorURL?: string 14 | tsaServerURL?: string 15 | } 16 | 17 | export const SIGSTORE_PUBLIC_GOOD: Endpoints = { 18 | fulcioURL: FULCIO_PUBLIC_GOOD_URL, 19 | rekorURL: REKOR_PUBLIC_GOOD_URL 20 | } 21 | 22 | export const signingEndpoints = (sigstore?: SigstoreInstance): Endpoints => { 23 | let instance: SigstoreInstance 24 | 25 | // An explicitly set instance type takes precedence, but if not set, use the 26 | // repository's visibility to determine the instance type. 27 | if (sigstore && [PUBLIC_GOOD_ID, GITHUB_ID].includes(sigstore)) { 28 | instance = sigstore 29 | } else { 30 | instance = 31 | github.context.payload.repository?.visibility === 'public' 32 | ? PUBLIC_GOOD_ID 33 | : GITHUB_ID 34 | } 35 | 36 | switch (instance) { 37 | case PUBLIC_GOOD_ID: 38 | return SIGSTORE_PUBLIC_GOOD 39 | case GITHUB_ID: 40 | return buildGitHubEndpoints() 41 | } 42 | } 43 | 44 | function buildGitHubEndpoints(): Endpoints { 45 | const serverURL = process.env.GITHUB_SERVER_URL || 'https://github.com' 46 | let host = new URL(serverURL).hostname 47 | 48 | if (host === 'github.com') { 49 | host = 'githubapp.com' 50 | } 51 | return { 52 | fulcioURL: `https://fulcio.${host}`, 53 | tsaServerURL: `https://timestamp.${host}` 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "scripts": { 5 | "audit-all": "lerna run audit-moderate", 6 | "bootstrap": "lerna exec -- npm install", 7 | "build": "lerna run tsc", 8 | "clean": "lerna clean", 9 | "repair": "lerna repair", 10 | "check-all": "concurrently \"npm:format-check\" \"npm:lint\" \"npm:test\" \"npm:build -- -- --noEmit\"", 11 | "format": "prettier --write packages/**/*.ts", 12 | "format-check": "prettier --check packages/**/*.ts", 13 | "lint": "eslint packages/**/*.ts", 14 | "lint-fix": "eslint packages/**/*.ts --fix", 15 | "new-package": "scripts/create-package", 16 | "test": "jest --testTimeout 70000" 17 | }, 18 | "devDependencies": { 19 | "@types/jest": "^29.5.4", 20 | "@types/node": "^24.1.0", 21 | "@types/signale": "^1.4.1", 22 | "concurrently": "^6.1.0", 23 | "eslint": "^8.0.1", 24 | "eslint-config-prettier": "^8.9.0", 25 | "eslint-plugin-github": "^4.9.2", 26 | "eslint-plugin-jest": "^27.2.3", 27 | "eslint-plugin-prettier": "^5.0.0", 28 | "flow-bin": "^0.115.0", 29 | "jest": "^29.6.4", 30 | "lerna": "^6.4.1", 31 | "nx": "16.6.0", 32 | "prettier": "^3.0.0", 33 | "ts-jest": "^29.1.1", 34 | "typescript": "^5.2.2" 35 | }, 36 | "overrides": { 37 | "semver": "^7.6.0", 38 | "tar": "^6.2.1", 39 | "@octokit/plugin-paginate-rest": "^9.2.2", 40 | "@octokit/request": "^8.4.1", 41 | "@octokit/request-error": "^5.1.1", 42 | "@octokit/core": "^5.0.3", 43 | "tmp": "^0.2.4", 44 | "@types/node": "^24.1.0", 45 | "brace-expansion": "^2.0.2", 46 | "form-data": "^4.0.4", 47 | "uri-js": "npm:uri-js-replace@^1.0.1", 48 | "node-fetch": "^3.3.2" 49 | } 50 | } -------------------------------------------------------------------------------- /packages/artifact/src/internal/find/retry-options.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | import {OctokitOptions} from '@octokit/core/dist-types/types' 3 | import {RequestRequestOptions} from '@octokit/types' 4 | 5 | export type RetryOptions = { 6 | doNotRetry?: number[] 7 | enabled?: boolean 8 | } 9 | 10 | // Defaults for fetching artifacts 11 | const defaultMaxRetryNumber = 5 12 | const defaultExemptStatusCodes = [400, 401, 403, 404, 422] // https://github.com/octokit/plugin-retry.js/blob/9a2443746c350b3beedec35cf26e197ea318a261/src/index.ts#L14 13 | 14 | export function getRetryOptions( 15 | defaultOptions: OctokitOptions, 16 | retries: number = defaultMaxRetryNumber, 17 | exemptStatusCodes: number[] = defaultExemptStatusCodes 18 | ): [RetryOptions, RequestRequestOptions | undefined] { 19 | if (retries <= 0) { 20 | return [{enabled: false}, defaultOptions.request] 21 | } 22 | 23 | const retryOptions: RetryOptions = { 24 | enabled: true 25 | } 26 | 27 | if (exemptStatusCodes.length > 0) { 28 | retryOptions.doNotRetry = exemptStatusCodes 29 | } 30 | 31 | // The GitHub type has some defaults for `options.request` 32 | // see: https://github.com/actions/toolkit/blob/4fbc5c941a57249b19562015edbd72add14be93d/packages/github/src/utils.ts#L15 33 | // We pass these in here so they are not overridden. 34 | const requestOptions: RequestRequestOptions = { 35 | ...defaultOptions.request, 36 | retries 37 | } 38 | 39 | core.debug( 40 | `GitHub client configured with: (retries: ${ 41 | requestOptions.retries 42 | }, retry-exempt-status-code: ${ 43 | retryOptions.doNotRetry ?? 'octokit default: [400, 401, 403, 404, 422]' 44 | })` 45 | ) 46 | 47 | return [retryOptions, requestOptions] 48 | } 49 | -------------------------------------------------------------------------------- /packages/cache/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@actions/cache", 3 | "version": "5.0.1", 4 | "preview": true, 5 | "description": "Actions cache lib", 6 | "keywords": [ 7 | "github", 8 | "actions", 9 | "cache" 10 | ], 11 | "homepage": "https://github.com/actions/toolkit/tree/main/packages/cache", 12 | "license": "MIT", 13 | "main": "lib/cache.js", 14 | "types": "lib/cache.d.ts", 15 | "directories": { 16 | "lib": "lib", 17 | "test": "__tests__" 18 | }, 19 | "files": [ 20 | "lib", 21 | "!.DS_Store" 22 | ], 23 | "publishConfig": { 24 | "access": "public" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/actions/toolkit.git", 29 | "directory": "packages/cache" 30 | }, 31 | "scripts": { 32 | "audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json", 33 | "test": "echo \"Error: run tests from root\" && exit 1", 34 | "tsc": "tsc" 35 | }, 36 | "bugs": { 37 | "url": "https://github.com/actions/toolkit/issues" 38 | }, 39 | "dependencies": { 40 | "@actions/core": "^2.0.0", 41 | "@actions/exec": "^2.0.0", 42 | "@actions/glob": "^0.5.0", 43 | "@protobuf-ts/runtime-rpc": "^2.11.1", 44 | "@actions/http-client": "^3.0.0", 45 | "@actions/io": "^2.0.0", 46 | "@azure/abort-controller": "^1.1.0", 47 | "@azure/core-rest-pipeline": "^1.22.0", 48 | "@azure/storage-blob": "^12.29.1", 49 | "semver": "^6.3.1" 50 | }, 51 | "devDependencies": { 52 | "@types/node": "^24.1.0", 53 | "@types/semver": "^6.0.0", 54 | "@protobuf-ts/plugin": "^2.9.4", 55 | "typescript": "^5.2.2" 56 | }, 57 | "overrides": { 58 | "uri-js": "npm:uri-js-replace@^1.0.1", 59 | "node-fetch": "^3.3.2" 60 | } 61 | } -------------------------------------------------------------------------------- /packages/tool-cache/src/retry-helper.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | 3 | /** 4 | * Internal class for retries 5 | */ 6 | export class RetryHelper { 7 | private maxAttempts: number 8 | private minSeconds: number 9 | private maxSeconds: number 10 | 11 | constructor(maxAttempts: number, minSeconds: number, maxSeconds: number) { 12 | if (maxAttempts < 1) { 13 | throw new Error('max attempts should be greater than or equal to 1') 14 | } 15 | 16 | this.maxAttempts = maxAttempts 17 | this.minSeconds = Math.floor(minSeconds) 18 | this.maxSeconds = Math.floor(maxSeconds) 19 | if (this.minSeconds > this.maxSeconds) { 20 | throw new Error('min seconds should be less than or equal to max seconds') 21 | } 22 | } 23 | 24 | async execute( 25 | action: () => Promise, 26 | isRetryable?: (e: Error) => boolean 27 | ): Promise { 28 | let attempt = 1 29 | while (attempt < this.maxAttempts) { 30 | // Try 31 | try { 32 | return await action() 33 | } catch (err) { 34 | if (isRetryable && !isRetryable(err)) { 35 | throw err 36 | } 37 | 38 | core.info(err.message) 39 | } 40 | 41 | // Sleep 42 | const seconds = this.getSleepAmount() 43 | core.info(`Waiting ${seconds} seconds before trying again`) 44 | await this.sleep(seconds) 45 | attempt++ 46 | } 47 | 48 | // Last attempt 49 | return await action() 50 | } 51 | 52 | private getSleepAmount(): number { 53 | return ( 54 | Math.floor(Math.random() * (this.maxSeconds - this.minSeconds + 1)) + 55 | this.minSeconds 56 | ) 57 | } 58 | 59 | private async sleep(seconds: number): Promise { 60 | return new Promise(resolve => setTimeout(resolve, seconds * 1000)) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | Thank you 🙇‍♀ for wanting to create an issue in this repository. Before you do, please ensure you are filing the issue in the right place. Issues should only be opened on if the issue **relates to code in this repository**. 11 | 12 | * If you have found a security issue [please submit it here](https://hackerone.com/github) 13 | * If you have questions about writing workflows or action files, then please [visit the GitHub Community Forum's Actions Board](https://github.community/t5/GitHub-Actions/bd-p/actions) 14 | * If you are having an issue or question about GitHub Actions then please [contact customer support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-github-actions#contacting-support) 15 | 16 | If your issue is relevant to this repository, please include the information below: 17 | 18 | **Describe the bug** 19 | A clear and concise description of what the bug is. 20 | 21 | **To Reproduce** 22 | Steps to reproduce the behavior: 23 | 1. Go to '...' 24 | 2. Click on '....' 25 | 3. Scroll down to '....' 26 | 4. See error 27 | 28 | **Expected behavior** 29 | A clear and concise description of what you expected to happen. 30 | 31 | **Screenshots** 32 | If applicable, add screenshots to help explain your problem. 33 | 34 | **Desktop (please complete the following information):** 35 | - OS: [e.g. iOS] 36 | - Browser [e.g. chrome, safari] 37 | - Version [e.g. 22] 38 | 39 | **Smartphone (please complete the following information):** 40 | - Device: [e.g. iPhone6] 41 | - OS: [e.g. iOS8.1] 42 | - Browser [e.g. stock browser, safari] 43 | - Version [e.g. 22] 44 | 45 | **Additional context** 46 | Add any other context about the problem here. 47 | -------------------------------------------------------------------------------- /docs/container-action.md: -------------------------------------------------------------------------------- 1 | # Creating a Docker Action 2 | 3 | The [container-template](https://github.com/actions/container-template) repo contains the base files to create a Docker action. 4 | 5 | # Create a Repo from the Template 6 | 7 | Navigate to https://github.com/actions/container-template 8 | 9 | Click on `Use this template` to create the repo for your action. 10 | 11 | ![template](assets/node12-template.png) 12 | 13 | Complete creating your repo and clone the repo. 14 | 15 | > NOTE: The location of the repo will be how users will reference your action in their workflow file with the using keyword. 16 | 17 | e.g. To use https://github.com/actions/setup-node, users will author: 18 | 19 | ```yaml 20 | steps: 21 | using: actions/setup-node@v5 22 | ``` 23 | 24 | # Define Metadata 25 | 26 | Your action has a name and a description. Update the author. 27 | 28 | Create inputs that your unit of work will need. These will be what workflow authors set with the `with:` keyword. 29 | 30 | ```yaml 31 | name: 'My Container Action' 32 | description: 'Get started with Container actions' 33 | author: 'GitHub' 34 | inputs: 35 | myInput: 36 | description: 'Input to use' 37 | default: 'world' 38 | runs: 39 | using: 'docker' 40 | image: 'Dockerfile' 41 | args: 42 | - ${{ inputs.myInput }} 43 | ``` 44 | 45 | It will be run with docker and the input is mapped into the args 46 | 47 | # Change Code 48 | 49 | The entry point is in entrypoint.sh 50 | 51 | ```bash 52 | #!/bin/sh -l 53 | 54 | echo "hello $1" 55 | ``` 56 | 57 | # Publish 58 | 59 | Simply push your action to publish. 60 | 61 | ```bash 62 | $ git push 63 | ``` 64 | 65 | The runner will download the action and build the docker container on the fly at runtime. 66 | 67 | > Consider versioning your actions with tags. See [versioning](/docs/action-versioning.md) 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /docs/action-types.md: -------------------------------------------------------------------------------- 1 | # Action Types 2 | 3 | There are two types of actions. JavaScript and Docker actions. 4 | 5 | - **JavaScript Actions**: JavaScript actions run on the host machine. The unit of work is decoupled from the environment. 6 | - **Docker Actions**: A container action is a container which carries both the unit of work along with the environment and its dependencies packaged up as a container. 7 | 8 | Both have access to the workspace and the github event payload and context. 9 | 10 | ## Why would I choose a Docker action? 11 | 12 | Docker actions carry both the unit of work and the environment. 13 | 14 | This creates a more consistent and reliable unit of work where the consumer of the action does not need to worry about the toolsets and its dependencies. 15 | 16 | Docker actions are currently limited to Linux only. 17 | 18 | ## Why would I choose a host action? 19 | 20 | JavaScript actions decouple the unit of work from the environment and run directly on the host machine or VM. 21 | 22 | Consider a simple example of testing a node lib on node 8, 10 and running a custom action. Each job will setup a node version on the host and custom-action will run its unit of work on each environment (node8+ubuntu16, node8+windows-2019, etc.) 23 | 24 | ```yaml 25 | on: push 26 | 27 | jobs: 28 | build: 29 | strategy: 30 | matrix: 31 | node: [8.x, 10.x] 32 | os: [ubuntu-16.04, windows-2019] 33 | runs-on: ${{matrix.os}} 34 | actions: 35 | - uses: actions/setup-node@v5 36 | with: 37 | version: ${{matrix.node}} 38 | - run: | 39 | npm install 40 | - run: | 41 | npm test 42 | - uses: actions/custom-action@v1 43 | ``` 44 | 45 | JavaScript actions work on any environment that host action runtime is supported on which is currently node 12. However, a host action that runs a toolset expects the environment that it's running on to have that toolset in its PATH or using a setup-* action to acquire it on demand. 46 | -------------------------------------------------------------------------------- /packages/artifact/docs/generated/interfaces/UploadArtifactOptions.md: -------------------------------------------------------------------------------- 1 | [@actions/artifact](../README.md) / UploadArtifactOptions 2 | 3 | # Interface: UploadArtifactOptions 4 | 5 | Options for uploading an artifact 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [compressionLevel](UploadArtifactOptions.md#compressionlevel) 12 | - [retentionDays](UploadArtifactOptions.md#retentiondays) 13 | 14 | ## Properties 15 | 16 | ### compressionLevel 17 | 18 | • `Optional` **compressionLevel**: `number` 19 | 20 | The level of compression for Zlib to be applied to the artifact archive. 21 | The value can range from 0 to 9: 22 | - 0: No compression 23 | - 1: Best speed 24 | - 6: Default compression (same as GNU Gzip) 25 | - 9: Best compression 26 | Higher levels will result in better compression, but will take longer to complete. 27 | For large files that are not easily compressed, a value of 0 is recommended for significantly faster uploads. 28 | 29 | #### Defined in 30 | 31 | [src/internal/shared/interfaces.ts:52](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L52) 32 | 33 | ___ 34 | 35 | ### retentionDays 36 | 37 | • `Optional` **retentionDays**: `number` 38 | 39 | Duration after which artifact will expire in days. 40 | 41 | By default artifact expires after 90 days: 42 | https://docs.github.com/en/actions/configuring-and-managing-workflows/persisting-workflow-data-using-artifacts#downloading-and-deleting-artifacts-after-a-workflow-run-is-complete 43 | 44 | Use this option to override the default expiry. 45 | 46 | Min value: 1 47 | Max value: 90 unless changed by repository setting 48 | 49 | If this is set to a greater value than the retention settings allowed, the retention on artifacts 50 | will be reduced to match the max value allowed on server, and the upload process will continue. An 51 | input of 0 assumes default retention setting. 52 | 53 | #### Defined in 54 | 55 | [src/internal/shared/interfaces.ts:41](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L41) 56 | -------------------------------------------------------------------------------- /docs/assets/action-releases.drawio: -------------------------------------------------------------------------------- 1 | 7VvbctsqFP0aP9YjkISkx9hJcx7amZ7mTNs8dYiEZVpZeBC+9esPyMi64UsdW3Ymdh4Mm5tgrbU3AqdnDyfLR46n488sIkkPWtGyZ9/3IASWi+SXsqzWFhf5a0PMaaQrlYYn+ocULbV1RiOS1SoKxhJBp3VjyNKUhKJmw5yzRb3aiCX1Uac4Ji3DU4iTwtp3S/t3GomxtgMUlAX/EBqP9eA+1FN+weHvmLNZqkfsQXuUf9bFE1z0paeajXHEFhWT/dCzh5wxsU5NlkOSqNUtFq5oJ1bF0/bswVhMEpkBMpkXf9zSGBzSWE6Ok1RUh9vW38h6CcMgsC2HoJGLPwSt/kkkV1ZnU5bKr0E443MS6RErg2cCc6HJIJlgD0ga3Sk0ZT5McJbRcG38SJOijcxVW2SCs99kyBLG8+FtK/9sSgos1bRHLN2MBvOuBF/92PQrM88yY0kq6Oy94re1ya2K3JKKH5V0pZXMlY1UpmjTXucCGTbjITEvLtRKwDwmwlxF96IWvdKtxu6RsAmRTy4rcJJgQed1EWCtmnhTb9P0C6PyQaGlFe4UQtX69jyr3sV6GrpVyR+ZqDxGacpZdRjDADo1xfaxaSdnTNjvRH7Dsgqxnquk28Kyoxhjd0MH5NfpgNwGHdaUPQsd1gPNcTIrHFqLHkki44ZixWJMBXma4ny5FjJ01cmRe+2cNTn4hWMGJr+C8k+LI06dI7bqCCc0TpULk8gRripIwlW6GrnqbxfEc8IFWe5EUJfCBhJuIcxFGcSKKuNq+LK2Y15D62+gaUeqa1PqHi99bEQ4Sqtov3eH3cjZDRoksjuTM2zJud/vX1TR4MKKdrzrUbR9Xc720tB4Th0aH14OGucGTQ2apgu7ZBx0bnHwL+Kgsz8Oom7ioGdfLA6im5yrckbO5eQc/+T3377+Gz/Mp9/++5SxT+Hwg/G1AyVy1MGLTMR5guM0lM92V5TIgTaF8htPFE7pS6a+JFMJzkgm68zbZzQSs6lKhjhJ2Ezsh3xKOJUTVbAUjb6UpsGUZVRQlsJcxcA/kc9FjUMBpw0SgF2idNuv1AACln25DYsRoFtk3BUZd3F6V3js6NTHQ6DvAdeDvufK2OgFdW45xtI9B4Tto6XGRg6hs8Vg43L7+x19yAkWZL2eyon3lKoGOUbxVvdvgT7o25u+eCUw1Hvf2UnnsULCeBJXFDjNWAHasQJ1GtGtW7DYhdDlt1xtrr+xYHG80y/IeW6X7lsNbwsbaB7qtv3GqRUC3bptELTUPMGZEk2TQ1IOokmVqkI1r6pK06aWGpW4qHShd7pgQqMo2eYt6i4ip5d+qIbyFVgn2f017uyQZdieewZFg2A7jV6laNh2ubvfg94PVm5jo24f+CrV1OvpsDrkoq28rtfLHuFsbPLGBy/ZXodXWRDXsB6F7ZV+ETSu1bymOzvULwLg7enpdI4xcFf8bjX8NcOfvybsz+Pz/XffcMFysv2s1bfe3H7WLvezLXUaCLn93rVx9uEEBsGeaz9rBNp09nEKoOvnVvAkqLddydtA3XP9K0PddA10HtTBu0U9gAdovWBGJ6i753PqoA/frVNH1rU59fa10ASnqxyoZYmt3D1bhl9NXOPu+fUgOY5bA8kFBpDsM22VjSB5LZCGcs6YpgoXMVbSk5tRkqlt4QT/kgudL0Em+XulmJ0iVDbPcQ3HSYHbIUqG874RVSKi6QamjPA5DWkay7S+zoMWG6mJczKnbJa9JwiDxqtLYFKa6UjwCAxltvwJ/Prdp/xPA/vhfw== -------------------------------------------------------------------------------- /packages/http-client/README.md: -------------------------------------------------------------------------------- 1 | # `@actions/http-client` 2 | 3 | A lightweight HTTP client optimized for building actions. 4 | 5 | ## Features 6 | 7 | - HTTP client with TypeScript generics and async/await/Promises 8 | - Typings included! 9 | - [Proxy support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners#using-a-proxy-server-with-self-hosted-runners) just works with actions and the runner 10 | - Targets ES2019 (runner runs actions with node 24+). Only supported on node 20+. 11 | - Basic, Bearer and PAT Support out of the box. Extensible handlers for others. 12 | - Redirects supported 13 | 14 | Features and releases [here](./RELEASES.md) 15 | 16 | ## Install 17 | 18 | ``` 19 | npm install @actions/http-client --save 20 | ``` 21 | 22 | ## Samples 23 | 24 | See the [tests](./__tests__) for detailed examples. 25 | 26 | ## Errors 27 | 28 | ### HTTP 29 | 30 | The HTTP client does not throw unless truly exceptional. 31 | 32 | * A request that successfully executes resulting in a 404, 500 etc... will return a response object with a status code and a body. 33 | * Redirects (3xx) will be followed by default. 34 | 35 | See the [tests](./__tests__) for detailed examples. 36 | 37 | ## Debugging 38 | 39 | To enable detailed console logging of all HTTP requests and responses, set the NODE_DEBUG environment varible: 40 | 41 | ```shell 42 | export NODE_DEBUG=http 43 | ``` 44 | 45 | ## Node support 46 | 47 | The http-client is built using Node 24. It may work on previous node LTS versions but it's tested and officially supported on Node 20+. 48 | 49 | ## Support and Versioning 50 | 51 | We follow semver and will hold compatibility between major versions and increment the minor version with new features and capabilities (while holding compat). 52 | 53 | ## Contributing 54 | 55 | We welcome PRs. Please create an issue and if applicable, a design before proceeding with code. 56 | 57 | once: 58 | 59 | ``` 60 | npm install 61 | ``` 62 | 63 | To build: 64 | 65 | ``` 66 | npm run build 67 | ``` 68 | 69 | To run all tests: 70 | 71 | ``` 72 | npm test 73 | ``` 74 | -------------------------------------------------------------------------------- /packages/artifact/__tests__/path-and-artifact-name-validation.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | validateArtifactName, 3 | validateFilePath 4 | } from '../src/internal/upload/path-and-artifact-name-validation' 5 | 6 | import {noopLogs} from './common' 7 | 8 | describe('Path and artifact name validation', () => { 9 | beforeAll(() => { 10 | noopLogs() 11 | }) 12 | 13 | it('Check Artifact Name for any invalid characters', () => { 14 | const invalidNames = [ 15 | 'my\\artifact', 16 | 'my/artifact', 17 | 'my"artifact', 18 | 'my:artifact', 19 | 'myartifact', 21 | 'my|artifact', 22 | 'my*artifact', 23 | 'my?artifact', 24 | '' 25 | ] 26 | for (const invalidName of invalidNames) { 27 | expect(() => { 28 | validateArtifactName(invalidName) 29 | }).toThrow() 30 | } 31 | 32 | const validNames = [ 33 | 'my-normal-artifact', 34 | 'myNormalArtifact', 35 | 'm¥ñðrmålÄr†ï£å¢†' 36 | ] 37 | for (const validName of validNames) { 38 | expect(() => { 39 | validateArtifactName(validName) 40 | }).not.toThrow() 41 | } 42 | }) 43 | 44 | it('Check Artifact File Path for any invalid characters', () => { 45 | const invalidNames = [ 46 | 'some/invalid"artifact/path', 47 | 'some/invalid:artifact/path', 48 | 'some/invalidartifact/path', 50 | 'some/invalid|artifact/path', 51 | 'some/invalid*artifact/path', 52 | 'some/invalid?artifact/path', 53 | 'some/invalid\rartifact/path', 54 | 'some/invalid\nartifact/path', 55 | 'some/invalid\r\nartifact/path', 56 | '' 57 | ] 58 | for (const invalidName of invalidNames) { 59 | expect(() => { 60 | validateFilePath(invalidName) 61 | }).toThrow() 62 | } 63 | 64 | const validNames = [ 65 | 'my/perfectly-normal/artifact-path', 66 | 'my/perfectly\\Normal/Artifact-path', 67 | 'm¥/ñðrmål/Är†ï£å¢†' 68 | ] 69 | for (const validName of validNames) { 70 | expect(() => { 71 | validateFilePath(validName) 72 | }).not.toThrow() 73 | } 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /packages/core/src/platform.ts: -------------------------------------------------------------------------------- 1 | import os from 'os' 2 | import * as exec from '@actions/exec' 3 | 4 | const getWindowsInfo = async (): Promise<{name: string; version: string}> => { 5 | const {stdout: version} = await exec.getExecOutput( 6 | 'powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Version"', 7 | undefined, 8 | { 9 | silent: true 10 | } 11 | ) 12 | 13 | const {stdout: name} = await exec.getExecOutput( 14 | 'powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Caption"', 15 | undefined, 16 | { 17 | silent: true 18 | } 19 | ) 20 | 21 | return { 22 | name: name.trim(), 23 | version: version.trim() 24 | } 25 | } 26 | 27 | const getMacOsInfo = async (): Promise<{ 28 | name: string 29 | version: string 30 | }> => { 31 | const {stdout} = await exec.getExecOutput('sw_vers', undefined, { 32 | silent: true 33 | }) 34 | 35 | const version = stdout.match(/ProductVersion:\s*(.+)/)?.[1] ?? '' 36 | const name = stdout.match(/ProductName:\s*(.+)/)?.[1] ?? '' 37 | 38 | return { 39 | name, 40 | version 41 | } 42 | } 43 | 44 | const getLinuxInfo = async (): Promise<{ 45 | name: string 46 | version: string 47 | }> => { 48 | const {stdout} = await exec.getExecOutput('lsb_release', ['-i', '-r', '-s'], { 49 | silent: true 50 | }) 51 | 52 | const [name, version] = stdout.trim().split('\n') 53 | 54 | return { 55 | name, 56 | version 57 | } 58 | } 59 | 60 | export const platform = os.platform() 61 | export const arch = os.arch() 62 | export const isWindows = platform === 'win32' 63 | export const isMacOS = platform === 'darwin' 64 | export const isLinux = platform === 'linux' 65 | 66 | export async function getDetails(): Promise<{ 67 | name: string 68 | platform: string 69 | arch: string 70 | version: string 71 | isWindows: boolean 72 | isMacOS: boolean 73 | isLinux: boolean 74 | }> { 75 | return { 76 | ...(await (isWindows 77 | ? getWindowsInfo() 78 | : isMacOS 79 | ? getMacOsInfo() 80 | : getLinuxInfo())), 81 | platform, 82 | arch, 83 | isWindows, 84 | isMacOS, 85 | isLinux 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /packages/artifact/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@actions/artifact", 3 | "version": "5.0.1", 4 | "preview": true, 5 | "description": "Actions artifact lib", 6 | "keywords": [ 7 | "github", 8 | "actions", 9 | "artifact" 10 | ], 11 | "homepage": "https://github.com/actions/toolkit/tree/main/packages/artifact", 12 | "license": "MIT", 13 | "main": "lib/artifact.js", 14 | "types": "lib/artifact.d.ts", 15 | "directories": { 16 | "lib": "lib", 17 | "test": "__tests__" 18 | }, 19 | "files": [ 20 | "lib", 21 | "!.DS_Store" 22 | ], 23 | "publishConfig": { 24 | "access": "public" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/actions/toolkit.git", 29 | "directory": "packages/artifact" 30 | }, 31 | "scripts": { 32 | "audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json", 33 | "test": "cd ../../ && npm run test ./packages/artifact", 34 | "bootstrap": "cd ../../ && npm run bootstrap", 35 | "tsc-run": "tsc", 36 | "tsc": "npm run bootstrap && npm run tsc-run", 37 | "gen:docs": "typedoc --plugin typedoc-plugin-markdown --out docs/generated src/artifact.ts --githubPages false --readme none" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/actions/toolkit/issues" 41 | }, 42 | "dependencies": { 43 | "@actions/core": "^2.0.0", 44 | "@actions/github": "^6.0.1", 45 | "@actions/http-client": "^3.0.0", 46 | "@azure/storage-blob": "^12.29.1", 47 | "@octokit/core": "^5.2.1", 48 | "@octokit/plugin-request-log": "^1.0.4", 49 | "@octokit/plugin-retry": "^3.0.9", 50 | "@octokit/request": "^8.4.1", 51 | "@octokit/request-error": "^5.1.1", 52 | "@protobuf-ts/plugin": "^2.2.3-alpha.1", 53 | "archiver": "^7.0.1", 54 | "jwt-decode": "^3.1.2", 55 | "unzip-stream": "^0.3.1" 56 | }, 57 | "devDependencies": { 58 | "@types/archiver": "^5.3.2", 59 | "@types/unzip-stream": "^0.3.4", 60 | "typedoc": "^0.28.13", 61 | "typedoc-plugin-markdown": "^3.17.1", 62 | "typescript": "^5.2.2" 63 | }, 64 | "overrides": { 65 | "uri-js": "npm:uri-js-replace@^1.0.1", 66 | "node-fetch": "^3.3.2" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/artifact/__tests__/retention.test.ts: -------------------------------------------------------------------------------- 1 | import {Timestamp} from '../src/generated' 2 | import * as retention from '../src/internal/upload/retention' 3 | 4 | describe('retention', () => { 5 | beforeEach(() => { 6 | delete process.env['GITHUB_RETENTION_DAYS'] 7 | }) 8 | it('should return the inputted retention days if it is less than the max retention days', () => { 9 | // setup 10 | const mockDate = new Date('2020-01-01') 11 | jest.useFakeTimers().setSystemTime(mockDate) 12 | process.env['GITHUB_RETENTION_DAYS'] = '90' 13 | 14 | const exp = retention.getExpiration(30) 15 | 16 | expect(exp).toBeDefined() 17 | if (exp) { 18 | const expDate = Timestamp.toDate(exp) 19 | const expected = new Date() 20 | expected.setDate(expected.getDate() + 30) 21 | 22 | expect(expDate).toEqual(expected) 23 | } 24 | }) 25 | 26 | it('should return the max retention days if the inputted retention days is greater than the max retention days', () => { 27 | // setup 28 | const mockDate = new Date('2020-01-01') 29 | jest.useFakeTimers().setSystemTime(mockDate) 30 | process.env['GITHUB_RETENTION_DAYS'] = '90' 31 | 32 | const exp = retention.getExpiration(120) 33 | 34 | expect(exp).toBeDefined() 35 | if (exp) { 36 | const expDate = Timestamp.toDate(exp) // we check whether exp is defined above 37 | const expected = new Date() 38 | expected.setDate(expected.getDate() + 90) 39 | 40 | expect(expDate).toEqual(expected) 41 | } 42 | }) 43 | 44 | it('should return undefined if the inputted retention days is undefined', () => { 45 | const exp = retention.getExpiration() 46 | expect(exp).toBeUndefined() 47 | }) 48 | 49 | it('should return the inputted retention days if there is no max retention days', () => { 50 | // setup 51 | const mockDate = new Date('2020-01-01') 52 | jest.useFakeTimers().setSystemTime(mockDate) 53 | 54 | const exp = retention.getExpiration(30) 55 | 56 | expect(exp).toBeDefined() 57 | if (exp) { 58 | const expDate = Timestamp.toDate(exp) // we check whether exp is defined above 59 | const expected = new Date() 60 | expected.setDate(expected.getDate() + 30) 61 | 62 | expect(expDate).toEqual(expected) 63 | } 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /packages/exec/src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import * as stream from 'stream' 2 | /** 3 | * Interface for exec options 4 | */ 5 | export interface ExecOptions { 6 | /** optional working directory. defaults to current */ 7 | cwd?: string 8 | 9 | /** optional envvar dictionary. defaults to current process's env */ 10 | env?: {[key: string]: string} 11 | 12 | /** optional. defaults to false */ 13 | silent?: boolean 14 | 15 | /** optional out stream to use. Defaults to process.stdout */ 16 | outStream?: stream.Writable 17 | 18 | /** optional err stream to use. Defaults to process.stderr */ 19 | errStream?: stream.Writable 20 | 21 | /** optional. whether to skip quoting/escaping arguments if needed. defaults to false. */ 22 | windowsVerbatimArguments?: boolean 23 | 24 | /** optional. whether to fail if output to stderr. defaults to false */ 25 | failOnStdErr?: boolean 26 | 27 | /** optional. defaults to failing on non zero. ignore will not fail leaving it up to the caller */ 28 | ignoreReturnCode?: boolean 29 | 30 | /** optional. How long in ms to wait for STDIO streams to close after the exit event of the process before terminating. defaults to 10000 */ 31 | delay?: number 32 | 33 | /** optional. input to write to the process on STDIN. */ 34 | input?: Buffer 35 | 36 | /** optional. Listeners for output. Callback functions that will be called on these events */ 37 | listeners?: ExecListeners 38 | } 39 | 40 | /** 41 | * Interface for the output of getExecOutput() 42 | */ 43 | export interface ExecOutput { 44 | /**The exit code of the process */ 45 | exitCode: number 46 | 47 | /**The entire stdout of the process as a string */ 48 | stdout: string 49 | 50 | /**The entire stderr of the process as a string */ 51 | stderr: string 52 | } 53 | 54 | /** 55 | * The user defined listeners for an exec call 56 | */ 57 | export interface ExecListeners { 58 | /** A call back for each buffer of stdout */ 59 | stdout?: (data: Buffer) => void 60 | 61 | /** A call back for each buffer of stderr */ 62 | stderr?: (data: Buffer) => void 63 | 64 | /** A call back for each line of stdout */ 65 | stdline?: (data: string) => void 66 | 67 | /** A call back for each line of stderr */ 68 | errline?: (data: string) => void 69 | 70 | /** A call back for each debug log */ 71 | debug?: (data: string) => void 72 | } 73 | -------------------------------------------------------------------------------- /docs/action-debugging.md: -------------------------------------------------------------------------------- 1 | # Debugging 2 | If the job logs do not provide enough detail on why a job may be failing, some other options exist to assist with troubleshooting. 3 | 4 | ## Step Debug Logs 5 | This is the primary way for customers to debug job failures caused by failed steps. 6 | 7 | Step debug logs increase the verbosity of a job's logs during and after a job's execution to assist with troubleshooting. 8 | 9 | Additional log events with the prefix `::debug::` will now also appear in the job's logs, these log events are provided by the Action's author and the runner process. 10 | 11 | ### How to Access Step Debug Logs 12 | This flag can be enabled by [setting the secret](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets#creating-encrypted-secrets) `ACTIONS_STEP_DEBUG` to `true`. 13 | 14 | All actions ran while this secret is enabled will show debug events in the [Downloaded Logs](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/managing-a-workflow-run#downloading-logs) and [Web Logs](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/managing-a-workflow-run#viewing-logs-to-diagnose-failures). 15 | 16 | ## Runner Diagnostic Logs 17 | Runner Diagnostic Logs provide additional log files detailing how the Runner is executing an action. 18 | 19 | You need the runner diagnostic logs only if you think there is an infrastructure problem with GitHub Actions and you want the product team to check the logs. 20 | 21 | Each file contains different logging information that corresponds to that process: 22 | * The Runner process coordinates setting up workers to execute jobs. 23 | * The Worker process executes the job. 24 | 25 | These files contain the prefix `Runner_` or `Worker_` to indicate the log source. 26 | 27 | ### How to Access Runner Diagnostic Logs 28 | These log files are enabled by [setting the secret](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets#creating-encrypted-secrets) `ACTIONS_RUNNER_DEBUG` to `true`. 29 | 30 | All actions ran while this secret is enabled contain additional diagnostic log files in the `runner-diagnostic-logs` folder of the [log archive](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/managing-a-workflow-run#downloading-logs). 31 | 32 | -------------------------------------------------------------------------------- /packages/glob/src/internal-pattern-helper.ts: -------------------------------------------------------------------------------- 1 | import * as pathHelper from './internal-path-helper' 2 | import {MatchKind} from './internal-match-kind' 3 | import {Pattern} from './internal-pattern' 4 | 5 | const IS_WINDOWS = process.platform === 'win32' 6 | 7 | /** 8 | * Given an array of patterns, returns an array of paths to search. 9 | * Duplicates and paths under other included paths are filtered out. 10 | */ 11 | export function getSearchPaths(patterns: Pattern[]): string[] { 12 | // Ignore negate patterns 13 | patterns = patterns.filter(x => !x.negate) 14 | 15 | // Create a map of all search paths 16 | const searchPathMap: {[key: string]: string} = {} 17 | for (const pattern of patterns) { 18 | const key = IS_WINDOWS 19 | ? pattern.searchPath.toUpperCase() 20 | : pattern.searchPath 21 | searchPathMap[key] = 'candidate' 22 | } 23 | 24 | const result: string[] = [] 25 | 26 | for (const pattern of patterns) { 27 | // Check if already included 28 | const key = IS_WINDOWS 29 | ? pattern.searchPath.toUpperCase() 30 | : pattern.searchPath 31 | if (searchPathMap[key] === 'included') { 32 | continue 33 | } 34 | 35 | // Check for an ancestor search path 36 | let foundAncestor = false 37 | let tempKey = key 38 | let parent = pathHelper.dirname(tempKey) 39 | while (parent !== tempKey) { 40 | if (searchPathMap[parent]) { 41 | foundAncestor = true 42 | break 43 | } 44 | 45 | tempKey = parent 46 | parent = pathHelper.dirname(tempKey) 47 | } 48 | 49 | // Include the search pattern in the result 50 | if (!foundAncestor) { 51 | result.push(pattern.searchPath) 52 | searchPathMap[key] = 'included' 53 | } 54 | } 55 | 56 | return result 57 | } 58 | 59 | /** 60 | * Matches the patterns against the path 61 | */ 62 | export function match(patterns: Pattern[], itemPath: string): MatchKind { 63 | let result: MatchKind = MatchKind.None 64 | 65 | for (const pattern of patterns) { 66 | if (pattern.negate) { 67 | result &= ~pattern.match(itemPath) 68 | } else { 69 | result |= pattern.match(itemPath) 70 | } 71 | } 72 | 73 | return result 74 | } 75 | 76 | /** 77 | * Checks whether to descend further into the directory 78 | */ 79 | export function partialMatch(patterns: Pattern[], itemPath: string): boolean { 80 | return patterns.some(x => !x.negate && x.partialMatch(itemPath)) 81 | } 82 | -------------------------------------------------------------------------------- /docs/specs/github-package.md: -------------------------------------------------------------------------------- 1 | # Github Package 2 | 3 | In order to support using actions to interact with GitHub, I propose adding a `github` package to the toolkit. 4 | 5 | Its main purpose will be to provide a hydrated GitHub context/Octokit client with some convenience functions. It is largely pulled from the GitHub utilities provided in https://github.com/JasonEtco/actions-toolkit, though it has been condensed. 6 | 7 | ### Spec 8 | 9 | ##### interfaces.ts 10 | 11 | ```ts 12 | /* 13 | * Interfaces 14 | */ 15 | 16 | export interface PayloadRepository { 17 | [key: string]: any 18 | full_name?: string 19 | name: string 20 | owner: { 21 | [key: string]: any 22 | login: string 23 | name?: string 24 | } 25 | html_url?: string 26 | } 27 | 28 | export interface WebhookPayloadWithRepository { 29 | [key: string]: any 30 | repository?: PayloadRepository 31 | issue?: { 32 | [key: string]: any 33 | number: number 34 | html_url?: string 35 | body?: string 36 | } 37 | pull_request?: { 38 | [key: string]: any 39 | number: number 40 | html_url?: string 41 | body?: string 42 | } 43 | sender?: { 44 | [key: string]: any 45 | type: string 46 | } 47 | action?: string 48 | installation?: { 49 | id: number 50 | [key: string]: any 51 | } 52 | } 53 | ``` 54 | 55 | ##### context.ts 56 | 57 | Contains a GitHub context 58 | 59 | ```ts 60 | export class Context { 61 | /** 62 | * Webhook payload object that triggered the workflow 63 | */ 64 | public payload: WebhookPayloadWithRepository 65 | 66 | /** 67 | * Name of the event that triggered the workflow 68 | */ 69 | public event: string 70 | public sha: string 71 | public ref: string 72 | public workflow: string 73 | public action: string 74 | public actor: string 75 | 76 | /** 77 | * Hydrate the context from the environment 78 | */ 79 | constructor () 80 | 81 | public get issue () 82 | 83 | public get repo () 84 | } 85 | 86 | ``` 87 | 88 | ##### github.ts 89 | 90 | Contains a hydrated Octokit client 91 | 92 | ```ts 93 | export class GithubClient extends Octokit { 94 | // For making GraphQL requests 95 | public graphql: (query: string, variables?: Variables) => Promise 96 | 97 | // Calls super and initializes graphql 98 | constructor (token: string) 99 | } 100 | ``` 101 | -------------------------------------------------------------------------------- /packages/cache/__tests__/uploadUtils.test.ts: -------------------------------------------------------------------------------- 1 | import * as uploadUtils from '../src/internal/uploadUtils' 2 | import {TransferProgressEvent} from '@azure/core-rest-pipeline' 3 | 4 | test('upload progress tracked correctly', () => { 5 | const progress = new uploadUtils.UploadProgress(1000) 6 | 7 | expect(progress.contentLength).toBe(1000) 8 | expect(progress.sentBytes).toBe(0) 9 | expect(progress.displayedComplete).toBe(false) 10 | expect(progress.timeoutHandle).toBeUndefined() 11 | expect(progress.getTransferredBytes()).toBe(0) 12 | expect(progress.isDone()).toBe(false) 13 | 14 | progress.onProgress()({loadedBytes: 0} as TransferProgressEvent) 15 | 16 | expect(progress.contentLength).toBe(1000) 17 | expect(progress.sentBytes).toBe(0) 18 | expect(progress.displayedComplete).toBe(false) 19 | expect(progress.timeoutHandle).toBeUndefined() 20 | expect(progress.getTransferredBytes()).toBe(0) 21 | expect(progress.isDone()).toBe(false) 22 | 23 | progress.onProgress()({loadedBytes: 250} as TransferProgressEvent) 24 | 25 | expect(progress.contentLength).toBe(1000) 26 | expect(progress.sentBytes).toBe(250) 27 | expect(progress.displayedComplete).toBe(false) 28 | expect(progress.timeoutHandle).toBeUndefined() 29 | expect(progress.getTransferredBytes()).toBe(250) 30 | expect(progress.isDone()).toBe(false) 31 | 32 | progress.onProgress()({loadedBytes: 500} as TransferProgressEvent) 33 | 34 | expect(progress.contentLength).toBe(1000) 35 | expect(progress.sentBytes).toBe(500) 36 | expect(progress.displayedComplete).toBe(false) 37 | expect(progress.timeoutHandle).toBeUndefined() 38 | expect(progress.getTransferredBytes()).toBe(500) 39 | expect(progress.isDone()).toBe(false) 40 | 41 | progress.onProgress()({loadedBytes: 750} as TransferProgressEvent) 42 | 43 | expect(progress.contentLength).toBe(1000) 44 | expect(progress.sentBytes).toBe(750) 45 | expect(progress.displayedComplete).toBe(false) 46 | expect(progress.timeoutHandle).toBeUndefined() 47 | expect(progress.getTransferredBytes()).toBe(750) 48 | expect(progress.isDone()).toBe(false) 49 | 50 | progress.onProgress()({loadedBytes: 1000} as TransferProgressEvent) 51 | 52 | expect(progress.contentLength).toBe(1000) 53 | expect(progress.sentBytes).toBe(1000) 54 | expect(progress.displayedComplete).toBe(false) 55 | expect(progress.timeoutHandle).toBeUndefined() 56 | expect(progress.getTransferredBytes()).toBe(1000) 57 | expect(progress.isDone()).toBe(true) 58 | }) 59 | -------------------------------------------------------------------------------- /packages/cache/src/internal/shared/errors.ts: -------------------------------------------------------------------------------- 1 | export class FilesNotFoundError extends Error { 2 | files: string[] 3 | 4 | constructor(files: string[] = []) { 5 | let message = 'No files were found to upload' 6 | if (files.length > 0) { 7 | message += `: ${files.join(', ')}` 8 | } 9 | 10 | super(message) 11 | this.files = files 12 | this.name = 'FilesNotFoundError' 13 | } 14 | } 15 | 16 | export class InvalidResponseError extends Error { 17 | constructor(message: string) { 18 | super(message) 19 | this.name = 'InvalidResponseError' 20 | } 21 | } 22 | 23 | export class CacheNotFoundError extends Error { 24 | constructor(message = 'Cache not found') { 25 | super(message) 26 | this.name = 'CacheNotFoundError' 27 | } 28 | } 29 | 30 | export class GHESNotSupportedError extends Error { 31 | constructor( 32 | message = '@actions/cache v4.1.4+, actions/cache/save@v4+ and actions/cache/restore@v4+ are not currently supported on GHES.' 33 | ) { 34 | super(message) 35 | this.name = 'GHESNotSupportedError' 36 | } 37 | } 38 | 39 | export class NetworkError extends Error { 40 | code: string 41 | 42 | constructor(code: string) { 43 | const message = `Unable to make request: ${code}\nIf you are using self-hosted runners, please make sure your runner has access to all GitHub endpoints: https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#communication-between-self-hosted-runners-and-github` 44 | super(message) 45 | this.code = code 46 | this.name = 'NetworkError' 47 | } 48 | 49 | static isNetworkErrorCode = (code?: string): boolean => { 50 | if (!code) return false 51 | return [ 52 | 'ECONNRESET', 53 | 'ENOTFOUND', 54 | 'ETIMEDOUT', 55 | 'ECONNREFUSED', 56 | 'EHOSTUNREACH' 57 | ].includes(code) 58 | } 59 | } 60 | 61 | export class UsageError extends Error { 62 | constructor() { 63 | const message = `Cache storage quota has been hit. Unable to upload any new cache entries. Usage is recalculated every 6-12 hours.\nMore info on storage limits: https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#calculating-minute-and-storage-spending` 64 | super(message) 65 | this.name = 'UsageError' 66 | } 67 | 68 | static isUsageErrorMessage = (msg?: string): boolean => { 69 | if (!msg) return false 70 | return msg.includes('insufficient usage') 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/artifact/src/internal/shared/errors.ts: -------------------------------------------------------------------------------- 1 | export class FilesNotFoundError extends Error { 2 | files: string[] 3 | 4 | constructor(files: string[] = []) { 5 | let message = 'No files were found to upload' 6 | if (files.length > 0) { 7 | message += `: ${files.join(', ')}` 8 | } 9 | 10 | super(message) 11 | this.files = files 12 | this.name = 'FilesNotFoundError' 13 | } 14 | } 15 | 16 | export class InvalidResponseError extends Error { 17 | constructor(message: string) { 18 | super(message) 19 | this.name = 'InvalidResponseError' 20 | } 21 | } 22 | 23 | export class ArtifactNotFoundError extends Error { 24 | constructor(message = 'Artifact not found') { 25 | super(message) 26 | this.name = 'ArtifactNotFoundError' 27 | } 28 | } 29 | 30 | export class GHESNotSupportedError extends Error { 31 | constructor( 32 | message = '@actions/artifact v2.0.0+, upload-artifact@v4+ and download-artifact@v4+ are not currently supported on GHES.' 33 | ) { 34 | super(message) 35 | this.name = 'GHESNotSupportedError' 36 | } 37 | } 38 | 39 | export class NetworkError extends Error { 40 | code: string 41 | 42 | constructor(code: string) { 43 | const message = `Unable to make request: ${code}\nIf you are using self-hosted runners, please make sure your runner has access to all GitHub endpoints: https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#communication-between-self-hosted-runners-and-github` 44 | super(message) 45 | this.code = code 46 | this.name = 'NetworkError' 47 | } 48 | 49 | static isNetworkErrorCode = (code?: string): boolean => { 50 | if (!code) return false 51 | return [ 52 | 'ECONNRESET', 53 | 'ENOTFOUND', 54 | 'ETIMEDOUT', 55 | 'ECONNREFUSED', 56 | 'EHOSTUNREACH' 57 | ].includes(code) 58 | } 59 | } 60 | 61 | export class UsageError extends Error { 62 | constructor() { 63 | const message = `Artifact storage quota has been hit. Unable to upload any new artifacts. Usage is recalculated every 6-12 hours.\nMore info on storage limits: https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#calculating-minute-and-storage-spending` 64 | super(message) 65 | this.name = 'UsageError' 66 | } 67 | 68 | static isUsageErrorMessage = (msg?: string): boolean => { 69 | if (!msg) return false 70 | return msg.includes('insufficient usage') 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/tool-cache/scripts/Invoke-7zdec.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [Parameter(Mandatory = $true)] 4 | [string]$Source, 5 | 6 | [Parameter(Mandatory = $true)] 7 | [string]$Target) 8 | 9 | # This script translates the output from 7zdec into UTF8. Node has limited 10 | # built-in support for encodings. 11 | # 12 | # 7zdec uses the system default code page. The system default code page varies 13 | # depending on the locale configuration. On an en-US box, the system default code 14 | # page is Windows-1252. 15 | # 16 | # Note, on a typical en-US box, testing with the 'ç' character is a good way to 17 | # determine whether data is passed correctly between processes. This is because 18 | # the 'ç' character has a different code point across each of the common encodings 19 | # on a typical en-US box, i.e. 20 | # 1) the default console-output code page (IBM437) 21 | # 2) the system default code page (i.e. CP_ACP) (Windows-1252) 22 | # 3) UTF8 23 | 24 | $ErrorActionPreference = 'Stop' 25 | 26 | # Redefine the wrapper over STDOUT to use UTF8. Node expects UTF8 by default. 27 | $stdout = [System.Console]::OpenStandardOutput() 28 | $utf8 = New-Object System.Text.UTF8Encoding($false) # do not emit BOM 29 | $writer = New-Object System.IO.StreamWriter($stdout, $utf8) 30 | [System.Console]::SetOut($writer) 31 | 32 | # All subsequent output must be written using [System.Console]::WriteLine(). In 33 | # PowerShell 4, Write-Host and Out-Default do not consider the updated stream writer. 34 | 35 | Set-Location -LiteralPath $Target 36 | 37 | # Print the ##command. 38 | $_7zdec = Join-Path -Path "$PSScriptRoot" -ChildPath "externals/7zdec.exe" 39 | [System.Console]::WriteLine("##[command]$_7zdec x `"$Source`"") 40 | 41 | # The $OutputEncoding variable instructs PowerShell how to interpret the output 42 | # from the external command. 43 | $OutputEncoding = [System.Text.Encoding]::Default 44 | 45 | # Note, the output from 7zdec.exe needs to be iterated over. Otherwise PowerShell.exe 46 | # will launch the external command in such a way that it inherits the streams. 47 | & $_7zdec x $Source 2>&1 | 48 | ForEach-Object { 49 | if ($_ -is [System.Management.Automation.ErrorRecord]) { 50 | [System.Console]::WriteLine($_.Exception.Message) 51 | } 52 | else { 53 | [System.Console]::WriteLine($_) 54 | } 55 | } 56 | [System.Console]::WriteLine("##[debug]7zdec.exe exit code '$LASTEXITCODE'") 57 | [System.Console]::Out.Flush() 58 | if ($LASTEXITCODE -ne 0) { 59 | exit $LASTEXITCODE 60 | } -------------------------------------------------------------------------------- /packages/github/__tests__/lib.test.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import {Context} from '../src/context' 3 | 4 | /* eslint-disable @typescript-eslint/no-require-imports */ 5 | /* eslint-disable @typescript-eslint/no-var-requires */ 6 | 7 | describe('@actions/context', () => { 8 | let context: Context 9 | 10 | beforeEach(() => { 11 | process.env.GITHUB_EVENT_PATH = path.join(__dirname, 'payload.json') 12 | process.env.GITHUB_REPOSITORY = 'actions/toolkit' 13 | context = new Context() 14 | }) 15 | 16 | it('returns the payload object', () => { 17 | expect(context.payload).toEqual(require('./payload.json')) 18 | }) 19 | 20 | it('returns an empty payload if the GITHUB_EVENT_PATH environment variable is falsey', () => { 21 | delete process.env.GITHUB_EVENT_PATH 22 | 23 | context = new Context() 24 | expect(context.payload).toEqual({}) 25 | }) 26 | 27 | it('returns attributes from the GITHUB_REPOSITORY', () => { 28 | expect(context.repo).toEqual({owner: 'actions', repo: 'toolkit'}) 29 | }) 30 | 31 | it('returns attributes from the repository payload', () => { 32 | delete process.env.GITHUB_REPOSITORY 33 | 34 | context.payload.repository = { 35 | name: 'test', 36 | owner: {login: 'user'} 37 | } 38 | expect(context.repo).toEqual({owner: 'user', repo: 'test'}) 39 | }) 40 | 41 | it("return error for context.repo when repository doesn't exist", () => { 42 | delete process.env.GITHUB_REPOSITORY 43 | 44 | context.payload.repository = undefined 45 | expect(() => context.repo).toThrowErrorMatchingSnapshot() 46 | }) 47 | 48 | it('returns issue attributes from the repository', () => { 49 | expect(context.issue).toEqual({ 50 | owner: 'actions', 51 | repo: 'toolkit', 52 | number: 1 53 | }) 54 | }) 55 | 56 | it('works with pull_request payloads', () => { 57 | delete process.env.GITHUB_REPOSITORY 58 | context.payload = { 59 | pull_request: {number: 2}, 60 | repository: {owner: {login: 'user'}, name: 'test'} 61 | } 62 | expect(context.issue).toEqual({ 63 | number: 2, 64 | owner: 'user', 65 | repo: 'test' 66 | }) 67 | }) 68 | 69 | it('works with payload.number payloads', () => { 70 | delete process.env.GITHUB_REPOSITORY 71 | context.payload = { 72 | number: 2, 73 | repository: {owner: {login: 'user'}, name: 'test'} 74 | } 75 | expect(context.issue).toEqual({ 76 | number: 2, 77 | owner: 'user', 78 | repo: 'test' 79 | }) 80 | }) 81 | }) 82 | -------------------------------------------------------------------------------- /packages/http-client/src/auth.ts: -------------------------------------------------------------------------------- 1 | import * as http from 'http' 2 | import * as ifm from './interfaces' 3 | import {HttpClientResponse} from './index' 4 | 5 | export class BasicCredentialHandler implements ifm.RequestHandler { 6 | username: string 7 | password: string 8 | 9 | constructor(username: string, password: string) { 10 | this.username = username 11 | this.password = password 12 | } 13 | 14 | prepareRequest(options: http.RequestOptions): void { 15 | if (!options.headers) { 16 | throw Error('The request has no headers') 17 | } 18 | options.headers['Authorization'] = `Basic ${Buffer.from( 19 | `${this.username}:${this.password}` 20 | ).toString('base64')}` 21 | } 22 | 23 | // This handler cannot handle 401 24 | canHandleAuthentication(): boolean { 25 | return false 26 | } 27 | 28 | async handleAuthentication(): Promise { 29 | throw new Error('not implemented') 30 | } 31 | } 32 | 33 | export class BearerCredentialHandler implements ifm.RequestHandler { 34 | token: string 35 | 36 | constructor(token: string) { 37 | this.token = token 38 | } 39 | 40 | // currently implements pre-authorization 41 | // TODO: support preAuth = false where it hooks on 401 42 | prepareRequest(options: http.RequestOptions): void { 43 | if (!options.headers) { 44 | throw Error('The request has no headers') 45 | } 46 | options.headers['Authorization'] = `Bearer ${this.token}` 47 | } 48 | 49 | // This handler cannot handle 401 50 | canHandleAuthentication(): boolean { 51 | return false 52 | } 53 | 54 | async handleAuthentication(): Promise { 55 | throw new Error('not implemented') 56 | } 57 | } 58 | 59 | export class PersonalAccessTokenCredentialHandler 60 | implements ifm.RequestHandler 61 | { 62 | token: string 63 | 64 | constructor(token: string) { 65 | this.token = token 66 | } 67 | 68 | // currently implements pre-authorization 69 | // TODO: support preAuth = false where it hooks on 401 70 | prepareRequest(options: http.RequestOptions): void { 71 | if (!options.headers) { 72 | throw Error('The request has no headers') 73 | } 74 | options.headers['Authorization'] = `Basic ${Buffer.from( 75 | `PAT:${this.token}` 76 | ).toString('base64')}` 77 | } 78 | 79 | // This handler cannot handle 401 80 | canHandleAuthentication(): boolean { 81 | return false 82 | } 83 | 84 | async handleAuthentication(): Promise { 85 | throw new Error('not implemented') 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /packages/cache/src/internal/shared/util.ts: -------------------------------------------------------------------------------- 1 | import {debug, setSecret} from '@actions/core' 2 | 3 | /** 4 | * Masks the `sig` parameter in a URL and sets it as a secret. 5 | * 6 | * @param url - The URL containing the signature parameter to mask 7 | * @remarks 8 | * This function attempts to parse the provided URL and identify the 'sig' query parameter. 9 | * If found, it registers both the raw and URL-encoded signature values as secrets using 10 | * the Actions `setSecret` API, which prevents them from being displayed in logs. 11 | * 12 | * The function handles errors gracefully if URL parsing fails, logging them as debug messages. 13 | * 14 | * @example 15 | * ```typescript 16 | * // Mask a signature in an Azure SAS token URL 17 | * maskSigUrl('https://example.blob.core.windows.net/container/file.txt?sig=abc123&se=2023-01-01'); 18 | * ``` 19 | */ 20 | export function maskSigUrl(url: string): void { 21 | if (!url) return 22 | try { 23 | const parsedUrl = new URL(url) 24 | const signature = parsedUrl.searchParams.get('sig') 25 | if (signature) { 26 | setSecret(signature) 27 | setSecret(encodeURIComponent(signature)) 28 | } 29 | } catch (error) { 30 | debug( 31 | `Failed to parse URL: ${url} ${ 32 | error instanceof Error ? error.message : String(error) 33 | }` 34 | ) 35 | } 36 | } 37 | 38 | /** 39 | * Masks sensitive information in URLs containing signature parameters. 40 | * Currently supports masking 'sig' parameters in the 'signed_upload_url' 41 | * and 'signed_download_url' properties of the provided object. 42 | * 43 | * @param body - The object should contain a signature 44 | * @remarks 45 | * This function extracts URLs from the object properties and calls maskSigUrl 46 | * on each one to redact sensitive signature information. The function doesn't 47 | * modify the original object; it only marks the signatures as secrets for 48 | * logging purposes. 49 | * 50 | * @example 51 | * ```typescript 52 | * const responseBody = { 53 | * signed_upload_url: 'https://blob.core.windows.net/?sig=abc123', 54 | * signed_download_url: 'https://blob.core/windows.net/?sig=def456' 55 | * }; 56 | * maskSecretUrls(responseBody); 57 | * ``` 58 | */ 59 | export function maskSecretUrls(body: Record | null): void { 60 | if (typeof body !== 'object' || body === null) { 61 | debug('body is not an object or is null') 62 | return 63 | } 64 | if ( 65 | 'signed_upload_url' in body && 66 | typeof body.signed_upload_url === 'string' 67 | ) { 68 | maskSigUrl(body.signed_upload_url) 69 | } 70 | if ( 71 | 'signed_download_url' in body && 72 | typeof body.signed_download_url === 'string' 73 | ) { 74 | maskSigUrl(body.signed_download_url) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /packages/http-client/__tests__/auth.test.ts: -------------------------------------------------------------------------------- 1 | import * as httpm from '../lib' 2 | import * as am from '../lib/auth' 3 | 4 | describe('auth', () => { 5 | beforeEach(() => {}) 6 | 7 | afterEach(() => {}) 8 | 9 | it('does basic http get request with basic auth', async () => { 10 | const bh: am.BasicCredentialHandler = new am.BasicCredentialHandler( 11 | 'johndoe', 12 | 'password' 13 | ) 14 | const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [ 15 | bh 16 | ]) 17 | const res: httpm.HttpClientResponse = await http.get( 18 | 'https://postman-echo.com/get' 19 | ) 20 | expect(res.message.statusCode).toBe(200) 21 | const body: string = await res.readBody() 22 | const obj = JSON.parse(body) 23 | const auth: string = obj.headers.authorization 24 | const creds: string = Buffer.from( 25 | auth.substring('Basic '.length), 26 | 'base64' 27 | ).toString() 28 | expect(creds).toBe('johndoe:password') 29 | expect(obj.url).toBe('https://postman-echo.com/get') 30 | }) 31 | 32 | it('does basic http get request with pat token auth', async () => { 33 | const token = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs' 34 | const ph: am.PersonalAccessTokenCredentialHandler = 35 | new am.PersonalAccessTokenCredentialHandler(token) 36 | 37 | const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [ 38 | ph 39 | ]) 40 | const res: httpm.HttpClientResponse = await http.get( 41 | 'https://postman-echo.com/get' 42 | ) 43 | expect(res.message.statusCode).toBe(200) 44 | const body: string = await res.readBody() 45 | const obj = JSON.parse(body) 46 | const auth: string = obj.headers.authorization 47 | const creds: string = Buffer.from( 48 | auth.substring('Basic '.length), 49 | 'base64' 50 | ).toString() 51 | expect(creds).toBe(`PAT:${token}`) 52 | expect(obj.url).toBe('https://postman-echo.com/get') 53 | }) 54 | 55 | it('does basic http get request with pat token auth', async () => { 56 | const token = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs' 57 | const ph: am.BearerCredentialHandler = new am.BearerCredentialHandler(token) 58 | 59 | const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [ 60 | ph 61 | ]) 62 | const res: httpm.HttpClientResponse = await http.get( 63 | 'https://postman-echo.com/get' 64 | ) 65 | expect(res.message.statusCode).toBe(200) 66 | const body: string = await res.readBody() 67 | const obj = JSON.parse(body) 68 | const auth: string = obj.headers.authorization 69 | expect(auth).toBe(`Bearer ${token}`) 70 | expect(obj.url).toBe('https://postman-echo.com/get') 71 | }) 72 | }) 73 | -------------------------------------------------------------------------------- /packages/core/src/oidc-utils.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-extraneous-class */ 2 | import * as actions_http_client from '@actions/http-client' 3 | import {RequestOptions} from '@actions/http-client/lib/interfaces' 4 | import {HttpClient} from '@actions/http-client' 5 | import {BearerCredentialHandler} from '@actions/http-client/lib/auth' 6 | import {debug, setSecret} from './core' 7 | interface TokenResponse { 8 | value?: string 9 | } 10 | 11 | export class OidcClient { 12 | private static createHttpClient( 13 | allowRetry = true, 14 | maxRetry = 10 15 | ): actions_http_client.HttpClient { 16 | const requestOptions: RequestOptions = { 17 | allowRetries: allowRetry, 18 | maxRetries: maxRetry 19 | } 20 | 21 | return new HttpClient( 22 | 'actions/oidc-client', 23 | [new BearerCredentialHandler(OidcClient.getRequestToken())], 24 | requestOptions 25 | ) 26 | } 27 | 28 | private static getRequestToken(): string { 29 | const token = process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'] 30 | if (!token) { 31 | throw new Error( 32 | 'Unable to get ACTIONS_ID_TOKEN_REQUEST_TOKEN env variable' 33 | ) 34 | } 35 | return token 36 | } 37 | 38 | private static getIDTokenUrl(): string { 39 | const runtimeUrl = process.env['ACTIONS_ID_TOKEN_REQUEST_URL'] 40 | if (!runtimeUrl) { 41 | throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable') 42 | } 43 | return runtimeUrl 44 | } 45 | 46 | private static async getCall(id_token_url: string): Promise { 47 | const httpclient = OidcClient.createHttpClient() 48 | 49 | const res = await httpclient 50 | .getJson(id_token_url) 51 | .catch(error => { 52 | throw new Error( 53 | `Failed to get ID Token. \n 54 | Error Code : ${error.statusCode}\n 55 | Error Message: ${error.message}` 56 | ) 57 | }) 58 | 59 | const id_token = res.result?.value 60 | if (!id_token) { 61 | throw new Error('Response json body do not have ID Token field') 62 | } 63 | return id_token 64 | } 65 | 66 | static async getIDToken(audience?: string): Promise { 67 | try { 68 | // New ID Token is requested from action service 69 | let id_token_url: string = OidcClient.getIDTokenUrl() 70 | if (audience) { 71 | const encodedAudience = encodeURIComponent(audience) 72 | id_token_url = `${id_token_url}&audience=${encodedAudience}` 73 | } 74 | 75 | debug(`ID token url is ${id_token_url}`) 76 | 77 | const id_token = await OidcClient.getCall(id_token_url) 78 | setSecret(id_token) 79 | return id_token 80 | } catch (error) { 81 | throw new Error(`Error message: ${error.message}`) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /.github/workflows/releases.yml: -------------------------------------------------------------------------------- 1 | name: Publish NPM 2 | 3 | run-name: Publish NPM - ${{ github.event.inputs.package }} 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | package: 9 | type: choice 10 | required: true 11 | description: 'Which package to release' 12 | options: 13 | - artifact 14 | - attest 15 | - cache 16 | - core 17 | - exec 18 | - github 19 | - glob 20 | - http-client 21 | - io 22 | - tool-cache 23 | 24 | 25 | jobs: 26 | test: 27 | runs-on: macos-latest-large 28 | 29 | steps: 30 | - name: setup repo 31 | uses: actions/checkout@v5 32 | 33 | - name: verify package exists 34 | run: ls packages/${{ github.event.inputs.package }} 35 | 36 | - name: Set Node.js 24.x 37 | uses: actions/setup-node@v5 38 | with: 39 | node-version: 24.x 40 | 41 | - name: npm install 42 | run: npm install 43 | 44 | - name: bootstrap 45 | run: npm run bootstrap 46 | 47 | - name: build 48 | run: npm run build 49 | 50 | - name: test 51 | run: npm run test 52 | 53 | - name: pack 54 | run: npm pack 55 | working-directory: packages/${{ github.event.inputs.package }} 56 | 57 | - name: upload artifact 58 | uses: actions/upload-artifact@v4 59 | with: 60 | name: ${{ github.event.inputs.package }} 61 | path: packages/${{ github.event.inputs.package }}/*.tgz 62 | 63 | publish: 64 | runs-on: macos-latest-large 65 | needs: test 66 | environment: npm-publish 67 | permissions: 68 | contents: read 69 | id-token: write 70 | steps: 71 | 72 | - name: Set Node.js 24.x 73 | uses: actions/setup-node@v5 74 | with: 75 | node-version: 24.x 76 | 77 | - name: download artifact 78 | uses: actions/download-artifact@v4 79 | with: 80 | name: ${{ github.event.inputs.package }} 81 | 82 | - name: publish 83 | run: npm publish --provenance *.tgz 84 | 85 | - name: notify slack on failure 86 | if: failure() 87 | run: | 88 | curl -X POST -H 'Content-type: application/json' --data '{"text":":pb__failed: Failed to publish a new version of ${{ github.event.inputs.package }}"}' $SLACK_WEBHOOK 89 | env: 90 | SLACK_WEBHOOK: ${{ secrets.SLACK }} 91 | 92 | - name: notify slack on success 93 | if: success() 94 | run: | 95 | curl -X POST -H 'Content-type: application/json' --data '{"text":":dance: Successfully published a new version of ${{ github.event.inputs.package }}"}' $SLACK_WEBHOOK 96 | env: 97 | SLACK_WEBHOOK: ${{ secrets.SLACK }} 98 | 99 | -------------------------------------------------------------------------------- /packages/cache/__tests__/cache.test.ts: -------------------------------------------------------------------------------- 1 | import * as cache from '../src/cache' 2 | 3 | describe('isFeatureAvailable', () => { 4 | const originalEnv = process.env 5 | 6 | beforeEach(() => { 7 | jest.resetModules() 8 | process.env = {...originalEnv} 9 | // Clean cache-related environment variables 10 | delete process.env['ACTIONS_CACHE_URL'] 11 | delete process.env['ACTIONS_RESULTS_URL'] 12 | delete process.env['ACTIONS_CACHE_SERVICE_V2'] 13 | delete process.env['GITHUB_SERVER_URL'] 14 | }) 15 | 16 | afterAll(() => { 17 | process.env = originalEnv 18 | }) 19 | 20 | test('returns true for cache service v1 when ACTIONS_CACHE_URL is set', () => { 21 | process.env['ACTIONS_CACHE_URL'] = 'http://cache.com' 22 | expect(cache.isFeatureAvailable()).toBe(true) 23 | }) 24 | 25 | test('returns false for cache service v1 when only ACTIONS_RESULTS_URL is set', () => { 26 | process.env['ACTIONS_RESULTS_URL'] = 'http://results.com' 27 | expect(cache.isFeatureAvailable()).toBe(false) 28 | }) 29 | 30 | test('returns true for cache service v1 when both URLs are set', () => { 31 | process.env['ACTIONS_CACHE_URL'] = 'http://cache.com' 32 | process.env['ACTIONS_RESULTS_URL'] = 'http://results.com' 33 | expect(cache.isFeatureAvailable()).toBe(true) 34 | }) 35 | 36 | test('returns true for cache service v2 when ACTIONS_RESULTS_URL is set', () => { 37 | process.env['ACTIONS_CACHE_SERVICE_V2'] = 'true' 38 | process.env['ACTIONS_RESULTS_URL'] = 'http://results.com' 39 | expect(cache.isFeatureAvailable()).toBe(true) 40 | }) 41 | 42 | test('returns false for cache service v2 when only ACTIONS_CACHE_URL is set', () => { 43 | process.env['ACTIONS_CACHE_SERVICE_V2'] = 'true' 44 | process.env['ACTIONS_CACHE_URL'] = 'http://cache.com' 45 | expect(cache.isFeatureAvailable()).toBe(false) 46 | }) 47 | 48 | test('returns false when no cache URLs are set', () => { 49 | expect(cache.isFeatureAvailable()).toBe(false) 50 | }) 51 | 52 | test('returns false for cache service v2 when no URLs are set', () => { 53 | process.env['ACTIONS_CACHE_SERVICE_V2'] = 'true' 54 | expect(cache.isFeatureAvailable()).toBe(false) 55 | }) 56 | 57 | test('returns true for GHES with v1 even when v2 flag is set', () => { 58 | process.env['GITHUB_SERVER_URL'] = 'https://my-enterprise.github.com' 59 | process.env['ACTIONS_CACHE_SERVICE_V2'] = 'true' 60 | process.env['ACTIONS_CACHE_URL'] = 'http://cache.com' 61 | expect(cache.isFeatureAvailable()).toBe(true) 62 | }) 63 | 64 | test('returns false for GHES with only ACTIONS_RESULTS_URL', () => { 65 | process.env['GITHUB_SERVER_URL'] = 'https://my-enterprise.github.com' 66 | process.env['ACTIONS_RESULTS_URL'] = 'http://results.com' 67 | expect(cache.isFeatureAvailable()).toBe(false) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /packages/http-client/__tests__/keepalive.test.ts: -------------------------------------------------------------------------------- 1 | import * as httpm from '../lib' 2 | 3 | describe('basics', () => { 4 | let _http: httpm.HttpClient 5 | 6 | beforeEach(() => { 7 | _http = new httpm.HttpClient('http-client-tests', [], {keepAlive: true}) 8 | }) 9 | 10 | afterEach(() => { 11 | _http.dispose() 12 | }) 13 | 14 | it.each([true, false])('creates Agent with keepAlive %s', keepAlive => { 15 | const http = new httpm.HttpClient('http-client-tests', [], {keepAlive}) 16 | const agent = http.getAgent('https://postman-echo.com') 17 | expect(agent).toHaveProperty('keepAlive', keepAlive) 18 | }) 19 | 20 | it('does basic http get request with keepAlive true', async () => { 21 | const res: httpm.HttpClientResponse = await _http.get( 22 | 'https://postman-echo.com/get' 23 | ) 24 | expect(res.message.statusCode).toBe(200) 25 | const body: string = await res.readBody() 26 | const obj = JSON.parse(body) 27 | expect(obj.url).toBe('https://postman-echo.com/get') 28 | }) 29 | 30 | it('does basic head request with keepAlive true', async () => { 31 | const res: httpm.HttpClientResponse = await _http.head( 32 | 'https://postman-echo.com/get' 33 | ) 34 | expect(res.message.statusCode).toBe(200) 35 | }) 36 | 37 | it('does basic http delete request with keepAlive true', async () => { 38 | const res: httpm.HttpClientResponse = await _http.del( 39 | 'https://postman-echo.com/delete' 40 | ) 41 | expect(res.message.statusCode).toBe(200) 42 | const body: string = await res.readBody() 43 | JSON.parse(body) 44 | }) 45 | 46 | it('does basic http post request with keepAlive true', async () => { 47 | const b = 'Hello World!' 48 | const res: httpm.HttpClientResponse = await _http.post( 49 | 'https://postman-echo.com/post', 50 | b 51 | ) 52 | expect(res.message.statusCode).toBe(200) 53 | const body: string = await res.readBody() 54 | const obj = JSON.parse(body) 55 | expect(obj.data).toBe(b) 56 | expect(obj.url).toBe('https://postman-echo.com/post') 57 | }) 58 | 59 | it('does basic http patch request with keepAlive true', async () => { 60 | const b = 'Hello World!' 61 | const res: httpm.HttpClientResponse = await _http.patch( 62 | 'https://postman-echo.com/patch', 63 | b 64 | ) 65 | expect(res.message.statusCode).toBe(200) 66 | const body: string = await res.readBody() 67 | const obj = JSON.parse(body) 68 | expect(obj.data).toBe(b) 69 | expect(obj.url).toBe('https://postman-echo.com/patch') 70 | }) 71 | 72 | it('does basic http options request with keepAlive true', async () => { 73 | const res: httpm.HttpClientResponse = await _http.options( 74 | 'https://postman-echo.com' 75 | ) 76 | expect(res.message.statusCode).toBe(200) 77 | await res.readBody() 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /packages/http-client/src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import * as http from 'http' 2 | import * as https from 'https' 3 | import {HttpClientResponse} from './index' 4 | 5 | export interface HttpClient { 6 | options( 7 | requestUrl: string, 8 | additionalHeaders?: http.OutgoingHttpHeaders 9 | ): Promise 10 | get( 11 | requestUrl: string, 12 | additionalHeaders?: http.OutgoingHttpHeaders 13 | ): Promise 14 | del( 15 | requestUrl: string, 16 | additionalHeaders?: http.OutgoingHttpHeaders 17 | ): Promise 18 | post( 19 | requestUrl: string, 20 | data: string, 21 | additionalHeaders?: http.OutgoingHttpHeaders 22 | ): Promise 23 | patch( 24 | requestUrl: string, 25 | data: string, 26 | additionalHeaders?: http.OutgoingHttpHeaders 27 | ): Promise 28 | put( 29 | requestUrl: string, 30 | data: string, 31 | additionalHeaders?: http.OutgoingHttpHeaders 32 | ): Promise 33 | sendStream( 34 | verb: string, 35 | requestUrl: string, 36 | stream: NodeJS.ReadableStream, 37 | additionalHeaders?: http.OutgoingHttpHeaders 38 | ): Promise 39 | request( 40 | verb: string, 41 | requestUrl: string, 42 | data: string | NodeJS.ReadableStream, 43 | headers: http.OutgoingHttpHeaders 44 | ): Promise 45 | requestRaw( 46 | info: RequestInfo, 47 | data: string | NodeJS.ReadableStream 48 | ): Promise 49 | requestRawWithCallback( 50 | info: RequestInfo, 51 | data: string | NodeJS.ReadableStream, 52 | onResult: (err?: Error, res?: HttpClientResponse) => void 53 | ): void 54 | } 55 | 56 | export interface RequestHandler { 57 | prepareRequest(options: http.RequestOptions): void 58 | canHandleAuthentication(response: HttpClientResponse): boolean 59 | handleAuthentication( 60 | httpClient: HttpClient, 61 | requestInfo: RequestInfo, 62 | data: string | NodeJS.ReadableStream | null 63 | ): Promise 64 | } 65 | 66 | export interface RequestInfo { 67 | options: http.RequestOptions 68 | parsedUrl: URL 69 | httpModule: typeof http | typeof https 70 | } 71 | 72 | export interface RequestOptions { 73 | headers?: http.OutgoingHttpHeaders 74 | socketTimeout?: number 75 | ignoreSslError?: boolean 76 | allowRedirects?: boolean 77 | allowRedirectDowngrade?: boolean 78 | maxRedirects?: number 79 | maxSockets?: number 80 | keepAlive?: boolean 81 | deserializeDates?: boolean 82 | // Allows retries only on Read operations (since writes may not be idempotent) 83 | allowRetries?: boolean 84 | maxRetries?: number 85 | } 86 | 87 | export interface TypedResponse { 88 | statusCode: number 89 | result: T | null 90 | headers: http.IncomingHttpHeaders 91 | } 92 | -------------------------------------------------------------------------------- /packages/attest/__tests__/store.test.ts: -------------------------------------------------------------------------------- 1 | import {MockAgent, setGlobalDispatcher} from 'undici' 2 | import {writeAttestation} from '../src/store' 3 | 4 | describe('writeAttestation', () => { 5 | const originalEnv = process.env 6 | const attestation = {foo: 'bar '} 7 | const token = 'token' 8 | const headers = {'X-GitHub-Foo': 'true'} 9 | 10 | const mockAgent = new MockAgent() 11 | setGlobalDispatcher(mockAgent) 12 | 13 | beforeEach(() => { 14 | process.env = { 15 | ...originalEnv, 16 | GITHUB_REPOSITORY: 'foo/bar' 17 | } 18 | }) 19 | 20 | afterEach(() => { 21 | process.env = originalEnv 22 | }) 23 | 24 | describe('when the api call is successful', () => { 25 | beforeEach(() => { 26 | mockAgent 27 | .get('https://api.github.com') 28 | .intercept({ 29 | path: '/repos/foo/bar/attestations', 30 | method: 'POST', 31 | headers: {authorization: `token ${token}`, ...headers}, 32 | body: JSON.stringify({bundle: attestation}) 33 | }) 34 | .reply(201, {id: '123'}) 35 | }) 36 | 37 | it('persists the attestation', async () => { 38 | await expect( 39 | writeAttestation(attestation, token, {headers}) 40 | ).resolves.toEqual('123') 41 | }) 42 | }) 43 | 44 | describe('when the api call fails', () => { 45 | beforeEach(() => { 46 | mockAgent 47 | .get('https://api.github.com') 48 | .intercept({ 49 | path: '/repos/foo/bar/attestations', 50 | method: 'POST', 51 | headers: {authorization: `token ${token}`}, 52 | body: JSON.stringify({bundle: attestation}) 53 | }) 54 | .reply(500, 'oops') 55 | }) 56 | 57 | it('throws an error', async () => { 58 | await expect( 59 | writeAttestation(attestation, token, {retry: 0}) 60 | ).rejects.toThrow(/oops/) 61 | }) 62 | }) 63 | 64 | describe('when the api call fails but succeeds on retry', () => { 65 | beforeEach(() => { 66 | const pool = mockAgent.get('https://api.github.com') 67 | 68 | pool 69 | .intercept({ 70 | path: '/repos/foo/bar/attestations', 71 | method: 'POST', 72 | headers: {authorization: `token ${token}`}, 73 | body: JSON.stringify({bundle: attestation}) 74 | }) 75 | .reply(500, 'oops') 76 | .times(1) 77 | 78 | pool 79 | .intercept({ 80 | path: '/repos/foo/bar/attestations', 81 | method: 'POST', 82 | headers: {authorization: `token ${token}`}, 83 | body: JSON.stringify({bundle: attestation}) 84 | }) 85 | .reply(201, {id: '123'}) 86 | .times(1) 87 | }) 88 | 89 | it('persists the attestation', async () => { 90 | await expect(writeAttestation(attestation, token)).resolves.toEqual('123') 91 | }) 92 | }) 93 | }) 94 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributions 2 | 3 | We welcome contributions in the form of issues and pull requests. We view the contributions and process as the same for internal and external contributors. 4 | 5 | ## Issues 6 | 7 | Log issues for both bugs and enhancement requests. Logging issues are important for the open community. 8 | 9 | Issues in this repository should be for the toolkit packages. General feedback for GitHub Actions should be filed in the [community forums.](https://github.community/t5/GitHub-Actions/bd-p/actions) Runner specific issues can be filed [in the runner repository](https://github.com/actions/runner). 10 | 11 | ## Enhancements and Feature Requests 12 | 13 | We ask that before significant effort is put into code changes, that we have agreement on taking the change before time is invested in code changes. 14 | 15 | 1. Create a feature request. 16 | 2. When we agree to take the enhancement, create an ADR to agree on the details of the change. 17 | 18 | An ADR is an Architectural Decision Record. This allows consensus on the direction forward and also serves as a record of the change and motivation. [Read more here](../docs/adrs/README.md). 19 | 20 | ## Development Life Cycle 21 | 22 | This repository uses [Lerna](https://github.com/lerna/lerna#readme) to manage multiple packages. Read the documentation there to begin contributing. 23 | 24 | Note that before a PR will be accepted, you must ensure: 25 | - all tests are passing 26 | - `npm run format` reports no issues 27 | - `npm run lint` reports no issues 28 | 29 | ### Useful Scripts 30 | 31 | - `npm run bootstrap` This runs `lerna exec -- npm install` which will install dependencies in this repository's packages and cross-link packages where necessary. 32 | - `npm run build` This compiles TypeScript code in each package (this is especially important if one package relies on changes in another when you're running tests). This is just an alias for `lerna run tsc`. 33 | - `npm run format` This checks that formatting has been applied with Prettier. 34 | - `npm test` This runs all Jest tests in all packages in this repository. 35 | - If you need to run tests for only one package, you can pass normal Jest CLI options: 36 | ```console 37 | $ npm test -- packages/toolkit 38 | ``` 39 | - `npm run create-package [name]` This runs a script that automates a couple of parts of creating a new package. 40 | 41 | ### Creating a Package 42 | 43 | 1. In a new branch, create a new Lerna package: 44 | 45 | ```console 46 | $ npm run new-package [name] 47 | ``` 48 | 49 | This will ask you some questions about the new package. Start with `0.0.0` as the first version (look generally at some of the other packages for how the package.json is structured). 50 | 51 | 2. Add `tsc` script to the new package's package.json file: 52 | 53 | ```json 54 | "scripts": { 55 | "tsc": "tsc" 56 | } 57 | ``` 58 | 59 | 3. Start developing 😄. 60 | -------------------------------------------------------------------------------- /packages/artifact/CONTRIBUTIONS.md: -------------------------------------------------------------------------------- 1 | # Contributions 2 | 3 | This package is used internally by the v4 versions of [upload-artifact](https://github.com/actions/upload-artifact) and [download-artifact](https://github.com/actions/download-artifact). This package can also be used by other actions to interact with artifacts. Any changes or updates to this package will propagate updates to these actions so it is important that major changes or updates get properly tested. 4 | 5 | Any issues or feature requests that are related to the artifact actions should be filled in the appropriate repo. 6 | 7 | A limited range of unit tests run as part of each PR when making changes to the artifact packages. For small contributions and fixes, they should be sufficient. 8 | 9 | If making large changes, there are a few scenarios that should be tested: 10 | 11 | - Uploading very large artifacts 12 | - Uploading artifacts with lots of small files 13 | - Uploading artifacts using a self-hosted runner (uploads and downloads behave differently due to extra latency) 14 | - Downloading a single artifact (large and small, if lots of small files are part of an artifact, timeouts and non-success HTTP responses can be expected) 15 | - Downloading all artifacts at once 16 | 17 | Large architectural changes can impact upload/download performance so it is important to separately run extra tests. We request that any large contributions/changes have extra detailed testing so we can verify performance and possible regressions. 18 | 19 | Tests will run for every push/pull_request [via Actions](https://github.com/actions/toolkit/blob/main/.github/workflows/artifact-tests.yml). 20 | 21 | # Testing 22 | 23 | ## Package tests 24 | 25 | To run unit tests for the `@actions/artifact` package: 26 | 27 | 1. Clone `actions/toolkit` locally 28 | 2. Install dependencies: `npm bootstrap` 29 | 3. Change working directory to `packages/artifact` 30 | 4. Run jest tests: `npm run test` 31 | 32 | ## Within upload-artifact or download-artifact actions 33 | 34 | Any easy way to test changes for the official upload/download actions is to fork them, compile changes and run them. 35 | 36 | 1. For your local `actions/toolkit` changes: 37 | 1. Change directory to `packages/artifact` 38 | 2. Compile the changes: `npm run tsc` 39 | 3. Symlink your package change: `npm link` 40 | 2. Fork and clone either [upload-artifact](https://github.com/actions/upload-artifact) and [download-artifact](https://github.com/actions/download-artifact) 41 | 1. In the locally cloned fork, link to your local toolkit changes: `npm link @actions/artifact` 42 | 2. Then, compile your changes with: `npm run release`. The local `dist/index.js` should be updated with your changes. 43 | 3. Commit and push to your fork, you can then test with a `uses:` in your workflow pointed at your fork. 44 | 4. The format for the above is `//@`, i.e. `me/myrepo/@HEAD` 45 | -------------------------------------------------------------------------------- /packages/github/src/context.ts: -------------------------------------------------------------------------------- 1 | // Originally pulled from https://github.com/JasonEtco/actions-toolkit/blob/main/src/context.ts 2 | import {WebhookPayload} from './interfaces' 3 | import {readFileSync, existsSync} from 'fs' 4 | import {EOL} from 'os' 5 | 6 | export class Context { 7 | /** 8 | * Webhook payload object that triggered the workflow 9 | */ 10 | payload: WebhookPayload 11 | 12 | eventName: string 13 | sha: string 14 | ref: string 15 | workflow: string 16 | action: string 17 | actor: string 18 | job: string 19 | runAttempt: number 20 | runNumber: number 21 | runId: number 22 | apiUrl: string 23 | serverUrl: string 24 | graphqlUrl: string 25 | 26 | /** 27 | * Hydrate the context from the environment 28 | */ 29 | constructor() { 30 | this.payload = {} 31 | if (process.env.GITHUB_EVENT_PATH) { 32 | if (existsSync(process.env.GITHUB_EVENT_PATH)) { 33 | this.payload = JSON.parse( 34 | readFileSync(process.env.GITHUB_EVENT_PATH, {encoding: 'utf8'}) 35 | ) 36 | } else { 37 | const path = process.env.GITHUB_EVENT_PATH 38 | process.stdout.write(`GITHUB_EVENT_PATH ${path} does not exist${EOL}`) 39 | } 40 | } 41 | this.eventName = process.env.GITHUB_EVENT_NAME as string 42 | this.sha = process.env.GITHUB_SHA as string 43 | this.ref = process.env.GITHUB_REF as string 44 | this.workflow = process.env.GITHUB_WORKFLOW as string 45 | this.action = process.env.GITHUB_ACTION as string 46 | this.actor = process.env.GITHUB_ACTOR as string 47 | this.job = process.env.GITHUB_JOB as string 48 | this.runAttempt = parseInt(process.env.GITHUB_RUN_ATTEMPT as string, 10) 49 | this.runNumber = parseInt(process.env.GITHUB_RUN_NUMBER as string, 10) 50 | this.runId = parseInt(process.env.GITHUB_RUN_ID as string, 10) 51 | this.apiUrl = process.env.GITHUB_API_URL ?? `https://api.github.com` 52 | this.serverUrl = process.env.GITHUB_SERVER_URL ?? `https://github.com` 53 | this.graphqlUrl = 54 | process.env.GITHUB_GRAPHQL_URL ?? `https://api.github.com/graphql` 55 | } 56 | 57 | get issue(): {owner: string; repo: string; number: number} { 58 | const payload = this.payload 59 | 60 | return { 61 | ...this.repo, 62 | number: (payload.issue || payload.pull_request || payload).number 63 | } 64 | } 65 | 66 | get repo(): {owner: string; repo: string} { 67 | if (process.env.GITHUB_REPOSITORY) { 68 | const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/') 69 | return {owner, repo} 70 | } 71 | 72 | if (this.payload.repository) { 73 | return { 74 | owner: this.payload.repository.owner.login, 75 | repo: this.payload.repository.name 76 | } 77 | } 78 | 79 | throw new Error( 80 | "context.repo requires a GITHUB_REPOSITORY environment variable like 'owner/repo'" 81 | ) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /packages/attest/src/sign.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Bundle, 3 | BundleBuilder, 4 | CIContextProvider, 5 | DSSEBundleBuilder, 6 | FulcioSigner, 7 | RekorWitness, 8 | TSAWitness, 9 | Witness 10 | } from '@sigstore/sign' 11 | 12 | const OIDC_AUDIENCE = 'sigstore' 13 | const DEFAULT_TIMEOUT = 10000 14 | const DEFAULT_RETRIES = 3 15 | 16 | /** 17 | * The payload to be signed (body) and its media type (type). 18 | */ 19 | export type Payload = { 20 | body: Buffer 21 | type: string 22 | } 23 | 24 | /** 25 | * Options for signing a document. 26 | */ 27 | export type SignOptions = { 28 | /** 29 | * The URL of the Fulcio service. 30 | */ 31 | fulcioURL: string 32 | /** 33 | * The URL of the Rekor service. 34 | */ 35 | rekorURL?: string 36 | /** 37 | * The URL of the TSA (Time Stamping Authority) server. 38 | */ 39 | tsaServerURL?: string 40 | /** 41 | * The timeout duration in milliseconds when communicating with Sigstore 42 | * services. 43 | */ 44 | timeout?: number 45 | /** 46 | * The number of retry attempts. 47 | */ 48 | retry?: number 49 | } 50 | 51 | /** 52 | * Signs the provided payload with a Sigstore-issued certificate and returns the 53 | * signature bundle. 54 | * @param payload Payload to be signed. 55 | * @param options Signing options. 56 | * @returns A promise that resolves to the Sigstore signature bundle. 57 | */ 58 | export const signPayload = async ( 59 | payload: Payload, 60 | options: SignOptions 61 | ): Promise => { 62 | const artifact = { 63 | data: payload.body, 64 | type: payload.type 65 | } 66 | 67 | // Sign the artifact and build the bundle 68 | return initBundleBuilder(options).create(artifact) 69 | } 70 | 71 | // Assembles the Sigstore bundle builder with the appropriate options 72 | const initBundleBuilder = (opts: SignOptions): BundleBuilder => { 73 | const identityProvider = new CIContextProvider(OIDC_AUDIENCE) 74 | const timeout = opts.timeout || DEFAULT_TIMEOUT 75 | const retry = opts.retry || DEFAULT_RETRIES 76 | const witnesses: Witness[] = [] 77 | 78 | const signer = new FulcioSigner({ 79 | identityProvider, 80 | fulcioBaseURL: opts.fulcioURL, 81 | timeout, 82 | retry 83 | }) 84 | 85 | if (opts.rekorURL) { 86 | witnesses.push( 87 | new RekorWitness({ 88 | rekorBaseURL: opts.rekorURL, 89 | fetchOnConflict: true, 90 | timeout, 91 | retry 92 | }) 93 | ) 94 | } 95 | 96 | if (opts.tsaServerURL) { 97 | witnesses.push( 98 | new TSAWitness({ 99 | tsaBaseURL: opts.tsaServerURL, 100 | timeout, 101 | retry 102 | }) 103 | ) 104 | } 105 | 106 | // Build the bundle with the singleCertificate option which will 107 | // trigger the creation of v0.3 DSSE bundles 108 | return new DSSEBundleBuilder({signer, witnesses}) 109 | } 110 | -------------------------------------------------------------------------------- /packages/tool-cache/README.md: -------------------------------------------------------------------------------- 1 | # `@actions/tool-cache` 2 | 3 | > Functions necessary for downloading and caching tools. 4 | 5 | ## Usage 6 | 7 | #### Download 8 | 9 | You can use this to download tools (or other files) from a download URL: 10 | 11 | ```js 12 | const tc = require('@actions/tool-cache'); 13 | 14 | const node12Path = await tc.downloadTool('https://nodejs.org/dist/v12.7.0/node-v12.7.0-linux-x64.tar.gz'); 15 | ``` 16 | 17 | #### Extract 18 | 19 | These can then be extracted in platform specific ways: 20 | 21 | ```js 22 | const tc = require('@actions/tool-cache'); 23 | 24 | if (process.platform === 'win32') { 25 | const node12Path = await tc.downloadTool('https://nodejs.org/dist/v12.7.0/node-v12.7.0-win-x64.zip'); 26 | const node12ExtractedFolder = await tc.extractZip(node12Path, 'path/to/extract/to'); 27 | 28 | // Or alternately 29 | const node12Path = await tc.downloadTool('https://nodejs.org/dist/v12.7.0/node-v12.7.0-win-x64.7z'); 30 | const node12ExtractedFolder = await tc.extract7z(node12Path, 'path/to/extract/to'); 31 | } 32 | else if (process.platform === 'darwin') { 33 | const node12Path = await tc.downloadTool('https://nodejs.org/dist/v12.7.0/node-v12.7.0.pkg'); 34 | const node12ExtractedFolder = await tc.extractXar(node12Path, 'path/to/extract/to'); 35 | } 36 | else { 37 | const node12Path = await tc.downloadTool('https://nodejs.org/dist/v12.7.0/node-v12.7.0-linux-x64.tar.gz'); 38 | const node12ExtractedFolder = await tc.extractTar(node12Path, 'path/to/extract/to'); 39 | } 40 | ``` 41 | 42 | #### Cache 43 | 44 | Finally, you can cache these directories in our tool-cache. This is useful if you want to switch back and forth between versions of a tool, or save a tool between runs for self-hosted runners. 45 | 46 | You'll often want to add it to the path as part of this step: 47 | 48 | ```js 49 | const tc = require('@actions/tool-cache'); 50 | const core = require('@actions/core'); 51 | 52 | const node12Path = await tc.downloadTool('https://nodejs.org/dist/v12.7.0/node-v12.7.0-linux-x64.tar.gz'); 53 | const node12ExtractedFolder = await tc.extractTar(node12Path, 'path/to/extract/to'); 54 | 55 | const cachedPath = await tc.cacheDir(node12ExtractedFolder, 'node', '12.7.0'); 56 | core.addPath(cachedPath); 57 | ``` 58 | 59 | You can also cache files for reuse. 60 | 61 | ```js 62 | const tc = require('@actions/tool-cache'); 63 | 64 | const cachedPath = await tc.cacheFile('path/to/exe', 'destFileName.exe', 'myExeName', '1.1.0'); 65 | ``` 66 | 67 | #### Find 68 | 69 | Finally, you can find directories and files you've previously cached: 70 | 71 | ```js 72 | const tc = require('@actions/tool-cache'); 73 | const core = require('@actions/core'); 74 | 75 | const nodeDirectory = tc.find('node', '12.x', 'x64'); 76 | core.addPath(nodeDirectory); 77 | ``` 78 | 79 | You can even find all cached versions of a tool: 80 | 81 | ```js 82 | const tc = require('@actions/tool-cache'); 83 | 84 | const allNodeVersions = tc.findAllVersions('node'); 85 | console.log(`Versions of node available: ${allNodeVersions}`); 86 | ``` 87 | -------------------------------------------------------------------------------- /packages/github/RELEASES.md: -------------------------------------------------------------------------------- 1 | # @actions/github Releases 2 | 3 | ### 6.0.1 4 | 5 | - Dependency updates [#2043](https://github.com/actions/toolkit/pull/2043) 6 | - Add `context.runAttempt` [#1588](https://github.com/actions/toolkit/pull/1588) 7 | 8 | ### 6.0.0 9 | - Support the latest Octokit in @actions/github [#1553](https://github.com/actions/toolkit/pull/1553) 10 | - Drop support of NodeJS v14, v16 11 | 12 | ### 5.1.1 13 | - Export default octokit options [#1188](https://github.com/actions/toolkit/pull/1188) 14 | 15 | ### 5.1.0 16 | - Add additionalPlugins parameter to getOctokit method [#1181](https://github.com/actions/toolkit/pull/1181) 17 | - Dependency updates [#1180](https://github.com/actions/toolkit/pull/1180) 18 | 19 | 20 | ### 5.0.3 21 | - - Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087) 22 | 23 | ### 5.0.2 24 | - Update to v2.0.0 of `@actions/http-client` 25 | 26 | ### 5.0.1 27 | - [Update Octokit Dependencies](https://github.com/actions/toolkit/pull/1037) 28 | ### 5.0.0 29 | - [Update @actions/github to include latest octokit definitions](https://github.com/actions/toolkit/pull/783) 30 | - [Add urls to context](https://github.com/actions/toolkit/pull/794) 31 | 32 | ### 4.0.0 33 | - [Add execution state information to context](https://github.com/actions/toolkit/pull/499) 34 | - [Update Octokit Dependencies with some api breaking changes](https://github.com/actions/toolkit/pull/498) 35 | - The full list of api changes are [here](https://github.com/octokit/plugin-rest-endpoint-methods.js/releases/tag/v4.0.0) 36 | - `GitHub.plugin()` no longer supports an array as first argument. Multiple args must be passed in instead. 37 | 38 | ### 3.0.0 39 | - [Swap to @octokit/core and use plugins to leverage lastest octokit apis](https://github.com/actions/toolkit/pull/453) 40 | - [Add comment field to payload context](https://github.com/actions/toolkit/pull/375) 41 | 42 | ### 2.2.0 43 | 44 | - [Support GHES: Use GITHUB_API_URL and GITHUB_GRAPHQL_URL to determine baseUrl](https://github.com/actions/toolkit/pull/449) 45 | 46 | ### 2.1.1 47 | 48 | - [Use import {Octokit}](https://github.com/actions/toolkit/pull/332) 49 | - [Check proxy bypass before setting proxy agent](https://github.com/actions/toolkit/pull/320) 50 | 51 | ### 2.1.0 52 | 53 | - [Octokit client follows proxy settings](https://github.com/actions/toolkit/pull/314) 54 | - [Fix issue number for pull request comment events](https://github.com/actions/toolkit/pull/311) 55 | 56 | ### 2.0.1 57 | 58 | - [Add \"types\" to package.json](https://github.com/actions/toolkit/pull/221) 59 | 60 | ### 2.0.0 61 | 62 | - Upgrade Octokit version to 4.x to include typescript types [#228](https://github.com/actions/toolkit/pull/228) 63 | 64 | ### 1.1.0 65 | 66 | - Accept Octokit.Options in the GitHub constructor [#113](https://github.com/actions/toolkit/pull/113) 67 | 68 | ### 1.0.1 69 | 70 | - Simplify WebPack configs by removing dynamic require - [#101](https://github.com/actions/toolkit/pull/101) 71 | 72 | ### 1.0.0 73 | 74 | - Initial release 75 | -------------------------------------------------------------------------------- /packages/artifact/docs/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | - [Frequently Asked Questions](#frequently-asked-questions) 4 | - [Supported Characters](#supported-characters) 5 | - [Compression? ZIP? How is my artifact stored?](#compression-zip-how-is-my-artifact-stored) 6 | - [Which versions of the artifacts packages are compatible?](#which-versions-of-the-artifacts-packages-are-compatible) 7 | - [How long will my artifact be available?](#how-long-will-my-artifact-be-available) 8 | 9 | ## Supported Characters 10 | 11 | When uploading an artifact, the inputted `name` parameter along with the files specified in `files` cannot contain any of the following characters. If they are present in `name` or `files`, the Artifact will be rejected by the server and the upload will fail. These characters are not allowed due to limitations and restrictions with certain file systems such as NTFS. To maintain platform-agnostic behavior, characters that are not supported by an individual filesystem/platform will not be supported on all filesystems/platforms. 12 | 13 | - " 14 | - : 15 | - < 16 | - \> 17 | - | 18 | - \* 19 | - ? 20 | 21 | In addition to the aforementioned characters, the inputted `name` also cannot include the following 22 | - \ 23 | - / 24 | 25 | ## Compression? ZIP? How is my artifact stored? 26 | 27 | When creating an Artifact, the files are dynamically compressed and streamed into a ZIP archive. Since they are stored in a ZIP, they can be compressed by Zlib in varying levels. 28 | 29 | The value can range from 0 to 9: 30 | 31 | - 0: No compression 32 | - 1: Best speed 33 | - 6: Default compression (same as GNU Gzip) 34 | - 9: Best compression 35 | 36 | Higher levels will result in better compression, but will take longer to complete. 37 | For large files that are not easily compressed, a value of 0 is recommended for significantly faster uploads. 38 | 39 | ## Which versions of the artifacts packages are compatible? 40 | [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact), leverage [GitHub Actions toolkit](https://github.com/actions/toolkit) and are typically used together to upload and download artifacts in your workflows. 41 | 42 | | upload-artifact | download-artifact | toolkit | 43 | |---|---|---| 44 | | v4 | v4 | v2 | 45 | | < v3 | < v3 | < v1 | 46 | 47 | Use matching versions of `actions/upload-artifact` and `actions/download-artifact` to ensure compatibility. 48 | 49 | In your GitHub Actions workflow YAML file, you specify the version of the actions you want to use. For example: 50 | 51 | ```yaml 52 | uses: actions/upload-artifact@v4 53 | # ... 54 | uses: actions/download-artifact@v4 55 | # ... 56 | ``` 57 | 58 | **Release Notes:** 59 | Check the release notes for each repository to see if there are any specific notes about compatibility or changes in behavior. 60 | 61 | ## How long will my artifact be available? 62 | The default retention period is **90 days**. For more information, visit: https://github.com/actions/upload-artifact?tab=readme-ov-file#retention-period 63 | -------------------------------------------------------------------------------- /packages/http-client/src/proxy.ts: -------------------------------------------------------------------------------- 1 | export function getProxyUrl(reqUrl: URL): URL | undefined { 2 | const usingSsl = reqUrl.protocol === 'https:' 3 | 4 | if (checkBypass(reqUrl)) { 5 | return undefined 6 | } 7 | 8 | const proxyVar = (() => { 9 | if (usingSsl) { 10 | return process.env['https_proxy'] || process.env['HTTPS_PROXY'] 11 | } else { 12 | return process.env['http_proxy'] || process.env['HTTP_PROXY'] 13 | } 14 | })() 15 | 16 | if (proxyVar) { 17 | try { 18 | return new DecodedURL(proxyVar) 19 | } catch { 20 | if (!proxyVar.startsWith('http://') && !proxyVar.startsWith('https://')) 21 | return new DecodedURL(`http://${proxyVar}`) 22 | } 23 | } else { 24 | return undefined 25 | } 26 | } 27 | 28 | export function checkBypass(reqUrl: URL): boolean { 29 | if (!reqUrl.hostname) { 30 | return false 31 | } 32 | 33 | const reqHost = reqUrl.hostname 34 | if (isLoopbackAddress(reqHost)) { 35 | return true 36 | } 37 | 38 | const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || '' 39 | if (!noProxy) { 40 | return false 41 | } 42 | 43 | // Determine the request port 44 | let reqPort: number | undefined 45 | if (reqUrl.port) { 46 | reqPort = Number(reqUrl.port) 47 | } else if (reqUrl.protocol === 'http:') { 48 | reqPort = 80 49 | } else if (reqUrl.protocol === 'https:') { 50 | reqPort = 443 51 | } 52 | 53 | // Format the request hostname and hostname with port 54 | const upperReqHosts = [reqUrl.hostname.toUpperCase()] 55 | if (typeof reqPort === 'number') { 56 | upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`) 57 | } 58 | 59 | // Compare request host against noproxy 60 | for (const upperNoProxyItem of noProxy 61 | .split(',') 62 | .map(x => x.trim().toUpperCase()) 63 | .filter(x => x)) { 64 | if ( 65 | upperNoProxyItem === '*' || 66 | upperReqHosts.some( 67 | x => 68 | x === upperNoProxyItem || 69 | x.endsWith(`.${upperNoProxyItem}`) || 70 | (upperNoProxyItem.startsWith('.') && 71 | x.endsWith(`${upperNoProxyItem}`)) 72 | ) 73 | ) { 74 | return true 75 | } 76 | } 77 | 78 | return false 79 | } 80 | 81 | function isLoopbackAddress(host: string): boolean { 82 | const hostLower = host.toLowerCase() 83 | return ( 84 | hostLower === 'localhost' || 85 | hostLower.startsWith('127.') || 86 | hostLower.startsWith('[::1]') || 87 | hostLower.startsWith('[0:0:0:0:0:0:0:1]') 88 | ) 89 | } 90 | 91 | class DecodedURL extends URL { 92 | private _decodedUsername: string 93 | private _decodedPassword: string 94 | 95 | constructor(url: string | URL, base?: string | URL) { 96 | super(url, base) 97 | this._decodedUsername = decodeURIComponent(super.username) 98 | this._decodedPassword = decodeURIComponent(super.password) 99 | } 100 | 101 | get username(): string { 102 | return this._decodedUsername 103 | } 104 | 105 | get password(): string { 106 | return this._decodedPassword 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /packages/attest/src/artifactMetadata.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github' 2 | import {retry} from '@octokit/plugin-retry' 3 | import {RequestHeaders} from '@octokit/types' 4 | 5 | const CREATE_STORAGE_RECORD_REQUEST = 6 | 'POST /orgs/{owner}/artifacts/metadata/storage-record' 7 | const DEFAULT_RETRY_COUNT = 5 8 | 9 | /** 10 | * Options for creating a storage record for an attested artifact. 11 | */ 12 | export type ArtifactOptions = { 13 | // Includes details about the attested artifact 14 | // The name of the artifact 15 | name: string 16 | // The digest of the artifact 17 | digest: string 18 | // The version of the artifact 19 | version?: string 20 | // The status of the artifact 21 | status?: string 22 | } 23 | // Includes details about the package registry the artifact was published to 24 | export type PackageRegistryOptions = { 25 | // The URL of the package registry 26 | registryUrl: string 27 | // The URL of the artifact in the package registry 28 | artifactUrl?: string 29 | // The package registry repository the artifact was published to. 30 | repo?: string 31 | // The path of the artifact in the package registry repository. 32 | path?: string 33 | } 34 | 35 | /** 36 | * Writes a storage record on behalf of an artifact that has been attested 37 | * @param artifactOptions - parameters for the storage record API request. 38 | * @param packageRegistryOptions - parameters for the package registry API request. 39 | * @param token - GitHub token used to authenticate the request. 40 | * @param retryAttempts - The number of retries to attempt if the request fails. 41 | * @param headers - Additional headers to include in the request. 42 | * 43 | * @returns The ID of the storage record. 44 | * @throws Error if the storage record fails to persist. 45 | */ 46 | export async function createStorageRecord( 47 | artifactOptions: ArtifactOptions, 48 | packageRegistryOptions: PackageRegistryOptions, 49 | token: string, 50 | retryAttempts?: number, 51 | headers?: RequestHeaders 52 | ): Promise { 53 | const retries = retryAttempts ?? DEFAULT_RETRY_COUNT 54 | const octokit = github.getOctokit(token, {retry: {retries}}, retry) 55 | try { 56 | const response = await octokit.request(CREATE_STORAGE_RECORD_REQUEST, { 57 | owner: github.context.repo.owner, 58 | headers, 59 | ...buildRequestParams(artifactOptions, packageRegistryOptions) 60 | }) 61 | 62 | const data = 63 | typeof response.data == 'string' 64 | ? JSON.parse(response.data) 65 | : response.data 66 | 67 | return data?.storage_records.map((r: {id: number}) => r.id) 68 | } catch (err) { 69 | const message = err instanceof Error ? err.message : err 70 | throw new Error(`Failed to persist storage record: ${message}`) 71 | } 72 | } 73 | 74 | function buildRequestParams( 75 | artifactOptions: ArtifactOptions, 76 | packageRegistryOptions: PackageRegistryOptions 77 | ): Record { 78 | const {registryUrl, artifactUrl, ...rest} = packageRegistryOptions 79 | return { 80 | ...artifactOptions, 81 | registry_url: registryUrl, 82 | artifact_url: artifactUrl, 83 | ...rest 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /packages/core/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@actions/core", 3 | "version": "2.0.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@actions/core", 9 | "version": "2.0.1", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@actions/exec": "^2.0.0", 13 | "@actions/http-client": "^3.0.0" 14 | }, 15 | "devDependencies": { 16 | "@types/node": "^16.18.112" 17 | } 18 | }, 19 | "node_modules/@actions/exec": { 20 | "version": "2.0.0", 21 | "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-2.0.0.tgz", 22 | "integrity": "sha512-k8ngrX2voJ/RIN6r9xB82NVqKpnMRtxDoiO+g3olkIUpQNqjArXrCQceduQZCQj3P3xm32pChRLqRrtXTlqhIw==", 23 | "license": "MIT", 24 | "dependencies": { 25 | "@actions/io": "^2.0.0" 26 | } 27 | }, 28 | "node_modules/@actions/http-client": { 29 | "version": "3.0.0", 30 | "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.0.tgz", 31 | "integrity": "sha512-1s3tXAfVMSz9a4ZEBkXXRQD4QhY3+GAsWSbaYpeknPOKEeyRiU3lH+bHiLMZdo2x/fIeQ/hscL1wCkDLVM2DZQ==", 32 | "license": "MIT", 33 | "dependencies": { 34 | "tunnel": "^0.0.6", 35 | "undici": "^5.28.5" 36 | } 37 | }, 38 | "node_modules/@actions/io": { 39 | "version": "2.0.0", 40 | "resolved": "https://registry.npmjs.org/@actions/io/-/io-2.0.0.tgz", 41 | "integrity": "sha512-Jv33IN09XLO+0HS79aaODsvIRyduiF7NY/F6LYeK5oeUmrsz7aFdRphQjFoESF4jS7lMauDOttKALcpapVDIAg==", 42 | "license": "MIT" 43 | }, 44 | "node_modules/@fastify/busboy": { 45 | "version": "2.1.1", 46 | "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", 47 | "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", 48 | "license": "MIT", 49 | "engines": { 50 | "node": ">=14" 51 | } 52 | }, 53 | "node_modules/@types/node": { 54 | "version": "16.18.126", 55 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz", 56 | "integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==", 57 | "dev": true, 58 | "license": "MIT" 59 | }, 60 | "node_modules/tunnel": { 61 | "version": "0.0.6", 62 | "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", 63 | "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", 64 | "license": "MIT", 65 | "engines": { 66 | "node": ">=0.6.11 <=0.7.0 || >=0.7.3" 67 | } 68 | }, 69 | "node_modules/undici": { 70 | "version": "5.29.0", 71 | "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", 72 | "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", 73 | "license": "MIT", 74 | "dependencies": { 75 | "@fastify/busboy": "^2.0.0" 76 | }, 77 | "engines": { 78 | "node": ">=14.0" 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/exec/src/exec.ts: -------------------------------------------------------------------------------- 1 | import {StringDecoder} from 'string_decoder' 2 | import {ExecOptions, ExecOutput, ExecListeners} from './interfaces' 3 | import * as tr from './toolrunner' 4 | 5 | export {ExecOptions, ExecOutput, ExecListeners} 6 | 7 | /** 8 | * Exec a command. 9 | * Output will be streamed to the live console. 10 | * Returns promise with return code 11 | * 12 | * @param commandLine command to execute (can include additional args). Must be correctly escaped. 13 | * @param args optional arguments for tool. Escaping is handled by the lib. 14 | * @param options optional exec options. See ExecOptions 15 | * @returns Promise exit code 16 | */ 17 | export async function exec( 18 | commandLine: string, 19 | args?: string[], 20 | options?: ExecOptions 21 | ): Promise { 22 | const commandArgs = tr.argStringToArray(commandLine) 23 | if (commandArgs.length === 0) { 24 | throw new Error(`Parameter 'commandLine' cannot be null or empty.`) 25 | } 26 | // Path to tool to execute should be first arg 27 | const toolPath = commandArgs[0] 28 | args = commandArgs.slice(1).concat(args || []) 29 | const runner: tr.ToolRunner = new tr.ToolRunner(toolPath, args, options) 30 | return runner.exec() 31 | } 32 | 33 | /** 34 | * Exec a command and get the output. 35 | * Output will be streamed to the live console. 36 | * Returns promise with the exit code and collected stdout and stderr 37 | * 38 | * @param commandLine command to execute (can include additional args). Must be correctly escaped. 39 | * @param args optional arguments for tool. Escaping is handled by the lib. 40 | * @param options optional exec options. See ExecOptions 41 | * @returns Promise exit code, stdout, and stderr 42 | */ 43 | 44 | export async function getExecOutput( 45 | commandLine: string, 46 | args?: string[], 47 | options?: ExecOptions 48 | ): Promise { 49 | let stdout = '' 50 | let stderr = '' 51 | 52 | //Using string decoder covers the case where a mult-byte character is split 53 | const stdoutDecoder = new StringDecoder('utf8') 54 | const stderrDecoder = new StringDecoder('utf8') 55 | 56 | const originalStdoutListener = options?.listeners?.stdout 57 | const originalStdErrListener = options?.listeners?.stderr 58 | 59 | const stdErrListener = (data: Buffer): void => { 60 | stderr += stderrDecoder.write(data) 61 | if (originalStdErrListener) { 62 | originalStdErrListener(data) 63 | } 64 | } 65 | 66 | const stdOutListener = (data: Buffer): void => { 67 | stdout += stdoutDecoder.write(data) 68 | if (originalStdoutListener) { 69 | originalStdoutListener(data) 70 | } 71 | } 72 | 73 | const listeners: ExecListeners = { 74 | ...options?.listeners, 75 | stdout: stdOutListener, 76 | stderr: stdErrListener 77 | } 78 | 79 | const exitCode = await exec(commandLine, args, {...options, listeners}) 80 | 81 | //flush any remaining characters 82 | stdout += stdoutDecoder.end() 83 | stderr += stderrDecoder.end() 84 | 85 | return { 86 | exitCode, 87 | stdout, 88 | stderr 89 | } 90 | } 91 | --------------------------------------------------------------------------------