fix: packages update + various perf fixes

This commit is contained in:
diced
2026-04-06 22:14:15 -07:00
parent 9b7759520c
commit 4c86b7fc38
19 changed files with 1331 additions and 1631 deletions

View File

@@ -101,7 +101,7 @@ export default defineConfig(
},
settings: {
react: { version: 'detect' },
react: { version: '19' },
},
},
);

View File

@@ -5,12 +5,12 @@
"version": "4.5.2",
"scripts": {
"build": "tsx scripts/build.ts",
"dev": "cross-env NODE_ENV=development DEBUG=zipline tsx --require dotenv/config --enable-source-maps ./src/server",
"dev:nd": "cross-env NODE_ENV=development tsx --require dotenv/config --enable-source-maps ./src/server",
"dev:inspector": "cross-env NODE_ENV=development DEBUG=zipline tsx --require dotenv/config --inspect=0.0.0.0:9229 --enable-source-maps ./src/server",
"start": "cross-env NODE_ENV=production node --trace-warnings --require dotenv/config ./build/server",
"start:inspector": "cross-env NODE_ENV=production node --require dotenv/config --inspect=0.0.0.0:9229 --enable-source-maps ./build/server",
"ctl": "NODE_ENV=production node --require dotenv/config --enable-source-maps ./build/ctl",
"dev": "cross-env NODE_ENV=development DEBUG=zipline tsx --require ./src/dotenv.js --enable-source-maps ./src/server",
"dev:nd": "cross-env NODE_ENV=development tsx --require ./src/dotenv.js --enable-source-maps ./src/server",
"dev:inspector": "cross-env NODE_ENV=development DEBUG=zipline tsx --require ./src/dotenv.js --inspect=0.0.0.0:9229 --enable-source-maps ./src/server",
"start": "cross-env NODE_ENV=production node --trace-warnings --require ./src/dotenv.js ./build/server",
"start:inspector": "cross-env NODE_ENV=production node --require ./src/dotenv.js --inspect=0.0.0.0:9229 --enable-source-maps ./build/server",
"ctl": "NODE_ENV=production node --require ./src/dotenv.js --enable-source-maps ./build/ctl",
"validate": "tsx scripts/validate.ts",
"openapi": "tsx scripts/openapi.ts",
"db:prototype": "prisma db push --skip-generate && prisma generate --no-hints",
@@ -22,8 +22,8 @@
"docker:compose:dev:logs": "docker compose --file docker-compose.dev.yml logs -f"
},
"dependencies": {
"@aws-sdk/client-s3": "3.726.1",
"@aws-sdk/lib-storage": "3.726.1",
"@aws-sdk/client-s3": "3.1025.0",
"@aws-sdk/lib-storage": "3.1025.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
@@ -34,15 +34,15 @@
"@fastify/sensible": "^6.0.4",
"@fastify/static": "^9.0.0",
"@fastify/swagger": "^9.7.0",
"@mantine/charts": "^8.3.18",
"@mantine/code-highlight": "^8.3.18",
"@mantine/core": "^8.3.18",
"@mantine/dates": "^8.3.18",
"@mantine/dropzone": "^8.3.18",
"@mantine/form": "^8.3.18",
"@mantine/hooks": "^8.3.18",
"@mantine/modals": "^8.3.18",
"@mantine/notifications": "^8.3.18",
"@mantine/charts": "^9.0.1",
"@mantine/code-highlight": "^9.0.1",
"@mantine/core": "^9.0.1",
"@mantine/dates": "^9.0.1",
"@mantine/dropzone": "^9.0.1",
"@mantine/form": "^9.0.1",
"@mantine/hooks": "^9.0.1",
"@mantine/modals": "^9.0.1",
"@mantine/notifications": "^9.0.1",
"@prisma/adapter-pg": "6.13.0",
"@prisma/client": "6.13.0",
"@prisma/engines": "6.13.0",
@@ -50,8 +50,8 @@
"@prisma/migrate": "6.13.0",
"@simplewebauthn/browser": "^13.3.0",
"@simplewebauthn/server": "^13.3.0",
"@smithy/node-http-handler": "^4.1.1",
"@tabler/icons-react": "^3.40.0",
"@smithy/node-http-handler": "^4.5.2",
"@tabler/icons-react": "^3.41.1",
"archiver": "^7.0.1",
"argon2": "^0.44.0",
"asciinema-player": "^3.15.1",
@@ -63,8 +63,7 @@
"cross-env": "^10.1.0",
"dayjs": "^1.11.20",
"detect-browser": "^5.3.0",
"devalue": "^5.6.4",
"dotenv": "^17.3.1",
"devalue": "^5.7.0",
"fast-glob": "^3.3.3",
"fastify": "^5.8.4",
"fastify-plugin": "^5.1.0",
@@ -74,7 +73,7 @@
"highlight.js": "^11.11.1",
"iron-session": "^8.0.4",
"isomorphic-dompurify": "^3.7.1",
"katex": "^0.16.42",
"katex": "^0.16.45",
"mantine-datatable": "^8.3.13",
"ms": "^2.1.3",
"multer": "2.1.1",
@@ -84,13 +83,12 @@
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.13.2",
"react-window": "1.8.11",
"react-router-dom": "^7.14.0",
"react-virtuoso": "^4.18.4",
"remark-gfm": "^4.0.1",
"sharp": "^0.34.5",
"swr": "^2.4.1",
"typescript-eslint": "^8.57.2",
"vite": "^8.0.2",
"vite": "^8.0.5",
"zod": "^4.3.6",
"zustand": "^5.0.12"
},
@@ -102,19 +100,18 @@
"@types/katex": "^0.16.8",
"@types/ms": "^2.1.0",
"@types/multer": "^2.1.0",
"@types/node": "^24.10.1",
"@types/node": "^24.12.2",
"@types/qrcode": "^1.5.6",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/react-window": "^1.8.8",
"@vitejs/plugin-react": "^6.0.1",
"eslint": "^9.39.1",
"eslint": "^10.2.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"eslint-plugin-react-refresh": "^0.5.2",
"eslint-plugin-unused-imports": "^4.3.0",
"postcss": "^8.5.8",
"postcss-preset-mantine": "^1.18.0",
@@ -124,7 +121,8 @@
"tsc-alias": "^1.8.16",
"tsup": "^8.5.1",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
"typescript": "^6.0.2",
"typescript-eslint": "^8.58.0"
},
"engines": {
"node": ">=22"

2763
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,7 @@ export default function ReloadPage() {
Why am I seeing this?
</Button>
<Collapse in={view}>
<Collapse expanded={view}>
<GenericError
title='Failed to fetch dynamically imported module'
message='This error can occur when a new version of the app is deployed while you have the page open. Please reload the page to update to the latest version.'

View File

@@ -114,7 +114,7 @@ export default function ViewFileId() {
</Group>
</Paper>
<Collapse in={detailsOpen}>
<Collapse expanded={detailsOpen}>
<Paper m='md' p='md' withBorder>
{user?.view!.content && (
<Typography>

View File

@@ -396,7 +396,7 @@ export default function FileTable({
)}
<Box>
<Collapse in={selectedFiles.length > 0}>
<Collapse expanded={selectedFiles.length > 0}>
<Paper withBorder p='sm' my='sm'>
<Text size='sm' c='dimmed' mb='xs'>
Selections are saved across page changes
@@ -487,7 +487,7 @@ export default function FileTable({
</Collapse>
{modals && setModals && modals.idSearch && (
<Collapse in={modals.idSearch}>
<Collapse expanded={modals.idSearch}>
<Paper withBorder p='sm' mt='sm'>
<TextInput
placeholder='Search by ID'

View File

@@ -236,7 +236,7 @@ export default function DashboardFolders() {
{filesOpen ? '▼' : '▶'} {currentFolder.name}&#39;s files{' '}
{currentFolder._count ? `(${currentFolder._count.files})` : ''}
</Text>
<Collapse in={filesOpen}>
<Collapse expanded={filesOpen}>
{view === 'grid' ? (
<Paper withBorder p='sm'>
<FilesGridView folderId={currentFolderId} />

View File

@@ -139,7 +139,7 @@ export default function Export3Details({ export3 }: { export3: Export3 }) {
{envOpened ? 'Hide' : 'Show'} OS Details
</Button>
<Collapse in={osOpened}>
<Collapse expanded={osOpened}>
<HighlightCode language='json' code={JSON.stringify(export3.request.os, null, 2)} />
</Collapse>
@@ -147,7 +147,7 @@ export default function Export3Details({ export3 }: { export3: Export3 }) {
{envOpened ? 'Hide' : 'Show'} Environment
</Button>
<Collapse in={envOpened}>
<Collapse expanded={envOpened}>
<Paper withBorder>
<Table>
<Table.Thead>

View File

@@ -195,7 +195,7 @@ export default function Export3Details({ export4 }: { export4: Export4 }) {
{envOpened ? 'Hide' : 'Show'} OS Details
</Button>
<Collapse in={osOpened}>
<Collapse expanded={osOpened}>
<Paper withBorder>
<Table>
<Table.Thead>
@@ -217,7 +217,7 @@ export default function Export3Details({ export4 }: { export4: Export4 }) {
{envOpened ? 'Hide' : 'Show'} Environment
</Button>
<Collapse in={envOpened}>
<Collapse expanded={envOpened}>
<Paper withBorder>
<Table>
<Table.Thead>

View File

@@ -32,7 +32,7 @@ export default function Export4ImportSettings({
{showSettings ? 'Hide' : 'Show'} Settings to be Imported
</Button>
<Collapse in={showSettings}>
<Collapse expanded={showSettings}>
<Paper withBorder>
<Table>
<Table.Thead>

View File

@@ -297,7 +297,7 @@ export default function DashboardServerSettings() {
</Group>
{(data?.tampered?.length ?? 0) > 0 && (
<Collapse in={opened} transitionDuration={180}>
<Collapse expanded={opened} transitionDuration={180}>
<Alert
color='red'
title='Environment Variable Settings'

View File

@@ -245,7 +245,7 @@ export default function Discord({
{...formOnUpload.getInputProps('discordOnUploadEmbed', { type: 'checkbox' })}
/>
<Collapse in={formOnUpload.values.discordOnUploadEmbed}>
<Collapse expanded={formOnUpload.values.discordOnUploadEmbed}>
<Paper withBorder p='sm' mt='md'>
<SimpleGrid cols={{ base: 1, md: 2 }} spacing='lg'>
<TextInput
@@ -348,7 +348,7 @@ export default function Discord({
{...formOnShorten.getInputProps('discordOnShortenEmbed', { type: 'checkbox' })}
/>
<Collapse in={formOnShorten.values.discordOnShortenEmbed}>
<Collapse expanded={formOnShorten.values.discordOnShortenEmbed}>
<Paper withBorder p='sm' mt='md'>
<SimpleGrid cols={{ base: 1, md: 2 }} spacing='lg'>
<TextInput

View File

@@ -57,7 +57,7 @@ export default function SettingsDashboard() {
label='Default Domain'
description='Set the default domain used for copied links anywhere in the dashboard. Leave blank or select "Default domain" to use the current domain that serves the dashboard.'
value={settings.domain}
onChange={(value) => update('domain', value ?? '')}
onChange={(value) => update('domain', (value as string) ?? '')}
/>
<Select

View File

@@ -194,7 +194,7 @@ export default function UploadFile({ title, folder }: { title?: string; folder?:
</Group>
</Dropzone>
<Collapse in={progress.percent > 0 && progress.percent < 100}>
<Collapse expanded={progress.percent > 0 && progress.percent < 100}>
{progress.percent > 0 && progress.percent < 100 && (
<Progress.Root my='sm' size='xl'>
<Progress.Section value={progress.percent} animated>
@@ -204,7 +204,7 @@ export default function UploadFile({ title, folder }: { title?: string; folder?:
)}
</Collapse>
<Collapse in={progress.speed > 0 && progress.remaining > 0}>
<Collapse expanded={progress.speed > 0 && progress.remaining > 0}>
<Paper withBorder p='xs' radius='sm'>
<Text ta='center' size='sm'>
{bytes(progress.speed)}/s, {humanizeDuration(progress.remaining)} remaining
@@ -212,7 +212,7 @@ export default function UploadFile({ title, folder }: { title?: string; folder?:
</Paper>
</Collapse>
<Collapse in={progress.percent === 100}>
<Collapse expanded={progress.percent === 100}>
<Paper withBorder p='xs' radius='sm'>
<Text ta='center' size='sm' c='yellow' fw={500}>
Finalizing upload(s)...

View File

@@ -380,7 +380,7 @@ export default function UploadOptionsButton({ folder, numFiles }: { folder?: str
</>
}
value={options.overrides_returnDomain ?? ''}
onChange={(value) => setOption('overrides_returnDomain', value || null)}
onChange={(value) => setOption('overrides_returnDomain', (value as string) || null)}
comboboxProps={{
withinPortal: true,
portalProps: {

View File

@@ -1,12 +1,11 @@
import { ActionIcon, Button, CopyButton, Paper, ScrollArea, Text, useMantineTheme } from '@mantine/core';
import { ActionIcon, Button, CopyButton, Paper, Text, useMantineTheme } from '@mantine/core';
import { IconCheck, IconChevronDown, IconChevronUp, IconClipboardCopy } from '@tabler/icons-react';
import type { HLJSApi } from 'highlight.js';
import { useEffect, useMemo, useState } from 'react';
import { FixedSizeList as List } from 'react-window';
import { Virtuoso } from 'react-virtuoso';
import { useLocation } from 'react-router-dom';
import './HighlightCode.theme.scss';
import * as sanitize from 'isomorphic-dompurify';
import './HighlightCode.theme.scss';
export default function HighlightCode({ language, code }: { language: string; code: string }) {
const { pathname } = useLocation();
@@ -20,37 +19,33 @@ export default function HighlightCode({ language, code }: { language: string; co
import('highlight.js').then((mod) => setHljs(mod.default || mod));
}, []);
const lines = useMemo(() => code.split('\n'), [code]);
const visible = expanded || noClamp ? lines.length : Math.min(lines.length, 50);
const expandable = !noClamp && lines.length > 50;
const cleanedCode = sanitize.sanitize(code, { USE_PROFILES: { html: true } });
const lines = cleanedCode.split('\n');
const isExpandable = !noClamp && lines.length > 50;
const totalCount = isExpandable && !expanded ? 50 : lines.length;
const estimatedHeight = Math.min(totalCount * 24, 400);
const lang = useMemo(() => {
if (!hljs) return 'plaintext';
if (hljs.getLanguage(language)) return language;
return 'plaintext';
return hljs.getLanguage(language) ? language : 'plaintext';
}, [hljs, language]);
const hlLines = useMemo(() => {
if (!hljs) return lines;
return lines.map(
(line) =>
hljs.highlight(line, {
language: lang,
}).value,
);
return lines.map((line) => hljs.highlight(line || ' ', { language: lang }).value);
}, [lines, hljs, lang]);
const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
const rowRenderer = (index: number) => (
<div
style={{
...style,
display: 'flex',
alignItems: 'flex-start',
whiteSpace: 'pre',
fontFamily: 'monospace',
fontFamily: theme.fontFamilyMonospace,
fontSize: '0.8rem',
lineHeight: '1.5',
backgroundColor: 'transparent',
}}
>
<Text
@@ -69,16 +64,14 @@ export default function HighlightCode({ language, code }: { language: string; co
<code
className='theme hljs'
style={{ flex: 1, fontSize: '0.8rem' }}
dangerouslySetInnerHTML={{
__html: sanitize.sanitize(hlLines[index], { USE_PROFILES: { html: true } }),
}}
style={{ flex: 1, padding: 0, background: 'none', alignSelf: 'center' }}
dangerouslySetInnerHTML={{ __html: hlLines[index] }}
/>
</div>
);
return (
<Paper withBorder p='xs' my='md' pos='relative'>
<Paper withBorder p='xs' my='md' pos='relative' style={{ overflow: 'hidden' }}>
<CopyButton value={code}>
{({ copied, copy }) => (
<ActionIcon
@@ -86,40 +79,39 @@ export default function HighlightCode({ language, code }: { language: string; co
variant='outline'
color={copied ? 'green' : 'gray'}
size='md'
style={{ zIndex: 4, position: 'absolute', top: '0.5rem', right: '0.5rem' }}
style={{ zIndex: 10, position: 'absolute', top: '0.5rem', right: '0.5rem' }}
>
{!copied ? (
<IconClipboardCopy size='1rem' />
) : (
{copied ? (
<IconCheck color={theme.colors.green[4]} size='1rem' />
) : (
<IconClipboardCopy size='1rem' />
)}
</ActionIcon>
)}
</CopyButton>
{noClamp ? (
<ScrollArea type='auto' offsetScrollbars={false}>
<div>
{hlLines.map((_, index) => (
<Row key={index} index={index} style={{}} />
))}
<div style={{ height: noClamp && (expanded || !isExpandable) ? 'auto' : estimatedHeight }}>
<Virtuoso
style={{ height: '100%' }}
totalCount={totalCount}
itemContent={rowRenderer}
initialItemCount={30}
increaseViewportBy={200}
/>
</div>
</ScrollArea>
) : (
<ScrollArea type='auto' offsetScrollbars={false} style={{ maxHeight: 400 }}>
<List height={400} width='100%' itemCount={visible} itemSize={20} overscanCount={10}>
{Row}
</List>
</ScrollArea>
)}
{expandable && (
{isExpandable && (
<Button
variant='light'
size='compact-sm'
onClick={() => setExpanded((e) => !e)}
leftSection={expanded ? <IconChevronUp size='1rem' /> : <IconChevronDown size='1rem' />}
style={{ position: 'absolute', bottom: '0.5rem', right: '0.5rem' }}
style={{
position: 'absolute',
bottom: '0.5rem',
right: '0.5rem',
zIndex: 10,
}}
>
{expanded ? 'Show Less' : `Show More (${lines.length - 50} more lines)`}
</Button>

5
src/dotenv.js Normal file
View File

@@ -0,0 +1,5 @@
// --require ./src/dotenv.js
// loads environment variables from a .env file into process.env on startup
try {
process.loadEnvFile('.env');
} catch {}

View File

@@ -63,6 +63,8 @@ export class S3Datasource extends Datasource {
keepAlive: true,
}),
}),
requestChecksumCalculation: 'WHEN_REQUIRED',
responseChecksumValidation: 'WHEN_REQUIRED',
});
this.ensureReadWriteAccess();

View File

@@ -4,10 +4,14 @@ const MAX = 256 - (256 % CHARSET_LENGTH);
function getRandomValues(array: Uint8Array) {
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
return crypto.getRandomValues(array);
// TODO: remove any cast when the types are fixed...
return crypto.getRandomValues(<any>array);
} else {
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require('crypto').webcrypto.getRandomValues(array);
console.error(
'No secure random number generator available. Please use node@22+ and a supported platform.',
);
process.exit(1);
}
}