diff --git a/packages/api/src/router/widgets/index.ts b/packages/api/src/router/widgets/index.ts index ddce0b9a0..d97741c29 100644 --- a/packages/api/src/router/widgets/index.ts +++ b/packages/api/src/router/widgets/index.ts @@ -12,6 +12,7 @@ import { minecraftRouter } from "./minecraft"; import { networkControllerRouter } from "./network-controller"; import { notebookRouter } from "./notebook"; import { optionsRouter } from "./options"; +import { releasesRouter } from "./releases"; import { rssFeedRouter } from "./rssFeed"; import { smartHomeRouter } from "./smart-home"; import { stockPriceRouter } from "./stocks"; @@ -34,5 +35,6 @@ export const widgetRouter = createTRPCRouter({ mediaTranscoding: mediaTranscodingRouter, minecraft: minecraftRouter, options: optionsRouter, + releases: releasesRouter, networkController: networkControllerRouter, }); diff --git a/packages/api/src/router/widgets/releases.ts b/packages/api/src/router/widgets/releases.ts new file mode 100644 index 000000000..a8b797b44 --- /dev/null +++ b/packages/api/src/router/widgets/releases.ts @@ -0,0 +1,53 @@ +import { escapeForRegEx } from "@tiptap/react"; +import { z } from "zod"; + +import { releasesRequestHandler } from "@homarr/request-handler/releases"; + +import { createTRPCRouter, publicProcedure } from "../../trpc"; + +const formatVersionFilterRegex = (versionFilter: z.infer | undefined) => { + if (!versionFilter) return undefined; + + const escapedPrefix = versionFilter.prefix ? escapeForRegEx(versionFilter.prefix) : ""; + const precision = "[0-9]+\\.".repeat(versionFilter.precision).slice(0, -2); + const escapedSuffix = versionFilter.suffix ? escapeForRegEx(versionFilter.suffix) : ""; + + return `^${escapedPrefix}${precision}${escapedSuffix}$`; +}; + +const _releaseVersionFilterSchema = z.object({ + prefix: z.string().optional(), + precision: z.number(), + suffix: z.string().optional(), +}); + +export const releasesRouter = createTRPCRouter({ + getLatest: publicProcedure + .input( + z.object({ + repositories: z.array( + z.object({ + providerKey: z.string(), + identifier: z.string(), + versionFilter: _releaseVersionFilterSchema.optional(), + }), + ), + }), + ) + .query(async ({ input }) => { + const result = await Promise.all( + input.repositories.map(async (repository) => { + const innerHandler = releasesRequestHandler.handler({ + providerKey: repository.providerKey, + identifier: repository.identifier, + versionRegex: formatVersionFilterRegex(repository.versionFilter), + }); + return await innerHandler.getCachedOrUpdatedDataAsync({ + forceUpdate: false, + }); + }), + ); + + return result; + }), +}); diff --git a/packages/definitions/src/widget.ts b/packages/definitions/src/widget.ts index aff019899..263ea533f 100644 --- a/packages/definitions/src/widget.ts +++ b/packages/definitions/src/widget.ts @@ -23,5 +23,6 @@ export const widgetKinds = [ "bookmarks", "indexerManager", "healthMonitoring", + "releases", ] as const; export type WidgetKind = (typeof widgetKinds)[number]; diff --git a/packages/request-handler/src/releases-providers.ts b/packages/request-handler/src/releases-providers.ts new file mode 100644 index 000000000..3ed3383ea --- /dev/null +++ b/packages/request-handler/src/releases-providers.ts @@ -0,0 +1,306 @@ +import { z } from "zod"; + +export interface ReleasesProvider { + getDetailsUrl: (identifier: string) => string | undefined; + parseDetailsResponse: (response: unknown) => z.SafeParseReturnType | undefined; + getReleasesUrl: (identifier: string) => string; + parseReleasesResponse: (response: unknown) => z.SafeParseReturnType; +} + +interface ProvidersProps { + [key: string]: ReleasesProvider; + DockerHub: ReleasesProvider; + Github: ReleasesProvider; + Gitlab: ReleasesProvider; + Npm: ReleasesProvider; + Codeberg: ReleasesProvider; +} + +export const Providers: ProvidersProps = { + DockerHub: { + getDetailsUrl(identifier) { + if (identifier.indexOf("/") > 0) { + const [owner, name] = identifier.split("/"); + if (!owner || !name) { + return ""; + } + return `https://hub.docker.com/v2/namespaces/${encodeURIComponent(owner)}/repositories/${encodeURIComponent(name)}`; + } else { + return `https://hub.docker.com/v2/repositories/library/${encodeURIComponent(identifier)}`; + } + }, + parseDetailsResponse(response) { + return z + .object({ + name: z.string(), + namespace: z.string(), + description: z.string(), + star_count: z.number(), + date_registered: z.string().transform((value) => new Date(value)), + }) + .transform((resp) => ({ + projectUrl: `https://hub.docker.com/r/${resp.namespace === "library" ? "_" : resp.namespace}/${resp.name}`, + projectDescription: resp.description, + isFork: false, + isArchived: false, + createdAt: resp.date_registered, + starsCount: resp.star_count, + openIssues: 0, + forksCount: 0, + })) + .safeParse(response); + }, + getReleasesUrl(identifier) { + return `${this.getDetailsUrl(identifier)}/tags?page_size=200`; + }, + parseReleasesResponse(response) { + return z + .object({ + results: z.array( + z + .object({ name: z.string(), last_updated: z.string().transform((value) => new Date(value)) }) + .transform((tag) => ({ + identifier: "", + latestRelease: tag.name, + latestReleaseAt: tag.last_updated, + })), + ), + }) + .transform((resp) => { + return resp.results.map((release) => ({ + ...release, + releaseUrl: "", + releaseDescription: "", + isPreRelease: false, + })); + }) + .safeParse(response); + }, + }, + Github: { + getDetailsUrl(identifier) { + const [owner, name] = identifier.split("/"); + if (!owner || !name) { + return ""; + } + return `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`; + }, + parseDetailsResponse(response) { + return z + .object({ + html_url: z.string(), + description: z.string(), + fork: z.boolean(), + archived: z.boolean(), + created_at: z.string().transform((value) => new Date(value)), + stargazers_count: z.number(), + open_issues_count: z.number(), + forks_count: z.number(), + }) + .transform((resp) => ({ + projectUrl: resp.html_url, + projectDescription: resp.description, + isFork: resp.fork, + isArchived: resp.archived, + createdAt: resp.created_at, + starsCount: resp.stargazers_count, + openIssues: resp.open_issues_count, + forksCount: resp.forks_count, + })) + .safeParse(response); + }, + getReleasesUrl(identifier) { + return `${this.getDetailsUrl(identifier)}/releases`; + }, + parseReleasesResponse(response) { + return z + .array( + z + .object({ + tag_name: z.string(), + published_at: z.string().transform((value) => new Date(value)), + html_url: z.string(), + body: z.string(), + prerelease: z.boolean(), + }) + .transform((tag) => ({ + identifier: "", + latestRelease: tag.tag_name, + latestReleaseAt: tag.published_at, + releaseUrl: tag.html_url, + releaseDescription: tag.body, + isPreRelease: tag.prerelease, + })), + ) + .safeParse(response); + }, + }, + Gitlab: { + getDetailsUrl(identifier) { + return `https://gitlab.com/api/v4/projects/${encodeURIComponent(identifier)}`; + }, + parseDetailsResponse(response) { + return z + .object({ + web_url: z.string(), + description: z.string(), + forked_from_project: z.object({ id: z.number() }).nullable(), + archived: z.boolean(), + created_at: z.string().transform((value) => new Date(value)), + star_count: z.number(), + open_issues_count: z.number(), + forks_count: z.number(), + }) + .transform((resp) => ({ + projectUrl: resp.web_url, + projectDescription: resp.description, + isFork: resp.forked_from_project !== null, + isArchived: resp.archived, + createdAt: resp.created_at, + starsCount: resp.star_count, + openIssues: resp.open_issues_count, + forksCount: resp.forks_count, + })) + .safeParse(response); + }, + getReleasesUrl(identifier) { + return `${this.getDetailsUrl(identifier)}/releases`; + }, + parseReleasesResponse(response) { + return z + .array( + z + .object({ + name: z.string(), + released_at: z.string().transform((value) => new Date(value)), + description: z.string(), + _links: z.object({ self: z.string() }), + upcoming_release: z.boolean(), + }) + .transform((tag) => ({ + identifier: "", + latestRelease: tag.name, + latestReleaseAt: tag.released_at, + releaseUrl: tag._links.self, + releaseDescription: tag.description, + isPreRelease: tag.upcoming_release, + })), + ) + .safeParse(response); + }, + }, + Npm: { + getDetailsUrl(_) { + return undefined; + }, + parseDetailsResponse(_) { + return undefined; + }, + getReleasesUrl(identifier) { + return `https://registry.npmjs.org/${encodeURIComponent(identifier)}`; + }, + parseReleasesResponse(response) { + return z + .object({ + time: z.record(z.string().transform((value) => new Date(value))).transform((version) => + Object.entries(version).map(([key, value]) => ({ + identifier: "", + latestRelease: key, + latestReleaseAt: value, + })), + ), + versions: z.record(z.object({ description: z.string() })), + name: z.string(), + }) + .transform((resp) => { + return resp.time.map((release) => ({ + ...release, + releaseUrl: `https://www.npmjs.com/package/${resp.name}/v/${release.latestRelease}`, + releaseDescription: resp.versions[release.latestRelease]?.description ?? "", + isPreRelease: false, + })); + }) + .safeParse(response); + }, + }, + Codeberg: { + getDetailsUrl(identifier) { + const [owner, name] = identifier.split("/"); + if (!owner || !name) { + return ""; + } + return `https://codeberg.org/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`; + }, + parseDetailsResponse(response) { + return z + .object({ + html_url: z.string(), + description: z.string(), + fork: z.boolean(), + archived: z.boolean(), + created_at: z.string().transform((value) => new Date(value)), + stars_count: z.number(), + open_issues_count: z.number(), + forks_count: z.number(), + }) + .transform((resp) => ({ + projectUrl: resp.html_url, + projectDescription: resp.description, + isFork: resp.fork, + isArchived: resp.archived, + createdAt: resp.created_at, + starsCount: resp.stars_count, + openIssues: resp.open_issues_count, + forksCount: resp.forks_count, + })) + .safeParse(response); + }, + getReleasesUrl(identifier) { + return `${this.getDetailsUrl(identifier)}/releases`; + }, + parseReleasesResponse(response) { + return z + .array( + z + .object({ + tag_name: z.string(), + published_at: z.string().transform((value) => new Date(value)), + url: z.string(), + body: z.string(), + prerelease: z.boolean(), + }) + .transform((tag) => ({ + latestRelease: tag.tag_name, + latestReleaseAt: tag.published_at, + releaseUrl: tag.url, + releaseDescription: tag.body, + isPreRelease: tag.prerelease, + })), + ) + .safeParse(response); + }, + }, +}; + +const _detailsSchema = z.object({ + projectUrl: z.string(), + projectDescription: z.string(), + isFork: z.boolean(), + isArchived: z.boolean(), + createdAt: z.date(), + starsCount: z.number(), + openIssues: z.number(), + forksCount: z.number(), +}); + +const _releasesSchema = z.object({ + latestRelease: z.string(), + latestReleaseAt: z.date(), + releaseUrl: z.string(), + releaseDescription: z.string(), + isPreRelease: z.boolean(), +}); + +export type DetailsResponse = z.infer; + +export type ReleasesResponse = z.infer; diff --git a/packages/request-handler/src/releases.ts b/packages/request-handler/src/releases.ts new file mode 100644 index 000000000..17d7b924e --- /dev/null +++ b/packages/request-handler/src/releases.ts @@ -0,0 +1,105 @@ +import dayjs from "dayjs"; +import { z } from "zod"; + +import { fetchWithTimeout } from "@homarr/common"; +import { logger } from "@homarr/log"; + +import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler"; +import { Providers } from "./releases-providers"; +import type { DetailsResponse } from "./releases-providers"; + +const _reponseSchema = z.object({ + identifier: z.string(), + providerKey: z.string(), + latestRelease: z.string(), + latestReleaseAt: z.date(), + releaseUrl: z.string(), + releaseDescription: z.string(), + isPreRelease: z.boolean(), + projectUrl: z.string(), + projectDescription: z.string(), + isFork: z.boolean(), + isArchived: z.boolean(), + createdAt: z.date(), + starsCount: z.number(), + openIssues: z.number(), + forksCount: z.number(), +}); + +export const releasesRequestHandler = createCachedWidgetRequestHandler({ + queryKey: "releasesApiResult", + widgetKind: "releases", + async requestAsync(input: { providerKey: string; identifier: string; versionRegex: string | undefined }) { + const provider = Providers[input.providerKey]; + + if (!provider) return undefined; + + let detailsResult: DetailsResponse = { + projectUrl: "", + projectDescription: "", + isFork: false, + isArchived: false, + createdAt: new Date(0), + starsCount: 0, + openIssues: 0, + forksCount: 0, + }; + + const detailsUrl = provider.getDetailsUrl(input.identifier); + if (detailsUrl !== undefined) { + const detailsResponse = await fetchWithTimeout(detailsUrl); + const parsedDetails = provider.parseDetailsResponse(await detailsResponse.json()); + + if (parsedDetails?.success) { + detailsResult = parsedDetails.data; + } else { + logger.warn("Failed to parse details response", { + provider: input.providerKey, + identifier: input.identifier, + detailsUrl, + error: parsedDetails?.error, + }); + } + } + + const releasesResponse = await fetchWithTimeout(provider.getReleasesUrl(input.identifier)); + const releasesResult = provider.parseReleasesResponse(await releasesResponse.json()); + + if (!releasesResult.success) return undefined; + + const latest: ResponseResponse = releasesResult.data + .filter((result) => (input.versionRegex ? new RegExp(input.versionRegex).test(result.latestRelease) : true)) + .reduce( + (latest, result) => { + return { + ...detailsResult, + ...(result.latestReleaseAt > latest.latestReleaseAt ? result : latest), + identifier: input.identifier, + providerKey: input.providerKey, + }; + }, + { + identifier: "", + providerKey: "", + latestRelease: "", + latestReleaseAt: new Date(0), + releaseUrl: "", + releaseDescription: "", + isPreRelease: false, + projectUrl: "", + projectDescription: "", + isFork: false, + isArchived: false, + createdAt: new Date(0), + starsCount: 0, + openIssues: 0, + forksCount: 0, + }, + ); + + return latest; + }, + cacheDuration: dayjs.duration(5, "minutes"), +}); + +export type ResponseResponse = z.infer; diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index eb85cecca..abb0b0b74 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -2052,6 +2052,85 @@ } } }, + "releases": { + "name": "Releases", + "description": "Displays a list of the current version of the given repositories with the given version regex.", + "option": { + "newReleaseWithin": { + "label": "New Release Within", + "description": "Usage example: 1w (1 week), 10m (10 months). Accepted unit types h (hours), d (days), w (weeks), m (months), y (years). Leave empty for no highlighting of new releases." + }, + "staleReleaseWithin": { + "label": "Stale Release Within", + "description": "Usage example: 1w (1 week), 10m (10 months). Accepted unit types h (hours), d (days), w (weeks), m (months), y (years). Leave empty for no highlighting of stale releases." + }, + "showOnlyHighlighted": { + "label": "Show Only Highlighted", + "description": "Show only new or stale releases. As per the above." + }, + "showDetails": { + "label": "Show Details" + }, + "repositories": { + "label": "Repositories", + "addRRepository": { + "label": "Add repository" + }, + "provider": { + "label": "Provider" + }, + "identifier": { + "label": "Identifier", + "placeholder": "Name or Owner/Name" + }, + "versionFilter": { + "label": "Version Filter", + "prefix": { + "label": "Prefix" + }, + "precision": { + "label": "Precision", + "options": { + "none": "None" + } + }, + "suffix": { + "label": "Suffix" + }, + "regex": { + "label": "Regular Expression" + } + }, + "edit": { + "label": "Edit" + }, + "editForm": { + "title": "Edit Repository", + "cancel": { + "label": "Cancel" + }, + "confirm": { + "label": "Confirm" + } + }, + "example": { + "label": "Example" + }, + "invalid": "Invalid repository definition, please check the values" + } + }, + "not-found": "Not Found", + "pre-release": "Pre-Release", + "archived": "Archived", + "forked": "Forked", + "starsCount": "Stars", + "forksCount": "Forks", + "issuesCount": "Open Issues", + "openProjectPage": "Open Project Page", + "openReleasePage": "Open Release Page", + "releaseDescription": "Release Description", + "created": "Created" + }, "networkControllerSummary": { "option": {}, "card": { diff --git a/packages/ui/src/components/masked-image.tsx b/packages/ui/src/components/masked-image.tsx index 45b073d76..17b6b1cbd 100644 --- a/packages/ui/src/components/masked-image.tsx +++ b/packages/ui/src/components/masked-image.tsx @@ -7,7 +7,7 @@ import type { Property } from "csstype"; import classes from "./masked-image.module.css"; interface MaskedImageProps { - imageUrl: string; + imageUrl?: string; color: MantineColor; alt?: string; style?: React.CSSProperties; @@ -41,7 +41,7 @@ export const MaskedImage = ({ maskSize, maskRepeat, maskPosition, - maskImage: `url(${imageUrl})`, + maskImage: imageUrl ? `url(${imageUrl})` : undefined, } as React.CSSProperties } /> diff --git a/packages/ui/src/components/masked-or-normal-image.tsx b/packages/ui/src/components/masked-or-normal-image.tsx index 3a62a4c43..af60b28d9 100644 --- a/packages/ui/src/components/masked-or-normal-image.tsx +++ b/packages/ui/src/components/masked-or-normal-image.tsx @@ -6,7 +6,7 @@ import type { Property } from "csstype"; import { MaskedImage } from "./masked-image"; interface MaskedOrNormalImageProps { - imageUrl: string; + imageUrl?: string; hasColor?: boolean; color?: MantineColor; alt?: string; diff --git a/packages/widgets/package.json b/packages/widgets/package.json index 106856983..d6203804c 100644 --- a/packages/widgets/package.json +++ b/packages/widgets/package.json @@ -33,6 +33,7 @@ "@homarr/db": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0", "@homarr/form": "workspace:^0.1.0", + "@homarr/forms-collection": "workspace:^0.1.0", "@homarr/integrations": "workspace:^0.1.0", "@homarr/modals": "workspace:^0.1.0", "@homarr/modals-collection": "workspace:^0.1.0", @@ -69,6 +70,7 @@ "next": "15.3.1", "react": "19.1.0", "react-dom": "19.1.0", + "react-markdown": "^10.1.0", "recharts": "^2.15.3", "video.js": "^8.22.0", "zod": "^3.24.3" diff --git a/packages/widgets/src/_inputs/index.ts b/packages/widgets/src/_inputs/index.ts index d675e5c1c..b19ab8c9c 100644 --- a/packages/widgets/src/_inputs/index.ts +++ b/packages/widgets/src/_inputs/index.ts @@ -2,6 +2,7 @@ import type { WidgetOptionType } from "../options"; import { WidgetAppInput } from "./widget-app-input"; import { WidgetLocationInput } from "./widget-location-input"; import { WidgetMultiTextInput } from "./widget-multi-text-input"; +import { WidgetMultiReleasesRepositoriesInput } from "./widget-multiReleasesRepositories-input"; import { WidgetMultiSelectInput } from "./widget-multiselect-input"; import { WidgetNumberInput } from "./widget-number-input"; import { WidgetSelectInput } from "./widget-select-input"; @@ -21,6 +22,7 @@ const mapping = { switch: WidgetSwitchInput, app: WidgetAppInput, sortableItemList: WidgetSortedItemListInput, + multiReleasesRepositories: WidgetMultiReleasesRepositoriesInput, } satisfies Record; export const getInputForType = (type: TType) => { diff --git a/packages/widgets/src/_inputs/widget-multiReleasesRepositories-input.tsx b/packages/widgets/src/_inputs/widget-multiReleasesRepositories-input.tsx new file mode 100644 index 000000000..ddd7123ef --- /dev/null +++ b/packages/widgets/src/_inputs/widget-multiReleasesRepositories-input.tsx @@ -0,0 +1,326 @@ +"use client"; + +import React, { useCallback, useMemo, useState } from "react"; +import { ActionIcon, Button, Divider, Fieldset, Group, Select, Stack, Text, TextInput } from "@mantine/core"; +import type { FormErrors } from "@mantine/form"; +import { IconEdit, IconTrash, IconTriangleFilled } from "@tabler/icons-react"; +import { escapeForRegEx } from "@tiptap/react"; + +import { IconPicker } from "@homarr/forms-collection"; +import { createModal, useModalAction } from "@homarr/modals"; +import { useScopedI18n } from "@homarr/translation/client"; +import { MaskedOrNormalImage } from "@homarr/ui"; + +import { Providers } from "../releases/releases-providers"; +import type { ReleasesRepository, ReleasesVersionFilter } from "../releases/releases-repository"; +import type { CommonWidgetInputProps } from "./common"; +import { useWidgetInputTranslation } from "./common"; +import { useFormContext } from "./form"; + +interface FormValidation { + hasErrors: boolean; + errors: FormErrors; +} + +export const WidgetMultiReleasesRepositoriesInput = ({ + property, + kind, +}: CommonWidgetInputProps<"multiReleasesRepositories">) => { + const t = useWidgetInputTranslation(kind, property); + const tRepository = useScopedI18n("widget.releases.option.repositories"); + const form = useFormContext(); + const repositories = form.values.options[property] as ReleasesRepository[]; + const { openModal } = useModalAction(ReleaseEditModal); + const versionFilterPrecisionOptions = useMemo( + () => [tRepository("versionFilter.precision.options.none"), "#", "#.#", "#.#.#", "#.#.#.#", "#.#.#.#.#"], + [tRepository], + ); + + const onRepositorySave = useCallback( + (repository: ReleasesRepository, index: number): FormValidation => { + form.setFieldValue(`options.${property}.${index}.providerKey`, repository.providerKey); + form.setFieldValue(`options.${property}.${index}.identifier`, repository.identifier); + form.setFieldValue(`options.${property}.${index}.versionFilter`, repository.versionFilter); + form.setFieldValue(`options.${property}.${index}.iconUrl`, repository.iconUrl); + + const formValidation = form.validate(); + const fieldErrors: FormErrors = Object.entries(formValidation.errors).reduce((acc, [key, value]) => { + if (key.startsWith(`options.${property}.${index}.`)) { + acc[key] = value; + } + return acc; + }, {} as FormErrors); + + return { + hasErrors: Object.keys(fieldErrors).length > 0, + errors: fieldErrors, + }; + }, + [form, property], + ); + + const addNewItem = () => { + const item = { + providerKey: "DockerHub", + identifier: "", + } as ReleasesRepository; + + form.setValues((previous) => { + const previousValues = previous.options?.[property] as ReleasesRepository[]; + return { + ...previous, + options: { + ...previous.options, + [property]: [...previousValues, item], + }, + }; + }); + + const index = repositories.length; + + openModal({ + fieldPath: `options.${property}.${index}`, + repository: item, + onRepositorySave: (saved) => onRepositorySave(saved, index), + versionFilterPrecisionOptions, + }); + }; + + const onReleaseRemove = (index: number) => { + form.setValues((previous) => { + const previousValues = previous.options?.[property] as ReleasesRepository[]; + return { + ...previous, + options: { + ...previous.options, + [property]: previousValues.filter((_, i) => i !== index), + }, + }; + }); + }; + + return ( +
+ + + + + {repositories.map((repository, index) => { + return ( + + + + + + {Providers[repository.providerKey]?.name} + + + + + {repository.identifier} + + + + {formatVersionFilterRegex(repository.versionFilter) ?? ""} + + + + + + onReleaseRemove(index)}> + + + + {Object.keys(form.errors).filter((key) => key.startsWith(`options.${property}.${index}.`)).length > 0 && ( + + + + {tRepository("invalid")} + + + )} + + + ); + })} + +
+ ); +}; + +const formatVersionFilterRegex = (versionFilter: ReleasesVersionFilter | undefined) => { + if (!versionFilter) return undefined; + + const escapedPrefix = versionFilter.prefix ? escapeForRegEx(versionFilter.prefix) : ""; + const precision = "[0-9]+\\.".repeat(versionFilter.precision).slice(0, -2); + const escapedSuffix = versionFilter.suffix ? escapeForRegEx(versionFilter.suffix) : ""; + + return `^${escapedPrefix}${precision}${escapedSuffix}$`; +}; + +interface ReleaseEditProps { + fieldPath: string; + repository: ReleasesRepository; + onRepositorySave: (repository: ReleasesRepository) => FormValidation; + versionFilterPrecisionOptions: string[]; +} + +const ReleaseEditModal = createModal(({ innerProps, actions }) => { + const tRepository = useScopedI18n("widget.releases.option.repositories"); + const [loading, setLoading] = useState(false); + const [tempRepository, setTempRepository] = useState(() => ({ ...innerProps.repository })); + const [formErrors, setFormErrors] = useState({}); + + const handleConfirm = useCallback(() => { + setLoading(true); + + const validation = innerProps.onRepositorySave(tempRepository); + setFormErrors(validation.errors); + if (!validation.hasErrors) { + actions.closeModal(); + } + + setLoading(false); + }, [innerProps, tempRepository, actions]); + + const handleChange = useCallback((changedValue: Partial) => { + setTempRepository((prev) => ({ ...prev, ...changedValue })); + }, []); + + return ( + + + ({ + value: key, + label: value, + }))} + value={tempRepository.versionFilter?.precision.toString() ?? "0"} + onChange={(value) => { + const precision = value ? parseInt(value) : 0; + handleChange({ + versionFilter: + isNaN(precision) || precision <= 0 + ? undefined + : { + ...(tempRepository.versionFilter ?? {}), + precision, + }, + }); + }} + error={formErrors[`${innerProps.fieldPath}.versionFilter.precision`]} + /> + { + handleChange({ + versionFilter: { + ...(tempRepository.versionFilter ?? { precision: 0 }), + suffix: event.currentTarget.value, + }, + }); + }} + error={formErrors[`${innerProps.fieldPath}.versionFilter.suffix`]} + disabled={!tempRepository.versionFilter} + /> + + + + {tRepository("versionFilter.regex.label")}:{" "} + {formatVersionFilterRegex(tempRepository.versionFilter) ?? + tRepository("versionFilter.precision.options.none")} + + + + handleChange({ iconUrl: url })} + error={formErrors[`${innerProps.fieldPath}.iconUrl`] as string} + /> + + + + + + + + + ); +}).withOptions({ + defaultTitle(t) { + return t("widget.releases.option.repositories.editForm.title"); + }, + size: "xl", +}); diff --git a/packages/widgets/src/index.tsx b/packages/widgets/src/index.tsx index b9d21e825..36cdcfa9f 100644 --- a/packages/widgets/src/index.tsx +++ b/packages/widgets/src/index.tsx @@ -28,6 +28,7 @@ import * as networkControllerStatus from "./network-controller/network-status"; import * as networkControllerSummary from "./network-controller/summary"; import * as notebook from "./notebook"; import type { WidgetOptionDefinition } from "./options"; +import * as releases from "./releases"; import * as rssFeed from "./rssFeed"; import * as smartHomeEntityState from "./smart-home/entity-state"; import * as smartHomeExecuteAutomation from "./smart-home/execute-automation"; @@ -63,6 +64,7 @@ export const widgetImports = { healthMonitoring, mediaTranscoding, minecraftServerStatus, + releases, } satisfies WidgetImportRecord; export type WidgetImports = typeof widgetImports; diff --git a/packages/widgets/src/options.ts b/packages/widgets/src/options.ts index 4f74a13a7..8ea413c50 100644 --- a/packages/widgets/src/options.ts +++ b/packages/widgets/src/options.ts @@ -7,6 +7,7 @@ import type { ZodType } from "zod"; import type { IntegrationKind } from "@homarr/definitions"; import type { inferSelectOptionValue, SelectOption } from "./_inputs/widget-select-input"; +import type { ReleasesRepository } from "./releases/releases-repository"; interface CommonInput { defaultValue?: TType; @@ -119,6 +120,13 @@ const optionsFactory = { values: [] as string[], validate: input?.validate, }), + multiReleasesRepositories: (input?: CommonInput & { validate?: ZodType }) => ({ + type: "multiReleasesRepositories" as const, + defaultValue: input?.defaultValue ?? [], + withDescription: input?.withDescription ?? false, + values: [] as ReleasesRepository[], + validate: input?.validate, + }), app: () => ({ type: "app" as const, defaultValue: "", diff --git a/packages/widgets/src/releases/component.module.scss b/packages/widgets/src/releases/component.module.scss new file mode 100644 index 000000000..7501d7a13 --- /dev/null +++ b/packages/widgets/src/releases/component.module.scss @@ -0,0 +1,30 @@ +.releasesRepository { + border-left: 2px solid transparent; + + &:has(.releasesRepositoryHeader:hover), + &:has(.releasesRepositoryHeader.active), + &:has(.releasesRepositoryDetails:hover) { + border-left-color: var(--mantine-color-secondaryColor-text); + } +} + +.releasesRepositoryHeader { + user-select: none; + cursor: pointer; + + &:hover, + &.active, + &:has(~ .releasesRepositoryDetails:hover) { + background-color: var(--mantine-color-secondaryColor-light); + } +} + +.releasesRepositoryDetails { + background-color: var(--mantine-color-default-hover); + user-select: none; + cursor: pointer; +} + +.releasesRepositoryExpanded { + background-color: var(--mantine-color-default-hover); +} diff --git a/packages/widgets/src/releases/component.tsx b/packages/widgets/src/releases/component.tsx new file mode 100644 index 000000000..603b3db17 --- /dev/null +++ b/packages/widgets/src/releases/component.tsx @@ -0,0 +1,366 @@ +"use client"; + +import { useCallback, useMemo, useState } from "react"; +import { Button, Divider, Group, Stack, Text, Title, Tooltip } from "@mantine/core"; +import { + IconArchive, + IconCircleDot, + IconCircleFilled, + IconExternalLink, + IconGitFork, + IconProgressCheck, + IconStar, +} from "@tabler/icons-react"; +import combineClasses from "clsx"; +import { useFormatter, useNow } from "next-intl"; +import ReactMarkdown from "react-markdown"; + +import { clientApi } from "@homarr/api/client"; +import { useRequiredBoard } from "@homarr/boards/context"; +import { useScopedI18n } from "@homarr/translation/client"; +import { MaskedOrNormalImage } from "@homarr/ui"; + +import type { WidgetComponentProps } from "../definition"; +import classes from "./component.module.scss"; +import { Providers } from "./releases-providers"; +import type { ReleasesRepository } from "./releases-repository"; + +function isDateWithin(date: Date, relativeDate: string): boolean { + const amount = parseInt(relativeDate.slice(0, -1), 10); + const unit = relativeDate.slice(-1); + + const startTime = new Date().getTime(); + const endTime = new Date(date).getTime(); + const diffTime = Math.abs(endTime - startTime); + const diffHours = Math.ceil(diffTime / (1000 * 60 * 60)); + + switch (unit) { + case "h": + return diffHours < amount; + + case "d": + return diffHours / 24 < amount; + + case "w": + return diffHours / (24 * 7) < amount; + + case "m": + return diffHours / (24 * 30) < amount; + + case "y": + return diffHours / (24 * 365) < amount; + + default: + throw new Error("Invalid unit"); + } +} + +export default function ReleasesWidget({ options }: WidgetComponentProps<"releases">) { + const t = useScopedI18n("widget.releases"); + const now = useNow(); + const formatter = useFormatter(); + const board = useRequiredBoard(); + const [expandedRepository, setExpandedRepository] = useState(""); + const hasIconColor = useMemo(() => board.iconColor !== null, [board.iconColor]); + + const [results] = clientApi.widget.releases.getLatest.useSuspenseQuery( + { + repositories: options.repositories.map((repository) => ({ + providerKey: repository.providerKey, + identifier: repository.identifier, + versionFilter: repository.versionFilter, + })), + }, + { + refetchOnMount: false, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + retry: false, + }, + ); + + const repositories = useMemo(() => { + return results + .map(({ data }) => { + if (data === undefined) return undefined; + + const repository = options.repositories.find( + (repository: ReleasesRepository) => + repository.providerKey === data.providerKey && repository.identifier === data.identifier, + ); + + if (repository === undefined) return undefined; + + return { + ...repository, + ...data, + isNewRelease: + options.newReleaseWithin !== "" ? isDateWithin(data.latestReleaseAt, options.newReleaseWithin) : false, + isStaleRelease: + options.staleReleaseWithin !== "" ? !isDateWithin(data.latestReleaseAt, options.staleReleaseWithin) : false, + }; + }) + .filter( + (repository) => + repository !== undefined && + (!options.showOnlyHighlighted || repository.isNewRelease || repository.isStaleRelease), + ) + .sort((repoA, repoB) => { + if (repoA?.latestReleaseAt === undefined) return 1; + if (repoB?.latestReleaseAt === undefined) return -1; + return repoA.latestReleaseAt > repoB.latestReleaseAt ? -1 : 1; + }) as ReleasesRepository[]; + }, [ + results, + options.repositories, + options.showOnlyHighlighted, + options.newReleaseWithin, + options.staleReleaseWithin, + ]); + + const toggleExpandedRepository = useCallback( + (identifier: string) => { + setExpandedRepository(expandedRepository === identifier ? "" : identifier); + }, + [expandedRepository], + ); + + return ( + + {repositories.map((repository: ReleasesRepository) => { + const isActive = expandedRepository === repository.identifier; + + return ( + + toggleExpandedRepository(repository.identifier)} + > + + + + {repository.identifier} + + + + {repository.latestRelease ?? t("not-found")} + + + + + + + {repository.latestReleaseAt && + formatter.relativeTime(repository.latestReleaseAt, { + now, + style: "narrow", + })} + + {(repository.isNewRelease || repository.isStaleRelease) && ( + + )} + + + {options.showDetails && ( + + )} + {isActive && } + + + ); + })} + + ); +} + +interface DetailsDisplayProps { + repository: ReleasesRepository; + toggleExpandedRepository: (identifier: string) => void; +} + +const DetailsDisplay = ({ repository, toggleExpandedRepository }: DetailsDisplayProps) => { + const t = useScopedI18n("widget.releases"); + const formatter = useFormatter(); + + return ( + <> + toggleExpandedRepository(repository.identifier)} /> + toggleExpandedRepository(repository.identifier)} + > + + + + + + + + + + + + + + + + + + + {repository.starsCount === 0 + ? "-" + : formatter.number(repository.starsCount ?? 0, { + notation: "compact", + maximumFractionDigits: 1, + })} + + + + + + + + + {repository.forksCount === 0 + ? "-" + : formatter.number(repository.forksCount ?? 0, { + notation: "compact", + maximumFractionDigits: 1, + })} + + + + + + + + + {repository.openIssues === 0 + ? "-" + : formatter.number(repository.openIssues ?? 0, { + notation: "compact", + maximumFractionDigits: 1, + })} + + + + + + + ); +}; + +interface ExtendedDisplayProps { + repository: ReleasesRepository; + hasIconColor: boolean; +} + +const ExpandedDisplay = ({ repository, hasIconColor }: ExtendedDisplayProps) => { + const t = useScopedI18n("widget.releases"); + const now = useNow(); + const formatter = useFormatter(); + + return ( + <> + + + + + + + {Providers[repository.providerKey]?.name} + + + {repository.createdAt && ( + + {t("created")} + | + + {formatter.relativeTime(repository.createdAt, { + now, + style: "narrow", + })} + + + )} + + + + {repository.releaseDescription && ( + <> + + + {t("releaseDescription")} + + + {repository.releaseDescription} + + + )} + + + ); +}; diff --git a/packages/widgets/src/releases/index.ts b/packages/widgets/src/releases/index.ts new file mode 100644 index 000000000..1afeabb1e --- /dev/null +++ b/packages/widgets/src/releases/index.ts @@ -0,0 +1,53 @@ +import { IconRocket } from "@tabler/icons-react"; +import { z } from "zod"; + +import { createWidgetDefinition } from "../definition"; +import { optionsBuilder } from "../options"; + +export const { definition, componentLoader } = createWidgetDefinition("releases", { + icon: IconRocket, + createOptions() { + return optionsBuilder.from((factory) => ({ + newReleaseWithin: factory.text({ + defaultValue: "1w", + withDescription: true, + validate: z + .string() + .regex(/^\d+[hdwmy]$/) + .or(z.literal("")), + }), + staleReleaseWithin: factory.text({ + defaultValue: "6m", + withDescription: true, + validate: z + .string() + .regex(/^\d+[hdwmy]$/) + .or(z.literal("")), + }), + showOnlyHighlighted: factory.switch({ + withDescription: true, + defaultValue: true, + }), + showDetails: factory.switch({ + defaultValue: true, + }), + repositories: factory.multiReleasesRepositories({ + defaultValue: [], + validate: z.array( + z.object({ + providerKey: z.string().min(1), + identifier: z.string().min(1), + versionFilter: z + .object({ + prefix: z.string().optional(), + precision: z.number(), + suffix: z.string().optional(), + }) + .optional(), + iconUrl: z.string().url().optional(), + }), + ), + }), + })); + }, +}).withDynamicImport(() => import("./component")); diff --git a/packages/widgets/src/releases/releases-providers.ts b/packages/widgets/src/releases/releases-providers.ts new file mode 100644 index 000000000..a30c16908 --- /dev/null +++ b/packages/widgets/src/releases/releases-providers.ts @@ -0,0 +1,36 @@ +export interface ReleasesProvider { + name: string; + iconUrl: string; +} + +interface ProvidersProps { + [key: string]: ReleasesProvider; + DockerHub: ReleasesProvider; + Github: ReleasesProvider; + Gitlab: ReleasesProvider; + Npm: ReleasesProvider; + Codeberg: ReleasesProvider; +} + +export const Providers: ProvidersProps = { + DockerHub: { + name: "Docker Hub", + iconUrl: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/docker.svg", + }, + Github: { + name: "Github", + iconUrl: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/github-dark.svg", + }, + Gitlab: { + name: "Gitlab", + iconUrl: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitlab.svg", + }, + Npm: { + name: "Npm", + iconUrl: "https://cdn.jsdelivr.net/gh/loganmarchione/homelab-svg-assets//assets/npm.svg", + }, + Codeberg: { + name: "Codeberg", + iconUrl: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/codeberg.svg", + }, +}; diff --git a/packages/widgets/src/releases/releases-repository.ts b/packages/widgets/src/releases/releases-repository.ts new file mode 100644 index 000000000..a6fe7f368 --- /dev/null +++ b/packages/widgets/src/releases/releases-repository.ts @@ -0,0 +1,30 @@ +export interface ReleasesVersionFilter { + prefix?: string; + precision: number; + suffix?: string; +} + +export interface ReleasesRepository { + providerKey: string; + identifier: string; + versionFilter?: ReleasesVersionFilter; + iconUrl?: string; + + latestRelease?: string; + latestReleaseAt?: Date; + isNewRelease: boolean; + isStaleRelease: boolean; + + releaseUrl?: string; + releaseDescription?: string; + isPreRelease?: boolean; + + projectUrl?: string; + projectDescription?: string; + isFork?: boolean; + isArchived?: boolean; + createdAt?: Date; + starsCount?: number; + forksCount?: number; + openIssues?: number; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e657bf06..6d043e25e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,7 +78,7 @@ importers: version: 5.1.4(typescript@5.8.3)(vite@5.4.5(@types/node@22.15.2)(sass@1.87.0)(sugarss@4.0.1(postcss@8.4.47))(terser@5.39.0)) vitest: specifier: ^3.1.2 - version: 3.1.2(@types/node@22.15.2)(@vitest/ui@3.1.2)(jsdom@26.1.0)(sass@1.87.0)(sugarss@4.0.1(postcss@8.4.47))(terser@5.39.0) + version: 3.1.2(@types/debug@4.1.12)(@types/node@22.15.2)(@vitest/ui@3.1.2)(jsdom@26.1.0)(sass@1.87.0)(sugarss@4.0.1(postcss@8.4.47))(terser@5.39.0) apps/nextjs: dependencies: @@ -2051,6 +2051,9 @@ importers: '@homarr/form': specifier: workspace:^0.1.0 version: link:../form + '@homarr/forms-collection': + specifier: workspace:^0.1.0 + version: link:../forms-collection '@homarr/integrations': specifier: workspace:^0.1.0 version: link:../integrations @@ -2159,6 +2162,9 @@ importers: react-dom: specifier: 19.1.0 version: 19.1.0(react@19.1.0) + react-markdown: + specifier: ^10.1.0 + version: 10.1.0(@types/react@19.1.2)(react@19.1.0) recharts: specifier: ^2.15.3 version: 2.15.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -4773,12 +4779,18 @@ packages: '@types/d3-timer@3.0.2': resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/docker-modem@3.0.6': resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==} '@types/dockerode@3.3.38': resolution: {integrity: sha512-nnrcfUe2iR+RyOuz0B4bZgQwD9djQa9ADEjp7OAgBs10pYT0KSCtplJjcmBDJz0qaReX5T7GbE5i4VplvzUHvA==} + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -4797,6 +4809,9 @@ packages: '@types/hast@2.3.10': resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/http-errors@2.0.4': resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} @@ -4821,6 +4836,9 @@ packages: '@types/markdown-it@14.1.2': resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdurl@2.0.0': resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} @@ -4830,6 +4848,9 @@ packages: '@types/minimatch@5.1.2': resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node-cron@3.0.11': resolution: {integrity: sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==} @@ -4907,6 +4928,9 @@ packages: '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/use-sync-external-store@0.0.6': resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} @@ -4969,6 +4993,9 @@ packages: '@umami/node@0.4.0': resolution: {integrity: sha512-pyphprbiF7KiDSc+SWZ4/rVM8B5vU27zIiFfEPj2lEqczpI4xAKSp+dM3tlzyRAWJL32fcbCfAaLGhJZQV13Rg==} + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@videojs/http-streaming@3.17.0': resolution: {integrity: sha512-Ch1P3tvvIEezeZXyK11UfWgp4cWKX4vIhZ30baN/lRinqdbakZ5hiAI3pGjRy3d+q/Epyc8Csz5xMdKNNGYpcw==} engines: {node: '>=8', npm: '>=5'} @@ -5377,6 +5404,9 @@ packages: babel-plugin-syntax-hermes-parser@0.21.1: resolution: {integrity: sha512-tUCEa+EykZx3oJXc+PolKz2iwDscCJ2hCONMvEqjAb4jIQH5ZapDd5Brs2Nk4TQpSJ/1Ykz53ksQbevXbF0wxg==} + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -5556,6 +5586,9 @@ packages: caniuse-lite@1.0.30001703: resolution: {integrity: sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.2.0: resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} engines: {node: '>=12'} @@ -5583,15 +5616,27 @@ packages: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + character-entities-legacy@1.1.4: resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + character-entities@1.2.4: resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + character-reference-invalid@1.1.4: resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} @@ -5731,6 +5776,9 @@ packages: comma-separated-tokens@1.0.8: resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} @@ -6011,6 +6059,9 @@ packages: decimal.js@10.5.0: resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} + decode-named-character-reference@1.1.0: + resolution: {integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==} + decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -6094,6 +6145,9 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -6559,6 +6613,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} @@ -6600,6 +6657,9 @@ packages: resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} engines: {node: '>=12.0.0'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} @@ -7029,6 +7089,12 @@ packages: hast-util-parse-selector@2.2.5: resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==} + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hastscript@6.0.0: resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==} @@ -7077,6 +7143,9 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -7180,6 +7249,9 @@ packages: resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} engines: {node: '>=10'} + inline-style-parser@0.2.4: + resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} + inquirer@7.3.3: resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} engines: {node: '>=8.0.0'} @@ -7224,9 +7296,15 @@ packages: is-alphabetical@1.0.4: resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + is-alphanumerical@1.0.4: resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-array-buffer@3.0.4: resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} @@ -7291,6 +7369,9 @@ packages: is-decimal@1.0.4: resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -7317,6 +7398,9 @@ packages: is-hexadecimal@1.0.4: resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-installed-globally@0.4.0: resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} engines: {node: '>=10'} @@ -7742,6 +7826,9 @@ packages: long@5.2.3: resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -7832,6 +7919,30 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} @@ -7850,6 +7961,69 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -8455,6 +8629,9 @@ packages: parse-entities@2.0.0: resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-json@4.0.0: resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} engines: {node: '>=4'} @@ -8705,6 +8882,9 @@ packages: property-information@5.6.0: resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==} + property-information@7.0.0: + resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==} + prosemirror-changeset@2.2.1: resolution: {integrity: sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==} @@ -8884,6 +9064,12 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + react-number-format@5.4.3: resolution: {integrity: sha512-VCY5hFg/soBighAoGcdE+GagkJq0230qN6jcS5sp8wQX1qy1fYN/RX7/BXkrs0oyzzwqR8/+eSUrqXbGeywdUQ==} peerDependencies: @@ -9062,6 +9248,12 @@ packages: resolution: {integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==} engines: {node: '>=8'} + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.1: + resolution: {integrity: sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==} + remarkable@2.0.1: resolution: {integrity: sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==} engines: {node: '>= 6.0.0'} @@ -9428,6 +9620,9 @@ packages: space-separated-tokens@1.1.5: resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spawn-error-forwarder@1.0.0: resolution: {integrity: sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==} @@ -9543,6 +9738,9 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -9578,6 +9776,12 @@ packages: strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + style-to-js@1.1.16: + resolution: {integrity: sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==} + + style-to-object@1.0.8: + resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==} + styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -9849,10 +10053,16 @@ packages: tree-sitter@0.22.1: resolution: {integrity: sha512-gRO+jk2ljxZlIn20QRskIvpLCMtzuLl5T0BY6L9uvPYD17uUrxlxWkvYCiVqED2q2q7CVtY52Uex4WcYo2FEXw==} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + triple-beam@1.4.1: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + trpc-to-openapi@2.2.0: resolution: {integrity: sha512-tIKpykAKsZqad/AQXc0JKDd1Evq8fm8j3jVMVVJ+keo2KUb67uyucHRzEsJWSN99RPf6esuytsmnetx7Q0S9wg==} peerDependencies: @@ -10097,6 +10307,9 @@ packages: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + unique-string@2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} @@ -10105,6 +10318,21 @@ packages: resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} engines: {node: '>=12'} + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + universal-github-app-jwt@2.2.0: resolution: {integrity: sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==} @@ -10257,6 +10485,12 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + victory-vendor@36.9.2: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} @@ -10620,6 +10854,9 @@ packages: zod@3.24.3: resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@ampproject/remapping@2.3.0': @@ -13259,6 +13496,10 @@ snapshots: '@types/d3-timer@3.0.2': {} + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + '@types/docker-modem@3.0.6': dependencies: '@types/node': 22.15.2 @@ -13270,6 +13511,10 @@ snapshots: '@types/node': 22.15.2 '@types/ssh2': 1.15.1 + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.6 + '@types/estree@1.0.5': {} '@types/estree@1.0.6': {} @@ -13297,6 +13542,10 @@ snapshots: dependencies: '@types/unist': 2.0.11 + '@types/hast@3.0.4': + dependencies: + '@types/unist': 2.0.11 + '@types/http-errors@2.0.4': {} '@types/inquirer@6.5.0': @@ -13319,12 +13568,18 @@ snapshots: '@types/linkify-it': 5.0.0 '@types/mdurl': 2.0.0 + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 2.0.11 + '@types/mdurl@2.0.0': {} '@types/mime@1.3.5': {} '@types/minimatch@5.1.2': {} + '@types/ms@2.1.0': {} + '@types/node-cron@3.0.11': {} '@types/node-fetch@2.6.12': @@ -13412,6 +13667,8 @@ snapshots: '@types/unist@2.0.11': {} + '@types/unist@3.0.3': {} + '@types/use-sync-external-store@0.0.6': {} '@types/video.js@7.3.58': {} @@ -13503,6 +13760,8 @@ snapshots: '@umami/node@0.4.0': {} + '@ungap/structured-clone@1.3.0': {} + '@videojs/http-streaming@3.17.0(video.js@8.22.0)': dependencies: '@babel/runtime': 7.25.6 @@ -13556,7 +13815,7 @@ snapshots: std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.1.2(@types/node@22.15.2)(@vitest/ui@3.1.2)(jsdom@26.1.0)(sass@1.87.0)(sugarss@4.0.1(postcss@8.4.47))(terser@5.39.0) + vitest: 3.1.2(@types/debug@4.1.12)(@types/node@22.15.2)(@vitest/ui@3.1.2)(jsdom@26.1.0)(sass@1.87.0)(sugarss@4.0.1(postcss@8.4.47))(terser@5.39.0) transitivePeerDependencies: - supports-color @@ -13603,7 +13862,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.13 tinyrainbow: 2.0.0 - vitest: 3.1.2(@types/node@22.15.2)(@vitest/ui@3.1.2)(jsdom@26.1.0)(sass@1.87.0)(sugarss@4.0.1(postcss@8.4.47))(terser@5.39.0) + vitest: 3.1.2(@types/debug@4.1.12)(@types/node@22.15.2)(@vitest/ui@3.1.2)(jsdom@26.1.0)(sass@1.87.0)(sugarss@4.0.1(postcss@8.4.47))(terser@5.39.0) '@vitest/utils@3.1.2': dependencies: @@ -14019,6 +14278,8 @@ snapshots: dependencies: hermes-parser: 0.21.1 + bail@2.0.2: {} + balanced-match@1.0.2: {} bare-events@2.5.4: @@ -14204,6 +14465,8 @@ snapshots: caniuse-lite@1.0.30001703: {} + ccount@2.0.1: {} + chai@5.2.0: dependencies: assertion-error: 2.0.1 @@ -14253,12 +14516,20 @@ snapshots: char-regex@1.0.2: {} + character-entities-html4@2.1.0: {} + character-entities-legacy@1.1.4: {} + character-entities-legacy@3.0.0: {} + character-entities@1.2.4: {} + character-entities@2.0.2: {} + character-reference-invalid@1.1.4: {} + character-reference-invalid@2.0.1: {} + chardet@0.7.0: {} check-error@2.1.1: {} @@ -14390,6 +14661,8 @@ snapshots: comma-separated-tokens@1.0.8: {} + comma-separated-tokens@2.0.3: {} + commander@10.0.1: {} commander@2.20.3: {} @@ -14664,6 +14937,10 @@ snapshots: decimal.js@10.5.0: {} + decode-named-character-reference@1.1.0: + dependencies: + character-entities: 2.0.2 + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -14734,6 +15011,10 @@ snapshots: detect-node-es@1.1.0: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + diff@4.0.2: {} diff@5.2.0: {} @@ -15414,6 +15695,8 @@ snapshots: estraverse@5.3.0: {} + estree-util-is-identifier-name@3.0.0: {} + estree-walker@2.0.2: {} estree-walker@3.0.3: @@ -15471,6 +15754,8 @@ snapshots: expect-type@1.2.1: {} + extend@3.0.2: {} + external-editor@3.1.0: dependencies: chardet: 0.7.0 @@ -15941,6 +16226,30 @@ snapshots: hast-util-parse-selector@2.2.5: {} + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.6 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.0.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.16 + unist-util-position: 5.0.0 + vfile-message: 4.0.2 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + hastscript@6.0.0: dependencies: '@types/hast': 2.3.10 @@ -15986,6 +16295,8 @@ snapshots: html-escaper@2.0.2: {} + html-url-attributes@3.0.1: {} + http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -16081,6 +16392,8 @@ snapshots: ini@2.0.0: {} + inline-style-parser@0.2.4: {} + inquirer@7.3.3: dependencies: ansi-escapes: 4.3.2 @@ -16168,11 +16481,18 @@ snapshots: is-alphabetical@1.0.4: {} + is-alphabetical@2.0.1: {} + is-alphanumerical@1.0.4: dependencies: is-alphabetical: 1.0.4 is-decimal: 1.0.4 + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + is-array-buffer@3.0.4: dependencies: call-bind: 1.0.8 @@ -16241,6 +16561,8 @@ snapshots: is-decimal@1.0.4: {} + is-decimal@2.0.1: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -16261,6 +16583,8 @@ snapshots: is-hexadecimal@1.0.4: {} + is-hexadecimal@2.0.1: {} + is-installed-globally@0.4.0: dependencies: global-dirs: 3.0.1 @@ -16673,6 +16997,8 @@ snapshots: long@5.2.3: {} + longest-streak@3.1.0: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -16768,6 +17094,95 @@ snapshots: math-intrinsics@1.1.0: {} + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.1.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.0 + + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdurl@2.0.0: {} media-typer@0.3.0: {} @@ -16778,6 +17193,139 @@ snapshots: merge2@1.4.1: {} + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.1.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.0 + decode-named-character-reference: 1.1.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -17355,6 +17903,16 @@ snapshots: is-decimal: 1.0.4 is-hexadecimal: 1.0.4 + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.1.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + parse-json@4.0.0: dependencies: error-ex: 1.3.2 @@ -17590,6 +18148,8 @@ snapshots: dependencies: xtend: 4.0.2 + property-information@7.0.0: {} + prosemirror-changeset@2.2.1: dependencies: prosemirror-transform: 1.10.2 @@ -17829,6 +18389,24 @@ snapshots: react-is@18.3.1: {} + react-markdown@10.1.0(@types/react@19.1.2)(react@19.1.0): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 19.1.2 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.0 + react: 19.1.0 + remark-parse: 11.0.0 + remark-rehype: 11.1.1 + unified: 11.0.5 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + react-number-format@5.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 @@ -18054,6 +18632,23 @@ snapshots: dependencies: rc: 1.2.8 + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.0 + unified: 11.0.5 + vfile: 6.0.3 + remarkable@2.0.1: dependencies: argparse: 1.0.10 @@ -18529,6 +19124,8 @@ snapshots: space-separated-tokens@1.1.5: {} + space-separated-tokens@2.0.2: {} + spawn-error-forwarder@1.0.0: {} spdx-correct@3.2.0: @@ -18681,6 +19278,11 @@ snapshots: dependencies: safe-buffer: 5.2.1 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -18703,6 +19305,14 @@ snapshots: strnum@1.0.5: {} + style-to-js@1.1.16: + dependencies: + style-to-object: 1.0.8 + + style-to-object@1.0.8: + dependencies: + inline-style-parser: 0.2.4 + styled-jsx@5.1.6(@babel/core@7.26.0)(react@19.1.0): dependencies: client-only: 0.0.1 @@ -19062,8 +19672,12 @@ snapshots: node-gyp-build: 4.8.4 optional: true + trim-lines@3.0.1: {} + triple-beam@1.4.1: {} + trough@2.2.0: {} + trpc-to-openapi@2.2.0(@trpc/server@11.1.1(typescript@5.8.3))(zod-openapi@2.19.0(zod@3.24.3))(zod@3.24.3): dependencies: '@trpc/server': 11.1.1(typescript@5.8.3) @@ -19319,6 +19933,16 @@ snapshots: unicorn-magic@0.3.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + unique-string@2.0.0: dependencies: crypto-random-string: 2.0.0 @@ -19327,6 +19951,29 @@ snapshots: dependencies: crypto-random-string: 4.0.0 + unist-util-is@6.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + universal-github-app-jwt@2.2.0: {} universal-user-agent@7.0.2: {} @@ -19469,6 +20116,16 @@ snapshots: vary@1.1.2: {} + vfile-message@4.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.2 + victory-vendor@36.9.2: dependencies: '@types/d3-array': 3.2.1 @@ -19553,7 +20210,7 @@ snapshots: sugarss: 4.0.1(postcss@8.4.47) terser: 5.39.0 - vitest@3.1.2(@types/node@22.15.2)(@vitest/ui@3.1.2)(jsdom@26.1.0)(sass@1.87.0)(sugarss@4.0.1(postcss@8.4.47))(terser@5.39.0): + vitest@3.1.2(@types/debug@4.1.12)(@types/node@22.15.2)(@vitest/ui@3.1.2)(jsdom@26.1.0)(sass@1.87.0)(sugarss@4.0.1(postcss@8.4.47))(terser@5.39.0): dependencies: '@vitest/expect': 3.1.2 '@vitest/mocker': 3.1.2(vite@5.4.5(@types/node@22.15.2)(sass@1.87.0)(sugarss@4.0.1(postcss@8.4.47))(terser@5.39.0)) @@ -19577,6 +20234,7 @@ snapshots: vite-node: 3.1.2(@types/node@22.15.2)(sass@1.87.0)(sugarss@4.0.1(postcss@8.4.47))(terser@5.39.0) why-is-node-running: 2.3.0 optionalDependencies: + '@types/debug': 4.1.12 '@types/node': 22.15.2 '@vitest/ui': 3.1.2(vitest@3.1.2) jsdom: 26.1.0 @@ -19887,3 +20545,5 @@ snapshots: zod: 3.24.3 zod@3.24.3: {} + + zwitch@2.0.4: {}