From e4ddb2326dba2757146345ae9761e0a52f5746a3 Mon Sep 17 00:00:00 2001 From: HiDeoo <494699+HiDeoo@users.noreply.github.com> Date: Sat, 11 May 2024 17:02:24 +0200 Subject: [PATCH] feat: adds an RSS feed to the generated blog --- docs/astro.config.mjs | 1 + docs/src/content/docs/blog/vario-nunc-polo.md | 7 +- packages/starlight-blog/index.ts | 39 ++++++++++- packages/starlight-blog/libs/i18n.ts | 15 +++++ packages/starlight-blog/libs/markdown.ts | 17 +++++ packages/starlight-blog/libs/page.ts | 4 +- packages/starlight-blog/libs/rss.ts | 64 +++++++++++++++++++ packages/starlight-blog/libs/vite.ts | 10 ++- .../starlight-blog/overrides/Sidebar.astro | 12 ++++ packages/starlight-blog/package.json | 7 +- packages/starlight-blog/routes/rss.xml.ts | 12 ++++ .../starlight-blog/tests/e2e/blog.test.ts | 9 +++ .../tests/unit/basics/i18n.test.ts | 59 +++++++++++++++++ .../tests/unit/basics/rss.test.ts | 57 +++++++++++++++++ .../tests/unit/basics/vitest.config.ts | 9 ++- packages/starlight-blog/tests/unit/test.ts | 7 +- packages/starlight-blog/tests/unit/utils.ts | 2 +- pnpm-lock.yaml | 56 +++++++++++++--- 18 files changed, 364 insertions(+), 23 deletions(-) create mode 100644 packages/starlight-blog/libs/i18n.ts create mode 100644 packages/starlight-blog/libs/markdown.ts create mode 100644 packages/starlight-blog/libs/rss.ts create mode 100644 packages/starlight-blog/routes/rss.xml.ts create mode 100644 packages/starlight-blog/tests/unit/basics/i18n.test.ts create mode 100644 packages/starlight-blog/tests/unit/basics/rss.test.ts diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 57bc1aa..43a34b2 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -44,4 +44,5 @@ export default defineConfig({ title: 'Starlight Blog', }), ], + site: 'https://starlight-blog-docs.vercel.app', }) diff --git a/docs/src/content/docs/blog/vario-nunc-polo.md b/docs/src/content/docs/blog/vario-nunc-polo.md index ad67457..a4d7db8 100644 --- a/docs/src/content/docs/blog/vario-nunc-polo.md +++ b/docs/src/content/docs/blog/vario-nunc-polo.md @@ -1,7 +1,6 @@ --- title: Vario nunc polo date: 2020-12-12 -excerpt: Cras placerat sollicitudin purus, at gravida ligula tincidunt vel. Integer blandit placerat enim, eget suscipit erat venenatis vitae. Nullam et enim egestas, posuere ex id, tempus nibh. Nullam consequat, magna id consequat porttitor, sapien ex vestibulum nisi, vitae vestibulum velit. tags: - Placeholder authors: @@ -11,13 +10,13 @@ authors: - hideoo --- -## Canes plura palmas eodem huc scelerate spectat +### Canes plura palmas eodem huc scelerate spectat Lorem markdownum, numen adducere Parthaoniae versa mirabere mea. Magnum utque, et senserat ferendum disiectisque sub coniunx et stolidi telum; duabus. > Crebri pantherarum talis sed etiamnum habet: modo transire. Cum Xanthos rura per iter erat tunc **insanos tibi** ignorant cum inde fulgebant, dicentem certamina **temptabat iam inpleratque**. Enim figuram aliter, semper curru mihi est. -## Notae esse et gurgite sibi dat dona +### Notae esse et gurgite sibi dat dona Tangi silentibus argento; laetis tu gratus iuveni ut iuvenum latos saepe, _deum_ pius similis munere inmansuetusque. Fine defessa. In natum luminis notat terra pulcherrima nomen falsi magis! Anguipedum armenta minoribus _tolluntur_ fecerit instanti quid iactantem haesit exaudire sanguineam, delapsa **fores exegi serpente** liquida; de. @@ -27,7 +26,7 @@ Tangi silentibus argento; laetis tu gratus iuveni ut iuvenum latos saepe, _deum_ 4. Quod longique parce pennisque saepe 5. Conrepta pendentia tua quod Deianira -## Manibusque Mnemosynen nam aut accipit illa +### Manibusque Mnemosynen nam aut accipit illa Aiacis regisque roganti descendere videat ornat fratrum reperire adulterio genetrix. Erit vota _tempore aram quoque_ ille sceleri mittit saxificos deferre, illa inque animi renovare Oetaeus vertice, prius regia. Hippomene curis, pudorque potestas bene, dis deus mota hominum praestate. Et tibi Ixione tantum haec venata: nihil calentibus, ferox, illo corpore Aeson. Contra ab mora ne ventrem admirantibus longam summas cunis inundet? diff --git a/packages/starlight-blog/index.ts b/packages/starlight-blog/index.ts index 7d06294..5537e3b 100644 --- a/packages/starlight-blog/index.ts +++ b/packages/starlight-blog/index.ts @@ -2,6 +2,8 @@ import type { StarlightPlugin, StarlightUserConfig } from '@astrojs/starlight/ty import type { AstroIntegrationLogger } from 'astro' import { type StarlightBlogConfig, validateConfig, type StarlightBlogUserConfig } from './libs/config' +import { getDefaultLocale } from './libs/i18n' +import { stripLeadingSlash, stripTrailingSlash } from './libs/path' import { vitePluginStarlightBlogConfig } from './libs/vite' export type { StarlightBlogConfig, StarlightBlogUserConfig } @@ -20,6 +22,24 @@ export default function starlightBlogPlugin(userConfig?: StarlightBlogUserConfig ...overrideStarlightComponent(starlightConfig.components, logger, 'Sidebar'), ...overrideStarlightComponent(starlightConfig.components, logger, 'ThemeSelect'), }, + head: [ + ...(starlightConfig.head ?? []), + ...(astroConfig.site + ? [ + { + tag: 'link' as const, + attrs: { + href: `${stripTrailingSlash(astroConfig.site)}${stripTrailingSlash( + astroConfig.base, + )}/${stripLeadingSlash(stripTrailingSlash(config.prefix))}/rss.xml`, + rel: 'alternate', + title: config.title, + type: 'application/rss+xml', + }, + }, + ] + : []), + ], }) addIntegration({ @@ -38,9 +58,26 @@ export default function starlightBlogPlugin(userConfig?: StarlightBlogUserConfig prerender: true, }) + if (astroConfig.site) { + injectRoute({ + entrypoint: 'starlight-blog/routes/rss', + pattern: '/[prefix]/rss.xml', + prerender: true, + }) + } + updateConfig({ vite: { - plugins: [vitePluginStarlightBlogConfig(config, astroConfig)], + plugins: [ + vitePluginStarlightBlogConfig(config, { + defaultLocale: getDefaultLocale(starlightConfig as StarlightUserConfig), + description: starlightConfig.description, + site: astroConfig.site, + title: starlightConfig.title, + titleDelimiter: starlightConfig.titleDelimiter, + trailingSlash: astroConfig.trailingSlash, + }), + ], }, }) }, diff --git a/packages/starlight-blog/libs/i18n.ts b/packages/starlight-blog/libs/i18n.ts new file mode 100644 index 0000000..fdf3f17 --- /dev/null +++ b/packages/starlight-blog/libs/i18n.ts @@ -0,0 +1,15 @@ +import type { StarlightUserConfig } from '@astrojs/starlight/types' + +export function getDefaultLocale(starlightConfig: StarlightUserConfig) { + let defaultLocale = 'en' + const defaultLocaleKey = starlightConfig.defaultLocale + + if (starlightConfig.locales) { + defaultLocale = + defaultLocaleKey && defaultLocaleKey !== 'root' + ? starlightConfig.locales[defaultLocaleKey]?.lang ?? defaultLocaleKey + : starlightConfig.locales['root']?.lang ?? defaultLocale + } + + return defaultLocale +} diff --git a/packages/starlight-blog/libs/markdown.ts b/packages/starlight-blog/libs/markdown.ts new file mode 100644 index 0000000..6c373f1 --- /dev/null +++ b/packages/starlight-blog/libs/markdown.ts @@ -0,0 +1,17 @@ +import { Marked } from 'marked' +import markedPlaintify from 'marked-plaintify' +import { transform } from 'ultrahtml' +import sanitize from 'ultrahtml/transformers/sanitize' + +const markedMd = new Marked({ gfm: true }) +const markedText = new Marked({ gfm: true }, markedPlaintify()) + +export async function stripMarkdown(markdown: string) { + return await markedText.parse(markdown) +} + +export async function renderMarkdownToHTML(markdown: string) { + const content = await markedMd.parse(markdown) + + return transform(content, [sanitize()]) +} diff --git a/packages/starlight-blog/libs/page.ts b/packages/starlight-blog/libs/page.ts index 43f8ecc..2ac1b53 100644 --- a/packages/starlight-blog/libs/page.ts +++ b/packages/starlight-blog/libs/page.ts @@ -13,10 +13,10 @@ const trailingSlashTransformers: Record { + return { + title: entry.data.title, + link: getPathWithBase(`/${entry.slug}`), + pubDate: entry.data.date, + categories: entry.data.tags, + description: await getRSSDescription(entry), + content: await getRSSContent(entry), + } + }), + ), + customData: `${context.defaultLocale}`, + } + + if (context.trailingSlash !== 'ignore') { + options.trailingSlash = context.trailingSlash === 'always' + } + + return options +} + +function getRSSTitle() { + let title = (typeof context.title === 'string' ? context.title : context.title[context.defaultLocale]) ?? '' + + if (title.length > 0) { + title += ` ${context.titleDelimiter ?? '|'} ` + } + + title += config.title + + return title +} + +function getRSSDescription(entry: StarlightBlogEntry): Promise | undefined { + if (!entry.data.excerpt) return + + return stripMarkdown(entry.data.excerpt) +} + +function getRSSContent(entry: StarlightBlogEntry): Promise { + return renderMarkdownToHTML(entry.body) +} diff --git a/packages/starlight-blog/libs/vite.ts b/packages/starlight-blog/libs/vite.ts index 1f9268c..b74dea9 100644 --- a/packages/starlight-blog/libs/vite.ts +++ b/packages/starlight-blog/libs/vite.ts @@ -1,3 +1,4 @@ +import type { StarlightUserConfig } from '@astrojs/starlight/types' import type { AstroConfig, ViteUserConfig } from 'astro' import type { StarlightBlogConfig } from './config' @@ -9,9 +10,7 @@ export function vitePluginStarlightBlogConfig( ): VitePlugin { const modules = { 'virtual:starlight-blog-config': `export default ${JSON.stringify(starlightBlogConfig)}`, - 'virtual:starlight-blog-context': `export default ${JSON.stringify({ - trailingSlash: context.trailingSlash, - })}`, + 'virtual:starlight-blog-context': `export default ${JSON.stringify(context)}`, } const moduleResolutionMap = Object.fromEntries( @@ -35,6 +34,11 @@ function resolveVirtualModuleId(id: TModuleId): `\0${T } export interface StarlightBlogContext { + defaultLocale: string + description: StarlightUserConfig['description'] + site: AstroConfig['site'] + title: StarlightUserConfig['title'] + titleDelimiter: StarlightUserConfig['titleDelimiter'] trailingSlash: AstroConfig['trailingSlash'] } diff --git a/packages/starlight-blog/overrides/Sidebar.astro b/packages/starlight-blog/overrides/Sidebar.astro index 9295a5d..2c3e172 100644 --- a/packages/starlight-blog/overrides/Sidebar.astro +++ b/packages/starlight-blog/overrides/Sidebar.astro @@ -2,6 +2,7 @@ import StarlightSidebar from '@astrojs/starlight/components/Sidebar.astro' import type { Props } from '@astrojs/starlight/props' import config from 'virtual:starlight-blog-config' +import context from 'virtual:starlight-blog-context' import { getRecentBlogEntries } from '../libs/content' import { @@ -71,6 +72,17 @@ if (isBlog) { type: 'group', }) } + + if (context.site) { + blogSidebar.push({ + attrs: {}, + badge: undefined, + href: getBlogPathWithBase('/rss.xml'), + isCurrent: false, + label: 'RSS', + type: 'link', + }) + } } --- diff --git a/packages/starlight-blog/package.json b/packages/starlight-blog/package.json index da89c07..211b4b6 100644 --- a/packages/starlight-blog/package.json +++ b/packages/starlight-blog/package.json @@ -12,6 +12,7 @@ "./overrides/ThemeSelect.astro": "./overrides/ThemeSelect.astro", "./routes/Blog.astro": "./routes/Blog.astro", "./routes/Tags.astro": "./routes/Tags.astro", + "./routes/rss": "./routes/rss.xml.ts", "./schema": "./schema.ts", "./package.json": "./package.json" }, @@ -22,8 +23,12 @@ "lint": "prettier -c --cache . && eslint . --cache --max-warnings=0" }, "dependencies": { + "@astrojs/rss": "4.0.5", "astro-remote": "0.3.2", - "github-slugger": "2.0.0" + "github-slugger": "2.0.0", + "marked": "12.0.2", + "marked-plaintify": "1.0.1", + "ultrahtml": "1.5.3" }, "devDependencies": { "@astrojs/starlight": "0.22.1", diff --git a/packages/starlight-blog/routes/rss.xml.ts b/packages/starlight-blog/routes/rss.xml.ts new file mode 100644 index 0000000..a59d2f0 --- /dev/null +++ b/packages/starlight-blog/routes/rss.xml.ts @@ -0,0 +1,12 @@ +import rss from '@astrojs/rss' +import type { APIRoute } from 'astro' + +import { getRSSOptions, getRSSStaticPaths } from '../libs/rss' + +export function getStaticPaths() { + return getRSSStaticPaths() +} + +export const GET: APIRoute = async ({ site }) => { + return rss(await getRSSOptions(site)) +} diff --git a/packages/starlight-blog/tests/e2e/blog.test.ts b/packages/starlight-blog/tests/e2e/blog.test.ts index 9a48091..6ec3ba9 100644 --- a/packages/starlight-blog/tests/e2e/blog.test.ts +++ b/packages/starlight-blog/tests/e2e/blog.test.ts @@ -191,3 +191,12 @@ test('should not list draft blog posts in production', async ({ blogPage }) => { blogPage.page.getByRole('article').getByRole('link', { exact: true, name: 'Pertimuit munere' }), ).not.toBeVisible() }) + +test('should add a link to the RSS feed in the sidebar', async ({ blogPage }) => { + await blogPage.goto() + + const link = blogPage.page.getByRole('link', { name: 'RSS' }) + + await expect(link).toBeVisible() + expect(await link.getAttribute('href')).toBe('/blog/rss.xml') +}) diff --git a/packages/starlight-blog/tests/unit/basics/i18n.test.ts b/packages/starlight-blog/tests/unit/basics/i18n.test.ts new file mode 100644 index 0000000..0c6fd43 --- /dev/null +++ b/packages/starlight-blog/tests/unit/basics/i18n.test.ts @@ -0,0 +1,59 @@ +import type { StarlightUserConfig } from '@astrojs/starlight/types' +import { describe, expect, test } from 'vitest' + +import { getDefaultLocale } from '../../../libs/i18n' + +describe('getDefaultLocale', () => { + test('returns the default locale for the built-in default locale', () => { + expect(getTestDefaultLocale({})).toBe('en') + }) + + test('returns the default locale with a root locale', () => { + expect( + getTestDefaultLocale({ + locales: { + root: { label: 'Français', lang: 'fr' }, + }, + }), + ).toBe('fr') + }) + + test('returns the default locale with a root locale and a default locale', () => { + expect( + getTestDefaultLocale({ + defaultLocale: 'root', + locales: { + root: { label: 'Français', lang: 'fr' }, + 'zh-cn': { label: '简体中文', lang: 'zh-CN' }, + }, + }), + ).toBe('fr') + }) + + test('returns the default locale with no root locale', () => { + expect( + getTestDefaultLocale({ + defaultLocale: 'fr', + locales: { + fr: { label: 'Français', lang: 'fr' }, + 'zh-cn': { label: '简体中文', lang: 'zh-CN' }, + }, + }), + ).toBe('fr') + }) + + test('returns the default locale with non root single locale', () => { + expect( + getTestDefaultLocale({ + defaultLocale: 'fr', + locales: { + fr: { label: 'Français', lang: 'fr' }, + }, + }), + ).toBe('fr') + }) +}) + +function getTestDefaultLocale(config: Partial) { + return getDefaultLocale(config as StarlightUserConfig) +} diff --git a/packages/starlight-blog/tests/unit/basics/rss.test.ts b/packages/starlight-blog/tests/unit/basics/rss.test.ts new file mode 100644 index 0000000..aec8d6f --- /dev/null +++ b/packages/starlight-blog/tests/unit/basics/rss.test.ts @@ -0,0 +1,57 @@ +import type { RSSFeedItem } from '@astrojs/rss' +import { describe, expect, test, vi } from 'vitest' + +import { getRSSOptions } from '../../../libs/rss' + +vi.mock('astro:content', async () => { + const { mockBlogPosts } = await import('../utils') + + return mockBlogPosts([ + ['post-21.md', { title: 'Post 21', date: new Date('2024-02-24') }], + ['post-20.md', { title: 'Post 20', date: new Date('2024-01-24') }], + ['post-19.md', { title: 'Post 19', date: new Date('2023-12-24') }], + ['post-18.md', { title: 'Post 18', date: new Date('2023-11-24') }], + ['post-17.md', { title: 'Post 17', date: new Date('2023-10-24') }], + ['post-16.md', { title: 'Post 16', date: new Date('2023-09-24') }], + ['post-15.md', { title: 'Post 15', date: new Date('2023-08-24') }], + ['post-14.md', { title: 'Post 14', date: new Date('2023-07-24') }], + ['post-13.md', { title: 'Post 13', date: new Date('2023-06-24') }], + ['post-12.md', { title: 'Post 12', date: new Date('2023-05-24') }], + ['post-11.md', { title: 'Post 11', date: new Date('2023-04-24') }], + ['post-10.md', { title: 'Post 10', date: new Date('2023-03-24') }], + ['post-9.md', { title: 'Post 9', date: new Date('2023-02-24') }], + ['post-8.md', { title: 'Post 8', date: new Date('2023-01-24') }], + ['post-7.md', { title: 'Post 7', date: new Date('2022-12-24') }], + ['post-6.md', { title: 'Post 6', date: new Date('2022-11-24') }], + ['post-5.md', { title: 'Post 5', date: new Date('2022-10-24') }], + ['post-4.md', { title: 'Post 4', date: new Date('2022-09-24') }], + ['post-3.md', { title: 'Post 3', date: new Date('2022-08-24') }], + ['post-2.md', { title: 'Post 2', date: new Date('2022-07-24') }], + ['post-1.md', { title: 'Post 1', date: new Date('2022-06-24') }], + ]) +}) + +describe('getRSSOptions', () => { + test('includes only the last 20 blog posts', async () => { + const { items } = await getRSSOptions(new URL('http://example.com')) + + expect(items).toHaveLength(20) + expect(getItemAtIndex(items, 0)?.title).toBe('Post 21') + expect(getItemAtIndex(items, 19)?.title).toBe('Post 2') + }) + + test('includes top-level metadata', async () => { + const url = new URL('http://example.com') + + const options = await getRSSOptions(url) + + expect(options.title).toBe('Starlight Blog Basics | Blog') + expect(options.description).toMatchInlineSnapshot(`"Basic tests for the Starlight Blog plugin."`) + expect(options.site).toBe(url) + expect(options.customData).toMatchInlineSnapshot(`"fr"`) + }) +}) + +function getItemAtIndex(items: Awaited>['items'], index: number) { + return (items as RSSFeedItem[])[index] +} diff --git a/packages/starlight-blog/tests/unit/basics/vitest.config.ts b/packages/starlight-blog/tests/unit/basics/vitest.config.ts index ea9852e..1a86dd3 100644 --- a/packages/starlight-blog/tests/unit/basics/vitest.config.ts +++ b/packages/starlight-blog/tests/unit/basics/vitest.config.ts @@ -1,3 +1,10 @@ import { defineVitestConfig } from '../test' -export default defineVitestConfig({}) +export default defineVitestConfig( + {}, + { + title: 'Starlight Blog Basics', + description: 'Basic tests for the Starlight Blog plugin.', + defaultLocale: 'fr', + }, +) diff --git a/packages/starlight-blog/tests/unit/test.ts b/packages/starlight-blog/tests/unit/test.ts index a5ff6f2..2d9dbc4 100644 --- a/packages/starlight-blog/tests/unit/test.ts +++ b/packages/starlight-blog/tests/unit/test.ts @@ -3,12 +3,17 @@ import { getViteConfig } from 'astro/config' import { validateConfig, type StarlightBlogUserConfig } from '../../libs/config' import { vitePluginStarlightBlogConfig, type StarlightBlogContext } from '../../libs/vite' -export function defineVitestConfig(userConfig: StarlightBlogUserConfig, context?: StarlightBlogContext) { +export function defineVitestConfig(userConfig: StarlightBlogUserConfig, context?: Partial) { const config = validateConfig(userConfig) return getViteConfig({ plugins: [ vitePluginStarlightBlogConfig(config, { + defaultLocale: context?.defaultLocale ?? 'en', + description: context?.description, + site: context?.site, + title: context?.title ?? 'Starlight Blog Test', + titleDelimiter: context?.titleDelimiter, trailingSlash: context?.trailingSlash ?? 'ignore', }), { diff --git a/packages/starlight-blog/tests/unit/utils.ts b/packages/starlight-blog/tests/unit/utils.ts index f43ed13..4a3333c 100644 --- a/packages/starlight-blog/tests/unit/utils.ts +++ b/packages/starlight-blog/tests/unit/utils.ts @@ -20,7 +20,7 @@ function mockBlogPost(id: string, entry: StarlightBlogEntryData): StarlightBlogE id: `blog/${id}`, slug: `blog/${slug(id.replace(/\.[^.]+$/, '').replace(/\/index$/, ''))}`, collection: 'docs', - data: blogEntrySchema.parse(entry) as StarlightBlogEntryData, + data: blogEntrySchema.passthrough().parse(entry) as StarlightBlogEntryData, body: '', render: (() => { // We do not care about the render function in the unit tests. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b355c6..d4e4d28 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,12 +56,24 @@ importers: packages/starlight-blog: dependencies: + '@astrojs/rss': + specifier: 4.0.5 + version: 4.0.5 astro-remote: specifier: 0.3.2 version: 0.3.2 github-slugger: specifier: 2.0.0 version: 2.0.0 + marked: + specifier: 12.0.2 + version: 12.0.2 + marked-plaintify: + specifier: 1.0.1 + version: 1.0.1(marked@12.0.2) + ultrahtml: + specifier: 1.5.3 + version: 1.5.3 devDependencies: '@astrojs/starlight': specifier: 0.22.1 @@ -151,6 +163,13 @@ packages: dependencies: prismjs: 1.29.0 + /@astrojs/rss@4.0.5: + resolution: {integrity: sha512-IyJVL6z09AQtxbgLaAwebT3T5YKe4oTHDesqydJv1KLHw+zEzzMCFuuNsEyxjiqu7df9+DDCpDXLj/WRiEUXvw==} + dependencies: + fast-xml-parser: 4.3.6 + kleur: 4.1.5 + dev: false + /@astrojs/sitemap@3.0.5: resolution: {integrity: sha512-60eLzNjMza3ABypiQPUC6ElOSZNZeY5CwSwgJ03hfeonl+Db9x12CCzBFdTw7A5Mq+O54xEZVUrR0tB+yWgX8w==} dependencies: @@ -1439,9 +1458,9 @@ packages: engines: {node: '>=18.14.1'} dependencies: entities: 4.5.0 - marked: 12.0.1 - marked-footnote: 1.2.2(marked@12.0.1) - marked-smartypants: 1.1.6(marked@12.0.1) + marked: 12.0.2 + marked-footnote: 1.2.2(marked@12.0.2) + marked-smartypants: 1.1.6(marked@12.0.2) ultrahtml: 1.5.3 dev: false @@ -2631,6 +2650,13 @@ packages: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true + /fast-xml-parser@4.3.6: + resolution: {integrity: sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw==} + hasBin: true + dependencies: + strnum: 1.0.5 + dev: false + /fastq@1.15.0: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: @@ -3714,25 +3740,33 @@ packages: /markdown-table@3.0.3: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} - /marked-footnote@1.2.2(marked@12.0.1): + /marked-footnote@1.2.2(marked@12.0.2): resolution: {integrity: sha512-TFBEHwHLSSedub7P6XHHs+dMMOnDeNV5+kFDo4trU//gDd8iM57lg9jr9NGwDifPwLllHwKmFcRNp5uYvO2Fnw==} peerDependencies: marked: '>=7.0.0' dependencies: - marked: 12.0.1 + marked: 12.0.2 dev: false - /marked-smartypants@1.1.6(marked@12.0.1): + /marked-plaintify@1.0.1(marked@12.0.2): + resolution: {integrity: sha512-KQhxtuVWf3Ij3YMiW4ArlgNOVmzOAlP0o/upsu2+h7Q4TCAwG4UvkYTteZF2sDDomXQnNSLmfyAhoR0gx2Orgw==} + peerDependencies: + marked: '>=7.0.0' + dependencies: + marked: 12.0.2 + dev: false + + /marked-smartypants@1.1.6(marked@12.0.2): resolution: {integrity: sha512-38rdxcV3+EHrvoHioSrgBDvOmFb+TNcszZggrl15qe4MEfQxBArfSgsGgFP0YqHlGy8Rgoyi4gN4ThBWzwNJeA==} peerDependencies: marked: '>=4 <13' dependencies: - marked: 12.0.1 + marked: 12.0.2 smartypants: 0.2.2 dev: false - /marked@12.0.1: - resolution: {integrity: sha512-Y1/V2yafOcOdWQCX0XpAKXzDakPOpn6U0YLxTJs3cww6VxOzZV1BTOOYWLvH3gX38cq+iLwljHHTnMtlDfg01Q==} + /marked@12.0.2: + resolution: {integrity: sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==} engines: {node: '>= 18'} hasBin: true dev: false @@ -5526,6 +5560,10 @@ packages: js-tokens: 8.0.3 dev: true + /strnum@1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + dev: false + /style-to-object@0.4.1: resolution: {integrity: sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==} dependencies: