Compare commits

..
Author SHA1 Message Date
copilot-swe-agent[bot]andangel-jiakou 831f1a15f4 Fix integration test: update user-agent validation to allow optional orchestration ID
Co-authored-by: angel-jiakou <115738347+angel-jiakou@users.noreply.github.com>
Agent-Logs-Url: https://github.com/actions/github-script/sessions/d3f337be-6e6b-4278-a1da-9baf8f3c0263
2026-03-23 20:39:06 +00:00
copilot-swe-agent[bot] 3841a7fa4e Initial plan 2026-03-23 20:35:56 +00:00
copilot-swe-agent[bot]andangel-jiakou 4bcf639368 Upgrade @actions/github to v9, @octokit/core to v7, and related plugins
- @actions/github: ^6.0.0 → ^9.0.0
- @octokit/core: ^5.0.1 → ^7.0.0
- @octokit/plugin-request-log: ^4.0.0 → ^6.0.0
- @octokit/plugin-retry: ^6.0.1 → ^8.0.0
- Update tsconfig.json to use moduleResolution: "bundler" for ESM exports map support
- Update import paths for new package structures
- Update build:types script for compatible compiler options

Co-authored-by: angel-jiakou <115738347+angel-jiakou@users.noreply.github.com>
Agent-Logs-Url: https://github.com/actions/github-script/sessions/17de5ca1-8bdc-41e4-a06d-ab2d8c2e6e8c
2026-03-23 20:26:01 +00:00
copilot-swe-agent[bot] 179b645869 Initial plan 2026-03-23 20:17:28 +00:00
33 changed files with 65 additions and 827 deletions

No files matched your search

+11 -46
View File
@@ -42,8 +42,7 @@ jobs:
result-encoding: string
- run: |
echo "- Validating relative require output"
expected="@actions/github-script"
if [[ "${{steps.relative-require.outputs.result}}" != "$expected" ]]; then
if [[ "${{steps.relative-require.outputs.result}}" != "@actions/github-script" ]]; then
echo $'::error::\u274C' "Expected '$expected', got ${{steps.relative-require.outputs.result}}"
exit 1
fi
@@ -155,54 +154,22 @@ 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"
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"
actual="${{steps.user-agent-default.outputs.result}}"
if [[ ! "$actual" =~ ^actions/github-script([[:space:]]actions_orchestration_id/[^[:space:]]+)?[[:space:]]octokit-core\.js/ ]]; then
echo $'::error::\u274C' "Expected user-agent to match 'actions/github-script [actions_orchestration_id/...] octokit-core.js/...', got $actual"
exit 1
fi
echo "- Validating user-agent set to a value"
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"
actual="${{steps.user-agent-set.outputs.result}}"
if [[ ! "$actual" =~ ^foobar([[:space:]]actions_orchestration_id/[^[:space:]]+)?[[:space:]]octokit-core\.js/ ]]; then
echo $'::error::\u274C' "Expected user-agent to match 'foobar [actions_orchestration_id/...] octokit-core.js/...', got $actual"
exit 1
fi
echo "- Validating user-agent set to an empty string"
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}}"
actual="${{steps.user-agent-empty.outputs.result}}"
if [[ ! "$actual" =~ ^actions/github-script([[:space:]]actions_orchestration_id/[^[:space:]]+)?[[:space:]]octokit-core\.js/ ]]; then
echo $'::error::\u274C' "Expected user-agent to match 'actions/github-script [actions_orchestration_id/...] octokit-core.js/...', got $actual"
exit 1
fi
echo $'\u2705 Test passed' | tee -a $GITHUB_STEP_SUMMARY
@@ -211,9 +178,7 @@ jobs:
strategy:
matrix:
environment: ['', 'debug-integration-test']
environment:
name: ${{ matrix.environment }}
deployment: false
environment: ${{ matrix.environment }}
name: "Integration test: debug option (runner.debug mode ${{ matrix.environment && 'enabled' || 'disabled' }})"
runs-on: ubuntu-latest
steps:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+35 -116
View File
@@ -38,7 +38,6 @@ 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
@@ -54,21 +53,6 @@ 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
@@ -106,16 +90,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@v9
- uses: actions/github-script@v8
env:
TITLE: ${{ github.event.pull_request.title }}
with:
@@ -134,7 +118,7 @@ The return value of the script will be in the step's outputs under the
"result" key.
```yaml
- uses: actions/github-script@v9
- uses: actions/github-script@v8
id: set-result
with:
script: return "Hello!"
@@ -153,7 +137,7 @@ output of a github-script step. For some workflows, string encoding is preferred
`result-encoding` input:
```yaml
- uses: actions/github-script@v9
- uses: actions/github-script@v8
id: my-script
with:
result-encoding: string
@@ -165,7 +149,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@v9
- uses: actions/github-script@v8
id: my-script
with:
result-encoding: string
@@ -183,7 +167,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@v9
- uses: actions/github-script@v8
id: my-script
with:
result-encoding: string
@@ -212,7 +196,7 @@ By default, github-script will use the token provided to your workflow.
```yaml
- name: View context attributes
uses: actions/github-script@v9
uses: actions/github-script@v8
with:
script: console.log(context)
```
@@ -228,7 +212,7 @@ jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v9
- uses: actions/github-script@v8
with:
script: |
github.rest.issues.createComment({
@@ -250,7 +234,7 @@ jobs:
apply-label:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v9
- uses: actions/github-script@v8
with:
script: |
github.rest.issues.addLabels({
@@ -272,7 +256,7 @@ jobs:
welcome:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v9
- uses: actions/github-script@v8
with:
script: |
// Get a list of all issues created by the PR opener
@@ -317,7 +301,7 @@ jobs:
diff:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v9
- uses: actions/github-script@v8
with:
script: |
const diff_url = context.payload.pull_request.diff_url
@@ -341,7 +325,7 @@ jobs:
list-issues:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v9
- uses: actions/github-script@v8
with:
script: |
const query = `query($owner:String!, $name:String!, $label:String!) {
@@ -375,7 +359,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/github-script@v9
- uses: actions/github-script@v8
with:
script: |
const script = require('./path/to/script.js')
@@ -413,7 +397,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/github-script@v9
- uses: actions/github-script@v8
env:
SHA: '${{env.parentSHA}}'
with:
@@ -457,7 +441,7 @@ jobs:
- run: npm ci
# or one-off:
- run: npm install execa
- uses: actions/github-script@v9
- uses: actions/github-script@v8
with:
script: |
const execa = require('execa')
@@ -487,7 +471,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/github-script@v9
- uses: actions/github-script@v8
with:
script: |
const { default: printStuff } = await import('${{ github.workspace }}/src/print-stuff.js')
@@ -499,22 +483,21 @@ 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
}
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).
@@ -523,89 +506,25 @@ 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)
### Creating additional clients with `getOctokit`
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 }}
on:
issues:
types: [opened]
jobs:
apply-label:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
github-token: ${{ secrets.MY_PAT }}
script: |
// `github` uses GITHUB_TOKEN (scoped to this repo)
await github.rest.issues.addLabels({
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['triage']
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
@@ -620,7 +539,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/github-script@v9
- uses: actions/github-script@v8
with:
script: |
const exitCode = await exec.exec('echo', ['hello'])
@@ -638,7 +557,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/github-script@v9
- uses: actions/github-script@v8
with:
script: |
const {
-106
View File
@@ -8,94 +8,6 @@ describe('callAsyncFunction', () => {
expect(result).toEqual('bar')
})
test('passes getOctokit through the script context', async () => {
const getOctokit = jest.fn().mockReturnValue('secondary-client')
const result = await callAsyncFunction(
{getOctokit} as any,
"return getOctokit('token')"
)
expect(getOctokit).toHaveBeenCalledWith('token')
expect(result).toEqual('secondary-client')
})
test('getOctokit creates client independent from github', async () => {
const github = {rest: {issues: 'primary'}}
const getOctokit = jest.fn().mockReturnValue({rest: {issues: 'secondary'}})
const result = await callAsyncFunction(
{github, getOctokit} as any,
`
const secondary = getOctokit('other-token')
return {
primary: github.rest.issues,
secondary: secondary.rest.issues,
different: github !== secondary
}
`
)
expect(result).toEqual({
primary: 'primary',
secondary: 'secondary',
different: true
})
expect(getOctokit).toHaveBeenCalledWith('other-token')
})
test('getOctokit passes options through', async () => {
const getOctokit = jest.fn().mockReturnValue('client-with-opts')
const result = await callAsyncFunction(
{getOctokit} as any,
`return getOctokit('my-token', { baseUrl: 'https://ghes.example.com/api/v3' })`
)
expect(getOctokit).toHaveBeenCalledWith('my-token', {
baseUrl: 'https://ghes.example.com/api/v3'
})
expect(result).toEqual('client-with-opts')
})
test('getOctokit supports plugins', async () => {
const getOctokit = jest.fn().mockReturnValue('client-with-plugins')
const result = await callAsyncFunction(
{getOctokit} as any,
`return getOctokit('my-token', { previews: ['v3'] }, 'pluginA', 'pluginB')`
)
expect(getOctokit).toHaveBeenCalledWith(
'my-token',
{previews: ['v3']},
'pluginA',
'pluginB'
)
expect(result).toEqual('client-with-plugins')
})
test('multiple getOctokit calls produce independent clients', async () => {
const getOctokit = jest
.fn()
.mockReturnValueOnce({id: 'client-a'})
.mockReturnValueOnce({id: 'client-b'})
const result = await callAsyncFunction(
{getOctokit} as any,
`
const a = getOctokit('token-a')
const b = getOctokit('token-b')
return { a: a.id, b: b.id, different: a !== b }
`
)
expect(getOctokit).toHaveBeenCalledTimes(2)
expect(getOctokit).toHaveBeenNthCalledWith(1, 'token-a')
expect(getOctokit).toHaveBeenNthCalledWith(2, 'token-b')
expect(result).toEqual({a: 'client-a', b: 'client-b', different: true})
})
test('throws on ReferenceError', async () => {
expect.assertions(1)
@@ -113,22 +25,4 @@ describe('callAsyncFunction', () => {
test('can access console', async () => {
await callAsyncFunction({} as any, 'console')
})
test('injected names are accessible when not redeclared', async () => {
const getOctokit = jest.fn().mockReturnValue('from-injected')
const result = await callAsyncFunction(
{getOctokit} as any,
`return getOctokit('token')`
)
expect(result).toEqual('from-injected')
expect(getOctokit).toHaveBeenCalledWith('token')
})
test('syntax errors in user code still throw', () => {
expect(() => callAsyncFunction({} as any, 'const x = {')).toThrow(
SyntaxError
)
})
})
@@ -1,288 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {createConfiguredGetOctokit} from '../src/create-configured-getoctokit'
describe('createConfiguredGetOctokit', () => {
const mockRetryPlugin = jest.fn()
const mockRequestLogPlugin = jest.fn()
function makeMockGetOctokit() {
return jest.fn().mockReturnValue('mock-client')
}
test('passes token and merged defaults to underlying getOctokit', () => {
const raw = makeMockGetOctokit()
const defaults = {
userAgent: 'actions/github-script actions_orchestration_id/abc',
retry: {enabled: true},
request: {retries: 3}
}
const wrapped = createConfiguredGetOctokit(
raw as any,
defaults,
mockRetryPlugin,
mockRequestLogPlugin
)
wrapped('my-token' as any)
expect(raw).toHaveBeenCalledWith(
'my-token',
expect.objectContaining({
userAgent: 'actions/github-script actions_orchestration_id/abc',
retry: {enabled: true},
request: {retries: 3}
}),
mockRetryPlugin,
mockRequestLogPlugin
)
})
test('user options override top-level defaults', () => {
const raw = makeMockGetOctokit()
const defaults = {
userAgent: 'default-agent',
previews: ['v3']
}
const wrapped = createConfiguredGetOctokit(raw as any, defaults)
wrapped('tok' as any, {userAgent: 'custom-agent'} as any)
expect(raw).toHaveBeenCalledWith(
'tok',
expect.objectContaining({userAgent: 'custom-agent', previews: ['v3']})
)
})
test('deep-merges request so partial overrides preserve retries', () => {
const raw = makeMockGetOctokit()
const defaults = {
request: {retries: 3, agent: 'proxy-agent', fetch: 'proxy-fetch'}
}
const wrapped = createConfiguredGetOctokit(raw as any, defaults)
wrapped('tok' as any, {request: {timeout: 5000}} as any)
expect(raw).toHaveBeenCalledWith(
'tok',
expect.objectContaining({
request: {
retries: 3,
agent: 'proxy-agent',
fetch: 'proxy-fetch',
timeout: 5000
}
})
)
})
test('deep-merges retry so partial overrides preserve existing settings', () => {
const raw = makeMockGetOctokit()
const defaults = {
retry: {enabled: true, retries: 3}
}
const wrapped = createConfiguredGetOctokit(raw as any, defaults)
wrapped('tok' as any, {retry: {retries: 5}} as any)
expect(raw).toHaveBeenCalledWith(
'tok',
expect.objectContaining({
retry: {enabled: true, retries: 5}
})
)
})
test('user can override request.retries explicitly', () => {
const raw = makeMockGetOctokit()
const defaults = {request: {retries: 3}}
const wrapped = createConfiguredGetOctokit(raw as any, defaults)
wrapped('tok' as any, {request: {retries: 0}} as any)
expect(raw).toHaveBeenCalledWith(
'tok',
expect.objectContaining({request: {retries: 0}})
)
})
test('user plugins are appended after default plugins', () => {
const raw = makeMockGetOctokit()
const customPlugin = jest.fn()
const wrapped = createConfiguredGetOctokit(
raw as any,
{},
mockRetryPlugin,
mockRequestLogPlugin
)
wrapped('tok' as any, {} as any, customPlugin as any)
expect(raw).toHaveBeenCalledWith(
'tok',
expect.any(Object),
mockRetryPlugin,
mockRequestLogPlugin,
customPlugin
)
})
test('duplicate plugins are deduplicated', () => {
const raw = makeMockGetOctokit()
const wrapped = createConfiguredGetOctokit(
raw as any,
{},
mockRetryPlugin,
mockRequestLogPlugin
)
// User passes retry again — should not duplicate
wrapped('tok' as any, {} as any, mockRetryPlugin as any)
expect(raw).toHaveBeenCalledWith(
'tok',
expect.any(Object),
mockRetryPlugin,
mockRequestLogPlugin
)
})
test('applies defaults when no user options provided', () => {
const raw = makeMockGetOctokit()
const defaults = {
userAgent: 'actions/github-script',
retry: {enabled: true},
request: {retries: 3}
}
const wrapped = createConfiguredGetOctokit(
raw as any,
defaults,
mockRetryPlugin
)
wrapped('tok' as any)
expect(raw).toHaveBeenCalledWith(
'tok',
{
userAgent: 'actions/github-script',
retry: {enabled: true},
request: {retries: 3}
},
mockRetryPlugin
)
})
test('baseUrl: undefined from user does not clobber default', () => {
const raw = makeMockGetOctokit()
const defaults = {baseUrl: 'https://ghes.example.com/api/v3'}
const wrapped = createConfiguredGetOctokit(raw as any, defaults)
wrapped('tok' as any, {baseUrl: undefined} as any)
const calledOpts = raw.mock.calls[0][1]
expect(calledOpts.baseUrl).toBe('https://ghes.example.com/api/v3')
})
test('undefined values in nested request are stripped', () => {
const raw = makeMockGetOctokit()
const defaults = {request: {retries: 3, agent: 'proxy'}}
const wrapped = createConfiguredGetOctokit(raw as any, defaults)
wrapped('tok' as any, {request: {retries: undefined, timeout: 5000}} as any)
const calledOpts = raw.mock.calls[0][1]
expect(calledOpts.request).toEqual({
retries: 3,
agent: 'proxy',
timeout: 5000
})
})
test('undefined values in nested retry are stripped', () => {
const raw = makeMockGetOctokit()
const defaults = {retry: {enabled: true, retries: 3}}
const wrapped = createConfiguredGetOctokit(raw as any, defaults)
wrapped('tok' as any, {retry: {enabled: undefined, retries: 5}} as any)
const calledOpts = raw.mock.calls[0][1]
expect(calledOpts.retry).toEqual({enabled: true, retries: 5})
})
test('each call creates an independent client', () => {
const raw = jest
.fn()
.mockReturnValueOnce('client-a')
.mockReturnValueOnce('client-b')
const wrapped = createConfiguredGetOctokit(raw as any, {})
const a = wrapped('token-a' as any)
const b = wrapped('token-b' as any)
expect(a).toBe('client-a')
expect(b).toBe('client-b')
expect(raw).toHaveBeenCalledTimes(2)
})
test('does not mutate defaultOptions between calls', () => {
const raw = makeMockGetOctokit()
const defaults = {
request: {retries: 3},
retry: {enabled: true}
}
const originalDefaults = JSON.parse(JSON.stringify(defaults))
const wrapped = createConfiguredGetOctokit(raw as any, defaults)
wrapped(
'tok' as any,
{request: {timeout: 5000}, retry: {retries: 10}} as any
)
wrapped('tok' as any, {request: {timeout: 9000}} as any)
expect(defaults).toEqual(originalDefaults)
})
test('undefined values in defaults are stripped (e.g. log: undefined)', () => {
const raw = makeMockGetOctokit()
const defaults = {
userAgent: 'actions/github-script',
log: undefined,
previews: undefined,
retry: {enabled: true}
}
const wrapped = createConfiguredGetOctokit(raw as any, defaults)
wrapped('tok' as any)
const calledOpts = raw.mock.calls[0][1]
expect(calledOpts.userAgent).toBe('actions/github-script')
expect(calledOpts.retry).toEqual({enabled: true})
// undefined defaults should be stripped, not passed through
expect('log' in calledOpts).toBe(false)
expect('previews' in calledOpts).toBe(false)
})
test('falsy-but-valid values are preserved, only undefined is stripped', () => {
const raw = makeMockGetOctokit()
const defaults = {baseUrl: 'https://ghes.example.com/api/v3'}
const wrapped = createConfiguredGetOctokit(raw as any, defaults)
wrapped(
'tok' as any,
{
log: null,
retries: 0,
debug: false,
userAgent: ''
} as any
)
const calledOpts = raw.mock.calls[0][1]
expect(calledOpts.log).toBeNull()
expect(calledOpts.retries).toBe(0)
expect(calledOpts.debug).toBe(false)
expect(calledOpts.userAgent).toBe('')
expect(calledOpts.baseUrl).toBe('https://ghes.example.com/api/v3')
})
})
-102
View File
@@ -1,102 +0,0 @@
/* 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
})
})
})
+2 -74
View File
@@ -60602,19 +60602,6 @@ function getProxyFetch(destinationUrl) {
function getApiBaseUrl() {
return process.env['GITHUB_API_URL'] || 'https://api.github.com';
}
function getUserAgentWithOrchestrationId(baseUserAgent) {
var _a;
const orchId = (_a = process.env['ACTIONS_ORCHESTRATION_ID']) === null || _a === void 0 ? void 0 : _a.trim();
if (orchId) {
const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, '_');
const tag = `actions_orchestration_id/${sanitizedId}`;
if (baseUserAgent === null || baseUserAgent === void 0 ? void 0 : baseUserAgent.includes(tag))
return baseUserAgent;
const ua = baseUserAgent ? `${baseUserAgent} ` : '';
return `${ua}${tag}`;
}
return baseUserAgent;
}
//# sourceMappingURL=utils.js.map
;// CONCATENATED MODULE: ./node_modules/universal-user-agent/index.js
function getUserAgent() {
@@ -64787,7 +64774,6 @@ const defaults = {
}
};
const GitHub = Octokit.plugin(restEndpointMethods, paginateRest).defaults(defaults);
/**
* Convience function to correctly format Octokit Options to pass into the constructor.
*
@@ -64801,11 +64787,6 @@ function getOctokitOptions(token, options) {
if (auth) {
opts.auth = auth;
}
// Orchestration ID
const userAgent = getUserAgentWithOrchestrationId(opts.userAgent);
if (userAgent) {
opts.userAgent = userAgent;
}
return opts;
}
//# sourceMappingURL=utils.js.map
@@ -64950,53 +64931,6 @@ function callAsyncFunction(args, source) {
return fn(...Object.values(args));
}
;// CONCATENATED MODULE: ./src/create-configured-getoctokit.ts
/**
* Strip keys whose value is `undefined` so they don't clobber defaults
* during object spread (e.g. `{baseUrl: undefined}` would wipe a GHES URL).
*/
function stripUndefined(obj) {
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
}
/**
* Creates a wrapped getOctokit that inherits default options and plugins.
* Secondary clients created via the wrapper get the same retry, logging,
* orchestration ID, and retries count as the pre-built `github` client.
*
* - `request` and `retry` are deep-merged so partial overrides
* (e.g. `{request: {timeout: 5000}}`) don't clobber inherited values.
* - `undefined` values in both default and user options are stripped to prevent
* accidental clobbering (e.g. GHES `baseUrl`, or `log: undefined` from defaults).
* - Default plugins (retry, requestLog) are always included; duplicates are skipped.
*/
function createConfiguredGetOctokit(rawGetOctokit, defaultOptions,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...defaultPlugins) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return ((token, options, ...plugins) => {
const cleanDefaults = stripUndefined(defaultOptions);
const userOpts = stripUndefined(options ?? {});
const defaultRequest = cleanDefaults.request ?? {};
const userRequest = stripUndefined(userOpts.request ?? {});
const defaultRetry = cleanDefaults.retry ?? {};
const userRetry = stripUndefined(userOpts.retry ?? {});
const merged = {
...cleanDefaults,
...userOpts,
request: { ...defaultRequest, ...userRequest },
retry: { ...defaultRetry, ...userRetry }
};
// Deduplicate: default plugins first, then user plugins that aren't already present
const allPlugins = [...defaultPlugins];
for (const plugin of plugins) {
if (!allPlugins.includes(plugin)) {
allPlugins.push(plugin);
}
}
return rawGetOctokit(token, merged, ...allPlugins);
});
}
;// CONCATENATED MODULE: ./src/retry-options.ts
function getRetryOptions(retries, exemptStatusCodes, defaultOptions) {
@@ -65064,7 +64998,6 @@ const wrapRequire = new Proxy(require, {
process.on('unhandledRejection', handleError);
main().catch(handleError);
async function main() {
@@ -65077,7 +65010,7 @@ async function main() {
const exemptStatusCodes = parseNumberArray(core.getInput('retry-exempt-status-codes'));
const [retryOpts, requestOpts] = getRetryOptions(retries, exemptStatusCodes, defaults);
const baseUserAgent = userAgent || 'actions/github-script';
const finalUserAgent = main_getUserAgentWithOrchestrationId(baseUserAgent);
const finalUserAgent = getUserAgentWithOrchestrationId(baseUserAgent);
const opts = {
log: debug ? console : undefined,
userAgent: finalUserAgent,
@@ -65092,17 +65025,12 @@ async function main() {
}
const github = getOctokit(token, opts, retry, requestLog);
const script = core.getInput('script', { required: true });
// Wrap getOctokit so secondary clients inherit retry, logging,
// orchestration ID, and the action's retries input.
// Deep-copy opts to prevent shared references with the primary client.
const configuredGetOctokit = createConfiguredGetOctokit(getOctokit, { ...opts, retry: { ...opts.retry }, request: { ...opts.request } }, retry, requestLog);
// Using property/value shorthand on `require` (e.g. `{require}`) causes compilation errors.
const result = await callAsyncFunction({
require: wrapRequire,
__original_require__: require,
github,
octokit: github,
getOctokit: configuredGetOctokit,
context: github_context,
core: core,
exec: exec,
@@ -65134,7 +65062,7 @@ function handleError(err) {
* @param userAgent The base user agent string
* @returns The user agent string with orchestration ID appended if ACTIONS_ORCHESTRATION_ID is set
*/
function main_getUserAgentWithOrchestrationId(userAgent) {
function getUserAgentWithOrchestrationId(userAgent) {
const orchestrationId = process.env['ACTIONS_ORCHESTRATION_ID'];
if (!orchestrationId) {
return userAgent;
+8 -8
View File
@@ -1,12 +1,12 @@
{
"name": "@actions/github-script",
"version": "9.0.0",
"version": "7.0.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@actions/github-script",
"version": "9.0.0",
"version": "7.0.1",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.10.1",
@@ -65,9 +65,9 @@
}
},
"node_modules/@actions/github": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-9.1.0.tgz",
"integrity": "sha512-u0hDGQeCS+7VNoLA8hYG65RLdPLMaPGfka0sZ0up7P0AiShqfX6xcuXNteGkQ7X7Tod7AMNwHd4p7DS63i8zzA==",
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-9.0.0.tgz",
"integrity": "sha512-yJ0RoswsAaKcvkmpCE4XxBRiy/whH2SdTBHWzs0gi4wkqTDhXMChjSdqBz/F4AeiDlP28rQqL33iHb+kjAMX6w==",
"license": "MIT",
"dependencies": {
"@actions/http-client": "^3.0.2",
@@ -7369,9 +7369,9 @@
}
},
"@actions/github": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-9.1.0.tgz",
"integrity": "sha512-u0hDGQeCS+7VNoLA8hYG65RLdPLMaPGfka0sZ0up7P0AiShqfX6xcuXNteGkQ7X7Tod7AMNwHd4p7DS63i8zzA==",
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-9.0.0.tgz",
"integrity": "sha512-yJ0RoswsAaKcvkmpCE4XxBRiy/whH2SdTBHWzs0gi4wkqTDhXMChjSdqBz/F4AeiDlP28rQqL33iHb+kjAMX6w==",
"requires": {
"@actions/http-client": "^3.0.2",
"@octokit/core": "^7.0.6",
+1 -1
View File
@@ -4,7 +4,7 @@
"engines": {
"node": ">=24"
},
"version": "9.0.0",
"version": "7.0.1",
"author": "GitHub",
"license": "MIT",
"main": "dist/index.js",
+4 -5
View File
@@ -1,17 +1,16 @@
import * as core from '@actions/core'
import * as exec from '@actions/exec'
import type {context, getOctokit} from '@actions/github'
import {context as _ghContext, GitHub} from '@actions/github/lib/utils'
import * as glob from '@actions/glob'
import * as io from '@actions/io'
const AsyncFunction = Object.getPrototypeOf(async () => null).constructor
export declare type AsyncFunctionArguments = {
context: typeof context
context: typeof _ghContext
core: typeof core
github: ReturnType<typeof getOctokit>
octokit: ReturnType<typeof getOctokit>
getOctokit: typeof getOctokit
github: InstanceType<typeof GitHub>
octokit: InstanceType<typeof GitHub>
exec: typeof exec
glob: typeof glob
io: typeof io
-64
View File
@@ -1,64 +0,0 @@
import {getOctokit} from '@actions/github'
/**
* Strip keys whose value is `undefined` so they don't clobber defaults
* during object spread (e.g. `{baseUrl: undefined}` would wipe a GHES URL).
*/
function stripUndefined(obj: Record<string, unknown>): Record<string, unknown> {
return Object.fromEntries(
Object.entries(obj).filter(([, v]) => v !== undefined)
)
}
/**
* Creates a wrapped getOctokit that inherits default options and plugins.
* Secondary clients created via the wrapper get the same retry, logging,
* orchestration ID, and retries count as the pre-built `github` client.
*
* - `request` and `retry` are deep-merged so partial overrides
* (e.g. `{request: {timeout: 5000}}`) don't clobber inherited values.
* - `undefined` values in both default and user options are stripped to prevent
* accidental clobbering (e.g. GHES `baseUrl`, or `log: undefined` from defaults).
* - Default plugins (retry, requestLog) are always included; duplicates are skipped.
*/
export function createConfiguredGetOctokit(
rawGetOctokit: typeof getOctokit,
defaultOptions: Record<string, unknown>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...defaultPlugins: any[]
): typeof getOctokit {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return ((token: string, options?: any, ...plugins: any[]) => {
const cleanDefaults = stripUndefined(defaultOptions)
const userOpts = stripUndefined(options ?? {})
const defaultRequest =
(cleanDefaults.request as Record<string, unknown> | undefined) ?? {}
const userRequest = stripUndefined(
(userOpts.request as Record<string, unknown> | undefined) ?? {}
)
const defaultRetry =
(cleanDefaults.retry as Record<string, unknown> | undefined) ?? {}
const userRetry = stripUndefined(
(userOpts.retry as Record<string, unknown> | undefined) ?? {}
)
const merged = {
...cleanDefaults,
...userOpts,
request: {...defaultRequest, ...userRequest},
retry: {...defaultRetry, ...userRetry}
}
// Deduplicate: default plugins first, then user plugins that aren't already present
const allPlugins = [...defaultPlugins]
for (const plugin of plugins) {
if (!allPlugins.includes(plugin)) {
allPlugins.push(plugin)
}
}
return rawGetOctokit(token, merged, ...allPlugins)
}) as typeof getOctokit
}
-12
View File
@@ -8,7 +8,6 @@ import {requestLog} from '@octokit/plugin-request-log'
import {retry} from '@octokit/plugin-retry'
import {RequestRequestOptions} from '@octokit/types'
import {callAsyncFunction} from './async-function'
import {createConfiguredGetOctokit} from './create-configured-getoctokit'
import {RetryOptions, getRetryOptions, parseNumberArray} from './retry-options'
import {wrapRequire} from './wrap-require'
@@ -60,16 +59,6 @@ async function main(): Promise<void> {
const github = getOctokit(token, opts, retry, requestLog)
const script = core.getInput('script', {required: true})
// Wrap getOctokit so secondary clients inherit retry, logging,
// orchestration ID, and the action's retries input.
// Deep-copy opts to prevent shared references with the primary client.
const configuredGetOctokit = createConfiguredGetOctokit(
getOctokit,
{...opts, retry: {...opts.retry}, request: {...opts.request}},
retry,
requestLog
)
// Using property/value shorthand on `require` (e.g. `{require}`) causes compilation errors.
const result = await callAsyncFunction(
{
@@ -77,7 +66,6 @@ async function main(): Promise<void> {
__original_require__: __non_webpack_require__,
github,
octokit: github,
getOctokit: configuredGetOctokit,
context,
core,
exec,
+4 -5
View File
@@ -1,15 +1,14 @@
/// <reference types="node" />
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import type { context, getOctokit } from '@actions/github';
import { context as _ghContext, GitHub } from '@actions/github/lib/utils';
import * as glob from '@actions/glob';
import * as io from '@actions/io';
export declare type AsyncFunctionArguments = {
context: typeof context;
context: typeof _ghContext;
core: typeof core;
github: ReturnType<typeof getOctokit>;
octokit: ReturnType<typeof getOctokit>;
getOctokit: typeof getOctokit;
github: InstanceType<typeof GitHub>;
octokit: InstanceType<typeof GitHub>;
exec: typeof exec;
glob: typeof glob;
io: typeof io;