diff --git a/components/GameEditor/Version.vue b/components/GameEditor/Version.vue index 4e832b7..d2d38ad 100644 --- a/components/GameEditor/Version.vue +++ b/components/GameEditor/Version.vue @@ -22,21 +22,17 @@ {{ - unimportedVersions.length > 0 + canImport ? $t("library.admin.import.version.import") : $t("library.admin.import.version.noVersions") }} @@ -124,10 +120,16 @@ import { ExclamationCircleIcon } from "@heroicons/vue/24/outline"; // TODO implement UI for this page -defineProps<{ unimportedVersions: string[] }>(); +const props = defineProps<{ unimportedVersions: string[] }>(); const { t } = useI18n(); +const hasDeleted = ref(false); + +const canImport = computed( + () => hasDeleted.value || props.unimportedVersions.length > 0, +); + type GameAndVersions = GameModel & { versions: GameVersionModel[] }; const game = defineModel>() as Ref< SerializeObject @@ -176,6 +178,7 @@ async function deleteVersion(versionName: string) { game.value.versions.findIndex((e) => e.versionName === versionName), 1, ); + hasDeleted.value = true; } catch (e) { createModal( ModalType.Notification, diff --git a/components/LanguageSelector.vue b/components/LanguageSelector.vue index d929c29..3f84e80 100644 --- a/components/LanguageSelector.vue +++ b/components/LanguageSelector.vue @@ -18,8 +18,12 @@ - {{ $t("welcome") }} + + {{ $t("welcome") }} + + diff --git a/package.json b/package.json index 4f84692..3a68d92 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@discordapp/twemoji": "^16.0.1", - "@drop-oss/droplet": "1.6.0", + "@drop-oss/droplet": "2.3.0", "@headlessui/vue": "^1.7.23", "@heroicons/vue": "^2.1.5", "@lobomfz/prismark": "0.0.3", diff --git a/pages/admin/task/index.vue b/pages/admin/task/index.vue index 29b9740..b9f3f6c 100644 --- a/pages/admin/task/index.vue +++ b/pages/admin/task/index.vue @@ -47,7 +47,7 @@ /> - {{ parseTaskLog(task.value.log.at(-1) ?? "").message }} + {{ parseTaskLog(task.value.log.at(-1)).message }} - {{ parseTaskLog(task.log.at(-1) ?? "").message }} + {{ parseTaskLog(task.log.at(-1)).message }} { statusCode: 500, statusMessage: "Failed to create read stream", }); + let length = 0; await gameReadStream.pipeTo( new WritableStream({ 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(); diff --git a/server/internal/library/index.ts b/server/internal/library/index.ts index 56bed15..b26f6df 100644 --- a/server/internal/library/index.ts +++ b/server/internal/library/index.ts @@ -15,12 +15,17 @@ import { GameNotFoundError, type LibraryProvider } from "./provider"; import { logger } from "../logging"; 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 { private libraries: Map> = new Map(); - private gameImportLocks: Map> = new Map(); // Library ID to Library Path - private versionImportLocks: Map> = new Map(); // Game ID to Version Name - addLibrary(library: LibraryProvider) { this.libraries.set(library.id(), library); } @@ -58,12 +63,10 @@ class LibraryManager { for (const [id, library] of this.libraries.entries()) { const providerGames = await library.listGames(); - const locks = this.gameImportLocks.get(id) ?? []; const providerUnimportedGames = providerGames.filter( (libraryPath) => - instanceGames[id] && - !instanceGames[id][libraryPath] && - !locks.includes(libraryPath), + !instanceGames[id]?.[libraryPath] && + !taskHandler.hasTask(createGameImportTaskId(id, libraryPath)), ); unimportedGames[id] = providerUnimportedGames; } @@ -93,7 +96,7 @@ class LibraryManager { const unimportedVersions = versions.filter( (e) => game.versions.findIndex((v) => v.versionName == e) == -1 && - !(this.versionImportLocks.get(game.id) ?? []).includes(e), + !taskHandler.hasTask(createVersionImportTaskId(game.id, e)), ); return unimportedVersions; } catch (e) { @@ -177,7 +180,8 @@ class LibraryManager { for (const filename of files) { const basename = path.basename(filename); 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 checkExt of checkExts) { 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( gameId: string, versionName: string, @@ -295,7 +235,7 @@ class LibraryManager { umuId: string; }, ) { - const taskId = `import:${gameId}:${versionName}`; + const taskId = createVersionImportTaskId(gameId, versionName); const platform = parsePlatform(metadata.platform); if (!platform) return undefined; @@ -309,8 +249,6 @@ class LibraryManager { const library = this.libraries.get(game.libraryId); if (!library) return undefined; - await this.lockVersion(gameId, versionName); - taskHandler.create({ id: taskId, taskGroup: "import:game", @@ -387,9 +325,6 @@ class LibraryManager { progress(100); }, - async finally() { - await libraryManager.unlockVersion(gameId, versionName); - }, }); return taskId; @@ -403,7 +338,7 @@ class LibraryManager { ) { const library = this.libraries.get(libraryId); if (!library) return undefined; - return library.peekFile(game, version, filename); + return await library.peekFile(game, version, filename); } async readFile( @@ -415,7 +350,7 @@ class LibraryManager { ) { const library = this.libraries.get(libraryId); if (!library) return undefined; - return library.readFile(game, version, filename, options); + return await library.readFile(game, version, filename, options); } } diff --git a/server/internal/library/providers/filesystem.ts b/server/internal/library/providers/filesystem.ts index 1b8f549..de59b46 100644 --- a/server/internal/library/providers/filesystem.ts +++ b/server/internal/library/providers/filesystem.ts @@ -7,12 +7,14 @@ import { import { LibraryBackend } from "~/prisma/client/enums"; import fs from "fs"; import path from "path"; -import droplet from "@drop-oss/droplet"; +import droplet, { DropletHandler } from "@drop-oss/droplet"; export const FilesystemProviderConfig = type({ baseDir: "string", }); +export const DROPLET_HANDLER = new DropletHandler(); + export class FilesystemProvider implements LibraryProvider { @@ -57,7 +59,7 @@ export class FilesystemProvider const versionDirs = fs.readdirSync(gameDir); const validVersionDirs = versionDirs.filter((e) => { const fullDir = path.join(this.config.baseDir, game, e); - return droplet.hasBackendForPath(fullDir); + return DROPLET_HANDLER.hasBackendForPath(fullDir); }); return validVersionDirs; } @@ -65,7 +67,7 @@ export class FilesystemProvider async versionReaddir(game: string, version: string): Promise { const versionDir = path.join(this.config.baseDir, game, version); if (!fs.existsSync(versionDir)) throw new VersionNotFoundError(); - return droplet.listFiles(versionDir); + return DROPLET_HANDLER.listFiles(versionDir); } async generateDropletManifest( @@ -77,10 +79,16 @@ export class FilesystemProvider const versionDir = path.join(this.config.baseDir, game, version); if (!fs.existsSync(versionDir)) throw new VersionNotFoundError(); const manifest = await new Promise((r, j) => - droplet.generateManifest(versionDir, progress, log, (err, result) => { - if (err) return j(err); - r(result); - }), + droplet.generateManifest( + DROPLET_HANDLER, + versionDir, + progress, + log, + (err, result) => { + if (err) return j(err); + r(result); + }, + ), ); return manifest; } @@ -88,7 +96,7 @@ export class FilesystemProvider async peekFile(game: string, version: string, filename: string) { const filepath = path.join(this.config.baseDir, game, version); if (!fs.existsSync(filepath)) return undefined; - const stat = droplet.peekFile(filepath, filename); + const stat = DROPLET_HANDLER.peekFile(filepath, filename); return { size: Number(stat) }; } @@ -100,13 +108,17 @@ export class FilesystemProvider ) { const filepath = path.join(this.config.baseDir, game, version); if (!fs.existsSync(filepath)) return undefined; - const stream = droplet.readFile( - filepath, - filename, - options?.start ? BigInt(options.start) : undefined, - options?.end ? BigInt(options.end) : undefined, - ); - if (!stream) return undefined; + let stream; + while (!(stream instanceof ReadableStream)) { + const v = DROPLET_HANDLER.readFile( + filepath, + filename, + options?.start ? BigInt(options.start) : undefined, + options?.end ? BigInt(options.end) : undefined, + ); + if (!v) return undefined; + stream = v.getStream() as ReadableStream; + } return stream; } diff --git a/server/internal/library/providers/flat.ts b/server/internal/library/providers/flat.ts index 5bb7157..9ebb5ef 100644 --- a/server/internal/library/providers/flat.ts +++ b/server/internal/library/providers/flat.ts @@ -5,6 +5,7 @@ import { LibraryBackend } from "~/prisma/client/enums"; import fs from "fs"; import path from "path"; import droplet from "@drop-oss/droplet"; +import { DROPLET_HANDLER } from "./filesystem"; export const FlatFilesystemProviderConfig = type({ baseDir: "string", @@ -46,7 +47,7 @@ export class FlatFilesystemProvider const versionDirs = fs.readdirSync(this.config.baseDir); const validVersionDirs = versionDirs.filter((e) => { const fullDir = path.join(this.config.baseDir, e); - return droplet.hasBackendForPath(fullDir); + return DROPLET_HANDLER.hasBackendForPath(fullDir); }); return validVersionDirs; } @@ -63,7 +64,7 @@ export class FlatFilesystemProvider async versionReaddir(game: string, _version: string) { const versionDir = path.join(this.config.baseDir, game); if (!fs.existsSync(versionDir)) throw new VersionNotFoundError(); - return droplet.listFiles(versionDir); + return DROPLET_HANDLER.listFiles(versionDir); } async generateDropletManifest( @@ -75,17 +76,23 @@ export class FlatFilesystemProvider const versionDir = path.join(this.config.baseDir, game); if (!fs.existsSync(versionDir)) throw new VersionNotFoundError(); const manifest = await new Promise((r, j) => - droplet.generateManifest(versionDir, progress, log, (err, result) => { - if (err) return j(err); - r(result); - }), + droplet.generateManifest( + DROPLET_HANDLER, + versionDir, + progress, + log, + (err, result) => { + if (err) return j(err); + r(result); + }, + ), ); return manifest; } async peekFile(game: string, _version: string, filename: string) { const filepath = path.join(this.config.baseDir, game); if (!fs.existsSync(filepath)) return undefined; - const stat = droplet.peekFile(filepath, filename); + const stat = DROPLET_HANDLER.peekFile(filepath, filename); return { size: Number(stat) }; } async readFile( @@ -96,7 +103,7 @@ export class FlatFilesystemProvider ) { const filepath = path.join(this.config.baseDir, game); if (!fs.existsSync(filepath)) return undefined; - const stream = droplet.readFile( + const stream = DROPLET_HANDLER.readFile( filepath, filename, options?.start ? BigInt(options.start) : undefined, @@ -104,6 +111,6 @@ export class FlatFilesystemProvider ); if (!stream) return undefined; - return stream; + return stream.getStream(); } } diff --git a/server/internal/metadata/index.ts b/server/internal/metadata/index.ts index 05a2267..88025e8 100644 --- a/server/internal/metadata/index.ts +++ b/server/internal/metadata/index.ts @@ -18,7 +18,7 @@ import taskHandler, { wrapTaskContext } from "../tasks"; import { randomUUID } from "crypto"; import { fuzzy } from "fast-fuzzy"; import { logger } from "~/server/internal/logging"; -import libraryManager from "../library"; +import { createGameImportTaskId } from "../library"; import type { GameTagModel } from "~/prisma/client/models"; export class MissingMetadataProviderConfig extends Error { @@ -185,11 +185,9 @@ export class MetadataHandler { }); if (existing) return undefined; - await libraryManager.lockGame(libraryId, libraryPath); - const gameId = randomUUID(); - const taskId = `import:${gameId}`; + const taskId = createGameImportTaskId(libraryId, libraryPath); await taskHandler.create({ name: `Import game "${result.name}" (${libraryPath})`, id: taskId, @@ -280,9 +278,6 @@ export class MetadataHandler { logger.info(`Finished game import.`); progress(100); }, - async finally() { - await libraryManager.unlockGame(libraryId, libraryPath); - }, }); return taskId; diff --git a/server/internal/tasks/index.ts b/server/internal/tasks/index.ts index e7ee65f..f6f93fe 100644 --- a/server/internal/tasks/index.ts +++ b/server/internal/tasks/index.ts @@ -73,6 +73,8 @@ class TaskHandler { } async create(task: Task) { + if (this.hasTask(task.id)) throw new Error("Task with ID already exists."); + let updateCollectTimeout: NodeJS.Timeout | undefined; let updateCollectResolves: Array<(value: unknown) => void> = []; let logOffset: number = 0; @@ -206,8 +208,6 @@ class TaskHandler { }; } - if (task.finally) await task.finally(); - taskEntry.endTime = new Date().toISOString(); await updateAllClients(); @@ -247,7 +247,10 @@ class TaskHandler { ) { const task = this.taskPool.get(taskId) ?? - (await prisma.task.findUnique({ where: { id: taskId } })); + (await prisma.task.findFirst({ + where: { id: taskId }, + orderBy: { started: "desc" }, + })); if (!task) { peer.send( `error/${taskId}/Unknown task/Drop couldn't find the task you're looking for.`, @@ -324,6 +327,10 @@ class TaskHandler { .toArray(); } + hasTask(id: string) { + return this.taskPool.has(id); + } + dailyTasks() { return this.dailyScheduledTasks; } @@ -429,7 +436,6 @@ export interface Task { taskGroup: TaskGroup; name: string; run: (context: TaskRunContext) => Promise; - finally?: () => Promise | void; acls: GlobalACL[]; } diff --git a/utils/parseTaskLog.ts b/utils/parseTaskLog.ts index 92482df..4cc0b4f 100644 --- a/utils/parseTaskLog.ts +++ b/utils/parseTaskLog.ts @@ -1,6 +1,9 @@ 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); return { diff --git a/yarn.lock b/yarn.lock index 7461432..7d3f9a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -342,71 +342,71 @@ jsonfile "^5.0.0" universalify "^0.1.2" -"@drop-oss/droplet-darwin-arm64@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-arm64/-/droplet-darwin-arm64-1.6.0.tgz#9697e38c46b02192e8e180b7deaaa20a389a9b0d" - integrity sha512-EqTx+Mk5SHP17n19r5coacUDd7lklT4opJ2keNQyGsQjrcf+9FeCX1O5Y+PGIjpQK6UkAVdnBqM+jR7NeFmkAQ== +"@drop-oss/droplet-darwin-arm64@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-arm64/-/droplet-darwin-arm64-2.3.0.tgz#f4f0ded9c9f5b5cac25dd56f59817e1c13e865ab" + integrity sha512-5k1VwGZTFc61FvKyL4cvYxFYB7aCY5cWCo0Q7yTkkj+KR+ewH6ucylU8kDG7M+aBLvbC/zbntXUp4RtYZi4AZQ== -"@drop-oss/droplet-darwin-universal@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-universal/-/droplet-darwin-universal-1.6.0.tgz#2f780416052ac7d1752b0a7828dc3ef9d1789c92" - integrity sha512-TxVpoVDI9aGuBCHA8HktbrIkS/C1gu5laM5+ZbIZkXnIUpTicJIbHRyneXJ4MLnW703gUbW8LTISgm7xKwZJsg== +"@drop-oss/droplet-darwin-universal@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-universal/-/droplet-darwin-universal-2.3.0.tgz#1d8659bc2869e5d30308622bcc6cb230030d738e" + integrity sha512-4V/HMnNtmHgn156pTpa3mVTAwTmO9jqtZrDcVko7PdSotEbXiwBpTFzbgb4bPafbPmkSNoRh4G9d3BLQCh4mgw== -"@drop-oss/droplet-darwin-x64@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-x64/-/droplet-darwin-x64-1.6.0.tgz#5d6a3c596eca706e40b35cdf49ada65e59c51b8d" - integrity sha512-V/1xh4s16AmesDOEHiQ4vj9XQq6AWmXRY5RQf4RKBQqkxsHzmQoa37CTLK25Wf9OUoiJFGpnjViqKOFG4y5Q+g== +"@drop-oss/droplet-darwin-x64@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-x64/-/droplet-darwin-x64-2.3.0.tgz#c7ff5dae8ba520866b7cd49714625ada8fa0a7c2" + integrity sha512-PUcNjE09N7qEFsbssKxL8rjmCt9AUYPz1yK34d8N2W9DboS1KI+PShWdd/NOk4GYzTJQuJhMp8wNcUrljfqXmQ== -"@drop-oss/droplet-linux-arm64-gnu@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-arm64-gnu/-/droplet-linux-arm64-gnu-1.6.0.tgz#265d5e7854c4c61081b8fd74b3e8305ea2c7b5ac" - integrity sha512-WjaRl9VW0qE+YkOCaYuNIXzyBbps2lopbpeXELZ9/f/1jBfzfmIe4m6C2hMy4NWUcWnrBbiVTEjnq2cHj/TaBA== +"@drop-oss/droplet-linux-arm64-gnu@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-arm64-gnu/-/droplet-linux-arm64-gnu-2.3.0.tgz#8819b34c5ff8bd8182c5cd0c3f1784dc2afd9507" + integrity sha512-6VyOwYu9sMrCL82UZOvvjU9G/4wHdA8P6q3+EDIVdABg5jVEYZsxkrT0Kp/5h9Xs0mPFNB/su8ZwB9FRQ63o1w== -"@drop-oss/droplet-linux-arm64-musl@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-arm64-musl/-/droplet-linux-arm64-musl-1.6.0.tgz#7126e194e5ef9018d61ef7dd0cc3af80734e00e2" - integrity sha512-B8KoBYk0YVUZIL+etCcOc99NuoBcTm6KDOIQkN9SHWC4YLRu8um3w8DHzv4VV3arUnEGjyDHuraaOSONfP6NqA== +"@drop-oss/droplet-linux-arm64-musl@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-arm64-musl/-/droplet-linux-arm64-musl-2.3.0.tgz#06601aa8af4bffeb26956ff79ed494265e313342" + integrity sha512-2BZreAg1XOBxr+iY2hFcX4x6bFC7AKXkIHa9130rmStH/HxnGq6K5H49eJd6ezzNMH/lQ7Sm7uJP2+sH8mjeCw== -"@drop-oss/droplet-linux-riscv64-gnu@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-riscv64-gnu/-/droplet-linux-riscv64-gnu-1.6.0.tgz#40d060eafaca08b47a468950d7dc5ec4f1fb2a5a" - integrity sha512-nbNr/38EX8Mjj20+paohlOD35apmaNKZan4OO97KOwvq5oZ/pXbkjOGC0zkpsizyxbwKx7Jl4Se7teRVPWWVWw== +"@drop-oss/droplet-linux-riscv64-gnu@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-riscv64-gnu/-/droplet-linux-riscv64-gnu-2.3.0.tgz#6d5629631aeeceadb292998e21b6e2b2cf839bdc" + integrity sha512-E7i86Q8IU7rh2FVtXa0NxoGRhB7AZU+AWPumTAuDQS3xPg3sj+c3q/A7YI1Ay4lnvzR/fevP2p/7iSJUEDcchQ== -"@drop-oss/droplet-linux-x64-gnu@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-x64-gnu/-/droplet-linux-x64-gnu-1.6.0.tgz#c3a8408644194e59ac2110229e9a99885b3bc533" - integrity sha512-n/zA1ftqGey5yQK/1HiCok3MaLA4stVTzQEuRUzyq8BQ1BC6TmKCgdFnI4Q3tuGm3/Mz2CCbfbHY4bYwND9qOQ== +"@drop-oss/droplet-linux-x64-gnu@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-x64-gnu/-/droplet-linux-x64-gnu-2.3.0.tgz#a924aada38dbc54f6967b17435c10bf3b6e6ffb0" + integrity sha512-eIHhhoSgpvMAc9tn/K0ldZRXvDe1Xyd9okSSqaclCEKjdVfWU8UMycUz1SzQH9YefiqEB4Qjd3y1iRgaEa8niA== -"@drop-oss/droplet-linux-x64-musl@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-x64-musl/-/droplet-linux-x64-musl-1.6.0.tgz#206b5c85b02b7fdf53bc5f0cdf68a9d9a7d501cd" - integrity sha512-egZWqKK1+vHoVKNuMle2Kn8WbbJ7Y9WJScUNXjF8hdUDNo9eHwJT/DfnA+BhvFQuJXkU58vwv6MqZ5VLdOsGiA== +"@drop-oss/droplet-linux-x64-musl@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-x64-musl/-/droplet-linux-x64-musl-2.3.0.tgz#4eb71112f7641e1fad3b53f5f8d1b98b9cb84bf0" + integrity sha512-0taR945NvK+xNBicSYriKDJgBxpcozzgcALDp/cX2UaYV9cb5PF/xw80DArCyUDvKOfRzeFALx4KRC2ghPr6tw== -"@drop-oss/droplet-win32-arm64-msvc@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-win32-arm64-msvc/-/droplet-win32-arm64-msvc-1.6.0.tgz#fbb0387536f5b2a88f03877d730f7f863646ce08" - integrity sha512-AwGYHae8ZmQV2QGp+3B0DhsBdYynrZ4AS1xNc+U1tXt5CiMp9wLLM/4a+WySYHX7XrEo8pKmRRa0I8QdAdxk5A== +"@drop-oss/droplet-win32-arm64-msvc@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-win32-arm64-msvc/-/droplet-win32-arm64-msvc-2.3.0.tgz#36568f87024eb48ce7e82d76ea83a2c6ec25a856" + integrity sha512-5HkO98h/PboM+/wPulKVGFTklijlqht8w13iW1ipUcRFsOHmS1o8nejjLL7KEr2X8G4JwYOqBeX8tY3OhaU9bw== -"@drop-oss/droplet-win32-x64-msvc@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-win32-x64-msvc/-/droplet-win32-x64-msvc-1.6.0.tgz#600058775641b4c5c051291e5a13135aa1ae28bb" - integrity sha512-Viz+J87rF7I++nLpPBvdhsjUQAHivA6wSHrBXa+4MwIymUvlQXcvNReFqzObRH4eiuiY4e3s3t9X7+paqd847Q== +"@drop-oss/droplet-win32-x64-msvc@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-win32-x64-msvc/-/droplet-win32-x64-msvc-2.3.0.tgz#e794ea7cfdc0ea148707e4f3e60f2aa547328c03" + integrity sha512-6lNXOMyy9sPaO4wbklOIr2jbuvZHIVrd+dXu2UOI2YqFlHdxiDD1sZnqSZmlfCP58yeA+SpTfhxDHwUHJTFI/g== -"@drop-oss/droplet@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet/-/droplet-1.6.0.tgz#b6aa382dc5df494c4233a2bd8f19721878edad71" - integrity sha512-nTZvLo+GFLlpxgFlObP4zitVctz02bRD3ZSVDiMv7jXxYK0V/GktITJFcKK0J87ZRxneoFHYbLs1lH3MFYoSIw== +"@drop-oss/droplet@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet/-/droplet-2.3.0.tgz#eb2891346cf7fadcc847d5dee37674fc1106d2fc" + integrity sha512-ffEoS3LYBfPm0++p7f7F/NkYH5PfauQzuj1gTz7qVWZOSP5VQWYhOc9BEg0fsCCzTB/mct0jwOsK92URmthpxA== optionalDependencies: - "@drop-oss/droplet-darwin-arm64" "1.6.0" - "@drop-oss/droplet-darwin-universal" "1.6.0" - "@drop-oss/droplet-darwin-x64" "1.6.0" - "@drop-oss/droplet-linux-arm64-gnu" "1.6.0" - "@drop-oss/droplet-linux-arm64-musl" "1.6.0" - "@drop-oss/droplet-linux-riscv64-gnu" "1.6.0" - "@drop-oss/droplet-linux-x64-gnu" "1.6.0" - "@drop-oss/droplet-linux-x64-musl" "1.6.0" - "@drop-oss/droplet-win32-arm64-msvc" "1.6.0" - "@drop-oss/droplet-win32-x64-msvc" "1.6.0" + "@drop-oss/droplet-darwin-arm64" "2.3.0" + "@drop-oss/droplet-darwin-universal" "2.3.0" + "@drop-oss/droplet-darwin-x64" "2.3.0" + "@drop-oss/droplet-linux-arm64-gnu" "2.3.0" + "@drop-oss/droplet-linux-arm64-musl" "2.3.0" + "@drop-oss/droplet-linux-riscv64-gnu" "2.3.0" + "@drop-oss/droplet-linux-x64-gnu" "2.3.0" + "@drop-oss/droplet-linux-x64-musl" "2.3.0" + "@drop-oss/droplet-win32-arm64-msvc" "2.3.0" + "@drop-oss/droplet-win32-x64-msvc" "2.3.0" "@emnapi/core@^1.4.3": version "1.4.5"
- {{ parseTaskLog(task.value.log.at(-1) ?? "").message }} + {{ parseTaskLog(task.value.log.at(-1)).message }}
- {{ parseTaskLog(task.log.at(-1) ?? "").message }} + {{ parseTaskLog(task.log.at(-1)).message }}