Various fixes (#186)

* fix: #181

* fix: use taskHandler as source of truth for imports

* fix: task formatting

* fix: zip downloads

* feat: re-enable import version button on delete + lint
This commit is contained in:
DecDuck
2025-08-15 22:57:56 +10:00
committed by GitHub
parent 9ff541059d
commit abec952e39
14 changed files with 180 additions and 185 deletions
+11 -8
View File
@@ -22,21 +22,17 @@
<!-- import games button --> <!-- import games button -->
<NuxtLink <NuxtLink
:href=" :href="canImport ? `/admin/library/${game.id}/import` : ''"
unimportedVersions.length > 0
? `/admin/library/${game.id}/import`
: ''
"
type="button" type="button"
:class="[ :class="[
unimportedVersions.length > 0 canImport
? 'bg-blue-600 hover:bg-blue-700' ? 'bg-blue-600 hover:bg-blue-700'
: 'bg-blue-800/50', : 'bg-blue-800/50',
'inline-flex w-fit items-center gap-x-2 rounded-md px-3 py-1 text-sm font-semibold font-display text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600', 'inline-flex w-fit items-center gap-x-2 rounded-md px-3 py-1 text-sm font-semibold font-display text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600',
]" ]"
> >
{{ {{
unimportedVersions.length > 0 canImport
? $t("library.admin.import.version.import") ? $t("library.admin.import.version.import")
: $t("library.admin.import.version.noVersions") : $t("library.admin.import.version.noVersions")
}} }}
@@ -124,10 +120,16 @@ import { ExclamationCircleIcon } from "@heroicons/vue/24/outline";
// TODO implement UI for this page // TODO implement UI for this page
defineProps<{ unimportedVersions: string[] }>(); const props = defineProps<{ unimportedVersions: string[] }>();
const { t } = useI18n(); const { t } = useI18n();
const hasDeleted = ref(false);
const canImport = computed(
() => hasDeleted.value || props.unimportedVersions.length > 0,
);
type GameAndVersions = GameModel & { versions: GameVersionModel[] }; type GameAndVersions = GameModel & { versions: GameVersionModel[] };
const game = defineModel<SerializeObject<GameAndVersions>>() as Ref< const game = defineModel<SerializeObject<GameAndVersions>>() as Ref<
SerializeObject<GameAndVersions> SerializeObject<GameAndVersions>
@@ -176,6 +178,7 @@ async function deleteVersion(versionName: string) {
game.value.versions.findIndex((e) => e.versionName === versionName), game.value.versions.findIndex((e) => e.versionName === versionName),
1, 1,
); );
hasDeleted.value = true;
} catch (e) { } catch (e) {
createModal( createModal(
ModalType.Notification, ModalType.Notification,
+6 -2
View File
@@ -18,8 +18,12 @@
</i18n-t> </i18n-t>
</NuxtLink> </NuxtLink>
<DevOnly <DevOnly>
><h1 class="mt-3 text-sm text-gray-500">{{ $t("welcome") }}</h1> <h1 class="mt-3 text-sm text-gray-500">{{ $t("welcome") }}</h1>
</DevOnly> </DevOnly>
</div> </div>
</template> </template>
<script setup lang="ts">
import { ArrowTopRightOnSquareIcon } from "@heroicons/vue/24/outline";
</script>
+1 -1
View File
@@ -21,7 +21,7 @@
}, },
"dependencies": { "dependencies": {
"@discordapp/twemoji": "^16.0.1", "@discordapp/twemoji": "^16.0.1",
"@drop-oss/droplet": "1.6.0", "@drop-oss/droplet": "2.3.0",
"@headlessui/vue": "^1.7.23", "@headlessui/vue": "^1.7.23",
"@heroicons/vue": "^2.1.5", "@heroicons/vue": "^2.1.5",
"@lobomfz/prismark": "0.0.3", "@lobomfz/prismark": "0.0.3",
+2 -2
View File
@@ -47,7 +47,7 @@
/> />
</div> </div>
<p class="mt-1 truncate text-sm text-zinc-400"> <p class="mt-1 truncate text-sm text-zinc-400">
{{ parseTaskLog(task.value.log.at(-1) ?? "").message }} {{ parseTaskLog(task.value.log.at(-1)).message }}
</p> </p>
<NuxtLink <NuxtLink
type="button" type="button"
@@ -115,7 +115,7 @@
{{ task.id }} {{ task.id }}
</p> </p>
<p class="mt-1 truncate text-sm text-zinc-400"> <p class="mt-1 truncate text-sm text-zinc-400">
{{ parseTaskLog(task.log.at(-1) ?? "").message }} {{ parseTaskLog(task.log.at(-1)).message }}
</p> </p>
<NuxtLink <NuxtLink
type="button" type="button"
@@ -0,0 +1,15 @@
/*
Warnings:
- The primary key for the `Task` table will be changed. If it partially fails, the table could be left without primary key constraint.
*/
-- DropIndex
DROP INDEX "GameTag_name_idx";
-- AlterTable
ALTER TABLE "Task" DROP CONSTRAINT "Task_pkey",
ADD CONSTRAINT "Task_pkey" PRIMARY KEY ("id", "started");
-- CreateIndex
CREATE INDEX "GameTag_name_idx" ON "GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
+3 -1
View File
@@ -1,5 +1,5 @@
model Task { model Task {
id String @id id String
taskGroup String taskGroup String
name String name String
@@ -12,4 +12,6 @@ model Task {
log String[] log String[]
acls String[] acls String[]
@@id([id, started])
} }
+13
View File
@@ -2,6 +2,7 @@ import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype"; import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import contextManager from "~/server/internal/downloads/coordinator"; import contextManager from "~/server/internal/downloads/coordinator";
import libraryManager from "~/server/internal/library"; import libraryManager from "~/server/internal/library";
import { logger } from "~/server/internal/logging";
const GetChunk = type({ const GetChunk = type({
context: "string", context: "string",
@@ -58,13 +59,25 @@ export default defineEventHandler(async (h3) => {
statusCode: 500, statusCode: 500,
statusMessage: "Failed to create read stream", statusMessage: "Failed to create read stream",
}); });
let length = 0;
await gameReadStream.pipeTo( await gameReadStream.pipeTo(
new WritableStream({ new WritableStream({
write(chunk) { write(chunk) {
h3.node.res.write(chunk); h3.node.res.write(chunk);
length += chunk.length;
}, },
}), }),
); );
if (length != file.end - file.start) {
logger.warn(
`failed to read enough from ${file.filename}. read ${length}, required: ${file.end - file.start}`,
);
throw createError({
statusCode: 500,
statusMessage: "Failed to read enough from stream.",
});
}
} }
await h3.node.res.end(); await h3.node.res.end();
+16 -81
View File
@@ -15,12 +15,17 @@ import { GameNotFoundError, type LibraryProvider } from "./provider";
import { logger } from "../logging"; import { logger } from "../logging";
import type { GameModel } from "~/prisma/client/models"; import type { GameModel } from "~/prisma/client/models";
export function createGameImportTaskId(libraryId: string, libraryPath: string) {
return btoa(`import:${libraryId}:${libraryPath}`);
}
export function createVersionImportTaskId(gameId: string, versionName: string) {
return btoa(`import:${gameId}:${versionName}`);
}
class LibraryManager { class LibraryManager {
private libraries: Map<string, LibraryProvider<unknown>> = new Map(); private libraries: Map<string, LibraryProvider<unknown>> = new Map();
private gameImportLocks: Map<string, Array<string>> = new Map(); // Library ID to Library Path
private versionImportLocks: Map<string, Array<string>> = new Map(); // Game ID to Version Name
addLibrary(library: LibraryProvider<unknown>) { addLibrary(library: LibraryProvider<unknown>) {
this.libraries.set(library.id(), library); this.libraries.set(library.id(), library);
} }
@@ -58,12 +63,10 @@ class LibraryManager {
for (const [id, library] of this.libraries.entries()) { for (const [id, library] of this.libraries.entries()) {
const providerGames = await library.listGames(); const providerGames = await library.listGames();
const locks = this.gameImportLocks.get(id) ?? [];
const providerUnimportedGames = providerGames.filter( const providerUnimportedGames = providerGames.filter(
(libraryPath) => (libraryPath) =>
instanceGames[id] && !instanceGames[id]?.[libraryPath] &&
!instanceGames[id][libraryPath] && !taskHandler.hasTask(createGameImportTaskId(id, libraryPath)),
!locks.includes(libraryPath),
); );
unimportedGames[id] = providerUnimportedGames; unimportedGames[id] = providerUnimportedGames;
} }
@@ -93,7 +96,7 @@ class LibraryManager {
const unimportedVersions = versions.filter( const unimportedVersions = versions.filter(
(e) => (e) =>
game.versions.findIndex((v) => v.versionName == e) == -1 && game.versions.findIndex((v) => v.versionName == e) == -1 &&
!(this.versionImportLocks.get(game.id) ?? []).includes(e), !taskHandler.hasTask(createVersionImportTaskId(game.id, e)),
); );
return unimportedVersions; return unimportedVersions;
} catch (e) { } catch (e) {
@@ -177,7 +180,8 @@ class LibraryManager {
for (const filename of files) { for (const filename of files) {
const basename = path.basename(filename); const basename = path.basename(filename);
const dotLocation = filename.lastIndexOf("."); const dotLocation = filename.lastIndexOf(".");
const ext = dotLocation == -1 ? "" : filename.slice(dotLocation); const ext =
dotLocation == -1 ? "" : filename.slice(dotLocation).toLowerCase();
for (const [platform, checkExts] of Object.entries(fileExts)) { for (const [platform, checkExts] of Object.entries(fileExts)) {
for (const checkExt of checkExts) { for (const checkExt of checkExts) {
if (checkExt != ext) continue; if (checkExt != ext) continue;
@@ -215,70 +219,6 @@ class LibraryManager {
} }
*/ */
/**
* Locks the game so you can't be imported
* @param libraryId
* @param libraryPath
*/
async lockGame(libraryId: string, libraryPath: string) {
let games = this.gameImportLocks.get(libraryId);
if (!games) this.gameImportLocks.set(libraryId, (games = []));
if (!games.includes(libraryPath)) games.push(libraryPath);
this.gameImportLocks.set(libraryId, games);
}
/**
* Unlocks the game, call once imported
* @param libraryId
* @param libraryPath
*/
async unlockGame(libraryId: string, libraryPath: string) {
let games = this.gameImportLocks.get(libraryId);
if (!games) this.gameImportLocks.set(libraryId, (games = []));
if (games.includes(libraryPath))
games.splice(
games.findIndex((e) => e === libraryPath),
1,
);
this.gameImportLocks.set(libraryId, games);
}
/**
* Locks a version so it can't be imported
* @param gameId
* @param versionName
*/
async lockVersion(gameId: string, versionName: string) {
let versions = this.versionImportLocks.get(gameId);
if (!versions) this.versionImportLocks.set(gameId, (versions = []));
if (!versions.includes(versionName)) versions.push(versionName);
this.versionImportLocks.set(gameId, versions);
}
/**
* Unlocks the version, call once imported
* @param libraryId
* @param libraryPath
*/
async unlockVersion(gameId: string, versionName: string) {
let versions = this.versionImportLocks.get(gameId);
if (!versions) this.versionImportLocks.set(gameId, (versions = []));
if (versions.includes(gameId))
versions.splice(
versions.findIndex((e) => e === versionName),
1,
);
this.versionImportLocks.set(gameId, versions);
}
async importVersion( async importVersion(
gameId: string, gameId: string,
versionName: string, versionName: string,
@@ -295,7 +235,7 @@ class LibraryManager {
umuId: string; umuId: string;
}, },
) { ) {
const taskId = `import:${gameId}:${versionName}`; const taskId = createVersionImportTaskId(gameId, versionName);
const platform = parsePlatform(metadata.platform); const platform = parsePlatform(metadata.platform);
if (!platform) return undefined; if (!platform) return undefined;
@@ -309,8 +249,6 @@ class LibraryManager {
const library = this.libraries.get(game.libraryId); const library = this.libraries.get(game.libraryId);
if (!library) return undefined; if (!library) return undefined;
await this.lockVersion(gameId, versionName);
taskHandler.create({ taskHandler.create({
id: taskId, id: taskId,
taskGroup: "import:game", taskGroup: "import:game",
@@ -387,9 +325,6 @@ class LibraryManager {
progress(100); progress(100);
}, },
async finally() {
await libraryManager.unlockVersion(gameId, versionName);
},
}); });
return taskId; return taskId;
@@ -403,7 +338,7 @@ class LibraryManager {
) { ) {
const library = this.libraries.get(libraryId); const library = this.libraries.get(libraryId);
if (!library) return undefined; if (!library) return undefined;
return library.peekFile(game, version, filename); return await library.peekFile(game, version, filename);
} }
async readFile( async readFile(
@@ -415,7 +350,7 @@ class LibraryManager {
) { ) {
const library = this.libraries.get(libraryId); const library = this.libraries.get(libraryId);
if (!library) return undefined; if (!library) return undefined;
return library.readFile(game, version, filename, options); return await library.readFile(game, version, filename, options);
} }
} }
+27 -15
View File
@@ -7,12 +7,14 @@ import {
import { LibraryBackend } from "~/prisma/client/enums"; import { LibraryBackend } from "~/prisma/client/enums";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import droplet from "@drop-oss/droplet"; import droplet, { DropletHandler } from "@drop-oss/droplet";
export const FilesystemProviderConfig = type({ export const FilesystemProviderConfig = type({
baseDir: "string", baseDir: "string",
}); });
export const DROPLET_HANDLER = new DropletHandler();
export class FilesystemProvider export class FilesystemProvider
implements LibraryProvider<typeof FilesystemProviderConfig.infer> implements LibraryProvider<typeof FilesystemProviderConfig.infer>
{ {
@@ -57,7 +59,7 @@ export class FilesystemProvider
const versionDirs = fs.readdirSync(gameDir); const versionDirs = fs.readdirSync(gameDir);
const validVersionDirs = versionDirs.filter((e) => { const validVersionDirs = versionDirs.filter((e) => {
const fullDir = path.join(this.config.baseDir, game, e); const fullDir = path.join(this.config.baseDir, game, e);
return droplet.hasBackendForPath(fullDir); return DROPLET_HANDLER.hasBackendForPath(fullDir);
}); });
return validVersionDirs; return validVersionDirs;
} }
@@ -65,7 +67,7 @@ export class FilesystemProvider
async versionReaddir(game: string, version: string): Promise<string[]> { async versionReaddir(game: string, version: string): Promise<string[]> {
const versionDir = path.join(this.config.baseDir, game, version); const versionDir = path.join(this.config.baseDir, game, version);
if (!fs.existsSync(versionDir)) throw new VersionNotFoundError(); if (!fs.existsSync(versionDir)) throw new VersionNotFoundError();
return droplet.listFiles(versionDir); return DROPLET_HANDLER.listFiles(versionDir);
} }
async generateDropletManifest( async generateDropletManifest(
@@ -77,10 +79,16 @@ export class FilesystemProvider
const versionDir = path.join(this.config.baseDir, game, version); const versionDir = path.join(this.config.baseDir, game, version);
if (!fs.existsSync(versionDir)) throw new VersionNotFoundError(); if (!fs.existsSync(versionDir)) throw new VersionNotFoundError();
const manifest = await new Promise<string>((r, j) => const manifest = await new Promise<string>((r, j) =>
droplet.generateManifest(versionDir, progress, log, (err, result) => { droplet.generateManifest(
if (err) return j(err); DROPLET_HANDLER,
r(result); versionDir,
}), progress,
log,
(err, result) => {
if (err) return j(err);
r(result);
},
),
); );
return manifest; return manifest;
} }
@@ -88,7 +96,7 @@ export class FilesystemProvider
async peekFile(game: string, version: string, filename: string) { async peekFile(game: string, version: string, filename: string) {
const filepath = path.join(this.config.baseDir, game, version); const filepath = path.join(this.config.baseDir, game, version);
if (!fs.existsSync(filepath)) return undefined; if (!fs.existsSync(filepath)) return undefined;
const stat = droplet.peekFile(filepath, filename); const stat = DROPLET_HANDLER.peekFile(filepath, filename);
return { size: Number(stat) }; return { size: Number(stat) };
} }
@@ -100,13 +108,17 @@ export class FilesystemProvider
) { ) {
const filepath = path.join(this.config.baseDir, game, version); const filepath = path.join(this.config.baseDir, game, version);
if (!fs.existsSync(filepath)) return undefined; if (!fs.existsSync(filepath)) return undefined;
const stream = droplet.readFile( let stream;
filepath, while (!(stream instanceof ReadableStream)) {
filename, const v = DROPLET_HANDLER.readFile(
options?.start ? BigInt(options.start) : undefined, filepath,
options?.end ? BigInt(options.end) : undefined, filename,
); options?.start ? BigInt(options.start) : undefined,
if (!stream) return undefined; options?.end ? BigInt(options.end) : undefined,
);
if (!v) return undefined;
stream = v.getStream() as ReadableStream<unknown>;
}
return stream; return stream;
} }
+16 -9
View File
@@ -5,6 +5,7 @@ import { LibraryBackend } from "~/prisma/client/enums";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import droplet from "@drop-oss/droplet"; import droplet from "@drop-oss/droplet";
import { DROPLET_HANDLER } from "./filesystem";
export const FlatFilesystemProviderConfig = type({ export const FlatFilesystemProviderConfig = type({
baseDir: "string", baseDir: "string",
@@ -46,7 +47,7 @@ export class FlatFilesystemProvider
const versionDirs = fs.readdirSync(this.config.baseDir); const versionDirs = fs.readdirSync(this.config.baseDir);
const validVersionDirs = versionDirs.filter((e) => { const validVersionDirs = versionDirs.filter((e) => {
const fullDir = path.join(this.config.baseDir, e); const fullDir = path.join(this.config.baseDir, e);
return droplet.hasBackendForPath(fullDir); return DROPLET_HANDLER.hasBackendForPath(fullDir);
}); });
return validVersionDirs; return validVersionDirs;
} }
@@ -63,7 +64,7 @@ export class FlatFilesystemProvider
async versionReaddir(game: string, _version: string) { async versionReaddir(game: string, _version: string) {
const versionDir = path.join(this.config.baseDir, game); const versionDir = path.join(this.config.baseDir, game);
if (!fs.existsSync(versionDir)) throw new VersionNotFoundError(); if (!fs.existsSync(versionDir)) throw new VersionNotFoundError();
return droplet.listFiles(versionDir); return DROPLET_HANDLER.listFiles(versionDir);
} }
async generateDropletManifest( async generateDropletManifest(
@@ -75,17 +76,23 @@ export class FlatFilesystemProvider
const versionDir = path.join(this.config.baseDir, game); const versionDir = path.join(this.config.baseDir, game);
if (!fs.existsSync(versionDir)) throw new VersionNotFoundError(); if (!fs.existsSync(versionDir)) throw new VersionNotFoundError();
const manifest = await new Promise<string>((r, j) => const manifest = await new Promise<string>((r, j) =>
droplet.generateManifest(versionDir, progress, log, (err, result) => { droplet.generateManifest(
if (err) return j(err); DROPLET_HANDLER,
r(result); versionDir,
}), progress,
log,
(err, result) => {
if (err) return j(err);
r(result);
},
),
); );
return manifest; return manifest;
} }
async peekFile(game: string, _version: string, filename: string) { async peekFile(game: string, _version: string, filename: string) {
const filepath = path.join(this.config.baseDir, game); const filepath = path.join(this.config.baseDir, game);
if (!fs.existsSync(filepath)) return undefined; if (!fs.existsSync(filepath)) return undefined;
const stat = droplet.peekFile(filepath, filename); const stat = DROPLET_HANDLER.peekFile(filepath, filename);
return { size: Number(stat) }; return { size: Number(stat) };
} }
async readFile( async readFile(
@@ -96,7 +103,7 @@ export class FlatFilesystemProvider
) { ) {
const filepath = path.join(this.config.baseDir, game); const filepath = path.join(this.config.baseDir, game);
if (!fs.existsSync(filepath)) return undefined; if (!fs.existsSync(filepath)) return undefined;
const stream = droplet.readFile( const stream = DROPLET_HANDLER.readFile(
filepath, filepath,
filename, filename,
options?.start ? BigInt(options.start) : undefined, options?.start ? BigInt(options.start) : undefined,
@@ -104,6 +111,6 @@ export class FlatFilesystemProvider
); );
if (!stream) return undefined; if (!stream) return undefined;
return stream; return stream.getStream();
} }
} }
+2 -7
View File
@@ -18,7 +18,7 @@ import taskHandler, { wrapTaskContext } from "../tasks";
import { randomUUID } from "crypto"; import { randomUUID } from "crypto";
import { fuzzy } from "fast-fuzzy"; import { fuzzy } from "fast-fuzzy";
import { logger } from "~/server/internal/logging"; import { logger } from "~/server/internal/logging";
import libraryManager from "../library"; import { createGameImportTaskId } from "../library";
import type { GameTagModel } from "~/prisma/client/models"; import type { GameTagModel } from "~/prisma/client/models";
export class MissingMetadataProviderConfig extends Error { export class MissingMetadataProviderConfig extends Error {
@@ -185,11 +185,9 @@ export class MetadataHandler {
}); });
if (existing) return undefined; if (existing) return undefined;
await libraryManager.lockGame(libraryId, libraryPath);
const gameId = randomUUID(); const gameId = randomUUID();
const taskId = `import:${gameId}`; const taskId = createGameImportTaskId(libraryId, libraryPath);
await taskHandler.create({ await taskHandler.create({
name: `Import game "${result.name}" (${libraryPath})`, name: `Import game "${result.name}" (${libraryPath})`,
id: taskId, id: taskId,
@@ -280,9 +278,6 @@ export class MetadataHandler {
logger.info(`Finished game import.`); logger.info(`Finished game import.`);
progress(100); progress(100);
}, },
async finally() {
await libraryManager.unlockGame(libraryId, libraryPath);
},
}); });
return taskId; return taskId;
+10 -4
View File
@@ -73,6 +73,8 @@ class TaskHandler {
} }
async create(task: Task) { async create(task: Task) {
if (this.hasTask(task.id)) throw new Error("Task with ID already exists.");
let updateCollectTimeout: NodeJS.Timeout | undefined; let updateCollectTimeout: NodeJS.Timeout | undefined;
let updateCollectResolves: Array<(value: unknown) => void> = []; let updateCollectResolves: Array<(value: unknown) => void> = [];
let logOffset: number = 0; let logOffset: number = 0;
@@ -206,8 +208,6 @@ class TaskHandler {
}; };
} }
if (task.finally) await task.finally();
taskEntry.endTime = new Date().toISOString(); taskEntry.endTime = new Date().toISOString();
await updateAllClients(); await updateAllClients();
@@ -247,7 +247,10 @@ class TaskHandler {
) { ) {
const task = const task =
this.taskPool.get(taskId) ?? this.taskPool.get(taskId) ??
(await prisma.task.findUnique({ where: { id: taskId } })); (await prisma.task.findFirst({
where: { id: taskId },
orderBy: { started: "desc" },
}));
if (!task) { if (!task) {
peer.send( peer.send(
`error/${taskId}/Unknown task/Drop couldn't find the task you're looking for.`, `error/${taskId}/Unknown task/Drop couldn't find the task you're looking for.`,
@@ -324,6 +327,10 @@ class TaskHandler {
.toArray(); .toArray();
} }
hasTask(id: string) {
return this.taskPool.has(id);
}
dailyTasks() { dailyTasks() {
return this.dailyScheduledTasks; return this.dailyScheduledTasks;
} }
@@ -429,7 +436,6 @@ export interface Task {
taskGroup: TaskGroup; taskGroup: TaskGroup;
name: string; name: string;
run: (context: TaskRunContext) => Promise<void>; run: (context: TaskRunContext) => Promise<void>;
finally?: () => Promise<void> | void;
acls: GlobalACL[]; acls: GlobalACL[];
} }
+4 -1
View File
@@ -1,6 +1,9 @@
import type { TaskLog } from "~/server/internal/tasks"; import type { TaskLog } from "~/server/internal/tasks";
export function parseTaskLog(logStr: string): typeof TaskLog.infer { export function parseTaskLog(
logStr?: string | undefined,
): typeof TaskLog.infer {
if (!logStr) return { message: "", timestamp: "" };
const log = JSON.parse(logStr); const log = JSON.parse(logStr);
return { return {
+54 -54
View File
@@ -342,71 +342,71 @@
jsonfile "^5.0.0" jsonfile "^5.0.0"
universalify "^0.1.2" universalify "^0.1.2"
"@drop-oss/droplet-darwin-arm64@1.6.0": "@drop-oss/droplet-darwin-arm64@2.3.0":
version "1.6.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-arm64/-/droplet-darwin-arm64-1.6.0.tgz#9697e38c46b02192e8e180b7deaaa20a389a9b0d" resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-arm64/-/droplet-darwin-arm64-2.3.0.tgz#f4f0ded9c9f5b5cac25dd56f59817e1c13e865ab"
integrity sha512-EqTx+Mk5SHP17n19r5coacUDd7lklT4opJ2keNQyGsQjrcf+9FeCX1O5Y+PGIjpQK6UkAVdnBqM+jR7NeFmkAQ== integrity sha512-5k1VwGZTFc61FvKyL4cvYxFYB7aCY5cWCo0Q7yTkkj+KR+ewH6ucylU8kDG7M+aBLvbC/zbntXUp4RtYZi4AZQ==
"@drop-oss/droplet-darwin-universal@1.6.0": "@drop-oss/droplet-darwin-universal@2.3.0":
version "1.6.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-universal/-/droplet-darwin-universal-1.6.0.tgz#2f780416052ac7d1752b0a7828dc3ef9d1789c92" resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-universal/-/droplet-darwin-universal-2.3.0.tgz#1d8659bc2869e5d30308622bcc6cb230030d738e"
integrity sha512-TxVpoVDI9aGuBCHA8HktbrIkS/C1gu5laM5+ZbIZkXnIUpTicJIbHRyneXJ4MLnW703gUbW8LTISgm7xKwZJsg== integrity sha512-4V/HMnNtmHgn156pTpa3mVTAwTmO9jqtZrDcVko7PdSotEbXiwBpTFzbgb4bPafbPmkSNoRh4G9d3BLQCh4mgw==
"@drop-oss/droplet-darwin-x64@1.6.0": "@drop-oss/droplet-darwin-x64@2.3.0":
version "1.6.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-x64/-/droplet-darwin-x64-1.6.0.tgz#5d6a3c596eca706e40b35cdf49ada65e59c51b8d" resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-x64/-/droplet-darwin-x64-2.3.0.tgz#c7ff5dae8ba520866b7cd49714625ada8fa0a7c2"
integrity sha512-V/1xh4s16AmesDOEHiQ4vj9XQq6AWmXRY5RQf4RKBQqkxsHzmQoa37CTLK25Wf9OUoiJFGpnjViqKOFG4y5Q+g== integrity sha512-PUcNjE09N7qEFsbssKxL8rjmCt9AUYPz1yK34d8N2W9DboS1KI+PShWdd/NOk4GYzTJQuJhMp8wNcUrljfqXmQ==
"@drop-oss/droplet-linux-arm64-gnu@1.6.0": "@drop-oss/droplet-linux-arm64-gnu@2.3.0":
version "1.6.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-arm64-gnu/-/droplet-linux-arm64-gnu-1.6.0.tgz#265d5e7854c4c61081b8fd74b3e8305ea2c7b5ac" resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-arm64-gnu/-/droplet-linux-arm64-gnu-2.3.0.tgz#8819b34c5ff8bd8182c5cd0c3f1784dc2afd9507"
integrity sha512-WjaRl9VW0qE+YkOCaYuNIXzyBbps2lopbpeXELZ9/f/1jBfzfmIe4m6C2hMy4NWUcWnrBbiVTEjnq2cHj/TaBA== integrity sha512-6VyOwYu9sMrCL82UZOvvjU9G/4wHdA8P6q3+EDIVdABg5jVEYZsxkrT0Kp/5h9Xs0mPFNB/su8ZwB9FRQ63o1w==
"@drop-oss/droplet-linux-arm64-musl@1.6.0": "@drop-oss/droplet-linux-arm64-musl@2.3.0":
version "1.6.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-arm64-musl/-/droplet-linux-arm64-musl-1.6.0.tgz#7126e194e5ef9018d61ef7dd0cc3af80734e00e2" resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-arm64-musl/-/droplet-linux-arm64-musl-2.3.0.tgz#06601aa8af4bffeb26956ff79ed494265e313342"
integrity sha512-B8KoBYk0YVUZIL+etCcOc99NuoBcTm6KDOIQkN9SHWC4YLRu8um3w8DHzv4VV3arUnEGjyDHuraaOSONfP6NqA== integrity sha512-2BZreAg1XOBxr+iY2hFcX4x6bFC7AKXkIHa9130rmStH/HxnGq6K5H49eJd6ezzNMH/lQ7Sm7uJP2+sH8mjeCw==
"@drop-oss/droplet-linux-riscv64-gnu@1.6.0": "@drop-oss/droplet-linux-riscv64-gnu@2.3.0":
version "1.6.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-riscv64-gnu/-/droplet-linux-riscv64-gnu-1.6.0.tgz#40d060eafaca08b47a468950d7dc5ec4f1fb2a5a" resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-riscv64-gnu/-/droplet-linux-riscv64-gnu-2.3.0.tgz#6d5629631aeeceadb292998e21b6e2b2cf839bdc"
integrity sha512-nbNr/38EX8Mjj20+paohlOD35apmaNKZan4OO97KOwvq5oZ/pXbkjOGC0zkpsizyxbwKx7Jl4Se7teRVPWWVWw== integrity sha512-E7i86Q8IU7rh2FVtXa0NxoGRhB7AZU+AWPumTAuDQS3xPg3sj+c3q/A7YI1Ay4lnvzR/fevP2p/7iSJUEDcchQ==
"@drop-oss/droplet-linux-x64-gnu@1.6.0": "@drop-oss/droplet-linux-x64-gnu@2.3.0":
version "1.6.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-x64-gnu/-/droplet-linux-x64-gnu-1.6.0.tgz#c3a8408644194e59ac2110229e9a99885b3bc533" resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-x64-gnu/-/droplet-linux-x64-gnu-2.3.0.tgz#a924aada38dbc54f6967b17435c10bf3b6e6ffb0"
integrity sha512-n/zA1ftqGey5yQK/1HiCok3MaLA4stVTzQEuRUzyq8BQ1BC6TmKCgdFnI4Q3tuGm3/Mz2CCbfbHY4bYwND9qOQ== integrity sha512-eIHhhoSgpvMAc9tn/K0ldZRXvDe1Xyd9okSSqaclCEKjdVfWU8UMycUz1SzQH9YefiqEB4Qjd3y1iRgaEa8niA==
"@drop-oss/droplet-linux-x64-musl@1.6.0": "@drop-oss/droplet-linux-x64-musl@2.3.0":
version "1.6.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-x64-musl/-/droplet-linux-x64-musl-1.6.0.tgz#206b5c85b02b7fdf53bc5f0cdf68a9d9a7d501cd" resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-x64-musl/-/droplet-linux-x64-musl-2.3.0.tgz#4eb71112f7641e1fad3b53f5f8d1b98b9cb84bf0"
integrity sha512-egZWqKK1+vHoVKNuMle2Kn8WbbJ7Y9WJScUNXjF8hdUDNo9eHwJT/DfnA+BhvFQuJXkU58vwv6MqZ5VLdOsGiA== integrity sha512-0taR945NvK+xNBicSYriKDJgBxpcozzgcALDp/cX2UaYV9cb5PF/xw80DArCyUDvKOfRzeFALx4KRC2ghPr6tw==
"@drop-oss/droplet-win32-arm64-msvc@1.6.0": "@drop-oss/droplet-win32-arm64-msvc@2.3.0":
version "1.6.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-win32-arm64-msvc/-/droplet-win32-arm64-msvc-1.6.0.tgz#fbb0387536f5b2a88f03877d730f7f863646ce08" resolved "https://registry.yarnpkg.com/@drop-oss/droplet-win32-arm64-msvc/-/droplet-win32-arm64-msvc-2.3.0.tgz#36568f87024eb48ce7e82d76ea83a2c6ec25a856"
integrity sha512-AwGYHae8ZmQV2QGp+3B0DhsBdYynrZ4AS1xNc+U1tXt5CiMp9wLLM/4a+WySYHX7XrEo8pKmRRa0I8QdAdxk5A== integrity sha512-5HkO98h/PboM+/wPulKVGFTklijlqht8w13iW1ipUcRFsOHmS1o8nejjLL7KEr2X8G4JwYOqBeX8tY3OhaU9bw==
"@drop-oss/droplet-win32-x64-msvc@1.6.0": "@drop-oss/droplet-win32-x64-msvc@2.3.0":
version "1.6.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-win32-x64-msvc/-/droplet-win32-x64-msvc-1.6.0.tgz#600058775641b4c5c051291e5a13135aa1ae28bb" resolved "https://registry.yarnpkg.com/@drop-oss/droplet-win32-x64-msvc/-/droplet-win32-x64-msvc-2.3.0.tgz#e794ea7cfdc0ea148707e4f3e60f2aa547328c03"
integrity sha512-Viz+J87rF7I++nLpPBvdhsjUQAHivA6wSHrBXa+4MwIymUvlQXcvNReFqzObRH4eiuiY4e3s3t9X7+paqd847Q== integrity sha512-6lNXOMyy9sPaO4wbklOIr2jbuvZHIVrd+dXu2UOI2YqFlHdxiDD1sZnqSZmlfCP58yeA+SpTfhxDHwUHJTFI/g==
"@drop-oss/droplet@1.6.0": "@drop-oss/droplet@2.3.0":
version "1.6.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/@drop-oss/droplet/-/droplet-1.6.0.tgz#b6aa382dc5df494c4233a2bd8f19721878edad71" resolved "https://registry.yarnpkg.com/@drop-oss/droplet/-/droplet-2.3.0.tgz#eb2891346cf7fadcc847d5dee37674fc1106d2fc"
integrity sha512-nTZvLo+GFLlpxgFlObP4zitVctz02bRD3ZSVDiMv7jXxYK0V/GktITJFcKK0J87ZRxneoFHYbLs1lH3MFYoSIw== integrity sha512-ffEoS3LYBfPm0++p7f7F/NkYH5PfauQzuj1gTz7qVWZOSP5VQWYhOc9BEg0fsCCzTB/mct0jwOsK92URmthpxA==
optionalDependencies: optionalDependencies:
"@drop-oss/droplet-darwin-arm64" "1.6.0" "@drop-oss/droplet-darwin-arm64" "2.3.0"
"@drop-oss/droplet-darwin-universal" "1.6.0" "@drop-oss/droplet-darwin-universal" "2.3.0"
"@drop-oss/droplet-darwin-x64" "1.6.0" "@drop-oss/droplet-darwin-x64" "2.3.0"
"@drop-oss/droplet-linux-arm64-gnu" "1.6.0" "@drop-oss/droplet-linux-arm64-gnu" "2.3.0"
"@drop-oss/droplet-linux-arm64-musl" "1.6.0" "@drop-oss/droplet-linux-arm64-musl" "2.3.0"
"@drop-oss/droplet-linux-riscv64-gnu" "1.6.0" "@drop-oss/droplet-linux-riscv64-gnu" "2.3.0"
"@drop-oss/droplet-linux-x64-gnu" "1.6.0" "@drop-oss/droplet-linux-x64-gnu" "2.3.0"
"@drop-oss/droplet-linux-x64-musl" "1.6.0" "@drop-oss/droplet-linux-x64-musl" "2.3.0"
"@drop-oss/droplet-win32-arm64-msvc" "1.6.0" "@drop-oss/droplet-win32-arm64-msvc" "2.3.0"
"@drop-oss/droplet-win32-x64-msvc" "1.6.0" "@drop-oss/droplet-win32-x64-msvc" "2.3.0"
"@emnapi/core@^1.4.3": "@emnapi/core@^1.4.3":
version "1.4.5" version "1.4.5"