diff --git a/package-lock.json b/package-lock.json index 76e8212e..a8b8d0e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4322,15 +4322,15 @@ } }, "node_modules/axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.1.tgz", + "integrity": "sha512-WOG+Jj8ZOvR0a3rAn+Tuf1UQJRxw5venr6DgdbJzngJE3qG7X0kL83CZGpdHMxEm+ZK3seAbvFsw4FfOfP9vxg==", "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" } }, "node_modules/axobject-query": { @@ -7235,9 +7235,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "dev": true, "funding": [ { @@ -13278,9 +13278,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -13570,11 +13570,14 @@ "license": "MIT" }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/pure-rand": { "version": "6.1.0", diff --git a/packages/github/RELEASES.md b/packages/github/RELEASES.md index d56d82d4..e9d69fbb 100644 --- a/packages/github/RELEASES.md +++ b/packages/github/RELEASES.md @@ -1,5 +1,9 @@ # @actions/github Releases +### 9.1.0 + +- Append `actions_orchestration_id` to user-agent when the `ACTIONS_ORCHESTRATION_ID` environment variable is set [#2364](https://github.com/actions/toolkit/pull/2364) + ### 9.0.0 - **Breaking change**: Package is now ESM-only diff --git a/packages/github/__tests__/orchestration.test.ts b/packages/github/__tests__/orchestration.test.ts new file mode 100644 index 00000000..044fd458 --- /dev/null +++ b/packages/github/__tests__/orchestration.test.ts @@ -0,0 +1,130 @@ +import {getOctokitOptions, getUserAgentWithOrchestrationId} from '../src/utils' +import {getUserAgentWithOrchestrationId as internalGetUserAgentWithOrchestrationId} from '../src/internal/utils' + +describe('orchestration ID support', () => { + let originalOrchId: string | undefined + + beforeEach(() => { + originalOrchId = process.env['ACTIONS_ORCHESTRATION_ID'] + delete process.env['ACTIONS_ORCHESTRATION_ID'] + }) + + afterEach(() => { + if (originalOrchId !== undefined) { + process.env['ACTIONS_ORCHESTRATION_ID'] = originalOrchId + } else { + delete process.env['ACTIONS_ORCHESTRATION_ID'] + } + }) + + describe('getUserAgentWithOrchestrationId', () => { + it('returns undefined when env var is not set and no base user agent', () => { + expect(getUserAgentWithOrchestrationId()).toBeUndefined() + }) + + it('returns base user agent unchanged when env var is not set', () => { + expect(getUserAgentWithOrchestrationId('my-app')).toBe('my-app') + }) + + it('returns orchestration ID without base when env var is set and no base', () => { + process.env['ACTIONS_ORCHESTRATION_ID'] = 'abc-123' + expect(getUserAgentWithOrchestrationId()).toBe( + 'actions_orchestration_id/abc-123' + ) + }) + + it('appends orchestration ID to base user agent', () => { + process.env['ACTIONS_ORCHESTRATION_ID'] = 'abc-123' + expect(getUserAgentWithOrchestrationId('my-app')).toBe( + 'my-app actions_orchestration_id/abc-123' + ) + }) + + it('sanitizes special characters in orchestration ID', () => { + process.env['ACTIONS_ORCHESTRATION_ID'] = 'id with spaces/and$pecial!' + expect(getUserAgentWithOrchestrationId('my-app')).toBe( + 'my-app actions_orchestration_id/id_with_spaces_and_pecial_' + ) + }) + + it('preserves allowed characters in orchestration ID', () => { + process.env['ACTIONS_ORCHESTRATION_ID'] = + 'valid_id-with.allowed_chars.123' + expect(getUserAgentWithOrchestrationId()).toBe( + 'actions_orchestration_id/valid_id-with.allowed_chars.123' + ) + }) + + it('ignores whitespace-only orchestration ID', () => { + process.env['ACTIONS_ORCHESTRATION_ID'] = ' ' + expect(getUserAgentWithOrchestrationId('my-app')).toBe('my-app') + }) + + it('does not duplicate orchestration ID if already present in base', () => { + process.env['ACTIONS_ORCHESTRATION_ID'] = 'abc-123' + const alreadyTagged = 'my-app actions_orchestration_id/abc-123' + expect(getUserAgentWithOrchestrationId(alreadyTagged)).toBe(alreadyTagged) + }) + }) + + describe('public re-export', () => { + it('exports getUserAgentWithOrchestrationId from utils (public API)', () => { + expect(getUserAgentWithOrchestrationId).toBe( + internalGetUserAgentWithOrchestrationId + ) + }) + }) + + describe('getOctokitOptions', () => { + it('sets userAgent when ACTIONS_ORCHESTRATION_ID is set', () => { + process.env['ACTIONS_ORCHESTRATION_ID'] = 'test-orch-id' + const opts = getOctokitOptions('fake-token') + expect(opts.userAgent).toBe('actions_orchestration_id/test-orch-id') + }) + + it('does not set userAgent when ACTIONS_ORCHESTRATION_ID is not set', () => { + const opts = getOctokitOptions('fake-token') + expect(opts.userAgent).toBeUndefined() + }) + + it('preserves and appends to caller-provided userAgent', () => { + process.env['ACTIONS_ORCHESTRATION_ID'] = 'test-orch-id' + const opts = getOctokitOptions('fake-token', { + userAgent: 'custom-agent/1.0' + }) + expect(opts.userAgent).toBe( + 'custom-agent/1.0 actions_orchestration_id/test-orch-id' + ) + }) + + it('leaves caller-provided userAgent intact when env var is not set', () => { + const opts = getOctokitOptions('fake-token', { + userAgent: 'custom-agent/1.0' + }) + expect(opts.userAgent).toBe('custom-agent/1.0') + }) + + it('does not mutate the original options object', () => { + process.env['ACTIONS_ORCHESTRATION_ID'] = 'test-orch-id' + const original = {userAgent: 'original/1.0'} + getOctokitOptions('fake-token', original) + expect(original.userAgent).toBe('original/1.0') + }) + + it('sanitizes special characters through getOctokitOptions', () => { + process.env['ACTIONS_ORCHESTRATION_ID'] = 'bad chars here!' + const opts = getOctokitOptions('fake-token') + expect(opts.userAgent).toBe('actions_orchestration_id/bad_chars_here_') + }) + + it('does not duplicate orchestration ID when caller already applied it', () => { + process.env['ACTIONS_ORCHESTRATION_ID'] = 'test-orch-id' + const opts = getOctokitOptions('fake-token', { + userAgent: 'my-app actions_orchestration_id/test-orch-id' + }) + expect(opts.userAgent).toBe( + 'my-app actions_orchestration_id/test-orch-id' + ) + }) + }) +}) diff --git a/packages/github/package-lock.json b/packages/github/package-lock.json index 04cb0338..536fcf61 100644 --- a/packages/github/package-lock.json +++ b/packages/github/package-lock.json @@ -1,12 +1,12 @@ { "name": "@actions/github", - "version": "9.0.0", + "version": "9.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@actions/github", - "version": "9.0.0", + "version": "9.1.0", "license": "MIT", "dependencies": { "@actions/http-client": "^3.0.2", diff --git a/packages/github/package.json b/packages/github/package.json index 21345966..c3d6fa18 100644 --- a/packages/github/package.json +++ b/packages/github/package.json @@ -1,6 +1,6 @@ { "name": "@actions/github", - "version": "9.0.0", + "version": "9.1.0", "description": "Actions github lib", "keywords": [ "github", diff --git a/packages/github/src/internal/utils.ts b/packages/github/src/internal/utils.ts index c97db185..03c5822a 100644 --- a/packages/github/src/internal/utils.ts +++ b/packages/github/src/internal/utils.ts @@ -42,3 +42,17 @@ export function getProxyFetch(destinationUrl): typeof fetch { export function getApiBaseUrl(): string { return process.env['GITHUB_API_URL'] || 'https://api.github.com' } + +export function getUserAgentWithOrchestrationId( + baseUserAgent?: string +): string | undefined { + const orchId = process.env['ACTIONS_ORCHESTRATION_ID']?.trim() + if (orchId) { + const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, '_') + const tag = `actions_orchestration_id/${sanitizedId}` + if (baseUserAgent?.includes(tag)) return baseUserAgent + const ua = baseUserAgent ? `${baseUserAgent} ` : '' + return `${ua}${tag}` + } + return baseUserAgent +} diff --git a/packages/github/src/utils.ts b/packages/github/src/utils.ts index e06c4f98..d783278d 100644 --- a/packages/github/src/utils.ts +++ b/packages/github/src/utils.ts @@ -23,6 +23,8 @@ export const GitHub = Octokit.plugin( paginateRest ).defaults(defaults) +export {getUserAgentWithOrchestrationId} from './internal/utils.js' + /** * Convience function to correctly format Octokit Options to pass into the constructor. * @@ -41,5 +43,13 @@ export function getOctokitOptions( opts.auth = auth } + // Orchestration ID + const userAgent = Utils.getUserAgentWithOrchestrationId( + opts.userAgent as string | undefined + ) + if (userAgent) { + opts.userAgent = userAgent + } + return opts } diff --git a/packages/glob/package-lock.json b/packages/glob/package-lock.json index 50cbc17c..55fa5b92 100644 --- a/packages/glob/package-lock.json +++ b/packages/glob/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@actions/core": "^3.0.0", - "minimatch": "^10.2.4" + "minimatch": "^10.2.5" } }, "node_modules/@actions/core": { @@ -58,9 +58,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -70,12 +70,12 @@ } }, "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" diff --git a/packages/glob/package.json b/packages/glob/package.json index b40834b7..b0104c4b 100644 --- a/packages/glob/package.json +++ b/packages/glob/package.json @@ -45,6 +45,6 @@ }, "dependencies": { "@actions/core": "^3.0.0", - "minimatch": "^10.2.4" + "minimatch": "^10.2.5" } } diff --git a/packages/http-client/package-lock.json b/packages/http-client/package-lock.json index 2a625161..41352196 100644 --- a/packages/http-client/package-lock.json +++ b/packages/http-client/package-lock.json @@ -232,9 +232,9 @@ } }, "node_modules/undici": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", - "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "version": "6.24.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.0.tgz", + "integrity": "sha512-lVLNosgqo5EkGqh5XUDhGfsMSoO8K0BAN0TyJLvwNRSl4xWGZlCVYsAIpa/OpA3TvmnM01GWcoKmc3ZWo5wKKA==", "license": "MIT", "engines": { "node": ">=18.17"