mirror of https://github.com/buster-so/buster.git
Compare commits
6 Commits
d95155a8e2
...
d443c1c333
Author | SHA1 | Date |
---|---|---|
|
d443c1c333 | |
|
fda1878f09 | |
|
a01ee7fa70 | |
|
dbc85f9a42 | |
|
04287fe4c4 | |
|
b5076b3079 |
|
@ -16,7 +16,6 @@ const app = new Hono().get(
|
|||
zValidator('query', GetChatScreenshotQuerySchema),
|
||||
async (c) => {
|
||||
const chatId = c.req.valid('param').id;
|
||||
const search = c.req.valid('query');
|
||||
const user = c.get('busterUser');
|
||||
|
||||
const chat = await getChatById(chatId);
|
||||
|
@ -40,16 +39,15 @@ const app = new Hono().get(
|
|||
}
|
||||
|
||||
try {
|
||||
const type = 'png' as const;
|
||||
const screenshotBuffer = await getChatScreenshot({
|
||||
chatId,
|
||||
width: search.width,
|
||||
height: search.height,
|
||||
type: search.type,
|
||||
accessToken: c.get('accessToken'),
|
||||
organizationId: chat.organizationId,
|
||||
type,
|
||||
});
|
||||
|
||||
return createImageResponse(screenshotBuffer, search.type);
|
||||
return createImageResponse(screenshotBuffer, type);
|
||||
} catch (error) {
|
||||
console.error('Failed to generate chat screenshot URL', {
|
||||
chatId,
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
getCollectionsAssociatedWithDashboard,
|
||||
getDashboardById,
|
||||
getOrganizationMemberCount,
|
||||
getUserOrganizationId,
|
||||
getUsersWithAssetPermissions,
|
||||
} from '@buster/database/queries';
|
||||
import {
|
||||
|
@ -54,6 +55,27 @@ const app = new Hono().get(
|
|||
c
|
||||
);
|
||||
|
||||
const tag = `take-dashboard-screenshot-${id}`;
|
||||
if (
|
||||
await shouldTakeScreenshot({
|
||||
tag,
|
||||
key: screenshots_task_keys.take_dashboard_screenshot,
|
||||
context: c,
|
||||
})
|
||||
) {
|
||||
console.log('Taking dashboard screenshot');
|
||||
tasks.trigger(
|
||||
screenshots_task_keys.take_dashboard_screenshot,
|
||||
{
|
||||
dashboardId: id,
|
||||
organizationId: (await getUserOrganizationId(user.id))?.organizationId || '',
|
||||
accessToken: c.get('accessToken'),
|
||||
isOnSaveEvent: false,
|
||||
} satisfies TakeDashboardScreenshotTrigger,
|
||||
{ tags: [tag] }
|
||||
);
|
||||
}
|
||||
|
||||
return c.json(response);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -43,14 +43,16 @@ const app = new Hono()
|
|||
}
|
||||
|
||||
try {
|
||||
const type = 'png' as const;
|
||||
const screenshotBuffer = await getDashboardScreenshot({
|
||||
...search,
|
||||
dashboardId,
|
||||
accessToken: c.get('accessToken'),
|
||||
organizationId: dashboard.organizationId,
|
||||
type,
|
||||
});
|
||||
|
||||
return createImageResponse(screenshotBuffer, search.type);
|
||||
return createImageResponse(screenshotBuffer, type);
|
||||
} catch (error) {
|
||||
console.error('Failed to generate chat screenshot URL', {
|
||||
dashboardId,
|
||||
|
|
|
@ -18,7 +18,7 @@ const app = new Hono()
|
|||
zValidator('query', GetMetricScreenshotQuerySchema),
|
||||
async (c) => {
|
||||
const metricId = c.req.valid('param').id;
|
||||
const { version_number, width, height, type } = c.req.valid('query');
|
||||
const { version_number } = c.req.valid('query');
|
||||
const user = c.get('busterUser');
|
||||
|
||||
const metric = await getMetricFileById(metricId);
|
||||
|
@ -42,14 +42,13 @@ const app = new Hono()
|
|||
}
|
||||
|
||||
try {
|
||||
const type = 'png' as const;
|
||||
const screenshotBuffer = await getMetricScreenshot({
|
||||
metricId,
|
||||
width,
|
||||
height,
|
||||
version_number,
|
||||
type,
|
||||
accessToken: c.get('accessToken'),
|
||||
organizationId: metric.organizationId,
|
||||
type,
|
||||
});
|
||||
|
||||
return createImageResponse(screenshotBuffer, type);
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
import { Hono } from 'hono';
|
||||
import GET from './GET';
|
||||
import PUT from './PUT';
|
||||
import SCREENSHOT from './screenshot';
|
||||
import SHARING from './sharing';
|
||||
|
||||
const app = new Hono()
|
||||
.route('/', GET)
|
||||
.route('/', PUT)
|
||||
.route('/sharing', SHARING)
|
||||
.route('/screenshot', SCREENSHOT);
|
||||
const app = new Hono().route('/', GET).route('/', PUT).route('/sharing', SHARING);
|
||||
|
||||
export default app;
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import { getAssetScreenshotBucketKey, getReportFileById } from '@buster/database/queries';
|
||||
import { getAssetScreenshotSignedUrl } from '@buster/search';
|
||||
import {
|
||||
GetReportScreenshotParamsSchema,
|
||||
type GetScreenshotResponse,
|
||||
} from '@buster/server-shared/screenshots';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
|
||||
const app = new Hono().get('/', zValidator('param', GetReportScreenshotParamsSchema), async (c) => {
|
||||
const reportId = c.req.valid('param').id;
|
||||
const user = c.get('busterUser');
|
||||
|
||||
const report = await getReportFileById({ reportId, userId: user.id });
|
||||
|
||||
if (!report) {
|
||||
throw new HTTPException(404, { message: 'Report not found' });
|
||||
}
|
||||
|
||||
const existingKey = await getAssetScreenshotBucketKey({
|
||||
assetType: 'report_file',
|
||||
assetId: reportId,
|
||||
});
|
||||
|
||||
if (!existingKey) {
|
||||
const result: GetScreenshotResponse = {
|
||||
success: false,
|
||||
error: 'Screenshot not found',
|
||||
};
|
||||
return c.json(result);
|
||||
}
|
||||
|
||||
const permission = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: reportId,
|
||||
assetType: 'report_file',
|
||||
requiredRole: 'can_view',
|
||||
workspaceSharing: report.workspace_sharing,
|
||||
organizationId: report.organization_id,
|
||||
});
|
||||
|
||||
if (!permission.hasAccess) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to view this report',
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const signedUrl = await getAssetScreenshotSignedUrl({
|
||||
key: existingKey,
|
||||
organizationId: report.organization_id,
|
||||
});
|
||||
|
||||
const result: GetScreenshotResponse = {
|
||||
success: true,
|
||||
url: signedUrl,
|
||||
};
|
||||
return c.json(result);
|
||||
} catch (error) {
|
||||
console.error('Failed to generate report screenshot URL', {
|
||||
reportId,
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
||||
const response: GetScreenshotResponse = {
|
||||
success: false,
|
||||
error: 'Failed to generate screenshot URL',
|
||||
};
|
||||
return c.json(response);
|
||||
});
|
||||
|
||||
export default app;
|
|
@ -1,6 +0,0 @@
|
|||
import { Hono } from 'hono';
|
||||
import GET from './GET';
|
||||
|
||||
const app = new Hono().route('/', GET);
|
||||
|
||||
export default app;
|
|
@ -6,7 +6,7 @@ import type { Context } from 'hono';
|
|||
// It checks if a job for the given tag and key is already running or queued before starting a new one.
|
||||
|
||||
const currentlyCheckingTags = new Set<string>();
|
||||
const CACHE_TAG_EXPIRATION_TIME = 1000 * 15; // 15 seconds
|
||||
const CACHE_TAG_EXPIRATION_TIME = 1000 * 30; // 30 seconds
|
||||
|
||||
export const shouldTakeScreenshot = async ({
|
||||
tag,
|
||||
|
|
|
@ -53,10 +53,12 @@ const shouldTakeChatScreenshot = async (
|
|||
return true;
|
||||
}
|
||||
|
||||
const isScreenshotExpired = await hasChatScreenshotBeenTakenWithin(
|
||||
const hasRecentScreenshot = await hasChatScreenshotBeenTakenWithin(
|
||||
args.chatId,
|
||||
dayjs().subtract(4, 'weeks')
|
||||
);
|
||||
|
||||
return !isScreenshotExpired;
|
||||
logger.info('Has recent screenshot', { hasRecentScreenshot });
|
||||
|
||||
return !hasRecentScreenshot;
|
||||
};
|
||||
|
|
|
@ -25,6 +25,8 @@ export const takeDashboardScreenshotHandlerTask: ReturnType<
|
|||
isOnSaveEvent,
|
||||
});
|
||||
|
||||
logger.info('Should take new screenshot', { shouldTakeNewScreenshot });
|
||||
|
||||
if (!shouldTakeNewScreenshot) {
|
||||
return;
|
||||
}
|
||||
|
@ -54,10 +56,12 @@ const shouldTakenNewScreenshot = async ({
|
|||
return true;
|
||||
}
|
||||
|
||||
const isScreenshotExpired = await hasDashboardScreenshotBeenTakenWithin(
|
||||
const hasRecentScreenshot = await hasDashboardScreenshotBeenTakenWithin(
|
||||
dashboardId,
|
||||
dayjs().subtract(24, 'hours')
|
||||
);
|
||||
|
||||
return !isScreenshotExpired;
|
||||
logger.info('Is screenshot expired', { hasRecentScreenshot });
|
||||
|
||||
return !hasRecentScreenshot;
|
||||
};
|
||||
|
|
|
@ -15,7 +15,7 @@ export const takeMetricScreenshotHandlerTask: ReturnType<
|
|||
> = schemaTask({
|
||||
id: screenshots_task_keys.take_metric_screenshot,
|
||||
schema: TakeMetricScreenshotTriggerSchema,
|
||||
maxDuration: 60 * 3, // 3 minutes max
|
||||
maxDuration: 60 * 2, // 2 minutes max
|
||||
retry: {
|
||||
maxAttempts: 1,
|
||||
minTimeoutInMs: 1000, // 1 second
|
||||
|
|
|
@ -46,10 +46,10 @@ export const takeReportScreenshotHandlerTask: ReturnType<
|
|||
});
|
||||
|
||||
const shouldTakenNewScreenshot = async ({ reportId }: { reportId: string }) => {
|
||||
const isScreenshotExpired = await hasReportScreenshotBeenTakenWithin(
|
||||
const hasRecentScreenshot = await hasReportScreenshotBeenTakenWithin(
|
||||
reportId,
|
||||
dayjs().subtract(24, 'hours')
|
||||
);
|
||||
|
||||
return !isScreenshotExpired;
|
||||
return !hasRecentScreenshot;
|
||||
};
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import type { DashboardConfig, GetDashboardResponse } from '@buster/server-shared/dashboards';
|
||||
import type {
|
||||
DashboardConfig,
|
||||
GetDashboardParams,
|
||||
GetDashboardQuery,
|
||||
GetDashboardResponse,
|
||||
} from '@buster/server-shared/dashboards';
|
||||
import type {
|
||||
ShareDeleteRequest,
|
||||
ShareDeleteResponse,
|
||||
|
@ -26,15 +31,8 @@ export const getDashboardById = async ({
|
|||
id,
|
||||
password,
|
||||
version_number,
|
||||
}: {
|
||||
/** The unique identifier of the dashboard */
|
||||
id: string;
|
||||
/** Optional password for accessing protected dashboards */
|
||||
password?: string;
|
||||
/** The version number of the dashboard */
|
||||
version_number?: number;
|
||||
}) => {
|
||||
return await mainApi
|
||||
}: GetDashboardParams & GetDashboardQuery) => {
|
||||
return await mainApiV2
|
||||
.get<GetDashboardResponse>(`/dashboards/${id}`, {
|
||||
params: { password, version_number },
|
||||
})
|
||||
|
|
|
@ -45,6 +45,11 @@ export const GlobalSearchModalBase = ({
|
|||
const navigate = useNavigate();
|
||||
const [viewedItem, setViewedItem] = useState<SearchTextData | null>(null);
|
||||
|
||||
const resetModal = () => {
|
||||
setViewedItem(null);
|
||||
onChangeValue('');
|
||||
};
|
||||
|
||||
const searchItems: SearchItems[] = useMemo(() => {
|
||||
const makeItem = (item: SearchTextData, makeSecondary?: boolean): SearchItem => {
|
||||
const Icon = assetTypeToIcon(item.assetType);
|
||||
|
@ -68,6 +73,9 @@ export const GlobalSearchModalBase = ({
|
|||
}) as Parameters<typeof navigate>[0];
|
||||
await navigate(link);
|
||||
onClose();
|
||||
setTimeout(() => {
|
||||
resetModal();
|
||||
}, 200);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -28,8 +28,8 @@ const editorVariants = cva(
|
|||
},
|
||||
variant: {
|
||||
comment: cn('rounded-none border-none bg-transparent text-sm'),
|
||||
default: 'px-16 pt-4 pb-72 text-base sm:px-[max(64px,calc(50%-350px))]',
|
||||
fullWidth: 'px-16 pt-4 pb-72 text-base sm:px-24',
|
||||
default: 'px-16 pt-4 pb-72 text-base',
|
||||
fullWidth: 'pt-4 pb-72 text-base px-24',
|
||||
none: '',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -55,7 +55,11 @@ export function ReportEditorSkeleton({
|
|||
}: ReportEditorSkeletonProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn('mx-auto mt-8 w-full space-y-6 sm:px-[max(64px,calc(50%-350px))]', className)}
|
||||
className={cn(
|
||||
'mx-auto mt-8 w-full space-y-6',
|
||||
'sm:px-[max(64px,calc(50%-350px))] px-[max(24px,calc(50%-350px))]',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* Toolbar skeleton */}
|
||||
{showToolbar && (
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import { PlateStatic, type PlateStaticProps } from 'platejs';
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export const editorVariants = cva(
|
||||
cn(
|
||||
'group/editor',
|
||||
'relative w-full cursor-text overflow-x-hidden break-words whitespace-pre-wrap select-text',
|
||||
'rounded ring-offset-background focus-visible:outline-none',
|
||||
'placeholder:text-muted-foreground/80 **:data-slate-placeholder:top-[auto_!important] **:data-slate-placeholder:text-muted-foreground/80 **:data-slate-placeholder:opacity-100!',
|
||||
'[&_strong]:font-semibold'
|
||||
),
|
||||
{
|
||||
defaultVariants: {
|
||||
variant: 'none',
|
||||
},
|
||||
variants: {
|
||||
disabled: {
|
||||
true: 'cursor-not-allowed opacity-50',
|
||||
},
|
||||
focused: {
|
||||
true: 'ring-2 ring-ring ring-offset-2',
|
||||
},
|
||||
variant: {
|
||||
ai: 'w-full px-0 text-base md:text-sm',
|
||||
aiChat:
|
||||
'max-h-[min(70vh,320px)] w-full max-w-[700px] overflow-y-auto px-5 py-3 text-base md:text-sm',
|
||||
default: 'size-full px-16 pt-4 pb-72 text-base sm:px-[max(64px,calc(50%-350px))]',
|
||||
demo: 'size-full px-16 pt-4 pb-72 text-base sm:px-[max(64px,calc(50%-350px))]',
|
||||
fullWidth: 'size-full px-16 pt-4 pb-72 text-base sm:px-24',
|
||||
none: '',
|
||||
select: 'px-3 py-2 text-base data-readonly:w-fit',
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export function EditorStatic({
|
||||
className,
|
||||
variant,
|
||||
...props
|
||||
}: PlateStaticProps & VariantProps<typeof editorVariants>) {
|
||||
return <PlateStatic className={cn(editorVariants({ variant }), className)} {...props} />;
|
||||
}
|
|
@ -16,7 +16,7 @@ import { useGetCurrentMessageId, useIsStreamingMessage } from '../../context/Cha
|
|||
import { GeneratingContent } from './GeneratingContent';
|
||||
import { ReportPageHeader } from './ReportPageHeader';
|
||||
|
||||
const commonClassName = 'sm:px-[max(64px,calc(50%-350px))]';
|
||||
const commonClassName = 'sm:px-[max(64px,calc(50%-350px))] px-[max(24px,calc(50%-350px))]';
|
||||
|
||||
export const ReportPageController: React.FC<{
|
||||
reportId: string;
|
||||
|
@ -125,7 +125,7 @@ export const ReportPageController: React.FC<{
|
|||
}
|
||||
/>
|
||||
) : (
|
||||
<ReportEditorSkeleton />
|
||||
<ReportEditorSkeleton className={commonClassName} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -18,7 +18,7 @@ export const Route = createFileRoute('/screenshots')({
|
|||
head: ({ loaderData }) => ({
|
||||
styles: [
|
||||
{
|
||||
children: `body { background: ${loaderData?.backgroundColor || '#ffffff'}; }`,
|
||||
children: `body { background: ${loaderData?.backgroundColor || '#ffffff'}; min-width: auto; }`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
|
|
@ -136,6 +136,7 @@
|
|||
"@buster/vitest-config": "workspace:*",
|
||||
"@supabase/supabase-js": "catalog:",
|
||||
"playwright": "^1.55.1",
|
||||
"sharp": "^0.34.4",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import type { User } from '@supabase/supabase-js';
|
||||
import type { Browser, Page } from 'playwright';
|
||||
import { z } from 'zod';
|
||||
import { getSupabaseCookieKey, getSupabaseUser } from '../../supabase/server';
|
||||
|
@ -8,12 +7,25 @@ type BrowserParamsBase<T> = {
|
|||
width?: number | undefined;
|
||||
height?: number | undefined;
|
||||
fullPath: string;
|
||||
callback: ({ page, browser }: { page: Page; browser: Browser }) => Promise<T>;
|
||||
callback: ({
|
||||
page,
|
||||
browser,
|
||||
type,
|
||||
}: { page: Page; browser: Browser; type: 'png' | 'webp' }) => Promise<T>;
|
||||
};
|
||||
|
||||
export const BrowserParamsContextSchema = z.object({
|
||||
accessToken: z.string(),
|
||||
organizationId: z.string(),
|
||||
width: z.number().min(100).max(3840).default(DEFAULT_SCREENSHOT_CONFIG.width).optional(),
|
||||
height: z.number().min(100).max(7000).default(DEFAULT_SCREENSHOT_CONFIG.height).optional(),
|
||||
deviceScaleFactor: z
|
||||
.number()
|
||||
.min(1)
|
||||
.max(4)
|
||||
.default(DEFAULT_SCREENSHOT_CONFIG.deviceScaleFactor)
|
||||
.optional(),
|
||||
type: z.enum(['png', 'webp']).default(DEFAULT_SCREENSHOT_CONFIG.type).optional(),
|
||||
});
|
||||
|
||||
export type BrowserParamsContext = z.infer<typeof BrowserParamsContextSchema>;
|
||||
|
@ -27,6 +39,8 @@ export const browserLogin = async <T = Buffer<ArrayBufferLike>>({
|
|||
fullPath,
|
||||
callback,
|
||||
accessToken,
|
||||
deviceScaleFactor,
|
||||
type,
|
||||
}: BrowserParams<T>) => {
|
||||
if (!accessToken) {
|
||||
throw new Error('Missing Authorization header');
|
||||
|
@ -62,6 +76,7 @@ export const browserLogin = async <T = Buffer<ArrayBufferLike>>({
|
|||
try {
|
||||
const context = await browser.newContext({
|
||||
viewport: { width, height },
|
||||
deviceScaleFactor: deviceScaleFactor || DEFAULT_SCREENSHOT_CONFIG.deviceScaleFactor, // High-DPI rendering for better quality screenshots
|
||||
});
|
||||
|
||||
// Format cookie value as Supabase expects: base64-<encoded_session>
|
||||
|
@ -93,7 +108,7 @@ export const browserLogin = async <T = Buffer<ArrayBufferLike>>({
|
|||
|
||||
await page.goto(fullPath, { waitUntil: 'networkidle' });
|
||||
|
||||
const result = await callback({ page, browser });
|
||||
const result = await callback({ page, browser, type: type || DEFAULT_SCREENSHOT_CONFIG.type });
|
||||
|
||||
if (pageError) {
|
||||
throw pageError;
|
||||
|
|
|
@ -1,32 +1,24 @@
|
|||
import { z } from 'zod';
|
||||
import { BrowserParamsContextSchema, browserLogin } from './browser-login';
|
||||
import { createHrefFromLink } from './create-href-from-link';
|
||||
import { DEFAULT_SCREENSHOT_CONFIG } from './screenshot-config';
|
||||
import { takeScreenshot } from './take-screenshot';
|
||||
|
||||
export const GetChatScreenshotHandlerArgsSchema = z
|
||||
.object({
|
||||
chatId: z.string().uuid('Chat ID must be a valid UUID'),
|
||||
width: z.number().default(DEFAULT_SCREENSHOT_CONFIG.width).optional(),
|
||||
height: z.number().default(DEFAULT_SCREENSHOT_CONFIG.height).optional(),
|
||||
type: z.enum(['png', 'jpeg']).default(DEFAULT_SCREENSHOT_CONFIG.type).optional(),
|
||||
})
|
||||
.extend(BrowserParamsContextSchema.shape);
|
||||
|
||||
export type GetChatScreenshotHandlerArgs = z.infer<typeof GetChatScreenshotHandlerArgsSchema>;
|
||||
|
||||
export const getChatScreenshot = async (args: GetChatScreenshotHandlerArgs) => {
|
||||
const { type = DEFAULT_SCREENSHOT_CONFIG.type } = args;
|
||||
|
||||
const { result: screenshotBuffer } = await browserLogin({
|
||||
...args,
|
||||
fullPath: createHrefFromLink({
|
||||
to: '/screenshots/chats/$chatId/content' as const,
|
||||
params: { chatId: args.chatId },
|
||||
}),
|
||||
callback: async ({ page }) => {
|
||||
const screenshotBuffer = await page.screenshot({ type });
|
||||
return screenshotBuffer;
|
||||
},
|
||||
callback: takeScreenshot,
|
||||
});
|
||||
|
||||
return screenshotBuffer;
|
||||
|
|
|
@ -2,14 +2,12 @@ import { z } from 'zod';
|
|||
import { BrowserParamsContextSchema, browserLogin } from './browser-login';
|
||||
import { createHrefFromLink } from './create-href-from-link';
|
||||
import { DEFAULT_SCREENSHOT_CONFIG } from './screenshot-config';
|
||||
import { takeScreenshot } from './take-screenshot';
|
||||
|
||||
export const GetDashboardScreenshotHandlerArgsSchema = z
|
||||
.object({
|
||||
dashboardId: z.string().uuid('Dashboard ID must be a valid UUID'),
|
||||
width: z.number().default(DEFAULT_SCREENSHOT_CONFIG.width).optional(),
|
||||
height: z.number().default(DEFAULT_SCREENSHOT_CONFIG.height).optional(),
|
||||
version_number: z.number().optional(),
|
||||
type: z.enum(['png', 'jpeg']).default(DEFAULT_SCREENSHOT_CONFIG.type).optional(),
|
||||
})
|
||||
.extend(BrowserParamsContextSchema.shape);
|
||||
|
||||
|
@ -18,8 +16,6 @@ export type GetDashboardScreenshotHandlerArgs = z.infer<
|
|||
>;
|
||||
|
||||
export const getDashboardScreenshot = async (args: GetDashboardScreenshotHandlerArgs) => {
|
||||
const { type = DEFAULT_SCREENSHOT_CONFIG.type } = args;
|
||||
|
||||
const { result: screenshotBuffer } = await browserLogin({
|
||||
...args,
|
||||
fullPath: createHrefFromLink({
|
||||
|
@ -29,10 +25,7 @@ export const getDashboardScreenshot = async (args: GetDashboardScreenshotHandler
|
|||
version_number: args.version_number,
|
||||
},
|
||||
}),
|
||||
callback: async ({ page }) => {
|
||||
const screenshotBuffer = await page.screenshot({ type });
|
||||
return screenshotBuffer;
|
||||
},
|
||||
callback: takeScreenshot,
|
||||
});
|
||||
|
||||
return screenshotBuffer;
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
import { z } from 'zod';
|
||||
import { BrowserParamsContextSchema, browserLogin } from './browser-login';
|
||||
import { createHrefFromLink } from './create-href-from-link';
|
||||
import { DEFAULT_SCREENSHOT_CONFIG } from './screenshot-config';
|
||||
import { takeScreenshot } from './take-screenshot';
|
||||
|
||||
export const GetMetricScreenshotHandlerArgsSchema = z
|
||||
.object({
|
||||
metricId: z.string().uuid('Metric ID must be a valid UUID'),
|
||||
width: z.number().default(DEFAULT_SCREENSHOT_CONFIG.width).optional(),
|
||||
height: z.number().default(DEFAULT_SCREENSHOT_CONFIG.height).optional(),
|
||||
version_number: z.number().optional(),
|
||||
type: z.enum(['png', 'jpeg']).default(DEFAULT_SCREENSHOT_CONFIG.type).optional(),
|
||||
})
|
||||
.extend(BrowserParamsContextSchema.shape);
|
||||
|
||||
|
@ -18,8 +15,6 @@ export type GetMetricScreenshotHandlerArgs = z.infer<typeof GetMetricScreenshotH
|
|||
export const getMetricScreenshot = async (
|
||||
args: GetMetricScreenshotHandlerArgs
|
||||
): Promise<Buffer<ArrayBufferLike>> => {
|
||||
const { type = DEFAULT_SCREENSHOT_CONFIG.type } = args;
|
||||
|
||||
const { result: screenshotBuffer } = await browserLogin({
|
||||
...args,
|
||||
fullPath: createHrefFromLink({
|
||||
|
@ -29,10 +24,7 @@ export const getMetricScreenshot = async (
|
|||
version_number: args.version_number,
|
||||
},
|
||||
}),
|
||||
callback: async ({ page }) => {
|
||||
const screenshotBuffer = await page.screenshot({ type });
|
||||
return screenshotBuffer;
|
||||
},
|
||||
callback: takeScreenshot,
|
||||
});
|
||||
|
||||
return screenshotBuffer;
|
||||
|
|
|
@ -1,23 +1,18 @@
|
|||
import { z } from 'zod';
|
||||
import { BrowserParamsContextSchema, browserLogin } from './browser-login';
|
||||
import { createHrefFromLink } from './create-href-from-link';
|
||||
import { DEFAULT_SCREENSHOT_CONFIG } from './screenshot-config';
|
||||
import { takeScreenshot } from './take-screenshot';
|
||||
|
||||
export const GetReportScreenshotHandlerArgsSchema = z
|
||||
.object({
|
||||
reportId: z.string().uuid('Report ID must be a valid UUID'),
|
||||
width: z.number().default(DEFAULT_SCREENSHOT_CONFIG.width).optional(),
|
||||
height: z.number().default(DEFAULT_SCREENSHOT_CONFIG.height).optional(),
|
||||
version_number: z.number().optional(),
|
||||
type: z.enum(['png', 'jpeg']).default(DEFAULT_SCREENSHOT_CONFIG.type).optional(),
|
||||
})
|
||||
.extend(BrowserParamsContextSchema.shape);
|
||||
|
||||
export type GetReportScreenshotHandlerArgs = z.infer<typeof GetReportScreenshotHandlerArgsSchema>;
|
||||
|
||||
export const getReportScreenshot = async (args: GetReportScreenshotHandlerArgs) => {
|
||||
const { type = DEFAULT_SCREENSHOT_CONFIG.type } = args;
|
||||
|
||||
const { result: screenshotBuffer } = await browserLogin({
|
||||
...args,
|
||||
fullPath: createHrefFromLink({
|
||||
|
@ -27,10 +22,7 @@ export const getReportScreenshot = async (args: GetReportScreenshotHandlerArgs)
|
|||
version_number: args.version_number,
|
||||
},
|
||||
}),
|
||||
callback: async ({ page }) => {
|
||||
const screenshotBuffer = await page.screenshot({ type });
|
||||
return screenshotBuffer;
|
||||
},
|
||||
callback: takeScreenshot,
|
||||
});
|
||||
|
||||
return screenshotBuffer;
|
||||
|
|
|
@ -3,5 +3,6 @@ const multiplier = 2;
|
|||
export const DEFAULT_SCREENSHOT_CONFIG = {
|
||||
width: 400 * multiplier,
|
||||
height: 240 * multiplier,
|
||||
type: 'png' as const,
|
||||
type: 'webp' as const,
|
||||
deviceScaleFactor: 1.62,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import type { Page } from 'playwright';
|
||||
import sharp from 'sharp';
|
||||
|
||||
export const takeScreenshot = async ({ page, type }: { page: Page; type: 'png' | 'webp' }) => {
|
||||
const screenshotBuffer = await page.screenshot({ type: 'png' });
|
||||
if (type === 'png') {
|
||||
return await sharp(screenshotBuffer)
|
||||
.png({
|
||||
compressionLevel: 2,
|
||||
quality: 100,
|
||||
})
|
||||
.toBuffer();
|
||||
}
|
||||
|
||||
const compressed = await sharp(screenshotBuffer)
|
||||
.webp({ nearLossless: true }) // Much smaller than PNG with same quality
|
||||
.toBuffer();
|
||||
return compressed;
|
||||
};
|
|
@ -15,12 +15,6 @@ export const GetChatScreenshotParamsSchema = z.object({
|
|||
|
||||
export type GetChatScreenshotParams = z.infer<typeof GetChatScreenshotParamsSchema>;
|
||||
|
||||
export const GetChatScreenshotQuerySchema = z
|
||||
.object({
|
||||
width: z.coerce.number().min(600).max(3840).default(600),
|
||||
height: z.coerce.number().min(300).max(2160).default(338),
|
||||
type: z.enum(['png', 'jpeg']).default('png'),
|
||||
})
|
||||
.merge(BaseScreenshotSearchSchema);
|
||||
export const GetChatScreenshotQuerySchema = z.object({}).merge(BaseScreenshotSearchSchema);
|
||||
|
||||
export type GetChatScreenshotQuery = z.infer<typeof GetChatScreenshotQuerySchema>;
|
||||
|
|
|
@ -17,9 +17,6 @@ export type GetDashboardScreenshotParams = z.infer<typeof GetDashboardScreenshot
|
|||
|
||||
export const GetDashboardScreenshotQuerySchema = z
|
||||
.object({
|
||||
width: z.coerce.number().min(100).max(3840).default(800),
|
||||
height: z.coerce.number().min(100).max(4160).default(450),
|
||||
type: z.enum(['png', 'jpeg']).default('png'),
|
||||
version_number: z.coerce.number().optional(),
|
||||
})
|
||||
.merge(BaseScreenshotSearchSchema);
|
||||
|
|
|
@ -8,9 +8,6 @@ export const GetMetricScreenshotParamsSchema = z.object({
|
|||
export const GetMetricScreenshotQuerySchema = z
|
||||
.object({
|
||||
version_number: z.coerce.number().min(1).optional(),
|
||||
width: z.coerce.number().min(100).max(3840).default(800),
|
||||
height: z.coerce.number().min(100).max(2160).default(450),
|
||||
type: z.enum(['png', 'jpeg']).default('png').optional(),
|
||||
})
|
||||
.merge(BaseScreenshotSearchSchema);
|
||||
|
||||
|
|
|
@ -17,9 +17,6 @@ export type GetReportScreenshotParams = z.infer<typeof GetReportScreenshotParams
|
|||
|
||||
export const GetReportScreenshotQuerySchema = z
|
||||
.object({
|
||||
width: z.coerce.number().min(100).max(3840).default(800),
|
||||
height: z.coerce.number().min(100).max(2160).default(450),
|
||||
type: z.enum(['png', 'jpeg']).default('png'),
|
||||
version_number: z.coerce.number().optional(),
|
||||
})
|
||||
.merge(BaseScreenshotSearchSchema);
|
||||
|
|
317
pnpm-lock.yaml
317
pnpm-lock.yaml
|
@ -1359,6 +1359,9 @@ importers:
|
|||
playwright:
|
||||
specifier: ^1.55.1
|
||||
version: 1.55.1
|
||||
sharp:
|
||||
specifier: ^0.34.4
|
||||
version: 0.34.4
|
||||
zod:
|
||||
specifier: 'catalog:'
|
||||
version: 3.25.76
|
||||
|
@ -2394,6 +2397,9 @@ packages:
|
|||
react:
|
||||
optional: true
|
||||
|
||||
'@emnapi/runtime@1.5.0':
|
||||
resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==}
|
||||
|
||||
'@emoji-mart/data@1.2.1':
|
||||
resolution: {integrity: sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==}
|
||||
|
||||
|
@ -3124,6 +3130,146 @@ packages:
|
|||
'@iarna/toml@2.2.5':
|
||||
resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==}
|
||||
|
||||
'@img/colour@1.0.0':
|
||||
resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@img/sharp-darwin-arm64@0.34.4':
|
||||
resolution: {integrity: sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-darwin-x64@0.34.4':
|
||||
resolution: {integrity: sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-libvips-darwin-arm64@1.2.3':
|
||||
resolution: {integrity: sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-libvips-darwin-x64@1.2.3':
|
||||
resolution: {integrity: sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-libvips-linux-arm64@1.2.3':
|
||||
resolution: {integrity: sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.2.3':
|
||||
resolution: {integrity: sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-ppc64@1.2.3':
|
||||
resolution: {integrity: sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.2.3':
|
||||
resolution: {integrity: sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.2.3':
|
||||
resolution: {integrity: sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.3':
|
||||
resolution: {integrity: sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.2.3':
|
||||
resolution: {integrity: sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linux-arm64@0.34.4':
|
||||
resolution: {integrity: sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-arm@0.34.4':
|
||||
resolution: {integrity: sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-ppc64@0.34.4':
|
||||
resolution: {integrity: sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-s390x@0.34.4':
|
||||
resolution: {integrity: sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-x64@0.34.4':
|
||||
resolution: {integrity: sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.34.4':
|
||||
resolution: {integrity: sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.34.4':
|
||||
resolution: {integrity: sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-wasm32@0.34.4':
|
||||
resolution: {integrity: sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [wasm32]
|
||||
|
||||
'@img/sharp-win32-arm64@0.34.4':
|
||||
resolution: {integrity: sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@img/sharp-win32-ia32@0.34.4':
|
||||
resolution: {integrity: sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@img/sharp-win32-x64@0.34.4':
|
||||
resolution: {integrity: sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@inquirer/ansi@1.0.0':
|
||||
resolution: {integrity: sha512-JWaTfCxI1eTmJ1BIv86vUfjVatOdxwD0DAVKYevY8SazeUUZtW+tNbsdejVO1GYE0GXJW1N1ahmiC3TFd+7wZA==}
|
||||
engines: {node: '>=18'}
|
||||
|
@ -8012,6 +8158,10 @@ packages:
|
|||
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
detect-node-es@1.1.0:
|
||||
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||
|
||||
|
@ -11752,6 +11902,10 @@ packages:
|
|||
resolution: {integrity: sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
sharp@0.34.4:
|
||||
resolution: {integrity: sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -15099,6 +15253,11 @@ snapshots:
|
|||
optionalDependencies:
|
||||
react: 19.1.1
|
||||
|
||||
'@emnapi/runtime@1.5.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emoji-mart/data@1.2.1': {}
|
||||
|
||||
'@epic-web/invariant@1.0.0': {}
|
||||
|
@ -15568,6 +15727,94 @@ snapshots:
|
|||
|
||||
'@iarna/toml@2.2.5': {}
|
||||
|
||||
'@img/colour@1.0.0': {}
|
||||
|
||||
'@img/sharp-darwin-arm64@0.34.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-darwin-arm64': 1.2.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-darwin-x64@0.34.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-darwin-x64': 1.2.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-darwin-arm64@1.2.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-darwin-x64@1.2.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-arm64@1.2.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.2.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-ppc64@1.2.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.2.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.2.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.2.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-arm64@0.34.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-arm64': 1.2.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-arm@0.34.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-arm': 1.2.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-ppc64@0.34.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-ppc64': 1.2.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-s390x@0.34.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-s390x': 1.2.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-x64@0.34.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-x64': 1.2.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.34.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linuxmusl-arm64': 1.2.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.34.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linuxmusl-x64': 1.2.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-wasm32@0.34.4':
|
||||
dependencies:
|
||||
'@emnapi/runtime': 1.5.0
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-arm64@0.34.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-ia32@0.34.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-x64@0.34.4':
|
||||
optional: true
|
||||
|
||||
'@inquirer/ansi@1.0.0': {}
|
||||
|
||||
'@inquirer/checkbox@4.2.4(@types/node@24.3.1)':
|
||||
|
@ -19970,11 +20217,11 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitest/browser@3.2.4(msw@2.11.4(@types/node@22.18.1)(typescript@5.9.2))(playwright@1.55.1)(vite@7.1.4(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)':
|
||||
'@vitest/browser@3.2.4(msw@2.11.4(@types/node@22.18.1)(typescript@5.9.2))(playwright@1.55.1)(vite@7.1.9(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)':
|
||||
dependencies:
|
||||
'@testing-library/dom': 10.4.1
|
||||
'@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
|
||||
'@vitest/mocker': 3.2.4(msw@2.11.4(@types/node@22.18.1)(typescript@5.9.2))(vite@7.1.4(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))
|
||||
'@vitest/mocker': 3.2.4(msw@2.11.4(@types/node@22.18.1)(typescript@5.9.2))(vite@7.1.9(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))
|
||||
'@vitest/utils': 3.2.4
|
||||
magic-string: 0.30.17
|
||||
sirv: 3.0.1
|
||||
|
@ -20024,7 +20271,7 @@ snapshots:
|
|||
std-env: 3.9.0
|
||||
test-exclude: 7.0.1
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@24.3.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.11.4(@types/node@24.3.1)(typescript@5.9.2))(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
|
||||
vitest: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@24.3.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.1)(msw@2.11.4(@types/node@24.3.1)(typescript@5.9.2))(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
|
||||
optionalDependencies:
|
||||
'@vitest/browser': 3.2.4(msw@2.11.4(@types/node@24.3.1)(typescript@5.9.2))(playwright@1.55.1)(vite@7.1.4(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)
|
||||
transitivePeerDependencies:
|
||||
|
@ -20047,6 +20294,16 @@ snapshots:
|
|||
msw: 2.11.4(@types/node@22.18.1)(typescript@5.9.2)
|
||||
vite: 7.1.4(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
|
||||
|
||||
'@vitest/mocker@3.2.4(msw@2.11.4(@types/node@22.18.1)(typescript@5.9.2))(vite@7.1.9(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.2.4
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.17
|
||||
optionalDependencies:
|
||||
msw: 2.11.4(@types/node@22.18.1)(typescript@5.9.2)
|
||||
vite: 7.1.9(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
|
||||
optional: true
|
||||
|
||||
'@vitest/mocker@3.2.4(msw@2.11.4(@types/node@24.3.1)(typescript@5.9.2))(vite@7.1.4(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.2.4
|
||||
|
@ -20085,7 +20342,7 @@ snapshots:
|
|||
sirv: 3.0.1
|
||||
tinyglobby: 0.2.14
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@22.18.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.11.4(@types/node@22.18.1)(typescript@5.9.2))(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
|
||||
vitest: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@24.3.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.11.4(@types/node@24.3.1)(typescript@5.9.2))(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
|
||||
|
||||
'@vitest/utils@3.2.4':
|
||||
dependencies:
|
||||
|
@ -21288,6 +21545,8 @@ snapshots:
|
|||
|
||||
detect-libc@2.0.4: {}
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
detect-node-es@1.1.0: {}
|
||||
|
||||
devalue@5.3.2: {}
|
||||
|
@ -25990,6 +26249,35 @@ snapshots:
|
|||
lazy-cache: 0.2.7
|
||||
mixin-object: 2.0.1
|
||||
|
||||
sharp@0.34.4:
|
||||
dependencies:
|
||||
'@img/colour': 1.0.0
|
||||
detect-libc: 2.1.2
|
||||
semver: 7.7.2
|
||||
optionalDependencies:
|
||||
'@img/sharp-darwin-arm64': 0.34.4
|
||||
'@img/sharp-darwin-x64': 0.34.4
|
||||
'@img/sharp-libvips-darwin-arm64': 1.2.3
|
||||
'@img/sharp-libvips-darwin-x64': 1.2.3
|
||||
'@img/sharp-libvips-linux-arm': 1.2.3
|
||||
'@img/sharp-libvips-linux-arm64': 1.2.3
|
||||
'@img/sharp-libvips-linux-ppc64': 1.2.3
|
||||
'@img/sharp-libvips-linux-s390x': 1.2.3
|
||||
'@img/sharp-libvips-linux-x64': 1.2.3
|
||||
'@img/sharp-libvips-linuxmusl-arm64': 1.2.3
|
||||
'@img/sharp-libvips-linuxmusl-x64': 1.2.3
|
||||
'@img/sharp-linux-arm': 0.34.4
|
||||
'@img/sharp-linux-arm64': 0.34.4
|
||||
'@img/sharp-linux-ppc64': 0.34.4
|
||||
'@img/sharp-linux-s390x': 0.34.4
|
||||
'@img/sharp-linux-x64': 0.34.4
|
||||
'@img/sharp-linuxmusl-arm64': 0.34.4
|
||||
'@img/sharp-linuxmusl-x64': 0.34.4
|
||||
'@img/sharp-wasm32': 0.34.4
|
||||
'@img/sharp-win32-arm64': 0.34.4
|
||||
'@img/sharp-win32-ia32': 0.34.4
|
||||
'@img/sharp-win32-x64': 0.34.4
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
|
@ -27344,6 +27632,25 @@ snapshots:
|
|||
tsx: 4.20.5
|
||||
yaml: 2.8.1
|
||||
|
||||
vite@7.1.9(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1):
|
||||
dependencies:
|
||||
esbuild: 0.25.9
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
postcss: 8.5.6
|
||||
rollup: 4.50.0
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 22.18.1
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.6.1
|
||||
lightningcss: 1.30.1
|
||||
sass: 1.93.2
|
||||
terser: 5.43.1
|
||||
tsx: 4.20.5
|
||||
yaml: 2.8.1
|
||||
optional: true
|
||||
|
||||
vite@7.1.9(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1):
|
||||
dependencies:
|
||||
esbuild: 0.25.9
|
||||
|
@ -27405,7 +27712,7 @@ snapshots:
|
|||
'@edge-runtime/vm': 3.2.0
|
||||
'@types/debug': 4.1.12
|
||||
'@types/node': 22.18.1
|
||||
'@vitest/browser': 3.2.4(msw@2.11.4(@types/node@22.18.1)(typescript@5.9.2))(playwright@1.55.1)(vite@7.1.4(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)
|
||||
'@vitest/browser': 3.2.4(msw@2.11.4(@types/node@22.18.1)(typescript@5.9.2))(playwright@1.55.1)(vite@7.1.9(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)
|
||||
'@vitest/ui': 3.2.4(vitest@3.2.4)
|
||||
jsdom: 27.0.0(postcss@8.5.6)
|
||||
transitivePeerDependencies:
|
||||
|
|
Loading…
Reference in New Issue