Skip to content

Instantly share code, notes, and snippets.

@lleyton
Last active July 18, 2022 23:12
Show Gist options
  • Save lleyton/3ac08fc856a824bc9f81b609ab7bd440 to your computer and use it in GitHub Desktop.
Save lleyton/3ac08fc856a824bc9f81b609ab7bd440 to your computer and use it in GitHub Desktop.
Powercord Package Manager (PPM)

A simple and bodgey package manager for Powercord

Installation

Download the ppm.js file, and add it your your PATH. (Make sure to chmod +x it.)

You will need to set the POWERCORD_PATH enviroment variable to your Powercord installation, the root git repository.

(Automated installer coming soon:tm:)

Bye!

If you have any questions, feel free to message me at Leа#7962 on Discord. The a in my username is a special character, so you will have to copy it.

#!/usr/bin/env node
const fsPromises = require("fs").promises;
const fs = require("fs");
const util = require("util");
const path = require("path");
const exec = util.promisify(require("child_process").exec);
const powercordPath = process.env.POWERCORD_PATH;
if (!powercordPath) {
console.error("POWERCORD_PATH environment variable not set");
process.exit(1);
}
const plugins = path.join(powercordPath, "src", "Powercord", "plugins");
const themes = path.join(powercordPath, "src", "Powercord", "themes");
const args = process.argv.slice(2);
const getPackages = async () => {
const pluginNames = (
await fsPromises.readdir(plugins, { withFileTypes: true })
)
.filter((f) => f.isDirectory())
.map((dir) => dir.name)
.filter((name) => !name.startsWith("pc-"));
const themeNames = (await fsPromises.readdir(themes, { withFileTypes: true }))
.filter((f) => f.isDirectory())
.map((dir) => dir.name);
const info = await Promise.all(
[
...pluginNames.map((n) => ({ name: n, type: "plugin" })),
...themeNames.map((n) => ({ name: n, type: "theme" })),
].map(async ({ name, type }) => {
const packagePath = path.join(type === "plugin" ? plugins : themes, name);
const { stdout } = await exec("git config --get remote.origin.url", {
cwd: packagePath,
});
return {
name,
url: stdout.trim(),
type: type,
path: packagePath,
};
})
);
return info;
};
const getPackageName = (url) => {
return path.basename(url, ".git");
};
const packageInstalled = (url, packageList) => {
return !!packageList.find(
(i) => i.url === url || i.name === getPackageName(url)
);
};
(async () => {
switch (args[0]) {
case "install": {
const [, ...packages] = args;
if (packages.length === 0) {
console.error(
"Usage: ppm install <theme|plugin:git_url> [theme|plugin:git_url...]"
);
process.exit(1);
}
const packageList = await getPackages();
const targets = packages.map((p) => {
if (p.startsWith("theme:")) {
const url = p.slice(6);
if (packageInstalled(url, packageList)) {
console.error(`Package ${getPackageName(p)} already installed`);
process.exit(1);
}
return {
type: "theme",
url,
};
} else if (p.startsWith("plugin:")) {
const url = p.slice(7);
if (packageInstalled(url, packageList)) {
console.error(`Package ${getPackageName(p)} already installed`);
process.exit(1);
}
return {
type: "plugin",
url,
};
} else {
console.error(
`Package ${p} is not a valid package target, must be either "theme:<git_url>" or "plugin:<git_url>"`
);
process.exit(1);
}
});
await Promise.all(
targets.map(async ({ url, type }) => {
await exec(`git clone ${url}`, {
cwd: type === "theme" ? themes : plugins,
});
})
);
console.log(`Installed ${targets.length} packages successfully!`);
return;
}
case "remove": {
const [, ...packages] = args;
if (packages.length === 0) {
console.error("Usage: ppm remove <package_name> [package_name...]");
process.exit(1);
}
const packageList = await getPackages();
// Initial check to make sure the packages exist
const resolved = packages.map((package) => {
const found = packageList.find((p) => p.name === package);
if (!found) {
return { name: package, found: false };
}
return { path: found.path, found: true };
});
const notFound = resolved
.filter((package) => !package.found)
.map((p) => p.name);
if (notFound.length > 0) {
console.error(
`The following packages are not installed: ${notFound.join(", ")}`
);
process.exit(1);
}
await Promise.all(
resolved.map(async ({ path }) =>
fsPromises.rm(path, {
recursive: true,
force: true,
})
)
);
console.log(`Removed ${resolved.length} packages successfully!`);
return;
}
case "upgrade": {
console.log(
"NOTE: This command isn't necessary for most users. Powercord will automatically upgrade plugins through its own mechanism. Use this only if Powercord fails."
);
const packageList = await getPackages();
const paths = packageList.map((p) => p.path);
await Promise.all(
paths.map(async (p) => {
await exec(`git pull`, { cwd: p });
})
);
console.log(`Upgraded ${paths.length} packages successfully!`);
return;
}
case "list": {
const packages = await getPackages();
console.log(
`${packages.length} packages installed:\n${packages
.map((i) => `${i.type === "theme" ? "T" : "P"} ${i.name} - ${i.url}`)
.join("\n")}`
);
return;
}
case "export": {
const [, out] = args;
if (!out) {
console.error("Usage: ppm export <out_file>");
process.exit(1);
}
const packages = (await getPackages()).map((p) => ({
type: p.type,
url: p.url,
}));
await fsPromises.writeFile(out, JSON.stringify(packages, null, 2));
console.log(`Exported ${packages.length} packages successfully!`);
return;
}
case "import": {
const [, in_file] = args;
if (!in_file) {
console.error("Usage: ppm import <in_file>");
process.exit(1);
}
const packageList = await getPackages();
const packages = JSON.parse(await fsPromises.readFile(in_file));
const result = (
await Promise.all(
packages.map(async ({ url, type }) => {
if (packageInstalled(url, packageList)) {
console.error(
`Package ${getPackageName(
url
)} already installed, not installing`
);
return null;
}
try {
await exec(`git clone ${url}`, {
cwd: type === "theme" ? themes : plugins,
});
return { status: true, url: url };
} catch (e) {
console.error(`Failed to import ${url} because:`);
console.error(e);
console.error("Continuing import...");
return { status: false, url: url };
}
})
)
).filter((i) => i !== null);
console.log(
`Imported and installed ${
result.filter((p) => p.status).length
} packages successfully!`
);
const failed = result.filter((p) => !p.status);
if (failed.length > 0) {
console.error(`Failed to import ${failed.length} packages:`);
console.error(failed.map((p) => p.url).join("\n"));
}
return;
}
default: {
console.error(
[
"A super simple ~~bodgy~~ package manager for Powercord",
"By @lleyton (Leа#7962 on Discord, the a is a special character, so copy it)",
"",
"Usage: ppm <command> [args...]",
"Commands:",
" install <theme|plugin:git_url> [theme|plugin:git_url...]",
" remove <package_name> [package_name...]",
" upgrade",
" list",
" export <out_file>",
" import <in_file>",
].join("\n")
);
return;
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment