mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-01-23 13:34:54 +03:00
feat: adding cloud sync
This commit is contained in:
parent
d88e06e289
commit
e64a414309
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,5 +1,5 @@
|
|||||||
.vscode
|
.vscode/
|
||||||
node_modules
|
node_modules/
|
||||||
hydra-download-manager/
|
hydra-download-manager/
|
||||||
fastlist.exe
|
fastlist.exe
|
||||||
__pycache__
|
__pycache__
|
||||||
@ -10,3 +10,4 @@ out
|
|||||||
.env
|
.env
|
||||||
.vite
|
.vite
|
||||||
sentry.properties
|
sentry.properties
|
||||||
|
ludusavi/
|
@ -3,6 +3,7 @@ productName: Hydra
|
|||||||
directories:
|
directories:
|
||||||
buildResources: build
|
buildResources: build
|
||||||
extraResources:
|
extraResources:
|
||||||
|
- ludusavi
|
||||||
- hydra-download-manager
|
- hydra-download-manager
|
||||||
- seeds
|
- seeds
|
||||||
- from: node_modules/create-desktop-shortcuts/src/windows.vbs
|
- from: node_modules/create-desktop-shortcuts/src/windows.vbs
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"start": "electron-vite preview",
|
"start": "electron-vite preview",
|
||||||
"dev": "electron-vite dev",
|
"dev": "electron-vite dev",
|
||||||
"build": "npm run typecheck && electron-vite build",
|
"build": "npm run typecheck && electron-vite build",
|
||||||
"postinstall": "electron-builder install-app-deps",
|
"postinstall": "electron-builder install-app-deps && node ./postinstall.cjs",
|
||||||
"build:unpack": "npm run build && electron-builder --dir",
|
"build:unpack": "npm run build && electron-builder --dir",
|
||||||
"build:win": "electron-vite build && electron-builder --win",
|
"build:win": "electron-vite build && electron-builder --win",
|
||||||
"build:mac": "electron-vite build && electron-builder --mac",
|
"build:mac": "electron-vite build && electron-builder --mac",
|
||||||
@ -42,6 +42,8 @@
|
|||||||
"@vanilla-extract/css": "^1.14.2",
|
"@vanilla-extract/css": "^1.14.2",
|
||||||
"@vanilla-extract/dynamic": "^2.1.1",
|
"@vanilla-extract/dynamic": "^2.1.1",
|
||||||
"@vanilla-extract/recipes": "^0.5.2",
|
"@vanilla-extract/recipes": "^0.5.2",
|
||||||
|
"adm-zip": "^0.5.16",
|
||||||
|
"archiver": "^7.0.1",
|
||||||
"auto-launch": "^5.0.6",
|
"auto-launch": "^5.0.6",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"better-sqlite3": "^11.2.1",
|
"better-sqlite3": "^11.2.1",
|
||||||
@ -86,8 +88,11 @@
|
|||||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||||
"@sentry/vite-plugin": "^2.20.1",
|
"@sentry/vite-plugin": "^2.20.1",
|
||||||
"@swc/core": "^1.4.16",
|
"@swc/core": "^1.4.16",
|
||||||
|
"@types/adm-zip": "^0.5.5",
|
||||||
|
"@types/archiver": "^6.0.2",
|
||||||
"@types/auto-launch": "^5.0.5",
|
"@types/auto-launch": "^5.0.5",
|
||||||
"@types/color": "^3.0.6",
|
"@types/color": "^3.0.6",
|
||||||
|
"@types/folder-hash": "^4.0.4",
|
||||||
"@types/jsdom": "^21.1.6",
|
"@types/jsdom": "^21.1.6",
|
||||||
"@types/jsonwebtoken": "^9.0.6",
|
"@types/jsonwebtoken": "^9.0.6",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
44
postinstall.cjs
Normal file
44
postinstall.cjs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
const { default: axios } = require("axios");
|
||||||
|
const util = require("node:util");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
|
||||||
|
const exec = util.promisify(require("node:child_process").exec);
|
||||||
|
|
||||||
|
const fileName = {
|
||||||
|
win32: "ludusavi-v0.25.0-win64.zip",
|
||||||
|
linux: "ludusavi-v0.25.0-linux.zip",
|
||||||
|
darwin: "ludusavi-v0.25.0-mac.zip",
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadLudusavi = async () => {
|
||||||
|
if (fs.existsSync("ludusavi")) {
|
||||||
|
console.log("Ludusavi already exists, skipping download...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = fileName[process.platform];
|
||||||
|
const downloadUrl = `https://github.com/mtkennerly/ludusavi/releases/download/v0.25.0/${file}`;
|
||||||
|
|
||||||
|
console.log(`Downloading ${file}...`);
|
||||||
|
|
||||||
|
const response = await axios.get(downloadUrl, { responseType: "stream" });
|
||||||
|
|
||||||
|
const stream = response.data.pipe(fs.createWriteStream(file));
|
||||||
|
|
||||||
|
stream.on("finish", async () => {
|
||||||
|
console.log(`Downloaded ${file}, extracting...`);
|
||||||
|
|
||||||
|
const pwd = process.cwd();
|
||||||
|
const targetPath = path.join(pwd, "ludusavi");
|
||||||
|
|
||||||
|
await exec(`npx extract-zip ${file} ${targetPath}`);
|
||||||
|
fs.chmodSync(path.join(targetPath, "ludusavi"), 0o755);
|
||||||
|
console.log("Extracted. Renaming folder...");
|
||||||
|
|
||||||
|
console.log(`Extracted ${file}, removing compressed downloaded file...`);
|
||||||
|
fs.rmSync(file);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
downloadLudusavi();
|
@ -12,4 +12,6 @@ export const seedsPath = app.isPackaged
|
|||||||
? path.join(process.resourcesPath, "seeds")
|
? path.join(process.resourcesPath, "seeds")
|
||||||
: path.join(__dirname, "..", "..", "seeds");
|
: path.join(__dirname, "..", "..", "seeds");
|
||||||
|
|
||||||
|
export const backupsPath = path.join(app.getPath("userData"), "Backups");
|
||||||
|
|
||||||
export const appVersion = app.getVersion();
|
export const appVersion = app.getVersion();
|
||||||
|
14
src/main/events/cloud-sync/check-game-cloud-sync-support.ts
Normal file
14
src/main/events/cloud-sync/check-game-cloud-sync-support.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
import { GameShop } from "@types";
|
||||||
|
import { Ludusavi } from "@main/services";
|
||||||
|
|
||||||
|
const checkGameCloudSyncSupport = async (
|
||||||
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
|
objectId: string,
|
||||||
|
shop: GameShop
|
||||||
|
) => {
|
||||||
|
const games = await Ludusavi.findGames(shop, objectId);
|
||||||
|
return games.length === 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
registerEvent("checkGameCloudSyncSupport", checkGameCloudSyncSupport);
|
9
src/main/events/cloud-sync/delete-game-artifact.ts
Normal file
9
src/main/events/cloud-sync/delete-game-artifact.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { HydraApi } from "@main/services";
|
||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
|
||||||
|
const deleteGameArtifact = async (
|
||||||
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
|
gameArtifactId: string
|
||||||
|
) => HydraApi.delete<{ ok: boolean }>(`/games/artifacts/${gameArtifactId}`);
|
||||||
|
|
||||||
|
registerEvent("deleteGameArtifact", deleteGameArtifact);
|
56
src/main/events/cloud-sync/download-game-artifact.ts
Normal file
56
src/main/events/cloud-sync/download-game-artifact.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { HydraApi, logger, Ludusavi, WindowManager } from "@main/services";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import AdmZip from "adm-zip";
|
||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
import axios from "axios";
|
||||||
|
import { app } from "electron";
|
||||||
|
import path from "node:path";
|
||||||
|
import { backupsPath } from "@main/constants";
|
||||||
|
import type { GameShop } from "@types";
|
||||||
|
|
||||||
|
const downloadGameArtifact = async (
|
||||||
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
|
objectId: string,
|
||||||
|
shop: GameShop,
|
||||||
|
gameArtifactId: string
|
||||||
|
) => {
|
||||||
|
const { downloadUrl, objectKey } = await HydraApi.post<{
|
||||||
|
downloadUrl: string;
|
||||||
|
objectKey: string;
|
||||||
|
}>(`/games/artifacts/${gameArtifactId}/download`);
|
||||||
|
|
||||||
|
const response = await axios.get(downloadUrl, {
|
||||||
|
responseType: "stream",
|
||||||
|
});
|
||||||
|
|
||||||
|
const zipLocation = path.join(app.getPath("userData"), objectKey);
|
||||||
|
const backupPath = path.join(backupsPath, `${shop}-${objectId}`);
|
||||||
|
|
||||||
|
const writer = fs.createWriteStream(zipLocation);
|
||||||
|
|
||||||
|
response.data.pipe(writer);
|
||||||
|
|
||||||
|
writer.on("error", (err) => {
|
||||||
|
logger.error("Failed to write zip", err);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
writer.on("close", () => {
|
||||||
|
const zip = new AdmZip(zipLocation);
|
||||||
|
zip.extractAllToAsync(backupPath, true, true, (err) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error("Failed to extract zip", err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ludusavi.restoreBackup(backupPath).then(() => {
|
||||||
|
WindowManager.mainWindow?.webContents.send(
|
||||||
|
`on-download-complete-${objectId}-${shop}`,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
registerEvent("downloadGameArtifact", downloadGameArtifact);
|
18
src/main/events/cloud-sync/get-game-artifacts.ts
Normal file
18
src/main/events/cloud-sync/get-game-artifacts.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { HydraApi } from "@main/services";
|
||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
import type { GameArtifact, GameShop } from "@types";
|
||||||
|
|
||||||
|
const getGameArtifacts = async (
|
||||||
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
|
objectId: string,
|
||||||
|
shop: GameShop
|
||||||
|
) => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
objectId,
|
||||||
|
shop,
|
||||||
|
});
|
||||||
|
|
||||||
|
return HydraApi.get<GameArtifact[]>(`/games/artifacts?${params.toString()}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
registerEvent("getGameArtifacts", getGameArtifacts);
|
17
src/main/events/cloud-sync/get-game-backup-preview.ts
Normal file
17
src/main/events/cloud-sync/get-game-backup-preview.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
import { GameShop } from "@types";
|
||||||
|
import { Ludusavi } from "@main/services";
|
||||||
|
import path from "node:path";
|
||||||
|
import { backupsPath } from "@main/constants";
|
||||||
|
|
||||||
|
const getGameBackupPreview = async (
|
||||||
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
|
objectId: string,
|
||||||
|
shop: GameShop
|
||||||
|
) => {
|
||||||
|
const backupPath = path.join(backupsPath, `${shop}-${objectId}`);
|
||||||
|
|
||||||
|
return Ludusavi.getBackupPreview(shop, objectId, backupPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
registerEvent("getGameBackupPreview", getGameBackupPreview);
|
101
src/main/events/cloud-sync/upload-save-game.ts
Normal file
101
src/main/events/cloud-sync/upload-save-game.ts
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { HydraApi, logger, Ludusavi, WindowManager } from "@main/services";
|
||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import archiver from "archiver";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
import { GameShop } from "@types";
|
||||||
|
import axios from "axios";
|
||||||
|
import os from "node:os";
|
||||||
|
import { app } from "electron";
|
||||||
|
import { backupsPath } from "@main/constants";
|
||||||
|
|
||||||
|
const compressBackupToArtifact = async (
|
||||||
|
shop: GameShop,
|
||||||
|
objectId: string,
|
||||||
|
cb: (zipLocation: string) => void
|
||||||
|
) => {
|
||||||
|
const backupPath = path.join(backupsPath, `${shop}-${objectId}`);
|
||||||
|
|
||||||
|
await Ludusavi.backupGame(shop, objectId, backupPath);
|
||||||
|
|
||||||
|
const archive = archiver("zip", {
|
||||||
|
zlib: { level: 9 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const zipLocation = path.join(
|
||||||
|
app.getPath("userData"),
|
||||||
|
`${crypto.randomUUID()}.zip`
|
||||||
|
);
|
||||||
|
|
||||||
|
const output = fs.createWriteStream(zipLocation);
|
||||||
|
|
||||||
|
output.on("close", () => {
|
||||||
|
cb(zipLocation);
|
||||||
|
});
|
||||||
|
|
||||||
|
output.on("error", (err) => {
|
||||||
|
logger.error("Failed to compress folder", err);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
archive.pipe(output);
|
||||||
|
|
||||||
|
archive.directory(backupPath, false);
|
||||||
|
archive.finalize();
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadSaveGame = async (
|
||||||
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
|
objectId: string,
|
||||||
|
shop: GameShop
|
||||||
|
) => {
|
||||||
|
compressBackupToArtifact(shop, objectId, (zipLocation) => {
|
||||||
|
fs.stat(zipLocation, async (err, stat) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error("Failed to get zip file stats", err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { uploadUrl } = await HydraApi.post<{
|
||||||
|
id: string;
|
||||||
|
uploadUrl: string;
|
||||||
|
}>("/games/artifacts", {
|
||||||
|
artifactLengthInBytes: stat.size,
|
||||||
|
shop,
|
||||||
|
objectId,
|
||||||
|
hostname: os.hostname(),
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.readFile(zipLocation, async (err, fileBuffer) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error("Failed to read zip file", err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.put(uploadUrl, fileBuffer, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/zip",
|
||||||
|
},
|
||||||
|
onUploadProgress: (progressEvent) => {
|
||||||
|
if (progressEvent.progress === 1) {
|
||||||
|
fs.rm(zipLocation, (err) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error("Failed to remove zip file", err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowManager.mainWindow?.webContents.send(
|
||||||
|
`on-upload-complete-${objectId}-${shop}`,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
registerEvent("uploadSaveGame", uploadSaveGame);
|
@ -58,6 +58,12 @@ import "./profile/update-profile";
|
|||||||
import "./profile/process-profile-image";
|
import "./profile/process-profile-image";
|
||||||
import "./profile/send-friend-request";
|
import "./profile/send-friend-request";
|
||||||
import "./profile/sync-friend-requests";
|
import "./profile/sync-friend-requests";
|
||||||
|
import "./cloud-sync/download-game-artifact";
|
||||||
|
import "./cloud-sync//get-game-artifacts";
|
||||||
|
import "./cloud-sync/get-game-backup-preview";
|
||||||
|
import "./cloud-sync/upload-save-game";
|
||||||
|
import "./cloud-sync/check-game-cloud-sync-support";
|
||||||
|
import "./cloud-sync/delete-game-artifact";
|
||||||
import { isPortableVersion } from "@main/helpers";
|
import { isPortableVersion } from "@main/helpers";
|
||||||
|
|
||||||
ipcMain.handle("ping", () => "pong");
|
ipcMain.handle("ping", () => "pong");
|
||||||
|
@ -33,6 +33,9 @@ const getNewProfileImageUrl = async (localImageUrl: string) => {
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": mimeType,
|
"Content-Type": mimeType,
|
||||||
},
|
},
|
||||||
|
onUploadProgress: (progressEvent) => {
|
||||||
|
console.log(progressEvent);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return profileImageUrl;
|
return profileImageUrl;
|
||||||
|
@ -9,3 +9,4 @@ export * from "./process-watcher";
|
|||||||
export * from "./main-loop";
|
export * from "./main-loop";
|
||||||
export * from "./repacks-manager";
|
export * from "./repacks-manager";
|
||||||
export * from "./hydra-api";
|
export * from "./hydra-api";
|
||||||
|
export * from "./ludusavi";
|
||||||
|
63
src/main/services/ludusavi.ts
Normal file
63
src/main/services/ludusavi.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { GameShop, LudusaviBackup } from "@types";
|
||||||
|
import Piscina from "piscina";
|
||||||
|
|
||||||
|
import { app } from "electron";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
import ludusaviWorkerPath from "../workers/ludusavi.worker?modulePath";
|
||||||
|
|
||||||
|
const binaryPath = app.isPackaged
|
||||||
|
? path.join(process.resourcesPath, "ludusavi", "ludusavi")
|
||||||
|
: path.join(__dirname, "..", "..", "ludusavi", "ludusavi");
|
||||||
|
|
||||||
|
export class Ludusavi {
|
||||||
|
private static worker = new Piscina({
|
||||||
|
filename: ludusaviWorkerPath,
|
||||||
|
workerData: {
|
||||||
|
binaryPath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
static async findGames(shop: GameShop, objectId: string): Promise<string[]> {
|
||||||
|
const games = await this.worker.run(
|
||||||
|
{ objectId, shop },
|
||||||
|
{ name: "findGames" }
|
||||||
|
);
|
||||||
|
|
||||||
|
return games;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async backupGame(
|
||||||
|
shop: GameShop,
|
||||||
|
objectId: string,
|
||||||
|
backupPath: string
|
||||||
|
): Promise<LudusaviBackup> {
|
||||||
|
const games = await this.findGames(shop, objectId);
|
||||||
|
if (!games.length) throw new Error("Game not found");
|
||||||
|
|
||||||
|
return this.worker.run(
|
||||||
|
{ title: games[0], backupPath },
|
||||||
|
{ name: "backupGame" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getBackupPreview(
|
||||||
|
shop: GameShop,
|
||||||
|
objectId: string,
|
||||||
|
backupPath: string
|
||||||
|
): Promise<LudusaviBackup | null> {
|
||||||
|
const games = await this.findGames(shop, objectId);
|
||||||
|
if (!games.length) return null;
|
||||||
|
|
||||||
|
const backupData = await this.worker.run(
|
||||||
|
{ title: games[0], backupPath, preview: true },
|
||||||
|
{ name: "backupGame" }
|
||||||
|
);
|
||||||
|
|
||||||
|
return backupData;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async restoreBackup(backupPath: string) {
|
||||||
|
return this.worker.run(backupPath, { name: "restoreBackup" });
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { GameShop } from "@types";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
export interface SteamGridResponse {
|
export interface SteamGridResponse {
|
||||||
@ -22,7 +23,7 @@ export interface SteamGridGameResponse {
|
|||||||
export const getSteamGridData = async (
|
export const getSteamGridData = async (
|
||||||
objectID: string,
|
objectID: string,
|
||||||
path: string,
|
path: string,
|
||||||
shop: string,
|
shop: GameShop,
|
||||||
params: Record<string, string> = {}
|
params: Record<string, string> = {}
|
||||||
): Promise<SteamGridResponse> => {
|
): Promise<SteamGridResponse> => {
|
||||||
const searchParams = new URLSearchParams(params);
|
const searchParams = new URLSearchParams(params);
|
||||||
|
61
src/main/workers/ludusavi.worker.ts
Normal file
61
src/main/workers/ludusavi.worker.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import type { GameShop, LudusaviBackup, LudusaviFindResult } from "@types";
|
||||||
|
import cp from "node:child_process";
|
||||||
|
|
||||||
|
import { workerData } from "node:worker_threads";
|
||||||
|
|
||||||
|
const { binaryPath } = workerData;
|
||||||
|
|
||||||
|
export const findGames = ({
|
||||||
|
shop,
|
||||||
|
objectId,
|
||||||
|
}: {
|
||||||
|
shop: GameShop;
|
||||||
|
objectId: string;
|
||||||
|
}) => {
|
||||||
|
const args = ["find", "--api"];
|
||||||
|
|
||||||
|
if (shop === "steam") {
|
||||||
|
args.push("--steam-id", objectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = cp.execFileSync(binaryPath, args);
|
||||||
|
|
||||||
|
const games = JSON.parse(result.toString("utf-8")) as LudusaviFindResult;
|
||||||
|
return Object.keys(games.games);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const backupGame = ({
|
||||||
|
title,
|
||||||
|
backupPath,
|
||||||
|
preview = false,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
backupPath: string;
|
||||||
|
preview?: boolean;
|
||||||
|
}) => {
|
||||||
|
const args = ["backup", title, "--api", "--force"];
|
||||||
|
|
||||||
|
if (preview) {
|
||||||
|
args.push("--preview");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backupPath) {
|
||||||
|
args.push("--path", backupPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = cp.execFileSync(binaryPath, args);
|
||||||
|
|
||||||
|
return JSON.parse(result.toString("utf-8")) as LudusaviBackup;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const restoreBackup = (backupPath: string) => {
|
||||||
|
const result = cp.execFileSync(binaryPath, [
|
||||||
|
"restore",
|
||||||
|
"--path",
|
||||||
|
backupPath,
|
||||||
|
"--api",
|
||||||
|
"--force",
|
||||||
|
]);
|
||||||
|
|
||||||
|
return JSON.parse(result.toString("utf-8")) as LudusaviBackup;
|
||||||
|
};
|
@ -115,6 +115,42 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
getDiskFreeSpace: (path: string) =>
|
getDiskFreeSpace: (path: string) =>
|
||||||
ipcRenderer.invoke("getDiskFreeSpace", path),
|
ipcRenderer.invoke("getDiskFreeSpace", path),
|
||||||
|
|
||||||
|
/* Cloud sync */
|
||||||
|
uploadSaveGame: (objectId: string, shop: GameShop) =>
|
||||||
|
ipcRenderer.invoke("uploadSaveGame", objectId, shop),
|
||||||
|
downloadGameArtifact: (
|
||||||
|
objectId: string,
|
||||||
|
shop: GameShop,
|
||||||
|
gameArtifactId: string
|
||||||
|
) =>
|
||||||
|
ipcRenderer.invoke("downloadGameArtifact", objectId, shop, gameArtifactId),
|
||||||
|
getGameArtifacts: (objectId: string, shop: GameShop) =>
|
||||||
|
ipcRenderer.invoke("getGameArtifacts", objectId, shop),
|
||||||
|
getGameBackupPreview: (objectId: string, shop: GameShop) =>
|
||||||
|
ipcRenderer.invoke("getGameBackupPreview", objectId, shop),
|
||||||
|
checkGameCloudSyncSupport: (objectId: string, shop: GameShop) =>
|
||||||
|
ipcRenderer.invoke("checkGameCloudSyncSupport", objectId, shop),
|
||||||
|
deleteGameArtifact: (gameArtifactId: string) =>
|
||||||
|
ipcRenderer.invoke("deleteGameArtifact", gameArtifactId),
|
||||||
|
onUploadComplete: (objectId: string, shop: GameShop, cb: () => void) => {
|
||||||
|
const listener = (_event: Electron.IpcRendererEvent) => cb();
|
||||||
|
ipcRenderer.on(`on-upload-complete-${objectId}-${shop}`, listener);
|
||||||
|
return () =>
|
||||||
|
ipcRenderer.removeListener(
|
||||||
|
`on-upload-complete-${objectId}-${shop}`,
|
||||||
|
listener
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onDownloadComplete: (objectId: string, shop: GameShop, cb: () => void) => {
|
||||||
|
const listener = (_event: Electron.IpcRendererEvent) => cb();
|
||||||
|
ipcRenderer.on(`on-download-complete-${objectId}-${shop}`, listener);
|
||||||
|
return () =>
|
||||||
|
ipcRenderer.removeListener(
|
||||||
|
`on-download-complete-${objectId}-${shop}`,
|
||||||
|
listener
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
/* Misc */
|
/* Misc */
|
||||||
ping: () => ipcRenderer.invoke("ping"),
|
ping: () => ipcRenderer.invoke("ping"),
|
||||||
getVersion: () => ipcRenderer.invoke("getVersion"),
|
getVersion: () => ipcRenderer.invoke("getVersion"),
|
||||||
|
1
src/renderer/src/assets/lottie/cloud.json
Normal file
1
src/renderer/src/assets/lottie/cloud.json
Normal file
File diff suppressed because one or more lines are too long
187
src/renderer/src/context/cloud-sync/cloud-sync.context.tsx
Normal file
187
src/renderer/src/context/cloud-sync/cloud-sync.context.tsx
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
import { gameBackupsTable } from "@renderer/dexie";
|
||||||
|
import { useToast } from "@renderer/hooks";
|
||||||
|
import type { LudusaviBackup, GameArtifact, GameShop } from "@types";
|
||||||
|
import React, {
|
||||||
|
createContext,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
|
export enum CloudSyncState {
|
||||||
|
New,
|
||||||
|
Different,
|
||||||
|
Same,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CloudSyncContext {
|
||||||
|
backupPreview: LudusaviBackup | null;
|
||||||
|
artifacts: GameArtifact[];
|
||||||
|
showCloudSyncModal: boolean;
|
||||||
|
supportsCloudSync: boolean | null;
|
||||||
|
backupState: CloudSyncState;
|
||||||
|
setShowCloudSyncModal: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
downloadGameArtifact: (gameArtifactId: string) => Promise<void>;
|
||||||
|
uploadSaveGame: () => Promise<void>;
|
||||||
|
deleteGameArtifact: (gameArtifactId: string) => Promise<{ ok: boolean }>;
|
||||||
|
restoringBackup: boolean;
|
||||||
|
uploadingBackup: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cloudSyncContext = createContext<CloudSyncContext>({
|
||||||
|
backupPreview: null,
|
||||||
|
showCloudSyncModal: false,
|
||||||
|
supportsCloudSync: null,
|
||||||
|
backupState: CloudSyncState.Unknown,
|
||||||
|
setShowCloudSyncModal: () => {},
|
||||||
|
downloadGameArtifact: async () => {},
|
||||||
|
uploadSaveGame: async () => {},
|
||||||
|
artifacts: [],
|
||||||
|
deleteGameArtifact: async () => ({ ok: false }),
|
||||||
|
restoringBackup: false,
|
||||||
|
uploadingBackup: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { Provider } = cloudSyncContext;
|
||||||
|
export const { Consumer: CloudSyncContextConsumer } = cloudSyncContext;
|
||||||
|
|
||||||
|
export interface CloudSyncContextProviderProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
objectId: string;
|
||||||
|
shop: GameShop;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CloudSyncContextProvider({
|
||||||
|
children,
|
||||||
|
objectId,
|
||||||
|
shop,
|
||||||
|
}: CloudSyncContextProviderProps) {
|
||||||
|
const [supportsCloudSync, setSupportsCloudSync] = useState<boolean | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [artifacts, setArtifacts] = useState<GameArtifact[]>([]);
|
||||||
|
const [showCloudSyncModal, setShowCloudSyncModal] = useState(false);
|
||||||
|
const [backupPreview, setBackupPreview] = useState<LudusaviBackup | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [restoringBackup, setRestoringBackup] = useState(false);
|
||||||
|
const [uploadingBackup, setUploadingBackup] = useState(false);
|
||||||
|
|
||||||
|
const { showSuccessToast } = useToast();
|
||||||
|
|
||||||
|
const downloadGameArtifact = useCallback(
|
||||||
|
async (gameArtifactId: string) => {
|
||||||
|
setRestoringBackup(true);
|
||||||
|
window.electron.downloadGameArtifact(objectId, shop, gameArtifactId);
|
||||||
|
},
|
||||||
|
[objectId, shop]
|
||||||
|
);
|
||||||
|
|
||||||
|
const getGameBackupPreview = useCallback(async () => {
|
||||||
|
window.electron.getGameArtifacts(objectId, shop).then((results) => {
|
||||||
|
setArtifacts(results);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.electron.getGameBackupPreview(objectId, shop).then((preview) => {
|
||||||
|
if (preview && Object.keys(preview.games).length) {
|
||||||
|
setBackupPreview(preview);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [objectId, shop]);
|
||||||
|
|
||||||
|
const uploadSaveGame = useCallback(async () => {
|
||||||
|
setUploadingBackup(true);
|
||||||
|
window.electron.uploadSaveGame(objectId, shop);
|
||||||
|
}, [objectId, shop]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const removeUploadCompleteListener = window.electron.onUploadComplete(
|
||||||
|
objectId,
|
||||||
|
shop,
|
||||||
|
() => {
|
||||||
|
showSuccessToast("backup_uploaded");
|
||||||
|
|
||||||
|
setUploadingBackup(false);
|
||||||
|
gameBackupsTable.add({
|
||||||
|
objectId,
|
||||||
|
shop,
|
||||||
|
createdAt: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
getGameBackupPreview();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeDownloadCompleteListener = window.electron.onDownloadComplete(
|
||||||
|
objectId,
|
||||||
|
shop,
|
||||||
|
() => {
|
||||||
|
showSuccessToast("backup_restored");
|
||||||
|
|
||||||
|
setRestoringBackup(false);
|
||||||
|
getGameBackupPreview();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
removeUploadCompleteListener();
|
||||||
|
removeDownloadCompleteListener();
|
||||||
|
};
|
||||||
|
}, [objectId, shop, showSuccessToast, getGameBackupPreview]);
|
||||||
|
|
||||||
|
const deleteGameArtifact = useCallback(
|
||||||
|
async (gameArtifactId: string) => {
|
||||||
|
return window.electron.deleteGameArtifact(gameArtifactId).then(() => {
|
||||||
|
getGameBackupPreview();
|
||||||
|
return { ok: true };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[getGameBackupPreview]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getGameBackupPreview();
|
||||||
|
|
||||||
|
window.electron.checkGameCloudSyncSupport(objectId, shop).then((result) => {
|
||||||
|
setSupportsCloudSync(result);
|
||||||
|
});
|
||||||
|
}, [objectId, shop, getGameBackupPreview]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showCloudSyncModal) {
|
||||||
|
getGameBackupPreview();
|
||||||
|
}
|
||||||
|
}, [getGameBackupPreview, showCloudSyncModal]);
|
||||||
|
|
||||||
|
const backupState = useMemo(() => {
|
||||||
|
if (!backupPreview) return CloudSyncState.Unknown;
|
||||||
|
if (backupPreview.overall.changedGames.new) return CloudSyncState.New;
|
||||||
|
if (backupPreview.overall.changedGames.different)
|
||||||
|
return CloudSyncState.Different;
|
||||||
|
if (backupPreview.overall.changedGames.same) return CloudSyncState.Same;
|
||||||
|
|
||||||
|
return CloudSyncState.Unknown;
|
||||||
|
}, [backupPreview]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Provider
|
||||||
|
value={{
|
||||||
|
supportsCloudSync,
|
||||||
|
backupPreview,
|
||||||
|
showCloudSyncModal,
|
||||||
|
artifacts,
|
||||||
|
backupState,
|
||||||
|
restoringBackup,
|
||||||
|
uploadingBackup,
|
||||||
|
setShowCloudSyncModal,
|
||||||
|
uploadSaveGame,
|
||||||
|
downloadGameArtifact,
|
||||||
|
deleteGameArtifact,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
}
|
@ -5,7 +5,6 @@ import {
|
|||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useParams, useSearchParams } from "react-router-dom";
|
|
||||||
|
|
||||||
import { setHeaderTitle } from "@renderer/features";
|
import { setHeaderTitle } from "@renderer/features";
|
||||||
import { getSteamLanguage } from "@renderer/helpers";
|
import { getSteamLanguage } from "@renderer/helpers";
|
||||||
@ -51,13 +50,17 @@ export const { Consumer: GameDetailsContextConsumer } = gameDetailsContext;
|
|||||||
|
|
||||||
export interface GameDetailsContextProps {
|
export interface GameDetailsContextProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
objectId: string;
|
||||||
|
gameTitle: string;
|
||||||
|
shop: GameShop;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GameDetailsContextProvider({
|
export function GameDetailsContextProvider({
|
||||||
children,
|
children,
|
||||||
|
objectId,
|
||||||
|
gameTitle,
|
||||||
|
shop,
|
||||||
}: GameDetailsContextProps) {
|
}: GameDetailsContextProps) {
|
||||||
const { objectID, shop } = useParams();
|
|
||||||
|
|
||||||
const [shopDetails, setShopDetails] = useState<ShopDetails | null>(null);
|
const [shopDetails, setShopDetails] = useState<ShopDetails | null>(null);
|
||||||
const [game, setGame] = useState<Game | null>(null);
|
const [game, setGame] = useState<Game | null>(null);
|
||||||
const [hasNSFWContentBlocked, setHasNSFWContentBlocked] = useState(false);
|
const [hasNSFWContentBlocked, setHasNSFWContentBlocked] = useState(false);
|
||||||
@ -72,10 +75,6 @@ export function GameDetailsContextProvider({
|
|||||||
|
|
||||||
const [repacks, setRepacks] = useState<GameRepack[]>([]);
|
const [repacks, setRepacks] = useState<GameRepack[]>([]);
|
||||||
|
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
|
|
||||||
const gameTitle = searchParams.get("title")!;
|
|
||||||
|
|
||||||
const { searchRepacks, isIndexingRepacks } = useContext(repacksContext);
|
const { searchRepacks, isIndexingRepacks } = useContext(repacksContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -98,9 +97,9 @@ export function GameDetailsContextProvider({
|
|||||||
|
|
||||||
const updateGame = useCallback(async () => {
|
const updateGame = useCallback(async () => {
|
||||||
return window.electron
|
return window.electron
|
||||||
.getGameByObjectID(objectID!)
|
.getGameByObjectID(objectId!)
|
||||||
.then((result) => setGame(result));
|
.then((result) => setGame(result));
|
||||||
}, [setGame, objectID]);
|
}, [setGame, objectId]);
|
||||||
|
|
||||||
const isGameDownloading = lastPacket?.game.id === game?.id;
|
const isGameDownloading = lastPacket?.game.id === game?.id;
|
||||||
|
|
||||||
@ -111,7 +110,7 @@ export function GameDetailsContextProvider({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.electron
|
window.electron
|
||||||
.getGameShopDetails(
|
.getGameShopDetails(
|
||||||
objectID!,
|
objectId!,
|
||||||
shop as GameShop,
|
shop as GameShop,
|
||||||
getSteamLanguage(i18n.language)
|
getSteamLanguage(i18n.language)
|
||||||
)
|
)
|
||||||
@ -130,12 +129,12 @@ export function GameDetailsContextProvider({
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
window.electron.getGameStats(objectID!, shop as GameShop).then((result) => {
|
window.electron.getGameStats(objectId!, shop as GameShop).then((result) => {
|
||||||
setStats(result);
|
setStats(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
updateGame();
|
updateGame();
|
||||||
}, [updateGame, dispatch, gameTitle, objectID, shop, i18n.language]);
|
}, [updateGame, dispatch, gameTitle, objectId, shop, i18n.language]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShopDetails(null);
|
setShopDetails(null);
|
||||||
@ -143,7 +142,7 @@ export function GameDetailsContextProvider({
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setisGameRunning(false);
|
setisGameRunning(false);
|
||||||
dispatch(setHeaderTitle(gameTitle));
|
dispatch(setHeaderTitle(gameTitle));
|
||||||
}, [objectID, gameTitle, dispatch]);
|
}, [objectId, gameTitle, dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = window.electron.onGamesRunning((gamesIds) => {
|
const unsubscribe = window.electron.onGamesRunning((gamesIds) => {
|
||||||
@ -200,7 +199,7 @@ export function GameDetailsContextProvider({
|
|||||||
gameTitle,
|
gameTitle,
|
||||||
isGameRunning,
|
isGameRunning,
|
||||||
isLoading,
|
isLoading,
|
||||||
objectID,
|
objectID: objectId,
|
||||||
gameColor,
|
gameColor,
|
||||||
showGameOptionsModal,
|
showGameOptionsModal,
|
||||||
showRepacksModal,
|
showRepacksModal,
|
||||||
|
@ -2,3 +2,4 @@ export * from "./game-details/game-details.context";
|
|||||||
export * from "./settings/settings.context";
|
export * from "./settings/settings.context";
|
||||||
export * from "./user-profile/user-profile.context";
|
export * from "./user-profile/user-profile.context";
|
||||||
export * from "./repacks/repacks.context";
|
export * from "./repacks/repacks.context";
|
||||||
|
export * from "./cloud-sync/cloud-sync.context";
|
||||||
|
33
src/renderer/src/declaration.d.ts
vendored
33
src/renderer/src/declaration.d.ts
vendored
@ -26,6 +26,8 @@ import type {
|
|||||||
UserDetails,
|
UserDetails,
|
||||||
FriendRequestSync,
|
FriendRequestSync,
|
||||||
DownloadSourceValidationResult,
|
DownloadSourceValidationResult,
|
||||||
|
GameArtifact,
|
||||||
|
LudusaviBackup,
|
||||||
} from "@types";
|
} from "@types";
|
||||||
import type { DiskSpace } from "check-disk-space";
|
import type { DiskSpace } from "check-disk-space";
|
||||||
|
|
||||||
@ -113,6 +115,37 @@ declare global {
|
|||||||
/* Hardware */
|
/* Hardware */
|
||||||
getDiskFreeSpace: (path: string) => Promise<DiskSpace>;
|
getDiskFreeSpace: (path: string) => Promise<DiskSpace>;
|
||||||
|
|
||||||
|
/* Cloud sync */
|
||||||
|
uploadSaveGame: (objectId: string, shop: GameShop) => Promise<void>;
|
||||||
|
downloadGameArtifact: (
|
||||||
|
objectId: string,
|
||||||
|
shop: GameShop,
|
||||||
|
gameArtifactId: string
|
||||||
|
) => Promise<void>;
|
||||||
|
getGameArtifacts: (
|
||||||
|
objectId: string,
|
||||||
|
shop: GameShop
|
||||||
|
) => Promise<GameArtifact[]>;
|
||||||
|
getGameBackupPreview: (
|
||||||
|
objectId: string,
|
||||||
|
shop: GameShop
|
||||||
|
) => Promise<LudusaviBackup | null>;
|
||||||
|
checkGameCloudSyncSupport: (
|
||||||
|
objectId: string,
|
||||||
|
shop: GameShop
|
||||||
|
) => Promise<boolean>;
|
||||||
|
deleteGameArtifact: (gameArtifactId: string) => Promise<{ ok: boolean }>;
|
||||||
|
onDownloadComplete: (
|
||||||
|
objectId: string,
|
||||||
|
shop: GameShop,
|
||||||
|
cb: () => void
|
||||||
|
) => () => Electron.IpcRenderer;
|
||||||
|
onUploadComplete: (
|
||||||
|
objectId: string,
|
||||||
|
shop: GameShop,
|
||||||
|
cb: () => void
|
||||||
|
) => () => Electron.IpcRenderer;
|
||||||
|
|
||||||
/* Misc */
|
/* Misc */
|
||||||
openExternal: (src: string) => Promise<void>;
|
openExternal: (src: string) => Promise<void>;
|
||||||
getVersion: () => Promise<string>;
|
getVersion: () => Promise<string>;
|
||||||
|
@ -1,13 +1,23 @@
|
|||||||
|
import { GameShop } from "@types";
|
||||||
import { Dexie } from "dexie";
|
import { Dexie } from "dexie";
|
||||||
|
|
||||||
|
export interface GameBackup {
|
||||||
|
id?: number;
|
||||||
|
shop: GameShop;
|
||||||
|
objectId: string;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
export const db = new Dexie("Hydra");
|
export const db = new Dexie("Hydra");
|
||||||
|
|
||||||
db.version(1).stores({
|
db.version(3).stores({
|
||||||
repacks: `++id, title, uris, fileSize, uploadDate, downloadSourceId, repacker, createdAt, updatedAt`,
|
repacks: `++id, title, uris, fileSize, uploadDate, downloadSourceId, repacker, createdAt, updatedAt`,
|
||||||
downloadSources: `++id, url, name, etag, downloadCount, status, createdAt, updatedAt`,
|
downloadSources: `++id, url, name, etag, downloadCount, status, createdAt, updatedAt`,
|
||||||
|
gameBackups: `++id, [shop+objectId], createdAt`,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const downloadSourcesTable = db.table("downloadSources");
|
export const downloadSourcesTable = db.table("downloadSources");
|
||||||
export const repacksTable = db.table("repacks");
|
export const repacksTable = db.table("repacks");
|
||||||
|
export const gameBackupsTable = db.table<GameBackup>("gameBackups");
|
||||||
|
|
||||||
db.open();
|
db.open();
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
import { style } from "@vanilla-extract/css";
|
||||||
|
|
||||||
|
import { SPACING_UNIT, vars } from "../../../theme.css";
|
||||||
|
|
||||||
|
export const artifacts = style({
|
||||||
|
display: "flex",
|
||||||
|
gap: `${SPACING_UNIT}px`,
|
||||||
|
flexDirection: "column",
|
||||||
|
listStyle: "none",
|
||||||
|
margin: "0",
|
||||||
|
padding: "0",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const artifactButton = style({
|
||||||
|
display: "flex",
|
||||||
|
textAlign: "left",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: `${SPACING_UNIT}px`,
|
||||||
|
color: vars.color.body,
|
||||||
|
padding: `${SPACING_UNIT * 2}px`,
|
||||||
|
backgroundColor: vars.color.darkBackground,
|
||||||
|
border: `1px solid ${vars.color.border}`,
|
||||||
|
borderRadius: "4px",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
});
|
@ -0,0 +1,178 @@
|
|||||||
|
import { Button, Modal, ModalProps } from "@renderer/components";
|
||||||
|
import { useContext, useEffect, useMemo, useState } from "react";
|
||||||
|
import { cloudSyncContext, gameDetailsContext } from "@renderer/context";
|
||||||
|
|
||||||
|
import * as styles from "./cloud-sync-modal.css";
|
||||||
|
import { formatBytes } from "@shared";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import {
|
||||||
|
CheckCircleFillIcon,
|
||||||
|
ClockIcon,
|
||||||
|
DeviceDesktopIcon,
|
||||||
|
DownloadIcon,
|
||||||
|
SyncIcon,
|
||||||
|
TrashIcon,
|
||||||
|
UploadIcon,
|
||||||
|
} from "@primer/octicons-react";
|
||||||
|
import { useToast } from "@renderer/hooks";
|
||||||
|
import { GameBackup, gameBackupsTable } from "@renderer/dexie";
|
||||||
|
|
||||||
|
export interface CloudSyncModalProps
|
||||||
|
extends Omit<ModalProps, "children" | "title"> {}
|
||||||
|
|
||||||
|
export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
|
||||||
|
const [deletingArtifact, setDeletingArtifact] = useState(false);
|
||||||
|
const [lastBackup, setLastBackup] = useState<GameBackup | null>(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
artifacts,
|
||||||
|
backupPreview,
|
||||||
|
uploadingBackup,
|
||||||
|
restoringBackup,
|
||||||
|
uploadSaveGame,
|
||||||
|
downloadGameArtifact,
|
||||||
|
deleteGameArtifact,
|
||||||
|
} = useContext(cloudSyncContext);
|
||||||
|
|
||||||
|
const { objectID, shop, gameTitle } = useContext(gameDetailsContext);
|
||||||
|
|
||||||
|
const { showSuccessToast, showErrorToast } = useToast();
|
||||||
|
|
||||||
|
const handleDeleteArtifactClick = async (gameArtifactId: string) => {
|
||||||
|
setDeletingArtifact(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteGameArtifact(gameArtifactId);
|
||||||
|
|
||||||
|
showSuccessToast("backup_successfully_deleted");
|
||||||
|
} catch (err) {
|
||||||
|
showErrorToast("backup_deletion_failed");
|
||||||
|
} finally {
|
||||||
|
setDeletingArtifact(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
gameBackupsTable
|
||||||
|
.where({ shop: shop, objectId: objectID })
|
||||||
|
.last()
|
||||||
|
.then((lastBackup) => setLastBackup(lastBackup || null));
|
||||||
|
}, [backupPreview, objectID, shop]);
|
||||||
|
|
||||||
|
const backupStateLabel = useMemo(() => {
|
||||||
|
if (uploadingBackup) {
|
||||||
|
return (
|
||||||
|
<span style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||||
|
<SyncIcon />
|
||||||
|
creating_backup
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restoringBackup) {
|
||||||
|
return (
|
||||||
|
<span style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||||
|
<SyncIcon />
|
||||||
|
restoring_backup
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastBackup) {
|
||||||
|
return (
|
||||||
|
<p style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||||
|
<CheckCircleFillIcon />
|
||||||
|
Último backup em {format(lastBackup.createdAt, "dd/MM/yyyy HH:mm")}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "no_backups";
|
||||||
|
}, [uploadingBackup, lastBackup, restoringBackup]);
|
||||||
|
|
||||||
|
const disableActions = uploadingBackup || restoringBackup || deletingArtifact;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
visible={visible}
|
||||||
|
title="cloud_sync"
|
||||||
|
description="cloud_sync_description"
|
||||||
|
onClose={onClose}
|
||||||
|
large
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: 24,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", gap: 4, flexDirection: "column" }}>
|
||||||
|
<h2>{gameTitle}</h2>
|
||||||
|
{backupStateLabel}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={uploadSaveGame}
|
||||||
|
disabled={disableActions}
|
||||||
|
>
|
||||||
|
<UploadIcon />
|
||||||
|
create_backup
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 style={{ marginBottom: 16 }}>backups</h2>
|
||||||
|
|
||||||
|
<ul className={styles.artifacts}>
|
||||||
|
{artifacts.map((artifact) => (
|
||||||
|
<li key={artifact.id} className={styles.artifactButton}>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h3>Backup do dia {format(artifact.createdAt, "dd/MM")}</h3>
|
||||||
|
<small>{formatBytes(artifact.artifactLengthInBytes)}</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||||
|
<DeviceDesktopIcon size={14} />
|
||||||
|
{artifact.hostname}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||||
|
<ClockIcon size={14} />
|
||||||
|
{format(artifact.createdAt, "dd/MM/yyyy HH:mm:ss")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={() => downloadGameArtifact(artifact.id)}
|
||||||
|
disabled={disableActions}
|
||||||
|
>
|
||||||
|
<DownloadIcon />
|
||||||
|
install_artifact
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleDeleteArtifactClick(artifact.id)}
|
||||||
|
theme="danger"
|
||||||
|
disabled={disableActions}
|
||||||
|
>
|
||||||
|
<TrashIcon />
|
||||||
|
delete_artifact
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
@ -9,8 +9,11 @@ import { Sidebar } from "./sidebar/sidebar";
|
|||||||
|
|
||||||
import * as styles from "./game-details.css";
|
import * as styles from "./game-details.css";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { gameDetailsContext } from "@renderer/context";
|
import { cloudSyncContext, gameDetailsContext } from "@renderer/context";
|
||||||
import { steamUrlBuilder } from "@shared";
|
import { steamUrlBuilder } from "@shared";
|
||||||
|
import Lottie from "lottie-react";
|
||||||
|
|
||||||
|
import downloadingAnimation from "@renderer/assets/lottie/cloud.json";
|
||||||
|
|
||||||
const HERO_ANIMATION_THRESHOLD = 25;
|
const HERO_ANIMATION_THRESHOLD = 25;
|
||||||
|
|
||||||
@ -30,6 +33,9 @@ export function GameDetailsContent() {
|
|||||||
hasNSFWContentBlocked,
|
hasNSFWContentBlocked,
|
||||||
} = useContext(gameDetailsContext);
|
} = useContext(gameDetailsContext);
|
||||||
|
|
||||||
|
const { supportsCloudSync, setShowCloudSyncModal } =
|
||||||
|
useContext(cloudSyncContext);
|
||||||
|
|
||||||
const [backdropOpactiy, setBackdropOpacity] = useState(1);
|
const [backdropOpactiy, setBackdropOpacity] = useState(1);
|
||||||
|
|
||||||
const handleHeroLoad = async () => {
|
const handleHeroLoad = async () => {
|
||||||
@ -102,6 +108,33 @@ export function GameDetailsContent() {
|
|||||||
className={styles.gameLogo}
|
className={styles.gameLogo}
|
||||||
alt={game?.title}
|
alt={game?.title}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{supportsCloudSync && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={styles.cloudSyncButton}
|
||||||
|
onClick={() => setShowCloudSyncModal(true)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 16 + 4,
|
||||||
|
height: 16,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Lottie
|
||||||
|
animationData={downloadingAnimation}
|
||||||
|
loop
|
||||||
|
autoplay
|
||||||
|
style={{ width: 26, position: "absolute", top: -3 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
cloud_sync
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,8 +6,8 @@ import { recipe } from "@vanilla-extract/recipes";
|
|||||||
export const HERO_HEIGHT = 300;
|
export const HERO_HEIGHT = 300;
|
||||||
|
|
||||||
export const slideIn = keyframes({
|
export const slideIn = keyframes({
|
||||||
"0%": { transform: `translateY(${40 + SPACING_UNIT * 2}px)` },
|
"0%": { transform: `translateY(${40 + SPACING_UNIT * 2}px)`, opacity: "0px" },
|
||||||
"100%": { transform: "translateY(0)" },
|
"100%": { transform: "translateY(0)", opacity: "1" },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const wrapper = recipe({
|
export const wrapper = recipe({
|
||||||
@ -49,6 +49,8 @@ export const heroContent = style({
|
|||||||
height: "100%",
|
height: "100%",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "flex-end",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const heroLogoBackdrop = style({
|
export const heroLogoBackdrop = style({
|
||||||
@ -200,3 +202,33 @@ globalStyle(`${description} img`, {
|
|||||||
globalStyle(`${description} a`, {
|
globalStyle(`${description} a`, {
|
||||||
color: vars.color.body,
|
color: vars.color.body,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const cloudSyncButton = style({
|
||||||
|
padding: `${SPACING_UNIT * 1.5}px ${SPACING_UNIT * 2}px`,
|
||||||
|
backgroundColor: "rgba(0, 0, 0, 0.6)",
|
||||||
|
backdropFilter: "blur(20px)",
|
||||||
|
borderRadius: "8px",
|
||||||
|
transition: "all ease 0.2s",
|
||||||
|
cursor: "pointer",
|
||||||
|
minHeight: "40px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
gap: `${SPACING_UNIT}px`,
|
||||||
|
color: vars.color.muted,
|
||||||
|
fontSize: "14px",
|
||||||
|
border: `solid 1px ${vars.color.border}`,
|
||||||
|
boxShadow: "0px 0px 10px 0px rgba(0, 0, 0, 0.8)",
|
||||||
|
animation: `${slideIn} 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running`,
|
||||||
|
animationDuration: "0.3s",
|
||||||
|
":active": {
|
||||||
|
opacity: "0.9",
|
||||||
|
},
|
||||||
|
":disabled": {
|
||||||
|
opacity: vars.opacity.disabled,
|
||||||
|
cursor: "not-allowed",
|
||||||
|
},
|
||||||
|
":hover": {
|
||||||
|
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@ -18,21 +18,25 @@ import { vars } from "@renderer/theme.css";
|
|||||||
|
|
||||||
import { GameDetailsContent } from "./game-details-content";
|
import { GameDetailsContent } from "./game-details-content";
|
||||||
import {
|
import {
|
||||||
|
CloudSyncContextConsumer,
|
||||||
|
CloudSyncContextProvider,
|
||||||
GameDetailsContextConsumer,
|
GameDetailsContextConsumer,
|
||||||
GameDetailsContextProvider,
|
GameDetailsContextProvider,
|
||||||
} from "@renderer/context";
|
} from "@renderer/context";
|
||||||
import { useDownload } from "@renderer/hooks";
|
import { useDownload } from "@renderer/hooks";
|
||||||
import { GameOptionsModal, RepacksModal } from "./modals";
|
import { GameOptionsModal, RepacksModal } from "./modals";
|
||||||
import { Downloader, getDownloadersForUri } from "@shared";
|
import { Downloader, getDownloadersForUri } from "@shared";
|
||||||
|
import { CloudSyncModal } from "./cloud-sync-modal/cloud-sync-modal";
|
||||||
|
|
||||||
export function GameDetails() {
|
export function GameDetails() {
|
||||||
const [randomGame, setRandomGame] = useState<Steam250Game | null>(null);
|
const [randomGame, setRandomGame] = useState<Steam250Game | null>(null);
|
||||||
const [randomizerLocked, setRandomizerLocked] = useState(false);
|
const [randomizerLocked, setRandomizerLocked] = useState(false);
|
||||||
|
|
||||||
const { objectID } = useParams();
|
const { objectID, shop } = useParams();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
const fromRandomizer = searchParams.get("fromRandomizer");
|
const fromRandomizer = searchParams.get("fromRandomizer");
|
||||||
|
const gameTitle = searchParams.get("title");
|
||||||
|
|
||||||
const { startDownload } = useDownload();
|
const { startDownload } = useDownload();
|
||||||
|
|
||||||
@ -74,7 +78,11 @@ export function GameDetails() {
|
|||||||
repack.uris.find((uri) => getDownloadersForUri(uri).includes(downloader))!;
|
repack.uris.find((uri) => getDownloadersForUri(uri).includes(downloader))!;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GameDetailsContextProvider>
|
<GameDetailsContextProvider
|
||||||
|
gameTitle={gameTitle!}
|
||||||
|
shop={shop! as GameShop}
|
||||||
|
objectId={objectID!}
|
||||||
|
>
|
||||||
<GameDetailsContextConsumer>
|
<GameDetailsContextConsumer>
|
||||||
{({
|
{({
|
||||||
isLoading,
|
isLoading,
|
||||||
@ -115,64 +123,80 @@ export function GameDetails() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SkeletonTheme
|
<CloudSyncContextProvider
|
||||||
baseColor={vars.color.background}
|
objectId={objectID!}
|
||||||
highlightColor="#444"
|
shop={shop! as GameShop}
|
||||||
>
|
>
|
||||||
{isLoading ? <GameDetailsSkeleton /> : <GameDetailsContent />}
|
<CloudSyncContextConsumer>
|
||||||
|
{({ showCloudSyncModal, setShowCloudSyncModal }) => (
|
||||||
|
<CloudSyncModal
|
||||||
|
onClose={() => setShowCloudSyncModal(false)}
|
||||||
|
visible={showCloudSyncModal}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</CloudSyncContextConsumer>
|
||||||
|
|
||||||
<RepacksModal
|
<SkeletonTheme
|
||||||
visible={showRepacksModal}
|
baseColor={vars.color.background}
|
||||||
startDownload={handleStartDownload}
|
highlightColor="#444"
|
||||||
onClose={() => setShowRepacksModal(false)}
|
>
|
||||||
/>
|
{isLoading ? <GameDetailsSkeleton /> : <GameDetailsContent />}
|
||||||
|
|
||||||
<ConfirmationModal
|
<RepacksModal
|
||||||
visible={hasNSFWContentBlocked}
|
visible={showRepacksModal}
|
||||||
onClose={handleNSFWContentRefuse}
|
startDownload={handleStartDownload}
|
||||||
title={t("nsfw_content_title")}
|
onClose={() => setShowRepacksModal(false)}
|
||||||
descriptionText={t("nsfw_content_description", {
|
|
||||||
title: gameTitle,
|
|
||||||
})}
|
|
||||||
confirmButtonLabel={t("allow_nsfw_content")}
|
|
||||||
cancelButtonLabel={t("refuse_nsfw_content")}
|
|
||||||
onConfirm={() => setHasNSFWContentBlocked(false)}
|
|
||||||
clickOutsideToClose={false}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{game && (
|
|
||||||
<GameOptionsModal
|
|
||||||
visible={showGameOptionsModal}
|
|
||||||
game={game}
|
|
||||||
onClose={() => {
|
|
||||||
setShowGameOptionsModal(false);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
{fromRandomizer && (
|
<ConfirmationModal
|
||||||
<Button
|
visible={hasNSFWContentBlocked}
|
||||||
className={styles.randomizerButton}
|
onClose={handleNSFWContentRefuse}
|
||||||
onClick={handleRandomizerClick}
|
title={t("nsfw_content_title")}
|
||||||
theme="outline"
|
descriptionText={t("nsfw_content_description", {
|
||||||
disabled={!randomGame || randomizerLocked}
|
title: gameTitle,
|
||||||
>
|
})}
|
||||||
<div style={{ width: 16, height: 16, position: "relative" }}>
|
confirmButtonLabel={t("allow_nsfw_content")}
|
||||||
<Lottie
|
cancelButtonLabel={t("refuse_nsfw_content")}
|
||||||
animationData={starsAnimation}
|
onConfirm={() => setHasNSFWContentBlocked(false)}
|
||||||
style={{
|
clickOutsideToClose={false}
|
||||||
width: 70,
|
/>
|
||||||
position: "absolute",
|
|
||||||
top: -28,
|
{game && (
|
||||||
left: -27,
|
<GameOptionsModal
|
||||||
}}
|
visible={showGameOptionsModal}
|
||||||
loop
|
game={game}
|
||||||
/>
|
onClose={() => {
|
||||||
</div>
|
setShowGameOptionsModal(false);
|
||||||
{t("next_suggestion")}
|
}}
|
||||||
</Button>
|
/>
|
||||||
)}
|
)}
|
||||||
</SkeletonTheme>
|
|
||||||
|
{fromRandomizer && (
|
||||||
|
<Button
|
||||||
|
className={styles.randomizerButton}
|
||||||
|
onClick={handleRandomizerClick}
|
||||||
|
theme="outline"
|
||||||
|
disabled={!randomGame || randomizerLocked}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{ width: 16, height: 16, position: "relative" }}
|
||||||
|
>
|
||||||
|
<Lottie
|
||||||
|
animationData={starsAnimation}
|
||||||
|
style={{
|
||||||
|
width: 70,
|
||||||
|
position: "absolute",
|
||||||
|
top: -28,
|
||||||
|
left: -27,
|
||||||
|
}}
|
||||||
|
loop
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{t("next_suggestion")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</SkeletonTheme>
|
||||||
|
</CloudSyncContextProvider>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</GameDetailsContextConsumer>
|
</GameDetailsContextConsumer>
|
||||||
|
@ -266,5 +266,15 @@ export interface UserStats {
|
|||||||
friendsCount: number;
|
friendsCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GameArtifact {
|
||||||
|
id: string;
|
||||||
|
artifactLengthInBytes: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
hostname: string;
|
||||||
|
downloadCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
export * from "./steam.types";
|
export * from "./steam.types";
|
||||||
export * from "./real-debrid.types";
|
export * from "./real-debrid.types";
|
||||||
|
export * from "./ludusavi.types";
|
||||||
|
23
src/types/ludusavi.types.ts
Normal file
23
src/types/ludusavi.types.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export interface LudusaviScanChange {
|
||||||
|
change: "New" | "Different" | "Removed" | "Same" | "Unknown";
|
||||||
|
decision: "Processed" | "Cancelled" | "Ignore";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LudusaviBackup {
|
||||||
|
overall: {
|
||||||
|
totalGames: number;
|
||||||
|
totalBytes: number;
|
||||||
|
processedGames: number;
|
||||||
|
processedBytes: number;
|
||||||
|
changedGames: {
|
||||||
|
new: number;
|
||||||
|
different: number;
|
||||||
|
same: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
games: Record<string, LudusaviScanChange>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LudusaviFindResult {
|
||||||
|
games: Record<string, unknown>;
|
||||||
|
}
|
@ -144,8 +144,8 @@ class TorrentDownloader:
|
|||||||
|
|
||||||
status = torrent_handle.status()
|
status = torrent_handle.status()
|
||||||
info = torrent_handle.get_torrent_info()
|
info = torrent_handle.get_torrent_info()
|
||||||
|
|
||||||
return {
|
response = {
|
||||||
'folderName': info.name() if info else "",
|
'folderName': info.name() if info else "",
|
||||||
'fileSize': info.total_size() if info else 0,
|
'fileSize': info.total_size() if info else 0,
|
||||||
'gameId': self.downloading_game_id,
|
'gameId': self.downloading_game_id,
|
||||||
@ -156,3 +156,10 @@ class TorrentDownloader:
|
|||||||
'status': status.state,
|
'status': status.state,
|
||||||
'bytesDownloaded': status.progress * info.total_size() if info else status.all_time_download,
|
'bytesDownloaded': status.progress * info.total_size() if info else status.all_time_download,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if status.progress == 1:
|
||||||
|
torrent_handle.pause()
|
||||||
|
self.session.remove_torrent(torrent_handle)
|
||||||
|
self.downloading_game_id = -1
|
||||||
|
|
||||||
|
return response
|
||||||
|
275
yarn.lock
275
yarn.lock
@ -1923,6 +1923,20 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/adm-zip@^0.5.5":
|
||||||
|
version "0.5.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/adm-zip/-/adm-zip-0.5.5.tgz#4588042726aa5f351d7ea88232e4a952f60e7c1a"
|
||||||
|
integrity sha512-YCGstVMjc4LTY5uK9/obvxBya93axZOVOyf2GSUulADzmLhYE45u2nAssCs/fWBs1Ifq5Vat75JTPwd5XZoPJw==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/archiver@^6.0.2":
|
||||||
|
version "6.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/archiver/-/archiver-6.0.2.tgz#0daf8c83359cbde69de1e4b33dcade6a48a929e2"
|
||||||
|
integrity sha512-KmROQqbQzKGuaAbmK+ZcytkJ51+YqDa7NmbXjmtC5YBLSyQYo21YaUnQ3HbaPFKL1ooo6RQ6OPYPIDyxfpDDXw==
|
||||||
|
dependencies:
|
||||||
|
"@types/readdir-glob" "*"
|
||||||
|
|
||||||
"@types/auto-launch@^5.0.5":
|
"@types/auto-launch@^5.0.5":
|
||||||
version "5.0.5"
|
version "5.0.5"
|
||||||
resolved "https://registry.npmjs.org/@types/auto-launch/-/auto-launch-5.0.5.tgz"
|
resolved "https://registry.npmjs.org/@types/auto-launch/-/auto-launch-5.0.5.tgz"
|
||||||
@ -2066,6 +2080,11 @@
|
|||||||
"@types/qs" "*"
|
"@types/qs" "*"
|
||||||
"@types/serve-static" "*"
|
"@types/serve-static" "*"
|
||||||
|
|
||||||
|
"@types/folder-hash@^4.0.4":
|
||||||
|
version "4.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/folder-hash/-/folder-hash-4.0.4.tgz#c3262d58a01b756ee2aae3694707fad1ef676a9f"
|
||||||
|
integrity sha512-c+PwHm51Dw3fXM8SDK+93PO3oXdk4XNouCCvV67lj4aijRkZz5g67myk+9wqWWnyv3go6q96hT6ywcd3XtoZiQ==
|
||||||
|
|
||||||
"@types/fs-extra@9.0.13", "@types/fs-extra@^9.0.11":
|
"@types/fs-extra@9.0.13", "@types/fs-extra@^9.0.11":
|
||||||
version "9.0.13"
|
version "9.0.13"
|
||||||
resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz"
|
resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz"
|
||||||
@ -2292,6 +2311,13 @@
|
|||||||
"@types/prop-types" "*"
|
"@types/prop-types" "*"
|
||||||
csstype "^3.0.2"
|
csstype "^3.0.2"
|
||||||
|
|
||||||
|
"@types/readdir-glob@*":
|
||||||
|
version "1.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/readdir-glob/-/readdir-glob-1.1.5.tgz#21a4a98898fc606cb568ad815f2a0eedc24d412a"
|
||||||
|
integrity sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/responselike@^1.0.0":
|
"@types/responselike@^1.0.0":
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz"
|
resolved "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz"
|
||||||
@ -2583,6 +2609,11 @@ acorn@^8.8.1, acorn@^8.8.2:
|
|||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c"
|
||||||
integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==
|
integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==
|
||||||
|
|
||||||
|
adm-zip@^0.5.16:
|
||||||
|
version "0.5.16"
|
||||||
|
resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.16.tgz#0b5e4c779f07dedea5805cdccb1147071d94a909"
|
||||||
|
integrity sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==
|
||||||
|
|
||||||
agent-base@6:
|
agent-base@6:
|
||||||
version "6.0.2"
|
version "6.0.2"
|
||||||
resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz"
|
resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz"
|
||||||
@ -2712,6 +2743,32 @@ applescript@^1.0.0:
|
|||||||
resolved "https://registry.npmjs.org/applescript/-/applescript-1.0.0.tgz"
|
resolved "https://registry.npmjs.org/applescript/-/applescript-1.0.0.tgz"
|
||||||
integrity sha512-yvtNHdWvtbYEiIazXAdp/NY+BBb65/DAseqlNiJQjOx9DynuzOYDbVLBJvuc0ve0VL9x6B3OHF6eH52y9hCBtQ==
|
integrity sha512-yvtNHdWvtbYEiIazXAdp/NY+BBb65/DAseqlNiJQjOx9DynuzOYDbVLBJvuc0ve0VL9x6B3OHF6eH52y9hCBtQ==
|
||||||
|
|
||||||
|
archiver-utils@^5.0.0, archiver-utils@^5.0.2:
|
||||||
|
version "5.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-5.0.2.tgz#63bc719d951803efc72cf961a56ef810760dd14d"
|
||||||
|
integrity sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==
|
||||||
|
dependencies:
|
||||||
|
glob "^10.0.0"
|
||||||
|
graceful-fs "^4.2.0"
|
||||||
|
is-stream "^2.0.1"
|
||||||
|
lazystream "^1.0.0"
|
||||||
|
lodash "^4.17.15"
|
||||||
|
normalize-path "^3.0.0"
|
||||||
|
readable-stream "^4.0.0"
|
||||||
|
|
||||||
|
archiver@^7.0.1:
|
||||||
|
version "7.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/archiver/-/archiver-7.0.1.tgz#c9d91c350362040b8927379c7aa69c0655122f61"
|
||||||
|
integrity sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==
|
||||||
|
dependencies:
|
||||||
|
archiver-utils "^5.0.2"
|
||||||
|
async "^3.2.4"
|
||||||
|
buffer-crc32 "^1.0.0"
|
||||||
|
readable-stream "^4.0.0"
|
||||||
|
readdir-glob "^1.1.2"
|
||||||
|
tar-stream "^3.0.0"
|
||||||
|
zip-stream "^6.0.1"
|
||||||
|
|
||||||
arg@^4.1.0:
|
arg@^4.1.0:
|
||||||
version "4.1.3"
|
version "4.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
|
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
|
||||||
@ -2851,6 +2908,11 @@ async@^3.2.3:
|
|||||||
resolved "https://registry.npmjs.org/async/-/async-3.2.5.tgz"
|
resolved "https://registry.npmjs.org/async/-/async-3.2.5.tgz"
|
||||||
integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
|
integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
|
||||||
|
|
||||||
|
async@^3.2.4:
|
||||||
|
version "3.2.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce"
|
||||||
|
integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==
|
||||||
|
|
||||||
asynckit@^0.4.0:
|
asynckit@^0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
|
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
|
||||||
@ -2900,11 +2962,21 @@ axobject-query@^3.2.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
dequal "^2.0.3"
|
dequal "^2.0.3"
|
||||||
|
|
||||||
|
b4a@^1.6.4:
|
||||||
|
version "1.6.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.6.tgz#a4cc349a3851987c3c4ac2d7785c18744f6da9ba"
|
||||||
|
integrity sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==
|
||||||
|
|
||||||
balanced-match@^1.0.0:
|
balanced-match@^1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
|
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
|
||||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||||
|
|
||||||
|
bare-events@^2.2.0:
|
||||||
|
version "2.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.0.tgz#305b511e262ffd8b9d5616b056464f8e1b3329cc"
|
||||||
|
integrity sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==
|
||||||
|
|
||||||
base64-arraybuffer@^1.0.2:
|
base64-arraybuffer@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz"
|
resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz"
|
||||||
@ -3017,6 +3089,11 @@ browserslist@^4.22.2:
|
|||||||
node-releases "^2.0.14"
|
node-releases "^2.0.14"
|
||||||
update-browserslist-db "^1.0.13"
|
update-browserslist-db "^1.0.13"
|
||||||
|
|
||||||
|
buffer-crc32@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-1.0.0.tgz#a10993b9055081d55304bd9feb4a072de179f405"
|
||||||
|
integrity sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==
|
||||||
|
|
||||||
buffer-crc32@~0.2.3:
|
buffer-crc32@~0.2.3:
|
||||||
version "0.2.13"
|
version "0.2.13"
|
||||||
resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz"
|
resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz"
|
||||||
@ -3337,6 +3414,17 @@ compare-version@^0.1.2:
|
|||||||
resolved "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz"
|
resolved "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz"
|
||||||
integrity sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==
|
integrity sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==
|
||||||
|
|
||||||
|
compress-commons@^6.0.2:
|
||||||
|
version "6.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-6.0.2.tgz#26d31251a66b9d6ba23a84064ecd3a6a71d2609e"
|
||||||
|
integrity sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==
|
||||||
|
dependencies:
|
||||||
|
crc-32 "^1.2.0"
|
||||||
|
crc32-stream "^6.0.0"
|
||||||
|
is-stream "^2.0.1"
|
||||||
|
normalize-path "^3.0.0"
|
||||||
|
readable-stream "^4.0.0"
|
||||||
|
|
||||||
concat-map@0.0.1:
|
concat-map@0.0.1:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
||||||
@ -3389,6 +3477,11 @@ core-util-is@1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==
|
integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==
|
||||||
|
|
||||||
|
core-util-is@~1.0.0:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
|
||||||
|
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
|
||||||
|
|
||||||
cosmiconfig-typescript-loader@^5.0.0:
|
cosmiconfig-typescript-loader@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-5.0.0.tgz"
|
resolved "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-5.0.0.tgz"
|
||||||
@ -3416,6 +3509,19 @@ cosmiconfig@^9.0.0:
|
|||||||
js-yaml "^4.1.0"
|
js-yaml "^4.1.0"
|
||||||
parse-json "^5.2.0"
|
parse-json "^5.2.0"
|
||||||
|
|
||||||
|
crc-32@^1.2.0:
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff"
|
||||||
|
integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
|
||||||
|
|
||||||
|
crc32-stream@^6.0.0:
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-6.0.0.tgz#8529a3868f8b27abb915f6c3617c0fadedbf9430"
|
||||||
|
integrity sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==
|
||||||
|
dependencies:
|
||||||
|
crc-32 "^1.2.0"
|
||||||
|
readable-stream "^4.0.0"
|
||||||
|
|
||||||
crc@^3.8.0:
|
crc@^3.8.0:
|
||||||
version "3.8.0"
|
version "3.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6"
|
resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6"
|
||||||
@ -4239,6 +4345,11 @@ event-target-shim@^5.0.0:
|
|||||||
resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz"
|
resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz"
|
||||||
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
|
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
|
||||||
|
|
||||||
|
events@^3.3.0:
|
||||||
|
version "3.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
|
||||||
|
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
|
||||||
|
|
||||||
execa@^8.0.1:
|
execa@^8.0.1:
|
||||||
version "8.0.1"
|
version "8.0.1"
|
||||||
resolved "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz"
|
resolved "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz"
|
||||||
@ -4285,6 +4396,11 @@ fast-diff@^1.1.2:
|
|||||||
resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz"
|
resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz"
|
||||||
integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
|
integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
|
||||||
|
|
||||||
|
fast-fifo@^1.2.0, fast-fifo@^1.3.2:
|
||||||
|
version "1.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c"
|
||||||
|
integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==
|
||||||
|
|
||||||
fast-glob@^3.2.9:
|
fast-glob@^3.2.9:
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz"
|
resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz"
|
||||||
@ -4605,6 +4721,18 @@ glob-parent@^6.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-glob "^4.0.3"
|
is-glob "^4.0.3"
|
||||||
|
|
||||||
|
glob@^10.0.0:
|
||||||
|
version "10.4.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956"
|
||||||
|
integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==
|
||||||
|
dependencies:
|
||||||
|
foreground-child "^3.1.0"
|
||||||
|
jackspeak "^3.1.2"
|
||||||
|
minimatch "^9.0.4"
|
||||||
|
minipass "^7.1.2"
|
||||||
|
package-json-from-dist "^1.0.0"
|
||||||
|
path-scurry "^1.11.1"
|
||||||
|
|
||||||
glob@^10.3.10:
|
glob@^10.3.10:
|
||||||
version "10.3.15"
|
version "10.3.15"
|
||||||
resolved "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz"
|
resolved "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz"
|
||||||
@ -4973,7 +5101,7 @@ inflight@^1.0.4:
|
|||||||
once "^1.3.0"
|
once "^1.3.0"
|
||||||
wrappy "1"
|
wrappy "1"
|
||||||
|
|
||||||
inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4:
|
inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
|
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
|
||||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||||
@ -5170,6 +5298,11 @@ is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bind "^1.0.7"
|
call-bind "^1.0.7"
|
||||||
|
|
||||||
|
is-stream@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
|
||||||
|
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
|
||||||
|
|
||||||
is-stream@^3.0.0:
|
is-stream@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz"
|
resolved "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz"
|
||||||
@ -5228,6 +5361,11 @@ isarray@^2.0.5:
|
|||||||
resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz"
|
resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz"
|
||||||
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
|
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
|
||||||
|
|
||||||
|
isarray@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||||
|
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
|
||||||
|
|
||||||
isbinaryfile@^4.0.8:
|
isbinaryfile@^4.0.8:
|
||||||
version "4.0.10"
|
version "4.0.10"
|
||||||
resolved "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz"
|
resolved "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz"
|
||||||
@ -5263,6 +5401,15 @@ jackspeak@^2.3.6:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
"@pkgjs/parseargs" "^0.11.0"
|
"@pkgjs/parseargs" "^0.11.0"
|
||||||
|
|
||||||
|
jackspeak@^3.1.2:
|
||||||
|
version "3.4.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a"
|
||||||
|
integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==
|
||||||
|
dependencies:
|
||||||
|
"@isaacs/cliui" "^8.0.2"
|
||||||
|
optionalDependencies:
|
||||||
|
"@pkgjs/parseargs" "^0.11.0"
|
||||||
|
|
||||||
jake@^10.8.5:
|
jake@^10.8.5:
|
||||||
version "10.9.1"
|
version "10.9.1"
|
||||||
resolved "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz"
|
resolved "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz"
|
||||||
@ -5475,6 +5622,13 @@ lazy-val@^1.0.4, lazy-val@^1.0.5:
|
|||||||
resolved "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz"
|
resolved "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz"
|
||||||
integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==
|
integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==
|
||||||
|
|
||||||
|
lazystream@^1.0.0:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638"
|
||||||
|
integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==
|
||||||
|
dependencies:
|
||||||
|
readable-stream "^2.0.5"
|
||||||
|
|
||||||
levn@^0.4.1:
|
levn@^0.4.1:
|
||||||
version "0.4.1"
|
version "0.4.1"
|
||||||
resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz"
|
resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz"
|
||||||
@ -5763,7 +5917,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^1.1.7"
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
minimatch@^5.0.1, minimatch@^5.1.1:
|
minimatch@^5.0.1, minimatch@^5.1.0, minimatch@^5.1.1:
|
||||||
version "5.1.6"
|
version "5.1.6"
|
||||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz"
|
resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz"
|
||||||
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
|
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
|
||||||
@ -5784,6 +5938,13 @@ minimatch@^9.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^2.0.1"
|
brace-expansion "^2.0.1"
|
||||||
|
|
||||||
|
minimatch@^9.0.4:
|
||||||
|
version "9.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
|
||||||
|
integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
|
||||||
|
dependencies:
|
||||||
|
brace-expansion "^2.0.1"
|
||||||
|
|
||||||
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6, minimist@^1.2.8:
|
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6, minimist@^1.2.8:
|
||||||
version "1.2.8"
|
version "1.2.8"
|
||||||
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz"
|
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz"
|
||||||
@ -5811,6 +5972,11 @@ minipass@^5.0.0:
|
|||||||
resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz"
|
resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz"
|
||||||
integrity sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==
|
integrity sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==
|
||||||
|
|
||||||
|
minipass@^7.1.2:
|
||||||
|
version "7.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
|
||||||
|
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
|
||||||
|
|
||||||
minizlib@^2.1.1:
|
minizlib@^2.1.1:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz"
|
resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz"
|
||||||
@ -6116,6 +6282,11 @@ p-locate@^6.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-limit "^4.0.0"
|
p-limit "^4.0.0"
|
||||||
|
|
||||||
|
package-json-from-dist@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00"
|
||||||
|
integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==
|
||||||
|
|
||||||
parent-module@^1.0.0:
|
parent-module@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
|
resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
|
||||||
@ -6199,7 +6370,7 @@ path-parse@^1.0.7:
|
|||||||
resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
|
resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
|
||||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||||
|
|
||||||
path-scurry@^1.11.0, path-scurry@^1.6.1:
|
path-scurry@^1.11.0, path-scurry@^1.11.1, path-scurry@^1.6.1:
|
||||||
version "1.11.1"
|
version "1.11.1"
|
||||||
resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz"
|
resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz"
|
||||||
integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==
|
integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==
|
||||||
@ -6409,6 +6580,16 @@ prettier@^3.2.4:
|
|||||||
resolved "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz"
|
resolved "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz"
|
||||||
integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==
|
integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==
|
||||||
|
|
||||||
|
process-nextick-args@~2.0.0:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||||
|
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
||||||
|
|
||||||
|
process@^0.11.10:
|
||||||
|
version "0.11.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
||||||
|
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
|
||||||
|
|
||||||
progress@^2.0.3:
|
progress@^2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz"
|
resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz"
|
||||||
@ -6469,6 +6650,11 @@ queue-microtask@^1.2.2, queue-microtask@^1.2.3:
|
|||||||
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
|
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
|
||||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||||
|
|
||||||
|
queue-tick@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142"
|
||||||
|
integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==
|
||||||
|
|
||||||
quick-lru@^5.1.1:
|
quick-lru@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz"
|
resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz"
|
||||||
@ -6562,6 +6748,19 @@ read-config-file@6.3.2:
|
|||||||
json5 "^2.2.0"
|
json5 "^2.2.0"
|
||||||
lazy-val "^1.0.4"
|
lazy-val "^1.0.4"
|
||||||
|
|
||||||
|
readable-stream@^2.0.5:
|
||||||
|
version "2.3.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
|
||||||
|
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
|
||||||
|
dependencies:
|
||||||
|
core-util-is "~1.0.0"
|
||||||
|
inherits "~2.0.3"
|
||||||
|
isarray "~1.0.0"
|
||||||
|
process-nextick-args "~2.0.0"
|
||||||
|
safe-buffer "~5.1.1"
|
||||||
|
string_decoder "~1.1.1"
|
||||||
|
util-deprecate "~1.0.1"
|
||||||
|
|
||||||
readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
|
readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
|
||||||
version "3.6.2"
|
version "3.6.2"
|
||||||
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz"
|
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz"
|
||||||
@ -6571,6 +6770,17 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
|
|||||||
string_decoder "^1.1.1"
|
string_decoder "^1.1.1"
|
||||||
util-deprecate "^1.0.1"
|
util-deprecate "^1.0.1"
|
||||||
|
|
||||||
|
readable-stream@^4.0.0:
|
||||||
|
version "4.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09"
|
||||||
|
integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==
|
||||||
|
dependencies:
|
||||||
|
abort-controller "^3.0.0"
|
||||||
|
buffer "^6.0.3"
|
||||||
|
events "^3.3.0"
|
||||||
|
process "^0.11.10"
|
||||||
|
string_decoder "^1.3.0"
|
||||||
|
|
||||||
readable-web-to-node-stream@^3.0.2:
|
readable-web-to-node-stream@^3.0.2:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz"
|
resolved "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz"
|
||||||
@ -6578,6 +6788,13 @@ readable-web-to-node-stream@^3.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
readable-stream "^3.6.0"
|
readable-stream "^3.6.0"
|
||||||
|
|
||||||
|
readdir-glob@^1.1.2:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584"
|
||||||
|
integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==
|
||||||
|
dependencies:
|
||||||
|
minimatch "^5.1.0"
|
||||||
|
|
||||||
readdirp@~3.6.0:
|
readdirp@~3.6.0:
|
||||||
version "3.6.0"
|
version "3.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
||||||
@ -6795,6 +7012,11 @@ safe-buffer@^5.0.1, safe-buffer@~5.2.0:
|
|||||||
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
|
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
|
||||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||||
|
|
||||||
|
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||||
|
version "5.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
|
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||||
|
|
||||||
safe-regex-test@^1.0.3:
|
safe-regex-test@^1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz"
|
resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz"
|
||||||
@ -7012,6 +7234,17 @@ stat-mode@^1.0.0:
|
|||||||
resolved "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz"
|
resolved "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz"
|
||||||
integrity sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==
|
integrity sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==
|
||||||
|
|
||||||
|
streamx@^2.15.0:
|
||||||
|
version "2.20.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.20.1.tgz#471c4f8b860f7b696feb83d5b125caab2fdbb93c"
|
||||||
|
integrity sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==
|
||||||
|
dependencies:
|
||||||
|
fast-fifo "^1.3.2"
|
||||||
|
queue-tick "^1.0.1"
|
||||||
|
text-decoder "^1.1.0"
|
||||||
|
optionalDependencies:
|
||||||
|
bare-events "^2.2.0"
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0":
|
"string-width-cjs@npm:string-width@^4.2.0":
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
||||||
@ -7085,13 +7318,20 @@ string.prototype.trimstart@^1.0.8:
|
|||||||
define-properties "^1.2.1"
|
define-properties "^1.2.1"
|
||||||
es-object-atoms "^1.0.0"
|
es-object-atoms "^1.0.0"
|
||||||
|
|
||||||
string_decoder@^1.1.1:
|
string_decoder@^1.1.1, string_decoder@^1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
|
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
|
||||||
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
|
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.2.0"
|
safe-buffer "~5.2.0"
|
||||||
|
|
||||||
|
string_decoder@~1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||||
|
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "~5.1.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
||||||
@ -7206,6 +7446,15 @@ tar-stream@^2.1.4:
|
|||||||
inherits "^2.0.3"
|
inherits "^2.0.3"
|
||||||
readable-stream "^3.1.1"
|
readable-stream "^3.1.1"
|
||||||
|
|
||||||
|
tar-stream@^3.0.0:
|
||||||
|
version "3.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.7.tgz#24b3fb5eabada19fe7338ed6d26e5f7c482e792b"
|
||||||
|
integrity sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==
|
||||||
|
dependencies:
|
||||||
|
b4a "^1.6.4"
|
||||||
|
fast-fifo "^1.2.0"
|
||||||
|
streamx "^2.15.0"
|
||||||
|
|
||||||
tar@^6.1.12:
|
tar@^6.1.12:
|
||||||
version "6.2.1"
|
version "6.2.1"
|
||||||
resolved "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz"
|
resolved "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz"
|
||||||
@ -7231,6 +7480,13 @@ temp-file@^3.4.0:
|
|||||||
async-exit-hook "^2.0.1"
|
async-exit-hook "^2.0.1"
|
||||||
fs-extra "^10.0.0"
|
fs-extra "^10.0.0"
|
||||||
|
|
||||||
|
text-decoder@^1.1.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.0.tgz#85f19d4d5088e0b45cd841bdfaeac458dbffeefc"
|
||||||
|
integrity sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg==
|
||||||
|
dependencies:
|
||||||
|
b4a "^1.6.4"
|
||||||
|
|
||||||
text-extensions@^2.0.0:
|
text-extensions@^2.0.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz"
|
resolved "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz"
|
||||||
@ -7581,7 +7837,7 @@ utf8-byte-length@^1.0.1:
|
|||||||
resolved "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz"
|
resolved "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz"
|
||||||
integrity sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==
|
integrity sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==
|
||||||
|
|
||||||
util-deprecate@^1.0.1:
|
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
||||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||||
@ -7914,6 +8170,15 @@ yup@^1.4.0:
|
|||||||
toposort "^2.0.2"
|
toposort "^2.0.2"
|
||||||
type-fest "^2.19.0"
|
type-fest "^2.19.0"
|
||||||
|
|
||||||
|
zip-stream@^6.0.1:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-6.0.1.tgz#e141b930ed60ccaf5d7fa9c8260e0d1748a2bbfb"
|
||||||
|
integrity sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==
|
||||||
|
dependencies:
|
||||||
|
archiver-utils "^5.0.0"
|
||||||
|
compress-commons "^6.0.2"
|
||||||
|
readable-stream "^4.0.0"
|
||||||
|
|
||||||
zod@^3.23.8:
|
zod@^3.23.8:
|
||||||
version "3.23.8"
|
version "3.23.8"
|
||||||
resolved "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz"
|
resolved "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz"
|
||||||
|
Loading…
Reference in New Issue
Block a user