From 68880886eed1f3fb2ee4b9f8784628760d4eb74f Mon Sep 17 00:00:00 2001 From: William Killerud Date: Thu, 8 May 2025 10:53:02 +0200 Subject: [PATCH] fix: redirections bug with Node 24 --- .github/workflows/test.yml | 2 +- package.json | 2 +- src/index.js | 50 +++++++++++++++++++++++++++++++------- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f617614..145042f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] - node-version: [20, 22] + node-version: [20, 22, 24] runs-on: ${{ matrix.os }} steps: diff --git a/package.json b/package.json index 6d23d34..51fd59c 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "dependencies": { "@eik/common": "5.0.4", "abslog": "2.4.4", - "undici": "6.21.2" + "undici": "7.8.0" }, "devDependencies": { "@eik/eslint-config": "1.0.13", diff --git a/src/index.js b/src/index.js index 4af78b3..b771cb3 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ import { helpers } from "@eik/common"; -import { request } from "undici"; +import { request, interceptors, Agent } from "undici"; import { join } from "path"; import { Asset } from "./asset.js"; @@ -8,21 +8,47 @@ const trimSlash = (value = "") => { return value; }; -const fetchImportMaps = async (urls = []) => { +/** + * @param {string[]} urls + * @param {object} options + * @param {number} options.maxRedirections - undici option for limiting redirects + * @returns {Promise} + */ +const fetchImportMaps = async (urls = [], options) => { try { const maps = urls.map(async (map) => { - const { statusCode, body } = await request(map, { - maxRedirections: 2, + const response = await request(map, { + dispatcher: new Agent().compose( + interceptors.redirect({ + maxRedirections: options.maxRedirections, + }), + ), }); - if (statusCode === 404) { + if (response.statusCode === 404) { throw new Error("Import map could not be found on server"); - } else if (statusCode >= 400 && statusCode < 500) { + } else if (response.statusCode >= 400 && response.statusCode < 500) { throw new Error("Server rejected client request"); - } else if (statusCode >= 500) { + } else if (response.statusCode >= 500) { throw new Error("Server error"); } - return body.json(); + let contentType = response.headers["content-type"]; + if (!Array.isArray(contentType)) contentType = [contentType]; + + if (!contentType.find((type) => type.startsWith("application/json"))) { + const content = await response.body.text(); + if (content.length === 0) { + throw new Error( + `${map} did not return JSON, got an empty response. HTTP status: ${response.statusCode}`, + ); + } + throw new Error( + `${map} did not return JSON, got: ${content}. HTTP status: ${response.statusCode}`, + ); + } + + const json = await response.body.json(); + return /** @type {ImportMap}*/ (json); }); return await Promise.all(maps); } catch (err) { @@ -38,6 +64,7 @@ const fetchImportMaps = async (urls = []) => { * @property {boolean} [development=false] * @property {boolean} [loadMaps=false] * @property {string} [path=process.cwd()] + * @property {number} [maxRedirections=2] Maximum number of redirects when looking up URLs. */ /** @@ -111,6 +138,7 @@ export default class Eik { #path; #base; #maps; + #maxRedirections; /** * @param {Options} options @@ -120,6 +148,7 @@ export default class Eik { loadMaps = false, base = "", path = process.cwd(), + maxRedirections = 2, } = {}) { this.#development = development; this.#loadMaps = loadMaps; @@ -127,6 +156,7 @@ export default class Eik { this.#path = path; this.#base = trimSlash(base); this.#maps = []; + this.#maxRedirections = maxRedirections; } /** @@ -139,7 +169,9 @@ export default class Eik { async load() { this.#config = await helpers.getDefaults(this.#path); if (this.#loadMaps) { - this.#maps = await fetchImportMaps(this.#config.map); + this.#maps = await fetchImportMaps(this.#config.map, { + maxRedirections: this.#maxRedirections, + }); } }