From 1b0793cc055e5dd9553cb43915856a72b9d1db32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:59:45 +0000 Subject: [PATCH 01/12] Bump the minor-actions-dependencies group across 1 directory with 2 updates Bumps the minor-actions-dependencies group with 2 updates in the / directory: [actions/publish-immutable-action](https://github.com/actions/publish-immutable-action) and [docker/build-push-action](https://github.com/docker/build-push-action). Updates `actions/publish-immutable-action` from 0.0.3 to 0.0.4 - [Release notes](https://github.com/actions/publish-immutable-action/releases) - [Commits](https://github.com/actions/publish-immutable-action/compare/0.0.3...v0.0.4) Updates `docker/build-push-action` from 6.5.0 to 6.10.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v6.5.0...v6.10.0) --- updated-dependencies: - dependency-name: actions/publish-immutable-action dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-actions-dependencies - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-actions-dependencies ... Signed-off-by: dependabot[bot] --- .github/workflows/publish-immutable-actions.yml | 2 +- .github/workflows/update-test-ubuntu-git.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-immutable-actions.yml b/.github/workflows/publish-immutable-actions.yml index 87c0207..7c25834 100644 --- a/.github/workflows/publish-immutable-actions.yml +++ b/.github/workflows/publish-immutable-actions.yml @@ -17,4 +17,4 @@ jobs: uses: actions/checkout@v4 - name: Publish id: publish - uses: actions/publish-immutable-action@0.0.3 + uses: actions/publish-immutable-action@v0.0.4 diff --git a/.github/workflows/update-test-ubuntu-git.yml b/.github/workflows/update-test-ubuntu-git.yml index 5c252b9..b89e8db 100644 --- a/.github/workflows/update-test-ubuntu-git.yml +++ b/.github/workflows/update-test-ubuntu-git.yml @@ -48,7 +48,7 @@ jobs: # Use `docker/build-push-action` to build (and optionally publish) the image. - name: Build Docker Image (with optional Push) - uses: docker/build-push-action@v6.5.0 + uses: docker/build-push-action@v6.10.0 with: context: . file: images/test-ubuntu-git.Dockerfile From 009b9ae9e446ad8d9b8c809870b0fbcc5e03573e Mon Sep 17 00:00:00 2001 From: Ben Wells Date: Thu, 16 Jan 2025 14:14:48 -0500 Subject: [PATCH 02/12] Documentation update - add recommended permissions to Readme (#2043) * Update README.md * Update README.md Co-authored-by: Josh Gross --------- Co-authored-by: Josh Gross --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index b0f6224..f28fec7 100644 --- a/README.md +++ b/README.md @@ -311,6 +311,16 @@ jobs: git commit -m "generated" git push ``` + +## 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 +``` + *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 From 85e6279cec87321a52edac9c87bce653a07cf6c2 Mon Sep 17 00:00:00 2001 From: Josh Gross Date: Thu, 16 Jan 2025 15:56:18 -0500 Subject: [PATCH 03/12] Adjust positioning of user email note and permissions heading (#2044) --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f28fec7..64dc025 100644 --- a/README.md +++ b/README.md @@ -312,7 +312,9 @@ jobs: git push ``` -## Recommended permissions +*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: @@ -321,9 +323,6 @@ permissions: contents: read ``` -*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 - - # License The scripts and documentation in this project are released under the [MIT License](LICENSE) From 09d2acae674a48949e3602304ab46fd20ae0c42f Mon Sep 17 00:00:00 2001 From: Ben De St Paer-Gotch Date: Fri, 6 Jun 2025 09:19:16 +0100 Subject: [PATCH 04/12] Update README.md (#2194) --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 64dc025..8969446 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,24 @@ 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. +### 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 we’re working on and what stage they’re 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 Please refer to the [release page](https://github.com/actions/checkout/releases/latest) for the latest release notes. From 8edcb1bdb4e267140fa742c62e395cd74f332709 Mon Sep 17 00:00:00 2001 From: Tingluo Huang Date: Wed, 23 Jul 2025 09:20:20 -0400 Subject: [PATCH 05/12] Update CODEOWNERS for actions (#2224) --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 65ed322..992d27f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @actions/actions-launch +* @actions/actions-runtime From 631c7dc4f80f88219c5ee78fee08c6b62fac8da1 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 11 Aug 2025 11:22:41 +0100 Subject: [PATCH 06/12] Update package dependencies (#2236) * package updates * update dist * Update license files --- .licenses/npm/@octokit/endpoint.dep.yml | 4 +- .../npm/@octokit/plugin-paginate-rest.dep.yml | 4 +- .licenses/npm/@octokit/request-error.dep.yml | 4 +- .licenses/npm/@octokit/request.dep.yml | 4 +- .licenses/npm/undici.dep.yml | 2 +- dist/index.js | 98 +++--- package-lock.json | 314 +++++++----------- 7 files changed, 185 insertions(+), 245 deletions(-) diff --git a/.licenses/npm/@octokit/endpoint.dep.yml b/.licenses/npm/@octokit/endpoint.dep.yml index 06e0ae0..71234c6 100644 --- a/.licenses/npm/@octokit/endpoint.dep.yml +++ b/.licenses/npm/@octokit/endpoint.dep.yml @@ -1,9 +1,9 @@ --- name: "@octokit/endpoint" -version: 9.0.5 +version: 9.0.6 type: npm summary: Turns REST API endpoints into generic request options -homepage: +homepage: license: mit licenses: - sources: LICENSE diff --git a/.licenses/npm/@octokit/plugin-paginate-rest.dep.yml b/.licenses/npm/@octokit/plugin-paginate-rest.dep.yml index ba74754..c1853a6 100644 --- a/.licenses/npm/@octokit/plugin-paginate-rest.dep.yml +++ b/.licenses/npm/@octokit/plugin-paginate-rest.dep.yml @@ -1,9 +1,9 @@ --- name: "@octokit/plugin-paginate-rest" -version: 9.2.1 +version: 9.2.2 type: npm summary: Octokit plugin to paginate REST API endpoint responses -homepage: +homepage: license: mit licenses: - sources: LICENSE diff --git a/.licenses/npm/@octokit/request-error.dep.yml b/.licenses/npm/@octokit/request-error.dep.yml index f551e72..9c9d702 100644 --- a/.licenses/npm/@octokit/request-error.dep.yml +++ b/.licenses/npm/@octokit/request-error.dep.yml @@ -1,9 +1,9 @@ --- name: "@octokit/request-error" -version: 5.1.0 +version: 5.1.1 type: npm summary: Error class for Octokit request errors -homepage: +homepage: license: mit licenses: - sources: LICENSE diff --git a/.licenses/npm/@octokit/request.dep.yml b/.licenses/npm/@octokit/request.dep.yml index 31cecab..ef1a554 100644 --- a/.licenses/npm/@octokit/request.dep.yml +++ b/.licenses/npm/@octokit/request.dep.yml @@ -1,10 +1,10 @@ --- name: "@octokit/request" -version: 8.4.0 +version: 8.4.1 type: npm summary: Send parameterized requests to GitHub's APIs with sensible defaults in browsers and Node -homepage: +homepage: license: mit licenses: - sources: LICENSE diff --git a/.licenses/npm/undici.dep.yml b/.licenses/npm/undici.dep.yml index cc74a6d..fadecf4 100644 --- a/.licenses/npm/undici.dep.yml +++ b/.licenses/npm/undici.dep.yml @@ -1,6 +1,6 @@ --- name: undici -version: 5.28.4 +version: 5.29.0 type: npm summary: An HTTP/1.1 client, written from scratch for Node.js homepage: https://undici.nodejs.org diff --git a/dist/index.js b/dist/index.js index b0db713..f3ae6f3 100644 --- a/dist/index.js +++ b/dist/index.js @@ -7802,7 +7802,7 @@ module.exports = __toCommonJS(dist_src_exports); var import_universal_user_agent = __nccwpck_require__(5030); // pkg/dist-src/version.js -var VERSION = "9.0.5"; +var VERSION = "9.0.6"; // pkg/dist-src/defaults.js var userAgent = `octokit-endpoint.js/${VERSION} ${(0, import_universal_user_agent.getUserAgent)()}`; @@ -7907,9 +7907,9 @@ function addQueryParameters(url, parameters) { } // pkg/dist-src/util/extract-url-variable-names.js -var urlVariableRegex = /\{[^}]+\}/g; +var urlVariableRegex = /\{[^{}}]+\}/g; function removeNonChars(variableName) { - return variableName.replace(/^\W+|\W+$/g, "").split(/,/); + return variableName.replace(/(?:^\W+)|(?:(? { const format = options.mediaType.format ? `.${options.mediaType.format}` : "+json"; return `application/vnd.github.${preview}-preview${format}`; @@ -8344,7 +8344,7 @@ __export(dist_src_exports, { module.exports = __toCommonJS(dist_src_exports); // pkg/dist-src/version.js -var VERSION = "9.2.1"; +var VERSION = "9.2.2"; // pkg/dist-src/normalize-paginated-list-response.js function normalizePaginatedListResponse(response) { @@ -8392,7 +8392,7 @@ function iterator(octokit, route, parameters) { const response = await requestMethod({ method, url, headers }); const normalizedResponse = normalizePaginatedListResponse(response); url = ((normalizedResponse.headers.link || "").match( - /<([^>]+)>;\s*rel="next"/ + /<([^<>]+)>;\s*rel="next"/ ) || [])[1]; return { value: normalizedResponse }; } catch (error) { @@ -10944,7 +10944,7 @@ var RequestError = class extends Error { if (options.request.headers.authorization) { requestCopy.headers = Object.assign({}, options.request.headers, { authorization: options.request.headers.authorization.replace( - / .*$/, + /(?]+)>; rel="deprecation"/); + const matches = headers.link && headers.link.match(/<([^<>]+)>; rel="deprecation"/); const deprecationLink = matches && matches.pop(); log.warn( `[@octokit/request] "${requestOptions.method} ${requestOptions.url}" is deprecated. It is scheduled to be removed on ${headers.sunset}${deprecationLink ? `. See ${deprecationLink}` : ""}` @@ -18725,7 +18725,7 @@ module.exports = { const { parseSetCookie } = __nccwpck_require__(4408) -const { stringify, getHeadersList } = __nccwpck_require__(3121) +const { stringify } = __nccwpck_require__(3121) const { webidl } = __nccwpck_require__(1744) const { Headers } = __nccwpck_require__(554) @@ -18801,14 +18801,13 @@ function getSetCookies (headers) { webidl.brandCheck(headers, Headers, { strict: false }) - const cookies = getHeadersList(headers).cookies + const cookies = headers.getSetCookie() if (!cookies) { return [] } - // In older versions of undici, cookies is a list of name:value. - return cookies.map((pair) => parseSetCookie(Array.isArray(pair) ? pair[1] : pair)) + return cookies.map((pair) => parseSetCookie(pair)) } /** @@ -19236,14 +19235,15 @@ module.exports = { /***/ }), /***/ 3121: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +/***/ ((module) => { "use strict"; -const assert = __nccwpck_require__(9491) -const { kHeadersList } = __nccwpck_require__(2785) - +/** + * @param {string} value + * @returns {boolean} + */ function isCTLExcludingHtab (value) { if (value.length === 0) { return false @@ -19504,31 +19504,13 @@ function stringify (cookie) { 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 = { isCTLExcludingHtab, - stringify, - getHeadersList + validateCookieName, + validateCookiePath, + validateCookieValue, + toIMFDate, + stringify } @@ -21457,6 +21439,14 @@ const { isUint8Array, isArrayBuffer } = __nccwpck_require__(9830) const { File: UndiciFile } = __nccwpck_require__(8511) 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 /** @type {globalThis['File']} */ @@ -21542,7 +21532,7 @@ function extractBody (object, keepalive = false) { // Set source to a copy of the bytes held by object. source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength)) } else if (util.isFormDataLike(object)) { - const boundary = `----formdata-undici-0${`${Math.floor(Math.random() * 1e11)}`.padStart(11, '0')}` + const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}` const prefix = `--${boundary}\r\nContent-Disposition: form-data` /*! formdata-polyfill. MIT License. Jimmy Wärting */ @@ -23524,6 +23514,7 @@ const { isValidHeaderName, isValidHeaderValue } = __nccwpck_require__(2538) +const util = __nccwpck_require__(3837) const { webidl } = __nccwpck_require__(1744) const assert = __nccwpck_require__(9491) @@ -24077,6 +24068,9 @@ Object.defineProperties(Headers.prototype, { [Symbol.toStringTag]: { value: 'Headers', configurable: true + }, + [util.inspect.custom]: { + enumerable: false } }) @@ -33253,6 +33247,20 @@ class Pool extends PoolBase { ? { ...options.interceptors } : undefined 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] () { @@ -36408,6 +36416,14 @@ module.exports = require("net"); /***/ }), +/***/ 6005: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:crypto"); + +/***/ }), + /***/ 5673: /***/ ((module) => { diff --git a/package-lock.json b/package-lock.json index 25753a2..556dca6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -129,13 +129,15 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.2", - "picocolors": "^1.0.0" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -310,19 +312,21 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -337,110 +341,28 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", - "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", + "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", - "@babel/types": "^7.24.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", - "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", - "dev": true, - "dependencies": { - "@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==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.0" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -626,26 +548,25 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", - "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", + "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", "dev": true, - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -682,14 +603,14 @@ } }, "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -749,10 +670,11 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -808,10 +730,11 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1343,9 +1266,10 @@ } }, "node_modules/@octokit/endpoint": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.5.tgz", - "integrity": "sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", + "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "license": "MIT", "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" @@ -1373,9 +1297,10 @@ "integrity": "sha512-pGUdSP+eEPfZiQHNkZI0U01HLipxncisdJQB4G//OAmfeO8sqTQ9KRa0KF03TUPCziNsoXUrTg4B2Q1EX++T0Q==" }, "node_modules/@octokit/plugin-paginate-rest": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.1.tgz", - "integrity": "sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", + "integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", + "license": "MIT", "dependencies": { "@octokit/types": "^12.6.0" }, @@ -1427,12 +1352,13 @@ } }, "node_modules/@octokit/request": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz", - "integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", + "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "license": "MIT", "dependencies": { - "@octokit/endpoint": "^9.0.1", - "@octokit/request-error": "^5.1.0", + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" }, @@ -1441,9 +1367,10 @@ } }, "node_modules/@octokit/request-error": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.0.tgz", - "integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", + "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "license": "MIT", "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", @@ -2238,10 +2165,11 @@ "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -2502,10 +2430,11 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3175,10 +3104,11 @@ } }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3273,10 +3203,11 @@ } }, "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3371,10 +3302,11 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3836,10 +3768,11 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4635,10 +4568,11 @@ } }, "node_modules/jake/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5248,7 +5182,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -5528,12 +5463,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -5869,10 +5805,11 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -6115,12 +6052,6 @@ "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": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -6622,10 +6553,11 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6655,15 +6587,6 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "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": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6930,9 +6853,10 @@ } }, "node_modules/undici": { - "version": "5.28.4", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", - "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "license": "MIT", "dependencies": { "@fastify/busboy": "^2.0.0" }, From 08eba0b27e820071cde6df949e0beb9ba4906955 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 11 Aug 2025 11:30:37 +0100 Subject: [PATCH 07/12] Prepare release v4.3.0 (#2237) --- CHANGELOG.md | 9 +++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a96c76e..baf5c2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 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 * `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 diff --git a/package-lock.json b/package-lock.json index 556dca6..aef29d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "checkout", - "version": "4.2.2", + "version": "4.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "checkout", - "version": "4.2.2", + "version": "4.3.0", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", diff --git a/package.json b/package.json index 5661d70..dbbaabb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "checkout", - "version": "4.2.2", + "version": "4.3.0", "description": "checkout action", "main": "lib/main.js", "scripts": { From 9f265659d3bb64ab1440b03b12f4d47a24320917 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 11 Aug 2025 11:52:51 +0100 Subject: [PATCH 08/12] Update actions checkout to use node 24 (#2226) * use node 24 * update other parts to node 24 * bump to major version, audit fix, changelog * update licenses * update dist * update major version * will do separate pr for v5 and will do a minor version for previous changes --- .github/workflows/check-dist.yml | 4 ++-- .github/workflows/test.yml | 2 +- action.yml | 2 +- package-lock.json | 16 ++++++++-------- package.json | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/check-dist.yml b/.github/workflows/check-dist.yml index 53902ee..db3e37f 100644 --- a/.github/workflows/check-dist.yml +++ b/.github/workflows/check-dist.yml @@ -24,10 +24,10 @@ jobs: steps: - uses: actions/checkout@v4.1.6 - - name: Set Node.js 20.x + - name: Set Node.js 24.x uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 24.x - name: Install dependencies run: npm ci diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cde9f06..e62ac3b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 24.x - uses: actions/checkout@v4.1.6 - run: npm ci - run: npm run build diff --git a/action.yml b/action.yml index 6842eb8..767c416 100644 --- a/action.yml +++ b/action.yml @@ -104,6 +104,6 @@ outputs: commit: description: 'The commit SHA that was checked out' runs: - using: node20 + using: node24 main: dist/index.js post: dist/index.js diff --git a/package-lock.json b/package-lock.json index aef29d3..73c773e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ }, "devDependencies": { "@types/jest": "^29.5.12", - "@types/node": "^20.12.12", + "@types/node": "^24.1.0", "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.9.0", "@typescript-eslint/parser": "^7.9.0", @@ -1515,12 +1515,12 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", + "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~7.8.0" } }, "node_modules/@types/stack-utils": { @@ -6865,9 +6865,9 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "dev": true }, "node_modules/universal-user-agent": { diff --git a/package.json b/package.json index dbbaabb..f273992 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ }, "devDependencies": { "@types/jest": "^29.5.12", - "@types/node": "^20.12.12", + "@types/node": "^24.1.0", "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.9.0", "@typescript-eslint/parser": "^7.9.0", From 08c6903cd8c0fde910a37f88322edcfb5dd907a8 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 11 Aug 2025 13:35:28 +0100 Subject: [PATCH 09/12] Prepare v5.0.0 release (#2238) --- .github/workflows/update-main-version.yml | 1 + CHANGELOG.md | 4 ++ README.md | 72 +++++++++++++---------- package-lock.json | 4 +- package.json | 2 +- src/misc/generate-docs.ts | 2 +- 6 files changed, 51 insertions(+), 34 deletions(-) diff --git a/.github/workflows/update-main-version.yml b/.github/workflows/update-main-version.yml index 7bec7d5..643b954 100644 --- a/.github/workflows/update-main-version.yml +++ b/.github/workflows/update-main-version.yml @@ -11,6 +11,7 @@ on: type: choice description: The major version to update options: + - v5 - v4 - v3 - v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index baf5c2d..ff8b4e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # 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 diff --git a/README.md b/README.md index 8969446..f9175e9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ [![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 + +Checkout v5 now supports Node.js 24 + # Checkout V4 This action checks-out your repository under `$GITHUB_WORKSPACE`, so your workflow can access it. @@ -36,7 +40,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ ```yaml -- uses: actions/checkout@v4 +- uses: actions/checkout@v5 with: # Repository name with owner. For example, actions/checkout # Default: ${{ github.repository }} @@ -149,24 +153,32 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ # Scenarios -- [Fetch only the root files](#Fetch-only-the-root-files) -- [Fetch only the root files and `.github` and `src` folder](#Fetch-only-the-root-files-and-github-and-src-folder) -- [Fetch only a single file](#Fetch-only-a-single-file) -- [Fetch all history for all tags and branches](#Fetch-all-history-for-all-tags-and-branches) -- [Checkout a different branch](#Checkout-a-different-branch) -- [Checkout HEAD^](#Checkout-HEAD) -- [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) +- [Checkout V5](#checkout-v5) +- [Checkout V4](#checkout-v4) + - [Note](#note) +- [What's new](#whats-new) +- [Usage](#usage) +- [Scenarios](#scenarios) + - [Fetch only the root files](#fetch-only-the-root-files) + - [Fetch only the root files and `.github` and `src` folder](#fetch-only-the-root-files-and-github-and-src-folder) + - [Fetch only a single file](#fetch-only-a-single-file) + - [Fetch all history for all tags and branches](#fetch-all-history-for-all-tags-and-branches) + - [Checkout a different branch](#checkout-a-different-branch) + - [Checkout HEAD^](#checkout-head) + - [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 ```yaml -- uses: actions/checkout@v4 +- uses: actions/checkout@v5 with: sparse-checkout: . ``` @@ -174,7 +186,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ ## Fetch only the root files and `.github` and `src` folder ```yaml -- uses: actions/checkout@v4 +- uses: actions/checkout@v5 with: sparse-checkout: | .github @@ -184,7 +196,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ ## Fetch only a single file ```yaml -- uses: actions/checkout@v4 +- uses: actions/checkout@v5 with: sparse-checkout: | README.md @@ -194,7 +206,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ ## Fetch all history for all tags and branches ```yaml -- uses: actions/checkout@v4 +- uses: actions/checkout@v5 with: fetch-depth: 0 ``` @@ -202,7 +214,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ ## Checkout a different branch ```yaml -- uses: actions/checkout@v4 +- uses: actions/checkout@v5 with: ref: my-branch ``` @@ -210,7 +222,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ ## Checkout HEAD^ ```yaml -- uses: actions/checkout@v4 +- uses: actions/checkout@v5 with: fetch-depth: 2 - run: git checkout HEAD^ @@ -220,12 +232,12 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ ```yaml - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: main - name: Checkout tools repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: my-org/my-tools path: my-tools @@ -236,10 +248,10 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ ```yaml - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Checkout tools repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: my-org/my-tools path: my-tools @@ -250,12 +262,12 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ ```yaml - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: main - name: Checkout private tools - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: my-org/my-private-tools token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT @@ -268,7 +280,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ ## Checkout pull request HEAD commit instead of merge commit ```yaml -- uses: actions/checkout@v4 +- uses: actions/checkout@v5 with: ref: ${{ github.event.pull_request.head.sha }} ``` @@ -284,7 +296,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 ``` ## Push a commit using the built-in token @@ -295,7 +307,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: | date > generated.txt # Note: the following account information will not work on GHES @@ -317,7 +329,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: ${{ github.head_ref }} - run: | diff --git a/package-lock.json b/package-lock.json index 73c773e..98eb420 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "checkout", - "version": "4.3.0", + "version": "5.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "checkout", - "version": "4.3.0", + "version": "5.0.0", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", diff --git a/package.json b/package.json index f273992..4b2b58a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "checkout", - "version": "4.3.0", + "version": "5.0.0", "description": "checkout action", "main": "lib/main.js", "scripts": { diff --git a/src/misc/generate-docs.ts b/src/misc/generate-docs.ts index 4b3c8ff..6d4816f 100644 --- a/src/misc/generate-docs.ts +++ b/src/misc/generate-docs.ts @@ -120,7 +120,7 @@ function updateUsage( } updateUsage( - 'actions/checkout@v4', + 'actions/checkout@v5', path.join(__dirname, '..', '..', 'action.yml'), path.join(__dirname, '..', '..', 'README.md') ) From ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Wed, 13 Aug 2025 13:57:25 +0100 Subject: [PATCH 10/12] Update README to include Node.js 24 support details and requirements (#2248) * Update README to include Node.js 24 support details and requirements * Update --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f9175e9..9a32e9a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,11 @@ # Checkout V5 -Checkout v5 now supports Node.js 24 +## 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 @@ -154,9 +158,10 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ # Scenarios - [Checkout V5](#checkout-v5) + - [What's new](#whats-new) - [Checkout V4](#checkout-v4) - [Note](#note) -- [What's new](#whats-new) +- [What's new](#whats-new-1) - [Usage](#usage) - [Scenarios](#scenarios) - [Fetch only the root files](#fetch-only-the-root-files) From 069c6959146423d11cd0184e6accf28f9d45f06e Mon Sep 17 00:00:00 2001 From: eric sciple Date: Mon, 3 Nov 2025 13:08:38 -0600 Subject: [PATCH 11/12] Persist creds to a separate file (#2286) --- .github/workflows/test.yml | 9 +- __test__/git-auth-helper.test.ts | 409 ++++++++++++++++++++++-- __test__/git-directory-helper.test.ts | 4 + __test__/verify-submodules-recursive.sh | 2 +- __test__/verify-submodules-true.sh | 2 +- dist/index.js | 338 +++++++++++++++++--- src/git-auth-helper.ts | 332 +++++++++++++++---- src/git-command-manager.ts | 113 ++++++- 8 files changed, 1060 insertions(+), 149 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e62ac3b..7c47d7b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -302,12 +302,15 @@ jobs: # Clone this repo - name: Checkout uses: actions/checkout@v4.1.6 + with: + path: actions-checkout # Basic checkout using git - name: Checkout basic id: checkout - uses: ./ + uses: ./actions-checkout with: + path: cloned-using-local-action ref: test-data/v2/basic # Verify output @@ -325,7 +328,3 @@ jobs: echo "Expected commit to be 82f71901cf8c021332310dcc8cdba84c4193ff5d" exit 1 fi - - # needed to make checkout post cleanup succeed - - name: Fix Checkout - uses: actions/checkout@v4.1.6 diff --git a/__test__/git-auth-helper.test.ts b/__test__/git-auth-helper.test.ts index 7633704..ad3566a 100644 --- a/__test__/git-auth-helper.test.ts +++ b/__test__/git-auth-helper.test.ts @@ -86,16 +86,29 @@ describe('git-auth-helper tests', () => { // Act await authHelper.configureAuth() - // Assert config - const configContent = ( + // Assert config - check that .git/config contains includeIf entries + const localConfigContent = ( await fs.promises.readFile(localGitConfigPath) ).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( `x-access-token:${settings.authToken}`, 'utf8' ).toString('base64') expect( - configContent.indexOf( + credentialsContent.indexOf( `http.${expectedServerUrl}/.extraheader AUTHORIZATION: basic ${basicCredential}` ) ).toBeGreaterThanOrEqual(0) @@ -120,7 +133,7 @@ describe('git-auth-helper tests', () => { 'inject https://github.com as github server url' it(configureAuth_AcceptsGitHubServerUrlSetToGHEC, async () => { await testAuthHeader( - configureAuth_AcceptsGitHubServerUrl, + configureAuth_AcceptsGitHubServerUrlSetToGHEC, 'https://github.com' ) }) @@ -141,12 +154,17 @@ describe('git-auth-helper tests', () => { // Act await authHelper.configureAuth() - // Assert config - const configContent = ( - await fs.promises.readFile(localGitConfigPath) + // Assert config - check credentials config file (not local .git/config) + 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() expect( - configContent.indexOf( + credentialsContent.indexOf( `http.https://github.com/.extraheader AUTHORIZATION` ) ).toBeGreaterThanOrEqual(0) @@ -251,13 +269,16 @@ describe('git-auth-helper tests', () => { expectedSshCommand ) - // Asserty git config + // Assert git config const gitConfigLines = (await fs.promises.readFile(localGitConfigPath)) .toString() .split('\n') .filter(x => x) - expect(gitConfigLines).toHaveLength(1) - expect(gitConfigLines[0]).toMatch(/^http\./) + // Should have includeIf entries pointing to credentials file + expect(gitConfigLines.length).toBeGreaterThan(0) + expect( + gitConfigLines.some(line => line.indexOf('includeIf.gitdir:') >= 0) + ).toBeTruthy() }) const configureAuth_setsSshCommandWhenPersistCredentialsTrue = @@ -419,8 +440,20 @@ describe('git-auth-helper tests', () => { expect( configContent.indexOf('value-from-global-config') ).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( - configContent.indexOf( + credentialsContent.indexOf( `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` ) ).toBeGreaterThanOrEqual(0) @@ -463,8 +496,20 @@ describe('git-auth-helper tests', () => { const configContent = ( await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig')) ).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( - configContent.indexOf( + credentialsContent.indexOf( `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` ) ).toBeGreaterThanOrEqual(0) @@ -550,15 +595,15 @@ describe('git-auth-helper tests', () => { await authHelper.configureSubmoduleAuth() // Assert - expect(mockSubmoduleForeach).toHaveBeenCalledTimes(4) + // Should configure insteadOf (2 calls for two values) + expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3) expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch( /unset-all.*insteadOf/ ) - expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/) - expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch( + expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch( /url.*insteadOf.*git@github.com:/ ) - expect(mockSubmoduleForeach.mock.calls[3][0]).toMatch( + expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch( /url.*insteadOf.*org-123456@github.com:/ ) } @@ -589,12 +634,12 @@ describe('git-auth-helper tests', () => { await authHelper.configureSubmoduleAuth() // Assert - expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3) + // Should configure sshCommand (1 call) + expect(mockSubmoduleForeach).toHaveBeenCalledTimes(2) expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch( /unset-all.*insteadOf/ ) - expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/) - expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(/core\.sshCommand/) + expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/core\.sshCommand/) } ) @@ -660,19 +705,201 @@ describe('git-auth-helper tests', () => { await setup(removeAuth_removesToken) const authHelper = gitAuthHelper.createAuthHelper(git, settings) await authHelper.configureAuth() - let gitConfigContent = ( + + // Verify includeIf entries exist in local config + let localConfigContent = ( await fs.promises.readFile(localGitConfigPath) ).toString() - expect(gitConfigContent.indexOf('http.')).toBeGreaterThanOrEqual(0) // sanity check + expect( + localConfigContent.indexOf('includeIf.gitdir:') + ).toBeGreaterThanOrEqual(0) + + // Verify both host and container includeIf entries are present + const hostGitDir = path.join(workspace, '.git').replace(/\\/g, '/') + expect( + localConfigContent.indexOf(`includeIf.gitdir:${hostGitDir}.path`) + ).toBeGreaterThanOrEqual(0) + expect( + localConfigContent.indexOf('includeIf.gitdir:/github/workspace/.git.path') + ).toBeGreaterThanOrEqual(0) + + // 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) + const credentialsFilePath = path.join(runnerTemp, credentialsFiles[0]) + + // Verify credentials file contains the auth token + let credentialsContent = ( + await fs.promises.readFile(credentialsFilePath) + ).toString() + const basicCredential = Buffer.from( + `x-access-token:${settings.authToken}`, + 'utf8' + ).toString('base64') + expect( + credentialsContent.indexOf( + `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` + ) + ).toBeGreaterThanOrEqual(0) + + // Verify the includeIf entries point to the credentials file + const containerCredentialsPath = path.posix.join( + '/github/runner_temp', + path.basename(credentialsFilePath) + ) + expect( + localConfigContent.indexOf(credentialsFilePath) + ).toBeGreaterThanOrEqual(0) + expect( + localConfigContent.indexOf(containerCredentialsPath) + ).toBeGreaterThanOrEqual(0) // Act await authHelper.removeAuth() - // Assert git config - gitConfigContent = ( + // Assert all includeIf entries removed from local git config + localConfigContent = ( await fs.promises.readFile(localGitConfigPath) ).toString() - expect(gitConfigContent.indexOf('http.')).toBeLessThan(0) + expect(localConfigContent.indexOf('includeIf.gitdir:')).toBeLessThan(0) + expect( + localConfigContent.indexOf(`includeIf.gitdir:${hostGitDir}.path`) + ).toBeLessThan(0) + expect( + localConfigContent.indexOf('includeIf.gitdir:/github/workspace/.git.path') + ).toBeLessThan(0) + expect(localConfigContent.indexOf(credentialsFilePath)).toBeLessThan(0) + expect(localConfigContent.indexOf(containerCredentialsPath)).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) + + // Verify credentials file no longer exists on disk + try { + await fs.promises.stat(credentialsFilePath) + throw new Error('Credentials file should have been deleted') + } catch (err) { + if ((err as any)?.code !== 'ENOENT') { + throw err + } + } + }) + + const removeAuth_removesTokenFromSubmodules = + 'removeAuth removes token from submodules' + it(removeAuth_removesTokenFromSubmodules, async () => { + // Arrange + await setup(removeAuth_removesTokenFromSubmodules) + + // Create fake submodule config paths + const submodule1Dir = path.join(workspace, '.git', 'modules', 'submodule-1') + const submodule2Dir = path.join(workspace, '.git', 'modules', 'submodule-2') + const submodule1ConfigPath = path.join(submodule1Dir, 'config') + const submodule2ConfigPath = path.join(submodule2Dir, 'config') + + await fs.promises.mkdir(submodule1Dir, {recursive: true}) + await fs.promises.mkdir(submodule2Dir, {recursive: true}) + await fs.promises.writeFile(submodule1ConfigPath, '') + await fs.promises.writeFile(submodule2ConfigPath, '') + + // Mock getSubmoduleConfigPaths to return our fake submodules (for both configure and remove) + const mockGetSubmoduleConfigPaths = + git.getSubmoduleConfigPaths as jest.Mock + mockGetSubmoduleConfigPaths.mockResolvedValue([ + submodule1ConfigPath, + submodule2ConfigPath + ]) + + const authHelper = gitAuthHelper.createAuthHelper(git, settings) + await authHelper.configureAuth() + await authHelper.configureSubmoduleAuth() + + // 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) + const credentialsFilePath = path.join(runnerTemp, credentialsFiles[0]) + + // Verify submodule 1 config has includeIf entries + let submodule1Content = ( + await fs.promises.readFile(submodule1ConfigPath) + ).toString() + const submodule1GitDir = submodule1Dir.replace(/\\/g, '/') + expect( + submodule1Content.indexOf(`includeIf.gitdir:${submodule1GitDir}.path`) + ).toBeGreaterThanOrEqual(0) + expect( + submodule1Content.indexOf(credentialsFilePath) + ).toBeGreaterThanOrEqual(0) + + // Verify submodule 2 config has includeIf entries + let submodule2Content = ( + await fs.promises.readFile(submodule2ConfigPath) + ).toString() + const submodule2GitDir = submodule2Dir.replace(/\\/g, '/') + expect( + submodule2Content.indexOf(`includeIf.gitdir:${submodule2GitDir}.path`) + ).toBeGreaterThanOrEqual(0) + expect( + submodule2Content.indexOf(credentialsFilePath) + ).toBeGreaterThanOrEqual(0) + + // Verify both host and container paths are in each submodule config + const containerCredentialsPath = path.posix.join( + '/github/runner_temp', + path.basename(credentialsFilePath) + ) + expect( + submodule1Content.indexOf(containerCredentialsPath) + ).toBeGreaterThanOrEqual(0) + expect( + submodule2Content.indexOf(containerCredentialsPath) + ).toBeGreaterThanOrEqual(0) + + // Act - ensure mock persists for removeAuth + mockGetSubmoduleConfigPaths.mockResolvedValue([ + submodule1ConfigPath, + submodule2ConfigPath + ]) + await authHelper.removeAuth() + + // Assert submodule 1 includeIf entries removed + submodule1Content = ( + await fs.promises.readFile(submodule1ConfigPath) + ).toString() + expect(submodule1Content.indexOf('includeIf.gitdir:')).toBeLessThan(0) + expect(submodule1Content.indexOf(credentialsFilePath)).toBeLessThan(0) + expect(submodule1Content.indexOf(containerCredentialsPath)).toBeLessThan(0) + + // Assert submodule 2 includeIf entries removed + submodule2Content = ( + await fs.promises.readFile(submodule2ConfigPath) + ).toString() + expect(submodule2Content.indexOf('includeIf.gitdir:')).toBeLessThan(0) + expect(submodule2Content.indexOf(credentialsFilePath)).toBeLessThan(0) + expect(submodule2Content.indexOf(containerCredentialsPath)).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) + + // Verify credentials file no longer exists on disk + try { + await fs.promises.stat(credentialsFilePath) + throw new Error('Credentials file should have been deleted') + } catch (err) { + if ((err as any)?.code !== 'ENOENT') { + throw err + } + } }) const removeGlobalConfig_removesOverride = @@ -701,6 +928,52 @@ 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 { @@ -715,6 +988,7 @@ async function setup(testName: string): Promise { await fs.promises.mkdir(tempHomedir, {recursive: true}) process.env['RUNNER_TEMP'] = runnerTemp process.env['HOME'] = tempHomedir + process.env['GITHUB_WORKSPACE'] = workspace // Create git config globalGitConfigPath = path.join(tempHomedir, '.gitconfig') @@ -733,10 +1007,20 @@ async function setup(testName: string): Promise { checkout: jest.fn(), checkoutDetach: jest.fn(), config: jest.fn( - async (key: string, value: string, globalConfig?: boolean) => { - const configPath = globalConfig - ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig') - : localGitConfigPath + async ( + key: string, + value: string, + globalConfig?: boolean, + 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}`) } ), @@ -756,6 +1040,7 @@ async function setup(testName: string): Promise { env: {}, fetch: jest.fn(), getDefaultBranch: jest.fn(), + getSubmoduleConfigPaths: jest.fn(async () => []), getWorkingDirectory: jest.fn(() => workspace), init: jest.fn(), isDetached: jest.fn(), @@ -794,8 +1079,72 @@ async function setup(testName: string): Promise { return true } ), + tryConfigUnsetValue: jest.fn( + async ( + key: string, + value: string, + globalConfig?: boolean, + configPath?: string + ): Promise => { + const targetConfigPath = + configPath || + (globalConfig + ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig') + : localGitConfigPath) + let content = await fs.promises.readFile(targetConfigPath) + let lines = content + .toString() + .split('\n') + .filter(x => x) + .filter(x => !(x.startsWith(key) && x.includes(value))) + await fs.promises.writeFile(targetConfigPath, lines.join('\n')) + return true + } + ), tryDisableAutomaticGarbageCollection: jest.fn(), tryGetFetchUrl: jest.fn(), + tryGetConfigValues: jest.fn( + async ( + key: string, + globalConfig?: boolean, + configPath?: string + ): Promise => { + const targetConfigPath = + configPath || + (globalConfig + ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig') + : localGitConfigPath) + const content = await fs.promises.readFile(targetConfigPath) + 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, + configPath?: string + ): Promise => { + const targetConfigPath = + configPath || + (globalConfig + ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig') + : localGitConfigPath) + const content = await fs.promises.readFile(targetConfigPath) + 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(), version: jest.fn() } @@ -830,6 +1179,7 @@ async function setup(testName: string): Promise { async function getActualSshKeyPath(): Promise { let actualTempFiles = (await fs.promises.readdir(runnerTemp)) + .filter(x => !x.startsWith('git-credentials-')) // Exclude credentials config file .sort() .map(x => path.join(runnerTemp, x)) if (actualTempFiles.length === 0) { @@ -843,6 +1193,7 @@ async function getActualSshKeyPath(): Promise { async function getActualSshKnownHostsPath(): Promise { let actualTempFiles = (await fs.promises.readdir(runnerTemp)) + .filter(x => !x.startsWith('git-credentials-')) // Exclude credentials config file .sort() .map(x => path.join(runnerTemp, x)) if (actualTempFiles.length === 0) { diff --git a/__test__/git-directory-helper.test.ts b/__test__/git-directory-helper.test.ts index 22e9ae6..de79dc8 100644 --- a/__test__/git-directory-helper.test.ts +++ b/__test__/git-directory-helper.test.ts @@ -471,6 +471,7 @@ async function setup(testName: string): Promise { configExists: jest.fn(), fetch: jest.fn(), getDefaultBranch: jest.fn(), + getSubmoduleConfigPaths: jest.fn(async () => []), getWorkingDirectory: jest.fn(() => repositoryPath), init: jest.fn(), isDetached: jest.fn(), @@ -493,12 +494,15 @@ async function setup(testName: string): Promise { return true }), tryConfigUnset: jest.fn(), + tryConfigUnsetValue: jest.fn(), tryDisableAutomaticGarbageCollection: jest.fn(), tryGetFetchUrl: jest.fn(async () => { // Sanity check - this function shouldn't be called when the .git directory doesn't exist await fs.promises.stat(path.join(repositoryPath, '.git')) return repositoryUrl }), + tryGetConfigValues: jest.fn(), + tryGetConfigKeys: jest.fn(), tryReset: jest.fn(async () => { return true }), diff --git a/__test__/verify-submodules-recursive.sh b/__test__/verify-submodules-recursive.sh index 1b68f9b..5ecbb42 100755 --- a/__test__/verify-submodules-recursive.sh +++ b/__test__/verify-submodules-recursive.sh @@ -17,7 +17,7 @@ fi echo "Testing persisted credential" pushd ./submodules-recursive/submodule-level-1/submodule-level-2 -git config --local --name-only --get-regexp http.+extraheader && git fetch +git config --local --includes --name-only --get-regexp http.+extraheader && git fetch if [ "$?" != "0" ]; then echo "Failed to validate persisted credential" popd diff --git a/__test__/verify-submodules-true.sh b/__test__/verify-submodules-true.sh index 43769fe..4c311f8 100755 --- a/__test__/verify-submodules-true.sh +++ b/__test__/verify-submodules-true.sh @@ -17,7 +17,7 @@ fi echo "Testing persisted credential" pushd ./submodules-true/submodule-level-1 -git config --local --name-only --get-regexp http.+extraheader && git fetch +git config --local --includes --name-only --get-regexp http.+extraheader && git fetch if [ "$?" != "0" ]; then echo "Failed to validate persisted credential" popd diff --git a/dist/index.js b/dist/index.js index f3ae6f3..a251a19 100644 --- a/dist/index.js +++ b/dist/index.js @@ -162,6 +162,7 @@ class GitAuthHelper { this.sshKeyPath = ''; this.sshKnownHostsPath = ''; this.temporaryHomePath = ''; + this.credentialsConfigPath = ''; // Path to separate credentials config file in RUNNER_TEMP this.git = gitCommandManager; this.settings = gitSourceSettings || {}; // Token auth header @@ -229,15 +230,17 @@ class GitAuthHelper { configureGlobalAuth() { return __awaiter(this, void 0, void 0, function* () { // 'configureTempGlobalConfig' noops if already set, just returns the path - const newGitConfigPath = yield this.configureTempGlobalConfig(); + yield this.configureTempGlobalConfig(); try { // Configure the token - yield this.configureToken(newGitConfigPath, true); + yield this.configureToken(true); // Configure HTTPS instead of SSH yield this.git.tryConfigUnset(this.insteadOfKey, true); if (!this.settings.sshKey) { for (const insteadOfValue of this.insteadOfValues) { - yield this.git.config(this.insteadOfKey, insteadOfValue, true, true); + yield this.git.config(this.insteadOfKey, insteadOfValue, true, // globalConfig? + true // add? + ); } } } @@ -252,19 +255,34 @@ class GitAuthHelper { configureSubmoduleAuth() { return __awaiter(this, void 0, void 0, function* () { // Remove possible previous HTTPS instead of SSH - yield this.removeGitConfig(this.insteadOfKey, true); + yield this.removeSubmoduleGitConfig(this.insteadOfKey); if (this.settings.persistCredentials) { - // 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 - const output = 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 - `sh -c "git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`, this.settings.nestedSubmodules); - // Replace the placeholder - const configPaths = output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || []; + // Get the credentials config file path in RUNNER_TEMP + const credentialsConfigPath = this.getCredentialsConfigPath(); + // Container credentials config path + const containerCredentialsPath = path.posix.join('/github/runner_temp', path.basename(credentialsConfigPath)); + // Get submodule config file paths. + const configPaths = yield this.git.getSubmoduleConfigPaths(this.settings.nestedSubmodules); + // For each submodule, configure includeIf entries pointing to the shared credentials file. + // Configure both host and container paths to support Docker container actions. for (const configPath of configPaths) { - core.debug(`Replacing token placeholder in '${configPath}'`); - yield this.replaceTokenPlaceholder(configPath); + // Submodule Git directory + let submoduleGitDir = path.dirname(configPath); // The config file is at .git/modules/submodule-name/config + 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) { // Configure core.sshCommand @@ -295,6 +313,10 @@ class GitAuthHelper { } }); } + /** + * Configures SSH authentication by writing the SSH key and known hosts, + * and setting up the GIT_SSH_COMMAND environment variable. + */ configureSsh() { return __awaiter(this, void 0, void 0, function* () { if (!this.settings.sshKey) { @@ -351,43 +373,88 @@ 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* () { - // Validate args - assert.ok((configPath && globalConfig) || (!configPath && !globalConfig), 'Unexpected configureToken parameter combinations'); - // Default config path - if (!configPath && !globalConfig) { - configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config'); - } - // 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 - yield this.git.config(this.tokenConfigKey, this.tokenPlaceholderConfigValue, globalConfig); - // 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(); + // Get the credentials config file path in RUNNER_TEMP + const credentialsConfigPath = this.getCredentialsConfigPath(); + // Write placeholder to the separate credentials config file using git config. + // 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 + yield this.git.config(this.tokenConfigKey, this.tokenPlaceholderConfigValue, false, // globalConfig? + false, // add? + credentialsConfigPath); + // Replace the placeholder in the credentials config file + let content = (yield fs.promises.readFile(credentialsConfigPath)).toString(); const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue); if (placeholderIndex < 0 || placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue)) { - throw new Error(`Unable to replace auth placeholder in ${configPath}`); + throw new Error(`Unable to replace auth placeholder in ${credentialsConfigPath}`); } assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined'); content = content.replace(this.tokenPlaceholderConfigValue, this.tokenConfigValue); - yield fs.promises.writeFile(configPath, content); + yield fs.promises.writeFile(credentialsConfigPath, 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() { return __awaiter(this, void 0, void 0, function* () { - var _a; + var _a, _b; // SSH key const keyPath = this.sshKeyPath || stateHelper.SshKeyPath; if (keyPath) { try { + core.info(`Removing SSH key '${keyPath}'`); yield io.rmRF(keyPath); } catch (err) { @@ -399,37 +466,136 @@ class GitAuthHelper { const knownHostsPath = this.sshKnownHostsPath || stateHelper.SshKnownHostsPath; if (knownHostsPath) { try { + core.info(`Removing SSH known hosts '${knownHostsPath}'`); yield io.rmRF(knownHostsPath); } - catch (_b) { - // Intentionally empty + catch (err) { + core.debug(`${(_b = err === null || err === void 0 ? void 0 : err.message) !== null && _b !== void 0 ? _b : err}`); + core.warning(`Failed to remove SSH known hosts '${knownHostsPath}'`); } } // SSH command + core.info('Removing SSH command configuration'); 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() { return __awaiter(this, void 0, void 0, function* () { - // HTTP extra header + var _a; + // Remove HTTP extra header + core.info('Removing HTTP extra header'); yield this.removeGitConfig(this.tokenConfigKey); - }); - } - removeGitConfig(configKey_1) { - return __awaiter(this, arguments, void 0, function* (configKey, submoduleOnly = false) { - if (!submoduleOnly) { - if ((yield this.git.configExists(configKey)) && - !(yield this.git.tryConfigUnset(configKey))) { - // Load the config contents - core.warning(`Failed to remove '${configKey}' from the git config`); + 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`); } } + }); + } + /** + * Removes a git config key from the local repository config. + * @param configKey The git config key to remove + */ + removeGitConfig(configKey) { + return __awaiter(this, void 0, void 0, function* () { + if ((yield this.git.configExists(configKey)) && + !(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); 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); }); } + /** + * 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-.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); + } } @@ -627,9 +793,15 @@ class GitCommandManager { yield this.execGit(args); }); } - config(configKey, configValue, globalConfig, add) { + config(configKey, configValue, globalConfig, add, configFile) { return __awaiter(this, void 0, void 0, function* () { - const args = ['config', globalConfig ? '--global' : '--local']; + const args = ['config']; + if (configFile) { + args.push('--file', configFile); + } + else { + args.push(globalConfig ? '--global' : '--local'); + } if (add) { args.push('--add'); } @@ -706,6 +878,16 @@ class GitCommandManager { 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() { return this.workingDirectory; } @@ -836,6 +1018,20 @@ class GitCommandManager { 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() { return __awaiter(this, void 0, void 0, function* () { const output = yield this.execGit(['config', '--local', 'gc.auto', '0'], true); @@ -855,6 +1051,46 @@ class GitCommandManager { 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() { return __awaiter(this, void 0, void 0, function* () { const output = yield this.execGit(['reset', '--hard', 'HEAD'], true); diff --git a/src/git-auth-helper.ts b/src/git-auth-helper.ts index 126e8e5..a1950a6 100644 --- a/src/git-auth-helper.ts +++ b/src/git-auth-helper.ts @@ -43,6 +43,7 @@ class GitAuthHelper { private sshKeyPath = '' private sshKnownHostsPath = '' private temporaryHomePath = '' + private credentialsConfigPath = '' // Path to separate credentials config file in RUNNER_TEMP constructor( gitCommandManager: IGitCommandManager, @@ -126,16 +127,21 @@ class GitAuthHelper { async configureGlobalAuth(): Promise { // 'configureTempGlobalConfig' noops if already set, just returns the path - const newGitConfigPath = await this.configureTempGlobalConfig() + await this.configureTempGlobalConfig() try { // Configure the token - await this.configureToken(newGitConfigPath, true) + await this.configureToken(true) // Configure HTTPS instead of SSH await this.git.tryConfigUnset(this.insteadOfKey, true) if (!this.settings.sshKey) { for (const insteadOfValue of this.insteadOfValues) { - await this.git.config(this.insteadOfKey, insteadOfValue, true, true) + await this.git.config( + this.insteadOfKey, + insteadOfValue, + true, // globalConfig? + true // add? + ) } } } catch (err) { @@ -150,24 +156,60 @@ class GitAuthHelper { async configureSubmoduleAuth(): Promise { // Remove possible previous HTTPS instead of SSH - await this.removeGitConfig(this.insteadOfKey, true) + await this.removeSubmoduleGitConfig(this.insteadOfKey) if (this.settings.persistCredentials) { - // 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 - const output = 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 - `sh -c "git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`, + // Get the credentials config file path in RUNNER_TEMP + const credentialsConfigPath = this.getCredentialsConfigPath() + + // Container credentials config path + const containerCredentialsPath = path.posix.join( + '/github/runner_temp', + path.basename(credentialsConfigPath) + ) + + // Get submodule config file paths. + const configPaths = await this.git.getSubmoduleConfigPaths( this.settings.nestedSubmodules ) - // Replace the placeholder - const configPaths: string[] = - output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || [] + // For each submodule, configure includeIf entries pointing to the shared credentials file. + // Configure both host and container paths to support Docker container actions. for (const configPath of configPaths) { - core.debug(`Replacing token placeholder in '${configPath}'`) - await this.replaceTokenPlaceholder(configPath) + // Submodule Git directory + let submoduleGitDir = path.dirname(configPath) // The config file is at .git/modules/submodule-name/config + 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) { @@ -201,6 +243,10 @@ 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 { if (!this.settings.sshKey) { return @@ -272,57 +318,116 @@ class GitAuthHelper { } } - private async configureToken( - configPath?: string, - globalConfig?: boolean - ): Promise { - // Validate args - assert.ok( - (configPath && globalConfig) || (!configPath && !globalConfig), - 'Unexpected configureToken parameter combinations' - ) + /** + * 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 + */ + private async configureToken(globalConfig?: boolean): Promise { + // Get the credentials config file path in RUNNER_TEMP + const credentialsConfigPath = this.getCredentialsConfigPath() - // Default config path - if (!configPath && !globalConfig) { - configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config') - } - - // 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 + // Write placeholder to the separate credentials config file using git config. + // 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( this.tokenConfigKey, this.tokenPlaceholderConfigValue, - globalConfig + false, // globalConfig? + false, // add? + credentialsConfigPath ) - // Replace the placeholder - await this.replaceTokenPlaceholder(configPath || '') - } - - private async replaceTokenPlaceholder(configPath: string): Promise { - assert.ok(configPath, 'configPath is not defined') - let content = (await fs.promises.readFile(configPath)).toString() + // Replace the placeholder in the credentials config file + let content = (await fs.promises.readFile(credentialsConfigPath)).toString() const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue) if ( placeholderIndex < 0 || placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue) ) { - throw new Error(`Unable to replace auth placeholder in ${configPath}`) + throw new Error( + `Unable to replace auth placeholder in ${credentialsConfigPath}` + ) } assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined') content = content.replace( this.tokenPlaceholderConfigValue, this.tokenConfigValue ) - await fs.promises.writeFile(configPath, content) + await fs.promises.writeFile(credentialsConfigPath, 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 { // SSH key const keyPath = this.sshKeyPath || stateHelper.SshKeyPath if (keyPath) { try { + core.info(`Removing SSH key '${keyPath}'`) await io.rmRF(keyPath) } catch (err) { core.debug(`${(err as any)?.message ?? err}`) @@ -335,40 +440,149 @@ class GitAuthHelper { this.sshKnownHostsPath || stateHelper.SshKnownHostsPath if (knownHostsPath) { try { + core.info(`Removing SSH known hosts '${knownHostsPath}'`) await io.rmRF(knownHostsPath) - } catch { - // Intentionally empty + } catch (err) { + core.debug(`${(err as any)?.message ?? err}`) + core.warning(`Failed to remove SSH known hosts '${knownHostsPath}'`) } } // SSH command + core.info('Removing SSH command configuration') 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 { - // HTTP extra header + // Remove HTTP extra header + core.info('Removing HTTP extra header') await this.removeGitConfig(this.tokenConfigKey) - } + await this.removeSubmoduleGitConfig(this.tokenConfigKey) - private async removeGitConfig( - configKey: string, - submoduleOnly: boolean = false - ): Promise { - if (!submoduleOnly) { - 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`) - } + // 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 = await this.removeIncludeIfCredentials() + mainCredentialsPaths.forEach(path => credentialsPaths.add(path)) + + // Remove submodule includeIf entries that point to git-credentials-*.config files + const submoduleConfigPaths = await this.git.getSubmoduleConfigPaths(true) + for (const configPath of submoduleConfigPaths) { + 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 { + 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 { const pattern = regexpHelper.escape(configKey) 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}' || :"`, 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 { + const credentialsPaths = new Set() + + 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-.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) + } } diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts index 8e42a38..a45e15a 100644 --- a/src/git-command-manager.ts +++ b/src/git-command-manager.ts @@ -28,7 +28,8 @@ export interface IGitCommandManager { configKey: string, configValue: string, globalConfig?: boolean, - add?: boolean + add?: boolean, + configFile?: string ): Promise configExists(configKey: string, globalConfig?: boolean): Promise fetch( @@ -41,6 +42,7 @@ export interface IGitCommandManager { } ): Promise getDefaultBranch(repositoryUrl: string): Promise + getSubmoduleConfigPaths(recursive: boolean): Promise getWorkingDirectory(): string init(): Promise isDetached(): Promise @@ -59,8 +61,24 @@ export interface IGitCommandManager { tagExists(pattern: string): Promise tryClean(): Promise tryConfigUnset(configKey: string, globalConfig?: boolean): Promise + tryConfigUnsetValue( + configKey: string, + configValue: string, + globalConfig?: boolean, + configFile?: string + ): Promise tryDisableAutomaticGarbageCollection(): Promise tryGetFetchUrl(): Promise + tryGetConfigValues( + configKey: string, + globalConfig?: boolean, + configFile?: string + ): Promise + tryGetConfigKeys( + pattern: string, + globalConfig?: boolean, + configFile?: string + ): Promise tryReset(): Promise version(): Promise } @@ -223,9 +241,15 @@ class GitCommandManager { configKey: string, configValue: string, globalConfig?: boolean, - add?: boolean + add?: boolean, + configFile?: string ): Promise { - const args: string[] = ['config', globalConfig ? '--global' : '--local'] + const args: string[] = ['config'] + if (configFile) { + args.push('--file', configFile) + } else { + args.push(globalConfig ? '--global' : '--local') + } if (add) { args.push('--add') } @@ -323,6 +347,21 @@ class GitCommandManager { throw new Error('Unexpected output when retrieving default branch') } + async getSubmoduleConfigPaths(recursive: boolean): Promise { + // 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 { return this.workingDirectory } @@ -455,6 +494,24 @@ class GitCommandManager { return output.exitCode === 0 } + async tryConfigUnsetValue( + configKey: string, + configValue: string, + globalConfig?: boolean, + configFile?: string + ): Promise { + 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 { const output = await this.execGit( ['config', '--local', 'gc.auto', '0'], @@ -481,6 +538,56 @@ class GitCommandManager { return stdout } + async tryGetConfigValues( + configKey: string, + globalConfig?: boolean, + configFile?: string + ): Promise { + 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 { + 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 { const output = await this.execGit(['reset', '--hard', 'HEAD'], true) return output.exitCode === 0 From 71cf2267d89c5cb81562390fa70a37fa40b1305e Mon Sep 17 00:00:00 2001 From: eric sciple Date: Mon, 3 Nov 2025 13:40:10 -0600 Subject: [PATCH 12/12] v6-beta (#2298) --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9a32e9a..b24767b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ [![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 +# Checkout v6-beta + +## What's new + +- Updated `persist-credentials` to store the credentials under `$RUNNER_TEMP` instead of directly in the local git config. + - This requires a minimum Actions Runner version of [v2.329.0](https://github.com/actions/runner/releases/tag/v2.329.0) to access the persisted credentials for [Docker container action](https://docs.github.com/en/actions/tutorials/use-containerized-services/create-a-docker-container-action) scenarios. + +# Checkout v5 ## What's new @@ -8,7 +15,7 @@ - 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.