Electron wrapper refactor

pull/1425/head
Даниїл Григор'єв 2 years ago
parent 57678664d4
commit 3e0f00b077
No known key found for this signature in database
GPG Key ID: B890DF16341D8C1D

@ -1,6 +1,5 @@
artwork/*
build/*
electron/*
gulp/*
node_modules/*
res/*

@ -1,48 +1,55 @@
/* eslint-disable quotes,no-undef */
const { app, BrowserWindow, Menu, MenuItem, ipcMain, shell, dialog, session } = require("electron");
const { app, BrowserWindow, Menu, ipcMain, shell, session } = require("electron");
const { initializeMenu } = require("./src/menu");
const { initializeSwitches, isLocal, isDev, shouldHideDevtools } = require("./src/switches");
const { initializeFolders } = require("./src/folders");
const { initializeCrashLogs } = require("./src/crashlogs");
const { initializeFilesystem } = require("./src/filesystem");
const { showMissingExternalMods, initializeMods, showModErrors } = require("./src/mods");
const { initializeSteam } = require("./src/steam");
const path = require("path");
const url = require("url");
const fs = require("fs");
const steam = require("./steam");
const asyncLock = require("async-lock");
const windowStateKeeper = require("electron-window-state");
// Disable hardware key handling, i.e. being able to pause/resume the game music
// with hardware keys
app.commandLine.appendSwitch("disable-features", "HardwareMediaKeyHandling");
/** @type {BrowserWindow} */
let win = null;
const isDev = app.commandLine.hasSwitch("dev");
const isLocal = app.commandLine.hasSwitch("local");
const safeMode = app.commandLine.hasSwitch("safe-mode");
const externalMod = app.commandLine.getSwitchValue("load-mod");
async function onReady() {
// Show external mod errors before we open anything
await showMissingExternalMods();
const roamingFolder =
process.env.APPDATA ||
(process.platform == "darwin"
? process.env.HOME + "/Library/Preferences"
: process.env.HOME + "/.local/share");
// Create a new in-memory session
const appSession = session.fromPartition("default");
win = createWindow(appSession);
let storePath = path.join(roamingFolder, "shapez.io", "saves");
let modsPath = path.join(roamingFolder, "shapez.io", "mods");
win.once("ready-to-show", () => {
win.show();
if (!fs.existsSync(storePath)) {
// No try-catch by design
fs.mkdirSync(storePath, { recursive: true });
}
if (isDev && !shouldHideDevtools) {
// Show developer tools initially
win.webContents.toggleDevTools();
}
});
win.on("closed", () => (win = null));
if (!fs.existsSync(modsPath)) {
fs.mkdirSync(modsPath, { recursive: true });
ipcMain.on("set-fullscreen", (_, flag) => win.setFullScreen(flag));
}
/** @type {BrowserWindow} */
let win = null;
let menu = null;
/**
* Opens a URL in external browser if it's HTTPS, does
* nothing if it isn't.
* @param {string} url
*/
function openSecureURL(url) {
if (!url.startsWith("https://")) {
return;
}
return shell.openExternal(url);
}
function createWindow() {
let faviconExtension = ".png";
function createWindow(appSession) {
let faviconName = "favicon.png";
if (process.platform === "win32") {
faviconExtension = ".ico";
faviconName = "favicon.ico";
}
const mainWindowState = windowStateKeeper({
@ -50,340 +57,93 @@ function createWindow() {
defaultHeight: 800,
});
win = new BrowserWindow({
const window = new BrowserWindow({
x: mainWindowState.x,
y: mainWindowState.y,
width: mainWindowState.width,
height: mainWindowState.height,
show: false,
backgroundColor: "#222428",
useContentSize: false,
minWidth: 800,
minHeight: 600,
title: "shapez.io Standalone",
transparent: false,
icon: path.join(__dirname, "favicon" + faviconExtension),
// fullscreen: true,
icon: path.join(__dirname, faviconName),
autoHideMenuBar: !isDev,
webPreferences: {
nodeIntegration: false,
nodeIntegrationInWorker: false,
nodeIntegrationInSubFrames: false,
contextIsolation: true,
enableRemoteModule: false,
disableBlinkFeatures: "Auxclick",
webSecurity: true,
sandbox: true,
preload: path.join(__dirname, "preload.js"),
experimentalFeatures: false,
preload: path.join(__dirname, "src/preload.js"),
session: appSession,
},
allowRunningInsecureContent: false,
});
mainWindowState.manage(win);
mainWindowState.manage(window);
if (isLocal) {
win.loadURL("http://localhost:3005");
window.loadURL("http://localhost:3005");
} else {
win.loadURL(
url.format({
pathname: path.join(__dirname, "index.html"),
protocol: "file:",
slashes: true,
})
);
window.loadFile("index.html");
}
win.webContents.session.clearCache();
win.webContents.session.clearStorageData();
////// SECURITY
// Disable permission requests
win.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
callback(false);
});
session.fromPartition("default").setPermissionRequestHandler((webContents, permission, callback) => {
window.webContents.session.setPermissionRequestHandler((_webContents, _permission, callback) => {
callback(false);
});
app.on("web-contents-created", (event, contents) => {
// Disable vewbiew
contents.on("will-attach-webview", (event, webPreferences, params) => {
event.preventDefault();
});
// Disable navigation
contents.on("will-navigate", (event, navigationUrl) => {
event.preventDefault();
});
});
win.webContents.on("will-redirect", (contentsEvent, navigationUrl) => {
// Log and prevent the app from redirecting to a new page
console.error(
`The application tried to redirect to the following address: '${navigationUrl}'. This attempt was blocked.`
);
contentsEvent.preventDefault();
});
// Filter loading any module via remote;
// you shouldn't be using remote at all, though
// https://electronjs.org/docs/tutorial/security#16-filter-the-remote-module
app.on("remote-require", (event, webContents, moduleName) => {
event.preventDefault();
});
// built-ins are modules such as "app"
app.on("remote-get-builtin", (event, webContents, moduleName) => {
event.preventDefault();
});
app.on("remote-get-global", (event, webContents, globalName) => {
event.preventDefault();
});
app.on("remote-get-current-window", (event, webContents) => {
event.preventDefault();
});
window.webContents.on("will-navigate", (event, url) => {
if (url == window.webContents.getURL()) {
// Allow location.reload()
return;
}
app.on("remote-get-current-web-contents", (event, webContents) => {
event.preventDefault();
openSecureURL(url);
});
//// END SECURITY
win.webContents.on("new-window", (event, pth) => {
window.webContents.on("will-redirect", event => {
event.preventDefault();
if (pth.startsWith("https://")) {
shell.openExternal(pth);
}
});
win.on("closed", () => {
console.log("Window closed");
win = null;
window.webContents.setWindowOpenHandler(({ url }) => {
openSecureURL(url);
return { action: "deny" };
});
if (isDev) {
menu = new Menu();
win.webContents.toggleDevTools();
const mainItem = new MenuItem({
label: "Toggle Dev Tools",
click: () => win.webContents.toggleDevTools(),
accelerator: "F12",
});
menu.append(mainItem);
const reloadItem = new MenuItem({
label: "Reload",
click: () => win.reload(),
accelerator: "F5",
});
menu.append(reloadItem);
const fullscreenItem = new MenuItem({
label: "Fullscreen",
click: () => win.setFullScreen(!win.isFullScreen()),
accelerator: "F11",
});
menu.append(fullscreenItem);
const mainMenu = new Menu();
mainMenu.append(
new MenuItem({
label: "shapez.io",
submenu: menu,
})
);
Menu.setApplicationMenu(mainMenu);
initializeMenu(window);
} else {
Menu.setApplicationMenu(null);
}
win.once("ready-to-show", () => {
win.show();
win.focus();
});
}
if (!app.requestSingleInstanceLock()) {
app.exit(0);
} else {
app.on("second-instance", () => {
// Someone tried to run a second instance, we should focus
if (win) {
if (win.isMinimized()) {
win.restore();
}
win.focus();
}
});
return window;
}
app.on("ready", createWindow);
app.on("window-all-closed", () => {
console.log("All windows closed");
app.quit();
});
ipcMain.on("set-fullscreen", (event, flag) => {
win.setFullScreen(flag);
});
ipcMain.on("exit-app", () => {
win.close();
app.quit();
});
let renameCounter = 1;
const fileLock = new asyncLock({
timeout: 30000,
maxPending: 1000,
});
function niceFileName(filename) {
return filename.replace(storePath, "@");
}
async function writeFileSafe(filename, contents) {
++renameCounter;
const prefix = "[ " + renameCounter + ":" + niceFileName(filename) + " ] ";
const transactionId = String(new Date().getTime()) + "." + renameCounter;
if (fileLock.isBusy()) {
console.warn(prefix, "Concurrent write process on", filename);
}
fileLock.acquire(filename, async () => {
console.log(prefix, "Starting write on", niceFileName(filename), "in transaction", transactionId);
if (!fs.existsSync(filename)) {
// this one is easy
console.log(prefix, "Writing file instantly because it does not exist:", niceFileName(filename));
await fs.promises.writeFile(filename, contents, "utf8");
return;
}
// first, write a temporary file (.tmp-XXX)
const tempName = filename + ".tmp-" + transactionId;
console.log(prefix, "Writing temporary file", niceFileName(tempName));
await fs.promises.writeFile(tempName, contents, "utf8");
// now, rename the original file to (.backup-XXX)
const oldTemporaryName = filename + ".backup-" + transactionId;
console.log(
prefix,
"Renaming old file",
niceFileName(filename),
"to",
niceFileName(oldTemporaryName)
);
await fs.promises.rename(filename, oldTemporaryName);
// now, rename the temporary file (.tmp-XXX) to the target
console.log(
prefix,
"Renaming the temporary file",
niceFileName(tempName),
"to the original",
niceFileName(filename)
);
await fs.promises.rename(tempName, filename);
initializeSwitches();
initializeFolders();
initializeCrashLogs();
// we are done now, try to create a backup, but don't fail if the backup fails
try {
// check if there is an old backup file
const backupFileName = filename + ".backup";
if (fs.existsSync(backupFileName)) {
console.log(prefix, "Deleting old backup file", niceFileName(backupFileName));
// delete the old backup
await fs.promises.unlink(backupFileName);
}
// rename the old file to the new backup file
console.log(prefix, "Moving", niceFileName(oldTemporaryName), "to the backup file location");
await fs.promises.rename(oldTemporaryName, backupFileName);
} catch (ex) {
console.error(prefix, "Failed to switch backup files:", ex);
}
});
if (!app.requestSingleInstanceLock()) {
// Already running
app.exit();
}
ipcMain.handle("fs-job", async (event, job) => {
const filenameSafe = job.filename.replace(/[^a-z\.\-_0-9]/gi, "_");
const fname = path.join(storePath, filenameSafe);
switch (job.type) {
case "read": {
if (!fs.existsSync(fname)) {
// Special FILE_NOT_FOUND error code
return { error: "file_not_found" };
}
return await fs.promises.readFile(fname, "utf8");
}
case "write": {
await writeFileSafe(fname, job.contents);
return job.contents;
}
case "delete": {
await fs.promises.unlink(fname);
return;
app.on("ready", onReady);
app.on("second-instance", () => {
// Someone tried to run a second instance, we should focus
if (win) {
if (win.isMinimized()) {
win.restore();
}
default:
throw new Error("Unknown fs job: " + job.type);
win.focus();
}
});
ipcMain.handle("open-mods-folder", async () => {
shell.openPath(modsPath);
});
console.log("Loading mods ...");
function loadMods() {
if (safeMode) {
console.log("Safe Mode enabled for mods, skipping mod search");
}
console.log("Loading mods from", modsPath);
let modFiles = safeMode
? []
: fs
.readdirSync(modsPath)
.filter(filename => filename.endsWith(".js"))
.map(filename => path.join(modsPath, filename));
if (externalMod) {
console.log("Adding external mod source:", externalMod);
const externalModPaths = externalMod.split(",");
modFiles = modFiles.concat(externalModPaths);
}
return modFiles.map(filename => fs.readFileSync(filename, "utf8"));
}
let mods = [];
try {
mods = loadMods();
console.log("Loaded", mods.length, "mods");
} catch (ex) {
console.error("Failed to load mods");
dialog.showErrorBox("Failed to load mods:", ex);
}
ipcMain.handle("get-mods", async () => {
return mods;
ipcMain.on("restart-app", () => {
app.relaunch();
app.exit(0);
});
ipcMain.on("exit-app", () => app.quit());
steam.init(isDev);
// Only allow achievements and puzzle DLC if no mods are loaded
if (mods.length === 0) {
steam.listen();
}
initializeFilesystem();
initializeMods();
initializeSteam();

@ -0,0 +1,7 @@
{
"compilerOptions": {
"target": "ES2019",
"module": "CommonJS"
},
"include": ["./src/**/*.js", "index.js"]
}

@ -1,6 +0,0 @@
Here you can place mods. Every mod should be a single file ending with ".js".
--- WARNING ---
Mods can potentially access to your filesystem.
Please only install mods from trusted sources and developers.
--- WARNING ---

@ -9,13 +9,14 @@
"startDevGpu": "electron --enable-gpu-rasterization --enable-accelerated-2d-canvas --num-raster-threads=8 --enable-zero-copy . --dev --local",
"start": "electron --disable-direct-composition --in-process-gpu ."
},
"devDependencies": {},
"devDependencies": {
"electron": "^18.3.1"
},
"optionalDependencies": {
"shapez.io-private-artifacts": "github:tobspr/shapez.io-private-artifacts#abi-v99"
},
"dependencies": {
"async-lock": "^1.2.8",
"electron": "16.0.7",
"electron-window-state": "^5.0.3"
}
}

@ -0,0 +1,57 @@
const { app, ipcMain, shell } = require("electron");
const { writeFile } = require("fs/promises");
const path = require("path");
const { crashLogsDir } = require("./folders");
/**
* Writes a crash log and reveals it in file manager.
* @param {string} errorStack Stacktrace from renderer
*/
async function writeCrashLog(errorStack) {
const separator = `\n${"=".repeat(20)}\n\n`;
let contents = errorStack + separator;
contents += "GPU Features:\n";
const gpuFeatures = Object.entries(app.getGPUFeatureStatus());
for (const [feature, status] of gpuFeatures) {
contents += `${feature}:\t${status}`;
}
const gpuInfo = await app.getGPUInfo("basic");
contents += separator;
for (const gpu of gpuInfo.gpuDevice) {
contents += "GPU Active: " + (gpu.active ? "Yes" : "No");
contents += "Vendor: 0x" + gpu.vendorId.toString(16);
contents += "Device: 0x" + gpu.deviceId.toString(16);
contents += "\n";
}
const date = new Date().toISOString();
contents += separator + "Date: " + date;
const target = path.join(crashLogsDir, date + ".log");
await writeFile(target, contents, "utf-8");
console.log("Wrote crash log to", path.basename(target));
shell.showItemInFolder(target);
}
/**
* Setup an IPC handler to write and reveal crash logs as soon
* as an error occurs.
*/
function initializeCrashLogs() {
ipcMain.on("write-crash-log", (_, stack) => {
// Write crash logs if we've been told to
writeCrashLog(stack);
});
// Also attempt to catch main process errors
process.on("uncaughtException", error => {
writeCrashLog(error.stack);
});
}
module.exports = { initializeCrashLogs };

@ -0,0 +1,107 @@
const AsyncLock = require("async-lock");
const { ipcMain, shell } = require("electron");
const { existsSync } = require("fs");
const { unlink, readFile, writeFile, rename } = require("fs/promises");
const path = require("path");
const { savesDir } = require("./folders");
let renameCounter = 1;
const fileLock = new AsyncLock({
timeout: 30000,
maxPending: 1000,
});
/**
* Generic handler for FS jobs.
* @param {{ type: "read"|"write"|"delete"|"reveal", filename: string, contents?: any }} job
*/
async function onFilesystemJob(_, job) {
const safeFileName = sanitizeFileName(job.filename);
const filePath = path.join(savesDir, safeFileName);
switch (job.type) {
case "read":
if (!existsSync(filePath)) {
// Notify the renderer
return { error: "file_not_found" };
}
return await readFile(filePath, "utf-8");
case "write":
await writeFileSafe(filePath, job.contents);
return job.contents;
case "delete":
await unlink(filePath);
return;
case "reveal":
shell.showItemInFolder(filePath);
return;
default:
throw new Error("Unknown FS job: " + job.type);
}
}
async function writeFileSafe(file, contents) {
renameCounter++;
const prefix = `[ ${renameCounter}:${path.basename(file)} ] `;
const transactionId = Date.now() + "." + renameCounter;
if (fileLock.isBusy()) {
console.warn(prefix, "Concurrent write process on", file);
}
fileLock.acquire(file, async () => {
console.log(prefix, "Starting write in transaction", transactionId);
if (!existsSync(file)) {
// This one is easy - write directly
console.log(prefix, "Creating a new file");
await writeFile(file, contents, "utf-8");
return;
}
// First, write a temporary file (.tmp-XXX)
const tempName = file + ".tmp-" + transactionId;
console.log(prefix, "Writing temporary file", path.basename(tempName));
await writeFile(tempName, contents, "utf-8");
// Now, rename the original file to (.backup-XXX)
const oldTemporaryName = file + ".backup-" + transactionId;
console.log(prefix, "Renaming old file to", path.basename(oldTemporaryName));
await rename(file, oldTemporaryName);
// Now, rename the temporary file (.tmp-XXX) to the target
console.log(prefix, "Renaming the temporary file", path.basename(tempName), "to the original file");
await rename(tempName, file);
// We are done now, try to create a backup, but don't fail if the backup fails
try {
// Check if there is an old backup file
const backupFileName = file + ".backup";
if (existsSync(backupFileName)) {
console.log(prefix, "Deleting old backup file", path.basename(backupFileName));
await unlink(backupFileName);
}
// Rename the old file to the new backup file
console.log(prefix, "Moving", path.basename(oldTemporaryName), "to the backup file location");
await rename(oldTemporaryName, backupFileName);
} catch (err) {
console.error(prefix, "Failed to swap backup files:", err);
}
});
}
function sanitizeFileName(filename) {
return filename.replace(/[^a-z.\-_0-9]/gi, "_");
}
/**
* Registers IPC handler for filesystem-related tasks.
*/
function initializeFilesystem() {
ipcMain.handle("fs-job", onFilesystemJob);
}
module.exports = { initializeFilesystem };

@ -0,0 +1,51 @@
const { app, ipcMain, shell } = require("electron");
const { mkdirSync } = require("fs");
const path = require("path");
// Need this to avoid migrating savegames and mods
const userHome = app.getPath("home");
const platformStorageRoots = {
win32: app.getPath("appData"),
linux: process.env.XDG_DATA_HOME ?? path.join(userHome, ".local/share"),
darwin: path.join(userHome, "Library/Preferences"),
};
app.setPath("appData", platformStorageRoots[process.platform]);
const appData = path.join(app.getPath("appData"), "shapez.io");
const savesDir = path.join(appData, "saves");
const modsDir = path.join(appData, "mods");
const crashLogsDir = path.join(appData, "crashes");
// Here, { recursive: true } permits omitting existsSync check
mkdirSync(savesDir, { recursive: true });
mkdirSync(modsDir, { recursive: true });
mkdirSync(crashLogsDir, { recursive: true });
// Folders need to exist before it is possible to set them
app.setPath("userData", appData);
/**
* Sets IPC handler to open various folders.
*/
function initializeFolders() {
ipcMain.handle("open-folder", (_, folder) => {
const folderPath = {
saves: savesDir,
mods: modsDir,
}[folder];
if (folderPath === undefined) {
// Asked to open unknown folder
return;
}
return shell.openPath(folderPath);
});
}
module.exports = {
initializeFolders,
savesDir,
modsDir,
crashLogsDir,
};

@ -0,0 +1,71 @@
const { Menu, MenuItem, app } = require("electron");
/**
* Returns menu items for the specified window.
* @param {Electron.BrowserWindow} window The window to use for actions
*/
function createMenuItems(window) {
/**
* Specifying options directly for simplicity.
* @type {Electron.MenuItemConstructorOptions[]}
*/
const itemOptions = [];
itemOptions.push({
label: "Developer Tools",
accelerator: "F12",
click: () => window.webContents.toggleDevTools(),
});
itemOptions.push({
label: "Reload",
accelerator: "F5",
click: () => window.reload(),
});
itemOptions.push({
label: "Restart",
accelerator: "F5",
click: () => {
app.relaunch();
app.exit(0);
},
});
itemOptions.push({
label: "Full Screen",
accelerator: "F11",
click: () => window.setFullScreen(!window.fullScreen),
});
return itemOptions.map(options => new MenuItem(options));
}
/**
* Create and set a menu for quick access to development tasks.
* @param {Electron.BrowserWindow} window The window to set menu on
*/
function initializeMenu(window) {
const menu = new Menu();
for (const item of createMenuItems(window)) {
menu.append(item);
}
if (process.platform == "darwin") {
// We're on macOS, so a root menu is needed
const rootMenu = new Menu();
rootMenu.append(
new MenuItem({
label: "shapez.io",
submenu: menu,
})
);
window.setMenu(rootMenu);
}
// Items can be directly used on Windows/Linux
Menu.setApplicationMenu(menu);
}
module.exports = { initializeMenu };

@ -0,0 +1,136 @@
const { dialog, ipcMain } = require("electron");
const { readdirSync, readFileSync } = require("fs");
const { basename, join } = require("path");
const { modsDir } = require("./folders");
const { externalModList, isSafeMode, isDev } = require("./switches");
/**
* Map of mod files to source code, populated when initializing mods.
* @type {Map<string, string>}
* @todo Leverage this to implement disalbing mods
*/
const modSources = new Map();
/**
* Stores all mod loading errors to report them later.
* @type {Map<string, Error>}
*/
const modErrors = new Map();
/**
* Returns an array of all mod files found in mods/ directory,
* skipping the search if safe mode is turned on.
* @return {string[]}
*/
function getModFiles() {
if (isSafeMode) {
return [];
}
const files = readdirSync(modsDir).filter(file => file.endsWith(".js"));
return files.map(file => join(modsDir, file));
}
/**
* Tries to read all mod files and store their source code in a map,
* and registers an IPC handler to return mod source code.
*/
function initializeMods() {
// Not checking whether external mods exist, it's done later
const loadOrder = [...getModFiles(), ...externalModList];
for (const file of loadOrder) {
// Each mod has own try/catch block so a single mod won't
// break everything else
try {
const code = readFileSync(file, "utf-8");
modSources.set(file, code);
} catch (err) {
if (err instanceof Error) {
// Ensure only Error objects get there
modErrors.set(basename(file), err);
} else {
// Otherwise, silently log them - the mod is throwing
// random stuff
console.error("A mod reported unknown error:", err);
}
console.warn("Failed to load a mod:", file);
}
}
console.log(modSources.size, "mod files found");
ipcMain.handle("get-mods", () => {
// Renderer only needs relative file names
/** @type {{ filename: string, source: string }[]} */
const mods = [];
for (const [file, source] of modSources.entries()) {
// Note: duplicates are possible, so we're not using a map here
mods.push({ filename: basename(file), source });
}
return mods;
});
ipcMain.on("mod-error", (_, filename, error) => {
// A single mod can't have more than one error,
// so just set filename -> error
modErrors.set(filename, error);
});
ipcMain.on("show-mod-errors", () => showModErrors());
}
/**
* Displays a warning about missing external mod files.
* Make sure to call this after "ready" app event.
*/
function showMissingExternalMods() {
const missing = externalModList.filter(mod => !existsSync(mod));
if (missing.length == 0) {
// None missing, or none were specified
return;
}
const message = missing.map(mod => ` - ${mod}`).join("\n");
return dialog.showMessageBox({
title: "External Mod Errors",
message: "These mod files could not be found:",
detail: message,
type: "warning",
});
}
/**
* Shows a dialog with collected mod loading errors, if
* there were any. Can only be called once the app is ready.
*/
function showModErrors() {
if (modErrors.size == 0) {
// We're lucky - no errors reported
return;
}
let errorText = "";
for (const [mod, error] of modErrors.entries()) {
// Show full errors with --dev
errorText += `${mod}: ${isDev ? "\n" + error.stack : error.message}\n`;
}
return dialog.showMessageBox({
title: "Mod Errors",
message: "Failed to load some mods:",
detail: errorText,
});
}
function anyModLoaded() {
return modSources.size > 0;
}
module.exports = {
initializeMods,
showMissingExternalMods,
anyModLoaded,
};

@ -0,0 +1,113 @@
const { readFileSync } = require("fs");
const { join } = require("path");
const { ipcMain, app, dialog } = require("electron");
const { isDev } = require("./switches");
const { anyModLoaded } = require("./mods");
let greenworks = null;
let appId = null;
let isInitialized = false;
try {
greenworks = require("shapez.io-private-artifacts/steam/greenworks");
appId = parseInt(readFileSync("steam_appid.txt", "utf-8"));
} catch (err) {
console.warn("Failed to load Steam API:", err.message);
}
/**
* Restarts the game immediately if Steam cannot be initialized
* without restarting (game launched outside of Steam). Doesn't
* restart if shapez.io was launched with --dev.
*/
function restartIfNeeded() {
if (isDev) {
// Skip restart when in development mode
return;
}
if (greenworks.restartAppIfNecessary(appId)) {
console.log("Restarting with Steam...");
app.exit(0);
}
}
/**
* Initializes Steam API (if possible) and registers relevant
* IPC handlers for achievements and DLC. Can be safely run
* before "ready" event.
*/
function initializeSteam() {
ipcMain.handle("steam:is-initialized", () => isInitialized);
ipcMain.handle("steam:activate-achievement", (_, id) => activateAchievement(id));
ipcMain.handle("steam:check-app-ownership", (_, id) => checkAppOwnership(id));
ipcMain.handle("steam:get-ticket", () => getTicket());
if (greenworks === null || anyModLoaded()) {
// Skip initialization - we won't need it anyway
return;
}
restartIfNeeded();
try {
isInitialized = greenworks.init();
} catch (err) {
// This mostly happens when Steam glitches occur
dialog.showErrorBox("Steam API Error", err.message);
app.exit(1);
}
}
/**
* Activates an achievement by ID, if the game isn't running
* with mods.
* @param {string} id ID of achievement to activate
*/
function activateAchievement(id) {
if (!isInitialized) {
// Either missing greenworks or running with mods
return;
}
return new Promise((resolve, reject) => {
greenworks.activateAchievement(
id,
() => resolve(),
err => reject(err)
);
});
}
/**
* Checks whether the user owns specified app, used for DLC.
* @param {number} id ID of application to check ownership of
*/
function checkAppOwnership(id) {
if (!isInitialized) {
// No Steam access, therefore it's impossible to check
return false;
}
return greenworks.isDLCInstalled(id);
}
function getTicket() {
if (!isInitialized) {
// Just fail because there's nothing to do
return Promise.reject(new Error("Steam API is not initialized."));
}
console.log("Requesting Steam ticket...");
return new Promise((resolve, reject) => {
greenworks.getAuthSessionTicket(
success => resolve(success.ticket.toString("hex")),
error => {
console.error("Failed to get steam ticket:", error);
reject(error);
}
);
});
}
module.exports = { initializeSteam };

@ -0,0 +1,85 @@
const { app, dialog } = require("electron");
const { readFileSync } = require("fs");
const path = require("path");
/**
* List of mods to load manually
* @type {string[]}
*/
const externalModList = [];
/**
* Retrieves external mod list and sets up the application,
* should be called only before the app is ready.
*/
function initializeSwitches() {
if (!app.commandLine.hasSwitch("disable-features")) {
// Disable Chromium's media keys handler to avoid interfering with
// media controls and other apps (such as pausing in-game music)
app.commandLine.appendSwitch("disable-features", "HardwareMediaKeyHandling");
}
// First load mod list, then other mods
externalModList.push(...parseExternalModList());
externalModList.push(...parseCommandLineMods());
}
/**
* Returns an array of absolute file paths to mods loaded using
* --load-mod=path/mod1.js,path/mod2.js argument, or none if the
* argument is missing.
*/
function parseCommandLineMods() {
const loadModList = app.commandLine.getSwitchValue("load-mod");
if (!loadModList) {
// Empty or missing
return [];
}
const files = loadModList.split(",");
return resolveAllFiles(files);
}
/**
* Returns an array of absolute file paths to mods loaded using
* --mod-list=path/to/mods.json argument (JSON array), or none
* if the file wasn't specified or found.
*/
function parseExternalModList() {
const modListPath = app.commandLine.getSwitchValue("mod-list");
if (!modListPath) {
// None requested, let's just skip that
return [];
}
try {
// Read the file and return resolved mod file paths
const json = readFileSync(modListPath, "utf-8");
return resolveAllFiles(JSON.parse(json));
} catch (err) {
// Something went wrong - notify and continue
dialog.showErrorBox("Failed to load external mod list!", err.stack);
return [];
}
}
/**
* Small utility to resolve all file paths in an array.
* @param {string[]} files
*/
function resolveAllFiles(files) {
return files.map(file => path.resolve(file));
}
module.exports = {
initializeSwitches,
// Shows a menu on the window with useful actions
isDev: app.commandLine.hasSwitch("dev"),
// Instructs the renderer to use bundle hosted on localhost
isLocal: app.commandLine.hasSwitch("local"),
// Disables all mods except manually loaded ones
isSafeMode: app.commandLine.hasSwitch("safe-mode"),
// Suppresses initial toggle of developer tools
shouldHideDevtools: app.commandLine.hasSwitch("hide-devtools"),
externalModList,
};

@ -1,112 +0,0 @@
const fs = require("fs");
const path = require("path");
const { ipcMain } = require("electron");
let greenworks = null;
let appId = null;
let initialized = false;
try {
greenworks = require("shapez.io-private-artifacts/steam/greenworks");
appId = parseInt(fs.readFileSync(path.join(__dirname, "steam_appid.txt"), "utf8"));
} catch (err) {
// greenworks is not installed
console.warn("Failed to load steam api:", err);
}
console.log("App ID:", appId);
function init(isDev) {
if (!greenworks) {
return;
}
if (!isDev) {
if (greenworks.restartAppIfNecessary(appId)) {
console.log("Restarting ...");
process.exit(0);
}
}
if (!greenworks.init()) {
console.log("Failed to initialize greenworks");
process.exit(1);
}
initialized = true;
}
function listen() {
ipcMain.handle("steam:is-initialized", isInitialized);
if (!initialized) {
console.warn("Steam not initialized, won't be able to listen");
return;
}
if (!greenworks) {
console.warn("Greenworks not loaded, won't be able to listen");
return;
}
console.log("Adding listeners");
ipcMain.handle("steam:get-achievement-names", getAchievementNames);
ipcMain.handle("steam:activate-achievement", activateAchievement);
function bufferToHex(buffer) {
return Array.from(new Uint8Array(buffer))
.map(b => b.toString(16).padStart(2, "0"))
.join("");
}
ipcMain.handle("steam:get-ticket", (event, arg) => {
console.log("Requested steam ticket ...");
return new Promise((resolve, reject) => {
greenworks.getAuthSessionTicket(
success => {
const ticketHex = bufferToHex(success.ticket);
resolve(ticketHex);
},
error => {
console.error("Failed to get steam ticket:", error);
reject(error);
}
);
});
});
ipcMain.handle("steam:check-app-ownership", (event, appId) => {
return Promise.resolve(greenworks.isDLCInstalled(appId));
});
}
function isInitialized(event) {
return Promise.resolve(initialized);
}
function getAchievementNames(event) {
return new Promise((resolve, reject) => {
try {
const achievements = greenworks.getAchievementNames();
resolve(achievements);
} catch (err) {
reject(err);
}
});
}
function activateAchievement(event, id) {
return new Promise((resolve, reject) => {
greenworks.activateAchievement(
id,
() => resolve(),
err => reject(err)
);
});
}
module.exports = {
init,
listen,
};

@ -3,9 +3,9 @@
"@electron/get@^1.13.0":
version "1.13.1"
resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.13.1.tgz#42a0aa62fd1189638bd966e23effaebb16108368"
integrity sha512-U5vkXDZ9DwXtkPqlB45tfYnnYBN8PePp1z/XDCupnSpdrxT8/ThCv9WCwPLf9oqiSGZTkH6dx2jDUPuoXpjkcA==
version "1.14.1"
resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.14.1.tgz#16ba75f02dffb74c23965e72d617adc721d27f40"
integrity sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw==
dependencies:
debug "^4.1.1"
env-paths "^2.2.0"
@ -30,10 +30,10 @@
dependencies:
defer-to-connect "^1.0.1"
"@types/node@^14.6.2":
version "14.18.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.5.tgz#0dd636fe7b2c6055cbed0d4ca3b7fb540f130a96"
integrity sha512-LMy+vDDcQR48EZdEx5wRX1q/sEl6NdGuHXPnfeL8ixkwCOSZ2qnIyIZmcCbdX0MeRqHhAcHmX+haCbrS8Run+A==
"@types/node@^16.11.26":
version "16.11.36"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.36.tgz#9ab9f8276987132ed2b225cace2218ba794fc751"
integrity sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==
async-lock@^1.2.8:
version "1.2.8"
@ -149,13 +149,13 @@ electron-window-state@^5.0.3:
jsonfile "^4.0.0"
mkdirp "^0.5.1"
electron@16.0.7:
version "16.0.7"
resolved "https://registry.yarnpkg.com/electron/-/electron-16.0.7.tgz#87eaccd05ab61563d3c17dfbad2949bba7ead162"
integrity sha512-/IMwpBf2svhA1X/7Q58RV+Nn0fvUJsHniG4TizaO7q4iKFYSQ6hBvsLz+cylcZ8hRMKmVy5G1XaMNJID2ah23w==
electron@^18.3.1:
version "18.3.1"
resolved "https://registry.yarnpkg.com/electron/-/electron-18.3.1.tgz#822ca54bd0a32098712377376617cfdd230846d6"
integrity sha512-46lH3iEdvbbDSa0s2JiOysGruQlJwGUae0UrEfZ4NgHZUnHbglkvezfKSoRSOEob3c9rENZlvgEv9gCbhYx5Yw==
dependencies:
"@electron/get" "^1.13.0"
"@types/node" "^14.6.2"
"@types/node" "^16.11.26"
extract-zip "^1.0.3"
encodeurl@^1.0.2:

@ -117,7 +117,7 @@ function gulptasksStandalone($, gulp) {
});
gulp.task(taskPrefix + "standalone.prepare.minifyCode", () => {
return gulp.src(path.join(electronBaseDir, "*.js")).pipe(gulp.dest(tempDestBuildDir));
return gulp.src(path.join(electronBaseDir, "**/*.js")).pipe(gulp.dest(tempDestBuildDir));
});
gulp.task(taskPrefix + "standalone.prepare.copyGamefiles", () => {

@ -85,6 +85,7 @@
"babel-plugin-danger-remove-unused-import": "^1.1.2",
"css-mqpacker": "^7.0.0",
"cssnano": "^4.1.10",
"electron": "^18",
"eslint-config-prettier": "6.11.0",
"eslint-plugin-prettier": "3.1.3",
"faster.js": "^1.1.0",

@ -120,6 +120,8 @@ function catchErrors(message, source, lineno, colno, error) {
}, 200);
}
ipcRenderer.send("write-crash-log", (error && error.stack) || message);
return true;
}

@ -25,7 +25,8 @@ const LOG = createLogger("mods");
* id: string;
* minimumGameVersion?: string;
* settings: [];
* doesNotAffectSavegame?: boolean
* doesNotAffectSavegame?: boolean;
* filename: string
* }} ModMetadata
*/
@ -150,6 +151,7 @@ export class ModLoader {
LOG.log("hook:init", this.app, this.app.storage);
this.exposeExports();
/** @type {{ filename: string, source: string }[]} */
let mods = [];
if (G_IS_STANDALONE) {
mods = await ipcRenderer.invoke("get-mods");
@ -168,11 +170,14 @@ export class ModLoader {
"Failed to load " + modURLs[i] + ": " + response.status + " " + response.statusText
);
}
mods.push(await response.text());
mods.push({
filename: modURLs[i],
source: await response.text(),
});
}
}
window.$shapez_registerMod = (modClass, meta) => {
const registerMod = (modFile, modClass, meta) => {
if (this.initialized) {
throw new Error("Can't register mod after modloader is initialized");
}
@ -180,14 +185,17 @@ export class ModLoader {
console.warn("Not registering mod", meta, "since a mod with the same id is already loaded");
return;
}
meta.filename = modFile;
this.modLoadQueue.push({
modClass,
meta,
});
};
mods.forEach(modCode => {
modCode += `
mods.forEach(({ filename, source }) => {
window.$shapez_registerMod = registerMod.bind(this, filename);
source += `
if (typeof Mod !== 'undefined') {
if (typeof METADATA !== 'object') {
throw new Error("No METADATA variable found");
@ -196,11 +204,11 @@ export class ModLoader {
}
`;
try {
const func = new Function(modCode);
const func = new Function(source);
func();
} catch (ex) {
console.error(ex);
alert("Failed to parse mod (launch with --dev for more info): \n\n" + ex);
ipcRenderer.send("mod-error", filename, ex);
}
});
@ -213,19 +221,16 @@ export class ModLoader {
if (meta.minimumGameVersion) {
const minimumGameVersion = meta.minimumGameVersion;
if (!semverValidRange(minimumGameVersion)) {
alert("Mod " + meta.id + " has invalid minimumGameVersion: " + minimumGameVersion);
const error = new Error(`Invalid minimumGameVersion specified: ${minimumGameVersion}`);
ipcRenderer.send("mod-error", meta.filename, error);
continue;
}
if (!semverSatisifies(G_BUILD_VERSION, minimumGameVersion)) {
alert(
"Mod '" +
meta.id +
"' is incompatible with this version of the game: \n\n" +
"Mod requires version " +
minimumGameVersion +
" but this game has version " +
G_BUILD_VERSION
const error = new Error(
`This game version (${G_BUILD_VERSION}) is not supported, ${minimumGameVersion} is required`
);
ipcRenderer.send("mod-error", meta.filename, error);
continue;
}
}
@ -258,10 +263,12 @@ export class ModLoader {
this.mods.push(mod);
} catch (ex) {
console.error(ex);
alert("Failed to initialize mods (launch with --dev for more info): \n\n" + ex);
ipcRenderer.send("mod-error", meta.filename, ex);
}
}
// Once initialization of all mods is done, show all errors
ipcRenderer.send("show-mod-errors");
this.modLoadQueue = [];
this.initialized = true;
}

@ -126,7 +126,7 @@ export class ModsState extends TextualGameState {
this.dialogs.showWarning(T.global.error, T.mods.folderOnlyStandalone);
return;
}
ipcRenderer.invoke("open-mods-folder");
ipcRenderer.invoke("open-folder", "mods");
}
openBrowseMods() {

@ -748,6 +748,22 @@
resolved "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz"
integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==
"@electron/get@^1.13.0":
version "1.14.1"
resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.14.1.tgz#16ba75f02dffb74c23965e72d617adc721d27f40"
integrity sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw==
dependencies:
debug "^4.1.1"
env-paths "^2.2.0"
fs-extra "^8.1.0"
got "^9.6.0"
progress "^2.0.3"
semver "^6.2.0"
sumchecker "^3.0.1"
optionalDependencies:
global-agent "^3.0.0"
global-tunnel-ng "^2.7.1"
"@jimp/bmp@^0.6.8":
version "0.6.8"
resolved "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.6.8.tgz"
@ -1097,11 +1113,23 @@
dependencies:
"@types/node" ">= 8"
"@sindresorhus/is@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
"@sindresorhus/is@^0.7.0":
version "0.7.0"
resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz"
integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
"@szmarczak/http-timer@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==
dependencies:
defer-to-connect "^1.0.1"
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz"
@ -1139,6 +1167,11 @@
resolved "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz"
integrity sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA==
"@types/node@^16.11.26":
version "16.11.36"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.36.tgz#9ab9f8276987132ed2b225cace2218ba794fc751"
integrity sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==
"@types/q@^1.5.1":
version "1.5.2"
resolved "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz"
@ -1935,6 +1968,11 @@ boolbase@^1.0.0, boolbase@~1.0.0:
resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz"
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
boolean@^3.0.1:
version "3.2.0"
resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b"
integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz"
@ -2156,6 +2194,19 @@ cacheable-request@^2.1.1:
normalize-url "2.0.1"
responselike "1.0.2"
cacheable-request@^6.0.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==
dependencies:
clone-response "^1.0.2"
get-stream "^5.1.0"
http-cache-semantics "^4.0.0"
keyv "^3.0.0"
lowercase-keys "^2.0.0"
normalize-url "^4.1.0"
responselike "^1.0.2"
calipers-gif@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/calipers-gif/-/calipers-gif-2.0.0.tgz"
@ -2435,7 +2486,7 @@ cliui@^6.0.0:
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
clone-response@1.0.2:
clone-response@1.0.2, clone-response@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz"
integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
@ -2546,7 +2597,7 @@ concat-map@0.0.1:
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
concat-stream@^1.5.0:
concat-stream@^1.5.0, concat-stream@^1.6.2:
version "1.6.2"
resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz"
integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
@ -3051,6 +3102,11 @@ deep-scope-analyser@^1.7.0:
esrecurse "^4.2.1"
estraverse "^4.2.0"
defer-to-connect@^1.0.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==
define-properties@^1.1.2, define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz"
@ -3115,6 +3171,11 @@ detect-indent@^4.0.0:
dependencies:
repeating "^2.0.0"
detect-node@^2.0.4:
version "2.1.0"
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
diffie-hellman@^5.0.0:
version "5.0.3"
resolved "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz"
@ -3244,6 +3305,15 @@ electron-to-chromium@^1.3.390:
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.403.tgz"
integrity sha512-JaoxV4RzdBAZOnsF4dAlZ2ijJW72MbqO5lNfOBHUWiBQl3Rwe+mk2RCUMrRI3rSClLJ8HSNQNqcry12H+0ZjFw==
electron@^18:
version "18.3.1"
resolved "https://registry.yarnpkg.com/electron/-/electron-18.3.1.tgz#822ca54bd0a32098712377376617cfdd230846d6"
integrity sha512-46lH3iEdvbbDSa0s2JiOysGruQlJwGUae0UrEfZ4NgHZUnHbglkvezfKSoRSOEob3c9rENZlvgEv9gCbhYx5Yw==
dependencies:
"@electron/get" "^1.13.0"
"@types/node" "^16.11.26"
extract-zip "^1.0.3"
elliptic@^6.0.0, elliptic@^6.5.2:
version "6.5.3"
resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz"
@ -3282,7 +3352,7 @@ emojis-list@^3.0.0:
resolved "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz"
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
encodeurl@~1.0.2:
encodeurl@^1.0.2, encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
@ -3317,6 +3387,11 @@ entities@^2.0.0:
resolved "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz"
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
env-paths@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
errno@^0.1.3, errno@~0.1.7:
version "0.1.7"
resolved "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz"
@ -3357,6 +3432,11 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
es6-error@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
es6-templates@^0.2.3:
version "0.2.3"
resolved "https://registry.npmjs.org/es6-templates/-/es6-templates-0.2.3.tgz"
@ -3375,6 +3455,11 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
escape-string-regexp@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
eslint-config-prettier@6.11.0:
version "6.11.0"
resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz"
@ -3685,6 +3770,16 @@ extglob@^2.0.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
extract-zip@^1.0.3:
version "1.7.0"
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927"
integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==
dependencies:
concat-stream "^1.6.2"
debug "^2.6.9"
mkdirp "^0.5.4"
yauzl "^2.10.0"
fast-deep-equal@^3.1.1:
version "3.1.3"
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
@ -3965,6 +4060,15 @@ fs-constants@^1.0.0:
resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-write-stream-atomic@^1.0.8:
version "1.0.10"
resolved "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz"
@ -4043,13 +4147,20 @@ get-stream@^2.2.0:
object-assign "^4.0.1"
pinkie-promise "^2.0.0"
get-stream@^4.0.0:
get-stream@^4.0.0, get-stream@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz"
integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
dependencies:
pump "^3.0.0"
get-stream@^5.1.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
dependencies:
pump "^3.0.0"
get-value@^2.0.3, get-value@^2.0.6:
version "2.0.6"
resolved "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz"
@ -4090,6 +4201,18 @@ glob@^7.0.5, glob@^7.0.6, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
once "^1.3.0"
path-is-absolute "^1.0.0"
global-agent@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6"
integrity sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==
dependencies:
boolean "^3.0.1"
es6-error "^4.1.1"
matcher "^3.0.0"
roarr "^2.15.3"
semver "^7.3.2"
serialize-error "^7.0.1"
global-modules@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz"
@ -4126,6 +4249,16 @@ global-prefix@^3.0.0:
kind-of "^6.0.2"
which "^1.3.1"
global-tunnel-ng@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz#d03b5102dfde3a69914f5ee7d86761ca35d57d8f"
integrity sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==
dependencies:
encodeurl "^1.0.2"
lodash "^4.17.10"
npm-conf "^1.1.3"
tunnel "^0.0.6"
global@~4.3.0:
version "4.3.2"
resolved "https://registry.npmjs.org/global/-/global-4.3.2.tgz"
@ -4151,6 +4284,13 @@ globals@^9.18.0:
resolved "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz"
integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
globalthis@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf"
integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==
dependencies:
define-properties "^1.1.3"
gonzales-pe@^4.2.3:
version "4.3.0"
resolved "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz"
@ -4201,6 +4341,23 @@ got@^8.3.1:
url-parse-lax "^3.0.0"
url-to-options "^1.0.1"
got@^9.6.0:
version "9.6.0"
resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==
dependencies:
"@sindresorhus/is" "^0.14.0"
"@szmarczak/http-timer" "^1.1.2"
cacheable-request "^6.0.0"
decompress-response "^3.3.0"
duplexer3 "^0.1.4"
get-stream "^4.1.0"
lowercase-keys "^1.0.1"
mimic-response "^1.0.1"
p-cancelable "^1.0.0"
to-readable-stream "^1.0.0"
url-parse-lax "^3.0.0"
graceful-fs@^4.1.10, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
version "4.2.3"
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz"
@ -4211,6 +4368,11 @@ graceful-fs@^4.1.11:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.10"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
"graceful-readlink@>= 1.0.0":
version "1.0.1"
resolved "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
@ -4411,6 +4573,11 @@ http-cache-semantics@3.8.1:
resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz"
integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==
http-cache-semantics@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
http-errors@1.7.2, http-errors@~1.7.2:
version "1.7.2"
resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz"
@ -4974,6 +5141,11 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
json-stringify-safe@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
json5@^0.5.0, json5@^0.5.1:
version "0.5.1"
resolved "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz"
@ -4993,6 +5165,13 @@ json5@^2.1.2:
dependencies:
minimist "^1.2.5"
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
optionalDependencies:
graceful-fs "^4.1.6"
keyv@3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz"
@ -5000,6 +5179,13 @@ keyv@3.0.0:
dependencies:
json-buffer "3.0.0"
keyv@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==
dependencies:
json-buffer "3.0.0"
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
version "3.2.2"
resolved "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz"
@ -5172,6 +5358,11 @@ lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
lodash@^4.17.10:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
logalot@^2.0.0, logalot@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/logalot/-/logalot-2.1.0.tgz"
@ -5215,11 +5406,16 @@ lowercase-keys@1.0.0:
resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz"
integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=
lowercase-keys@^1.0.0:
lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz"
integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
lowercase-keys@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
lpad-align@^1.0.1:
version "1.1.2"
resolved "https://registry.npmjs.org/lpad-align/-/lpad-align-1.1.2.tgz"
@ -5314,6 +5510,13 @@ match-all@^1.2.5:
resolved "https://registry.npmjs.org/match-all/-/match-all-1.2.5.tgz"
integrity sha512-KW4trRDMYbVkAKZ1J655vh0931mk3XM1lIJ480TXUL3KBrOsZ6WpryYJELonvtXC1O4erLYB069uHidLkswbjQ==
matcher@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca"
integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==
dependencies:
escape-string-regexp "^4.0.0"
md5.js@^1.3.4:
version "1.3.5"
resolved "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz"
@ -5443,7 +5646,7 @@ mimic-fn@^2.0.0, mimic-fn@^2.1.0:
resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
mimic-response@^1.0.0:
mimic-response@^1.0.0, mimic-response@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz"
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
@ -5482,6 +5685,11 @@ minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5:
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
minimist@^1.2.6:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
mississippi@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz"
@ -5520,6 +5728,13 @@ mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1:
dependencies:
minimist "^1.2.5"
mkdirp@^0.5.4:
version "0.5.6"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
dependencies:
minimist "^1.2.6"
move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz"
@ -5695,7 +5910,12 @@ normalize-url@^3.0.0:
resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz"
integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
npm-conf@^1.1.0:
normalize-url@^4.1.0:
version "4.5.1"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
npm-conf@^1.1.0, npm-conf@^1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz"
integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==
@ -5879,6 +6099,11 @@ p-cancelable@^0.4.0:
resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz"
integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==
p-cancelable@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
p-defer@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz"
@ -6958,7 +7183,7 @@ process@~0.5.1:
resolved "https://registry.npmjs.org/process/-/process-0.5.2.tgz"
integrity sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=
progress@^2.0.0:
progress@^2.0.0, progress@^2.0.3:
version "2.0.3"
resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
@ -7340,7 +7565,7 @@ resolve@^1.10.0, resolve@^1.3.2:
dependencies:
path-parse "^1.0.6"
responselike@1.0.2:
responselike@1.0.2, responselike@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz"
integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=
@ -7385,6 +7610,18 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
roarr@^2.15.3:
version "2.15.4"
resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd"
integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==
dependencies:
boolean "^3.0.1"
detect-node "^2.0.4"
globalthis "^1.0.1"
json-stringify-safe "^5.0.1"
semver-compare "^1.0.0"
sprintf-js "^1.1.2"
run-async@^2.4.0:
version "2.4.1"
resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz"
@ -7476,6 +7713,11 @@ seek-bzip@^1.0.5:
dependencies:
commander "~2.8.1"
semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
semver-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz"
@ -7498,6 +7740,11 @@ semver@7.0.0:
resolved "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz"
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
semver@^6.2.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.2.1, semver@^7.3.2:
version "7.3.2"
resolved "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz"
@ -7534,6 +7781,13 @@ serialize-error@^3.0.0:
resolved "https://registry.npmjs.org/serialize-error/-/serialize-error-3.0.0.tgz"
integrity sha512-+y3nkkG/go1Vdw+2f/+XUXM1DXX1XcxTl99FfiD/OEPUNw4uo0i6FKABfTAN5ZcgGtjTRZcEbxcE/jtXbEY19A==
serialize-error@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18"
integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==
dependencies:
type-fest "^0.13.1"
serialize-javascript@^2.1.2:
version "2.1.2"
resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz"
@ -7774,6 +8028,11 @@ split-string@^3.0.1, split-string@^3.0.2:
dependencies:
extend-shallow "^3.0.0"
sprintf-js@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
@ -8006,6 +8265,13 @@ stylehacks@^4.0.0:
postcss "^7.0.0"
postcss-selector-parser "^3.0.0"
sumchecker@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42"
integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==
dependencies:
debug "^4.1.0"
supports-color@6.1.0, supports-color@^6.1.0:
version "6.1.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz"
@ -8202,6 +8468,11 @@ to-object-path@^0.3.0:
dependencies:
kind-of "^3.0.2"
to-readable-stream@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771"
integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==
to-regex-range@^2.1.0:
version "2.1.1"
resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz"
@ -8283,6 +8554,11 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
tunnel@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz"
@ -8295,6 +8571,11 @@ type-fest@^0.11.0:
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz"
integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
type-fest@^0.13.1:
version "0.13.1"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"
integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
type-fest@^0.5.1:
version "0.5.2"
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz"
@ -8406,6 +8687,11 @@ universal-user-agent@^6.0.0:
resolved "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz"
integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==
universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"
@ -8884,7 +9170,7 @@ yarn@^1.22.4:
resolved "https://registry.npmjs.org/yarn/-/yarn-1.22.4.tgz"
integrity sha512-oYM7hi/lIWm9bCoDMEWgffW8aiNZXCWeZ1/tGy0DWrN6vmzjCXIKu2Y21o8DYVBUtiktwKcNoxyGl/2iKLUNGA==
yauzl@^2.4.2:
yauzl@^2.10.0, yauzl@^2.4.2:
version "2.10.0"
resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz"
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=

Loading…
Cancel
Save