Compare commits

..
2 Commits
Author SHA1 Message Date
Bruno BorgesandCopilot App 8cd57eb681 Update Undici to 6.29.1 (#1308)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-30 12:52:17 -04:00
Bruno BorgesandCopilot App 816722acf8 Fix Liberica SDKMAN patch release resolution (#1291)
* Fix Liberica SDKMAN patch release resolution

Preserve BellSoft's fourth version component as SDKMAN patch metadata and cover real API version formats for Java 17 and 25.

Fixes #1290

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d5b53868-538f-4c5f-b2aa-c66dc4ad06e2

* Fix Liberica patch ordering and build-less requests

Share overridable matching and ordering with toolcache selection. Keep patch-only requests distinct from exact SDKMAN build requests and cover the reported API pair across remote and cached resolution.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d5b53868-538f-4c5f-b2aa-c66dc4ad06e2

---------

Copilot-Session: d5b53868-538f-4c5f-b2aa-c66dc4ad06e2
2026-10-19 10:48:21 -04:00
10 changed files with 611 additions and 87 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
---
name: undici
version: 6.29.0
version: 6.29.1
type: npm
summary: An HTTP/1.1 client, written from scratch for Node.js
homepage: https://undici.nodejs.org
+232 -14
View File
@@ -15,6 +15,7 @@ import type {
} from '../../src/distributions/liberica/models.js';
import {HttpClient} from '@actions/http-client';
import os from 'os';
import path from 'path';
import manifestData from '../data/liberica.json' with {type: 'json'};
@@ -253,35 +254,68 @@ describe('findPackageForDownload', () => {
expect(result.version).toBe(expected);
});
describe('compound build versions', () => {
describe.each([
[17, 20],
[25, 4]
])('patch releases for Java %s.0.%s', (featureVersion, updateVersion) => {
const mainVersion = `${featureVersion}.0.${updateVersion}`;
beforeEach(() => {
distribution['getAvailableVersions'] = async () =>
['1', '1.1', '1.2', '1.10'].map(build => ({
featureVersion: 25,
['+1', '+10', '.1+1', '.1+2', '.1+10', '.2+1', '.10+1'].map(suffix => ({
featureVersion,
interimVersion: 0,
updateVersion: 4,
buildVersion: 1,
version: `25.0.4+${build}`,
downloadUrl: `https://download.bell-sw.com/java/25.0.4+${build}/bellsoft-jdk25.0.4+${build}-macos-aarch64.tar.gz`
updateVersion,
buildVersion: Number(suffix.split('+')[1]),
version: `${mainVersion}${suffix}`,
downloadUrl: `https://download.bell-sw.com/java/${mainVersion}${suffix}/bellsoft-jdk${mainVersion}${suffix}-macos-aarch64.tar.gz`
}));
});
it.each([
['25.0.4+1.1', '25.0.4+1.1'],
['25.0.4+1', '25.0.4+1'],
['25.0.4', '25.0.4+1.10'],
['25', '25.0.4+1.10']
])('version is %s -> %s', async (input, expected) => {
[`${mainVersion}+1.1`, `${mainVersion}+1.1`, '.1+1'],
[`${mainVersion}+1.2`, `${mainVersion}+1.2`, '.1+2'],
[`${mainVersion}+1.10`, `${mainVersion}+1.10`, '.1+10'],
[`${mainVersion}+1`, `${mainVersion}+1`, '+1'],
[mainVersion, `${mainVersion}+10.1`, '.10+1'],
[`${featureVersion}`, `${mainVersion}+10.1`, '.10+1']
])('version is %s -> %s', async (input, expected, suffix) => {
const result = await distribution['findPackageForDownload'](input);
expect(result).toEqual({
version: expected,
url: `https://download.bell-sw.com/java/${expected}/bellsoft-jdk${expected}-macos-aarch64.tar.gz`
url: `https://download.bell-sw.com/java/${mainVersion}${suffix}/bellsoft-jdk${mainVersion}${suffix}-macos-aarch64.tar.gz`
});
});
it('sorts builds within the same patch numerically', async () => {
const versions = await distribution['getAvailableVersions']();
distribution['getAvailableVersions'] = async () =>
versions.filter(item => item.version.startsWith(`${mainVersion}.1+`));
const result = await distribution['findPackageForDownload'](mainVersion);
expect(result.version).toBe(`${mainVersion}+1.10`);
});
it('selects the newest build for a four-component patch-only request', async () => {
const patchDistribution = new LibericaDistributions({
version: `${mainVersion}.1`,
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
patchDistribution['getAvailableVersions'] =
distribution['getAvailableVersions'];
await expect(
patchDistribution['findPackageForDownload'](
patchDistribution['version']
)
).resolves.toMatchObject({version: `${mainVersion}+1.10`});
});
it('does not substitute a different compound build', async () => {
await expect(
distribution['findPackageForDownload']('25.0.4+1.3')
distribution['findPackageForDownload'](`${mainVersion}+1.3`)
).rejects.toThrow(/No matching version found for SemVer/);
});
});
@@ -293,6 +327,160 @@ describe('findPackageForDownload', () => {
});
});
describe('patch request resolution from the constructor', () => {
const releases: LibericaVersion[] = ['17.0.20+10', '17.0.20.1+1'].map(
version => ({
featureVersion: 17,
interimVersion: 0,
updateVersion: 20,
buildVersion: Number(version.split('+')[1]),
version,
downloadUrl: `https://download.bell-sw.com/java/${version}/jdk.tar.gz`
})
);
let cacheRoot: string;
let previousToolcache: string | undefined;
beforeEach(async () => {
previousToolcache = process.env['RUNNER_TOOL_CACHE'];
cacheRoot = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'liberica-'));
process.env['RUNNER_TOOL_CACHE'] = cacheRoot;
for (const version of ['17.0.20-10', '17.0.20-1.1']) {
const javaPath = path.join(
cacheRoot,
'Java_Liberica_jdk',
version,
'x64'
);
await fs.promises.mkdir(javaPath, {recursive: true});
await fs.promises.writeFile(`${javaPath}.complete`, '');
}
});
afterEach(async () => {
if (previousToolcache === undefined) {
delete process.env['RUNNER_TOOL_CACHE'];
} else {
process.env['RUNNER_TOOL_CACHE'] = previousToolcache;
}
await fs.promises.rm(cacheRoot, {recursive: true, force: true});
});
it.each([
['17', '17.0.20+1.1', '17.0.20.1+1'],
['17.0.20', '17.0.20+1.1', '17.0.20.1+1'],
['17.0.20+10', '17.0.20+10', '17.0.20+10'],
['17.0.20+1.1', '17.0.20+1.1', '17.0.20.1+1'],
['17.0.20.1', '17.0.20+1.1', '17.0.20.1+1'],
['17.0.20.1+1', '17.0.20+1.1', '17.0.20.1+1'],
['17.0.20.0', '17.0.20+10', '17.0.20+10']
])(
'resolves %s consistently remotely and in cache',
async (input, expected, apiVersion) => {
const distribution = new LibericaDistributions({
version: input,
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
distribution['getAvailableVersions'] = async () => releases;
await expect(
distribution['findPackageForDownload'](distribution['version'])
).resolves.toEqual({
version: expected,
url: `https://download.bell-sw.com/java/${apiVersion}/jdk.tar.gz`
});
expect(distribution['findInToolcache']()).toEqual({
version: expected,
path: path.join(
cacheRoot,
'Java_Liberica_jdk',
expected.replace('+', '-'),
'x64'
)
});
}
);
it.each(['17.0.20.2', '17.0.20+1.2'])(
'does not substitute a different patch or exact build for %s',
async version => {
const distribution = new LibericaDistributions({
version,
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
distribution['getAvailableVersions'] = async () => releases;
await expect(
distribution['findPackageForDownload'](distribution['version'])
).rejects.toThrow(/No matching version found/);
expect(distribution['findInToolcache']()).toBeNull();
}
);
it('keeps patch-only and exact-build resolution cache identities distinct', () => {
const distribution = new LibericaDistributions({
version: '17.0.20.1',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
expect(distribution['version']).toBe('17.0.20.1');
});
it('orders main versions, patches, and builds consistently in cache', async () => {
for (const version of ['17.0.20-1.2', '17.0.20-1.10', '17.0.21-1']) {
const javaPath = path.join(
cacheRoot,
'Java_Liberica_jdk',
version,
'x64'
);
await fs.promises.mkdir(javaPath, {recursive: true});
await fs.promises.writeFile(`${javaPath}.complete`, '');
}
for (const [version, expected] of [
['17', '17.0.21+1'],
['17.0.20', '17.0.20+1.10'],
['17.0.20.1', '17.0.20+1.10']
]) {
const distribution = new LibericaDistributions({
version,
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
expect(distribution['findInToolcache']()?.version).toBe(expected);
}
});
it('preserves early-access handling for patch-only requests', async () => {
const javaPath = path.join(
cacheRoot,
'Java_Liberica_jdk',
'17.0.20-ea.1.2',
'x64'
);
await fs.promises.mkdir(javaPath, {recursive: true});
await fs.promises.writeFile(`${javaPath}.complete`, '');
const distribution = new LibericaDistributions({
version: '17.0.20.1-ea',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
expect(distribution['stable']).toBe(false);
expect(distribution['findInToolcache']()).toEqual({
version: '17.0.20+1.2',
path: javaPath
});
});
});
describe('getPlatformOption', () => {
beforeEach(() => {
// The linux row below is glibc, so pin the Alpine probe rather than
@@ -369,6 +557,36 @@ describe('convertVersionToSemver', () => {
},
'11.0.0+13'
],
[
{
version: '17.0.20.1+1',
featureVersion: 17,
interimVersion: 0,
updateVersion: 20,
buildVersion: 1
},
'17.0.20+1.1'
],
[
{
version: '25.0.4.1+1',
featureVersion: 25,
interimVersion: 0,
updateVersion: 4,
buildVersion: 1
},
'25.0.4+1.1'
],
[
{
version: '17.0.20.1+2',
featureVersion: 17,
interimVersion: 0,
updateVersion: 20,
buildVersion: 2
},
'17.0.20+1.2'
],
[
{
version: '25.0.4+1.1',
+132 -28
View File
@@ -7217,7 +7217,7 @@ function defaultFactory (origin, opts) {
}
class Agent extends DispatcherBase {
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, tls, ...options } = {}) {
if (typeof factory !== 'function') {
throw new InvalidArgumentError('factory must be a function.')
}
@@ -7236,11 +7236,15 @@ class Agent extends DispatcherBase {
connect = { ...connect }
}
if (tls && typeof tls !== 'function') {
tls = { ...tls }
}
this[kInterceptors] = options.interceptors?.Agent && Array.isArray(options.interceptors.Agent)
? options.interceptors.Agent
: [createRedirectInterceptor({ maxRedirections })]
this[kOptions] = { ...util.deepClone(options), connect }
this[kOptions] = { ...util.deepClone(options), connect, tls }
this[kOptions].interceptors = options.interceptors
? { ...options.interceptors }
: undefined
@@ -8273,8 +8277,6 @@ async function connectH1 (client, socket) {
socket[kParser] = new Parser(client, socket, llhttpInstance)
addListener(socket, 'error', function (err) {
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
const parser = this[kParser]
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
@@ -9204,8 +9206,6 @@ async function connectH2 (client, socket) {
socket[kHTTP2Session] = session
util.addListener(socket, 'error', function (err) {
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
this[kError] = err
this[kClient][kOnError](err)
@@ -9279,8 +9279,6 @@ function resumeH2 (client) {
}
function onHttp2SessionError (err) {
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
this[kSocket][kError] = err
this[kClient][kOnError](err)
}
@@ -9978,9 +9976,10 @@ class Client extends DispatcherBase {
// h2
maxConcurrentStreams,
allowH2,
webSocket
webSocket,
eventSource
} = {}) {
super({ webSocket })
super({ webSocket, eventSource })
if (keepAlive !== undefined) {
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
@@ -10501,6 +10500,7 @@ module.exports = Client
const buffer = __nccwpck_require__(4573)
const Dispatcher = __nccwpck_require__(883)
const {
ClientDestroyedError,
@@ -10513,6 +10513,7 @@ const kOnDestroyed = Symbol('onDestroyed')
const kOnClosed = Symbol('onClosed')
const kInterceptedDispatch = Symbol('Intercepted Dispatch')
const kWebSocketOptions = Symbol('webSocketOptions')
const kEventSourceOptions = Symbol('eventSourceOptions')
class DispatcherBase extends Dispatcher {
constructor (opts) {
@@ -10523,6 +10524,7 @@ class DispatcherBase extends Dispatcher {
this[kClosed] = false
this[kOnClosed] = []
this[kWebSocketOptions] = opts?.webSocket ?? {}
this[kEventSourceOptions] = opts?.eventSource ?? {}
}
get webSocketOptions () {
@@ -10532,6 +10534,12 @@ class DispatcherBase extends Dispatcher {
}
}
get eventSourceOptions () {
return {
maxEventSize: this[kEventSourceOptions].maxEventSize ?? buffer.kStringMaxLength
}
}
get destroyed () {
return this[kDestroyed]
}
@@ -16890,6 +16898,7 @@ module.exports = {
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
const buffer = __nccwpck_require__(4573)
const { Transform } = __nccwpck_require__(7075)
const { isASCIINumber, isValidLastEventId } = __nccwpck_require__(4811)
@@ -16914,11 +16923,19 @@ const COLON = 0x3A
*/
const SPACE = 0x20
const defaultMaxEventSize = buffer.kStringMaxLength
const DATA = Buffer.from('data')
const EVENT = Buffer.from('event')
const ID = Buffer.from('id')
const RETRY = Buffer.from('retry')
// Each data field creates a string node, and each unconsumed input chunk keeps
// a Buffer wrapper. The absolute ceiling protects the large default byte limit.
const minParserItems = 1024
const maxParserItems = 131072
const bytesPerParserItem = 128
function isASCIINumberBytes (buffer, start) {
if (start >= buffer.length) {
return false
@@ -16957,6 +16974,12 @@ function isFieldName (line, length, field) {
return true
}
function createMaxEventSizeExceededError () {
const error = new Error('EventSource message size exceeded')
error.aborted = false
return error
}
/**
* @typedef {object} EventSourceStreamEvent
* @type {object}
@@ -17005,6 +17028,13 @@ class EventSourceStream extends Transform {
pos = 0
lineChunkIndex = 0
linePos = 0
lineLength = 0
eventSize = 0
eventFieldSizes = { data: 0, event: 0, id: 0, retry: 0 }
dataLineCount = 0
maxEventSize
maxDataLines
maxBufferedChunks
event = {
data: undefined,
@@ -17015,6 +17045,7 @@ class EventSourceStream extends Transform {
/**
* @param {object} options
* @param {number} [options.maxEventSize]
* @param {eventSourceSettings} options.eventSourceSettings
* @param {Function} [options.push]
*/
@@ -17026,6 +17057,12 @@ class EventSourceStream extends Transform {
super(options)
this.state = options.eventSourceSettings || {}
this.maxEventSize = options.maxEventSize ?? defaultMaxEventSize
const parserItemLimit = this.maxEventSize > 0
? Math.min(maxParserItems, Math.max(minParserItems, Math.floor(this.maxEventSize / bytesPerParserItem)))
: 0
this.maxDataLines = parserItemLimit
this.maxBufferedChunks = parserItemLimit
if (options.push) {
this.push = options.push
}
@@ -17043,6 +17080,14 @@ class EventSourceStream extends Transform {
return
}
// A leading line ending can immediately release the currently buffered
// line, even when it uses the final allowed chunk slot.
if (this.maxEventSize > 0 &&
this.chunks.length >= this.maxBufferedChunks &&
chunk[0] !== LF && chunk[0] !== CR) {
callback(createMaxEventSizeExceededError())
return
}
this.chunks.push(chunk)
// Strip leading byte-order-mark if we opened the stream and started
@@ -17121,7 +17166,12 @@ class EventSourceStream extends Transform {
// In any case, we can process the line as we reached an
// end-of-line character
try {
this.parseLine(this.readLine(), this.event)
} catch (error) {
callback(error)
return
}
this.consumeCurrentByte()
// A line was processed and this could be the end of the event. We need
// to check if the next line is empty to determine if the event is
@@ -17130,15 +17180,28 @@ class EventSourceStream extends Transform {
continue
}
// readLine() is only called after CR or LF. Reject an unfinished line
// before it can retain an unbounded number of decoded chunks. The seven
// extra bytes allow the longest field prefix ("event: " or "retry: ").
if (this.maxEventSize > 0 && ++this.lineLength > this.maxEventSize + 7) {
callback(createMaxEventSizeExceededError())
return
}
this.advanceCursor()
}
callback()
}
_destroy (error, callback) {
this.chunks.length = 0
this.clearEvent()
super._destroy(error, callback)
}
/**
* @param {Buffer} line
* @param {EventStreamEvent} event
* @param {EventSourceStreamEvent} event
*/
parseLine (line, event) {
// If the line is empty (a blank line)
@@ -17172,6 +17235,13 @@ class EventSourceStream extends Transform {
}
if (isFieldName(line, fieldLength, DATA)) {
if (this.maxEventSize > 0 && this.dataLineCount >= this.maxDataLines) {
throw createMaxEventSizeExceededError()
}
const valueBytes = line.length - valueStart
const dataSize = this.eventFieldSizes.data + (event.data === undefined ? 0 : 1) + valueBytes
this.setEventFieldSize('data', dataSize)
const value = line.toString('utf8', valueStart)
if (event.data === undefined) {
@@ -17179,11 +17249,13 @@ class EventSourceStream extends Transform {
} else {
event.data += `\n${value}`
}
this.dataLineCount++
return
}
if (isFieldName(line, fieldLength, RETRY)) {
if (isASCIINumberBytes(line, valueStart)) {
this.setEventFieldSize('retry', line.length - valueStart)
event.retry = line.toString('utf8', valueStart)
}
return
@@ -17191,18 +17263,27 @@ class EventSourceStream extends Transform {
if (isFieldName(line, fieldLength, ID)) {
if (isValidLastEventIdBytes(line, valueStart)) {
this.setEventFieldSize('id', line.length - valueStart)
event.id = line.toString('utf8', valueStart)
}
return
}
if (isFieldName(line, fieldLength, EVENT)) {
const value = line.toString('utf8', valueStart)
if (valueStart < line.length) {
this.setEventFieldSize('event', line.length - valueStart)
event.event = line.toString('utf8', valueStart)
}
}
}
if (value.length > 0) {
event.event = value
}
setEventFieldSize (field, size) {
const eventSize = this.eventSize - this.eventFieldSizes[field] + size
if (this.maxEventSize > 0 && eventSize > this.maxEventSize) {
throw createMaxEventSizeExceededError()
}
this.eventSize = eventSize
this.eventFieldSizes[field] = size
}
/**
@@ -17235,6 +17316,12 @@ class EventSourceStream extends Transform {
this.event.event = undefined
this.event.id = undefined
this.event.retry = undefined
this.eventSize = 0
this.dataLineCount = 0
this.eventFieldSizes.data = 0
this.eventFieldSizes.event = 0
this.eventFieldSizes.id = 0
this.eventFieldSizes.retry = 0
}
hasPendingEvent () {
@@ -17270,14 +17357,16 @@ class EventSourceStream extends Transform {
syncLineStartToCursor () {
this.lineChunkIndex = this.chunkIndex
this.linePos = this.pos
this.lineLength = 0
this.dropConsumedChunks()
}
dropConsumedChunks () {
while (this.lineChunkIndex > 0) {
this.chunks.shift()
this.lineChunkIndex--
this.chunkIndex--
if (this.lineChunkIndex > 0) {
const consumed = this.lineChunkIndex
this.chunks.splice(0, consumed)
this.lineChunkIndex = 0
this.chunkIndex -= consumed
}
if (this.chunkIndex === this.chunks.length) {
@@ -17402,6 +17491,7 @@ const { isNetworkError } = __nccwpck_require__(9051)
const { delay } = __nccwpck_require__(4811)
const { kEnumerableProperty } = __nccwpck_require__(3440)
const { environmentSettingsObject } = __nccwpck_require__(3168)
const { getGlobalDispatcher } = __nccwpck_require__(2581)
let experimentalWarned = false
@@ -17683,6 +17773,7 @@ class EventSource extends EventTarget {
const eventSourceStream = new EventSourceStream({
eventSourceSettings: this.#state,
maxEventSize: this.#dispatcher.eventSourceOptions?.maxEventSize,
push: (event) => {
this.dispatchEvent(createFastMessageEvent(
event.type,
@@ -17861,7 +17952,8 @@ webidl.converters.EventSourceInitDict = webidl.dictionaryConverter([
},
{
key: 'dispatcher', // undici only
converter: webidl.converters.any
converter: webidl.converters.any,
defaultValue: () => getGlobalDispatcher()
}
])
@@ -22957,8 +23049,10 @@ async function httpNetworkFetch (
// 19. Run these steps in parallel:
// 1. Run these steps, but abort when fetchParams is canceled:
fetchParams.controller.onAborted = onAborted
if (!fetchParams.controller.resume) {
fetchParams.controller.on('terminated', onAborted)
}
fetchParams.controller.resume = async () => {
// 1. While true
while (true) {
@@ -23127,8 +23221,6 @@ async function httpNetworkFetch (
}
location = headersList.get('location', true)
this.body = new Readable({ read: resume })
const decoders = []
const willFollow = location && request.redirect === 'follow' &&
@@ -23146,7 +23238,7 @@ async function httpNetworkFetch (
// CVE fix similar to urllib3 (GHSA-gm62-xv2j-4w53) and curl (CVE-2022-32206).
const maxContentEncodings = 5
if (codings.length > maxContentEncodings) {
reject(new Error(`too many content-encodings in response: ${codings.length}, maximum allowed is ${maxContentEncodings}`))
this.abort(new Error(`too many content-encodings in response: ${codings.length}, maximum allowed is ${maxContentEncodings}`))
return true
}
@@ -23179,6 +23271,8 @@ async function httpNetworkFetch (
}
}
this.body = new Readable({ read: resume })
const onError = this.onError.bind(this)
resolve({
@@ -23225,10 +23319,6 @@ async function httpNetworkFetch (
fetchParams.controller.off('terminated', this.abort)
}
if (fetchParams.controller.onAborted) {
fetchParams.controller.off('terminated', fetchParams.controller.onAborted)
}
fetchParams.controller.ended = true
this.body.push(null)
@@ -29703,6 +29793,20 @@ class ByteParser extends Writable {
if (isControlFrame(this.#info.opcode)) {
this.#loop = this.parseControlFrame(body)
this.#state = parserStates.INFO
// If the automatic PONG/CLOSE reply filled the socket's write buffer,
// stop parsing and resume on drain so a PING flood cannot grow the
// outbound queue without bound.
const socket = this.ws[kResponse].socket
if (this.#loop && socket.writableNeedDrain) {
this.#loop = false
socket.once('drain', () => {
if (this.destroyed) return
this.#loop = true
this.run(callback)
})
return
}
} else {
if (!this.#info.compressed) {
if (!this.writeFragments(body)) {
+10 -4
View File
@@ -378,7 +378,7 @@ class JavaBase {
core/* info */.pq(`Java ${foundJava.version} was downloaded`);
if (javaRelease.floating) {
if (!semver_default().valid(foundJava.version) ||
!(0,util/* isVersionSatisfies */.y)(this.version, foundJava.version)) {
!this.isVersionSatisfied(this.version, foundJava.version)) {
throw new Error(`The downloaded ${this.distribution} artifact reported Java ${foundJava.version}, which does not satisfy '${this.version}'.`);
}
javaRelease = { ...javaRelease, version: foundJava.version };
@@ -501,7 +501,7 @@ class JavaBase {
return javaRelease;
}
if (!semver_default().valid(restored.release.version) ||
!(0,util/* isVersionSatisfies */.y)(this.version, restored.release.version)) {
!this.isVersionSatisfied(this.version, restored.release.version)) {
core/* debug */.Yz(`Ignoring the cached concrete version '${restored.release.version}' for ${this.distribution} ${this.version}.`);
return javaRelease;
}
@@ -684,10 +684,10 @@ class JavaBase {
})
.filter(item => item.stable === this.stable);
const satisfiedVersions = availableVersions
.filter(item => (0,util/* isVersionSatisfies */.y)(this.version, item.version))
.filter(item => this.isVersionSatisfied(this.version, item.version))
.filter(item => item.path)
.sort((a, b) => {
return -semver_default().compareBuild(a.version, b.version);
return -this.compareVersions(a.version, b.version);
});
if (!satisfiedVersions || satisfiedVersions.length === 0) {
return null;
@@ -697,6 +697,12 @@ class JavaBase {
path: satisfiedVersions[0].path
};
}
isVersionSatisfied(range, version) {
return (0,util/* isVersionSatisfies */.y)(range, version);
}
compareVersions(a, b) {
return semver_default().compareBuild(a, b);
}
normalizeVersion(version) {
let stable = true;
const latest = false;
+38 -2
View File
@@ -52,14 +52,46 @@ class LibericaDistributions extends _base_installer_js__WEBPACK_IMPORTED_MODULE_
version: this.convertVersionToSemver(item)
}));
const satisfiedVersion = availableVersions
.filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .isVersionSatisfies */ .y)(range, item.version))
.sort((a, b) => -semver__WEBPACK_IMPORTED_MODULE_1___default().compareBuild(a.version, b.version))[0];
.filter(item => this.isVersionSatisfied(range, item.version))
.sort((a, b) => -this.compareVersions(a.version, b.version))[0];
if (!satisfiedVersion) {
const availableVersionStrings = availableVersions.map(item => item.version);
throw this.createVersionNotFoundError(range, availableVersionStrings);
}
return satisfiedVersion;
}
normalizeVersion(version) {
const normalized = super.normalizeVersion(version);
if (/^\d+\.\d+\.\d+\.\d+(?:-ea)?$/.test(version)) {
// Keep patch-only requests distinct from exact SDKMAN build requests.
normalized.version = version.replace(/-ea$/, '');
}
return normalized;
}
isVersionSatisfied(range, version) {
const patchRequest = range.match(/^(\d+\.\d+\.\d+)\.(\d+)$/);
if (patchRequest) {
const candidate = semver__WEBPACK_IMPORTED_MODULE_1___default().parse(version);
if (!candidate || !semver__WEBPACK_IMPORTED_MODULE_1___default().eq(patchRequest[1], candidate)) {
return false;
}
const patch = candidate.build.length > 1 ? candidate.build[0] : '0';
return patch === patchRequest[2];
}
return super.isVersionSatisfied(range, version);
}
compareVersions(a, b) {
const mainComparison = semver__WEBPACK_IMPORTED_MODULE_1___default().compare(a, b);
if (mainComparison !== 0) {
return mainComparison;
}
// SDKMAN encodes patch.build, but unpatched releases contain only build.
const patchAndBuild = (version) => {
const build = new (semver__WEBPACK_IMPORTED_MODULE_1___default().SemVer)(version).build;
return (build.length > 1 ? build : ['0', ...build]).join('.');
};
return semver__WEBPACK_IMPORTED_MODULE_1___default().compareBuild(`0.0.0+${patchAndBuild(a)}`, `0.0.0+${patchAndBuild(b)}`);
}
async getAvailableVersions() {
if (_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .isDebug */ ._o()) {
console.time('Retrieving available versions for Liberica took'); // eslint-disable-line no-console
@@ -131,6 +163,10 @@ class LibericaDistributions extends _base_installer_js__WEBPACK_IMPORTED_MODULE_
const { featureVersion, interimVersion, updateVersion } = version;
const buildVersion = version.version.split('+')[1] || version.buildVersion;
const mainVersion = [featureVersion, interimVersion, updateVersion].join('.');
const patchVersion = version.version.split('+')[0].split('.')[3];
if (patchVersion) {
return `${mainVersion}+${patchVersion}.${buildVersion}`;
}
if (buildVersion != 0) {
return `${mainVersion}+${buildVersion}`;
}
+132 -28
View File
@@ -7217,7 +7217,7 @@ function defaultFactory (origin, opts) {
}
class Agent extends DispatcherBase {
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, tls, ...options } = {}) {
if (typeof factory !== 'function') {
throw new InvalidArgumentError('factory must be a function.')
}
@@ -7236,11 +7236,15 @@ class Agent extends DispatcherBase {
connect = { ...connect }
}
if (tls && typeof tls !== 'function') {
tls = { ...tls }
}
this[kInterceptors] = options.interceptors?.Agent && Array.isArray(options.interceptors.Agent)
? options.interceptors.Agent
: [createRedirectInterceptor({ maxRedirections })]
this[kOptions] = { ...util.deepClone(options), connect }
this[kOptions] = { ...util.deepClone(options), connect, tls }
this[kOptions].interceptors = options.interceptors
? { ...options.interceptors }
: undefined
@@ -8273,8 +8277,6 @@ async function connectH1 (client, socket) {
socket[kParser] = new Parser(client, socket, llhttpInstance)
addListener(socket, 'error', function (err) {
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
const parser = this[kParser]
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
@@ -9204,8 +9206,6 @@ async function connectH2 (client, socket) {
socket[kHTTP2Session] = session
util.addListener(socket, 'error', function (err) {
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
this[kError] = err
this[kClient][kOnError](err)
@@ -9279,8 +9279,6 @@ function resumeH2 (client) {
}
function onHttp2SessionError (err) {
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
this[kSocket][kError] = err
this[kClient][kOnError](err)
}
@@ -9978,9 +9976,10 @@ class Client extends DispatcherBase {
// h2
maxConcurrentStreams,
allowH2,
webSocket
webSocket,
eventSource
} = {}) {
super({ webSocket })
super({ webSocket, eventSource })
if (keepAlive !== undefined) {
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
@@ -10501,6 +10500,7 @@ module.exports = Client
const buffer = __nccwpck_require__(4573)
const Dispatcher = __nccwpck_require__(883)
const {
ClientDestroyedError,
@@ -10513,6 +10513,7 @@ const kOnDestroyed = Symbol('onDestroyed')
const kOnClosed = Symbol('onClosed')
const kInterceptedDispatch = Symbol('Intercepted Dispatch')
const kWebSocketOptions = Symbol('webSocketOptions')
const kEventSourceOptions = Symbol('eventSourceOptions')
class DispatcherBase extends Dispatcher {
constructor (opts) {
@@ -10523,6 +10524,7 @@ class DispatcherBase extends Dispatcher {
this[kClosed] = false
this[kOnClosed] = []
this[kWebSocketOptions] = opts?.webSocket ?? {}
this[kEventSourceOptions] = opts?.eventSource ?? {}
}
get webSocketOptions () {
@@ -10532,6 +10534,12 @@ class DispatcherBase extends Dispatcher {
}
}
get eventSourceOptions () {
return {
maxEventSize: this[kEventSourceOptions].maxEventSize ?? buffer.kStringMaxLength
}
}
get destroyed () {
return this[kDestroyed]
}
@@ -16890,6 +16898,7 @@ module.exports = {
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
const buffer = __nccwpck_require__(4573)
const { Transform } = __nccwpck_require__(7075)
const { isASCIINumber, isValidLastEventId } = __nccwpck_require__(4811)
@@ -16914,11 +16923,19 @@ const COLON = 0x3A
*/
const SPACE = 0x20
const defaultMaxEventSize = buffer.kStringMaxLength
const DATA = Buffer.from('data')
const EVENT = Buffer.from('event')
const ID = Buffer.from('id')
const RETRY = Buffer.from('retry')
// Each data field creates a string node, and each unconsumed input chunk keeps
// a Buffer wrapper. The absolute ceiling protects the large default byte limit.
const minParserItems = 1024
const maxParserItems = 131072
const bytesPerParserItem = 128
function isASCIINumberBytes (buffer, start) {
if (start >= buffer.length) {
return false
@@ -16957,6 +16974,12 @@ function isFieldName (line, length, field) {
return true
}
function createMaxEventSizeExceededError () {
const error = new Error('EventSource message size exceeded')
error.aborted = false
return error
}
/**
* @typedef {object} EventSourceStreamEvent
* @type {object}
@@ -17005,6 +17028,13 @@ class EventSourceStream extends Transform {
pos = 0
lineChunkIndex = 0
linePos = 0
lineLength = 0
eventSize = 0
eventFieldSizes = { data: 0, event: 0, id: 0, retry: 0 }
dataLineCount = 0
maxEventSize
maxDataLines
maxBufferedChunks
event = {
data: undefined,
@@ -17015,6 +17045,7 @@ class EventSourceStream extends Transform {
/**
* @param {object} options
* @param {number} [options.maxEventSize]
* @param {eventSourceSettings} options.eventSourceSettings
* @param {Function} [options.push]
*/
@@ -17026,6 +17057,12 @@ class EventSourceStream extends Transform {
super(options)
this.state = options.eventSourceSettings || {}
this.maxEventSize = options.maxEventSize ?? defaultMaxEventSize
const parserItemLimit = this.maxEventSize > 0
? Math.min(maxParserItems, Math.max(minParserItems, Math.floor(this.maxEventSize / bytesPerParserItem)))
: 0
this.maxDataLines = parserItemLimit
this.maxBufferedChunks = parserItemLimit
if (options.push) {
this.push = options.push
}
@@ -17043,6 +17080,14 @@ class EventSourceStream extends Transform {
return
}
// A leading line ending can immediately release the currently buffered
// line, even when it uses the final allowed chunk slot.
if (this.maxEventSize > 0 &&
this.chunks.length >= this.maxBufferedChunks &&
chunk[0] !== LF && chunk[0] !== CR) {
callback(createMaxEventSizeExceededError())
return
}
this.chunks.push(chunk)
// Strip leading byte-order-mark if we opened the stream and started
@@ -17121,7 +17166,12 @@ class EventSourceStream extends Transform {
// In any case, we can process the line as we reached an
// end-of-line character
try {
this.parseLine(this.readLine(), this.event)
} catch (error) {
callback(error)
return
}
this.consumeCurrentByte()
// A line was processed and this could be the end of the event. We need
// to check if the next line is empty to determine if the event is
@@ -17130,15 +17180,28 @@ class EventSourceStream extends Transform {
continue
}
// readLine() is only called after CR or LF. Reject an unfinished line
// before it can retain an unbounded number of decoded chunks. The seven
// extra bytes allow the longest field prefix ("event: " or "retry: ").
if (this.maxEventSize > 0 && ++this.lineLength > this.maxEventSize + 7) {
callback(createMaxEventSizeExceededError())
return
}
this.advanceCursor()
}
callback()
}
_destroy (error, callback) {
this.chunks.length = 0
this.clearEvent()
super._destroy(error, callback)
}
/**
* @param {Buffer} line
* @param {EventStreamEvent} event
* @param {EventSourceStreamEvent} event
*/
parseLine (line, event) {
// If the line is empty (a blank line)
@@ -17172,6 +17235,13 @@ class EventSourceStream extends Transform {
}
if (isFieldName(line, fieldLength, DATA)) {
if (this.maxEventSize > 0 && this.dataLineCount >= this.maxDataLines) {
throw createMaxEventSizeExceededError()
}
const valueBytes = line.length - valueStart
const dataSize = this.eventFieldSizes.data + (event.data === undefined ? 0 : 1) + valueBytes
this.setEventFieldSize('data', dataSize)
const value = line.toString('utf8', valueStart)
if (event.data === undefined) {
@@ -17179,11 +17249,13 @@ class EventSourceStream extends Transform {
} else {
event.data += `\n${value}`
}
this.dataLineCount++
return
}
if (isFieldName(line, fieldLength, RETRY)) {
if (isASCIINumberBytes(line, valueStart)) {
this.setEventFieldSize('retry', line.length - valueStart)
event.retry = line.toString('utf8', valueStart)
}
return
@@ -17191,18 +17263,27 @@ class EventSourceStream extends Transform {
if (isFieldName(line, fieldLength, ID)) {
if (isValidLastEventIdBytes(line, valueStart)) {
this.setEventFieldSize('id', line.length - valueStart)
event.id = line.toString('utf8', valueStart)
}
return
}
if (isFieldName(line, fieldLength, EVENT)) {
const value = line.toString('utf8', valueStart)
if (valueStart < line.length) {
this.setEventFieldSize('event', line.length - valueStart)
event.event = line.toString('utf8', valueStart)
}
}
}
if (value.length > 0) {
event.event = value
}
setEventFieldSize (field, size) {
const eventSize = this.eventSize - this.eventFieldSizes[field] + size
if (this.maxEventSize > 0 && eventSize > this.maxEventSize) {
throw createMaxEventSizeExceededError()
}
this.eventSize = eventSize
this.eventFieldSizes[field] = size
}
/**
@@ -17235,6 +17316,12 @@ class EventSourceStream extends Transform {
this.event.event = undefined
this.event.id = undefined
this.event.retry = undefined
this.eventSize = 0
this.dataLineCount = 0
this.eventFieldSizes.data = 0
this.eventFieldSizes.event = 0
this.eventFieldSizes.id = 0
this.eventFieldSizes.retry = 0
}
hasPendingEvent () {
@@ -17270,14 +17357,16 @@ class EventSourceStream extends Transform {
syncLineStartToCursor () {
this.lineChunkIndex = this.chunkIndex
this.linePos = this.pos
this.lineLength = 0
this.dropConsumedChunks()
}
dropConsumedChunks () {
while (this.lineChunkIndex > 0) {
this.chunks.shift()
this.lineChunkIndex--
this.chunkIndex--
if (this.lineChunkIndex > 0) {
const consumed = this.lineChunkIndex
this.chunks.splice(0, consumed)
this.lineChunkIndex = 0
this.chunkIndex -= consumed
}
if (this.chunkIndex === this.chunks.length) {
@@ -17402,6 +17491,7 @@ const { isNetworkError } = __nccwpck_require__(9051)
const { delay } = __nccwpck_require__(4811)
const { kEnumerableProperty } = __nccwpck_require__(3440)
const { environmentSettingsObject } = __nccwpck_require__(3168)
const { getGlobalDispatcher } = __nccwpck_require__(2581)
let experimentalWarned = false
@@ -17683,6 +17773,7 @@ class EventSource extends EventTarget {
const eventSourceStream = new EventSourceStream({
eventSourceSettings: this.#state,
maxEventSize: this.#dispatcher.eventSourceOptions?.maxEventSize,
push: (event) => {
this.dispatchEvent(createFastMessageEvent(
event.type,
@@ -17861,7 +17952,8 @@ webidl.converters.EventSourceInitDict = webidl.dictionaryConverter([
},
{
key: 'dispatcher', // undici only
converter: webidl.converters.any
converter: webidl.converters.any,
defaultValue: () => getGlobalDispatcher()
}
])
@@ -22957,8 +23049,10 @@ async function httpNetworkFetch (
// 19. Run these steps in parallel:
// 1. Run these steps, but abort when fetchParams is canceled:
fetchParams.controller.onAborted = onAborted
if (!fetchParams.controller.resume) {
fetchParams.controller.on('terminated', onAborted)
}
fetchParams.controller.resume = async () => {
// 1. While true
while (true) {
@@ -23127,8 +23221,6 @@ async function httpNetworkFetch (
}
location = headersList.get('location', true)
this.body = new Readable({ read: resume })
const decoders = []
const willFollow = location && request.redirect === 'follow' &&
@@ -23146,7 +23238,7 @@ async function httpNetworkFetch (
// CVE fix similar to urllib3 (GHSA-gm62-xv2j-4w53) and curl (CVE-2022-32206).
const maxContentEncodings = 5
if (codings.length > maxContentEncodings) {
reject(new Error(`too many content-encodings in response: ${codings.length}, maximum allowed is ${maxContentEncodings}`))
this.abort(new Error(`too many content-encodings in response: ${codings.length}, maximum allowed is ${maxContentEncodings}`))
return true
}
@@ -23179,6 +23271,8 @@ async function httpNetworkFetch (
}
}
this.body = new Readable({ read: resume })
const onError = this.onError.bind(this)
resolve({
@@ -23225,10 +23319,6 @@ async function httpNetworkFetch (
fetchParams.controller.off('terminated', this.abort)
}
if (fetchParams.controller.onAborted) {
fetchParams.controller.off('terminated', fetchParams.controller.onAborted)
}
fetchParams.controller.ended = true
this.body.push(null)
@@ -29703,6 +29793,20 @@ class ByteParser extends Writable {
if (isControlFrame(this.#info.opcode)) {
this.#loop = this.parseControlFrame(body)
this.#state = parserStates.INFO
// If the automatic PONG/CLOSE reply filled the socket's write buffer,
// stop parsing and resume on drain so a PING flood cannot grow the
// outbound queue without bound.
const socket = this.ws[kResponse].socket
if (this.#loop && socket.writableNeedDrain) {
this.#loop = false
socket.once('drain', () => {
if (this.destroyed) return
this.#loop = true
this.run(callback)
})
return
}
} else {
if (!this.#info.compressed) {
if (!this.writeFragments(body)) {
+6
View File
@@ -83,6 +83,12 @@ steps:
### Liberica
Exact patch releases use SDKMAN's `X.Y.Z+P.B` notation, including in `.sdkmanrc`.
For example, `java=17.0.20+1.1-librca` selects BellSoft's `17.0.20.1+1` release.
Alternatively, `java-version: '17.0.20.1'` selects the newest build of patch 1,
whereas `java-version: '17.0.20+1'` selects the exact unpatched build 1.
Floating versions such as `17` prefer newer patches before comparing build numbers.
```yaml
steps:
- uses: actions/checkout@v7
+3 -3
View File
@@ -6375,9 +6375,9 @@
}
},
"node_modules/undici": {
"version": "6.29.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.29.0.tgz",
"integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==",
"version": "6.29.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.29.1.tgz",
"integrity": "sha512-EvRlUt/QqAEvXdQeRMYUfwOQsfzpDztykrG2owGEE/rBOBoYv9uF39ZzsNDh4lTFHhTr2XOZjKkYgueD12ZmGA==",
"license": "MIT",
"engines": {
"node": ">=18.17"
+12 -4
View File
@@ -241,7 +241,7 @@ export abstract class JavaBase {
if (javaRelease.floating) {
if (
!semver.valid(foundJava.version) ||
!isVersionSatisfies(this.version, foundJava.version)
!this.isVersionSatisfied(this.version, foundJava.version)
) {
throw new Error(
`The downloaded ${this.distribution} artifact reported Java ${foundJava.version}, which does not satisfy '${this.version}'.`
@@ -401,7 +401,7 @@ export abstract class JavaBase {
}
if (
!semver.valid(restored.release.version) ||
!isVersionSatisfies(this.version, restored.release.version)
!this.isVersionSatisfied(this.version, restored.release.version)
) {
core.debug(
`Ignoring the cached concrete version '${restored.release.version}' for ${this.distribution} ${this.version}.`
@@ -622,10 +622,10 @@ export abstract class JavaBase {
.filter(item => item.stable === this.stable);
const satisfiedVersions = availableVersions
.filter(item => isVersionSatisfies(this.version, item.version))
.filter(item => this.isVersionSatisfied(this.version, item.version))
.filter(item => item.path)
.sort((a, b) => {
return -semver.compareBuild(a.version, b.version);
return -this.compareVersions(a.version, b.version);
});
if (!satisfiedVersions || satisfiedVersions.length === 0) {
return null;
@@ -637,6 +637,14 @@ export abstract class JavaBase {
};
}
protected isVersionSatisfied(range: string, version: string): boolean {
return isVersionSatisfies(range, version);
}
protected compareVersions(a: string, b: string): number {
return semver.compareBuild(a, b);
}
protected normalizeVersion(version: string) {
let stable = true;
const latest = false;
+45 -3
View File
@@ -9,7 +9,6 @@ import {
cacheJdkDir,
extractJdkFile,
getDownloadArchiveExtension,
isVersionSatisfies,
renameWinArchive
} from '../../util.js';
import * as core from '@actions/core';
@@ -66,8 +65,8 @@ export class LibericaDistributions extends JavaBase {
}));
const satisfiedVersion = availableVersions
.filter(item => isVersionSatisfies(range, item.version))
.sort((a, b) => -semver.compareBuild(a.version, b.version))[0];
.filter(item => this.isVersionSatisfied(range, item.version))
.sort((a, b) => -this.compareVersions(a.version, b.version))[0];
if (!satisfiedVersion) {
const availableVersionStrings = availableVersions.map(
@@ -79,6 +78,45 @@ export class LibericaDistributions extends JavaBase {
return satisfiedVersion;
}
protected normalizeVersion(version: string) {
const normalized = super.normalizeVersion(version);
if (/^\d+\.\d+\.\d+\.\d+(?:-ea)?$/.test(version)) {
// Keep patch-only requests distinct from exact SDKMAN build requests.
normalized.version = version.replace(/-ea$/, '');
}
return normalized;
}
protected isVersionSatisfied(range: string, version: string): boolean {
const patchRequest = range.match(/^(\d+\.\d+\.\d+)\.(\d+)$/);
if (patchRequest) {
const candidate = semver.parse(version);
if (!candidate || !semver.eq(patchRequest[1], candidate)) {
return false;
}
const patch = candidate.build.length > 1 ? candidate.build[0] : '0';
return patch === patchRequest[2];
}
return super.isVersionSatisfied(range, version);
}
protected compareVersions(a: string, b: string): number {
const mainComparison = semver.compare(a, b);
if (mainComparison !== 0) {
return mainComparison;
}
// SDKMAN encodes patch.build, but unpatched releases contain only build.
const patchAndBuild = (version: string): string => {
const build = new semver.SemVer(version).build;
return (build.length > 1 ? build : ['0', ...build]).join('.');
};
return semver.compareBuild(
`0.0.0+${patchAndBuild(a)}`,
`0.0.0+${patchAndBuild(b)}`
);
}
private async getAvailableVersions(): Promise<LibericaVersion[]> {
if (core.isDebug()) {
console.time('Retrieving available versions for Liberica took'); // eslint-disable-line no-console
@@ -171,6 +209,10 @@ export class LibericaDistributions extends JavaBase {
const mainVersion = [featureVersion, interimVersion, updateVersion].join(
'.'
);
const patchVersion = version.version.split('+')[0].split('.')[3];
if (patchVersion) {
return `${mainVersion}+${patchVersion}.${buildVersion}`;
}
if (buildVersion != 0) {
return `${mainVersion}+${buildVersion}`;
}