feat: adding change hero

This commit is contained in:
Chubby Granny Chaser 2024-10-05 02:21:41 +01:00
parent 586df616e8
commit 035e424a76
No known key found for this signature in database
48 changed files with 520 additions and 500 deletions

View file

@ -42,8 +42,6 @@
"@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",
@ -74,6 +72,7 @@
"react-redux": "^9.1.1", "react-redux": "^9.1.1",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.22.3",
"sudo-prompt": "^9.2.1", "sudo-prompt": "^9.2.1",
"tar": "^7.4.3",
"typeorm": "^0.3.20", "typeorm": "^0.3.20",
"user-agents": "^1.1.193", "user-agents": "^1.1.193",
"yaml": "^2.4.1", "yaml": "^2.4.1",
@ -88,8 +87,6 @@
"@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/folder-hash": "^4.0.4",

View file

@ -30,7 +30,7 @@ const getCatalogue = async (
title: steamGame.name, title: steamGame.name,
shop: game.shop, shop: game.shop,
cover: steamUrlBuilder.library(game.objectId), cover: steamUrlBuilder.library(game.objectId),
objectID: game.objectId, objectId: game.objectId,
}; };
}) })
); );

View file

@ -7,16 +7,16 @@ import { registerEvent } from "../register-event";
import { steamGamesWorker } from "@main/workers"; import { steamGamesWorker } from "@main/workers";
const getLocalizedSteamAppDetails = async ( const getLocalizedSteamAppDetails = async (
objectID: string, objectId: string,
language: string language: string
): Promise<ShopDetails | null> => { ): Promise<ShopDetails | null> => {
if (language === "english") { if (language === "english") {
return getSteamAppDetails(objectID, language); return getSteamAppDetails(objectId, language);
} }
return getSteamAppDetails(objectID, language).then( return getSteamAppDetails(objectId, language).then(
async (localizedAppDetails) => { async (localizedAppDetails) => {
const steamGame = await steamGamesWorker.run(Number(objectID), { const steamGame = await steamGamesWorker.run(Number(objectId), {
name: "getById", name: "getById",
}); });
@ -34,21 +34,21 @@ const getLocalizedSteamAppDetails = async (
const getGameShopDetails = async ( const getGameShopDetails = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
objectID: string, objectId: string,
shop: GameShop, shop: GameShop,
language: string language: string
): Promise<ShopDetails | null> => { ): Promise<ShopDetails | null> => {
if (shop === "steam") { if (shop === "steam") {
const cachedData = await gameShopCacheRepository.findOne({ const cachedData = await gameShopCacheRepository.findOne({
where: { objectID, language }, where: { objectID: objectId, language },
}); });
const appDetails = getLocalizedSteamAppDetails(objectID, language).then( const appDetails = getLocalizedSteamAppDetails(objectId, language).then(
(result) => { (result) => {
if (result) { if (result) {
gameShopCacheRepository.upsert( gameShopCacheRepository.upsert(
{ {
objectID, objectID: objectId,
shop: "steam", shop: "steam",
language, language,
serializedData: JSON.stringify(result), serializedData: JSON.stringify(result),
@ -68,7 +68,7 @@ const getGameShopDetails = async (
if (cachedGame) { if (cachedGame) {
return { return {
...cachedGame, ...cachedGame,
objectID, objectId,
} as ShopDetails; } as ShopDetails;
} }

View file

@ -1,28 +1,29 @@
import type { CatalogueEntry } from "@types"; import type { CatalogueEntry } from "@types";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { steamGamesWorker } from "@main/workers"; import { HydraApi } from "@main/services";
import { steamUrlBuilder } from "@shared"; import { steamUrlBuilder } from "@shared";
const getGames = async ( const getGames = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
take = 12, take = 12,
cursor = 0 skip = 0
): Promise<{ results: CatalogueEntry[]; cursor: number }> => { ): Promise<CatalogueEntry[]> => {
const steamGames = await steamGamesWorker.run( const searchParams = new URLSearchParams({
{ limit: take, offset: cursor }, take: take.toString(),
{ name: "list" } skip: skip.toString(),
});
const games = await HydraApi.get<CatalogueEntry[]>(
`/games/catalogue?${searchParams.toString()}`,
undefined,
{ needsAuth: false }
); );
return { return games.map((game) => ({
results: steamGames.map((steamGame) => ({ ...game,
title: steamGame.name, cover: steamUrlBuilder.library(game.objectId),
shop: "steam", }));
cover: steamUrlBuilder.library(steamGame.id),
objectID: steamGame.id,
})),
cursor: cursor + steamGames.length,
};
}; };
registerEvent("getGames", getGames); registerEvent("getGames", getGames);

View file

@ -6,14 +6,14 @@ import { gameShopCacheRepository } from "@main/repository";
const getHowLongToBeat = async ( const getHowLongToBeat = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
objectID: string, objectId: string,
shop: GameShop, shop: GameShop,
title: string title: string
): Promise<HowLongToBeatCategory[] | null> => { ): Promise<HowLongToBeatCategory[] | null> => {
const searchHowLongToBeatPromise = searchHowLongToBeat(title); const searchHowLongToBeatPromise = searchHowLongToBeat(title);
const gameShopCache = await gameShopCacheRepository.findOne({ const gameShopCache = await gameShopCacheRepository.findOne({
where: { objectID, shop }, where: { objectID: objectId, shop },
}); });
const howLongToBeatCachedData = gameShopCache?.howLongToBeatSerializedData const howLongToBeatCachedData = gameShopCache?.howLongToBeatSerializedData
@ -23,7 +23,7 @@ const getHowLongToBeat = async (
return searchHowLongToBeatPromise.then(async (response) => { return searchHowLongToBeatPromise.then(async (response) => {
const game = response.data.find( const game = response.data.find(
(game) => game.profile_steam === Number(objectID) (game) => game.profile_steam === Number(objectId)
); );
if (!game) return null; if (!game) return null;
@ -31,7 +31,7 @@ const getHowLongToBeat = async (
gameShopCacheRepository.upsert( gameShopCacheRepository.upsert(
{ {
objectID, objectID: objectId,
shop, shop,
howLongToBeatSerializedData: JSON.stringify(howLongToBeat), howLongToBeatSerializedData: JSON.stringify(howLongToBeat),
}, },

View file

@ -4,6 +4,9 @@ import { registerEvent } from "../register-event";
const deleteGameArtifact = async ( const deleteGameArtifact = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
gameArtifactId: string gameArtifactId: string
) => HydraApi.delete<{ ok: boolean }>(`/games/artifacts/${gameArtifactId}`); ) =>
HydraApi.delete<{ ok: boolean }>(
`/profile/games/artifacts/${gameArtifactId}`
);
registerEvent("deleteGameArtifact", deleteGameArtifact); registerEvent("deleteGameArtifact", deleteGameArtifact);

View file

@ -1,6 +1,6 @@
import { HydraApi, logger, Ludusavi, WindowManager } from "@main/services"; import { HydraApi, logger, Ludusavi, WindowManager } from "@main/services";
import fs from "node:fs"; import fs from "node:fs";
import AdmZip from "adm-zip"; import * as tar from "tar";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import axios from "axios"; import axios from "axios";
import { app } from "electron"; import { app } from "electron";
@ -8,16 +8,54 @@ import path from "node:path";
import { backupsPath } from "@main/constants"; import { backupsPath } from "@main/constants";
import type { GameShop } from "@types"; import type { GameShop } from "@types";
import YAML from "yaml";
export interface LudusaviBackup {
files: {
[key: string]: {
hash: string;
size: number;
};
};
}
const replaceLudusaviBackupWithCurrentUser = (
mappingPath: string,
backupHomeDir: string
) => {
const data = fs.readFileSync(mappingPath, "utf8");
const manifest = YAML.parse(data);
const currentHomeDir = app.getPath("home");
const backups = manifest.backups.map((backup: LudusaviBackup) => {
const files = Object.entries(backup.files).reduce((prev, [key, value]) => {
return {
...prev,
[key.replace(backupHomeDir, currentHomeDir)]: value,
};
}, {});
return {
...backup,
files,
};
});
fs.writeFileSync(mappingPath, YAML.stringify({ ...manifest, backups }));
};
const downloadGameArtifact = async ( const downloadGameArtifact = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
objectId: string, objectId: string,
shop: GameShop, shop: GameShop,
gameArtifactId: string gameArtifactId: string
) => { ) => {
const { downloadUrl, objectKey } = await HydraApi.post<{ const { downloadUrl, objectKey, homeDir } = await HydraApi.post<{
downloadUrl: string; downloadUrl: string;
objectKey: string; objectKey: string;
}>(`/games/artifacts/${gameArtifactId}/download`); homeDir: string;
}>(`/profile/games/artifacts/${gameArtifactId}/download`);
const zipLocation = path.join(app.getPath("userData"), objectKey); const zipLocation = path.join(app.getPath("userData"), objectKey);
const backupPath = path.join(backupsPath, `${shop}-${objectId}`); const backupPath = path.join(backupsPath, `${shop}-${objectId}`);
@ -42,20 +80,31 @@ const downloadGameArtifact = async (
}); });
writer.on("close", () => { writer.on("close", () => {
const zip = new AdmZip(zipLocation); tar
zip.extractAllToAsync(backupPath, true, true, (err) => { .x({
if (err) { file: zipLocation,
logger.error("Failed to extract zip", err); cwd: backupPath,
throw err; })
} .then(async () => {
const [game] = await Ludusavi.findGames(shop, objectId);
if (!game) throw new Error("Game not found in Ludusavi manifest");
Ludusavi.restoreBackup(backupPath).then(() => { const mappingPath = path.join(
WindowManager.mainWindow?.webContents.send( backupsPath,
`on-backup-download-complete-${objectId}-${shop}`, `${shop}-${objectId}`,
true game,
"mapping.yaml"
); );
replaceLudusaviBackupWithCurrentUser(mappingPath, homeDir);
Ludusavi.restoreBackup(backupPath).then(() => {
WindowManager.mainWindow?.webContents.send(
`on-backup-download-complete-${objectId}-${shop}`,
true
);
});
}); });
});
}); });
}; };

View file

@ -12,7 +12,9 @@ const getGameArtifacts = async (
shop, shop,
}); });
return HydraApi.get<GameArtifact[]>(`/games/artifacts?${params.toString()}`); return HydraApi.get<GameArtifact[]>(
`/profile/games/artifacts?${params.toString()}`
);
}; };
registerEvent("getGameArtifacts", getGameArtifacts); registerEvent("getGameArtifacts", getGameArtifacts);

View file

@ -2,47 +2,31 @@ import { HydraApi, logger, Ludusavi, WindowManager } from "@main/services";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import fs from "node:fs"; import fs from "node:fs";
import path from "node:path"; import path from "node:path";
import archiver from "archiver"; import * as tar from "tar";
import crypto from "node:crypto"; import crypto from "node:crypto";
import { GameShop } from "@types"; import { GameShop } from "@types";
import axios from "axios"; import axios from "axios";
import os from "node:os"; import os from "node:os";
import { app } from "electron";
import { backupsPath } from "@main/constants"; import { backupsPath } from "@main/constants";
import { app } from "electron";
const compressBackupToArtifact = async ( const bundleBackup = async (shop: GameShop, objectId: string) => {
shop: GameShop,
objectId: string,
cb: (zipLocation: string) => void
) => {
const backupPath = path.join(backupsPath, `${shop}-${objectId}`); const backupPath = path.join(backupsPath, `${shop}-${objectId}`);
await Ludusavi.backupGame(shop, objectId, backupPath); await Ludusavi.backupGame(shop, objectId, backupPath);
const archive = archiver("zip", { const tarLocation = path.join(backupsPath, `${crypto.randomUUID()}.zip`);
zlib: { level: 9 },
});
const zipLocation = path.join( await tar.create(
app.getPath("userData"), {
`${crypto.randomUUID()}.zip` gzip: false,
file: tarLocation,
cwd: backupPath,
},
["."]
); );
const output = fs.createWriteStream(zipLocation); return tarLocation;
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 ( const uploadSaveGame = async (
@ -50,49 +34,51 @@ const uploadSaveGame = async (
objectId: string, objectId: string,
shop: GameShop shop: GameShop
) => { ) => {
compressBackupToArtifact(shop, objectId, (zipLocation) => { const bundleLocation = await bundleBackup(shop, objectId);
fs.stat(zipLocation, async (err, stat) => {
fs.stat(bundleLocation, 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;
}>("/profile/games/artifacts", {
artifactLengthInBytes: stat.size,
shop,
objectId,
hostname: os.hostname(),
homeDir: app.getPath("home"),
platform: os.platform(),
});
fs.readFile(bundleLocation, async (err, fileBuffer) => {
if (err) { if (err) {
logger.error("Failed to get zip file stats", err); logger.error("Failed to read zip file", err);
throw err; throw err;
} }
const { uploadUrl } = await HydraApi.post<{ await axios.put(uploadUrl, fileBuffer, {
id: string; headers: {
uploadUrl: string; "Content-Type": "application/tar",
}>("/games/artifacts", { },
artifactLengthInBytes: stat.size, onUploadProgress: (progressEvent) => {
shop, console.log(progressEvent);
objectId, },
hostname: os.hostname(),
}); });
fs.readFile(zipLocation, async (err, fileBuffer) => { WindowManager.mainWindow?.webContents.send(
`on-upload-complete-${objectId}-${shop}`,
true
);
fs.rm(bundleLocation, (err) => {
if (err) { if (err) {
logger.error("Failed to read zip file", err); logger.error("Failed to remove tar file", err);
throw err; throw err;
} }
await axios.put(uploadUrl, fileBuffer, {
headers: {
"Content-Type": "application/zip",
},
onUploadProgress: (progressEvent) => {
console.log(progressEvent);
},
});
WindowManager.mainWindow?.webContents.send(
`on-upload-complete-${objectId}-${shop}`,
true
);
fs.rm(zipLocation, (err) => {
if (err) {
logger.error("Failed to remove zip file", err);
throw err;
}
});
}); });
}); });
}); });

View file

@ -12,7 +12,7 @@ export interface SearchGamesArgs {
export const convertSteamGameToCatalogueEntry = ( export const convertSteamGameToCatalogueEntry = (
game: SteamGame game: SteamGame
): CatalogueEntry => ({ ): CatalogueEntry => ({
objectID: String(game.id), objectId: String(game.id),
title: game.name, title: game.name,
shop: "steam" as GameShop, shop: "steam" as GameShop,
cover: steamUrlBuilder.library(String(game.id)), cover: steamUrlBuilder.library(String(game.id)),

View file

@ -10,14 +10,14 @@ import { steamUrlBuilder } from "@shared";
const addGameToLibrary = async ( const addGameToLibrary = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
objectID: string, objectId: string,
title: string, title: string,
shop: GameShop shop: GameShop
) => { ) => {
return gameRepository return gameRepository
.update( .update(
{ {
objectID, objectID: objectId,
}, },
{ {
shop, shop,
@ -27,23 +27,25 @@ const addGameToLibrary = async (
) )
.then(async ({ affected }) => { .then(async ({ affected }) => {
if (!affected) { if (!affected) {
const steamGame = await steamGamesWorker.run(Number(objectID), { const steamGame = await steamGamesWorker.run(Number(objectId), {
name: "getById", name: "getById",
}); });
const iconUrl = steamGame?.clientIcon const iconUrl = steamGame?.clientIcon
? steamUrlBuilder.icon(objectID, steamGame.clientIcon) ? steamUrlBuilder.icon(objectId, steamGame.clientIcon)
: null; : null;
await gameRepository.insert({ await gameRepository.insert({
title, title,
iconUrl, iconUrl,
objectID, objectID: objectId,
shop, shop,
}); });
} }
const game = await gameRepository.findOne({ where: { objectID } }); const game = await gameRepository.findOne({
where: { objectID: objectId },
});
createGame(game!).catch(() => {}); createGame(game!).catch(() => {});
}); });

View file

@ -2,15 +2,15 @@ import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
const getGameByObjectID = async ( const getGameByObjectId = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
objectID: string objectId: string
) => ) =>
gameRepository.findOne({ gameRepository.findOne({
where: { where: {
objectID, objectID: objectId,
isDeleted: false, isDeleted: false,
}, },
}); });
registerEvent("getGameByObjectID", getGameByObjectID); registerEvent("getGameByObjectId", getGameByObjectId);

View file

@ -14,7 +14,7 @@ const startGameDownload = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
payload: StartGameDownloadPayload payload: StartGameDownloadPayload
) => { ) => {
const { objectID, title, shop, downloadPath, downloader, uri } = payload; const { objectId, title, shop, downloadPath, downloader, uri } = payload;
return dataSource.transaction(async (transactionalEntityManager) => { return dataSource.transaction(async (transactionalEntityManager) => {
const gameRepository = transactionalEntityManager.getRepository(Game); const gameRepository = transactionalEntityManager.getRepository(Game);
@ -23,7 +23,7 @@ const startGameDownload = async (
const game = await gameRepository.findOne({ const game = await gameRepository.findOne({
where: { where: {
objectID, objectID: objectId,
shop, shop,
}, },
}); });
@ -51,18 +51,18 @@ const startGameDownload = async (
} }
); );
} else { } else {
const steamGame = await steamGamesWorker.run(Number(objectID), { const steamGame = await steamGamesWorker.run(Number(objectId), {
name: "getById", name: "getById",
}); });
const iconUrl = steamGame?.clientIcon const iconUrl = steamGame?.clientIcon
? steamUrlBuilder.icon(objectID, steamGame.clientIcon) ? steamUrlBuilder.icon(objectId, steamGame.clientIcon)
: null; : null;
await gameRepository.insert({ await gameRepository.insert({
title, title,
iconUrl, iconUrl,
objectID, objectID: objectId,
downloader, downloader,
shop, shop,
status: "active", status: "active",
@ -73,7 +73,7 @@ const startGameDownload = async (
const updatedGame = await gameRepository.findOne({ const updatedGame = await gameRepository.findOne({
where: { where: {
objectID, objectID: objectId,
}, },
}); });

View file

@ -2,7 +2,7 @@ import { IsNull, Not } from "typeorm";
import { gameRepository } from "@main/repository"; import { gameRepository } from "@main/repository";
import { WindowManager } from "./window-manager"; import { WindowManager } from "./window-manager";
import { createGame, updateGamePlaytime } from "./library-sync"; import { createGame, updateGamePlaytime } from "./library-sync";
import { GameRunning } from "@types"; import type { GameRunning } from "@types";
import { PythonInstance } from "./download"; import { PythonInstance } from "./download";
import { Game } from "@main/entity"; import { Game } from "@main/entity";

View file

@ -17,7 +17,7 @@ export const requestSteam250 = async (path: string) => {
return { return {
title: $title.textContent, title: $title.textContent,
objectID: steamGameUrl.split("/").pop(), objectId: steamGameUrl.split("/").pop(),
} as Steam250Game; } as Steam250Game;
}) })
.filter((game) => game != null); .filter((game) => game != null);
@ -38,7 +38,7 @@ export const getSteam250List = async () => {
).flat(); ).flat();
const gamesMap: Map<string, Steam250Game> = gamesList.reduce((map, item) => { const gamesMap: Map<string, Steam250Game> = gamesList.reduce((map, item) => {
if (item) map.set(item.objectID, item); if (item) map.set(item.objectId, item);
return map; return map;
}, new Map()); }, new Map());

View file

@ -21,7 +21,7 @@ export interface SteamGridGameResponse {
} }
export const getSteamGridData = async ( export const getSteamGridData = async (
objectID: string, objectId: string,
path: string, path: string,
shop: GameShop, shop: GameShop,
params: Record<string, string> = {} params: Record<string, string> = {}
@ -33,7 +33,7 @@ export const getSteamGridData = async (
} }
const response = await axios.get( const response = await axios.get(
`https://www.steamgriddb.com/api/v2/${path}/${shop}/${objectID}?${searchParams.toString()}`, `https://www.steamgriddb.com/api/v2/${path}/${shop}/${objectId}?${searchParams.toString()}`,
{ {
headers: { headers: {
Authorization: `Bearer ${import.meta.env.MAIN_VITE_STEAMGRIDDB_API_KEY}`, Authorization: `Bearer ${import.meta.env.MAIN_VITE_STEAMGRIDDB_API_KEY}`,
@ -59,10 +59,10 @@ export const getSteamGridGameById = async (
return response.data; return response.data;
}; };
export const getSteamGameClientIcon = async (objectID: string) => { export const getSteamGameClientIcon = async (objectId: string) => {
const { const {
data: { id: steamGridGameId }, data: { id: steamGridGameId },
} = await getSteamGridData(objectID, "games", "steam"); } = await getSteamGridData(objectId, "games", "steam");
const steamGridGame = await getSteamGridGameById(steamGridGameId); const steamGridGame = await getSteamGridGameById(steamGridGameId);
return steamGridGame.data.platforms.steam.metadata.clienticon; return steamGridGame.data.platforms.steam.metadata.clienticon;

View file

@ -12,11 +12,11 @@ export interface SteamAppDetailsResponse {
} }
export const getSteamAppDetails = async ( export const getSteamAppDetails = async (
objectID: string, objectId: string,
language: string language: string
) => { ) => {
const searchParams = new URLSearchParams({ const searchParams = new URLSearchParams({
appids: objectID, appids: objectId,
l: language, l: language,
}); });
@ -25,7 +25,7 @@ export const getSteamAppDetails = async (
`http://store.steampowered.com/api/appdetails?${searchParams.toString()}` `http://store.steampowered.com/api/appdetails?${searchParams.toString()}`
) )
.then((response) => { .then((response) => {
if (response.data[objectID].success) return response.data[objectID].data; if (response.data[objectId].success) return response.data[objectId].data;
return null; return null;
}) })
.catch((err) => { .catch((err) => {

View file

@ -28,20 +28,18 @@ export const backupGame = ({
title, title,
backupPath, backupPath,
preview = false, preview = false,
winePrefix,
}: { }: {
title: string; title: string;
backupPath: string; backupPath: string;
preview?: boolean; preview?: boolean;
winePrefix?: string;
}) => { }) => {
const args = ["backup", title, "--api", "--force"]; const args = ["backup", title, "--api", "--force"];
if (preview) { if (preview) args.push("--preview");
args.push("--preview"); if (backupPath) args.push("--path", backupPath);
} if (winePrefix) args.push("--wine-prefix", winePrefix);
if (backupPath) {
args.push("--path", backupPath);
}
const result = cp.execFileSync(binaryPath, args); const result = cp.execFileSync(binaryPath, args);
@ -59,3 +57,5 @@ export const restoreBackup = (backupPath: string) => {
return JSON.parse(result.toString("utf-8")) as LudusaviBackup; return JSON.parse(result.toString("utf-8")) as LudusaviBackup;
}; };
// --wine-prefix

View file

@ -38,13 +38,13 @@ contextBridge.exposeInMainWorld("electron", {
searchGames: (query: string) => ipcRenderer.invoke("searchGames", query), searchGames: (query: string) => ipcRenderer.invoke("searchGames", query),
getCatalogue: (category: CatalogueCategory) => getCatalogue: (category: CatalogueCategory) =>
ipcRenderer.invoke("getCatalogue", category), ipcRenderer.invoke("getCatalogue", category),
getGameShopDetails: (objectID: string, shop: GameShop, language: string) => getGameShopDetails: (objectId: string, shop: GameShop, language: string) =>
ipcRenderer.invoke("getGameShopDetails", objectID, shop, language), ipcRenderer.invoke("getGameShopDetails", objectId, shop, language),
getRandomGame: () => ipcRenderer.invoke("getRandomGame"), getRandomGame: () => ipcRenderer.invoke("getRandomGame"),
getHowLongToBeat: (objectID: string, shop: GameShop, title: string) => getHowLongToBeat: (objectId: string, shop: GameShop, title: string) =>
ipcRenderer.invoke("getHowLongToBeat", objectID, shop, title), ipcRenderer.invoke("getHowLongToBeat", objectId, shop, title),
getGames: (take?: number, prevCursor?: number) => getGames: (take?: number, skip?: number) =>
ipcRenderer.invoke("getGames", take, prevCursor), ipcRenderer.invoke("getGames", take, skip),
searchGameRepacks: (query: string) => searchGameRepacks: (query: string) =>
ipcRenderer.invoke("searchGameRepacks", query), ipcRenderer.invoke("searchGameRepacks", query),
getGameStats: (objectId: string, shop: GameShop) => getGameStats: (objectId: string, shop: GameShop) =>
@ -65,8 +65,8 @@ contextBridge.exposeInMainWorld("electron", {
ipcRenderer.invoke("deleteDownloadSource", id), ipcRenderer.invoke("deleteDownloadSource", id),
/* Library */ /* Library */
addGameToLibrary: (objectID: string, title: string, shop: GameShop) => addGameToLibrary: (objectId: string, title: string, shop: GameShop) =>
ipcRenderer.invoke("addGameToLibrary", objectID, title, shop), ipcRenderer.invoke("addGameToLibrary", objectId, title, shop),
createGameShortcut: (id: number) => createGameShortcut: (id: number) =>
ipcRenderer.invoke("createGameShortcut", id), ipcRenderer.invoke("createGameShortcut", id),
updateExecutablePath: (id: number, executablePath: string) => updateExecutablePath: (id: number, executablePath: string) =>
@ -88,8 +88,8 @@ contextBridge.exposeInMainWorld("electron", {
removeGame: (gameId: number) => ipcRenderer.invoke("removeGame", gameId), removeGame: (gameId: number) => ipcRenderer.invoke("removeGame", gameId),
deleteGameFolder: (gameId: number) => deleteGameFolder: (gameId: number) =>
ipcRenderer.invoke("deleteGameFolder", gameId), ipcRenderer.invoke("deleteGameFolder", gameId),
getGameByObjectID: (objectID: string) => getGameByObjectId: (objectId: string) =>
ipcRenderer.invoke("getGameByObjectID", objectID), ipcRenderer.invoke("getGameByObjectId", objectId),
onGamesRunning: ( onGamesRunning: (
cb: ( cb: (
gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[] gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[]

View file

@ -26,6 +26,10 @@ globalStyle("::-webkit-scrollbar-thumb", {
borderRadius: "24px", borderRadius: "24px",
}); });
globalStyle("::-webkit-scrollbar-thumb:hover", {
backgroundColor: "rgba(255, 255, 255, 0.16)",
});
globalStyle("html, body, #root, main", { globalStyle("html, body, #root, main", {
height: "100%", height: "100%",
}); });

View file

@ -44,7 +44,7 @@ export function GameCard({ game, ...props }: GameCardProps) {
const handleHover = useCallback(() => { const handleHover = useCallback(() => {
if (!stats) { if (!stats) {
window.electron.getGameStats(game.objectID, game.shop).then((stats) => { window.electron.getGameStats(game.objectId, game.shop).then((stats) => {
setStats(stats); setStats(stats);
}); });
} }

View file

@ -140,7 +140,10 @@ export function Sidebar() {
event: React.MouseEvent, event: React.MouseEvent,
game: LibraryGame game: LibraryGame
) => { ) => {
const path = buildGameDetailsPath(game); const path = buildGameDetailsPath({
...game,
objectId: game.objectID,
});
if (path !== location.pathname) { if (path !== location.pathname) {
navigate(path); navigate(path);
} }

View file

@ -31,7 +31,7 @@ export const gameDetailsContext = createContext<GameDetailsContext>({
gameTitle: "", gameTitle: "",
isGameRunning: false, isGameRunning: false,
isLoading: false, isLoading: false,
objectID: undefined, objectId: undefined,
gameColor: "", gameColor: "",
showRepacksModal: false, showRepacksModal: false,
showGameOptionsModal: false, showGameOptionsModal: false,
@ -97,7 +97,7 @@ 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]);
@ -199,7 +199,7 @@ export function GameDetailsContextProvider({
gameTitle, gameTitle,
isGameRunning, isGameRunning,
isLoading, isLoading,
objectID: objectId, objectId,
gameColor, gameColor,
showGameOptionsModal, showGameOptionsModal,
showRepacksModal, showRepacksModal,

View file

@ -14,7 +14,7 @@ export interface GameDetailsContext {
gameTitle: string; gameTitle: string;
isGameRunning: boolean; isGameRunning: boolean;
isLoading: boolean; isLoading: boolean;
objectID: string | undefined; objectId: string | undefined;
gameColor: string; gameColor: string;
showRepacksModal: boolean; showRepacksModal: boolean;
showGameOptionsModal: boolean; showGameOptionsModal: boolean;

View file

@ -50,6 +50,7 @@ export function RepacksContextProvider({ children }: RepacksContextProps) {
}, []); }, []);
useEffect(() => { useEffect(() => {
console.log("CALLED");
indexRepacks(); indexRepacks();
}, [indexRepacks]); }, [indexRepacks]);

View file

@ -51,27 +51,24 @@ declare global {
searchGames: (query: string) => Promise<CatalogueEntry[]>; searchGames: (query: string) => Promise<CatalogueEntry[]>;
getCatalogue: (category: CatalogueCategory) => Promise<CatalogueEntry[]>; getCatalogue: (category: CatalogueCategory) => Promise<CatalogueEntry[]>;
getGameShopDetails: ( getGameShopDetails: (
objectID: string, objectId: string,
shop: GameShop, shop: GameShop,
language: string language: string
) => Promise<ShopDetails | null>; ) => Promise<ShopDetails | null>;
getRandomGame: () => Promise<Steam250Game>; getRandomGame: () => Promise<Steam250Game>;
getHowLongToBeat: ( getHowLongToBeat: (
objectID: string, objectId: string,
shop: GameShop, shop: GameShop,
title: string title: string
) => Promise<HowLongToBeatCategory[] | null>; ) => Promise<HowLongToBeatCategory[] | null>;
getGames: ( getGames: (take?: number, skip?: number) => Promise<CatalogueEntry[]>;
take?: number,
prevCursor?: number
) => Promise<{ results: CatalogueEntry[]; cursor: number }>;
searchGameRepacks: (query: string) => Promise<GameRepack[]>; searchGameRepacks: (query: string) => Promise<GameRepack[]>;
getGameStats: (objectId: string, shop: GameShop) => Promise<GameStats>; getGameStats: (objectId: string, shop: GameShop) => Promise<GameStats>;
getTrendingGames: () => Promise<TrendingGame[]>; getTrendingGames: () => Promise<TrendingGame[]>;
/* Library */ /* Library */
addGameToLibrary: ( addGameToLibrary: (
objectID: string, objectId: string,
title: string, title: string,
shop: GameShop shop: GameShop
) => Promise<void>; ) => Promise<void>;
@ -87,7 +84,7 @@ declare global {
removeGameFromLibrary: (gameId: number) => Promise<void>; removeGameFromLibrary: (gameId: number) => Promise<void>;
removeGame: (gameId: number) => Promise<void>; removeGame: (gameId: number) => Promise<void>;
deleteGameFolder: (gameId: number) => Promise<unknown>; deleteGameFolder: (gameId: number) => Promise<unknown>;
getGameByObjectID: (objectID: string) => Promise<Game | null>; getGameByObjectId: (objectId: string) => Promise<Game | null>;
onGamesRunning: ( onGamesRunning: (
cb: ( cb: (
gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[] gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[]

View file

@ -27,11 +27,11 @@ export const getSteamLanguage = (language: string) => {
}; };
export const buildGameDetailsPath = ( export const buildGameDetailsPath = (
game: { shop: GameShop; objectID: string; title: string }, game: { shop: GameShop; objectId: string; title: string },
params: Record<string, string> = {} params: Record<string, string> = {}
) => { ) => {
const searchParams = new URLSearchParams({ title: game.title, ...params }); const searchParams = new URLSearchParams({ title: game.title, ...params });
return `/game/${game.shop}/${game.objectID}?${searchParams.toString()}`; return `/game/${game.shop}/${game.objectId}?${searchParams.toString()}`;
}; };
export const darkenColor = (color: string, amount: number, alpha: number = 1) => export const darkenColor = (color: string, amount: number, alpha: number = 1) =>

View file

@ -64,7 +64,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
<Route path="/" Component={Home} /> <Route path="/" Component={Home} />
<Route path="/catalogue" Component={Catalogue} /> <Route path="/catalogue" Component={Catalogue} />
<Route path="/downloads" Component={Downloads} /> <Route path="/downloads" Component={Downloads} />
<Route path="/game/:shop/:objectID" Component={GameDetails} /> <Route path="/game/:shop/:objectId" Component={GameDetails} />
<Route path="/search" Component={SearchResults} /> <Route path="/search" Component={SearchResults} />
<Route path="/settings" Component={Settings} /> <Route path="/settings" Component={Settings} />
<Route path="/profile/:userId" Component={Profile} /> <Route path="/profile/:userId" Component={Profile} />

View file

@ -24,12 +24,10 @@ export function Catalogue() {
const contentRef = useRef<HTMLElement>(null); const contentRef = useRef<HTMLElement>(null);
const cursorRef = useRef<number>(0);
const navigate = useNavigate(); const navigate = useNavigate();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const cursor = Number(searchParams.get("cursor") ?? 0); const skip = Number(searchParams.get("skip") ?? 0);
const handleGameClick = (game: CatalogueEntry) => { const handleGameClick = (game: CatalogueEntry) => {
dispatch(clearSearch()); dispatch(clearSearch());
@ -42,11 +40,10 @@ export function Catalogue() {
setSearchResults([]); setSearchResults([]);
window.electron window.electron
.getGames(24, cursor) .getGames(24, skip)
.then(({ results, cursor }) => { .then((results) => {
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
cursorRef.current = cursor;
setSearchResults(results); setSearchResults(results);
resolve(null); resolve(null);
}, 500); }, 500);
@ -55,11 +52,11 @@ export function Catalogue() {
.finally(() => { .finally(() => {
setIsLoading(false); setIsLoading(false);
}); });
}, [dispatch, cursor, searchParams]); }, [dispatch, skip, searchParams]);
const handleNextPage = () => { const handleNextPage = () => {
const params = new URLSearchParams({ const params = new URLSearchParams({
cursor: cursorRef.current.toString(), skip: String(skip + 24),
}); });
navigate(`/catalogue?${params.toString()}`); navigate(`/catalogue?${params.toString()}`);
@ -80,7 +77,7 @@ export function Catalogue() {
<Button <Button
onClick={() => navigate(-1)} onClick={() => navigate(-1)}
theme="outline" theme="outline"
disabled={cursor === 0 || isLoading} disabled={skip === 0 || isLoading}
> >
<ArrowLeftIcon /> <ArrowLeftIcon />
{t("previous_page")} {t("previous_page")}
@ -103,7 +100,7 @@ export function Catalogue() {
<> <>
{searchResults.map((game) => ( {searchResults.map((game) => (
<GameCard <GameCard
key={game.objectID} key={game.objectId}
game={game} game={game}
onClick={() => handleGameClick(game)} onClick={() => handleGameClick(game)}
/> />

View file

@ -93,6 +93,7 @@ export const downloadRightContent = style({
padding: `${SPACING_UNIT * 2}px`, padding: `${SPACING_UNIT * 2}px`,
flex: "1", flex: "1",
gap: `${SPACING_UNIT}px`, gap: `${SPACING_UNIT}px`,
background: "linear-gradient(90deg, transparent 20%, rgb(0 0 0 / 20%) 100%)",
}); });
export const downloadActions = style({ export const downloadActions = style({

View file

@ -227,7 +227,14 @@ export function DownloadGroup({
<button <button
type="button" type="button"
className={styles.downloadTitle} className={styles.downloadTitle}
onClick={() => navigate(buildGameDetailsPath(game))} onClick={() =>
navigate(
buildGameDetailsPath({
...game,
objectId: game.objectID,
})
)
}
> >
{game.title} {game.title}
</button> </button>

View file

@ -1,4 +1,4 @@
import { Modal, ModalProps, TextField } from "@renderer/components"; import { Modal, ModalProps } from "@renderer/components";
import { useContext, useMemo } from "react"; import { useContext, useMemo } from "react";
import { cloudSyncContext } from "@renderer/context"; import { cloudSyncContext } from "@renderer/context";
@ -11,6 +11,8 @@ export function CloudSyncFilesModal({
}: CloudSyncFilesModalProps) { }: CloudSyncFilesModalProps) {
const { backupPreview } = useContext(cloudSyncContext); const { backupPreview } = useContext(cloudSyncContext);
console.log(backupPreview);
const files = useMemo(() => { const files = useMemo(() => {
if (!backupPreview) { if (!backupPreview) {
return []; return [];
@ -51,22 +53,24 @@ export function CloudSyncFilesModal({
))} ))}
</div> */} </div> */}
<ul <table>
style={{ <thead>
margin: 0, <tr>
padding: 0, <th style={{ textAlign: "left" }}>Arquivo</th>
listStyle: "none", <th style={{ textAlign: "left" }}>Hash</th>
gap: 16, <th style={{ textAlign: "left" }}>Tamanho</th>
display: "flex", </tr>
flexDirection: "column", </thead>
}} <tbody>
> {files.map((file) => (
{files.map((file) => ( <tr key={file.path}>
<li key={file.path}> <td style={{ textAlign: "left" }}>{file.path}</td>
<TextField value={file.path} readOnly /> <td style={{ textAlign: "left" }}>{file.change}</td>
</li> <td style={{ textAlign: "left" }}>{file.path}</td>
))} </tr>
</ul> ))}
</tbody>
</table>
</Modal> </Modal>
); );
} }

View file

@ -43,7 +43,7 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
setShowCloudSyncFilesModal, setShowCloudSyncFilesModal,
} = useContext(cloudSyncContext); } = useContext(cloudSyncContext);
const { objectID, shop, gameTitle } = useContext(gameDetailsContext); const { objectId, shop, gameTitle } = useContext(gameDetailsContext);
const { showSuccessToast, showErrorToast } = useToast(); const { showSuccessToast, showErrorToast } = useToast();
@ -63,13 +63,13 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
useEffect(() => { useEffect(() => {
gameBackupsTable gameBackupsTable
.where({ shop: shop, objectId: objectID }) .where({ shop: shop, objectId })
.last() .last()
.then((lastBackup) => setLastBackup(lastBackup || null)); .then((lastBackup) => setLastBackup(lastBackup || null));
const removeBackupDownloadProgressListener = const removeBackupDownloadProgressListener =
window.electron.onBackupDownloadProgress( window.electron.onBackupDownloadProgress(
objectID!, objectId!,
shop, shop,
(progressEvent) => { (progressEvent) => {
setBackupDownloadProgress(progressEvent); setBackupDownloadProgress(progressEvent);
@ -79,7 +79,7 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
return () => { return () => {
removeBackupDownloadProgressListener(); removeBackupDownloadProgressListener();
}; };
}, [backupPreview, objectID, shop]); }, [backupPreview, objectId, shop]);
const handleBackupInstallClick = async (artifactId: string) => { const handleBackupInstallClick = async (artifactId: string) => {
setBackupDownloadProgress(null); setBackupDownloadProgress(null);

View file

@ -26,7 +26,7 @@ export function GameDetailsContent() {
const { t } = useTranslation("game_details"); const { t } = useTranslation("game_details");
const { const {
objectID, objectId,
shopDetails, shopDetails,
game, game,
gameColor, gameColor,
@ -42,7 +42,7 @@ export function GameDetailsContent() {
const [backdropOpactiy, setBackdropOpacity] = useState(1); const [backdropOpactiy, setBackdropOpacity] = useState(1);
const handleHeroLoad = async () => { const handleHeroLoad = async () => {
const output = await average(steamUrlBuilder.libraryHero(objectID!), { const output = await average(steamUrlBuilder.libraryHero(objectId!), {
amount: 1, amount: 1,
format: "hex", format: "hex",
}); });
@ -56,7 +56,7 @@ export function GameDetailsContent() {
useEffect(() => { useEffect(() => {
setBackdropOpacity(1); setBackdropOpacity(1);
}, [objectID]); }, [objectId]);
const onScroll: React.UIEventHandler<HTMLElement> = (event) => { const onScroll: React.UIEventHandler<HTMLElement> = (event) => {
const heroHeight = heroRef.current?.clientHeight ?? styles.HERO_HEIGHT; const heroHeight = heroRef.current?.clientHeight ?? styles.HERO_HEIGHT;
@ -90,7 +90,7 @@ export function GameDetailsContent() {
return ( return (
<div className={styles.wrapper({ blurredContent: hasNSFWContentBlocked })}> <div className={styles.wrapper({ blurredContent: hasNSFWContentBlocked })}>
<img <img
src={steamUrlBuilder.libraryHero(objectID!)} src={steamUrlBuilder.libraryHero(objectId!)}
className={styles.heroImage} className={styles.heroImage}
alt={game?.title} alt={game?.title}
onLoad={handleHeroLoad} onLoad={handleHeroLoad}
@ -116,7 +116,7 @@ export function GameDetailsContent() {
> >
<div className={styles.heroContent}> <div className={styles.heroContent}>
<img <img
src={steamUrlBuilder.logo(objectID!)} src={steamUrlBuilder.logo(objectId!)}
className={styles.gameLogo} className={styles.gameLogo}
alt={game?.title} alt={game?.title}
/> />

View file

@ -33,7 +33,7 @@ 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, shop } = useParams(); const { objectId, shop } = useParams();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const fromRandomizer = searchParams.get("fromRandomizer"); const fromRandomizer = searchParams.get("fromRandomizer");
@ -50,7 +50,7 @@ export function GameDetails() {
window.electron.getRandomGame().then((randomGame) => { window.electron.getRandomGame().then((randomGame) => {
setRandomGame(randomGame); setRandomGame(randomGame);
}); });
}, [objectID]); }, [objectId]);
const handleRandomizerClick = () => { const handleRandomizerClick = () => {
if (randomGame) { if (randomGame) {
@ -82,7 +82,7 @@ export function GameDetails() {
<GameDetailsContextProvider <GameDetailsContextProvider
gameTitle={gameTitle!} gameTitle={gameTitle!}
shop={shop! as GameShop} shop={shop! as GameShop}
objectId={objectID!} objectId={objectId!}
> >
<GameDetailsContextConsumer> <GameDetailsContextConsumer>
{({ {({
@ -105,7 +105,7 @@ export function GameDetails() {
) => { ) => {
await startDownload({ await startDownload({
repackId: repack.id, repackId: repack.id,
objectID: objectID!, objectId: objectId!,
title: gameTitle, title: gameTitle,
downloader, downloader,
shop: shop as GameShop, shop: shop as GameShop,
@ -125,7 +125,7 @@ export function GameDetails() {
return ( return (
<CloudSyncContextProvider <CloudSyncContextProvider
objectId={objectID!} objectId={objectId!}
shop={shop! as GameShop} shop={shop! as GameShop}
> >
<CloudSyncContextConsumer> <CloudSyncContextConsumer>

View file

@ -18,7 +18,7 @@ export function HeroPanelActions() {
game, game,
repacks, repacks,
isGameRunning, isGameRunning,
objectID, objectId,
gameTitle, gameTitle,
setShowGameOptionsModal, setShowGameOptionsModal,
setShowRepacksModal, setShowRepacksModal,
@ -39,7 +39,7 @@ export function HeroPanelActions() {
setToggleLibraryGameDisabled(true); setToggleLibraryGameDisabled(true);
try { try {
await window.electron.addGameToLibrary(objectID!, gameTitle, "steam"); await window.electron.addGameToLibrary(objectId!, gameTitle, "steam");
updateLibrary(); updateLibrary();
updateGame(); updateGame();

View file

@ -24,11 +24,11 @@ export function Sidebar() {
const { numberFormatter } = useFormat(); const { numberFormatter } = useFormat();
// useEffect(() => { // useEffect(() => {
// if (objectID) { // if (objectId) {
// setHowLongToBeat({ isLoading: true, data: null }); // setHowLongToBeat({ isLoading: true, data: null });
// window.electron // window.electron
// .getHowLongToBeat(objectID, "steam", gameTitle) // .getHowLongToBeat(objectId, "steam", gameTitle)
// .then((howLongToBeat) => { // .then((howLongToBeat) => {
// setHowLongToBeat({ isLoading: false, data: howLongToBeat }); // setHowLongToBeat({ isLoading: false, data: howLongToBeat });
// }) // })
@ -36,7 +36,7 @@ export function Sidebar() {
// setHowLongToBeat({ isLoading: false, data: null }); // setHowLongToBeat({ isLoading: false, data: null });
// }); // });
// } // }
// }, [objectID, gameTitle]); // }, [objectId, gameTitle]);
return ( return (
<aside className={styles.contentSidebar}> <aside className={styles.contentSidebar}>

View file

@ -186,7 +186,7 @@ export function Home() {
)) ))
: catalogue[currentCatalogueCategory].map((result) => ( : catalogue[currentCatalogueCategory].map((result) => (
<GameCard <GameCard
key={result.objectID} key={result.objectId}
game={result} game={result}
onClick={() => navigate(buildGameDetailsPath(result))} onClick={() => navigate(buildGameDetailsPath(result))}
/> />

View file

@ -115,7 +115,7 @@ export function SearchResults() {
<> <>
{searchResults.map((game) => ( {searchResults.map((game) => (
<GameCard <GameCard
key={game.objectID} key={game.objectId}
game={game} game={game}
onClick={() => handleGameClick(game)} onClick={() => handleGameClick(game)}
/> />

View file

@ -15,7 +15,7 @@ export const gameCover = style({
height: "172%", height: "172%",
position: "absolute", position: "absolute",
background: background:
"linear-gradient(35deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.07) 51.5%, rgba(255, 255, 255, 0.15) 54%, rgba(255, 255, 255, 0.15) 100%);", "linear-gradient(35deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.07) 51.5%, rgba(255, 255, 255, 0.15) 54%, rgba(255, 255, 255, 0.15) 100%)",
transition: "all ease 0.3s", transition: "all ease 0.3s",
transform: "translateY(-36%)", transform: "translateY(-36%)",
opacity: "0.5", opacity: "0.5",
@ -189,3 +189,15 @@ export const defaultAvatarWrapper = style({
border: `solid 1px ${vars.color.border}`, border: `solid 1px ${vars.color.border}`,
borderRadius: "4px", borderRadius: "4px",
}); });
export const achievementsProgressBar = style({
width: "100%",
height: "4px",
transition: "all ease 0.2s",
"::-webkit-progress-bar": {
backgroundColor: "rgba(255, 255, 255, 0.15)",
},
"::-webkit-progress-value": {
backgroundColor: vars.color.muted,
},
});

View file

@ -7,7 +7,7 @@ import { steamUrlBuilder } from "@shared";
import { SPACING_UNIT, vars } from "@renderer/theme.css"; import { SPACING_UNIT, vars } from "@renderer/theme.css";
import * as styles from "./profile-content.css"; import * as styles from "./profile-content.css";
import { ClockIcon, TelescopeIcon } from "@primer/octicons-react"; import { ClockIcon, TelescopeIcon, TrophyIcon } from "@primer/octicons-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { LockedProfile } from "./locked-profile"; import { LockedProfile } from "./locked-profile";
@ -15,7 +15,10 @@ import { ReportProfile } from "../report-profile/report-profile";
import { FriendsBox } from "./friends-box"; import { FriendsBox } from "./friends-box";
import { RecentGamesBox } from "./recent-games-box"; import { RecentGamesBox } from "./recent-games-box";
import { UserGame } from "@types"; import { UserGame } from "@types";
import { buildGameDetailsPath } from "@renderer/helpers"; import {
buildGameDetailsPath,
formatDownloadProgress,
} from "@renderer/helpers";
import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants"; import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants";
export function ProfileContent() { export function ProfileContent() {
@ -44,7 +47,7 @@ export function ProfileContent() {
const buildUserGameDetailsPath = (game: UserGame) => const buildUserGameDetailsPath = (game: UserGame) =>
buildGameDetailsPath({ buildGameDetailsPath({
...game, ...game,
objectID: game.objectId, objectId: game.objectId,
}); });
const formatPlayTime = useCallback( const formatPlayTime = useCallback(
@ -115,6 +118,7 @@ export function ProfileContent() {
borderRadius: 4, borderRadius: 4,
overflow: "hidden", overflow: "hidden",
position: "relative", position: "relative",
display: "flex",
}} }}
className={styles.game} className={styles.game}
> >
@ -126,23 +130,85 @@ export function ProfileContent() {
className={styles.gameCover} className={styles.gameCover}
onClick={() => navigate(buildUserGameDetailsPath(game))} onClick={() => navigate(buildUserGameDetailsPath(game))}
> >
<div style={{ position: "absolute", padding: 4 }}> <div
style={{
position: "absolute",
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "space-between",
height: "100%",
width: "100%",
background:
"linear-gradient(0deg, rgba(0, 0, 0, 0.7) 20%, transparent 100%)",
padding: 8,
}}
>
<small <small
style={{ style={{
backgroundColor: vars.color.background, backgroundColor: vars.color.background,
color: vars.color.muted, color: vars.color.muted,
// border: `solid 1px ${vars.color.border}`, border: `solid 1px ${vars.color.border}`,
borderRadius: 4, borderRadius: 4,
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: 4, gap: 4,
padding: "4px 4px", padding: "4px",
}} }}
> >
<ClockIcon size={11} /> <ClockIcon size={11} />
{formatPlayTime(game.playTimeInSeconds)} {formatPlayTime(game.playTimeInSeconds)}
</small> </small>
<div
style={{
color: "white",
width: "100%",
display: "flex",
flexDirection: "column",
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: 8,
color: vars.color.muted,
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: 8,
}}
>
<TrophyIcon size={13} />
<span>
{game.unlockedAchievementCount} /{" "}
{game.achievementCount}
</span>
</div>
<span>
{formatDownloadProgress(
game.unlockedAchievementCount /
game.achievementCount
)}
</span>
</div>
<progress
max={1}
value={
game.unlockedAchievementCount /
game.achievementCount
}
className={styles.achievementsProgressBar}
/>
</div>
</div> </div>
<img <img
src={steamUrlBuilder.cover(game.objectId)} src={steamUrlBuilder.cover(game.objectId)}
alt={game.title} alt={game.title}
@ -178,6 +244,7 @@ export function ProfileContent() {
userStats, userStats,
numberFormatter, numberFormatter,
t, t,
formatPlayTime,
navigate, navigate,
]); ]);

View file

@ -37,7 +37,7 @@ export function RecentGamesBox() {
const buildUserGameDetailsPath = (game: UserGame) => const buildUserGameDetailsPath = (game: UserGame) =>
buildGameDetailsPath({ buildGameDetailsPath({
...game, ...game,
objectID: game.objectId, objectId: game.objectId,
}); });
if (!userProfile?.recentGames.length) return null; if (!userProfile?.recentGames.length) return null;

View file

@ -70,7 +70,7 @@ export const heroPanel = style({
export const userInformation = style({ export const userInformation = style({
display: "flex", display: "flex",
padding: `${SPACING_UNIT * 4}px ${SPACING_UNIT * 3}px`, padding: `${SPACING_UNIT * 6}px ${SPACING_UNIT * 3}px`,
alignItems: "center", alignItems: "center",
gap: `${SPACING_UNIT * 2}px`, gap: `${SPACING_UNIT * 2}px`,
}); });

View file

@ -1,4 +1,4 @@
import { SPACING_UNIT } from "@renderer/theme.css"; import { SPACING_UNIT, vars } from "@renderer/theme.css";
import * as styles from "./profile-hero.css"; import * as styles from "./profile-hero.css";
import { useCallback, useContext, useMemo, useState } from "react"; import { useCallback, useContext, useMemo, useState } from "react";
@ -10,6 +10,7 @@ import {
PersonAddIcon, PersonAddIcon,
PersonIcon, PersonIcon,
SignOutIcon, SignOutIcon,
UploadIcon,
XCircleFillIcon, XCircleFillIcon,
} from "@primer/octicons-react"; } from "@primer/octicons-react";
import { buildGameDetailsPath } from "@renderer/helpers"; import { buildGameDetailsPath } from "@renderer/helpers";
@ -36,8 +37,7 @@ export function ProfileHero() {
const [showEditProfileModal, setShowEditProfileModal] = useState(false); const [showEditProfileModal, setShowEditProfileModal] = useState(false);
const [isPerformingAction, setIsPerformingAction] = useState(false); const [isPerformingAction, setIsPerformingAction] = useState(false);
const { isMe, heroBackground, getUserProfile, userProfile } = const { isMe, getUserProfile, userProfile } = useContext(userProfileContext);
useContext(userProfileContext);
const { const {
signOut, signOut,
updateFriendRequestState, updateFriendRequestState,
@ -48,6 +48,8 @@ export function ProfileHero() {
const { gameRunning } = useAppSelector((state) => state.gameRunning); const { gameRunning } = useAppSelector((state) => state.gameRunning);
const [hero, setHero] = useState("");
const { t } = useTranslation("user_profile"); const { t } = useTranslation("user_profile");
const { formatDistance } = useDate(); const { formatDistance } = useDate();
@ -124,6 +126,7 @@ export function ProfileHero() {
theme="outline" theme="outline"
onClick={() => setShowEditProfileModal(true)} onClick={() => setShowEditProfileModal(true)}
disabled={isPerformingAction} disabled={isPerformingAction}
style={{ borderColor: vars.color.body }}
> >
<PencilIcon /> <PencilIcon />
{t("edit_profile")} {t("edit_profile")}
@ -148,6 +151,7 @@ export function ProfileHero() {
theme="outline" theme="outline"
onClick={() => handleFriendAction(userProfile.id, "SEND")} onClick={() => handleFriendAction(userProfile.id, "SEND")}
disabled={isPerformingAction} disabled={isPerformingAction}
style={{ borderColor: vars.color.body }}
> >
<PersonAddIcon /> <PersonAddIcon />
{t("add_friend")} {t("add_friend")}
@ -198,6 +202,7 @@ export function ProfileHero() {
handleFriendAction(userProfile.relation!.BId, "CANCEL") handleFriendAction(userProfile.relation!.BId, "CANCEL")
} }
disabled={isPerformingAction} disabled={isPerformingAction}
style={{ borderColor: vars.color.body }}
> >
<XCircleFillIcon /> {t("cancel_request")} <XCircleFillIcon /> {t("cancel_request")}
</Button> </Button>
@ -212,11 +217,12 @@ export function ProfileHero() {
handleFriendAction(userProfile.relation!.AId, "ACCEPTED") handleFriendAction(userProfile.relation!.AId, "ACCEPTED")
} }
disabled={isPerformingAction} disabled={isPerformingAction}
style={{ borderColor: vars.color.body }}
> >
<CheckCircleFillIcon /> {t("accept_request")} <CheckCircleFillIcon /> {t("accept_request")}
</Button> </Button>
<Button <Button
theme="outline" theme="danger"
onClick={() => onClick={() =>
handleFriendAction(userProfile.relation!.AId, "REFUSED") handleFriendAction(userProfile.relation!.AId, "REFUSED")
} }
@ -246,7 +252,6 @@ export function ProfileHero() {
if (gameRunning) if (gameRunning)
return { return {
...gameRunning, ...gameRunning,
objectId: gameRunning.objectID,
sessionDurationInSeconds: gameRunning.sessionDurationInMillis / 1000, sessionDurationInSeconds: gameRunning.sessionDurationInMillis / 1000,
}; };
@ -255,6 +260,43 @@ export function ProfileHero() {
return userProfile?.currentGame; return userProfile?.currentGame;
}, [isMe, userProfile, gameRunning]); }, [isMe, userProfile, gameRunning]);
const handleChangeCoverClick = async () => {
const { filePaths } = await window.electron.showOpenDialog({
properties: ["openFile"],
filters: [
{
name: "Image",
extensions: ["jpg", "jpeg", "png", "gif", "webp"],
},
],
});
if (filePaths && filePaths.length > 0) {
const path = filePaths[0];
const { imagePath } = await window.electron
.processProfileImage(path)
.catch(() => {
showErrorToast(t("image_process_failure"));
return { imagePath: null };
});
console.log("imagePath", imagePath);
setHero(imagePath);
// onChange(imagePath);
}
};
const getImageUrl = () => {
if (hero) return `local:${hero}`;
// if (userDetails?.profileImageUrl) return userDetails.profileImageUrl;
return null;
};
// const imageUrl = getImageUrl();
return ( return (
<> <>
{/* <ConfirmationModal {/* <ConfirmationModal
@ -270,12 +312,9 @@ export function ProfileHero() {
onClose={() => setShowEditProfileModal(false)} onClose={() => setShowEditProfileModal(false)}
/> />
<section <section className={styles.profileContentBox}>
className={styles.profileContentBox}
// style={{ background: heroBackground }}
>
<img <img
src="https://wallpapers.com/images/featured/cyberpunk-anime-dfyw8eb7bqkw278u.jpg" src={getImageUrl()}
alt="" alt=""
style={{ style={{
position: "absolute", position: "absolute",
@ -286,7 +325,8 @@ export function ProfileHero() {
/> />
<div <div
style={{ style={{
background: heroBackground, background:
"linear-gradient(135deg, rgb(0 0 0 / 70%), rgb(0 0 0 / 60%))",
width: "100%", width: "100%",
height: "100%", height: "100%",
zIndex: 1, zIndex: 1,
@ -324,7 +364,7 @@ export function ProfileHero() {
<Link <Link
to={buildGameDetailsPath({ to={buildGameDetailsPath({
...currentGame, ...currentGame,
objectID: currentGame.objectId, objectId: currentGame.objectID,
})} })}
> >
{currentGame.title} {currentGame.title}
@ -345,12 +385,30 @@ export function ProfileHero() {
</div> </div>
)} )}
</div> </div>
<Button
theme="outline"
style={{
position: "absolute",
top: 16,
right: 16,
borderColor: vars.color.body,
}}
onClick={handleChangeCoverClick}
>
<UploadIcon />
Upload cover
</Button>
</div> </div>
</div> </div>
<div <div
className={styles.heroPanel} className={styles.heroPanel}
style={{ background: heroBackground }} // style={{ background: heroBackground }}
style={{
background:
"linear-gradient(135deg, rgb(0 0 0 / 70%), rgb(0 0 0 / 60%))",
}}
> >
<div <div
style={{ style={{

View file

@ -5,14 +5,14 @@ import flexSearch from "flexsearch";
const index = new flexSearch.Index(); const index = new flexSearch.Index();
const state = {
repacks: [] as any[],
};
interface SerializedGameRepack extends Omit<GameRepack, "uris"> { interface SerializedGameRepack extends Omit<GameRepack, "uris"> {
uris: string; uris: string;
} }
const state = {
repacks: [] as SerializedGameRepack[],
};
self.onmessage = async ( self.onmessage = async (
event: MessageEvent<[string, string] | "INDEX_REPACKS"> event: MessageEvent<[string, string] | "INDEX_REPACKS">
) => { ) => {

View file

@ -91,14 +91,14 @@ export const getDownloadersForUris = (uris: string[]) => {
}; };
export const steamUrlBuilder = { export const steamUrlBuilder = {
library: (objectID: string) => library: (objectId: string) =>
`https://steamcdn-a.akamaihd.net/steam/apps/${objectID}/header.jpg`, `https://steamcdn-a.akamaihd.net/steam/apps/${objectId}/header.jpg`,
libraryHero: (objectID: string) => libraryHero: (objectId: string) =>
`https://steamcdn-a.akamaihd.net/steam/apps/${objectID}/library_hero.jpg`, `https://steamcdn-a.akamaihd.net/steam/apps/${objectId}/library_hero.jpg`,
logo: (objectID: string) => logo: (objectId: string) =>
`https://cdn.cloudflare.steamstatic.com/steam/apps/${objectID}/logo.png`, `https://cdn.cloudflare.steamstatic.com/steam/apps/${objectId}/logo.png`,
cover: (objectID: string) => cover: (objectId: string) =>
`https://cdn.cloudflare.steamstatic.com/steam/apps/${objectID}/library_600x900.jpg`, `https://cdn.cloudflare.steamstatic.com/steam/apps/${objectId}/library_600x900.jpg`,
icon: (objectID: string, clientIcon: string) => icon: (objectId: string, clientIcon: string) =>
`https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/${objectID}/${clientIcon}.ico`, `https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/${objectId}/${clientIcon}.ico`,
}; };

View file

@ -29,7 +29,7 @@ export interface GameRepack {
} }
export type ShopDetails = SteamAppDetails & { export type ShopDetails = SteamAppDetails & {
objectID: string; objectId: string;
}; };
export interface TorrentFile { export interface TorrentFile {
@ -39,7 +39,7 @@ export interface TorrentFile {
/* Used by the catalogue */ /* Used by the catalogue */
export interface CatalogueEntry { export interface CatalogueEntry {
objectID: string; objectId: string;
shop: GameShop; shop: GameShop;
title: string; title: string;
/* Epic Games covers cannot be guessed with objectID */ /* Epic Games covers cannot be guessed with objectID */
@ -54,6 +54,8 @@ export interface UserGame {
cover: string; cover: string;
playTimeInSeconds: number; playTimeInSeconds: number;
lastTimePlayed: Date | null; lastTimePlayed: Date | null;
unlockedAchievementCount: number;
achievementCount: number;
} }
export interface DownloadQueue { export interface DownloadQueue {
@ -126,7 +128,7 @@ export interface HowLongToBeatCategory {
export interface Steam250Game { export interface Steam250Game {
title: string; title: string;
objectID: string; objectId: string;
} }
export interface SteamGame { export interface SteamGame {
@ -142,7 +144,7 @@ export type AppUpdaterEvent =
/* Events */ /* Events */
export interface StartGameDownloadPayload { export interface StartGameDownloadPayload {
repackId: number; repackId: number;
objectID: string; objectId: string;
title: string; title: string;
shop: GameShop; shop: GameShop;
uri: string; uri: string;
@ -187,7 +189,7 @@ export interface UserRelation {
updatedAt: string; updatedAt: string;
} }
export interface UserProfileCurrentGame extends Omit<GameRunning, "objectID"> { export interface UserProfileCurrentGame extends Omit<GameRunning, "objectId"> {
objectId: string; objectId: string;
sessionDurationInSeconds: number; sessionDurationInSeconds: number;
} }

303
yarn.lock
View file

@ -991,6 +991,13 @@
wrap-ansi "^8.1.0" wrap-ansi "^8.1.0"
wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
"@isaacs/fs-minipass@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32"
integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==
dependencies:
minipass "^7.0.4"
"@jimp/bmp@^0.22.10": "@jimp/bmp@^0.22.10":
version "0.22.12" version "0.22.12"
resolved "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz" resolved "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz"
@ -1923,20 +1930,6 @@
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"
@ -2311,13 +2304,6 @@
"@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"
@ -2609,11 +2595,6 @@ 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"
@ -2743,32 +2724,6 @@ 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"
@ -2908,11 +2863,6 @@ 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"
@ -2962,21 +2912,11 @@ 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"
@ -3089,11 +3029,6 @@ 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"
@ -3269,6 +3204,11 @@ chownr@^2.0.0:
resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz"
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
chownr@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4"
integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==
chromium-pickle-js@^0.2.0: chromium-pickle-js@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz" resolved "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz"
@ -3414,17 +3354,6 @@ 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"
@ -3477,11 +3406,6 @@ 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"
@ -3509,19 +3433,6 @@ 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"
@ -4345,11 +4256,6 @@ 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"
@ -4396,11 +4302,6 @@ 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"
@ -4721,18 +4622,6 @@ 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"
@ -4744,6 +4633,18 @@ glob@^10.3.10:
minipass "^7.0.4" minipass "^7.0.4"
path-scurry "^1.11.0" path-scurry "^1.11.0"
glob@^10.3.7:
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@^7.1.3, glob@^7.1.6: glob@^7.1.3, glob@^7.1.6:
version "7.2.3" version "7.2.3"
resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz"
@ -5101,7 +5002,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.0.3: inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4:
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==
@ -5298,11 +5199,6 @@ 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"
@ -5361,11 +5257,6 @@ 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"
@ -5622,13 +5513,6 @@ 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"
@ -5917,7 +5801,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.0, minimatch@^5.1.1: minimatch@^5.0.1, 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==
@ -5985,6 +5869,14 @@ minizlib@^2.1.1:
minipass "^3.0.0" minipass "^3.0.0"
yallist "^4.0.0" yallist "^4.0.0"
minizlib@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.1.tgz#46d5329d1eb3c83924eff1d3b858ca0a31581012"
integrity sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==
dependencies:
minipass "^7.0.4"
rimraf "^5.0.5"
mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
version "0.5.3" version "0.5.3"
resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz" resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz"
@ -6007,6 +5899,11 @@ mkdirp@^2.1.3:
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz"
integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==
mkdirp@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50"
integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==
mlly@^1.4.2, mlly@^1.7.0: mlly@^1.4.2, mlly@^1.7.0:
version "1.7.0" version "1.7.0"
resolved "https://registry.npmjs.org/mlly/-/mlly-1.7.0.tgz" resolved "https://registry.npmjs.org/mlly/-/mlly-1.7.0.tgz"
@ -6580,16 +6477,6 @@ 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"
@ -6650,11 +6537,6 @@ 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"
@ -6748,19 +6630,6 @@ 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"
@ -6770,17 +6639,6 @@ 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"
@ -6788,13 +6646,6 @@ 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"
@ -6948,6 +6799,13 @@ rimraf@^3.0.2:
dependencies: dependencies:
glob "^7.1.3" glob "^7.1.3"
rimraf@^5.0.5:
version "5.0.10"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c"
integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==
dependencies:
glob "^10.3.7"
roarr@^2.15.3: roarr@^2.15.3:
version "2.15.4" version "2.15.4"
resolved "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz" resolved "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz"
@ -7012,11 +6870,6 @@ 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"
@ -7234,17 +7087,6 @@ 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"
@ -7318,20 +7160,13 @@ 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.3.0: string_decoder@^1.1.1:
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"
@ -7446,15 +7281,6 @@ 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"
@ -7467,6 +7293,18 @@ tar@^6.1.12:
mkdirp "^1.0.3" mkdirp "^1.0.3"
yallist "^4.0.0" yallist "^4.0.0"
tar@^7.4.3:
version "7.4.3"
resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571"
integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==
dependencies:
"@isaacs/fs-minipass" "^4.0.0"
chownr "^3.0.0"
minipass "^7.1.2"
minizlib "^3.0.1"
mkdirp "^3.0.1"
yallist "^5.0.0"
tarn@^3.0.2: tarn@^3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693" resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693"
@ -7480,13 +7318,6 @@ 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"
@ -7837,7 +7668,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==
@ -8096,6 +7927,11 @@ yallist@^4.0.0:
resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yallist@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533"
integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==
yaml@^2.4.1: yaml@^2.4.1:
version "2.4.2" version "2.4.2"
resolved "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz" resolved "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz"
@ -8170,15 +8006,6 @@ 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"