Compare commits

...

10 Commits

Author SHA1 Message Date
Salman Chishti
3a2844b7e9 Merge pull request #700 from actions/salmanmkc/expose-getoctokit + prepare release for v9
Some checks failed
Licensed / Check licenses (push) Has been cancelled
Check dist/ / check-dist (push) Has been cancelled
CI / ci (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Integration / Integration test: return (push) Has been cancelled
Integration / Integration test: relative-path require (push) Has been cancelled
Integration / Integration test: npm package require (push) Has been cancelled
Integration / Integration test: GraphQL previews option (push) Has been cancelled
Integration / Integration test: user-agent option (push) Has been cancelled
Integration / Integration test: getOctokit with token (push) Has been cancelled
Integration / Integration test: debug option (runner.debug mode disabled) (push) Has been cancelled
Integration / Integration test: debug option (runner.debug mode enabled) (push) Has been cancelled
Integration / Integration test: base-url option (push) Has been cancelled
feat!: add getOctokit to script context, upgrade @actions/github v9, @octokit/core v7, and related packages
2026-04-09 23:39:54 +01:00
Salman Muin Kayser Chishti
ca10bbdd1a fix: use @octokit/core/types import for v7 compatibility
The merge from main incorrectly used the v5 import path
(@octokit/core/dist-types/types). v7 exports types via
@octokit/core/types in its package.json exports map.

Rebuild dist with correct dependency resolution.
2026-04-09 22:14:16 +01:00
Salman Muin Kayser Chishti
86e48e20ac merge: incorporate main branch changes
- Resolve integration.yml merge conflicts (user-agent assertions)
- Fix OctokitOptions import path to @octokit/core/dist-types/types
- Rebuild dist
2026-04-09 22:10:00 +01:00
Salman Muin Kayser Chishti
c1084728b5 chore: rebuild dist for v9 upgrade and getOctokit factory 2026-04-09 22:02:13 +01:00
Salman Chishti
afff112e4f Merge pull request #712 from actions/salmanmkc/deployment-false + fix user-agent test to handle orchestration ID
ci: use deployment: false for integration test environments
2026-04-09 21:40:10 +01:00
Salman Muin Kayser Chishti
ff8117e5b7 ci: fix user-agent test to handle orchestration ID 2026-04-09 19:24:48 +00:00
Salman Muin Kayser Chishti
81c6b78760 ci: use deployment: false to suppress deployment noise from integration tests 2026-04-09 19:02:40 +00:00
Salman Muin Kayser Chishti
3953caf885 docs: update README examples from @v8 to @v9, add getOctokit docs and v9 breaking changes 2026-04-09 19:16:01 +01:00
Salman Muin Kayser Chishti
c17d55b90d ci: add getOctokit integration test job 2026-04-09 19:16:00 +01:00
Salman Muin Kayser Chishti
a047196d9a test: add getOctokit integration tests via callAsyncFunction 2026-04-09 19:15:58 +01:00
4 changed files with 33361 additions and 4329 deletions

View File

@@ -42,7 +42,8 @@ jobs:
result-encoding: string
- run: |
echo "- Validating relative require output"
if [[ "${{steps.relative-require.outputs.result}}" != "@actions/github-script" ]]; then
expected="@actions/github-script"
if [[ "${{steps.relative-require.outputs.result}}" != "$expected" ]]; then
echo $'::error::\u274C' "Expected '$expected', got ${{steps.relative-require.outputs.result}}"
exit 1
fi
@@ -154,22 +155,54 @@ jobs:
return endpoint({}).headers['user-agent']
result-encoding: string
- run: |
# User-agent format: <prefix> [actions_orchestration_id/<id>] octokit-core.js/<version> ...
# When ACTIONS_ORCHESTRATION_ID is set, the orchestration ID is inserted after the prefix
echo "- Validating user-agent default"
expected="actions/github-script octokit-core.js/"
if [[ "${{steps.user-agent-default.outputs.result}}" != "$expected"* ]]; then
echo $'::error::\u274C' "Expected user-agent to start with '$expected', got ${{steps.user-agent-default.outputs.result}}"
ua="${{steps.user-agent-default.outputs.result}}"
if [[ "$ua" != "actions/github-script"* ]] || [[ "$ua" != *"octokit-core.js/"* ]]; then
echo $'::error::\u274C' "Expected user-agent to start with 'actions/github-script' and contain 'octokit-core.js/', got $ua"
exit 1
fi
echo "- Validating user-agent set to a value"
expected="foobar octokit-core.js/"
if [[ "${{steps.user-agent-set.outputs.result}}" != "$expected"* ]]; then
echo $'::error::\u274C' "Expected user-agent to start with '$expected', got ${{steps.user-agent-set.outputs.result}}"
ua="${{steps.user-agent-set.outputs.result}}"
if [[ "$ua" != "foobar"* ]] || [[ "$ua" != *"octokit-core.js/"* ]]; then
echo $'::error::\u274C' "Expected user-agent to start with 'foobar' and contain 'octokit-core.js/', got $ua"
exit 1
fi
echo "- Validating user-agent set to an empty string"
expected="actions/github-script octokit-core.js/"
if [[ "${{steps.user-agent-empty.outputs.result}}" != "$expected"* ]]; then
echo $'::error::\u274C' "Expected user-agent to start with '$expected', got ${{steps.user-agent-empty.outputs.result}}"
ua="${{steps.user-agent-empty.outputs.result}}"
if [[ "$ua" != "actions/github-script"* ]] || [[ "$ua" != *"octokit-core.js/"* ]]; then
echo $'::error::\u274C' "Expected user-agent to start with 'actions/github-script' and contain 'octokit-core.js/', got $ua"
exit 1
fi
echo $'\u2705 Test passed' | tee -a $GITHUB_STEP_SUMMARY
test-get-octokit:
name: 'Integration test: getOctokit with token'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/install-dependencies
- id: secondary-client
name: Create a second client with getOctokit
uses: ./
env:
APP_TOKEN: ${{ github.token }}
with:
script: |
const appOctokit = getOctokit(process.env.APP_TOKEN)
const {data} = await appOctokit.rest.repos.get({
owner: context.repo.owner,
repo: context.repo.repo
})
return `${appOctokit !== github}:${data.full_name}`
result-encoding: string
- run: |
echo "- Validating secondary client output"
expected="true:${{ github.repository }}"
if [[ "${{steps.secondary-client.outputs.result}}" != "$expected" ]]; then
echo $'::error::\u274C' "Expected '$expected', got ${{steps.secondary-client.outputs.result}}"
exit 1
fi
echo $'\u2705 Test passed' | tee -a $GITHUB_STEP_SUMMARY
@@ -178,7 +211,9 @@ jobs:
strategy:
matrix:
environment: ['', 'debug-integration-test']
environment: ${{ matrix.environment }}
environment:
name: ${{ matrix.environment }}
deployment: false
name: "Integration test: debug option (runner.debug mode ${{ matrix.environment && 'enabled' || 'disabled' }})"
runs-on: ubuntu-latest
steps:

165
README.md
View File

@@ -38,6 +38,7 @@ The following arguments will be provided:
- `glob` A reference to the [@actions/glob](https://github.com/actions/toolkit/tree/main/packages/glob) package
- `io` A reference to the [@actions/io](https://github.com/actions/toolkit/tree/main/packages/io) package
- `exec` A reference to the [@actions/exec](https://github.com/actions/toolkit/tree/main/packages/exec) package
- `getOctokit` A factory function to create additional authenticated Octokit clients with different tokens (see [Creating additional clients](#creating-additional-clients-with-getoctokit))
- `require` A proxy wrapper around the normal Node.js `require` to enable
requiring relative paths (relative to the current working directory) and
requiring npm packages installed in the current working directory. If for
@@ -53,6 +54,21 @@ documentation.
## Breaking Changes
### V9
Version 9 of this action upgrades to `@actions/github` v9, which brings the latest Octokit types and features.
**New features:**
- **`getOctokit` factory function** — Available directly in the script context. Create additional authenticated Octokit clients with different tokens for multi-token workflows, GitHub App tokens, and cross-org access. See [Creating additional clients with `getOctokit`](#creating-additional-clients-with-getoctokit) for details and examples.
- **Orchestration ID in user-agent** — The `ACTIONS_ORCHESTRATION_ID` environment variable is automatically appended to the user-agent string for request tracing.
**Breaking changes:**
- **`require('@actions/github')` no longer works in scripts.** The upgrade to `@actions/github` v9 (ESM-only) means `require('@actions/github')` will fail at runtime. If you previously used patterns like `const { getOctokit } = require('@actions/github')` to create secondary clients, use the new injected `getOctokit` function instead — it's available directly in the script context with no imports needed.
- `getOctokit` is now an injected function parameter. Scripts that declare `const getOctokit = ...` or `let getOctokit = ...` will get a `SyntaxError` because JavaScript does not allow `const`/`let` redeclaration of function parameters. Use the injected `getOctokit` directly, or use `var getOctokit = ...` if you need to redeclare it.
- If your script accesses other `@actions/github` internals beyond the standard `github`/`octokit` client, you may need to update those references for v9 compatibility.
### V8
Version 8 of this action updated the runtime to Node 24 - https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-javascript-actions
@@ -90,16 +106,16 @@ See [development.md](/docs/development.md).
## Passing inputs to the script
Actions expressions are evaluated before the `script` is passed to the action, so the result of any expressions
*will be evaluated as JavaScript code*.
_will be evaluated as JavaScript code_.
It's highly recommended to *not* evaluate expressions directly in the `script` to avoid
It's highly recommended to _not_ evaluate expressions directly in the `script` to avoid
[script injections](https://docs.github.com/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections)
and potential `SyntaxError`s when the expression is not valid JavaScript code (particularly when it comes to improperly escaped strings).
To pass inputs, set `env` vars on the action step and reference them in your script with `process.env`:
```yaml
- uses: actions/github-script@v8
- uses: actions/github-script@v9
env:
TITLE: ${{ github.event.pull_request.title }}
with:
@@ -118,7 +134,7 @@ The return value of the script will be in the step's outputs under the
"result" key.
```yaml
- uses: actions/github-script@v8
- uses: actions/github-script@v9
id: set-result
with:
script: return "Hello!"
@@ -137,7 +153,7 @@ output of a github-script step. For some workflows, string encoding is preferred
`result-encoding` input:
```yaml
- uses: actions/github-script@v8
- uses: actions/github-script@v9
id: my-script
with:
result-encoding: string
@@ -149,7 +165,7 @@ output of a github-script step. For some workflows, string encoding is preferred
By default, requests made with the `github` instance will not be retried. You can configure this with the `retries` option:
```yaml
- uses: actions/github-script@v8
- uses: actions/github-script@v9
id: my-script
with:
result-encoding: string
@@ -167,7 +183,7 @@ In this example, request failures from `github.rest.issues.get()` will be retrie
You can also configure which status codes should be exempt from retries via the `retry-exempt-status-codes` option:
```yaml
- uses: actions/github-script@v8
- uses: actions/github-script@v9
id: my-script
with:
result-encoding: string
@@ -196,7 +212,7 @@ By default, github-script will use the token provided to your workflow.
```yaml
- name: View context attributes
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: console.log(context)
```
@@ -212,7 +228,7 @@ jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@v9
with:
script: |
github.rest.issues.createComment({
@@ -234,7 +250,7 @@ jobs:
apply-label:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@v9
with:
script: |
github.rest.issues.addLabels({
@@ -256,7 +272,7 @@ jobs:
welcome:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@v9
with:
script: |
// Get a list of all issues created by the PR opener
@@ -301,7 +317,7 @@ jobs:
diff:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@v9
with:
script: |
const diff_url = context.payload.pull_request.diff_url
@@ -325,7 +341,7 @@ jobs:
list-issues:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@v9
with:
script: |
const query = `query($owner:String!, $name:String!, $label:String!) {
@@ -359,7 +375,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/github-script@v8
- uses: actions/github-script@v9
with:
script: |
const script = require('./path/to/script.js')
@@ -397,7 +413,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/github-script@v8
- uses: actions/github-script@v9
env:
SHA: '${{env.parentSHA}}'
with:
@@ -441,7 +457,7 @@ jobs:
- run: npm ci
# or one-off:
- run: npm install execa
- uses: actions/github-script@v8
- uses: actions/github-script@v9
with:
script: |
const execa = require('execa')
@@ -471,7 +487,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/github-script@v8
- uses: actions/github-script@v9
with:
script: |
const { default: printStuff } = await import('${{ github.workspace }}/src/print-stuff.js')
@@ -483,21 +499,22 @@ jobs:
If you want type support for your scripts, you could use the command below to install the
`@actions/github-script` type declaration.
```sh
$ npm i -D @actions/github-script@github:actions/github-script
```
And then add the `jsDoc` declaration to your script like this:
```js
// @ts-check
/** @param {import('@actions/github-script').AsyncFunctionArguments} AsyncFunctionArguments */
export default async ({ core, context }) => {
core.debug("Running something at the moment");
return context.actor;
};
export default async ({core, context}) => {
core.debug('Running something at the moment')
return context.actor
}
```
### Using a separate GitHub token
The `GITHUB_TOKEN` used by default is scoped to the current repository, see [Authentication in a workflow](https://docs.github.com/actions/reference/authentication-in-a-workflow).
@@ -506,25 +523,89 @@ If you need access to a different repository or an API that the `GITHUB_TOKEN` d
[Learn more about creating and using encrypted secrets](https://docs.github.com/actions/reference/encrypted-secrets)
```yaml
on:
issues:
types: [opened]
### Creating additional clients with `getOctokit`
jobs:
apply-label:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
with:
github-token: ${{ secrets.MY_PAT }}
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['Triage']
})
The `getOctokit` function is available in the script context and lets you create additional authenticated Octokit clients — useful when you need to interact with the GitHub API using a different token than the one provided to the action (e.g. a GitHub App installation token, a PAT for cross-org access, or a fine-grained token with different permissions).
```js
getOctokit(token)
getOctokit(token, opts)
```
**Parameters:**
| Name | Type | Description |
| ------- | -------- | --------------------------------------------------------------------------------------- |
| `token` | `string` | **Required.** A GitHub token (PAT, GitHub App token, etc.) |
| `opts` | `object` | Optional. Octokit constructor options (e.g. `userAgent`, `baseUrl`, `request`, `retry`) |
The returned client is fully configured with the same plugins as `github` (retry, request-log, proxy support) — you don't need to set those up yourself.
**Option merging behavior:** `request` and `retry` are deep-merged with the action's defaults, so you can override individual fields (e.g. `{request: {timeout: 5000}}`) without losing the inherited retry count or proxy settings. All other top-level options (like `baseUrl` or `userAgent`) are replaced outright if you provide them.
> **Note:** `getOctokit` is injected as a function parameter (like `github`, `context`, `core`, etc.). You cannot redeclare it with `const` or `let` — this will cause a `SyntaxError`. Use `getOctokit` directly, or use `var` if you need to redeclare it. See [V9 breaking changes](#v9) for details.
#### Basic usage — one primary token, one secondary token
```yaml
- uses: actions/github-script@v9
env:
APP_TOKEN: ${{ secrets.MY_APP_TOKEN }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
// `github` uses GITHUB_TOKEN (scoped to this repo)
await github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['triage']
})
// `getOctokit` creates a second client with a different token
const appOctokit = getOctokit(process.env.APP_TOKEN)
await appOctokit.rest.repos.createDispatchEvent({
owner: 'my-org',
repo: 'another-repo',
event_type: 'trigger-deploy'
})
```
#### Multiple clients for cross-org workflows
```yaml
- uses: actions/github-script@v9
env:
ORG_A_TOKEN: ${{ secrets.ORG_A_PAT }}
ORG_B_TOKEN: ${{ secrets.ORG_B_PAT }}
with:
script: |
const orgA = getOctokit(process.env.ORG_A_TOKEN)
const orgB = getOctokit(process.env.ORG_B_TOKEN)
const [repoA, repoB] = await Promise.all([
orgA.rest.repos.get({ owner: 'org-a', repo: 'service' }),
orgB.rest.repos.get({ owner: 'org-b', repo: 'service' })
])
console.log(`Org A: ${repoA.data.full_name}`)
console.log(`Org B: ${repoB.data.full_name}`)
```
#### Custom options
```yaml
- uses: actions/github-script@v9
env:
GHES_TOKEN: ${{ secrets.GHES_PAT }}
with:
script: |
const ghes = getOctokit(process.env.GHES_TOKEN, {
baseUrl: 'https://github.example.com/api/v3'
})
const { data } = await ghes.rest.repos.listForOrg({ org: 'internal' })
console.log(`Found ${data.length} repos on GHES`)
```
### Using exec package
@@ -539,7 +620,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/github-script@v8
- uses: actions/github-script@v9
with:
script: |
const exitCode = await exec.exec('echo', ['hello'])
@@ -557,7 +638,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/github-script@v8
- uses: actions/github-script@v9
with:
script: |
const {

View File

@@ -0,0 +1,102 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {callAsyncFunction} from '../src/async-function'
// Create a mock getOctokit that returns Octokit-like objects.
// Real @actions/github integration is tested in the CI workflow
// (integration.yml test-get-octokit job). Here we verify the
// script context wiring — getOctokit is passed through and
// callable from user scripts.
function mockGetOctokit(token: string, options?: any) {
return {
_token: token,
_options: options,
rest: {
issues: {get: async () => ({data: {id: 1}})},
pulls: {get: async () => ({data: {id: 2}})}
},
graphql: async () => ({}),
request: async () => ({})
}
}
describe('getOctokit integration via callAsyncFunction', () => {
test('getOctokit creates a functional client in script scope', async () => {
const result = await callAsyncFunction(
{getOctokit: mockGetOctokit} as any,
`
const client = getOctokit('fake-token-for-test')
return {
hasRest: typeof client.rest === 'object',
hasGraphql: typeof client.graphql === 'function',
hasRequest: typeof client.request === 'function',
hasIssues: typeof client.rest.issues === 'object',
hasPulls: typeof client.rest.pulls === 'object'
}
`
)
expect(result).toEqual({
hasRest: true,
hasGraphql: true,
hasRequest: true,
hasIssues: true,
hasPulls: true
})
})
test('secondary client is independent from primary github client', async () => {
const primary = mockGetOctokit('primary-token')
const result = await callAsyncFunction(
{github: primary, getOctokit: mockGetOctokit} as any,
`
const secondary = getOctokit('secondary-token')
return {
bothHaveRest: typeof github.rest === 'object' && typeof secondary.rest === 'object',
areDistinct: github !== secondary
}
`
)
expect(result).toEqual({
bothHaveRest: true,
areDistinct: true
})
})
test('getOctokit accepts options for GHES base URL', async () => {
const result = await callAsyncFunction(
{getOctokit: mockGetOctokit} as any,
`
const client = getOctokit('fake-token', {
baseUrl: 'https://ghes.example.com/api/v3'
})
return typeof client.rest === 'object'
`
)
expect(result).toBe(true)
})
test('multiple getOctokit calls produce independent clients with different tokens', async () => {
const result = await callAsyncFunction(
{getOctokit: mockGetOctokit} as any,
`
const clientA = getOctokit('token-a')
const clientB = getOctokit('token-b')
return {
aHasRest: typeof clientA.rest === 'object',
bHasRest: typeof clientB.rest === 'object',
areDistinct: clientA !== clientB
}
`
)
expect(result).toEqual({
aHasRest: true,
bHasRest: true,
areDistinct: true
})
})
})

37360
dist/index.js vendored

File diff suppressed because one or more lines are too long