From ca8eccf89927a45e66e702e9647c2076a4120f5e Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Fri, 6 Dec 2024 18:24:23 +0800 Subject: [PATCH] upgrade execa and node types --- package.json | 7 +- script/postversion.mts | 2 +- script/xcrun-wrapper.mts | 2 +- src/main/compatPlayer.ts | 11 +- src/main/ffmpeg.ts | 43 +++-- src/renderer/src/MediaSourcePlayer.tsx | 5 +- src/renderer/src/edlStore.ts | 2 +- src/renderer/src/ffmpeg.ts | 26 +-- src/renderer/src/hooks/useFfmpegOperations.ts | 6 +- src/renderer/src/hooks/useThumbnails.ts | 3 +- src/renderer/src/hooks/useWaveform.ts | 4 +- src/renderer/src/reporting.tsx | 20 +-- src/renderer/src/util.ts | 18 +- tsconfig.node.json | 2 +- yarn.lock | 156 ++++++++++++++++-- 15 files changed, 226 insertions(+), 81 deletions(-) diff --git a/package.json b/package.json index 037a1ff0..19207225 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-switch": "^1.0.1", "@tsconfig/node18": "^18.2.2", + "@tsconfig/node20": "^20.1.4", "@tsconfig/strictest": "^2.0.2", "@tsconfig/vite-react": "^3.0.0", "@types/color": "^3.0.6", @@ -58,7 +59,7 @@ "@types/mime-types": "^2.1.4", "@types/morgan": "^1.9.9", "@types/mousetrap": "^1.6.15", - "@types/node": "18", + "@types/node": "20", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", "@types/sortablejs": "^1.15.0", @@ -122,7 +123,7 @@ "tiny-invariant": "^1.3.3", "tsx": "^4.7.1", "type-fest": "^4.23.0", - "typescript": "^5.5.4", + "typescript": "^5.7.2", "use-debounce": "^5.1.0", "use-trace-update": "^1.3.0", "vite": "^5.3.6", @@ -134,7 +135,7 @@ "cue-parser": "^0.3.0", "electron-store": "5.1.1", "electron-unhandled": "^5.0.0", - "execa": "^8.0.1", + "execa": "^9.5.1", "express": "^4.20.0", "express-async-handler": "^1.2.0", "file-type": "patch:file-type@npm%3A19.4.1#~/.yarn/patches/file-type-npm-19.4.1-d18086444c.patch", diff --git a/script/postversion.mts b/script/postversion.mts index f9b24708..bea6d0ef 100644 --- a/script/postversion.mts +++ b/script/postversion.mts @@ -5,7 +5,7 @@ import { DateTime } from 'luxon'; const xmlUrl = new URL('../no.mifi.losslesscut.appdata.xml', import.meta.url); const xmlData = await readFile(xmlUrl); -const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url)) as unknown as string); +const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')); const parser = new XMLParser({ alwaysCreateTextNode: true, ignoreAttributes: false, ignoreDeclaration: false }); const xml = parser.parse(xmlData); diff --git a/script/xcrun-wrapper.mts b/script/xcrun-wrapper.mts index 9c341d16..dd42c230 100644 --- a/script/xcrun-wrapper.mts +++ b/script/xcrun-wrapper.mts @@ -18,7 +18,7 @@ const bundleId = args[4]; // seems to be the same const ascPublicId = apiIssuer; -const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url)) as unknown as string); +const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')); console.log('Using version', packageJson.version); diff --git a/src/main/compatPlayer.ts b/src/main/compatPlayer.ts index 0ee7fbd5..bc0c050e 100644 --- a/src/main/compatPlayer.ts +++ b/src/main/compatPlayer.ts @@ -1,4 +1,5 @@ import assert from 'node:assert'; +import { ExecaError } from 'execa'; import logger from './logger.js'; import { createMediaSourceProcess, readOneJpegFrame as readOneJpegFrameRaw } from './ffmpeg.js'; @@ -64,16 +65,12 @@ export function createMediaSourceStream({ path, videoStreamIndex, audioStreamInd try { await process; } catch (err) { - if (err instanceof Error && err.name === 'AbortError') { + if (err instanceof ExecaError && err.isTerminated) { return; } - // @ts-expect-error todo - if (!(err.killed)) { - // @ts-expect-error todo - logger.warn(err.message); - logger.warn(stderr.toString('utf8')); - } + logger.warn((err as Error).message); + logger.warn(stderr.toString('utf8')); } })(); diff --git a/src/main/ffmpeg.ts b/src/main/ffmpeg.ts index c29f2e96..792c48b7 100644 --- a/src/main/ffmpeg.ts +++ b/src/main/ffmpeg.ts @@ -1,7 +1,7 @@ import { join } from 'node:path'; import readline from 'node:readline'; import stringToStream from 'string-to-stream'; -import { BufferEncodingOption, execa, ExecaChildProcess, Options as ExecaOptions } from 'execa'; +import { execa, Options as ExecaOptions, ResultPromise } from 'execa'; import assert from 'node:assert'; import { Readable } from 'node:stream'; // eslint-disable-next-line import/no-extraneous-dependencies @@ -14,7 +14,7 @@ import logger from './logger.js'; import { parseFfmpegProgressLine } from './progress.js'; -const runningFfmpegs = new Set>(); +const runningFfmpegs = new Set & { encoding: 'buffer' }>>(); // setInterval(() => console.log(runningFfmpegs.size), 1000); let customFfPath: string | undefined; @@ -59,7 +59,7 @@ export const getFfmpegPath = () => getFfPath('ffmpeg'); export function abortFfmpegs() { logger.info('Aborting', runningFfmpegs.size, 'ffmpeg process(es)'); runningFfmpegs.forEach((process) => { - process.kill('SIGTERM', { forceKillAfterTimeout: 10000 }); + process.kill(); }); } @@ -88,20 +88,27 @@ function handleProgress( }); } -function getExecaOptions({ env, ...customExecaOptions }: Omit, 'buffer'> = {}) { - const execaOptions: Omit, 'buffer'> = { ...customExecaOptions, encoding: 'buffer' }; - // https://github.com/mifi/lossless-cut/issues/1143#issuecomment-1500883489 - if (isLinux && !isDev && !customFfPath) { - return { - ...execaOptions, - env: { ...env, LD_LIBRARY_PATH: process.resourcesPath }, - }; - } +function getExecaOptions({ env, cancelSignal, ...rest }: ExecaOptions = {}) { + // This is a ugly hack to please execa which expects cancelSignal to be a prototype of AbortSignal + // however this gets lost during @electron/remote passing + // https://github.com/sindresorhus/execa/blob/c8cff27a47b6e6f1cfbfec2bf7fa9dcd08cefed1/lib/terminate/cancel.js#L5 + if (cancelSignal != null) Object.setPrototypeOf(cancelSignal, new AbortController().signal); + + const execaOptions: Pick & { encoding: 'buffer' } = { + ...(cancelSignal != null && { cancelSignal }), + ...rest, + encoding: 'buffer' as const, + env: { + ...env, + // https://github.com/mifi/lossless-cut/issues/1143#issuecomment-1500883489 + ...(isLinux && !isDev && !customFfPath && { LD_LIBRARY_PATH: process.resourcesPath }), + }, + }; return execaOptions; } // todo collect warnings from ffmpeg output and show them after export? example: https://github.com/mifi/lossless-cut/issues/1469 -function runFfmpegProcess(args: readonly string[], customExecaOptions?: Omit, 'encoding'>, additionalOptions?: { logCli?: boolean }) { +function runFfmpegProcess(args: readonly string[], customExecaOptions?: ExecaOptions, additionalOptions?: { logCli?: boolean }) { const ffmpegPath = getFfmpegPath(); const { logCli = true } = additionalOptions ?? {}; if (logCli) logger.info(getFfCommandLine('ffmpeg', args)); @@ -187,8 +194,8 @@ export async function renderWaveformPng({ filePath, start, duration, color, stre logger.info(`${getFfCommandLine('ffmpeg1', args1)} | \n${getFfCommandLine('ffmpeg2', args2)}`); - let ps1: ExecaChildProcess | undefined; - let ps2: ExecaChildProcess | undefined; + let ps1: ResultPromise<{ encoding: 'buffer' }> | undefined; + let ps2: ResultPromise<{ encoding: 'buffer' }> | undefined; try { ps1 = runFfmpegProcess(args1, { buffer: false }, { logCli: false }); ps2 = runFfmpegProcess(args2, undefined, { logCli: false }); @@ -210,7 +217,7 @@ export async function renderWaveformPng({ filePath, start, duration, color, stre } return { - buffer: stdout, + buffer: Buffer.from(stdout), }; } catch (err) { if (ps1) ps1.kill(); @@ -465,7 +472,7 @@ async function readFormatData(filePath: string) { const { stdout } = await runFfprobe([ '-of', 'json', '-show_format', '-i', filePath, '-hide_banner', ]); - return JSON.parse(stdout as unknown as string).format; + return JSON.parse(new TextDecoder().decode(stdout)).format; } export async function getDuration(filePath: string) { @@ -557,7 +564,7 @@ export function createMediaSourceProcess({ path, videoStreamIndex, audioStreamIn if (enableLog) logger.info(getFfCommandLine('ffmpeg', args)); - return execa(getFfmpegPath(), args, { encoding: null, buffer: false, stderr: enableLog ? 'inherit' : 'pipe' }); + return execa(getFfmpegPath(), args, { encoding: 'buffer', buffer: false, stderr: enableLog ? 'inherit' : 'pipe' }); } export async function downloadMediaUrl(url: string, outPath: string) { diff --git a/src/renderer/src/MediaSourcePlayer.tsx b/src/renderer/src/MediaSourcePlayer.tsx index 22798086..91b2020c 100644 --- a/src/renderer/src/MediaSourcePlayer.tsx +++ b/src/renderer/src/MediaSourcePlayer.tsx @@ -230,7 +230,7 @@ async function startPlayback({ path, video, videoStreamIndex, audioStreamIndex, processChunk(); } -function drawJpegFrame(canvas: HTMLCanvasElement | null, jpegImage: Buffer) { +function drawJpegFrame(canvas: HTMLCanvasElement | null, jpegImage: Uint8Array) { if (!canvas) return; const ctx = canvas.getContext('2d'); @@ -244,7 +244,8 @@ function drawJpegFrame(canvas: HTMLCanvasElement | null, jpegImage: Buffer) { img.onload = () => ctx.drawImage(img, 0, 0, canvas.width, canvas.height); // eslint-disable-next-line unicorn/prefer-add-event-listener img.onerror = (error) => console.error('Canvas JPEG image error', error); - img.src = `data:image/jpeg;base64,${jpegImage.toString('base64')}`; + // todo use Blob? + img.src = `data:image/jpeg;base64,${Buffer.from(jpegImage).toString('base64')}`; } async function createPauseImage({ path, seekTo, videoStreamIndex, canvas, signal }: { diff --git a/src/renderer/src/edlStore.ts b/src/renderer/src/edlStore.ts index 7ea36ac4..c3dffe9f 100644 --- a/src/renderer/src/edlStore.ts +++ b/src/renderer/src/edlStore.ts @@ -84,7 +84,7 @@ export async function saveLlcProject({ savePath, filePath, cutSegments }) { } export async function loadLlcProject(path: string) { - const parsed = JSON5.parse(await readFile(path) as unknown as string) as unknown; + const parsed = JSON5.parse(await readFile(path, 'utf8')) as unknown; if (parsed == null || typeof parsed !== 'object') throw new Error('Invalid LLC file'); let mediaFileName: string | undefined; if ('mediaFileName' in parsed && typeof parsed.mediaFileName === 'string') { diff --git a/src/renderer/src/ffmpeg.ts b/src/renderer/src/ffmpeg.ts index 672df8a7..0cc4999c 100644 --- a/src/renderer/src/ffmpeg.ts +++ b/src/renderer/src/ffmpeg.ts @@ -27,23 +27,23 @@ export class RefuseOverwriteError extends Error { } } -export function fixRemoteBuffer(buffer: Buffer) { +export function safeCreateBlob(array: Uint8Array, options?: BlobPropertyBag) { // if we don't do this when creating a Blob, we get: // "Failed to construct 'Blob': The provided ArrayBufferView value must not be resizable." // maybe when moving away from @electron/remote, it's not needed anymore? - const buffer2 = Buffer.allocUnsafe(buffer.length); - buffer.copy(buffer2); - return buffer2; + // https://stackoverflow.com/a/25255750/6519037 + const cloned = new Uint8Array(array); + return new Blob([cloned], options); } -export function logStdoutStderr({ stdout, stderr }: { stdout: Buffer, stderr: Buffer }) { +export function logStdoutStderr({ stdout, stderr }: { stdout: Uint8Array, stderr: Uint8Array }) { if (stdout.length > 0) { console.log('%cSTDOUT:', 'color: green; font-weight: bold'); - console.log(stdout.toString('utf8')); + console.log(new TextDecoder().decode(stdout)); } if (stderr.length > 0) { console.log('%cSTDERR:', 'color: blue; font-weight: bold'); - console.log(stderr.toString('utf8')); + console.log(new TextDecoder().decode(stderr)); } } @@ -77,7 +77,7 @@ export async function readFrames({ filePath, from, to, streamIndex }: { }) { const intervalsArgs = from != null && to != null ? ['-read_intervals', `${from}%${to}`] : []; const { stdout } = await runFfprobe(['-v', 'error', ...intervalsArgs, '-show_packets', '-select_streams', String(streamIndex), '-show_entries', 'packet=pts_time,flags', '-of', 'json', filePath], { logCli: false }); - const packetsFiltered: Frame[] = (JSON.parse(stdout as unknown as string).packets as { flags: string, pts_time: string }[]) + const packetsFiltered: Frame[] = (JSON.parse(new TextDecoder().decode(stdout)).packets as { flags: string, pts_time: string }[]) .map((p) => ({ keyframe: p.flags[0] === 'K', time: parseFloat(p.pts_time), @@ -356,7 +356,7 @@ export async function readFileMeta(filePath: string) { let parsedJson: FFprobeProbeResult; try { // https://github.com/mifi/lossless-cut/issues/1342 - parsedJson = JSON.parse(stdout.toString('utf8')); + parsedJson = JSON.parse(new TextDecoder().decode(stdout)); } catch { console.log('ffprobe stdout', stdout); throw new Error('ffprobe returned malformed data'); @@ -383,9 +383,9 @@ async function renderThumbnail(filePath: string, timestamp: number, signal: Abor '-', ]; - const { stdout } = await runFfmpeg(args, { signal }, { logCli: false }); + const { stdout } = await runFfmpeg(args, { cancelSignal: signal }, { logCli: false }); - const blob = new Blob([fixRemoteBuffer(stdout)], { type: 'image/jpeg' }); + const blob = safeCreateBlob(stdout, { type: 'image/jpeg' }); return URL.createObjectURL(blob); } @@ -399,7 +399,7 @@ export async function extractSubtitleTrack(filePath: string, streamId: number) { ]; const { stdout } = await runFfmpeg(args); - return stdout.toString('utf8'); + return new TextDecoder().decode(stdout); } export async function extractSubtitleTrackToSegments(filePath: string, streamId: number) { @@ -423,7 +423,7 @@ export async function extractSubtitleTrackVtt(filePath: string, streamId: number const { stdout } = await runFfmpeg(args); - const blob = new Blob([fixRemoteBuffer(stdout)], { type: 'text/vtt' }); + const blob = safeCreateBlob(stdout, { type: 'text/vtt' }); return URL.createObjectURL(blob); } diff --git a/src/renderer/src/hooks/useFfmpegOperations.ts b/src/renderer/src/hooks/useFfmpegOperations.ts index 753705ba..73ab27e6 100644 --- a/src/renderer/src/hooks/useFfmpegOperations.ts +++ b/src/renderer/src/hooks/useFfmpegOperations.ts @@ -762,7 +762,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea appendFfmpegCommandLog(ffmpegArgs); const { stdout } = await runFfmpegWithProgress({ ffmpegArgs, duration, onProgress }); - console.log(stdout.toString('utf8')); + console.log(new TextDecoder().decode(stdout)); invariant(outPath != null); await transferTimestamps({ inPath: filePathArg, outPath, treatOutputFileModifiedTimeAsStart }); @@ -901,7 +901,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea appendFfmpegCommandLog(ffmpegArgs); const { stdout } = await runFfmpeg(ffmpegArgs); - console.log(stdout.toString('utf8')); + console.log(new TextDecoder().decode(stdout)); return outPaths; }, [appendFfmpegCommandLog, enableOverwriteOutput, filePath]); @@ -939,7 +939,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea try { appendFfmpegCommandLog(ffmpegArgs); const { stdout } = await runFfmpeg(ffmpegArgs); - console.log(stdout.toString('utf8')); + console.log(new TextDecoder().decode(stdout)); } catch (err) { // Unfortunately ffmpeg will exit with code 1 even though it's a success // Note: This is kind of hacky: diff --git a/src/renderer/src/hooks/useThumbnails.ts b/src/renderer/src/hooks/useThumbnails.ts index 9d633a03..bb668921 100644 --- a/src/renderer/src/hooks/useThumbnails.ts +++ b/src/renderer/src/hooks/useThumbnails.ts @@ -6,6 +6,7 @@ import sortBy from 'lodash/sortBy'; import { renderThumbnails as ffmpegRenderThumbnails } from '../ffmpeg'; import { Thumbnail } from '../types'; import { isDurationValid } from '../segments'; +import { isExecaError } from '../util'; export default ({ filePath, zoomedDuration, zoomWindowStartTime, showThumbnails }: { @@ -39,7 +40,7 @@ export default ({ filePath, zoomedDuration, zoomWindowStartTime, showThumbnails await ffmpegRenderThumbnails({ signal: abortController.signal, filePath: debounced.filePath, from: debounced.zoomWindowStartTime, duration: debounced.zoomedDuration, onThumbnail: addThumbnail }); } catch (err) { - if ((err as Error).name !== 'AbortError') { + if ((err as Error).name !== 'AbortError' && !(isExecaError(err) && err.isCanceled)) { console.error('Failed to render thumbnails', err); } } diff --git a/src/renderer/src/hooks/useWaveform.ts b/src/renderer/src/hooks/useWaveform.ts index 63022807..ca9f7049 100644 --- a/src/renderer/src/hooks/useWaveform.ts +++ b/src/renderer/src/hooks/useWaveform.ts @@ -3,7 +3,7 @@ import sortBy from 'lodash/sortBy'; import { useThrottle } from '@uidotdev/usehooks'; import { waveformColorDark, waveformColorLight } from '../colors'; -import { fixRemoteBuffer, renderWaveformPng } from '../ffmpeg'; +import { renderWaveformPng, safeCreateBlob } from '../ffmpeg'; import { RenderableWaveform } from '../types'; import { FFprobeStream } from '../../../../ffprobe'; @@ -75,7 +75,7 @@ export default ({ darkMode, filePath, relevantTime, duration, waveformEnabled, a return { ...w, - url: URL.createObjectURL(new Blob([fixRemoteBuffer(buffer)], { type: 'image/png' })), + url: URL.createObjectURL(safeCreateBlob(buffer, { type: 'image/png' })), }; })); } catch (err) { diff --git a/src/renderer/src/reporting.tsx b/src/renderer/src/reporting.tsx index 0bd56421..c3f401ff 100644 --- a/src/renderer/src/reporting.tsx +++ b/src/renderer/src/reporting.tsx @@ -2,7 +2,7 @@ import i18n from 'i18next'; import { Trans } from 'react-i18next'; import CopyClipboardButton from './components/CopyClipboardButton'; -import { isStoreBuild, isMasBuild, isWindowsStoreBuild } from './util'; +import { isStoreBuild, isMasBuild, isWindowsStoreBuild, isExecaError } from './util'; import { ReactSwal } from './swal'; const electron = window.require('electron'); @@ -33,15 +33,15 @@ export function openSendReportDialog(err: unknown | undefined, state?: unknown) const version = app.getVersion(); const text = `${err instanceof Error ? err.stack : 'No error occurred.'}\n\n${JSON.stringify({ - err: err instanceof Error && { - code: err['code'], - killed: err['killed'], - failed: err['failed'], - timedOut: err['timedOut'], - isCanceled: err['isCanceled'], - exitCode: err['exitCode'], - signal: err['signal'], - signalDescription: err['signalDescription'], + err: isExecaError(err) && { + code: err.code, + isTerminated: err.isTerminated, + failed: err.failed, + timedOut: err.timedOut, + isCanceled: err.isCanceled, + exitCode: err.exitCode, + signal: err.signal, + signalDescription: err.signalDescription, }, state, diff --git a/src/renderer/src/util.ts b/src/renderer/src/util.ts index 075b2466..8b3f6cd6 100644 --- a/src/renderer/src/util.ts +++ b/src/renderer/src/util.ts @@ -291,17 +291,23 @@ export const deleteDispositionValue = 'llc_disposition_remove'; export const mirrorTransform = 'matrix(-1, 0, 0, 1, 0, 0)'; -export type InvariantExecaError = ExecaError | ExecaError | ExecaError; +// todo this is not a correct assumption +export type InvariantExecaError = ExecaError<{ encoding: 'utf8' }> | ExecaError<{ encoding: 'buffer' }>; -// note: I don't think we can use instanceof ExecaError because the error has been sent over the main-renderer bridge +// We can't use `instanceof ExecaError` because the error has been sent over the main-renderer bridge (@electron/remote) +// so instead we just check if it has some of execa's specific error properties export function isExecaError(err: unknown): err is InvariantExecaError { - return err instanceof Error && 'stdout' in err && 'stderr' in err; + // https://github.com/sindresorhus/execa/blob/main/docs/api.md#resultfailed + return err instanceof Error && ('failed' in err && 'shortMessage' in err && 'isForcefullyTerminated' in err); } -// execa killed (aborted by user) -export const isAbortedError = (err: unknown) => isExecaError(err) && err.killed; +export const isAbortedError = (err: unknown) => ( + // execa killed (aborted by user). isTerminated because runningFfmpegs process.kill + (isExecaError(err) && (err.isCanceled || err.isTerminated)) + || (err instanceof Error && err.name === 'AbortError') +); -export const getStdioString = (stdio: string | Buffer | undefined) => (stdio instanceof Buffer ? stdio.toString('utf8') : stdio); +export const getStdioString = (stdio: string | Uint8Array) => (stdio instanceof Uint8Array ? Buffer.from(stdio).toString('utf8') : stdio); // A bit hacky but it works, unless someone has a file called "No space left on device" ( ͡° ͜ʖ ͡°) export const isOutOfSpaceError = (err: InvariantExecaError) => ( diff --git a/tsconfig.node.json b/tsconfig.node.json index 2ad1fad0..c389cad6 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -1,5 +1,5 @@ { - "extends": ["@tsconfig/strictest", "@tsconfig/node18/tsconfig.json"], + "extends": ["@tsconfig/strictest", "@tsconfig/node20/tsconfig.json"], "compilerOptions": { "noEmit": true, }, diff --git a/yarn.lock b/yarn.lock index 56c6d45f..dfe2b541 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1804,6 +1804,13 @@ __metadata: languageName: node linkType: hard +"@sindresorhus/merge-streams@npm:^4.0.0": + version: 4.0.0 + resolution: "@sindresorhus/merge-streams@npm:4.0.0" + checksum: 10/16551c787f5328c8ef05fd9831ade64369ccc992df78deb635ec6c44af217d2f1b43f8728c348cdc4e00585ff2fad6e00d8155199cbf6b154acc45fe65cbf0aa + languageName: node + linkType: hard + "@szmarczak/http-timer@npm:^4.0.5": version: 4.0.6 resolution: "@szmarczak/http-timer@npm:4.0.6" @@ -1843,6 +1850,13 @@ __metadata: languageName: node linkType: hard +"@tsconfig/node20@npm:^20.1.4": + version: 20.1.4 + resolution: "@tsconfig/node20@npm:20.1.4" + checksum: 10/345dba8074647f6c11b8d78afa76d9c16e3436cb56a8e78fe2060014d33a09f3f4fd6ed81dc90e955d3509f926cd7fd61c6ddfd3d5a1d80758d7844f7cc3a99e + languageName: node + linkType: hard + "@tsconfig/strictest@npm:^2.0.2": version: 2.0.2 resolution: "@tsconfig/strictest@npm:2.0.2" @@ -2172,12 +2186,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:18": - version: 18.19.21 - resolution: "@types/node@npm:18.19.21" +"@types/node@npm:20": + version: 20.17.9 + resolution: "@types/node@npm:20.17.9" dependencies: - undici-types: "npm:~5.26.4" - checksum: 10/3a5c5841f294bc35b5b416a32764b5c0c2f22f4cef48cb7d2e3b4e068a52d5857e50da8e6e0685e743127c70344301c833849a3904ce3bd3f67448da5e85487a + undici-types: "npm:~6.19.2" + checksum: 10/11604a47adf383892394a59a339136b2746a71addf4a3b13f661d23b6e81e8a4f3b35e69dbcffc94698368e5ab5ec056a43a86c87eff00b1b8ea8cfcbfe641df languageName: node linkType: hard @@ -5430,6 +5444,26 @@ __metadata: languageName: node linkType: hard +"execa@npm:^9.5.1": + version: 9.5.1 + resolution: "execa@npm:9.5.1" + dependencies: + "@sindresorhus/merge-streams": "npm:^4.0.0" + cross-spawn: "npm:^7.0.3" + figures: "npm:^6.1.0" + get-stream: "npm:^9.0.0" + human-signals: "npm:^8.0.0" + is-plain-obj: "npm:^4.1.0" + is-stream: "npm:^4.0.1" + npm-run-path: "npm:^6.0.0" + pretty-ms: "npm:^9.0.0" + signal-exit: "npm:^4.1.0" + strip-final-newline: "npm:^4.0.0" + yoctocolors: "npm:^2.0.0" + checksum: 10/aa030cdd43ffbf6a8825c16eec1515729553ce3655a8fa5165f0ddab2320957a9783effbeff37662e238e6f5d979d9732e3baa4bcaaeba4360856e627a214177 + languageName: node + linkType: hard + "expand-template@npm:^2.0.3": version: 2.0.3 resolution: "expand-template@npm:2.0.3" @@ -5622,6 +5656,15 @@ __metadata: languageName: node linkType: hard +"figures@npm:^6.1.0": + version: 6.1.0 + resolution: "figures@npm:6.1.0" + dependencies: + is-unicode-supported: "npm:^2.0.0" + checksum: 10/9822d13630bee8e6a9f2da866713adf13854b07e0bfde042defa8bba32d47a1c0b2afa627ce73837c674cf9a5e3edce7e879ea72cb9ea7960b2390432d8e1167 + languageName: node + linkType: hard + "file-entry-cache@npm:^6.0.1": version: 6.0.1 resolution: "file-entry-cache@npm:6.0.1" @@ -6040,7 +6083,7 @@ __metadata: languageName: node linkType: hard -"get-stream@npm:^9.0.1": +"get-stream@npm:^9.0.0, get-stream@npm:^9.0.1": version: 9.0.1 resolution: "get-stream@npm:9.0.1" dependencies: @@ -6516,6 +6559,13 @@ __metadata: languageName: node linkType: hard +"human-signals@npm:^8.0.0": + version: 8.0.0 + resolution: "human-signals@npm:8.0.0" + checksum: 10/89acdc7081ac2a065e41cca7351c4b0fe2382e213b7372f90df6a554e340f31b49388a307adc1d6f4c60b2b4fe81eeff0bc1f44be6f5d844311cd92ccc7831c6 + languageName: node + linkType: hard + "humanize-ms@npm:^1.2.1": version: 1.2.1 resolution: "humanize-ms@npm:1.2.1" @@ -7016,7 +7066,7 @@ __metadata: languageName: node linkType: hard -"is-plain-obj@npm:^4.0.0": +"is-plain-obj@npm:^4.0.0, is-plain-obj@npm:^4.1.0": version: 4.1.0 resolution: "is-plain-obj@npm:4.1.0" checksum: 10/6dc45da70d04a81f35c9310971e78a6a3c7a63547ef782e3a07ee3674695081b6ca4e977fbb8efc48dae3375e0b34558d2bcd722aec9bddfa2d7db5b041be8ce @@ -7111,6 +7161,13 @@ __metadata: languageName: node linkType: hard +"is-unicode-supported@npm:^2.0.0": + version: 2.1.0 + resolution: "is-unicode-supported@npm:2.1.0" + checksum: 10/f254e3da6b0ab1a57a94f7273a7798dd35d1d45b227759f600d0fa9d5649f9c07fa8d3c8a6360b0e376adf916d151ec24fc9a50c5295c58bae7ca54a76a063f9 + languageName: node + linkType: hard + "is-valid-glob@npm:^1.0.0": version: 1.0.0 resolution: "is-valid-glob@npm:1.0.0" @@ -7605,6 +7662,7 @@ __metadata: "@radix-ui/react-checkbox": "npm:^1.0.4" "@radix-ui/react-switch": "npm:^1.0.1" "@tsconfig/node18": "npm:^18.2.2" + "@tsconfig/node20": "npm:^20.1.4" "@tsconfig/strictest": "npm:^2.0.2" "@tsconfig/vite-react": "npm:^3.0.0" "@types/color": "npm:^3.0.6" @@ -7618,7 +7676,7 @@ __metadata: "@types/mime-types": "npm:^2.1.4" "@types/morgan": "npm:^1.9.9" "@types/mousetrap": "npm:^1.6.15" - "@types/node": "npm:18" + "@types/node": "npm:20" "@types/react": "npm:^18.2.66" "@types/react-dom": "npm:^18.2.22" "@types/sortablejs": "npm:^1.15.0" @@ -7647,7 +7705,7 @@ __metadata: eslint-plugin-react-hooks: "npm:^4.3.0" eslint-plugin-unicorn: "npm:^51.0.1" evergreen-ui: "npm:^6.13.1" - execa: "npm:^8.0.1" + execa: "npm:^9.5.1" express: "npm:^4.20.0" express-async-handler: "npm:^1.2.0" fast-xml-parser: "npm:^4.4.1" @@ -7698,7 +7756,7 @@ __metadata: tiny-invariant: "npm:^1.3.3" tsx: "npm:^4.7.1" type-fest: "npm:^4.23.0" - typescript: "npm:^5.5.4" + typescript: "npm:^5.7.2" use-debounce: "npm:^5.1.0" use-trace-update: "npm:^1.3.0" vite: "npm:^5.3.6" @@ -8354,6 +8412,16 @@ __metadata: languageName: node linkType: hard +"npm-run-path@npm:^6.0.0": + version: 6.0.0 + resolution: "npm-run-path@npm:6.0.0" + dependencies: + path-key: "npm:^4.0.0" + unicorn-magic: "npm:^0.3.0" + checksum: 10/1a1b50aba6e6af7fd34a860ba2e252e245c4a59b316571a990356417c0cdf0414cabf735f7f52d9c330899cb56f0ab804a8e21fb12a66d53d7843e39ada4a3b6 + languageName: node + linkType: hard + "npmlog@npm:^6.0.0": version: 6.0.2 resolution: "npmlog@npm:6.0.2" @@ -8676,6 +8744,13 @@ __metadata: languageName: node linkType: hard +"parse-ms@npm:^4.0.0": + version: 4.0.0 + resolution: "parse-ms@npm:4.0.0" + checksum: 10/673c801d9f957ff79962d71ed5a24850163f4181a90dd30c4e3666b3a804f53b77f1f0556792e8b2adbb5d58757907d1aa51d7d7dc75997c2a56d72937cbc8b7 + languageName: node + linkType: hard + "parse5-htmlparser2-tree-adapter@npm:^7.0.0": version: 7.0.0 resolution: "parse5-htmlparser2-tree-adapter@npm:7.0.0" @@ -8912,6 +8987,15 @@ __metadata: languageName: node linkType: hard +"pretty-ms@npm:^9.0.0": + version: 9.2.0 + resolution: "pretty-ms@npm:9.2.0" + dependencies: + parse-ms: "npm:^4.0.0" + checksum: 10/a65a1d81560867f4f7128862fdbf0e1c2d3c5607bf75cae7758bf8111e2c4b744be46e084704125a38ba918bb43defa7a53aaff0f48c5c2d95367d3148c980d9 + languageName: node + linkType: hard + "prismjs@npm:^1.25.0": version: 1.26.0 resolution: "prismjs@npm:1.26.0" @@ -10483,6 +10567,13 @@ __metadata: languageName: node linkType: hard +"strip-final-newline@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-final-newline@npm:4.0.0" + checksum: 10/b5fe48f695d74863153a3b3155220e6e9bf51f4447832998c8edec38e6559b3af87a9fe5ac0df95570a78a26f5fa91701358842eab3c15480e27980b154a145f + languageName: node + linkType: hard + "strip-indent@npm:^3.0.0": version: 3.0.0 resolution: "strip-indent@npm:3.0.0" @@ -11068,7 +11159,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.0.4, typescript@npm:^5.5.4": +"typescript@npm:^5.0.4": version: 5.5.4 resolution: "typescript@npm:5.5.4" bin: @@ -11078,6 +11169,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:^5.7.2": + version: 5.7.2 + resolution: "typescript@npm:5.7.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10/4caa3904df69db9d4a8bedc31bafc1e19ffb7b24fbde2997a1633ae1398d0de5bdbf8daf602ccf3b23faddf1aeeb9b795223a2ed9c9a4fdcaf07bfde114a401a + languageName: node + linkType: hard + "typescript@patch:typescript@npm%3A^4.0.2#optional!builtin": version: 4.9.5 resolution: "typescript@patch:typescript@npm%3A4.9.5#optional!builtin::version=4.9.5&hash=289587" @@ -11088,7 +11189,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.0.4#optional!builtin, typescript@patch:typescript@npm%3A^5.5.4#optional!builtin": +"typescript@patch:typescript@npm%3A^5.0.4#optional!builtin": version: 5.5.4 resolution: "typescript@patch:typescript@npm%3A5.5.4#optional!builtin::version=5.5.4&hash=379a07" bin: @@ -11098,6 +11199,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A^5.7.2#optional!builtin": + version: 5.7.2 + resolution: "typescript@patch:typescript@npm%3A5.7.2#optional!builtin::version=5.7.2&hash=74658d" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10/ff27fc124bceb8969be722baa38af945b2505767cf794de3e2715e58f61b43780284060287d651fcbbdfb6f917f4653b20f4751991f17e0706db389b9bb3f75d + languageName: node + linkType: hard + "ua-parser-js@npm:^0.7.30": version: 0.7.33 resolution: "ua-parser-js@npm:0.7.33" @@ -11154,6 +11265,20 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~6.19.2": + version: 6.19.8 + resolution: "undici-types@npm:6.19.8" + checksum: 10/cf0b48ed4fc99baf56584afa91aaffa5010c268b8842f62e02f752df209e3dea138b372a60a963b3b2576ed932f32329ce7ddb9cb5f27a6c83040d8cd74b7a70 + languageName: node + linkType: hard + +"unicorn-magic@npm:^0.3.0": + version: 0.3.0 + resolution: "unicorn-magic@npm:0.3.0" + checksum: 10/bdd7d7c522f9456f32a0b77af23f8854f9a7db846088c3868ec213f9550683ab6a2bdf3803577eacbafddb4e06900974385841ccb75338d17346ccef45f9cb01 + languageName: node + linkType: hard + "unique-filename@npm:^2.0.0": version: 2.0.1 resolution: "unique-filename@npm:2.0.1" @@ -11761,6 +11886,13 @@ __metadata: languageName: node linkType: hard +"yoctocolors@npm:^2.0.0": + version: 2.1.1 + resolution: "yoctocolors@npm:2.1.1" + checksum: 10/563fbec88bce9716d1044bc98c96c329e1d7a7c503e6f1af68f1ff914adc3ba55ce953c871395e2efecad329f85f1632f51a99c362032940321ff80c42a6f74d + languageName: node + linkType: hard + "zod@npm:^3.22.5": version: 3.22.5 resolution: "zod@npm:3.22.5"