Compare commits

..

2 Commits

22 changed files with 440 additions and 1168 deletions

View File

@@ -24,10 +24,10 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
- name: Set Node.js 24.x - name: Set Node.js 20.x
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 24.x node-version: 20.x
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci

View File

@@ -18,7 +18,7 @@ jobs:
steps: steps:
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 24.x node-version: 20.x
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
- run: npm ci - run: npm ci
- run: npm run build - run: npm run build
@@ -302,15 +302,12 @@ jobs:
# Clone this repo # Clone this repo
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.6
with:
path: actions-checkout
# Basic checkout using git # Basic checkout using git
- name: Checkout basic - name: Checkout basic
id: checkout id: checkout
uses: ./actions-checkout uses: ./
with: with:
path: cloned-using-local-action
ref: test-data/v2/basic ref: test-data/v2/basic
# Verify output # Verify output
@@ -328,3 +325,7 @@ jobs:
echo "Expected commit to be 82f71901cf8c021332310dcc8cdba84c4193ff5d" echo "Expected commit to be 82f71901cf8c021332310dcc8cdba84c4193ff5d"
exit 1 exit 1
fi fi
# needed to make checkout post cleanup succeed
- name: Fix Checkout
uses: actions/checkout@v4.1.6

View File

@@ -11,7 +11,6 @@ on:
type: choice type: choice
description: The major version to update description: The major version to update
options: options:
- v5
- v4 - v4
- v3 - v3
- v2 - v2

View File

@@ -1,9 +1,9 @@
--- ---
name: "@octokit/endpoint" name: "@octokit/endpoint"
version: 9.0.6 version: 9.0.5
type: npm type: npm
summary: Turns REST API endpoints into generic request options summary: Turns REST API endpoints into generic request options
homepage: homepage:
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE

View File

@@ -1,9 +1,9 @@
--- ---
name: "@octokit/plugin-paginate-rest" name: "@octokit/plugin-paginate-rest"
version: 9.2.2 version: 9.2.1
type: npm type: npm
summary: Octokit plugin to paginate REST API endpoint responses summary: Octokit plugin to paginate REST API endpoint responses
homepage: homepage:
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE

View File

@@ -1,9 +1,9 @@
--- ---
name: "@octokit/request-error" name: "@octokit/request-error"
version: 5.1.1 version: 5.1.0
type: npm type: npm
summary: Error class for Octokit request errors summary: Error class for Octokit request errors
homepage: homepage:
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE

View File

@@ -1,10 +1,10 @@
--- ---
name: "@octokit/request" name: "@octokit/request"
version: 8.4.1 version: 8.4.0
type: npm type: npm
summary: Send parameterized requests to GitHub's APIs with sensible defaults in browsers summary: Send parameterized requests to GitHub's APIs with sensible defaults in browsers
and Node and Node
homepage: homepage:
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE

View File

@@ -1,6 +1,6 @@
--- ---
name: undici name: undici
version: 5.29.0 version: 5.28.4
type: npm type: npm
summary: An HTTP/1.1 client, written from scratch for Node.js summary: An HTTP/1.1 client, written from scratch for Node.js
homepage: https://undici.nodejs.org homepage: https://undici.nodejs.org

View File

@@ -1,18 +1,5 @@
# Changelog # Changelog
## V5.0.0
* Update actions checkout to use node 24 by @salmanmkc in https://github.com/actions/checkout/pull/2226
## V4.3.0
* docs: update README.md by @motss in https://github.com/actions/checkout/pull/1971
* Add internal repos for checking out multiple repositories by @mouismail in https://github.com/actions/checkout/pull/1977
* Documentation update - add recommended permissions to Readme by @benwells in https://github.com/actions/checkout/pull/2043
* Adjust positioning of user email note and permissions heading by @joshmgross in https://github.com/actions/checkout/pull/2044
* Update README.md by @nebuk89 in https://github.com/actions/checkout/pull/2194
* Update CODEOWNERS for actions by @TingluoHuang in https://github.com/actions/checkout/pull/2224
* Update package dependencies by @salmanmkc in https://github.com/actions/checkout/pull/2236
## v4.2.2 ## v4.2.2
* `url-helper.ts` now leverages well-known environment variables by @jww3 in https://github.com/actions/checkout/pull/1941 * `url-helper.ts` now leverages well-known environment variables by @jww3 in https://github.com/actions/checkout/pull/1941
* Expand unit test coverage for `isGhes` by @jww3 in https://github.com/actions/checkout/pull/1946 * Expand unit test coverage for `isGhes` by @jww3 in https://github.com/actions/checkout/pull/1946

View File

@@ -1 +1 @@
* @actions/actions-runtime * @actions/actions-launch

104
README.md
View File

@@ -1,13 +1,5 @@
[![Build and Test](https://github.com/actions/checkout/actions/workflows/test.yml/badge.svg)](https://github.com/actions/checkout/actions/workflows/test.yml) [![Build and Test](https://github.com/actions/checkout/actions/workflows/test.yml/badge.svg)](https://github.com/actions/checkout/actions/workflows/test.yml)
# Checkout V5
## What's new
- Updated to the node24 runtime
- This requires a minimum Actions Runner version of [v2.327.1](https://github.com/actions/runner/releases/tag/v2.327.1) to run.
# Checkout V4 # Checkout V4
This action checks-out your repository under `$GITHUB_WORKSPACE`, so your workflow can access it. This action checks-out your repository under `$GITHUB_WORKSPACE`, so your workflow can access it.
@@ -18,24 +10,6 @@ The auth token is persisted in the local git config. This enables your scripts t
When Git 2.18 or higher is not in your PATH, falls back to the REST API to download the files. When Git 2.18 or higher is not in your PATH, falls back to the REST API to download the files.
### Note
Thank you for your interest in this GitHub action, however, right now we are not taking contributions.
We continue to focus our resources on strategic areas that help our customers be successful while making developers' lives easier. While GitHub Actions remains a key part of this vision, we are allocating resources towards other areas of Actions and are not taking contributions to this repository at this time. The GitHub public roadmap is the best place to follow along for any updates on features were working on and what stage theyre in.
We are taking the following steps to better direct requests related to GitHub Actions, including:
1. We will be directing questions and support requests to our [Community Discussions area](https://github.com/orgs/community/discussions/categories/actions)
2. High Priority bugs can be reported through Community Discussions or you can report these to our support team https://support.github.com/contact/bug-report.
3. Security Issues should be handled as per our [security.md](security.md)
We will still provide security updates for this project and fix major breaking changes during this time.
You are welcome to still raise bugs in this repo.
# What's new # What's new
Please refer to the [release page](https://github.com/actions/checkout/releases/latest) for the latest release notes. Please refer to the [release page](https://github.com/actions/checkout/releases/latest) for the latest release notes.
@@ -44,7 +18,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
<!-- start usage --> <!-- start usage -->
```yaml ```yaml
- uses: actions/checkout@v5 - uses: actions/checkout@v4
with: with:
# Repository name with owner. For example, actions/checkout # Repository name with owner. For example, actions/checkout
# Default: ${{ github.repository }} # Default: ${{ github.repository }}
@@ -157,33 +131,24 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
# Scenarios # Scenarios
- [Checkout V5](#checkout-v5) - [Fetch only the root files](#Fetch-only-the-root-files)
- [What's new](#whats-new) - [Fetch only the root files and `.github` and `src` folder](#Fetch-only-the-root-files-and-github-and-src-folder)
- [Checkout V4](#checkout-v4) - [Fetch only a single file](#Fetch-only-a-single-file)
- [Note](#note) - [Fetch all history for all tags and branches](#Fetch-all-history-for-all-tags-and-branches)
- [What's new](#whats-new-1) - [Checkout a different branch](#Checkout-a-different-branch)
- [Usage](#usage) - [Checkout HEAD^](#Checkout-HEAD)
- [Scenarios](#scenarios) - [Checkout multiple repos (side by side)](#Checkout-multiple-repos-side-by-side)
- [Fetch only the root files](#fetch-only-the-root-files) - [Checkout multiple repos (nested)](#Checkout-multiple-repos-nested)
- [Fetch only the root files and `.github` and `src` folder](#fetch-only-the-root-files-and-github-and-src-folder) - [Checkout multiple repos (private)](#Checkout-multiple-repos-private)
- [Fetch only a single file](#fetch-only-a-single-file) - [Checkout pull request HEAD commit instead of merge commit](#Checkout-pull-request-HEAD-commit-instead-of-merge-commit)
- [Fetch all history for all tags and branches](#fetch-all-history-for-all-tags-and-branches) - [Checkout pull request on closed event](#Checkout-pull-request-on-closed-event)
- [Checkout a different branch](#checkout-a-different-branch) - [Push a commit using the built-in token](#Push-a-commit-using-the-built-in-token)
- [Checkout HEAD^](#checkout-head) - [Push a commit to a PR using the built-in token](#Push-a-commit-to-a-PR-using-the-built-in-token)
- [Checkout multiple repos (side by side)](#checkout-multiple-repos-side-by-side)
- [Checkout multiple repos (nested)](#checkout-multiple-repos-nested)
- [Checkout multiple repos (private)](#checkout-multiple-repos-private)
- [Checkout pull request HEAD commit instead of merge commit](#checkout-pull-request-head-commit-instead-of-merge-commit)
- [Checkout pull request on closed event](#checkout-pull-request-on-closed-event)
- [Push a commit using the built-in token](#push-a-commit-using-the-built-in-token)
- [Push a commit to a PR using the built-in token](#push-a-commit-to-a-pr-using-the-built-in-token)
- [Recommended permissions](#recommended-permissions)
- [License](#license)
## Fetch only the root files ## Fetch only the root files
```yaml ```yaml
- uses: actions/checkout@v5 - uses: actions/checkout@v4
with: with:
sparse-checkout: . sparse-checkout: .
``` ```
@@ -191,7 +156,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
## Fetch only the root files and `.github` and `src` folder ## Fetch only the root files and `.github` and `src` folder
```yaml ```yaml
- uses: actions/checkout@v5 - uses: actions/checkout@v4
with: with:
sparse-checkout: | sparse-checkout: |
.github .github
@@ -201,7 +166,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
## Fetch only a single file ## Fetch only a single file
```yaml ```yaml
- uses: actions/checkout@v5 - uses: actions/checkout@v4
with: with:
sparse-checkout: | sparse-checkout: |
README.md README.md
@@ -211,7 +176,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
## Fetch all history for all tags and branches ## Fetch all history for all tags and branches
```yaml ```yaml
- uses: actions/checkout@v5 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
``` ```
@@ -219,7 +184,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
## Checkout a different branch ## Checkout a different branch
```yaml ```yaml
- uses: actions/checkout@v5 - uses: actions/checkout@v4
with: with:
ref: my-branch ref: my-branch
``` ```
@@ -227,7 +192,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
## Checkout HEAD^ ## Checkout HEAD^
```yaml ```yaml
- uses: actions/checkout@v5 - uses: actions/checkout@v4
with: with:
fetch-depth: 2 fetch-depth: 2
- run: git checkout HEAD^ - run: git checkout HEAD^
@@ -237,12 +202,12 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
```yaml ```yaml
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v4
with: with:
path: main path: main
- name: Checkout tools repo - name: Checkout tools repo
uses: actions/checkout@v5 uses: actions/checkout@v4
with: with:
repository: my-org/my-tools repository: my-org/my-tools
path: my-tools path: my-tools
@@ -253,10 +218,10 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
```yaml ```yaml
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v4
- name: Checkout tools repo - name: Checkout tools repo
uses: actions/checkout@v5 uses: actions/checkout@v4
with: with:
repository: my-org/my-tools repository: my-org/my-tools
path: my-tools path: my-tools
@@ -267,12 +232,12 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
```yaml ```yaml
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v4
with: with:
path: main path: main
- name: Checkout private tools - name: Checkout private tools
uses: actions/checkout@v5 uses: actions/checkout@v4
with: with:
repository: my-org/my-private-tools repository: my-org/my-private-tools
token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT
@@ -285,7 +250,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
## Checkout pull request HEAD commit instead of merge commit ## Checkout pull request HEAD commit instead of merge commit
```yaml ```yaml
- uses: actions/checkout@v5 - uses: actions/checkout@v4
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
``` ```
@@ -301,7 +266,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v4
``` ```
## Push a commit using the built-in token ## Push a commit using the built-in token
@@ -312,7 +277,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v4
- run: | - run: |
date > generated.txt date > generated.txt
# Note: the following account information will not work on GHES # Note: the following account information will not work on GHES
@@ -334,7 +299,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v4
with: with:
ref: ${{ github.head_ref }} ref: ${{ github.head_ref }}
- run: | - run: |
@@ -346,17 +311,8 @@ jobs:
git commit -m "generated" git commit -m "generated"
git push git push
``` ```
*NOTE:* The user email is `{user.id}+{user.login}@users.noreply.github.com`. See users API: https://api.github.com/users/github-actions%5Bbot%5D *NOTE:* The user email is `{user.id}+{user.login}@users.noreply.github.com`. See users API: https://api.github.com/users/github-actions%5Bbot%5D
# Recommended permissions
When using the `checkout` action in your GitHub Actions workflow, it is recommended to set the following `GITHUB_TOKEN` permissions to ensure proper functionality, unless alternative auth is provided via the `token` or `ssh-key` inputs:
```yaml
permissions:
contents: read
```
# License # License

View File

@@ -86,29 +86,16 @@ describe('git-auth-helper tests', () => {
// Act // Act
await authHelper.configureAuth() await authHelper.configureAuth()
// Assert config - check that .git/config contains includeIf entries // Assert config
const localConfigContent = ( const configContent = (
await fs.promises.readFile(localGitConfigPath) await fs.promises.readFile(localGitConfigPath)
).toString() ).toString()
expect(
localConfigContent.indexOf('includeIf.gitdir:')
).toBeGreaterThanOrEqual(0)
// Assert credentials config file contains the actual credentials
const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
f => f.startsWith('git-credentials-') && f.endsWith('.config')
)
expect(credentialsFiles.length).toBe(1)
const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0])
const credentialsContent = (
await fs.promises.readFile(credentialsConfigPath)
).toString()
const basicCredential = Buffer.from( const basicCredential = Buffer.from(
`x-access-token:${settings.authToken}`, `x-access-token:${settings.authToken}`,
'utf8' 'utf8'
).toString('base64') ).toString('base64')
expect( expect(
credentialsContent.indexOf( configContent.indexOf(
`http.${expectedServerUrl}/.extraheader AUTHORIZATION: basic ${basicCredential}` `http.${expectedServerUrl}/.extraheader AUTHORIZATION: basic ${basicCredential}`
) )
).toBeGreaterThanOrEqual(0) ).toBeGreaterThanOrEqual(0)
@@ -133,7 +120,7 @@ describe('git-auth-helper tests', () => {
'inject https://github.com as github server url' 'inject https://github.com as github server url'
it(configureAuth_AcceptsGitHubServerUrlSetToGHEC, async () => { it(configureAuth_AcceptsGitHubServerUrlSetToGHEC, async () => {
await testAuthHeader( await testAuthHeader(
configureAuth_AcceptsGitHubServerUrlSetToGHEC, configureAuth_AcceptsGitHubServerUrl,
'https://github.com' 'https://github.com'
) )
}) })
@@ -154,17 +141,12 @@ describe('git-auth-helper tests', () => {
// Act // Act
await authHelper.configureAuth() await authHelper.configureAuth()
// Assert config - check credentials config file (not local .git/config) // Assert config
const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter( const configContent = (
f => f.startsWith('git-credentials-') && f.endsWith('.config') await fs.promises.readFile(localGitConfigPath)
)
expect(credentialsFiles.length).toBe(1)
const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0])
const credentialsContent = (
await fs.promises.readFile(credentialsConfigPath)
).toString() ).toString()
expect( expect(
credentialsContent.indexOf( configContent.indexOf(
`http.https://github.com/.extraheader AUTHORIZATION` `http.https://github.com/.extraheader AUTHORIZATION`
) )
).toBeGreaterThanOrEqual(0) ).toBeGreaterThanOrEqual(0)
@@ -269,16 +251,13 @@ describe('git-auth-helper tests', () => {
expectedSshCommand expectedSshCommand
) )
// Assert git config // Asserty git config
const gitConfigLines = (await fs.promises.readFile(localGitConfigPath)) const gitConfigLines = (await fs.promises.readFile(localGitConfigPath))
.toString() .toString()
.split('\n') .split('\n')
.filter(x => x) .filter(x => x)
// Should have includeIf entries pointing to credentials file expect(gitConfigLines).toHaveLength(1)
expect(gitConfigLines.length).toBeGreaterThan(0) expect(gitConfigLines[0]).toMatch(/^http\./)
expect(
gitConfigLines.some(line => line.indexOf('includeIf.gitdir:') >= 0)
).toBeTruthy()
}) })
const configureAuth_setsSshCommandWhenPersistCredentialsTrue = const configureAuth_setsSshCommandWhenPersistCredentialsTrue =
@@ -440,20 +419,8 @@ describe('git-auth-helper tests', () => {
expect( expect(
configContent.indexOf('value-from-global-config') configContent.indexOf('value-from-global-config')
).toBeGreaterThanOrEqual(0) ).toBeGreaterThanOrEqual(0)
// Global config should have include.path pointing to credentials file
expect(configContent.indexOf('include.path')).toBeGreaterThanOrEqual(0)
// Check credentials in the separate config file
const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
f => f.startsWith('git-credentials-') && f.endsWith('.config')
)
expect(credentialsFiles.length).toBeGreaterThan(0)
const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0])
const credentialsContent = (
await fs.promises.readFile(credentialsConfigPath)
).toString()
expect( expect(
credentialsContent.indexOf( configContent.indexOf(
`http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
) )
).toBeGreaterThanOrEqual(0) ).toBeGreaterThanOrEqual(0)
@@ -496,20 +463,8 @@ describe('git-auth-helper tests', () => {
const configContent = ( const configContent = (
await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig')) await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
).toString() ).toString()
// Global config should have include.path pointing to credentials file
expect(configContent.indexOf('include.path')).toBeGreaterThanOrEqual(0)
// Check credentials in the separate config file
const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
f => f.startsWith('git-credentials-') && f.endsWith('.config')
)
expect(credentialsFiles.length).toBeGreaterThan(0)
const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0])
const credentialsContent = (
await fs.promises.readFile(credentialsConfigPath)
).toString()
expect( expect(
credentialsContent.indexOf( configContent.indexOf(
`http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
) )
).toBeGreaterThanOrEqual(0) ).toBeGreaterThanOrEqual(0)
@@ -595,15 +550,15 @@ describe('git-auth-helper tests', () => {
await authHelper.configureSubmoduleAuth() await authHelper.configureSubmoduleAuth()
// Assert // Assert
// Should configure insteadOf (2 calls for two values) expect(mockSubmoduleForeach).toHaveBeenCalledTimes(4)
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3)
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch( expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
/unset-all.*insteadOf/ /unset-all.*insteadOf/
) )
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch( expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(
/url.*insteadOf.*git@github.com:/ /url.*insteadOf.*git@github.com:/
) )
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch( expect(mockSubmoduleForeach.mock.calls[3][0]).toMatch(
/url.*insteadOf.*org-123456@github.com:/ /url.*insteadOf.*org-123456@github.com:/
) )
} }
@@ -634,12 +589,12 @@ describe('git-auth-helper tests', () => {
await authHelper.configureSubmoduleAuth() await authHelper.configureSubmoduleAuth()
// Assert // Assert
// Should configure sshCommand (1 call) expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3)
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(2)
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch( expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
/unset-all.*insteadOf/ /unset-all.*insteadOf/
) )
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/core\.sshCommand/) expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(/core\.sshCommand/)
} }
) )
@@ -705,35 +660,19 @@ describe('git-auth-helper tests', () => {
await setup(removeAuth_removesToken) await setup(removeAuth_removesToken)
const authHelper = gitAuthHelper.createAuthHelper(git, settings) const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth() await authHelper.configureAuth()
let gitConfigContent = (
// Sanity check - verify includeIf entries exist in local config
let localConfigContent = (
await fs.promises.readFile(localGitConfigPath) await fs.promises.readFile(localGitConfigPath)
).toString() ).toString()
expect( expect(gitConfigContent.indexOf('http.')).toBeGreaterThanOrEqual(0) // sanity check
localConfigContent.indexOf('includeIf.gitdir:')
).toBeGreaterThanOrEqual(0)
// Sanity check - verify credentials file exists
let credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
f => f.startsWith('git-credentials-') && f.endsWith('.config')
)
expect(credentialsFiles.length).toBe(1)
// Act // Act
await authHelper.removeAuth() await authHelper.removeAuth()
// Assert includeIf entries removed from local git config // Assert git config
localConfigContent = ( gitConfigContent = (
await fs.promises.readFile(localGitConfigPath) await fs.promises.readFile(localGitConfigPath)
).toString() ).toString()
expect(localConfigContent.indexOf('includeIf.gitdir:')).toBeLessThan(0) expect(gitConfigContent.indexOf('http.')).toBeLessThan(0)
// Assert credentials config file deleted
credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
f => f.startsWith('git-credentials-') && f.endsWith('.config')
)
expect(credentialsFiles.length).toBe(0)
}) })
const removeGlobalConfig_removesOverride = const removeGlobalConfig_removesOverride =
@@ -762,52 +701,6 @@ describe('git-auth-helper tests', () => {
} }
} }
}) })
const testCredentialsConfigPath_matchesCredentialsConfigPaths =
'testCredentialsConfigPath matches credentials config paths'
it(testCredentialsConfigPath_matchesCredentialsConfigPaths, async () => {
// Arrange
await setup(testCredentialsConfigPath_matchesCredentialsConfigPaths)
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
// Get a real credentials config path
const credentialsConfigPath = await (
authHelper as any
).getCredentialsConfigPath()
// Act & Assert
expect(
(authHelper as any).testCredentialsConfigPath(credentialsConfigPath)
).toBe(true)
expect(
(authHelper as any).testCredentialsConfigPath(
'/some/path/git-credentials-12345678-abcd-1234-5678-123456789012.config'
)
).toBe(true)
expect(
(authHelper as any).testCredentialsConfigPath(
'/some/path/git-credentials-abcdef12-3456-7890-abcd-ef1234567890.config'
)
).toBe(true)
// Test invalid paths
expect(
(authHelper as any).testCredentialsConfigPath(
'/some/path/other-config.config'
)
).toBe(false)
expect(
(authHelper as any).testCredentialsConfigPath(
'/some/path/git-credentials-invalid.config'
)
).toBe(false)
expect(
(authHelper as any).testCredentialsConfigPath(
'/some/path/git-credentials-.config'
)
).toBe(false)
expect((authHelper as any).testCredentialsConfigPath('')).toBe(false)
})
}) })
async function setup(testName: string): Promise<void> { async function setup(testName: string): Promise<void> {
@@ -822,7 +715,6 @@ async function setup(testName: string): Promise<void> {
await fs.promises.mkdir(tempHomedir, {recursive: true}) await fs.promises.mkdir(tempHomedir, {recursive: true})
process.env['RUNNER_TEMP'] = runnerTemp process.env['RUNNER_TEMP'] = runnerTemp
process.env['HOME'] = tempHomedir process.env['HOME'] = tempHomedir
process.env['GITHUB_WORKSPACE'] = workspace
// Create git config // Create git config
globalGitConfigPath = path.join(tempHomedir, '.gitconfig') globalGitConfigPath = path.join(tempHomedir, '.gitconfig')
@@ -841,20 +733,10 @@ async function setup(testName: string): Promise<void> {
checkout: jest.fn(), checkout: jest.fn(),
checkoutDetach: jest.fn(), checkoutDetach: jest.fn(),
config: jest.fn( config: jest.fn(
async ( async (key: string, value: string, globalConfig?: boolean) => {
key: string, const configPath = globalConfig
value: string, ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
globalConfig?: boolean, : localGitConfigPath
add?: boolean,
configFile?: string
) => {
const configPath =
configFile ||
(globalConfig
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
: localGitConfigPath)
// Ensure directory exists
await fs.promises.mkdir(path.dirname(configPath), {recursive: true})
await fs.promises.appendFile(configPath, `\n${key} ${value}`) await fs.promises.appendFile(configPath, `\n${key} ${value}`)
} }
), ),
@@ -874,7 +756,6 @@ async function setup(testName: string): Promise<void> {
env: {}, env: {},
fetch: jest.fn(), fetch: jest.fn(),
getDefaultBranch: jest.fn(), getDefaultBranch: jest.fn(),
getSubmoduleConfigPaths: jest.fn(async () => []),
getWorkingDirectory: jest.fn(() => workspace), getWorkingDirectory: jest.fn(() => workspace),
init: jest.fn(), init: jest.fn(),
isDetached: jest.fn(), isDetached: jest.fn(),
@@ -913,57 +794,8 @@ async function setup(testName: string): Promise<void> {
return true return true
} }
), ),
tryConfigUnsetValue: jest.fn(
async (
key: string,
value: string,
globalConfig?: boolean
): Promise<boolean> => {
const configPath = globalConfig
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
: localGitConfigPath
let content = await fs.promises.readFile(configPath)
let lines = content
.toString()
.split('\n')
.filter(x => x)
.filter(x => !(x.startsWith(key) && x.includes(value)))
await fs.promises.writeFile(configPath, lines.join('\n'))
return true
}
),
tryDisableAutomaticGarbageCollection: jest.fn(), tryDisableAutomaticGarbageCollection: jest.fn(),
tryGetFetchUrl: jest.fn(), tryGetFetchUrl: jest.fn(),
tryGetConfigValues: jest.fn(
async (key: string, globalConfig?: boolean): Promise<string[]> => {
const configPath = globalConfig
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
: localGitConfigPath
const content = await fs.promises.readFile(configPath)
const lines = content
.toString()
.split('\n')
.filter(x => x && x.startsWith(key))
.map(x => x.substring(key.length).trim())
return lines
}
),
tryGetConfigKeys: jest.fn(
async (pattern: string, globalConfig?: boolean): Promise<string[]> => {
const configPath = globalConfig
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
: localGitConfigPath
const content = await fs.promises.readFile(configPath)
const lines = content
.toString()
.split('\n')
.filter(x => x)
const keys = lines
.filter(x => new RegExp(pattern).test(x.split(' ')[0]))
.map(x => x.split(' ')[0])
return [...new Set(keys)] // Remove duplicates
}
),
tryReset: jest.fn(), tryReset: jest.fn(),
version: jest.fn() version: jest.fn()
} }
@@ -998,7 +830,6 @@ async function setup(testName: string): Promise<void> {
async function getActualSshKeyPath(): Promise<string> { async function getActualSshKeyPath(): Promise<string> {
let actualTempFiles = (await fs.promises.readdir(runnerTemp)) let actualTempFiles = (await fs.promises.readdir(runnerTemp))
.filter(x => !x.startsWith('git-credentials-')) // Exclude credentials config file
.sort() .sort()
.map(x => path.join(runnerTemp, x)) .map(x => path.join(runnerTemp, x))
if (actualTempFiles.length === 0) { if (actualTempFiles.length === 0) {
@@ -1012,7 +843,6 @@ async function getActualSshKeyPath(): Promise<string> {
async function getActualSshKnownHostsPath(): Promise<string> { async function getActualSshKnownHostsPath(): Promise<string> {
let actualTempFiles = (await fs.promises.readdir(runnerTemp)) let actualTempFiles = (await fs.promises.readdir(runnerTemp))
.filter(x => !x.startsWith('git-credentials-')) // Exclude credentials config file
.sort() .sort()
.map(x => path.join(runnerTemp, x)) .map(x => path.join(runnerTemp, x))
if (actualTempFiles.length === 0) { if (actualTempFiles.length === 0) {

View File

@@ -471,7 +471,6 @@ async function setup(testName: string): Promise<void> {
configExists: jest.fn(), configExists: jest.fn(),
fetch: jest.fn(), fetch: jest.fn(),
getDefaultBranch: jest.fn(), getDefaultBranch: jest.fn(),
getSubmoduleConfigPaths: jest.fn(async () => []),
getWorkingDirectory: jest.fn(() => repositoryPath), getWorkingDirectory: jest.fn(() => repositoryPath),
init: jest.fn(), init: jest.fn(),
isDetached: jest.fn(), isDetached: jest.fn(),
@@ -494,15 +493,12 @@ async function setup(testName: string): Promise<void> {
return true return true
}), }),
tryConfigUnset: jest.fn(), tryConfigUnset: jest.fn(),
tryConfigUnsetValue: jest.fn(),
tryDisableAutomaticGarbageCollection: jest.fn(), tryDisableAutomaticGarbageCollection: jest.fn(),
tryGetFetchUrl: jest.fn(async () => { tryGetFetchUrl: jest.fn(async () => {
// Sanity check - this function shouldn't be called when the .git directory doesn't exist // Sanity check - this function shouldn't be called when the .git directory doesn't exist
await fs.promises.stat(path.join(repositoryPath, '.git')) await fs.promises.stat(path.join(repositoryPath, '.git'))
return repositoryUrl return repositoryUrl
}), }),
tryGetConfigValues: jest.fn(),
tryGetConfigKeys: jest.fn(),
tryReset: jest.fn(async () => { tryReset: jest.fn(async () => {
return true return true
}), }),

View File

@@ -17,7 +17,7 @@ fi
echo "Testing persisted credential" echo "Testing persisted credential"
pushd ./submodules-recursive/submodule-level-1/submodule-level-2 pushd ./submodules-recursive/submodule-level-1/submodule-level-2
git config --local --includes --name-only --get-regexp http.+extraheader && git fetch git config --local --name-only --get-regexp http.+extraheader && git fetch
if [ "$?" != "0" ]; then if [ "$?" != "0" ]; then
echo "Failed to validate persisted credential" echo "Failed to validate persisted credential"
popd popd

View File

@@ -17,7 +17,7 @@ fi
echo "Testing persisted credential" echo "Testing persisted credential"
pushd ./submodules-true/submodule-level-1 pushd ./submodules-true/submodule-level-1
git config --local --includes --name-only --get-regexp http.+extraheader && git fetch git config --local --name-only --get-regexp http.+extraheader && git fetch
if [ "$?" != "0" ]; then if [ "$?" != "0" ]; then
echo "Failed to validate persisted credential" echo "Failed to validate persisted credential"
popd popd

View File

@@ -104,6 +104,6 @@ outputs:
commit: commit:
description: 'The commit SHA that was checked out' description: 'The commit SHA that was checked out'
runs: runs:
using: node24 using: node20
main: dist/index.js main: dist/index.js
post: dist/index.js post: dist/index.js

434
dist/index.js vendored
View File

@@ -162,7 +162,6 @@ class GitAuthHelper {
this.sshKeyPath = ''; this.sshKeyPath = '';
this.sshKnownHostsPath = ''; this.sshKnownHostsPath = '';
this.temporaryHomePath = ''; this.temporaryHomePath = '';
this.credentialsConfigPath = ''; // Path to separate credentials config file in RUNNER_TEMP
this.git = gitCommandManager; this.git = gitCommandManager;
this.settings = gitSourceSettings || {}; this.settings = gitSourceSettings || {};
// Token auth header // Token auth header
@@ -230,17 +229,15 @@ class GitAuthHelper {
configureGlobalAuth() { configureGlobalAuth() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
// 'configureTempGlobalConfig' noops if already set, just returns the path // 'configureTempGlobalConfig' noops if already set, just returns the path
yield this.configureTempGlobalConfig(); const newGitConfigPath = yield this.configureTempGlobalConfig();
try { try {
// Configure the token // Configure the token
yield this.configureToken(true); yield this.configureToken(newGitConfigPath, true);
// Configure HTTPS instead of SSH // Configure HTTPS instead of SSH
yield this.git.tryConfigUnset(this.insteadOfKey, true); yield this.git.tryConfigUnset(this.insteadOfKey, true);
if (!this.settings.sshKey) { if (!this.settings.sshKey) {
for (const insteadOfValue of this.insteadOfValues) { for (const insteadOfValue of this.insteadOfValues) {
yield this.git.config(this.insteadOfKey, insteadOfValue, true, // globalConfig? yield this.git.config(this.insteadOfKey, insteadOfValue, true, true);
true // add?
);
} }
} }
} }
@@ -255,34 +252,19 @@ class GitAuthHelper {
configureSubmoduleAuth() { configureSubmoduleAuth() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
// Remove possible previous HTTPS instead of SSH // Remove possible previous HTTPS instead of SSH
yield this.removeSubmoduleGitConfig(this.insteadOfKey); yield this.removeGitConfig(this.insteadOfKey, true);
if (this.settings.persistCredentials) { if (this.settings.persistCredentials) {
// Get the credentials config file path in RUNNER_TEMP // Configure a placeholder value. This approach avoids the credential being captured
const credentialsConfigPath = this.getCredentialsConfigPath(); // by process creation audit events, which are commonly logged. For more information,
// Container credentials config path // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
const containerCredentialsPath = path.posix.join('/github/runner_temp', path.basename(credentialsConfigPath)); const output = yield this.git.submoduleForeach(
// Get submodule config file paths. // wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline
const configPaths = yield this.git.getSubmoduleConfigPaths(this.settings.nestedSubmodules); `sh -c "git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`, this.settings.nestedSubmodules);
// For each submodule, configure includeIf entries pointing to the shared credentials file. // Replace the placeholder
// Configure both host and container paths to support Docker container actions. const configPaths = output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || [];
for (const configPath of configPaths) { for (const configPath of configPaths) {
// Submodule Git directory core.debug(`Replacing token placeholder in '${configPath}'`);
let submoduleGitDir = path.dirname(configPath); // The config file is at .git/modules/submodule-name/config yield this.replaceTokenPlaceholder(configPath);
submoduleGitDir = submoduleGitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows
// Configure host includeIf
yield this.git.config(`includeIf.gitdir:${submoduleGitDir}.path`, credentialsConfigPath, false, // globalConfig?
false, // add?
configPath);
// Container submodule git directory
const githubWorkspace = process.env['GITHUB_WORKSPACE'];
assert.ok(githubWorkspace, 'GITHUB_WORKSPACE is not defined');
let relativeSubmoduleGitDir = path.relative(githubWorkspace, submoduleGitDir);
relativeSubmoduleGitDir = relativeSubmoduleGitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows
const containerSubmoduleGitDir = path.posix.join('/github/workspace', relativeSubmoduleGitDir);
// Configure container includeIf
yield this.git.config(`includeIf.gitdir:${containerSubmoduleGitDir}.path`, containerCredentialsPath, false, // globalConfig?
false, // add?
configPath);
} }
if (this.settings.sshKey) { if (this.settings.sshKey) {
// Configure core.sshCommand // Configure core.sshCommand
@@ -313,10 +295,6 @@ class GitAuthHelper {
} }
}); });
} }
/**
* Configures SSH authentication by writing the SSH key and known hosts,
* and setting up the GIT_SSH_COMMAND environment variable.
*/
configureSsh() { configureSsh() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
if (!this.settings.sshKey) { if (!this.settings.sshKey) {
@@ -373,88 +351,43 @@ class GitAuthHelper {
} }
}); });
} }
/** configureToken(configPath, globalConfig) {
* Configures token-based authentication by creating a credentials config file
* and setting up includeIf entries to reference it.
* @param globalConfig Whether to configure global config instead of local
*/
configureToken(globalConfig) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
// Get the credentials config file path in RUNNER_TEMP // Validate args
const credentialsConfigPath = this.getCredentialsConfigPath(); assert.ok((configPath && globalConfig) || (!configPath && !globalConfig), 'Unexpected configureToken parameter combinations');
// Write placeholder to the separate credentials config file using git config. // Default config path
// This approach avoids the credential being captured by process creation audit events, if (!configPath && !globalConfig) {
// which are commonly logged. For more information, refer to configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config');
// https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing }
yield this.git.config(this.tokenConfigKey, this.tokenPlaceholderConfigValue, false, // globalConfig? // Configure a placeholder value. This approach avoids the credential being captured
false, // add? // by process creation audit events, which are commonly logged. For more information,
credentialsConfigPath); // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
// Replace the placeholder in the credentials config file yield this.git.config(this.tokenConfigKey, this.tokenPlaceholderConfigValue, globalConfig);
let content = (yield fs.promises.readFile(credentialsConfigPath)).toString(); // Replace the placeholder
yield this.replaceTokenPlaceholder(configPath || '');
});
}
replaceTokenPlaceholder(configPath) {
return __awaiter(this, void 0, void 0, function* () {
assert.ok(configPath, 'configPath is not defined');
let content = (yield fs.promises.readFile(configPath)).toString();
const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue); const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue);
if (placeholderIndex < 0 || if (placeholderIndex < 0 ||
placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue)) { placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue)) {
throw new Error(`Unable to replace auth placeholder in ${credentialsConfigPath}`); throw new Error(`Unable to replace auth placeholder in ${configPath}`);
} }
assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined'); assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined');
content = content.replace(this.tokenPlaceholderConfigValue, this.tokenConfigValue); content = content.replace(this.tokenPlaceholderConfigValue, this.tokenConfigValue);
yield fs.promises.writeFile(credentialsConfigPath, content); yield fs.promises.writeFile(configPath, content);
// Add include or includeIf to reference the credentials config
if (globalConfig) {
// Global config file is temporary
yield this.git.config('include.path', credentialsConfigPath, true // globalConfig?
);
}
else {
// Host git directory
let gitDir = path.join(this.git.getWorkingDirectory(), '.git');
gitDir = gitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows
// Configure host includeIf
const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`;
yield this.git.config(hostIncludeKey, credentialsConfigPath);
// Container git directory
const workingDirectory = this.git.getWorkingDirectory();
const githubWorkspace = process.env['GITHUB_WORKSPACE'];
assert.ok(githubWorkspace, 'GITHUB_WORKSPACE is not defined');
let relativePath = path.relative(githubWorkspace, workingDirectory);
relativePath = relativePath.replace(/\\/g, '/'); // Use forward slashes, even on Windows
const containerGitDir = path.posix.join('/github/workspace', relativePath, '.git');
// Container credentials config path
const containerCredentialsPath = path.posix.join('/github/runner_temp', path.basename(credentialsConfigPath));
// Configure container includeIf
const containerIncludeKey = `includeIf.gitdir:${containerGitDir}.path`;
yield this.git.config(containerIncludeKey, containerCredentialsPath);
}
}); });
} }
/**
* Gets or creates the path to the credentials config file in RUNNER_TEMP.
* @returns The absolute path to the credentials config file
*/
getCredentialsConfigPath() {
if (this.credentialsConfigPath) {
return this.credentialsConfigPath;
}
const runnerTemp = process.env['RUNNER_TEMP'] || '';
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined');
// Create a unique filename for this checkout instance
const configFileName = `git-credentials-${(0, uuid_1.v4)()}.config`;
this.credentialsConfigPath = path.join(runnerTemp, configFileName);
core.debug(`Credentials config path: ${this.credentialsConfigPath}`);
return this.credentialsConfigPath;
}
/**
* Removes SSH authentication configuration by cleaning up SSH keys,
* known hosts files, and SSH command configurations.
*/
removeSsh() { removeSsh() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
var _a, _b; var _a;
// SSH key // SSH key
const keyPath = this.sshKeyPath || stateHelper.SshKeyPath; const keyPath = this.sshKeyPath || stateHelper.SshKeyPath;
if (keyPath) { if (keyPath) {
try { try {
core.info(`Removing SSH key '${keyPath}'`);
yield io.rmRF(keyPath); yield io.rmRF(keyPath);
} }
catch (err) { catch (err) {
@@ -466,136 +399,37 @@ class GitAuthHelper {
const knownHostsPath = this.sshKnownHostsPath || stateHelper.SshKnownHostsPath; const knownHostsPath = this.sshKnownHostsPath || stateHelper.SshKnownHostsPath;
if (knownHostsPath) { if (knownHostsPath) {
try { try {
core.info(`Removing SSH known hosts '${knownHostsPath}'`);
yield io.rmRF(knownHostsPath); yield io.rmRF(knownHostsPath);
} }
catch (err) { catch (_b) {
core.debug(`${(_b = err === null || err === void 0 ? void 0 : err.message) !== null && _b !== void 0 ? _b : err}`); // Intentionally empty
core.warning(`Failed to remove SSH known hosts '${knownHostsPath}'`);
} }
} }
// SSH command // SSH command
core.info('Removing SSH command configuration');
yield this.removeGitConfig(SSH_COMMAND_KEY); yield this.removeGitConfig(SSH_COMMAND_KEY);
yield this.removeSubmoduleGitConfig(SSH_COMMAND_KEY);
}); });
} }
/**
* Removes token-based authentication by cleaning up HTTP headers,
* includeIf entries, and credentials config files.
*/
removeToken() { removeToken() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
var _a; // HTTP extra header
// Remove HTTP extra header
core.info('Removing HTTP extra header');
yield this.removeGitConfig(this.tokenConfigKey); yield this.removeGitConfig(this.tokenConfigKey);
yield this.removeSubmoduleGitConfig(this.tokenConfigKey);
// Collect credentials config paths that need to be removed
const credentialsPaths = new Set();
// Remove includeIf entries that point to git-credentials-*.config files
core.info('Removing includeIf entries pointing to credentials config files');
const mainCredentialsPaths = yield this.removeIncludeIfCredentials();
mainCredentialsPaths.forEach(path => credentialsPaths.add(path));
// Remove submodule includeIf entries that point to git-credentials-*.config files
const submoduleConfigPaths = yield this.git.getSubmoduleConfigPaths(true);
for (const configPath of submoduleConfigPaths) {
const submoduleCredentialsPaths = yield this.removeIncludeIfCredentials(configPath);
submoduleCredentialsPaths.forEach(path => credentialsPaths.add(path));
}
// Remove credentials config files
for (const credentialsPath of credentialsPaths) {
// Only remove credentials config files if they are under RUNNER_TEMP
const runnerTemp = process.env['RUNNER_TEMP'];
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined');
if (credentialsPath.startsWith(runnerTemp)) {
try {
core.info(`Removing credentials config '${credentialsPath}'`);
yield io.rmRF(credentialsPath);
}
catch (err) {
core.debug(`${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
core.warning(`Failed to remove credentials config '${credentialsPath}'`);
}
}
else {
core.debug(`Skipping removal of credentials config '${credentialsPath}' - not under RUNNER_TEMP`);
}
}
}); });
} }
/** removeGitConfig(configKey_1) {
* Removes a git config key from the local repository config. return __awaiter(this, arguments, void 0, function* (configKey, submoduleOnly = false) {
* @param configKey The git config key to remove if (!submoduleOnly) {
*/ if ((yield this.git.configExists(configKey)) &&
removeGitConfig(configKey) { !(yield this.git.tryConfigUnset(configKey))) {
return __awaiter(this, void 0, void 0, function* () { // Load the config contents
if ((yield this.git.configExists(configKey)) && core.warning(`Failed to remove '${configKey}' from the git config`);
!(yield this.git.tryConfigUnset(configKey))) { }
// Load the config contents
core.warning(`Failed to remove '${configKey}' from the git config`);
} }
});
}
/**
* Removes a git config key from all submodule configs.
* @param configKey The git config key to remove
*/
removeSubmoduleGitConfig(configKey) {
return __awaiter(this, void 0, void 0, function* () {
const pattern = regexpHelper.escape(configKey); const pattern = regexpHelper.escape(configKey);
yield this.git.submoduleForeach( yield this.git.submoduleForeach(
// Wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline. // wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline
`sh -c "git config --local --name-only --get-regexp '${pattern}' && git config --local --unset-all '${configKey}' || :"`, true); `sh -c "git config --local --name-only --get-regexp '${pattern}' && git config --local --unset-all '${configKey}' || :"`, true);
}); });
} }
/**
* Removes includeIf entries that point to git-credentials-*.config files.
* @param configPath Optional path to a specific git config file to operate on
* @returns Array of unique credentials config file paths that were found and removed
*/
removeIncludeIfCredentials(configPath) {
return __awaiter(this, void 0, void 0, function* () {
const credentialsPaths = new Set();
try {
// Get all includeIf.gitdir keys
const keys = yield this.git.tryGetConfigKeys('^includeIf\\.gitdir:', false, // globalConfig?
configPath);
for (const key of keys) {
// Get all values for this key
const values = yield this.git.tryGetConfigValues(key, false, // globalConfig?
configPath);
if (values.length > 0) {
// Remove only values that match git-credentials-<uuid>.config pattern
for (const value of values) {
if (this.testCredentialsConfigPath(value)) {
credentialsPaths.add(value);
yield this.git.tryConfigUnsetValue(key, value, false, configPath);
}
}
}
}
}
catch (err) {
// Ignore errors - this is cleanup code
if (configPath) {
core.debug(`Error during includeIf cleanup for ${configPath}: ${err}`);
}
else {
core.debug(`Error during includeIf cleanup: ${err}`);
}
}
return Array.from(credentialsPaths);
});
}
/**
* Tests if a path matches the git-credentials-*.config pattern.
* @param path The path to test
* @returns True if the path matches the credentials config pattern
*/
testCredentialsConfigPath(path) {
return /git-credentials-[0-9a-f-]+\.config$/i.test(path);
}
} }
@@ -793,15 +627,9 @@ class GitCommandManager {
yield this.execGit(args); yield this.execGit(args);
}); });
} }
config(configKey, configValue, globalConfig, add, configFile) { config(configKey, configValue, globalConfig, add) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const args = ['config']; const args = ['config', globalConfig ? '--global' : '--local'];
if (configFile) {
args.push('--file', configFile);
}
else {
args.push(globalConfig ? '--global' : '--local');
}
if (add) { if (add) {
args.push('--add'); args.push('--add');
} }
@@ -878,16 +706,6 @@ class GitCommandManager {
throw new Error('Unexpected output when retrieving default branch'); throw new Error('Unexpected output when retrieving default branch');
}); });
} }
getSubmoduleConfigPaths(recursive) {
return __awaiter(this, void 0, void 0, function* () {
// Get submodule config file paths.
// Use `--show-origin` to get the config file path for each submodule.
const output = yield this.submoduleForeach(`git config --local --show-origin --name-only --get-regexp remote.origin.url`, recursive);
// Extract config file paths from the output (lines starting with "file:").
const configPaths = output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || [];
return configPaths;
});
}
getWorkingDirectory() { getWorkingDirectory() {
return this.workingDirectory; return this.workingDirectory;
} }
@@ -1018,20 +836,6 @@ class GitCommandManager {
return output.exitCode === 0; return output.exitCode === 0;
}); });
} }
tryConfigUnsetValue(configKey, configValue, globalConfig, configFile) {
return __awaiter(this, void 0, void 0, function* () {
const args = ['config'];
if (configFile) {
args.push('--file', configFile);
}
else {
args.push(globalConfig ? '--global' : '--local');
}
args.push('--unset', configKey, configValue);
const output = yield this.execGit(args, true);
return output.exitCode === 0;
});
}
tryDisableAutomaticGarbageCollection() { tryDisableAutomaticGarbageCollection() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const output = yield this.execGit(['config', '--local', 'gc.auto', '0'], true); const output = yield this.execGit(['config', '--local', 'gc.auto', '0'], true);
@@ -1051,46 +855,6 @@ class GitCommandManager {
return stdout; return stdout;
}); });
} }
tryGetConfigValues(configKey, globalConfig, configFile) {
return __awaiter(this, void 0, void 0, function* () {
const args = ['config'];
if (configFile) {
args.push('--file', configFile);
}
else {
args.push(globalConfig ? '--global' : '--local');
}
args.push('--get-all', configKey);
const output = yield this.execGit(args, true);
if (output.exitCode !== 0) {
return [];
}
return output.stdout
.trim()
.split('\n')
.filter(value => value.trim());
});
}
tryGetConfigKeys(pattern, globalConfig, configFile) {
return __awaiter(this, void 0, void 0, function* () {
const args = ['config'];
if (configFile) {
args.push('--file', configFile);
}
else {
args.push(globalConfig ? '--global' : '--local');
}
args.push('--name-only', '--get-regexp', pattern);
const output = yield this.execGit(args, true);
if (output.exitCode !== 0) {
return [];
}
return output.stdout
.trim()
.split('\n')
.filter(key => key.trim());
});
}
tryReset() { tryReset() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const output = yield this.execGit(['reset', '--hard', 'HEAD'], true); const output = yield this.execGit(['reset', '--hard', 'HEAD'], true);
@@ -8038,7 +7802,7 @@ module.exports = __toCommonJS(dist_src_exports);
var import_universal_user_agent = __nccwpck_require__(5030); var import_universal_user_agent = __nccwpck_require__(5030);
// pkg/dist-src/version.js // pkg/dist-src/version.js
var VERSION = "9.0.6"; var VERSION = "9.0.5";
// pkg/dist-src/defaults.js // pkg/dist-src/defaults.js
var userAgent = `octokit-endpoint.js/${VERSION} ${(0, import_universal_user_agent.getUserAgent)()}`; var userAgent = `octokit-endpoint.js/${VERSION} ${(0, import_universal_user_agent.getUserAgent)()}`;
@@ -8143,9 +7907,9 @@ function addQueryParameters(url, parameters) {
} }
// pkg/dist-src/util/extract-url-variable-names.js // pkg/dist-src/util/extract-url-variable-names.js
var urlVariableRegex = /\{[^{}}]+\}/g; var urlVariableRegex = /\{[^}]+\}/g;
function removeNonChars(variableName) { function removeNonChars(variableName) {
return variableName.replace(/(?:^\W+)|(?:(?<!\W)\W+$)/g, "").split(/,/); return variableName.replace(/^\W+|\W+$/g, "").split(/,/);
} }
function extractUrlVariableNames(url) { function extractUrlVariableNames(url) {
const matches = url.match(urlVariableRegex); const matches = url.match(urlVariableRegex);
@@ -8331,7 +8095,7 @@ function parse(options) {
} }
if (url.endsWith("/graphql")) { if (url.endsWith("/graphql")) {
if (options.mediaType.previews?.length) { if (options.mediaType.previews?.length) {
const previewsFromAcceptHeader = headers.accept.match(/(?<![\w-])[\w-]+(?=-preview)/g) || []; const previewsFromAcceptHeader = headers.accept.match(/[\w-]+(?=-preview)/g) || [];
headers.accept = previewsFromAcceptHeader.concat(options.mediaType.previews).map((preview) => { headers.accept = previewsFromAcceptHeader.concat(options.mediaType.previews).map((preview) => {
const format = options.mediaType.format ? `.${options.mediaType.format}` : "+json"; const format = options.mediaType.format ? `.${options.mediaType.format}` : "+json";
return `application/vnd.github.${preview}-preview${format}`; return `application/vnd.github.${preview}-preview${format}`;
@@ -8580,7 +8344,7 @@ __export(dist_src_exports, {
module.exports = __toCommonJS(dist_src_exports); module.exports = __toCommonJS(dist_src_exports);
// pkg/dist-src/version.js // pkg/dist-src/version.js
var VERSION = "9.2.2"; var VERSION = "9.2.1";
// pkg/dist-src/normalize-paginated-list-response.js // pkg/dist-src/normalize-paginated-list-response.js
function normalizePaginatedListResponse(response) { function normalizePaginatedListResponse(response) {
@@ -8628,7 +8392,7 @@ function iterator(octokit, route, parameters) {
const response = await requestMethod({ method, url, headers }); const response = await requestMethod({ method, url, headers });
const normalizedResponse = normalizePaginatedListResponse(response); const normalizedResponse = normalizePaginatedListResponse(response);
url = ((normalizedResponse.headers.link || "").match( url = ((normalizedResponse.headers.link || "").match(
/<([^<>]+)>;\s*rel="next"/ /<([^>]+)>;\s*rel="next"/
) || [])[1]; ) || [])[1];
return { value: normalizedResponse }; return { value: normalizedResponse };
} catch (error) { } catch (error) {
@@ -11180,7 +10944,7 @@ var RequestError = class extends Error {
if (options.request.headers.authorization) { if (options.request.headers.authorization) {
requestCopy.headers = Object.assign({}, options.request.headers, { requestCopy.headers = Object.assign({}, options.request.headers, {
authorization: options.request.headers.authorization.replace( authorization: options.request.headers.authorization.replace(
/(?<! ) .*$/, / .*$/,
" [REDACTED]" " [REDACTED]"
) )
}); });
@@ -11248,7 +11012,7 @@ var import_endpoint = __nccwpck_require__(9440);
var import_universal_user_agent = __nccwpck_require__(5030); var import_universal_user_agent = __nccwpck_require__(5030);
// pkg/dist-src/version.js // pkg/dist-src/version.js
var VERSION = "8.4.1"; var VERSION = "8.4.0";
// pkg/dist-src/is-plain-object.js // pkg/dist-src/is-plain-object.js
function isPlainObject(value) { function isPlainObject(value) {
@@ -11307,7 +11071,7 @@ function fetchWrapper(requestOptions) {
headers[keyAndValue[0]] = keyAndValue[1]; headers[keyAndValue[0]] = keyAndValue[1];
} }
if ("deprecation" in headers) { if ("deprecation" in headers) {
const matches = headers.link && headers.link.match(/<([^<>]+)>; rel="deprecation"/); const matches = headers.link && headers.link.match(/<([^>]+)>; rel="deprecation"/);
const deprecationLink = matches && matches.pop(); const deprecationLink = matches && matches.pop();
log.warn( log.warn(
`[@octokit/request] "${requestOptions.method} ${requestOptions.url}" is deprecated. It is scheduled to be removed on ${headers.sunset}${deprecationLink ? `. See ${deprecationLink}` : ""}` `[@octokit/request] "${requestOptions.method} ${requestOptions.url}" is deprecated. It is scheduled to be removed on ${headers.sunset}${deprecationLink ? `. See ${deprecationLink}` : ""}`
@@ -18961,7 +18725,7 @@ module.exports = {
const { parseSetCookie } = __nccwpck_require__(4408) const { parseSetCookie } = __nccwpck_require__(4408)
const { stringify } = __nccwpck_require__(3121) const { stringify, getHeadersList } = __nccwpck_require__(3121)
const { webidl } = __nccwpck_require__(1744) const { webidl } = __nccwpck_require__(1744)
const { Headers } = __nccwpck_require__(554) const { Headers } = __nccwpck_require__(554)
@@ -19037,13 +18801,14 @@ function getSetCookies (headers) {
webidl.brandCheck(headers, Headers, { strict: false }) webidl.brandCheck(headers, Headers, { strict: false })
const cookies = headers.getSetCookie() const cookies = getHeadersList(headers).cookies
if (!cookies) { if (!cookies) {
return [] return []
} }
return cookies.map((pair) => parseSetCookie(pair)) // In older versions of undici, cookies is a list of name:value.
return cookies.map((pair) => parseSetCookie(Array.isArray(pair) ? pair[1] : pair))
} }
/** /**
@@ -19471,15 +19236,14 @@ module.exports = {
/***/ }), /***/ }),
/***/ 3121: /***/ 3121:
/***/ ((module) => { /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
"use strict"; "use strict";
/** const assert = __nccwpck_require__(9491)
* @param {string} value const { kHeadersList } = __nccwpck_require__(2785)
* @returns {boolean}
*/
function isCTLExcludingHtab (value) { function isCTLExcludingHtab (value) {
if (value.length === 0) { if (value.length === 0) {
return false return false
@@ -19740,13 +19504,31 @@ function stringify (cookie) {
return out.join('; ') return out.join('; ')
} }
let kHeadersListNode
function getHeadersList (headers) {
if (headers[kHeadersList]) {
return headers[kHeadersList]
}
if (!kHeadersListNode) {
kHeadersListNode = Object.getOwnPropertySymbols(headers).find(
(symbol) => symbol.description === 'headers list'
)
assert(kHeadersListNode, 'Headers cannot be parsed')
}
const headersList = headers[kHeadersListNode]
assert(headersList)
return headersList
}
module.exports = { module.exports = {
isCTLExcludingHtab, isCTLExcludingHtab,
validateCookieName, stringify,
validateCookiePath, getHeadersList
validateCookieValue,
toIMFDate,
stringify
} }
@@ -21675,14 +21457,6 @@ const { isUint8Array, isArrayBuffer } = __nccwpck_require__(9830)
const { File: UndiciFile } = __nccwpck_require__(8511) const { File: UndiciFile } = __nccwpck_require__(8511)
const { parseMIMEType, serializeAMimeType } = __nccwpck_require__(685) const { parseMIMEType, serializeAMimeType } = __nccwpck_require__(685)
let random
try {
const crypto = __nccwpck_require__(6005)
random = (max) => crypto.randomInt(0, max)
} catch {
random = (max) => Math.floor(Math.random(max))
}
let ReadableStream = globalThis.ReadableStream let ReadableStream = globalThis.ReadableStream
/** @type {globalThis['File']} */ /** @type {globalThis['File']} */
@@ -21768,7 +21542,7 @@ function extractBody (object, keepalive = false) {
// Set source to a copy of the bytes held by object. // Set source to a copy of the bytes held by object.
source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength)) source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength))
} else if (util.isFormDataLike(object)) { } else if (util.isFormDataLike(object)) {
const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}` const boundary = `----formdata-undici-0${`${Math.floor(Math.random() * 1e11)}`.padStart(11, '0')}`
const prefix = `--${boundary}\r\nContent-Disposition: form-data` const prefix = `--${boundary}\r\nContent-Disposition: form-data`
/*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */ /*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
@@ -23750,7 +23524,6 @@ const {
isValidHeaderName, isValidHeaderName,
isValidHeaderValue isValidHeaderValue
} = __nccwpck_require__(2538) } = __nccwpck_require__(2538)
const util = __nccwpck_require__(3837)
const { webidl } = __nccwpck_require__(1744) const { webidl } = __nccwpck_require__(1744)
const assert = __nccwpck_require__(9491) const assert = __nccwpck_require__(9491)
@@ -24304,9 +24077,6 @@ Object.defineProperties(Headers.prototype, {
[Symbol.toStringTag]: { [Symbol.toStringTag]: {
value: 'Headers', value: 'Headers',
configurable: true configurable: true
},
[util.inspect.custom]: {
enumerable: false
} }
}) })
@@ -33483,20 +33253,6 @@ class Pool extends PoolBase {
? { ...options.interceptors } ? { ...options.interceptors }
: undefined : undefined
this[kFactory] = factory this[kFactory] = factory
this.on('connectionError', (origin, targets, error) => {
// If a connection error occurs, we remove the client from the pool,
// and emit a connectionError event. They will not be re-used.
// Fixes https://github.com/nodejs/undici/issues/3895
for (const target of targets) {
// Do not use kRemoveClient here, as it will close the client,
// but the client cannot be closed in this state.
const idx = this[kClients].indexOf(target)
if (idx !== -1) {
this[kClients].splice(idx, 1)
}
}
})
} }
[kGetDispatcher] () { [kGetDispatcher] () {
@@ -36652,14 +36408,6 @@ module.exports = require("net");
/***/ }), /***/ }),
/***/ 6005:
/***/ ((module) => {
"use strict";
module.exports = require("node:crypto");
/***/ }),
/***/ 5673: /***/ 5673:
/***/ ((module) => { /***/ ((module) => {

332
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "checkout", "name": "checkout",
"version": "5.0.0", "version": "4.2.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "checkout", "name": "checkout",
"version": "5.0.0", "version": "4.2.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.1", "@actions/core": "^1.10.1",
@@ -18,7 +18,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/node": "^24.1.0", "@types/node": "^20.12.12",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^7.9.0", "@typescript-eslint/eslint-plugin": "^7.9.0",
"@typescript-eslint/parser": "^7.9.0", "@typescript-eslint/parser": "^7.9.0",
@@ -129,15 +129,13 @@
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.27.1", "version": "7.24.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-validator-identifier": "^7.27.1", "@babel/highlight": "^7.24.2",
"js-tokens": "^4.0.0", "picocolors": "^1.0.0"
"picocolors": "^1.1.1"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -312,21 +310,19 @@
} }
}, },
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
"version": "7.27.1", "version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/helper-validator-identifier": { "node_modules/@babel/helper-validator-identifier": {
"version": "7.27.1", "version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
@@ -341,28 +337,110 @@
} }
}, },
"node_modules/@babel/helpers": { "node_modules/@babel/helpers": {
"version": "7.28.2", "version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz",
"integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@babel/template": "^7.27.2", "@babel/template": "^7.24.0",
"@babel/types": "^7.28.2" "@babel/traverse": "^7.24.1",
"@babel/types": "^7.24.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/highlight": {
"version": "7.28.0", "version": "7.24.2",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz",
"integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.28.0" "@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
}, },
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/highlight/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/highlight/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/@babel/highlight/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"node_modules/@babel/highlight/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true,
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@babel/highlight/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/highlight/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/parser": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz",
"integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==",
"dev": true,
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
}, },
@@ -548,25 +626,26 @@
} }
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.28.2", "version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz",
"integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==",
"dev": true, "dev": true,
"license": "MIT", "dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.27.2", "version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz",
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.23.5",
"@babel/parser": "^7.27.2", "@babel/parser": "^7.24.0",
"@babel/types": "^7.27.1" "@babel/types": "^7.24.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -603,14 +682,14 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.28.2", "version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.27.1", "@babel/helper-string-parser": "^7.23.4",
"@babel/helper-validator-identifier": "^7.27.1" "@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -670,11 +749,10 @@
} }
}, },
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -730,11 +808,10 @@
} }
}, },
"node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -1266,10 +1343,9 @@
} }
}, },
"node_modules/@octokit/endpoint": { "node_modules/@octokit/endpoint": {
"version": "9.0.6", "version": "9.0.5",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.5.tgz",
"integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", "integrity": "sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==",
"license": "MIT",
"dependencies": { "dependencies": {
"@octokit/types": "^13.1.0", "@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0" "universal-user-agent": "^6.0.0"
@@ -1297,10 +1373,9 @@
"integrity": "sha512-pGUdSP+eEPfZiQHNkZI0U01HLipxncisdJQB4G//OAmfeO8sqTQ9KRa0KF03TUPCziNsoXUrTg4B2Q1EX++T0Q==" "integrity": "sha512-pGUdSP+eEPfZiQHNkZI0U01HLipxncisdJQB4G//OAmfeO8sqTQ9KRa0KF03TUPCziNsoXUrTg4B2Q1EX++T0Q=="
}, },
"node_modules/@octokit/plugin-paginate-rest": { "node_modules/@octokit/plugin-paginate-rest": {
"version": "9.2.2", "version": "9.2.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.1.tgz",
"integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", "integrity": "sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==",
"license": "MIT",
"dependencies": { "dependencies": {
"@octokit/types": "^12.6.0" "@octokit/types": "^12.6.0"
}, },
@@ -1352,13 +1427,12 @@
} }
}, },
"node_modules/@octokit/request": { "node_modules/@octokit/request": {
"version": "8.4.1", "version": "8.4.0",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz",
"integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", "integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==",
"license": "MIT",
"dependencies": { "dependencies": {
"@octokit/endpoint": "^9.0.6", "@octokit/endpoint": "^9.0.1",
"@octokit/request-error": "^5.1.1", "@octokit/request-error": "^5.1.0",
"@octokit/types": "^13.1.0", "@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0" "universal-user-agent": "^6.0.0"
}, },
@@ -1367,10 +1441,9 @@
} }
}, },
"node_modules/@octokit/request-error": { "node_modules/@octokit/request-error": {
"version": "5.1.1", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.0.tgz",
"integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", "integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==",
"license": "MIT",
"dependencies": { "dependencies": {
"@octokit/types": "^13.1.0", "@octokit/types": "^13.1.0",
"deprecation": "^2.0.0", "deprecation": "^2.0.0",
@@ -1515,12 +1588,12 @@
"dev": true "dev": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "24.1.0", "version": "20.12.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz",
"integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"undici-types": "~7.8.0" "undici-types": "~5.26.4"
} }
}, },
"node_modules/@types/stack-utils": { "node_modules/@types/stack-utils": {
@@ -2165,11 +2238,10 @@
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "2.0.2", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
@@ -2430,11 +2502,10 @@
} }
}, },
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"path-key": "^3.1.0", "path-key": "^3.1.0",
"shebang-command": "^2.0.0", "shebang-command": "^2.0.0",
@@ -3104,11 +3175,10 @@
} }
}, },
"node_modules/eslint-plugin-import/node_modules/brace-expansion": { "node_modules/eslint-plugin-import/node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -3203,11 +3273,10 @@
} }
}, },
"node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -3302,11 +3371,10 @@
} }
}, },
"node_modules/eslint/node_modules/brace-expansion": { "node_modules/eslint/node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -3768,11 +3836,10 @@
} }
}, },
"node_modules/glob/node_modules/brace-expansion": { "node_modules/glob/node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -4568,11 +4635,10 @@
} }
}, },
"node_modules/jake/node_modules/brace-expansion": { "node_modules/jake/node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -5182,8 +5248,7 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true, "dev": true
"license": "MIT"
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "4.1.0", "version": "4.1.0",
@@ -5463,13 +5528,12 @@
} }
}, },
"node_modules/micromatch": { "node_modules/micromatch": {
"version": "4.0.8", "version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"braces": "^3.0.3", "braces": "^3.0.2",
"picomatch": "^2.3.1" "picomatch": "^2.3.1"
}, },
"engines": { "engines": {
@@ -5805,11 +5869,10 @@
} }
}, },
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true, "dev": true
"license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.1",
@@ -6052,6 +6115,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"dev": true
},
"node_modules/regexp.prototype.flags": { "node_modules/regexp.prototype.flags": {
"version": "1.5.2", "version": "1.5.2",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
@@ -6553,11 +6622,10 @@
} }
}, },
"node_modules/test-exclude/node_modules/brace-expansion": { "node_modules/test-exclude/node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -6587,6 +6655,15 @@
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
"dev": true "dev": true
}, },
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -6853,10 +6930,9 @@
} }
}, },
"node_modules/undici": { "node_modules/undici": {
"version": "5.29.0", "version": "5.28.4",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==",
"license": "MIT",
"dependencies": { "dependencies": {
"@fastify/busboy": "^2.0.0" "@fastify/busboy": "^2.0.0"
}, },
@@ -6865,9 +6941,9 @@
} }
}, },
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "7.8.0", "version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true "dev": true
}, },
"node_modules/universal-user-agent": { "node_modules/universal-user-agent": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "checkout", "name": "checkout",
"version": "5.0.0", "version": "4.2.2",
"description": "checkout action", "description": "checkout action",
"main": "lib/main.js", "main": "lib/main.js",
"scripts": { "scripts": {
@@ -37,7 +37,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/node": "^24.1.0", "@types/node": "^20.12.12",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^7.9.0", "@typescript-eslint/eslint-plugin": "^7.9.0",
"@typescript-eslint/parser": "^7.9.0", "@typescript-eslint/parser": "^7.9.0",

View File

@@ -43,7 +43,6 @@ class GitAuthHelper {
private sshKeyPath = '' private sshKeyPath = ''
private sshKnownHostsPath = '' private sshKnownHostsPath = ''
private temporaryHomePath = '' private temporaryHomePath = ''
private credentialsConfigPath = '' // Path to separate credentials config file in RUNNER_TEMP
constructor( constructor(
gitCommandManager: IGitCommandManager, gitCommandManager: IGitCommandManager,
@@ -68,7 +67,7 @@ class GitAuthHelper {
this.insteadOfValues.push(`git@${serverUrl.hostname}:`) this.insteadOfValues.push(`git@${serverUrl.hostname}:`)
if (this.settings.workflowOrganizationId) { if (this.settings.workflowOrganizationId) {
this.insteadOfValues.push( this.insteadOfValues.push(
`org-${this.settings.workflowOrganizationId}@github.com:` `org-${this.settings.workflowOrganizationId}@${serverUrl.hostname}:`
) )
} }
} }
@@ -127,21 +126,16 @@ class GitAuthHelper {
async configureGlobalAuth(): Promise<void> { async configureGlobalAuth(): Promise<void> {
// 'configureTempGlobalConfig' noops if already set, just returns the path // 'configureTempGlobalConfig' noops if already set, just returns the path
await this.configureTempGlobalConfig() const newGitConfigPath = await this.configureTempGlobalConfig()
try { try {
// Configure the token // Configure the token
await this.configureToken(true) await this.configureToken(newGitConfigPath, true)
// Configure HTTPS instead of SSH // Configure HTTPS instead of SSH
await this.git.tryConfigUnset(this.insteadOfKey, true) await this.git.tryConfigUnset(this.insteadOfKey, true)
if (!this.settings.sshKey) { if (!this.settings.sshKey) {
for (const insteadOfValue of this.insteadOfValues) { for (const insteadOfValue of this.insteadOfValues) {
await this.git.config( await this.git.config(this.insteadOfKey, insteadOfValue, true, true)
this.insteadOfKey,
insteadOfValue,
true, // globalConfig?
true // add?
)
} }
} }
} catch (err) { } catch (err) {
@@ -156,60 +150,24 @@ class GitAuthHelper {
async configureSubmoduleAuth(): Promise<void> { async configureSubmoduleAuth(): Promise<void> {
// Remove possible previous HTTPS instead of SSH // Remove possible previous HTTPS instead of SSH
await this.removeSubmoduleGitConfig(this.insteadOfKey) await this.removeGitConfig(this.insteadOfKey, true)
if (this.settings.persistCredentials) { if (this.settings.persistCredentials) {
// Get the credentials config file path in RUNNER_TEMP // Configure a placeholder value. This approach avoids the credential being captured
const credentialsConfigPath = this.getCredentialsConfigPath() // by process creation audit events, which are commonly logged. For more information,
// refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
// Container credentials config path const output = await this.git.submoduleForeach(
const containerCredentialsPath = path.posix.join( // wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline
'/github/runner_temp', `sh -c "git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`,
path.basename(credentialsConfigPath)
)
// Get submodule config file paths.
const configPaths = await this.git.getSubmoduleConfigPaths(
this.settings.nestedSubmodules this.settings.nestedSubmodules
) )
// For each submodule, configure includeIf entries pointing to the shared credentials file. // Replace the placeholder
// Configure both host and container paths to support Docker container actions. const configPaths: string[] =
output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || []
for (const configPath of configPaths) { for (const configPath of configPaths) {
// Submodule Git directory core.debug(`Replacing token placeholder in '${configPath}'`)
let submoduleGitDir = path.dirname(configPath) // The config file is at .git/modules/submodule-name/config await this.replaceTokenPlaceholder(configPath)
submoduleGitDir = submoduleGitDir.replace(/\\/g, '/') // Use forward slashes, even on Windows
// Configure host includeIf
await this.git.config(
`includeIf.gitdir:${submoduleGitDir}.path`,
credentialsConfigPath,
false, // globalConfig?
false, // add?
configPath
)
// Container submodule git directory
const githubWorkspace = process.env['GITHUB_WORKSPACE']
assert.ok(githubWorkspace, 'GITHUB_WORKSPACE is not defined')
let relativeSubmoduleGitDir = path.relative(
githubWorkspace,
submoduleGitDir
)
relativeSubmoduleGitDir = relativeSubmoduleGitDir.replace(/\\/g, '/') // Use forward slashes, even on Windows
const containerSubmoduleGitDir = path.posix.join(
'/github/workspace',
relativeSubmoduleGitDir
)
// Configure container includeIf
await this.git.config(
`includeIf.gitdir:${containerSubmoduleGitDir}.path`,
containerCredentialsPath,
false, // globalConfig?
false, // add?
configPath
)
} }
if (this.settings.sshKey) { if (this.settings.sshKey) {
@@ -243,10 +201,6 @@ class GitAuthHelper {
} }
} }
/**
* Configures SSH authentication by writing the SSH key and known hosts,
* and setting up the GIT_SSH_COMMAND environment variable.
*/
private async configureSsh(): Promise<void> { private async configureSsh(): Promise<void> {
if (!this.settings.sshKey) { if (!this.settings.sshKey) {
return return
@@ -318,116 +272,57 @@ class GitAuthHelper {
} }
} }
/** private async configureToken(
* Configures token-based authentication by creating a credentials config file configPath?: string,
* and setting up includeIf entries to reference it. globalConfig?: boolean
* @param globalConfig Whether to configure global config instead of local ): Promise<void> {
*/ // Validate args
private async configureToken(globalConfig?: boolean): Promise<void> { assert.ok(
// Get the credentials config file path in RUNNER_TEMP (configPath && globalConfig) || (!configPath && !globalConfig),
const credentialsConfigPath = this.getCredentialsConfigPath() 'Unexpected configureToken parameter combinations'
)
// Write placeholder to the separate credentials config file using git config. // Default config path
// This approach avoids the credential being captured by process creation audit events, if (!configPath && !globalConfig) {
// which are commonly logged. For more information, refer to configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config')
// https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing }
// Configure a placeholder value. This approach avoids the credential being captured
// by process creation audit events, which are commonly logged. For more information,
// refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
await this.git.config( await this.git.config(
this.tokenConfigKey, this.tokenConfigKey,
this.tokenPlaceholderConfigValue, this.tokenPlaceholderConfigValue,
false, // globalConfig? globalConfig
false, // add?
credentialsConfigPath
) )
// Replace the placeholder in the credentials config file // Replace the placeholder
let content = (await fs.promises.readFile(credentialsConfigPath)).toString() await this.replaceTokenPlaceholder(configPath || '')
}
private async replaceTokenPlaceholder(configPath: string): Promise<void> {
assert.ok(configPath, 'configPath is not defined')
let content = (await fs.promises.readFile(configPath)).toString()
const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue) const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue)
if ( if (
placeholderIndex < 0 || placeholderIndex < 0 ||
placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue) placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue)
) { ) {
throw new Error( throw new Error(`Unable to replace auth placeholder in ${configPath}`)
`Unable to replace auth placeholder in ${credentialsConfigPath}`
)
} }
assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined') assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined')
content = content.replace( content = content.replace(
this.tokenPlaceholderConfigValue, this.tokenPlaceholderConfigValue,
this.tokenConfigValue this.tokenConfigValue
) )
await fs.promises.writeFile(credentialsConfigPath, content) await fs.promises.writeFile(configPath, content)
// Add include or includeIf to reference the credentials config
if (globalConfig) {
// Global config file is temporary
await this.git.config(
'include.path',
credentialsConfigPath,
true // globalConfig?
)
} else {
// Host git directory
let gitDir = path.join(this.git.getWorkingDirectory(), '.git')
gitDir = gitDir.replace(/\\/g, '/') // Use forward slashes, even on Windows
// Configure host includeIf
const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`
await this.git.config(hostIncludeKey, credentialsConfigPath)
// Container git directory
const workingDirectory = this.git.getWorkingDirectory()
const githubWorkspace = process.env['GITHUB_WORKSPACE']
assert.ok(githubWorkspace, 'GITHUB_WORKSPACE is not defined')
let relativePath = path.relative(githubWorkspace, workingDirectory)
relativePath = relativePath.replace(/\\/g, '/') // Use forward slashes, even on Windows
const containerGitDir = path.posix.join(
'/github/workspace',
relativePath,
'.git'
)
// Container credentials config path
const containerCredentialsPath = path.posix.join(
'/github/runner_temp',
path.basename(credentialsConfigPath)
)
// Configure container includeIf
const containerIncludeKey = `includeIf.gitdir:${containerGitDir}.path`
await this.git.config(containerIncludeKey, containerCredentialsPath)
}
} }
/**
* Gets or creates the path to the credentials config file in RUNNER_TEMP.
* @returns The absolute path to the credentials config file
*/
private getCredentialsConfigPath(): string {
if (this.credentialsConfigPath) {
return this.credentialsConfigPath
}
const runnerTemp = process.env['RUNNER_TEMP'] || ''
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
// Create a unique filename for this checkout instance
const configFileName = `git-credentials-${uuid()}.config`
this.credentialsConfigPath = path.join(runnerTemp, configFileName)
core.debug(`Credentials config path: ${this.credentialsConfigPath}`)
return this.credentialsConfigPath
}
/**
* Removes SSH authentication configuration by cleaning up SSH keys,
* known hosts files, and SSH command configurations.
*/
private async removeSsh(): Promise<void> { private async removeSsh(): Promise<void> {
// SSH key // SSH key
const keyPath = this.sshKeyPath || stateHelper.SshKeyPath const keyPath = this.sshKeyPath || stateHelper.SshKeyPath
if (keyPath) { if (keyPath) {
try { try {
core.info(`Removing SSH key '${keyPath}'`)
await io.rmRF(keyPath) await io.rmRF(keyPath)
} catch (err) { } catch (err) {
core.debug(`${(err as any)?.message ?? err}`) core.debug(`${(err as any)?.message ?? err}`)
@@ -440,149 +335,40 @@ class GitAuthHelper {
this.sshKnownHostsPath || stateHelper.SshKnownHostsPath this.sshKnownHostsPath || stateHelper.SshKnownHostsPath
if (knownHostsPath) { if (knownHostsPath) {
try { try {
core.info(`Removing SSH known hosts '${knownHostsPath}'`)
await io.rmRF(knownHostsPath) await io.rmRF(knownHostsPath)
} catch (err) { } catch {
core.debug(`${(err as any)?.message ?? err}`) // Intentionally empty
core.warning(`Failed to remove SSH known hosts '${knownHostsPath}'`)
} }
} }
// SSH command // SSH command
core.info('Removing SSH command configuration')
await this.removeGitConfig(SSH_COMMAND_KEY) await this.removeGitConfig(SSH_COMMAND_KEY)
await this.removeSubmoduleGitConfig(SSH_COMMAND_KEY)
} }
/**
* Removes token-based authentication by cleaning up HTTP headers,
* includeIf entries, and credentials config files.
*/
private async removeToken(): Promise<void> { private async removeToken(): Promise<void> {
// Remove HTTP extra header // HTTP extra header
core.info('Removing HTTP extra header')
await this.removeGitConfig(this.tokenConfigKey) await this.removeGitConfig(this.tokenConfigKey)
await this.removeSubmoduleGitConfig(this.tokenConfigKey) }
// Collect credentials config paths that need to be removed private async removeGitConfig(
const credentialsPaths = new Set<string>() configKey: string,
submoduleOnly: boolean = false
// Remove includeIf entries that point to git-credentials-*.config files ): Promise<void> {
core.info('Removing includeIf entries pointing to credentials config files') if (!submoduleOnly) {
const mainCredentialsPaths = await this.removeIncludeIfCredentials() if (
mainCredentialsPaths.forEach(path => credentialsPaths.add(path)) (await this.git.configExists(configKey)) &&
!(await this.git.tryConfigUnset(configKey))
// Remove submodule includeIf entries that point to git-credentials-*.config files ) {
const submoduleConfigPaths = await this.git.getSubmoduleConfigPaths(true) // Load the config contents
for (const configPath of submoduleConfigPaths) { core.warning(`Failed to remove '${configKey}' from the git config`)
const submoduleCredentialsPaths =
await this.removeIncludeIfCredentials(configPath)
submoduleCredentialsPaths.forEach(path => credentialsPaths.add(path))
}
// Remove credentials config files
for (const credentialsPath of credentialsPaths) {
// Only remove credentials config files if they are under RUNNER_TEMP
const runnerTemp = process.env['RUNNER_TEMP']
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
if (credentialsPath.startsWith(runnerTemp)) {
try {
core.info(`Removing credentials config '${credentialsPath}'`)
await io.rmRF(credentialsPath)
} catch (err) {
core.debug(`${(err as any)?.message ?? err}`)
core.warning(
`Failed to remove credentials config '${credentialsPath}'`
)
}
} else {
core.debug(
`Skipping removal of credentials config '${credentialsPath}' - not under RUNNER_TEMP`
)
} }
} }
}
/**
* Removes a git config key from the local repository config.
* @param configKey The git config key to remove
*/
private async removeGitConfig(configKey: string): Promise<void> {
if (
(await this.git.configExists(configKey)) &&
!(await this.git.tryConfigUnset(configKey))
) {
// Load the config contents
core.warning(`Failed to remove '${configKey}' from the git config`)
}
}
/**
* Removes a git config key from all submodule configs.
* @param configKey The git config key to remove
*/
private async removeSubmoduleGitConfig(configKey: string): Promise<void> {
const pattern = regexpHelper.escape(configKey) const pattern = regexpHelper.escape(configKey)
await this.git.submoduleForeach( await this.git.submoduleForeach(
// Wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline. // wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline
`sh -c "git config --local --name-only --get-regexp '${pattern}' && git config --local --unset-all '${configKey}' || :"`, `sh -c "git config --local --name-only --get-regexp '${pattern}' && git config --local --unset-all '${configKey}' || :"`,
true true
) )
} }
/**
* Removes includeIf entries that point to git-credentials-*.config files.
* @param configPath Optional path to a specific git config file to operate on
* @returns Array of unique credentials config file paths that were found and removed
*/
private async removeIncludeIfCredentials(
configPath?: string
): Promise<string[]> {
const credentialsPaths = new Set<string>()
try {
// Get all includeIf.gitdir keys
const keys = await this.git.tryGetConfigKeys(
'^includeIf\\.gitdir:',
false, // globalConfig?
configPath
)
for (const key of keys) {
// Get all values for this key
const values = await this.git.tryGetConfigValues(
key,
false, // globalConfig?
configPath
)
if (values.length > 0) {
// Remove only values that match git-credentials-<uuid>.config pattern
for (const value of values) {
if (this.testCredentialsConfigPath(value)) {
credentialsPaths.add(value)
await this.git.tryConfigUnsetValue(key, value, false, configPath)
}
}
}
}
} catch (err) {
// Ignore errors - this is cleanup code
if (configPath) {
core.debug(`Error during includeIf cleanup for ${configPath}: ${err}`)
} else {
core.debug(`Error during includeIf cleanup: ${err}`)
}
}
return Array.from(credentialsPaths)
}
/**
* Tests if a path matches the git-credentials-*.config pattern.
* @param path The path to test
* @returns True if the path matches the credentials config pattern
*/
private testCredentialsConfigPath(path: string): boolean {
return /git-credentials-[0-9a-f-]+\.config$/i.test(path)
}
} }

View File

@@ -28,8 +28,7 @@ export interface IGitCommandManager {
configKey: string, configKey: string,
configValue: string, configValue: string,
globalConfig?: boolean, globalConfig?: boolean,
add?: boolean, add?: boolean
configFile?: string
): Promise<void> ): Promise<void>
configExists(configKey: string, globalConfig?: boolean): Promise<boolean> configExists(configKey: string, globalConfig?: boolean): Promise<boolean>
fetch( fetch(
@@ -42,7 +41,6 @@ export interface IGitCommandManager {
} }
): Promise<void> ): Promise<void>
getDefaultBranch(repositoryUrl: string): Promise<string> getDefaultBranch(repositoryUrl: string): Promise<string>
getSubmoduleConfigPaths(recursive: boolean): Promise<string[]>
getWorkingDirectory(): string getWorkingDirectory(): string
init(): Promise<void> init(): Promise<void>
isDetached(): Promise<boolean> isDetached(): Promise<boolean>
@@ -61,24 +59,8 @@ export interface IGitCommandManager {
tagExists(pattern: string): Promise<boolean> tagExists(pattern: string): Promise<boolean>
tryClean(): Promise<boolean> tryClean(): Promise<boolean>
tryConfigUnset(configKey: string, globalConfig?: boolean): Promise<boolean> tryConfigUnset(configKey: string, globalConfig?: boolean): Promise<boolean>
tryConfigUnsetValue(
configKey: string,
configValue: string,
globalConfig?: boolean,
configFile?: string
): Promise<boolean>
tryDisableAutomaticGarbageCollection(): Promise<boolean> tryDisableAutomaticGarbageCollection(): Promise<boolean>
tryGetFetchUrl(): Promise<string> tryGetFetchUrl(): Promise<string>
tryGetConfigValues(
configKey: string,
globalConfig?: boolean,
configFile?: string
): Promise<string[]>
tryGetConfigKeys(
pattern: string,
globalConfig?: boolean,
configFile?: string
): Promise<string[]>
tryReset(): Promise<boolean> tryReset(): Promise<boolean>
version(): Promise<GitVersion> version(): Promise<GitVersion>
} }
@@ -241,15 +223,9 @@ class GitCommandManager {
configKey: string, configKey: string,
configValue: string, configValue: string,
globalConfig?: boolean, globalConfig?: boolean,
add?: boolean, add?: boolean
configFile?: string
): Promise<void> { ): Promise<void> {
const args: string[] = ['config'] const args: string[] = ['config', globalConfig ? '--global' : '--local']
if (configFile) {
args.push('--file', configFile)
} else {
args.push(globalConfig ? '--global' : '--local')
}
if (add) { if (add) {
args.push('--add') args.push('--add')
} }
@@ -347,21 +323,6 @@ class GitCommandManager {
throw new Error('Unexpected output when retrieving default branch') throw new Error('Unexpected output when retrieving default branch')
} }
async getSubmoduleConfigPaths(recursive: boolean): Promise<string[]> {
// Get submodule config file paths.
// Use `--show-origin` to get the config file path for each submodule.
const output = await this.submoduleForeach(
`git config --local --show-origin --name-only --get-regexp remote.origin.url`,
recursive
)
// Extract config file paths from the output (lines starting with "file:").
const configPaths =
output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || []
return configPaths
}
getWorkingDirectory(): string { getWorkingDirectory(): string {
return this.workingDirectory return this.workingDirectory
} }
@@ -494,24 +455,6 @@ class GitCommandManager {
return output.exitCode === 0 return output.exitCode === 0
} }
async tryConfigUnsetValue(
configKey: string,
configValue: string,
globalConfig?: boolean,
configFile?: string
): Promise<boolean> {
const args = ['config']
if (configFile) {
args.push('--file', configFile)
} else {
args.push(globalConfig ? '--global' : '--local')
}
args.push('--unset', configKey, configValue)
const output = await this.execGit(args, true)
return output.exitCode === 0
}
async tryDisableAutomaticGarbageCollection(): Promise<boolean> { async tryDisableAutomaticGarbageCollection(): Promise<boolean> {
const output = await this.execGit( const output = await this.execGit(
['config', '--local', 'gc.auto', '0'], ['config', '--local', 'gc.auto', '0'],
@@ -538,56 +481,6 @@ class GitCommandManager {
return stdout return stdout
} }
async tryGetConfigValues(
configKey: string,
globalConfig?: boolean,
configFile?: string
): Promise<string[]> {
const args = ['config']
if (configFile) {
args.push('--file', configFile)
} else {
args.push(globalConfig ? '--global' : '--local')
}
args.push('--get-all', configKey)
const output = await this.execGit(args, true)
if (output.exitCode !== 0) {
return []
}
return output.stdout
.trim()
.split('\n')
.filter(value => value.trim())
}
async tryGetConfigKeys(
pattern: string,
globalConfig?: boolean,
configFile?: string
): Promise<string[]> {
const args = ['config']
if (configFile) {
args.push('--file', configFile)
} else {
args.push(globalConfig ? '--global' : '--local')
}
args.push('--name-only', '--get-regexp', pattern)
const output = await this.execGit(args, true)
if (output.exitCode !== 0) {
return []
}
return output.stdout
.trim()
.split('\n')
.filter(key => key.trim())
}
async tryReset(): Promise<boolean> { async tryReset(): Promise<boolean> {
const output = await this.execGit(['reset', '--hard', 'HEAD'], true) const output = await this.execGit(['reset', '--hard', 'HEAD'], true)
return output.exitCode === 0 return output.exitCode === 0

View File

@@ -120,7 +120,7 @@ function updateUsage(
} }
updateUsage( updateUsage(
'actions/checkout@v5', 'actions/checkout@v4',
path.join(__dirname, '..', '..', 'action.yml'), path.join(__dirname, '..', '..', 'action.yml'),
path.join(__dirname, '..', '..', 'README.md') path.join(__dirname, '..', '..', 'README.md')
) )