diff --git a/patches/last_processed_commit.txt b/patches/last_processed_commit.txt
index 50b48bf13bb..4376a4d86a0 100644
--- a/patches/last_processed_commit.txt
+++ b/patches/last_processed_commit.txt
@@ -1 +1 @@
-40dd583defe00b07f45ff976c95b564f7929bf69
+f0c228635e874e39eb53db0bf091c4925e53cbb1
diff --git a/patches/runner-main-sdk8-ppc64le.patch b/patches/runner-main-sdk8-ppc64le.patch
index 192c69836d5..fe80d6feba6 100644
--- a/patches/runner-main-sdk8-ppc64le.patch
+++ b/patches/runner-main-sdk8-ppc64le.patch
@@ -86,7 +86,7 @@ index 14cc6bab..c8ed8b92 100755
if ! [ -x "$(command -v ldconfig)" ]; then
diff --git a/src/Runner.Common/Constants.cs b/src/Runner.Common/Constants.cs
-index 58395898..b3dc2e83 100644
+index 3326e947..02b3ada4 100644
--- a/src/Runner.Common/Constants.cs
+++ b/src/Runner.Common/Constants.cs
@@ -59,7 +59,9 @@ namespace GitHub.Runner.Common
@@ -216,7 +216,7 @@ index a5a19aea..b2086aa5 100644
NU1701;NU1603;NU1603;xUnit2013;SYSLIB0050;SYSLIB0051
diff --git a/src/dev.sh b/src/dev.sh
-index 716fa08e..5f20ca6a 100755
+index df0a4328..76b78740 100755
--- a/src/dev.sh
+++ b/src/dev.sh
@@ -54,6 +54,8 @@ elif [[ "$CURRENT_PLATFORM" == 'linux' ]]; then
@@ -284,4 +284,4 @@ index 056a312e..3f9a3679 100644
-# From upstream commit: 40dd583defe00b07f45ff976c95b564f7929bf69
+# From upstream commit: f0c228635e874e39eb53db0bf091c4925e53cbb1
diff --git a/patches/runner-main-sdk8-s390x.patch b/patches/runner-main-sdk8-s390x.patch
index 192c69836d5..fe80d6feba6 100644
--- a/patches/runner-main-sdk8-s390x.patch
+++ b/patches/runner-main-sdk8-s390x.patch
@@ -86,7 +86,7 @@ index 14cc6bab..c8ed8b92 100755
if ! [ -x "$(command -v ldconfig)" ]; then
diff --git a/src/Runner.Common/Constants.cs b/src/Runner.Common/Constants.cs
-index 58395898..b3dc2e83 100644
+index 3326e947..02b3ada4 100644
--- a/src/Runner.Common/Constants.cs
+++ b/src/Runner.Common/Constants.cs
@@ -59,7 +59,9 @@ namespace GitHub.Runner.Common
@@ -216,7 +216,7 @@ index a5a19aea..b2086aa5 100644
NU1701;NU1603;NU1603;xUnit2013;SYSLIB0050;SYSLIB0051
diff --git a/src/dev.sh b/src/dev.sh
-index 716fa08e..5f20ca6a 100755
+index df0a4328..76b78740 100755
--- a/src/dev.sh
+++ b/src/dev.sh
@@ -54,6 +54,8 @@ elif [[ "$CURRENT_PLATFORM" == 'linux' ]]; then
@@ -284,4 +284,4 @@ index 056a312e..3f9a3679 100644
-# From upstream commit: 40dd583defe00b07f45ff976c95b564f7929bf69
+# From upstream commit: f0c228635e874e39eb53db0bf091c4925e53cbb1
diff --git a/releaseNote.md b/releaseNote.md
index b4915dd6146..e8e76b6cf54 100644
--- a/releaseNote.md
+++ b/releaseNote.md
@@ -1,35 +1,33 @@
## What's Changed
-* chore: update Node versions by @github-actions[bot] in https://github.com/actions/runner/pull/4200
-* Update dotnet sdk to latest version @8.0.417 by @github-actions[bot] in https://github.com/actions/runner/pull/4201
-* Bump System.Formats.Asn1 and System.Security.Cryptography.Pkcs by @dependabot[bot] in https://github.com/actions/runner/pull/4202
-* Allow empty container options by @ericsciple in https://github.com/actions/runner/pull/4208
-* Update Docker to v29.1.5 and Buildx to v0.31.0 by @github-actions[bot] in https://github.com/actions/runner/pull/4212
-* Report job level annotations by @TingluoHuang in https://github.com/actions/runner/pull/4216
-* Fix local action display name showing `Run /./` instead of `Run ./` by @ericsciple in https://github.com/actions/runner/pull/4218
-* Update Docker to v29.2.0 and Buildx to v0.31.1 by @github-actions[bot] in https://github.com/actions/runner/pull/4219
-* Add support for libssl3 and libssl3t64 for newer Debian/Ubuntu versions by @nekketsuuu in https://github.com/actions/runner/pull/4213
-* Validate work dir during runner start up. by @TingluoHuang in https://github.com/actions/runner/pull/4227
-* Bump hook to 0.8.1 by @nikola-jokic in https://github.com/actions/runner/pull/4222
-* Support return job result as exitcode in hosted runner. by @TingluoHuang in https://github.com/actions/runner/pull/4233
-* Add telemetry tracking for deprecated set-output and save-state commands by @ericsciple in https://github.com/actions/runner/pull/4221
-* Fix parser comparison mismatches by @ericsciple in https://github.com/actions/runner/pull/4220
-* Remove unnecessary connection test during some registration flows by @zarenner in https://github.com/actions/runner/pull/4244
-* chore: update Node versions by @github-actions[bot] in https://github.com/actions/runner/pull/4249
-* Update dotnet sdk to latest version @8.0.418 by @github-actions[bot] in https://github.com/actions/runner/pull/4250
-* Fix link to SECURITY.md in README by @TingluoHuang in https://github.com/actions/runner/pull/4253
-* Try to infer runner is on hosted/ghes when githuburl is empty. by @TingluoHuang in https://github.com/actions/runner/pull/4254
-* Add Node.js 20 deprecation warning annotation (Phase 1) by @salmanmkc in https://github.com/actions/runner/pull/4242
-* Update Node.js 20 deprecation date to June 2nd, 2026 by @salmanmkc in https://github.com/actions/runner/pull/4258
-* Composite Action Step Markers by @ericsciple in https://github.com/actions/runner/pull/4243
-* Symlink actions cache by @paveliak in https://github.com/actions/runner/pull/4260
-* Bump minimatch in /src/Misc/expressionFunc/hashFiles by @dependabot[bot] in https://github.com/actions/runner/pull/4261
-* Bump @stylistic/eslint-plugin from 3.1.0 to 5.9.0 in /src/Misc/expressionFunc/hashFiles by @dependabot[bot] in https://github.com/actions/runner/pull/4257
+* Log inner exception message. by @TingluoHuang in https://github.com/actions/runner/pull/4265
+* Fix composite post-step marker display names by @ericsciple in https://github.com/actions/runner/pull/4267
+* Bump actions/download-artifact from 7 to 8 by @dependabot[bot] in https://github.com/actions/runner/pull/4269
+* chore: update Node versions by @github-actions[bot] in https://github.com/actions/runner/pull/4272
+* Avoid throw in SelfUpdaters. by @TingluoHuang in https://github.com/actions/runner/pull/4274
+* Fix parser comparison mismatches by @ericsciple in https://github.com/actions/runner/pull/4273
+* Devcontainer: bump base image Ubuntu version by @MaxHorstmann in https://github.com/actions/runner/pull/4277
+* Support `entrypoint` and `command` for service containers by @ericsciple in https://github.com/actions/runner/pull/4276
+* Bump actions/upload-artifact from 6 to 7 by @dependabot[bot] in https://github.com/actions/runner/pull/4270
+* Bump docker/login-action from 3 to 4 by @dependabot[bot] in https://github.com/actions/runner/pull/4278
+* Fix positional arg bug in ExpressionParser.CreateTree by @ericsciple in https://github.com/actions/runner/pull/4279
+* Bump docker/build-push-action from 6 to 7 by @dependabot[bot] in https://github.com/actions/runner/pull/4283
+* Bump docker/setup-buildx-action from 3 to 4 by @dependabot[bot] in https://github.com/actions/runner/pull/4282
+* Bump actions/attest-build-provenance from 3 to 4 by @dependabot[bot] in https://github.com/actions/runner/pull/4266
+* Bump @stylistic/eslint-plugin from 5.9.0 to 5.10.0 in /src/Misc/expressionFunc/hashFiles by @dependabot[bot] in https://github.com/actions/runner/pull/4281
+* Update Docker to v29.3.0 and Buildx to v0.32.1 by @github-actions[bot] in https://github.com/actions/runner/pull/4286
+* chore: update Node versions by @github-actions[bot] in https://github.com/actions/runner/pull/4287
+* Fix cancellation token race during parser comparison by @ericsciple in https://github.com/actions/runner/pull/4280
+* Bump @typescript-eslint/eslint-plugin from 8.47.0 to 8.54.0 in /src/Misc/expressionFunc/hashFiles by @dependabot[bot] in https://github.com/actions/runner/pull/4230
+* Exit with specified exit code when runner is outdated by @nikola-jokic in https://github.com/actions/runner/pull/4285
+* Report infra_error for action download failures. by @TingluoHuang in https://github.com/actions/runner/pull/4294
+* Update dotnet sdk to latest version @8.0.419 by @github-actions[bot] in https://github.com/actions/runner/pull/4301
+* Node 24 enforcement + Linux ARM32 deprecation support by @salmanmkc in https://github.com/actions/runner/pull/4303
+* Bump @typescript-eslint/eslint-plugin from 8.54.0 to 8.57.1 in /src/Misc/expressionFunc/hashFiles by @dependabot[bot] in https://github.com/actions/runner/pull/4304
## New Contributors
-* @nekketsuuu made their first contribution in https://github.com/actions/runner/pull/4213
-* @zarenner made their first contribution in https://github.com/actions/runner/pull/4244
+* @MaxHorstmann made their first contribution in https://github.com/actions/runner/pull/4277
-**Full Changelog**: https://github.com/actions/runner/compare/v2.331.0...v2.332.0
+**Full Changelog**: https://github.com/actions/runner/compare/v2.332.0...v2.333.0
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
diff --git a/src/Misc/expressionFunc/hashFiles/package-lock.json b/src/Misc/expressionFunc/hashFiles/package-lock.json
index 8d141cac50c..d8099aa529e 100644
--- a/src/Misc/expressionFunc/hashFiles/package-lock.json
+++ b/src/Misc/expressionFunc/hashFiles/package-lock.json
@@ -14,7 +14,7 @@
"devDependencies": {
"@stylistic/eslint-plugin": "^5.10.0",
"@types/node": "^22.0.0",
- "@typescript-eslint/eslint-plugin": "^8.0.0",
+ "@typescript-eslint/eslint-plugin": "^8.57.2",
"@typescript-eslint/parser": "^8.0.0",
"@vercel/ncc": "^0.38.3",
"eslint": "^8.47.0",
@@ -93,9 +93,9 @@
}
},
"node_modules/@eslint-community/regexpp": {
- "version": "4.10.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
- "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
"dev": true,
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
@@ -247,19 +247,6 @@
"eslint": "^9.0.0 || ^10.0.0"
}
},
- "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/types": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.0.tgz",
- "integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==",
- "dev": true,
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
"node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
@@ -321,21 +308,19 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz",
- "integrity": "sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz",
+ "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.47.0",
- "@typescript-eslint/type-utils": "8.47.0",
- "@typescript-eslint/utils": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0",
- "graphemer": "^1.4.0",
- "ignore": "^7.0.0",
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.57.2",
+ "@typescript-eslint/type-utils": "8.57.2",
+ "@typescript-eslint/utils": "8.57.2",
+ "@typescript-eslint/visitor-keys": "8.57.2",
+ "ignore": "^7.0.5",
"natural-compare": "^1.4.0",
- "ts-api-utils": "^2.1.0"
+ "ts-api-utils": "^2.4.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -345,8 +330,8 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.47.0",
- "eslint": "^8.57.0 || ^9.0.0",
+ "@typescript-eslint/parser": "^8.57.2",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
@@ -361,11 +346,10 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ts-api-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
- "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
+ "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=18.12"
},
@@ -374,17 +358,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.47.0.tgz",
- "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz",
+ "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.47.0",
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/typescript-estree": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0",
- "debug": "^4.3.4"
+ "@typescript-eslint/scope-manager": "8.57.2",
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/typescript-estree": "8.57.2",
+ "@typescript-eslint/visitor-keys": "8.57.2",
+ "debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -394,20 +377,19 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.47.0.tgz",
- "integrity": "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz",
+ "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.47.0",
- "@typescript-eslint/types": "^8.47.0",
- "debug": "^4.3.4"
+ "@typescript-eslint/tsconfig-utils": "^8.57.2",
+ "@typescript-eslint/types": "^8.57.2",
+ "debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -421,14 +403,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.47.0.tgz",
- "integrity": "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz",
+ "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0"
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/visitor-keys": "8.57.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -439,11 +420,10 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.47.0.tgz",
- "integrity": "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz",
+ "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==",
"dev": true,
- "license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
@@ -456,17 +436,16 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.47.0.tgz",
- "integrity": "sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz",
+ "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/typescript-estree": "8.47.0",
- "@typescript-eslint/utils": "8.47.0",
- "debug": "^4.3.4",
- "ts-api-utils": "^2.1.0"
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/typescript-estree": "8.57.2",
+ "@typescript-eslint/utils": "8.57.2",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.4.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -476,16 +455,15 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/ts-api-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
- "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=18.12"
},
@@ -494,11 +472,10 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.47.0.tgz",
- "integrity": "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz",
+ "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==",
"dev": true,
- "license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
@@ -508,22 +485,20 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.47.0.tgz",
- "integrity": "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz",
+ "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.47.0",
- "@typescript-eslint/tsconfig-utils": "8.47.0",
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0",
- "debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
- "ts-api-utils": "^2.1.0"
+ "@typescript-eslint/project-service": "8.57.2",
+ "@typescript-eslint/tsconfig-utils": "8.57.2",
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/visitor-keys": "8.57.2",
+ "debug": "^4.4.3",
+ "minimatch": "^10.2.2",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.4.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -546,9 +521,9 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
- "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
+ "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"dev": true,
"dependencies": {
"balanced-match": "^4.0.2"
@@ -558,26 +533,25 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
- "version": "9.0.7",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz",
- "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==",
+ "version": "10.2.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
+ "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true,
"dependencies": {
"brace-expansion": "^5.0.2"
},
"engines": {
- "node": ">=16 || 14 >=14.17"
+ "node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/ts-api-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
- "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=18.12"
},
@@ -586,16 +560,15 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.47.0.tgz",
- "integrity": "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz",
+ "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.47.0",
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/typescript-estree": "8.47.0"
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.57.2",
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/typescript-estree": "8.57.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -605,19 +578,18 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.47.0.tgz",
- "integrity": "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz",
+ "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.47.0",
- "eslint-visitor-keys": "^4.2.1"
+ "@typescript-eslint/types": "8.57.2",
+ "eslint-visitor-keys": "^5.0.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -628,13 +600,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
"dev": true,
- "license": "Apache-2.0",
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
@@ -1150,11 +1121,10 @@
"dev": true
},
"node_modules/debug": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
- "license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
@@ -2217,9 +2187,9 @@
}
},
"node_modules/flatted": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
- "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true
},
"node_modules/for-each": {
@@ -4019,9 +3989,9 @@
}
},
"node_modules/semver": {
- "version": "7.6.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
- "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"bin": {
"semver": "bin/semver.js"
@@ -4319,6 +4289,51 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/titleize": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
@@ -4756,9 +4771,9 @@
}
},
"@eslint-community/regexpp": {
- "version": "4.10.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
- "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
"dev": true
},
"@eslint/eslintrc": {
@@ -4867,12 +4882,6 @@
"picomatch": "^4.0.3"
},
"dependencies": {
- "@typescript-eslint/types": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.0.tgz",
- "integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==",
- "dev": true
- },
"eslint-visitor-keys": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
@@ -4914,20 +4923,19 @@
}
},
"@typescript-eslint/eslint-plugin": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz",
- "integrity": "sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz",
+ "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==",
"dev": true,
"requires": {
- "@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.47.0",
- "@typescript-eslint/type-utils": "8.47.0",
- "@typescript-eslint/utils": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0",
- "graphemer": "^1.4.0",
- "ignore": "^7.0.0",
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.57.2",
+ "@typescript-eslint/type-utils": "8.57.2",
+ "@typescript-eslint/utils": "8.57.2",
+ "@typescript-eslint/visitor-keys": "8.57.2",
+ "ignore": "^7.0.5",
"natural-compare": "^1.4.0",
- "ts-api-utils": "^2.1.0"
+ "ts-api-utils": "^2.4.0"
},
"dependencies": {
"ignore": {
@@ -4937,99 +4945,98 @@
"dev": true
},
"ts-api-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
- "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
+ "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
"dev": true,
"requires": {}
}
}
},
"@typescript-eslint/parser": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.47.0.tgz",
- "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz",
+ "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==",
"dev": true,
"requires": {
- "@typescript-eslint/scope-manager": "8.47.0",
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/typescript-estree": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0",
- "debug": "^4.3.4"
+ "@typescript-eslint/scope-manager": "8.57.2",
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/typescript-estree": "8.57.2",
+ "@typescript-eslint/visitor-keys": "8.57.2",
+ "debug": "^4.4.3"
}
},
"@typescript-eslint/project-service": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.47.0.tgz",
- "integrity": "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz",
+ "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==",
"dev": true,
"requires": {
- "@typescript-eslint/tsconfig-utils": "^8.47.0",
- "@typescript-eslint/types": "^8.47.0",
- "debug": "^4.3.4"
+ "@typescript-eslint/tsconfig-utils": "^8.57.2",
+ "@typescript-eslint/types": "^8.57.2",
+ "debug": "^4.4.3"
}
},
"@typescript-eslint/scope-manager": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.47.0.tgz",
- "integrity": "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz",
+ "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==",
"dev": true,
"requires": {
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0"
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/visitor-keys": "8.57.2"
}
},
"@typescript-eslint/tsconfig-utils": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.47.0.tgz",
- "integrity": "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz",
+ "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==",
"dev": true,
"requires": {}
},
"@typescript-eslint/type-utils": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.47.0.tgz",
- "integrity": "sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz",
+ "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==",
"dev": true,
"requires": {
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/typescript-estree": "8.47.0",
- "@typescript-eslint/utils": "8.47.0",
- "debug": "^4.3.4",
- "ts-api-utils": "^2.1.0"
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/typescript-estree": "8.57.2",
+ "@typescript-eslint/utils": "8.57.2",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.4.0"
},
"dependencies": {
"ts-api-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
- "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
"dev": true,
"requires": {}
}
}
},
"@typescript-eslint/types": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.47.0.tgz",
- "integrity": "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz",
+ "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.47.0.tgz",
- "integrity": "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz",
+ "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==",
"dev": true,
"requires": {
- "@typescript-eslint/project-service": "8.47.0",
- "@typescript-eslint/tsconfig-utils": "8.47.0",
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0",
- "debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
- "ts-api-utils": "^2.1.0"
+ "@typescript-eslint/project-service": "8.57.2",
+ "@typescript-eslint/tsconfig-utils": "8.57.2",
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/visitor-keys": "8.57.2",
+ "debug": "^4.4.3",
+ "minimatch": "^10.2.2",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.4.0"
},
"dependencies": {
"balanced-match": {
@@ -5039,58 +5046,58 @@
"dev": true
},
"brace-expansion": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
- "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
+ "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"dev": true,
"requires": {
"balanced-match": "^4.0.2"
}
},
"minimatch": {
- "version": "9.0.7",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz",
- "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==",
+ "version": "10.2.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
+ "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true,
"requires": {
"brace-expansion": "^5.0.2"
}
},
"ts-api-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
- "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
"dev": true,
"requires": {}
}
}
},
"@typescript-eslint/utils": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.47.0.tgz",
- "integrity": "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz",
+ "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==",
"dev": true,
"requires": {
- "@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.47.0",
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/typescript-estree": "8.47.0"
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.57.2",
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/typescript-estree": "8.57.2"
}
},
"@typescript-eslint/visitor-keys": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.47.0.tgz",
- "integrity": "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==",
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz",
+ "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==",
"dev": true,
"requires": {
- "@typescript-eslint/types": "8.47.0",
- "eslint-visitor-keys": "^4.2.1"
+ "@typescript-eslint/types": "8.57.2",
+ "eslint-visitor-keys": "^5.0.0"
},
"dependencies": {
"eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
"dev": true
}
}
@@ -5438,9 +5445,9 @@
"dev": true
},
"debug": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"requires": {
"ms": "^2.1.3"
@@ -6191,9 +6198,9 @@
}
},
"flatted": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
- "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true
},
"for-each": {
@@ -7389,9 +7396,9 @@
}
},
"semver": {
- "version": "7.6.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
- "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true
},
"shebang-command": {
@@ -7587,6 +7594,31 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
+ "tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "requires": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "dependencies": {
+ "fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "requires": {}
+ },
+ "picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true
+ }
+ }
+ },
"titleize": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
diff --git a/src/Misc/expressionFunc/hashFiles/package.json b/src/Misc/expressionFunc/hashFiles/package.json
index 01ea1459f17..0097a84b7e3 100644
--- a/src/Misc/expressionFunc/hashFiles/package.json
+++ b/src/Misc/expressionFunc/hashFiles/package.json
@@ -37,7 +37,7 @@
"devDependencies": {
"@stylistic/eslint-plugin": "^5.10.0",
"@types/node": "^22.0.0",
- "@typescript-eslint/eslint-plugin": "^8.0.0",
+ "@typescript-eslint/eslint-plugin": "^8.57.2",
"@typescript-eslint/parser": "^8.0.0",
"@vercel/ncc": "^0.38.3",
"eslint": "^8.47.0",
diff --git a/src/Misc/layoutroot/run-helper.cmd.template b/src/Misc/layoutroot/run-helper.cmd.template
index 6b594d4f357..389280ef7c8 100644
--- a/src/Misc/layoutroot/run-helper.cmd.template
+++ b/src/Misc/layoutroot/run-helper.cmd.template
@@ -10,6 +10,13 @@ if %ERRORLEVEL% EQU 0 (
exit /b 0
)
+if "%ACTIONS_RUNNER_RETURN_VERSION_DEPRECATED_EXIT_CODE%"=="1" (
+ if %ERRORLEVEL% EQU 7 (
+ echo "Runner listener exit with deprecated version error code: %ERRORLEVEL%."
+ exit /b %ERRORLEVEL%
+ )
+)
+
if %ERRORLEVEL% EQU 1 (
echo "Runner listener exit with terminated error, stop the service, no retry needed."
exit /b 0
diff --git a/src/Misc/layoutroot/run-helper.sh.template b/src/Misc/layoutroot/run-helper.sh.template
index 9f2b3cc4457..813c747355a 100755
--- a/src/Misc/layoutroot/run-helper.sh.template
+++ b/src/Misc/layoutroot/run-helper.sh.template
@@ -34,11 +34,13 @@ fi
updateFile="update.finished"
"$DIR"/bin/Runner.Listener run $*
-
returnCode=$?
if [[ $returnCode == 0 ]]; then
echo "Runner listener exit with 0 return code, stop the service, no retry needed."
exit 0
+elif [[ "$ACTIONS_RUNNER_RETURN_VERSION_DEPRECATED_EXIT_CODE" == "1" && $returnCode -eq 7 ]]; then
+ echo "Runner listener exit with deprecated version exit code: ${returnCode}."
+ exit "$returnCode"
elif [[ $returnCode == 1 ]]; then
echo "Runner listener exit with terminated error, stop the service, no retry needed."
exit 0
diff --git a/src/Misc/layoutroot/run.cmd b/src/Misc/layoutroot/run.cmd
index 692b38f9b9f..d0a4052c9bc 100644
--- a/src/Misc/layoutroot/run.cmd
+++ b/src/Misc/layoutroot/run.cmd
@@ -25,7 +25,14 @@ call "%~dp0run-helper.cmd" %*
if %ERRORLEVEL% EQU 1 (
echo "Restarting runner..."
goto :launch_helper
-) else (
- echo "Exiting runner..."
- exit /b 0
)
+
+if "%ACTIONS_RUNNER_RETURN_VERSION_DEPRECATED_EXIT_CODE%"=="1" (
+ if %ERRORLEVEL% EQU 7 (
+ echo "Exiting runner with deprecated version error code: %ERRORLEVEL%"
+ exit /b %ERRORLEVEL%
+ )
+)
+
+echo "Exiting runner..."
+exit /b 0
diff --git a/src/Misc/layoutroot/run.sh b/src/Misc/layoutroot/run.sh
index 57f18ee00e1..27b3cb404b2 100755
--- a/src/Misc/layoutroot/run.sh
+++ b/src/Misc/layoutroot/run.sh
@@ -19,6 +19,9 @@ run() {
returnCode=$?
if [[ $returnCode -eq 2 ]]; then
echo "Restarting runner..."
+ elif [[ "$ACTIONS_RUNNER_RETURN_VERSION_DEPRECATED_EXIT_CODE" == "1" && $returnCode -eq 7 ]]; then
+ echo "Exiting runner..."
+ exit "$returnCode"
else
echo "Exiting runner..."
exit 0
@@ -42,6 +45,9 @@ runWithManualTrap() {
returnCode=$?
if [[ $returnCode -eq 2 ]]; then
echo "Restarting runner..."
+ elif [[ "$ACTIONS_RUNNER_RETURN_VERSION_DEPRECATED_EXIT_CODE" == "1" && $returnCode -eq 7 ]]; then
+ echo "Exiting runner..."
+ exit "$returnCode"
else
echo "Exiting runner..."
# Unregister signal handling before exit
diff --git a/src/Runner.Common/Constants.cs b/src/Runner.Common/Constants.cs
index 583958981a9..3326e947d73 100644
--- a/src/Runner.Common/Constants.cs
+++ b/src/Runner.Common/Constants.cs
@@ -159,6 +159,7 @@ public static class ReturnCode
// and the runner should be restarted. This is a temporary code and will be removed in the future after
// the runner is migrated to runner admin.
public const int RunnerConfigurationRefreshed = 6;
+ public const int RunnerVersionDeprecated = 7;
}
public static class Features
@@ -194,8 +195,22 @@ public static class NodeMigration
public static readonly string RequireNode24Flag = "actions.runner.requirenode24";
public static readonly string WarnOnNode20Flag = "actions.runner.warnonnode20";
+ // Feature flags for Linux ARM32 deprecation
+ public static readonly string DeprecateLinuxArm32Flag = "actions_runner_deprecate_linux_arm32";
+ public static readonly string KillLinuxArm32Flag = "actions_runner_kill_linux_arm32";
+
// Blog post URL for Node 20 deprecation
public static readonly string Node20DeprecationUrl = "https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/";
+
+ // Node 20 migration dates (hardcoded fallbacks, can be overridden via job variables)
+ public static readonly string Node24DefaultDate = "June 2nd, 2026";
+ public static readonly string Node20RemovalDate = "September 16th, 2026";
+
+ // Variable keys for server-overridable dates
+ public static readonly string Node24DefaultDateVariable = "actions_runner_node24_default_date";
+ public static readonly string Node20RemovalDateVariable = "actions_runner_node20_removal_date";
+
+ public static readonly string LinuxArm32DeprecationMessage = "Linux ARM32 runners are deprecated and will no longer be supported after {0}. Please migrate to a supported platform.";
}
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
@@ -277,6 +292,7 @@ public static class Actions
public static readonly string AllowUnsupportedCommands = "ACTIONS_ALLOW_UNSECURE_COMMANDS";
public static readonly string AllowUnsupportedStopCommandTokens = "ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS";
public static readonly string RequireJobContainer = "ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER";
+ public static readonly string ReturnVersionDeprecatedExitCode = "ACTIONS_RUNNER_RETURN_VERSION_DEPRECATED_EXIT_CODE";
public static readonly string RunnerDebug = "ACTIONS_RUNNER_DEBUG";
public static readonly string StepDebug = "ACTIONS_STEP_DEBUG";
}
diff --git a/src/Runner.Common/Util/NodeUtil.cs b/src/Runner.Common/Util/NodeUtil.cs
index ff1a7a0af53..d87224f9820 100644
--- a/src/Runner.Common/Util/NodeUtil.cs
+++ b/src/Runner.Common/Util/NodeUtil.cs
@@ -58,7 +58,7 @@ public static (string nodeVersion, string warningMessage) DetermineActionsNodeVe
{
return (Constants.Runner.NodeMigration.Node24, null);
}
-
+
// Get environment variable details with source information
var forceNode24Details = GetEnvironmentVariableDetails(
Constants.Runner.NodeMigration.ForceNode24Variable, workflowEnvironment);
@@ -108,14 +108,50 @@ public static (string nodeVersion, string warningMessage) DetermineActionsNodeVe
///
/// Checks if Node24 is requested but running on ARM32 Linux, and determines if fallback is needed.
+ /// Also handles ARM32 deprecation and kill switch phases.
///
/// The preferred Node version
+ /// Feature flag indicating ARM32 Linux is deprecated
+ /// Feature flag indicating ARM32 Linux should no longer work
/// A tuple containing the adjusted node version and an optional warning message
- public static (string nodeVersion, string warningMessage) CheckNodeVersionForLinuxArm32(string preferredVersion)
+ public static (string nodeVersion, string warningMessage) CheckNodeVersionForLinuxArm32(
+ string preferredVersion,
+ bool deprecateArm32 = false,
+ bool killArm32 = false,
+ string node20RemovalDate = null)
{
- if (string.Equals(preferredVersion, Constants.Runner.NodeMigration.Node24, StringComparison.OrdinalIgnoreCase) &&
- Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.Arm) &&
- Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux))
+ bool isArm32Linux = Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.Arm) &&
+ Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux);
+
+ if (!isArm32Linux)
+ {
+ return (preferredVersion, null);
+ }
+
+ // ARM32 kill switch: runner should no longer work on this platform
+ if (killArm32)
+ {
+ return (null, "Linux ARM32 runners are no longer supported. Please migrate to a supported platform.");
+ }
+
+ // ARM32 deprecation warning: continue using node20 but warn about upcoming end of support
+ if (deprecateArm32)
+ {
+ string effectiveDate = string.IsNullOrEmpty(node20RemovalDate) ? Constants.Runner.NodeMigration.Node20RemovalDate : node20RemovalDate;
+ string deprecationWarning = string.Format(
+ Constants.Runner.NodeMigration.LinuxArm32DeprecationMessage,
+ effectiveDate);
+
+ if (string.Equals(preferredVersion, Constants.Runner.NodeMigration.Node24, StringComparison.OrdinalIgnoreCase))
+ {
+ return (Constants.Runner.NodeMigration.Node20, deprecationWarning);
+ }
+
+ return (preferredVersion, deprecationWarning);
+ }
+
+ // Legacy behavior: fall back to node20 if node24 was requested on ARM32
+ if (string.Equals(preferredVersion, Constants.Runner.NodeMigration.Node24, StringComparison.OrdinalIgnoreCase))
{
return (Constants.Runner.NodeMigration.Node20, "Node 24 is not supported on Linux ARM32 platforms. Falling back to Node 20.");
}
diff --git a/src/Runner.Listener/Program.cs b/src/Runner.Listener/Program.cs
index 80852d32c4d..d2923736402 100644
--- a/src/Runner.Listener/Program.cs
+++ b/src/Runner.Listener/Program.cs
@@ -141,9 +141,9 @@ private async static Task MainAsync(IHostContext context, string[] args)
}
catch (AccessDeniedException e) when (e.ErrorCode == 1)
{
- terminal.WriteError($"An error occured: {e.Message}");
+ terminal.WriteError($"An error occurred: {e.Message}");
trace.Error(e);
- return Constants.Runner.ReturnCode.TerminatedError;
+ return GetRunnerVersionDeprecatedExitCode();
}
catch (RunnerNotFoundException e)
{
@@ -159,6 +159,16 @@ private async static Task MainAsync(IHostContext context, string[] args)
}
}
+ private static int GetRunnerVersionDeprecatedExitCode()
+ {
+ if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Actions.ReturnVersionDeprecatedExitCode)))
+ {
+ return Constants.Runner.ReturnCode.RunnerVersionDeprecated;
+ }
+
+ return Constants.Runner.ReturnCode.TerminatedError;
+ }
+
private static void LoadAndSetEnv()
{
var binDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
diff --git a/src/Runner.Worker/ActionManager.cs b/src/Runner.Worker/ActionManager.cs
index 38c2ab8b320..6c066a150b4 100644
--- a/src/Runner.Worker/ActionManager.cs
+++ b/src/Runner.Worker/ActionManager.cs
@@ -115,6 +115,14 @@ public sealed class ActionManager : RunnerService, IActionManager
executionContext.Result = TaskResult.Failed;
throw;
}
+ catch (FailedToDownloadActionException ex)
+ {
+ // Log the error and fail the PrepareActionsAsync Initialization.
+ Trace.Error($"Caught exception from PrepareActionsAsync Initialization: {ex}");
+ executionContext.InfrastructureError(ex.InnerException?.Message ?? ex.Message, category: "error_download_action");
+ executionContext.Result = TaskResult.Failed;
+ throw;
+ }
catch (InvalidActionArchiveException ex)
{
// Log the error and fail the PrepareActionsAsync Initialization.
@@ -1157,93 +1165,102 @@ private async Task DownloadRepositoryArchive(IExecutionContext executionContext,
// Allow up to 20 * 60s for any action to be downloaded from github graph.
int timeoutSeconds = 20 * 60;
- while (retryCount < 3)
+ try
{
- string requestId = string.Empty;
- using (var actionDownloadTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)))
- using (var actionDownloadCancellation = CancellationTokenSource.CreateLinkedTokenSource(actionDownloadTimeout.Token, executionContext.CancellationToken))
+ while (retryCount < 3)
{
- try
+ string requestId = string.Empty;
+ using (var actionDownloadTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)))
+ using (var actionDownloadCancellation = CancellationTokenSource.CreateLinkedTokenSource(actionDownloadTimeout.Token, executionContext.CancellationToken))
{
- //open zip stream in async mode
- using (FileStream fs = new(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true))
- using (var httpClientHandler = HostContext.CreateHttpClientHandler())
- using (var httpClient = new HttpClient(httpClientHandler))
+ try
{
- httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadAuthToken);
-
- httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
- using (var response = await httpClient.GetAsync(downloadUrl))
+ //open zip stream in async mode
+ using (FileStream fs = new(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true))
+ using (var httpClientHandler = HostContext.CreateHttpClientHandler())
+ using (var httpClient = new HttpClient(httpClientHandler))
{
- requestId = UrlUtil.GetGitHubRequestId(response.Headers);
- if (!string.IsNullOrEmpty(requestId))
- {
- Trace.Info($"Request URL: {downloadUrl} X-GitHub-Request-Id: {requestId} Http Status: {response.StatusCode}");
- }
+ httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadAuthToken);
- if (response.IsSuccessStatusCode)
+ httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
+ using (var response = await httpClient.GetAsync(downloadUrl))
{
- using (var result = await response.Content.ReadAsStreamAsync())
+ requestId = UrlUtil.GetGitHubRequestId(response.Headers);
+ if (!string.IsNullOrEmpty(requestId))
{
- await result.CopyToAsync(fs, _defaultCopyBufferSize, actionDownloadCancellation.Token);
- await fs.FlushAsync(actionDownloadCancellation.Token);
+ Trace.Info($"Request URL: {downloadUrl} X-GitHub-Request-Id: {requestId} Http Status: {response.StatusCode}");
+ }
- // download succeed, break out the retry loop.
- break;
+ if (response.IsSuccessStatusCode)
+ {
+ using (var result = await response.Content.ReadAsStreamAsync())
+ {
+ await result.CopyToAsync(fs, _defaultCopyBufferSize, actionDownloadCancellation.Token);
+ await fs.FlushAsync(actionDownloadCancellation.Token);
+
+ // download succeed, break out the retry loop.
+ break;
+ }
+ }
+ else if (response.StatusCode == HttpStatusCode.NotFound)
+ {
+ // It doesn't make sense to retry in this case, so just stop
+ throw new ActionNotFoundException(new Uri(downloadUrl), requestId);
+ }
+ else
+ {
+ // Something else bad happened, let's go to our retry logic
+ response.EnsureSuccessStatusCode();
}
- }
- else if (response.StatusCode == HttpStatusCode.NotFound)
- {
- // It doesn't make sense to retry in this case, so just stop
- throw new ActionNotFoundException(new Uri(downloadUrl), requestId);
- }
- else
- {
- // Something else bad happened, let's go to our retry logic
- response.EnsureSuccessStatusCode();
}
}
}
- }
- catch (OperationCanceledException) when (executionContext.CancellationToken.IsCancellationRequested)
- {
- Trace.Info("Action download has been cancelled.");
- throw;
- }
- catch (OperationCanceledException ex) when (!executionContext.CancellationToken.IsCancellationRequested && retryCount >= 2)
- {
- Trace.Info($"Action download final retry timeout after {timeoutSeconds} seconds.");
- throw new TimeoutException($"Action '{downloadUrl}' download has timed out. Error: {ex.Message} {requestId}");
- }
- catch (ActionNotFoundException)
- {
- Trace.Info($"The action at '{downloadUrl}' does not exist");
- throw;
- }
- catch (Exception ex) when (retryCount < 2)
- {
- retryCount++;
- Trace.Error($"Fail to download archive '{downloadUrl}' -- Attempt: {retryCount}");
- Trace.Error(ex);
- if (actionDownloadTimeout.Token.IsCancellationRequested)
+ catch (OperationCanceledException) when (executionContext.CancellationToken.IsCancellationRequested)
{
- // action download didn't finish within timeout
- executionContext.Warning($"Action '{downloadUrl}' didn't finish download within {timeoutSeconds} seconds. {requestId}");
+ Trace.Info("Action download has been cancelled.");
+ throw;
}
- else
+ catch (OperationCanceledException ex) when (!executionContext.CancellationToken.IsCancellationRequested && retryCount >= 2)
+ {
+ Trace.Info($"Action download final retry timeout after {timeoutSeconds} seconds.");
+ throw new TimeoutException($"Action '{downloadUrl}' download has timed out. Error: {ex.Message} {requestId}");
+ }
+ catch (ActionNotFoundException)
{
- executionContext.Warning($"Failed to download action '{downloadUrl}'. Error: {ex.Message} {requestId}");
+ Trace.Info($"The action at '{downloadUrl}' does not exist");
+ throw;
+ }
+ catch (Exception ex) when (retryCount < 2)
+ {
+ retryCount++;
+ Trace.Error($"Fail to download archive '{downloadUrl}' -- Attempt: {retryCount}");
+ Trace.Error(ex);
+ if (actionDownloadTimeout.Token.IsCancellationRequested)
+ {
+ // action download didn't finish within timeout
+ executionContext.Warning($"Action '{downloadUrl}' didn't finish download within {timeoutSeconds} seconds. {requestId}");
+ }
+ else
+ {
+ executionContext.Warning($"Failed to download action '{downloadUrl}'. Error: {ex.Message} {requestId}");
+ }
}
}
- }
- if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_DOWNLOAD_NO_BACKOFF")))
- {
- var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30));
- executionContext.Warning($"Back off {backOff.TotalSeconds} seconds before retry.");
- await Task.Delay(backOff);
+ if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_DOWNLOAD_NO_BACKOFF")))
+ {
+ var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30));
+ executionContext.Warning($"Back off {backOff.TotalSeconds} seconds before retry.");
+ await Task.Delay(backOff);
+ }
}
}
+ catch (Exception ex) when (!(ex is OperationCanceledException) && !executionContext.CancellationToken.IsCancellationRequested)
+ {
+ Trace.Error($"Failed to download archive '{downloadUrl}' after {retryCount + 1} attempts.");
+ Trace.Error(ex);
+ throw new FailedToDownloadActionException($"Failed to download archive '{downloadUrl}' after {retryCount + 1} attempts.", ex);
+ }
ArgUtil.NotNullOrEmpty(archiveFile, nameof(archiveFile));
executionContext.Debug($"Download '{downloadUrl}' to '{archiveFile}'");
diff --git a/src/Runner.Worker/ActionManifestManager.cs b/src/Runner.Worker/ActionManifestManager.cs
index a70592381c7..014c053aa59 100644
--- a/src/Runner.Worker/ActionManifestManager.cs
+++ b/src/Runner.Worker/ActionManifestManager.cs
@@ -316,7 +316,6 @@ private TemplateContext CreateTemplateContext(
Schema = _actionManifestSchema,
// TODO: Switch to real tracewriter for cutover
TraceWriter = new GitHub.Actions.WorkflowParser.ObjectTemplating.EmptyTraceWriter(),
- AllowCaseFunction = false,
};
// Expression values from execution context
diff --git a/src/Runner.Worker/ActionManifestManagerLegacy.cs b/src/Runner.Worker/ActionManifestManagerLegacy.cs
index c332efd2eb7..d423aff8603 100644
--- a/src/Runner.Worker/ActionManifestManagerLegacy.cs
+++ b/src/Runner.Worker/ActionManifestManagerLegacy.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
@@ -315,7 +315,6 @@ private TemplateContext CreateTemplateContext(
maxBytes: 10 * 1024 * 1024),
Schema = _actionManifestSchema,
TraceWriter = executionContext.ToTemplateTraceWriter(),
- AllowCaseFunction = false,
};
// Expression values from execution context
diff --git a/src/Runner.Worker/Dap/DapDebugger.cs b/src/Runner.Worker/Dap/DapDebugger.cs
new file mode 100644
index 00000000000..9d0acca680d
--- /dev/null
+++ b/src/Runner.Worker/Dap/DapDebugger.cs
@@ -0,0 +1,1299 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using GitHub.DistributedTask.WebApi;
+using GitHub.Runner.Common;
+using GitHub.Runner.Sdk;
+using Newtonsoft.Json;
+
+namespace GitHub.Runner.Worker.Dap
+{
+ ///
+ /// Stores information about a completed step for stack trace display.
+ ///
+ internal sealed class CompletedStepInfo
+ {
+ public string DisplayName { get; set; }
+ public TaskResult? Result { get; set; }
+ public int FrameId { get; set; }
+ }
+
+ ///
+ /// Single public facade for the Debug Adapter Protocol subsystem.
+ /// Owns the full transport, handshake, step-level pauses, variable
+ /// inspection, reconnection, and cancellation flow.
+ ///
+ public sealed class DapDebugger : RunnerService, IDapDebugger
+ {
+ private const int _defaultPort = 4711;
+ private const int _defaultTimeoutMinutes = 15;
+ private const string _portEnvironmentVariable = "ACTIONS_RUNNER_DAP_PORT";
+ private const string _timeoutEnvironmentVariable = "ACTIONS_RUNNER_DAP_CONNECTION_TIMEOUT";
+ private const string _contentLengthHeader = "Content-Length: ";
+ private const int _maxMessageSize = 10 * 1024 * 1024; // 10 MB
+ private const int _maxHeaderLineLength = 8192; // 8 KB
+ private const int _connectionRetryDelayMilliseconds = 500;
+
+ // Thread ID for the single job execution thread
+ private const int _jobThreadId = 1;
+
+ // Frame ID for the current step (always 1)
+ private const int _currentFrameId = 1;
+
+ // Frame IDs for completed steps start at 1000
+ private const int _completedFrameIdBase = 1000;
+
+ private TcpListener _listener;
+ private TcpClient _client;
+ private NetworkStream _stream;
+ private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
+ private int _nextSeq = 1;
+ private Task _connectionLoopTask;
+ private volatile DapSessionState _state = DapSessionState.NotStarted;
+ private CancellationTokenRegistration? _cancellationRegistration;
+ private bool _isFirstStep = true;
+
+ // Synchronization for step execution
+ private TaskCompletionSource _commandTcs;
+ private readonly object _stateLock = new object();
+
+ // Session readiness — signaled when configurationDone is received
+ private TaskCompletionSource _readyTcs;
+
+ // Whether to pause before the next step (set by 'next' command)
+ private bool _pauseOnNextStep = true;
+
+ // Current execution context
+ private IStep _currentStep;
+ private IExecutionContext _jobContext;
+ private int _currentStepIndex;
+
+ // Track completed steps for stack trace
+ private readonly List _completedSteps = new List();
+ private int _nextCompletedFrameId = _completedFrameIdBase;
+
+ // Client connection tracking for reconnection support
+ private volatile bool _isClientConnected;
+
+ // Scope/variable inspection provider — reusable by future DAP features
+ private DapVariableProvider _variableProvider;
+
+ // REPL command executor for run() commands
+ private DapReplExecutor _replExecutor;
+
+ public bool IsActive =>
+ _state == DapSessionState.Ready ||
+ _state == DapSessionState.Paused ||
+ _state == DapSessionState.Running;
+
+ internal DapSessionState State => _state;
+
+ public override void Initialize(IHostContext hostContext)
+ {
+ base.Initialize(hostContext);
+ _variableProvider = new DapVariableProvider(hostContext.SecretMasker);
+ _replExecutor = new DapReplExecutor(hostContext, SendOutput);
+ Trace.Info("DapDebugger initialized");
+ }
+
+ public Task StartAsync(IExecutionContext jobContext)
+ {
+ ArgUtil.NotNull(jobContext, nameof(jobContext));
+ var port = ResolvePort();
+
+ Trace.Info($"Starting DAP debugger on port {port}");
+
+ _jobContext = jobContext;
+ _readyTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ _listener = new TcpListener(IPAddress.Loopback, port);
+ _listener.Start();
+ Trace.Info($"DAP debugger listening on {_listener.LocalEndpoint}");
+
+ _state = DapSessionState.WaitingForConnection;
+ _connectionLoopTask = ConnectionLoopAsync(jobContext.CancellationToken);
+
+ _cancellationRegistration = jobContext.CancellationToken.Register(() =>
+ {
+ Trace.Info("Job cancellation requested, unblocking pending waits.");
+ _readyTcs?.TrySetCanceled();
+ _commandTcs?.TrySetResult(DapCommand.Disconnect);
+ });
+
+ Trace.Info($"DAP debugger started on port {port}");
+ return Task.CompletedTask;
+ }
+
+ public async Task WaitUntilReadyAsync()
+ {
+ if (_state == DapSessionState.NotStarted || _listener == null || _jobContext == null)
+ {
+ return;
+ }
+
+ var timeoutMinutes = ResolveTimeout();
+ using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMinutes(timeoutMinutes));
+
+ try
+ {
+ Trace.Info($"Waiting for debugger client connection (timeout: {timeoutMinutes} minutes)...");
+ using (timeoutCts.Token.Register(() => _readyTcs?.TrySetCanceled()))
+ {
+ await _readyTcs.Task;
+ }
+
+ Trace.Info("DAP debugger ready.");
+ }
+ catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested && !_jobContext.CancellationToken.IsCancellationRequested)
+ {
+ throw new TimeoutException($"No debugger client connected within {timeoutMinutes} minutes.");
+ }
+ }
+
+ public async Task OnJobCompletedAsync()
+ {
+ if (_state != DapSessionState.NotStarted)
+ {
+ try
+ {
+ OnJobCompleted();
+ }
+ catch (Exception ex)
+ {
+ Trace.Warning($"DAP OnJobCompleted error: {ex.Message}");
+ }
+ }
+
+ await StopAsync();
+ }
+
+ public async Task StopAsync()
+ {
+ if (_cancellationRegistration.HasValue)
+ {
+ _cancellationRegistration.Value.Dispose();
+ _cancellationRegistration = null;
+ }
+
+ if (_state != DapSessionState.NotStarted)
+ {
+ try
+ {
+ Trace.Info("Stopping DAP debugger");
+
+ CleanupConnection();
+
+ try { _listener?.Stop(); }
+ catch { /* best effort */ }
+
+ if (_connectionLoopTask != null)
+ {
+ try
+ {
+ await Task.WhenAny(_connectionLoopTask, Task.Delay(5000));
+ }
+ catch { /* best effort */ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Trace.Error("Error stopping DAP debugger");
+ Trace.Error(ex);
+ }
+ }
+
+ lock (_stateLock)
+ {
+ if (_state != DapSessionState.NotStarted && _state != DapSessionState.Terminated)
+ {
+ _state = DapSessionState.Terminated;
+ }
+ }
+
+ _isClientConnected = false;
+ _listener = null;
+ _client = null;
+ _stream = null;
+ _readyTcs = null;
+ _connectionLoopTask = null;
+ }
+
+ public async Task OnStepStartingAsync(IStep step)
+ {
+ if (!IsActive)
+ {
+ return;
+ }
+
+ try
+ {
+ bool isFirst = _isFirstStep;
+ _isFirstStep = false;
+ await OnStepStartingAsync(step, isFirst);
+ }
+ catch (Exception ex)
+ {
+ Trace.Warning($"DAP OnStepStarting error: {ex.Message}");
+ }
+ }
+
+ public void OnStepCompleted(IStep step)
+ {
+ if (!IsActive)
+ {
+ return;
+ }
+
+ try
+ {
+ var result = step.ExecutionContext?.Result;
+ Trace.Info("Step completed");
+
+ // Add to completed steps list for stack trace
+ lock (_stateLock)
+ {
+ if (_state != DapSessionState.Ready &&
+ _state != DapSessionState.Paused &&
+ _state != DapSessionState.Running)
+ {
+ return;
+ }
+
+ _completedSteps.Add(new CompletedStepInfo
+ {
+ DisplayName = step.DisplayName,
+ Result = result,
+ FrameId = _nextCompletedFrameId++
+ });
+ }
+ }
+ catch (Exception ex)
+ {
+ Trace.Warning($"DAP OnStepCompleted error: {ex.Message}");
+ }
+ }
+
+ internal async Task HandleMessageAsync(string messageJson, CancellationToken cancellationToken)
+ {
+ Request request = null;
+ try
+ {
+ request = JsonConvert.DeserializeObject(messageJson);
+ if (request == null)
+ {
+ Trace.Warning("Failed to deserialize DAP request");
+ return;
+ }
+
+ if (!string.Equals(request.Type, "request", StringComparison.OrdinalIgnoreCase))
+ {
+ Trace.Warning("Received DAP message that was not a request");
+ return;
+ }
+
+ Trace.Info("Handling DAP request");
+
+ Response response;
+ if (request.Command == "evaluate")
+ {
+ response = await HandleEvaluateAsync(request, cancellationToken);
+ }
+ else
+ {
+ response = request.Command switch
+ {
+ "initialize" => HandleInitialize(request),
+ "attach" => HandleAttach(request),
+ "configurationDone" => HandleConfigurationDone(request),
+ "disconnect" => HandleDisconnect(request),
+ "threads" => HandleThreads(request),
+ "stackTrace" => HandleStackTrace(request),
+ "scopes" => HandleScopes(request),
+ "variables" => HandleVariables(request),
+ "continue" => HandleContinue(request),
+ "next" => HandleNext(request),
+ "setBreakpoints" => HandleSetBreakpoints(request),
+ "setExceptionBreakpoints" => HandleSetExceptionBreakpoints(request),
+ "completions" => HandleCompletions(request),
+ "stepIn" => CreateResponse(request, false, "Step In is not supported. Actions jobs debug at the step level - use 'next' to advance to the next step.", body: null),
+ "stepOut" => CreateResponse(request, false, "Step Out is not supported. Actions jobs debug at the step level - use 'continue' to resume.", body: null),
+ "stepBack" => CreateResponse(request, false, "Step Back is not yet supported.", body: null),
+ "reverseContinue" => CreateResponse(request, false, "Reverse Continue is not yet supported.", body: null),
+ "pause" => CreateResponse(request, false, "Pause is not supported. The debugger pauses automatically at step boundaries.", body: null),
+ _ => CreateResponse(request, false, $"Unsupported command: {request.Command}", body: null)
+ };
+ }
+
+ response.RequestSeq = request.Seq;
+ response.Command = request.Command;
+
+ SendResponse(response);
+
+ if (request.Command == "initialize")
+ {
+ SendEvent(new Event
+ {
+ EventType = "initialized"
+ });
+ Trace.Info("Sent initialized event");
+ }
+ }
+ catch (Exception ex)
+ {
+ Trace.Error($"Error handling DAP request ({ex.GetType().Name})");
+ if (request != null)
+ {
+ var maskedMessage = HostContext?.SecretMasker?.MaskSecrets(ex.Message) ?? ex.Message;
+ var errorResponse = CreateResponse(request, false, maskedMessage, body: null);
+ errorResponse.RequestSeq = request.Seq;
+ errorResponse.Command = request.Command;
+ SendResponse(errorResponse);
+ }
+ }
+ }
+
+ internal void HandleClientConnected()
+ {
+ _isClientConnected = true;
+ Trace.Info("Client connected to debug session");
+
+ // If we're paused, re-send the stopped event so the new client
+ // knows the current state (important for reconnection)
+ string description = null;
+ lock (_stateLock)
+ {
+ if (_state == DapSessionState.Paused && _currentStep != null)
+ {
+ description = $"Stopped before step: {_currentStep.DisplayName}";
+ }
+ }
+
+ if (description != null)
+ {
+ Trace.Info("Re-sending stopped event to reconnected client");
+ SendStoppedEvent("step", description);
+ }
+ }
+
+ internal void HandleClientDisconnected()
+ {
+ _isClientConnected = false;
+ Trace.Info("Client disconnected from debug session");
+
+ // Intentionally do NOT release the command TCS here.
+ // The session stays paused, waiting for a client to reconnect.
+ // The debugger's connection loop will accept a new client and
+ // call HandleClientConnected, which re-sends the stopped event.
+ }
+
+ private async Task ConnectionLoopAsync(CancellationToken cancellationToken)
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ Trace.Info("Waiting for debug client connection...");
+ _client = await _listener.AcceptTcpClientAsync();
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ break;
+ }
+
+ _stream = _client.GetStream();
+ var remoteEndPoint = _client.Client.RemoteEndPoint;
+ Trace.Info($"Debug client connected from {remoteEndPoint}");
+
+ HandleClientConnected();
+
+ // Enter message processing loop until client disconnects or cancellation is requested
+ await ProcessMessagesAsync(cancellationToken);
+
+ Trace.Info("Client disconnected, waiting for reconnection...");
+ HandleClientDisconnected();
+ CleanupConnection();
+ }
+ catch (Exception ex)
+ {
+ CleanupConnection();
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ break;
+ }
+
+ Trace.Error("Debugger connection error");
+ Trace.Error(ex);
+
+ try
+ {
+ await Task.Delay(_connectionRetryDelayMilliseconds, cancellationToken);
+ }
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ }
+ }
+
+ Trace.Info("Connection loop ended");
+ }
+
+ private void CleanupConnection()
+ {
+ _sendLock.Wait();
+ try
+ {
+ try { _stream?.Close(); } catch { /* best effort */ }
+ try { _client?.Close(); } catch { /* best effort */ }
+ _stream = null;
+ _client = null;
+ }
+ finally
+ {
+ _sendLock.Release();
+ }
+ }
+
+ private async Task ProcessMessagesAsync(CancellationToken cancellationToken)
+ {
+ Trace.Info("Starting DAP message processing loop");
+
+ try
+ {
+ while (!cancellationToken.IsCancellationRequested && _client?.Connected == true)
+ {
+ var json = await ReadMessageAsync(cancellationToken);
+ if (json == null)
+ {
+ Trace.Info("Client disconnected (end of stream)");
+ break;
+ }
+
+ await HandleMessageAsync(json, cancellationToken);
+ }
+ }
+ catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
+ {
+ Trace.Info("Message processing cancelled");
+ }
+ catch (IOException ex)
+ {
+ Trace.Info($"Connection closed ({ex.GetType().Name})");
+ }
+ catch (Exception ex)
+ {
+ Trace.Error($"Error in message loop ({ex.GetType().Name})");
+ }
+
+ Trace.Info("DAP message processing loop ended");
+ }
+
+ private async Task ReadMessageAsync(CancellationToken cancellationToken)
+ {
+ int contentLength = -1;
+
+ while (true)
+ {
+ var line = await ReadLineAsync(cancellationToken);
+ if (line == null)
+ {
+ return null;
+ }
+
+ if (line.Length == 0)
+ {
+ break;
+ }
+
+ if (line.StartsWith(_contentLengthHeader, StringComparison.OrdinalIgnoreCase))
+ {
+ var lengthStr = line.Substring(_contentLengthHeader.Length).Trim();
+ if (!int.TryParse(lengthStr, out contentLength))
+ {
+ throw new InvalidDataException($"Invalid Content-Length: {lengthStr}");
+ }
+ }
+ }
+
+ if (contentLength < 0)
+ {
+ throw new InvalidDataException("Missing Content-Length header");
+ }
+
+ if (contentLength > _maxMessageSize)
+ {
+ throw new InvalidDataException($"Message size {contentLength} exceeds maximum allowed size of {_maxMessageSize}");
+ }
+
+ var buffer = new byte[contentLength];
+ var totalRead = 0;
+ while (totalRead < contentLength)
+ {
+ var bytesRead = await _stream.ReadAsync(buffer, totalRead, contentLength - totalRead, cancellationToken);
+ if (bytesRead == 0)
+ {
+ throw new EndOfStreamException("Connection closed while reading message body");
+ }
+ totalRead += bytesRead;
+ }
+
+ var json = Encoding.UTF8.GetString(buffer);
+ Trace.Verbose("Received DAP message body");
+ return json;
+ }
+
+ private async Task ReadLineAsync(CancellationToken cancellationToken)
+ {
+ var lineBuilder = new StringBuilder();
+ var buffer = new byte[1];
+ var previousWasCr = false;
+
+ while (true)
+ {
+ var bytesRead = await _stream.ReadAsync(buffer, 0, 1, cancellationToken);
+ if (bytesRead == 0)
+ {
+ return lineBuilder.Length > 0 ? lineBuilder.ToString() : null;
+ }
+
+ var c = (char)buffer[0];
+
+ if (c == '\n' && previousWasCr)
+ {
+ if (lineBuilder.Length > 0 && lineBuilder[lineBuilder.Length - 1] == '\r')
+ {
+ lineBuilder.Length--;
+ }
+ return lineBuilder.ToString();
+ }
+
+ previousWasCr = c == '\r';
+ lineBuilder.Append(c);
+
+ if (lineBuilder.Length > _maxHeaderLineLength)
+ {
+ throw new InvalidDataException($"Header line exceeds maximum length of {_maxHeaderLineLength}");
+ }
+ }
+ }
+
+ ///
+ /// Serializes and writes a DAP message with Content-Length framing.
+ /// Must be called within the _sendLock.
+ ///
+ /// Secret masking is intentionally NOT applied here at the serialization
+ /// layer. Masking the raw JSON would corrupt protocol envelope fields
+ /// (type, event, command, seq) if a secret collides with those strings.
+ /// Instead, each DAP producer masks user-visible text at the point of
+ /// construction via the runner's SecretMasker. See DapVariableProvider,
+ /// DapReplExecutor, and DapDebugger for the call sites.
+ ///
+ private void SendMessageInternal(ProtocolMessage message)
+ {
+ var json = JsonConvert.SerializeObject(message, new JsonSerializerSettings
+ {
+ NullValueHandling = NullValueHandling.Ignore
+ });
+
+ var bodyBytes = Encoding.UTF8.GetBytes(json);
+ var header = $"Content-Length: {bodyBytes.Length}\r\n\r\n";
+ var headerBytes = Encoding.ASCII.GetBytes(header);
+
+ _stream.Write(headerBytes, 0, headerBytes.Length);
+ _stream.Write(bodyBytes, 0, bodyBytes.Length);
+ _stream.Flush();
+
+ Trace.Verbose("Sent DAP message");
+ }
+
+ private void SendMessage(ProtocolMessage message)
+ {
+ try
+ {
+ _sendLock.Wait();
+ try
+ {
+ if (_stream == null)
+ {
+ Trace.Warning("Cannot send message: no client connected");
+ return;
+ }
+
+ message.Seq = _nextSeq++;
+ SendMessageInternal(message);
+ }
+ finally
+ {
+ _sendLock.Release();
+ }
+
+ Trace.Info("Sent message");
+ }
+ catch (Exception ex)
+ {
+ Trace.Warning($"Failed to send message ({ex.GetType().Name})");
+ }
+ }
+
+ private void SendEvent(Event evt)
+ {
+ SendMessage(evt);
+ }
+
+ private void SendResponse(Response response)
+ {
+ SendMessage(response);
+ }
+
+ private void SendOutput(string category, string text)
+ {
+ SendEvent(new Event
+ {
+ EventType = "output",
+ Body = new OutputEventBody
+ {
+ Category = category,
+ Output = text
+ }
+ });
+ }
+
+ internal async Task OnStepStartingAsync(IStep step, bool isFirstStep)
+ {
+ bool pauseOnNextStep;
+ CancellationToken cancellationToken;
+ lock (_stateLock)
+ {
+ if (_state != DapSessionState.Ready &&
+ _state != DapSessionState.Paused &&
+ _state != DapSessionState.Running)
+ {
+ return;
+ }
+
+ _currentStep = step;
+ _currentStepIndex = _completedSteps.Count;
+ pauseOnNextStep = _pauseOnNextStep;
+ cancellationToken = _jobContext?.CancellationToken ?? CancellationToken.None;
+ }
+
+ // Reset variable references so stale nested refs from the
+ // previous step are not served to the client.
+ _variableProvider?.Reset();
+
+ // Determine if we should pause
+ bool shouldPause = isFirstStep || pauseOnNextStep;
+
+ if (!shouldPause)
+ {
+ Trace.Info("Step starting without debugger pause");
+ return;
+ }
+
+ var reason = isFirstStep ? "entry" : "step";
+ var description = isFirstStep
+ ? $"Stopped at job entry: {step.DisplayName}"
+ : $"Stopped before step: {step.DisplayName}";
+
+ Trace.Info("Step starting with debugger pause");
+
+ // Send stopped event to debugger (only if client is connected)
+ SendStoppedEvent(reason, description);
+
+ // Wait for debugger command
+ await WaitForCommandAsync(cancellationToken);
+ }
+
+ internal void OnJobCompleted()
+ {
+ Trace.Info("Job completed, sending terminated event");
+
+ int exitCode;
+ lock (_stateLock)
+ {
+ if (_state == DapSessionState.Terminated)
+ {
+ Trace.Info("Session already terminated, skipping OnJobCompleted events");
+ return;
+ }
+ _state = DapSessionState.Terminated;
+ exitCode = _jobContext?.Result == TaskResult.Succeeded ? 0 : 1;
+ }
+
+ SendEvent(new Event
+ {
+ EventType = "terminated",
+ Body = new TerminatedEventBody()
+ });
+
+ SendEvent(new Event
+ {
+ EventType = "exited",
+ Body = new ExitedEventBody
+ {
+ ExitCode = exitCode
+ }
+ });
+ }
+
+ private Response HandleInitialize(Request request)
+ {
+ if (request.Arguments != null)
+ {
+ try
+ {
+ request.Arguments.ToObject();
+ Trace.Info("Initialize arguments received");
+ }
+ catch (Exception ex)
+ {
+ Trace.Warning($"Failed to parse initialize arguments ({ex.GetType().Name})");
+ }
+ }
+
+ lock (_stateLock)
+ {
+ _state = DapSessionState.Initializing;
+ }
+
+ // Build capabilities — MVP only supports configurationDone
+ var capabilities = new Capabilities
+ {
+ SupportsConfigurationDoneRequest = true,
+ SupportsEvaluateForHovers = true,
+
+ // All other capabilities are false for MVP
+ SupportsFunctionBreakpoints = false,
+ SupportsConditionalBreakpoints = false,
+ SupportsStepBack = false,
+ SupportsSetVariable = false,
+ SupportsRestartFrame = false,
+ SupportsGotoTargetsRequest = false,
+ SupportsStepInTargetsRequest = false,
+ SupportsCompletionsRequest = true,
+ SupportsModulesRequest = false,
+ SupportsTerminateRequest = false,
+ SupportTerminateDebuggee = false,
+ SupportsDelayedStackTraceLoading = false,
+ SupportsLoadedSourcesRequest = false,
+ SupportsProgressReporting = false,
+ SupportsRunInTerminalRequest = false,
+ SupportsCancelRequest = false,
+ SupportsExceptionOptions = false,
+ SupportsValueFormattingOptions = false,
+ SupportsExceptionInfoRequest = false,
+ };
+
+ Trace.Info("Initialize request handled, capabilities sent");
+ return CreateResponse(request, true, body: capabilities);
+ }
+
+ private Response HandleAttach(Request request)
+ {
+ Trace.Info("Attach request handled");
+ return CreateResponse(request, true, body: null);
+ }
+
+ private Response HandleConfigurationDone(Request request)
+ {
+ lock (_stateLock)
+ {
+ _state = DapSessionState.Ready;
+ }
+
+ _readyTcs.TrySetResult(true);
+
+ Trace.Info("Configuration done, debug session is ready");
+ return CreateResponse(request, true, body: null);
+ }
+
+ private Response HandleDisconnect(Request request)
+ {
+ Trace.Info("Disconnect request received");
+
+ lock (_stateLock)
+ {
+ _state = DapSessionState.Terminated;
+
+ // Release any blocked step execution
+ _commandTcs?.TrySetResult(DapCommand.Disconnect);
+ }
+
+ return CreateResponse(request, true, body: null);
+ }
+
+ private Response HandleThreads(Request request)
+ {
+ IExecutionContext jobContext;
+ lock (_stateLock)
+ {
+ jobContext = _jobContext;
+ }
+
+ var threadName = jobContext != null
+ ? MaskUserVisibleText($"Job: {jobContext.GetGitHubContext("job") ?? "workflow job"}")
+ : "Job Thread";
+
+ var body = new ThreadsResponseBody
+ {
+ Threads = new List
+ {
+ new Thread
+ {
+ Id = _jobThreadId,
+ Name = threadName
+ }
+ }
+ };
+
+ return CreateResponse(request, true, body: body);
+ }
+
+ private Response HandleStackTrace(Request request)
+ {
+ IStep currentStep;
+ int currentStepIndex;
+ CompletedStepInfo[] completedSteps;
+ lock (_stateLock)
+ {
+ currentStep = _currentStep;
+ currentStepIndex = _currentStepIndex;
+ completedSteps = _completedSteps.ToArray();
+ }
+
+ var frames = new List();
+
+ // Add current step as the top frame
+ if (currentStep != null)
+ {
+ var resultIndicator = currentStep.ExecutionContext?.Result != null
+ ? $" [{currentStep.ExecutionContext.Result}]"
+ : " [running]";
+
+ frames.Add(new StackFrame
+ {
+ Id = _currentFrameId,
+ Name = MaskUserVisibleText($"{currentStep.DisplayName ?? "Current Step"}{resultIndicator}"),
+ Line = currentStepIndex + 1,
+ Column = 1,
+ PresentationHint = "normal"
+ });
+ }
+ else
+ {
+ frames.Add(new StackFrame
+ {
+ Id = _currentFrameId,
+ Name = "(no step executing)",
+ Line = 0,
+ Column = 1,
+ PresentationHint = "subtle"
+ });
+ }
+
+ // Add completed steps as additional frames (most recent first)
+ for (int i = completedSteps.Length - 1; i >= 0; i--)
+ {
+ var completedStep = completedSteps[i];
+ var resultStr = completedStep.Result.HasValue ? $" [{completedStep.Result}]" : "";
+ frames.Add(new StackFrame
+ {
+ Id = completedStep.FrameId,
+ Name = MaskUserVisibleText($"{completedStep.DisplayName}{resultStr}"),
+ Line = 1,
+ Column = 1,
+ PresentationHint = "subtle"
+ });
+ }
+
+ var body = new StackTraceResponseBody
+ {
+ StackFrames = frames,
+ TotalFrames = frames.Count
+ };
+
+ return CreateResponse(request, true, body: body);
+ }
+
+ private Response HandleScopes(Request request)
+ {
+ var args = request.Arguments?.ToObject();
+ var frameId = args?.FrameId ?? _currentFrameId;
+
+ var context = GetExecutionContextForFrame(frameId);
+ if (context == null)
+ {
+ return CreateResponse(request, true, body: new ScopesResponseBody
+ {
+ Scopes = new List()
+ });
+ }
+
+ var scopes = _variableProvider.GetScopes(context);
+ return CreateResponse(request, true, body: new ScopesResponseBody
+ {
+ Scopes = scopes
+ });
+ }
+
+ private Response HandleVariables(Request request)
+ {
+ var args = request.Arguments?.ToObject();
+ var variablesRef = args?.VariablesReference ?? 0;
+
+ var context = GetCurrentExecutionContext();
+ if (context == null)
+ {
+ return CreateResponse(request, true, body: new VariablesResponseBody
+ {
+ Variables = new List()
+ });
+ }
+
+ var variables = _variableProvider.GetVariables(context, variablesRef);
+ return CreateResponse(request, true, body: new VariablesResponseBody
+ {
+ Variables = variables
+ });
+ }
+
+ private async Task HandleEvaluateAsync(Request request, CancellationToken cancellationToken)
+ {
+ var args = request.Arguments?.ToObject();
+ var expression = args?.Expression ?? string.Empty;
+ var frameId = args?.FrameId ?? _currentFrameId;
+ var evalContext = args?.Context ?? "hover";
+
+ Trace.Info("Evaluate request received");
+
+ // REPL context -> route through the DSL dispatcher
+ if (string.Equals(evalContext, "repl", StringComparison.OrdinalIgnoreCase))
+ {
+ var result = await HandleReplInputAsync(expression, frameId, cancellationToken);
+ return CreateResponse(request, true, body: result);
+ }
+
+ // Watch/hover/variables/clipboard -> expression evaluation only
+ var context = GetExecutionContextForFrame(frameId);
+ var evalResult = _variableProvider.EvaluateExpression(expression, context);
+ return CreateResponse(request, true, body: evalResult);
+ }
+
+ ///
+ /// Routes REPL input through the DSL parser. If the input matches a
+ /// known command it is dispatched; otherwise it falls through to
+ /// expression evaluation.
+ ///
+ private async Task HandleReplInputAsync(
+ string input,
+ int frameId,
+ CancellationToken cancellationToken)
+ {
+ // Try to parse as a DSL command
+ var command = DapReplParser.TryParse(input, out var parseError);
+
+ if (parseError != null)
+ {
+ return new EvaluateResponseBody
+ {
+ Result = parseError,
+ Type = "error",
+ VariablesReference = 0
+ };
+ }
+
+ if (command != null)
+ {
+ return await DispatchReplCommandAsync(command, frameId, cancellationToken);
+ }
+
+ // Not a DSL command -> evaluate as a GitHub Actions expression
+ // (this lets the REPL console also work for ad-hoc expression queries)
+ var context = GetExecutionContextForFrame(frameId);
+ return _variableProvider.EvaluateExpression(input, context);
+ }
+
+ private async Task DispatchReplCommandAsync(
+ DapReplCommand command,
+ int frameId,
+ CancellationToken cancellationToken)
+ {
+ switch (command)
+ {
+ case HelpCommand help:
+ var helpText = string.IsNullOrEmpty(help.Topic)
+ ? DapReplParser.GetGeneralHelp()
+ : help.Topic.Equals("run", StringComparison.OrdinalIgnoreCase)
+ ? DapReplParser.GetRunHelp()
+ : $"Unknown help topic: {help.Topic}. Try: help or help(\"run\")";
+ return new EvaluateResponseBody
+ {
+ Result = helpText,
+ Type = "string",
+ VariablesReference = 0
+ };
+
+ case RunCommand run:
+ var context = GetExecutionContextForFrame(frameId);
+ return await _replExecutor.ExecuteRunCommandAsync(run, context, cancellationToken);
+
+ default:
+ return new EvaluateResponseBody
+ {
+ Result = $"Unknown command type: {command.GetType().Name}",
+ Type = "error",
+ VariablesReference = 0
+ };
+ }
+ }
+
+ private Response HandleCompletions(Request request)
+ {
+ var args = request.Arguments?.ToObject();
+ var text = args?.Text ?? string.Empty;
+
+ var items = new List();
+
+ // Offer DSL commands when the user is starting to type
+ if (string.IsNullOrEmpty(text) || "help".StartsWith(text, StringComparison.OrdinalIgnoreCase))
+ {
+ items.Add(new CompletionItem
+ {
+ Label = "help",
+ Text = "help",
+ Detail = "Show available debug console commands",
+ Type = "function"
+ });
+ }
+ if (string.IsNullOrEmpty(text) || "help(\"run\")".StartsWith(text, StringComparison.OrdinalIgnoreCase))
+ {
+ items.Add(new CompletionItem
+ {
+ Label = "help(\"run\")",
+ Text = "help(\"run\")",
+ Detail = "Show help for the run command",
+ Type = "function"
+ });
+ }
+ if (string.IsNullOrEmpty(text) || "run(".StartsWith(text, StringComparison.OrdinalIgnoreCase)
+ || text.StartsWith("run(", StringComparison.OrdinalIgnoreCase))
+ {
+ items.Add(new CompletionItem
+ {
+ Label = "run(\"...\")",
+ Text = "run(\"",
+ Detail = "Execute a script (like a workflow run step)",
+ Type = "function"
+ });
+ }
+
+ return CreateResponse(request, true, body: new CompletionsResponseBody
+ {
+ Targets = items
+ });
+ }
+
+ private Response HandleContinue(Request request)
+ {
+ Trace.Info("Continue command received");
+
+ lock (_stateLock)
+ {
+ if (_state == DapSessionState.Paused)
+ {
+ _state = DapSessionState.Running;
+ _pauseOnNextStep = false;
+ _commandTcs?.TrySetResult(DapCommand.Continue);
+ }
+ }
+
+ return CreateResponse(request, true, body: new ContinueResponseBody
+ {
+ AllThreadsContinued = true
+ });
+ }
+
+ private Response HandleNext(Request request)
+ {
+ Trace.Info("Next (step over) command received");
+
+ lock (_stateLock)
+ {
+ if (_state == DapSessionState.Paused)
+ {
+ _state = DapSessionState.Running;
+ _pauseOnNextStep = true;
+ _commandTcs?.TrySetResult(DapCommand.Next);
+ }
+ }
+
+ return CreateResponse(request, true, body: null);
+ }
+
+ private Response HandleSetBreakpoints(Request request)
+ {
+ // MVP: acknowledge but don't process breakpoints
+ // All steps pause automatically via _pauseOnNextStep
+ return CreateResponse(request, true, body: new { breakpoints = Array.Empty