From 1acbd4c02f0ca4d959e23850ad70c50828476634 Mon Sep 17 00:00:00 2001 From: Gustavo Perdomo Date: Thu, 13 Aug 2026 16:16:13 -0400 Subject: [PATCH] feat: add support for mise.toml file (#1421) * feat: add support for mise.toml file feat: add support for mise.toml file feat: update licensed cache feat: switch from js-toml to smol-toml to reduce deps and bundle size * feat: added docs and improved tests * feat: added docs and improved tests * ci: fix ci * ci: fix ci --- .github/workflows/versions.yml | 2 +- .licenses/npm/brace-expansion.dep.yml | Bin 1390 -> 1390 bytes .licenses/npm/smol-toml.dep.yml | Bin 0 -> 1770 bytes .licenses/npm/undici.dep.yml | Bin 1396 -> 1396 bytes README.md | 2 +- __tests__/data/mise.toml | 2 + __tests__/main.test.ts | 20 +- action.yml | 2 +- dist/cache-save/index.js | 1404 ++++++++++++++++++++++++- dist/setup/index.js | 1400 +++++++++++++++++++++++- docs/advanced-usage.md | 2 +- package-lock.json | 33 +- package.json | 5 +- src/util.ts | 21 + 14 files changed, 2827 insertions(+), 66 deletions(-) create mode 100644 .licenses/npm/smol-toml.dep.yml create mode 100644 __tests__/data/mise.toml diff --git a/.github/workflows/versions.yml b/.github/workflows/versions.yml index 6aa86df1..e9aabd78 100644 --- a/.github/workflows/versions.yml +++ b/.github/workflows/versions.yml @@ -158,7 +158,7 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel] node-version-file: - [.nvmrc, .tool-versions, .tool-versions-node, package.json] + [.nvmrc, .tool-versions, .tool-versions-node, package.json, mise.toml] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup node from node version file diff --git a/.licenses/npm/brace-expansion.dep.yml b/.licenses/npm/brace-expansion.dep.yml index f40e8707d1931cc03a0c8472d321cb3c5b20322f..fd7e18fe1b3f615654ea811a0ccc3898e986e444 100644 GIT binary patch delta 12 TcmaFI^^R+TI-})A4L4Q*AISs; delta 12 TcmaFI^^R+TI-|u#4L4Q*AH)O& diff --git a/.licenses/npm/smol-toml.dep.yml b/.licenses/npm/smol-toml.dep.yml new file mode 100644 index 0000000000000000000000000000000000000000..e12de58d4bd136c6260eb7404669d3be9a4f4839 GIT binary patch literal 1770 zcmb7F!EWO=6uj#z9`>{WWG~V^6mUs(s7fZ zMT;H`)8c#c=FN=l*=!b#^`IAI2h-1-8T#l+Pqs7Tf^O$O&Tk|4a>Dg^8X|ihhI)Eg z5JPp{_c0x6>td?MmKrlnx^bk6ZAPbhvU>W0S6BDlcRfW%Gw4(Os25S+HF~r_e6a28 z>#XVPv(?w9b`j0Un)B3XyP!-YJeNEoa{70-pg(XTO3dkH>K>1dJ~uZce>-=W+*5L_ z9cf2(KaYv^J%t)elLn0^-Oi)XUFx>8Zt5P+4m$?wm`~mKIyO9c=tk^G2Qv*ersvKb z$xPwhoLzt!Oxqp0ruHBZVA6Eb)6hAmTRKhVscUg?o{~Fi0QJ3j?#4&RG`5{@vY~0v zZgJstJExD-ESbZVQ)609!`V7)<7#mAp!I`!(g3`wF&YPHW88H%aD-MpcJ(;#$HJ_S zoFE8X=mtH_L*Ea7r-G61G`Uj2qV2iS|HkG@#FP5JY`iI8dWC3Ba~|~Qg67_)FYv^m zV4|URA!_zb`(T>i^S#}d{l3mA*YFgLzQD)dYvj*wY93ABqF}^~Yf*5dbJnYQNsyWT zQhKOg=s^>*1bGeVvBf2DCr@cG4gm>jp&hs#0!as4035kyUyAic(Wb~!UP@wl zil@0M#j;Wb9(-aFouB+gP@Ls^;@@^9my(K-#CDen0AfPPawWKoN#sdZr6OO)1Q5y# zg|F;ZD3qxp4ufCY0^(aM6)W2EGT8u$Ek!2OJ`h?7mHT-s3}m!pr4mV%v66OGxho_m ztQVkCA(M=WEl=lQfyu=0c&%)5K-icmu0n6qR4OH`#mJOz$Far zX;8e#@fI>tlzR{CH3^2qbid)q4y?~F$}umDL}C)}^R+Ax#9V|(z1x>^zRtv&=Lz?$ z3XgOzB)@@&0u0w4){nktn2YpawofXUT@d}pcd3TvaG1o3Sb7JX^xA-2Nb#DXAYrnB SFkb$7booidKgln@Mt=fy{~o>o literal 0 HcmV?d00001 diff --git a/.licenses/npm/undici.dep.yml b/.licenses/npm/undici.dep.yml index c46a5c7bff13518993f1b33ce194be381f516fde..b339a44877b8be19c54dc517b80ea7f673b4ff21 100644 GIT binary patch delta 12 Tcmeyu^@VGKEThFnxiD4$AOr*r delta 12 Tcmeyu^@VGKETj2GxiD4$AO8dl diff --git a/README.md b/README.md index e4158422..bd0edb87 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ See [action.yml](action.yml) # Examples: 12.x, 10.15.1, >=10.15.0, lts/Hydrogen, 16-nightly, latest, node node-version: '' - # File containing the version Spec of the version to use. Examples: package.json, .nvmrc, .node-version, .tool-versions. + # File containing the version Spec of the version to use. Examples: package.json, mise.toml, .nvmrc, .node-version, .tool-versions. # If node-version and node-version-file are both provided the action will use version from node-version. node-version-file: '' diff --git a/__tests__/data/mise.toml b/__tests__/data/mise.toml new file mode 100644 index 00000000..5a012336 --- /dev/null +++ b/__tests__/data/mise.toml @@ -0,0 +1,2 @@ +[tools] +node = "24.0.0" diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index 382d8b19..99cc0c86 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -1,16 +1,15 @@ import { - jest, - describe, - it, - expect, - beforeEach, + afterAll, afterEach, - afterAll + beforeEach, + describe, + expect, + it, + jest } from '@jest/globals'; -import {fileURLToPath} from 'url'; import fs from 'fs'; import path from 'path'; -import osm from 'os'; +import {fileURLToPath} from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -197,6 +196,11 @@ describe('main tests', () => { ${'{"engines": {"node": "17.0.0"}}'} | ${'17.0.0'} ${'{"devEngines": {"runtime": {"name": "node", "version": "22.0.0"}}}'} | ${'22.0.0'} ${'{"devEngines": {"runtime": [{"name": "bun"}, {"name": "node", "version": "22.0.0"}]}}'} | ${'22.0.0'} + ${'[tools]\ngo="latest"\nnode = "24.10"'} | ${'24.10'} + ${'[tools]\nnode = "22.12"'} | ${'22.12'} + ${'[tools]\ngo="latest"\nnode = "24.10"'} | ${'24.10'} + ${'[tools]\nnode = { version = "22.20" }'} | ${'22.20'} + ${'[tools]\nnode = { postinstall = "corepack enable" }'} | ${null} `.it('parses "$contents"', ({contents, expected}: any) => { const existsSpy = jest.spyOn(fs, 'existsSync'); existsSpy.mockImplementation(() => true); diff --git a/action.yml b/action.yml index dc1cefd5..31057efa 100644 --- a/action.yml +++ b/action.yml @@ -5,7 +5,7 @@ inputs: node-version: description: 'Version Spec of the version to use. Examples: 12.x, 10.15.1, >=10.15.0.' node-version-file: - description: 'File containing the version Spec of the version to use. Examples: package.json, .nvmrc, .node-version, .tool-versions.' + description: 'File containing the version Spec of the version to use. Examples: package.json, .nvmrc, .node-version, .tool-versions, mise.toml.' architecture: description: 'Target architecture for Node to use. Examples: x86, x64. Will use system architecture by default.' check-latest: diff --git a/dist/cache-save/index.js b/dist/cache-save/index.js index 501e837f..df712a2c 100644 --- a/dist/cache-save/index.js +++ b/dist/cache-save/index.js @@ -13899,7 +13899,13 @@ function processHeader (request, key, val) { } else if (typeof val[i] === 'object') { throw new InvalidArgumentError(`invalid ${key} header`) } else { - arr.push(`${val[i]}`) + // Coerce primitives (and reject unsafe coercions such as functions + // with a crafted toString/Symbol.toPrimitive). + const str = `${val[i]}` + if (!isValidHeaderValue(str)) { + throw new InvalidArgumentError(`invalid ${key} header`) + } + arr.push(str) } } val = arr @@ -13910,7 +13916,12 @@ function processHeader (request, key, val) { } else if (val === null) { val = '' } else { + // Coerce primitives (and reject unsafe coercions such as functions + // with a crafted toString/Symbol.toPrimitive). val = `${val}` + if (!isValidHeaderValue(val)) { + throw new InvalidArgumentError(`invalid ${key} header`) + } } if (headerName === 'host') { @@ -15282,6 +15293,7 @@ const { RequestContentLengthMismatchError, ResponseContentLengthMismatchError, RequestAbortedError, + InvalidArgumentError, HeadersTimeoutError, HeadersOverflowError, SocketError, @@ -16265,8 +16277,16 @@ function writeH1 (client, request) { } body = bodyStream.stream contentLength = bodyStream.length - } else if (util.isBlobLike(body) && request.contentType == null && body.type) { - headers.push('content-type', body.type) + } else if (util.isBlobLike(body) && request.contentType == null) { + const contentType = body.type + if (contentType) { + const contentTypeValue = `${contentType}` + if (!util.isValidHeaderValue(contentTypeValue)) { + util.errorRequest(client, request, new InvalidArgumentError('invalid content-type header')) + return false + } + headers.push('content-type', contentTypeValue) + } } if (body && typeof body.read === 'function') { @@ -19739,6 +19759,28 @@ function calculateRetryAfterHeader (retryAfter) { return new Date(retryAfter).getTime() - current } +function validatePartialResponseContentLength (headers, range, statusCode, retryCount) { + const contentLength = headers['content-length'] + if (contentLength == null) { + return null + } + + if (!Number.isFinite(range.start) || !Number.isFinite(range.end)) { + return null + } + + const length = Number(contentLength) + const expectedLength = range.end - range.start + 1 + if (!Number.isFinite(length) || length !== expectedLength) { + return new RequestRetryError('Content-Length mismatch', statusCode, { + headers, + data: { count: retryCount } + }) + } + + return null +} + class RetryHandler { constructor (opts, handlers) { const { retryOptions, ...dispatchOpts } = opts @@ -19953,6 +19995,12 @@ class RetryHandler { return false } + const contentLengthError = validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount) + if (contentLengthError != null) { + this.abort(contentLengthError) + return false + } + const { start, size, end = size - 1 } = contentRange assert(this.start === start, 'content-range mismatch') @@ -19976,6 +20024,12 @@ class RetryHandler { ) } + const contentLengthError = validatePartialResponseContentLength(headers, range, statusCode, this.retryCount) + if (contentLengthError != null) { + this.abort(contentLengthError) + return false + } + const { start, size, end = size - 1 } = range assert( start != null && Number.isFinite(start), @@ -24220,7 +24274,7 @@ function validateCookiePath (path) { if ( code < 0x20 || // exclude CTLs (0-31) - code === 0x7F || // DEL + code > 0x7E || // exclude DEL and non-ascii code === 0x3B // ; ) { throw new Error('Invalid cookie path') @@ -24229,16 +24283,80 @@ function validateCookiePath (path) { } /** - * I have no idea why these values aren't allowed to be honest, - * but Deno tests these. - Khafra + * ::= | + * + * ::= any one of the 52 alphabetic characters A through Z in + * upper case and a through z in lower case + * + * ::= any one of the ten digits 0 through 9r + * + * @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5 + * @param {number} code + */ +function isLetterOrDigit (code) { + return ( + (code >= 0x30 && code <= 0x39) || // 0-9 + (code >= 0x41 && code <= 0x5A) || // A-Z + (code >= 0x61 && code <= 0x7A) // a-z + ) +} + +/** + * Validates a cookie domain against the "preferred name syntax". + * + * ::= | " " + * ::=