From 99b81f7439c1ff14c8f02d6a6569251ba5c32d89 Mon Sep 17 00:00:00 2001 From: Brandon Keepers Date: Thu, 8 May 2025 10:47:10 -0400 Subject: [PATCH 1/4] Add appstore --- package-lock.json | 84 +++++++++++- package.json | 2 + src/assets/styles/tailwind.css | 2 +- src/components/appstore/Label.astro | 1 + src/components/appstore/ListItem.astro | 31 +++++ src/content/appstore.ts | 70 ++++++++++ src/content/config.ts | 2 + src/navigation.ts | 4 + src/pages/appstore/[...id].astro | 183 +++++++++++++++++++++++++ src/pages/appstore/index.astro | 31 +++++ src/utils/gravatar.ts | 14 ++ 11 files changed, 422 insertions(+), 2 deletions(-) create mode 100644 src/components/appstore/Label.astro create mode 100644 src/components/appstore/ListItem.astro create mode 100644 src/content/appstore.ts create mode 100644 src/pages/appstore/[...id].astro create mode 100644 src/pages/appstore/index.astro create mode 100644 src/utils/gravatar.ts diff --git a/package-lock.json b/package-lock.json index e2b53e28..f0c4254b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,8 @@ "astro-icon": "^1.1.5", "limax": "4.1.0", "lodash.merge": "^4.6.2", + "marked": "^15.0.11", + "sanitize-html": "^2.16.0", "unpic": "^4.1.2" }, "devDependencies": { @@ -5167,6 +5169,15 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/deepmerge-ts": { "version": "7.1.5", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", @@ -5634,7 +5645,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -7434,6 +7444,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -8032,6 +8051,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/marked": { + "version": "15.0.11", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.11.tgz", + "integrity": "sha512-1BEXAU2euRCG3xwgLVT1y0xbJEld1XOrmRJpUwRCcy7rxhSCwMrmEu9LXoPhHSCJG41V7YcQ2mjKRr5BA3ITIA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -9713,6 +9744,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", + "license": "MIT" + }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -10850,6 +10887,51 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/sanitize-html": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.16.0.tgz", + "integrity": "sha512-0s4caLuHHaZFVxFTG74oW91+j6vW7gKbGD6CD2+miP73CE6z6YtOBN0ArtLd2UGyi4IC7K47v3ENUbQX4jV3Mg==", + "license": "MIT", + "dependencies": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^8.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + } + }, + "node_modules/sanitize-html/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "node_modules/sass-formatter": { "version": "0.7.9", "resolved": "https://registry.npmjs.org/sass-formatter/-/sass-formatter-0.7.9.tgz", diff --git a/package.json b/package.json index 378df28f..9a3c3d1f 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "astro-icon": "^1.1.5", "limax": "4.1.0", "lodash.merge": "^4.6.2", + "marked": "^15.0.11", + "sanitize-html": "^2.16.0", "unpic": "^4.1.2" }, "devDependencies": { diff --git a/src/assets/styles/tailwind.css b/src/assets/styles/tailwind.css index d36f8fac..3851ce08 100644 --- a/src/assets/styles/tailwind.css +++ b/src/assets/styles/tailwind.css @@ -22,7 +22,7 @@ @layer components { .btn { - @apply inline-flex items-center justify-center rounded-full border-gray-400 border bg-transparent font-medium text-center text-base text-page leading-snug transition py-3.5 px-6 md:px-8 ease-in duration-200 focus:ring-blue-500 focus:ring-offset-blue-200 focus:ring-2 focus:ring-offset-2 hover:bg-gray-100 hover:border-gray-600 dark:text-slate-300 dark:border-slate-500 dark:hover:bg-slate-800 dark:hover:border-slate-800 cursor-pointer; + @apply inline-flex items-center justify-center rounded-full border-gray-400 border bg-transparent font-medium text-center text-base text-page leading-snug transition py-3 px-6 md:px-8 ease-in duration-200 focus:ring-blue-500 focus:ring-offset-blue-200 focus:ring-2 focus:ring-offset-2 hover:bg-gray-100 hover:border-gray-600 dark:text-slate-300 dark:border-slate-500 dark:hover:bg-slate-800 dark:hover:border-slate-800 cursor-pointer; } .btn-primary { diff --git a/src/components/appstore/Label.astro b/src/components/appstore/Label.astro new file mode 100644 index 00000000..137c5a00 --- /dev/null +++ b/src/components/appstore/Label.astro @@ -0,0 +1 @@ +

diff --git a/src/components/appstore/ListItem.astro b/src/components/appstore/ListItem.astro new file mode 100644 index 00000000..f48c8c16 --- /dev/null +++ b/src/components/appstore/ListItem.astro @@ -0,0 +1,31 @@ +--- +import { Icon } from 'astro-icon/components'; + +export interface Props { + text?: string; + icon?: string; + image?: string; + href?: string; +} + +const { + text = await Astro.slots.render('default'), + image = await Astro.slots.render('image'), + icon, + href, +} = Astro.props; + +const Element = href ? 'a' : 'div'; +--- + + + {icon && } + { + image && ( + + + + ) + } + + diff --git a/src/content/appstore.ts b/src/content/appstore.ts new file mode 100644 index 00000000..7e85bf8b --- /dev/null +++ b/src/content/appstore.ts @@ -0,0 +1,70 @@ +import { z, defineCollection } from 'astro:content'; + +export default defineCollection({ + schema: z.object({ + id: z.string(), + package: z.object({ + name: z.string(), + description: z.string(), + version: z.string(), + keywords: z.array(z.string()), + publisher: z.object({ + username: z.string(), + email: z.string(), + }), + maintainers: z.array( + z.object({ + username: z.string(), + email: z.string(), + }) + ), + license: z.string().optional(), + date: z.string().datetime(), + links: z.record(z.string(), z.string()) + }), + downloads: z.object({ + monthly: z.number(), + weekly: z.number(), + }), + updated: z.string(), + score: z.object({ + final: z.number(), + detail: z.object({ + popularity: z.number(), + quality: z.number(), + maintenance: z.number(), + }), + }), + flags: z.object({ + insecure: z.number(), + }) + }), + + // TODO: update to use content loader api + // https://docs.astro.build/en/reference/content-loader-reference/ + loader: async () => { + const keywords = [ + 'signalk-node-server-plugin', + 'signalk-embeddable-webapp', + 'signalk-webapp' + ]; + + // TODO paginate + const url = new URL('https://registry.npmjs.org/-/v1/search'); + url.search = new URLSearchParams({ + size: '250', + from: '0', + text: `keywords:${keywords[0]}` + }).toString(); + + const response = await fetch(url); + const data = await response.json(); + + const result = data.objects.map((item) => ({ + id: item.package.name, + ...item, + })); + + return result; + }, +}) diff --git a/src/content/config.ts b/src/content/config.ts index 88440f2d..3f560178 100644 --- a/src/content/config.ts +++ b/src/content/config.ts @@ -1,5 +1,6 @@ import { z, defineCollection } from 'astro:content'; import { glob } from 'astro/loaders'; +import appstore from './appstore'; const metadataDefinition = () => z @@ -67,4 +68,5 @@ const postCollection = defineCollection({ export const collections = { post: postCollection, + appstore, }; diff --git a/src/navigation.ts b/src/navigation.ts index 50faa39d..fd49bfb6 100644 --- a/src/navigation.ts +++ b/src/navigation.ts @@ -15,6 +15,10 @@ export const headerData = { target: '_blank', href: 'https://demo.signalk.org/documentation/', }, + { + text: "AppStore", + href: getPermalink('appstore'), + }, { text: 'Blog', href: getBlogPermalink(), diff --git a/src/pages/appstore/[...id].astro b/src/pages/appstore/[...id].astro new file mode 100644 index 00000000..117dde21 --- /dev/null +++ b/src/pages/appstore/[...id].astro @@ -0,0 +1,183 @@ +--- +import Tagline from '~/components/ui/Tagline.astro'; +import Button from '~/components/ui/Button.astro'; +import { Icon } from 'astro-icon/components'; +import PageLayout from '~/layouts/PageLayout.astro'; +import { getCollection } from 'astro:content'; +import { marked } from 'marked'; +import sanitize from 'sanitize-html'; +import gravatar from '~/utils/gravatar'; +import Label from '~/components/appstore/Label.astro'; +import ListItem from '~/components/appstore/ListItem.astro'; + +export async function getStaticPaths() { + const extensions = await getCollection('appstore'); + return extensions.map((extension) => ({ + params: { id: extension.id }, + props: extension, + })); +} + +function keywordsToTags(keywords) { + return keywords + .map((k) => k.replace(/^signalk(-category|-node-server)?-/, '')) + .filter((k) => k !== 'signalk') + .map((keyword) => ({ + name: keyword, + url: `/appstore/${keyword}`, + })); +} + +const LinkDefaults = { + homepage: { name: 'Website', icon: 'tabler:home' }, + repository: { name: 'Source', icon: 'tabler:code' }, + bugs: { name: 'Issues', icon: 'tabler:bug' }, + npm: { name: 'NPM', icon: 'tabler:package' }, +}; + +function expandLinks(links: Record) { + return Object.entries(links).map(([key, value]) => { + return { + ...LinkDefaults[key], + url: value, + }; + }); +} + +const { data } = Astro.props; + +const res = await fetch(`https://registry.npmjs.org/${Astro.params.id}`); +if (!res.ok) throw new Error(`Failed to fetch package data: ${res.statusText}`); +const remoteData = await res.json(); + +const readme = sanitize(await marked.parse(remoteData.readme)); +const pkg = remoteData.versions[data.package.version]; +--- + + +
+
+
+ + AppStore + +
+
+ { + pkg.signalk?.appIcon ? ( + {pkg.name} + ) : ( + + ) + } +
+ +
+

{pkg.signalk?.displayName ?? pkg.name}

+

+ {pkg.description} +

+ { + pkg.keywords && ( +
+ {keywordsToTags(pkg.keywords).map((keyword) => ( + + {keyword.name} + + ))} +
+ ) + } +
+
+
+
+
+
+
+
+ +
+
+
+
+ + +
+ + {data.package.version} + + {new Date(data.package.date).toLocaleString(undefined, { dateStyle: 'medium' })} + + +
+
+ +
+ + + {data.downloads.monthly} / month + +
+ +
+ +
+ { + expandLinks(data.package.links).map(({ name, icon, url }) => ( + + {name} + + )) + } +
+
+ +
+ + { + data.package.maintainers && ( +
+ {data.package.maintainers.map((maintainer) => ( + + + {maintainer.username} + + + ))} +
+ ) + } +
+ +
+ + {data.package.license} +
+
+
+
+
diff --git a/src/pages/appstore/index.astro b/src/pages/appstore/index.astro new file mode 100644 index 00000000..2cd2c543 --- /dev/null +++ b/src/pages/appstore/index.astro @@ -0,0 +1,31 @@ +--- +import Hero from '~/components/widgets/Hero.astro'; +import PageLayout from '~/layouts/PageLayout.astro'; +import { getCollection } from 'astro:content'; +const extensions = await getCollection('appstore'); +--- + + + + diff --git a/src/utils/gravatar.ts b/src/utils/gravatar.ts new file mode 100644 index 00000000..bb4d42fe --- /dev/null +++ b/src/utils/gravatar.ts @@ -0,0 +1,14 @@ +import { createHash } from 'crypto'; + +export default (email: string, options: Record = {}) => { + const hash = createHash('md5').update(email.trim().toLowerCase()).digest('hex'); + + const url = new URL('https://www.gravatar.com/avatar/' + hash); + url.search = new URLSearchParams({ + s: "50", + d: "identicon", + ...options, + }).toString(); + + return url.toString(); +} From 274d8391ce6b66ce78e4f1b1fe0794cb64583db5 Mon Sep 17 00:00:00 2001 From: Brandon Keepers Date: Thu, 8 May 2025 15:30:27 -0400 Subject: [PATCH 2/4] Improve npm data loader, cache npm responses --- package-lock.json | 645 +++++++++++++++++++++++++++++-- package.json | 4 + src/content/appstore.ts | 169 +++++--- src/pages/appstore/[...id].astro | 8 +- 4 files changed, 728 insertions(+), 98 deletions(-) diff --git a/package-lock.json b/package-lock.json index f0c4254b..dc67b7ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,9 @@ "astro-icon": "^1.1.5", "limax": "4.1.0", "lodash.merge": "^4.6.2", + "make-fetch-happen": "^14.0.3", "marked": "^15.0.11", + "p-map": "^7.0.3", "sanitize-html": "^2.16.0", "unpic": "^4.1.2" }, @@ -30,7 +32,9 @@ "@tailwindcss/typography": "^0.5.16", "@types/js-yaml": "^4.0.9", "@types/lodash.merge": "^4.6.9", + "@types/make-fetch-happen": "^10.0.4", "@types/mdx": "^2.0.13", + "@types/sanitize-html": "^2.16.0", "@typescript-eslint/eslint-plugin": "^8.30.1", "@typescript-eslint/parser": "^8.30.1", "astro-compress": "2.3.8", @@ -1793,7 +1797,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -1811,14 +1814,12 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -1836,7 +1837,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -1850,6 +1850,27 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@isaacs/fs-minipass/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", @@ -2010,6 +2031,34 @@ "node": ">= 8" } }, + "node_modules/@npmcli/agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/@oslojs/encoding": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", @@ -2020,7 +2069,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -2555,6 +2603,18 @@ "@types/lodash": "*" } }, + "node_modules/@types/make-fetch-happen": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@types/make-fetch-happen/-/make-fetch-happen-10.0.4.tgz", + "integrity": "sha512-jKzweQaEMMAi55ehvR1z0JF6aSVQm/h1BXBhPLOJriaeQBctjw5YbpIGs7zAx9dN0Sa2OO5bcXwCkrlgenoPEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node-fetch": "*", + "@types/retry": "*", + "@types/ssri": "*" + } + }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", @@ -2595,6 +2655,67 @@ "undici-types": "~6.20.0" } }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz", + "integrity": "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sanitize-html": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.16.0.tgz", + "integrity": "sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "htmlparser2": "^8.0.0" + } + }, + "node_modules/@types/sanitize-html/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@types/sanitize-html/node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "node_modules/@types/sax": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", @@ -2604,6 +2725,16 @@ "@types/node": "*" } }, + "node_modules/@types/ssri": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@types/ssri/-/ssri-7.1.5.tgz", + "integrity": "sha512-odD/56S3B51liILSk5aXJlnYt99S6Rt9EFDDqGtJM26rKHApHcwyU/UoYHrzKkdkHMAIquGWCuHtQTbes+FRQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/tar": { "version": "6.1.13", "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.13.tgz", @@ -2963,6 +3094,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -4258,7 +4398,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/base-64": { @@ -4373,7 +4512,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4466,6 +4604,112 @@ "node": ">= 0.8" } }, + "node_modules/cacache": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/cacache/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cacache/node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/cacache/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/cacache/node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/cacache/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cacache/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -4880,7 +5124,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -4893,7 +5136,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, "license": "MIT" }, "node_modules/color-string": { @@ -5033,7 +5275,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -5415,7 +5656,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, "license": "MIT" }, "node_modules/ee-first": { @@ -5465,6 +5705,16 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, "node_modules/encoding-sniffer": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", @@ -5499,6 +5749,12 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT" + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -6459,7 +6715,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -6673,7 +6928,6 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -6707,7 +6961,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -7181,6 +7434,32 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -7234,7 +7513,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -7254,6 +7532,19 @@ "dev": true, "license": "MIT" }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -7479,7 +7770,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/iso-datestring-validator": { @@ -7492,7 +7782,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -7526,6 +7815,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -8028,6 +8323,37 @@ "source-map-js": "^1.2.0" } }, + "node_modules/make-fetch-happen": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/markdown-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", @@ -9207,7 +9533,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -9228,6 +9553,137 @@ "node": ">=8" } }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch/node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", @@ -9352,7 +9808,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -9628,6 +10083,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-queue": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.0.tgz", @@ -9660,7 +10127,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/package-manager-detector": { @@ -9829,7 +10295,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9846,7 +10311,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -9863,7 +10327,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -10175,6 +10638,28 @@ "node": ">=6" } }, + "node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -10762,6 +11247,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -11051,7 +11545,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -11064,7 +11557,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11166,7 +11658,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -11216,6 +11707,16 @@ "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", "license": "MIT" }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/smol-toml": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.4.tgz", @@ -11228,6 +11729,34 @@ "url": "https://github.com/sponsors/cyyynthia" } }, + "node_modules/socks": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", + "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -11287,6 +11816,33 @@ "node": ">=0.10.0" } }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, + "node_modules/ssri": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/ssri/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -11325,7 +11881,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -11340,7 +11895,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11350,14 +11904,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -11400,7 +11952,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -11413,7 +11964,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12152,6 +12702,30 @@ "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", "license": "CC0-1.0" }, + "node_modules/unique-filename": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/unique-slug": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/unist-util-find-after": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", @@ -12917,7 +13491,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -12985,7 +13558,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -13003,7 +13575,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -13013,7 +13584,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -13029,14 +13599,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -13051,7 +13619,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" diff --git a/package.json b/package.json index 9a3c3d1f..59911e67 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,9 @@ "astro-icon": "^1.1.5", "limax": "4.1.0", "lodash.merge": "^4.6.2", + "make-fetch-happen": "^14.0.3", "marked": "^15.0.11", + "p-map": "^7.0.3", "sanitize-html": "^2.16.0", "unpic": "^4.1.2" }, @@ -45,7 +47,9 @@ "@tailwindcss/typography": "^0.5.16", "@types/js-yaml": "^4.0.9", "@types/lodash.merge": "^4.6.9", + "@types/make-fetch-happen": "^10.0.4", "@types/mdx": "^2.0.13", + "@types/sanitize-html": "^2.16.0", "@typescript-eslint/eslint-plugin": "^8.30.1", "@typescript-eslint/parser": "^8.30.1", "astro-compress": "2.3.8", diff --git a/src/content/appstore.ts b/src/content/appstore.ts index 7e85bf8b..dfab70b5 100644 --- a/src/content/appstore.ts +++ b/src/content/appstore.ts @@ -1,70 +1,133 @@ import { z, defineCollection } from 'astro:content'; +import doFetch from 'make-fetch-happen'; +import { join } from 'node:path'; +import type { LoaderContext } from 'astro/loaders'; +import pMap from 'p-map'; -export default defineCollection({ - schema: z.object({ - id: z.string(), - package: z.object({ - name: z.string(), - description: z.string(), - version: z.string(), - keywords: z.array(z.string()), - publisher: z.object({ +const cachePath = join(import.meta.dirname, '../../node_modules/.cache/fetch') + +export const fetch = doFetch.defaults({ + cachePath, +}); + +const schema = z.object({ + package: z.object({ + name: z.string(), + description: z.string().optional(), + version: z.string(), + keywords: z.array(z.string()), + publisher: z.object({ + username: z.string(), + email: z.string(), + }), + maintainers: z.array( + z.object({ username: z.string(), email: z.string(), - }), - maintainers: z.array( - z.object({ - username: z.string(), - email: z.string(), - }) - ), - license: z.string().optional(), - date: z.string().datetime(), - links: z.record(z.string(), z.string()) - }), - downloads: z.object({ - monthly: z.number(), - weekly: z.number(), - }), - updated: z.string(), - score: z.object({ - final: z.number(), - detail: z.object({ - popularity: z.number(), - quality: z.number(), - maintenance: z.number(), - }), + }) + ), + license: z.string().optional(), + date: z.string().datetime(), + links: z.record(z.string(), z.string()) + }), + downloads: z.object({ + monthly: z.number(), + weekly: z.number(), + }), + updated: z.string(), + score: z.object({ + final: z.number(), + detail: z.object({ + popularity: z.number(), + quality: z.number(), + maintenance: z.number(), }), - flags: z.object({ - insecure: z.number(), - }) }), + flags: z.object({ + insecure: z.number(), + }), + readme: z.string(), + versions: z.record( + z.string(), + z.object({ + name: z.string(), + version: z.string(), + description: z.string().optional(), + keywords: z.array(z.string()), + signalk: z.object({ + appIcon: z.string().optional(), + displayName: z.string().optional(), + }) + }) + ) +}); + +export type Module = z.infer; - // TODO: update to use content loader api - // https://docs.astro.build/en/reference/content-loader-reference/ - loader: async () => { - const keywords = [ +export default defineCollection({ + schema, + loader: npmLoader({ + keywords: [ 'signalk-node-server-plugin', 'signalk-embeddable-webapp', 'signalk-webapp' - ]; + ] + }) +}) + +export function npmLoader({ keywords = [], concurrency = 10 }: { keywords?: string[], concurrency?: number } = {}) { + return { + name: 'npm', + load: async ({ store, logger, meta }: LoaderContext): Promise => { + logger.info(`Loading npm modules with keywords: ${keywords.join(', ')}`); - // TODO paginate + await pMap(keywords, (async (keyword) => { + const modules = await fetchModulesByKeyword(keyword); + await pMap(modules, (async (module) => { + const id = module.package.name; + + logger.debug(`Fetching ${module.package.name}`); + const res = await fetch(`https://registry.npmjs.org/${id}`); + if (!res.ok) throw new Error(`Failed to fetch package data: ${res.statusText}`); + const data = await res.json(); + + meta.set("lastModified", module.package.date) + + store.set({ + id: module.package.name, + data: { + ...module, + ...data + }, + }) + }), { concurrency }); + }), { concurrency }); + } + } +} + +async function fetchModulesByKeyword(keyword: string): Promise { + const items: Module[] = [] + const size = 250; + + while (true) { const url = new URL('https://registry.npmjs.org/-/v1/search'); url.search = new URLSearchParams({ - size: '250', - from: '0', - text: `keywords:${keywords[0]}` + size: size.toString(), + from: items.length.toString(), + text: `keywords:${keyword}` }).toString(); - const response = await fetch(url); - const data = await response.json(); + const response = await fetch(url.toString()); + if (!response.ok) throw new Error(`Failed to fetch modules: ${response.statusText}`); + const data = await response.json() as { objects: Module[], total: number }; - const result = data.objects.map((item) => ({ - id: item.package.name, - ...item, - })); + items.push(...data.objects); - return result; - }, -}) + if (data.objects.length < 250) { + break; + } + } + + return items; +} diff --git a/src/pages/appstore/[...id].astro b/src/pages/appstore/[...id].astro index 117dde21..68cafcb0 100644 --- a/src/pages/appstore/[...id].astro +++ b/src/pages/appstore/[...id].astro @@ -46,12 +46,8 @@ function expandLinks(links: Record) { const { data } = Astro.props; -const res = await fetch(`https://registry.npmjs.org/${Astro.params.id}`); -if (!res.ok) throw new Error(`Failed to fetch package data: ${res.statusText}`); -const remoteData = await res.json(); - -const readme = sanitize(await marked.parse(remoteData.readme)); -const pkg = remoteData.versions[data.package.version]; +const readme = sanitize(await marked.parse(data.readme)); +const pkg = data.versions[data.package.version]; --- From 5cf9c912ff519679a51b13971edf1b33d06755e4 Mon Sep 17 00:00:00 2001 From: Brandon Keepers Date: Thu, 8 May 2025 15:53:31 -0400 Subject: [PATCH 3/4] Fix lint errors --- src/components/appstore/ListItem.astro | 6 +- src/content/appstore.ts | 80 ++++++++++++++------------ src/navigation.ts | 2 +- src/pages/appstore/[...id].astro | 2 +- src/utils/gravatar.ts | 6 +- 5 files changed, 52 insertions(+), 44 deletions(-) diff --git a/src/components/appstore/ListItem.astro b/src/components/appstore/ListItem.astro index f48c8c16..3a0d1f51 100644 --- a/src/components/appstore/ListItem.astro +++ b/src/components/appstore/ListItem.astro @@ -6,6 +6,8 @@ export interface Props { icon?: string; image?: string; href?: string; + target?: string; + rel?: string; } const { @@ -13,12 +15,14 @@ const { image = await Astro.slots.render('image'), icon, href, + target, + rel, } = Astro.props; const Element = href ? 'a' : 'div'; --- - + {icon && } { image && ( diff --git a/src/content/appstore.ts b/src/content/appstore.ts index dfab70b5..f5de47d7 100644 --- a/src/content/appstore.ts +++ b/src/content/appstore.ts @@ -4,7 +4,7 @@ import { join } from 'node:path'; import type { LoaderContext } from 'astro/loaders'; import pMap from 'p-map'; -const cachePath = join(import.meta.dirname, '../../node_modules/.cache/fetch') +const cachePath = join(import.meta.dirname, '../../node_modules/.cache/fetch'); export const fetch = doFetch.defaults({ cachePath, @@ -28,7 +28,7 @@ const schema = z.object({ ), license: z.string().optional(), date: z.string().datetime(), - links: z.record(z.string(), z.string()) + links: z.record(z.string(), z.string()), }), downloads: z.object({ monthly: z.number(), @@ -57,9 +57,9 @@ const schema = z.object({ signalk: z.object({ appIcon: z.string().optional(), displayName: z.string().optional(), - }) + }), }) - ) + ), }); export type Module = z.infer; @@ -67,47 +67,51 @@ export type Module = z.infer; export default defineCollection({ schema, loader: npmLoader({ - keywords: [ - 'signalk-node-server-plugin', - 'signalk-embeddable-webapp', - 'signalk-webapp' - ] - }) -}) - -export function npmLoader({ keywords = [], concurrency = 10 }: { keywords?: string[], concurrency?: number } = {}) { + keywords: ['signalk-node-server-plugin', 'signalk-embeddable-webapp', 'signalk-webapp'], + }), +}); + +export function npmLoader({ keywords = [], concurrency = 10 }: { keywords?: string[]; concurrency?: number } = {}) { return { name: 'npm', load: async ({ store, logger, meta }: LoaderContext): Promise => { logger.info(`Loading npm modules with keywords: ${keywords.join(', ')}`); - await pMap(keywords, (async (keyword) => { - const modules = await fetchModulesByKeyword(keyword); - await pMap(modules, (async (module) => { - const id = module.package.name; - - logger.debug(`Fetching ${module.package.name}`); - const res = await fetch(`https://registry.npmjs.org/${id}`); - if (!res.ok) throw new Error(`Failed to fetch package data: ${res.statusText}`); - const data = await res.json(); - - meta.set("lastModified", module.package.date) - - store.set({ - id: module.package.name, - data: { - ...module, - ...data + await pMap( + keywords, + async (keyword) => { + const modules = await fetchModulesByKeyword(keyword); + await pMap( + modules, + async (module) => { + const id = module.package.name; + + logger.debug(`Fetching ${module.package.name}`); + const res = await fetch(`https://registry.npmjs.org/${id}`); + if (!res.ok) throw new Error(`Failed to fetch package data: ${res.statusText}`); + const data = await res.json(); + + meta.set('lastModified', module.package.date); + + store.set({ + id: module.package.name, + data: { + ...module, + ...data, + }, + }); }, - }) - }), { concurrency }); - }), { concurrency }); - } - } + { concurrency } + ); + }, + { concurrency } + ); + }, + }; } async function fetchModulesByKeyword(keyword: string): Promise { - const items: Module[] = [] + const items: Module[] = []; const size = 250; while (true) { @@ -115,12 +119,12 @@ async function fetchModulesByKeyword(keyword: string): Promise { url.search = new URLSearchParams({ size: size.toString(), from: items.length.toString(), - text: `keywords:${keyword}` + text: `keywords:${keyword}`, }).toString(); const response = await fetch(url.toString()); if (!response.ok) throw new Error(`Failed to fetch modules: ${response.statusText}`); - const data = await response.json() as { objects: Module[], total: number }; + const data = (await response.json()) as { objects: Module[]; total: number }; items.push(...data.objects); diff --git a/src/navigation.ts b/src/navigation.ts index fd49bfb6..2efa9d11 100644 --- a/src/navigation.ts +++ b/src/navigation.ts @@ -16,7 +16,7 @@ export const headerData = { href: 'https://demo.signalk.org/documentation/', }, { - text: "AppStore", + text: 'AppStore', href: getPermalink('appstore'), }, { diff --git a/src/pages/appstore/[...id].astro b/src/pages/appstore/[...id].astro index 68cafcb0..e7d1ce63 100644 --- a/src/pages/appstore/[...id].astro +++ b/src/pages/appstore/[...id].astro @@ -18,7 +18,7 @@ export async function getStaticPaths() { })); } -function keywordsToTags(keywords) { +function keywordsToTags(keywords: string[]) { return keywords .map((k) => k.replace(/^signalk(-category|-node-server)?-/, '')) .filter((k) => k !== 'signalk') diff --git a/src/utils/gravatar.ts b/src/utils/gravatar.ts index bb4d42fe..0c475a12 100644 --- a/src/utils/gravatar.ts +++ b/src/utils/gravatar.ts @@ -5,10 +5,10 @@ export default (email: string, options: Record = {}) => { const url = new URL('https://www.gravatar.com/avatar/' + hash); url.search = new URLSearchParams({ - s: "50", - d: "identicon", + s: '50', + d: 'identicon', ...options, }).toString(); return url.toString(); -} +}; From ad8f4894e3e3805ebdb8ef19b7c4b6c0505a3777 Mon Sep 17 00:00:00 2001 From: Brandon Keepers Date: Wed, 14 May 2025 10:59:07 -0400 Subject: [PATCH 4/4] WIP: index --- src/components/appstore/AppIcon.astro | 27 +++++++++ src/components/appstore/AppItem.astro | 14 +++++ src/content/appstore.ts | 80 +++++++++++++-------------- src/pages/appstore/[...id].astro | 40 +++++--------- src/pages/appstore/index.astro | 26 +++------ 5 files changed, 101 insertions(+), 86 deletions(-) create mode 100644 src/components/appstore/AppIcon.astro create mode 100644 src/components/appstore/AppItem.astro diff --git a/src/components/appstore/AppIcon.astro b/src/components/appstore/AppIcon.astro new file mode 100644 index 00000000..b5172cb1 --- /dev/null +++ b/src/components/appstore/AppIcon.astro @@ -0,0 +1,27 @@ +--- +import { Image } from 'astro:assets'; +import { Icon } from 'astro-icon/components'; + +export interface Props { + name: string; + path?: string; + version?: string; +} + +const { path, name, version = 'latest' } = Astro.props; +--- + +{ + path ? ( + {name} + ) : ( + + ) +} diff --git a/src/components/appstore/AppItem.astro b/src/components/appstore/AppItem.astro new file mode 100644 index 00000000..f271f4e0 --- /dev/null +++ b/src/components/appstore/AppItem.astro @@ -0,0 +1,14 @@ +--- +import AppIcon from './AppIcon.astro'; +import type { Module } from '../../content/appstore'; + +export type Props = Module; +const module = Astro.props; +--- + + + +

{module.name}

+

{module.description}

+ {/*
{JSON.stringify(module, null, 2)}
*/} +
diff --git a/src/content/appstore.ts b/src/content/appstore.ts index f5de47d7..92cf3498 100644 --- a/src/content/appstore.ts +++ b/src/content/appstore.ts @@ -11,25 +11,23 @@ export const fetch = doFetch.defaults({ }); const schema = z.object({ - package: z.object({ - name: z.string(), - description: z.string().optional(), - version: z.string(), - keywords: z.array(z.string()), - publisher: z.object({ + name: z.string(), + description: z.string().optional(), + version: z.string(), + keywords: z.array(z.string()), + publisher: z.object({ + username: z.string(), + email: z.string(), + }), + maintainers: z.array( + z.object({ username: z.string(), email: z.string(), - }), - maintainers: z.array( - z.object({ - username: z.string(), - email: z.string(), - }) - ), - license: z.string().optional(), - date: z.string().datetime(), - links: z.record(z.string(), z.string()), - }), + }) + ), + license: z.string().optional(), + date: z.string().datetime(), + links: z.record(z.string(), z.string()), downloads: z.object({ monthly: z.number(), weekly: z.number(), @@ -47,19 +45,10 @@ const schema = z.object({ insecure: z.number(), }), readme: z.string(), - versions: z.record( - z.string(), - z.object({ - name: z.string(), - version: z.string(), - description: z.string().optional(), - keywords: z.array(z.string()), - signalk: z.object({ - appIcon: z.string().optional(), - displayName: z.string().optional(), - }), - }) - ), + signalk: z.object({ + appIcon: z.string().optional(), + displayName: z.string().optional(), + }).optional(), }); export type Module = z.infer; @@ -74,7 +63,7 @@ export default defineCollection({ export function npmLoader({ keywords = [], concurrency = 10 }: { keywords?: string[]; concurrency?: number } = {}) { return { name: 'npm', - load: async ({ store, logger, meta }: LoaderContext): Promise => { + load: async ({ store, logger, meta, parseData }: LoaderContext): Promise => { logger.info(`Loading npm modules with keywords: ${keywords.join(', ')}`); await pMap( @@ -83,18 +72,28 @@ export function npmLoader({ keywords = [], concurrency = 10 }: { keywords?: stri const modules = await fetchModulesByKeyword(keyword); await pMap( modules, - async (module) => { - const id = module.package.name; + async ({ package: pkg, ...module }) => { + const id = pkg.name; - logger.debug(`Fetching ${module.package.name}`); + logger.debug(`Fetching ${id}`); const res = await fetch(`https://registry.npmjs.org/${id}`); if (!res.ok) throw new Error(`Failed to fetch package data: ${res.statusText}`); - const data = await res.json(); + const { readme, versions } = await res.json(); + + const data = await parseData({ + id, + data: { + ...module, + ...versions[pkg.version], + ...pkg, + readme, + } + }) - meta.set('lastModified', module.package.date); + meta.set('lastModified', data.date); store.set({ - id: module.package.name, + id, data: { ...module, ...data, @@ -110,8 +109,9 @@ export function npmLoader({ keywords = [], concurrency = 10 }: { keywords?: stri }; } -async function fetchModulesByKeyword(keyword: string): Promise { - const items: Module[] = []; +async function fetchModulesByKeyword(keyword: string) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const items: any[] = []; const size = 250; while (true) { @@ -124,7 +124,7 @@ async function fetchModulesByKeyword(keyword: string): Promise { const response = await fetch(url.toString()); if (!response.ok) throw new Error(`Failed to fetch modules: ${response.statusText}`); - const data = (await response.json()) as { objects: Module[]; total: number }; + const data = await response.json(); items.push(...data.objects); diff --git a/src/pages/appstore/[...id].astro b/src/pages/appstore/[...id].astro index e7d1ce63..e0d22fe5 100644 --- a/src/pages/appstore/[...id].astro +++ b/src/pages/appstore/[...id].astro @@ -1,12 +1,12 @@ --- import Tagline from '~/components/ui/Tagline.astro'; import Button from '~/components/ui/Button.astro'; -import { Icon } from 'astro-icon/components'; import PageLayout from '~/layouts/PageLayout.astro'; import { getCollection } from 'astro:content'; import { marked } from 'marked'; import sanitize from 'sanitize-html'; import gravatar from '~/utils/gravatar'; +import AppIcon from '~/components/appstore/AppIcon.astro'; import Label from '~/components/appstore/Label.astro'; import ListItem from '~/components/appstore/ListItem.astro'; @@ -47,7 +47,6 @@ function expandLinks(links: Record) { const { data } = Astro.props; const readme = sanitize(await marked.parse(data.readme)); -const pkg = data.versions[data.package.version]; --- @@ -59,31 +58,18 @@ const pkg = data.versions[data.package.version];
- { - pkg.signalk?.appIcon ? ( - {pkg.name} - ) : ( - - ) - } +
-

{pkg.signalk?.displayName ?? pkg.name}

+

{data.signalk?.displayName ?? data.name}

- {pkg.description} + {data.description}

{ - pkg.keywords && ( + data.keywords && ( @@ -127,7 +113,7 @@ const pkg = data.versions[data.package.version]; @@ -139,7 +125,7 @@ const pkg = data.versions[data.package.version];
{ - expandLinks(data.package.links).map(({ name, icon, url }) => ( + expandLinks(data.links).map(({ name, icon, url }) => ( {name} @@ -151,9 +137,9 @@ const pkg = data.versions[data.package.version];
{ - data.package.maintainers && ( + data.maintainers && (
- {data.package.maintainers.map((maintainer) => ( + {data.maintainers.map((maintainer) => ( - {data.package.license} + {data.license}
diff --git a/src/pages/appstore/index.astro b/src/pages/appstore/index.astro index 2cd2c543..02ef5062 100644 --- a/src/pages/appstore/index.astro +++ b/src/pages/appstore/index.astro @@ -2,30 +2,18 @@ import Hero from '~/components/widgets/Hero.astro'; import PageLayout from '~/layouts/PageLayout.astro'; import { getCollection } from 'astro:content'; -const extensions = await getCollection('appstore'); +import AppItem from '~/components/appstore/AppItem.astro'; + +const modules = await getCollection('appstore'); --- -