summary refs log tree commit diff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--scripts/benchmark.js26
-rw-r--r--scripts/benchmark/connections.js (renamed from bundle/scripts/benchmark/connections.js)8
-rw-r--r--scripts/benchmark/index.js (renamed from bundle/scripts/benchmark/index.js)0
-rw-r--r--scripts/benchmark/users.js (renamed from bundle/scripts/benchmark/users.js)4
-rw-r--r--scripts/build.js116
-rw-r--r--scripts/build/clean.js18
-rw-r--r--scripts/build/compile_tsc.js48
-rw-r--r--scripts/build/plugin_prepare.js31
-rw-r--r--scripts/build/plugin_resources.js13
-rw-r--r--scripts/build/remap_imports.js15
-rw-r--r--scripts/build_new.js31
-rw-r--r--scripts/code_quality.js75
-rw-r--r--scripts/db_migrations.js80
-rw-r--r--scripts/depcheck.js50
-rw-r--r--scripts/depclean.js53
-rw-r--r--scripts/droptables.sql (renamed from api/scripts/droptables.sql)0
-rwxr-xr-xscripts/first_setup.js272
-rw-r--r--scripts/gen_index.js37
-rw-r--r--scripts/generate_openapi.js (renamed from api/scripts/generate_openapi.js)2
-rw-r--r--scripts/generate_schema.js (renamed from api/scripts/generate_schema.js)22
-rw-r--r--scripts/migrate_db_engine.js (renamed from util/scripts/migrate_db_engine.js)6
-rw-r--r--scripts/rights.js34
-rw-r--r--scripts/stresstest/.gitignore (renamed from api/scripts/stresstest/.gitignore)0
-rw-r--r--scripts/stresstest/accounts.json.example (renamed from api/scripts/stresstest/accounts.json.example)0
-rw-r--r--scripts/stresstest/config.json.example (renamed from api/scripts/stresstest/config.json.example)0
-rw-r--r--scripts/stresstest/index.js (renamed from api/scripts/stresstest/index.js)10
-rw-r--r--scripts/stresstest/package-lock.json (renamed from api/scripts/stresstest/package-lock.json)0
-rw-r--r--scripts/stresstest/package.json (renamed from api/scripts/stresstest/package.json)0
-rw-r--r--scripts/stresstest/src/login/index.js (renamed from api/scripts/stresstest/src/login/index.js)6
-rw-r--r--scripts/stresstest/src/message/send.js (renamed from api/scripts/stresstest/src/message/send.js)6
-rw-r--r--scripts/stresstest/src/register/index.js (renamed from api/scripts/stresstest/src/register/index.js)10
-rw-r--r--scripts/update_schemas.js9
-rw-r--r--scripts/utils.js87
-rw-r--r--scripts/utils/ask.js20
34 files changed, 1049 insertions, 40 deletions
diff --git a/scripts/benchmark.js b/scripts/benchmark.js
new file mode 100644

index 00000000..53db92c5 --- /dev/null +++ b/scripts/benchmark.js
@@ -0,0 +1,26 @@ +const typeorm = require("typeorm"); +const Models = require("../dist/entities"); +const { PrimaryColumn } = require("typeorm"); + +function shouldIncludeEntity(name) { + return ![Models.BaseClassWithoutId, PrimaryColumn, Models.BaseClass, Models.PrimaryGeneratedColumn].map((x) => x?.name).includes(name); +} + +async function main() { + console.log("starting"); + const db = new typeorm.DataSource({ + type: "sqlite", + database: ":memory:", + entities: Object.values(Models).filter((x) => x.constructor.name == "Function" && shouldIncludeEntity(x.name)), + synchronize: true + }); + await db.initialize(); + console.log("Initialized database"); + + for (var i = 0; i < 100; i++) { + await Models.User.register({ username: "User" + i }); + console.log("registered user " + i); + } +} + +main(); diff --git a/bundle/scripts/benchmark/connections.js b/scripts/benchmark/connections.js
index ffca2628..f74d0c6d 100644 --- a/bundle/scripts/benchmark/connections.js +++ b/scripts/benchmark/connections.js
@@ -4,11 +4,11 @@ const WebSocket = require("ws"); const endpoint = process.env.GATEWAY || "ws://localhost:3001"; const connections = Number(process.env.CONNECTIONS) || 50; const token = process.env.TOKEN; -var cores = 1; +let cores = 1; try { cores = Number(process.env.THREADS) || os.cpus().length; } catch { - console.log("[Bundle] Failed to get thread count! Using 1...") + console.log("[Bundle] Failed to get thread count! Using 1..."); } if (!token) { @@ -46,8 +46,8 @@ function connect() { op: 2, d: { token, - properties: {}, - }, + properties: {} + } }) ); diff --git a/bundle/scripts/benchmark/index.js b/scripts/benchmark/index.js
index 37ac5633..37ac5633 100644 --- a/bundle/scripts/benchmark/index.js +++ b/scripts/benchmark/index.js
diff --git a/bundle/scripts/benchmark/users.js b/scripts/benchmark/users.js
index bce67bf4..415d6d8b 100644 --- a/bundle/scripts/benchmark/users.js +++ b/scripts/benchmark/users.js
@@ -14,9 +14,9 @@ async function main() { consent: true, date_of_birth: "2000-01-01", gift_code_sku_id: null, - captcha_key: null, + captcha_key: null }), - headers: { "content-type": "application/json" }, + headers: { "content-type": "application/json" } }); console.log(i); } diff --git a/scripts/build.js b/scripts/build.js new file mode 100644
index 00000000..f618100c --- /dev/null +++ b/scripts/build.js
@@ -0,0 +1,116 @@ +const { execSync } = require("child_process"); +const path = require("path"); +const fs = require("fs"); +const { argv, stdout, exit } = require("process"); +const { execIn, parts, getDirs, walk, sanitizeVarName } = require("./utils"); + +if (argv.includes("help")) { + console.log(`Fosscord build script help: +Arguments: + clean Cleans up previous builds + verbose Enable verbose logging + logerrors Log build errors to console + pretty-errors Pretty-print build errors + silent No output to console or files. + propagate-err Exit script with error code if build fails.`); + exit(0); +} + +let steps = 5, + i = 0; +if (argv.includes("clean")) steps++; + +const verbose = argv.includes("verbose") || argv.includes("v"); +const logerr = argv.includes("logerrors"); +const pretty = argv.includes("pretty-errors"); +const silent = argv.includes("silent"); + +if (silent) console.error = console.log = function () {}; + +if (argv.includes("clean")) { + console.log(`[${++i}/${steps}] Cleaning...`); + let d = "dist"; + if (fs.existsSync(d)) { + fs.rmSync(d, { recursive: true }); + if (verbose) console.log(`Deleted ${d}!`); + } +} + +console.log(`[${++i}/${steps}] Compiling src files ...`); + +let buildFlags = ""; +if (pretty) buildFlags += "--pretty "; + +console.log(`[${++i}/${steps}] Building plugin index...`); +let pluginDir = path.join(__dirname, "..", "src", "plugins"); +let output = 'import { Plugin } from "util/plugin";\n'; + +const dirs = fs.readdirSync(pluginDir).filter((x) => { + try { + fs.readdirSync(path.join(pluginDir, x)); + return true; + } catch (e) { + return false; + } +}); +dirs.forEach((x) => { + let pluginManifest = require(path.join(pluginDir, x, "plugin.json")); + output += `import * as ${sanitizeVarName(x)} from "./${x}/${pluginManifest.mainClass}";\n`; +}); +output += `\nexport const PluginIndex: any = {\n`; +dirs.forEach((x) => { + output += ` "${x}": new ${sanitizeVarName(x)}.default(),\n`; //ctor test: '${path.resolve(path.join(pluginDir, x))}', require('./${x}/plugin.json') +}); +output += `};`; + +fs.writeFileSync(path.join(__dirname, "..", "src", "plugins", "PluginIndex.ts"), output); + +if (!argv.includes("copyonly")) { + console.log(`[${++i}/${steps}] Compiling source code...`); + + let buildFlags = ""; + if (pretty) buildFlags += "--pretty "; + + try { + execSync( + 'node "' + + path.join(__dirname, "..", "node_modules", "typescript", "lib", "tsc.js") + + '" -p "' + + path.join(__dirname, "..") + + '" ' + + buildFlags, + { + cwd: path.join(__dirname, ".."), + shell: true, + env: process.env, + encoding: "utf8" + } + ); + } catch (error) { + if (verbose || logerr) { + error.stdout.split(/\r?\n/).forEach((line) => { + let _line = line.replace("dist/", "", 1); + if (!pretty && _line.includes(".ts(")) { + //reformat file path for easy jumping + _line = _line.replace("(", ":", 1).replace(",", ":", 1).replace(")", "", 1); + } + console.error(_line); + }); + } + console.error(`Build failed! Please check build.log for info!`); + if (!silent) { + if (pretty) fs.writeFileSync("build.log.ansi", error.stdout); + fs.writeFileSync( + "build.log", + error.stdout.replaceAll(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "") + ); + } + throw error; + } +} + +console.log(`[${++i}/${steps}] Copying plugin data...`); +let pluginFiles = walk(pluginDir).filter((x) => !x.endsWith(".ts")); +pluginFiles.forEach((x) => { + fs.copyFileSync(x, x.replace("src", "dist")); +}); diff --git a/scripts/build/clean.js b/scripts/build/clean.js new file mode 100644
index 00000000..92ec6d77 --- /dev/null +++ b/scripts/build/clean.js
@@ -0,0 +1,18 @@ +const { execSync } = require("child_process"); +const path = require("path"); +const fs = require("fs"); +const { argv, stdout, exit } = require("process"); +const { execIn, parts, getDirs, walk, sanitizeVarName } = require("../utils"); + +module.exports = function (config) { + if (fs.existsSync(config.buildLog)) fs.rmSync(config.buildLog); + if (fs.existsSync(config.buildLogAnsi)) fs.rmSync(config.buildLogAnsi); + + if (config.clean) { + console.log(`==> Cleaning...`); + if (fs.existsSync(config.distDir)) { + fs.rmSync(config.distDir, { recursive: true }); + if (config.verbose) console.log(`Deleted ${path.resolve(config.distDir)}!`); + } + } +}; diff --git a/scripts/build/compile_tsc.js b/scripts/build/compile_tsc.js new file mode 100644
index 00000000..179707a3 --- /dev/null +++ b/scripts/build/compile_tsc.js
@@ -0,0 +1,48 @@ +const { execSync } = require("child_process"); +const path = require("path"); +const fs = require("fs"); +const { argv, stdout, exit } = require("process"); +const { execIn, parts, getDirs, walk, sanitizeVarName } = require("../utils"); + +module.exports = function (config) { + console.log("==> Compiling source with tsc..."); + let buildFlags = ""; + if (config.pretty) buildFlags += "--pretty "; + + try { + execSync( + 'node "' + + path.join(config.rootDir, "node_modules", "typescript", "lib", "tsc.js") + + '" -p "' + + path.join(config.rootDir) + + '" ' + + buildFlags, + { + cwd: path.join(config.rootDir), + shell: true, + env: process.env, + encoding: "utf8" + } + ); + } catch (error) { + if (config.verbose || config.logerr) { + error.stdout.split(/\r?\n/).forEach((line) => { + let _line = line.replace("dist/", "", 1); + if (!config.pretty && _line.includes(".ts(")) { + //reformat file path for easy jumping + _line = _line.replace("(", ":", 1).replace(",", ":", 1).replace(")", "", 1); + } + console.error(_line); + }); + } + console.error(`Build failed! Please check build.log for info!`); + if (!config.silent) { + if (config.pretty) fs.writeFileSync(path.join(config.rootDir, "build.log.ansi"), error.stdout); + fs.writeFileSync( + path.join(config.rootDir, "build.log"), + error.stdout.replaceAll(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "") + ); + } + throw error; + } +}; diff --git a/scripts/build/plugin_prepare.js b/scripts/build/plugin_prepare.js new file mode 100644
index 00000000..247ad22d --- /dev/null +++ b/scripts/build/plugin_prepare.js
@@ -0,0 +1,31 @@ +const { execSync } = require("child_process"); +const path = require("path"); +const fs = require("fs"); +const { argv, stdout, exit } = require("process"); +const { execIn, parts, getDirs, walk, sanitizeVarName } = require("../utils"); + +module.exports = function (config) { + console.log(`==> Building plugin index...`); + let output = 'import { Plugin } from "util/plugin";\n'; + + const dirs = fs.readdirSync(config.pluginDir).filter((x) => { + try { + fs.readdirSync(path.join(config.pluginDir, x)); + return true; + } catch (e) { + return false; + } + }); + dirs.forEach((x) => { + let pluginManifest = require(path.join(config.pluginDir, x, "plugin.json")); + console.log(` ==> Registering plugin: ${pluginManifest.name} (${pluginManifest.id}) by ${pluginManifest.authors}`); + output += `import * as ${sanitizeVarName(x)} from "./${x}/${pluginManifest.mainClass}";\n`; + }); + output += `\nexport const PluginIndex: any = {\n`; + dirs.forEach((x) => { + output += ` "${x}": new ${sanitizeVarName(x)}.default(),\n`; //ctor test: '${path.resolve(path.join(pluginDir, x))}', require('./${x}/plugin.json') + }); + output += `};`; + + fs.writeFileSync(path.join(config.pluginDir, "PluginIndex.ts"), output); +}; diff --git a/scripts/build/plugin_resources.js b/scripts/build/plugin_resources.js new file mode 100644
index 00000000..5b4b97f2 --- /dev/null +++ b/scripts/build/plugin_resources.js
@@ -0,0 +1,13 @@ +const { execSync } = require("child_process"); +const path = require("path"); +const fs = require("fs"); +const { argv, stdout, exit } = require("process"); +const { execIn, parts, getDirs, walk, sanitizeVarName } = require("../utils"); + +module.exports = function (config) { + console.log(`==> Copying all plugin resources...`); + let pluginFiles = walk(config.pluginDir).filter((x) => !x.endsWith(".ts")); + pluginFiles.forEach((x) => { + fs.copyFileSync(x, x.replace("src", "dist")); + }); +}; diff --git a/scripts/build/remap_imports.js b/scripts/build/remap_imports.js new file mode 100644
index 00000000..cdcd571a --- /dev/null +++ b/scripts/build/remap_imports.js
@@ -0,0 +1,15 @@ +const { execSync } = require("child_process"); +const path = require("path"); +const fs = require("fs"); +const { argv, stdout, exit } = require("process"); +const { execIn, parts, getDirs, walk, sanitizeVarName } = require("../utils"); + +module.exports = function (config) { + console.log(`==> Remapping module imports...`); + let files = walk(config.distDir).filter((x) => x.endsWith(".js")); + files.forEach((x) => { + let fc = fs.readFileSync(x).toString(); + fc = fc.replaceAll("@fosscord/", "#"); + fs.writeFileSync(x, fc); + }); +}; diff --git a/scripts/build_new.js b/scripts/build_new.js new file mode 100644
index 00000000..6a56e7f7 --- /dev/null +++ b/scripts/build_new.js
@@ -0,0 +1,31 @@ +const { execSync } = require("child_process"); +const path = require("path"); +const fs = require("fs"); +const { argv, stdout, exit } = require("process"); +const { execIn, parts, getDirs, walk, sanitizeVarName } = require("./utils"); + +//file paths +const rootDir = path.join(__dirname, ".."); +const srcDir = path.join(rootDir, "src"); +const distDir = path.join(rootDir, "dist"); +const scriptsDir = path.join(rootDir, "scripts"); +const configPath = path.join(rootDir, "build.json"); +const buildLog = path.join(rootDir, "build.log"); +const buildLogAnsi = path.join(rootDir, "build.log.ansi"); +const pluginDir = path.join(srcDir, "plugins"); + +//more, dont export +const buildStepDir = path.join(scriptsDir, "build"); + +if (!fs.existsSync(configPath)) { + if (!fs.existsSync(path.join(configPath + ".default"))) { + console.log("build.json.default not found! Exiting!"); + exit(1); + } + fs.copyFileSync(configPath + ".default", configPath); +} +let config = { rootDir, srcDir, distDir, configPath, buildLog, buildLogAnsi, pluginDir, ...require(configPath) }; + +config.steps.pre.forEach((step) => require(path.join(buildStepDir, step))(config)); +require(path.join(buildStepDir, "compile_" + config.compiler))(config); +config.steps.post.forEach((step) => require(path.join(buildStepDir, step))(config)); diff --git a/scripts/code_quality.js b/scripts/code_quality.js new file mode 100644
index 00000000..aca01d24 --- /dev/null +++ b/scripts/code_quality.js
@@ -0,0 +1,75 @@ +const path = require("path"); +const fs = require("fs").promises; +const { execIn, getLines, walk, projectRoot } = require("./utils"); + +let printTodos = process.argv.includes("TODOS"); + +let root = path.resolve(path.join(__dirname, "..", "src")); +let files = walk(root); +let _files = []; +let errors = 0, + warnings = 0, + todos = 0; + +Promise.all(files.map(getFile)).then((f) => { + Promise.all(Object.keys(_files).map(checkFile)); + console.log(`\n${errors} errors, ${warnings} warnings, ${todos} TODOs.`); + + let loc = 0; + Object.values(_files).forEach((x) => { + loc += x.length; + }); + console.log("\nStats:\n"); + console.log(`Lines of code: ${loc} lines in ${Object.values(_files).length} files.`); + + debugger; +}); + +async function getFile(name) { + let contents = (await fs.readFile(name)).toString().split("\n"); + _files[name] = contents; +} + +async function checkFile(x) { + _files[x].forEach((line) => scanLine(x, line)); +} + +function log(file, line, msg) { + let lineNum = _files[file].indexOf(line) + 1; + console.log(msg, "File:", file.replace(root + "/", "") + ":" + lineNum); +} + +function scanLine(x, line) { + if (/import/.test(line)) { + if (/import {?.*}? from '.*'/.test(line)) { + log(x, line, `[WARN] Inconsistent import syntax, please use double quotes!`); + warnings++; + } + } else { + if (line.trim().endsWith("TODO:")) { + log(x, line, `[ERRO] Empty TODO!`); + errors++; + } else if (/\/\/\s{0,3}TODO:/.test(line)) { + if (printTodos) log(x, line, `[TODO] Found a TODO: ${line.split("TODO:")[1].trim()}.`); + todos++; + } + if (/(:|=)/.test(line)) { + if (/(:|=) {2,}/.test(line)) { + log(x, line, `[WARN] Multiple spaces in assignment!`); + warnings++; + } + if (/(:|=)\t'/.test(line)) { + log(x, line, `[WARN] Tab in assignment!`); + warnings++; + } + if (/(:|=)\w'/.test(line)) { + log(x, line, `[WARN] Missing space in assignment!`); + warnings++; + } + if (/(:|=) undefined/.test(line) && !/(:|=){2,} undefined/.test(line)) { + log(x, line, `[WARN] Use of undefined!`); + warnings++; + } + } + } +} diff --git a/scripts/db_migrations.js b/scripts/db_migrations.js new file mode 100644
index 00000000..df5196b1 --- /dev/null +++ b/scripts/db_migrations.js
@@ -0,0 +1,80 @@ +#!/usr/bin/node +const path = require("path"); +const fs = require("fs"); +const { stdout, exit } = require("process"); +const { execIn } = require("./utils.js"); +const { ask } = require("./utils/ask.js"); + +async function main() { + let filename; + if(process.argv[2]) filename = process.argv[2]; + else filename = await ask("Please enter the name of your migration: "); + let dbconf; + try { + dbconf = JSON.parse(fs.readFileSync("dbconf.json")); + } catch (e) { + console.log("No dbconf.json found!"); + dbconf = {}; + } + + if(!dbconf["sqlite"]) + dbconf.sqlite = { + conn_str: "migrations.db", + migrations_dir: "sqlite", + package: "sqlite3" + } + if(!dbconf["postgres"] && process.env.FC_DB_POSTGRES) { + console.log("Found FC_DB_POSTGRES environment variable. Using it!"); + dbconf.postgres = { + conn_str: process.env.FC_DB_POSTGRES, + migrations_dir: "postgres", + package: "pg" + } + } + if(!dbconf["mariadb"] && process.env.FC_DB_MARIADB){ + console.log("Found FC_DB_MARIADB environment variable. Using it!"); + dbconf.mariadb = { + conn_str: process.env.FC_DB_MARIADB, + migrations_dir: "mariadb", + package: "mysql2" + } + } + fs.writeFileSync("dbconf.json", JSON.stringify(dbconf, null, 4)); + + //build + execIn(`node scripts/build_new.js`, process.cwd(), {stdio: "inherit"}); + + if(fs.existsSync(".env") && !fs.existsSync(".env.bak")) + fs.renameSync(".env", ".env.bak"); + Object.keys(dbconf).forEach((db) => { + console.log(`Applying migrations for ${db}`); + if(!fs.existsSync(path.join("node_modules", dbconf[db].package))) + execIn(`npm i ${dbconf[db].package}`, process.cwd()); + fs.writeFileSync( + `.env`, + `DATABASE=${dbconf[db].conn_str} + THREADS=1 + DB_MIGRATE=true + DB_VERBOSE=true` + ); + execIn(`node dist/start.js`, process.cwd(), {stdio: "inherit"}); + }); + + Object.keys(dbconf).forEach((db) => { + console.log(`Generating new migrations for ${db}`); + fs.writeFileSync( + `.env`, + `DATABASE=${dbconf[db].conn_str} + THREADS=1 + DB_MIGRATE=true + DB_VERBOSE=true` + ); + execIn(`node node_modules/typeorm/cli.js migration:generate "src/util/migrations/${db}/${filename}" -d dist/util/util/Database.js -p`, process.cwd(), {stdio: "inherit"}); + }); + if(fs.existsSync(".env.bak")) { + fs.rmSync(".env"); + fs.renameSync(".env.bak", ".env"); + } + exit(0); +} +main(); \ No newline at end of file diff --git a/scripts/depcheck.js b/scripts/depcheck.js new file mode 100644
index 00000000..44ac2bb6 --- /dev/null +++ b/scripts/depcheck.js
@@ -0,0 +1,50 @@ +const path = require("path"); +const fs = require("fs"); +const { env } = require("process"); +const { execSync } = require("child_process"); +const { argv, stdout, exit } = require("process"); + +const { execIn, getLines } = require("./utils"); + +let npmi_extra_flags = ""; + +const resolveminor = argv.includes("resolveminor"); +if (argv.includes("nobuild")) npmi_extra_flags += "--ignore-scripts "; + +parts.forEach((part) => { + let partDir = path.join(__dirname, "..", "..", part); + let distDir = path.join(partDir, "dist"); + console.log(`Checking updates for ${part} (${partDir})`); + if (part == "bundle") { + execIn(`npm run syncdeps`, partDir); + } + if (resolveminor) { + fs.rmSync(path.join(partDir, "node_modules"), { + recursive: true, + force: true + }); + execIn(`npm i --save --no-fund --no-audit --no-package-lock ${npmi_extra_flags}`, partDir); + } + let x = [ + [ + "pkg", + { + current: "1.0", + wanted: "2.0", + latest: "2.0", + dependent: "cdn", + location: "/usr/src/fosscord/bundle/node_packages/pkg" + } + ] + ]; + x = Object.entries(JSON.parse(execIn("npm outdated --json", partDir))); + x.forEach((a) => { + let pkgname = a[0]; + let pkginfo = a[1]; + if (!pkginfo.current) console.log(`MISSING ${pkgname}: ${pkginfo.current} -> ${pkginfo.wanted} (latest: ${pkginfo.latest})`); + else if (pkginfo.latest != pkginfo.wanted) { + if (pkginfo.current != pkginfo.wanted) console.log(`MINOR ${pkgname}: ${pkginfo.current} -> ${pkginfo.wanted}`); + console.log(`MAJOR ${pkgname}: ${pkginfo.current} -> ${pkginfo.latest}`); + } else console.log(`MINOR ${pkgname}: ${pkginfo.current} -> ${pkginfo.wanted}`); + }); +}); diff --git a/scripts/depclean.js b/scripts/depclean.js new file mode 100644
index 00000000..5a402331 --- /dev/null +++ b/scripts/depclean.js
@@ -0,0 +1,53 @@ +const path = require("path"); +const fs = require("fs"); +const { env } = require("process"); +const { execSync } = require("child_process"); +const { argv, stdout, exit } = require("process"); +const { execIn, getLines } = require("./utils"); + +const bundleRequired = ["@ovos-media/ts-transform-paths"]; +const removeModules = argv.includes("cleanup"); + +console.log(`Installing all packages...`); +execIn("npm i", path.join(__dirname, "..")); + +let partDir = path.join(__dirname, ".."); +let distDir = path.join(partDir, "dist"); +let start = 0; +start = getLines(execIn("npm ls --parseable --package-lock-only -a", partDir)); +if (fs.existsSync(distDir)) + fs.rmSync(distDir, { + recursive: true, + force: true + }); +let x = { + dependencies: [], + devDependencies: [], + invalidDirs: [], + invalidFiles: [], + missing: [], + using: [] +}; +let dcproc = execIn("npx depcheck --json", partDir); +if (dcproc.stdout) x = JSON.parse(dcproc.stdout); +else x = JSON.parse(dcproc); + +fs.writeFileSync(path.join(__dirname, "..", `depclean.out.json`), JSON.stringify(x, null, "\t"), { encoding: "utf8" }); + +let depsToRemove = x.dependencies.join(" "); +if (depsToRemove) execIn(`npm r --save ${depsToRemove}`, partDir); + +depsToRemove = x.devDependencies.join(" "); +if (depsToRemove) execIn(`npm r --save --dev ${depsToRemove}`, partDir); + +if (removeModules && fs.existsSync(path.join(partDir, "node_modules"))) + fs.rmSync(path.join(partDir, "node_modules"), { + recursive: true, + force: true + }); +let end = getLines(execIn("npm ls --parseable --package-lock-only -a", partDir)); +console.log(`${part}: ${start} -> ${end} (diff: ${start - end})`); + +console.log("Installing required packages for bundle..."); + +execIn(`npm i --save ${bundleRequired.join(" ")}`, path.join(__dirname, "..")); diff --git a/api/scripts/droptables.sql b/scripts/droptables.sql
index 8a852048..8a852048 100644 --- a/api/scripts/droptables.sql +++ b/scripts/droptables.sql
diff --git a/scripts/first_setup.js b/scripts/first_setup.js new file mode 100755
index 00000000..4ce6e7de --- /dev/null +++ b/scripts/first_setup.js
@@ -0,0 +1,272 @@ +#!/usr/bin/node +const path = require("path"); +const fs = require("fs"); +const { stdout, exit } = require("process"); +const { execIn } = require("./utils.js"); +const { ask } = require("./utils/ask.js"); + + +const data = { env: [], config: { register: {} }, extra_pkgs: [] }; +let rights = []; + +process.on("SIGINT", function () { + console.log("Caught interrupt signal"); + process.exit(); +}); + +console.log("Welcome to Fosscord!"); +console.log("Please remember this is pre-release software!"); +console.log("We will guide you through some important setup steps."); +console.log(); + +if (fs.existsSync("package-lock.json")) fs.rmSync("package-lock.json"); +if (fs.existsSync("yarn.lock")) fs.rmSync("yarn.lock"); + +async function main() { + printTitle("Step 1: Database setup"); + console.log("1. PostgreSQL (recommended)"); + console.log("2. MariaDB/MySQL"); + console.log("3. SQLite (not recommended, but good for a simple test)"); + + while (!data.db) { + let answer = await ask("Please select a database type: "); + if (answer == "1") { + data.db = "postgres"; + data.extra_pkgs.push("pg"); + } else if (answer == "2") { + data.db = "mariadb"; + data.extra_pkgs.push("mysql2"); + } else if (answer == "3") { + data.db = "sqlite"; + data.extra_pkgs.push("sqlite3"); + } else { + console.log("Invalid choice!"); + } + } + + printTitle("Step 2: Database credentials"); + if (data.db != "sqlite") { + console.log("Please enter your database credentials."); + console.log("You can leave the password field empty if you don't want to set a password."); + console.log(); + while (!data.db_host) { + data.db_host = await ask("Host: "); + } + while (!data.db_port) { + data.db_port = await ask("Port: "); + } + while (!data.db_user) { + data.db_user = await ask("Username: "); + } + while (!data.db_pass) { + data.db_pass = await ask("Password: "); + } + while (!data.db_name) { + data.db_name = await ask("Database name: "); + } + } else { + console.log("SQLite does not use credentials..."); + } + + printTitle("Step 3: Domain setup"); + console.log("Please enter your domain."); + console.log("You can leave the port field empty if you don't want to set a port."); + console.log(); + + data.domain = await ask("Domain (default=localhost): "); + if (!data.domain) data.domain = "localhost"; + else data.ssl = /y?/i.test(await ask("SSL/HTTPS (Y/n): ")); + + data.port = await ask("Port (default=3001): "); + if (!data.port) data.port = "3001"; + + if (data.db != "sqlite") + data.env.push(`DATABASE=${data.db}://${data.db_user}:${data.db_pass}@${data.db_host}:${data.db_port}/${data.db_name}`); + data.env.push(`PORT=${data.port}`); + data.env.push("THREADS=1"); + + printTitle("Step 4: Default rights"); + console.log("Please enter the default rights for new users."); + console.log("Valid rights are: none, discord, full, custom."); + console.log(); + let lines = fs.readFileSync(path.join(__dirname, "..", "src", "util", "util", "Rights.ts")).toString(); + let lines2 = lines.split("\n"); + let lines3 = lines2.filter((y) => y.includes(": BitFlag(")); + let lines4 = lines3.map((x) => x.split("//")[0].trim()); + + let maxRights = 0n; + lines4.forEach((x) => { + maxRights += eval(`rights.${x.replace(":", " = ").replace(",", ";")}`); + }); + discordRights = maxRights; + discordRights -= rights.SEND_BACKDATED_EVENTS; + discordRights -= rights.MANAGE_GUILD_DIRECTORY; + discordRights -= rights.CREDITABLE; + discordRights -= rights.BYPASS_RATE_LIMITS; + discordRights -= rights.ADD_MEMBERS; + discordRights -= rights.MANAGE_RATE_LIMITS; + discordRights -= rights.OPERATOR; + + data.default_rights = await ask("Rights (default=none): "); + if (!data.default_rights || data.defaultRights == "none") data.config.register.defaultRights = "0"; + else if (data.default_rights == "discord") data.config.register.defaultRights = discordRights.toString(); + else if (data.default_rights == "full") data.config.register.defaultRights = maxRights.toString(); + else if (data.default_rights == "custom") data.config.register.defaultRights = (await askRights()).toString(); + + if (data.domain != "localhost") + data.config = { + cdn: { + endpointPrivate: `http://localhost:${data.port}`, + endpointPublic: `${data.ssl ? "https" : "http"}://${data.domain}:${data.port}` + }, + gateway: { + endpointPrivate: `ws://localhost:${data.port}`, + endpointPublic: `${data.ssl ? "wss" : "ws"}://${data.domain}:${data.port}` + }, + ...data.config + }; + printTitle("Step 5: extra options"); + + if (/y?/i.test(await ask("Use fast BCrypt implementation (requires a compiler) (Y/n): "))) data.extra_pkgs.push("bcrypt"); + if (/y?/i.test(await ask("Enable support for widgets (requires compiler, known to fail on some ARM devices.) (Y/n): "))) + data.extra_pkgs.push("canvas"); + + printTitle("Step 6: finalizing..."); + //save + console.log("==> Writing .env..."); + fs.writeFileSync(".env", data.env.join("\n")); + console.log("==> Writing initial.json"); + fs.writeFileSync("initial.json", JSON.stringify(data.config, (space = 4))); + //install packages... + console.log("==> Installing packages..."); + console.log(" ==> Ensuring yarn is up to date (v3, not v1)..."); + execIn("npx yarn set version stable", process.cwd()); + console.log(" ==> Installing base packages"); + execIn("npx --yes yarn install", process.cwd(), { stdio: "inherit" }); + if (data.extra_pkgs.length > 0) { + console.log(" ==> Checking dependencies..."); + checkCompilers(); + if (data.extra_pkgs.includes("canvas")) checkCanvasDeps(); + if (data.extra_pkgs.includes("bcrypt")) checkBcryptDeps(); + + console.log(` ==> Installing extra packages: ${data.extra_pkgs.join(", ")}...`); + execIn(`npx --yes yarn add -O ${data.extra_pkgs.join(" ")}`, process.cwd(), { stdio: "inherit" }); + } + + console.log("==> Building..."); + execIn("npx --yes yarn run build", process.cwd(), { stdio: "inherit" }); + printTitle("Step 6: run your instance!"); + console.log("Installation is complete!"); + console.log("You can now start your instance by running 'npm run start:bundle'!"); + exit(0); +} +main(); + +async function askRights() { + let w = 0; + let brights = { ...eval(`rights`) }; + Object.keys(rights).forEach((x) => { + brights[x] = false; + let str = `[x] ${Object.keys(rights).length}: ${x}`; + if (str.length > w) w = str.length; + }); + + let resp = ""; + let selectedRights = 0n; + while (resp != "q") { + selectedRights = 0n; + Object.keys(brights).forEach((x) => { + if (brights[x]) selectedRights += rights[x]; + }); + console.clear(); + printTitle("Step 4: Default rights"); + printTitle(`Current rights: ${selectedRights} (0b${selectedRights.toString(2)}, 0x${selectedRights.toString(16)})`); + let xpos = 0; + Object.keys(rights).forEach((x) => { + let str = `[${brights[x] ? "X" : " "}] ${Object.keys(rights).indexOf(x)}: ${x}`.padEnd(w + 1, " "); + if (xpos + str.length > stdout.columns) { + console.log(); + xpos = 0; + } + stdout.write(str); + xpos += str.length; + }); + + console.log(); + resp = await ask("Enter an option, or q to exit: "); + if (/\d{1,}/.test(resp) && resp < Object.keys(rights).length && resp > -1) { + brights[Object.keys(brights)[parseInt(resp)]] ^= true; + } + } + return selectedRights; +} + + + +function printTitle(input) { + let width = stdout.columns / 2 - 1; //40 + console.log(); + console.log("-".repeat(width - input.length / 2), input, "-".repeat(width - input.length / 2)); + console.log(); +} + + +function BitFlag(int) { + return 1n << BigInt(int); +} + +function checkCanvasDeps() { + if ( + !( + checkDep("pixman", "/usr/include/pixman-1/pixman.h") && + checkDep("pixman", "/usr/lib/libpixman-1.so") && + checkDep("cairo", "/usr/include/cairo/cairo.h") && + checkDep("cairo", "/usr/lib/libcairo.so") && + checkDep("pango", "/usr/include/pango-1.0/pango/pangocairo.h") && + checkDep("pango", "/usr/lib/libpango-1.0.so") && + checkDep("pkgconfig", "/usr/bin/pkg-config") + ) + ) { + console.log("Canvas requires the following dependencies to be installed: pixman, cairo, pango, pkgconfig"); + exit(1); + } +} +function checkBcryptDeps() { + /*if (!(checkDep("bcrypt", "/usr/include/bcrypt.h") && checkDep("bcrypt", "/usr/lib/libbcrypt.so"))) { + console.log("Bcrypt requires the following dependencies to be installed: bcrypt"); + exit(1); + }*/ + //TODO: check if required +} + +function checkCompilers() { + //check for gcc, grep, make, python-is-python3 + if ( + !( + checkDep("gcc", "/usr/bin/gcc") && + checkDep("grep", "/usr/bin/grep") && + checkDep("make", "/usr/bin/make") && + checkDep("python3", "/usr/bin/python3") + ) + ) { + console.log("Compiler requirements not met. Please install the following: gcc, grep, make, python3"); + exit(1); + } + + //check if /usr/bin/python is a symlink to /usr/bin/python3 + if (!fs.lstatSync("/usr/bin/python").isSymbolicLink()) { + console.log("/usr/bin/python is not a symlink. Please make sure it is a symlink to /usr/bin/python3"); + if (fs.existsSync("/usr/bin/python3")) { + console.log("Hint: sudo ln -s /usr/bin/python3 /usr/bin/python"); + } + exit(1); + } +} + +function checkDep(name, path, message) { + if (!fs.existsSync(path)) { + console.log(`${name} not found at ${path}! Installation of some modules may fail!`); + console.log(message ?? `Please consult your distro's manual for installation instructions.`); + } + return fs.existsSync(path); +} diff --git a/scripts/gen_index.js b/scripts/gen_index.js new file mode 100644
index 00000000..8a3c7eb8 --- /dev/null +++ b/scripts/gen_index.js
@@ -0,0 +1,37 @@ +const path = require("path"); +const fs = require("fs"); +const { execIn, getLines } = require("./utils"); + +if (!process.argv[2] || !fs.existsSync(process.argv[2])) { + console.log("Please pass a directory that exists!"); + process.exit(1); +} +console.log(`// ${process.argv[2]}/index.ts`); +const recurse = process.argv.includes("--recursive"); + +const files = fs.readdirSync(process.argv[2]).filter((x) => x.endsWith(".ts") && x != "index.ts"); + +let output = ""; + +files.forEach((x) => (output += `export * from "./${x.replaceAll(".ts", "")}";\n`)); + +const dirs = fs.readdirSync(process.argv[2]).filter((x) => { + try { + fs.readdirSync(path.join(process.argv[2], x)); + return true; + } catch (e) { + return false; + } +}); +dirs.forEach((x) => { + output += `export * from "./${x}/index";\n`; +}); +console.log(output); +fs.writeFileSync(path.join(process.argv[2], "index.ts"), output); + +dirs.forEach((x) => { + if (recurse) + console.log( + execIn([process.argv[0], process.argv[1], `"${path.join(process.argv[2], x)}"`, "--recursive"].join(" "), process.cwd()) + ); +}); diff --git a/api/scripts/generate_openapi.js b/scripts/generate_openapi.js
index c9de9fa6..9624a5b9 100644 --- a/api/scripts/generate_openapi.js +++ b/scripts/generate_openapi.js
@@ -11,7 +11,7 @@ const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); const specification = JSON.parse(fs.readFileSync(openapiPath, { encoding: "utf8" })); function combineSchemas(schemas) { - var definitions = {}; + let definitions = {}; for (const name in schemas) { definitions = { diff --git a/api/scripts/generate_schema.js b/scripts/generate_schema.js
index b56c3fbc..e4bdd0c4 100644 --- a/api/scripts/generate_schema.js +++ b/scripts/generate_schema.js
@@ -36,11 +36,11 @@ const Excluded = [ "UncheckedPropertiesSchema", "PropertiesSchema", "AsyncSchema", - "AnySchema", + "AnySchema" ]; function modify(obj) { - for (var k in obj) { + for (let k in obj) { if (typeof obj[k] === "object" && obj[k] !== null) { modify(obj[k]); } @@ -48,21 +48,15 @@ function modify(obj) { } function main() { - const files = [ - ...walk(path.join(__dirname, "..", "src", "routes")), - ...walk(path.join(__dirname, "..", "..", "util", "src")), - ]; - const program = TJS.getProgramFromFiles( - files, - compilerOptions - ); + const files = [...walk(path.join(__dirname, "..", "src", "util", "schemas"))]; + const program = TJS.getProgramFromFiles(files, compilerOptions); const generator = TJS.buildGenerator(program, settings); if (!generator || !program) return; let schemas = generator.getUserSymbols().filter((x) => (x.endsWith("Schema") || x.endsWith("Response")) && !Excluded.includes(x)); console.log(schemas); - var definitions = {}; + let definitions = {}; for (const name of schemas) { const part = TJS.generateSchema(program, name, settings, [], generator); @@ -79,11 +73,11 @@ function main() { main(); function walk(dir) { - var results = []; - var list = fs.readdirSync(dir); + let results = []; + let list = fs.readdirSync(dir); list.forEach(function (file) { file = dir + "/" + file; - var stat = fs.statSync(file); + let stat = fs.statSync(file); if (stat && stat.isDirectory()) { /* Recurse into a subdirectory */ results = results.concat(walk(file)); diff --git a/util/scripts/migrate_db_engine.js b/scripts/migrate_db_engine.js
index 79e9d86f..b5b8008b 100644 --- a/util/scripts/migrate_db_engine.js +++ b/scripts/migrate_db_engine.js
@@ -25,7 +25,7 @@ const { Template, User, VoiceState, - Webhook, + Webhook } = require("../../dist/entities/index"); async function main() { @@ -54,7 +54,7 @@ async function main() { VoiceState, Webhook, Message, - Attachment, + Attachment ]; const oldDB = await initDatabase(); @@ -69,7 +69,7 @@ async function main() { database: isSqlite ? process.env.TO : undefined, entities, name: "new", - synchronize: true, + synchronize: true }); let i = 0; diff --git a/scripts/rights.js b/scripts/rights.js new file mode 100644
index 00000000..5ae576ef --- /dev/null +++ b/scripts/rights.js
@@ -0,0 +1,34 @@ +const path = require("path"); +const fs = require("fs"); +const { env } = require("process"); +const { execSync } = require("child_process"); +const { argv, stdout, exit } = require("process"); + +const { execIn, getLines, parts } = require("./utils"); + +let lines = fs.readFileSync(path.join(__dirname, "..", "src", "util", "util", "Rights.ts")).toString(); +let lines2 = lines.split("\n"); +let lines3 = lines2.filter((y) => y.includes(": BitFlag(")); +let lines4 = lines3.map((x) => x.split("//")[0].trim()); + +function BitFlag(int) { + return 1n << BigInt(int); +} + +let rights = []; +let maxRights = 0n; +lines4.forEach((x) => { + maxRights += eval(`rights.${x.replace(":", " = ").replace(",", ";")}`); +}); +//max rights... +console.log(`Maximum rights: ${maxRights}`); +//discord rights... +discordRights = maxRights; +discordRights -= rights.SEND_BACKDATED_EVENTS; +discordRights -= rights.MANAGE_GUILD_DIRECTORY; +discordRights -= rights.CREDITABLE; +discordRights -= rights.BYPASS_RATE_LIMITS; +discordRights -= rights.ADD_MEMBERS; +discordRights -= rights.MANAGE_RATE_LIMITS; +discordRights -= rights.OPERATOR; +console.log(`Discord-like rights: ${discordRights}`); diff --git a/api/scripts/stresstest/.gitignore b/scripts/stresstest/.gitignore
index bde26fd4..bde26fd4 100644 --- a/api/scripts/stresstest/.gitignore +++ b/scripts/stresstest/.gitignore
diff --git a/api/scripts/stresstest/accounts.json.example b/scripts/stresstest/accounts.json.example
index 61904c5e..61904c5e 100644 --- a/api/scripts/stresstest/accounts.json.example +++ b/scripts/stresstest/accounts.json.example
diff --git a/api/scripts/stresstest/config.json.example b/scripts/stresstest/config.json.example
index 73f52f05..73f52f05 100644 --- a/api/scripts/stresstest/config.json.example +++ b/scripts/stresstest/config.json.example
diff --git a/api/scripts/stresstest/index.js b/scripts/stresstest/index.js
index a9a65097..740a9011 100644 --- a/api/scripts/stresstest/index.js +++ b/scripts/stresstest/index.js
@@ -19,19 +19,19 @@ setInterval(() => { getUsers(); }, 60 * 1000); async function generate() { - var accounts = await JSON.parse(fs.readFileSync("accounts.json")); + let accounts = await JSON.parse(fs.readFileSync("accounts.json")); console.log(accounts); - var account = await register(); + let account = await register(); accounts.push(account); fs.writeFileSync("accounts.json", JSON.stringify(accounts)); console.log(accounts.length); - var y = await login(account); + let y = await login(account); sendMessage(y); } async function getUsers() { - var accounts = await JSON.parse(fs.readFileSync("accounts.json")); + let accounts = await JSON.parse(fs.readFileSync("accounts.json")); accounts.forEach(async (x) => { - var y = await login(x); + let y = await login(x); console.log(y); sendMessage(y); }); diff --git a/api/scripts/stresstest/package-lock.json b/scripts/stresstest/package-lock.json
index 81c9b817..81c9b817 100644 --- a/api/scripts/stresstest/package-lock.json +++ b/scripts/stresstest/package-lock.json
diff --git a/api/scripts/stresstest/package.json b/scripts/stresstest/package.json
index 8d94d05b..8d94d05b 100644 --- a/api/scripts/stresstest/package.json +++ b/scripts/stresstest/package.json
diff --git a/api/scripts/stresstest/src/login/index.js b/scripts/stresstest/src/login/index.js
index bd9fea87..b153550e 100644 --- a/api/scripts/stresstest/src/login/index.js +++ b/scripts/stresstest/src/login/index.js
@@ -1,14 +1,14 @@ const fetch = require("node-fetch"); const fs = require("fs"); -var config = require("./../../config.json"); +let config = require("../../config.json"); module.exports = login; async function login(account) { - var body = { + let body = { fingerprint: "805826570869932034.wR8vi8lGlFBJerErO9LG5NViJFw", login: account.email, password: account.password }; - var x = await fetch(config.url + "/auth/login", { + let x = await fetch(config.url + "/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) diff --git a/api/scripts/stresstest/src/message/send.js b/scripts/stresstest/src/message/send.js
index 1f8af8aa..d1b86914 100644 --- a/api/scripts/stresstest/src/message/send.js +++ b/scripts/stresstest/src/message/send.js
@@ -1,14 +1,14 @@ const fetch = require("node-fetch"); const fs = require("fs"); -var config = require("./../../config.json"); +let config = require("./../../config.json"); module.exports = sendMessage; async function sendMessage(account) { - var body = { + let body = { fingerprint: "805826570869932034.wR8vi8lGlFBJerErO9LG5NViJFw", content: "Test", tts: false }; - var x = await fetch(config.url + "/channels/" + config["text-channel"] + "/messages", { + let x = await fetch(config.url + "/channels/" + config["text-channel"] + "/messages", { method: "POST", headers: { "Content-Type": "application/json", diff --git a/api/scripts/stresstest/src/register/index.js b/scripts/stresstest/src/register/index.js
index bb6f839f..578b9022 100644 --- a/api/scripts/stresstest/src/register/index.js +++ b/scripts/stresstest/src/register/index.js
@@ -1,17 +1,17 @@ const fetch = require("node-fetch"); const fs = require("fs"); -var config = require("./../../config.json"); +let config = require("./../../config.json"); module.exports = generate; async function generate() { - var mail = (Math.random() + 10).toString(36).substring(2); + let mail = (Math.random() + 10).toString(36).substring(2); mail = mail + "." + (Math.random() + 10).toString(36).substring(2) + "@stresstest.com"; - var password = + let password = (Math.random() * 69).toString(36).substring(-7) + (Math.random() * 69).toString(36).substring(-7) + (Math.random() * 69).toString(36).substring(-8); console.log(mail); console.log(password); - var body = { + let body = { fingerprint: "805826570869932034.wR8vi8lGlFBJerErO9LG5NViJFw", email: mail, username: "Fosscord Stress Test", @@ -22,7 +22,7 @@ async function generate() { gift_code_sku_id: null, captcha_key: null }; - var x = await fetch(config.url + "/auth/register", { + let x = await fetch(config.url + "/auth/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) diff --git a/scripts/update_schemas.js b/scripts/update_schemas.js new file mode 100644
index 00000000..151b52d2 --- /dev/null +++ b/scripts/update_schemas.js
@@ -0,0 +1,9 @@ +const path = require("path"); +const fs = require("fs"); +const { env } = require("process"); +const { execSync } = require("child_process"); +const { argv, stdout, exit } = require("process"); + +const { execIn, getLines, parts } = require("./utils"); + +execIn("node scripts/generate_schema.js", path.join(".")); diff --git a/scripts/utils.js b/scripts/utils.js new file mode 100644
index 00000000..b679392b --- /dev/null +++ b/scripts/utils.js
@@ -0,0 +1,87 @@ +const path = require("path"); +const fs = require("fs"); +const { env } = require("process"); +const { execSync } = require("child_process"); +const { argv, stdout, exit } = require("process"); + +const projectRoot = path.resolve(path.join(__dirname, "..")); + +function copyRecursiveSync(src, dest) { + //if (verbose) console.log(`cpsync: ${src} -> ${dest}`); + let exists = fs.existsSync(src); + if (!exists) { + console.log(src + " doesn't exist, not copying!"); + return; + } + let stats = exists && fs.statSync(src); + let isDirectory = exists && stats.isDirectory(); + if (isDirectory) { + fs.mkdirSync(dest, { recursive: true }); + fs.readdirSync(src).forEach(function (childItemName) { + copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName)); + }); + } else { + fs.copyFileSync(src, dest); + } +} + +function execIn(cmd, workdir, opts) { + try { + return execSync(cmd, { + cwd: workdir, + shell: true, + env: process.env, + encoding: "utf-8", + ...opts + }); + } catch (error) { + return error.stdout; + } +} + +function getLines(output) { + return output.split("\n").length; +} + +function getDirs(dir) { + return fs.readdirSync(dir).filter((x) => { + try { + fs.readdirSync(dir.join(dir, x)); + return true; + } catch (e) { + return false; + } + }); +} + +function walk(dir) { + let results = []; + let list = fs.readdirSync(dir); + list.forEach(function (file) { + file = dir + "/" + file; + let stat = fs.statSync(file); + if (stat && stat.isDirectory()) { + /* Recurse into a subdirectory */ + results = results.concat(walk(file)); + } else { + results.push(file); + } + }); + return results; +} + +function sanitizeVarName(str) { + return str.replace("-", "_").replace(/[^\w\s]/gi, ""); +} + +module.exports = { + //consts + projectRoot, + //functions + copyRecursiveSync, + execIn, + getLines, + getDirs, + walk, + sanitizeVarName +}; diff --git a/scripts/utils/ask.js b/scripts/utils/ask.js new file mode 100644
index 00000000..cb8a29f6 --- /dev/null +++ b/scripts/utils/ask.js
@@ -0,0 +1,20 @@ +const readline = require("readline"); +const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + +async function ask(question) { + return new Promise((resolve, _reject) => { + return rl.question(question, (answer) => { + resolve(answer); + }); + }).catch((err) => { + console.log(err); + }); +} +async function askBool(question) { + return /y?/i.test(await ask(question)); +} + +module.exports = { + ask, + askBool +} \ No newline at end of file