Compare commits

..
36 changed files with 108 additions and 581 deletions
-5
View File
@@ -57,8 +57,3 @@ This will ask you some questions about the new package. Start with `0.0.0` as th
```
3. Start developing 😄.
## Releasing Packages
For information on how packages are published to npm, including workflow inputs, dist-tags, and safety guards, see the [release documentation](../docs/release.md).
+12 -39
View File
@@ -1,6 +1,6 @@
name: Publish NPM
run-name: Publish NPM - ${{ inputs.package }} from ${{ inputs.branch }}
run-name: Publish NPM - ${{ github.event.inputs.package }}
on:
workflow_dispatch:
@@ -20,21 +20,7 @@ on:
- http-client
- io
- tool-cache
branch:
type: string
required: false
default: 'main'
description: 'Branch to release from'
npm-tag:
type: string
required: false
default: 'latest'
description: 'npm dist-tag for the release. Use "latest" for main branch releases. For non-main branches, use a non-semver tag like "v1-longlived". Semver values (e.g. "5.0.0") are not valid dist-tags and will be rejected by npm.'
test-all:
type: boolean
required: false
default: false
description: 'Run tests for all packages instead of only the package being published'
jobs:
test:
@@ -42,15 +28,13 @@ jobs:
steps:
- name: setup repo
uses: actions/checkout@v6
with:
ref: ${{ inputs.branch }}
uses: actions/checkout@v5
- name: verify package exists
run: ls packages/${{ github.event.inputs.package }}
- name: Set Node.js 24.x
uses: actions/setup-node@v6
uses: actions/setup-node@v5
with:
node-version: 24.x
@@ -64,19 +48,14 @@ jobs:
run: npm run build
- name: test
run: |
if [ "${{ inputs.test-all }}" = "true" ]; then
npm run test
else
npm run test -- --testPathPattern="packages/${{ inputs.package }}"
fi
run: npm run test
- name: pack
run: npm pack
working-directory: packages/${{ github.event.inputs.package }}
- name: upload artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: ${{ github.event.inputs.package }}
path: packages/${{ github.event.inputs.package }}/*.tgz
@@ -91,35 +70,29 @@ jobs:
steps:
- name: Set Node.js 24.x
uses: actions/setup-node@v6
uses: actions/setup-node@v5
with:
node-version: 24.x
- name: download artifact
uses: actions/download-artifact@v8
uses: actions/download-artifact@v4
with:
name: ${{ inputs.package }}
- name: guard against publishing latest from non-main branch
if: inputs.branch != 'main' && inputs.npm-tag == 'latest'
run: |
echo "::error::Publishing with the 'latest' dist-tag from a non-main branch ('${{ inputs.branch }}') is not allowed. Use the package major version as the tag (e.g. v5)."
exit 1
name: ${{ github.event.inputs.package }}
- name: publish
run: npm publish --provenance --tag "${{ inputs.npm-tag }}" *.tgz
run: npm publish --provenance *.tgz
- name: notify slack on failure
if: failure()
run: |
curl -X POST -H 'Content-type: application/json' --data '{"text":":pb__failed: Failed to publish a new version of ${{ inputs.package }} from ${{ inputs.branch }} (tag: ${{ inputs.npm-tag }})"}' $SLACK_WEBHOOK
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
env:
SLACK_WEBHOOK: ${{ secrets.SLACK }}
- name: notify slack on success
if: success()
run: |
curl -X POST -H 'Content-type: application/json' --data '{"text":":dance: Successfully published a new version of ${{ inputs.package }} from ${{ inputs.branch }} (tag: ${{ inputs.npm-tag }})"}' $SLACK_WEBHOOK
curl -X POST -H 'Content-type: application/json' --data '{"text":":dance: Successfully published a new version of ${{ github.event.inputs.package }}"}' $SLACK_WEBHOOK
env:
SLACK_WEBHOOK: ${{ secrets.SLACK }}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

-82
View File
@@ -1,82 +0,0 @@
# Releasing Packages
Packages are published to npm via the **Publish NPM** workflow ([`.github/workflows/releases.yml`](../.github/workflows/releases.yml)). The workflow is triggered manually through `workflow_dispatch` from the GitHub Actions UI.
## How it works
The workflow has two jobs:
1. **test** — Checks out the specified branch, installs dependencies, bootstraps the monorepo, builds all packages, runs tests, then packs the target package into a `.tgz` archive and uploads it as a workflow artifact.
2. **publish** — Downloads the packed artifact and publishes it to npm with `--provenance` (OIDC-based). Sends a Slack notification on success or failure. Requires the `npm-publish` environment.
## Inputs
| Input | Type | Required | Default | Description |
|---|---|---|---|---|
| `package` | choice | **yes** | — | Which package to release. One of: `artifact`, `attest`, `cache`, `core`, `exec`, `github`, `glob`, `http-client`, `io`, `tool-cache`. |
| `branch` | string | no | `main` | The branch to check out and release from. |
| `npm-tag` | string | no | `latest` | The npm dist-tag to publish under. See [Dist-tags](#dist-tags) below. |
| `test-all` | boolean | no | `false` | When `false`, only tests for the selected package are run. Set to `true` to run the full test suite across all packages. |
## Dist-tags
npm dist-tags control which version users get when they `npm install @actions/<package>` (or `@<tag>`).
> **Important:** npm dist-tags **cannot** be valid semver strings. Values like `5.0.0` or `1.2.3` will be rejected by npm. Use a descriptive name instead.
- **`latest`** — The default tag. This is what users get with a plain `npm install`. Should only be used for releases from `main`.
- **Custom tags** (e.g. `v1-longlived`) — Used for releases from long-lived or experimental branches.
Examples of **valid** dist-tags: `latest`, `next`, `beta`, `v1-longlived`
Examples of **invalid** dist-tags: `5.0.0`, `1.2.3`, `6.0.0-rc.1` (these are semver and will be rejected)
| ![Screenshot showcasing the npm distribution tags](assets/npm-dist-tags.png) |
|---|
| npm distribution tags |
### Safety guard
The workflow **blocks** publishing with the `latest` dist-tag from any branch other than `main`. This prevents accidentally overwriting `latest` with a version from an older or experimental branch. If you're releasing from a non-main branch, use the package's major version as the tag (e.g. `v5`).
## Examples
### Standard release from main
Use default inputs — just pick the package:
| Input | Value |
|---|---|
| `package` | `core` |
| `branch` | `main` (default) |
| `npm-tag` | `latest` (default) |
| `test-all` | `false` (default) |
This publishes the version in `packages/core/package.json` on `main` as `@actions/core@latest`.
### Patch release from a long-lived branch
| Input | Value |
|---|---|
| `package` | `artifact` |
| `branch` | `releases/v5` |
| `npm-tag` | `v5` |
| `test-all` | `false` (default) |
This publishes the version in `packages/artifact/package.json` on the `releases/v5` branch under the `v5` dist-tag. The `latest` tag remains untouched.
### Release with full test suite
| Input | Value |
|---|---|
| `package` | `cache` |
| `branch` | `main` (default) |
| `npm-tag` | `latest` (default) |
| `test-all` | `true` |
Same as a standard release, but runs tests for all packages before publishing.
## Prerequisites
- You must have permission to trigger workflows on this repository.
- The `npm-publish` environment must be configured with npm credentials and OIDC.
- The `SLACK` secret must be set for Slack notifications to work.
+19 -22
View File
@@ -4322,15 +4322,15 @@
}
},
"node_modules/axios": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.15.1.tgz",
"integrity": "sha512-WOG+Jj8ZOvR0a3rAn+Tuf1UQJRxw5venr6DgdbJzngJE3qG7X0kL83CZGpdHMxEm+ZK3seAbvFsw4FfOfP9vxg==",
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.11",
"form-data": "^4.0.5",
"proxy-from-env": "^2.1.0"
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axobject-query": {
@@ -7215,9 +7215,9 @@
}
},
"node_modules/flatted": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
"dev": true,
"license": "ISC"
},
@@ -7235,9 +7235,9 @@
}
},
"node_modules/follow-redirects": {
"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==",
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"dev": true,
"funding": [
{
@@ -13278,9 +13278,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -13570,14 +13570,11 @@
"license": "MIT"
},
"node_modules/proxy-from-env": {
"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==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
}
"license": "MIT"
},
"node_modules/pure-rand": {
"version": "6.1.0",
-4
View File
@@ -1,9 +1,5 @@
# @actions/artifact Releases
## 6.2.1
- Support the RFC 5987 `filename*` field in the `content-disposition` header. This allows us to correctly download files and artifacts with Chinese/Japanese/Korean (among other) characters in their name.
## 6.2.0
- Support uploading single un-archived files (not zipped). Direct uploads are only supported for artifacts version 7+ (based on the major version of `actions/upload-artifact`). Callers must pass the `skipArchive` option to `uploadArtifact`. Only single files can be uploaded at a time right now. Default behavior should remain unchanged if `skipArchive = false`. When `skipArchive = true`, the name of the file is used as the name of the artifact for consistency with the downloads: you upload `artifact.txt`, you download `artifact.txt`.
@@ -977,125 +977,5 @@ describe('download-artifact', () => {
)
expect(fs.existsSync(maliciousPath)).toBe(false)
})
it('should correctly handle Content-Disposition with filename* parameter (RFC 5987)', async () => {
const rawFileContent = 'content with rfc5987 encoding'
const expectedFileName = '报告-土-x.txt'
const asciiFileName = '__-_-x.txt'
const mockGetRfc5987File = jest.fn(() => {
const message = new http.IncomingMessage(new net.Socket())
message.statusCode = 200
message.headers['content-type'] = 'text/plain'
// Server sends both: filename with _ fallbacks, filename* with UTF-8 encoding
message.headers['content-disposition'] =
`attachment; filename="${asciiFileName}"; filename*=UTF-8''${encodeURIComponent(expectedFileName)}`
message.push(Buffer.from(rawFileContent, 'utf8'))
message.push(null)
return {
message
}
})
const mockHttpClient = (HttpClient as jest.Mock).mockImplementation(
() => {
return {
get: mockGetRfc5987File
}
}
)
await streamExtractExternal(
fixtures.blobStorageUrl,
fixtures.workspaceDir
)
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
const savedFilePath = path.join(fixtures.workspaceDir, expectedFileName)
expect(fs.existsSync(savedFilePath)).toBe(true)
expect(fs.readFileSync(savedFilePath, 'utf8')).toBe(rawFileContent)
})
it('should handle zip artifacts with Chinese characters in the artifact name', async () => {
// Simulate Azure Blob Storage URL with rscd containing Chinese filename
const chineseArtifactName = 'probe-土-x'
const asciiArtifactName = 'probe-_-x'
const blobUrlWithChineseName = `https://blob-storage.local/artifact.zip?rscd=${encodeURIComponent(`attachment; filename="${asciiArtifactName}.zip"; filename*=UTF-8''${encodeURIComponent(`${chineseArtifactName}.zip`)}`)}&rsct=application%2Fzip&sig=abc123`
const mockGetZip = jest.fn(() => {
const message = new http.IncomingMessage(new net.Socket())
message.statusCode = 200
message.headers['content-type'] = 'application/zip'
message.headers['content-disposition'] =
`attachment; filename="${asciiArtifactName}.zip"; filename*=UTF-8''${encodeURIComponent(`${chineseArtifactName}.zip`)}`
message.push(fs.readFileSync(fixtures.exampleArtifact.path))
message.push(null)
return {
message
}
})
const mockHttpClient = (HttpClient as jest.Mock).mockImplementation(
() => {
return {
get: mockGetZip
}
}
)
await streamExtractExternal(blobUrlWithChineseName, fixtures.workspaceDir)
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
// Zip should be extracted normally regardless of Chinese artifact name
await expectExtractedArchive(fixtures.workspaceDir)
})
it.each([
['土', '_'], // U+571F - known to cause 400 errors
['日', '_'], // U+65E5 - reported to work fine
['中文测试', '____'], // multiple Chinese characters
['文件-2026年', '__-2026_'], // mixed Chinese and numbers
['データ', '___'], // Japanese katakana
['테스트', '___'] // Korean characters
])(
'should prefer filename* over filename for non-ASCII character %s (%s)',
async (chars, asciiReplacement) => {
const rawFileContent = `content for ${chars}`
const expectedFileName = `artifact-${chars}.txt`
const asciiFileName = `artifact-${asciiReplacement}.txt`
const mockGetFile = jest.fn(() => {
const message = new http.IncomingMessage(new net.Socket())
message.statusCode = 200
message.headers['content-type'] = 'text/plain'
// Server sends filename with _ replacing non-ASCII, filename* with proper encoding
message.headers['content-disposition'] =
`attachment; filename="${asciiFileName}"; filename*=UTF-8''${encodeURIComponent(expectedFileName)}`
message.push(Buffer.from(rawFileContent, 'utf8'))
message.push(null)
return {
message
}
})
const mockHttpClient = (HttpClient as jest.Mock).mockImplementation(
() => {
return {
get: mockGetFile
}
}
)
await streamExtractExternal(
fixtures.blobStorageUrl,
fixtures.workspaceDir
)
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
const savedFilePath = path.join(fixtures.workspaceDir, expectedFileName)
expect(fs.existsSync(savedFilePath)).toBe(true)
expect(fs.readFileSync(savedFilePath, 'utf8')).toBe(rawFileContent)
}
)
})
})
+5 -5
View File
@@ -1,12 +1,12 @@
{
"name": "@actions/artifact",
"version": "6.2.1",
"version": "6.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@actions/artifact",
"version": "6.2.1",
"version": "6.2.0",
"license": "MIT",
"dependencies": {
"@actions/core": "^3.0.0",
@@ -1812,9 +1812,9 @@
"license": "MIT"
},
"node_modules/undici": {
"version": "6.24.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.24.0.tgz",
"integrity": "sha512-lVLNosgqo5EkGqh5XUDhGfsMSoO8K0BAN0TyJLvwNRSl4xWGZlCVYsAIpa/OpA3TvmnM01GWcoKmc3ZWo5wKKA==",
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
"license": "MIT",
"engines": {
"node": ">=18.17"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/artifact",
"version": "6.2.1",
"version": "6.2.0",
"preview": true,
"description": "Actions artifact lib",
"keywords": [
@@ -93,22 +93,16 @@ export async function streamExtractExternal(
urlEndsWithZip
// Extract filename from Content-Disposition header
// Prefer filename* (RFC 5987) which supports UTF-8 encoded filenames,
// fall back to filename which may contain ASCII-only replacements
const contentDisposition =
response.message.headers['content-disposition'] || ''
let fileName = 'artifact'
const filenameStar = contentDisposition.match(
/filename\*\s*=\s*UTF-8''([^;\r\n]*)/i
const filenameMatch = contentDisposition.match(
/filename\*?=['"]?(?:UTF-\d['"]*)?([^;\r\n"']*)['"]?/i
)
const filenamePlain = contentDisposition.match(
/(?<!\*)filename\s*=\s*['"]?([^;\r\n"']*)['"]?/i
)
const rawName = filenameStar?.[1] || filenamePlain?.[1]
if (rawName) {
if (filenameMatch && filenameMatch[1]) {
// Sanitize fileName to prevent path traversal attacks
// Use path.basename to extract only the filename component
fileName = path.basename(decodeURIComponent(rawName.trim()))
fileName = path.basename(decodeURIComponent(filenameMatch[1].trim()))
}
core.debug(
@@ -60,7 +60,7 @@ export class NetworkError extends Error {
export class UsageError extends Error {
constructor() {
const message = `Artifact storage quota has been hit. Unable to upload any new artifacts.\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`
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`
super(message)
this.name = 'UsageError'
}
-8
View File
@@ -1,13 +1,5 @@
# @actions/attest Releases
## 3.2.0
- Add custom user-agent for more API calls [#2321](https://github.com/actions/toolkit/pull/2321)
## 3.1.0
- Add support for `ACTIONS_ORCHESTRATION_ID` in user-agent [#2320](https://github.com/actions/toolkit/pull/2320)
## 3.0.0
- **Breaking change**: Package is now ESM-only
+5 -5
View File
@@ -1,12 +1,12 @@
{
"name": "@actions/attest",
"version": "3.2.0",
"version": "3.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@actions/attest",
"version": "3.2.0",
"version": "3.0.0",
"license": "MIT",
"dependencies": {
"@actions/core": "^3.0.0",
@@ -1529,9 +1529,9 @@
}
},
"node_modules/tar": {
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.10.tgz",
"integrity": "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==",
"version": "7.5.7",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
"integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/attest",
"version": "3.2.0",
"version": "3.0.0",
"description": "Actions attestation lib",
"keywords": [
"github",
@@ -36,7 +36,7 @@
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1",
"tsc": "tsc && cp src/internal/package-version.cjs lib/internal/"
"tsc": "tsc"
},
"bugs": {
"url": "https://github.com/actions/toolkit/issues"
+1 -8
View File
@@ -1,7 +1,6 @@
import * as github from '@actions/github'
import {retry} from '@octokit/plugin-retry'
import {RequestHeaders} from '@octokit/types'
import {getUserAgent} from './internal/utils.js'
const CREATE_STORAGE_RECORD_REQUEST =
'POST /orgs/{owner}/artifacts/metadata/storage-record'
@@ -53,16 +52,10 @@ export async function createStorageRecord(
): Promise<number[]> {
const retries = retryAttempts ?? DEFAULT_RETRY_COUNT
const octokit = github.getOctokit(token, {retry: {retries}}, retry)
const headersWithUserAgent = {
'User-Agent': getUserAgent(),
...headers
}
try {
const response = await octokit.request(CREATE_STORAGE_RECORD_REQUEST, {
owner: github.context.repo.owner,
headers: headersWithUserAgent,
headers,
...buildRequestParams(artifactOptions, packageRegistryOptions)
})
@@ -1,7 +0,0 @@
// This file exists as a CommonJS module to read the version from package.json.
// In an ESM package, using `require()` directly in .ts files requires disabling
// ESLint rules and doesn't work reliably across all Node.js versions.
// By keeping this as a .cjs file, we can use require() naturally and export
// the version for the ESM modules to import.
const packageJson = require('../../package.json')
module.exports = {version: packageJson.version}
-15
View File
@@ -1,15 +0,0 @@
import {version} from './package-version.cjs'
export const getUserAgent = (): string => {
const baseUserAgent = `@actions/attest-${version}`
const orchId = process.env['ACTIONS_ORCHESTRATION_ID']
if (orchId) {
// Sanitize the orchestration ID to ensure it contains only valid characters
// Valid characters: 0-9, a-z, _, -, .
const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, '_')
return `${baseUserAgent} actions_orchestration_id/${sanitizedId}`
}
return baseUserAgent
}
+1 -7
View File
@@ -1,7 +1,6 @@
import * as github from '@actions/github'
import {retry} from '@octokit/plugin-retry'
import {RequestHeaders} from '@octokit/types'
import {getUserAgent} from './internal/utils.js'
const CREATE_ATTESTATION_REQUEST = 'POST /repos/{owner}/{repo}/attestations'
const DEFAULT_RETRY_COUNT = 5
@@ -25,16 +24,11 @@ export const writeAttestation = async (
const retries = options.retry ?? DEFAULT_RETRY_COUNT
const octokit = github.getOctokit(token, {retry: {retries}}, retry)
const headers = {
'User-Agent': getUserAgent(),
...options.headers
}
try {
const response = await octokit.request(CREATE_ATTESTATION_REQUEST, {
owner: github.context.repo.owner,
repo: github.context.repo.repo,
headers,
headers: options.headers,
bundle: attestation as {
mediaType?: string
verificationMaterial?: {[key: string]: unknown}
+1 -1
View File
@@ -60,7 +60,7 @@ export class NetworkError extends Error {
export class UsageError extends Error {
constructor() {
const message = `Cache storage quota has been hit. Unable to upload any new cache entries.\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`
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`
super(message)
this.name = 'UsageError'
}
-4
View File
@@ -1,9 +1,5 @@
# @actions/core Releases
## 3.0.1
- Bump `undici` from `6.23.0` to `6.24.1` [#2348](https://github.com/actions/toolkit/pull/2348)
## 3.0.0
- **Breaking change**: Package is now ESM-only
+5 -5
View File
@@ -1,12 +1,12 @@
{
"name": "@actions/core",
"version": "3.0.1",
"version": "3.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@actions/core",
"version": "3.0.1",
"version": "3.0.0",
"license": "MIT",
"dependencies": {
"@actions/exec": "^3.0.0",
@@ -61,9 +61,9 @@
}
},
"node_modules/undici": {
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz",
"integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==",
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
"license": "MIT",
"engines": {
"node": ">=18.17"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/core",
"version": "3.0.1",
"version": "3.0.0",
"description": "Actions core lib",
"keywords": [
"github",
-8
View File
@@ -1,13 +1,5 @@
# @actions/github Releases
### 9.1.1
- Bump `undici` from `6.23.0` to `6.24.0` [#2346](https://github.com/actions/toolkit/pull/2346)
### 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
@@ -1,130 +0,0 @@
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'
)
})
})
})
+5 -5
View File
@@ -1,12 +1,12 @@
{
"name": "@actions/github",
"version": "9.1.1",
"version": "9.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@actions/github",
"version": "9.1.1",
"version": "9.0.0",
"license": "MIT",
"dependencies": {
"@actions/http-client": "^3.0.2",
@@ -363,9 +363,9 @@
}
},
"node_modules/undici": {
"version": "6.24.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.24.0.tgz",
"integrity": "sha512-lVLNosgqo5EkGqh5XUDhGfsMSoO8K0BAN0TyJLvwNRSl4xWGZlCVYsAIpa/OpA3TvmnM01GWcoKmc3ZWo5wKKA==",
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
"license": "MIT",
"engines": {
"node": ">=18.17"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/github",
"version": "9.1.1",
"version": "9.0.0",
"description": "Actions github lib",
"keywords": [
"github",
-14
View File
@@ -42,17 +42,3 @@ 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
}
-10
View File
@@ -23,8 +23,6 @@ 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.
*
@@ -43,13 +41,5 @@ export function getOctokitOptions(
opts.auth = auth
}
// Orchestration ID
const userAgent = Utils.getUserAgentWithOrchestrationId(
opts.userAgent as string | undefined
)
if (userAgent) {
opts.userAgent = userAgent
}
return opts
}
-6
View File
@@ -1,11 +1,5 @@
# @actions/glob Releases
## 0.7.0
- Bump `minimatch` from `^3.0.4` to `^10.2.5` [#2355](https://github.com/actions/toolkit/pull/2355)
- Bump `undici` from `6.23.0` to `6.24.0` [#2345](https://github.com/actions/toolkit/pull/2345)
- Bump `brace-expansion` in `/packages/glob` [#2369](https://github.com/actions/toolkit/pull/2369)
## 0.6.1
- Fix a bad import for `minimatch`
@@ -303,7 +303,7 @@ describe('pattern', () => {
expect(pattern.match(`${root}foo/bar/baz`)).toBeFalsy()
pattern = new Pattern(`${root}foo/b[!]r/b*`)
expect(pattern.searchPath).toBe(`${root}foo${path.sep}b!r`)
expect(pattern.match(`${root}foo/b!r/baz`)).toBeFalsy()
expect(pattern.match(`${root}foo/b!r/baz`)).toBeTruthy()
pattern = new Pattern(`${root}foo/b[[]ar/b*`)
expect(pattern.searchPath).toBe(`${root}foo${path.sep}b[ar`)
expect(pattern.match(`${root}foo/b[ar/baz`)).toBeTruthy()
@@ -340,18 +340,9 @@ describe('pattern', () => {
pattern = new Pattern('C:/foo/b\\[a]r/b*')
expect(pattern.searchPath).toBe(`C:\\foo\\b\\ar`)
expect(pattern.match('C:/foo/b/ar/baz')).toBeTruthy()
// Regression testing for minimatch v3
// Historically, minimatch/glob had a bug when parsing a character class
// containing an escaped '!' (e.g. `[\\!]`). In some cases, the internal
// pattern construction would incorrectly insert the literal string
// "undefined" into the generated pattern/segment, which could make a
// pattern intended to match `b[\\!]r` also match a path segment like
// `b[undefined/!]r`. This test ensures that a pattern with a literal
// `[\\!]` in the directory name does *not* match such malformed paths.
pattern = new Pattern('C:/foo/b[\\!]r/b*')
expect(pattern.searchPath).toBe('C:\\foo\\b[\\!]r')
expect(pattern.match('C:/foo/b[undefined/!]r/baz')).toBeFalsy()
expect(pattern.match('C:/foo/b[undefined/!]r/baz')).toBeTruthy() // Note, "undefined" substr to accommodate a bug in Minimatch when nocase=true
}
})
})
+27 -29
View File
@@ -1,16 +1,16 @@
{
"name": "@actions/glob",
"version": "0.7.0",
"version": "0.6.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@actions/glob",
"version": "0.7.0",
"version": "0.6.1",
"license": "MIT",
"dependencies": {
"@actions/core": "^3.0.0",
"minimatch": "^10.2.5"
"minimatch": "^3.0.4"
}
},
"node_modules/@actions/core": {
@@ -49,39 +49,37 @@
"license": "MIT"
},
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"license": "MIT"
},
"node_modules/minimatch": {
"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",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^5.0.5"
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
"node": "*"
}
},
"node_modules/tunnel": {
@@ -94,9 +92,9 @@
}
},
"node_modules/undici": {
"version": "6.24.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.24.0.tgz",
"integrity": "sha512-lVLNosgqo5EkGqh5XUDhGfsMSoO8K0BAN0TyJLvwNRSl4xWGZlCVYsAIpa/OpA3TvmnM01GWcoKmc3ZWo5wKKA==",
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
"license": "MIT",
"engines": {
"node": ">=18.17"
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/glob",
"version": "0.7.0",
"version": "0.6.1",
"preview": true,
"description": "Actions glob lib",
"keywords": [
@@ -45,6 +45,6 @@
},
"dependencies": {
"@actions/core": "^3.0.0",
"minimatch": "^10.2.5"
"minimatch": "^3.0.4"
}
}
+7 -3
View File
@@ -2,10 +2,14 @@ import * as os from 'os'
import * as path from 'path'
import * as pathHelper from './internal-path-helper.js'
import assert from 'assert'
import {Minimatch, type MinimatchOptions} from 'minimatch'
import minimatch from 'minimatch'
import {MatchKind} from './internal-match-kind.js'
import {Path} from './internal-path.js'
type IMinimatch = minimatch.IMinimatch
type IMinimatchOptions = minimatch.IOptions
const {Minimatch} = minimatch
const IS_WINDOWS = process.platform === 'win32'
export class Pattern {
@@ -34,7 +38,7 @@ export class Pattern {
/**
* The Minimatch object used for matching
*/
private readonly minimatch: Minimatch
private readonly minimatch: IMinimatch
/**
* Used to workaround a limitation with Minimatch when determining a partial
@@ -122,7 +126,7 @@ export class Pattern {
this.isImplicitPattern = isImplicitPattern
// Create minimatch
const minimatchOptions: MinimatchOptions = {
const minimatchOptions: IMinimatchOptions = {
dot: true,
nobrace: true,
nocase: IS_WINDOWS,
-4
View File
@@ -1,9 +1,5 @@
# Releases
## 4.0.1
- Bump `undici` from `6.23.0` to `6.24.0` [#2347](https://github.com/actions/toolkit/pull/2347)
## 4.0.0
- **Breaking change**: Package is now ESM-only
+5 -5
View File
@@ -1,12 +1,12 @@
{
"name": "@actions/http-client",
"version": "4.0.1",
"version": "4.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@actions/http-client",
"version": "4.0.1",
"version": "4.0.0",
"license": "MIT",
"dependencies": {
"tunnel": "^0.0.6",
@@ -232,9 +232,9 @@
}
},
"node_modules/undici": {
"version": "6.24.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.24.0.tgz",
"integrity": "sha512-lVLNosgqo5EkGqh5XUDhGfsMSoO8K0BAN0TyJLvwNRSl4xWGZlCVYsAIpa/OpA3TvmnM01GWcoKmc3ZWo5wKKA==",
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
"license": "MIT",
"engines": {
"node": ">=18.17"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/http-client",
"version": "4.0.1",
"version": "4.0.0",
"description": "Actions Http Client",
"keywords": [
"github",