mirror of
https://github.com/thelounge/thelounge
synced 2024-11-22 12:03:11 +00:00
97f553eea8
A user reported in the IRC chan that installing packages fails with ``` 2023-12-13 20:02:34 [INFO] Installing thelounge-theme-solarized v1.1.9... undefined:1 (node:3329) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead. ^ SyntaxError: Unexpected token '(', "(node:3329"... is not valid JSON ``` Now, this happens as yarn helpfully prints a deprecation warning that is shown in the stack trace. Let's assume that we may get non json messages and log them at debug, as we don't know their severity.
205 lines
5.4 KiB
TypeScript
205 lines
5.4 KiB
TypeScript
import _ from "lodash";
|
|
import log from "../log";
|
|
import colors from "chalk";
|
|
import fs from "fs";
|
|
import Helper from "../helper";
|
|
import Config from "../config";
|
|
import path from "path";
|
|
import {spawn} from "child_process";
|
|
let home: string;
|
|
|
|
class Utils {
|
|
static extraHelp(this: void) {
|
|
[
|
|
"",
|
|
"Environment variable:",
|
|
` THELOUNGE_HOME Path for all configuration files and folders. Defaults to ${colors.green(
|
|
Helper.expandHome(Utils.defaultHome())
|
|
)}`,
|
|
"",
|
|
].forEach((e) => log.raw(e));
|
|
}
|
|
|
|
static defaultHome() {
|
|
if (home) {
|
|
return home;
|
|
}
|
|
|
|
const distConfig = Utils.getFileFromRelativeToRoot(".thelounge_home");
|
|
|
|
home = fs.readFileSync(distConfig, "utf-8").trim();
|
|
|
|
return home;
|
|
}
|
|
|
|
static getFileFromRelativeToRoot(...fileName: string[]) {
|
|
// e.g. /thelounge/server/command-line/utils.ts
|
|
if (process.env.NODE_ENV === "test" || process.env.NODE_ENV === "development") {
|
|
return path.resolve(path.join(__dirname, "..", "..", ...fileName));
|
|
}
|
|
|
|
// e.g. /thelounge/dist/server/command-line/utils.ts
|
|
return path.resolve(path.join(__dirname, "..", "..", "..", ...fileName));
|
|
}
|
|
|
|
// Parses CLI options such as `-c public=true`, `-c debug.raw=true`, etc.
|
|
static parseConfigOptions(this: void, val: string, memo?: any) {
|
|
// Invalid option that is not of format `key=value`, do nothing
|
|
if (!val.includes("=")) {
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
return memo;
|
|
}
|
|
|
|
const parseValue = (value: string) => {
|
|
switch (value) {
|
|
case "true":
|
|
return true;
|
|
case "false":
|
|
return false;
|
|
case "undefined":
|
|
return undefined;
|
|
case "null":
|
|
return null;
|
|
default:
|
|
if (/^-?[0-9]+$/.test(value)) {
|
|
// Numbers like port
|
|
return parseInt(value, 10);
|
|
} else if (/^\[.*\]$/.test(value)) {
|
|
// Arrays
|
|
// Supporting arrays `[a,b]` and `[a, b]`
|
|
const array = value.slice(1, -1).split(/,\s*/);
|
|
|
|
// If [] is given, it will be parsed as `[ "" ]`, so treat this as empty
|
|
if (array.length === 1 && array[0] === "") {
|
|
return [];
|
|
}
|
|
|
|
return array.map(parseValue) as Array<Record<string, string>>; // Re-parses all values of the array
|
|
}
|
|
|
|
return value;
|
|
}
|
|
};
|
|
|
|
// First time the option is parsed, memo is not set
|
|
if (memo === undefined) {
|
|
memo = {};
|
|
}
|
|
|
|
// Note: If passed `-c foo="bar=42"` (with single or double quotes), `val`
|
|
// will always be passed as `foo=bar=42`, never with quotes.
|
|
const position = val.indexOf("="); // Only split on the first = found
|
|
const key = val.slice(0, position);
|
|
const value = val.slice(position + 1);
|
|
const parsedValue = parseValue(value);
|
|
|
|
if (_.has(memo, key)) {
|
|
log.warn(`Configuration key ${colors.bold(key)} was already specified, ignoring...`);
|
|
} else {
|
|
memo = _.set(memo, key, parsedValue);
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
return memo;
|
|
}
|
|
|
|
static executeYarnCommand(command: string, ...parameters: string[]) {
|
|
const yarn = require.resolve("yarn/bin/yarn.js");
|
|
const packagesPath = Config.getPackagesPath();
|
|
const cachePath = path.join(packagesPath, "package_manager_cache");
|
|
|
|
const staticParameters = [
|
|
"--cache-folder",
|
|
cachePath,
|
|
"--cwd",
|
|
packagesPath,
|
|
"--json",
|
|
"--ignore-scripts",
|
|
"--non-interactive",
|
|
];
|
|
|
|
const env = {
|
|
// We only ever operate in production mode
|
|
NODE_ENV: "production",
|
|
|
|
// If The Lounge runs from a user that does not have a home directory,
|
|
// yarn may fail when it tries to read certain folders,
|
|
// we give it an existing folder so the reads do not throw a permission error.
|
|
// Yarn uses os.homedir() to figure out the path, which internally reads
|
|
// from the $HOME env on unix. On Windows it uses $USERPROFILE, but
|
|
// the user folder should always exist on Windows, so we don't set it.
|
|
HOME: cachePath,
|
|
};
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let success = false;
|
|
const add = spawn(
|
|
process.execPath,
|
|
[yarn, command, ...staticParameters, ...parameters],
|
|
{env: env}
|
|
);
|
|
|
|
add.stdout.on("data", (data) => {
|
|
data.toString()
|
|
.trim()
|
|
.split("\n")
|
|
.forEach((line: string) => {
|
|
try {
|
|
const json = JSON.parse(line);
|
|
|
|
if (json.type === "success") {
|
|
success = true;
|
|
}
|
|
} catch (e: any) {
|
|
// Stdout buffer has limitations and yarn may print
|
|
// big package trees, for example in the upgrade command
|
|
// See https://github.com/thelounge/thelounge/issues/3679
|
|
}
|
|
});
|
|
});
|
|
|
|
add.stderr.on("data", (data) => {
|
|
data.toString()
|
|
.trim()
|
|
.split("\n")
|
|
.forEach((line: string) => {
|
|
try {
|
|
const json = JSON.parse(line);
|
|
|
|
switch (json.type) {
|
|
case "error":
|
|
log.error(json.data);
|
|
break;
|
|
case "warning":
|
|
// this includes pointless things like "ignored scripts due to flag"
|
|
// so let's hide it
|
|
break;
|
|
}
|
|
|
|
return;
|
|
} catch (e: any) {
|
|
// we simply fall through and log at debug... chances are there's nothing the user can do about it
|
|
// as it includes things like deprecation warnings, but we might want to know as developers
|
|
}
|
|
|
|
log.debug(line);
|
|
});
|
|
});
|
|
|
|
add.on("error", (e) => {
|
|
log.error(`${e.message}:`, e.stack || "");
|
|
process.exit(1);
|
|
});
|
|
|
|
add.on("close", (code) => {
|
|
if (!success || code !== 0) {
|
|
return reject(code);
|
|
}
|
|
|
|
resolve(true);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
export default Utils;
|