From ec93c15b877803617d231f4fc8e71c773b9b8d19 Mon Sep 17 00:00:00 2001 From: Nintendoboi2/Nintendoboi22 <131544102+Nintendoboi22@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:22:41 -0600 Subject: [PATCH] shapez io round 5 --- shapez-io/electron_gog/favicon.png | Bin 0 -> 21546 bytes shapez-io/electron_gog/index.js | 381 +++++++++++++++++ shapez-io/electron_gog/package.json | 17 + shapez-io/electron_gog/preload.js | 7 + shapez-io/electron_gog/yarn.lock | 580 ++++++++++++++++++++++++++ shapez-io/gulp/atlas2json.js | 127 ++++++ shapez-io/gulp/babel-es6.config.js | 47 +++ shapez-io/gulp/babel.config.js | 55 +++ shapez-io/gulp/build_variants.js | 74 ++++ shapez-io/gulp/buildutils.js | 47 +++ shapez-io/gulp/css.js | 136 ++++++ shapez-io/gulp/docs.js | 39 ++ shapez-io/gulp/entitlements.plist | 18 + shapez-io/gulp/ftp.js | 101 +++++ shapez-io/gulp/gulpfile.js | 317 ++++++++++++++ shapez-io/gulp/html.js | 205 +++++++++ shapez-io/gulp/image-resources.js | 205 +++++++++ shapez-io/gulp/js.js | 129 ++++++ shapez-io/gulp/jsconfig.json | 6 + shapez-io/gulp/loader.compressjson.js | 11 + shapez-io/gulp/loader.strip_block.js | 21 + shapez-io/gulp/local-config.js | 18 + shapez-io/gulp/mod.js | 39 ++ shapez-io/gulp/package.json | 124 ++++++ shapez-io/gulp/sounds.js | 134 ++++++ shapez-io/gulp/standalone.js | 339 +++++++++++++++ shapez-io/gulp/translations.js | 64 +++ 27 files changed, 3241 insertions(+) create mode 100644 shapez-io/electron_gog/favicon.png create mode 100644 shapez-io/electron_gog/index.js create mode 100644 shapez-io/electron_gog/package.json create mode 100644 shapez-io/electron_gog/preload.js create mode 100644 shapez-io/electron_gog/yarn.lock create mode 100644 shapez-io/gulp/atlas2json.js create mode 100644 shapez-io/gulp/babel-es6.config.js create mode 100644 shapez-io/gulp/babel.config.js create mode 100644 shapez-io/gulp/build_variants.js create mode 100644 shapez-io/gulp/buildutils.js create mode 100644 shapez-io/gulp/css.js create mode 100644 shapez-io/gulp/docs.js create mode 100644 shapez-io/gulp/entitlements.plist create mode 100644 shapez-io/gulp/ftp.js create mode 100644 shapez-io/gulp/gulpfile.js create mode 100644 shapez-io/gulp/html.js create mode 100644 shapez-io/gulp/image-resources.js create mode 100644 shapez-io/gulp/js.js create mode 100644 shapez-io/gulp/jsconfig.json create mode 100644 shapez-io/gulp/loader.compressjson.js create mode 100644 shapez-io/gulp/loader.strip_block.js create mode 100644 shapez-io/gulp/local-config.js create mode 100644 shapez-io/gulp/mod.js create mode 100644 shapez-io/gulp/package.json create mode 100644 shapez-io/gulp/sounds.js create mode 100644 shapez-io/gulp/standalone.js create mode 100644 shapez-io/gulp/translations.js diff --git a/shapez-io/electron_gog/favicon.png b/shapez-io/electron_gog/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c837c787678840e430de3a3c65aa7d4f7c4a570e GIT binary patch literal 21546 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 b)QCHlLd;qF1!(G^p>ftX_y@}AAAkNIV9V-) literal 0 HcmV?d00001 diff --git a/shapez-io/electron_gog/index.js b/shapez-io/electron_gog/index.js new file mode 100644 index 00000000..563f187d --- /dev/null +++ b/shapez-io/electron_gog/index.js @@ -0,0 +1,381 @@ +/* eslint-disable quotes,no-undef */ + +const { app, BrowserWindow, Menu, MenuItem, ipcMain, shell, dialog, session } = require("electron"); +const path = require("path"); +const url = require("url"); +const fs = require("fs"); +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"); + +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"); + +const roamingFolder = + process.env.APPDATA || + (process.platform == "darwin" + ? process.env.HOME + "/Library/Preferences" + : process.env.HOME + "/.local/share"); + +let storePath = path.join(roamingFolder, "shapez.io", "saves"); +let modsPath = path.join(roamingFolder, "shapez.io", "mods"); + +if (!fs.existsSync(storePath)) { + // No try-catch by design + fs.mkdirSync(storePath, { recursive: true }); +} + +if (!fs.existsSync(modsPath)) { + fs.mkdirSync(modsPath, { recursive: true }); +} + +/** @type {BrowserWindow} */ +let win = null; +let menu = null; + +function createWindow() { + let faviconExtension = ".png"; + if (process.platform === "win32") { + faviconExtension = ".ico"; + } + + const mainWindowState = windowStateKeeper({ + defaultWidth: 1000, + defaultHeight: 800, + }); + + win = 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", + transparent: false, + icon: path.join(__dirname, "favicon" + faviconExtension), + // fullscreen: true, + 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, + }, + allowRunningInsecureContent: false, + }); + + mainWindowState.manage(win); + + if (isLocal) { + win.loadURL("http://localhost:3005"); + } else { + win.loadURL( + url.format({ + pathname: path.join(__dirname, "index.html"), + protocol: "file:", + slashes: true, + }) + ); + } + 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) => { + 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(); + }); + + app.on("remote-get-current-web-contents", (event, webContents) => { + event.preventDefault(); + }); + + //// END SECURITY + + win.webContents.on("new-window", (event, pth) => { + event.preventDefault(); + + if (pth.startsWith("https://")) { + shell.openExternal(pth); + } + }); + + win.on("closed", () => { + console.log("Window closed"); + win = null; + }); + + 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); + } 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(); + } + }); +} + +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); + + // 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); + } + }); +} + +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; + } + + default: + throw new Error("Unknown fs job: " + job.type); + } +}); + +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; +}); diff --git a/shapez-io/electron_gog/package.json b/shapez-io/electron_gog/package.json new file mode 100644 index 00000000..082055e4 --- /dev/null +++ b/shapez-io/electron_gog/package.json @@ -0,0 +1,17 @@ +{ + "name": "electron", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "private": true, + "scripts": { + "startDev": "electron --disable-direct-composition --in-process-gpu . --dev --local", + "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 ." + }, + "dependencies": { + "async-lock": "^1.2.8", + "electron": "16.2.8", + "electron-window-state": "^5.0.3" + } +} diff --git a/shapez-io/electron_gog/preload.js b/shapez-io/electron_gog/preload.js new file mode 100644 index 00000000..c6336230 --- /dev/null +++ b/shapez-io/electron_gog/preload.js @@ -0,0 +1,7 @@ +const { contextBridge, ipcRenderer } = require("electron"); + +contextBridge.exposeInMainWorld("ipcRenderer", { + invoke: ipcRenderer.invoke.bind(ipcRenderer), + on: ipcRenderer.on.bind(ipcRenderer), + send: ipcRenderer.send.bind(ipcRenderer), +}); diff --git a/shapez-io/electron_gog/yarn.lock b/shapez-io/electron_gog/yarn.lock new file mode 100644 index 00000000..c9238b1f --- /dev/null +++ b/shapez-io/electron_gog/yarn.lock @@ -0,0 +1,580 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@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== + 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" + +"@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== + +"@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/node@^14.6.2": + version "14.18.20" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.20.tgz#268f028b36eaf51181c3300252f605488c4f0650" + integrity sha512-Q8KKwm9YqEmUBRsqJ2GWJDtXltBDxTdC4m5vTdXBolu2PeQh8LX+f6BTwU+OuXPu37fLxoN6gidqBmnky36FXA== + +async-lock@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.2.8.tgz#7b02bdfa2de603c0713acecd11184cf97bbc7c4c" + integrity sha512-G+26B2jc0Gw0EG/WN2M6IczuGepBsfR1+DtqLnyFSH4p2C668qkOCtEkGNVEaaNAVlYwEMazy1+/jnLxltBkIQ== + +boolean@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.2.tgz#df1baa18b6a2b0e70840475e1d93ec8fe75b2570" + integrity sha512-RwywHlpCRc3/Wh81MiCKun4ydaIFyW5Ea6JbL6sRCVx5q5irDw7pMXBUFYF/jArQ6YrG36q0kpovc9P/Kd3I4g== + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +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" + +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + +concat-stream@^1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +config-chain@^1.1.11: + version "1.1.12" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" + integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^4.1.0, debug@^4.1.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= + dependencies: + mimic-response "^1.0.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.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +detect-node@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" + integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== + +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + +electron-window-state@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/electron-window-state/-/electron-window-state-5.0.3.tgz#4f36d09e3f953d87aff103bf010f460056050aa8" + integrity sha512-1mNTwCfkolXl3kMf50yW3vE2lZj0y92P/HYWFBrb+v2S/pCka5mdwN3cagKm458A7NjndSwijynXgcLWRodsVg== + dependencies: + jsonfile "^4.0.0" + mkdirp "^0.5.1" + +electron@16.2.8: + version "16.2.8" + resolved "https://registry.yarnpkg.com/electron/-/electron-16.2.8.tgz#b7f2bd1184701e54a1bc902839d5a3ec95bb8982" + integrity sha512-KSOytY6SPLsh3iCziztqa/WgJyfDOKzCvNqku9gGIqSdT8CqtV66dTU1SOrKZQjRFLxHaF8LbyxUL1vwe4taqw== + dependencies: + "@electron/get" "^1.13.0" + "@types/node" "^14.6.2" + extract-zip "^1.0.3" + +encodeurl@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +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== + +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== + +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== + +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" + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + dependencies: + pend "~1.2.0" + +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" + +get-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + 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" + +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-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" + +globalthis@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.2.tgz#2a235d34f4d8036219f7e34929b5de9e18166b8b" + integrity sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ== + dependencies: + define-properties "^1.1.3" + +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.6, graceful-fs@^4.2.0: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + +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== + +inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@^1.3.4: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + +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= + +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.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" + +lodash@^4.17.10: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + 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== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +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" + +mimic-response@^1.0.0, mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mkdirp@^0.5.1, mkdirp@^0.5.4: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +normalize-url@^4.1.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + +npm-conf@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" + integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw== + dependencies: + config-chain "^1.1.11" + pify "^3.0.0" + +object-keys@^1.0.12: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +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== + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= + +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== + +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +proto-list@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" + integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +readable-stream@^2.2.2: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + 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" + +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= + dependencies: + lowercase-keys "^1.0.0" + +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" + +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== + +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@^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.3.2: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + +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" + +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== + +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" + +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" + +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== + +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-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== + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +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== + +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= + dependencies: + prepend-http "^2.0.0" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" diff --git a/shapez-io/gulp/atlas2json.js b/shapez-io/gulp/atlas2json.js new file mode 100644 index 00000000..b77a47f3 --- /dev/null +++ b/shapez-io/gulp/atlas2json.js @@ -0,0 +1,127 @@ +const { join, resolve } = require("path"); +const { readFileSync, readdirSync, writeFileSync } = require("fs"); + +const suffixToScale = { + lq: "0.25", + mq: "0.5", + hq: "0.75" +}; + +function convert(srcDir) { + const full = resolve(srcDir); + const srcFiles = readdirSync(full) + .filter(n => n.endsWith(".atlas")) + .map(n => join(full, n)); + + for (const atlas of srcFiles) { + console.log(`Processing: ${atlas}`); + + // Read all text, split it into line array + // and filter all empty lines + const lines = readFileSync(atlas, "utf-8") + .split("\n") + .filter(n => n.trim()); + + // Get source image name + const image = lines.shift(); + const srcMeta = {}; + + // Read all metadata (supports only one page) + while (true) { + const kv = lines.shift().split(":"); + if (kv.length != 2) { + lines.unshift(kv[0]); + break; + } + + srcMeta[kv[0]] = kv[1].trim(); + } + + const frames = {}; + let current = null; + + lines.push("Dummy line to make it convert last frame"); + + for (const line of lines) { + if (!line.startsWith(" ")) { + // New frame, convert previous if it exists + if (current != null) { + let { name, rotate, xy, size, orig, offset, index } = current; + + // Convert to arrays because Node.js doesn't + // support latest JS features + xy = xy.split(",").map(v => Number(v)); + size = size.split(",").map(v => Number(v)); + orig = orig.split(",").map(v => Number(v)); + offset = offset.split(",").map(v => Number(v)); + + // GDX TexturePacker removes index suffixes + const indexSuff = index != -1 ? `_${index}` : ""; + const isTrimmed = size != orig; + + frames[`${name}${indexSuff}.png`] = { + // Bounds on atlas + frame: { + x: xy[0], + y: xy[1], + w: size[0], + h: size[1] + }, + + // Whether image was rotated + rotated: rotate == "true", + trimmed: isTrimmed, + + // How is the image trimmed + spriteSourceSize: { + x: offset[0], + y: (orig[1] - size[1]) - offset[1], + w: size[0], + h: size[1] + }, + + sourceSize: { + w: orig[0], + h: orig[1] + } + } + } + + // Simple object that will hold other metadata + current = { + name: line + }; + } else { + // Read and set current image metadata + const kv = line.split(":").map(v => v.trim()); + current[kv[0]] = isNaN(Number(kv[1])) ? kv[1] : Number(kv[1]); + } + } + + const atlasSize = srcMeta.size.split(",").map(v => Number(v)); + const atlasScale = suffixToScale[atlas.match(/_(\w+)\.atlas$/)[1]]; + + const result = JSON.stringify({ + frames, + meta: { + image, + format: srcMeta.format, + size: { + w: atlasSize[0], + h: atlasSize[1] + }, + scale: atlasScale.toString() + } + }); + + writeFileSync(atlas.replace(".atlas", ".json"), result, { + encoding: "utf-8" + }); + } +} + +if (require.main == module) { + convert(process.argv[2]); +} + +module.exports = { convert }; diff --git a/shapez-io/gulp/babel-es6.config.js b/shapez-io/gulp/babel-es6.config.js new file mode 100644 index 00000000..b3f088f9 --- /dev/null +++ b/shapez-io/gulp/babel-es6.config.js @@ -0,0 +1,47 @@ +module.exports = function (api) { + api.cache(true); + const presets = [ + [ + "@babel/preset-env", + { + targets: "> 5%", + useBuiltIns: "usage", + corejs: 3, + loose: true, + spec: false, + modules: "auto", + }, + ], + ]; + const plugins = [ + "closure-elimination", + // var is faster than let and const! + [ + "@babel/plugin-transform-block-scoping", + { + throwIfClosureRequired: false, + }, + ], + [ + "@babel/plugin-transform-classes", + { + loose: true, + }, + ], + ]; + return { + presets, + plugins, + highlightCode: true, + sourceType: "module", + sourceMaps: false, + parserOpts: {}, + only: ["../src/js"], + generatorOpts: { + retainLines: false, + compact: true, + minified: true, + comments: true, + }, + }; +}; diff --git a/shapez-io/gulp/babel.config.js b/shapez-io/gulp/babel.config.js new file mode 100644 index 00000000..62839421 --- /dev/null +++ b/shapez-io/gulp/babel.config.js @@ -0,0 +1,55 @@ +module.exports = function (api) { + api.cache(true); + const presets = [ + [ + "@babel/preset-env", + { + // targets: ">0.01%", + targets: { + edge: 10, + firefox: 37, + chrome: 24, + safari: 10, + ie: 10, + }, + useBuiltIns: "usage", + corejs: 3, + loose: true, + spec: false, + modules: "auto", + }, + ], + ]; + const plugins = [ + "@babel/plugin-transform-arrow-functions", + "closure-elimination", + // var is faster than let and const! + // [ + // "@babel/plugin-transform-block-scoping", + // { + // throwIfClosureRequired: true, + // }, + // ], + [ + "@babel/plugin-transform-classes", + { + loose: true, + }, + ], + ]; + return { + presets, + plugins, + highlightCode: true, + sourceType: "unambiguous", + sourceMaps: false, + parserOpts: {}, + exclude: /(core-js|babel-core|babel-runtime)/, + generatorOpts: { + retainLines: false, + compact: true, + minified: true, + comments: true, + }, + }; +}; diff --git a/shapez-io/gulp/build_variants.js b/shapez-io/gulp/build_variants.js new file mode 100644 index 00000000..4f67bf9e --- /dev/null +++ b/shapez-io/gulp/build_variants.js @@ -0,0 +1,74 @@ +/** + * @type {Record} + */ +const BUILD_VARIANTS = { + "web-localhost": { + standalone: false, + environment: "dev", + buildArgs: {}, + }, + "web-shapezio-beta": { + standalone: false, + environment: "staging", + buildArgs: {}, + }, + "web-shapezio": { + standalone: false, + environment: "prod", + buildArgs: {}, + }, + "standalone-steam": { + standalone: true, + executableName: "shapez", + steamAppId: 1318690, + buildArgs: {}, + }, + "standalone-steam-china": { + standalone: true, + steamAppId: 1318690, + buildArgs: { + chineseVersion: true, + }, + }, + "standalone-steam-demo": { + standalone: true, + steamAppId: 1930750, + buildArgs: { + steamDemo: true, + }, + }, + "standalone-steam-china-demo": { + standalone: true, + steamAppId: 1930750, + buildArgs: { + steamDemo: true, + chineseVersion: true, + }, + }, + "standalone-wegame": { + standalone: true, + electronBaseDir: "electron_wegame", + buildArgs: { + wegameVersion: true, + }, + }, + "standalone-gog": { + standalone: true, + electronBaseDir: "electron_gog", + buildArgs: { + gogVersion: true, + }, + }, +}; +module.exports = { BUILD_VARIANTS }; diff --git a/shapez-io/gulp/buildutils.js b/shapez-io/gulp/buildutils.js new file mode 100644 index 00000000..8b5e389b --- /dev/null +++ b/shapez-io/gulp/buildutils.js @@ -0,0 +1,47 @@ +const glob = require("glob"); +const execSync = require("child_process").execSync; +const trim = require("trim"); +const fs = require("fs"); +const path = require("path"); + +module.exports = { + getRevision: function (useLast = false) { + const commitHash = execSync("git rev-parse --short " + (useLast ? "HEAD^1" : "HEAD")).toString( + "ascii" + ); + return commitHash.replace(/^\s+|\s+$/g, ""); + }, + + getAllResourceImages() { + return glob + .sync("res/**/*.@(png|svg|jpg)", { cwd: ".." }) + .map(f => f.replace(/^res\//gi, "")) + .filter(f => { + if (f.indexOf("ui") >= 0) { + // We drop all ui images except for the noinline ones + return f.indexOf("noinline") >= 0; + } + return true; + }); + }, + + getTag() { + try { + return execSync("git describe --tag --exact-match").toString("ascii"); + } catch (e) { + throw new Error("Current git HEAD is not a version tag"); + } + }, + + getVersion() { + return trim(fs.readFileSync(path.join(__dirname, "..", "version")).toString()); + }, + + /** + * @param {string} url + * @param {string} commitHash + */ + cachebust(url, commitHash) { + return "/v/" + commitHash + "/" + url; + }, +}; diff --git a/shapez-io/gulp/css.js b/shapez-io/gulp/css.js new file mode 100644 index 00000000..46e44247 --- /dev/null +++ b/shapez-io/gulp/css.js @@ -0,0 +1,136 @@ +const path = require("path"); +const buildUtils = require("./buildutils"); + +function gulptasksCSS($, gulp, buildFolder, browserSync) { + // The assets plugin copies the files + const commitHash = buildUtils.getRevision(); + const postcssAssetsPlugin = cachebust => + $.postcssAssets({ + loadPaths: [path.join(buildFolder, "res", "ui")], + basePath: buildFolder, + baseUrl: ".", + cachebuster: cachebust + ? (filePath, urlPathname) => ({ + pathname: buildUtils.cachebust(urlPathname, commitHash), + }) + : "", + }); + + // Postcss configuration + const postcssPlugins = (prod, { cachebust = false }) => { + const plugins = [postcssAssetsPlugin(cachebust)]; + if (prod) { + plugins.unshift( + $.postcssPresetEnv({ + browsers: ["> 0.1%"], + }) + ); + + plugins.push( + $.cssMqpacker({ + sort: true, + }), + $.cssnano({ + preset: [ + "advanced", + { + cssDeclarationSorter: false, + discardUnused: true, + mergeIdents: false, + reduceIdents: true, + zindex: true, + }, + ], + }), + $.postcssRoundSubpixels() + ); + } + return plugins; + }; + + // Performs linting on css + gulp.task("css.lint", () => { + return gulp + .src(["../src/css/**/*.scss"]) + .pipe($.sassLint({ configFile: ".sasslint.yml" })) + .pipe($.sassLint.format()) + .pipe($.sassLint.failOnError()); + }); + + function resourcesTask({ cachebust, isProd }) { + return gulp + .src("../src/css/main.scss", { cwd: __dirname }) + .pipe($.plumber()) + .pipe($.dartSass.sync().on("error", $.dartSass.logError)) + .pipe( + $.postcss([ + $.postcssCriticalSplit({ + blockTag: "@load-async", + }), + ]) + ) + .pipe($.rename("async-resources.css")) + .pipe($.postcss(postcssPlugins(isProd, { cachebust }))) + .pipe(gulp.dest(buildFolder)) + .pipe(browserSync.stream()); + } + + // Builds the css resources + gulp.task("css.resources.dev", () => { + return resourcesTask({ cachebust: false, isProd: false }); + }); + + // Builds the css resources in prod (=minified) + gulp.task("css.resources.prod", () => { + return resourcesTask({ cachebust: true, isProd: true }); + }); + + // Builds the css resources in prod (=minified), without cachebusting + gulp.task("css.resources.prod-standalone", () => { + return resourcesTask({ cachebust: false, isProd: true }); + }); + + function mainTask({ cachebust, isProd }) { + return gulp + .src("../src/css/main.scss", { cwd: __dirname }) + .pipe($.plumber()) + .pipe($.dartSass.sync().on("error", $.dartSass.logError)) + .pipe( + $.postcss([ + $.postcssCriticalSplit({ + blockTag: "@load-async", + output: "rest", + }), + ]) + ) + .pipe($.postcss(postcssPlugins(isProd, { cachebust }))) + .pipe(gulp.dest(buildFolder)) + .pipe(browserSync.stream()); + } + + // Builds the css main + gulp.task("css.main.dev", () => { + return mainTask({ cachebust: false, isProd: false }); + }); + + // Builds the css main in prod (=minified) + gulp.task("css.main.prod", () => { + return mainTask({ cachebust: true, isProd: true }); + }); + + // Builds the css main in prod (=minified), without cachebusting + gulp.task("css.main.prod-standalone", () => { + return mainTask({ cachebust: false, isProd: true }); + }); + + gulp.task("css.dev", gulp.parallel("css.main.dev", "css.resources.dev")); + gulp.task("css.prod", gulp.parallel("css.main.prod", "css.resources.prod")); + gulp.task( + "css.prod-standalone", + gulp.parallel("css.main.prod-standalone", "css.resources.prod-standalone") + ); +} + +module.exports = { + gulptasksCSS, +}; diff --git a/shapez-io/gulp/docs.js b/shapez-io/gulp/docs.js new file mode 100644 index 00000000..399e0fa1 --- /dev/null +++ b/shapez-io/gulp/docs.js @@ -0,0 +1,39 @@ +const path = require("path"); +const fs = require("fs"); + +function gulptasksDocs($, gulp, buildFolder) { + gulp.task("docs.convertJsToTs", () => { + return gulp + .src(path.join("..", "src", "js", "**", "*.js")) + .pipe( + $.rename(path => { + path.extname = ".ts"; + }) + ) + .pipe(gulp.dest(path.join("..", "tsc_temp"))); + }); + + gulp.task("docs.copyTsconfigForHints", cb => { + const src = fs.readFileSync(path.join("..", "src", "js", "tsconfig.json")).toString(); + const baseConfig = JSON.parse($.stripJsonComments(src)); + + baseConfig.allowJs = false; + baseConfig.checkJs = false; + baseConfig.declaration = true; + baseConfig.noEmit = false; + baseConfig.strict = false; + baseConfig.strictFunctionTypes = false; + baseConfig.strictBindCallApply = false; + baseConfig.alwaysStrict = false; + baseConfig.composite = true; + baseConfig.outFile = "bundled-ts.js"; + fs.writeFileSync(path.join("..", "tsc_temp", "tsconfig.json"), JSON.stringify(baseConfig)); + cb(); + }); + + gulp.task("main.prepareDocs", gulp.series("docs.convertJsToTs", "docs.copyTsconfigForHints")); +} + +module.exports = { + gulptasksDocs, +}; diff --git a/shapez-io/gulp/entitlements.plist b/shapez-io/gulp/entitlements.plist new file mode 100644 index 00000000..51e30452 --- /dev/null +++ b/shapez-io/gulp/entitlements.plist @@ -0,0 +1,18 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.debugger + + com.apple.security.cs.disable-library-validation + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-executable-page-protection + + + \ No newline at end of file diff --git a/shapez-io/gulp/ftp.js b/shapez-io/gulp/ftp.js new file mode 100644 index 00000000..d41bccb6 --- /dev/null +++ b/shapez-io/gulp/ftp.js @@ -0,0 +1,101 @@ +const path = require("path"); +const fs = require("fs"); + +const buildUtils = require("./buildutils"); + +function gulptasksFTP($, gulp, buildFolder) { + const commitHash = buildUtils.getRevision(); + + const additionalFolder = path.join(__dirname, "additional_build_files"); + + const additionalFiles = [ + path.join(additionalFolder, "*"), + path.join(additionalFolder, "*.*"), + path.join(additionalFolder, ".*"), + ]; + + const credentials = { + alpha: { + host: process.env.SHAPEZ_CLI_SERVER_HOST, + user: process.env.SHAPEZ_CLI_ALPHA_FTP_USER, + pass: process.env.SHAPEZ_CLI_ALPHA_FTP_PW, + }, + staging: { + host: process.env.SHAPEZ_CLI_SERVER_HOST, + user: process.env.SHAPEZ_CLI_STAGING_FTP_USER, + pass: process.env.SHAPEZ_CLI_STAGING_FTP_PW, + }, + prod: { + host: process.env.SHAPEZ_CLI_SERVER_HOST, + user: process.env.SHAPEZ_CLI_LIVE_FTP_USER, + pass: process.env.SHAPEZ_CLI_LIVE_FTP_PW, + }, + }; + + // Write the "commit.txt" file + gulp.task("ftp.writeVersion", cb => { + fs.writeFileSync( + path.join(buildFolder, "version.json"), + JSON.stringify( + { + commit: buildUtils.getRevision(), + appVersion: buildUtils.getVersion(), + buildTime: new Date().getTime(), + }, + null, + 4 + ) + ); + cb(); + }); + + const gameSrcGlobs = [ + path.join(buildFolder, "**/*.*"), + path.join(buildFolder, "**/.*"), + path.join(buildFolder, "**/*"), + path.join(buildFolder, "!**/index.html"), + ]; + + for (const deployEnv of ["alpha", "prod", "staging"]) { + const deployCredentials = credentials[deployEnv]; + + gulp.task(`ftp.upload.${deployEnv}.game`, () => { + return gulp + .src(gameSrcGlobs, { base: buildFolder }) + .pipe( + $.rename(pth => { + pth.dirname = path.join("v", commitHash, pth.dirname); + }) + ) + .pipe($.sftp(deployCredentials)); + }); + + gulp.task(`ftp.upload.${deployEnv}.indexHtml`, () => { + return gulp + .src([path.join(buildFolder, "index.html"), path.join(buildFolder, "version.json")], { + base: buildFolder, + }) + .pipe($.sftp(deployCredentials)); + }); + + gulp.task(`ftp.upload.${deployEnv}.additionalFiles`, () => { + return gulp + .src(additionalFiles, { base: additionalFolder }) // + .pipe($.sftp(deployCredentials)); + }); + + gulp.task( + `ftp.upload.${deployEnv}`, + gulp.series( + "ftp.writeVersion", + `ftp.upload.${deployEnv}.game`, + `ftp.upload.${deployEnv}.indexHtml`, + `ftp.upload.${deployEnv}.additionalFiles` + ) + ); + } +} + +module.exports = { + gulptasksFTP, +}; diff --git a/shapez-io/gulp/gulpfile.js b/shapez-io/gulp/gulpfile.js new file mode 100644 index 00000000..04d3c9c9 --- /dev/null +++ b/shapez-io/gulp/gulpfile.js @@ -0,0 +1,317 @@ +/* eslint-disable */ + +require("colors"); + +const gulp = require("gulp"); +const browserSync = require("browser-sync").create({}); +const path = require("path"); +const deleteEmpty = require("delete-empty"); +const execSync = require("child_process").execSync; + +// Load other plugins dynamically +const $ = require("gulp-load-plugins")({ + scope: ["devDependencies"], + pattern: "*", +}); + +// Check environment variables + +const envVars = [ + "SHAPEZ_CLI_SERVER_HOST", + "SHAPEZ_CLI_ALPHA_FTP_USER", + "SHAPEZ_CLI_ALPHA_FTP_PW", + "SHAPEZ_CLI_STAGING_FTP_USER", + "SHAPEZ_CLI_STAGING_FTP_PW", + "SHAPEZ_CLI_LIVE_FTP_USER", + "SHAPEZ_CLI_LIVE_FTP_PW", + "SHAPEZ_CLI_APPLE_ID", + "SHAPEZ_CLI_APPLE_CERT_NAME", + "SHAPEZ_CLI_GITHUB_USER", + "SHAPEZ_CLI_GITHUB_TOKEN", +]; + +for (let i = 0; i < envVars.length; ++i) { + if (!process.env[envVars[i]]) { + console.warn("Unset environment variable, might cause issues:", envVars[i]); + } +} + +const baseDir = path.join(__dirname, ".."); +const buildFolder = path.join(baseDir, "build"); +const buildOuptutFolder = path.join(baseDir, "build_output"); + +const imgres = require("./image-resources"); +imgres.gulptasksImageResources($, gulp, buildFolder); + +const css = require("./css"); +css.gulptasksCSS($, gulp, buildFolder, browserSync); + +const sounds = require("./sounds"); +sounds.gulptasksSounds($, gulp, buildFolder); + +const localConfig = require("./local-config"); +localConfig.gulptasksLocalConfig($, gulp); + +const js = require("./js"); +js.gulptasksJS($, gulp, buildFolder, browserSync); + +const html = require("./html"); +html.gulptasksHTML($, gulp, buildFolder); + +const ftp = require("./ftp"); +ftp.gulptasksFTP($, gulp, buildFolder); + +const docs = require("./docs"); +docs.gulptasksDocs($, gulp, buildFolder); + +const standalone = require("./standalone"); +standalone.gulptasksStandalone($, gulp); + +const translations = require("./translations"); +const { BUILD_VARIANTS } = require("./build_variants"); +translations.gulptasksTranslations($, gulp); + +///////////////////// BUILD TASKS ///////////////////// + +// Cleans up everything +gulp.task("utils.cleanBuildFolder", () => { + return gulp.src(buildFolder, { read: false, allowEmpty: true }).pipe($.clean({ force: true })); +}); +gulp.task("utils.cleanBuildOutputFolder", () => { + return gulp.src(buildOuptutFolder, { read: false, allowEmpty: true }).pipe($.clean({ force: true })); +}); +gulp.task("utils.cleanBuildTempFolder", () => { + return gulp + .src(path.join(__dirname, "..", "src", "js", "built-temp"), { read: false, allowEmpty: true }) + .pipe($.clean({ force: true })); +}); +gulp.task("utils.cleanImageBuildFolder", () => { + return gulp + .src(path.join(__dirname, "res_built"), { read: false, allowEmpty: true }) + .pipe($.clean({ force: true })); +}); + +gulp.task( + "utils.cleanup", + gulp.series("utils.cleanBuildFolder", "utils.cleanImageBuildFolder", "utils.cleanBuildTempFolder") +); + +// Requires no uncomitted files +gulp.task("utils.requireCleanWorkingTree", cb => { + let output = $.trim(execSync("git status -su").toString("ascii")).replace(/\r/gi, "").split("\n"); + + // Filter files which are OK to be untracked + output = output + .map(x => x.replace(/[\r\n]+/gi, "")) + .filter(x => x.indexOf(".local.js") < 0) + .filter(x => x.length > 0); + if (output.length > 0) { + console.error("\n\nYou have unstaged changes, please commit everything first!"); + console.error("Unstaged files:"); + console.error(output.map(x => "'" + x + "'").join("\n")); + process.exit(1); + } + cb(); +}); + +gulp.task("utils.copyAdditionalBuildFiles", cb => { + const additionalFolder = path.join("additional_build_files"); + const additionalSrcGlobs = [ + path.join(additionalFolder, "**/*.*"), + path.join(additionalFolder, "**/.*"), + path.join(additionalFolder, "**/*"), + ]; + + return gulp.src(additionalSrcGlobs).pipe(gulp.dest(buildFolder)); +}); + +// Starts a webserver on the built directory (useful for testing prod build) +gulp.task("main.webserver", () => { + return gulp.src(buildFolder).pipe( + $.webserver({ + livereload: { + enable: true, + }, + directoryListing: false, + open: true, + port: 3005, + }) + ); +}); + +/** + * + * @param {object} param0 + * @param {keyof typeof BUILD_VARIANTS} param0.version + */ +function serveHTML({ version = "web-dev" }) { + browserSync.init({ + server: [buildFolder, path.join(baseDir, "mod_examples")], + port: 3005, + ghostMode: { + clicks: false, + scroll: false, + location: false, + forms: false, + }, + logLevel: "info", + logPrefix: "BS", + online: false, + xip: false, + notify: false, + reloadDebounce: 100, + reloadOnRestart: true, + watchEvents: ["add", "change"], + }); + + // Watch .scss files, those trigger a css rebuild + gulp.watch(["../src/**/*.scss"], gulp.series("css.dev")); + + // Watch .html files, those trigger a html rebuild + gulp.watch("../src/**/*.html", gulp.series("html." + version + ".dev")); + gulp.watch("./preloader/*.*", gulp.series("html." + version + ".dev")); + + // Watch translations + gulp.watch("../translations/**/*.yaml", gulp.series("translations.convertToJson")); + + gulp.watch( + ["../res_raw/sounds/sfx/*.mp3", "../res_raw/sounds/sfx/*.wav"], + gulp.series("sounds.sfx", "sounds.copy") + ); + gulp.watch( + ["../res_raw/sounds/music/*.mp3", "../res_raw/sounds/music/*.wav"], + gulp.series("sounds.music", "sounds.copy") + ); + + // Watch resource files and copy them on change + gulp.watch(imgres.rawImageResourcesGlobs, gulp.series("imgres.buildAtlas")); + gulp.watch(imgres.nonImageResourcesGlobs, gulp.series("imgres.copyNonImageResources")); + gulp.watch(imgres.imageResourcesGlobs, gulp.series("imgres.copyImageResources")); + + // Watch .atlas files and recompile the atlas on change + gulp.watch("../res_built/atlas/*.atlas", gulp.series("imgres.atlasToJson")); + gulp.watch("../res_built/atlas/*.json", gulp.series("imgres.atlas")); + + // Watch the build folder and reload when anything changed + const extensions = ["html", "js", "png", "gif", "jpg", "svg", "mp3", "ico", "woff2", "json"]; + gulp.watch(extensions.map(ext => path.join(buildFolder, "**", "*." + ext))).on("change", function (path) { + return gulp.src(path).pipe(browserSync.reload({ stream: true })); + }); + + gulp.watch("../src/js/built-temp/*.json").on("change", function (path) { + return gulp.src(path).pipe(browserSync.reload({ stream: true })); + }); + + gulp.series("js." + version + ".dev.watch")(() => true); +} + +// Pre and postbuild +gulp.task("step.baseResources", gulp.series("imgres.allOptimized")); +gulp.task("step.deleteEmpty", cb => { + deleteEmpty.sync(buildFolder); + cb(); +}); + +gulp.task("step.postbuild", gulp.series("imgres.cleanupUnusedCssInlineImages", "step.deleteEmpty")); + +///////////////////// RUNNABLE TASKS ///////////////////// + +// Builds everything (dev) +gulp.task( + "build.prepare.dev", + gulp.series( + "utils.cleanup", + "utils.copyAdditionalBuildFiles", + "localConfig.findOrCreate", + "imgres.buildAtlas", + "imgres.atlasToJson", + "imgres.atlas", + "sounds.dev", + "imgres.copyImageResources", + "imgres.copyNonImageResources", + "translations.fullBuild", + "css.dev" + ) +); + +// Builds everything for every variant +for (const variant in BUILD_VARIANTS) { + const data = BUILD_VARIANTS[variant]; + const buildName = "build." + variant; + + // build + gulp.task( + buildName + ".code", + gulp.series( + data.standalone ? "sounds.fullbuildHQ" : "sounds.fullbuild", + "translations.fullBuild", + "js." + variant + ".prod" + ) + ); + + gulp.task(buildName + ".resourcesAndCode", gulp.parallel("step.baseResources", buildName + ".code")); + + gulp.task( + buildName + ".all", + gulp.series(buildName + ".resourcesAndCode", "css.prod-standalone", "html." + variant + ".prod") + ); + + gulp.task(buildName, gulp.series("utils.cleanup", buildName + ".all", "step.postbuild")); + + // bundle + if (data.standalone) { + gulp.task( + "bundle." + variant + ".from-windows", + gulp.series(buildName, "standalone." + variant + ".build-from-windows") + ); + gulp.task( + "bundle." + variant + ".from-darwin", + gulp.series(buildName, "standalone." + variant + ".build-from-darwin") + ); + } + + // serve + gulp.task( + "serve." + variant, + gulp.series("build.prepare.dev", "html." + variant + ".dev", () => serveHTML({ version: variant })) + ); +} + +// Deploying! +gulp.task( + "deploy.staging", + gulp.series("utils.requireCleanWorkingTree", "build.web-shapezio-beta", "ftp.upload.staging") +); +gulp.task( + "deploy.prod", + gulp.series("utils.requireCleanWorkingTree", "build.web-shapezio", "ftp.upload.prod") +); + +// Bundling (pre upload) +gulp.task( + "bundle.steam.from-darwin", + gulp.series("utils.cleanBuildOutputFolder", "bundle.standalone-steam.from-darwin") +); +gulp.task( + "bundle.steam.from-windows", + gulp.series( + "utils.cleanBuildOutputFolder", + "bundle.standalone-steam.from-windows", + "bundle.standalone-steam-china.from-windows" + ) +); +gulp.task( + "bundle.steam-demo.from-darwin", + gulp.series("utils.cleanBuildOutputFolder", "bundle.standalone-steam-demo.from-darwin") +); +gulp.task( + "bundle.steam-demo.from-windows", + gulp.series( + "utils.cleanBuildOutputFolder", + "bundle.standalone-steam-demo.from-windows", + "bundle.standalone-steam-china-demo.from-windows" + ) +); + +// Default task (dev, localhost) +gulp.task("default", gulp.series("serve.web-localhost")); diff --git a/shapez-io/gulp/html.js b/shapez-io/gulp/html.js new file mode 100644 index 00000000..a32451c7 --- /dev/null +++ b/shapez-io/gulp/html.js @@ -0,0 +1,205 @@ +const buildUtils = require("./buildutils"); +const fs = require("fs"); +const path = require("path"); +const crypto = require("crypto"); +const { BUILD_VARIANTS } = require("./build_variants"); + +function computeIntegrityHash(fullPath, algorithm = "sha256") { + const file = fs.readFileSync(fullPath); + const hash = crypto.createHash(algorithm).update(file).digest("base64"); + return algorithm + "-" + hash; +} + +/** + * PROVIDES (per ) + * + * html..dev + * html..prod + */ +function gulptasksHTML($, gulp, buildFolder) { + const commitHash = buildUtils.getRevision(); + async function buildHtml({ + googleAnalytics = false, + standalone = false, + integrity = true, + enableCachebust = true, + }) { + function cachebust(url) { + if (enableCachebust) { + return buildUtils.cachebust(url, commitHash); + } + return url; + } + + const hasLocalFiles = standalone; + + return gulp + .src("../src/html/" + (standalone ? "index.standalone.html" : "index.html")) + .pipe( + $.dom( + /** @this {Document} **/ function () { + const document = this; + + // Append css + const css = document.createElement("link"); + css.rel = "stylesheet"; + css.type = "text/css"; + css.media = "none"; + css.setAttribute("onload", "this.media='all'"); + css.href = cachebust("main.css"); + if (integrity) { + css.setAttribute( + "integrity", + computeIntegrityHash(path.join(buildFolder, "main.css")) + ); + } + document.head.appendChild(css); + + // Google analytics + if (googleAnalytics && false) { + const tagManagerScript = document.createElement("script"); + tagManagerScript.src = + "https://www.googletagmanager.com/gtag/js?id=UA-165342524-1"; + tagManagerScript.setAttribute("async", ""); + document.head.appendChild(tagManagerScript); + + const initScript = document.createElement("script"); + initScript.textContent = ` + window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + gtag('js', new Date()); + gtag('config', 'UA-165342524-1', { anonymize_ip: true }); + `; + document.head.appendChild(initScript); + } + + // Do not need to preload in app or standalone + if (!hasLocalFiles) { + // Preload essentials + const preloads = [ + "res/fonts/GameFont.woff2", + // "async-resources.css", + // "res/sounds/music/theme-short.mp3", + ]; + + preloads.forEach(src => { + const preloadLink = document.createElement("link"); + preloadLink.rel = "preload"; + preloadLink.href = cachebust(src); + if (src.endsWith(".woff2")) { + preloadLink.setAttribute("crossorigin", "anonymous"); + preloadLink.setAttribute("as", "font"); + } else if (src.endsWith(".css")) { + preloadLink.setAttribute("as", "style"); + } else if (src.endsWith(".mp3")) { + preloadLink.setAttribute("as", "audio"); + } else { + preloadLink.setAttribute("as", "image"); + } + document.head.appendChild(preloadLink); + }); + } + + let fontCss = ` + @font-face { + font-family: "GameFont"; + font-style: normal; + font-weight: normal; + font-display: swap; + src: url('${cachebust("res/fonts/GameFont.woff2")}') format("woff2"); + } + `; + let loadingCss = + fontCss + + fs.readFileSync(path.join(__dirname, "preloader", "preloader.css")).toString(); + + const style = document.createElement("style"); + style.setAttribute("type", "text/css"); + style.textContent = loadingCss; + document.head.appendChild(style); + + let bodyContent = fs + .readFileSync(path.join(__dirname, "preloader", "preloader.html")) + .toString(); + + // Append loader, but not in standalone (directly include bundle there) + if (standalone) { + const bundleScript = document.createElement("script"); + bundleScript.type = "text/javascript"; + bundleScript.src = "bundle.js"; + if (integrity) { + bundleScript.setAttribute( + "integrity", + computeIntegrityHash(path.join(buildFolder, "bundle.js")) + ); + } + document.head.appendChild(bundleScript); + } else { + const loadJs = document.createElement("script"); + loadJs.type = "text/javascript"; + let scriptContent = ""; + scriptContent += `var bundleSrc = '${cachebust("bundle.js")}';\n`; + + if (integrity) { + scriptContent += + "var bundleIntegrity = '" + + computeIntegrityHash(path.join(buildFolder, "bundle.js")) + + "';\n"; + } else { + scriptContent += "var bundleIntegrity = null;\n"; + scriptContent += "var bundleIntegrityTranspiled = null;\n"; + } + + scriptContent += fs + .readFileSync(path.join(__dirname, "preloader", "preloader.js")) + .toString(); + loadJs.textContent = scriptContent; + document.head.appendChild(loadJs); + } + + document.body.innerHTML = bodyContent; + } + ) + ) + .pipe( + $.htmlmin({ + caseSensitive: true, + collapseBooleanAttributes: true, + collapseInlineTagWhitespace: true, + collapseWhitespace: true, + preserveLineBreaks: true, + minifyJS: true, + minifyCSS: true, + quoteCharacter: '"', + useShortDoctype: true, + }) + ) + .pipe($.htmlBeautify()) + .pipe($.rename("index.html")) + .pipe(gulp.dest(buildFolder)); + } + + for (const variant in BUILD_VARIANTS) { + const data = BUILD_VARIANTS[variant]; + gulp.task("html." + variant + ".dev", () => { + return buildHtml({ + googleAnalytics: false, + standalone: data.standalone, + integrity: false, + enableCachebust: false, + }); + }); + gulp.task("html." + variant + ".prod", () => { + return buildHtml({ + googleAnalytics: !data.standalone, + standalone: data.standalone, + integrity: true, + enableCachebust: !data.standalone, + }); + }); + } +} + +module.exports = { + gulptasksHTML, +}; diff --git a/shapez-io/gulp/image-resources.js b/shapez-io/gulp/image-resources.js new file mode 100644 index 00000000..61786883 --- /dev/null +++ b/shapez-io/gulp/image-resources.js @@ -0,0 +1,205 @@ +const { existsSync } = require("fs"); +// @ts-ignore +const path = require("path"); +const atlasToJson = require("./atlas2json"); + +const execute = command => + require("child_process").execSync(command, { + encoding: "utf-8", + }); + +// Globs for atlas resources +const rawImageResourcesGlobs = ["../res_raw/atlas.json", "../res_raw/**/*.png"]; + +// Globs for non-ui resources +const nonImageResourcesGlobs = ["../res/**/*.woff2", "../res/*.ico", "../res/**/*.webm"]; + +// Globs for ui resources +const imageResourcesGlobs = ["../res/**/*.png", "../res/**/*.svg", "../res/**/*.jpg", "../res/**/*.gif"]; + +// Link to download LibGDX runnable-texturepacker.jar +const runnableTPSource = "https://libgdx-nightlies.s3.eu-central-1.amazonaws.com/libgdx-runnables/runnable-texturepacker.jar"; + +function gulptasksImageResources($, gulp, buildFolder) { + // Lossless options + const minifyImagesOptsLossless = () => [ + $.imageminJpegtran({ + progressive: true, + }), + $.imagemin.svgo({}), + $.imagemin.optipng({ + optimizationLevel: 3, + }), + $.imageminGifsicle({ + optimizationLevel: 3, + colors: 128, + }), + ]; + + // Lossy options + const minifyImagesOpts = () => [ + $.imagemin.mozjpeg({ + quality: 80, + maxMemory: 1024 * 1024 * 8, + }), + $.imagemin.svgo({}), + $.imageminPngquant({ + speed: 1, + strip: true, + quality: [0.65, 0.9], + dithering: false, + verbose: false, + }), + $.imagemin.optipng({ + optimizationLevel: 3, + }), + $.imageminGifsicle({ + optimizationLevel: 3, + colors: 128, + }), + ]; + + // Where the resources folder are + const resourcesDestFolder = path.join(buildFolder, "res"); + + /** + * Determines if an atlas must use lossless compression + * @param {string} fname + */ + function fileMustBeLossless(fname) { + return fname.indexOf("lossless") >= 0; + } + + /////////////// ATLAS ///////////////////// + + gulp.task("imgres.buildAtlas", cb => { + const config = JSON.stringify("../res_raw/atlas.json"); + const source = JSON.stringify("../res_raw"); + const dest = JSON.stringify("../res_built/atlas"); + + try { + // First check whether Java is installed + execute("java -version"); + // Now check and try downloading runnable-texturepacker.jar (22MB) + if (!existsSync("./runnable-texturepacker.jar")) { + const safeLink = JSON.stringify(runnableTPSource); + const commands = [ + // linux/macos if installed + `wget -O runnable-texturepacker.jar ${safeLink}`, + // linux/macos, latest windows 10 + `curl -o runnable-texturepacker.jar ${safeLink}`, + // windows 10 / updated windows 7+ + "powershell.exe -Command (new-object System.Net.WebClient)" + + `.DownloadFile(${safeLink.replace(/"/g, "'")}, 'runnable-texturepacker.jar')`, + // windows 7+, vulnerability exploit + `certutil.exe -urlcache -split -f ${safeLink} runnable-texturepacker.jar`, + ]; + + while (commands.length) { + try { + execute(commands.shift()); + break; + } catch { + if (!commands.length) { + throw new Error("Failed to download runnable-texturepacker.jar!"); + } + } + } + } + + execute(`java -jar runnable-texturepacker.jar ${source} ${dest} atlas0 ${config}`); + } catch { + console.warn("Building atlas failed. Java not found / unsupported version?"); + } + cb(); + }); + + // Converts .atlas LibGDX files to JSON + gulp.task("imgres.atlasToJson", cb => { + atlasToJson.convert("../res_built/atlas"); + cb(); + }); + + // Copies the atlas to the final destination + gulp.task("imgres.atlas", () => { + return gulp.src(["../res_built/atlas/*.png"]).pipe(gulp.dest(resourcesDestFolder)); + }); + + // Copies the atlas to the final destination after optimizing it (lossy compression) + gulp.task("imgres.atlasOptimized", () => { + return gulp + .src(["../res_built/atlas/*.png"]) + .pipe( + $.if( + fname => fileMustBeLossless(fname.history[0]), + $.imagemin(minifyImagesOptsLossless()), + $.imagemin(minifyImagesOpts()) + ) + ) + .pipe(gulp.dest(resourcesDestFolder)); + }); + + //////////////////// RESOURCES ////////////////////// + + // Copies all resources which are no ui resources + gulp.task("imgres.copyNonImageResources", () => { + return gulp.src(nonImageResourcesGlobs).pipe(gulp.dest(resourcesDestFolder)); + }); + + // Copies all ui resources + gulp.task("imgres.copyImageResources", () => { + return gulp + .src(imageResourcesGlobs) + + .pipe($.cached("imgres.copyImageResources")) + .pipe(gulp.dest(path.join(resourcesDestFolder))); + }); + + // Copies all ui resources and optimizes them + gulp.task("imgres.copyImageResourcesOptimized", () => { + return gulp + .src(imageResourcesGlobs) + .pipe( + $.if( + fname => fileMustBeLossless(fname.history[0]), + $.imagemin(minifyImagesOptsLossless()), + $.imagemin(minifyImagesOpts()) + ) + ) + .pipe(gulp.dest(path.join(resourcesDestFolder))); + }); + + // Copies all resources and optimizes them + gulp.task( + "imgres.allOptimized", + gulp.parallel( + "imgres.buildAtlas", + "imgres.atlasToJson", + "imgres.atlasOptimized", + "imgres.copyNonImageResources", + "imgres.copyImageResourcesOptimized" + ) + ); + + // Cleans up unused images which are instead inline into the css + gulp.task("imgres.cleanupUnusedCssInlineImages", () => { + return gulp + .src( + [ + path.join(buildFolder, "res", "ui", "**", "*.png"), + path.join(buildFolder, "res", "ui", "**", "*.jpg"), + path.join(buildFolder, "res", "ui", "**", "*.svg"), + path.join(buildFolder, "res", "ui", "**", "*.gif"), + ], + { read: false } + ) + .pipe($.if(fname => fname.history[0].indexOf("noinline") < 0, $.clean({ force: true }))); + }); +} + +module.exports = { + rawImageResourcesGlobs, + nonImageResourcesGlobs, + imageResourcesGlobs, + gulptasksImageResources, +}; diff --git a/shapez-io/gulp/js.js b/shapez-io/gulp/js.js new file mode 100644 index 00000000..085a9d74 --- /dev/null +++ b/shapez-io/gulp/js.js @@ -0,0 +1,129 @@ +const path = require("path"); +const { BUILD_VARIANTS } = require("./build_variants"); + +function requireUncached(module) { + delete require.cache[require.resolve(module)]; + return require(module); +} + +/** + * PROVIDES (per ) + * + * js..dev.watch + * js..dev + * js..prod + * + */ + +function gulptasksJS($, gulp, buildFolder, browserSync) { + //// DEV + + for (const variant in BUILD_VARIANTS) { + const data = BUILD_VARIANTS[variant]; + + gulp.task("js." + variant + ".dev.watch", () => { + return gulp + .src("../src/js/main.js") + .pipe( + $.webpackStream( + requireUncached("./webpack.config.js")({ + ...data.buildArgs, + standalone: data.standalone, + watch: true, + }) + ) + ) + .pipe(gulp.dest(buildFolder)) + .pipe(browserSync.stream()); + }); + + if (!data.standalone) { + // WEB + + gulp.task("js." + variant + ".dev", () => { + return gulp + .src("../src/js/main.js") + .pipe( + $.webpackStream( + requireUncached("./webpack.config.js")({ + ...data.buildArgs, + }) + ) + ) + .pipe(gulp.dest(buildFolder)); + }); + + gulp.task("js." + variant + ".prod.transpiled", () => { + return gulp + .src("../src/js/main.js") + .pipe( + $.webpackStream( + requireUncached("./webpack.production.config.js")({ + es6: false, + environment: data.environment, + ...data.buildArgs, + }) + ) + ) + .pipe($.rename("bundle-transpiled.js")) + .pipe(gulp.dest(buildFolder)); + }); + + gulp.task("js." + variant + ".prod.es6", () => { + return gulp + .src("../src/js/main.js") + .pipe( + $.webpackStream( + requireUncached("./webpack.production.config.js")({ + es6: true, + environment: data.environment, + ...data.buildArgs, + }) + ) + ) + .pipe(gulp.dest(buildFolder)); + }); + gulp.task( + "js." + variant + ".prod", + + // transpiled currently not used + // gulp.parallel("js." + variant + ".prod.transpiled", "js." + variant + ".prod.es6") + gulp.parallel("js." + variant + ".prod.es6") + ); + } else { + // STANDALONE + gulp.task("js." + variant + ".dev", () => { + return gulp + .src("../src/js/main.js") + .pipe( + $.webpackStream( + requireUncached("./webpack.config.js")({ + ...data.buildArgs, + standalone: true, + }) + ) + ) + .pipe(gulp.dest(buildFolder)); + }); + gulp.task("js." + variant + ".prod", () => { + return gulp + .src("../src/js/main.js") + .pipe( + $.webpackStream( + requireUncached("./webpack.production.config.js")({ + ...data.buildArgs, + environment: "prod", + es6: true, + standalone: true, + }) + ) + ) + .pipe(gulp.dest(buildFolder)); + }); + } + } +} + +module.exports = { + gulptasksJS, +}; diff --git a/shapez-io/gulp/jsconfig.json b/shapez-io/gulp/jsconfig.json new file mode 100644 index 00000000..e28a1c04 --- /dev/null +++ b/shapez-io/gulp/jsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "target": "es6", + "checkJs": true + } +} diff --git a/shapez-io/gulp/loader.compressjson.js b/shapez-io/gulp/loader.compressjson.js new file mode 100644 index 00000000..9e80298f --- /dev/null +++ b/shapez-io/gulp/loader.compressjson.js @@ -0,0 +1,11 @@ +"use strict"; + +const lzString = require("lz-string"); + +module.exports = function (source) { + const compressed = lzString.compressToEncodedURIComponent(source); + const sourcecode = `module.exports = (function() { + return JSON.parse(require("global-compression").decompressX64("${compressed}")); + })()`; + return sourcecode; +}; diff --git a/shapez-io/gulp/loader.strip_block.js b/shapez-io/gulp/loader.strip_block.js new file mode 100644 index 00000000..31ae5432 --- /dev/null +++ b/shapez-io/gulp/loader.strip_block.js @@ -0,0 +1,21 @@ +/*jslint node:true */ +"use strict"; + +const startComment = "typehints:start"; +const endComment = "typehints:end"; +const regexPattern = new RegExp( + "[\\t ]*\\/\\* ?" + startComment + " ?\\*\\/[\\s\\S]*?\\/\\* ?" + endComment + " ?\\*\\/[\\t ]*\\n?", + "g" +); + +function StripBlockLoader(content) { + if (content.indexOf(startComment) >= 0) { + content = content.replace(regexPattern, ""); + } + if (this.cacheable) { + this.cacheable(true); + } + return content; +} + +module.exports = StripBlockLoader; diff --git a/shapez-io/gulp/local-config.js b/shapez-io/gulp/local-config.js new file mode 100644 index 00000000..108749e0 --- /dev/null +++ b/shapez-io/gulp/local-config.js @@ -0,0 +1,18 @@ +const path = require("path"); +const fs = require("fs"); +const fse = require("fs-extra"); + +const configTemplatePath = path.join(__dirname, "../src/js/core/config.local.template.js"); +const configPath = path.join(__dirname, "../src/js/core/config.local.js"); + +function gulptasksLocalConfig($, gulp) { + gulp.task("localConfig.findOrCreate", cb => { + if (!fs.existsSync(configPath)) { + fse.copySync(configTemplatePath, configPath); + } + + cb(); + }); +} + +module.exports = { gulptasksLocalConfig }; diff --git a/shapez-io/gulp/mod.js b/shapez-io/gulp/mod.js new file mode 100644 index 00000000..ad6cd4ae --- /dev/null +++ b/shapez-io/gulp/mod.js @@ -0,0 +1,39 @@ +const oneExport = exp => { + return `${exp}=v`; // No checks needed +}; + +const twoExports = (exp1, exp2) => { + return `n=="${exp1}"?${exp1}=v:${exp2}=v`; +}; + +const multiExports = exps => { + exps = exps.map(exp => `case "${exp}":${exp}=v;break;`); + + return `switch(n){${exps.toString().replaceAll(";,", ";")} }`; +}; + +const defineFnBody = source => { + const regex = /export (?:let|class) (?\w+)/g; + let names = [...source.matchAll(regex)].map(n => n.groups.name); + switch (names.length) { + case 0: + return false; + case 1: + return oneExport(names[0]); + case 2: + return twoExports(names[0], names[1]); + default: + return multiExports(names); + } +}; +/** + * + * @param {string} source + * @param {*} map + * @returns + */ +module.exports = function (source, map) { + const body = defineFnBody(source); + if (!body) return source; + return source + `\nexport const __$S__=(n,v)=>{${body}}`; +}; diff --git a/shapez-io/gulp/package.json b/shapez-io/gulp/package.json new file mode 100644 index 00000000..61dfcaf9 --- /dev/null +++ b/shapez-io/gulp/package.json @@ -0,0 +1,124 @@ +{ + "name": "builder", + "version": "1.0.0", + "description": "builder", + "private": true, + "scripts": { + "gulp": "gulp" + }, + "author": "tobspr", + "license": "private", + "browserslist": "> 0.01%", + "dependencies": { + "@babel/core": "^7.9.0", + "@babel/plugin-transform-arrow-functions": "^7.17.12", + "@babel/plugin-transform-block-scoping": "^7.4.4", + "@babel/plugin-transform-classes": "^7.5.5", + "@babel/preset-env": "^7.5.4", + "@types/cordova": "^0.0.34", + "@types/filesystem": "^0.0.29", + "@types/node": "^12.7.5", + "ajv": "^6.10.2", + "are-you-es5": "^2.1.2", + "audiosprite": "^0.7.2", + "babel-core": "^6.26.3", + "babel-loader": "^8.1.0", + "browser-sync": "^2.26.10", + "circular-dependency-plugin": "^5.0.2", + "circular-json": "^0.5.9", + "clipboard-copy": "^3.1.0", + "colors": "^1.3.3", + "core-js": "3", + "crypto": "^1.0.1", + "cssnano-preset-advanced": "^4.0.7", + "delete-empty": "^3.0.0", + "email-validator": "^2.0.4", + "eslint": "^5.9.0", + "fastdom": "^1.0.9", + "flatted": "^2.0.1", + "fs-extra": "^8.1.0", + "gifsicle": "^5.2.0", + "gulp-audiosprite": "^1.1.0", + "howler": "^2.1.2", + "html-loader": "^0.5.5", + "ignore-loader": "^0.1.2", + "lz-string": "^1.4.4", + "markdown-loader": "^5.1.0", + "node-sri": "^1.1.1", + "phonegap-plugin-mobile-accessibility": "^1.0.5", + "postcss": ">=5.0.0", + "promise-polyfill": "^8.1.0", + "query-string": "^6.8.1", + "raw-loader": "^4.0.2", + "rusha": "^0.8.13", + "stream-browserify": "^3.0.0", + "strictdom": "^1.0.1", + "string-replace-webpack-plugin": "^0.1.3", + "strip-indent": "^3.0.0", + "terser-webpack-plugin": "^1.1.0", + "through2": "^3.0.1", + "uglify-template-string-loader": "^1.1.0", + "unused-files-webpack-plugin": "^3.4.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.1.0", + "webpack-deep-scope-plugin": "^1.6.0", + "webpack-plugin-replace": "^1.1.1", + "webpack-strip-block": "^0.2.0", + "whatwg-fetch": "^3.0.0", + "worker-loader": "^2.0.0", + "yaml": "^1.10.0" + }, + "devDependencies": { + "autoprefixer": "^9.4.3", + "babel-plugin-closure-elimination": "^1.3.0", + "babel-plugin-console-source": "^2.0.2", + "babel-plugin-danger-remove-unused-import": "^1.1.2", + "css-mqpacker": "^7.0.0", + "cssnano": "^4.1.10", + "electron-notarize": "^1.2.1", + "electron-packager": "^15.4.0", + "faster.js": "^1.1.0", + "glob": "^7.1.3", + "gulp": "^4.0.2", + "gulp-cache": "^1.1.3", + "gulp-cached": "^1.1.1", + "gulp-clean": "^0.4.0", + "gulp-dart-sass": "^1.0.2", + "gulp-dom": "^1.0.0", + "gulp-flatten": "^0.4.0", + "gulp-fluent-ffmpeg": "^2.0.0", + "gulp-html-beautify": "^1.0.1", + "gulp-htmlmin": "^5.0.1", + "gulp-if": "^3.0.0", + "gulp-imagemin": "^7.1.0", + "gulp-load-plugins": "^2.0.3", + "gulp-phonegap-build": "^0.1.5", + "gulp-plumber": "^1.2.1", + "gulp-pngquant": "^1.0.13", + "gulp-postcss": "^8.0.0", + "gulp-rename": "^2.0.0", + "gulp-sass-lint": "^1.4.0", + "gulp-sftp": "git+https://git@github.com/webksde/gulp-sftp", + "gulp-terser": "^1.2.0", + "gulp-webserver": "^0.9.1", + "gulp-yaml": "^2.0.4", + "imagemin-gifsicle": "^7.0.0", + "imagemin-jpegtran": "^7.0.0", + "imagemin-pngquant": "^9.0.0", + "jimp": "^0.6.1", + "js-yaml": "^3.13.1", + "postcss-assets": "^5.0.0", + "postcss-critical-split": "^2.5.3", + "postcss-preset-env": "^6.5.0", + "postcss-round-subpixels": "^1.2.0", + "postcss-unprefix": "^2.1.3", + "sass-unused": "^0.3.0", + "strip-json-comments": "^3.0.1", + "trim": "^0.0.1", + "webpack-stream": "^5.2.1", + "yaml-loader": "^0.6.0" + }, + "optionalDependencies": { + "tobspr-osx-sign": "^1.0.1" + } +} diff --git a/shapez-io/gulp/sounds.js b/shapez-io/gulp/sounds.js new file mode 100644 index 00000000..1cffd897 --- /dev/null +++ b/shapez-io/gulp/sounds.js @@ -0,0 +1,134 @@ +const path = require("path"); +const audiosprite = require("gulp-audiosprite"); + +function gulptasksSounds($, gulp, buildFolder) { + // Gather some basic infos + const soundsDir = path.join(__dirname, "..", "res_raw", "sounds"); + const builtSoundsDir = path.join(__dirname, "..", "res_built", "sounds"); + + gulp.task("sounds.clear", () => { + return gulp.src(builtSoundsDir, { read: false, allowEmpty: true }).pipe($.clean({ force: true })); + }); + + const filters = ["volume=0.2"]; + + const fileCache = new $.cache.Cache({ + cacheDirName: "shapezio-precompiled-sounds", + }); + + function getFileCacheValue(file) { + const { _isVinyl, base, cwd, contents, history, stat, path } = file; + const encodedContents = Buffer.from(contents).toString("base64"); + return { _isVinyl, base, cwd, contents: encodedContents, history, stat, path }; + } + + // Encodes the game music + gulp.task("sounds.music", () => { + return gulp + .src([path.join(soundsDir, "music", "**", "*.wav"), path.join(soundsDir, "music", "**", "*.mp3")]) + .pipe($.plumber()) + .pipe( + $.cache( + $.fluentFfmpeg("mp3", function (cmd) { + return cmd + .audioBitrate(48) + .audioChannels(1) + .audioFrequency(22050) + .audioCodec("libmp3lame") + .audioFilters(["volume=0.15"]); + }), + { + name: "music", + fileCache, + value: getFileCacheValue, + } + ) + ) + .pipe(gulp.dest(path.join(builtSoundsDir, "music"))); + }); + + // Encodes the game music in high quality for the standalone + gulp.task("sounds.musicHQ", () => { + return gulp + .src([path.join(soundsDir, "music", "**", "*.wav"), path.join(soundsDir, "music", "**", "*.mp3")]) + .pipe($.plumber()) + .pipe( + $.cache( + $.fluentFfmpeg("mp3", function (cmd) { + return cmd + .audioBitrate(256) + .audioChannels(2) + .audioFrequency(44100) + .audioCodec("libmp3lame") + .audioFilters(["volume=0.15"]); + }), + { + name: "music-high-quality", + fileCache, + value: getFileCacheValue, + } + ) + ) + .pipe(gulp.dest(path.join(builtSoundsDir, "music"))); + }); + + // Encodes the ui sounds + gulp.task("sounds.sfxGenerateSprites", () => { + return gulp + .src([path.join(soundsDir, "sfx", "**", "*.wav"), path.join(soundsDir, "sfx", "**", "*.mp3")]) + .pipe($.plumber()) + .pipe( + audiosprite({ + format: "howler", + output: "sfx", + gap: 0.1, + export: "mp3", + }) + ) + .pipe(gulp.dest(path.join(builtSoundsDir))); + }); + gulp.task("sounds.sfxOptimize", () => { + return gulp + .src([path.join(builtSoundsDir, "sfx.mp3")]) + .pipe($.plumber()) + .pipe( + $.fluentFfmpeg("mp3", function (cmd) { + return cmd + .audioBitrate(128) + .audioChannels(1) + .audioFrequency(22050) + .audioCodec("libmp3lame") + .audioFilters(filters); + }) + ) + .pipe(gulp.dest(path.join(builtSoundsDir))); + }); + gulp.task("sounds.sfxCopyAtlas", () => { + return gulp + .src([path.join(builtSoundsDir, "sfx.json")]) + .pipe(gulp.dest(path.join(__dirname, "..", "src", "js", "built-temp"))); + }); + + gulp.task( + "sounds.sfx", + gulp.series("sounds.sfxGenerateSprites", "sounds.sfxOptimize", "sounds.sfxCopyAtlas") + ); + + gulp.task("sounds.copy", () => { + return gulp + .src(path.join(builtSoundsDir, "**", "*.mp3")) + .pipe($.plumber()) + .pipe(gulp.dest(path.join(buildFolder, "res", "sounds"))); + }); + + gulp.task("sounds.buildall", gulp.parallel("sounds.music", "sounds.sfx")); + gulp.task("sounds.buildallHQ", gulp.parallel("sounds.musicHQ", "sounds.sfx")); + + gulp.task("sounds.fullbuild", gulp.series("sounds.clear", "sounds.buildall", "sounds.copy")); + gulp.task("sounds.fullbuildHQ", gulp.series("sounds.clear", "sounds.buildallHQ", "sounds.copy")); + gulp.task("sounds.dev", gulp.series("sounds.buildall", "sounds.copy")); +} + +module.exports = { + gulptasksSounds, +}; diff --git a/shapez-io/gulp/standalone.js b/shapez-io/gulp/standalone.js new file mode 100644 index 00000000..b70ef04c --- /dev/null +++ b/shapez-io/gulp/standalone.js @@ -0,0 +1,339 @@ +require("colors"); +const packager = require("electron-packager"); +const pj = require("../electron/package.json"); +const path = require("path"); +const { getVersion } = require("./buildutils"); +const fs = require("fs"); +const fse = require("fs-extra"); +const buildutils = require("./buildutils"); +const execSync = require("child_process").execSync; +const electronNotarize = require("electron-notarize"); +const { BUILD_VARIANTS } = require("./build_variants"); + +let signAsync; +try { + signAsync = require("tobspr-osx-sign").signAsync; +} catch (ex) { + console.warn("tobspr-osx-sign not installed, can not create osx builds"); +} + +function gulptasksStandalone($, gulp) { + for (const variant in BUILD_VARIANTS) { + const variantData = BUILD_VARIANTS[variant]; + if (!variantData.standalone) { + continue; + } + const tempDestDir = path.join(__dirname, "..", "build_output", variant); + const taskPrefix = "standalone." + variant; + const electronBaseDir = path.join(__dirname, "..", variantData.electronBaseDir || "electron"); + const tempDestBuildDir = path.join(tempDestDir, "built"); + + gulp.task(taskPrefix + ".prepare.cleanup", () => { + return gulp.src(tempDestDir, { read: false, allowEmpty: true }).pipe($.clean({ force: true })); + }); + + gulp.task(taskPrefix + ".prepare.copyPrefab", () => { + const requiredFiles = [ + path.join(electronBaseDir, "node_modules", "**", "*.*"), + path.join(electronBaseDir, "node_modules", "**", ".*"), + path.join(electronBaseDir, "wegame_sdk", "**", "*.*"), + path.join(electronBaseDir, "wegame_sdk", "**", ".*"), + path.join(electronBaseDir, "favicon*"), + ]; + return gulp.src(requiredFiles, { base: electronBaseDir }).pipe(gulp.dest(tempDestBuildDir)); + }); + + gulp.task(taskPrefix + ".prepare.writeAppId", cb => { + if (variantData.steamAppId) { + fs.writeFileSync( + path.join(tempDestBuildDir, "steam_appid.txt"), + String(variantData.steamAppId) + ); + } + cb(); + }); + + gulp.task(taskPrefix + ".prepare.writePackageJson", cb => { + const packageJsonString = JSON.stringify( + { + scripts: { + start: pj.scripts.start, + }, + devDependencies: pj.devDependencies, + dependencies: pj.dependencies, + optionalDependencies: pj.optionalDependencies, + }, + null, + 4 + ); + + fs.writeFileSync(path.join(tempDestBuildDir, "package.json"), packageJsonString); + + cb(); + }); + + gulp.task(taskPrefix + ".prepare.minifyCode", () => { + return gulp.src(path.join(electronBaseDir, "*.js")).pipe(gulp.dest(tempDestBuildDir)); + }); + + gulp.task(taskPrefix + ".prepare.copyGamefiles", () => { + return gulp.src("../build/**/*.*", { base: "../build" }).pipe(gulp.dest(tempDestBuildDir)); + }); + + gulp.task(taskPrefix + ".killRunningInstances", cb => { + try { + execSync("taskkill /F /IM shapezio.exe"); + } catch (ex) { + console.warn("Failed to kill running instances, maybe none are up."); + } + cb(); + }); + + gulp.task( + taskPrefix + ".prepare", + gulp.series( + taskPrefix + ".killRunningInstances", + taskPrefix + ".prepare.cleanup", + taskPrefix + ".prepare.copyPrefab", + taskPrefix + ".prepare.writePackageJson", + taskPrefix + ".prepare.minifyCode", + taskPrefix + ".prepare.copyGamefiles", + taskPrefix + ".prepare.writeAppId" + ) + ); + + /** + * + * @param {'win32'|'linux'|'darwin'} platform + * @param {'x64'|'ia32'} arch + * @param {function():void} cb + */ + function packageStandalone(platform, arch, cb, isRelease = true) { + const privateArtifactsPath = "node_modules/shapez.io-private-artifacts"; + + // Only use asar on steam builds (not supported by wegame) + let asar = Boolean(variantData.steamAppId); + + // Unpack private artifacts though + if (asar && fs.existsSync(path.join(tempDestBuildDir, privateArtifactsPath))) { + // @ts-expect-error + asar = { unpackDir: privateArtifactsPath }; + } + + packager({ + dir: tempDestBuildDir, + appCopyright: "tobspr Games", + appVersion: getVersion(), + buildVersion: "1.0.0", + arch, + platform, + asar: asar, + executableName: "shapezio", + icon: path.join(electronBaseDir, "favicon"), + name: "shapez", + out: tempDestDir, + overwrite: true, + appBundleId: "tobspr.shapezio." + variant, + appCategoryType: "public.app-category.games", + ...(isRelease && + platform === "darwin" && { + osxSign: { + "identity": process.env.SHAPEZ_CLI_APPLE_CERT_NAME, + "hardenedRuntime": true, + "entitlements": "entitlements.plist", + "entitlements-inherit": "entitlements.plist", + // @ts-ignore + "signatureFlags": ["library"], + "version": "16.0.7", + }, + osxNotarize: { + appleId: process.env.SHAPEZ_CLI_APPLE_ID, + appleIdPassword: process.env.SHAPEZ_CLI_APPLE_APP_PW, + }, + }), + }).then( + appPaths => { + console.log("Packages created:", appPaths); + appPaths.forEach(appPath => { + if (!fs.existsSync(appPath)) { + console.error("Bad app path:", appPath); + return; + } + + if (variantData.steamAppId) { + fs.writeFileSync( + path.join(appPath, "LICENSE"), + fs.readFileSync(path.join(__dirname, "..", "LICENSE")) + ); + + fs.writeFileSync( + path.join(appPath, "steam_appid.txt"), + String(variantData.steamAppId) + ); + + if (platform === "linux") { + // Write launcher script + fs.writeFileSync( + path.join(appPath, "play.sh"), + '#!/usr/bin/env bash\n./shapezio --no-sandbox "$@"\n' + ); + fs.chmodSync(path.join(appPath, "play.sh"), 0o775); + } + + if (platform === "darwin") { + if (!isRelease) { + // Needs special location + fs.writeFileSync( + path.join( + appPath, + "shapez.app", + "Contents", + "MacOS", + "steam_appid.txt" + ), + String(variantData.steamAppId) + ); + } + } + } + }); + + cb(); + }, + err => { + console.error("Packaging error:", err); + cb(); + } + ); + } + + // Manual signing with patched @electron/osx-sign (we need --no-strict) + gulp.task(taskPrefix + ".package.darwin64", cb => + packageStandalone( + "darwin", + "x64", + () => { + const appFile = path.join(tempDestDir, "shapez-darwin-x64"); + const appFileInner = path.join(appFile, "shapez.app"); + console.warn("++ Signing ++"); + + if (variantData.steamAppId) { + const appIdDest = path.join( + path.join(appFileInner, "Contents", "MacOS"), + "steam_appid.txt" + ); + // console.warn("++ Preparing ++"); + // fse.copySync(path.join(tempDestBuildDir, "steam_appid.txt"), appIdDest); + + console.warn("Signing steam_appid.txt"); + + execSync( + `codesign --force --verbose --options runtime --timestamp --no-strict --sign "${ + process.env.SHAPEZ_CLI_APPLE_CERT_NAME + }" --entitlements "${path.join(__dirname, "entitlements.plist")}" ${appIdDest}`, + { + cwd: appFile, + } + ); + } + + console.warn("Base dir:", appFile); + + signAsync({ + app: appFileInner, + hardenedRuntime: true, + identity: process.env.SHAPEZ_CLI_APPLE_CERT_NAME, + strictVerify: false, + + version: "16.0.7", + type: "distribution", + optionsForFile: f => { + return { + entitlements: path.join(__dirname, "entitlements.plist"), + hardenedRuntime: true, + signatureFlags: ["runtime"], + }; + }, + }).then(() => { + execSync(`codesign --verify --verbose ${path.join(appFile, "shapez.app")}`, { + cwd: appFile, + }); + + console.warn("++ Notarizing ++"); + electronNotarize + .notarize({ + appPath: path.join(appFile, "shapez.app"), + tool: "legacy", + appBundleId: "tobspr.shapezio.standalone", + + appleId: process.env.SHAPEZ_CLI_APPLE_ID, + appleIdPassword: process.env.SHAPEZ_CLI_APPLE_APP_PW, + teamId: process.env.SHAPEZ_CLI_APPLE_TEAM_ID, + }) + .then(() => { + console.warn("-> Notarized!"); + cb(); + }); + }); + }, + false + ) + ); + + gulp.task(taskPrefix + ".package.win64", cb => packageStandalone("win32", "x64", cb)); + gulp.task(taskPrefix + ".package.linux64", cb => packageStandalone("linux", "x64", cb)); + gulp.task( + taskPrefix + ".build-from-windows", + gulp.series( + taskPrefix + ".prepare", + gulp.parallel(taskPrefix + ".package.win64", taskPrefix + ".package.linux64") + ) + ); + gulp.task( + taskPrefix + ".build-from-darwin", + gulp.series(taskPrefix + ".prepare", gulp.parallel(taskPrefix + ".package.darwin64")) + ); + } + + // Steam helpers + gulp.task("standalone.prepareVDF", cb => { + const hash = buildutils.getRevision(); + const version = buildutils.getVersion(); + + // for (const platform of ["steampipe", "steampipe-darwin"]) { + const templatesSource = path.join(__dirname, "steampipe", "templates"); + const templatesDest = path.join(__dirname, "steampipe", "built_vdfs"); + + const variables = { + PROJECT_DIR: path.resolve(path.join(__dirname, "..")).replace(/\\/g, "/"), + BUNDLE_DIR: path.resolve(path.join(__dirname, "..", "build_output")).replace(/\\/g, "/"), + + TMP_DIR: path.resolve(path.join(__dirname, "steampipe", "tmp")).replace(/\\/g, "/"), + // BUILD_DESC: "v" + version + " @ " + hash, + VDF_DIR: path.resolve(path.join(__dirname, "steampipe", "built_vdfs")).replace(/\\/g, "/"), + }; + + const files = fs.readdirSync(templatesSource); + for (const file of files) { + if (!file.endsWith(".vdf")) { + continue; + } + + variables.BUILD_DESC = file.replace(".vdf", "") + " - v" + version + " @ " + hash; + + let content = fs.readFileSync(path.join(templatesSource, file)).toString("utf-8"); + content = content.replace(/\$([^$]+)\$/gi, (_, variable) => { + if (!variables[variable]) { + throw new Error("Unknown variable " + variable + " in " + file); + } + + return variables[variable]; + }); + + fs.writeFileSync(path.join(templatesDest, file), content, { encoding: "utf8" }); + } + cb(); + }); +} + +module.exports = { gulptasksStandalone }; diff --git a/shapez-io/gulp/translations.js b/shapez-io/gulp/translations.js new file mode 100644 index 00000000..88afa989 --- /dev/null +++ b/shapez-io/gulp/translations.js @@ -0,0 +1,64 @@ +const path = require("path"); +const fs = require("fs"); +const gulpYaml = require("gulp-yaml"); +const YAML = require("yaml"); +const stripIndent = require("strip-indent"); +const trim = require("trim"); + +const translationsSourceDir = path.join(__dirname, "..", "translations"); +const translationsJsonDir = path.join(__dirname, "..", "src", "js", "built-temp"); + +function gulptasksTranslations($, gulp) { + gulp.task("translations.convertToJson", () => { + return gulp + .src(path.join(translationsSourceDir, "*.yaml")) + .pipe($.plumber()) + .pipe(gulpYaml({ space: 2, safe: true })) + .pipe(gulp.dest(translationsJsonDir)); + }); + + gulp.task("translations.fullBuild", gulp.series("translations.convertToJson")); + + gulp.task("translations.prepareSteamPage", cb => { + const files = fs.readdirSync(translationsSourceDir); + + files + .filter(name => name.endsWith(".yaml")) + .forEach(fname => { + console.log("Loading", fname); + const languageName = fname.replace(".yaml", ""); + const abspath = path.join(translationsSourceDir, fname); + + const destpath = path.join(translationsSourceDir, "tmp", languageName + "-store.txt"); + + const contents = fs.readFileSync(abspath, { encoding: "utf-8" }); + const data = YAML.parse(contents); + + const storePage = data.steamPage; + + const content = ` + [img]{STEAM_APP_IMAGE}/extras/store_page_gif.gif[/img] + + ${storePage.intro.replace(/\n/gi, "\n\n")} + + [h2]${storePage.what_others_say}[/h2] + + [list] + [*] [i]${storePage.nothernlion_comment}[/i] [b]- Northernlion, YouTube[/b] + [*] [i]${storePage.notch_comment}[/i] [b]- Notch[/b] + [*] [i]${storePage.steam_review_comment}[/i] [b]- Steam User[/b] + [/list] + `; + + fs.writeFileSync(destpath, trim(content.replace(/(\n[ \t\r]*)/gi, "\n")), { + encoding: "utf-8", + }); + }); + + cb(); + }); +} + +module.exports = { + gulptasksTranslations, +};