diff --git a/agents.md b/agents.md new file mode 100644 index 000000000..ddc5010f6 --- /dev/null +++ b/agents.md @@ -0,0 +1,41 @@ +# Agents + +## Success Criteria for Feed Processing + +The fulltext RSS proxy must be able to process the following feeds without any issue, with full content retrieved, and essential fields kept: + +### Test Feeds + +1. **UN News RSS** — `https://news.un.org/feed/subscribe/en/news/all/rss.xml` + - Standard RSS 2.0 feed + +2. **FreshRSS Atom** — `https://github.com/FreshRSS/FreshRSS/releases.atom` + - Atom feed format + - Author is in `...` (mapped to `item.author` by rss-parser, not `item.creator`) + - Entry ID is in `` (mapped to `item.id`, not `item.guid`) + +3. **AliasVault RSS** — `https://www.aliasvault.net/rss.xml` + - RSS 2.0 with `` elements (Dublin Core namespace) + - Has categories per item + - `item.creator` maps directly from rss-parser + +### Essential Fields + +For each feed item in the output: + +| Field | RSS Output | JSON Feed Output | +|-------|-----------|-----------------| +| Title | `` | `title` | +| Link | `<link>` | `url` | +| Date | `<pubDate>` | `date_published` | +| Full content | `<content:encoded>` | `content_html` | +| Description | `<description>` | `summary` | +| Author/Creator | `<dc:creator>` | `authors[].name` | +| Categories | `<category>` | `tags` | +| GUID/ID | `<guid>` | `id` | + +### Author Handling + +- RSS feeds with `<dc:creator>`: mapped via `item.creator` from rss-parser +- Atom feeds with `<author>`: mapped via `item.author` from rss-parser (fallback when `item.creator` is absent) +- Output as `<dc:creator>` in RSS and `authors` array in JSON Feed diff --git a/package.json b/package.json index cd6d8c400..c9ae5ef5a 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@types/cypress": "^1.1.3", "@types/express": "^4.17.17", "@types/jest": "^27.5.2", + "@types/node": "^22", "@types/redis": "2.8.32", "@types/xml2js": "^0.4.11", "@whtsky/prettier-config": "^1.0.1", @@ -31,7 +32,7 @@ "tailwindcss": "3.2.7", "ts-jest": "^29.4.6", "ts-node": "^10.9.1", - "typescript": "^4.9.5" + "typescript": "^5.0.0" }, "dependencies": { "@postlight/parser": "^2.2.3", @@ -39,7 +40,7 @@ "@sentry/node": "^7.29.0", "crypto-js": "^4.1.1", "express": "^4.18.2", - "feed": "^4.2.2", + "feedsmith": "^2.9.1", "redis": "3.1.2", "rss-parser": "^3.12.0" }, diff --git a/src/app.ts b/src/app.ts index 475800ecc..30e93a4a9 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,8 +1,7 @@ import express from 'express' import bodyParser from 'body-parser' import Parser from 'rss-parser' -import { Feed } from 'feed' -import type { FeedOptions, Item } from 'feed/lib/typings' +import { generateRssFeed, generateJsonFeed } from 'feedsmith' import * as Sentry from '@sentry/node' import { RewriteFrames } from '@sentry/integrations' @@ -48,60 +47,104 @@ app.use(express.static(constants.publicPath)) app.use(bodyParser.urlencoded({ extended: false })) -async function getFullTextFeed(feedUrl: string, maxItemsPerFeed: number) { - const parser = new Parser() +interface FeedData { + title: string + description?: string + link: string + image?: string + items: Array<{ + title: string + link: string + date: Date + content?: string + description?: string + creator?: string + categories?: string[] + guid?: string + }> +} + +async function getFullTextFeed(feedUrl: string, maxItemsPerFeed: number): Promise<FeedData> { + const parser = new Parser<{}, { author?: string; id?: string }>() try { const feed = await parser.parseURL(feedUrl) - const feedOptions: FeedOptions = { - ...feed, - title: feed.title!, - description: feed.description, - link: feedUrl, - id: feedUrl, - image: feed.image?.url, - copyright: '', - } - const outputFeed = new Feed(feedOptions) - const newItems = await Promise.all((feed.items || []).filter(item => !!item.link).slice(0, maxItemsPerFeed).map(async item => { - const newItem: Item = { - ...item, - title: item.title!, - link: item.link!, - date: new Date(item.pubDate!), - } + const items = await Promise.all((feed.items || []).filter(item => !!item.link).slice(0, maxItemsPerFeed).map(async item => { let content: string | undefined = await cache.get(item.link!) if (!content) { content = (await parsePageUsingMercury(item.link!)).content await cache.set(item.link!, content) } - newItem.content = content - return newItem + return { + ...item, + title: item.title!, + link: item.link!, + date: new Date(item.pubDate || item.isoDate || Date.now()), + content, + description: item.contentSnippet || item.content, + creator: item.creator || item.author, + guid: item.guid || item.id, + } })) - for (const newItem of newItems) { - outputFeed.addItem(newItem) + + return { + title: feed.title!, + description: feed.description, + link: feedUrl, + image: feed.image?.url, + items, } - return outputFeed } catch (e) { if (constants.sentryDsn) { Sentry.captureException(e) } - const outputFeed = new Feed({ - id: `${feedUrl}-failed`, + return { title: `Failed to get fulltext rss for ${feedUrl}.`, - copyright: '', - }) - const errorItem: Item = { - title: `Failed to get fulltext rss for ${feedUrl}.`, - content: `Exception: ${e}`, - link: 'https://github.com/whtsky/fulltextrssplz/issues', - date: new Date(), + link: feedUrl, + items: [{ + title: `Failed to get fulltext rss for ${feedUrl}.`, + content: `Exception: ${e}`, + link: 'https://github.com/whtsky/fulltextrssplz/issues', + date: new Date(), + }], } - outputFeed.addItem(errorItem) - return outputFeed } } +function feedToRss(data: FeedData): string { + return generateRssFeed({ + ...data, + description: data.description || '', + image: data.image ? { url: data.image } : undefined, + items: data.items.map(item => ({ + ...item, + pubDate: item.date, + guid: item.guid ? { value: item.guid } : undefined, + dc: item.creator ? { creator: item.creator } : undefined, + content: item.content ? { encoded: item.content } : undefined, + categories: item.categories?.map(name => ({ name })), + })), + }, { lenient: true }) +} + +function feedToJson(data: FeedData): string { + return JSON.stringify(generateJsonFeed({ + title: data.title, + home_page_url: data.link, + description: data.description, + items: data.items.map(item => ({ + id: item.guid || item.link, + url: item.link, + title: item.title, + date_published: item.date, + content_html: item.content, + summary: item.description, + authors: item.creator ? [{ name: item.creator }] : undefined, + tags: item.categories, + })), + }, { lenient: true })) +} + app.get('/feed', async (req, res) => { const feedUrl = req.query.url @@ -145,7 +188,7 @@ app.get('/feed', async (req, res) => { } } - const outputFeed = await getFullTextFeed(feedUrl, maxItemsPerFeed) + const feedData = await getFullTextFeed(feedUrl, maxItemsPerFeed) if (constants.cacheControlMaxAge > 0) { res.set('Cache-control', `public, max-age=${constants.cacheControlMaxAge}`) @@ -153,10 +196,10 @@ app.get('/feed', async (req, res) => { if (format == Format.RSS) { res.set('Content-type', 'application/rss+xml;charset=UTF-8') - res.end(outputFeed.rss2()) + res.end(feedToRss(feedData)) } else if (format == Format.JSON) { res.set('Content-type', 'application/json;charset=UTF-8') - res.end(outputFeed.json1()) + res.end(feedToJson(feedData)) } else { res.end('unknown format:' + format) } diff --git a/yarn.lock b/yarn.lock index 3bcf60125..fe29f6a40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -657,15 +657,15 @@ dependencies: "@babel/runtime-corejs2" "^7.2.0" "@postlight/ci-failed-test-reporter" "^1.0" - browser-request "*" + browser-request "github:postlight/browser-request#feat-add-headers-to-response" cheerio "^0.22.0" difflib "github:postlight/difflib.js" ellipsize "0.1.0" iconv-lite "0.5.0" - jquery "*" + jquery "^3.5.0" moment "^2.23.0" moment-parseformat "3.0.0" - moment-timezone "*" + moment-timezone "0.5.37" postman-request "^2.88.1-postman.31" string-direction "^0.1.2" turndown "^7.1.1" @@ -1001,6 +1001,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.37.tgz#0bfcd173e8e1e328337473a8317e37b3b14fd30d" integrity sha512-7GgtHCs/QZrBrDzgIJnQtuSvhFSwhyYSI2uafSwZoNt1iOGhEN5fwNrQMjtONyHm9+/LoA4453jH0CMYcr06Pg== +"@types/node@^22": + version "22.19.15" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.19.15.tgz#6091d99fdf7c08cb57dc8b1345d407ba9a1df576" + integrity sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg== + dependencies: + undici-types "~6.21.0" + "@types/prettier@^2.1.5": version "2.7.2" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" @@ -1421,10 +1428,11 @@ brotli@^1.3.3: dependencies: base64-js "^1.1.2" -browser-request@*: - version "0.3.3" - resolved "https://registry.yarnpkg.com/browser-request/-/browser-request-0.3.3.tgz#9ece5b5aca89a29932242e18bf933def9876cc17" - integrity sha512-YyNI4qJJ+piQG6MMEuo7J3Bzaqssufx04zpEKYfSrl/1Op59HWali9zMtBpXnkmqMcOuWJPZvudrm9wISmnCbg== +"browser-request@github:postlight/browser-request#feat-add-headers-to-response": + version "0.3.2" + resolved "https://codeload.github.com/postlight/browser-request/tar.gz/38faa5b85741aabfca61aa37d1ef044d68969ddf" + dependencies: + http-headers "^3.0.1" browserslist@^4.0.0, browserslist@^4.21.3, browserslist@^4.21.4, browserslist@^4.21.5: version "4.21.5" @@ -2082,7 +2090,6 @@ diff@^4.0.1: "difflib@github:postlight/difflib.js": version "0.2.6" - uid "32e8e38c7fcd935241b9baab71bb432fd9b166ed" resolved "https://codeload.github.com/postlight/difflib.js/tar.gz/32e8e38c7fcd935241b9baab71bb432fd9b166ed" dependencies: heap ">= 0.2.0" @@ -2243,6 +2250,11 @@ entities@^2.0.0, entities@^2.0.3: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +entities@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-7.0.1.tgz#26e8a88889db63417dcb9a1e79a3f1bc92b5976b" + integrity sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -2455,6 +2467,21 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-sta resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-xml-builder@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz#0c407a1d9d5996336c0cd76f7ff785cac6413017" + integrity sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg== + dependencies: + path-expression-matcher "^1.1.3" + +fast-xml-parser@~5.4.2: + version "5.4.2" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.4.2.tgz#7fc66463b59260b0c5fd57edf46148a418bde68b" + integrity sha512-pw/6pIl4k0CSpElPEJhDppLzaixDEuWui2CUQQBH/ECDf7+y6YwA4Gf7Tyb0Rfe4DIMuZipYj4AEL0nACKglvQ== + dependencies: + fast-xml-builder "^1.0.0" + strnum "^2.1.2" + fastq@^1.6.0: version "1.15.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" @@ -2476,12 +2503,13 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -feed@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e" - integrity sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ== +feedsmith@^2.9.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/feedsmith/-/feedsmith-2.9.1.tgz#aeaf00984b7a6b530f125c7b2e2685108ac5ea9a" + integrity sha512-XsHf3lv+i3juaUjE4FHB56nKN+lwvjJTmH3ZMVO4yELaeHV/siLygDlmfJXJoMzP9JZC+zYZUhPEH+9tL3ijzQ== dependencies: - xml-js "^1.6.11" + entities "^7.0.1" + fast-xml-parser "~5.4.2" figures@^3.2.0: version "3.2.0" @@ -2780,6 +2808,13 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +http-headers@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/http-headers/-/http-headers-3.0.2.tgz#5147771292f0b39d6778d930a3a59a76fc7ef44d" + integrity sha512-87E1I+2Wg4dxxz4rcxElo3dxO/w1ZtgL1yA0Sb6vH3qU16vRKq1NjWQv9SCY3ly2OQROcoxHZOUpmelS+k6wOw== + dependencies: + next-line "^1.1.0" + http-signature@~1.3.1, http-signature@~1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" @@ -3442,10 +3477,10 @@ joi@^17.7.0: "@sideway/formula" "^3.0.1" "@sideway/pinpoint" "^2.0.0" -jquery@*: - version "3.6.4" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.4.tgz#ba065c188142100be4833699852bf7c24dc0252f" - integrity sha512-v28EW9DWDFpzcD9O5iyJXg3R3+q+mET5JhnjJzQUZMHOv67bpSIHq81GEYpPNZHG+XXHsfSme3nxp/hndKEcsQ== +jquery@^3.5.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de" + integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg== js-tokens@^4.0.0: version "4.0.0" @@ -3814,14 +3849,19 @@ moment-parseformat@3.0.0: resolved "https://registry.yarnpkg.com/moment-parseformat/-/moment-parseformat-3.0.0.tgz#3a1dc438b4bc073b7e93cc298cfb6c5daac26dba" integrity sha512-dVgXe6b6DLnv4CHG7a1zUe5mSXaIZ3c6lSHm/EKeVeQI2/4pwe0VRde8OyoCE1Ro2lKT5P6uT9JElF7KDLV+jw== -moment-timezone@*: - version "0.5.41" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.41.tgz#a7ad3285fd24aaf5f93b8119a9d749c8039c64c5" - integrity sha512-e0jGNZDOHfBXJGz8vR/sIMXvBIGJJcqFjmlg9lmE+5KX1U7/RZNMswfD8nKnNCnQdKTIj50IaRKwl1fvMLyyRg== +moment-timezone@0.5.37: + version "0.5.37" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.37.tgz#adf97f719c4e458fdb12e2b4e87b8bec9f4eef1e" + integrity sha512-uEDzDNFhfaywRl+vwXxffjjq1q0Vzr+fcQpQ1bU0kbzorfS7zVtZnCnGc8mhWmF39d4g4YriF6kwA75mJKE/Zg== dependencies: - moment "^2.29.4" + moment ">= 2.9.0" -moment@^2.23.0, moment@^2.29.4: +"moment@>= 2.9.0": + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + +moment@^2.23.0: version "2.29.4" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== @@ -3861,6 +3901,11 @@ neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +next-line@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-line/-/next-line-1.1.0.tgz#fcae57853052b6a9bae8208e40dd7d3c2d304603" + integrity sha512-+I10J3wKNoKddNxn0CNpoZ3eTZuqxjNM3b1GImVx22+ePI+Y15P8g/j3WsbP0fhzzrFzrtjOAoq5NCCucswXOQ== + node-fetch@^2.3.0: version "2.6.9" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6" @@ -4028,6 +4073,11 @@ path-exists@^4.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== +path-expression-matcher@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz#9bdae3787f43b0857b0269e9caaa586c12c8abee" + integrity sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -4770,7 +4820,7 @@ safe-identifier@^0.4.2: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sax@>=0.6.0, sax@^1.2.4: +sax@>=0.6.0: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -5049,6 +5099,11 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strnum@^2.1.2: + version "2.2.1" + resolved "https://registry.yarnpkg.com/strnum/-/strnum-2.2.1.tgz#d28f896b4ef9985212494ce8bcf7ca304fad8368" + integrity sha512-BwRvNd5/QoAtyW1na1y1LsJGQNvRlkde6Q/ipqqEaivoMdV+B1OMOTVdwR+N/cwVUcIt9PYyHmV8HyexCZSupg== + style-inject@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/style-inject/-/style-inject-0.3.0.tgz#d21c477affec91811cc82355832a700d22bf8dd3" @@ -5308,16 +5363,21 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typescript@^4.9.5: - version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" - integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +typescript@^5.0.0: + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== uglify-js@^3.1.4: version "3.19.3" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -5495,13 +5555,6 @@ wuzzy@^0.1.4: dependencies: lodash "^4.17.15" -xml-js@^1.6.11: - version "1.6.11" - resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" - integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== - dependencies: - sax "^1.2.4" - xml2js@^0.4.19: version "0.4.23" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"