diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1dcd899db2..66d16234089 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -968,6 +968,9 @@ jobs: retention-days: 7 if-no-files-found: error + - name: Setup Docker Registry Mirrors + uses: ./.github/actions/setup-docker-registry-mirrors + - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 @@ -1175,6 +1178,9 @@ jobs: - name: Extract public app artifacts run: tar -xzf e2e-public-apps.tar.gz + - name: Setup Docker Registry Mirrors + uses: ./.github/actions/setup-docker-registry-mirrors + - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 with: @@ -1275,6 +1281,9 @@ jobs: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Setup Docker Registry Mirrors + uses: ./.github/actions/setup-docker-registry-mirrors + - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 @@ -1298,9 +1307,6 @@ jobs: image-tags: ${{ needs.job_build_e2e_image.outputs.image-tags }} artifact-name: docker-image-e2e - - name: Setup Docker Registry Mirrors - uses: ./.github/actions/setup-docker-registry-mirrors - - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: diff --git a/.secretlintrc.json b/.secretlintrc.json index fcef33c49ab..abec5ba6fd3 100644 --- a/.secretlintrc.json +++ b/.secretlintrc.json @@ -16,7 +16,7 @@ { "name": "credential in URL query string", "patterns": [ - "/[?&](?:token|api[_-]?key|access[_-]?token|auth[_-]?token|client[_-]?secret|secret|password)=(?(?!p\\.)[^&\\s\"'<>]{16,})/i" + "/[?&](?:token|api[_-]?key|access[_-]?token|auth[_-]?token|client[_-]?secret|secret|password)=(?(?!p\\.|\\$\\{)[^&\\s\"'<>]{16,})/i" ] }, { diff --git a/apps/activitypub/package.json b/apps/activitypub/package.json index 8a828ecee4d..3ec8667791c 100644 --- a/apps/activitypub/package.json +++ b/apps/activitypub/package.json @@ -1,6 +1,6 @@ { "name": "@tryghost/activitypub", - "version": "3.1.14", + "version": "3.1.16", "license": "MIT", "repository": { "type": "git", @@ -39,13 +39,11 @@ "devDependencies": { "@playwright/test": "1.59.1", "@testing-library/react": "14.3.1", - "@types/dompurify": "3.2.0", "@types/jest": "29.5.14", "@types/react": "18.3.28", "@types/react-dom": "18.3.7", "jest": "29.7.0", "tailwindcss": "^4.2.2", - "ts-jest": "29.4.9", "vite": "5.4.21", "vitest": "1.6.1" }, diff --git a/apps/admin-x-design-system/package.json b/apps/admin-x-design-system/package.json index ad74ca2364b..96757407195 100644 --- a/apps/admin-x-design-system/package.json +++ b/apps/admin-x-design-system/package.json @@ -28,7 +28,6 @@ "@codemirror/lang-html": "6.4.11", "@codemirror/state": "6.6.0", "@dnd-kit/utilities": "^3.2.2", - "@radix-ui/react-tooltip": "1.2.8", "@storybook/addon-docs": "10.3.5", "@storybook/addon-links": "10.3.5", "@storybook/react-vite": "10.3.5", @@ -54,7 +53,6 @@ "postcss-import": "16.1.1", "react": "18.3.1", "react-dom": "18.3.1", - "rollup-plugin-node-builtins": "2.1.2", "sinon": "18.0.1", "storybook": "10.3.5", "tailwindcss": "4.2.1", diff --git a/apps/admin-x-framework/package.json b/apps/admin-x-framework/package.json index c9488651280..4b08fc47de8 100644 --- a/apps/admin-x-framework/package.json +++ b/apps/admin-x-framework/package.json @@ -88,7 +88,6 @@ "glob": "^10.5.0", "jsdom": "28.1.0", "msw": "2.12.14", - "sinon": "18.0.1", "typescript": "5.9.3", "vite": "5.4.21", "vite-plugin-css-injected-by-js": "3.5.2", diff --git a/apps/admin/package.json b/apps/admin/package.json index 159e3e5f95e..d89c14220a3 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -20,7 +20,6 @@ "@tryghost/posts": "workspace:*", "@tryghost/shade": "workspace:*", "@tryghost/stats": "workspace:*", - "lodash": "4.18.1", "mingo": "2.5.3", "react": "18.3.1", "react-dom": "18.3.1", diff --git a/apps/announcement-bar/package.json b/apps/announcement-bar/package.json index 85a88ba34af..5f15ee6a83d 100644 --- a/apps/announcement-bar/package.json +++ b/apps/announcement-bar/package.json @@ -1,6 +1,6 @@ { "name": "@tryghost/announcement-bar", - "version": "1.1.18", + "version": "1.1.19", "license": "MIT", "repository": "https://github.com/TryGhost/Ghost", "author": "Ghost Foundation", @@ -14,7 +14,6 @@ "registry": "https://registry.npmjs.org/" }, "dependencies": { - "@tryghost/content-api": "1.12.6", "react": "17.0.2", "react-dom": "17.0.2" }, diff --git a/apps/comments-ui/package.json b/apps/comments-ui/package.json index 16bb1c35977..6d9689ce034 100644 --- a/apps/comments-ui/package.json +++ b/apps/comments-ui/package.json @@ -1,6 +1,6 @@ { "name": "@tryghost/comments-ui", - "version": "1.4.10", + "version": "1.4.11", "license": "MIT", "repository": "https://github.com/TryGhost/Ghost", "author": "Ghost Foundation", @@ -64,7 +64,6 @@ "@playwright/test": "1.59.1", "@testing-library/jest-dom": "5.17.0", "@testing-library/react": "12.1.5", - "@testing-library/user-event": "14.6.1", "@tryghost/i18n": "workspace:*", "@vitejs/plugin-react": "4.7.0", "@vitest/coverage-v8": "0.34.6", diff --git a/apps/portal/package.json b/apps/portal/package.json index f92b5f1a758..aec4a02e471 100644 --- a/apps/portal/package.json +++ b/apps/portal/package.json @@ -1,6 +1,6 @@ { "name": "@tryghost/portal", - "version": "2.68.29", + "version": "2.68.30", "license": "MIT", "repository": "https://github.com/TryGhost/Ghost", "author": "Ghost Foundation", @@ -109,7 +109,6 @@ ] }, "devDependencies": { - "@babel/eslint-parser": "7.28.4", "@doist/react-interpolate": "2.2.1", "@sentry/react": "7.120.4", "@testing-library/jest-dom": "6.9.1", diff --git a/apps/posts/package.json b/apps/posts/package.json index 5c993f8ca67..189b4788942 100644 --- a/apps/posts/package.json +++ b/apps/posts/package.json @@ -55,13 +55,13 @@ "@tryghost/nql-lang": "0.6.4", "@tryghost/shade": "workspace:*", "i18n-iso-countries": "7.14.0", - "moment": "2.24.0", "moment-timezone": "0.5.45", "papaparse": "5.5.3", "react": "18.3.1", "react-dom": "18.3.1", "react-router": "7.14.0", "sonner": "2.0.7", + "temporal-polyfill": "0.3.0", "use-debounce": "10.1.1", "zod": "4.1.12" }, diff --git a/apps/posts/src/hooks/filter-sources/create-combined-value-source.ts b/apps/posts/src/hooks/filter-sources/create-combined-value-source.ts index d6a645358f0..ee2f4e71fc0 100644 --- a/apps/posts/src/hooks/filter-sources/create-combined-value-source.ts +++ b/apps/posts/src/hooks/filter-sources/create-combined-value-source.ts @@ -1,11 +1,12 @@ -import {ValueSource, ValueSourceParams, ValueSourceState} from '@tryghost/shade/patterns'; +import {FilterOption, ValueSource, ValueSourceParams, ValueSourceState} from '@tryghost/shade/patterns'; import {ValueSourceHook, ValueSourceHookOptions} from './create-remote-value-source'; import {mergeFilterOptions} from './utils'; import {useCallback, useMemo} from 'react'; export function createCombinedValueSource( useFirstSource: ValueSourceHook, - useSecondSource: ValueSourceHook + useSecondSource: ValueSourceHook, + getMissingSelectedOption?: (selectedValue: T) => FilterOption ): ValueSourceHook { return function useCombinedValueSource(options?: ValueSourceHookOptions): ValueSource { const firstSource = useFirstSource(options); @@ -14,9 +15,19 @@ export function createCombinedValueSource( const useOptions = useCallback(({query, selectedValues}: ValueSourceParams): ValueSourceState => { const firstState = firstSource.useOptions({query, selectedValues}); const secondState = secondSource.useOptions({query, selectedValues}); + const mergedOptions = mergeFilterOptions(firstState.options, secondState.options); + const fallbackOptions = getMissingSelectedOption ? selectedValues.flatMap((selectedValue) => { + const hasMatch = mergedOptions.some(option => option.value === selectedValue); + + if (hasMatch) { + return []; + } + + return [getMissingSelectedOption(selectedValue)]; + }) : []; return { - options: mergeFilterOptions(firstState.options, secondState.options), + options: mergeFilterOptions(mergedOptions, fallbackOptions), isInitialLoad: firstState.options.length === 0 && secondState.options.length === 0 && (firstState.isInitialLoad || secondState.isInitialLoad), diff --git a/apps/posts/src/hooks/filter-sources/create-ghost-browse-value-source.ts b/apps/posts/src/hooks/filter-sources/create-ghost-browse-value-source.ts index 0ceb3d88b39..573f1dd8166 100644 --- a/apps/posts/src/hooks/filter-sources/create-ghost-browse-value-source.ts +++ b/apps/posts/src/hooks/filter-sources/create-ghost-browse-value-source.ts @@ -25,6 +25,7 @@ interface CreateGhostBrowseValueSourceConfig { debounceMs?: number; selectItems: (data: Data | undefined) => Item[] | undefined; toOption: (item: Item) => FilterOption; + getMissingSelectedOption?: (selectedValue: string) => FilterOption; useQuery: ( options: {enabled: boolean; searchParams: Record} ) => InfiniteBrowseResult; @@ -53,6 +54,7 @@ export function createGhostBrowseValueSource) { return createRemoteValueSource({ @@ -93,6 +95,7 @@ export function createGhostBrowseValueSource { useBrowse: (query: string, options: ValueSourceHookOptions) => BrowseState; useHydrate?: (selectedValues: T[], options: ValueSourceHookOptions) => HydrateState; toOption: (item: Item) => FilterOption; + getMissingSelectedOption?: (selectedValue: T) => FilterOption; debounceMs?: number; } @@ -52,6 +53,26 @@ export type RemoteValueSource = ValueSource & { export type RemoteValueSourceHook = (options?: ValueSourceHookOptions) => RemoteValueSource; +function buildFallbackOptions( + selectedValues: T[], + mergedOptions: FilterOption[], + getMissingSelectedOption?: (selectedValue: T) => FilterOption +): FilterOption[] { + if (!getMissingSelectedOption) { + return []; + } + + return selectedValues.flatMap((selectedValue) => { + const hasMatch = mergedOptions.some(option => option.value === selectedValue); + + if (hasMatch) { + return []; + } + + return [getMissingSelectedOption(selectedValue)]; + }); +} + export function createRemoteValueSource( config: RemoteValueSourceConfig ): RemoteValueSourceHook { @@ -88,6 +109,13 @@ export function createRemoteValueSource( const mergedOptions = useMemo(() => { return mergeFilterOptions(hydratedOptions, visibleOptions); }, [hydratedOptions, visibleOptions]); + const fallbackOptions = useMemo(() => { + return buildFallbackOptions( + selectedValues, + mergedOptions, + config.getMissingSelectedOption + ); + }, [mergedOptions, selectedValues]); if (!enabled) { return { @@ -101,7 +129,7 @@ export function createRemoteValueSource( } return { - options: mergedOptions, + options: mergeFilterOptions(fallbackOptions, mergedOptions), isInitialLoad: browse.isLoading && mergedOptions.length === 0, isSearching: !browse.isLoading && browse.isRefreshing && !browse.isLoadingMore, isLoadingMore: browse.isLoadingMore, diff --git a/apps/posts/src/hooks/filter-sources/use-member-value-source.ts b/apps/posts/src/hooks/filter-sources/use-member-value-source.ts index 86cb7826b0c..79ed9e7b225 100644 --- a/apps/posts/src/hooks/filter-sources/use-member-value-source.ts +++ b/apps/posts/src/hooks/filter-sources/use-member-value-source.ts @@ -17,6 +17,10 @@ const useRemoteMemberValueSource = createGhostBrowseValueSource ({ + value, + label: `ID: ${value}` + }), selectItems: data => data?.members, useQuery: ({enabled, searchParams}) => { return useBrowseMembersInfinite({ diff --git a/apps/posts/src/hooks/filter-sources/use-post-resource-value-source.ts b/apps/posts/src/hooks/filter-sources/use-post-resource-value-source.ts index 8d9fd94edd6..62e77f5dcff 100644 --- a/apps/posts/src/hooks/filter-sources/use-post-resource-value-source.ts +++ b/apps/posts/src/hooks/filter-sources/use-post-resource-value-source.ts @@ -68,7 +68,11 @@ const usePublishedPageValueSource = createGhostBrowseValueSource ({ + value, + label: `ID: ${value}` + }) ); export function usePostResourceValueSource(): ValueSource { diff --git a/apps/posts/src/views/comments/comment-fields.ts b/apps/posts/src/views/comments/comment-fields.ts new file mode 100644 index 00000000000..e9966fa1598 --- /dev/null +++ b/apps/posts/src/views/comments/comment-fields.ts @@ -0,0 +1,129 @@ +import {DATE_FILTER_OPERATORS, DEFAULT_DATE_OPERATOR} from '../filters/filter-date'; +import {dateCodec, scalarCodec, textCodec} from '../filters/filter-codecs'; +import {defineFields} from '../filters/filter-types'; +import {extractComparator} from '../filters/filter-ast'; +import type {FilterCodec} from '../filters/filter-types'; + +const reportedCodec: FilterCodec = { + parse(node, ctx) { + const comparator = extractComparator(node as Record); + + if (!comparator || comparator.field !== 'count.reports') { + return null; + } + + if (comparator.operator === '$eq' && comparator.value === 0) { + return { + field: ctx.key, + operator: 'is', + values: ['false'] + }; + } + + if (comparator.operator === '$gt' && comparator.value === 0) { + return { + field: ctx.key, + operator: 'is', + values: ['true'] + }; + } + + return null; + }, + serialize(predicate) { + const value = predicate.values[0]; + + if (predicate.operator !== 'is') { + return null; + } + + if (value === 'true') { + return ['count.reports:>0']; + } + + if (value === 'false') { + return ['count.reports:0']; + } + + return null; + } +}; + +export const commentFields = defineFields({ + status: { + operators: ['is'], + ui: { + label: 'Status', + type: 'select', + searchable: false, + hideOperatorSelect: true + }, + options: [ + {value: 'published', label: 'Published'}, + {value: 'hidden', label: 'Hidden'} + ], + codec: scalarCodec() + }, + created_at: { + operators: DATE_FILTER_OPERATORS, + ui: { + label: 'Date', + defaultOperator: DEFAULT_DATE_OPERATOR, + type: 'date', + className: 'w-full max-w-32' + }, + codec: dateCodec() + }, + body: { + operators: ['contains', 'does-not-contain'], + parseKeys: ['html'], + ui: { + label: 'Text', + type: 'text', + placeholder: 'Search comment text...', + defaultOperator: 'contains', + className: 'w-full max-w-48', + popoverContentClassName: 'w-full max-w-48' + }, + codec: textCodec({field: 'html'}) + }, + post: { + operators: ['is', 'is-not'], + parseKeys: ['post_id'], + ui: { + label: 'Post', + type: 'select', + searchable: true, + className: 'w-full max-w-80', + popoverContentClassName: 'w-full max-w-[calc(100vw-32px)] max-w-80' + }, + codec: scalarCodec({field: 'post_id'}) + }, + author: { + operators: ['is', 'is-not'], + parseKeys: ['member_id'], + ui: { + label: 'Author', + type: 'select', + searchable: true, + className: 'w-80', + popoverContentClassName: 'w-80' + }, + codec: scalarCodec({field: 'member_id'}) + }, + reported: { + operators: ['is'], + parseKeys: ['count.reports'], + ui: { + label: 'Reported', + type: 'select', + searchable: false, + hideOperatorSelect: true + }, + options: [ + {value: 'true', label: 'Yes'}, + {value: 'false', label: 'No'} + ], + codec: reportedCodec + } +}); diff --git a/apps/posts/src/views/comments/comment-filter-query.ts b/apps/posts/src/views/comments/comment-filter-query.ts new file mode 100644 index 00000000000..88fc2d998ef --- /dev/null +++ b/apps/posts/src/views/comments/comment-filter-query.ts @@ -0,0 +1,38 @@ +import {commentFields} from './comment-fields'; +import {dispatchSimpleNodes, getFieldKeysByType, hasFieldKey, parseFilterToAst, serializePredicates, stampPredicates} from '../filters/filter-query-core'; +import type {AstNode} from '../filters/filter-ast'; +import type {FilterPredicate, ParsedPredicate} from '../filters/filter-types'; + +const TIMEZONE_SENSITIVE_COMMENT_FIELDS = getFieldKeysByType(commentFields, 'date'); + +function parseCommentNode(node: AstNode, timezone: string): ParsedPredicate[] { + if (Array.isArray(node.$and)) { + return (node.$and as AstNode[]).flatMap(child => parseCommentNode(child, timezone)); + } + + return dispatchSimpleNodes([node], commentFields, timezone); +} + +export function parseCommentFilter(filter: string | undefined, timezone: string): FilterPredicate[] { + const ast = parseFilterToAst(filter ?? ''); + + if (!ast) { + return []; + } + + return stampPredicates(parseCommentNode(ast, timezone)); +} + +export function hasTimezoneSensitiveCommentFilter(filter: string | undefined): boolean { + const ast = parseFilterToAst(filter ?? ''); + + if (!ast) { + return false; + } + + return hasFieldKey(ast, TIMEZONE_SENSITIVE_COMMENT_FIELDS); +} + +export function serializeCommentFilters(predicates: FilterPredicate[], timezone: string): string | undefined { + return serializePredicates(predicates, commentFields, timezone); +} diff --git a/apps/posts/src/views/comments/comments.tsx b/apps/posts/src/views/comments/comments.tsx index 8b667eb2341..6048bb528f2 100644 --- a/apps/posts/src/views/comments/comments.tsx +++ b/apps/posts/src/views/comments/comments.tsx @@ -3,23 +3,65 @@ import CommentsFilters from './components/comments-filters'; import CommentsHeader from './components/comments-header'; import CommentsLayout from './components/comments-layout'; import CommentsList from './components/comments-list'; -import React, {useCallback} from 'react'; +import React, {useCallback, useMemo} from 'react'; import {Button, EmptyIndicator, LoadingIndicator} from '@tryghost/shade/components'; import {LucideIcon} from '@tryghost/shade/utils'; import {createFilter} from '@tryghost/shade/patterns'; +import {escapeNqlString} from '../filters/filter-normalization'; +import {getSiteTimezone} from '@src/utils/get-site-timezone'; +import {serializeCommentFilters} from './comment-filter-query'; +import {shouldDelayCommentDateFilterHydration, useFilterState} from './hooks/use-filter-state'; import {useBrowseComments} from '@tryghost/admin-x-framework/api/comments'; -import {useFilterState} from './hooks/use-filter-state'; +import {useBrowseSettings} from '@tryghost/admin-x-framework/api/settings'; +import {useSearchParams} from 'react-router'; -const Comments: React.FC = () => { - const {filters, nql, setFilters, clearFilters, isSingleIdFilter} = useFilterState(); +function getSingleCommentIdParam(searchParams: URLSearchParams): string | undefined { + const value = searchParams.get('id'); + const match = value?.match(/^is:(.+)$/); + + return match?.[1]; +} + +const CommentsPage: React.FC<{timezone: string; singleCommentId?: string}> = ({ + timezone, + singleCommentId +}) => { + const [searchParams, setSearchParams] = useSearchParams(); + const {filters, nql, setFilters} = useFilterState(timezone); const handleAddFilter = useCallback((field: string, value: string, operator: string = 'is') => { - setFilters((prevFilters) => { - // Remove any existing filter for the same field - const filtered = prevFilters.filter(f => f.field !== field); - // Add the new filter - return [...filtered, createFilter(field, operator, [value])]; - }, {replace: false}); - }, [setFilters]); + const nextFilters = [ + ...filters.filter(filter => filter.field !== field), + createFilter(field, operator, [value]) + ]; + + if (!singleCommentId) { + setFilters(nextFilters, {replace: false}); + return; + } + + const nextSearchParams = new URLSearchParams(searchParams); + const nextNql = serializeCommentFilters(nextFilters, timezone); + + nextSearchParams.delete('id'); + nextSearchParams.delete('filter'); + + if (nextNql) { + nextSearchParams.set('filter', nextNql); + } + + setSearchParams(nextSearchParams, {replace: false}); + }, [filters, searchParams, setFilters, setSearchParams, singleCommentId, timezone]); + const effectiveFilter = useMemo(() => { + if (singleCommentId) { + return `id:${escapeNqlString(singleCommentId)}`; + } + + return nql; + }, [nql, singleCommentId]); + + const handleShowAllComments = useCallback(() => { + setSearchParams(new URLSearchParams(), {replace: false}); + }, [setSearchParams]); const { data, @@ -30,18 +72,21 @@ const Comments: React.FC = () => { fetchNextPage, hasNextPage } = useBrowseComments({ - searchParams: nql ? {filter: nql} : {}, + searchParams: { + ...(effectiveFilter ? {filter: effectiveFilter} : {}) + }, keepPreviousData: true }); - // If we are fetching comments, but not fetching the next page and not refetching, we should show the loading indicator const shouldShowLoading = isFetching && !isFetchingNextPage && !isRefetching; + const resetKey = effectiveFilter ?? ''; return ( - {!isSingleIdFilter && ( + {!singleCommentId && ( )} @@ -65,11 +110,22 @@ const Comments: React.FC = () => { ) : !data?.comments.length ? (
- - - + {singleCommentId ? ( +
+ + + + +
+ ) : ( + + + + )}
) : ( <> @@ -79,13 +135,13 @@ const Comments: React.FC = () => { isFetchingNextPage={isFetchingNextPage} isLoading={isFetching && !isFetchingNextPage} items={data?.comments ?? []} - resetKey={nql ?? ''} + resetKey={resetKey} totalItems={data?.meta?.pagination?.total ?? 0} onAddFilter={handleAddFilter} /> - {isSingleIdFilter && ( + {singleCommentId && (
-
@@ -97,4 +153,29 @@ const Comments: React.FC = () => { ); }; +const Comments: React.FC = () => { + const [searchParams] = useSearchParams(); + const {data: settingsData, isLoading: isSettingsLoading} = useBrowseSettings({}); + const singleCommentId = useMemo(() => getSingleCommentIdParam(searchParams), [searchParams]); + const filterParam = searchParams.get('filter') ?? undefined; + const shouldDelayHydration = !singleCommentId && shouldDelayCommentDateFilterHydration(filterParam, Boolean(settingsData), isSettingsLoading); + + if (shouldDelayHydration) { + return ( + + + +
+ +
+
+
+ ); + } + + const timezone = getSiteTimezone(settingsData?.settings ?? []); + + return ; +}; + export default Comments; diff --git a/apps/posts/src/views/comments/components/comments-filters.tsx b/apps/posts/src/views/comments/components/comments-filters.tsx index 5f689f44364..1b59dc17f59 100644 --- a/apps/posts/src/views/comments/components/comments-filters.tsx +++ b/apps/posts/src/views/comments/components/comments-filters.tsx @@ -1,110 +1,28 @@ -import React, {useMemo} from 'react'; -import {Filter, FilterFieldConfig, Filters} from '@tryghost/shade/patterns'; +import React from 'react'; +import {Filter, Filters} from '@tryghost/shade/patterns'; import {LucideIcon, cn} from '@tryghost/shade/utils'; +import {useCommentFilterFields} from '../use-comment-filter-fields'; import {useMemberValueSource} from '@src/hooks/filter-sources/use-member-value-source'; import {usePostResourceValueSource} from '@src/hooks/filter-sources/use-post-resource-value-source'; interface CommentsFiltersProps { filters: Filter[]; + siteTimezone: string; onFiltersChange: (filters: Filter[]) => void; } const CommentsFilters: React.FC = ({ filters, + siteTimezone, onFiltersChange }) => { const postValueSource = usePostResourceValueSource(); const memberValueSource = useMemberValueSource(); - - const filterFields: FilterFieldConfig[] = useMemo( - () => [ - { - key: 'author', - label: 'Author', - type: 'select', - icon: , - searchable: true, - valueSource: memberValueSource, - className: 'w-80', - popoverContentClassName: 'w-80', - operators: [ - {value: 'is', label: 'is'}, - {value: 'is_not', label: 'is not'} - ] - }, - { - key: 'post', - label: 'Post', - type: 'select', - icon: , - searchable: true, - valueSource: postValueSource, - className: 'w-full max-w-80', - popoverContentClassName: 'w-full max-w-[calc(100vw-32px)] max-w-80', - operators: [ - {value: 'is', label: 'is'}, - {value: 'is_not', label: 'is not'} - ] - }, - { - key: 'body', - label: 'Text', - type: 'text', - icon: , - placeholder: 'Search comment text...', - operators: [ - {value: 'contains', label: 'contains'}, - {value: 'not_contains', label: 'does not contain'} - ], - defaultOperator: 'contains', - className: 'w-full max-w-48', - popoverContentClassName: 'w-full max-w-48' - }, - { - key: 'status', - label: 'Status', - type: 'select', - icon: , - options: [ - {value: 'published', label: 'Published'}, - {value: 'hidden', label: 'Hidden'} - ], - operators: [ - {value: 'is', label: 'is'} - ], - searchable: false, - hideOperatorSelect: true - }, - { - key: 'reported', - label: 'Reported', - type: 'select', - icon: , - options: [ - {value: 'true', label: 'Yes'}, - {value: 'false', label: 'No'} - ], - operators: [ - {value: 'is', label: 'is'} - ], - searchable: false, - hideOperatorSelect: true - }, - { - key: 'created_at', - label: 'Date', - type: 'date', - className: 'w-full max-w-32', - icon: , - operators: [ - {value: 'is', label: 'is'}, - {value: 'before', label: 'before'}, - {value: 'after', label: 'after'} - ] - } - ], - [memberValueSource, postValueSource] - ); + const filterFields = useCommentFilterFields({ + memberValueSource, + postValueSource, + siteTimezone + }); const hasFilters = filters.length > 0; diff --git a/apps/posts/src/views/comments/hooks/use-filter-state.ts b/apps/posts/src/views/comments/hooks/use-filter-state.ts index 0e6822745b3..e771dfb7ee2 100644 --- a/apps/posts/src/views/comments/hooks/use-filter-state.ts +++ b/apps/posts/src/views/comments/hooks/use-filter-state.ts @@ -1,213 +1,105 @@ import {Filter} from '@tryghost/shade/patterns'; -import {useCallback, useMemo} from 'react'; +import {hasTimezoneSensitiveCommentFilter, parseCommentFilter, serializeCommentFilters} from '../comment-filter-query'; +import {parseLegacyCommentFilters, removeLegacyCommentFilterParams} from '../legacy-comment-filter-query'; +import {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {useSearchParams} from '@tryghost/admin-x-framework'; -/** - * Comment filter field keys - single source of truth for filter definitions - */ -export const COMMENT_FILTER_FIELDS = ['id', 'status', 'created_at', 'body', 'post', 'author', 'reported'] as const; - -export type CommentFilterField = typeof COMMENT_FILTER_FIELDS[number]; - -export function buildNqlFilter(filters: Filter[]): string | undefined { - const parts: string[] = []; - - for (const filter of filters) { - if (!filter.values[0]) { - continue; - } - - switch (filter.field) { - case 'id': - parts.push(`id:'${filter.values[0]}'`); - break; - - case 'status': - parts.push(`status:${filter.values[0]}`); - break; - - case 'created_at': - if (filter.operator === 'before' && filter.values[0]) { - parts.push(`created_at:<'${filter.values[0]}'`); - } else if (filter.operator === 'after' && filter.values[0]) { - parts.push(`created_at:>'${filter.values[0]}'`); - } else if (filter.operator === 'is' && filter.values[0]) { - // Match all items from the selected day in the user's timezone - const dateValue = String(filter.values[0]); // Format: YYYY-MM-DD - - // Create Date objects in user's local timezone, then convert to UTC - const startOfDay = new Date(dateValue + 'T00:00:00').toISOString(); - const endOfDay = new Date(dateValue + 'T23:59:59.999').toISOString(); - - parts.push(`created_at:>='${startOfDay}'+created_at:<='${endOfDay}'`); - } - break; - - case 'body': - const value = filter.values[0] as string; - // Escape single quotes in the value - const escapedValue = value.replace(/'/g, '\\\''); - - if (filter.operator === 'contains') { - parts.push(`html:~'${escapedValue}'`); - } else if (filter.operator === 'not_contains') { - parts.push(`html:-~'${escapedValue}'`); - } - break; - - case 'post': - if (filter.operator === 'is_not') { - parts.push(`post_id:-${filter.values[0]}`); - } else { - // Default to 'is' operator - parts.push(`post_id:${filter.values[0]}`); - } - break; - - case 'author': - if (filter.operator === 'is_not') { - parts.push(`member_id:-${filter.values[0]}`); - } else { - // Default to 'is' operator - parts.push(`member_id:${filter.values[0]}`); - } - break; - - case 'reported': - if (filter.values[0] === 'true') { - parts.push('count.reports:>0'); - } else if (filter.values[0] === 'false') { - parts.push('count.reports:0'); - } - break; - } - } +type SetFiltersAction = Filter[] | ((prevFilters: Filter[]) => Filter[]); - return parts.length ? parts.join('+') : undefined; +interface SetFiltersOptions { + /** Whether to replace the current history entry (default: true) */ + replace?: boolean; } -/** - * Parse a filter value from URL format: "operator:value" - * e.g., "is:published", "contains:hello" - */ -function parseFilterValue(queryValue: string): {operator: string; value: string} | null { - if (!queryValue) { - return null; - } - const colonIndex = queryValue.indexOf(':'); - if (colonIndex <= 0) { - return null; // Invalid format, must have operator:value - } - - return { - operator: queryValue.substring(0, colonIndex), - value: queryValue.substring(colonIndex + 1) - }; +interface UseFilterStateReturn { + filters: Filter[]; + nql: string | undefined; + setFilters: (action: SetFiltersAction, options?: SetFiltersOptions) => void; + clearFilters: (options?: SetFiltersOptions) => void; } -/** - * Parse URL search params into Filter objects - * Preserves the order of filters as they appear in the URL - */ -function searchParamsToFilters(searchParams: URLSearchParams): Filter[] { - const filters: Filter[] = []; - - // Iterate over URL params in order to preserve filter order - for (const [field, queryValue] of searchParams.entries()) { - // Only process valid filter fields - if (!COMMENT_FILTER_FIELDS.includes(field as CommentFilterField)) { - continue; - } +function toSearchParams(baseSearchParams: URLSearchParams, filters: Filter[], timezone: string): URLSearchParams { + const params = new URLSearchParams(baseSearchParams); + const filter = serializeCommentFilters(filters, timezone); - if (!queryValue) { - continue; - } + params.delete('filter'); + removeLegacyCommentFilterParams(params); - const parsed = parseFilterValue(queryValue); - if (parsed) { - filters.push({ - id: field, - field, - operator: parsed.operator, - values: [parsed.value] - }); - } + if (filter) { + params.set('filter', filter); } - return filters; + return params; } -/** - * Serialize filters to URL search params format - */ -function filtersToSearchParams(filters: Filter[]): URLSearchParams { - const params = new URLSearchParams(); +export function shouldDelayCommentDateFilterHydration( + filterParam: string | undefined, + hasResolvedTimezone: boolean, + isSettingsLoading: boolean = !hasResolvedTimezone +): boolean { + return Boolean(filterParam) && isSettingsLoading && !hasResolvedTimezone && hasTimezoneSensitiveCommentFilter(filterParam); +} - for (const filter of filters) { - if (COMMENT_FILTER_FIELDS.includes(filter.field as CommentFilterField) && filter.values[0] !== undefined) { - const value = `${filter.operator}:${String(filter.values[0])}`; - params.set(filter.field, value); - } - } +export function useFilterState(timezone: string): UseFilterStateReturn { + const [searchParams, setSearchParams] = useSearchParams(); + const lastWrittenQueryRef = useRef(null); + const filterParam = useMemo(() => searchParams.get('filter') ?? undefined, [searchParams]); + const currentQuery = useMemo(() => searchParams.toString(), [searchParams]); - return params; -} + const parsedFilters = useMemo(() => { + if (filterParam !== undefined) { + return parseCommentFilter(filterParam, timezone); + } -type SetFiltersAction = Filter[] | ((prevFilters: Filter[]) => Filter[]); + return parseLegacyCommentFilters(searchParams); + }, [filterParam, searchParams, timezone]); + const [filters, setDraftFilters] = useState(parsedFilters); -interface SetFiltersOptions { - /** Whether to replace the current history entry (default: true) */ - replace?: boolean; -} + const nql = useMemo(() => { + return serializeCommentFilters(filters, timezone); + }, [filters, timezone]); -interface ClearFiltersOptions { - /** Whether to replace the current history entry (default: true) */ - replace?: boolean; -} + useEffect(() => { + if (currentQuery !== lastWrittenQueryRef.current) { + setDraftFilters(parsedFilters); + lastWrittenQueryRef.current = currentQuery; + } + }, [currentQuery, parsedFilters]); -interface UseFilterStateReturn { - filters: Filter[]; - nql: string | undefined; - setFilters: (action: SetFiltersAction, options?: SetFiltersOptions) => void; - clearFilters: (options?: ClearFiltersOptions) => void; - /** True when the only active filter is a single comment ID (used for deep linking) */ - isSingleIdFilter: boolean; -} + useEffect(() => { + if (lastWrittenQueryRef.current !== null && currentQuery !== lastWrittenQueryRef.current) { + return; + } -/** - * Hook to sync comment filter state with URL query parameters - * - * URL format: ?status=is:published&author=is:member-id&body=contains:search+term - */ -export function useFilterState(): UseFilterStateReturn { - const [searchParams, setSearchParams] = useSearchParams(); + const nextParams = toSearchParams(searchParams, filters, timezone); + const nextQuery = nextParams.toString(); - // Parse filters from URL - const filters = useMemo(() => { - return searchParamsToFilters(searchParams); - }, [searchParams]); + if (nextQuery !== currentQuery) { + lastWrittenQueryRef.current = nextQuery; + setSearchParams(nextParams, {replace: true}); + } + }, [currentQuery, filters, searchParams, setSearchParams, timezone]); - // Update URL when filters change const setFilters = useCallback((action: SetFiltersAction, options: SetFiltersOptions = {}) => { const newFilters = typeof action === 'function' ? action(filters) : action; - const newParams = filtersToSearchParams(newFilters); - - // Update URL - replace by default, but allow pushing to history + const newParams = toSearchParams(searchParams, newFilters, timezone); const replace = options.replace ?? true; + + setDraftFilters(newFilters); + lastWrittenQueryRef.current = newParams.toString(); setSearchParams(newParams, {replace}); - }, [filters, setSearchParams]); + }, [filters, searchParams, setSearchParams, timezone]); - // Clear all filter params from URL - const clearFilters = useCallback(({replace = true}: {replace?: boolean} = {}) => { - setSearchParams(new URLSearchParams(), {replace}); - }, [setSearchParams]); + const clearFilters = useCallback(({replace = true}: SetFiltersOptions = {}) => { + const newParams = new URLSearchParams(searchParams); - const nql = useMemo(() => buildNqlFilter(filters), [filters]); + newParams.delete('filter'); - // Check if the only active filter is a single comment ID (used for deep linking) - const isSingleIdFilter = useMemo(() => { - return filters.length === 1 && filters[0].field === 'id'; - }, [filters]); + removeLegacyCommentFilterParams(newParams); + setDraftFilters([]); + lastWrittenQueryRef.current = newParams.toString(); + setSearchParams(newParams, {replace}); + }, [searchParams, setSearchParams]); - return {filters, nql, setFilters, clearFilters, isSingleIdFilter}; + return {filters, nql, setFilters, clearFilters}; } diff --git a/apps/posts/src/views/comments/legacy-comment-filter-query.ts b/apps/posts/src/views/comments/legacy-comment-filter-query.ts new file mode 100644 index 00000000000..8c40b642c60 --- /dev/null +++ b/apps/posts/src/views/comments/legacy-comment-filter-query.ts @@ -0,0 +1,61 @@ +import {Filter} from '@tryghost/shade/patterns'; + +// TODO: Remove this file after the comment filters migration has safely rolled out. +const LEGACY_COMMENT_FILTER_FIELDS = ['status', 'created_at', 'body', 'post', 'author', 'reported'] as const; +const LEGACY_OPERATOR_MAP: Record = { + is_not: 'is-not', + not_contains: 'does-not-contain', + before: 'is-less', + after: 'is-greater', + on_or_before: 'is-or-less', + on_or_after: 'is-or-greater' +}; + +function parseLegacyFilterValue(queryValue: string): {operator: string; value: string} | null { + const colonIndex = queryValue.indexOf(':'); + + if (colonIndex <= 0) { + return null; + } + + const operator = queryValue.substring(0, colonIndex); + const value = queryValue.substring(colonIndex + 1); + + if (!value) { + return null; + } + + return { + operator: LEGACY_OPERATOR_MAP[operator] ?? operator, + value + }; +} + +export function parseLegacyCommentFilters(searchParams: URLSearchParams): Filter[] { + const filters: Filter[] = []; + + for (const [field, queryValue] of searchParams.entries()) { + if (!LEGACY_COMMENT_FILTER_FIELDS.includes(field as typeof LEGACY_COMMENT_FILTER_FIELDS[number])) { + continue; + } + + const parsed = parseLegacyFilterValue(queryValue); + + if (!parsed) { + continue; + } + + filters.push({ + id: `${field}:${filters.length + 1}`, + field, + operator: parsed.operator, + values: [parsed.value] + }); + } + + return filters; +} + +export function removeLegacyCommentFilterParams(searchParams: URLSearchParams): void { + LEGACY_COMMENT_FILTER_FIELDS.forEach(field => searchParams.delete(field)); +} diff --git a/apps/posts/src/views/comments/use-comment-filter-fields.ts b/apps/posts/src/views/comments/use-comment-filter-fields.ts new file mode 100644 index 00000000000..f6cb8b92200 --- /dev/null +++ b/apps/posts/src/views/comments/use-comment-filter-fields.ts @@ -0,0 +1,59 @@ +import React, {useMemo} from 'react'; +import {DATE_OPERATOR_LABELS} from '../filters/filter-date'; +import {FilterFieldConfig, ValueSource} from '@tryghost/shade/patterns'; +import {LucideIcon} from '@tryghost/shade/utils'; +import {commentFields} from './comment-fields'; +import {createOperatorOptions} from '../filters/filter-operator-options'; +import {getTodayInTimezone} from '../filters/filter-normalization'; + +interface UseCommentFilterFieldsOptions { + postValueSource: ValueSource; + memberValueSource: ValueSource; + siteTimezone?: string; +} + +const COMMENT_FIELD_ORDER = ['author', 'post', 'body', 'status', 'reported', 'created_at'] as const; + +function getFieldIcon(key: string) { + switch (key) { + case 'author': + return React.createElement(LucideIcon.User, {className: 'size-4'}); + case 'post': + return React.createElement(LucideIcon.FileText, {className: 'size-4'}); + case 'body': + return React.createElement(LucideIcon.MessageSquareText, {className: 'size-4'}); + case 'status': + return React.createElement(LucideIcon.Circle, {className: 'size-4'}); + case 'reported': + return React.createElement(LucideIcon.Flag, {className: 'size-4'}); + case 'created_at': + return React.createElement(LucideIcon.Calendar, {className: 'size-4'}); + default: + return undefined; + } +} + +export function useCommentFilterFields({ + postValueSource, + memberValueSource, + siteTimezone = 'UTC' +}: UseCommentFilterFieldsOptions): FilterFieldConfig[] { + return useMemo(() => { + const today = getTodayInTimezone(siteTimezone); + + return COMMENT_FIELD_ORDER.map((key) => { + const field = commentFields[key]; + + return { + key, + ...field.ui, + icon: getFieldIcon(key), + operators: createOperatorOptions(field.operators, {labels: DATE_OPERATOR_LABELS}), + ...('options' in field && field.options ? {options: field.options} : {}), + ...(key === 'created_at' ? {defaultValue: today} : {}), + ...(key === 'author' ? {valueSource: memberValueSource} : {}), + ...(key === 'post' ? {valueSource: postValueSource} : {}) + }; + }); + }, [memberValueSource, postValueSource, siteTimezone]); +} diff --git a/apps/posts/src/views/filters/filter-codecs.test.ts b/apps/posts/src/views/filters/filter-codecs.test.ts index ba805292c4e..dc27f8ba39d 100644 --- a/apps/posts/src/views/filters/filter-codecs.test.ts +++ b/apps/posts/src/views/filters/filter-codecs.test.ts @@ -1,6 +1,6 @@ import nql from '@tryghost/nql-lang'; +import {dateCodec, numberCodec, scalarCodec, setCodec, textCodec} from './filter-codecs'; import {describe, expect, it} from 'vitest'; -import {numberCodec, scalarCodec, setCodec, textCodec} from './filter-codecs'; import type {CodecContext, FilterPredicate} from './filter-types'; const statusContext: CodecContext = { @@ -52,6 +52,13 @@ const countContext: CodecContext = { timezone: 'UTC' }; +const dateContext: CodecContext = { + key: 'created_at', + pattern: 'created_at', + params: {}, + timezone: 'UTC' +}; + describe('scalarCodec', () => { it('parses simple scalar comparisons', () => { expect(scalarCodec().parse(nql.parse('status:paid') as never, statusContext)).toEqual({ @@ -335,3 +342,41 @@ describe('numberCodec', () => { expect(numberCodec().serialize(predicate, countContext)).toEqual(['email_count:<=10']); }); }); + +describe('dateCodec', () => { + it('parses date comparison operators', () => { + expect(dateCodec().parse(nql.parse('created_at:<=\'2024-01-01T23:59:59.999Z\'') as never, dateContext)).toEqual({ + field: 'created_at', + operator: 'is-or-less', + values: ['2024-01-01'] + }); + + expect(dateCodec().parse(nql.parse('created_at:>\'2024-01-01T23:59:59.999Z\'') as never, dateContext)).toEqual({ + field: 'created_at', + operator: 'is-greater', + values: ['2024-01-01'] + }); + }); + + it('serializes date comparison operators using site timezone day bounds', () => { + expect(dateCodec().serialize({ + id: '1', + field: 'created_at', + operator: 'is-or-less', + values: ['2024-02-01'] + }, { + ...dateContext, + timezone: 'Europe/Stockholm' + })).toEqual(['created_at:<=\'2024-02-01T22:59:59.999Z\'']); + }); + + it('returns null for invalid date values', () => { + expect(dateCodec().parse(nql.parse('created_at:<=\'not-a-date\'') as never, dateContext)).toBeNull(); + expect(dateCodec().serialize({ + id: '1', + field: 'created_at', + operator: 'is-or-less', + values: ['not-a-date'] + }, dateContext)).toBeNull(); + }); +}); diff --git a/apps/posts/src/views/filters/filter-codecs.ts b/apps/posts/src/views/filters/filter-codecs.ts index 5c6f4bbd525..06d8c4662ca 100644 --- a/apps/posts/src/views/filters/filter-codecs.ts +++ b/apps/posts/src/views/filters/filter-codecs.ts @@ -1,7 +1,10 @@ -import {escapeNqlString} from './filter-normalization'; +import {DATE_FILTER_OPERATORS} from './filter-date'; +import {escapeNqlString, formatDateInTimezone, getDayBoundsInUtc} from './filter-normalization'; import {extractComparator} from './filter-ast'; import type {FilterCodec} from './filter-types'; +type DateOperator = typeof DATE_FILTER_OPERATORS[number]; + const SCALAR_OPERATORS: Record = { $eq: 'is', $ne: 'is-not' @@ -15,6 +18,13 @@ const NUMBER_OPERATORS: Record = { $lte: 'is-or-less' }; +const DATE_OPERATORS: Record = { + $lt: 'is-less', + $lte: 'is-or-less', + $gt: 'is-greater', + $gte: 'is-or-greater' +}; + const TEXT_OPERATOR_SYMBOLS: Record = { contains: '~', 'does-not-contain': '-~', @@ -32,6 +42,13 @@ const NUMBER_OPERATOR_SYMBOLS: Record = { 'is-or-less': '<=' }; +const DATE_OPERATOR_SYMBOLS: Record = { + 'is-less': '<', + 'is-or-less': '<=', + 'is-greater': '>', + 'is-or-greater': '>=' +}; + const SET_OPERATOR_SYMBOLS: Record = { 'is-any': '', 'is-not-any': '-' @@ -314,3 +331,56 @@ export function numberCodec(config?: CodecConfig): FilterCodec { } }; } + +export function dateCodec(config?: CodecConfig): FilterCodec { + return { + parse(node, ctx) { + const comparator = extractComparator(node as Record); + const field = getCodecField(config, ctx.key); + + if (!comparator || comparator.field !== field || typeof comparator.value !== 'string') { + return null; + } + + const operator = DATE_OPERATORS[comparator.operator]; + const value = formatDateInTimezone(comparator.value, ctx.timezone); + + if (!operator || !value) { + return null; + } + + return { + field: ctx.key, + operator, + values: [value] + }; + }, + serialize(predicate, ctx) { + const rawValue = predicate.values[0]; + const field = getCodecField(config, ctx.key); + + if (typeof rawValue !== 'string' || rawValue === '') { + return null; + } + + const value = formatDateInTimezone(rawValue, ctx.timezone); + + if (!value) { + return null; + } + + const {start, end} = getDayBoundsInUtc(value, ctx.timezone); + const operator = DATE_OPERATOR_SYMBOLS[predicate.operator]; + + if (operator === undefined) { + return null; + } + + const boundary = predicate.operator === 'is-less' || predicate.operator === 'is-or-greater' + ? start + : end; + + return [`${field}:${operator}'${boundary}'`]; + } + }; +} diff --git a/apps/posts/src/views/filters/filter-date.ts b/apps/posts/src/views/filters/filter-date.ts new file mode 100644 index 00000000000..0bdd1567e43 --- /dev/null +++ b/apps/posts/src/views/filters/filter-date.ts @@ -0,0 +1,10 @@ +export const DATE_FILTER_OPERATORS = ['is-less', 'is-or-less', 'is-greater', 'is-or-greater'] as const; + +export const DATE_OPERATOR_LABELS: Record = { + 'is-less': 'before', + 'is-or-less': 'on or before', + 'is-greater': 'after', + 'is-or-greater': 'on or after' +}; + +export const DEFAULT_DATE_OPERATOR = 'is-or-less'; diff --git a/apps/posts/src/views/filters/filter-normalization.test.ts b/apps/posts/src/views/filters/filter-normalization.test.ts deleted file mode 100644 index 22cd601bacc..00000000000 --- a/apps/posts/src/views/filters/filter-normalization.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {describe, expect, it} from 'vitest'; -import {escapeNqlString} from './filter-normalization'; - -describe('filter-normalization', () => { - it('escapes single quotes for NQL strings', () => { - expect(escapeNqlString('can\'t stop')).toBe('\'can\\\'t stop\''); - }); - - it('escapes backslashes before single quotes for NQL strings', () => { - expect(escapeNqlString('test\\\'value')).toBe('\'test\\\\\\\'value\''); - }); -}); diff --git a/apps/posts/src/views/filters/filter-normalization.ts b/apps/posts/src/views/filters/filter-normalization.ts index 25fded8d67f..893b04c78db 100644 --- a/apps/posts/src/views/filters/filter-normalization.ts +++ b/apps/posts/src/views/filters/filter-normalization.ts @@ -1,3 +1,50 @@ +import {Temporal} from 'temporal-polyfill'; + export function escapeNqlString(value: string): string { return `'${value.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}'`; } + +const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/; +const LEGACY_UTC_DATE_TIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?$/; + +export function formatDateInTimezone(value: string, timezone: string): string | null { + try { + if (DATE_ONLY_PATTERN.test(value)) { + return Temporal.PlainDate.from(value).toString(); + } + + const instantValue = LEGACY_UTC_DATE_TIME_PATTERN.test(value) + ? `${value.replace(' ', 'T')}Z` + : value; + + return Temporal.Instant.from(instantValue).toZonedDateTimeISO(timezone).toPlainDate().toString(); + } catch { + return null; + } +} + +export function getTodayInTimezone(timezone: string): string { + return Temporal.Now.zonedDateTimeISO(timezone).toPlainDate().toString(); +} + +export function getDayBoundsInUtc(date: string, timezone: string): {start: string; end: string} { + let plainDate: Temporal.PlainDate; + + try { + plainDate = Temporal.PlainDate.from(date); + } catch { + throw new Error(`Invalid filter date: ${date}`); + } + + try { + const start = plainDate.toPlainDateTime(Temporal.PlainTime.from('00:00:00')).toZonedDateTime(timezone).toInstant(); + const end = plainDate.toPlainDateTime(Temporal.PlainTime.from('23:59:59.999')).toZonedDateTime(timezone).toInstant(); + + return { + start: start.toString({fractionalSecondDigits: 3}), + end: end.toString({fractionalSecondDigits: 3}) + }; + } catch { + throw new Error(`Invalid timezone: ${timezone}`); + } +} diff --git a/apps/posts/src/views/filters/filter-operator-options.ts b/apps/posts/src/views/filters/filter-operator-options.ts new file mode 100644 index 00000000000..095f6a05bb0 --- /dev/null +++ b/apps/posts/src/views/filters/filter-operator-options.ts @@ -0,0 +1,20 @@ +interface OperatorOption { + value: string; + label: string; +} + +interface CreateOperatorOptionsOptions { + labels?: Record; +} + +export function createOperatorOptions( + operators: readonly string[], + options: CreateOperatorOptionsOptions = {} +): OperatorOption[] { + const labels = options.labels || {}; + + return operators.map(operator => ({ + value: operator, + label: labels[operator] ?? operator.replaceAll('-', ' ') + })); +} diff --git a/apps/posts/src/views/filters/filter-query-core.test.ts b/apps/posts/src/views/filters/filter-query-core.test.ts index fe97afadb41..4b5648acb06 100644 --- a/apps/posts/src/views/filters/filter-query-core.test.ts +++ b/apps/posts/src/views/filters/filter-query-core.test.ts @@ -1,6 +1,6 @@ import {defineFields} from './filter-types'; import {describe, expect, it} from 'vitest'; -import {dispatchSimpleNodes, parseFilterToAst, serializePredicates} from './filter-query-core'; +import {dispatchSimpleNodes, getFieldKeysByType, hasFieldKey, parseFilterToAst, serializePredicates} from './filter-query-core'; import {numberCodec, scalarCodec} from './filter-codecs'; import type {AstNode} from './filter-ast'; import type {FilterPredicate} from './filter-types'; @@ -38,6 +38,15 @@ const fields = defineFields({ type: 'select' }, codec: scalarCodec({field: 'member_id'}) + }, + created_at: { + operators: ['is-or-less'], + parseKeys: ['created_at_utc'], + ui: { + label: 'Created', + type: 'date' + }, + codec: scalarCodec({field: 'created_at_utc'}) } }); @@ -102,4 +111,12 @@ describe('filter-query-core', () => { expect(serializePredicates(parsed, fields, 'UTC')).toBe('email_count:>5+status:paid'); }); + + it('finds fields by UI type and declared parse aliases in nested AST nodes', () => { + const ast = parseFilterToAst('(status:paid,created_at_utc:<\'2024-01-01T00:00:00.000Z\')') as AstNode; + const fieldKeys = getFieldKeysByType(fields, 'date'); + + expect([...fieldKeys]).toEqual(['created_at', 'created_at_utc']); + expect(hasFieldKey(ast, fieldKeys)).toBe(true); + }); }); diff --git a/apps/posts/src/views/filters/filter-query-core.ts b/apps/posts/src/views/filters/filter-query-core.ts index 1b7a14fa8f8..eff5ae88f95 100644 --- a/apps/posts/src/views/filters/filter-query-core.ts +++ b/apps/posts/src/views/filters/filter-query-core.ts @@ -22,6 +22,38 @@ export function stampPredicates(predicates: ParsedPredicate[]): FilterPredicate[ })); } +export function getFieldKeysByType>( + fields: TFields, + type: FilterField['ui']['type'] +): Set { + const keys = new Set(); + + Object.entries(fields).forEach(([key, definition]) => { + if (definition.ui.type !== type) { + return; + } + + keys.add(key); + definition.parseKeys?.forEach(parseKey => keys.add(parseKey)); + }); + + return keys; +} + +export function hasFieldKey(node: AstNode, fieldKeys: ReadonlySet): boolean { + if (Object.keys(node).some(key => fieldKeys.has(key))) { + return true; + } + + return Object.values(node).some((value) => { + if (Array.isArray(value)) { + return value.some(child => child !== null && typeof child === 'object' && hasFieldKey(child as AstNode, fieldKeys)); + } + + return value !== null && typeof value === 'object' && !(value instanceof RegExp) && hasFieldKey(value as AstNode, fieldKeys); + }); +} + export function dispatchSimpleNodes>(nodes: AstNode[], fields: TFields, timezone: string): ParsedPredicate[] { return nodes.flatMap((node) => { const keys = Object.keys(node); diff --git a/apps/posts/src/views/members/member-fields.test.ts b/apps/posts/src/views/members/member-fields.test.ts index 970bf66a57a..8e750198852 100644 --- a/apps/posts/src/views/members/member-fields.test.ts +++ b/apps/posts/src/views/members/member-fields.test.ts @@ -113,7 +113,7 @@ describe('memberFields', () => { }); }); -describe('memberDateCodec', () => { +describe('dateCodec', () => { it('serializes date boundaries in UTC day bounds', () => { const predicate: FilterPredicate = { id: '1', diff --git a/apps/posts/src/views/members/member-fields.ts b/apps/posts/src/views/members/member-fields.ts index f7ccdea8729..a1eef83a4ed 100644 --- a/apps/posts/src/views/members/member-fields.ts +++ b/apps/posts/src/views/members/member-fields.ts @@ -1,18 +1,10 @@ -import moment from 'moment-timezone'; +import {DATE_FILTER_OPERATORS, DEFAULT_DATE_OPERATOR} from '../filters/filter-date'; +import {dateCodec, numberCodec, scalarCodec, setCodec, textCodec} from '../filters/filter-codecs'; import {defineFields} from '../filters/filter-types'; import {escapeNqlString} from '../filters/filter-normalization'; -import {numberCodec, scalarCodec, setCodec, textCodec} from '../filters/filter-codecs'; import type {FilterCodec} from '../filters/filter-types'; -function getDayBoundsInUtc(date: string, timezone: string): {start: string; end: string} { - const start = moment.tz(date, 'YYYY-MM-DD', timezone).startOf('day').utc().toISOString(); - const end = moment.tz(date, 'YYYY-MM-DD', timezone).endOf('day').utc().toISOString(); - - return {start, end}; -} - const TEXT_OPERATORS = ['is', 'contains', 'does-not-contain', 'starts-with', 'ends-with'] as const; -const DATE_OPERATORS = ['is-less', 'is-or-less', 'is-greater', 'is-or-greater'] as const; const NUMBER_OPERATORS = ['is', 'is-greater', 'is-less'] as const; const SCALAR_OPERATORS = ['is', 'is-not'] as const; const SET_OPERATORS = ['is-any', 'is-not-any'] as const; @@ -26,78 +18,6 @@ const SUBSCRIPTION_STATUS_OPTIONS: Array<{value: string; label: string}> = [ {value: 'incomplete_expired', label: 'Incomplete - Expired'} ]; -function formatDateValue(value: unknown, timezone: string): string | null { - if (typeof value !== 'string' || !value) { - return null; - } - - const legacyUtc = moment.utc(value, ['YYYY-MM-DD HH:mm:ss.SSS', 'YYYY-MM-DD HH:mm:ss'], true); - - if (legacyUtc.isValid()) { - return legacyUtc.tz(timezone).format('YYYY-MM-DD'); - } - - const parsed = moment.tz(value, moment.ISO_8601, true, timezone); - - if (!parsed.isValid()) { - return null; - } - - return parsed.format('YYYY-MM-DD'); -} - -const memberDateCodec: FilterCodec = { - parse(node, ctx) { - const entry = Object.entries(node as Record)[0]; - - if (!entry || entry[0] !== ctx.key || typeof entry[1] !== 'object' || entry[1] === null) { - return null; - } - - const [operator, rawValue] = Object.entries(entry[1] as Record)[0] ?? []; - const value = formatDateValue(rawValue, ctx.timezone); - - if (!value) { - return null; - } - - switch (operator) { - case '$lt': - return {field: ctx.key, operator: 'is-less', values: [value]}; - case '$lte': - return {field: ctx.key, operator: 'is-or-less', values: [value]}; - case '$gt': - return {field: ctx.key, operator: 'is-greater', values: [value]}; - case '$gte': - return {field: ctx.key, operator: 'is-or-greater', values: [value]}; - default: - return null; - } - }, - serialize(predicate, ctx) { - const value = predicate.values[0]; - - if (typeof value !== 'string' || !value) { - return null; - } - - const {start, end} = getDayBoundsInUtc(value, ctx.timezone); - - switch (predicate.operator) { - case 'is-less': - return [`${ctx.key}:<'${start}'`]; - case 'is-or-less': - return [`${ctx.key}:<='${end}'`]; - case 'is-greater': - return [`${ctx.key}:>'${end}'`]; - case 'is-or-greater': - return [`${ctx.key}:>='${start}'`]; - default: - return null; - } - } -}; - const subscribedCodec: FilterCodec = { parse() { return null; @@ -226,24 +146,24 @@ export const memberFields = defineFields({ codec: subscribedCodec }, last_seen_at: { - operators: DATE_OPERATORS, + operators: DATE_FILTER_OPERATORS, ui: { label: 'Last seen', type: 'date', - defaultOperator: 'is-or-less', + defaultOperator: DEFAULT_DATE_OPERATOR, className: 'w-40' }, - codec: memberDateCodec + codec: dateCodec() }, created_at: { - operators: DATE_OPERATORS, + operators: DATE_FILTER_OPERATORS, ui: { label: 'Created', type: 'date', - defaultOperator: 'is-or-less', + defaultOperator: DEFAULT_DATE_OPERATOR, className: 'w-40' }, - codec: memberDateCodec + codec: dateCodec() }, signup: { operators: SCALAR_OPERATORS, @@ -340,11 +260,11 @@ export const memberFields = defineFields({ codec: scalarCodec() }, 'subscriptions.start_date': { - operators: DATE_OPERATORS, + operators: DATE_FILTER_OPERATORS, ui: { label: 'Paid start date', type: 'date', - defaultOperator: 'is-or-less', + defaultOperator: DEFAULT_DATE_OPERATOR, className: 'w-40' }, metadata: { @@ -354,14 +274,14 @@ export const memberFields = defineFields({ include: 'subscriptions' } }, - codec: memberDateCodec + codec: dateCodec() }, 'subscriptions.current_period_end': { - operators: DATE_OPERATORS, + operators: DATE_FILTER_OPERATORS, ui: { label: 'Next billing date', type: 'date', - defaultOperator: 'is-or-less', + defaultOperator: DEFAULT_DATE_OPERATOR, className: 'w-40' }, metadata: { @@ -371,7 +291,7 @@ export const memberFields = defineFields({ include: 'subscriptions' } }, - codec: memberDateCodec + codec: dateCodec() }, conversion: { operators: SCALAR_OPERATORS, diff --git a/apps/posts/src/views/members/member-filter-query.ts b/apps/posts/src/views/members/member-filter-query.ts index 1e3fe4b27e6..02db932eb5c 100644 --- a/apps/posts/src/views/members/member-filter-query.ts +++ b/apps/posts/src/views/members/member-filter-query.ts @@ -1,15 +1,10 @@ -import {dispatchSimpleNodes, parseFilterToAst, serializePredicates, stampPredicates} from '../filters/filter-query-core'; +import {dispatchSimpleNodes, getFieldKeysByType, hasFieldKey, parseFilterToAst, serializePredicates, stampPredicates} from '../filters/filter-query-core'; import {memberFields} from './member-fields'; import type {AstNode} from '../filters/filter-ast'; import type {FilterPredicate, ParsedPredicate} from '../filters/filter-types'; type CompoundMatcher = (node: AstNode) => ParsedPredicate | null; -const TIMEZONE_SENSITIVE_MEMBER_FIELDS = new Set([ - 'last_seen_at', - 'created_at', - 'subscriptions.start_date', - 'subscriptions.current_period_end' -]); +const TIMEZONE_SENSITIVE_MEMBER_FIELDS = getFieldKeysByType(memberFields, 'date'); function getCompoundChildren(node: AstNode): {operator: '$and' | '$or'; children: AstNode[]} | null { if (Array.isArray(node.$and)) { @@ -179,28 +174,6 @@ const MEMBER_COMPOUND_MATCHERS: CompoundMatcher[] = [ matchFeedbackGroupedNode ]; -function hasTimezoneSensitiveMemberField(node: AstNode): boolean { - if (Object.keys(node).some(key => TIMEZONE_SENSITIVE_MEMBER_FIELDS.has(key))) { - return true; - } - - const compound = getCompoundChildren(node); - - if (compound) { - return compound.children.some(child => hasTimezoneSensitiveMemberField(child as AstNode)); - } - - return Object.values(node).some((value) => { - if (Array.isArray(value)) { - return value.some((child) => { - return child !== null && typeof child === 'object' && hasTimezoneSensitiveMemberField(child as AstNode); - }); - } - - return value !== null && typeof value === 'object' && hasTimezoneSensitiveMemberField(value as AstNode); - }); -} - function parseMemberNode(node: AstNode, timezone: string): ParsedPredicate[] { for (const matcher of MEMBER_COMPOUND_MATCHERS) { const parsed = matcher(node); @@ -236,7 +209,7 @@ export function hasTimezoneSensitiveMemberFilter(filter: string | undefined): bo return false; } - return hasTimezoneSensitiveMemberField(ast); + return hasFieldKey(ast, TIMEZONE_SENSITIVE_MEMBER_FIELDS); } export function serializeMemberFilters(predicates: FilterPredicate[], timezone: string): string | undefined { diff --git a/apps/posts/src/views/members/use-member-filter-fields.ts b/apps/posts/src/views/members/use-member-filter-fields.ts index 764d6dfbe69..9bcee2a7ed5 100644 --- a/apps/posts/src/views/members/use-member-filter-fields.ts +++ b/apps/posts/src/views/members/use-member-filter-fields.ts @@ -1,8 +1,10 @@ import React, {useMemo} from 'react'; -import moment from 'moment-timezone'; +import {DATE_OPERATOR_LABELS} from '../filters/filter-date'; import {FilterFieldConfig, FilterFieldGroup, FilterOption, ValueSource} from '@tryghost/shade/patterns'; import {LabelFilterRenderer} from '@src/components/label-picker'; import {LucideIcon} from '@tryghost/shade/utils'; +import {createOperatorOptions} from '../filters/filter-operator-options'; +import {getTodayInTimezone} from '../filters/filter-normalization'; import {memberFields} from './member-fields'; import type {Offer} from '@tryghost/admin-x-framework/api/offers'; @@ -27,31 +29,11 @@ interface UseMemberFilterFieldsOptions { type OfferOption = FilterOption; type SearchableFieldOverrides = Pick; -interface OperatorOption { - value: string; - label: string; -} - -function createOperatorOptions( - operators: readonly string[], - options: {labels?: Record} = {} -): OperatorOption[] { - const labels = options.labels || {}; - - return operators.map(operator => ({ - value: operator, - label: labels[operator] ?? operator.replaceAll('-', ' ') - })); -} - const MEMBER_OPERATOR_LABELS: Record = { 'is-any': 'is any of', 'is-not-any': 'is none of', 'does-not-contain': 'does not contain', - 'is-less': 'before', - 'is-or-less': 'on or before', - 'is-greater': 'after', - 'is-or-greater': 'on or after', + ...DATE_OPERATOR_LABELS, 1: 'More like this', 0: 'Less like this' }; @@ -311,7 +293,7 @@ export function useMemberFilterFields({ const hiddenHydratedNewsletters = visibleHydratedNewsletters.filter(newsletter => !activeNewsletterSlugs.has(newsletter.slug)); const offerOptions = buildOfferOptions(offers); const offerLabels = createOfferLabelMap(offers); - const today = moment.tz(siteTimezone).format('YYYY-MM-DD'); + const today = getTodayInTimezone(siteTimezone); const basicFields: FilterFieldConfig[] = [ createFieldConfig('name'), diff --git a/apps/posts/test/unit/hooks/create-combined-value-source.test.tsx b/apps/posts/test/unit/hooks/create-combined-value-source.test.tsx new file mode 100644 index 00000000000..79813f94dd5 --- /dev/null +++ b/apps/posts/test/unit/hooks/create-combined-value-source.test.tsx @@ -0,0 +1,111 @@ +import {createCombinedValueSource} from '@src/hooks/filter-sources/create-combined-value-source'; +import {createRemoteValueSource} from '@src/hooks/filter-sources/create-remote-value-source'; +import {describe, expect, it} from 'vitest'; +import {renderHook} from '@testing-library/react'; + +type TestItem = { + id: string; + label: string; +}; + +let firstBrowseData: TestItem[] | undefined; +let firstHydrateData: TestItem[] | undefined; +let secondBrowseData: TestItem[] | undefined; +let secondHydrateData: TestItem[] | undefined; + +const useFirstSource = createRemoteValueSource({ + id: 'first.remote', + useBrowse: () => ({ + data: firstBrowseData, + isLoading: false, + isRefreshing: false, + isLoadingMore: false, + hasMore: false, + loadMore: () => {} + }), + useHydrate: () => ({ + data: firstHydrateData, + isLoading: false + }), + toOption: item => ({ + value: item.id, + label: item.label + }) +}); + +const useSecondSource = createRemoteValueSource({ + id: 'second.remote', + useBrowse: () => ({ + data: secondBrowseData, + isLoading: false, + isRefreshing: false, + isLoadingMore: false, + hasMore: false, + loadMore: () => {} + }), + useHydrate: () => ({ + data: secondHydrateData, + isLoading: false + }), + toOption: item => ({ + value: item.id, + label: item.label + }) +}); + +const useCombinedSource = createCombinedValueSource( + useFirstSource, + useSecondSource, + value => ({ + value, + label: `ID: ${value}` + }) +); + +describe('createCombinedValueSource', () => { + it('prefers a hydrated option from one source over a fallback from another source', () => { + firstBrowseData = []; + firstHydrateData = []; + secondBrowseData = []; + secondHydrateData = [{id: 'page-id', label: 'About page'}]; + + const {result} = renderHook(() => { + const source = useCombinedSource(); + + return source.useOptions({ + query: '', + selectedValues: ['page-id'] + }); + }); + + expect(result.current.options).toEqual([ + { + value: 'page-id', + label: 'About page' + } + ]); + }); + + it('adds a fallback option when neither source can hydrate a selected value', () => { + firstBrowseData = []; + firstHydrateData = []; + secondBrowseData = []; + secondHydrateData = []; + + const {result} = renderHook(() => { + const source = useCombinedSource(); + + return source.useOptions({ + query: '', + selectedValues: ['missing-id'] + }); + }); + + expect(result.current.options).toEqual([ + { + value: 'missing-id', + label: 'ID: missing-id' + } + ]); + }); +}); diff --git a/apps/posts/test/unit/hooks/create-remote-value-source.test.tsx b/apps/posts/test/unit/hooks/create-remote-value-source.test.tsx new file mode 100644 index 00000000000..dd428a0e6cb --- /dev/null +++ b/apps/posts/test/unit/hooks/create-remote-value-source.test.tsx @@ -0,0 +1,58 @@ +import {createRemoteValueSource} from '@src/hooks/filter-sources/create-remote-value-source'; +import {describe, expect, it} from 'vitest'; +import {renderHook} from '@testing-library/react'; + +type TestItem = { + id: string; + label: string; +}; + +let browseData: TestItem[] | undefined; +let hydrateData: TestItem[] | undefined; + +const useTestSource = createRemoteValueSource({ + id: 'test.remote', + useBrowse: () => ({ + data: browseData, + isLoading: false, + isRefreshing: false, + isLoadingMore: false, + hasMore: false, + loadMore: () => {} + }), + useHydrate: () => ({ + data: hydrateData, + isLoading: false + }), + toOption: item => ({ + value: item.id, + label: item.label + }), + getMissingSelectedOption: value => ({ + value, + label: `ID: ${value}` + }) +}); + +describe('createRemoteValueSource', () => { + it('keeps a fallback option for selected values that cannot be hydrated', () => { + browseData = []; + hydrateData = []; + + const {result} = renderHook(() => { + const source = useTestSource(); + + return source.useOptions({ + query: '', + selectedValues: ['missing-id'] + }); + }); + + expect(result.current.options).toEqual([ + { + value: 'missing-id', + label: 'ID: missing-id' + } + ]); + }); +}); diff --git a/apps/posts/test/unit/utils/filter-normalization.test.ts b/apps/posts/test/unit/utils/filter-normalization.test.ts new file mode 100644 index 00000000000..94596e06a3c --- /dev/null +++ b/apps/posts/test/unit/utils/filter-normalization.test.ts @@ -0,0 +1,39 @@ +import {describe, expect, it} from 'vitest'; +import {escapeNqlString, formatDateInTimezone, getDayBoundsInUtc} from '@src/views/filters/filter-normalization'; + +describe('filter-normalization', () => { + it('escapes single quotes for NQL strings', () => { + expect(escapeNqlString('can\'t stop')).toBe('\'can\\\'t stop\''); + }); + + it('escapes backslashes before single quotes for NQL strings', () => { + expect(escapeNqlString('test\\\'value')).toBe('\'test\\\\\\\'value\''); + }); + + it('computes UTC day bounds from a site timezone date', () => { + expect(getDayBoundsInUtc('2024-02-01', 'America/New_York')).toEqual({ + start: '2024-02-01T05:00:00.000Z', + end: '2024-02-02T04:59:59.999Z' + }); + }); + + it('computes shorter UTC day bounds across spring-forward DST transitions', () => { + expect(getDayBoundsInUtc('2024-03-10', 'America/New_York')).toEqual({ + start: '2024-03-10T05:00:00.000Z', + end: '2024-03-11T03:59:59.999Z' + }); + }); + + it('formats ISO instants in a site timezone', () => { + expect(formatDateInTimezone('2024-02-01T22:59:59.999Z', 'Europe/Stockholm')).toBe('2024-02-01'); + expect(formatDateInTimezone('2024-02-01T23:00:00.000Z', 'Europe/Stockholm')).toBe('2024-02-02'); + }); + + it('formats legacy UTC date-times in a site timezone', () => { + expect(formatDateInTimezone('2022-02-01 23:59:59', 'Europe/Stockholm')).toBe('2022-02-02'); + }); + + it('ignores invalid date values', () => { + expect(formatDateInTimezone('not-a-date', 'UTC')).toBeNull(); + }); +}); diff --git a/apps/posts/test/unit/views/comments/comment-fields.test.ts b/apps/posts/test/unit/views/comments/comment-fields.test.ts new file mode 100644 index 00000000000..08183b2c49a --- /dev/null +++ b/apps/posts/test/unit/views/comments/comment-fields.test.ts @@ -0,0 +1,143 @@ +import nql from '@tryghost/nql-lang'; +import {commentFields} from '@src/views/comments/comment-fields'; +import {describe, expect, it} from 'vitest'; +import type {CodecContext, FilterPredicate} from '@src/views/filters/filter-types'; + +const createdAtContext: CodecContext = { + key: 'created_at', + pattern: 'created_at', + params: {}, + timezone: 'UTC' +}; + +const reportedContext: CodecContext = { + key: 'reported', + pattern: 'reported', + params: {}, + timezone: 'UTC' +}; + +const bodyContext: CodecContext = { + key: 'body', + pattern: 'body', + params: {}, + timezone: 'UTC' +}; + +describe('commentFields', () => { + it('defines the expected comment field set', () => { + expect(Object.keys(commentFields)).toEqual([ + 'status', + 'created_at', + 'body', + 'post', + 'author', + 'reported' + ]); + }); + + it('keeps the expected operators for key comment fields', () => { + expect(commentFields.status.operators).toEqual(['is']); + expect(commentFields.created_at.operators).toEqual([ + 'is-less', + 'is-or-less', + 'is-greater', + 'is-or-greater' + ]); + expect(commentFields.body.operators).toEqual(['contains', 'does-not-contain']); + expect(commentFields.post.operators).toEqual(['is', 'is-not']); + expect(commentFields.author.operators).toEqual(['is', 'is-not']); + expect(commentFields.reported.operators).toEqual(['is']); + }); + + it('keeps parse aliases local to mapped comment fields', () => { + expect(commentFields.body.parseKeys).toEqual(['html']); + expect(commentFields.post.parseKeys).toEqual(['post_id']); + expect(commentFields.author.parseKeys).toEqual(['member_id']); + expect(commentFields.reported.parseKeys).toEqual(['count.reports']); + }); + + describe('commentDateCodec', () => { + it('serializes dates with member-compatible UTC day boundaries', () => { + const predicate: FilterPredicate = { + id: '1', + field: 'created_at', + operator: 'is-or-less', + values: ['2024-01-01'] + }; + + expect(commentFields.created_at.codec.serialize(predicate, createdAtContext)).toEqual([ + 'created_at:<=\'2024-01-01T23:59:59.999Z\'' + ]); + }); + + it('parses date comparators back to local dates', () => { + expect(commentFields.created_at.codec.parse( + nql.parse('created_at:<\'2024-01-03T00:00:00.000Z\'') as never, + createdAtContext + )).toEqual({ + field: 'created_at', + operator: 'is-less', + values: ['2024-01-03'] + }); + + expect(commentFields.created_at.codec.parse( + nql.parse('created_at:>\'2024-01-01T23:59:59.999Z\'') as never, + createdAtContext + )).toEqual({ + field: 'created_at', + operator: 'is-greater', + values: ['2024-01-01'] + }); + }); + }); + + describe('reportedCodec', () => { + it('serializes reported yes/no state', () => { + expect(commentFields.reported.codec.serialize({ + id: '1', + field: 'reported', + operator: 'is', + values: ['true'] + }, reportedContext)).toEqual(['count.reports:>0']); + + expect(commentFields.reported.codec.serialize({ + id: '2', + field: 'reported', + operator: 'is', + values: ['false'] + }, reportedContext)).toEqual(['count.reports:0']); + }); + + it('parses count-based report filters into boolean values', () => { + expect(commentFields.reported.codec.parse( + nql.parse('count.reports:>0') as never, + reportedContext + )).toEqual({ + field: 'reported', + operator: 'is', + values: ['true'] + }); + + expect(commentFields.reported.codec.parse( + nql.parse('count.reports:0') as never, + reportedContext + )).toEqual({ + field: 'reported', + operator: 'is', + values: ['false'] + }); + }); + }); + + describe('mapped shared codecs', () => { + it('uses the shared text codec with the html field override', () => { + expect(commentFields.body.codec.serialize({ + id: '1', + field: 'body', + operator: 'does-not-contain', + values: ['ghost'] + }, bodyContext)).toEqual(['html:-~\'ghost\'']); + }); + }); +}); diff --git a/apps/posts/test/unit/views/comments/comment-filter-query.test.ts b/apps/posts/test/unit/views/comments/comment-filter-query.test.ts new file mode 100644 index 00000000000..604fb0359d9 --- /dev/null +++ b/apps/posts/test/unit/views/comments/comment-filter-query.test.ts @@ -0,0 +1,175 @@ +import {describe, expect, it} from 'vitest'; +import {parseCommentFilter, serializeCommentFilters} from '@src/views/comments/comment-filter-query'; +import type {FilterPredicate} from '@src/views/filters/filter-types'; + +function stripIds(predicates: FilterPredicate[]) { + return predicates.map(predicate => ({ + field: predicate.field, + operator: predicate.operator, + values: predicate.values + })); +} + +describe('comment-filter-query', () => { + it('parses date compounds with member-compatible operators', () => { + const predicates = parseCommentFilter( + 'created_at:>=\'2024-01-01T00:00:00.000Z\'+created_at:<=\'2024-01-01T23:59:59.999Z\'', + 'UTC' + ); + + expect(stripIds(predicates)).toEqual([ + { + field: 'created_at', + operator: 'is-or-greater', + values: ['2024-01-01'] + }, + { + field: 'created_at', + operator: 'is-or-less', + values: ['2024-01-01'] + } + ]); + }); + + it('serializes date filters canonically', () => { + const predicates: FilterPredicate[] = [ + { + id: '1', + field: 'created_at', + operator: 'is-or-less', + values: ['2024-01-01'] + } + ]; + + expect(serializeCommentFilters(predicates, 'UTC')).toBe( + 'created_at:<=\'2024-01-01T23:59:59.999Z\'' + ); + }); + + it('round-trips comment filters in a non-UTC site timezone', () => { + const parsed = parseCommentFilter( + 'created_at:>=\'2024-01-01T05:00:00.000Z\'+created_at:<=\'2024-01-02T04:59:59.999Z\'+member_id:member_123+status:published', + 'America/New_York' + ); + + expect(stripIds(parsed)).toEqual([ + { + field: 'created_at', + operator: 'is-or-greater', + values: ['2024-01-01'] + }, + { + field: 'created_at', + operator: 'is-or-less', + values: ['2024-01-01'] + }, + { + field: 'author', + operator: 'is', + values: ['member_123'] + }, + { + field: 'status', + operator: 'is', + values: ['published'] + } + ]); + + expect(serializeCommentFilters(parsed, 'America/New_York')).toBe( + 'created_at:<=\'2024-01-02T04:59:59.999Z\'+created_at:>=\'2024-01-01T05:00:00.000Z\'+member_id:member_123+status:published' + ); + }); + + it('round-trips date boundaries across DST transitions', () => { + const parsed = parseCommentFilter( + 'created_at:>=\'2024-03-10T05:00:00.000Z\'+created_at:<=\'2024-03-11T03:59:59.999Z\'', + 'America/New_York' + ); + + expect(stripIds(parsed)).toEqual([ + { + field: 'created_at', + operator: 'is-or-greater', + values: ['2024-03-10'] + }, + { + field: 'created_at', + operator: 'is-or-less', + values: ['2024-03-10'] + } + ]); + + expect(serializeCommentFilters(parsed, 'America/New_York')).toBe( + 'created_at:<=\'2024-03-11T03:59:59.999Z\'+created_at:>=\'2024-03-10T05:00:00.000Z\'' + ); + }); + + it('parses and serializes reported filters', () => { + const parsed = parseCommentFilter('count.reports:>0', 'UTC'); + + expect(stripIds(parsed)).toEqual([ + { + field: 'reported', + operator: 'is', + values: ['true'] + } + ]); + + expect(serializeCommentFilters(parsed, 'UTC')).toBe('count.reports:>0'); + }); + + it('round-trips mapped comment fields through canonical NQL', () => { + const parsed = parseCommentFilter( + 'count.reports:0+html:~\'ghost\'+member_id:member_123+post_id:post_456+status:hidden', + 'UTC' + ); + + expect(stripIds(parsed)).toEqual([ + { + field: 'reported', + operator: 'is', + values: ['false'] + }, + { + field: 'body', + operator: 'contains', + values: ['ghost'] + }, + { + field: 'author', + operator: 'is', + values: ['member_123'] + }, + { + field: 'post', + operator: 'is', + values: ['post_456'] + }, + { + field: 'status', + operator: 'is', + values: ['hidden'] + } + ]); + + expect(serializeCommentFilters(parsed, 'UTC')).toBe( + 'count.reports:0+html:~\'ghost\'+member_id:member_123+post_id:post_456+status:hidden' + ); + }); + + it('drops unsupported fields such as id from canonical parsing', () => { + const parsed = parseCommentFilter('id:comment_123+status:published', 'UTC'); + + expect(stripIds(parsed)).toEqual([ + { + field: 'status', + operator: 'is', + values: ['published'] + } + ]); + }); + + it('ignores malformed NQL input', () => { + expect(parseCommentFilter('created_at:(', 'UTC')).toEqual([]); + }); +}); diff --git a/apps/posts/test/unit/views/comments/use-comment-filter-fields.test.tsx b/apps/posts/test/unit/views/comments/use-comment-filter-fields.test.tsx new file mode 100644 index 00000000000..759d9e9515c --- /dev/null +++ b/apps/posts/test/unit/views/comments/use-comment-filter-fields.test.tsx @@ -0,0 +1,43 @@ +import {afterEach, describe, expect, it, vi} from 'vitest'; +import {renderHook} from '@testing-library/react'; +import {useCommentFilterFields} from '@src/views/comments/use-comment-filter-fields'; +import type {ValueSource} from '@tryghost/shade/patterns'; + +const emptyValueSource: ValueSource = { + id: 'empty', + useOptions: () => ({ + options: [], + isInitialLoad: false, + isSearching: false, + isLoadingMore: false, + hasMore: false, + loadMore: vi.fn() + }) +}; + +describe('useCommentFilterFields', () => { + afterEach(() => { + vi.useRealTimers(); + }); + + it('sets date filter defaults in the site timezone', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2024-03-10T06:00:00.000Z')); + + const {result} = renderHook(() => useCommentFilterFields({ + memberValueSource: emptyValueSource, + postValueSource: emptyValueSource, + siteTimezone: 'America/Los_Angeles' + })); + + expect(result.current.find(field => field.key === 'created_at')).toMatchObject({ + defaultValue: '2024-03-09', + operators: [ + {value: 'is-less', label: 'before'}, + {value: 'is-or-less', label: 'on or before'}, + {value: 'is-greater', label: 'after'}, + {value: 'is-or-greater', label: 'on or after'} + ] + }); + }); +}); diff --git a/apps/posts/test/unit/views/comments/use-filter-state.test.tsx b/apps/posts/test/unit/views/comments/use-filter-state.test.tsx new file mode 100644 index 00000000000..3dc9892894c --- /dev/null +++ b/apps/posts/test/unit/views/comments/use-filter-state.test.tsx @@ -0,0 +1,171 @@ +// @vitest-environment jsdom + +import {MemoryRouter, useSearchParams} from 'react-router'; +import {act, renderHook} from '@testing-library/react'; +import {describe, expect, it} from 'vitest'; +import {shouldDelayCommentDateFilterHydration, useFilterState} from '@src/views/comments/hooks/use-filter-state'; +import type {ReactNode} from 'react'; + +function createWrapper(initialEntry: string) { + return function Wrapper({children}: {children: ReactNode}) { + return {children}; + }; +} + +describe('use-filter-state', () => { + describe('shouldDelayCommentDateFilterHydration', () => { + it('waits for timezone resolution when date filters are present', () => { + expect(shouldDelayCommentDateFilterHydration('created_at:<=\'2024-02-01T22:59:59.999Z\'', false, true)).toBe(true); + }); + + it('does not wait for non-date filters', () => { + expect(shouldDelayCommentDateFilterHydration('status:published+count.reports:>0', false, true)).toBe(false); + }); + }); + + describe('useFilterState', () => { + it('reads canonical filter params', () => { + const {result} = renderHook(() => useFilterState('UTC'), { + wrapper: createWrapper('/?filter=status:published') + }); + + expect(result.current.filters).toEqual([ + { + id: 'status:1', + field: 'status', + operator: 'is', + values: ['published'] + } + ]); + expect(result.current.nql).toBe('status:published'); + }); + + it('migrates legacy per-field filter params to canonical filters', () => { + const {result} = renderHook(() => { + const state = useFilterState('UTC'); + const [searchParams] = useSearchParams(); + + return { + ...state, + query: searchParams.toString() + }; + }, {wrapper: createWrapper('/?status=is:hidden&body=not_contains:spam&author=is_not:member_123&created_at=before:2024-02-01')}); + + expect(result.current.filters).toEqual([ + { + id: 'status:1', + field: 'status', + operator: 'is', + values: ['hidden'] + }, + { + id: 'body:2', + field: 'body', + operator: 'does-not-contain', + values: ['spam'] + }, + { + id: 'author:3', + field: 'author', + operator: 'is-not', + values: ['member_123'] + }, + { + id: 'created_at:4', + field: 'created_at', + operator: 'is-less', + values: ['2024-02-01'] + } + ]); + expect(result.current.query).toBe('filter=created_at%3A%3C%272024-02-01T00%3A00%3A00.000Z%27%2Bhtml%3A-%7E%27spam%27%2Bmember_id%3A-member_123%2Bstatus%3Ahidden'); + }); + + it('ignores legacy id params because single-comment mode is handled outside filter state', () => { + const {result} = renderHook(() => useFilterState('UTC'), { + wrapper: createWrapper('/?id=is:comment_123') + }); + + expect(result.current.filters).toEqual([]); + expect(result.current.nql).toBeUndefined(); + }); + + it('writes canonical filter params while preserving unrelated query params', () => { + const {result} = renderHook(() => { + const state = useFilterState('UTC'); + const [searchParams] = useSearchParams(); + + return { + ...state, + query: searchParams.toString() + }; + }, {wrapper: createWrapper('/?thread=is:comment_123')}); + + act(() => { + result.current.setFilters([ + { + id: '1', + field: 'reported', + operator: 'is', + values: ['true'] + } + ], {replace: false}); + }); + + expect(result.current.query).toBe('thread=is%3Acomment_123&filter=count.reports%3A%3E0'); + }); + + it('keeps draft filters that are not serializable yet', () => { + const {result} = renderHook(() => { + const state = useFilterState('UTC'); + const [searchParams] = useSearchParams(); + + return { + ...state, + query: searchParams.toString() + }; + }, {wrapper: createWrapper('/?thread=is:comment_123')}); + + act(() => { + result.current.setFilters([ + { + id: '1', + field: 'body', + operator: 'contains', + values: [''] + } + ], {replace: false}); + }); + + expect(result.current.filters).toEqual([ + { + id: '1', + field: 'body', + operator: 'contains', + values: [''] + } + ]); + expect(result.current.nql).toBeUndefined(); + expect(result.current.query).toBe('thread=is%3Acomment_123'); + }); + + it('removes only the canonical filter param when clearing filters', () => { + const {result} = renderHook(() => { + const state = useFilterState('UTC'); + const [searchParams] = useSearchParams(); + + return { + ...state, + query: searchParams.toString() + }; + }, {wrapper: createWrapper('/?filter=status:published&id=is:comment_123&thread=is:comment_456')}); + + act(() => { + result.current.clearFilters({replace: false}); + }); + + expect(result.current.query).toBe('id=is%3Acomment_123&thread=is%3Acomment_456'); + expect(result.current.filters).toEqual([]); + expect(result.current.nql).toBeUndefined(); + }); + }); +}); diff --git a/apps/shade/package.json b/apps/shade/package.json index bd5cc275f8b..59155d40e19 100644 --- a/apps/shade/package.json +++ b/apps/shade/package.json @@ -71,22 +71,17 @@ "preflight.css" ], "devDependencies": { - "@codemirror/lang-html": "6.4.11", - "@radix-ui/react-tooltip": "1.2.8", "@storybook/addon-docs": "10.3.5", "@storybook/addon-links": "10.3.5", "@storybook/react-vite": "10.3.5", "@tailwindcss/postcss": "4.2.1", "@tailwindcss/vite": "4.2.1", "@testing-library/react": "14.3.1", - "@testing-library/react-hooks": "8.0.1", - "@types/lodash-es": "4.17.12", "@types/node": "22.19.17", "@types/react-world-flags": "1.6.0", "@vitejs/plugin-react": "4.7.0", "@vitest/coverage-v8": "^1.6.1", "c8": "10.1.3", - "chai": "4.5.0", "eslint": "catalog:", "eslint-plugin-react-hooks": "4.6.2", "eslint-plugin-react-refresh": "0.4.24", @@ -94,10 +89,7 @@ "eslint-plugin-tailwindcss": "4.0.0-beta.0", "glob": "^10.5.0", "jsdom": "28.1.0", - "lodash-es": "4.18.1", "postcss": "8.5.6", - "rollup-plugin-node-builtins": "2.1.2", - "sinon": "18.0.1", "storybook": "10.3.5", "tailwindcss": "4.2.1", "tsc-alias": "^1.8.17", @@ -108,9 +100,6 @@ "vitest": "1.6.1" }, "dependencies": { - "@dnd-kit/core": "6.3.1", - "@dnd-kit/sortable": "7.0.2", - "@ebay/nice-modal-react": "1.2.13", "@hookform/resolvers": "5.2.2", "@number-flow/react": "0.5.10", "@radix-ui/react-accordion": "1.2.12", @@ -119,11 +108,9 @@ "@radix-ui/react-checkbox": "1.3.3", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-dropdown-menu": "2.1.16", - "@radix-ui/react-form": "0.1.8", "@radix-ui/react-hover-card": "1.1.15", "@radix-ui/react-label": "2.1.8", "@radix-ui/react-popover": "1.1.15", - "@radix-ui/react-radio-group": "1.3.8", "@radix-ui/react-select": "2.2.6", "@radix-ui/react-separator": "1.1.8", "@radix-ui/react-slider": "1.3.6", @@ -133,26 +120,19 @@ "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-toggle-group": "1.1.11", "@radix-ui/react-tooltip": "1.2.8", - "@sentry/react": "7.120.4", - "@types/color": "4.2.1", "@types/react": "18.3.28", "@types/react-dom": "18.3.7", "@types/validator": "13.15.10", - "@uiw/react-codemirror": "4.25.2", "class-variance-authority": "0.7.1", "clsx": "2.1.1", "cmdk": "1.1.1", "color": "^5.0.3", "lucide-react": "0.577.0", "moment-timezone": "^0.5.48", - "next-themes": "0.4.6", "react": "18.3.1", - "react-colorful": "5.6.1", "react-dom": "18.3.1", "react-dropzone": "14.2.3", "react-hook-form": "7.72.1", - "react-hot-toast": "2.6.0", - "react-select": "5.10.2", "react-world-flags": "1.6.0", "recharts": "2.15.4", "sonner": "2.0.7", diff --git a/apps/signup-form/package.json b/apps/signup-form/package.json index d795a41be24..c79ca510263 100644 --- a/apps/signup-form/package.json +++ b/apps/signup-form/package.json @@ -1,6 +1,6 @@ { "name": "@tryghost/signup-form", - "version": "0.3.19", + "version": "0.3.20", "license": "MIT", "repository": "https://github.com/TryGhost/Ghost", "author": "Ghost Foundation", @@ -55,7 +55,6 @@ "postcss": "8.5.6", "postcss-import": "16.1.1", "prop-types": "15.8.1", - "rollup-plugin-node-builtins": "2.1.2", "storybook": "10.3.5", "stylelint": "15.11.0", "tailwindcss": "3.4.18", diff --git a/e2e/package.json b/e2e/package.json index 65cc03d1779..7a68ad6b539 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -46,7 +46,6 @@ "knex": "3.1.0", "mysql2": "3.18.1", "stripe": "8.222.0", - "ts-node": "10.9.2", "typescript": "5.9.3", "typescript-eslint": "8.58.0" } diff --git a/ghost/admin/package.json b/ghost/admin/package.json index 307ac3fd96b..d9f14fc3253 100644 --- a/ghost/admin/package.json +++ b/ghost/admin/package.json @@ -171,8 +171,6 @@ "*.js": "eslint" }, "dependencies": { - "i18n-iso-countries": "7.14.0", - "lru-cache": "6.0.0", "path-browserify": "1.0.1", "webpack": "5.105.4" }, diff --git a/ghost/core/core/server/api/endpoints/automations.js b/ghost/core/core/server/api/endpoints/automations.js index 71b56e7ca3f..30c6cff71b8 100644 --- a/ghost/core/core/server/api/endpoints/automations.js +++ b/ghost/core/core/server/api/endpoints/automations.js @@ -24,6 +24,49 @@ const controller = { } }, + read: { + headers: { + cacheInvalidate: false + }, + data: [ + 'id' + ], + permissions: true, + query(frame) { + // TODO: NY-1265 - replace this static payload with persisted automation data. + return { + id: frame.data.id, + slug: 'member-welcome-email-free', + name: 'Welcome email', + status: 'active', + created_at: '2026-05-05T00:00:00.000Z', + updated_at: '2026-05-05T00:00:00.000Z', + actions: [{ + id: '67f3f3f3f3f3f3f3f3f3f3f4', + type: 'delay', + data: { + delay_hours: 24 + } + }, { + id: '67f3f3f3f3f3f3f3f3f3f3f5', + type: 'send email', + data: { + email_subject: 'Welcome!', + email_lexical: '{"root":{"children":[]}}', + email_sender_name: null, + email_sender_email: null, + email_sender_reply_to: null, + email_design_setting_id: '680000000000000000000001' + } + }], + edges: [{ + source_action_id: '67f3f3f3f3f3f3f3f3f3f3f4', + target_action_id: '67f3f3f3f3f3f3f3f3f3f3f5' + }] + }; + } + }, + poll: { statusCode: 204, headers: { diff --git a/ghost/core/core/server/web/api/endpoints/admin/routes.js b/ghost/core/core/server/web/api/endpoints/admin/routes.js index e3a45181dab..2c796cc10f2 100644 --- a/ghost/core/core/server/web/api/endpoints/admin/routes.js +++ b/ghost/core/core/server/web/api/endpoints/admin/routes.js @@ -187,6 +187,7 @@ module.exports = function apiRoutes() { // ## Automations router.get('/automations', mw.authAdminApi, http(api.automations.browse)); + router.get('/automations/:id', mw.authAdminApi, http(api.automations.read)); router.put('/automations/poll', mw.authAdminApiWithUrl, http(api.automations.poll)); // ## Automated Emails diff --git a/ghost/core/core/server/web/parent/middleware/request-id.js b/ghost/core/core/server/web/parent/middleware/request-id.js index 152b3b50928..4f5c4c39810 100644 --- a/ghost/core/core/server/web/parent/middleware/request-id.js +++ b/ghost/core/core/server/web/parent/middleware/request-id.js @@ -1,8 +1,6 @@ const crypto = require('crypto'); /** - * @TODO: move this middleware to Framework monorepo? - * * @param {import('express').Request} req * @param {import('express').Response} res * @param {import('express').NextFunction} next diff --git a/ghost/core/package.json b/ghost/core/package.json index 02033d294db..bf5965a5953 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -88,7 +88,6 @@ "@faker-js/faker": "7.6.0", "@isaacs/ttlcache": "1.4.1", "@sentry/node": "7.120.4", - "@slack/webhook": "7.0.9", "@tryghost/adapter-base-cache": "0.1.23", "@tryghost/admin-api-schema": "4.7.2", "@tryghost/api-framework": "1.0.7", @@ -103,7 +102,6 @@ "@tryghost/errors": "1.3.13", "@tryghost/helpers": "1.1.103", "@tryghost/html-to-plaintext": "1.0.8", - "@tryghost/http-cache-utils": "0.1.25", "@tryghost/i18n": "workspace:*", "@tryghost/image-transform": "1.4.13", "@tryghost/job-manager": "1.0.9", @@ -113,7 +111,6 @@ "@tryghost/kg-default-atoms": "5.2.1", "@tryghost/kg-default-cards": "10.3.1", "@tryghost/kg-default-nodes": "2.1.1", - "@tryghost/kg-default-transforms": "1.3.1", "@tryghost/kg-html-to-lexical": "1.3.1", "@tryghost/kg-lexical-html-renderer": "1.4.1", "@tryghost/kg-markdown-html-renderer": "7.2.1", @@ -154,7 +151,6 @@ "charset": "1.0.1", "cheerio": "0.22.0", "clsx": "2.1.1", - "cluster-key-slot": "1.1.2", "common-tags": "1.8.2", "compression": "1.8.1", "connect-slashes": "1.4.0", @@ -235,8 +231,6 @@ "simple-dom": "1.4.0", "stoppable": "1.1.0", "stripe": "8.222.0", - "superagent": "5.3.1", - "superagent-throttle": "1.0.1", "terser": "5.46.1", "tiny-glob": "0.2.9", "ua-parser-js": "1.0.41", @@ -273,10 +267,7 @@ "c8": "10.1.3", "cli-progress": "3.12.0", "cssnano": "7.1.1", - "detect-indent": "6.1.0", - "detect-newline": "3.1.0", "expect": "29.7.0", - "find-root": "1.1.0", "form-data": "4.0.5", "html-minifier": "4.0.0", "html-validate": "8.29.0", @@ -290,14 +281,12 @@ "nock": "13.5.6", "nodemon": "3.1.14", "papaparse": "5.5.3", - "parse-prometheus-text-format": "1.1.1", "postcss": "8.5.6", "postcss-cli": "11.0.1", "rewire": "9.0.1", "sinon": "18.0.1", "supertest": "6.3.4", "tmp": "0.2.5", - "toml": "3.0.0", "tsx": "4.21.0", "typescript": "5.9.3" }, diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/automations.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/automations.test.js.snap index c3e686e3ae2..8b0f0b7ffb0 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/automations.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/automations.test.js.snap @@ -72,3 +72,58 @@ Object { "x-powered-by": "Express", } `; + +exports[`Automations API read returns a placeholder automation for the requested id 1: [body] 1`] = ` +Object { + "automations": Array [ + Object { + "actions": Array [ + Object { + "data": Object { + "delay_hours": 24, + }, + "id": "67f3f3f3f3f3f3f3f3f3f3f4", + "type": "delay", + }, + Object { + "data": Object { + "email_design_setting_id": "680000000000000000000001", + "email_lexical": "{\\"root\\":{\\"children\\":[]}}", + "email_sender_email": null, + "email_sender_name": null, + "email_sender_reply_to": null, + "email_subject": "Welcome!", + }, + "id": "67f3f3f3f3f3f3f3f3f3f3f5", + "type": "send email", + }, + ], + "created_at": "2026-05-05T00:00:00.000Z", + "edges": Array [ + Object { + "source_action_id": "67f3f3f3f3f3f3f3f3f3f3f4", + "target_action_id": "67f3f3f3f3f3f3f3f3f3f3f5", + }, + ], + "id": "67f3f3f3f3f3f3f3f3f3f3f3", + "name": "Welcome email", + "slug": "member-welcome-email-free", + "status": "active", + "updated_at": "2026-05-05T00:00:00.000Z", + }, + ], +} +`; + +exports[`Automations API read returns a placeholder automation for the requested id 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "668", + "content-type": "application/json; charset=utf-8", + "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Version, Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; diff --git a/ghost/core/test/e2e-api/admin/automations.test.js b/ghost/core/test/e2e-api/admin/automations.test.js index 063069e0b68..240d3162de9 100644 --- a/ghost/core/test/e2e-api/admin/automations.test.js +++ b/ghost/core/test/e2e-api/admin/automations.test.js @@ -73,6 +73,22 @@ describe('Automations API', function () { }); }); + describe('read', function () { + it('returns a placeholder automation for the requested id', async function () { + const automationId = '67f3f3f3f3f3f3f3f3f3f3f3'; + + await agent + .get(`automations/${automationId}`) + .expectStatus(200) + .expect(cacheInvalidateHeaderNotSet()) + .matchBodySnapshot() + .matchHeaderSnapshot({ + 'content-version': anyContentVersion, + etag: anyEtag + }); + }); + }); + describe('poll', function () { /** @type {sinon.SinonStub} */ let dispatchStub; diff --git a/ghost/core/test/unit/api/endpoints/automations.test.js b/ghost/core/test/unit/api/endpoints/automations.test.js index 2d4fa98304f..f06b69a4aff 100644 --- a/ghost/core/test/unit/api/endpoints/automations.test.js +++ b/ghost/core/test/unit/api/endpoints/automations.test.js @@ -62,6 +62,47 @@ describe('Automations controller', function () { }); }); + describe('read', function () { + it('returns a placeholder automation for the requested id', function () { + const result = automationsController.read.query({ + data: { + id: '67f3f3f3f3f3f3f3f3f3f3f3' + } + }); + + assert.deepEqual(result, { + id: '67f3f3f3f3f3f3f3f3f3f3f3', + slug: 'member-welcome-email-free', + name: 'Welcome email', + status: 'active', + created_at: '2026-05-05T00:00:00.000Z', + updated_at: '2026-05-05T00:00:00.000Z', + actions: [{ + id: '67f3f3f3f3f3f3f3f3f3f3f4', + type: 'delay', + data: { + delay_hours: 24 + } + }, { + id: '67f3f3f3f3f3f3f3f3f3f3f5', + type: 'send email', + data: { + email_subject: 'Welcome!', + email_lexical: '{"root":{"children":[]}}', + email_sender_name: null, + email_sender_email: null, + email_sender_reply_to: null, + email_design_setting_id: '680000000000000000000001' + } + }], + edges: [{ + source_action_id: '67f3f3f3f3f3f3f3f3f3f3f4', + target_action_id: '67f3f3f3f3f3f3f3f3f3f3f5' + }] + }); + }); + }); + describe('poll', function () { it('dispatches a StartAutomationsPollEvent', function () { const result = automationsController.poll.query({}); diff --git a/ghost/core/test/unit/server/web/parent/middleware/ghost-locals.test.js b/ghost/core/test/unit/server/web/parent/middleware/ghost-locals.test.js index 3792d66c6ab..9e624177532 100644 --- a/ghost/core/test/unit/server/web/parent/middleware/ghost-locals.test.js +++ b/ghost/core/test/unit/server/web/parent/middleware/ghost-locals.test.js @@ -1,35 +1,23 @@ const assert = require('node:assert/strict'); const {assertExists} = require('../../../../../utils/assertions'); -const _ = require('lodash'); -const sinon = require('sinon'); +const express = require('express'); +const request = require('supertest'); const ghostLocals = require('../../../../../../core/server/web/parent/middleware/ghost-locals'); describe('Theme Handler', function () { - let req; - let res; - let next; - - beforeEach(function () { - req = sinon.spy(); - res = sinon.spy(); - next = sinon.spy(); - }); - - afterEach(function () { - sinon.restore(); + const app = express(); + app.use(ghostLocals); + app.get('/awesome-post', (_req, res) => { + res.json(res.locals); }); describe('ghostLocals', function () { - it('sets all locals', function () { - req.path = '/awesome-post'; - - ghostLocals(req, res, next); - - assert(_.isPlainObject(res.locals)); - assertExists(res.locals.version); - assertExists(res.locals.safeVersion); - assert.equal(res.locals.relativeUrl, req.path); - sinon.assert.called(next); + it('sets all locals', async function () { + const {body} = await request(app) + .get('/awesome-post'); + assertExists(body.version); + assertExists(body.safeVersion); + assert.equal(body.relativeUrl, '/awesome-post'); }); }); }); diff --git a/ghost/core/test/unit/server/web/parent/middleware/queue-request.test.js b/ghost/core/test/unit/server/web/parent/middleware/queue-request.test.js index 588aa03916b..aff2c543d96 100644 --- a/ghost/core/test/unit/server/web/parent/middleware/queue-request.test.js +++ b/ghost/core/test/unit/server/web/parent/middleware/queue-request.test.js @@ -1,20 +1,21 @@ -const assert = require('node:assert'); +const assert = require('node:assert/strict'); +const express = require('express'); const sinon = require('sinon'); +const request = require('supertest'); const queueRequest = require('../../../../../../core/server/web/parent/middleware/queue-request'); describe('Queue request middleware', function () { - let req, res, next, config, queueFactory, queue; + let config, queueFactory, queue; beforeEach(function () { - req = {}; - res = {}; - next = sinon.stub(); config = { concurrencyLimit: 123 }; - queue = sinon.stub(); + queue = sinon.stub().callsFake((req, res, next) => { + return next(); + }); queue.queue = { on: sinon.stub(), getLength: sinon.stub().returns(0) @@ -23,6 +24,17 @@ describe('Queue request middleware', function () { queueFactory = sinon.stub().returns(queue); }); + function createApp() { + const app = express(); + + app.use(queueRequest(config, queueFactory)); + app.get(['/foo/bar', '/foo/bar.css'], (req, res) => { + res.json({queueDepth: req.queueDepth}); + }); + + return app; + } + it('should configure the queue using the concurrency limit defined in the config', function () { queueRequest(config, queueFactory); @@ -39,40 +51,36 @@ describe('Queue request middleware', function () { }, /concurrencyLimit must be defined when using queueRequest middleware/, 'error should be thrown'); }); - it('should not queue requests for static assets', function () { - req.path = '/foo/bar.css'; // Assume any path with a file extension is a static asset - - const mw = queueRequest(config, queueFactory); - - mw(req, res, next); + it('should not queue requests for static assets', async function () { + await request(createApp()) + .get('/foo/bar.css') + .expect(200) + .expect({queueDepth: 0}); - assert(next.calledOnce, 'next should be called once'); - assert.equal(queue.calledOnce, 0, 'queue should not be called'); + assert.equal(queue.callCount, 0, 'queue should not be called'); }); - it('should queue the request', function () { - req.path = '/foo/bar'; + it('should queue the request', async function () { + await request(createApp()) + .get('/foo/bar') + .expect(200); - const mw = queueRequest(config, queueFactory); - - mw(req, res, next); - - assert(queue.calledOnce, 'queue should be called once'); - sinon.assert.calledWith(queue, req, res, next); + sinon.assert.calledOnce(queue); + assert.equal(queue.getCall(0).args[0].path, '/foo/bar'); + assert.equal(typeof queue.getCall(0).args[1].json, 'function'); + assert.equal(typeof queue.getCall(0).args[2], 'function'); }); - it('should record the queue depth on a request', function () { + it('should record the queue depth on a request', async function () { const queueLength = 123; queue.queue.getLength.returns(queueLength); - req.path = '/foo/bar'; - - const mw = queueRequest(config, queueFactory); - - mw(req, res, next); + await request(createApp()) + .get('/foo/bar') + .expect(200) + .expect({queueDepth: queueLength}); - assert(queue.queue.getLength, 'queue should be called once'); - assert(req.queueDepth === queueLength, 'queue depth should be set on the request'); + sinon.assert.calledOnce(queue.queue.getLength); }); }); diff --git a/ghost/core/test/unit/server/web/parent/middleware/request-id.test.js b/ghost/core/test/unit/server/web/parent/middleware/request-id.test.js index 4f34d9afe5d..6412b2d7c0a 100644 --- a/ghost/core/test/unit/server/web/parent/middleware/request-id.test.js +++ b/ghost/core/test/unit/server/web/parent/middleware/request-id.test.js @@ -1,50 +1,36 @@ const assert = require('node:assert/strict'); -const {assertExists} = require('../../../../../utils/assertions'); -const sinon = require('sinon'); +const express = require('express'); +const request = require('supertest'); const validator = require('@tryghost/validator'); const requestId = require('../../../../../../core/server/web/parent/middleware/request-id'); describe('Request ID middleware', function () { - let res; - let req; - let next; - - beforeEach(function () { - req = { - get: sinon.stub() - }; - res = { - redirect: sinon.spy(), - set: sinon.spy() - }; - - next = sinon.spy(); + const app = express(); + app.use(requestId); + app.get('/', (req, res) => { + res.json({requestId: req.requestId}); }); - afterEach(function () { - sinon.restore(); + it('generates a new request ID if X-Request-ID not present', async function () { + const {headers, body} = await request(app).get('/'); + assert(!('x-request-id' in headers)); + assert(validator.isUUID(body.requestId)); }); - it('generates a new request ID if X-Request-ID not present', function () { - assert.equal(req.requestId, undefined); - - requestId(req, res, next); - - assertExists(req.requestId); - assert.equal(validator.isUUID(req.requestId), true); - sinon.assert.notCalled(res.set); + it('generates a new request ID if X-Request-ID is an empty string', async function () { + const {headers, body} = await request(app) + .get('/') + .set('X-Request-ID', ''); + assert(!('x-request-id' in headers)); + assert(validator.isUUID(body.requestId)); }); - it('keeps the request ID if X-Request-ID is present', function () { - assert.equal(req.requestId, undefined); - req.get.withArgs('X-Request-ID').returns('abcd'); - - requestId(req, res, next); - - assertExists(req.requestId); - assert.equal(req.requestId, 'abcd'); - sinon.assert.calledOnce(res.set); - sinon.assert.calledWith(res.set, 'X-Request-ID', 'abcd'); + it('keeps the request ID if X-Request-ID is present', async function () { + await request(app) + .get('/') + .set('X-Request-ID', 'abcd') + .expect('X-Request-ID', 'abcd') + .expect({requestId: 'abcd'}); }); }); diff --git a/ghost/i18n/package.json b/ghost/i18n/package.json index 5292ec24179..8eb89b65d6e 100644 --- a/ghost/i18n/package.json +++ b/ghost/i18n/package.json @@ -19,7 +19,7 @@ "lint:test": "eslint -c test/.eslintrc.js test/ --ext .js --cache", "lint:translations": "node ./test/i18n.lint.js", "translate": "pnpm translate:ghost && pnpm translate:portal && pnpm translate:signup-form && pnpm translate:comments && pnpm translate:search && node generate-context.js", - "translate:ghost": "NAMESPACE=ghost i18next '../core/core/{frontend,server,shared}/**/*.{js,jsx}' '../core/core/server/services/email-rendering/partials/**/*.hbs' '../core/core/server/services/email-service/email-templates/**/*.hbs' '../core/core/server/services/comments/email-templates/**/*.hbs' '../core/core/server/services/member-welcome-emails/email-templates/**/*.hbs'", + "translate:ghost": "NAMESPACE=ghost i18next '../core/core/{frontend,server,shared}/**/*.{js,jsx}' '!../core/core/frontend/public/*.min.js' '../core/core/server/services/email-rendering/partials/**/*.hbs' '../core/core/server/services/email-service/email-templates/**/*.hbs' '../core/core/server/services/comments/email-templates/**/*.hbs' '../core/core/server/services/member-welcome-emails/email-templates/**/*.hbs'", "translate:portal": "NAMESPACE=portal i18next '../../apps/portal/src/**/*.{js,jsx}'", "translate:signup-form": "NAMESPACE=signup-form i18next '../../apps/signup-form/src/**/*.{ts,tsx}'", "translate:comments": "NAMESPACE=comments i18next '../../apps/comments-ui/src/**/*.{ts,tsx}'", @@ -33,7 +33,7 @@ "devDependencies": { "c8": "10.1.3", "glob": "^13.0.6", - "i18next-parser": "8.13.0", + "i18next-parser": "9.3.0", "mocha": "11.7.5" }, "dependencies": { diff --git a/ghost/parse-email-address/package.json b/ghost/parse-email-address/package.json index 5f3a717b353..ba7c8e16403 100644 --- a/ghost/parse-email-address/package.json +++ b/ghost/parse-email-address/package.json @@ -28,8 +28,6 @@ "@types/node": "25.6.0", "c8": "10.1.3", "mocha": "11.7.5", - "sinon": "21.0.1", - "ts-node": "10.9.2", "tsx": "4.21.0", "typescript": "5.9.3" }, diff --git a/knip.json b/knip.json new file mode 100644 index 00000000000..a0cad9dba42 --- /dev/null +++ b/knip.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://unpkg.com/knip@6/schema.json", + "ignoreWorkspaces": [ + "ghost/admin" + ], + "ignoreDependencies": [ + "secretlint", + "@secretlint/.*" + ] +} diff --git a/package.json b/package.json index aefb7e1b909..2d67f2caa05 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,8 @@ "docker:build": "docker compose -f compose.dev.yaml ${DEV_COMPOSE_FILES} build", "docker:clean": "docker compose -f compose.dev.yaml ${DEV_COMPOSE_FILES} --profile all down -v --remove-orphans --rmi local", "docker:down": "docker compose -f compose.dev.yaml ${DEV_COMPOSE_FILES} down", + "knip": "knip", + "knip:fix": "knip --fix --allow-remove-files=false", "lint": "pnpm nx run-many -t lint", "test": "pnpm nx run-many -t test --exclude @tryghost/e2e --exclude ghost-admin", "test:unit": "pnpm nx run-many -t test:unit", @@ -86,6 +88,7 @@ "debug@<2.6.9": "^2.6.9", "diff@<3.5.1": "^3.5.1", "diff@>=6.0.0 <8.0.3": "^8.0.3", + "fast-xml-parser@<5.7.0": "^5.7.0", "follow-redirects@<1.16.0": "^1.16.0", "form-data@<2.5.4": "^2.5.4", "growl@<1.10.0": "^1.10.0", @@ -129,18 +132,15 @@ ] }, "devDependencies": { - "@actions/core": "3.0.0", "@playwright/test": "1.59.1", "@secretlint/secretlint-rule-pattern": "12.3.1", "@secretlint/secretlint-rule-preset-recommend": "12.3.1", - "chalk": "4.1.2", - "chokidar": "3.6.0", "eslint": "catalog:", "eslint-plugin-ghost": "3.5.0", "eslint-plugin-react": "7.37.5", "husky": "9.1.7", - "inquirer": "8.2.7", "jsonc-parser": "3.3.1", + "knip": "6.12.0", "lint-staged": "16.4.0", "nx": "22.0.4", "rimraf": "6.1.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a82b89fe6a..d80bfeb2b73 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,6 +47,7 @@ overrides: debug@<2.6.9: ^2.6.9 diff@<3.5.1: ^3.5.1 diff@>=6.0.0 <8.0.3: ^8.0.3 + fast-xml-parser@<5.7.0: ^5.7.0 follow-redirects@<1.16.0: ^1.16.0 form-data@<2.5.4: ^2.5.4 growl@<1.10.0: ^1.10.0 @@ -77,9 +78,6 @@ importers: .: devDependencies: - '@actions/core': - specifier: 3.0.0 - version: 3.0.0 '@playwright/test': specifier: 1.59.1 version: 1.59.1 @@ -89,12 +87,6 @@ importers: '@secretlint/secretlint-rule-preset-recommend': specifier: 12.3.1 version: 12.3.1 - chalk: - specifier: 4.1.2 - version: 4.1.2 - chokidar: - specifier: 3.6.0 - version: 3.6.0 eslint: specifier: 'catalog:' version: 8.57.1 @@ -107,12 +99,12 @@ importers: husky: specifier: 9.1.7 version: 9.1.7 - inquirer: - specifier: 8.2.7 - version: 8.2.7(@types/node@25.6.0) jsonc-parser: specifier: 3.3.1 version: 3.3.1 + knip: + specifier: 6.12.0 + version: 6.12.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) lint-staged: specifier: 16.4.0 version: 16.4.0 @@ -186,9 +178,6 @@ importers: '@testing-library/react': specifier: 14.3.1 version: 14.3.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@types/dompurify': - specifier: 3.2.0 - version: 3.2.0 '@types/jest': specifier: 29.5.14 version: 29.5.14 @@ -204,9 +193,6 @@ importers: tailwindcss: specifier: ^4.2.2 version: 4.2.2 - ts-jest: - specifier: 29.4.9 - version: 29.4.9(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)))(typescript@5.9.3) vite: specifier: 5.4.21 version: 5.4.21(@types/node@25.6.0)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1) @@ -237,9 +223,6 @@ importers: '@tryghost/stats': specifier: workspace:* version: link:../stats - lodash: - specifier: 4.18.1 - version: 4.18.1 mingo: specifier: 2.5.3 version: 2.5.3 @@ -473,9 +456,6 @@ importers: react-dom: specifier: 18.3.1 version: 18.3.1(react@18.3.1) - rollup-plugin-node-builtins: - specifier: 2.1.2 - version: 2.1.2 sinon: specifier: 18.0.1 version: 18.0.1 @@ -579,9 +559,6 @@ importers: msw: specifier: 2.12.14 version: 2.12.14(@types/node@25.6.0)(typescript@5.9.3) - sinon: - specifier: 18.0.1 - version: 18.0.1 typescript: specifier: 5.9.3 version: 5.9.3 @@ -733,9 +710,6 @@ importers: apps/announcement-bar: dependencies: - '@tryghost/content-api': - specifier: 1.12.6 - version: 1.12.6 react: specifier: 17.0.2 version: 17.0.2 @@ -828,9 +802,6 @@ importers: '@testing-library/react': specifier: 12.1.5 version: 12.1.5(@types/react@18.3.28)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) - '@testing-library/user-event': - specifier: 14.6.1 - version: 14.6.1(@testing-library/dom@10.4.0) '@tryghost/i18n': specifier: workspace:* version: link:../../ghost/i18n @@ -898,9 +869,6 @@ importers: specifier: 2.1.0 version: 2.1.0 devDependencies: - '@babel/eslint-parser': - specifier: 7.28.4 - version: 7.28.4(@babel/core@7.29.0)(eslint@8.57.1) '@doist/react-interpolate': specifier: 2.2.1 version: 2.2.1(prop-types@15.8.1)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) @@ -979,9 +947,6 @@ importers: i18n-iso-countries: specifier: 7.14.0 version: 7.14.0 - moment: - specifier: 2.30.1 - version: 2.30.1 moment-timezone: specifier: 0.5.45 version: 0.5.45 @@ -1000,6 +965,9 @@ importers: sonner: specifier: 2.0.7 version: 2.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + temporal-polyfill: + specifier: 0.3.0 + version: 0.3.0 use-debounce: specifier: 10.1.1 version: 10.1.1(react@18.3.1) @@ -1046,15 +1014,6 @@ importers: apps/shade: dependencies: - '@dnd-kit/core': - specifier: 6.3.1 - version: 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@dnd-kit/sortable': - specifier: 7.0.2 - version: 7.0.2(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) - '@ebay/nice-modal-react': - specifier: 1.2.13 - version: 1.2.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@hookform/resolvers': specifier: 5.2.2 version: 5.2.2(react-hook-form@7.72.1(react@18.3.1)) @@ -1079,9 +1038,6 @@ importers: '@radix-ui/react-dropdown-menu': specifier: 2.1.16 version: 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-form': - specifier: 0.1.8 - version: 0.1.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-hover-card': specifier: 1.1.15 version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1091,9 +1047,6 @@ importers: '@radix-ui/react-popover': specifier: 1.1.15 version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-radio-group': - specifier: 1.3.8 - version: 1.3.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-select': specifier: 2.2.6 version: 2.2.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1121,12 +1074,6 @@ importers: '@radix-ui/react-tooltip': specifier: 1.2.8 version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@sentry/react': - specifier: 7.120.4 - version: 7.120.4(react@18.3.1) - '@types/color': - specifier: 4.2.1 - version: 4.2.1 '@types/react': specifier: 18.3.28 version: 18.3.28 @@ -1136,9 +1083,6 @@ importers: '@types/validator': specifier: 13.15.10 version: 13.15.10 - '@uiw/react-codemirror': - specifier: 4.25.2 - version: 4.25.2(@babel/runtime@7.29.2)(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.40.0)(codemirror@5.65.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) class-variance-authority: specifier: 0.7.1 version: 0.7.1 @@ -1157,15 +1101,9 @@ importers: moment-timezone: specifier: 0.5.45 version: 0.5.45 - next-themes: - specifier: 0.4.6 - version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: 18.3.1 version: 18.3.1 - react-colorful: - specifier: 5.6.1 - version: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-dom: specifier: 18.3.1 version: 18.3.1(react@18.3.1) @@ -1175,12 +1113,6 @@ importers: react-hook-form: specifier: 7.72.1 version: 7.72.1(react@18.3.1) - react-hot-toast: - specifier: 2.6.0 - version: 2.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-select: - specifier: 5.10.2 - version: 5.10.2(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-world-flags: specifier: 1.6.0 version: 1.6.0(react@18.3.1) @@ -1200,9 +1132,6 @@ importers: specifier: 4.1.12 version: 4.1.12 devDependencies: - '@codemirror/lang-html': - specifier: 6.4.11 - version: 6.4.11 '@storybook/addon-docs': specifier: 10.3.5 version: 10.3.5(@types/react@18.3.28)(esbuild@0.27.4)(rollup@4.60.0)(storybook@10.3.5(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4)) @@ -1221,12 +1150,6 @@ importers: '@testing-library/react': specifier: 14.3.1 version: 14.3.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@testing-library/react-hooks': - specifier: 8.0.1 - version: 8.0.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@types/lodash-es': - specifier: 4.17.12 - version: 4.17.12 '@types/node': specifier: 22.19.17 version: 22.19.17 @@ -1242,9 +1165,6 @@ importers: c8: specifier: 10.1.3 version: 10.1.3 - chai: - specifier: 4.5.0 - version: 4.5.0 eslint: specifier: 'catalog:' version: 8.57.1 @@ -1266,18 +1186,9 @@ importers: jsdom: specifier: 28.1.0 version: 28.1.0(@noble/hashes@1.8.0) - lodash-es: - specifier: 4.18.1 - version: 4.18.1 postcss: specifier: 8.5.6 version: 8.5.6 - rollup-plugin-node-builtins: - specifier: 2.1.2 - version: 2.1.2 - sinon: - specifier: 18.0.1 - version: 18.0.1 storybook: specifier: 10.3.5 version: 10.3.5(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1372,9 +1283,6 @@ importers: prop-types: specifier: 15.8.1 version: 15.8.1 - rollup-plugin-node-builtins: - specifier: 2.1.2 - version: 2.1.2 storybook: specifier: 10.3.5 version: 10.3.5(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1584,9 +1492,6 @@ importers: stripe: specifier: 8.222.0 version: 8.222.0 - ts-node: - specifier: 10.9.2 - version: 10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3) typescript: specifier: 5.9.3 version: 5.9.3 @@ -1596,12 +1501,6 @@ importers: ghost/admin: dependencies: - i18n-iso-countries: - specifier: 7.14.0 - version: 7.14.0 - lru-cache: - specifier: 6.0.0 - version: 6.0.0 path-browserify: specifier: 1.0.1 version: 1.0.1 @@ -2005,9 +1904,6 @@ importers: '@sentry/node': specifier: 7.120.4 version: 7.120.4 - '@slack/webhook': - specifier: 7.0.9 - version: 7.0.9 '@tryghost/adapter-base-cache': specifier: 0.1.23 version: 0.1.23 @@ -2050,9 +1946,6 @@ importers: '@tryghost/html-to-plaintext': specifier: 1.0.8 version: 1.0.8 - '@tryghost/http-cache-utils': - specifier: 0.1.25 - version: 0.1.25 '@tryghost/i18n': specifier: workspace:* version: link:../i18n @@ -2080,9 +1973,6 @@ importers: '@tryghost/kg-default-nodes': specifier: 2.1.1 version: 2.1.1(@noble/hashes@1.8.0) - '@tryghost/kg-default-transforms': - specifier: 1.3.1 - version: 1.3.1(@lexical/clipboard@0.13.1(lexical@0.13.1))(@lexical/selection@0.13.1(lexical@0.13.1))(@noble/hashes@1.8.0) '@tryghost/kg-html-to-lexical': specifier: 1.3.1 version: 1.3.1(@lexical/selection@0.13.1(lexical@0.13.1))(@lexical/utils@0.13.1(lexical@0.13.1))(@noble/hashes@1.8.0) @@ -2203,9 +2093,6 @@ importers: clsx: specifier: 2.1.1 version: 2.1.1 - cluster-key-slot: - specifier: 1.1.2 - version: 1.1.2 common-tags: specifier: 1.8.2 version: 1.8.2 @@ -2446,12 +2333,6 @@ importers: stripe: specifier: 8.222.0 version: 8.222.0 - superagent: - specifier: 5.3.1 - version: 5.3.1 - superagent-throttle: - specifier: 1.0.1 - version: 1.0.1 terser: specifier: 5.46.1 version: 5.46.1 @@ -2534,18 +2415,9 @@ importers: cssnano: specifier: 7.1.1 version: 7.1.1(postcss@8.5.6) - detect-indent: - specifier: 6.1.0 - version: 6.1.0 - detect-newline: - specifier: 3.1.0 - version: 3.1.0 expect: specifier: 29.7.0 version: 29.7.0 - find-root: - specifier: 1.1.0 - version: 1.1.0 html-minifier: specifier: 4.0.0 version: 4.0.0 @@ -2579,9 +2451,6 @@ importers: nodemon: specifier: 3.1.14 version: 3.1.14 - parse-prometheus-text-format: - specifier: 1.1.1 - version: 1.1.1 postcss: specifier: 8.5.6 version: 8.5.6 @@ -2600,9 +2469,6 @@ importers: tmp: specifier: 0.2.5 version: 0.2.5 - toml: - specifier: 3.0.0 - version: 3.0.0 tsx: specifier: 4.21.0 version: 4.21.0 @@ -2633,8 +2499,8 @@ importers: specifier: ^13.0.6 version: 13.0.6 i18next-parser: - specifier: 8.13.0 - version: 8.13.0 + specifier: 9.3.0 + version: 9.3.0 mocha: specifier: 11.7.5 version: 11.7.5 @@ -2654,12 +2520,6 @@ importers: mocha: specifier: 11.7.5 version: 11.7.5 - sinon: - specifier: 21.0.1 - version: 21.0.1 - ts-node: - specifier: 10.9.2 - version: 10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3) tsx: specifier: 4.21.0 version: 4.21.0 @@ -4114,15 +3974,24 @@ packages: '@glint/template': optional: true + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.9.1': resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==} + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/runtime@1.9.1': resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} '@emnapi/wasi-threads@1.2.0': resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emotion/babel-plugin@11.13.5': resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} @@ -4164,15 +4033,15 @@ packages: '@emotion/weak-memoize@0.4.0': resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} - '@esbuild/aix-ppc64@0.20.2': - resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -4182,15 +4051,15 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.20.2': - resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -4200,15 +4069,15 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm@0.20.2': - resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} cpu: [arm] os: [android] @@ -4218,15 +4087,15 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-x64@0.20.2': - resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} cpu: [x64] os: [android] @@ -4236,15 +4105,15 @@ packages: cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.20.2': - resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -4254,15 +4123,15 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.20.2': - resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -4272,15 +4141,15 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.20.2': - resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -4290,15 +4159,15 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.20.2': - resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -4308,15 +4177,15 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.20.2': - resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -4326,15 +4195,15 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.20.2': - resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -4344,15 +4213,15 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.20.2': - resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -4362,15 +4231,15 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.20.2': - resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -4380,15 +4249,15 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.20.2': - resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -4398,15 +4267,15 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.20.2': - resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -4416,15 +4285,15 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.20.2': - resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -4434,15 +4303,15 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.20.2': - resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -4452,15 +4321,15 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.20.2': - resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} cpu: [x64] os: [linux] @@ -4470,21 +4339,27 @@ packages: cpu: [x64] os: [linux] + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.27.4': resolution: {integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.20.2': - resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} cpu: [x64] os: [netbsd] @@ -4494,21 +4369,27 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.27.4': resolution: {integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.20.2': - resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} cpu: [x64] os: [openbsd] @@ -4518,21 +4399,27 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.27.4': resolution: {integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.20.2': - resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -4542,15 +4429,15 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.20.2': - resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -4560,15 +4447,15 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.20.2': - resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -4578,15 +4465,15 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.20.2': - resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -5319,6 +5206,12 @@ packages: '@napi-rs/wasm-runtime@0.2.4': resolution: {integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==} + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} @@ -5655,6 +5548,244 @@ packages: '@otplib/preset-v11@12.0.1': resolution: {integrity: sha512-9hSetMI7ECqbFiKICrNa4w70deTUfArtwXykPUvSHWOdzOlfa9ajglu7mNCntlvxycTiOAXkQGwjQCzzDEMRMg==} + '@oxc-parser/binding-android-arm-eabi@0.128.0': + resolution: {integrity: sha512-aca6ZvzmCBUGOANQRiRQRZuRKYI3ENhcit6GisnknOOmcezfQc7xJ4dxlPU7MV7mOvrC7RNR1u3LAD7xyaiCxA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.128.0': + resolution: {integrity: sha512-BbeDmuohoJ7Rz/it5wnkj69i/OsCPS3Z51nLEzwO/Y6YshtC4JU+15oNwhY8v4LRKRYclRc7ggOikwrsJ/eOEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.128.0': + resolution: {integrity: sha512-tRUHPt80417QmvNpoSslJT1VY8NUbWdrWR+L14Zn+RbOTcaqB8E6PYE/ZGN8jjWBzqporiA/H4MfO50ew/NCNA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.128.0': + resolution: {integrity: sha512-rWI2Hb1Nt3U/vKsjyNvZzDC8i/l144U20DKjhzaTmwIhIiSRGeroPWWiImwypmKLqrw8GuIixbWJkpGWLbkzrQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.128.0': + resolution: {integrity: sha512-hhpdVMaNCLgQxjgNPeeFzSeJMmZPc5lKfv0NGSI3egZq9EdnEGqeC8JsYsQjK7PoQgbvZ17xlj0SO5ziH5Obkg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.128.0': + resolution: {integrity: sha512-093zNw0zZ/e/obML+rhlSdmnzR0mVZluPcAkxunEc5E3F0yBVsFn24Y1ILfsEte11Ud041qn/gp2OJ1jxNqUng==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.128.0': + resolution: {integrity: sha512-fq7DmKmfC+dvD97IXrgbph6Jzwe0EDu+PYMofmzZ6fv5X1k9vtaqLpDGMuICO9MmUnyKAQmVl+wIv2RNy4Dz8g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.128.0': + resolution: {integrity: sha512-Xvm48jJah8TlIrURIjNOP/gNiGe6aKvCB+r06VliflFo8Kq7VOLE8PxtgShJzZIqubrgdMdYfvuPPozn7F6MbQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-arm64-musl@0.128.0': + resolution: {integrity: sha512-M7iwBGmYJTx+pKOYFjI0buop4gJvlmcVzFGaXPt21DKpQkbQZG1f63Yg7LloIYT/t9yLxCw0Lhfx/RFlAlMSjA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-ppc64-gnu@0.128.0': + resolution: {integrity: sha512-21LGNIZb1Pcfk5/EGsqabrxv4yqQOWis1407JJrClS7XpFCrbvr74YAB1V+m54cYbwvO6UWwQqS4WecxiyfCRg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-gnu@0.128.0': + resolution: {integrity: sha512-gyHjOTFpg9bTTYjxPmQirvufb89+VdZwVfcMtAUyPr6F5H8ZswvCQshK4qOW+Q+2Xyb33hduRgY/eFHJQjU/vQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.128.0': + resolution: {integrity: sha512-X6Q2oKUrP5GyDd2xniuEBLk6aFQCZ97W2+aVXGgJXdjx5t4/oFuA9ri0wLOUrBIX+qdSuK581snMBio4z910eA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.128.0': + resolution: {integrity: sha512-BdzTmqxfxoYkpgokoLaSnOX6T+R3/goL42klre2tnG+kHbG2TXS0VN+P5BPofH1axdKOHy5ei4ENZrjmCOt2lA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-gnu@0.128.0': + resolution: {integrity: sha512-OO1nW2Q7sSYYvJZpDHdvyFSdRaVcQqRijZSSmWVMqFxPYy8cEF45zJ9fcdIYuzIT3jYq6YRhEFm/VMWNWhE22Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-musl@0.128.0': + resolution: {integrity: sha512-4NehAe404MRdoZVS9DW8C5XbJwbXIc/KfVlYdpi5vE4081zc9Y0YzKVqyOYj/Puye7/Do+ohaONBFWlEHYl9hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-openharmony-arm64@0.128.0': + resolution: {integrity: sha512-kVbqgW9xLL8bh8oc7aYOJilRKXE5G33+tE0jan+duo/9OriaFRpijcCwT2waWs2oqYROYq0GlE7/p3ywoshVeg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.128.0': + resolution: {integrity: sha512-L38ojghJYHmgiz6fJd7jwLB/ESDBpB02NdFxh+smqVM6P2anCEvHn0jhaSrt5eVNR1Ak8+moOeftUlofeyvniA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.128.0': + resolution: {integrity: sha512-xgvO35GyHBtjlQ5AEpaYr7Rll1rvY7zqIhT6ty8E3ezBW2J1SFLjIDEvI/tcgDg6oaseDAqVcM+jU1HuCekgZw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.128.0': + resolution: {integrity: sha512-OY+3eM2SN72prHKRB22mPz8o5A/7dJ+f5DFLBVvggyZhEaNDAH9IB+ElMjmOkOIwf5MDCUAowCK7pAncNxzpBA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.128.0': + resolution: {integrity: sha512-NE9ny+cPUCCObXa0IKLfj0tCdPd7pe/dz9ZpkxpUOymB3miNeMPybdlYYTBSGJUalMWeBM85/4JcCErCNTqOXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.128.0': + resolution: {integrity: sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==} + + '@oxc-resolver/binding-android-arm-eabi@11.19.1': + resolution: {integrity: sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==} + cpu: [arm] + os: [android] + + '@oxc-resolver/binding-android-arm64@11.19.1': + resolution: {integrity: sha512-oolbkRX+m7Pq2LNjr/kKgYeC7bRDMVTWPgxBGMjSpZi/+UskVo4jsMU3MLheZV55jL6c3rNelPl4oD60ggYmqA==} + cpu: [arm64] + os: [android] + + '@oxc-resolver/binding-darwin-arm64@11.19.1': + resolution: {integrity: sha512-nUC6d2i3R5B12sUW4O646qD5cnMXf2oBGPLIIeaRfU9doJRORAbE2SGv4eW6rMqhD+G7nf2Y8TTJTLiiO3Q/dQ==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@11.19.1': + resolution: {integrity: sha512-cV50vE5+uAgNcFa3QY1JOeKDSkM/9ReIcc/9wn4TavhW/itkDGrXhw9jaKnkQnGbjJ198Yh5nbX/Gr2mr4Z5jQ==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@11.19.1': + resolution: {integrity: sha512-xZOQiYGFxtk48PBKff+Zwoym7ScPAIVp4c14lfLxizO2LTTTJe5sx9vQNGrBymrf/vatSPNMD4FgsaaRigPkqw==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1': + resolution: {integrity: sha512-lXZYWAC6kaGe/ky2su94e9jN9t6M0/6c+GrSlCqL//XO1cxi5lpAhnJYdyrKfm0ZEr/c7RNyAx3P7FSBcBd5+A==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm-musleabihf@11.19.1': + resolution: {integrity: sha512-veG1kKsuK5+t2IsO9q0DErYVSw2azvCVvWHnfTOS73WE0STdLLB7Q1bB9WR+yHPQM76ASkFyRbogWo1GR1+WbQ==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@11.19.1': + resolution: {integrity: sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-arm64-musl@11.19.1': + resolution: {integrity: sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-ppc64-gnu@11.19.1': + resolution: {integrity: sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-gnu@11.19.1': + resolution: {integrity: sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-musl@11.19.1': + resolution: {integrity: sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-s390x-gnu@11.19.1': + resolution: {integrity: sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-gnu@11.19.1': + resolution: {integrity: sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-musl@11.19.1': + resolution: {integrity: sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-openharmony-arm64@11.19.1': + resolution: {integrity: sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA==} + cpu: [arm64] + os: [openharmony] + + '@oxc-resolver/binding-wasm32-wasi@11.19.1': + resolution: {integrity: sha512-w8UCKhX826cP/ZLokXDS6+milN8y4X7zidsAttEdWlVoamTNf6lhBJldaWr3ukTDiye7s4HRcuPEPOXNC432Vg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@11.19.1': + resolution: {integrity: sha512-nJ4AsUVZrVKwnU/QRdzPCCrO0TrabBqgJ8pJhXITdZGYOV28TIYystV1VFLbQ7DtAcaBHpocT5/ZJnF78YJPtQ==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-ia32-msvc@11.19.1': + resolution: {integrity: sha512-EW+ND5q2Tl+a3pH81l1QbfgbF3HmqgwLfDfVithRFheac8OTcnbXt/JxqD2GbDkb7xYEqy1zNaVFRr3oeG8npA==} + cpu: [ia32] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.19.1': + resolution: {integrity: sha512-6hIU3RQu45B+VNTY4Ru8ppFwjVS/S5qwYyGhBotmjxfEKk41I2DlGtRfGJndZ5+6lneE2pwloqunlOyZuX/XAw==} + cpu: [x64] + os: [win32] + '@paralleldrive/cuid2@2.3.1': resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} @@ -6765,14 +6896,6 @@ packages: Deprecated: no longer maintained and no longer used by Sinon packages. See https://github.com/sinonjs/nise/issues/243 for replacement details. - '@slack/types@2.20.1': - resolution: {integrity: sha512-eWX2mdt1ktpn8+40iiMc404uGrih+2fxiky3zBcPjtXKj6HLRdYlmhrPkJi7JTJm8dpXR6BWVWEDBXtaWMKD6A==} - engines: {node: '>= 12.13.0', npm: '>= 6.12.0'} - - '@slack/webhook@7.0.9': - resolution: {integrity: sha512-hMfkQ5Y3Y7FtL+ZYhcxFblidx4Z2LPRFrhY1KJb6NqQdnK6kzTzTS1mjH5taVQIB496eqwpg9FE9mq9BFx0DWw==} - engines: {node: '>= 18', npm: '>= 8.6.0'} - '@smithy/chunked-blob-reader-native@4.2.3': resolution: {integrity: sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==} engines: {node: '>=18.0.0'} @@ -8474,9 +8597,6 @@ packages: '@tryghost/config@2.0.3': resolution: {integrity: sha512-hUC+OpeYKr8/5GJjc55KKe6M7r6S9MbnQZUEug572QY9f/iJwK1AwhbQ6rUR0iNOymPbv0MqUiT3lkYiB9nVbg==} - '@tryghost/content-api@1.12.6': - resolution: {integrity: sha512-QCsSG10onLqpXKN7F8yHefOgRBtWdmi30s2dV7BYY5+/Iwgsaf/o/U5oTQhlsGkO3P73Zfw++tnqOL2wO9IYUA==} - '@tryghost/custom-fonts@1.0.8': resolution: {integrity: sha512-56epGhRXXk1QfKYxzyvnm7OCszdlWmMbw9XYq1w5L/67mPoQPX+4NkkNmZqlq/0CjNahzExOB0nOm7aHVDwvWQ==} @@ -8744,6 +8864,9 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@tybys/wasm-util@0.9.0': resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} @@ -8789,22 +8912,13 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - '@types/color-convert@2.0.4': - resolution: {integrity: sha512-Ub1MmDdyZ7mX//g25uBAoH/mWGd9swVbt8BseymnaE18SU4po/PjmCrHxqIIRjBo3hV/vh1KGr0eMxUhp+t+dQ==} - '@types/color-convert@3.0.1': resolution: {integrity: sha512-Wj7O4Y7tPo/3y9z4K0XRLbLBP2hCHq2vlmLkR0uQDGmLdoUVDmUrXy50ZffMxZKzBpYxFT+4FnDT8xzMlnKRQQ==} deprecated: This is a stub types definition. color-convert provides its own type definitions, so you do not need this installed. - '@types/color-name@1.1.5': - resolution: {integrity: sha512-j2K5UJqGTxeesj6oQuGpMgifpT5k9HprgQd8D1Y0lOFqKHl3PJu5GMeS4Y5EgjS55AE6OQxf8mPED9uaGbf4Cg==} - '@types/color@4.2.0': resolution: {integrity: sha512-6+xrIRImMtGAL2X3qYkd02Mgs+gFGs+WsK0b7VVMaO4mYRISwyTjcqNrO0mNSmYEoq++rSLDB2F5HDNmqfOe+A==} - '@types/color@4.2.1': - resolution: {integrity: sha512-ResWeDLy1vozIMbD6JLRKuNBbIcIlBkjTIxVHHd5Cqtm77T+ahH3BWE/PWv1OhFd1HAwcn8no4ig2uTaRXpYQQ==} - '@types/command-line-args@5.2.3': resolution: {integrity: sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==} @@ -8865,10 +8979,6 @@ packages: '@types/doctrine@0.0.9': resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} - '@types/dompurify@3.2.0': - resolution: {integrity: sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg==} - deprecated: This is a stub types definition. dompurify provides its own type definitions, so you do not need this installed. - '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -9562,10 +9672,6 @@ packages: abortcontroller-polyfill@1.7.8: resolution: {integrity: sha512-9f1iZ2uWh92VcrU9Y8x+LdM4DLj75VE0MJB8zuF1iUnroEptStw+DQ8EQPMUdfe5k+PkB1uUfDQfWbhstH8LrQ==} - abstract-leveldown@0.12.4: - resolution: {integrity: sha512-TOod9d5RDExo6STLMGa+04HGkl+TlMfbDnTyN93/ETJ9DpQ0DaYLqcMZlbXvdc4W3vVo1Qrl+WhSp8zvDsJ+jA==} - deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -10463,9 +10569,6 @@ packages: bintrees@1.0.2: resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} - bl@0.8.2: - resolution: {integrity: sha512-pfqikmByp+lifZCS0p6j6KreV6kNU6Apzpm2nKOk+94cZb/jvle55+JxWiByUQ0Wo/+XnDXEy5MxxKMb6r0VIw==} - bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -10767,9 +10870,6 @@ packages: browserify-des@1.0.2: resolution: {integrity: sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==} - browserify-fs@1.0.0: - resolution: {integrity: sha512-8LqHRPuAEKvyTX34R6tsw4bO2ro6j9DmlYBhiYWHRM26Zv2cBw1fJOU0NeUQ0RkXkPn/PFBjhA0dm4AgaBurTg==} - browserify-rsa@4.1.1: resolution: {integrity: sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==} engines: {node: '>= 0.10'} @@ -10794,10 +10894,6 @@ packages: resolution: {integrity: sha512-rV2tY8amv+2ERYNNC7voCl1A4Mh+s2IvyyDo3DAMKhaR4ME8r+4t9MH0Fgqjpe1ievESYX9Pes7gf05LBBUCRA==} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - bs-logger@0.2.6: - resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} - engines: {node: '>= 6'} - bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} @@ -10818,9 +10914,6 @@ packages: buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - buffer-es6@4.9.3: - resolution: {integrity: sha512-Ibt+oXxhmeYJSsCkODPqNpPmyegefiD8rfutH1NYGhMZQhSp95Rz7haemgnJ6dxa6LT+JLLbtgOMORRluwKktw==} - buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -11252,9 +11345,6 @@ packages: clone-response@1.0.3: resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} - clone@0.1.19: - resolution: {integrity: sha512-IO78I0y6JcSpEPHzK4obKdsL7E7oLdRVDVOLwr2Hkbjsb+Eoz0dxW6tef0WizoKu0gLC4oZSZuEF4U2K6w1WQw==} - clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} @@ -11400,6 +11490,10 @@ packages: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + commander@14.0.3: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} @@ -12296,9 +12390,6 @@ packages: resolution: {integrity: sha512-/9+C44X7lot0IeiyfgJmETtRMhBidBYM2QFFIkGa0U1k+hSyY87Nw7PY3eDqpvCBm7I3WCSfPeZskW/YYq6m4g==} engines: {node: '>=4'} - de-indent@1.0.2: - resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} - debug-logfmt@1.4.10: resolution: {integrity: sha512-+8rNw7zjXNRntMoJyp5211Y4W3nkhCCMBO7qe8Pht/9NscMklHwyTXMLUzk84YUDSksg87XRmK/LCzJdJ4eU7Q==} engines: {node: '>= 8'} @@ -12434,10 +12525,6 @@ packages: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} - deferred-leveldown@0.2.0: - resolution: {integrity: sha512-+WCbb4+ez/SZ77Sdy1iadagFiVzMB89IKOBhglgnUkVxOxRWmmFsz8UDSNWh4Rhq+3wr/vMFlYj+rdEwWUDdng==} - deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -13395,16 +13482,16 @@ packages: es6-promise@4.2.8: resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} - esbuild@0.20.2: - resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} - engines: {node: '>=12'} - hasBin: true - esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.27.4: resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==} engines: {node: '>=18'} @@ -13886,16 +13973,9 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fast-xml-builder@1.1.4: - resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==} - fast-xml-builder@1.1.8: resolution: {integrity: sha512-sDVBc2gg8pSKvcbE8rBmOyjSGQf0AdsbqvHeIOv3D/uYNoV4eCReQXyDF8Pdv8+m1FHazACypSz2hR7O2S1LLw==} - fast-xml-parser@5.5.8: - resolution: {integrity: sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==} - hasBin: true - fast-xml-parser@5.7.2: resolution: {integrity: sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==} hasBin: true @@ -13917,6 +13997,9 @@ packages: fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + fd-package-json@2.0.0: + resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} + fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} @@ -14144,9 +14227,6 @@ packages: resolution: {integrity: sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==} engines: {node: '>=0.10.0'} - foreach@2.0.6: - resolution: {integrity: sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==} - foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -14174,9 +14254,10 @@ packages: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} - formidable@1.2.6: - resolution: {integrity: sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==} - deprecated: 'Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau' + formatly@0.3.0: + resolution: {integrity: sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==} + engines: {node: '>=18.3.0'} + hasBin: true formidable@2.1.5: resolution: {integrity: sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==} @@ -14307,9 +14388,6 @@ packages: resolution: {integrity: sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==} engines: {node: '>=10'} - fwd-stream@1.0.4: - resolution: {integrity: sha512-q2qaK2B38W07wfPSQDKMiKOD5Nzv2XyuvQlrmh1q0pxyHNanKHq8lwQ6n9zHucAwA5EbzRJKEgds2orn88rYTg==} - gauge@2.7.4: resolution: {integrity: sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==} deprecated: This package is no longer supported. @@ -14398,6 +14476,9 @@ packages: get-tsconfig@4.13.7: resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + getopts@2.2.5: resolution: {integrity: sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA==} @@ -14909,9 +14990,9 @@ packages: resolution: {integrity: sha512-nXHJZYtNrfsi1UQbyRqm3Gou431elgLjKl//CYlnBGt5aTWdRPH1PiS2T/p/n8Q8LnqYqzQJik3Q7mkwvLokeg==} engines: {node: '>= 12'} - i18next-parser@8.13.0: - resolution: {integrity: sha512-XU7resoeNcpJazh29OncQQUH6HsgCxk06RqBBDAmLHldafxopfCHY1vElyG/o3EY0Sn7XjelAmPTV0SgddJEww==} - engines: {node: '>=16.0.0 || >=18.0.0 || >=20.0.0', npm: '>=6', yarn: '>=1'} + i18next-parser@9.3.0: + resolution: {integrity: sha512-VaQqk/6nLzTFx1MDiCZFtzZXKKyBV6Dv0cJMFM/hOt4/BWHWRgYafzYfVQRUzotwUwjqeNCprWnutzD/YAGczg==} + engines: {node: ^18.0.0 || ^20.0.0 || ^22.0.0, npm: '>=6', yarn: '>=1'} deprecated: Project is deprecated, use i18next-cli instead hasBin: true @@ -14936,9 +15017,6 @@ packages: peerDependencies: postcss: ^8.1.0 - idb-wrapper@1.7.2: - resolution: {integrity: sha512-zfNREywMuf0NzDo9mVsL0yegjsirJxHpKHvWcyRozIqQy89g0a3U+oBPOCN4cc0oCiOuYgZHimzaW/R46G1Mpg==} - ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -15019,9 +15097,6 @@ packages: indexes-of@1.0.1: resolution: {integrity: sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA==} - indexof@0.0.1: - resolution: {integrity: sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==} - infer-owner@1.0.4: resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} @@ -15328,9 +15403,6 @@ packages: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} - is-object@0.1.2: - resolution: {integrity: sha512-GkfZZlIZtpkFrqyAXPQSRBMsaHAw+CgoKe2HXAkjd/sfoI9+hS8PT4wg2rJxdQyUKr7N2vHJbg7/jQtE5l5vBQ==} - is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} @@ -15505,9 +15577,6 @@ packages: resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} engines: {node: '>=16'} - is@0.2.7: - resolution: {integrity: sha512-ajQCouIvkcSnl2iRdK70Jug9mohIHVX9uKpoWnl115ov0R5mzBvRrXxrnHbsA+8AdwCwc/sfw7HXmd4I5EJBdQ==} - isarray@0.0.1: resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} @@ -15521,9 +15590,6 @@ packages: resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} engines: {node: '>= 8.0.0'} - isbuffer@0.0.0: - resolution: {integrity: sha512-xU+NoHp+YtKQkaM2HsQchYn0sltxMxew0HavMfHbjnucBoTSGbw745tL+Z7QBANleWM1eEQMenEpi174mIeS4g==} - isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -16129,6 +16195,11 @@ packages: tedious: optional: true + knip@6.12.0: + resolution: {integrity: sha512-nRg8+DOFcfBD6NjmNzu9+3D35QnEmMsnojJGOHQUqv+70r1aOx99wpSUXvEV7syQVOL5E6tNXXkoyG1Fuz8BWg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + known-css-properties@0.29.0: resolution: {integrity: sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==} @@ -16159,35 +16230,6 @@ packages: engines: {node: '>=18'} hasBin: true - level-blobs@0.1.7: - resolution: {integrity: sha512-n0iYYCGozLd36m/Pzm206+brIgXP8mxPZazZ6ZvgKr+8YwOZ8/PPpYC5zMUu2qFygRN8RO6WC/HH3XWMW7RMVg==} - - level-filesystem@1.2.0: - resolution: {integrity: sha512-PhXDuCNYpngpxp3jwMT9AYBMgOvB6zxj3DeuIywNKmZqFj2djj9XfT2XDVslfqmo0Ip79cAd3SBy3FsfOZPJ1g==} - - level-fix-range@1.0.2: - resolution: {integrity: sha512-9llaVn6uqBiSlBP+wKiIEoBa01FwEISFgHSZiyec2S0KpyLUkGR4afW/FCZ/X8y+QJvzS0u4PGOlZDdh1/1avQ==} - - level-fix-range@2.0.0: - resolution: {integrity: sha512-WrLfGWgwWbYPrHsYzJau+5+te89dUbENBg3/lsxOs4p2tYOhCHjbgXxBAj4DFqp3k/XBwitcRXoCh8RoCogASA==} - - level-hooks@4.5.0: - resolution: {integrity: sha512-fxLNny/vL/G4PnkLhWsbHnEaRi+A/k8r5EH/M77npZwYL62RHi2fV0S824z3QdpAk6VTgisJwIRywzBHLK4ZVA==} - - level-js@2.2.4: - resolution: {integrity: sha512-lZtjt4ZwHE00UMC1vAb271p9qzg8vKlnDeXfIesH3zL0KxhHRDjClQLGLWhyR0nK4XARnd4wc/9eD1ffd4PshQ==} - deprecated: Superseded by browser-level (https://github.com/Level/community#faq) - - level-peek@1.0.6: - resolution: {integrity: sha512-TKEzH5TxROTjQxWMczt9sizVgnmJ4F3hotBI48xCTYvOKd/4gA/uY0XjKkhJFo6BMic8Tqjf6jFMLWeg3MAbqQ==} - - level-sublevel@5.2.3: - resolution: {integrity: sha512-tO8jrFp+QZYrxx/Gnmjawuh1UBiifpvKNAcm4KCogesWr1Nm2+ckARitf+Oo7xg4OHqMW76eAqQ204BoIlscjA==} - - levelup@0.18.6: - resolution: {integrity: sha512-uB0auyRqIVXx+hrpIUtol4VAPhLRcnxcOsd2i2m6rbFIDarO5dnrupLOStYYpEcu8ZT087Z9HEuYw1wjr6RL6Q==} - deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) - leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -16648,9 +16690,6 @@ packages: resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==} engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} - ltgt@2.2.1: - resolution: {integrity: sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==} - lucide-react@0.577.0: resolution: {integrity: sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==} peerDependencies: @@ -17326,12 +17365,6 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - next-themes@0.4.6: - resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} - peerDependencies: - react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} @@ -17589,13 +17622,6 @@ packages: resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} engines: {node: '>= 0.4'} - object-keys@0.2.0: - resolution: {integrity: sha512-XODjdR2pBh/1qrjPcbSeSgEtKbYo7LqYNq64/TPuCf7j9SfDD3i21yatKoIy39yIWNvVM59iutfQQpCv1RfFzA==} - deprecated: Please update to the latest object-keys - - object-keys@0.4.0: - resolution: {integrity: sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw==} - object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -17635,9 +17661,6 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - octal@1.0.0: - resolution: {integrity: sha512-nnda7W8d+A3vEIY+UrDQzzboPf1vhs4JYVhff5CDkq9QNoZY7Xrxeo/htox37j9dZf7yNHevZzqtejWgy1vCqQ==} - on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -17725,6 +17748,13 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + oxc-parser@0.128.0: + resolution: {integrity: sha512-XkOw3eiIxAgQ19WRew/Bq9wc5Ga/guaWIzDBzq80z1PyuDNGvWBpPby9k6YGwV8A8uMw+Nlq3xqlzuDYmUFYUw==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-resolver@11.19.1: + resolution: {integrity: sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==} + p-cancelable@2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} @@ -17901,9 +17931,6 @@ packages: resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} engines: {node: '>=0.10.0'} - parse-prometheus-text-format@1.1.1: - resolution: {integrity: sha512-dBlhYVACjRdSqLMFe4/Q1l/Gd3UmXm8ruvsTi7J6ul3ih45AkzkVpI5XHV4aZ37juGZW5+3dGU5lwk+QLM9XJA==} - parse-srcset@1.0.2: resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==} @@ -17960,10 +17987,6 @@ packages: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - path-expression-matcher@1.2.0: - resolution: {integrity: sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==} - engines: {node: '>=14.0.0'} - path-expression-matcher@1.5.0: resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} engines: {node: '>=14.0.0'} @@ -18874,9 +18897,6 @@ packages: resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} engines: {node: ^20.17.0 || >=22.9.0} - process-es6@0.11.6: - resolution: {integrity: sha512-GYBRQtL4v3wgigq10Pv58jmTbFXlIiTbSfgnNqZLY0ldUPqy1rRxDI5fCjoCpnM6TqmHQI8ydzTBXW86OYc0gA==} - process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -19004,9 +19024,6 @@ packages: resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} engines: {node: '>=10'} - prr@0.0.0: - resolution: {integrity: sha512-LmUECmrW7RVj6mDWKjTXfKug7TFGdiz9P18HMcO4RHL+RW7MCOGNvpj5j47Rnp6ne6r4fZ2VzyUWEpKbg+tsjQ==} - prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} @@ -19324,9 +19341,6 @@ packages: readable-stream@1.0.34: resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} - readable-stream@1.1.14: - resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==} - readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -19682,9 +19696,6 @@ packages: resolution: {integrity: sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==} engines: {node: '>= 0.8'} - rollup-plugin-node-builtins@2.1.2: - resolution: {integrity: sha512-bxdnJw8jIivr2yEyt8IZSGqZkygIJOGAWypXvHXnwKAbUcN4Q/dGTx7K0oAJryC/m6aq6tKutltSeXtuogU6sw==} - rollup-pluginutils@2.8.2: resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} @@ -19850,10 +19861,6 @@ packages: selderee@0.6.0: resolution: {integrity: sha512-ibqWGV5aChDvfVdqNYuaJP/HnVBhlRGSRrlbttmlMpHcLuTqqbMH36QkSs9GEgj5M88JDYLI8eyP94JaQ8xRlg==} - semver@2.3.2: - resolution: {integrity: sha512-abLdIKCosKfpnmhS52NCTjO4RiLspDfsn37prjzGrp9im5DPJOgh82Os92vtwGh6XdQryKI/7SREZnV+aqiXrA==} - hasBin: true - semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -19927,9 +19934,6 @@ packages: engines: {node: '>= 0.10'} hasBin: true - shallow-equal@1.2.1: - resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==} - sharp@0.34.5: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -20014,9 +20018,6 @@ packages: sinon@18.0.1: resolution: {integrity: sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw==} - sinon@21.0.1: - resolution: {integrity: sha512-Z0NVCW45W8Mg5oC/27/+fCqIHFnW8kpkFOq0j9XJIev4Ld0mKmERaZv5DMLAb9fGCevjKwaEeIQz5+MBXfZcDw==} - sinon@21.1.1: resolution: {integrity: sha512-Tn8sLJZay8gRFQycxVwgL86PSUt9SnS9N2wCg12XyR75BssSS1c2fBPFUzwDj7XTNZlaM8+qkETTBYWnkKWXwg==} @@ -20070,6 +20071,10 @@ packages: resolution: {integrity: sha512-0R6YJ5hLpDH4mZR7N5eZ12oCMLspvGOHL9A9SEm2e3b/CQmQidekW4SWSKEmor/3x6m3NCBBEqLzikcZC9VJNQ==} engines: {node: '>=4.0.0'} + smol-toml@1.6.1: + resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} + engines: {node: '>= 18'} + snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} @@ -20315,9 +20320,6 @@ packages: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'} - string-range@1.2.2: - resolution: {integrity: sha512-tYft6IFi8SjplJpxCUxyqisD3b+R2CSkomrtJYCkvuf1KuCAWgz7YXt4O0jip7efpfCemwHEzTEAO8EuOYgh3w==} - string-template@0.2.1: resolution: {integrity: sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==} @@ -20440,6 +20442,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + strip-literal@2.1.1: resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==} @@ -20450,9 +20456,6 @@ packages: resolution: {integrity: sha512-hrA79fjmN2Eb6K3kxkDzU4ODeVGGjXQsuVaAPSUro6I9MM3X+BvIsVqdphm3BXWfimAGFvUqWtPtHy25mICY1w==} engines: {node: ^8.1 || >=10.*} - strnum@2.2.2: - resolution: {integrity: sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==} - strnum@2.2.3: resolution: {integrity: sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==} @@ -20504,14 +20507,6 @@ packages: sum-up@1.0.3: resolution: {integrity: sha512-zw5P8gnhiqokJUWRdR6F4kIIIke0+ubQSGyYUY506GCbJWtV7F6Xuy0j6S125eSX2oF+a8KdivsZ8PlVEH0Mcw==} - superagent-throttle@1.0.1: - resolution: {integrity: sha512-m5Ngf0S5QoA84zgwVqVnVA34u9uvo8uM+QEF9B7eNI5FDaSoSoUwQsx7V1GmLXgYLkolhIiucFDVJXF9z49hgQ==} - - superagent@5.3.1: - resolution: {integrity: sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==} - engines: {node: '>= 7.0.0'} - deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net - superagent@8.1.2: resolution: {integrity: sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==} engines: {node: '>=6.4.0 <13 || >=14'} @@ -20690,6 +20685,12 @@ packages: resolution: {integrity: sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==} engines: {node: '>=6.0.0'} + temporal-polyfill@0.3.0: + resolution: {integrity: sha512-qNsTkX9K8hi+FHDfHmf22e/OGuXmfBm9RqNismxBrnSmZVJKegQ+HYYXT+R7Ha8F/YSm2Y34vmzD4cxMu2u95g==} + + temporal-spec@0.3.0: + resolution: {integrity: sha512-n+noVpIqz4hYgFSMOSiINNOUOMFtV5cZQNCmmszA6GiVFVRt3G7AqVyhXjhCSmowvQn+NsGn+jMDMKJYHd3bSQ==} + terminal-link@5.0.0: resolution: {integrity: sha512-qFAy10MTMwjzjU8U16YS4YoZD+NQLHzLssFMNqgravjbvIPNiqkGFR4yjhJfmY9R5OFU7+yHxc6y+uGHkKwLRA==} engines: {node: '>=20'} @@ -20819,6 +20820,10 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + tinypool@0.8.4: resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} engines: {node: '>=14.0.0'} @@ -20895,9 +20900,6 @@ packages: resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==} engines: {node: '>=10'} - toml@3.0.0: - resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} - tooltip.js@1.3.3: resolution: {integrity: sha512-XWWuy/dBdF/F/YpRE955yqBZ4VdLfiTAUdOqoU+wJm6phJlMpEzl/iYHZ+qJswbeT9VG822bNfsETF9wzmoy5A==} deprecated: Tooltip.js is not supported anymore, please migrate to tippy.js @@ -20982,33 +20984,6 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - ts-jest@29.4.9: - resolution: {integrity: sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==} - engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@babel/core': '>=7.0.0-beta.0 <8' - '@jest/transform': ^29.0.0 || ^30.0.0 - '@jest/types': ^29.0.0 || ^30.0.0 - babel-jest: ^29.0.0 || ^30.0.0 - esbuild: '*' - jest: ^29.0.0 || ^30.0.0 - jest-util: ^29.0.0 || ^30.0.0 - typescript: '>=4.3 <7' - peerDependenciesMeta: - '@babel/core': - optional: true - '@jest/transform': - optional: true - '@jest/types': - optional: true - babel-jest: - optional: true - esbuild: - optional: true - jest-util: - optional: true - ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true @@ -21144,9 +21119,6 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typedarray-to-buffer@1.0.4: - resolution: {integrity: sha512-vjMKrfSoUDN8/Vnqitw2FmstOfuJ73G6CrSEKnf11A6RmasVxHqfeBcnTb6RsL4pTMuV5Zsv9IiHRphMZyckUw==} - typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} @@ -21194,6 +21166,10 @@ packages: resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==} engines: {node: '>= 0.8'} + unbash@3.0.0: + resolution: {integrity: sha512-FeFPZ/WFT0mbRCuydiZzpPFlrYN8ZUpphQKoq4EeElVIYjYyGzPMxQR/simUwCOJIyVhpFk4RbtyO7RuMpMnHA==} + engines: {node: '>=14'} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -21752,9 +21728,6 @@ packages: vm-browserify@1.1.2: resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} - vue-template-compiler@2.7.16: - resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==} - w3c-hr-time@1.0.2: resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==} deprecated: Use your platform's native performance.now() and performance.timeOrigin. @@ -21784,6 +21757,10 @@ packages: resolution: {integrity: sha512-41TvKmDGVpm2iuH7o+DAOt06yyu/cSHpX3uzAwetzASvlNtVddgIjXIb2DfB/Wa20B1Jo86+1Dv1CraSU7hWdw==} engines: {node: 10.* || >= 12.*} + walk-up-path@4.0.0: + resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} + engines: {node: 20 || >=22} + walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -22070,22 +22047,6 @@ packages: xregexp@2.0.0: resolution: {integrity: sha512-xl/50/Cf32VsGq/1R8jJE5ajH1yMCQkpmoS10QbFZWl2Oor4H0Me64Pu2yxvsRWK3m6soJbmGfzSR7BYmDcWAA==} - xtend@2.0.6: - resolution: {integrity: sha512-fOZg4ECOlrMl+A6Msr7EIFcON1L26mb4NY5rurSkOex/TWhazOrg6eXD/B0XkuiYcYhQDWLXzQxLMVJ7LXwokg==} - engines: {node: '>=0.4'} - - xtend@2.1.2: - resolution: {integrity: sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==} - engines: {node: '>=0.4'} - - xtend@2.2.0: - resolution: {integrity: sha512-SLt5uylT+4aoXxXuwtQp5ZnMMzhDb1Xkg4pEqc00WUJCQifPfV9Ub1VrNhp9kXkrjZD2I2Hl8WnjP37jzZLPZw==} - engines: {node: '>=0.4'} - - xtend@3.0.0: - resolution: {integrity: sha512-sp/sT9OALMjRW1fKDlPeuSZlDQpkqReA0pyJukniWbTGoEKefHxhGJynE3PNhUMlcM8qWIjPwecwCw4LArS5Eg==} - engines: {node: '>=0.4'} - xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -23006,7 +22967,7 @@ snapshots: '@aws-sdk/xml-builder@3.972.16': dependencies: '@smithy/types': 4.13.1 - fast-xml-parser: 5.5.8 + fast-xml-parser: 5.7.2 tslib: 2.8.1 '@aws-sdk/xml-builder@3.972.22': @@ -23999,6 +23960,7 @@ snapshots: '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 + optional: true '@csstools/color-helpers@6.0.2': {} @@ -24619,11 +24581,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + '@emnapi/core@1.9.1': dependencies: '@emnapi/wasi-threads': 1.2.0 tslib: 2.8.1 + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.9.1': dependencies: tslib: 2.8.1 @@ -24632,6 +24605,11 @@ snapshots: dependencies: tslib: 2.8.1 + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + '@emotion/babel-plugin@11.13.5': dependencies: '@babel/helper-module-imports': 7.28.6 @@ -24696,219 +24674,228 @@ snapshots: '@emotion/weak-memoize@0.4.0': {} - '@esbuild/aix-ppc64@0.20.2': + '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/aix-ppc64@0.21.5': + '@esbuild/aix-ppc64@0.25.12': optional: true '@esbuild/aix-ppc64@0.27.4': optional: true - '@esbuild/android-arm64@0.20.2': + '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/android-arm64@0.21.5': + '@esbuild/android-arm64@0.25.12': optional: true '@esbuild/android-arm64@0.27.4': optional: true - '@esbuild/android-arm@0.20.2': + '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/android-arm@0.21.5': + '@esbuild/android-arm@0.25.12': optional: true '@esbuild/android-arm@0.27.4': optional: true - '@esbuild/android-x64@0.20.2': + '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/android-x64@0.21.5': + '@esbuild/android-x64@0.25.12': optional: true '@esbuild/android-x64@0.27.4': optional: true - '@esbuild/darwin-arm64@0.20.2': + '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/darwin-arm64@0.21.5': + '@esbuild/darwin-arm64@0.25.12': optional: true '@esbuild/darwin-arm64@0.27.4': optional: true - '@esbuild/darwin-x64@0.20.2': + '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/darwin-x64@0.21.5': + '@esbuild/darwin-x64@0.25.12': optional: true '@esbuild/darwin-x64@0.27.4': optional: true - '@esbuild/freebsd-arm64@0.20.2': + '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/freebsd-arm64@0.21.5': + '@esbuild/freebsd-arm64@0.25.12': optional: true '@esbuild/freebsd-arm64@0.27.4': optional: true - '@esbuild/freebsd-x64@0.20.2': + '@esbuild/freebsd-x64@0.21.5': optional: true - '@esbuild/freebsd-x64@0.21.5': + '@esbuild/freebsd-x64@0.25.12': optional: true '@esbuild/freebsd-x64@0.27.4': optional: true - '@esbuild/linux-arm64@0.20.2': + '@esbuild/linux-arm64@0.21.5': optional: true - '@esbuild/linux-arm64@0.21.5': + '@esbuild/linux-arm64@0.25.12': optional: true '@esbuild/linux-arm64@0.27.4': optional: true - '@esbuild/linux-arm@0.20.2': + '@esbuild/linux-arm@0.21.5': optional: true - '@esbuild/linux-arm@0.21.5': + '@esbuild/linux-arm@0.25.12': optional: true '@esbuild/linux-arm@0.27.4': optional: true - '@esbuild/linux-ia32@0.20.2': + '@esbuild/linux-ia32@0.21.5': optional: true - '@esbuild/linux-ia32@0.21.5': + '@esbuild/linux-ia32@0.25.12': optional: true '@esbuild/linux-ia32@0.27.4': optional: true - '@esbuild/linux-loong64@0.20.2': + '@esbuild/linux-loong64@0.21.5': optional: true - '@esbuild/linux-loong64@0.21.5': + '@esbuild/linux-loong64@0.25.12': optional: true '@esbuild/linux-loong64@0.27.4': optional: true - '@esbuild/linux-mips64el@0.20.2': + '@esbuild/linux-mips64el@0.21.5': optional: true - '@esbuild/linux-mips64el@0.21.5': + '@esbuild/linux-mips64el@0.25.12': optional: true '@esbuild/linux-mips64el@0.27.4': optional: true - '@esbuild/linux-ppc64@0.20.2': + '@esbuild/linux-ppc64@0.21.5': optional: true - '@esbuild/linux-ppc64@0.21.5': + '@esbuild/linux-ppc64@0.25.12': optional: true '@esbuild/linux-ppc64@0.27.4': optional: true - '@esbuild/linux-riscv64@0.20.2': + '@esbuild/linux-riscv64@0.21.5': optional: true - '@esbuild/linux-riscv64@0.21.5': + '@esbuild/linux-riscv64@0.25.12': optional: true '@esbuild/linux-riscv64@0.27.4': optional: true - '@esbuild/linux-s390x@0.20.2': + '@esbuild/linux-s390x@0.21.5': optional: true - '@esbuild/linux-s390x@0.21.5': + '@esbuild/linux-s390x@0.25.12': optional: true '@esbuild/linux-s390x@0.27.4': optional: true - '@esbuild/linux-x64@0.20.2': + '@esbuild/linux-x64@0.21.5': optional: true - '@esbuild/linux-x64@0.21.5': + '@esbuild/linux-x64@0.25.12': optional: true '@esbuild/linux-x64@0.27.4': optional: true - '@esbuild/netbsd-arm64@0.27.4': + '@esbuild/netbsd-arm64@0.25.12': optional: true - '@esbuild/netbsd-x64@0.20.2': + '@esbuild/netbsd-arm64@0.27.4': optional: true '@esbuild/netbsd-x64@0.21.5': optional: true + '@esbuild/netbsd-x64@0.25.12': + optional: true + '@esbuild/netbsd-x64@0.27.4': optional: true - '@esbuild/openbsd-arm64@0.27.4': + '@esbuild/openbsd-arm64@0.25.12': optional: true - '@esbuild/openbsd-x64@0.20.2': + '@esbuild/openbsd-arm64@0.27.4': optional: true '@esbuild/openbsd-x64@0.21.5': optional: true + '@esbuild/openbsd-x64@0.25.12': + optional: true + '@esbuild/openbsd-x64@0.27.4': optional: true - '@esbuild/openharmony-arm64@0.27.4': + '@esbuild/openharmony-arm64@0.25.12': optional: true - '@esbuild/sunos-x64@0.20.2': + '@esbuild/openharmony-arm64@0.27.4': optional: true '@esbuild/sunos-x64@0.21.5': optional: true - '@esbuild/sunos-x64@0.27.4': + '@esbuild/sunos-x64@0.25.12': optional: true - '@esbuild/win32-arm64@0.20.2': + '@esbuild/sunos-x64@0.27.4': optional: true '@esbuild/win32-arm64@0.21.5': optional: true - '@esbuild/win32-arm64@0.27.4': + '@esbuild/win32-arm64@0.25.12': optional: true - '@esbuild/win32-ia32@0.20.2': + '@esbuild/win32-arm64@0.27.4': optional: true '@esbuild/win32-ia32@0.21.5': optional: true - '@esbuild/win32-ia32@0.27.4': + '@esbuild/win32-ia32@0.25.12': optional: true - '@esbuild/win32-x64@0.20.2': + '@esbuild/win32-ia32@0.27.4': optional: true '@esbuild/win32-x64@0.21.5': optional: true + '@esbuild/win32-x64@0.25.12': + optional: true + '@esbuild/win32-x64@0.27.4': optional: true @@ -25372,13 +25359,6 @@ snapshots: optionalDependencies: '@types/node': 22.19.17 - '@inquirer/external-editor@1.0.3(@types/node@25.6.0)': - dependencies: - chardet: 2.1.1 - iconv-lite: 0.7.2 - optionalDependencies: - '@types/node': 25.6.0 - '@inquirer/figures@1.0.15': {} '@inquirer/type@3.0.10(@types/node@25.6.0)': @@ -25720,6 +25700,7 @@ snapshots: dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + optional: true '@js-sdsl/ordered-map@4.4.2': {} @@ -25892,6 +25873,13 @@ snapshots: '@emnapi/runtime': 1.9.1 '@tybys/wasm-util': 0.9.0 + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': dependencies: eslint-scope: 5.1.1 @@ -26275,6 +26263,137 @@ snapshots: '@otplib/plugin-crypto': 12.0.1 '@otplib/plugin-thirty-two': 12.0.1 + '@oxc-parser/binding-android-arm-eabi@0.128.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.128.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.128.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.128.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.128.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.128.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.128.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.128.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.128.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.128.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.128.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.128.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.128.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.128.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.128.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.128.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.128.0': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.128.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.128.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.128.0': + optional: true + + '@oxc-project/types@0.128.0': {} + + '@oxc-resolver/binding-android-arm-eabi@11.19.1': + optional: true + + '@oxc-resolver/binding-android-arm64@11.19.1': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.19.1': + optional: true + + '@oxc-resolver/binding-darwin-x64@11.19.1': + optional: true + + '@oxc-resolver/binding-freebsd-x64@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-arm-musleabihf@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-arm64-gnu@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-arm64-musl@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-ppc64-gnu@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-riscv64-gnu@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-riscv64-musl@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-s390x-gnu@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-x64-gnu@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-x64-musl@11.19.1': + optional: true + + '@oxc-resolver/binding-openharmony-arm64@11.19.1': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + optional: true + + '@oxc-resolver/binding-win32-arm64-msvc@11.19.1': + optional: true + + '@oxc-resolver/binding-win32-ia32-msvc@11.19.1': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@11.19.1': + optional: true + '@paralleldrive/cuid2@2.3.1': dependencies: '@noble/hashes': 1.8.0 @@ -27454,16 +27573,6 @@ snapshots: '@sinonjs/text-encoding@0.7.3': {} - '@slack/types@2.20.1': {} - - '@slack/webhook@7.0.9': - dependencies: - '@slack/types': 2.20.1 - '@types/node': 25.6.0 - axios: 1.16.0 - transitivePeerDependencies: - - debug - '@smithy/chunked-blob-reader-native@4.2.3': dependencies: '@smithy/util-base64': 4.3.2 @@ -29600,12 +29709,6 @@ snapshots: '@tryghost/root-utils': 2.1.0 nconf: 0.13.0 - '@tryghost/content-api@1.12.6': - dependencies: - axios: 1.16.0 - transitivePeerDependencies: - - debug - '@tryghost/custom-fonts@1.0.8': {} '@tryghost/database-info@0.3.22': {} @@ -30214,13 +30317,22 @@ snapshots: - react-native-b4a - supports-color - '@tsconfig/node10@1.0.12': {} + '@tsconfig/node10@1.0.12': + optional: true - '@tsconfig/node12@1.0.11': {} + '@tsconfig/node12@1.0.11': + optional: true - '@tsconfig/node14@1.0.3': {} + '@tsconfig/node14@1.0.3': + optional: true - '@tsconfig/node16@1.0.4': {} + '@tsconfig/node16@1.0.4': + optional: true + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true '@tybys/wasm-util@0.9.0': dependencies: @@ -30294,24 +30406,14 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 - '@types/color-convert@2.0.4': - dependencies: - '@types/color-name': 1.1.5 - '@types/color-convert@3.0.1': dependencies: color-convert: 3.1.3 - '@types/color-name@1.1.5': {} - '@types/color@4.2.0': dependencies: '@types/color-convert': 3.0.1 - '@types/color@4.2.1': - dependencies: - '@types/color-convert': 2.0.4 - '@types/command-line-args@5.2.3': {} '@types/command-line-usage@5.0.4': {} @@ -30369,10 +30471,6 @@ snapshots: '@types/doctrine@0.0.9': {} - '@types/dompurify@3.2.0': - dependencies: - dompurify: 3.4.1 - '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -30864,7 +30962,7 @@ snapshots: debug: 4.4.3(supports-color@5.5.0) minimatch: 9.0.9 semver: 7.7.4 - tinyglobby: 0.2.15 + tinyglobby: 0.2.16 ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -30879,7 +30977,7 @@ snapshots: debug: 4.4.3(supports-color@5.5.0) minimatch: 10.2.4 semver: 7.7.4 - tinyglobby: 0.2.15 + tinyglobby: 0.2.16 ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -31428,10 +31526,6 @@ snapshots: abortcontroller-polyfill@1.7.8: {} - abstract-leveldown@0.12.4: - dependencies: - xtend: 3.0.0 - accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -31668,7 +31762,8 @@ snapshots: readable-stream: 3.6.2 optional: true - arg@4.1.3: {} + arg@4.1.3: + optional: true arg@5.0.2: {} @@ -32638,10 +32733,6 @@ snapshots: bintrees@1.0.2: {} - bl@0.8.2: - dependencies: - readable-stream: 1.0.34 - bl@4.1.0: dependencies: buffer: 5.7.1 @@ -33358,12 +33449,6 @@ snapshots: inherits: 2.0.4 safe-buffer: 5.2.1 - browserify-fs@1.0.0: - dependencies: - level-filesystem: 1.2.0 - level-js: 2.2.4 - levelup: 0.18.6 - browserify-rsa@4.1.1: dependencies: bn.js: 5.2.3 @@ -33412,10 +33497,6 @@ snapshots: - sqlite3 - supports-color - bs-logger@0.2.6: - dependencies: - fast-json-stable-stringify: 2.1.0 - bser@2.1.1: dependencies: node-int64: 0.4.0 @@ -33432,8 +33513,6 @@ snapshots: buffer-equal-constant-time@1.0.1: {} - buffer-es6@4.9.3: {} - buffer-from@1.1.2: {} buffer-xor@1.0.3: {} @@ -33980,8 +34059,6 @@ snapshots: dependencies: mimic-response: 1.0.1 - clone@0.1.19: {} - clone@1.0.4: {} clone@2.1.2: {} @@ -34106,6 +34183,8 @@ snapshots: commander@11.1.0: {} + commander@12.1.0: {} + commander@14.0.3: {} commander@2.20.3: {} @@ -34424,7 +34503,8 @@ snapshots: - supports-color - ts-node - create-require@1.1.1: {} + create-require@1.1.1: + optional: true crelt@1.0.6: {} @@ -34824,8 +34904,6 @@ snapshots: dependencies: time-zone: 1.0.0 - de-indent@1.0.2: {} - debug-logfmt@1.4.10: dependencies: '@kikobeats/time-span': 1.0.12 @@ -34947,10 +35025,6 @@ snapshots: defer-to-connect@2.0.1: {} - deferred-leveldown@0.2.0: - dependencies: - abstract-leveldown: 0.12.4 - define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -36895,32 +36969,6 @@ snapshots: es6-promise@4.2.8: {} - esbuild@0.20.2: - optionalDependencies: - '@esbuild/aix-ppc64': 0.20.2 - '@esbuild/android-arm': 0.20.2 - '@esbuild/android-arm64': 0.20.2 - '@esbuild/android-x64': 0.20.2 - '@esbuild/darwin-arm64': 0.20.2 - '@esbuild/darwin-x64': 0.20.2 - '@esbuild/freebsd-arm64': 0.20.2 - '@esbuild/freebsd-x64': 0.20.2 - '@esbuild/linux-arm': 0.20.2 - '@esbuild/linux-arm64': 0.20.2 - '@esbuild/linux-ia32': 0.20.2 - '@esbuild/linux-loong64': 0.20.2 - '@esbuild/linux-mips64el': 0.20.2 - '@esbuild/linux-ppc64': 0.20.2 - '@esbuild/linux-riscv64': 0.20.2 - '@esbuild/linux-s390x': 0.20.2 - '@esbuild/linux-x64': 0.20.2 - '@esbuild/netbsd-x64': 0.20.2 - '@esbuild/openbsd-x64': 0.20.2 - '@esbuild/sunos-x64': 0.20.2 - '@esbuild/win32-arm64': 0.20.2 - '@esbuild/win32-ia32': 0.20.2 - '@esbuild/win32-x64': 0.20.2 - esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -36947,6 +36995,35 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + esbuild@0.27.4: optionalDependencies: '@esbuild/aix-ppc64': 0.27.4 @@ -37741,20 +37818,10 @@ snapshots: fast-uri@3.1.0: {} - fast-xml-builder@1.1.4: - dependencies: - path-expression-matcher: 1.2.0 - fast-xml-builder@1.1.8: dependencies: path-expression-matcher: 1.5.0 - fast-xml-parser@5.5.8: - dependencies: - fast-xml-builder: 1.1.4 - path-expression-matcher: 1.2.0 - strnum: 2.2.2 - fast-xml-parser@5.7.2: dependencies: '@nodable/entities': 2.1.0 @@ -37783,6 +37850,10 @@ snapshots: dependencies: bser: 2.1.1 + fd-package-json@2.0.0: + dependencies: + walk-up-path: 4.0.0 + fd-slicer@1.1.0: dependencies: pend: 1.2.0 @@ -38040,8 +38111,6 @@ snapshots: dependencies: for-in: 1.0.2 - foreach@2.0.6: {} - foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -38078,7 +38147,9 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 - formidable@1.2.6: {} + formatly@0.3.0: + dependencies: + fd-package-json: 2.0.0 formidable@2.1.5: dependencies: @@ -38262,10 +38333,6 @@ snapshots: fuse.js@6.6.2: {} - fwd-stream@1.0.4: - dependencies: - readable-stream: 1.0.34 - gauge@2.7.4: dependencies: aproba: 1.2.0 @@ -38362,6 +38429,10 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + getopts@2.2.5: {} getopts@2.3.0: {} @@ -39027,15 +39098,15 @@ snapshots: dependencies: diacritics: 1.3.0 - i18next-parser@8.13.0: + i18next-parser@9.3.0: dependencies: '@babel/runtime': 7.29.2 broccoli-plugin: 4.0.7 - cheerio: 1.0.0-rc.12 + cheerio: 1.2.0 colors: 1.4.0 - commander: 11.1.0 + commander: 12.1.0 eol: 0.9.1 - esbuild: 0.20.2 + esbuild: 0.25.12 fs-extra: 11.3.4 gulp-sort: 2.0.0 i18next: 23.16.8 @@ -39046,7 +39117,6 @@ snapshots: typescript: 5.9.3 vinyl: 3.0.1 vinyl-fs: 4.0.2 - vue-template-compiler: 2.7.16 transitivePeerDependencies: - bare-abort-controller - react-native-b4a @@ -39072,8 +39142,6 @@ snapshots: dependencies: postcss: 8.5.6 - idb-wrapper@1.7.2: {} - ieee754@1.2.1: {} iferr@0.1.5: {} @@ -39138,8 +39206,6 @@ snapshots: indexes-of@1.0.1: {} - indexof@0.0.1: {} - infer-owner@1.0.4: {} inflected@2.1.0: {} @@ -39223,26 +39289,6 @@ snapshots: transitivePeerDependencies: - '@types/node' - inquirer@8.2.7(@types/node@25.6.0): - dependencies: - '@inquirer/external-editor': 1.0.3(@types/node@25.6.0) - ansi-escapes: 4.3.2 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-width: 3.0.0 - figures: 3.2.0 - lodash: 4.18.1 - mute-stream: 0.0.8 - ora: 5.4.1 - run-async: 2.4.1 - rxjs: 7.8.2 - string-width: 4.2.3 - strip-ansi: 6.0.1 - through: 2.3.8 - wrap-ansi: 6.2.0 - transitivePeerDependencies: - - '@types/node' - install-artifact-from-github@1.4.0: {} internal-slot@1.1.0: @@ -39474,8 +39520,6 @@ snapshots: is-obj@2.0.0: {} - is-object@0.1.2: {} - is-path-inside@3.0.3: {} is-path-inside@4.0.0: {} @@ -39615,8 +39659,6 @@ snapshots: dependencies: is-inside-container: 1.0.0 - is@0.2.7: {} - isarray@0.0.1: {} isarray@1.0.0: {} @@ -39625,8 +39667,6 @@ snapshots: isbinaryfile@4.0.10: {} - isbuffer@0.0.0: {} - isexe@2.0.0: {} isexe@4.0.0: {} @@ -40715,6 +40755,26 @@ snapshots: transitivePeerDependencies: - supports-color + knip@6.12.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + formatly: 0.3.0 + get-tsconfig: 4.14.0 + jiti: 2.6.1 + minimist: 1.2.8 + oxc-parser: 0.128.0 + oxc-resolver: 11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + picomatch: 4.0.4 + smol-toml: 1.6.1 + strip-json-comments: 5.0.3 + tinyglobby: 0.2.16 + unbash: 3.0.0 + yaml: 2.8.3 + zod: 4.1.12 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + known-css-properties@0.29.0: {} language-subtag-registry@0.3.23: {} @@ -40758,64 +40818,6 @@ snapshots: source-map: 0.6.1 optional: true - level-blobs@0.1.7: - dependencies: - level-peek: 1.0.6 - once: 1.4.0 - readable-stream: 1.1.14 - - level-filesystem@1.2.0: - dependencies: - concat-stream: 1.6.2 - errno: 0.1.8 - fwd-stream: 1.0.4 - level-blobs: 0.1.7 - level-peek: 1.0.6 - level-sublevel: 5.2.3 - octal: 1.0.0 - once: 1.4.0 - xtend: 2.2.0 - - level-fix-range@1.0.2: {} - - level-fix-range@2.0.0: - dependencies: - clone: 0.1.19 - - level-hooks@4.5.0: - dependencies: - string-range: 1.2.2 - - level-js@2.2.4: - dependencies: - abstract-leveldown: 0.12.4 - idb-wrapper: 1.7.2 - isbuffer: 0.0.0 - ltgt: 2.2.1 - typedarray-to-buffer: 1.0.4 - xtend: 2.1.2 - - level-peek@1.0.6: - dependencies: - level-fix-range: 1.0.2 - - level-sublevel@5.2.3: - dependencies: - level-fix-range: 2.0.0 - level-hooks: 4.5.0 - string-range: 1.2.2 - xtend: 2.0.6 - - levelup@0.18.6: - dependencies: - bl: 0.8.2 - deferred-leveldown: 0.2.0 - errno: 0.1.8 - prr: 0.0.0 - readable-stream: 1.0.34 - semver: 2.3.2 - xtend: 3.0.0 - leven@3.1.0: {} levn@0.4.1: @@ -41265,8 +41267,6 @@ snapshots: lru.min@1.1.4: {} - ltgt@2.2.1: {} - lucide-react@0.577.0(react@18.3.1): dependencies: react: 18.3.1 @@ -41322,7 +41322,8 @@ snapshots: dependencies: semver: 7.7.4 - make-error@1.3.6: {} + make-error@1.3.6: + optional: true make-fetch-happen@15.0.5: dependencies: @@ -42111,11 +42112,6 @@ snapshots: neo-async@2.6.2: {} - next-themes@0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - nice-try@1.0.5: {} nise@4.1.0: @@ -42187,7 +42183,7 @@ snapshots: proc-log: 6.1.0 semver: 7.7.4 tar: 7.5.13 - tinyglobby: 0.2.15 + tinyglobby: 0.2.16 which: 6.0.1 transitivePeerDependencies: - supports-color @@ -42547,14 +42543,6 @@ snapshots: call-bind: 1.0.8 define-properties: 1.2.1 - object-keys@0.2.0: - dependencies: - foreach: 2.0.6 - indexof: 0.0.1 - is: 0.2.7 - - object-keys@0.4.0: {} - object-keys@1.1.1: {} object.assign@4.1.7: @@ -42615,8 +42603,6 @@ snapshots: obug@2.1.1: {} - octal@1.0.0: {} - on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -42732,6 +42718,57 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + oxc-parser@0.128.0: + dependencies: + '@oxc-project/types': 0.128.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.128.0 + '@oxc-parser/binding-android-arm64': 0.128.0 + '@oxc-parser/binding-darwin-arm64': 0.128.0 + '@oxc-parser/binding-darwin-x64': 0.128.0 + '@oxc-parser/binding-freebsd-x64': 0.128.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.128.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.128.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.128.0 + '@oxc-parser/binding-linux-arm64-musl': 0.128.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.128.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.128.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.128.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.128.0 + '@oxc-parser/binding-linux-x64-gnu': 0.128.0 + '@oxc-parser/binding-linux-x64-musl': 0.128.0 + '@oxc-parser/binding-openharmony-arm64': 0.128.0 + '@oxc-parser/binding-wasm32-wasi': 0.128.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.128.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.128.0 + '@oxc-parser/binding-win32-x64-msvc': 0.128.0 + + oxc-resolver@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.19.1 + '@oxc-resolver/binding-android-arm64': 11.19.1 + '@oxc-resolver/binding-darwin-arm64': 11.19.1 + '@oxc-resolver/binding-darwin-x64': 11.19.1 + '@oxc-resolver/binding-freebsd-x64': 11.19.1 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.19.1 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.19.1 + '@oxc-resolver/binding-linux-arm64-gnu': 11.19.1 + '@oxc-resolver/binding-linux-arm64-musl': 11.19.1 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.19.1 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.19.1 + '@oxc-resolver/binding-linux-riscv64-musl': 11.19.1 + '@oxc-resolver/binding-linux-s390x-gnu': 11.19.1 + '@oxc-resolver/binding-linux-x64-gnu': 11.19.1 + '@oxc-resolver/binding-linux-x64-musl': 11.19.1 + '@oxc-resolver/binding-openharmony-arm64': 11.19.1 + '@oxc-resolver/binding-wasm32-wasi': 11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@oxc-resolver/binding-win32-arm64-msvc': 11.19.1 + '@oxc-resolver/binding-win32-ia32-msvc': 11.19.1 + '@oxc-resolver/binding-win32-x64-msvc': 11.19.1 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + p-cancelable@2.1.1: {} p-cancelable@3.0.0: {} @@ -42891,10 +42928,6 @@ snapshots: parse-passwd@1.0.0: {} - parse-prometheus-text-format@1.1.1: - dependencies: - shallow-equal: 1.2.1 - parse-srcset@1.0.2: {} parse-static-imports@1.1.0: {} @@ -42944,8 +42977,6 @@ snapshots: path-exists@5.0.0: {} - path-expression-matcher@1.2.0: {} - path-expression-matcher@1.5.0: {} path-is-absolute@1.0.1: {} @@ -43883,8 +43914,6 @@ snapshots: proc-log@6.1.0: {} - process-es6@0.11.6: {} - process-nextick-args@2.0.1: {} process-relative-require@1.0.0: @@ -44065,8 +44094,6 @@ snapshots: proxy-from-env@2.1.0: {} - prr@0.0.0: {} - prr@1.0.1: {} psl@1.15.0: @@ -44439,13 +44466,6 @@ snapshots: isarray: 0.0.1 string_decoder: 0.10.31 - readable-stream@1.1.14: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 0.0.1 - string_decoder: 0.10.31 - readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -44875,13 +44895,6 @@ snapshots: hash-base: 3.1.2 inherits: 2.0.4 - rollup-plugin-node-builtins@2.1.2: - dependencies: - browserify-fs: 1.0.0 - buffer-es6: 4.9.3 - crypto-browserify: 3.12.1 - process-es6: 0.11.6 - rollup-pluginutils@2.8.2: dependencies: estree-walker: 0.6.1 @@ -45118,8 +45131,6 @@ snapshots: dependencies: parseley: 0.7.0 - semver@2.3.2: {} - semver@5.7.2: {} semver@6.3.1: {} @@ -45233,8 +45244,6 @@ snapshots: safe-buffer: 5.2.1 to-buffer: 1.2.2 - shallow-equal@1.2.1: {} - sharp@0.34.5: dependencies: '@img/colour': 1.1.0 @@ -45365,14 +45374,6 @@ snapshots: nise: 6.1.4 supports-color: 7.2.0 - sinon@21.0.1: - dependencies: - '@sinonjs/commons': 3.0.1 - '@sinonjs/fake-timers': 15.1.1 - '@sinonjs/samsam': 8.0.3 - diff: 8.0.4 - supports-color: 7.2.0 - sinon@21.1.1: dependencies: '@sinonjs/commons': 3.0.1 @@ -45428,6 +45429,8 @@ snapshots: smartquotes@2.3.2: {} + smol-toml@1.6.1: {} + snake-case@3.0.4: dependencies: dot-case: 3.0.4 @@ -45738,8 +45741,6 @@ snapshots: char-regex: 1.0.2 strip-ansi: 6.0.1 - string-range@1.2.2: {} - string-template@0.2.1: {} string-width@1.0.2: @@ -45888,6 +45889,8 @@ snapshots: strip-json-comments@3.1.1: {} + strip-json-comments@5.0.3: {} + strip-literal@2.1.1: dependencies: js-tokens: 9.0.1 @@ -45901,8 +45904,6 @@ snapshots: '@types/node': 25.6.0 qs: 6.15.0 - strnum@2.2.2: {} - strnum@2.2.3: {} strtok3@6.3.0: @@ -46000,24 +46001,6 @@ snapshots: dependencies: chalk: 1.1.3 - superagent-throttle@1.0.1: {} - - superagent@5.3.1: - dependencies: - component-emitter: 1.3.1 - cookiejar: 2.1.4 - debug: 4.4.3(supports-color@5.5.0) - fast-safe-stringify: 2.1.1 - form-data: 3.0.4 - formidable: 1.2.6 - methods: 1.1.2 - mime: 2.6.0 - qs: 6.15.0 - readable-stream: 3.6.2 - semver: 7.7.4 - transitivePeerDependencies: - - supports-color - superagent@8.1.2: dependencies: component-emitter: 1.3.1 @@ -46283,6 +46266,12 @@ snapshots: mkdirp: 0.5.6 rimraf: 2.6.3 + temporal-polyfill@0.3.0: + dependencies: + temporal-spec: 0.3.0 + + temporal-spec@0.3.0: {} + terminal-link@5.0.0: dependencies: ansi-escapes: 7.3.0 @@ -46511,6 +46500,11 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinypool@0.8.4: {} tinypool@1.1.1: {} @@ -46569,8 +46563,6 @@ snapshots: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 - toml@3.0.0: {} - tooltip.js@1.3.3: dependencies: popper.js: 1.16.1 @@ -46656,26 +46648,6 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.4.9(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)))(typescript@5.9.3): - dependencies: - bs-logger: 0.2.6 - fast-json-stable-stringify: 2.1.0 - handlebars: 4.7.9 - jest: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)) - json5: 2.2.3 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - semver: 7.7.4 - type-fest: 4.41.0 - typescript: 5.9.3 - yargs-parser: 21.1.1 - optionalDependencies: - '@babel/core': 7.29.0 - '@jest/transform': 30.3.0 - '@jest/types': 30.3.0 - babel-jest: 29.7.0(@babel/core@7.29.0) - jest-util: 30.3.0 - ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -46716,6 +46688,7 @@ snapshots: yn: 3.1.1 optionalDependencies: '@swc/core': 1.15.21(@swc/helpers@0.5.21) + optional: true tsc-alias@1.8.17: dependencies: @@ -46834,8 +46807,6 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typedarray-to-buffer@1.0.4: {} - typedarray-to-buffer@3.1.5: dependencies: is-typedarray: 1.0.0 @@ -46884,6 +46855,8 @@ snapshots: dependencies: random-bytes: 1.0.0 + unbash@3.0.0: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -47129,7 +47102,8 @@ snapshots: uuid@9.0.1: {} - v8-compile-cache-lib@3.0.1: {} + v8-compile-cache-lib@3.0.1: + optional: true v8-compile-cache@2.4.0: {} @@ -47627,11 +47601,6 @@ snapshots: vm-browserify@1.1.2: {} - vue-template-compiler@2.7.16: - dependencies: - de-indent: 1.0.2 - he: 1.2.0 - w3c-hr-time@1.0.2: dependencies: browser-process-hrtime: 1.0.0 @@ -47671,6 +47640,8 @@ snapshots: matcher-collection: 2.0.1 minimatch: 3.1.5 + walk-up-path@4.0.0: {} + walker@1.0.8: dependencies: makeerror: 1.0.12 @@ -48041,19 +48012,6 @@ snapshots: xregexp@2.0.0: {} - xtend@2.0.6: - dependencies: - is-object: 0.1.2 - object-keys: 0.2.0 - - xtend@2.1.2: - dependencies: - object-keys: 0.4.0 - - xtend@2.2.0: {} - - xtend@3.0.0: {} - xtend@4.0.2: {} y18n@4.0.3: {} @@ -48111,7 +48069,8 @@ snapshots: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 - yn@3.1.1: {} + yn@3.1.1: + optional: true yocto-queue@0.1.0: {}