diff --git a/.gitignore b/.gitignore index 3f7d0ba99..ac21c4c4c 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,6 @@ Data/flashpoint.sqlite /.coveralls.yml /coverage /tests/result + +# nano's swap/lock files. +*.swp diff --git a/jest.config.ts b/jest.config.ts index 03ca46b9d..f185eb72a 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -12,6 +12,7 @@ const config: Config.InitialOptions = { '^@main(.*)$': '/src/main/$1', '^@renderer(.*)$': '/src/renderer/$1', '^@back(.*)$': '/src/back/$1', + '^@database(.*)$': '/src/database/$1', '^@tests(.*)$': '/tests/$1' }, testPathIgnorePatterns: [ diff --git a/lang/en.json b/lang/en.json index 30d6fd331..2dad855cd 100644 --- a/lang/en.json +++ b/lang/en.json @@ -301,7 +301,13 @@ "noGameMatchedSearch": "Try searching for something less restrictive.", "mountParameters": "Mount Parameters", "noMountParameters": "No Mount Parameters", - "showExtremeScreenshot": "Show Extreme Screenshot" + "showExtremeScreenshot": "Show Extreme Screenshot", + "extras": "Extras", + "noExtras": "No Extras", + "message": "Launch Message", + "noMessage": "No Message", + "extrasName": "Extras Name", + "noExtrasName": "No Extras Name" }, "tags": { "name": "Name", @@ -390,7 +396,10 @@ "noScreenshot": "There is no screenshot on this curation.", "ilc_notHttp": "Use HTTP.", "ilc_nonExistant": "Point to an existing file in your curation's 'content' folder.", - "sort": "Sort Curations (A-Z)" + "sort": "Sort Curations (A-Z)", + "parentGameId": "Parent Game ID", + "noParentGameId": "No Parent Game ID", + "invalidParentGameId": "Invalid Parent Game ID!" }, "playlist": { "enterDescriptionHere": "Enter a description here...", diff --git a/package-lock.json b/package-lock.json index 3f33867f2..56bfd0c48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "redux-devtools-extension": "2.13.8", "reflect-metadata": "0.1.10", "remark-gfm": "^2.0.0", - "sqlite3": "4.2.0", + "sqlite3": "^5.0.2", "tail": "2.0.3", "typeorm": "0.2.37", "typesafe-actions": "4.4.2", @@ -49,10 +49,10 @@ "@types/electron-devtools-installer": "2.2.0", "@types/follow-redirects": "1.8.0", "@types/fs-extra": "8.1.0", - "@types/jest": "^27.5.1", + "@types/jest": "27.5.1", "@types/mime": "2.0.1", "@types/node": "14.14.31", - "@types/ps-tree": "^1.1.2", + "@types/ps-tree": "1.1.2", "@types/react": "16.9.34", "@types/react-color": "3.0.1", "@types/react-dom": "16.9.7", @@ -75,8 +75,8 @@ "eslint-plugin-only-warn": "1.0.2", "eslint-plugin-react": "7.20.0", "gulp": "4.0.2", - "jest": "^28.1.0", - "ts-jest": "^28.0.3", + "jest": "28.1.0", + "ts-jest": "28.0.3", "ts-loader": "9.3.0", "ts-transform-paths": "2.0.1", "tsconfig-paths-webpack-plugin": "3.2.0", @@ -779,6 +779,12 @@ "react": ">=16.x" } }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "optional": true + }, "node_modules/@icons/material": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", @@ -1108,21 +1114,6 @@ "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", "dev": true }, - "node_modules/@jest/core/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@jest/core/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -1870,6 +1861,60 @@ "node": ">= 10.0.0" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz", + "integrity": "sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/move-file/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@sinclair/typebox": { "version": "0.23.5", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.5.tgz", @@ -2719,7 +2764,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, "dependencies": { "debug": "4" }, @@ -2727,6 +2771,33 @@ "node": ">= 6.0.0" } }, + "node_modules/agentkeepalive": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", + "optional": true, + "dependencies": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "optional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3061,9 +3132,9 @@ } }, "node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" }, "node_modules/archy": { "version": "1.0.0", @@ -3072,12 +3143,55 @@ "dev": true }, "node_modules/are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", "dependencies": { "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/are-we-there-yet/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/are-we-there-yet/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" } }, "node_modules/arg": { @@ -4331,6 +4445,47 @@ "node": ">=0.10.0" } }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cacache/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -4560,9 +4715,12 @@ } }, "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } }, "node_modules/chrome-trace-event": { "version": "1.0.3", @@ -4618,6 +4776,15 @@ "node": ">=0.10.0" } }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/cli-boxes": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", @@ -5133,7 +5300,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, "bin": { "color-support": "bin.js" } @@ -5525,9 +5691,9 @@ } }, "node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { "ms": "2.1.2" }, @@ -5584,6 +5750,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, "engines": { "node": ">=4.0.0" } @@ -5715,6 +5882,15 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", @@ -5725,14 +5901,11 @@ } }, "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", "engines": { - "node": ">=0.10" + "node": ">=8" } }, "node_modules/detect-newline": { @@ -6188,21 +6361,6 @@ "unzip-crx-3": "^0.2.0" } }, - "node_modules/electron-devtools-installer/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/electron-is-dev": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-1.2.0.tgz", @@ -6464,6 +6622,27 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -6506,6 +6685,12 @@ "node": ">=4" } }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "optional": true + }, "node_modules/errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -7961,11 +8146,14 @@ } }, "node_modules/fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dependencies": { - "minipass": "^2.6.0" + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" } }, "node_modules/fs-mkdirp-stream": { @@ -8013,18 +8201,62 @@ "dev": true }, "node_modules/gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", "dependencies": { - "aproba": "^1.0.3", + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/gensync": { @@ -8733,7 +8965,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -8751,6 +8982,15 @@ "node": ">=10.17.0" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/iconv-corefoundation": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", @@ -8772,6 +9012,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -8807,14 +9048,6 @@ "node": ">= 4" } }, - "node_modules/ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "dependencies": { - "minimatch": "^3.0.4" - } - }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -8866,11 +9099,26 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -8888,7 +9136,8 @@ "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "devOptional": true }, "node_modules/inline-style-parser": { "version": "0.1.1", @@ -9066,6 +9315,12 @@ "node": ">=0.10.0" } }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "optional": true + }, "node_modules/is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", @@ -9328,6 +9583,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", + "optional": true + }, "node_modules/is-negated-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", @@ -12468,11 +12729,6 @@ "node": ">=10" } }, - "node_modules/lru-cache/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/lunr": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", @@ -12483,7 +12739,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, "dependencies": { "semver": "^6.0.0" }, @@ -12498,7 +12753,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -12509,6 +12763,56 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -13440,6 +13744,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -13453,39 +13758,91 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "node_modules/minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", + "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", "dependencies": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/minipass/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } }, "node_modules/minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dependencies": { - "minipass": "^2.9.0" + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" } }, "node_modules/mixin-deep": { @@ -13557,7 +13914,9 @@ "node_modules/nan": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", + "dev": true, + "optional": true }, "node_modules/nanomatch": { "version": "1.2.13", @@ -13587,36 +13946,15 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "node_modules/needle": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz", - "integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==", - "dependencies": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "bin": { - "needle": "bin/needle" - }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "optional": true, "engines": { - "node": ">= 4.4.x" - } - }, - "node_modules/needle/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dependencies": { - "ms": "^2.1.1" + "node": ">= 0.6" } }, - "node_modules/needle/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -13661,41 +13999,223 @@ "dev": true, "optional": true }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } }, - "node_modules/node-pre-gyp": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", - "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", - "deprecated": "Please upgrade to @mapbox/node-pre-gyp: the non-scoped node-pre-gyp package is deprecated and only the @mapbox scoped package will recieve updates in the future", + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", "dependencies": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" }, "bin": { - "node-pre-gyp": "bin/node-pre-gyp" + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" } }, - "node_modules/node-pre-gyp/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "node_modules/node-gyp/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-gyp/node_modules/are-we-there-yet": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz", + "integrity": "sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw==", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/node-gyp/node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-gyp/node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/node-gyp/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/node-gyp/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/node-gyp/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-gyp/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, "bin": { - "semver": "bin/semver" + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, "node_modules/node-releases": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", @@ -13703,15 +14223,17 @@ "dev": true }, "node_modules/nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1" }, "bin": { "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" } }, "node_modules/normalize-package-data": { @@ -13769,14 +14291,6 @@ "node": ">= 0.10" } }, - "node_modules/npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "dependencies": { - "npm-normalize-package-bin": "^1.0.1" - } - }, "node_modules/npm-conf": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", @@ -13790,21 +14304,6 @@ "node": ">=4" } }, - "node_modules/npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" - }, - "node_modules/npm-packlist": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", - "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", - "dependencies": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -13827,14 +14326,14 @@ } }, "node_modules/npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", "dependencies": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" } }, "node_modules/number-is-nan": { @@ -14097,14 +14596,6 @@ "readable-stream": "^2.0.1" } }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -14121,19 +14612,11 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, "node_modules/p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", @@ -14169,6 +14652,21 @@ "node": ">=8" } }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -14609,6 +15107,25 @@ "node": ">=0.4.0" } }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -14746,6 +15263,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -15459,15 +15977,27 @@ "node": ">=0.12" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "optional": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/roarr": { @@ -15531,7 +16061,8 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "devOptional": true }, "node_modules/sanitize-filename": { "version": "1.6.3", @@ -15832,7 +16363,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, "optional": true, "engines": { "node": ">= 6.0.0", @@ -15988,6 +16518,34 @@ "node": ">=0.10.0" } }, + "node_modules/socks": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", + "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", + "optional": true, + "dependencies": { + "ip": "^1.1.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.0.tgz", + "integrity": "sha512-wWqJhjb32Q6GsrUqzuFkukxb/zzide5quXYcMVpIjxalDBBYy2nqKCFQ/9+Ie4dvOYSQdOk3hUlZSdzZOd3zMQ==", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -16107,15 +16665,32 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "node_modules/sqlite3": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.2.0.tgz", - "integrity": "sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.0.8.tgz", + "integrity": "sha512-f2ACsbSyb2D1qFFcqIXPfFscLtPVOWJr5GmUzYxf4W+0qelu5MWrR+FAQE1d5IUArEltBrzSDxDORG8P/IkqyQ==", "hasInstallScript": true, "dependencies": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.11.0" + "@mapbox/node-pre-gyp": "^1.0.0", + "node-addon-api": "^4.2.0", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } } }, + "node_modules/sqlite3/node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" + }, "node_modules/sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -16141,6 +16716,18 @@ "node": ">=0.10.0" } }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -16359,6 +16946,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -16541,40 +17129,31 @@ } }, "node_modules/tar": { - "version": "4.4.19", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", - "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", - "dependencies": { - "chownr": "^1.1.4", - "fs-minipass": "^1.2.7", - "minipass": "^2.9.0", - "minizlib": "^1.3.3", - "mkdirp": "^0.5.5", - "safe-buffer": "^5.2.1", - "yallist": "^3.1.1" + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" }, "engines": { - "node": ">=4.5" + "node": ">= 10" } }, - "node_modules/tar/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } }, "node_modules/temp-file": { "version": "3.4.0", @@ -16849,21 +17428,6 @@ "tmp": "^0.2.0" } }, - "node_modules/tmp-promise/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/tmp-promise/node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -17873,6 +18437,24 @@ "node": ">=0.10.0" } }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, "node_modules/unique-stream": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", @@ -18672,11 +19254,11 @@ } }, "node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", "dependencies": { - "string-width": "^1.0.2 || 2" + "string-width": "^1.0.2 || 2 || 3 || 4" } }, "node_modules/widest-line": { @@ -18947,9 +19529,9 @@ "dev": true }, "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { "version": "2.1.0", @@ -19636,6 +20218,12 @@ "prop-types": "^15.7.2" } }, + "@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "optional": true + }, "@icons/material": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", @@ -19883,15 +20471,6 @@ "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", "dev": true }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -20455,6 +21034,50 @@ } } }, + "@mapbox/node-pre-gyp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz", + "integrity": "sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==", + "requires": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + } + }, + "@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "optional": true, + "requires": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "optional": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true + } + } + }, "@sinclair/typebox": { "version": "0.23.5", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.5.tgz", @@ -21220,11 +21843,31 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, "requires": { "debug": "4" } }, + "agentkeepalive": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", + "optional": true, + "requires": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "optional": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -21492,9 +22135,9 @@ } }, "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" }, "archy": { "version": "1.0.0", @@ -21503,12 +22146,37 @@ "dev": true }, "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", "requires": { "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + } } }, "arg": { @@ -22456,6 +23124,40 @@ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, + "cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "optional": true, + "requires": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true + } + } + }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -22624,9 +23326,9 @@ } }, "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" }, "chrome-trace-event": { "version": "1.0.3", @@ -22675,6 +23377,12 @@ } } }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "optional": true + }, "cli-boxes": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", @@ -23072,8 +23780,7 @@ "color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" }, "colorette": { "version": "2.0.16", @@ -23367,9 +24074,9 @@ } }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { "ms": "2.1.2" }, @@ -23410,7 +24117,8 @@ "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true }, "deep-is": { "version": "0.1.3", @@ -23513,6 +24221,12 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "optional": true + }, "detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", @@ -23520,9 +24234,9 @@ "dev": true }, "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" }, "detect-newline": { "version": "3.1.0", @@ -23877,17 +24591,6 @@ "rimraf": "^3.0.2", "semver": "^7.2.1", "unzip-crx-3": "^0.2.0" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } } }, "electron-is-dev": { @@ -24096,6 +24799,26 @@ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", "optional": true }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -24126,6 +24849,12 @@ "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true }, + "err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "optional": true + }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -25272,11 +26001,11 @@ } }, "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "requires": { - "minipass": "^2.6.0" + "minipass": "^3.0.0" } }, "fs-mkdirp-stream": { @@ -25313,18 +26042,49 @@ "dev": true }, "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", "requires": { - "aproba": "^1.0.3", + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, "gensync": { @@ -25896,7 +26656,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, "requires": { "agent-base": "6", "debug": "4" @@ -25908,6 +26667,15 @@ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "optional": true, + "requires": { + "ms": "^2.0.0" + } + }, "iconv-corefoundation": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", @@ -25923,6 +26691,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -25938,14 +26707,6 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, - "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "requires": { - "minimatch": "^3.0.4" - } - }, "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -25982,7 +26743,19 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "devOptional": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "optional": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true }, "inflight": { "version": "1.0.6", @@ -26001,7 +26774,8 @@ "ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "devOptional": true }, "inline-style-parser": { "version": "0.1.1", @@ -26136,6 +26910,12 @@ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", "dev": true }, + "ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "optional": true + }, "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", @@ -26325,6 +27105,12 @@ "is-path-inside": "^3.0.2" } }, + "is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", + "optional": true + }, "is-negated-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", @@ -28681,13 +29467,6 @@ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "requires": { "yallist": "^4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } } }, "lunr": { @@ -28700,7 +29479,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, "requires": { "semver": "^6.0.0" }, @@ -28708,8 +29486,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -28719,6 +29496,49 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "optional": true, + "requires": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "dependencies": { + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "optional": true + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "optional": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + } + } + }, "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -29315,6 +30135,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -29325,27 +30146,68 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", + "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } + "yallist": "^4.0.0" + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "optional": true, + "requires": { + "encoding": "^0.1.12", + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "optional": true, + "requires": { + "minipass": "^3.0.0" } }, "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "requires": { - "minipass": "^2.9.0" + "minipass": "^3.0.0", + "yallist": "^4.0.0" } }, "mixin-deep": { @@ -29407,7 +30269,9 @@ "nan": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", + "dev": true, + "optional": true }, "nanomatch": { "version": "1.2.13", @@ -29434,30 +30298,11 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "needle": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz", - "integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==", - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "optional": true }, "neo-async": { "version": "2.6.2", @@ -29500,36 +30345,166 @@ "dev": true, "optional": true }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } }, - "node-pre-gyp": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", - "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", + "node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "optional": true, "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" }, "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true + }, + "are-we-there-yet": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz", + "integrity": "sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw==", + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "optional": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "optional": true + }, + "npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "optional": true, + "requires": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "optional": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "optional": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "optional": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "optional": true, + "requires": { + "isexe": "^2.0.0" + } } } }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, "node-releases": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", @@ -29537,12 +30512,11 @@ "dev": true }, "nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1" } }, "normalize-package-data": { @@ -29590,14 +30564,6 @@ "once": "^1.3.2" } }, - "npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, "npm-conf": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", @@ -29608,21 +30574,6 @@ "pify": "^3.0.0" } }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" - }, - "npm-packlist": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", - "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -29641,14 +30592,14 @@ } }, "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" } }, "number-is-nan": { @@ -29847,11 +30798,6 @@ "readable-stream": "^2.0.1" } }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, "os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -29864,16 +30810,8 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true }, "p-cancelable": { "version": "1.1.0", @@ -29898,6 +30836,15 @@ "p-limit": "^2.2.0" } }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "optional": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -30241,6 +31188,22 @@ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "optional": true + }, + "promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "optional": true, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, "prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -30358,6 +31321,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -30909,10 +31873,16 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "optional": true + }, "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "requires": { "glob": "^7.1.3" } @@ -30971,7 +31941,8 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "devOptional": true }, "sanitize-filename": { "version": "1.6.3", @@ -31213,7 +32184,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, "optional": true }, "snapdragon": { @@ -31338,6 +32308,27 @@ } } }, + "socks": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", + "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", + "optional": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.0.tgz", + "integrity": "sha512-wWqJhjb32Q6GsrUqzuFkukxb/zzide5quXYcMVpIjxalDBBYy2nqKCFQ/9+Ie4dvOYSQdOk3hUlZSdzZOd3zMQ==", + "optional": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -31439,12 +32430,21 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sqlite3": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.2.0.tgz", - "integrity": "sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.0.8.tgz", + "integrity": "sha512-f2ACsbSyb2D1qFFcqIXPfFscLtPVOWJr5GmUzYxf4W+0qelu5MWrR+FAQE1d5IUArEltBrzSDxDORG8P/IkqyQ==", "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.11.0" + "@mapbox/node-pre-gyp": "^1.0.0", + "node-addon-api": "^4.2.0", + "node-gyp": "8.x", + "tar": "^6.1.11" + }, + "dependencies": { + "node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" + } } }, "sshpk": { @@ -31464,6 +32464,15 @@ "tweetnacl": "~0.14.0" } }, + "ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "optional": true, + "requires": { + "minipass": "^3.1.1" + } + }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -31639,7 +32648,8 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true }, "style-to-object": { "version": "0.3.0", @@ -31778,23 +32788,22 @@ "dev": true }, "tar": { - "version": "4.4.19", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", - "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", - "requires": { - "chownr": "^1.1.4", - "fs-minipass": "^1.2.7", - "minipass": "^2.9.0", - "minizlib": "^1.3.3", - "mkdirp": "^0.5.5", - "safe-buffer": "^5.2.1", - "yallist": "^3.1.1" + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" }, "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" } } }, @@ -32014,15 +33023,6 @@ "tmp": "^0.2.0" }, "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -32703,6 +33703,24 @@ "set-value": "^2.0.1" } }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "optional": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "optional": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, "unique-stream": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", @@ -33304,11 +34322,11 @@ } }, "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", "requires": { - "string-width": "^1.0.2 || 2" + "string-width": "^1.0.2 || 2 || 3 || 4" } }, "widest-line": { @@ -33506,9 +34524,9 @@ "dev": true }, "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yaml": { "version": "2.1.0", diff --git a/package.json b/package.json index bcb4b809c..92bc9c25a 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "redux-devtools-extension": "2.13.8", "reflect-metadata": "0.1.10", "remark-gfm": "^2.0.0", - "sqlite3": "4.2.0", + "sqlite3": "^5.0.2", "tail": "2.0.3", "typeorm": "0.2.37", "typesafe-actions": "4.4.2", diff --git a/src/back/GameLauncher.ts b/src/back/GameLauncher.ts index bcebb81ea..32e84ade8 100644 --- a/src/back/GameLauncher.ts +++ b/src/back/GameLauncher.ts @@ -1,12 +1,9 @@ -import { AdditionalApp } from '@database/entity/AdditionalApp'; import { Game } from '@database/entity/Game'; import { AppProvider } from '@shared/extensions/interfaces'; -import { ExecMapping, Omit } from '@shared/interfaces'; +import { ExecMapping } from '@shared/interfaces'; import { LangContainer } from '@shared/lang'; -import { fixSlashes, padStart, stringifyArray } from '@shared/Util'; +import { fixSlashes } from '@shared/Util'; import { Coerce } from '@shared/utils/Coerce'; -import { ChildProcess, exec } from 'child_process'; -import { EventEmitter } from 'events'; import { AppPathOverride, GameData, ManagedChildProcess } from 'flashpoint-launcher'; import * as path from 'path'; import { ApiEmitter } from './extensions/ApiEmitter'; @@ -16,9 +13,8 @@ import * as GameDataManager from '@back/game/GameDataManager'; const { str } = Coerce; -export type LaunchAddAppOpts = LaunchBaseOpts & { - addApp: AdditionalApp; - native: boolean; +export type LaunchExtrasOpts = LaunchBaseOpts & { + extrasPath: string; } export type LaunchGameOpts = LaunchBaseOpts & { @@ -59,60 +55,20 @@ type LaunchBaseOpts = { export namespace GameLauncher { const logSource = 'Game Launcher'; - export function launchAdditionalApplication(opts: LaunchAddAppOpts): Promise { - // @FIXTHIS It is not possible to open dialog windows from the back process (all electron APIs are undefined). - switch (opts.addApp.applicationPath) { - case ':message:': - return new Promise((resolve, reject) => { - opts.openDialog({ - type: 'info', - title: 'About This Game', - message: opts.addApp.launchCommand, - buttons: ['Ok'], - }).finally(() => resolve()); - }); - case ':extras:': { - const folderPath = fixSlashes(path.join(opts.fpPath, path.posix.join('Extras', opts.addApp.launchCommand))); - return opts.openExternal(folderPath, { activate: true }) - .catch(error => { - if (error) { - opts.openDialog({ - type: 'error', - title: 'Failed to Open Extras', - message: `${error.toString()}\n`+ - `Path: ${folderPath}`, - buttons: ['Ok'], - }); - } + export async function launchExtras(opts: LaunchExtrasOpts): Promise { + const folderPath = fixSlashes(path.join(opts.fpPath, path.posix.join('Extras', opts.extrasPath))); + return opts.openExternal(`file://${folderPath}`, { activate: true }) + .catch(error => { + if (error) { + opts.openDialog({ + type: 'error', + title: 'Failed to Open Extras', + message: `${error.toString()}\n`+ + `Path: ${folderPath}`, + buttons: ['Ok'], }); } - default: { - let appPath: string = fixSlashes(path.join(opts.fpPath, getApplicationPath(opts.addApp.applicationPath, opts.execMappings, opts.native))); - const appPathOverride = opts.appPathOverrides.filter(a => a.enabled).find(a => a.path === appPath); - if (appPathOverride) { appPath = appPathOverride.override; } - const appArgs: string = opts.addApp.launchCommand; - const useWine: boolean = process.platform != 'win32' && appPath.endsWith('.exe'); - const launchInfo: LaunchInfo = { - gamePath: appPath, - gameArgs: appArgs, - useWine, - env: getEnvironment(opts.fpPath, opts.proxy), - }; - const proc = exec( - createCommand(launchInfo), - { env: launchInfo.env } - ); - logProcessOutput(proc); - log.info(logSource, `Launch Add-App "${opts.addApp.name}" (PID: ${proc.pid}) [ path: "${opts.addApp.applicationPath}", arg: "${opts.addApp.launchCommand}" ]`); - return new Promise((resolve, reject) => { - if (proc.killed) { resolve(); } - else { - proc.once('exit', () => { resolve(); }); - proc.once('error', error => { reject(error); }); - } - }); - } - } + }); } /** @@ -122,29 +78,12 @@ export namespace GameLauncher { export async function launchGame(opts: LaunchGameOpts, onWillEvent: ApiEmitter): Promise { // Abort if placeholder (placeholders are not "actual" games) if (opts.game.placeholder) { return; } - // Run all provided additional applications with "AutoRunBefore" enabled - if (opts.game.addApps) { - const addAppOpts: Omit = { - fpPath: opts.fpPath, - htdocsPath: opts.htdocsPath, - native: opts.native, - execMappings: opts.execMappings, - lang: opts.lang, - isDev: opts.isDev, - exePath: opts.exePath, - appPathOverrides: opts.appPathOverrides, - providers: opts.providers, - proxy: opts.proxy, - openDialog: opts.openDialog, - openExternal: opts.openExternal, - runGame: opts.runGame - }; - for (const addApp of opts.game.addApps) { - if (addApp.autoRunBefore) { - const promise = launchAdditionalApplication({ ...addAppOpts, addApp }); - if (addApp.waitForExit) { await promise; } - } - } + if (opts.game.message) { + await opts.openDialog({type: 'info', + title: 'About This Game', + message: opts.game.message, + buttons: ['Ok'], + }); } // Launch game let appPath: string = getApplicationPath(opts.game.applicationPath, opts.execMappings, opts.native); @@ -362,34 +301,6 @@ export namespace GameLauncher { throw Error('Unsupported platform'); } } - - function logProcessOutput(proc: ChildProcess): void { - // Log for debugging purposes - // (might be a bad idea to fill the console with junk?) - const logInfo = (event: string, args: any[]): void => { - log.info(logSource, `${event} (PID: ${padStart(proc.pid, 5)}) ${stringifyArray(args, stringifyArrayOpts)}`); - }; - const logErr = (event: string, args: any[]): void => { - log.error(logSource, `${event} (PID: ${padStart(proc.pid, 5)}) ${stringifyArray(args, stringifyArrayOpts)}`); - }; - registerEventListeners(proc, [/* 'close', */ 'disconnect', 'exit', 'message'], logInfo); - registerEventListeners(proc, ['error'], logErr); - if (proc.stdout) { proc.stdout.on('data', (data) => { logInfo('stdout', [data.toString('utf8')]); }); } - if (proc.stderr) { proc.stderr.on('data', (data) => { logErr('stderr', [data.toString('utf8')]); }); } - } -} - -const stringifyArrayOpts = { - trimStrings: true, -}; - -function registerEventListeners(emitter: EventEmitter, events: string[], callback: (event: string, args: any[]) => void): void { - for (let i = 0; i < events.length; i++) { - const e: string = events[i]; - emitter.on(e, (...args: any[]) => { - callback(e, args); - }); - } } /** diff --git a/src/back/MetaEdit.ts b/src/back/MetaEdit.ts index f07e4440f..5060822af 100644 --- a/src/back/MetaEdit.ts +++ b/src/back/MetaEdit.ts @@ -172,7 +172,7 @@ export async function importAllMetaEdits(fullMetaEditsFolderPath: string, openDi const game = await GameManager.findGame(id); if (game) { - games[id] = await GameManager.findGame(id); + games[id] = game; } else { // Game not found const combined = combinedMetas[id]; if (!combined) { throw new Error(`Failed to check for collisions. "combined meta" is missing (id: "${id}") (bug)`); } diff --git a/src/back/extensions/ApiImplementation.ts b/src/back/extensions/ApiImplementation.ts index 431f89e2b..196348af9 100644 --- a/src/back/extensions/ApiImplementation.ts +++ b/src/back/extensions/ApiImplementation.ts @@ -142,7 +142,7 @@ export function createApiFactory(extId: string, extManifest: IExtensionManifest, findGamesWithTag: GameManager.findGamesWithTag, updateGame: GameManager.save, updateGames: GameManager.updateGames, - removeGameAndAddApps: (gameId: string) => GameManager.removeGameAndAddApps(gameId, path.join(state.config.flashpointPath, state.preferences.dataPacksFolderPath)), + removeGameAndChildren: (gameId: string) => GameManager.removeGameAndChildren(gameId, path.join(state.config.flashpointPath, state.preferences.dataPacksFolderPath)), isGameExtreme: (game: Game) => { const extremeTags = state.preferences.tagFilters.filter(t => t.extreme).reduce((prev, cur) => prev.concat(cur.tags), []); return game.tagsStr.split(';').findIndex(t => extremeTags.includes(t.trim())) !== -1; @@ -156,27 +156,15 @@ export function createApiFactory(extId: string, extManifest: IExtensionManifest, get onWillLaunchGame() { return apiEmitters.games.onWillLaunchGame.event; }, - get onWillLaunchAddApp() { - return apiEmitters.games.onWillLaunchAddApp.event; - }, get onWillLaunchCurationGame() { return apiEmitters.games.onWillLaunchCurationGame.event; }, - get onWillLaunchCurationAddApp() { - return apiEmitters.games.onWillLaunchCurationAddApp.event; - }, get onDidLaunchGame() { return apiEmitters.games.onDidLaunchGame.event; }, - get onDidLaunchAddApp() { - return apiEmitters.games.onDidLaunchAddApp.event; - }, get onDidLaunchCurationGame() { return apiEmitters.games.onDidLaunchCurationGame.event; }, - get onDidLaunchCurationAddApp() { - return apiEmitters.games.onDidLaunchCurationAddApp.event; - }, get onDidUpdateGame() { return apiEmitters.games.onDidUpdateGame.event; }, diff --git a/src/back/game/GameManager.ts b/src/back/game/GameManager.ts index 571fc484d..f279415c2 100644 --- a/src/back/game/GameManager.ts +++ b/src/back/game/GameManager.ts @@ -1,7 +1,6 @@ import { ApiEmitter } from '@back/extensions/ApiEmitter'; import { chunkArray } from '@back/util/misc'; import { validateSqlName, validateSqlOrder } from '@back/util/sql'; -import { AdditionalApp } from '@database/entity/AdditionalApp'; import { Game } from '@database/entity/Game'; import { Playlist } from '@database/entity/Playlist'; import { PlaylistGame } from '@database/entity/PlaylistGame'; @@ -16,7 +15,7 @@ import { Coerce } from '@shared/utils/Coerce'; import * as fs from 'fs'; import * as path from 'path'; import * as TagManager from './TagManager'; -import { Brackets, FindOneOptions, getManager, SelectQueryBuilder } from 'typeorm'; +import { Brackets, FindOneOptions, getManager, SelectQueryBuilder, IsNull } from 'typeorm'; import * as GameDataManager from './GameDataManager'; const exactFields = [ 'broken', 'library', 'activeDataOnDisk' ]; @@ -36,35 +35,59 @@ export const onDidRemovePlaylistGame = new ApiEmitter(); export async function countGames(): Promise { const gameRepository = getManager().getRepository(Game); - return gameRepository.count(); + return gameRepository.count({ parentGameId: IsNull() }); } /** Find the game with the specified ID. */ -export async function findGame(id?: string, filter?: FindOneOptions): Promise { +export async function findGame(id?: string, filter?: FindOneOptions, noChildren?: boolean): Promise { if (id || filter) { const gameRepository = getManager().getRepository(Game); const game = await gameRepository.findOne(id, filter); + // Only fetch the children if the game exists, the caller didn't ask us not to, and it's not a child itself. + // This enforces the no-multiple-generations rule. + if (game && !noChildren && !game.parentGameId) { + game.children = await gameRepository.createQueryBuilder() + .relation('children') + .of(game) + .loadMany(); + // Load tags for the children too. + for (const child of game.children) { + child.tags = await gameRepository.createQueryBuilder() + .relation('tags') + .of(child) + .loadMany(); + } + } if (game) { - game.tags.sort(tagSort); + if (game.tags) { + game.tags.sort(tagSort); + } + // Not sure why the standard "if (game.children)" wasn't working here, but it wasn't. + // Sort the child games. It's probably a good idea. + if (game.children !== undefined && game.children !== null && game.children.length > 1) { + game.children.sort((a, b) => a.title.toLowerCase().localeCompare(b.title.toLowerCase())); + } } return game; } } - -export async function findGameRow(gameId: string, filterOpts?: FilterGameOpts, orderBy?: GameOrderBy, direction?: GameOrderReverse, index?: PageTuple): Promise { +/** Get the row number of an entry, specified by its gameId. */ +export async function findGameRow(gameId: string, orderBy: GameOrderBy, direction?: GameOrderReverse, filterOpts?: FilterGameOpts, index?: PageTuple): Promise { if (orderBy) { validateSqlName(orderBy); } // const startTime = Date.now(); const gameRepository = getManager().getRepository(Game); const subQ = gameRepository.createQueryBuilder('game') - .select(`game.id, row_number() over (order by game.${orderBy}) row_num`); + .select(`game.id, row_number() over (order by game.${orderBy} ${direction ? direction : ''}) row_num`) + .where('game.parentGameId IS NULL'); if (index) { if (!orderBy) { throw new Error('Failed to get game row. "index" is set but "orderBy" is missing.'); } - subQ.where(`(game.${orderBy}, game.id) > (:orderVal, :id)`, { orderVal: index.orderVal, id: index.id }); + subQ.andWhere(`(game.${orderBy}, game.id) ${direction === 'DESC' ? '<' : '>'} (:orderVal, :id)`, { orderVal: index.orderVal, id: index.id }); } if (filterOpts) { - applyFlatGameFilters('game', subQ, filterOpts, index ? 1 : 0); + // The "whereCount" param doesn't make much sense now, TODO change it. + applyFlatGameFilters('game', subQ, filterOpts, index ? 2 : 1); } if (orderBy) { subQ.orderBy(`game.${orderBy}`, direction); } @@ -78,11 +101,19 @@ export async function findGameRow(gameId: string, filterOpts?: FilterGameOpts, o // console.log(`${Date.now() - startTime}ms for row`); return raw ? Coerce.num(raw.row_num) : -1; // Coerce it, even though it is probably of type number or undefined } - +/** + * Randomly selects a number of games from the database + * @param count The number of games to find. + * @param broken Whether to include broken games. + * @param excludedLibraries A list of libraries to exclude. + * @param flatFilters A set of filters on tags. + * @returns A ViewGame[] representing the results. + */ export async function findRandomGames(count: number, broken: boolean, excludedLibraries: string[], flatFilters: string[]): Promise { const gameRepository = getManager().getRepository(Game); const query = gameRepository.createQueryBuilder('game'); query.select('game.id, game.title, game.platform, game.developer, game.publisher, game.tagsStr'); + query.where('game.parentGameId IS NULL'); if (!broken) { query.andWhere('broken = false'); } if (excludedLibraries.length > 0) { query.andWhere('library NOT IN (:...libs)', { libs: excludedLibraries }); @@ -105,7 +136,16 @@ export type GetPageKeysetResult = { total: number; } -export async function findGamePageKeyset(filterOpts: FilterGameOpts, orderBy: GameOrderBy, direction: GameOrderReverse, searchLimit?: number): Promise { +/** + * Gets the elements that occur before each page, and the total number of games that satisfy the filter. + * @param filterOpts The options that should be used to filter the results. + * @param orderBy The column to sort results by. + * @param direction The direction to sort results. + * @param searchLimit A limit on the number of returned results + * @param viewPageSize The size of a page. Mostly used for testing. + * @returns The elements that occur before each page, and the total number of games that satisfy the filter. + */ +export async function findGamePageKeyset(filterOpts: FilterGameOpts, orderBy: GameOrderBy, direction: GameOrderReverse, searchLimit?: number, viewPageSize = VIEW_PAGE_SIZE): Promise { // let startTime = Date.now(); validateSqlName(orderBy); @@ -114,11 +154,11 @@ export async function findGamePageKeyset(filterOpts: FilterGameOpts, orderBy: Ga // console.log('FindGamePageKeyset:'); const subQ = await getGameQuery('sub', filterOpts, orderBy, direction); - subQ.select(`sub.${orderBy}, sub.title, sub.id, case row_number() over(order by sub.${orderBy} ${direction}, sub.title ${direction}, sub.id) % ${VIEW_PAGE_SIZE} when 0 then 1 else 0 end page_boundary`); - subQ.orderBy(`sub.${orderBy} ${direction}, sub.title`, direction); + subQ.select(`sub.${orderBy}, sub.title, sub.id, case row_number() over(order by sub.${orderBy} ${direction}, sub.title ${direction}, sub.id ${direction}) % ${viewPageSize} when 0 then 1 else 0 end page_boundary`); + subQ.orderBy(`sub.${orderBy} ${direction}, sub.title ${direction}, sub.id`, direction); let query = getManager().createQueryBuilder() - .select(`g.${orderBy}, g.title, g.id, row_number() over(order by g.${orderBy} ${direction}, g.title ${direction}) + 1 page_number`) + .select(`g.${orderBy}, g.title, g.id, row_number() over(order by g.${orderBy} ${direction}, g.title ${direction}, g.id ${direction}) + 1 page_number`) .from('(' + subQ.getQuery() + ')', 'g') .where('g.page_boundary = 1') .setParameters(subQ.getParameters()); @@ -138,6 +178,7 @@ export async function findGamePageKeyset(filterOpts: FilterGameOpts, orderBy: Ga // Count games let total = -1; // startTime = Date.now(); + // TODO reuse subQ? const subGameQuery = await getGameQuery('sub', filterOpts, orderBy, direction, 0, searchLimit ? searchLimit : undefined, undefined); query = getManager().createQueryBuilder() .select('COUNT(*)') @@ -172,7 +213,7 @@ export type FindGamesOpts = { export async function findAllGames(): Promise { const gameRepository = getManager().getRepository(Game); - return gameRepository.find(); + return gameRepository.find({parentGameId: IsNull()}); } /** Search the database for games. */ @@ -195,6 +236,7 @@ export async function findGames(opts: FindGamesOpts, shallow: const games = (shallow) ? (await query.select('game.id, game.title, game.platform, game.developer, game.publisher, game.extreme, game.tagsStr').getRawMany()) as ViewGame[] : await query.getMany(); + rangesOut.push({ start: range.start, length: range.length, @@ -210,25 +252,13 @@ export async function findGames(opts: FindGamesOpts, shallow: return rangesOut; } -/** Find an add apps with the specified ID. */ -export async function findAddApp(id?: string, filter?: FindOneOptions): Promise { - if (id || filter) { - if (!filter) { - filter = { - relations: ['parentGame'] - }; - } - const addAppRepository = getManager().getRepository(AdditionalApp); - return addAppRepository.findOne(id, filter); - } -} - export async function findPlatformAppPaths(platform: string): Promise { const gameRepository = getManager().getRepository(Game); const values = await gameRepository.createQueryBuilder('game') .select('game.applicationPath') .distinct() .where('game.platform = :platform', {platform: platform}) + .andWhere('game.parentGameId IS NULL') .groupBy('game.applicationPath') .orderBy('COUNT(*)', 'DESC') .getRawMany(); @@ -257,13 +287,16 @@ export async function findUniqueValuesInOrder(entity: any, column: string): Prom return Coerce.strArray(values.map(v => v[`entity_${column}`])); } -export async function findPlatforms(library: string): Promise { +export async function findPlatforms(library: string, includeChildren?: boolean): Promise { const gameRepository = getManager().getRepository(Game); - const libraries = await gameRepository.createQueryBuilder('game') + const query = gameRepository.createQueryBuilder('game') .where('game.library = :library', {library: library}) .select('game.platform') - .distinct() - .getRawMany(); + .distinct(); + if (!includeChildren) { + query.andWhere('game.parentGameId IS NULL'); + } + const libraries = await query.getRawMany(); return Coerce.strArray(libraries.map(l => l.game_platform)); } @@ -272,6 +305,19 @@ export async function updateGames(games: Game[]): Promise { for (const chunk of chunks) { await getManager().transaction(async transEntityManager => { for (const game of chunk) { + // Set nullable properties to null if they're empty. + if (game.parentGameId === '') { + game.parentGameId = null; + } + if (game.extras === '') { + game.extras = null; + } + if (game.extrasName === '') { + game.extrasName = null; + } + if (game.message === '') { + game.message = null; + } await transEntityManager.save(Game, game); } }); @@ -280,15 +326,28 @@ export async function updateGames(games: Game[]): Promise { export async function save(game: Game): Promise { const gameRepository = getManager().getRepository(Game); + // Set nullable properties to null if they're empty. + if (game.parentGameId === '') { + game.parentGameId = null; + } + if (game.extras === '') { + game.extras = null; + } + if (game.extrasName === '') { + game.extrasName = null; + } + if (game.message === '') { + game.message = null; + } log.debug('Launcher', 'Saving game...'); const savedGame = await gameRepository.save(game); if (savedGame) { onDidUpdateGame.fire({oldGame: game, newGame: savedGame}); } return savedGame; } -export async function removeGameAndAddApps(gameId: string, dataPacksFolderPath: string): Promise { +// TODO this needs to re-parent somehow? Idk, wherever you want to put it. +export async function removeGameAndChildren(gameId: string, dataPacksFolderPath: string): Promise { const gameRepository = getManager().getRepository(Game); - const addAppRepository = getManager().getRepository(AdditionalApp); const game = await findGame(gameId); if (game) { // Delete GameData @@ -298,9 +357,11 @@ export async function removeGameAndAddApps(gameId: string, dataPacksFolderPath: } await GameDataManager.remove(gameData.id); } - // Delete Add Apps - for (const addApp of game.addApps) { - await addAppRepository.remove(addApp); + // Delete children + if (game.children) { + for (const child of game.children) { + await gameRepository.remove(child); + } } // Delete Game await gameRepository.remove(game); @@ -434,7 +495,7 @@ async function chunkedFindByIds(gameIds: string[]): Promise { const chunks = chunkArray(gameIds, 100); let gamesFound: Game[] = []; for (const chunk of chunks) { - gamesFound = gamesFound.concat(await gameRepository.findByIds(chunk)); + gamesFound = gamesFound.concat(await gameRepository.findByIds(chunk, {parentGameId: IsNull()})); } return gamesFound; @@ -472,6 +533,10 @@ function applyFlatGameFilters(alias: string, query: SelectQueryBuilder, fi return whereCount; } +/** + * Add a position-independent search term (whitelist or blacklist) in or'd WHERE clauses on title, alternateTitles, + * developer, and publisher. + */ function doWhereTitle(alias: string, query: SelectQueryBuilder, value: string, count: number, whitelist: boolean): void { validateSqlName(alias); @@ -500,6 +565,16 @@ function doWhereTitle(alias: string, query: SelectQueryBuilder, value: str } } +/** + * Add a search term in a WHERE clause on the given field to a selectquerybuilder. + * @param alias The name of the table. + * @param query The query to add to. + * @param field The field (column) to search on. + * @param value The value to search for. If it's a string, it will be interpreted as position-independent + * if the field is not on the exactFields list. + * @param count How many conditions we've already filtered. Determines whether we use .where() or .andWhere(). + * @param whitelist Whether this is a whitelist or a blacklist search. + */ function doWhereField(alias: string, query: SelectQueryBuilder, field: string, value: any, count: number, whitelist: boolean) { // Create comparator const typing = typeof value; @@ -570,6 +645,12 @@ async function getGameQuery( query.where(`(${alias}.${orderBy}, ${alias}.title, ${alias}.id) ${comparator} (:orderVal, :title, :id)`, { orderVal: index.orderVal, title: index.title, id: index.id }); whereCount++; } + if (whereCount === 0) { + query.where('parentGameId IS NULL'); + } else { + query.andWhere('parentGameId IS NULL'); + } + whereCount++; // Apply all flat game filters if (filterOpts) { whereCount = applyFlatGameFilters(alias, query, filterOpts, whereCount); @@ -585,8 +666,10 @@ async function getGameQuery( query.orderBy('pg.order', 'ASC'); if (whereCount === 0) { query.where('pg.playlistId = :playlistId', { playlistId: filterOpts.playlistId }); } else { query.andWhere('pg.playlistId = :playlistId', { playlistId: filterOpts.playlistId }); } + whereCount++; query.skip(offset); // TODO: Why doesn't offset work here? } + // Tag filtering if (filterOpts && filterOpts.searchQuery) { const aliasWhitelist = filterOpts.searchQuery.whitelist.filter(f => f.field === 'tag').map(f => f.value); diff --git a/src/back/importGame.ts b/src/back/importGame.ts index 0a7287ab9..a1eaffbd3 100644 --- a/src/back/importGame.ts +++ b/src/back/importGame.ts @@ -1,12 +1,11 @@ import * as GameDataManager from '@back/game/GameDataManager'; -import { AdditionalApp } from '@database/entity/AdditionalApp'; import { Game } from '@database/entity/Game'; import { Tag } from '@database/entity/Tag'; import { TagCategory } from '@database/entity/TagCategory'; import { validateSemiUUID } from '@renderer/util/uuid'; import { LOGOS, SCREENSHOTS } from '@shared/constants'; import { convertEditToCurationMetaFile } from '@shared/curate/metaToMeta'; -import { CurationIndexImage, EditAddAppCuration, EditAddAppCurationMeta, EditCuration, EditCurationMeta } from '@shared/curate/types'; +import { CurationIndexImage, EditCuration, EditCurationMeta } from '@shared/curate/types'; import { getCurationFolder } from '@shared/curate/util'; import * as child_process from 'child_process'; import { execFile } from 'child_process'; @@ -17,7 +16,7 @@ import { ApiEmitter } from './extensions/ApiEmitter'; import * as GameManager from './game/GameManager'; import * as TagManager from './game/TagManager'; import { GameManagerState } from './game/types'; -import { GameLauncher, GameLaunchInfo, LaunchAddAppOpts, LaunchGameOpts } from './GameLauncher'; +import { GameLauncher, GameLaunchInfo, LaunchExtrasOpts, LaunchGameOpts } from './GameLauncher'; import { OpenExternalFunc, ShowMessageBoxFunc } from './types'; import { getMklinkBatPath } from './util/elevate'; import { uuid } from './util/uuid'; @@ -71,7 +70,7 @@ export async function importCuration(opts: ImportCurationOpts): Promise { where: { launchCommand: curation.meta.launchCommand } - }); + }, true); if (existingGame) { // Warn user of possible duplicate const response = await opts.openDialog({ @@ -86,16 +85,55 @@ export async function importCuration(opts: ImportCurationOpts): Promise { } } } + // @TODO ditto as above. + if (curation.meta.parentGameId && curation.meta.parentGameId != '') { + const existingGame = await GameManager.findGame(curation.meta.parentGameId, undefined, true); + if (existingGame == undefined) { + // Warn user of invalid parent + const response = await opts.openDialog({ + title: 'Invalid Parent', + message: 'This curation has an invalid parent game id.\nContinue importing this curation? Warning: this will make the game be parentless!\n\n' + + `Curation:\n\tTitle: ${curation.meta.title}\n\tLaunch Command: ${curation.meta.launchCommand}\n\tPlatform: ${curation.meta.platform}\n\n`, + buttons: ['Yes', 'No'] + }); + if (response === 1) { + throw new Error('User Cancelled Import'); + } else { + curation.meta.parentGameId = undefined; + } + } + } else if (curation.meta.parentGameId == '') { + curation.meta.parentGameId = undefined; + } // Build content list const contentToMove = []; - const extrasAddApp = curation.addApps.find(a => a.meta.applicationPath === ':extras:'); - if (extrasAddApp && extrasAddApp.meta.launchCommand && extrasAddApp.meta.launchCommand.length > 0) { - // Add extras folder if meta has an entry - contentToMove.push([path.join(getCurationFolder(curation, fpPath), 'Extras'), path.join(fpPath, 'Extras', extrasAddApp.meta.launchCommand)]); + if (curation.meta.extras && curation.meta.extras.length > 0) { + const extrasPath = path.join(getCurationFolder(curation, fpPath), 'Extras'); + // Check that extras exist. + try { + // If this doesn't error out, the extras exist. + await fs.promises.stat(extrasPath); + // Add extras folder if meta has an entry + contentToMove.push([extrasPath, path.join(fpPath, 'Extras', curation.meta.extras)]); + } catch { + // It did error out, we need to tell the user. + const response = await opts.openDialog({ + title: 'Overwriting Game', + message: 'The curation claims to have extras but lacks an Extras folder!\nContinue importing this curation? Warning: this will remove the extras.\n\n' + + `Curation:\n\tTitle: ${curation.meta.title}\n\tLaunch Command: ${curation.meta.launchCommand}\n\tPlatform: ${curation.meta.platform}\n\t` + + `Expected extras path: ${extrasPath}`, + buttons: ['Yes', 'No'] + }); + if (response === 1) { + throw new Error('User Cancelled Import'); + } + curation.meta.extras = undefined; + curation.meta.extrasName = undefined; + } } // Create and add game and additional applications const gameId = validateSemiUUID(curation.key) ? curation.key : uuid(); - const oldGame = await GameManager.findGame(gameId); + const oldGame = await GameManager.findGame(gameId, undefined, true); if (oldGame) { const response = await opts.openDialog({ title: 'Overwriting Game', @@ -110,7 +148,7 @@ export async function importCuration(opts: ImportCurationOpts): Promise { } // Add game to database - let game = await createGameFromCurationMeta(gameId, curation.meta, curation.addApps, date); + let game = await createGameFromCurationMeta(gameId, curation.meta, date); game = await GameManager.save(game); // Store curation state for extension use later @@ -156,7 +194,7 @@ export async function importCuration(opts: ImportCurationOpts): Promise { if (saveCuration) { // Save working meta const metaPath = path.join(getCurationFolder(curation, fpPath), 'meta.yaml'); - const meta = YAML.stringify(convertEditToCurationMetaFile(curation.meta, opts.tagCategories, curation.addApps)); + const meta = YAML.stringify(convertEditToCurationMetaFile(curation.meta, opts.tagCategories)); await fs.writeFile(metaPath, meta); // Date in form 'YYYY-MM-DD' for folder sorting const date = new Date(); @@ -237,7 +275,7 @@ export async function importCuration(opts: ImportCurationOpts): Promise { console.warn(error.message); if (game.id) { // Clean up half imported entries - GameManager.removeGameAndAddApps(game.id, dataPacksFolderPath); + GameManager.removeGameAndChildren(game.id, dataPacksFolderPath); } }); } @@ -246,11 +284,11 @@ export async function importCuration(opts: ImportCurationOpts): Promise { * Create and launch a game from curation metadata. * @param curation Curation to launch */ -export async function launchCuration(key: string, meta: EditCurationMeta, addAppMetas: EditAddAppCurationMeta[], symlinkCurationContent: boolean, +export async function launchCuration(key: string, meta: EditCurationMeta, symlinkCurationContent: boolean, skipLink: boolean, opts: Omit, onWillEvent:ApiEmitter, onDidEvent: ApiEmitter) { if (!skipLink || !symlinkCurationContent) { await linkContentFolder(key, opts.fpPath, opts.isDev, opts.exePath, opts.htdocsPath, symlinkCurationContent); } curationLog(`Launching Curation ${meta.title}`); - const game = await createGameFromCurationMeta(key, meta, [], new Date()); + const game = await createGameFromCurationMeta(key, meta, new Date()); GameLauncher.launchGame({ ...opts, game: game, @@ -259,21 +297,16 @@ export async function launchCuration(key: string, meta: EditCurationMeta, addApp onDidEvent.fire(game); } -/** - * Create and launch an additional application from curation metadata. - * @param curationKey Key of the parent curation index - * @param appCuration Add App Curation to launch - */ -export async function launchAddAppCuration(curationKey: string, appCuration: EditAddAppCuration, symlinkCurationContent: boolean, - skipLink: boolean, opts: Omit, onWillEvent: ApiEmitter, onDidEvent: ApiEmitter) { - if (!skipLink || !symlinkCurationContent) { await linkContentFolder(curationKey, opts.fpPath, opts.isDev, opts.exePath, opts.htdocsPath, symlinkCurationContent); } - const addApp = createAddAppFromCurationMeta(appCuration, createPlaceholderGame()); - await onWillEvent.fire(addApp); - GameLauncher.launchAdditionalApplication({ - ...opts, - addApp: addApp, - }); - onDidEvent.fire(addApp); +// TODO this won't work, fix it. Actually, it's okay for now: the related back event *should* never be called. +export async function launchCurationExtras(key: string, meta: EditCurationMeta, symlinkCurationContent: boolean, + skipLink: boolean, opts: Omit) { + if (meta.extras) { + if (!skipLink || !symlinkCurationContent) { await linkContentFolder(key, opts.fpPath, opts.isDev, opts.exePath, opts.htdocsPath, symlinkCurationContent); } + await GameLauncher.launchExtras({ + ...opts, + extrasPath: meta.extras + }); + } } function logMessage(text: string, curation: EditCuration): void { @@ -285,10 +318,11 @@ function logMessage(text: string, curation: EditCuration): void { * @param curation Curation to get data from. * @param gameId ID to use for Game */ -async function createGameFromCurationMeta(gameId: string, gameMeta: EditCurationMeta, addApps : EditAddAppCuration[], date: Date): Promise { +async function createGameFromCurationMeta(gameId: string, gameMeta: EditCurationMeta, date: Date): Promise { const game: Game = new Game(); Object.assign(game, { id: gameId, // (Re-use the id of the curation) + parentGameId: gameMeta.parentGameId === '' ? null : gameMeta.parentGameId ? gameMeta.parentGameId : null, title: gameMeta.title || '', alternateTitles: gameMeta.alternateTitles || '', series: gameMeta.series || '', @@ -306,32 +340,22 @@ async function createGameFromCurationMeta(gameId: string, gameMeta: EditCuration version: gameMeta.version || '', originalDescription: gameMeta.originalDescription || '', language: gameMeta.language || '', + message: gameMeta.message || null, + extrasName: gameMeta.extrasName || null, + extras: gameMeta.extras || null, dateAdded: date.toISOString(), dateModified: date.toISOString(), broken: false, extreme: gameMeta.extreme || false, library: gameMeta.library || '', orderTitle: '', // This will be set when saved - addApps: [], + children: [], placeholder: false, activeDataOnDisk: false }); - game.addApps = addApps.map(addApp => createAddAppFromCurationMeta(addApp, game)); return game; } -function createAddAppFromCurationMeta(addAppMeta: EditAddAppCuration, game: Game): AdditionalApp { - return { - id: addAppMeta.key, - name: addAppMeta.meta.heading || '', - applicationPath: addAppMeta.meta.applicationPath || '', - launchCommand: addAppMeta.meta.launchCommand || '', - autoRunBefore: false, - waitForExit: false, - parentGame: game - }; -} - async function importGameImage(image: CurationIndexImage, gameId: string, folder: typeof LOGOS | typeof SCREENSHOTS, fullImagePath: string): Promise { if (image.exists) { const last = path.join(gameId.substr(0, 2), gameId.substr(2, 2), gameId+'.png'); @@ -491,7 +515,7 @@ function curationLog(content: string): void { // return buffer.equals(secondBuffer); // } -function createPlaceholderGame(): Game { +/* function createPlaceholderGame(): Game { const id = uuid(); const game = new Game(); Object.assign(game, { @@ -520,12 +544,12 @@ function createPlaceholderGame(): Game { language: '', library: '', orderTitle: '', - addApps: [], + children: [], placeholder: true, activeDataOnDisk: false }); return game; -} +}*/ export async function createTagsFromLegacy(tags: string, tagCache: Record): Promise { const allTags: Tag[] = []; diff --git a/src/back/index.ts b/src/back/index.ts index 7ea3495b9..622f84ebc 100644 --- a/src/back/index.ts +++ b/src/back/index.ts @@ -1,5 +1,4 @@ import * as GameDataManager from '@back/game/GameDataManager'; -import { AdditionalApp } from '@database/entity/AdditionalApp'; import { Game } from '@database/entity/Game'; import { GameData } from '@database/entity/GameData'; import { Playlist } from '@database/entity/Playlist'; @@ -17,6 +16,7 @@ import { SourceFileURL1612435692266 } from '@database/migration/1612435692266-So import { SourceFileCount1612436426353 } from '@database/migration/1612436426353-SourceFileCount'; import { GameTagsStr1613571078561 } from '@database/migration/1613571078561-GameTagsStr'; import { GameDataParams1619885915109 } from '@database/migration/1619885915109-GameDataParams'; +import { ChildCurations1648251821422 } from '@database/migration/1648251821422-ChildCurations'; import { BackIn, BackInit, BackInitArgs, BackOut } from '@shared/back/types'; import { ILogoSet, LogoSet } from '@shared/extensions/interfaces'; import { IBackProcessInfo, RecursivePartial } from '@shared/interfaces'; @@ -128,14 +128,10 @@ const state: BackState = { onLog: new ApiEmitter(), games: { onWillLaunchGame: new ApiEmitter(), - onWillLaunchAddApp: new ApiEmitter(), onWillLaunchCurationGame: new ApiEmitter(), - onWillLaunchCurationAddApp: new ApiEmitter(), onWillUninstallGameData: GameDataManager.onWillUninstallGameData, onDidLaunchGame: new ApiEmitter(), - onDidLaunchAddApp: new ApiEmitter(), onDidLaunchCurationGame: new ApiEmitter(), - onDidLaunchCurationAddApp: new ApiEmitter(), onDidUpdateGame: GameManager.onDidUpdateGame, onDidRemoveGame: GameManager.onDidRemoveGame, onDidUpdatePlaylist: GameManager.onDidUpdatePlaylist, @@ -218,7 +214,7 @@ async function main() { // Curation BackIn.IMPORT_CURATION, BackIn.LAUNCH_CURATION, - BackIn.LAUNCH_CURATION_ADDAPP, + BackIn.LAUNCH_CURATION_EXTRAS, // ? BackIn.SYNC_GAME_METADATA, // Meta Edits @@ -315,9 +311,9 @@ async function onProcessMessage(message: any, sendHandle: any): Promise { const options: ConnectionOptions = { type: 'sqlite', database: path.join(state.config.flashpointPath, 'Data', 'flashpoint.sqlite'), - entities: [Game, AdditionalApp, Playlist, PlaylistGame, Tag, TagAlias, TagCategory, GameData, Source, SourceData], + entities: [Game, Playlist, PlaylistGame, Tag, TagAlias, TagCategory, GameData, Source, SourceData], migrations: [Initial1593172736527, AddExtremeToPlaylist1599706152407, GameData1611753257950, SourceDataUrlPath1612434225789, SourceFileURL1612435692266, - SourceFileCount1612436426353, GameTagsStr1613571078561, GameDataParams1619885915109] + SourceFileCount1612436426353, GameTagsStr1613571078561, GameDataParams1619885915109, ChildCurations1648251821422] }; state.connection = await createConnection(options); // TypeORM forces on but breaks Playlist Game links to unimported games diff --git a/src/back/responses.ts b/src/back/responses.ts index 9adbc2abe..8a3979c2e 100644 --- a/src/back/responses.ts +++ b/src/back/responses.ts @@ -34,12 +34,12 @@ import * as GameDataManager from './game/GameDataManager'; import * as GameManager from './game/GameManager'; import * as TagManager from './game/TagManager'; import { escapeArgsForShell, GameLauncher, GameLaunchInfo } from './GameLauncher'; -import { importCuration, launchAddAppCuration, launchCuration } from './importGame'; +import { importCuration, launchCuration, launchCurationExtras } from './importGame'; import { ManagedChildProcess } from './ManagedChildProcess'; import { importAllMetaEdits } from './MetaEdit'; import { BackState, BareTag, TagsFile } from './types'; import { pathToBluezip } from './util/Bluezip'; -import { copyError, createAddAppFromLegacy, createContainer, createGameFromLegacy, createPlaylistFromJson, exit, pathExists, procToService, removeService, runService } from './util/misc'; +import { copyError, createChildFromFromLegacyAddApp, createContainer, createGameFromLegacy, createPlaylistFromJson, exit, pathExists, procToService, removeService, runService } from './util/misc'; import { sanitizeFilename } from './util/sanitizeFilename'; import { uuid } from './util/uuid'; @@ -95,7 +95,8 @@ export function registerRequestCallbacks(state: BackState): void { const mad4fpEnabled = state.serviceInfo ? (state.serviceInfo.server.findIndex(s => s.mad4fp === true) !== -1) : false; const platforms: Record = {}; for (const library of libraries) { - platforms[library] = (await GameManager.findPlatforms(library)).sort(); + // Explicitly exclude the platforms of child curations - they're mostly blank. + platforms[library] = (await GameManager.findPlatforms(library, false)).sort(); } // Fire after return has sent @@ -202,42 +203,12 @@ export function registerRequestCallbacks(state: BackState): void { return state.execMappings; }); - state.socketServer.register(BackIn.LAUNCH_ADDAPP, async (event, id) => { - const addApp = await GameManager.findAddApp(id); - if (addApp) { - // If it has GameData, make sure it's present - let gameData: GameData | undefined; - if (addApp.parentGame.activeDataId) { - gameData = await GameDataManager.findOne(addApp.parentGame.activeDataId); - if (gameData && !gameData.presentOnDisk) { - // Download GameData - const onProgress = (percent: number) => { - // Sent to PLACEHOLDER download dialog on client - state.socketServer.broadcast(BackOut.SET_PLACEHOLDER_DOWNLOAD_PERCENT, percent); - }; - state.socketServer.broadcast(BackOut.OPEN_PLACEHOLDER_DOWNLOAD_DIALOG); - try { - await GameDataManager.downloadGameData(gameData.id, path.join(state.config.flashpointPath, state.preferences.dataPacksFolderPath), onProgress) - .finally(() => { - // Close PLACEHOLDER download dialog on client, cosmetic delay to look nice - setTimeout(() => { - state.socketServer.broadcast(BackOut.CLOSE_PLACEHOLDER_DOWNLOAD_DIALOG); - }, 250); - }); - } catch (error: any) { - state.socketServer.broadcast(BackOut.OPEN_ALERT, error); - log.info('Game Launcher', `Game Launch Aborted: ${error}`); - return; - } - } - } - await state.apiEmitters.games.onWillLaunchAddApp.fire(addApp); - const platform = addApp.parentGame ? addApp.parentGame : ''; - GameLauncher.launchAdditionalApplication({ - addApp, + state.socketServer.register(BackIn.LAUNCH_EXTRAS, async (event, extrasPath) => { + if (extrasPath) { + await GameLauncher.launchExtras({ + extrasPath: extrasPath, fpPath: path.resolve(state.config.flashpointPath), htdocsPath: state.preferences.htdocsFolderPath, - native: addApp.parentGame && state.preferences.nativePlatforms.some(p => p === platform) || false, execMappings: state.execMappings, lang: state.languageContainer, isDev: state.isDev, @@ -247,16 +218,27 @@ export function registerRequestCallbacks(state: BackState): void { proxy: state.preferences.browserModeProxy, openDialog: state.socketServer.showMessageBoxBack(event.client), openExternal: state.socketServer.openExternal(event.client), - runGame: runGameFactory(state) + runGame: runGameFactory(state), }); - state.apiEmitters.games.onDidLaunchAddApp.fire(addApp); } }); state.socketServer.register(BackIn.LAUNCH_GAME, async (event, id) => { - const game = await GameManager.findGame(id); + const game = await GameManager.findGame(id, undefined, true); if (game) { + if (game.parentGameId && !game.parentGame) { + log.debug('Game Launcher', 'Fetching parent game.'); + // Note: we explicitly don't fetch the parent's children. We already have the only child we're interested in. + game.parentGame = await GameManager.findGame(game.parentGameId, undefined, true); + } + // Inherit empty fields. + if (game.parentGame) { + if (game.platform === '') { + game.platform = game.parentGame.platform; + } + // TODO any more I should add? + } // Make sure Server is set to configured server - Curations may have changed it const configServer = state.serviceInfo ? state.serviceInfo.server.find(s => s.name === state.config.server) : undefined; if (configServer) { @@ -296,6 +278,34 @@ export function registerRequestCallbacks(state: BackState): void { } } } + // Make sure the parent's GameData is present too. + if (game.parentGame && game.parentGame.activeDataId) { + gameData = await GameDataManager.findOne(game.parentGame.activeDataId); + if (gameData && !gameData.presentOnDisk) { + // Download GameData + const onDetails = (details: DownloadDetails) => { + state.socketServer.broadcast(BackOut.SET_PLACEHOLDER_DOWNLOAD_DETAILS, details); + }; + const onProgress = (percent: number) => { + // Sent to PLACEHOLDER download dialog on client + state.socketServer.broadcast(BackOut.SET_PLACEHOLDER_DOWNLOAD_PERCENT, percent); + }; + state.socketServer.broadcast(BackOut.OPEN_PLACEHOLDER_DOWNLOAD_DIALOG); + try { + await GameDataManager.downloadGameData(gameData.id, path.join(state.config.flashpointPath, state.preferences.dataPacksFolderPath), onProgress, onDetails) + .finally(() => { + // Close PLACEHOLDER download dialog on client, cosmetic delay to look nice + setTimeout(() => { + state.socketServer.broadcast(BackOut.CLOSE_PLACEHOLDER_DOWNLOAD_DIALOG); + }, 250); + }); + } catch (error) { + state.socketServer.broadcast(BackOut.OPEN_ALERT, error as string); + log.info('Game Launcher', `Game Launch Aborted: ${error}`); + return; + } + } + } // Launch game await GameLauncher.launchGame({ game, @@ -338,7 +348,9 @@ export function registerRequestCallbacks(state: BackState): void { }); state.socketServer.register(BackIn.DELETE_GAME, async (event, id) => { - const game = await GameManager.removeGameAndAddApps(id, path.join(state.config.flashpointPath, state.preferences.dataPacksFolderPath)); + // TODO This needs to somehow re-parent instead of just deleting all the children. It will have to wait + // until the frontend changes are made, I guess. + const game = await GameManager.removeGameAndChildren(id, path.join(state.config.flashpointPath, state.preferences.dataPacksFolderPath)); state.queries = {}; // Clear entire cache @@ -355,14 +367,17 @@ export function registerRequestCallbacks(state: BackState): void { if (game) { // Copy and apply new IDs + const newGame = deepCopy(game); - const newAddApps = game.addApps.map(addApp => deepCopy(addApp)); + const newChildren = game.children?.map(addApp => deepCopy(addApp)); newGame.id = uuid(); - for (let j = 0; j < newAddApps.length; j++) { - newAddApps[j].id = uuid(); - newAddApps[j].parentGame = newGame; + if (newChildren) { + for (let j = 0; j < newChildren.length; j++) { + newChildren[j].id = uuid(); + newChildren[j].parentGameId = newGame.id; + } } - newGame.addApps = newAddApps; + newGame.children = newChildren; // Add copies result = await GameManager.save(newGame); @@ -471,7 +486,7 @@ export function registerRequestCallbacks(state: BackState): void { state.socketServer.register(BackIn.EXPORT_GAME, async (event, id, location, metaOnly) => { if (await pathExists(metaOnly ? path.dirname(location) : location)) { - const game = await GameManager.findGame(id); + const game = await GameManager.findGame(id, undefined, true); if (game) { // Save to file try { @@ -502,7 +517,7 @@ export function registerRequestCallbacks(state: BackState): void { }); state.socketServer.register(BackIn.GET_GAME, async (event, id) => { - return GameManager.findGame(id); + return await GameManager.findGame(id); }); state.socketServer.register(BackIn.GET_GAME_DATA, async (event, id) => { @@ -549,7 +564,7 @@ export function registerRequestCallbacks(state: BackState): void { } const game = await GameManager.findGame(gameData.gameId); if (game) { - game.activeDataId = undefined; + game.activeDataId = null; game.activeDataOnDisk = false; await GameManager.save(game); } @@ -626,7 +641,8 @@ export function registerRequestCallbacks(state: BackState): void { }); state.socketServer.register(BackIn.GET_ALL_GAMES, async (event) => { - return GameManager.findAllGames(); + const games: Game[] = await GameManager.findAllGames(); + return games; }); state.socketServer.register(BackIn.RANDOM_GAMES, async (event, data) => { @@ -774,9 +790,9 @@ export function registerRequestCallbacks(state: BackState): void { state.socketServer.register(BackIn.BROWSE_VIEW_INDEX, async (event, gameId, query) => { const position = await GameManager.findGameRow( gameId, - query.filter, query.orderBy, query.orderReverse, + query.filter, undefined); return position - 1; // ("position" starts at 1, while "index" starts at 0) @@ -917,8 +933,7 @@ export function registerRequestCallbacks(state: BackState): void { }); state.socketServer.register(BackIn.GET_PLAYLIST_GAME, async (event, playlistId, gameId) => { - const playlistGame = await GameManager.findPlaylistGame(playlistId, gameId); - return playlistGame; + return await GameManager.findPlaylistGame(playlistId, gameId); }); state.socketServer.register(BackIn.ADD_PLAYLIST_GAME, async (event, playlistId, gameId) => { @@ -943,7 +958,7 @@ export function registerRequestCallbacks(state: BackState): void { for (const game of platform.collection.games) { const addApps = platform.collection.additionalApplications.filter(a => a.gameId === game.id); const translatedGame = await createGameFromLegacy(game, tagCache); - translatedGame.addApps = createAddAppFromLegacy(addApps, translatedGame); + translatedGames.push(...createChildFromFromLegacyAddApp(addApps, translatedGame)); translatedGames.push(translatedGame); } await GameManager.updateGames(translatedGames); @@ -1040,7 +1055,7 @@ export function registerRequestCallbacks(state: BackState): void { return { error: error || undefined }; }); - state.socketServer.register(BackIn.LAUNCH_CURATION, async (event, data) => { + state.socketServer.register(BackIn.LAUNCH_CURATION_EXTRAS, async (event, data) => { const skipLink = (data.key === state.lastLinkedCurationKey); state.lastLinkedCurationKey = data.symlinkCurationContent ? data.key : ''; try { @@ -1065,37 +1080,57 @@ export function registerRequestCallbacks(state: BackState): void { } } } - - await launchCuration(data.key, data.meta, data.addApps, data.symlinkCurationContent, skipLink, { - fpPath: path.resolve(state.config.flashpointPath), - htdocsPath: state.preferences.htdocsFolderPath, - native: state.preferences.nativePlatforms.some(p => p === data.meta.platform), - execMappings: state.execMappings, - lang: state.languageContainer, - isDev: state.isDev, - exePath: state.exePath, - appPathOverrides: state.preferences.appPathOverrides, - providers: await getProviders(state), - proxy: state.preferences.browserModeProxy, - openDialog: state.socketServer.showMessageBoxBack(event.client), - openExternal: state.socketServer.openExternal(event.client), - runGame: runGameFactory(state), - }, - state.apiEmitters.games.onWillLaunchCurationGame, - state.apiEmitters.games.onDidLaunchCurationGame); + if (data.meta.extras) { + await launchCurationExtras(data.key, data.meta, data.symlinkCurationContent, skipLink, { + fpPath: path.resolve(state.config.flashpointPath), + htdocsPath: state.preferences.htdocsFolderPath, + execMappings: state.execMappings, + lang: state.languageContainer, + isDev: state.isDev, + exePath: state.exePath, + appPathOverrides: state.preferences.appPathOverrides, + providers: await getProviders(state), + proxy: state.preferences.browserModeProxy, + openDialog: state.socketServer.showMessageBoxBack(event.client), + openExternal: state.socketServer.openExternal(event.client), + runGame: runGameFactory(state) + }); + } } catch (e) { log.error('Launcher', e + ''); } }); - state.socketServer.register(BackIn.LAUNCH_CURATION_ADDAPP, async (event, data) => { - const skipLink = (data.curationKey === state.lastLinkedCurationKey); - state.lastLinkedCurationKey = data.curationKey; + state.socketServer.register(BackIn.LAUNCH_CURATION, async (event, data) => { + const skipLink = (data.key === state.lastLinkedCurationKey); + state.lastLinkedCurationKey = data.symlinkCurationContent ? data.key : ''; try { - await launchAddAppCuration(data.curationKey, data.curation, data.symlinkCurationContent, skipLink, { + if (state.serviceInfo) { + // Make sure all 3 relevant server infos are present before considering MAD4FP opt + const configServer = state.serviceInfo.server.find(s => s.name === state.config.server); + const mad4fpServer = state.serviceInfo.server.find(s => s.mad4fp); + const activeServer = state.services.get('server'); + const activeServerInfo = state.serviceInfo.server.find(s => (activeServer && 'name' in activeServer.info && s.name === activeServer.info?.name)); + if (activeServer && configServer && mad4fpServer) { + if (data.mad4fp && activeServerInfo && !activeServerInfo.mad4fp) { + // Swap to mad4fp server + const mad4fpServerCopy = deepCopy(mad4fpServer); + // Set the content folder path as the final parameter + mad4fpServerCopy.arguments.push(getContentFolderByKey(data.key, state.config.flashpointPath)); + await removeService(state, 'server'); + runService(state, 'server', 'Server', state.config.flashpointPath, {}, mad4fpServerCopy); + } else if (!data.mad4fp && activeServerInfo && activeServerInfo.mad4fp && !configServer.mad4fp) { + // Swap to mad4fp server + await removeService(state, 'server'); + runService(state, 'server', 'Server', state.config.flashpointPath, {}, configServer); + } + } + } + + await launchCuration(data.key, data.meta, data.symlinkCurationContent, skipLink, { fpPath: path.resolve(state.config.flashpointPath), htdocsPath: state.preferences.htdocsFolderPath, - native: state.preferences.nativePlatforms.some(p => p === data.platform) || false, + native: state.preferences.nativePlatforms.some(p => p === data.meta.platform), execMappings: state.execMappings, lang: state.languageContainer, isDev: state.isDev, @@ -1107,8 +1142,8 @@ export function registerRequestCallbacks(state: BackState): void { openExternal: state.socketServer.openExternal(event.client), runGame: runGameFactory(state), }, - state.apiEmitters.games.onWillLaunchCurationAddApp, - state.apiEmitters.games.onDidLaunchCurationAddApp); + state.apiEmitters.games.onWillLaunchCurationGame, + state.apiEmitters.games.onDidLaunchCurationGame); } catch (e) { log.error('Launcher', e + ''); } @@ -1139,7 +1174,7 @@ export function registerRequestCallbacks(state: BackState): void { }); state.socketServer.register(BackIn.EXPORT_META_EDIT, async (event, id, properties) => { - const game = await GameManager.findGame(id); + const game = await GameManager.findGame(id, undefined, true); if (game) { const meta: MetaEditMeta = { id: game.id, diff --git a/src/back/types.ts b/src/back/types.ts index 76c71494f..3705f5766 100644 --- a/src/back/types.ts +++ b/src/back/types.ts @@ -163,14 +163,10 @@ export type ApiEmittersState = Readonly<{ onLog: ApiEmitter; games: Readonly<{ onWillLaunchGame: ApiEmitter; - onWillLaunchAddApp: ApiEmitter; onWillLaunchCurationGame: ApiEmitter; - onWillLaunchCurationAddApp: ApiEmitter; onWillUninstallGameData: ApiEmitter; onDidLaunchGame: ApiEmitter; - onDidLaunchAddApp: ApiEmitter; onDidLaunchCurationGame: ApiEmitter; - onDidLaunchCurationAddApp: ApiEmitter; onDidUpdateGame: ApiEmitter<{oldGame: flashpoint.Game, newGame: flashpoint.Game}>; onDidRemoveGame: ApiEmitter; onDidUpdatePlaylist: ApiEmitter<{oldPlaylist: flashpoint.Playlist, newPlaylist: flashpoint.Playlist}>; diff --git a/src/back/util/misc.ts b/src/back/util/misc.ts index fd8041b3a..d5bba4367 100644 --- a/src/back/util/misc.ts +++ b/src/back/util/misc.ts @@ -3,7 +3,6 @@ import { createTagsFromLegacy } from '@back/importGame'; import { ManagedChildProcess, ProcessOpts } from '@back/ManagedChildProcess'; import { SocketServer } from '@back/SocketServer'; import { BackState, ShowMessageBoxFunc, ShowOpenDialogFunc, ShowSaveDialogFunc, StatusState } from '@back/types'; -import { AdditionalApp } from '@database/entity/AdditionalApp'; import { Game } from '@database/entity/Game'; import { Playlist } from '@database/entity/Playlist'; import { Tag } from '@database/entity/Tag'; @@ -165,8 +164,53 @@ export async function execProcess(state: BackState, proc: IBackProcessInfo, sync } } -export function createAddAppFromLegacy(addApps: Legacy_IAdditionalApplicationInfo[], game: Game): AdditionalApp[] { - return addApps.map(a => { +export function createChildFromFromLegacyAddApp(addApps: Legacy_IAdditionalApplicationInfo[], game: Game): Game[] { + const retVal: Game[] = []; + for (const addApp of addApps) { + if (addApp.applicationPath === ':message:') { + game.message = addApp.launchCommand; + } else if (addApp.applicationPath === ':extras:') { + game.extras = addApp.launchCommand; + game.extrasName = addApp.name; + } else { + const newGame = new Game(); + Object.assign(newGame, { + id: addApp.id, + title: addApp.name, + applicationPath: addApp.applicationPath, + launchCommand: addApp.launchCommand, + parentGame: game, + parentGameId: game.id, + library: game.library, + alternateTitles: '', + series: '', + developer: '', + publisher: '', + dateAdded: '0000-00-00 00:00:00.000', + dateModified: '0000-00-00 00:00:00.000', + platform: '', + broken: false, + extreme: game.extreme, + playMode: '', + status: '', + notes: '', + source: '', + releaseDate: '', + version: '', + originalDescription: '', + language: '', + orderTitle: addApp.name.toLowerCase(), + activeDataId: undefined, + activeDataOnDisk: false, + tags: [], + extras: undefined, + extrasName: undefined, + message: undefined + }); + retVal.push(newGame); + } + } + /* return addApps.map(a => { return { id: a.id, name: a.name, @@ -176,14 +220,15 @@ export function createAddAppFromLegacy(addApps: Legacy_IAdditionalApplicationInf waitForExit: a.waitForExit, parentGame: game }; - }); + });*/ + return retVal; } export async function createGameFromLegacy(game: Legacy_IGameInfo, tagCache: Record): Promise { const newGame = new Game(); Object.assign(newGame, { id: game.id, - parentGameId: game.id, + parentGameId: null, title: game.title, alternateTitles: game.alternateTitles, series: game.series, @@ -208,7 +253,7 @@ export async function createGameFromLegacy(game: Legacy_IGameInfo, tagCache: Rec library: game.library, orderTitle: game.orderTitle, placeholder: false, - addApps: [], + children: [], activeDataOnDisk: false }); return newGame; @@ -271,7 +316,7 @@ export function runService(state: BackState, id: string, name: string, basePath: proc.spawn(); } catch (error) { log.error(SERVICES_SOURCE, `An unexpected error occurred while trying to run the background process "${proc.name}".` + - ` ${error.toString()}`); + ` ${(error as Error).toString()}`); } state.apiEmitters.services.onServiceNew.fire(proc); return proc; diff --git a/src/database/entity/AdditionalApp.ts b/src/database/entity/AdditionalApp.ts deleted file mode 100644 index ac6f499ab..000000000 --- a/src/database/entity/AdditionalApp.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; -import { Game } from './Game'; - -@Entity() -export class AdditionalApp { - @PrimaryGeneratedColumn('uuid') - /** ID of the additional application (unique identifier) */ - id: string; - @Column() - /** Path to the application that runs the additional application */ - applicationPath: string; - @Column() - /** - * If the additional application should run before the game. - * (If true, this will always run when the game is launched) - * (If false, this will only run when specifically launched) - */ - autoRunBefore: boolean; - @Column() - /** Command line argument(s) passed to the application to launch the game */ - launchCommand: string; - @Column() - /** Name of the additional application */ - @Column({collation: 'NOCASE'}) - name: string; - @Column() - /** Wait for this to exit before the Game will launch (if starting before launch) */ - waitForExit: boolean; - @ManyToOne(type => Game, game => game.addApps) - /** Parent of this add app */ - parentGame: Game; -} diff --git a/src/database/entity/Game.ts b/src/database/entity/Game.ts index 6adba1c99..d9118e76a 100644 --- a/src/database/entity/Game.ts +++ b/src/database/entity/Game.ts @@ -1,5 +1,4 @@ import { BeforeUpdate, Column, Entity, Index, JoinTable, ManyToMany, ManyToOne, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; -import { AdditionalApp } from './AdditionalApp'; import { GameData } from './GameData'; import { Tag } from './Tag'; @@ -17,11 +16,15 @@ export class Game { /** ID of the game (unique identifier) */ id: string; - @ManyToOne(type => Game) + @ManyToOne((type) => Game, (game) => game.children) parentGame?: Game; - @Column({ nullable: true }) - parentGameId?: string; + @Column({ type: 'varchar', nullable: true }) + parentGameId: string | null; + + // Careful: potential infinite loop here. DO NOT eager-load this. + @OneToMany((type) => Game, (game) => game.parentGame, { cascade: true }) + children?: Game[]; @Column({collation: 'NOCASE'}) @Index('IDX_gameTitle') @@ -120,19 +123,12 @@ export class Game { /** The title but reconstructed to be suitable for sorting and ordering (and not be shown visually) */ orderTitle: string; - @OneToMany(type => AdditionalApp, addApp => addApp.parentGame, { - cascade: true, - eager: true - }) - /** All attached Additional Apps of a game */ - addApps: AdditionalApp[]; - /** If the game is a placeholder (and can therefore not be saved) */ placeholder: boolean; /** ID of the active data */ - @Column({ nullable: true }) - activeDataId?: number; + @Column({ type: 'integer', nullable: true }) + activeDataId: number | null; /** Whether the data is present on disk */ @Column({ default: false }) @@ -141,6 +137,15 @@ export class Game { @OneToMany(type => GameData, datas => datas.game) data?: GameData[]; + @Column({ type: 'varchar', nullable: true }) + extras: string | null; + + @Column({ type: 'varchar', nullable: true }) + extrasName: string | null; + + @Column({ type: 'varchar', nullable: true }) + message: string | null; + // This doesn't run... sometimes. @BeforeUpdate() updateTagsStr() { diff --git a/src/database/migration/1648251821422-ChildCurations.ts b/src/database/migration/1648251821422-ChildCurations.ts new file mode 100644 index 000000000..146fac36a --- /dev/null +++ b/src/database/migration/1648251821422-ChildCurations.ts @@ -0,0 +1,20 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class ChildCurations1648251821422 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE game ADD extras varchar`, undefined); + await queryRunner.query(`ALTER TABLE game ADD extrasName varchar`, undefined); + await queryRunner.query(`ALTER TABLE game ADD message varchar`, undefined); + await queryRunner.query(`UPDATE game SET message = a.launchCommand FROM additional_app a WHERE game.id=a.parentGameId AND a.applicationPath=':message:'`); + await queryRunner.query(`UPDATE game SET extras = a.launchCommand, extrasName = a.name FROM additional_app a WHERE game.id = a.parentGameId AND a.applicationPath = ':extras:'`, undefined); + await queryRunner.query(`UPDATE game SET parentGameId = NULL WHERE id IS parentGameId`, undefined); + // Default value for dateAdded and dateModified values is the unix epoch. Later UI will have to realize this. + await queryRunner.query(`INSERT INTO game SELECT a.id,a.parentGameId,a.name AS title,"" AS alternateTitles,"" AS series,"" AS developer,"" AS publisher,"1970-01-01 00:00:00.000" AS dateAdded,"1970-01-01 00:00:00.000" AS dateModified,"" AS platform,false AS broken,g.extreme AS extreme,"" AS playMode,"" AS status,"" AS notes,"" AS source,a.applicationPath,a.launchCommand,"" AS releaseDate,"" AS version,"" AS originalDescription,"" AS language,library,LOWER(a.name) AS orderTitle,NULL AS activeDataId,false AS activeDataOnDisk,"" AS tagsStr,NULL as extras,NULL AS extrasName,NULL AS message FROM additional_app a INNER JOIN game g ON a.parentGameId = g.id WHERE a.applicationPath != ':message:' AND a.applicationPath != ':extras:'`, undefined); + await queryRunner.query(`DROP TABLE additional_app`, undefined); + } + + public async down(queryRunner: QueryRunner): Promise { + } + +} diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx index a120563d5..c8c8a72ce 100644 --- a/src/renderer/app.tsx +++ b/src/renderer/app.tsx @@ -888,8 +888,13 @@ export class App extends React.Component { const strings = this.props.main.lang; const library = getBrowseSubPath(this.props.location.pathname); window.Shared.back.request(BackIn.DELETE_GAME, gameId) - .then(() => { this.setViewQuery(library); }) - .catch((error) => { + .then((deleteResults) => { + this.props.dispatchMain({ + type: MainActionType.SET_GAMES_TOTAL, + total: deleteResults.gamesTotal, + }); + this.setViewQuery(library); + }).catch((error) => { log.error('Launcher', `Error deleting game: ${error}`); alert(strings.dialog.unableToDeleteGame + '\n\n' + error); }); diff --git a/src/renderer/components/CurateBox.tsx b/src/renderer/components/CurateBox.tsx index 08d2cdf91..9d47080fc 100644 --- a/src/renderer/components/CurateBox.tsx +++ b/src/renderer/components/CurateBox.tsx @@ -26,7 +26,6 @@ import { toForcedURL } from '../Util'; import { LangContext } from '../util/lang'; import { pathTo7z } from '../util/SevenZip'; import { ConfirmElement, ConfirmElementArgs } from './ConfirmElement'; -import { CurateBoxAddApp } from './CurateBoxAddApp'; import { CurateBoxRow } from './CurateBoxRow'; import { CurateBoxWarnings, CurationWarnings, getWarningCount } from './CurateBoxWarnings'; import { DropdownInputField } from './DropdownInputField'; @@ -87,7 +86,7 @@ export function CurateBox(props: CurateBoxProps) { saveThrottle(() => { if (props.curation) { const metaPath = path.join(getCurationFolder2(props.curation), 'meta.yaml'); - const meta = YAML.stringify(convertEditToCurationMetaFile(props.curation.meta, props.tagCategories, props.curation.addApps)); + const meta = YAML.stringify(convertEditToCurationMetaFile(props.curation.meta, props.tagCategories)); fs.writeFile(metaPath, meta); console.log('Auto-Saved Curation'); } @@ -205,6 +204,10 @@ export function CurateBox(props: CurateBoxProps) { const onOriginalDescriptionChange = useOnInputChange('originalDescription', key, props.dispatch); const onCurationNotesChange = useOnInputChange('curationNotes', key, props.dispatch); const onMountParametersChange = useOnInputChange('mountParameters', key, props.dispatch); + const onParentGameIdChange = useOnInputChange('parentGameId', key, props.dispatch); + const onMessageChange = useOnInputChange('message', key, props.dispatch); + const onExtrasChange = useOnInputChange('extras', key, props.dispatch); + const onExtrasNameChange = useOnInputChange('extrasName', key, props.dispatch); // Callbacks for the fields (onItemSelect) const onPlayModeSelect = useCallback(transformOnItemSelect(onPlayModeChange), [onPlayModeChange]); const onStatusSelect = useCallback(transformOnItemSelect(onStatusChange), [onStatusChange]); @@ -307,7 +310,6 @@ export function CurateBox(props: CurateBoxProps) { await window.Shared.back.request(BackIn.LAUNCH_CURATION, { key: props.curation.key, meta: props.curation.meta, - addApps: props.curation.addApps.map(addApp => addApp.meta), mad4fp: mad4fp, symlinkCurationContent: props.symlinkCurationContent }); @@ -378,42 +380,6 @@ export function CurateBox(props: CurateBoxProps) { }); } }, [props.dispatch, props.curation && props.curation.key]); - // Callback for when the new additional application button is clicked - const onNewAddApp = useCallback(() => { - if (props.curation) { - props.dispatch({ - type: 'new-addapp', - payload: { - key: props.curation.key, - type: 'normal' - } - }); - } - }, [props.dispatch, props.curation && props.curation.key]); - // Callback for when adding an Extras add app - const onAddExtras = useCallback(() => { - if (props.curation) { - props.dispatch({ - type: 'new-addapp', - payload: { - key: props.curation.key, - type: 'extras' - } - }); - } - }, [props.dispatch, props.curation && props.curation.key]); - // Callback for when adding a Message add app - const onAddMessage = useCallback(() => { - if (props.curation) { - props.dispatch({ - type: 'new-addapp', - payload: { - key: props.curation.key, - type: 'message' - } - }); - } - }, [props.dispatch, props.curation && props.curation.key]); // Callback for when the export button is clicked const onExportClick = useCallback(async () => { if (props.curation) { @@ -468,7 +434,7 @@ export function CurateBox(props: CurateBoxProps) { .catch((error) => { /* No file is okay, ignore error */ }); // Save working meta const metaPath = path.join(getCurationFolder2(curation), 'meta.yaml'); - const meta = YAML.stringify(convertEditToCurationMetaFile(curation.meta, props.tagCategories, curation.addApps)); + const meta = YAML.stringify(convertEditToCurationMetaFile(curation.meta, props.tagCategories)); const statusProgress = newProgress(props.curation.key, progressDispatch); ProgressDispatch.setText(statusProgress, 'Exporting Curation...'); ProgressDispatch.setUsePercentDone(statusProgress, false); @@ -514,42 +480,13 @@ export function CurateBox(props: CurateBoxProps) { const disabled = props.curation ? props.curation.locked : false; // Whether the platform used by the curation is native locked + // eslint-disable-next-line @typescript-eslint/no-unused-vars const native = useMemo(() => { if (props.curation && props.curation.meta.platform) { isPlatformNativeLocked(props.curation.meta.platform); } return false; }, [props.curation]); - // Render additional application elements - const addApps = useMemo(() => ( - <> - { strings.browse.additionalApplications }: - { props.curation && props.curation.addApps.length > 0 ? ( - - - { props.curation.addApps.map(addApp => ( - - )) } - -
- ) : undefined } - - ), [ - props.curation && props.curation.addApps, - props.curation && props.curation.key, - props.symlinkCurationContent, - props.dispatch, - native, - disabled - ]); // Count the number of collisions const collisionCount: number | undefined = useMemo(() => { @@ -686,6 +623,14 @@ export function CurateBox(props: CurateBoxProps) { onChange={onTitleChange} { ...sharedInputProps } /> + + + 0 ? 'input-field--info' : ''} { ...sharedInputProps } /> + + + + + + + + +
- {/* Additional Application */} -
- {addApps} -
-
- - - -
-
-
-
{/* Content */}
@@ -1001,14 +943,26 @@ function transformOnItemSelect(callback: (event: InputElementOnChangeEvent) => v function useOnInputChange(property: keyof EditCurationMeta, key: string | undefined, dispatch: React.Dispatch) { return useCallback((event: InputElementOnChangeEvent) => { if (key !== undefined) { - dispatch({ - type: 'edit-curation-meta', - payload: { - key: key, - property: property, - value: event.currentTarget.value - } - }); + // If it's one of the nullable types, treat '' as undefined. + if (property === 'parentGameId' || property === 'extras' || property === 'extrasName' || property === 'message') { + dispatch({ + type: 'edit-curation-meta', + payload: { + key: key, + property: property, + value: event.currentTarget.value === '' ? undefined : event.currentTarget.value + } + }); + } else { + dispatch({ + type: 'edit-curation-meta', + payload: { + key: key, + property: property, + value: event.currentTarget.value + } + }); + } } }, [dispatch, key]); } @@ -1202,6 +1156,18 @@ export function getCurationWarnings(curation: EditCuration, suggestions: Partial if (!warns.noLaunchCommand) { warns.invalidLaunchCommand = invalidLaunchCommandWarnings(getContentFolderByKey2(curation.key), launchCommand, strings); } + // @TODO check that the parentGameId is valid in some synchronous manner. + /* const parentId = curation.meta.parentGameId || ''; + log.debug("getCurationWarnings", "parentId: "+parentId); + if (parentId !== '') { + warns.invalidParentGameId = true; + window.Shared.back.request(BackIn.GET_GAME, parentId).then((result) => { + warns.invalidParentGameId = result === undefined; + }); + } else { + // If the parentGameId is undefined/empty, it's just a non-child game. That's fine. + warns.invalidParentGameId = false; + }*/ warns.noLogo = !curation.thumbnail.exists; warns.noScreenshot = !curation.screenshot.exists; warns.noTags = (!curation.meta.tags || curation.meta.tags.length === 0); diff --git a/src/renderer/components/CurateBoxAddApp.tsx b/src/renderer/components/CurateBoxAddApp.tsx deleted file mode 100644 index 665ba2bd1..000000000 --- a/src/renderer/components/CurateBoxAddApp.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { BackIn } from '@shared/back/types'; -import { EditAddAppCuration, EditAddAppCurationMeta } from '@shared/curate/types'; -import * as React from 'react'; -import { useCallback } from 'react'; -import { CurationAction } from '../context/CurationContext'; -import { LangContext } from '../util/lang'; -import { CurateBoxRow } from './CurateBoxRow'; -import { InputField } from './InputField'; -import { SimpleButton } from './SimpleButton'; - -export type CurateBoxAddAppProps = { - /** Key of the curation the displayed additional application belongs to. */ - curationKey: string; - /** Meta data for the additional application to display. */ - curation: EditAddAppCuration; - /** If editing any fields of this should be disabled. */ - disabled?: boolean; - /** Dispatcher for the curate page state reducer. */ - dispatch: React.Dispatch; - /** Platform of the game this belongs to. */ - platform?: string; - /** Whether to symlink curation content before running */ - symlinkCurationContent: boolean; - /** Callback for the "onKeyDown" event for all input fields. */ - onInputKeyDown?: (event: React.KeyboardEvent) => void; -}; - -export function CurateBoxAddApp(props: CurateBoxAddAppProps) { - // Callbacks for the fields (onChange) - const curationKey = props.curationKey; - const key = props.curation.key; - const onHeadingChange = useOnInputChange('heading', key, curationKey, props.dispatch); - const onApplicationPathChange = useOnInputChange('applicationPath', key, curationKey, props.dispatch); - const onLaunchCommandChange = useOnInputChange('launchCommand', key, curationKey, props.dispatch); - // Misc. - const editable = true; - const disabled = props.disabled; - // Localized strings - const strings = React.useContext(LangContext); - const specialType = props.curation.meta.applicationPath === ':extras:' || props.curation.meta.applicationPath === ':message:'; - let lcString = strings.browse.launchCommand; - let lcPlaceholderString = strings.browse.noLaunchCommand; - // Change Launch Command strings depending on add app type - switch (props.curation.meta.applicationPath) { - case ':message:': - lcString = strings.curate.message; - lcPlaceholderString = strings.curate.noMessage; - break; - case ':extras:': - lcString = strings.curate.folderName; - lcPlaceholderString = strings.curate.noFolderName; - break; - } - // Callback for the "remove" button - const onRemove = useCallback(() => { - props.dispatch({ - type: 'remove-addapp', - payload: { - curationKey: props.curationKey, - key: props.curation.key - } - }); - }, [props.curationKey, props.curation.key, props.dispatch]); - // Callback for the "run" button - const onRun = useCallback(() => { - return window.Shared.back.request(BackIn.LAUNCH_CURATION_ADDAPP, { - curationKey: props.curationKey, - curation: props.curation, - platform: props.platform, - symlinkCurationContent: props.symlinkCurationContent - }); - }, [props.curation && props.curation.meta && props.curationKey, props.symlinkCurationContent, props.platform]); - // Render - return ( - - - - - { specialType ? undefined : ( - - - - ) } - - - - - - - ); -} - -type InputElement = HTMLInputElement | HTMLTextAreaElement; - -/** Subset of the input elements on change event, with only the properties used by the callbacks. */ -type InputElementOnChangeEvent = { - currentTarget: { - value: React.ChangeEvent['currentTarget']['value'] - } -} - -/** - * Create a callback for InputField's onChange. - * When called, the callback will set the value of a metadata property to the value of the input field. - * @param property Property the input field should change. - * @param curationKey Key of the curation the additional application belongs to. - * @param key Key of the additional application to edit. - * @param dispatch Dispatcher to use. - */ -function useOnInputChange(property: keyof EditAddAppCurationMeta, key: string, curationKey: string, dispatch: React.Dispatch) { - return useCallback((event: InputElementOnChangeEvent) => { - if (key !== undefined) { - dispatch({ - type: 'edit-addapp-meta', - payload: { - curationKey: curationKey, - key: key, - property: property, - value: event.currentTarget.value - } - }); - } - }, [dispatch, key]); -} diff --git a/src/renderer/components/CurateBoxWarnings.tsx b/src/renderer/components/CurateBoxWarnings.tsx index ec99a68c4..ccc0775f7 100644 --- a/src/renderer/components/CurateBoxWarnings.tsx +++ b/src/renderer/components/CurateBoxWarnings.tsx @@ -13,6 +13,8 @@ export type CurationWarnings = { noLaunchCommand?: boolean; /** If the launch command is not a url with the "http" protocol and doesn't point to a file in 'content' */ invalidLaunchCommand?: string[]; + /** If the parentGameId is not valid. */ + invalidParentGameId?: boolean; /** If the release date is invalid (incorrectly formatted). */ releaseDateInvalid?: boolean; /** If the application path value isn't used by any other game. */ diff --git a/src/renderer/components/GameDataBrowser.tsx b/src/renderer/components/GameDataBrowser.tsx index 2686e4b18..f222285ad 100644 --- a/src/renderer/components/GameDataBrowser.tsx +++ b/src/renderer/components/GameDataBrowser.tsx @@ -31,7 +31,7 @@ export type GameDataBrowserProps = { game: Game; onClose: () => void; onEditGame: (game: Partial) => void; - onUpdateActiveGameData: (activeDataOnDisk: boolean, activeDataId?: number) => void; + onUpdateActiveGameData: (activeDataOnDisk: boolean, activeDataId: number | null) => void; onForceUpdateGameData: () => void; } @@ -122,7 +122,7 @@ export class GameDataBrowser extends React.Component { await window.Shared.back.request(BackIn.DELETE_GAME_DATA, id); if (this.props.game.activeDataId === id) { - this.props.onUpdateActiveGameData(false); + this.props.onUpdateActiveGameData(false, null); } const newPairedData = [...this.state.pairedData]; const idx = newPairedData.findIndex(pr => pr.id === id); @@ -146,7 +146,7 @@ export class GameDataBrowser extends React.Component this.onUpdateTitle(index, title)} onUpdateParameters={(parameters) => this.onUpdateParameters(index, parameters)} onActiveToggle={() => { - this.props.onUpdateActiveGameData(data.presentOnDisk, data.id); + this.props.onUpdateActiveGameData(data.presentOnDisk, data.id === this.props.game.activeDataId ? null : data.id); }} onUninstall={() => { window.Shared.back.request(BackIn.UNINSTALL_GAME_DATA, data.id) diff --git a/src/renderer/components/RightBrowseSidebar.tsx b/src/renderer/components/RightBrowseSidebar.tsx index 115f712b7..2994d4f66 100644 --- a/src/renderer/components/RightBrowseSidebar.tsx +++ b/src/renderer/components/RightBrowseSidebar.tsx @@ -7,7 +7,6 @@ import { WithConfirmDialogProps } from '@renderer/containers/withConfirmDialog'; import { BackIn, BackOut, BackOutTemplate, TagSuggestion } from '@shared/back/types'; import { LOGOS, SCREENSHOTS } from '@shared/constants'; import { wrapSearchTerm } from '@shared/game/GameFilter'; -import { ModelUtils } from '@shared/game/util'; import { GamePropSuggestions, PickType, ProcessAction } from '@shared/interfaces'; import { LangContainer } from '@shared/lang'; import { deepCopy, generateTagFilterGroup, sizeToString } from '@shared/Util'; @@ -19,7 +18,6 @@ import { WithPreferencesProps } from '../containers/withPreferences'; import { WithSearchProps } from '../containers/withSearch'; import { getGameImagePath, getGameImageURL } from '../Util'; import { LangContext } from '../util/lang'; -import { uuid } from '../util/uuid'; import { CheckBox } from './CheckBox'; import { ConfirmElement, ConfirmElementArgs } from './ConfirmElement'; import { DropdownInputField } from './DropdownInputField'; @@ -28,7 +26,8 @@ import { GameImageSplit } from './GameImageSplit'; import { ImagePreview } from './ImagePreview'; import { InputElement, InputField } from './InputField'; import { OpenIcon } from './OpenIcon'; -import { RightBrowseSidebarAddApp } from './RightBrowseSidebarAddApp'; +import { RightBrowseSidebarChild } from './RightBrowseSidebarAddApp'; +import { RightBrowseSidebarExtra } from './RightBrowseSidebarExtra'; import { SimpleButton } from './SimpleButton'; import { TagInputField } from './TagInputField'; @@ -53,6 +52,8 @@ type OwnProps = { onDeselectPlaylist: () => void; /** Called when the playlist notes for the selected game has been changed */ onEditPlaylistNotes: (text: string) => void; + /** Called when a child game needs to be deleted. */ + onDeleteGame: (gameId: string) => void; /** If the "edit mode" is currently enabled */ isEditing: boolean; /** If the selected game is a new game being created */ @@ -68,7 +69,7 @@ type OwnProps = { onOpenExportMetaEdit: (gameId: string) => void; onEditGame: (game: Partial) => void; - onUpdateActiveGameData: (activeDataOnDisk: boolean, activeDataId?: number) => void; + onUpdateActiveGameData: (activeDataOnDisk: boolean, activeDataId: number | null) => void; }; export type RightBrowseSidebarProps = OwnProps & WithPreferencesProps & WithSearchProps & WithConfirmDialogProps; @@ -109,6 +110,9 @@ export class RightBrowseSidebar extends React.Component this.props.onEditGame({ applicationPath: text })); onNotesChange = this.wrapOnTextChange((game, text) => this.props.onEditGame({ notes: text })); onOriginalDescriptionChange = this.wrapOnTextChange((game, text) => this.props.onEditGame({ originalDescription: text })); + onMessageChange = this.wrapOnTextChange((game, text) => this.props.onEditGame({ message: text })); + onExtrasChange = this.wrapOnTextChange((game, text) => this.props.onEditGame({ extras: text})); + onExtrasNameChange = this.wrapOnTextChange((game, text) => this.props.onEditGame({ extrasName: text})); onBrokenChange = this.wrapOnCheckBoxChange(game => { if (this.props.currentGame) { this.props.onEditGame({ broken: !this.props.currentGame.broken }); @@ -194,7 +198,7 @@ export class RightBrowseSidebar extends React.Component
+
+

{strings.message}:

+ +
+
+

{strings.extrasName}:

+ +
+
+

{strings.extras}:

+ +

{strings.dateAdded}:

) : undefined } + {(game.extras && game.extrasName) ? ( +
+
+

{strings.extras}:

+
+ +
+ ) : undefined } {/* -- Additional Applications -- */} - { editable || (currentAddApps && currentAddApps.length > 0) ? ( + { editable || (currentChildren && currentChildren.length > 0) ? (

{strings.additionalApplications}:

- { editable ? ( - - ) : undefined }
- { currentAddApps && currentAddApps.map((addApp) => ( - ( + + onEdit={this.onChildEdit} + onLaunch={() => { + addApp && this.props.onGameLaunch(addApp.id) + .then(this.onForceUpdateGameData); + }} + onDelete={this.onChildDelete} /> )) }
) : undefined } @@ -920,27 +962,35 @@ export class RightBrowseSidebar extends React.Component { + onChildDelete = (childId: string): void => { if (this.props.currentGame) { - const newAddApps = deepCopy(this.props.currentGame.addApps); - if (!newAddApps) { throw new Error('editAddApps is missing.'); } - const index = newAddApps.findIndex(addApp => addApp.id === addAppId); + const newChildren = deepCopy(this.props.currentGame.children); + if (!newChildren) { throw new Error('editAddApps is missing.'); } + const index = newChildren.findIndex(addApp => addApp.id === childId); if (index === -1) { throw new Error('Cant remove additional application because it was not found.'); } - newAddApps.splice(index, 1); - this.props.onEditGame({ addApps: newAddApps }); + newChildren.splice(index, 1); + this.props.onDeleteGame(childId); + // @TODO make this better. + this.props.onEditGame({children: newChildren}); } } - onNewAddAppClick = (): void => { - if (!this.props.currentGame) { throw new Error('Unable to add a new AddApp. "currentGame" is missing.'); } - const newAddApp = ModelUtils.createAddApp(this.props.currentGame); - newAddApp.id = uuid(); - this.props.onEditGame({ addApps: [...this.props.currentGame.addApps, ...[newAddApp]] }); - } + onChildEdit = (childId: string, diff: Partial) => { + if (this.props.currentGame && this.props.currentGame.children) { + const newChildren = [...this.props.currentGame.children]; + const childIndex = this.props.currentGame.children.findIndex(child => child.id === childId); + if (childIndex !== -1) { + newChildren[childIndex] = {...newChildren[childIndex], ...diff} as Game; + this.props.onEditGame({children: newChildren}); + } else { + throw new Error('Can\'t edit additional application because it was not found.'); + } + } + }; onScreenshotClick = (): void => { this.setState({ showPreview: true }); diff --git a/src/renderer/components/RightBrowseSidebarAddApp.tsx b/src/renderer/components/RightBrowseSidebarAddApp.tsx index 8890b1ed7..b0ba87d73 100644 --- a/src/renderer/components/RightBrowseSidebarAddApp.tsx +++ b/src/renderer/components/RightBrowseSidebarAddApp.tsx @@ -1,47 +1,44 @@ -import { AdditionalApp } from '@database/entity/AdditionalApp'; +import { Game } from '@database/entity/Game'; import { LangContainer } from '@shared/lang'; import * as React from 'react'; import { LangContext } from '../util/lang'; -import { CheckBox } from './CheckBox'; import { ConfirmElement, ConfirmElementArgs } from './ConfirmElement'; import { InputField } from './InputField'; import { OpenIcon } from './OpenIcon'; -export type RightBrowseSidebarAddAppProps = { +export type RightBrowseSidebarChildProps = { /** Additional Application to show and edit */ - addApp: AdditionalApp; + child: Game; /** Called when a field is edited */ - onEdit?: () => void; + onEdit?: (childId: string, diff: Partial) => void; /** Called when a field is edited */ - onDelete?: (addAppId: string) => void; + onDelete?: (childId: string) => void; /** Called when the launch button is clicked */ - onLaunch?: (addAppId: string) => void; + onLaunch?: (childId: string) => void; /** If the editing is disabled (it cant go into "edit mode") */ editDisabled?: boolean; }; -export interface RightBrowseSidebarAddApp { +export interface RightBrowseSidebarChild { context: LangContainer; } /** Displays an additional application for a game in the right sidebar of BrowsePage. */ -export class RightBrowseSidebarAddApp extends React.Component { - onNameEditDone = this.wrapOnTextChange((addApp, text) => { addApp.name = text; }); - onApplicationPathEditDone = this.wrapOnTextChange((addApp, text) => { addApp.applicationPath = text; }); - onLaunchCommandEditDone = this.wrapOnTextChange((addApp, text) => { addApp.launchCommand = text; }); - onAutoRunBeforeChange = this.wrapOnCheckBoxChange((addApp) => { addApp.autoRunBefore = !addApp.autoRunBefore; }); - onWaitForExitChange = this.wrapOnCheckBoxChange((addApp) => { addApp.waitForExit = !addApp.waitForExit; }); +export class RightBrowseSidebarChild extends React.Component { + onNameEditDone = this.wrapOnTextChange((addApp, text) => { this.onEdit({title: text}); }); + onApplicationPathEditDone = this.wrapOnTextChange((addApp, text) => { this.onEdit({applicationPath: text}); }); + onLaunchCommandEditDone = this.wrapOnTextChange((addApp, text) => { this.onEdit({launchCommand: text}); }); render() { const allStrings = this.context; const strings = allStrings.browse; - const { addApp, editDisabled } = this.props; + const { child: addApp, editDisabled } = this.props; return (
{/* Title & Launch Button */}
@@ -71,27 +68,8 @@ export class RightBrowseSidebarAddApp extends React.Component
- {/* Auto Run Before */} -
-
- -

{strings.autoRunBefore}

-
-
{/* Wait for Exit */}
-
- -

{strings.waitForExit}

-
{/* Delete Button */} { !editDisabled ? ( { if (this.props.onLaunch) { - this.props.onLaunch(this.props.addApp.id); + this.props.onLaunch(this.props.child.id); } } onDeleteClick = (): void => { if (this.props.onDelete) { - this.props.onDelete(this.props.addApp.id); + this.props.onDelete(this.props.child.id); } } - onEdit(): void { + onEdit(diff: Partial): void { if (this.props.onEdit) { - this.props.onEdit(); + this.props.onEdit(this.props.child.id, diff); } } /** Create a wrapper for a EditableTextWrap's onEditDone callback (this is to reduce redundancy). */ - wrapOnTextChange(func: (addApp: AdditionalApp, text: string) => void): (event: React.ChangeEvent) => void { + wrapOnTextChange(func: (addApp: Game, text: string) => void): (event: React.ChangeEvent) => void { return (event) => { - const addApp = this.props.addApp; + const addApp = this.props.child; if (addApp) { func(addApp, event.currentTarget.value); this.forceUpdate(); @@ -148,16 +126,5 @@ export class RightBrowseSidebarAddApp extends React.Component void) { - return () => { - if (!this.props.editDisabled) { - func(this.props.addApp); - this.onEdit(); - this.forceUpdate(); - } - }; - } - static contextType = LangContext; } diff --git a/src/renderer/components/RightBrowseSidebarExtra.tsx b/src/renderer/components/RightBrowseSidebarExtra.tsx new file mode 100644 index 000000000..bd03aec6a --- /dev/null +++ b/src/renderer/components/RightBrowseSidebarExtra.tsx @@ -0,0 +1,50 @@ +import { LangContainer } from '@shared/lang'; +import * as React from 'react'; +import { LangContext } from '../util/lang'; +import { InputField } from './InputField'; + +export type RightBrowseSidebarExtraProps = { + /** Extras to show and edit */ + // These two are explicitly non-nullable. + extrasPath: string; + extrasName: string; + /** Called when the launch button is clicked */ + onLaunch?: (extrasPath: string) => void; +}; + +export interface RightBrowseSidebarExtra { + context: LangContainer; +} + +/** Displays an additional application for a game in the right sidebar of BrowsePage. */ +export class RightBrowseSidebarExtra extends React.Component { + + render() { + const allStrings = this.context; + const strings = allStrings.browse; + return ( +
+ {/* Title & Launch Button */} +
+ + +
+
+ ); + } + + onLaunchClick = (): void => { + if (this.props.onLaunch) { + this.props.onLaunch(this.props.extrasPath); + } + } + + static contextType = LangContext; +} diff --git a/src/renderer/components/pages/BrowsePage.tsx b/src/renderer/components/pages/BrowsePage.tsx index 485b8c654..7cab74bfa 100644 --- a/src/renderer/components/pages/BrowsePage.tsx +++ b/src/renderer/components/pages/BrowsePage.tsx @@ -286,6 +286,7 @@ export class BrowsePage extends React.Component { + onUpdateActiveGameData = (activeDataOnDisk: boolean, activeDataId: number | null): void => { if (this.state.currentGame) { const newGame = new Game(); Object.assign(newGame, {...this.state.currentGame, activeDataOnDisk, activeDataId }); @@ -720,7 +721,7 @@ export class BrowsePage extends React.Component !c.delete)) { const metaPath = path.join(getCurationFolder2(curation), 'meta.yaml'); - const meta = YAML.stringify(convertEditToCurationMetaFile(curation.meta, props.tagCategories, curation.addApps)); + const meta = YAML.stringify(convertEditToCurationMetaFile(curation.meta, props.tagCategories)); try { fs.writeFileSync(metaPath, meta); } catch (error: any) { @@ -743,7 +743,6 @@ async function loadCurationFolder(key: string, fullPath: string, defaultGameMeta const loadedCuration: EditCuration = { key: key, meta: {}, - addApps: [], thumbnail: createCurationIndexImage(), screenshot: createCurationIndexImage(), content: [], @@ -790,13 +789,6 @@ async function loadCurationFolder(key: string, fullPath: string, defaultGameMeta await readCurationMeta(metaYamlPath, defaultGameMetaValues) .then(async (parsedMeta) => { loadedCuration.meta = parsedMeta.game; - for (let i = 0; i < parsedMeta.addApps.length; i++) { - const meta = parsedMeta.addApps[i]; - loadedCuration.addApps.push({ - key: uuid(), - meta: meta, - }); - } }) .catch((error) => { const formedMessage = `Error Parsing Curation Meta at ${metaYamlPath} - ${error.message}`; diff --git a/src/renderer/components/pages/DeveloperPage.tsx b/src/renderer/components/pages/DeveloperPage.tsx index 14386e6ce..06395db47 100644 --- a/src/renderer/components/pages/DeveloperPage.tsx +++ b/src/renderer/components/pages/DeveloperPage.tsx @@ -364,7 +364,7 @@ export class DeveloperPage extends React.Component { - this.setState({ text: text + filePath + '\n' + createTextBarProgress(current, files.length) }) + this.setState({ text: text + filePath + '\n' + createTextBarProgress(current, files.length) }); }) .catch((error) => { text = text + `Failure - ${fileName} - ERROR: ${error}\n`; diff --git a/src/renderer/context/CurationContext.ts b/src/renderer/context/CurationContext.ts index 295e19d7a..52947d36a 100644 --- a/src/renderer/context/CurationContext.ts +++ b/src/renderer/context/CurationContext.ts @@ -1,10 +1,9 @@ import { GameMetaDefaults } from '@shared/curate/defaultValues'; -import { generateExtrasAddApp, generateMessageAddApp, ParsedCurationMeta } from '@shared/curate/parse'; -import { CurationIndexImage, EditAddAppCurationMeta, EditCuration, EditCurationMeta, IndexedContent } from '@shared/curate/types'; +import { ParsedCurationMeta } from '@shared/curate/parse'; +import { CurationIndexImage, EditCuration, EditCurationMeta, IndexedContent } from '@shared/curate/types'; import { createContextReducer } from '../context-reducer/contextReducer'; import { ReducerAction } from '../context-reducer/interfaces'; import { createCurationIndexImage } from '../curate/importCuration'; -import { uuid } from '../util/uuid'; const curationDefaultState: CurationsState = { defaultMetaData: undefined, @@ -34,7 +33,7 @@ function curationReducer(prevState: CurationsState, action: CurationAction): Cur const index = nextCurations.findIndex(c => c.key === action.payload.key); if (index !== -1) { const prevCuration = nextCurations[index]; - const nextCuration = { ...prevCuration, addApps: [ ...prevCuration.addApps ] }; + const nextCuration = { ...prevCuration }; // Mark curation for deletion nextCuration.delete = true; nextCurations[index] = nextCuration; @@ -47,7 +46,7 @@ function curationReducer(prevState: CurationsState, action: CurationAction): Cur const index = nextCurations.findIndex(c => c.key === action.payload.key); if (index !== -1) { const prevCuration = nextCurations[index]; - const nextCuration = { ...prevCuration, addApps: [ ...prevCuration.addApps ] }; + const nextCuration = { ...prevCuration }; // Mark curation for deletion nextCuration.deleted = true; nextCurations[index] = nextCuration; @@ -59,17 +58,9 @@ function curationReducer(prevState: CurationsState, action: CurationAction): Cur const nextCurations = [ ...prevState.curations ]; const index = ensureCurationIndex(nextCurations, action.payload.key); const prevCuration = nextCurations[index]; - const nextCuration = { ...prevCuration, addApps: [ ...prevCuration.addApps ] }; + const nextCuration = { ...prevCuration }; const parsedMeta = action.payload.parsedMeta; nextCuration.meta = parsedMeta.game; - nextCuration.addApps = []; - for (let i = 0; i < parsedMeta.addApps.length; i++) { - const meta = parsedMeta.addApps[i]; - nextCuration.addApps.push({ - key: uuid(), - meta: meta, - }); - } nextCurations[index] = nextCuration; return { ...prevState, curations: nextCurations }; } @@ -78,7 +69,7 @@ function curationReducer(prevState: CurationsState, action: CurationAction): Cur const nextCurations = [ ...prevState.curations ]; const index = ensureCurationIndex(nextCurations, action.payload.key); const prevCuration = nextCurations[index]; - const nextCuration = { ...prevCuration, addApps: [ ...prevCuration.addApps ] }; + const nextCuration = { ...prevCuration }; nextCuration.thumbnail = action.payload.image; nextCuration.thumbnail.version = prevCuration.thumbnail.version + 1; nextCurations[index] = nextCuration; @@ -89,7 +80,7 @@ function curationReducer(prevState: CurationsState, action: CurationAction): Cur const nextCurations = [ ...prevState.curations ]; const index = ensureCurationIndex(nextCurations, action.payload.key); const prevCuration = nextCurations[index]; - const nextCuration = { ...prevCuration, addApps: [ ...prevCuration.addApps ] }; + const nextCuration = { ...prevCuration }; nextCuration.screenshot = action.payload.image; nextCuration.screenshot.version = prevCuration.screenshot.version + 1; nextCurations[index] = nextCuration; @@ -100,59 +91,11 @@ function curationReducer(prevState: CurationsState, action: CurationAction): Cur const nextCurations = [ ...prevState.curations ]; const index = ensureCurationIndex(nextCurations, action.payload.key); const prevCuration = nextCurations[index]; - const nextCuration = { ...prevCuration, addApps: [ ...prevCuration.addApps ] }; + const nextCuration = { ...prevCuration }; nextCuration.content = action.payload.content; nextCurations[index] = nextCuration; return { ...prevState, curations: nextCurations }; } - // Add an empty additional application to a curation - case 'new-addapp': { - const nextCurations = [ ...prevState.curations ]; - const index = nextCurations.findIndex(c => c.key === action.payload.key); - if (index >= 0) { - // Copy the previous curation (and the nested addApps array) - const prevCuration = nextCurations[index]; - const nextCuration = { ...prevCuration, addApps: [ ...prevCuration.addApps ] }; - switch (action.payload.type) { - case 'normal': - nextCuration.addApps.push({ - key: uuid(), - meta: {} - }); - break; - case 'extras': - nextCuration.addApps.push({ - key: uuid(), - meta: generateExtrasAddApp('') - }); - break; - case 'message': - nextCuration.addApps.push({ - key: uuid(), - meta: generateMessageAddApp('') - }); - break; - } - nextCurations[index] = nextCuration; - } - return { ...prevState, curations: nextCurations }; - } - // Remove an additional application from a curation - case 'remove-addapp': { - const nextCurations = [ ...prevState.curations ]; - const index = nextCurations.findIndex(c => c.key === action.payload.curationKey); - if (index >= 0) { - // Copy the previous curation (and the nested addApps array) - const prevCuration = nextCurations[index]; - const nextCuration = { ...prevCuration, addApps: [ ...prevCuration.addApps ] }; - const addAppIndex = nextCuration.addApps.findIndex(c => c.key === action.payload.key); - if (addAppIndex >= 0) { - nextCuration.addApps.splice(addAppIndex, 1); - } - nextCurations[index] = nextCuration; - } - return { ...prevState, curations: nextCurations }; - } // Edit curation's meta case 'edit-curation-meta': { // Find the curation @@ -169,30 +112,6 @@ function curationReducer(prevState: CurationsState, action: CurationAction): Cur } return { ...prevState, curations: nextCurations }; } - // Edit additional application's meta - case 'edit-addapp-meta': { - // Find the curation - const nextCurations = [ ...prevState.curations ]; // (New curations array to replace the current) - const index = nextCurations.findIndex(c => c.key === action.payload.curationKey); - if (index >= 0) { - // Copy the previous curation (and the nested addApps array) - const prevCuration = nextCurations[index]; - const nextCuration = { ...prevCuration, addApps: [ ...prevCuration.addApps ] }; - // Find the additional application - const addAppIndex = prevCuration.addApps.findIndex(c => c.key === action.payload.key); - if (addAppIndex >= 0) { - const prevAddApp = prevCuration.addApps[addAppIndex]; - const nextAddApp = { ...prevAddApp }; - // Replace the value (in the copied meta) - nextAddApp.meta[action.payload.property] = action.payload.value; - // Replace the previous additional application with the new (in the copied array) - nextCuration.addApps[addAppIndex] = nextAddApp; - } - // Replace the previous curation with the new (in the copied array) - nextCurations[index] = nextCuration; - } - return { ...prevState, curations: nextCurations }; - } // Sorts all curations A-Z case 'sort-curations': { const newCurations = [...prevState.curations].sort((a, b) => { @@ -263,7 +182,6 @@ export function createEditCuration(key: string): EditCuration { key: key, meta: {}, content: [], - addApps: [], thumbnail: createCurationIndexImage(), screenshot: createCurationIndexImage(), locked: false, @@ -307,16 +225,6 @@ export type CurationAction = ( key: string; content: IndexedContent[]; }> | - /** Add an empty additional application to curation */ - ReducerAction<'new-addapp', { - key: string; - type: 'normal' | 'extras' | 'message'; - }> | - /** Remove an additional application (by key) from a curation */ - ReducerAction<'remove-addapp', { - curationKey: string; - key: string; - }> | /** Edit the value of a curation's meta's property. */ ReducerAction<'edit-curation-meta', { /** Key of the curation to change. */ @@ -326,17 +234,6 @@ export type CurationAction = ( /** Value to set the property to. */ value: EditCurationMeta[keyof EditCurationMeta]; }> | - /** Edit the value of an additional application's meta's property. */ - ReducerAction<'edit-addapp-meta', { - /** Key of the curation the additional application belongs to. */ - curationKey: string; - /** Key of the additional application to change. */ - key: string; - /** Name of the property to change. */ - property: keyof EditAddAppCurationMeta; - /** Value to set the property to. */ - value: EditAddAppCurationMeta[keyof EditAddAppCurationMeta]; - }> | /** Sort Curations A-Z */ ReducerAction<'sort-curations', {}> | /** Change the lock status of a curation. */ diff --git a/src/renderer/curate/importCuration.ts b/src/renderer/curate/importCuration.ts index ebc796da2..134bec5cf 100644 --- a/src/renderer/curate/importCuration.ts +++ b/src/renderer/curate/importCuration.ts @@ -168,11 +168,14 @@ async function getRootPath(dir: string): Promise { // Convert it to lower-case, because the extensions we're matching against // are lower-case. if (endsWithList(fullpath.toLowerCase(), validMetaNames)) { - return fullpath; + return path.dirname(fullpath); } } else if (stats.isDirectory()) { + const contents: string[] = await fs.readdir(fullpath); // We have a directory. Push all of the directory's contents onto the end of the queue. - queue.push(...(await fs.readdir(fullpath))); + for (const k of contents) { + queue.push(path.join(entry, k)); + } } } } diff --git a/src/shared/back/types.ts b/src/shared/back/types.ts index 122f1ed90..be492e070 100644 --- a/src/shared/back/types.ts +++ b/src/shared/back/types.ts @@ -14,7 +14,7 @@ import { SocketTemplate } from '@shared/socket/types'; import { MessageBoxOptions, OpenDialogOptions, OpenExternalOptions, SaveDialogOptions } from 'electron'; import { GameData, TagAlias, TagFilterGroup } from 'flashpoint-launcher'; import { AppConfigData, AppExtConfigData } from '../config/interfaces'; -import { EditAddAppCuration, EditAddAppCurationMeta, EditCuration, EditCurationMeta } from '../curate/types'; +import { EditCuration, EditCurationMeta } from '../curate/types'; import { ExecMapping, GamePropSuggestions, IService, ProcessAction } from '../interfaces'; import { LangContainer, LangFile } from '../lang'; import { ILogEntry, ILogPreEntry, LogLevel } from '../Log/interface'; @@ -47,7 +47,7 @@ export enum BackIn { DELETE_GAME, DUPLICATE_GAME, EXPORT_GAME, - LAUNCH_ADDAPP, + LAUNCH_EXTRAS, SAVE_IMAGE, DELETE_IMAGE, ADD_LOG, @@ -67,7 +67,7 @@ export enum BackIn { SAVE_LEGACY_PLATFORM, IMPORT_CURATION, LAUNCH_CURATION, - LAUNCH_CURATION_ADDAPP, + LAUNCH_CURATION_EXTRAS, QUIT, // Sources @@ -199,7 +199,7 @@ export type BackInTemplate = SocketTemplate BrowseChangeData; [BackIn.DUPLICATE_GAME]: (id: string, dupeImages: boolean) => BrowseChangeData; [BackIn.EXPORT_GAME]: (id: string, location: string, metaOnly: boolean) => void; - [BackIn.LAUNCH_ADDAPP]: (id: string) => void; + [BackIn.LAUNCH_EXTRAS]: (extrasPath: string) => void; [BackIn.SAVE_IMAGE]: (folder: string, id: string, content: string) => void; [BackIn.DELETE_IMAGE]: (folder: string, id: string) => void; [BackIn.ADD_LOG]: (data: ILogPreEntry & { logLevel: LogLevel }) => void; @@ -219,7 +219,7 @@ export type BackInTemplate = SocketTemplate void; [BackIn.IMPORT_CURATION]: (data: ImportCurationData) => ImportCurationResponseData; [BackIn.LAUNCH_CURATION]: (data: LaunchCurationData) => void; - [BackIn.LAUNCH_CURATION_ADDAPP]: (data: LaunchCurationAddAppData) => void; + [BackIn.LAUNCH_CURATION_EXTRAS]: (data: LaunchCurationData) => void; [BackIn.QUIT]: () => void; // Tag funcs @@ -493,18 +493,10 @@ export type ImportCurationResponseData = { export type LaunchCurationData = { key: string; meta: EditCurationMeta; - addApps: EditAddAppCurationMeta[]; mad4fp: boolean; symlinkCurationContent: boolean; } -export type LaunchCurationAddAppData = { - curationKey: string; - curation: EditAddAppCuration; - platform?: string; - symlinkCurationContent: boolean; -} - export type TagSuggestion = { alias?: string; primaryAlias: string; diff --git a/src/shared/curate/metaToMeta.ts b/src/shared/curate/metaToMeta.ts index 61fc21321..8900829e8 100644 --- a/src/shared/curate/metaToMeta.ts +++ b/src/shared/curate/metaToMeta.ts @@ -1,7 +1,7 @@ import { Game } from '@database/entity/Game'; import { TagCategory } from '@database/entity/TagCategory'; import { ParsedCurationMeta } from './parse'; -import { EditAddAppCuration, EditCurationMeta } from './types'; +import { EditCurationMeta } from './types'; /** * Convert game and its additional applications into a raw object representation in the curation format. @@ -34,38 +34,11 @@ export function convertGameToCurationMetaFile(game: Game, categories: TagCategor parsed['Launch Command'] = game.launchCommand; parsed['Game Notes'] = game.notes; parsed['Original Description'] = game.originalDescription; - // Add-apps meta - const parsedAddApps: CurationFormatAddApps = {}; - for (let i = 0; i < game.addApps.length; i++) { - const addApp = game.addApps[i]; - if (addApp.applicationPath === ':extras:') { - parsedAddApps['Extras'] = addApp.launchCommand; - } else if (addApp.applicationPath === ':message:') { - parsedAddApps['Message'] = addApp.launchCommand; - } else { - let heading = addApp.name; - // Check if the property name is already in use - if (parsedAddApps[heading] !== undefined) { - // Look for an available name (by appending a number after it) - let index = 2; - while (true) { - const testHeading = `${heading} (${index})`; - if (parsedAddApps[testHeading] === undefined) { - heading = testHeading; - break; - } - index += 1; - } - } - // Add add-app - parsedAddApps[heading] = { - 'Heading': addApp.name, - 'Application Path': addApp.applicationPath, - 'Launch Command': addApp.launchCommand, - }; - } - } - parsed['Additional Applications'] = parsedAddApps; + // The meta files use undefined, the DB uses null. + parsed['Parent Game ID'] = game.parentGameId ? game.parentGameId : undefined; + parsed['Extras'] = game.extras ? game.extras : undefined; + parsed['Extras Name'] = game.extrasName ? game.extrasName : undefined; + parsed['Message'] = game.message ? game.message : undefined; // Return return parsed; } @@ -75,7 +48,7 @@ export function convertGameToCurationMetaFile(game: Game, categories: TagCategor * @param curation Curation to convert. * @param addApps Additional applications of the curation. */ -export function convertEditToCurationMetaFile(curation: EditCurationMeta, categories: TagCategory[], addApps?: EditAddAppCuration[]): CurationMetaFile { +export function convertEditToCurationMetaFile(curation: EditCurationMeta, categories: TagCategory[]): CurationMetaFile { const parsed: CurationMetaFile = {}; const tagCategories = curation.tags ? curation.tags.map(t => { const cat = categories.find(c => c.id === t.categoryId); @@ -104,42 +77,10 @@ export function convertEditToCurationMetaFile(curation: EditCurationMeta, catego parsed['Original Description'] = curation.originalDescription; parsed['Curation Notes'] = curation.curationNotes; parsed['Mount Parameters'] = curation.mountParameters; - // Add-apps meta - const parsedAddApps: CurationFormatAddApps = {}; - if (addApps) { - for (let i = 0; i < addApps.length; i++) { - const addApp = addApps[i].meta; - if (addApp.applicationPath === ':extras:') { - parsedAddApps['Extras'] = addApp.launchCommand; - } else if (addApp.applicationPath === ':message:') { - parsedAddApps['Message'] = addApp.launchCommand; - } else { - let heading = addApp.heading; - if (heading) { - // Check if the property name is already in use - if (parsedAddApps[heading] !== undefined) { - // Look for an available name (by appending a number after it) - let index = 2; - while (true) { - const testHeading = `${heading} (${index})`; - if (parsedAddApps[testHeading] === undefined) { - heading = testHeading; - break; - } - index += 1; - } - } - // Add add-app - parsedAddApps[heading] = { - 'Heading': addApp.heading, - 'Application Path': addApp.applicationPath, - 'Launch Command': addApp.launchCommand, - }; - } - } - } - } - parsed['Additional Applications'] = parsedAddApps; + parsed['Parent Game ID'] = curation.parentGameId; + parsed['Extras'] = curation.extras; + parsed['Extras Name'] = curation.extrasName; + parsed['Message'] = curation.message; // Return return parsed; } @@ -178,42 +119,10 @@ export function convertParsedToCurationMeta(curation: ParsedCurationMeta, catego parsed['Original Description'] = curation.game.originalDescription; parsed['Curation Notes'] = curation.game.curationNotes; parsed['Mount Parameters'] = curation.game.mountParameters; - // Add-apps meta - const parsedAddApps: CurationFormatAddApps = {}; - if (curation.addApps) { - for (let i = 0; i < curation.addApps.length; i++) { - const addApp = curation.addApps[i]; - if (addApp.applicationPath === ':extras:') { - parsedAddApps['Extras'] = addApp.launchCommand; - } else if (addApp.applicationPath === ':message:') { - parsedAddApps['Message'] = addApp.launchCommand; - } else { - let heading = addApp.heading; - if (heading) { - // Check if the property name is already in use - if (parsedAddApps[heading] !== undefined) { - // Look for an available name (by appending a number after it) - let index = 2; - while (true) { - const testHeading = `${heading} (${index})`; - if (parsedAddApps[testHeading] === undefined) { - heading = testHeading; - break; - } - index += 1; - } - } - // Add add-app - parsedAddApps[heading] = { - 'Heading': addApp.heading, - 'Application Path': addApp.applicationPath, - 'Launch Command': addApp.launchCommand, - }; - } - } - } - } - parsed['Additional Applications'] = parsedAddApps; + parsed['Extras'] = curation.game.extras; + parsed['Extras Name'] = curation.game.extrasName; + parsed['Message'] = curation.game.message; + parsed['Parent Game ID'] = curation.game.parentGameId; // Return return parsed; } @@ -239,20 +148,10 @@ type CurationMetaFile = { 'Alternate Titles'?: string; 'Library'?: string; 'Version'?: string; - 'Additional Applications'?: CurationFormatAddApps; 'Curation Notes'?: string; 'Mount Parameters'?: string; -}; - -type CurationFormatAddApps = { - [key: string]: CurationFormatAddApp; -} & { 'Extras'?: string; + 'Extras Name'?: string; 'Message'?: string; -}; - -type CurationFormatAddApp = { - 'Application Path'?: string; - 'Heading'?: string; - 'Launch Command'?: string; + 'Parent Game ID'?: string; }; diff --git a/src/shared/curate/parse.ts b/src/shared/curate/parse.ts index bd067ca0a..3b141288c 100644 --- a/src/shared/curate/parse.ts +++ b/src/shared/curate/parse.ts @@ -1,9 +1,9 @@ import { BackIn } from '@shared/back/types'; import { Coerce } from '@shared/utils/Coerce'; -import { IObjectParserProp, ObjectParser } from '../utils/ObjectParser'; +import { ObjectParser } from '../utils/ObjectParser'; import { CurationFormatObject, parseCurationFormat } from './format/parser'; import { CFTokenizer, tokenizeCurationFormat } from './format/tokenizer'; -import { EditAddAppCurationMeta, EditCurationMeta } from './types'; +import { EditCurationMeta } from './types'; import { getTagsFromStr } from './util'; const { str } = Coerce; @@ -12,8 +12,6 @@ const { str } = Coerce; export type ParsedCurationMeta = { /** Meta data of the game. */ game: EditCurationMeta; - /** Meta data of the additional applications. */ - addApps: EditAddAppCurationMeta[]; }; /** @@ -49,7 +47,6 @@ export async function parseCurationMetaFile(data: any, onError?: (error: string) // Default parsed data const parsed: ParsedCurationMeta = { game: {}, - addApps: [], }; // Make sure it exists before calling Object.keys if (!data) { @@ -93,6 +90,10 @@ export async function parseCurationMetaFile(data: any, onError?: (error: string) parser.prop('version', v => parsed.game.version = str(v)); parser.prop('library', v => parsed.game.library = str(v).toLowerCase()); // must be lower case parser.prop('mount parameters', v => parsed.game.mountParameters = str(v)); + parser.prop('extras', v => parsed.game.extras = str(v)); + parser.prop('extras name', v => parsed.game.extrasName = str(v)); + parser.prop('message', v => parsed.game.message = str(v)); + parser.prop('parent game id', v => parsed.game.parentGameId = str(v)); if (lowerCaseData.genre) { parsed.game.tags = await getTagsFromStr(arrayStr(lowerCaseData.genre), str(lowerCaseData['tag categories'])); } if (lowerCaseData.genres) { parsed.game.tags = await getTagsFromStr(arrayStr(lowerCaseData.genres), str(lowerCaseData['tag categories'])); } if (lowerCaseData.tags) { parsed.game.tags = await getTagsFromStr(arrayStr(lowerCaseData.tags), str(lowerCaseData['tag categories'])); } @@ -106,37 +107,10 @@ export async function parseCurationMetaFile(data: any, onError?: (error: string) } // property aliases parser.prop('animation notes', v => parsed.game.notes = str(v)); - // Add-apps - parser.prop('additional applications').map((item, label, map) => { - parsed.addApps.push(convertAddApp(item, label, map[label])); - }); // Return return parsed; } -/** - * Convert a "raw" curation additional application meta object into a more programmer friendly object. - * @param item Object parser, wrapped around the "raw" add-app meta object to convert. - * @param label Label of the object. - */ -function convertAddApp(item: IObjectParserProp, label: string | number | symbol, rawValue: any): EditAddAppCurationMeta { - const addApp: EditAddAppCurationMeta = {}; - const labelStr = str(label); - switch (labelStr.toLowerCase()) { - case 'extras': // (Extras add-app) - return generateExtrasAddApp(str(rawValue)); - case 'message': // (Message add-app) - return generateMessageAddApp(str(rawValue)); - default: // (Normal add-app) - addApp.heading = labelStr; - item.prop('Heading', v => addApp.heading = str(v), true); - item.prop('Application Path', v => addApp.applicationPath = str(v)); - item.prop('Launch Command', v => addApp.launchCommand = str(v)); - break; - } - return addApp; -} - // Coerce an object into a sensible string function arrayStr(rawStr: any): string { if (Array.isArray(rawStr)) { @@ -145,19 +119,3 @@ function arrayStr(rawStr: any): string { } return str(rawStr); } - -export function generateExtrasAddApp(folderName: string) : EditAddAppCurationMeta { - return { - heading: 'Extras', - applicationPath: ':extras:', - launchCommand: folderName - }; -} - -export function generateMessageAddApp(message: string) : EditAddAppCurationMeta { - return { - heading: 'Message', - applicationPath: ':message:', - launchCommand: message - }; -} diff --git a/src/shared/curate/types.ts b/src/shared/curate/types.ts index 10c7c1886..247f8bcfc 100644 --- a/src/shared/curate/types.ts +++ b/src/shared/curate/types.ts @@ -7,8 +7,6 @@ export type EditCuration = { key: string; /** Meta data of the curation. */ meta: EditCurationMeta; - /** Keys of additional applications that belong to this game. */ - addApps: EditAddAppCuration[]; /** Data of each file in the content folder (and sub-folders). */ content: IndexedContent[]; /** Screenshot. */ @@ -23,18 +21,11 @@ export type EditCuration = { deleted: boolean; } -/** Data of an additional application curation in the curation importer. */ -export type EditAddAppCuration = { - /** Unique key of the curation (UUIDv4). Generated when loaded. */ - key: string; - /** Meta data of the curation. */ - meta: EditAddAppCurationMeta; -} - /** Meta data of a curation. */ export type EditCurationMeta = Partial<{ // Game fields title: string; + parentGameId?: string; alternateTitles: string; series: string; developer: string; @@ -55,13 +46,9 @@ export type EditCurationMeta = Partial<{ originalDescription: string; language: string; mountParameters: string; -}> - -/** Meta data of an additional application curation. */ -export type EditAddAppCurationMeta = Partial<{ - heading: string; - applicationPath: string; - launchCommand: string; + extras?: string; + extrasName?: string; + message?: string; }> export type CurationIndex = { diff --git a/src/shared/game/interfaces.ts b/src/shared/game/interfaces.ts index 7f10ffa14..add83bd5e 100644 --- a/src/shared/game/interfaces.ts +++ b/src/shared/game/interfaces.ts @@ -1,4 +1,3 @@ -import { AdditionalApp } from '../../database/entity/AdditionalApp'; import { Game } from '../../database/entity/Game'; import { Playlist } from '../../database/entity/Playlist'; import { OrderGamesOpts } from './GameFilter'; @@ -39,12 +38,6 @@ export type GameAddRequest = { game: Game; } -/** Client Request - Add an additional application */ -export type AppAddRequest = { - /** Add App to add */ - addApp: AdditionalApp; -} - /** Client Request - Information needed to make a search */ export type SearchRequest = { /** String to use as a search query */ diff --git a/src/shared/game/util.ts b/src/shared/game/util.ts index 3344e912e..2c1096c0e 100644 --- a/src/shared/game/util.ts +++ b/src/shared/game/util.ts @@ -1,4 +1,3 @@ -import { AdditionalApp } from '../../database/entity/AdditionalApp'; import { Game } from '../../database/entity/Game'; export namespace ModelUtils { @@ -30,22 +29,10 @@ export namespace ModelUtils { language: '', library: '', orderTitle: '', - addApps: [], + children: [], placeholder: false, activeDataOnDisk: false }); return game; } - - export function createAddApp(game: Game): AdditionalApp { - return { - id: '', - parentGame: game, - applicationPath: '', - autoRunBefore: false, - launchCommand: '', - name: '', - waitForExit: false, - }; - } } diff --git a/src/shared/lang.ts b/src/shared/lang.ts index dc0354671..b25992fb2 100644 --- a/src/shared/lang.ts +++ b/src/shared/lang.ts @@ -314,6 +314,12 @@ const langTemplate = { 'mountParameters', 'noMountParameters', 'showExtremeScreenshot', + 'extras', + 'noExtras', + 'message', + 'noMessage', + 'extrasName', + 'noExtrasName', ] as const, tags: [ 'name', @@ -404,6 +410,9 @@ const langTemplate = { 'ilc_notHttp', 'ilc_nonExistant', 'sort', + 'parentGameId', + 'noParentGameId', + 'invalidParentGameId', ] as const, playlist: [ 'enterDescriptionHere', diff --git a/src/shared/socket/SocketAPI.ts b/src/shared/socket/SocketAPI.ts index e680353e9..50a35a7ea 100644 --- a/src/shared/socket/SocketAPI.ts +++ b/src/shared/socket/SocketAPI.ts @@ -126,7 +126,7 @@ export async function api_handle_message< result = await callback(event, ...data.args as any); } catch (e) { // console.error(`An error was thrown from inside a callback (type: ${data.type}).`, e); - error = e.toString(); + error = (e as Error).toString(); } } diff --git a/src/shared/socket/SocketServer.ts b/src/shared/socket/SocketServer.ts index 11944488b..c4a53a577 100644 --- a/src/shared/socket/SocketServer.ts +++ b/src/shared/socket/SocketServer.ts @@ -88,7 +88,8 @@ export function server_request< if (sent.error !== undefined) { reject(sent.error); } else { - resolve(sent.result); + // Hacky and bad, but makes it compile. + resolve(sent.result as U[T]); } }, }); diff --git a/tests/unit/back/TestDB.ts b/tests/unit/back/TestDB.ts new file mode 100644 index 000000000..efd5cc6f1 --- /dev/null +++ b/tests/unit/back/TestDB.ts @@ -0,0 +1,72 @@ +import { Game } from '@database/entity/Game'; +import { GameData } from '@database/entity/GameData'; +import { Playlist } from '@database/entity/Playlist'; +import { PlaylistGame } from '@database/entity/PlaylistGame'; +import { Source } from '@database/entity/Source'; +import { SourceData } from '@database/entity/SourceData'; +import { Tag } from '@database/entity/Tag'; +import { TagAlias } from '@database/entity/TagAlias'; +import { TagCategory } from '@database/entity/TagCategory'; +import { Initial1593172736527 } from '@database/migration/1593172736527-Initial'; +import { AddExtremeToPlaylist1599706152407 } from '@database/migration/1599706152407-AddExtremeToPlaylist'; +import { GameData1611753257950 } from '@database/migration/1611753257950-GameData'; +import { SourceDataUrlPath1612434225789 } from '@database/migration/1612434225789-SourceData_UrlPath'; +import { SourceFileURL1612435692266 } from '@database/migration/1612435692266-Source_FileURL'; +import { SourceFileCount1612436426353 } from '@database/migration/1612436426353-SourceFileCount'; +import { GameTagsStr1613571078561 } from '@database/migration/1613571078561-GameTagsStr'; +import { GameDataParams1619885915109 } from '@database/migration/1619885915109-GameDataParams'; +import { ChildCurations1648251821422 } from '@database/migration/1648251821422-ChildCurations'; +import { + ConnectionOptions, + createConnection, + getConnectionManager, + getManager +} from 'typeorm'; +import { gameArray as gameArray_small } from './smallDB'; + +const entities = [ + Game, + Playlist, + PlaylistGame, + Tag, + TagAlias, + TagCategory, + GameData, + Source, + SourceData, +]; +type entityType = typeof entities[number]; + +export async function createDefaultDB(path = ':memory:') { + if (!getConnectionManager().has('default')) { + const options: ConnectionOptions = { + type: 'sqlite', + database: path, + entities: entities, + migrations: [ + Initial1593172736527, + AddExtremeToPlaylist1599706152407, + GameData1611753257950, + SourceDataUrlPath1612434225789, + SourceFileURL1612435692266, + SourceFileCount1612436426353, + GameTagsStr1613571078561, + GameDataParams1619885915109, + ChildCurations1648251821422, + ], + }; + const connection = await createConnection(options); + // TypeORM forces on but breaks Playlist Game links to unimported games + await connection.query('PRAGMA foreign_keys=off;'); + await connection.runMigrations(); + } +} + +export async function clearDB(entity: entityType) { + await getManager().getRepository(entity).clear(); +} + +// TODO make this do Playlist, etc. instead of just Game. +export async function setSmall_gameOnly() { + await getManager().getRepository(Game).save(gameArray_small); +} \ No newline at end of file diff --git a/tests/unit/back/game/GameManager.test.ts b/tests/unit/back/game/GameManager.test.ts new file mode 100644 index 000000000..2a988fad3 --- /dev/null +++ b/tests/unit/back/game/GameManager.test.ts @@ -0,0 +1,577 @@ +import * as GameManager from '@back/game/GameManager'; +import { uuid } from '@back/util/uuid'; +import { Game } from '@database/entity/Game'; +import * as v8 from 'v8'; +import { gameArray } from '../smallDB'; +import { clearDB, createDefaultDB, setSmall_gameOnly } from '../TestDB'; + +// Only the keys of T that can't be null or undefined. +type DefinedKeysOf = { + [k in keyof T]-?: null extends T[k] + ? never + : undefined extends T[k] + ? never + : k; +}[keyof T]; + +// This will be a copy of the array that I can feel comfortable mutating. I want to leave gameArray clean. +let arrayCopy: Game[]; + +const formatLocal = (input: Game): Partial => { + const partial = input as Partial; + delete partial.placeholder; + delete partial.updateTagsStr; + return partial; +}; +const formatLocalMany = (input: Game[]): Partial[] => { + const partial = input as Partial[]; + partial.forEach((game) => { + delete game.placeholder; + delete game.updateTagsStr; + }); + return partial; +}; +const formatDB = (input?: Game): Game | undefined => { + if (input) { + input.dateAdded = new Date(input.dateAdded).toISOString(); + } + return input; +}; +const formatDBMany = (input?: Game[]): Partial[] | undefined => { + if (input) { + input.forEach((game) => { + // TODO It seems the types aren't quite right? This conversion *should* be unnecessary, but here we are? + game.dateAdded = new Date(game.dateAdded).toISOString(); + }); + } + return input; +}; +/** + * Filters and then sorts an array of Game objects. + * @param array The array to filter and sort. + * @param filterFunc The function that determines if an element should be left in by the filter. + * @param sortColumn The column to sort the array on. + * @param reverse Whether or not to sort the array backwards. + * @returns The filtered and sorted array. + */ +const filterAndSort = ( + array: Game[], + filterFunc: (game: Game) => boolean, + sortColumn: DefinedKeysOf, + reverse?: boolean +): Game[] => { + const filtered = array.filter(filterFunc); + const flip = reverse ? -1 : 1; + filtered.sort((a: Game, b: Game) => { + if (a[sortColumn] > b[sortColumn]) { + return flip * 1; + } + if (a[sortColumn] < b[sortColumn]) { + return flip * -1; + } + return 0; + }); + return filtered; +}; + +beforeAll(async () => { + await createDefaultDB(); +}); + +/* ASSUMPTIONS MADE: + * Each testing block will receive a clean database. Ensure that each testing block leaves a clean DB. + */ + +describe('GameManager.findGame()', () => { + beforeAll(async () => { + await setSmall_gameOnly(); + }); + afterAll(async () => { + await clearDB(Game); + }); + test('Find game by UUID', async () => { + expect( + formatDB(await GameManager.findGame(gameArray[0].id, undefined, true)) + ).toEqual(formatLocal(gameArray[0])); + }); + test('Dont find game by UUID', async () => { + // Generate a new UUID and try to fetch it. Should fail. + expect(formatDB(await GameManager.findGame(uuid()))).toBeUndefined(); + }); + test('Find game by property', async () => { + expect( + formatDB( + await GameManager.findGame( + undefined, + { where: { title: gameArray[0].title } }, + true + ) + ) + ).toEqual(formatLocal(gameArray[0])); + }); + test('Dont find game by property', async () => { + // At this point, I'm just using uuid() as a random string generator. + expect( + formatDB( + await GameManager.findGame(undefined, { where: { title: uuid() } }) + ) + ).toBeUndefined(); + }); + test('Find game including children', async () => { + expect( + formatDBMany((await GameManager.findGame(gameArray[0].id))?.children) + ).toEqual([formatLocal(gameArray[1])]); + }); + test('Find game excluding children', async () => { + expect( + formatDBMany( + (await GameManager.findGame(gameArray[0].id, undefined, true))?.children + ) + ).toBeUndefined(); + }); + test('Find game lacking children', async () => { + expect( + formatDBMany((await GameManager.findGame(gameArray[1].id))?.children) + ).toBeUndefined(); + }); +}); + +describe('GameManager.countGames()', () => { + beforeEach(async () => { + await setSmall_gameOnly(); + }); + afterEach(async () => { + await clearDB(Game); + }); + test('Count games', async () => { + // Count the number of games that have a null parentGameId. + let count = 0; + gameArray.forEach((game) => { + if (!game.parentGameId) { + count++; + } + }); + expect(await GameManager.countGames()).toBe(count); + }); + test('Count zero games', async () => { + await clearDB(Game); + expect(await GameManager.countGames()).toBe(0); + }); +}); + +describe('GameManager.findGameRow()', () => { + beforeAll(async () => { + await setSmall_gameOnly(); + }); + afterAll(async () => { + await clearDB(Game); + }); + test('Valid game ID, orderBy title', async () => { + // People on the internet say that this will be suboptimal. I don't care too much. + arrayCopy = v8.deserialize( + v8.serialize(formatLocalMany(gameArray)) + ) as Game[]; + const filtered = filterAndSort( + arrayCopy, + (game: Game) => game.parentGameId === null, + 'title' + ); + expect(await GameManager.findGameRow(gameArray[3].id, 'title', 'ASC')).toBe( + 1 + filtered.findIndex((game: Game) => game.id === gameArray[3].id) + ); + }); + test('Invalid game ID, orderBy title', async () => { + expect(await GameManager.findGameRow(uuid(), 'title', 'ASC')).toBe(-1); + }); + test('Reasonable game filter, orderBy title', async () => { + arrayCopy = v8.deserialize( + v8.serialize(formatLocalMany(gameArray)) + ) as Game[]; + const filtered = filterAndSort( + arrayCopy, + (game: Game) => + game.originalDescription.includes('t') && game.parentGameId === null, + 'title' + ); + expect( + await GameManager.findGameRow(gameArray[0].id, 'title', 'ASC', { + searchQuery: { + genericBlacklist: [], + genericWhitelist: [], + blacklist: [], + whitelist: [ + { + field: 'originalDescription', + value: 't', + }, + ], + }, + }) + ) + // Add one because row_number() is one-based, and JS arrays are zero-based. + .toBe(1 + filtered.findIndex((game: Game) => game.id === gameArray[0].id)); + }); + test('Exclusive game filter, orderBy title', async () => { + expect( + await GameManager.findGameRow(gameArray[0].id, 'title', 'ASC', { + searchQuery: { + genericBlacklist: [], + genericWhitelist: [], + blacklist: [], + whitelist: [ + { + field: 'originalDescription', + // Again, just a random string generator, essentially. + value: uuid(), + }, + ], + }, + }) + ).toBe(-1); + }); + test('Valid game ID, orderBy developer', async () => { + arrayCopy = v8.deserialize( + v8.serialize(formatLocalMany(gameArray)) + ) as Game[]; + const filtered = filterAndSort( + arrayCopy, + (game: Game) => game.parentGameId === null, + 'developer' + ); + expect(await GameManager.findGameRow(gameArray[0].id, 'developer', 'ASC')) + // Add one because row_number() is one-based, and JS arrays are zero-based. + .toBe(1 + filtered.findIndex((game: Game) => game.id === gameArray[0].id)); + }); + test('Invalid game filter', async () => { + // Invalid game filters should be ignored. + arrayCopy = v8.deserialize( + v8.serialize(formatLocalMany(gameArray)) + ) as Game[]; + const filtered = filterAndSort( + arrayCopy, + (game: Game) => + game.originalDescription.includes('t') && game.parentGameId === null, + 'title' + ); + expect( + await GameManager.findGameRow(gameArray[0].id, 'title', 'ASC', { + searchQuery: { + genericBlacklist: [], + genericWhitelist: [], + blacklist: [], + whitelist: [ + { + field: uuid(), + value: 't', + }, + { + field: 'originalDescription', + value: 't', + }, + ], + }, + }) + ).toBe(1 + filtered.findIndex((game: Game) => game.id === gameArray[0].id)); + }); + test('Valid game ID, orderBy title reverse', async () => { + arrayCopy = v8.deserialize( + v8.serialize(formatLocalMany(gameArray)) + ) as Game[]; + const filtered = filterAndSort( + arrayCopy, + (game: Game) => game.parentGameId === null, + 'title', + true + ); + expect( + await GameManager.findGameRow(gameArray[3].id, 'title', 'DESC') + ).toBe(1 + filtered.findIndex((game: Game) => game.id === gameArray[3].id)); + }); + test('Child game ID, orderBy title', async () => { + expect(await GameManager.findGameRow(gameArray[1].id, 'title', 'ASC')).toBe( + -1 + ); + }); + test('Valid game ID, orderBy title, with index before', async () => { + arrayCopy = v8.deserialize( + v8.serialize(formatLocalMany(gameArray)) + ) as Game[]; + const filtered = filterAndSort( + arrayCopy, + (game: Game) => game.parentGameId === null, + 'title' + ); + const indexPos = filtered.findIndex( + (game: Game) => game.id === gameArray[5].id + ); + const resultPos = filtered.findIndex( + (game: Game) => game.id === gameArray[0].id + ); + const diff = resultPos - indexPos; + expect( + await GameManager.findGameRow( + gameArray[0].id, + 'title', + 'ASC', + undefined, + { + orderVal: gameArray[5].title, + title: gameArray[5].title, + id: gameArray[5].id, + } + ) + ).toBe(diff > 0 ? diff : -1); + }); + test('Valid game ID, orderBy title, with index after', async () => { + arrayCopy = v8.deserialize( + v8.serialize(formatLocalMany(gameArray)) + ) as Game[]; + const filtered = filterAndSort( + arrayCopy, + (game: Game) => game.parentGameId === null, + 'title' + ); + const indexPos = filtered.findIndex( + (game: Game) => game.id === gameArray[4].id + ); + const resultPos = filtered.findIndex( + (game: Game) => game.id === gameArray[0].id + ); + const diff = resultPos - indexPos; + expect( + await GameManager.findGameRow( + gameArray[0].id, + 'title', + 'ASC', + undefined, + { + orderVal: gameArray[4].title, + title: gameArray[4].title, + id: gameArray[4].id, + } + ) + ).toBe(diff > 0 ? diff : -1); + }); + test('Valid game ID, orderBy title reverse, with index before', async () => { + arrayCopy = v8.deserialize( + v8.serialize(formatLocalMany(gameArray)) + ) as Game[]; + const filtered = filterAndSort( + arrayCopy, + (game: Game) => game.parentGameId === null, + 'title', + true + ); + const indexPos = filtered.findIndex( + (game: Game) => game.id === gameArray[4].id + ); + const resultPos = filtered.findIndex( + (game: Game) => game.id === gameArray[0].id + ); + const diff = resultPos - indexPos; + expect( + await GameManager.findGameRow( + gameArray[0].id, + 'title', + 'DESC', + undefined, + { + orderVal: gameArray[4].title, + title: gameArray[4].title, + id: gameArray[4].id, + } + ) + ).toBe(diff > 0 ? diff : -1); + }); + test('Valid game ID, orderBy title reverse, with index after', async () => { + arrayCopy = v8.deserialize( + v8.serialize(formatLocalMany(gameArray)) + ) as Game[]; + const filtered = filterAndSort( + arrayCopy, + (game: Game) => game.parentGameId === null, + 'title', + true + ); + const indexPos = filtered.findIndex( + (game: Game) => game.id === gameArray[5].id + ); + const resultPos = filtered.findIndex( + (game: Game) => game.id === gameArray[0].id + ); + const diff = resultPos - indexPos; + expect( + await GameManager.findGameRow( + gameArray[0].id, + 'title', + 'DESC', + undefined, + { + orderVal: gameArray[5].title, + title: gameArray[5].title, + id: gameArray[5].id, + } + ) + ).toBe(diff > 0 ? diff : -1); + }); +}); + +describe('GameManager.findGamePageKeyset()', () => { + beforeAll(async () => { + await setSmall_gameOnly(); + }); + afterAll(async () => { + await clearDB(Game); + }); + test('No filters, orderby title, pagesize 1', async () => { + arrayCopy = v8.deserialize( + v8.serialize(formatLocalMany(gameArray)) + ) as Game[]; + const filtered = filterAndSort( + arrayCopy, + (game: Game) => game.parentGameId === null, + 'title' + ); + const result = await GameManager.findGamePageKeyset( + {}, + 'title', + 'ASC', + undefined, + 1 + ); + for (const key in result.keyset) { + expect([result.keyset[key]?.id, result.keyset[key]?.title]).toEqual([ + filtered[Number(key) - 2].id, + filtered[Number(key) - 2].title, + ]); + } + expect(result.total).toBe(filtered.length); + }); + test('No filters, orderby title reverse, pagesize 1', async () => { + arrayCopy = v8.deserialize( + v8.serialize(formatLocalMany(gameArray)) + ) as Game[]; + const filtered = filterAndSort( + arrayCopy, + (game: Game) => game.parentGameId === null, + 'title', + true + ); + const result = await GameManager.findGamePageKeyset( + {}, + 'title', + 'DESC', + undefined, + 1 + ); + for (const key in result.keyset) { + expect([result.keyset[key]?.id, result.keyset[key]?.title]).toEqual([ + filtered[Number(key) - 2].id, + filtered[Number(key) - 2].title, + ]); + } + expect(result.total).toBe(filtered.length); + }); + test('No filters, orderby developer, pagesize 1', async () => { + arrayCopy = v8.deserialize( + v8.serialize(formatLocalMany(gameArray)) + ) as Game[]; + const filtered = filterAndSort( + arrayCopy, + (game: Game) => game.parentGameId === null, + 'developer' + ); + const result = await GameManager.findGamePageKeyset( + {}, + 'developer', + 'ASC', + undefined, + 1 + ); + for (const key in result.keyset) { + expect([result.keyset[key]?.id, result.keyset[key]?.title]).toEqual([ + filtered[Number(key) - 2].id, + filtered[Number(key) - 2].title, + ]); + } + expect(result.total).toBe(filtered.length); + }); + test('Filter out UUID, orderby title, pagesize 1', async () => { + arrayCopy = v8.deserialize( + v8.serialize(formatLocalMany(gameArray)) + ) as Game[]; + const filtered = filterAndSort( + arrayCopy, + (game: Game) => game.parentGameId === null && game.id !== gameArray[0].id, + 'title' + ); + const result = await GameManager.findGamePageKeyset( + { + searchQuery: { + genericBlacklist: [], + genericWhitelist: [], + blacklist: [ + { + field: 'id', + value: gameArray[0].id, + }, + ], + whitelist: [], + }, + }, + 'title', + 'ASC', + undefined, + 1 + ); + for (const key in result.keyset) { + expect([result.keyset[key]?.id, result.keyset[key]?.title]).toEqual([ + filtered[Number(key) - 2].id, + filtered[Number(key) - 2].title, + ]); + } + expect(result.total).toBe(filtered.length); + }); + test('No filters, orderby title, pagesize 1, limit 3', async () => { + arrayCopy = v8.deserialize( + v8.serialize(formatLocalMany(gameArray)) + ) as Game[]; + const filtered = filterAndSort( + arrayCopy, + (game: Game) => game.parentGameId === null, + 'title' + ); + const result = await GameManager.findGamePageKeyset({}, 'title', 'ASC', 3, 1); + for (const key in result.keyset) { + expect([result.keyset[key]?.id, result.keyset[key]?.title]).toEqual([ + filtered[Number(key) - 2].id, + filtered[Number(key) - 2].title, + ]); + } + expect(result.total).toBe(3); + }); + test('No filters, orderby title, pagesize 2', async () => { + arrayCopy = v8.deserialize( + v8.serialize(formatLocalMany(gameArray)) + ) as Game[]; + const filtered = filterAndSort( + arrayCopy, + (game: Game) => game.parentGameId === null, + 'title' + ); + const result = await GameManager.findGamePageKeyset( + {}, + 'title', + 'ASC', + undefined, + 2 + ); + for (const key in result.keyset) { + expect([result.keyset[key]?.id, result.keyset[key]?.title]).toEqual([ + filtered[2 * (Number(key) - 2) + 1].id, + filtered[2 * (Number(key) - 2) + 1].title, + ]); + } + expect(result.total).toBe(filtered.length); + }); +}); diff --git a/tests/unit/back/smallDB.ts b/tests/unit/back/smallDB.ts new file mode 100644 index 000000000..f5f330ebf --- /dev/null +++ b/tests/unit/back/smallDB.ts @@ -0,0 +1,224 @@ +import { Game } from '@database/entity/Game'; + +export const gameArray: Game[] = [ + { + id: 'c6ca5ded-42f4-4251-9423-55700140b096', + parentGameId: null, + title: '"Alone"', + alternateTitles: '', + series: '', + developer: 'Natpat', + publisher: 'MoFunZone; Sketchy', + dateAdded: '2019-11-24T23:39:57.629Z', + dateModified: '2021-03-07T02:08:12.000Z', + platform: 'Flash', + broken: false, + extreme: false, + playMode: 'Single Player', + status: 'Playable', + notes: '', + tagsStr: 'Platformer; Puzzle; Pixel', + source: 'https://www.newgrounds.com/portal/view/578326', + applicationPath: 'FPSoftware\\Flash\\flashplayer_32_sa.exe', + launchCommand: 'http://uploads.ungrounded.net/578000/578326_Preloader.swf', + releaseDate: '2011-08-28', + version: '', + originalDescription: + 'Play as a penguin and a tortoise solving puzzles using a jetpack and a gun in this challenging, funky, colourful platformer!\nPlay alongside a beautiful soundtrack with 3 different songs, and funky graphics. Can you beat all 20 levels?\nI\'m so glad I\'m finally getting this out. Finally! :D Enjoy :)', + language: 'en', + library: 'arcade', + orderTitle: '"alone"', + activeDataId: null, + activeDataOnDisk: false, + extras: null, + extrasName: null, + message: null, + tags: [], + placeholder: false, + updateTagsStr: new Game().updateTagsStr, + }, + { + id: '6fabbb7a-f614-455c-a239-360b6b69ea24', + // This is not the real parent game id. It doesn't matter, deal with it. + parentGameId: 'c6ca5ded-42f4-4251-9423-55700140b096', + title: '"Game feel" demo', + alternateTitles: '', + series: '', + developer: 'Sebastien Benard', + publisher: 'Deepnight.net', + dateAdded: '2021-01-25T23:30:49.267Z', + dateModified: '2022-04-03T17:37:21.000Z', + platform: 'HTML5', + broken: false, + extreme: false, + playMode: 'Single Player', + status: 'Playable', + notes: '', + tagsStr: + 'Demonstration; Action; Platformer; Shooter; Pixel; Side-Scrolling', + source: 'http://deepnight.net/games/game-feel/', + applicationPath: 'FPSoftware\\Basilisk-Portable\\Basilisk-Portable.exe', + launchCommand: 'http://deepnight.net/games/game-feel/', + releaseDate: '2019-12-19', + version: '', + originalDescription: + 'This prototype is not exactly an actual game. It was developed to serve as a demonstration for a “Game feel” talk in 2019 at the ENJMIN school.\n\nIt shows the impact of small details on the overall quality of a game.\n\nYou will need a GAMEPAD to test it. You can enable or disable game features in this demo by pressing the START button.\n\nGAMEPAD is required to play\nA\njump\nB\ndash\nX\nshoot\nSTART\nenable/disable features\nSELECT\nrestart', + language: 'en', + library: 'arcade', + orderTitle: '', + activeDataId: 8656, + activeDataOnDisk: false, + extras: null, + extrasName: null, + message: null, + tags: [], + placeholder: false, + updateTagsStr: new Game().updateTagsStr, + }, + { + id: '4b1c582f-c953-48d4-b839-22897adc8406', + parentGameId: null, + title: '"Eight Planets and a Dwarf" Sudoku', + alternateTitles: '', + series: '', + developer: 'Julia Genyuk; Dave Fisher', + publisher: 'Windows to the Universe', + dateAdded: '2022-02-16T03:34:35.697Z', + dateModified: '2022-02-16T04:08:14.000Z', + platform: 'Flash', + broken: false, + extreme: false, + playMode: 'Single Player', + status: 'Playable', + notes: '', + tagsStr: 'Space; Sudoku', + source: 'https://www.windows2universe.org/games/sudoku/sudoku.html', + applicationPath: 'FPSoftware\\Flash\\flashplayer_32_sa.exe', + launchCommand: + 'http://www.windows2universe.org/games/sudoku/planets_sudoku.swf', + releaseDate: '', + version: '', + originalDescription: '', + language: 'en', + library: 'arcade', + orderTitle: '', + activeDataId: 96874, + activeDataOnDisk: true, + extras: null, + extrasName: null, + message: null, + tags: [], + placeholder: false, + updateTagsStr: new Game().updateTagsStr, + }, + { + id: 'f82c01e0-a30e-49e2-84b2-9b45c437eda6', + parentGameId: null, + title: '"Mind Realm"', + alternateTitles: '', + series: '', + developer: 'Wisdomchild', + publisher: 'WET GAMIN', + dateAdded: '2021-09-03T13:42:06.533Z', + dateModified: '2021-12-13T02:09:25.000Z', + platform: 'HTML5', + broken: false, + extreme: false, + playMode: 'Single Player', + status: 'Playable', + notes: '', + tagsStr: 'Puzzle; Score-Attack; Pixel; Arcade', + source: 'http://wetgamin.com/mindrealm.php', + applicationPath: 'FPSoftware\\Basilisk-Portable\\Basilisk-Portable.exe', + launchCommand: 'http://wetgamin.com/html5/mindrealm/index.html', + releaseDate: '', + version: '', + originalDescription: '', + language: 'en', + library: 'arcade', + orderTitle: '', + activeDataId: 44546, + activeDataOnDisk: true, + extras: null, + extrasName: null, + message: null, + tags: [], + placeholder: false, + updateTagsStr: new Game().updateTagsStr, + }, + { + id: 'b7dbd71b-d099-4fdf-9127-6e0a341b7f2d', + parentGameId: null, + title: '"Build a Tree" Dendrochronology Activity', + alternateTitles: '', + series: '', + developer: '', + publisher: 'Windows to the Universe', + dateAdded: '2022-02-18T04:20:19.301Z', + dateModified: '2022-02-18T04:31:29.000Z', + platform: 'Flash', + broken: false, + extreme: false, + playMode: 'Single Player', + status: 'Playable', + notes: '', + tagsStr: 'Creative; Educational; Object Creator; Toy', + source: + 'https://www.windows2universe.org/earth/climate/dendrochronology_build_tree.html', + applicationPath: 'FPSoftware\\Flash\\flashplayer_32_sa.exe', + launchCommand: + 'http://www.windows2universe.org/earth/climate/images/dendrochronology_build_tree.swf', + releaseDate: '', + version: '', + originalDescription: + 'The interactive diagram below demonstrates a very simple model of tree ring growth.\n\nSelect a temperature range (Normal, Cool, or Warm) and a precipitation amount (Normal, Dry, or Wet) for the coming year. Click the "Add Yearly Growth" button. The tree (which you are viewing a cross-section of the trunk of) grows one year\'s worth, adding a new ring.\n\nAdd some rings while varying the temperature and precipitation. Which of these factors has a stronger influence on the growth of the type of tree being modeled here?\n\nUse the "Reset" button to start over.\n\nThe "Show Specimen Tree" button displays a section of an "actual" tree specimen. Can you model the annual climate during each year of the specimen tree\'s life, matching your diagram with the specimen, to determine the climate history "written" in the rings of the specimen tree? (The "answer" is listed below, lower down on this page).', + language: 'en', + library: 'arcade', + orderTitle: '', + activeDataId: 97171, + activeDataOnDisk: false, + extras: null, + extrasName: null, + message: null, + tags: [], + placeholder: false, + updateTagsStr: new Game().updateTagsStr, + }, + { + id: 'a95d0ff7-3ee9-460f-a4f9-0e0c77764d13', + parentGameId: null, + title: '!BETA! little bullet hell', + alternateTitles: '', + series: '', + developer: 'leonidoss341', + publisher: 'Newgrounds', + dateAdded: '2021-08-04T06:09:06.114Z', + dateModified: '2021-08-04T06:09:44.000Z', + platform: 'Flash', + broken: false, + extreme: false, + playMode: 'Single Player', + status: 'Playable', + notes: '', + tagsStr: 'Action; Shooter', + source: 'https://www.newgrounds.com/portal/view/624363', + applicationPath: 'FPSoftware\\Flash\\flashplayer_32_sa.exe', + launchCommand: + 'http://uploads.ungrounded.net/624000/624363_touhou_project_tutorial.swf', + releaseDate: '2013-08-29', + version: 'Beta', + originalDescription: + 'Testing bullets. Just some fun\n\nAuthor Comments:\n\nWARNING! It\'s just beta, no need to say me that you wanna game, please tell me: everything work good or not? Also i want to know about graphic.\nMovement: keys or WASD, Shift-focus, Z-shooting.', + language: 'en', + library: 'arcade', + orderTitle: '', + activeDataId: 32195, + activeDataOnDisk: true, + extras: null, + extrasName: null, + message: null, + tags: [], + placeholder: false, + updateTagsStr: new Game().updateTagsStr, + }, +]; diff --git a/tsconfig.json b/tsconfig.json index c6d53411b..de20c5cc3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "strictPropertyInitialization": false, - "strictNullChecks": false, + "strictNullChecks": true, "paths": { "@shared/*": [ "./src/shared/*" ], "@main/*": [ "./src/main/*" ], diff --git a/tsconfig.renderer.json b/tsconfig.renderer.json index 99ddab96b..61d9f25ba 100644 --- a/tsconfig.renderer.json +++ b/tsconfig.renderer.json @@ -2,10 +2,11 @@ "extends": "./tsconfig.json", "compilerOptions": { "jsx": "react", + "strictNullChecks": false, }, "exclude": [ "./src/main", "./src/back", "./tests" ] -} \ No newline at end of file +} diff --git a/typings/flashpoint-launcher.d.ts b/typings/flashpoint-launcher.d.ts index ec8c2c11c..be326db68 100644 --- a/typings/flashpoint-launcher.d.ts +++ b/typings/flashpoint-launcher.d.ts @@ -185,17 +185,18 @@ declare module 'flashpoint-launcher' { */ function updateGames(games: Game[]): Promise; /** - * Removes a Game and all its AddApps + * Removes a Game and all its children * @param gameId ID of Game to remove */ - function removeGameAndAddApps(gameId: string): Promise; + function removeGameAndChildren(gameId: string): Promise; // Misc /** * Returns all unique Platform strings in a library * @param library Library to search + * @param includeChildren Whether to include child curations in the platform search. Default: false. */ - function findPlatforms(library: string): Promise; + function findPlatforms(library: string, includeChildren?: boolean): Promise; /** * Parses a Playlist JSON file and returns an object you can save later. * @param jsonData Raw JSON data of the Playlist file @@ -210,14 +211,10 @@ declare module 'flashpoint-launcher' { // Events const onWillLaunchGame: Event; - const onWillLaunchAddApp: Event; const onWillLaunchCurationGame: Event; - const onWillLaunchCurationAddApp: Event; const onWillUninstallGameData: Event; const onDidLaunchGame: Event; - const onDidLaunchAddApp: Event; const onDidLaunchCurationGame: Event; - const onDidLaunchCurationAddApp: Event; const onDidInstallGameData: Event; const onDidUninstallGameData: Event; @@ -455,8 +452,12 @@ declare module 'flashpoint-launcher' { type Game = { /** ID of the game (unique identifier) */ id: string; + /** This game's parent game. */ + parentGame?: Game; /** ID of the game which owns this game */ - parentGameId?: string; + parentGameId: string | null; + /** A list of child games. */ + children?: Game[]; /** Full title of the game */ title: string; /** Any alternate titles to match against search */ @@ -503,16 +504,21 @@ declare module 'flashpoint-launcher' { language: string; /** Library this game belongs to */ library: string; - /** All attached Additional Apps of a game */ - addApps: AdditionalApp[]; /** Unused */ orderTitle: string; /** If the game is a placeholder (and can therefore not be saved) */ placeholder: boolean; /** ID of the active data */ - activeDataId?: number; + activeDataId: number | null; /** Whether the data is present on disk */ activeDataOnDisk: boolean; + /** The path to any extras. */ + extras: string | null; + /** The name to be displayed for those extras. */ + extrasName: string | null; + /** The message to display when the game starts. */ + message: string | null; + data?: GameData[]; updateTagsStr: () => void; }; @@ -566,28 +572,7 @@ declare module 'flashpoint-launcher' { lastUpdated: Date; /** Any data provided by this Source */ data?: SourceData[]; - } - - type AdditionalApp = { - /** ID of the additional application (unique identifier) */ - id: string; - /** Path to the application that runs the additional application */ - applicationPath: string; - /** - * If the additional application should run before the game. - * (If true, this will always run when the game is launched) - * (If false, this will only run when specifically launched) - */ - autoRunBefore: boolean; - /** Command line argument(s) passed to the application to launch the game */ - launchCommand: string; - /** Name of the additional application */ - name: string; - /** Wait for this to exit before the Game will launch (if starting before launch) */ - waitForExit: boolean; - /** Parent of this add app */ - parentGame: Game; - }; + } type Tag = { /** ID of the tag (unique identifier) */