Ittai v2: rollup edition (#24)

Reviewed-on: Ittai/ittai#24
This commit is contained in:
AAGaming 2022-07-18 20:27:03 +02:00
parent 7f448d0910
commit 47868b22f5
52 changed files with 1691 additions and 2882 deletions

View File

@ -1,6 +1,5 @@
<p align="center">
<img src="./assets/png/dark/Background.png" width="2000">
<sup>Logo made by Sylix#8103 (267131905688862720)</sup>
<img src="./assets/png/dark/Background.png" alt="Ittai Logo made by Sylix#8103" width="2000">
</p>
<p align="center">
@ -22,7 +21,7 @@
## Features
- Build even single-file BD plugins from multi-file projects.
- Write one codebase, distribute one product.
- Write one codebase, run it anywhere.
- Hot rebuild your plugins.
- Use various flavors of JS to create your plugins.
- [x] [JSX and TSX support](https://reactjs.org/docs/introducing-jsx.html)
@ -98,5 +97,5 @@ pnpm link --global ittai
This command will build your plugin for BetterDiscord, Powercord v2, and GooseMod, but will only copy it to BetterDiscord's plugins folder. It will also hot rebuild your plugin for you.
```bash
ittai --plugin="./test/plugin" --betterdiscord="/path/to/plugins/fgbd" --powercordv2 --goosemod --watch
ittai --plugin="./test/plugin" --betterdiscord="/path/to/plugins/fgbd" --powercordv2="/path/to/plugins/fgbd" --goosemod-"/path/to/plugins/fgbd" --watch
```

View File

@ -1,3 +0,0 @@
{
"sourceMaps": true
}

View File

@ -1,68 +1,34 @@
const yargs = require("yargs/yargs");
const { hideBin } = require("yargs/helpers");
const { argv } = yargs(hideBin(process.argv));
const fs = require("fs-extra");
const path = require("path");
const webpack = require("webpack");
const TerserPlugin = require("terser-webpack-plugin");
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const ExtraWatchWebpackPlugin = require("extra-watch-webpack-plugin");
// const beautify = require("js-beautify").js;
const rollup = require("rollup");
const commonjs = require("@rollup/plugin-commonjs");
const styles = require("rollup-plugin-styles");
const json = require("@rollup/plugin-json");
const { nodeResolve } = require("@rollup/plugin-node-resolve");
const { default: esbuild } = require("rollup-plugin-esbuild");
const alias = require("@rollup/plugin-alias");
const replace = require("@rollup/plugin-replace");
const memfs = require("rollup-plugin-memory-fs");
const isValidPath = require("is-valid-path");
const term = require("terminal-kit").terminal;
const { defaultIttaiSettings, ittaiSettings, ittaiSettingsPath } = require("../utils/settings");
const appliedSettings = Object.assign({}, defaultIttaiSettings, ittaiSettings)
const DevServer = require("../devserver");
const logger = require("../utils/logger")
const logger = require("../utils/logger");
const progress = require("./plugins/progress");
const appliedSettings = Object.assign({}, defaultIttaiSettings, ittaiSettings)
let core;
function nanoseconds() {
const hrTime = process.hrtime();
return hrTime[0] * 1000000000 + hrTime[1];
function sleep(ms) {
return new Promise(r => setTimeout(r, ms))
}
// const { header } = require("../ui");
let startTime;
let error = false;
let _argv;
let ds;
let hasCreatedOutputPath;
let dist = "";
let fsMap = new Map();
const mockFs = /*new Proxy(*/{
mkdir: (p, cb) => {
// console.log(path)
if (p === "/ittaiTemp") {
hasCreatedOutputPath = true;
cb();
}
},
join: path.join,
stat: (p, cb) => {
// if (p === "/ittaiTemp/index.js") {
cb(true);
// }
},
writeFile: (p, buf, cb) => {
fsMap.set(p.replace(/\\/g, "/").replace(/^\/ittaiTemp\//, ""), buf);
cb();
}
}/*, {
get: (t, p, r) => {
console.log("getting func " + p);
return (...args) => {
console.log("args of func " + p, args);
if (t[p]) {
return t[p](...args);
}
}
}
})*/
let watcher;
process.on("SIGINT", () => {
term("\n\n")
@ -70,13 +36,12 @@ process.on("SIGINT", () => {
if (_argv.watch) {
ds.stop();
}
// try {
// fs.rmSync(dist, { recursive: true });
// } catch (e) {}
watcher.close();
term.hideCursor(false);
term.processExit(130); //130 means "Script terminated by Control-C" for Bash
})
function build(argv, forceNoWatch = false) {
async function build(argv, forceNoWatch = false) {
const pluginPath = argv.plugin ?? argv.p ?? process.cwd()
const manifest = ittaiSettings?.manifest
@ -87,527 +52,179 @@ function build(argv, forceNoWatch = false) {
_argv = argv;
core = path.resolve(pluginPath, "node_modules", "ittai");
return new Promise((resolve) => {
if (
manifest != null &&
fs.existsSync(pluginPath) &&
fs.existsSync(ittaiSettingsPath) &&
fs.existsSync(core)
) {
dist = argv.output ?? ittaiSettings.output ?? path.resolve(path.join(pluginPath, "..", pluginName + "-dist"));
fs.ensureDirSync(dist)
let mods = [];
for (let mod of ["betterdiscord", "powercordv2", "goosemod"]) {
if (argv[mod]) mods.push(mod);
}
if (argv.watch && mods.length >> 1) {
term.red("You can only build for one mod at a time in watch mode.\n");
process.exit(1);
}
let promises = [];
for (let mod of mods) {
promises.push(new Promise((resolve) => {
if (
manifest != null &&
fs.existsSync(pluginPath) &&
fs.existsSync(ittaiSettingsPath) &&
fs.existsSync(core)
) {
const localFormat = Boolean(appliedSettings.customClassNames)
? (
typeof appliedSettings.customClassNames === "string" ?
appliedSettings.customClassNames.replace("[plugin]", pluginName.replace(/ /g, "")) :
defaultIttaiSettings.customClassNames.replace("[plugin]", pluginName.replace(/ /g, ""))
)
: "[local]"
// term.eraseDisplay();
// header(argv);
// fs.ensureFileSync(path.join(dist, "index.js"));
// fs.ensureFileSync(path.join(dist, "manifest.json"));
// const stylesheetLoader = path.resolve(
// path.join(__dirname, "stylesheetLoader.js")
// );
const styleLoader = {
loader: require.resolve("style-loader"),
options: {
attributes: { ittai: true, plugin: pluginName }
if (argv.watch) {
ds = new DevServer(pluginName);
ds.start();
term.fullscreen(false);
term.moveTo(1, 1);
// term.hideCursor()
}
};
const localFormat = Boolean(appliedSettings.customClassNames)
? (
typeof appliedSettings.customClassNames === "string" ?
appliedSettings.customClassNames.replace("[plugin]", pluginName.replace(/ /g, "")) :
defaultIttaiSettings.customClassNames.replace("[plugin]", pluginName.replace(/ /g, ""))
)
: "[local]"
// const jsBuilder = {
// loader: require.resolve("@sucrase/webpack-loader"),
// options: {
// production: argv.production ? true : false,
// // enableLegacyBabel5ModuleInterop: true,
// transforms: ["jsx", "imports"],
// }
// };
const jsBuilder = {
loader: require.resolve("swc-loader"),
options: {
jsc: {
target: "es2021",
parser: {
syntax: "ecmascript",
jsx: true,
dynamicImport: false,
privateMethod: false,
functionBind: false,
classPrivateProperty: false,
exportDefaultFrom: false,
exportNamespaceFrom: false,
decorators: false,
decoratorsBeforeExport: false,
importMeta: false,
topLevelAwait: true
},
transform: {
react: {
runtime: "classic"
}
}
}
const env = {
preventAssignment: true,
"process.env.NODE_ENV": argv.production ? '"production"' : '"development"',
"process.env.CLIENT_MOD": JSON.stringify(mod),
"process.env.DEV_MODE": mod !== "betterdiscord" && !argv.production ? '"true"' : '"false"',
"process.env.HAS_CHANGELOG": manifest.changelog ? '"true"' : '"false"'
}
}
const tsBuilder = {
loader: require.resolve("swc-loader"),
options: {
jsc: {
target: "es2021",
parser: {
syntax: "typescript",
tsx: true,
dynamicImport: false,
decorators: false,
topLevelAwait: true
},
transform: {
react: {
runtime: "classic"
}
}
}
}
}
if (argv.watch) {
ds = new DevServer(pluginName);
ds.start();
term.fullscreen(false);
term.moveTo(1, 1);
// term.hideCursor()
}
const progressBar = term.progressBar({
width: Math.round(term.width / 1.5),
title: ` ITTAI ^`,
titleStyle: term.bold.white[["bgBrightRed", "bgBrightGreen", "bgCyan", "bgBrightMagenta", "bgYellow"][Math.floor(Math.random() * 5)]],
eta: argv.production ?? false,
percent: true,
barStyle: term.green,
percentStyle: term.green
});
const compiler = webpack(
{
mode: argv.production ? "production" : "development",
devtool: argv.production ? "source-map" : false,
target: "browserslist:chrome 91",
// publicPath: "/",
entry: path.resolve(pluginPath),
context: path.resolve(pluginPath),
output: {
library: "plugin",
libraryTarget: "var",
filename: "index.js",
path: "/ittaiTemp",
devtoolModuleFilenameTemplate: info =>
`file:///${pluginName}/${info.resourcePath}`.replace(
/\\/g,
'/',
) // windows moment
// `file:///${path.resolve(info.absoluteResourcePath).replace(
// /\\/g,
// '/',
// )}`,
},
experiments: {
topLevelAwait: false
/**
* @type {import("rollup").RollupOptions}
*/
const rollupConfig = {
input: path.join(core, "loader", "entrypoint.ts"),
watch: {
skipWrite: true
},
plugins: [
new ExtraWatchWebpackPlugin({
dirs: [core],
}),
new webpack.ProgressPlugin((percentage, message, ...args) => {
if (percentage === 0) {
startTime = nanoseconds();
error = false;
if (argv.watch) {
process.stdout.write("\u001b[3J\u001b[2J\u001b[1J"); // clears scrollback
term.clear();
term.moveTo(1, 1);
}
// term.eraseDisplay();
// header(argv);
}
progressBar.update({
progress: percentage,
});
if (percentage === 1) {
term.eraseLine();
if (error) {
errored = true;
if (argv.watch) {
process.stdout.write("\u001b[3J\u001b[2J\u001b[1J"); // clears scrollback
term.clear();
term.moveTo(1, 1);
}
if (error.err) {
term.red((error.err.stack || error.err) + "\n");
if (err.details) {
term.red(error.err.details + "\n");
}
return;
}
const info = error.stats.toJson();
if (error.stats.hasErrors()) {
for (const error of info.errors)
term.red(error.message + "\n");
}
if (error.stats.hasWarnings()) {
for (const warning of info.warnings)
term.red(warning.message + "\n");
}
logger.makeBadge("!", term.bold.bgRed)("Build failed after ")
.red(`${Math.round((nanoseconds() - startTime) / 1000000).toLocaleString()} ms`)
(".\n");
// term.hideCursor();
// return resolve();
} else {
const localeOpts = { minimumIntegerDigits: 2, useGrouping: false }
const currentTime = new Date()
logger.makeBadge("✓", term.bold.bgGreen)
("Build done! Built in ")
.brightGreen(`${Math.round((nanoseconds() - startTime) / 1000000).toLocaleString()} ms`)
(` at `)
.cyan(`${currentTime.getHours().toLocaleString(undefined, localeOpts)}:${currentTime.getMinutes().toLocaleString(undefined, localeOpts)}:${currentTime.getSeconds().toLocaleString(undefined, localeOpts)}`)
(".\n\n")
}
replace(env),
alias({
entries: {
"@ittai/plugin": path.resolve(path.join(pluginPath, JSON.parse(fs.readFileSync(path.join(pluginPath, "package.json"), "utf-8")).main)),
"@ittai/config": path.resolve(path.join(pluginPath, "ittaiconfig.json")),
"react": path.resolve(path.join(core, "packages", "react.cjs")),
"react-dom": path.resolve(path.join(core, "packages", "react-dom.cjs")),
"react-spring": path.resolve(path.join(core, "packages", "react-spring.cjs")),
"lodash": path.resolve(path.join(core, "packages", "lodash.cjs"))
}
}),
new webpack.DefinePlugin({
__ITTAI_WATCH__: !!argv.watch,
__ITTAI_DEV__: !argv.production
commonjs(),
json(),
styles({
modules: {
generateScopedName: localFormat
},
autoModules: true,
}),
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1, // TODO: figure out proper code splitting, this just disables it
esbuild({
target: "es2021",
jsx: "transform",
sourceMap: true
}),
new ForkTsCheckerWebpackPlugin({
logger: {
log: message => {
if (~message.indexOf("No errors found.")) {
term.getCursorLocation((err, x, y) => term
.eraseLine().moveTo(1, y)
.bold.bgBrightBlue(" TS ").brightBlue(` No issues found!`)
)
} else if (~message.indexOf("Type-checking in progress...")) {
logger.makeBadge("TS", term.bold.bgGray).gray("Checking types...")
} else term(message)
},
error: message => term.getCursorLocation((err, x, y) => term
.eraseLine().moveTo(1, y)
.bold.bgRed(" TS ").brightBlue(` ${message}`)
)
}
})
// new webpack.ProvidePlugin({
// React: path.resolve(core, "libraries", "React.js"),
// }),
nodeResolve({
extensions: [".ts", ".tsx", ".jsx", ".js", ".cjs", ".mjs", ".css", ".scss"]
}),
memfs(),
progress(argv, logger, mod)
],
externals: [
function ({ context, request }, callback) {
if (/^(electron|powercord.*)$/.test(request)) {
// Externalize to a commonjs module using the request path
return callback(null, "commonjs2 " + request);
}
// Continue without externalizing the import
callback();
},
],
resolve: {
extensions: [
".js",
".jsx",
".ts",
".tsx",
".coffee",
".css",
".scss",
".sass",
".less",
".styl",
],
alias: {
ittai: path.resolve(core),
react: path.resolve(core, "packages", "react.js"),
"react-dom": path.resolve(core, "packages", "react-dom.js"),
"react-spring": path.resolve(core, "packages", "react-spring.js"),
"lodash": path.resolve(core, "packages", "lodash.js"),
"@ittai/config": path.resolve(pluginPath, "ittaiconfig.json")
},
output: {
dir: "./",
intro: "// Made with Ittai - https://git.catvibers.me/ittai/ittai\nlet IttaiInternals = {};\nlet ittaiPluginExport=(()=>{",
outro: "})();\nif (typeof module !== \"undefined\") module.exports = ittaiPluginExport;\nreturn ittaiPluginExport;",
interop: false,
format: "iife",
// exports: "default",
sourcemap: !argv.production ? "inline" : false,
sourcemapPathTransform: path =>
`file:///${pluginName}/${path}`.replace(
/\\/g,
'/',
),
manualChunks: () => "app",
},
optimization: {
minimize: false,
usedExports: true,
sideEffects: true,
minimize: !!argv.production,
minimizer: [
new TerserPlugin({
minify: (file, sourceMap) => {
// https://github.com/mishoo/UglifyJS2#minify-options
const uglifyJsOptions = {
/* your `uglify-js` package options */
output: {
beautify: true,
indent_level: "\t",
comments: "some",
},
mangle: false,
compress: {
// Enabled
dead_code: true,
annotations: true,
arguments: true,
default_values: true,
keep_fargs: false,
join_vars: true,
imports: true,
unused: true,
side_effects: true,
// Disabled
arrows: false,
assignments: false,
awaits: false,
booleans: false,
collapse_vars: false,
directives: false,
drop_console: false,
drop_debugger: false,
evaluate: false,
expression: false,
functions: false,
global_defs: false,
hoist_exports: false,
hoist_funs: false,
hoist_props: false,
if_return: false,
inline: false,
keep_infinity: true,
loops: false,
merge_vars: false,
module: false,
negate_iife: false,
objects: false,
properties: true,
pure_funcs: null,
pure_getters: "strict",
reduce_funcs: false,
reduce_vars: false,
rests: false,
sequences: false,
spreads: false,
strings: false,
switches: false,
templates: false,
top_retain: false,
toplevel: false,
typeofs: false,
varify: false,
yields: false
}
};
if (sourceMap) {
uglifyJsOptions.sourceMap = {
content: sourceMap,
};
}
return require("uglify-js").minify(file, uglifyJsOptions);
},
}),
],
},
module: {
rules: [
// {
// test: /\.[tj]sx?$/i,
// enforce: "pre",
// use: ["source-map-loader"],
// },
{
test: /\.css$/i,
use: [
styleLoader,
{
loader: require.resolve("css-loader"),
options: {
modules: true,
import: true,
modules: {
localIdentName: localFormat
}
},
},
],
},
{
test: /\.s[ac]ss$/i,
use: [
styleLoader,
{
loader: require.resolve("css-loader"),
options: {
modules: true,
import: true,
importLoaders: 1,
modules: {
localIdentName: localFormat
}
},
},
require.resolve("sass-loader"),
],
},
{
test: /\.styl$/i,
use: [
styleLoader,
{
loader: require.resolve("css-loader"),
options: {
modules: true,
import: true,
importLoaders: 1,
modules: {
localIdentName: localFormat
}
},
},
require.resolve("stylus-loader"),
],
},
{
test: /\.less$/i,
use: [
styleLoader,
{
loader: require.resolve("css-loader"),
options: {
modules: true,
import: true,
importLoaders: 1,
modules: {
localIdentName: localFormat
}
},
},
require.resolve("less-loader"),
],
},
{
test: /\.m?jsx?$/i,
// exclude: /node_modules/,
use: jsBuilder
},
{
test: /\.tsx?$/i,
use: [
tsBuilder,
// "ts-loader"
],
// exclude: /node_modules/
},
{
test: /\.coffee?$/i,
// exclude: /node_modules/,
use: [
jsBuilder,
require.resolve("coffee-loader")
], // cool not gonna test it have fun square
},
],
},
});
compiler.outputFileSystem = mockFs;
const runCb = (err, stats) => {
if (err || stats.hasErrors()) error = { err, stats };
stats?.toJson()?.errors?.forEach(e => console.error(e.stack));
const outputPath = path.join(dist, "index.js");
let builtCode = fsMap.get("index.js").toString("utf-8");
// if (!builtCode.endsWith("})();")) {
builtCode = "(()=>{let hasModule;try {module; hasModule = true;}catch(e) {hasModule = false;}" + builtCode + "\nplugin = plugin.default;let ittaiLoadDevMode = false;try{isIttaiLoadingDevMode;ittaiLoadDevMode=true;}catch(e){}let wrappedPlugin = plugin.__ittaiWrap(plugin, ittaiLoadDevMode);if (hasModule) module.exports = wrappedPlugin;return wrappedPlugin;})();";
// Add clientmod-specific code.
builtCode = require("./powercordv2")(builtCode);
builtCode = require("./betterdiscord")(builtCode, manifest);
builtCode = require("./goosemod")(builtCode);
// }
// builtCode = beautify(builtCode, {
// indent_with_tabs: true,
// });
// // Get rid of ugly blank lines.
// builtCode = builtCode.replace(/\n{2,}/g, "\n");
if (argv.watch) {
if (fs.existsSync(argv.betterdiscord)) {
fs.writeFileSync(
path.join(argv.betterdiscord, bdFileName),
builtCode
);
onwarn(warning, rollupWarn) {
if (warning.code !== 'CIRCULAR_DEPENDENCY' && warning.code !== 'EVAL' && warning.code !== "MISSING_NAME_OPTION_FOR_IIFE_EXPORT") {
rollupWarn(warning);
}
} else {
fs.writeFileSync(outputPath, builtCode);
// fs.copyFileSync(
// path.join(pluginPath, "manifest.json"),
// path.join(dist, "manifest.json")
// );
fs.writeFileSync(path.join(dist, "manifest.json"), JSON.stringify(manifest))
if (argv.goosemod) {
fs.writeFileSync(path.join(dist, "goosemodModule.json"), JSON.stringify(manifest))
}
if (isValidPath(argv.goosemod)) {
fs.ensureDirSync(argv.goosemod);
fs.copySync(dist, argv.goosemod);
}
if (isValidPath(argv.powercordv2)) {
fs.ensureDirSync(argv.powercordv2);
fs.copySync(dist, argv.powercordv2);
}
if (fs.existsSync(argv.betterdiscord)) {
fs.copyFileSync(
path.resolve(path.join(dist, "index.js")),
path.join(argv.betterdiscord, bdFileName)
);
}
}
resolve();
if (argv.watch) {
ds && ds.reload(builtCode);
}
};
if (forceNoWatch ? false : argv.watch) {
compiler.watch({
followSymlinks: true,
ignored: ["ittaiTemp", path.join(pluginPath, dist)],
}, runCb);
} else {
compiler.run(runCb);
}
} else {
logger.makeBadge("!", term.bold.bgRed)("Please install Ittai's core or check if the selected plugin folder contains a ").bold.yellow("ittaiconfig.json")(" file. Check ").underline.cyan("https://git.catvibers.me/Ittai/ittai/")(" for more information.\n")
term.processExit(1)
}
watcher = rollup.watch(rollupConfig)
watcher.on("event", async (event) => {
switch (event.code) {
case "BUNDLE_END":
const outputs = (await event.result.generate(rollupConfig.output))
let builtCode = outputs.output.map(x => x.code).join("\n/*ITTAI CONCAT BUNDLE*/\n");
if (!argv.production) {
const mapURL = outputs.output.find(x => x.map)?.map.toUrl();
builtCode = builtCode + "\n//# sourceMappingURL=" + mapURL;
}
// builtCode = "(()=>{let IttaiInternals = {};return " + builtCode + "\n})();";
// Add clientmod-specific code.
if (mod == "powercordv2") builtCode = require("./powercordv2")(builtCode);
if (mod == "betterdiscord")
builtCode = require("./betterdiscord")(builtCode, manifest);
if (mod == "betterdiscord") builtCode = require("./goosemod")(builtCode);
if (argv.watch) {
if (mod == "betterdiscord" && fs.existsSync(argv.betterdiscord)) {
fs.writeFileSync(
path.join(argv.betterdiscord, bdFileName),
builtCode
);
}
} else {
if (mod == "goosemod" && isValidPath(argv.goosemod)) {
fs.ensureDirSync(argv.goosemod);
fs.writeFileSync(
path.join(argv.goosemod, "index.js"),
builtCode
);
fs.writeFileSync(path.join(argv.goosemod, "goosemodModule.json"), JSON.stringify(manifest))
}
if (mod == "powercordv2" && isValidPath(argv.powercordv2)) {
fs.ensureDirSync(argv.powercordv2);
fs.writeFileSync(
path.join(argv.powercordv2, "index.js"),
builtCode
);
fs.writeFileSync(path.join(argv.powercordv2, "manifest.json"), JSON.stringify(manifest))
}
if (mod == "betterdiscord" && isValidPath(argv.betterdiscord)) {
fs.ensureDirSync(argv.betterdiscord);
fs.writeFileSync(
path.join(argv.betterdiscord, bdFileName),
builtCode
);
}
}
if (argv.watch) {
ds && ds.reload(builtCode);
}
event.result.close();
if (!argv.watch) {
resolve();
watcher.close();
}
break;
}
});
} else {
logger.makeBadge("!", term.bold.bgRed)("Please install Ittai's core or check if the selected plugin folder contains a ").bold.yellow("ittaiconfig.json")(" file. Check ").underline.cyan("https://git.catvibers.me/Ittai/ittai/")(" for more information.\n")
term.processExit(1)
}
}));
await sleep(1000);
}
Promise.all(promises).then(() => term.processExit(0)).catch((e) => {
term.red("An error occured in the builder, please report this issue.", e)
term.processExit(1)
});
}

View File

@ -0,0 +1,89 @@
const path = require("path");
const term = require("terminal-kit").terminal;
const { highlight } = require('cli-highlight')
function nanoseconds() {
const hrTime = process.hrtime();
return hrTime[0] * 1000000000 + hrTime[1];
}
const modColors = { betterdiscord: term.bold.bgBrightBlue.white, powercordv2: term.bold.bgCyan.black, goosemod: term.bold.bgBlack.white}
const modBadges = { betterdiscord: "BD", powercordv2: "PC", goosemod: "GM" }
module.exports = function createProgressBar(argv, logger, mod) {
let total = 0;
let last = 0;
let startTime;
const progressBar = term.progressBar({
width: Math.round(term.width / 1.5),
title: ` ${modBadges[mod]} ^`,
titleStyle: modColors[mod],
eta: argv.production ?? false,
items: 0,
barStyle: term.green,
percentStyle: term.green
});
return {
name: "ittaiProgressBar",
load(id) {
total++;
progressBar.startItem(id);
},
transform(_, id) {
progressBar.itemDone(id);
},
buildStart() {
startTime = nanoseconds();
error = false;
if (argv.watch) {
process.stdout.write("\u001b[3J\u001b[2J\u001b[1J"); // clears scrollback
term.clear();
term.moveTo(1, 1);
}
// progressBar.reset();
progressBar.update({items: last})
progressBar.resume();
progressBar.reset();
},
buildEnd(error) {
term.eraseLine();
last = total;
total = 0;
progressBar.stop();
if (error) {
errored = true;
if (argv.watch) {
process.stdout.write("\u001b[3J\u001b[2J\u001b[1J"); // clears scrollback
term.clear();
term.moveTo(1, 1);
}
if (error.loc) {
term.red(`${error.name}: ${error.message.replace(" in " + error.loc.file, "")}${error.loc ? ` in ${error.loc.file}:${error.loc.line}:${error.loc.column}` : ''}\n`);
term(highlight(error.frame, { language: path.extname(error.loc.file).split('.').pop(), ignoreIllegals: true }) + "\n");
} else {
term.red(error.toString() + "\n");
}
logger.makeBadge("!", term.bold.bgRed)("Build failed after ")
.red(`${Math.round((nanoseconds() - startTime) / 1000000).toLocaleString()} ms`)
(".\n");
if (argv.watch) term.hideCursor();
if (!argv.watch) {
term.processExit(1)
}
// return resolve();
} else {
const localeOpts = { minimumIntegerDigits: 2, useGrouping: false }
const currentTime = new Date()
logger.makeBadge(modBadges[mod], modColors[mod])
(`Build done for ${mod}! Built in `)
.brightGreen(`${Math.round((nanoseconds() - startTime) / 1000000).toLocaleString()} ms`)
(` at `)
.cyan(`${currentTime.getHours().toLocaleString(undefined, localeOpts)}:${currentTime.getMinutes().toLocaleString(undefined, localeOpts)}:${currentTime.getSeconds().toLocaleString(undefined, localeOpts)}`)
(".\n\n")
if (argv.watch) term.hideCursor();
}
}
}
}

View File

@ -70,7 +70,7 @@ module.exports = class DevServer {
("On ")
.brightCyan(`GooseMod`)
(` and `)
.brightCyan(`Powercord`)
.brightCyan(`Power${"C".toLowerCase()}ord`)
(` (make sure to switch to the Electron isolated context in Powercord), run the following code in the console to load your plugin in dev mode:\n`)
term.grey(`fetch("http://localhost:3000/client.js").then(r => r.text()).then(r => eval(r))\n\n`)
} else {

View File

@ -24,5 +24,4 @@ if (!areAllArgsAvaiable || argv.help || argv.h) {
}
const build = require("./build");
if (areAllArgsAvaiable) build(argv)

View File

@ -1,7 +1,7 @@
{
"name": "@ittai/builder",
"private": false,
"version": "1.2.6",
"version": "2.0.0",
"description": "Ittai Builder",
"scripts": {
"build-docs": "jsdoc -c jsdoc.json"
@ -14,42 +14,35 @@
"repository": "https://git.catvibers.me/ittai/ittai",
"author": "Kyza, AAGaming, A user",
"dependencies": {
"@swc/core": "^1.2.133",
"@rollup/plugin-alias": "^3.1.9",
"@rollup/plugin-commonjs": "^22.0.1",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.3.0",
"@rollup/plugin-replace": "^4.0.0",
"@types/css": "^0.0.31",
"coffee-loader": "^2.0.0",
"coffeescript": "^2.6.1",
"css-loader": "^5.2.7",
"cli-highlight": "^2.1.11",
"esbuild": "^0.14.49",
"express": "^4.17.2",
"extra-watch-webpack-plugin": "^1.0.3",
"fork-ts-checker-webpack-plugin": "^7.2.0",
"fs-extra": "^9.1.0",
"is-valid-path": "^0.1.1",
"less": "^4.1.2",
"less-loader": "^7.3.0",
"rollup": "^2.76.0",
"rollup-plugin-esbuild": "^4.9.1",
"rollup-plugin-memory-fs": "^1.0.3",
"rollup-plugin-styles": "^4.0.0",
"sass": "^1.48.0",
"sass-loader": "^10.2.1",
"style-loader": "^2.0.0",
"stylus": "^0.54.8",
"stylus-loader": "^4.3.3",
"swc-loader": "^0.1.15",
"terminal-kit": "^1.49.4",
"terser-webpack-plugin": "^5.3.0",
"ts-loader": "^9.2.6",
"typescript": "^4.5.5",
"uglify-js": "^3.16.2",
"webpack": "^5.66.0",
"ws": "^8.4.2",
"yargs": "^17.3.1"
},
"devDependencies": {
"better-docs": "^2.3.2"
},
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"react",
"react-dom",
"prop-types"
"prop-types",
"webpack"
]
}
}

File diff suppressed because it is too large Load Diff

View File

@ -37,7 +37,7 @@ Building options:\n`)
term("\nOther:\n")
term.table([
['Full argument', 'Abreviation', 'Description'],
['--help', "-h", "This help page"]
['--help', "-h", "Shows this help page"]
], tableOpts)
term("\n\n").bold.brightCyan("Note:")(" You can create a configuration file. For more information, run ").inverse("ittai --help=config")
}

View File

@ -1,10 +1,9 @@
import { React, ModalActions } from "ittai/webpack";
import { React, ModalActions } from "../webpack";
import * as settings from "../settings";
import { Markdown, Modal, Flex, Heading, Text } from "../components";
import { Changelog, ChangelogModal } from "../classes";
import { changelog as settingsChangelog, manifest } from "@ittai/config"
import * as config from "@ittai/config"
import { joinClasses } from "../utilities"
export const renderChangelogContent = (content) => {
return <>
{Object.entries(content).map(([title, { type, items }]) => <div className={ChangelogModal.content}>
@ -21,6 +20,7 @@ export const renderChangelogContent = (content) => {
}
export const openChangelogModal = (changelog = settingsChangelog) => {
const { changelog: settingsChangelog, manifest } = config;
ModalActions.openModal((props) => <Modal.ModalRoot {...props} size={Modal.ModalSize.SMALL} className={ChangelogModal.modal}>
<Modal.ModalHeader separator={false}>
<Flex>

View File

@ -1,6 +1,6 @@
import { findByProps } from "../webpack";
export const Changelog = findByProps("lead", "socialLink")
export const ChangelogModal = findByProps("maxModalWidth", "content") //i assume its related to the changelog modal
export const Margins = findByProps("marginLarge", "marginTop20")
export const CardLook = findByProps("arrow", "container", "description")
export const Changelog = /*#__PURE__*/ findByProps("lead", "socialLink")
export const ChangelogModal = /*#__PURE__*/ findByProps("maxModalWidth", "content") //i assume its related to the changelog modal
export const Margins = /*#__PURE__*/ findByProps("marginLarge", "marginTop20")
export const CardLook = /*#__PURE__*/ findByProps("arrow", "container", "description")

View File

@ -1,18 +0,0 @@
.category {
margin-bottom: 20px;
}
.category-header-icon{
width: 24px;
height: 24px;
color: var(--interactive-active);
}
.category-header-icon.closed{
transform: rotate(-90deg);
}
.category-content {
margin-top: 16px;
padding: 0 20px;
}

View File

@ -1,7 +1,6 @@
import { React } from "ittai/webpack";
import styles from "./Category.css"
import { React } from "../webpack";
import DiscordIcon from "./DiscordIcon";
import { Text, Forms } from "./index";
import { Text } from "./index";
import { CardLook } from "../classes";
/**
@ -22,7 +21,7 @@ import { CardLook } from "../classes";
export default function Category({ title, description, icon, children, openedByDefault = false }) {
const [opened, setOpened] = React.useState(openedByDefault);
return <div className={styles["category"]}>
return <div styles={{marginBottom: "20px"}}>
<div className={CardLook.container} onClick={() => setOpened(!opened)}>
{icon && <div className={CardLook.icon}>
<DiscordIcon name={icon} style={{width: "20px", height: "20px"}} />
@ -32,11 +31,11 @@ export default function Category({ title, description, icon, children, openedByD
{description && <Text>{description}</Text>}
</div>
<div className={CardLook.arrow}>
<DiscordIcon name="DropdownArrow" className={`${styles["category-header-icon"]} ${!opened ? styles["closed"] : ""}`} />
<DiscordIcon name="DropdownArrow" style={{width: "24px", height: "24px", color: "var(--interactive-active)", transform: !opened ? "rotate(-90deg)" : void 0}} />
</div>
</div>
{opened && <div className={styles["category-content"]}>{children}</div>}
{opened && <div style={{marginTop: "16px", padding: "0 20px"}}>{children}</div>}
{/* <Forms.FormDivider className="dividerDefault-3C2-ws"/> */}
</div>

View File

@ -1,10 +1,10 @@
import { React } from "ittai/webpack";
import { React } from "../webpack";
import { findByProps, findByDisplayName } from "../webpack";
const classes = {
default: findByProps("icon", "selected").icon,
contextmenu: findByProps("icon", "submenu").icon,
minipopover: findByProps("icon", "isHeader").icon,
default: /*#__PURE__*/(()=>findByProps("icon", "selected").icon)(),
contextmenu: /*#__PURE__*/(()=>findByProps("icon", "submenu").icon)(),
minipopover: /*#__PURE__*/(()=>findByProps("icon", "isHeader").icon)(),
};
/**

View File

@ -1,12 +1,12 @@
import { React } from "ittai/webpack";
import { React } from "../webpack";
import { findByProps } from "../webpack";
const LayerProvider = findByProps("AppLayerProvider").AppLayerProvider().props.layerContext
.Provider;
const AccessibilityProvider = findByProps(
const LayerProvider = /*#__PURE__*/(()=>findByProps("AppLayerProvider").AppLayerProvider().props.layerContext
.Provider)();
const AccessibilityProvider = /*#__PURE__*/(()=>findByProps(
"AccessibilityPreferencesContext"
).AccessibilityPreferencesContext.Provider;
const layerClass = findByProps("LayerClassName").LayerClassName;
).AccessibilityPreferencesContext.Provider)();
const layerClass = /*#__PURE__*/(()=>findByProps("LayerClassName").LayerClassName)();
/**
* Wrap a component rendered out-of-tree in Discord's providers

View File

@ -1,4 +1,4 @@
import { React, Flux } from "ittai/webpack";
import { React, Flux } from "../webpack";
export default function FluxWrapper(props) {
if (!props.children.displayName) props.children.displayName = "FluxProxy";

View File

@ -4,12 +4,13 @@
import { React, findByDisplayName, findByProps, find } from "../webpack";
// Don't re-export our components as they won't treeshake properly. Instead, people can manually import them.
export { default as DiscordIcon } from "./DiscordIcon";
export { default as DiscordProviders } from "./DiscordProviders";
export { default as FluxWrapper } from "./FluxWrapper";
export { default as Category } from "./Category";
// Wrapper for Switch component to make the switch box not being updatable. Check https://github.com/BetterDiscordBuilder/bdbuilder/blob/master/common/hooks/createUpdateWrapper.js
// Wrapper for Switch component to fix the switch box not being updatable. Check https://github.com/BetterDiscordBuilder/bdbuilder/blob/master/common/hooks/createUpdateWrapper.js
export const makeUpdateWrapper = (Component, checkPropName = "value", type = "switch") => {
const typeSwitch = (v) => {
switch (type) {
@ -40,47 +41,44 @@ export const makeUpdateWrapper = (Component, checkPropName = "value", type = "sw
}
}
export const Button = findByProps("Colors", "Looks", "DropdownSizes");
export const Spinner = findByDisplayName("Spinner");
export const Text = findByDisplayName("LegacyText");
export const TextInput = findByDisplayName("TextInput");
export const Tooltip = findByDisplayName("Tooltip");
export const TooltipContainer = findByProps("TooltipContainer")?.TooltipContainer;
export const SlideIn = findByDisplayName("SlideIn");
export const SettingsNotice = findByDisplayName("SettingsNotice");
export const TransitionGroup = findByDisplayName("TransitionGroup");
export const Flex = findByDisplayName("Flex");
export const Card = findByDisplayName("Card");
export const Popout = findByDisplayName("Popout");
export const Progress = findByDisplayName("Progress");
export const Modal = findByProps("ModalRoot")
export const Forms = findByProps('FormItem')
export const ColorPicker = findByDisplayName("ColorPicker")
export const Anchor = findByDisplayName("Anchor")
export const Heading = findByProps("Heading").Heading
export const KeyboardShortcut = findByProps("PRETTY_KEYS").default
export const SearchBar = findByProps("SearchIcon").default
export const OriginalRadioGroup = findByDisplayName("RadioGroup");
export const Button = /*#__PURE__*/findByProps("Colors", "Looks", "DropdownSizes");
export const Spinner = /*#__PURE__*/findByDisplayName("Spinner");
export const Text = /*#__PURE__*/findByDisplayName("LegacyText");
export const TextInput = /*#__PURE__*/findByDisplayName("TextInput");
export const Tooltip = /*#__PURE__*/findByDisplayName("Tooltip");
export const TooltipContainer = /*#__PURE__*/(() => findByProps("TooltipContainer")?.TooltipContainer)();
export const SlideIn = /*#__PURE__*/findByDisplayName("SlideIn");
export const SettingsNotice = /*#__PURE__*/findByDisplayName("SettingsNotice");
export const TransitionGroup = /*#__PURE__*/findByDisplayName("TransitionGroup");
export const Flex = /*#__PURE__*/findByDisplayName("Flex");
export const Card = /*#__PURE__*/findByDisplayName("Card");
export const Popout = /*#__PURE__*/findByDisplayName("Popout");
export const Progress = /*#__PURE__*/findByDisplayName("Progress");
export const Modal = /*#__PURE__*/findByProps("ModalRoot")
export const Forms = /*#__PURE__*/findByProps('FormItem')
export const ColorPicker = /*#__PURE__*/findByDisplayName("ColorPicker")
export const Anchor = /*#__PURE__*/findByDisplayName("Anchor")
export const Heading = /*#__PURE__*/(() => findByProps("Heading").Heading)();
export const KeyboardShortcut = /*#__PURE__*/(() => findByProps("PRETTY_KEYS").default)();
export const SearchBar = /*#__PURE__*/(() => findByProps("SearchIcon").default)();
export const OriginalRadioGroup = /*#__PURE__*/findByDisplayName("RadioGroup");
export const RadioGroup = makeUpdateWrapper(OriginalRadioGroup, "value", "radio");
export const OriginalSwitch = findByDisplayName("Switch");
export const OriginalSwitch = /*#__PURE__*/findByDisplayName("Switch");
export const Switch = makeUpdateWrapper(OriginalSwitch, "checked");
export const OriginalSwitchItem = findByDisplayName("SwitchItem");
export const OriginalSwitchItem = /*#__PURE__*/findByDisplayName("SwitchItem");
export const SwitchItem = makeUpdateWrapper(OriginalSwitchItem, "value");
export const Markdown = find(m => m?.default?.displayName == "Markdown" && m?.default?.rules)?.default
export const Markdown = /*#__PURE__*/(() => find(m => m?.default?.displayName == "Markdown" && m?.default?.rules)?.default)();
export const ContextMenu = findByProps("MenuItem").default
export const ContextMenu = /*#__PURE__*/findByProps("MenuItem").default
Object.entries(findByProps("MenuItem")).forEach(([key, contents]) => {
if (!ContextMenu[key]) {
ContextMenu[key] = contents
}
})
export const Avatar = findByProps("AnimatedAvatar").default
Object.entries(findByProps("AnimatedAvatar")).forEach(([key, contents]) => {
if (!Avatar[key]) {
Avatar[key] = contents
}
})
export const Avatar = /*#__PURE__*/(() => findByProps("AnimatedAvatar").default)()
export const AnimatedAvatar = /*#__PURE__*/(() => findByProps("AnimatedAvatar").AnimatedAvatar)()
export const AvatarSizes = /*#__PURE__*/(() => findByProps("AnimatedAvatar").Sizes)()
export const Slider = findByProps("MarkerPositions").default
Slider.MarkerPositions = findByProps("MarkerPositions").MarkerPositions
export const Slider = /*#__PURE__*/(() => findByProps("MarkerPositions").default)()
export const SliderMarkerPositions = /*#__PURE__*/(() => findByProps("MarkerPositions").MarkerPositions)()

View File

@ -1,4 +1,3 @@
import { getClientMod } from "ittai/utilities";
import { manifest as pluginManifest } from "@ittai/config";
const {name} = pluginManifest
@ -6,7 +5,7 @@ class WSManager {
start () {
this.ws = new WebSocket('ws://localhost:3000');
this.ws.addEventListener('open', () => {
this.ws.send(`identify,${getClientMod()}`);
this.ws.send(`identify,${process.env.CLIENT_MOD}`);
});
this.ws.addEventListener('message', (data) => {
@ -56,15 +55,14 @@ export function reloadPlugin() {
fetch("http://localhost:3000/plugin.js").then(r => r.text()).then(data => {
let __ITTAI_DEVSERVER_VERSION__ = version;
let __ITTAI_DEVSERVER_INSTANCE__ = { version, startDevServer, stopDevServer, reloadPlugin, loadPlugin, manager };
let _plugin = plugin;
plugin = undefined;
// Keep this log so rollup doesn't delete these variables
console.debug("reloading using instance", __ITTAI_DEVSERVER_INSTANCE__, "version", __ITTAI_DEVSERVER_VERSION__)
const instance = eval(data);
loadPlugin(instance);
})
}
export async function loadPlugin(p) {
let mod = getClientMod();
let mod = process.env.CLIENT_MOD;
switch (mod) {
case "goosemod":
@ -76,7 +74,7 @@ export async function loadPlugin(p) {
try {
window.powercord.pluginManager.unload(name);
} catch (e) {
!e.toString().startsWith("Tried to unload a non installed plugin (undefined)") && console.log(e)
!e.toString().startsWith("Error: Tried to unload a non installed plugin (undefined)") && console.log(e)
}
const manifest = Object.assign({
appMode: 'app',

View File

@ -1,13 +1,12 @@
import { getClientMod } from "ittai/utilities";
import * as DevClient from "./client.js";
let hasDevServer = false;
export default async function loadDevServer() {
if (getClientMod() === "betterdiscord") return
if (__ITTAI_DEV__ && __ITTAI_WATCH__) {
export async function loadDevServer() {
if (process.env.DEV_MODE == "true") {
if (process.env.CLIENT_MOD === "betterdiscord") return
hasDevServer = true;
try { __ITTAI_DEVSERVER_VERSION__; } catch (_) { console.log("no server"); hasDevServer = false; } // check for variable, js is weird
const version = (await import("./client")).version
const version = DevClient.version
if (hasDevServer && __ITTAI_DEVSERVER_VERSION__ !== version) {
console.log("Upgrading dev server client")
__ITTAI_DEVSERVER_INSTANCE__.stopDevServer()
@ -16,20 +15,22 @@ export default async function loadDevServer() {
if (hasDevServer) {
console.log("Using existing dev server client")
}
}
if (!hasDevServer && __ITTAI_DEV__ && __ITTAI_WATCH__) {
console.log("Loading dev server client")
import("./client").then(m => m.default());
if (!hasDevServer) {
console.log("Loading dev server client")
DevClient.default();
}
}
}
export function loadDevPlugin(p) {
if (getClientMod() === "betterdiscord") return
if (hasDevServer) {
console.log("Loading using existing dev server client")
__ITTAI_DEVSERVER_INSTANCE__.loadPlugin(p)
} else if (__ITTAI_DEV__ && __ITTAI_WATCH__) {
console.log("Loading plugin using new dev server client")
import("./client").then(m => m.loadPlugin(p));
if (process.env.DEV_MODE == "true") {
if (process.env.CLIENT_MOD === "betterdiscord") return
if (hasDevServer) {
console.log("Loading using existing dev server client")
__ITTAI_DEVSERVER_INSTANCE__.loadPlugin(p)
} else {
console.log("Loading plugin using new dev server client")
DevClient.loadPlugin(p);
}
}