123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- import { execSync } from "node:child_process";
- import { existsSync, rmSync, writeFileSync } from "node:fs";
- import { createRequire } from "node:module";
- import { basename, dirname, resolve } from "node:path";
- import { fileURLToPath } from "node:url";
- import actions from "@actions/core";
- import chalk from "chalk";
- import { config } from "dotenv";
- import webpack from "webpack";
- import { ErrorInfo } from "ts-loader/dist/interfaces";
- import { getFolderLetter } from "../util.js";
- const require = createRequire(import.meta.url),
- rootPath = resolve(fileURLToPath(new URL(".", import.meta.url)), "../..");
- if (!process.env.GITHUB_ACTIONS) config({ path: resolve(rootPath, ".env") });
- export const tsconfig = JSON.stringify({
- extends: "../../../tsconfig.json",
- });
- export default class PresenceCompiler {
- cwd: string;
- constructor(
- public options?: {
- cwd?: string;
- webpack?: webpack.Configuration;
- }
- ) {
- this.cwd = options?.cwd ?? rootPath;
- }
- getPresenceFolder(presence: string) {
- return resolve(this.cwd, "websites", getFolderLetter(presence), presence);
- }
- async compilePresence(
- presences: string | string[],
- options: {
- output?: string;
- transpileOnly?: boolean;
- emit?: boolean;
- } = {}
- ) {
- options.emit ??= true;
- options.transpileOnly ??= false;
- const webpackConfig: webpack.Configuration = {
- mode: "production",
- devtool: "inline-source-map",
- resolve: {
- extensions: [".ts"],
- },
- output: options.emit
- ? {
- iife: false,
- path: options.output,
- filename: "[name].js",
- }
- : undefined,
- plugins: [
- {
- apply(compiler) {
- compiler.hooks.emit.tap("PresenceCompiler", compilation => {
- //* Add empty line after file content to prevent errors from PreMiD
- for (const file in compilation.assets) {
- //@ts-expect-error - This is defined. (ConcatSource class)
- compilation.assets[file].add("\n");
- }
- });
- },
- },
- ],
- module: {
- rules: [
- {
- test: /\.ts$/,
- loader: "ts-loader",
- exclude: /node_modules/,
- options: {
- transpileOnly: options.transpileOnly,
- errorFormatter: (error: ErrorInfo) => {
- actions.error(error.content, {
- file: error.file,
- title: `TS ${error.code}`,
- startLine: error.line,
- });
- return chalk.cyan(
- basename(
- dirname(error.file) +
- "/" +
- basename(error.file) +
- ":" +
- chalk.yellowBright(error.line) +
- ":" +
- chalk.yellowBright(error.character) +
- " - " +
- chalk.redBright("Error ") +
- chalk.gray("TS" + error.code + ": ") +
- error.content
- )
- );
- },
- },
- },
- ],
- },
- ...this.options?.webpack,
- };
- if (Array.isArray(presences)) {
- let errors: webpack.WebpackError[] = [];
- actions.info(chalk.yellow(`Compiling ${presences.length} Presence(s)`));
- for (const presence of presences) {
- const presencePath = this.getPresenceFolder(presence);
- writeFileSync(resolve(presencePath, "tsconfig.json"), tsconfig);
- await this.installPresenceDependencies(presence);
- const job = await new Promise<{
- error: Error | undefined;
- stats: webpack.Stats | undefined;
- }>(r =>
- webpack.webpack(
- {
- ...webpackConfig,
- context: presencePath,
- output: options.emit
- ? {
- iife: false,
- path: presencePath,
- filename: "[name].js",
- }
- : undefined,
- entry: {
- presence: "./presence.ts",
- ...(existsSync(resolve(presencePath, "iframe.ts")) && {
- iframe: "./iframe.ts",
- }),
- },
- },
- (error, stats) => r({ error, stats })
- )
- );
- if (job.error) throw job.error;
- const { service } = require(resolve(presencePath, "metadata.json"));
- if (job.stats?.compilation.errors.length) {
- actions.info(chalk.red(`Failed to compile ${service || presence}`));
- errors.push(...(job.stats?.compilation?.errors || []));
- } else {
- actions.info(
- chalk.green(`Successfully compiled ${service || presence}`)
- );
- }
- }
- for (const presence of presences) {
- if (
- existsSync(resolve(this.getPresenceFolder(presence), "tsconfig.json"))
- )
- rmSync(resolve(this.getPresenceFolder(presence), "tsconfig.json"));
- }
- errors = errors.filter(e => e.name !== "ModuleBuildError");
- if (!errors.length) {
- if (!options.transpileOnly) {
- actions.info(
- chalk.green(`Successfully compiled ${presences.length} Presence(s)`)
- );
- } else {
- actions.info(
- chalk.green(
- `Successfully transpiled ${presences.length} Presence(s)`
- )
- );
- }
- }
- return errors;
- }
- const presencePath = this.getPresenceFolder(presences);
- if (!options.output) options.output = presencePath;
- writeFileSync(resolve(presencePath, "tsconfig.json"), tsconfig);
- await this.installPresenceDependencies(presences);
- actions.info(chalk.yellow(`Compiling ${presences}...`));
- const job = await new Promise<{
- err: Error | undefined;
- stats: webpack.Stats | undefined;
- }>(r => {
- return webpack.webpack(
- {
- ...webpackConfig,
- context: presencePath,
- entry: {
- presence: "./presence.ts",
- ...(existsSync(resolve(presencePath, "iframe.ts"))
- ? { iframe: "./iframe.ts" }
- : {}),
- },
- },
- (err, stats) => r({ err, stats })
- );
- });
- if (existsSync(resolve(presencePath, "tsconfig.json")))
- rmSync(resolve(presencePath, "tsconfig.json"));
- if (job.err) throw job.err;
- if (!job.stats?.compilation.errors.length) {
- if (!options.transpileOnly)
- actions.info(chalk.green(`Successfully compiled ${presences}`));
- else actions.info(chalk.green(`Successfully transpiled ${presences}`));
- }
- let errors = job.stats?.compilation.errors;
- errors = errors?.filter(e => e.name !== "ModuleBuildError");
- return errors || [];
- }
- async installPresenceDependencies(presence: string) {
- const folder = this.getPresenceFolder(presence);
- if (!existsSync(resolve(folder, "package.json"))) return;
- actions.info(chalk.blue(`Installing dependencies for ${basename(folder)}`));
- execSync("npm install --quiet --loglevel=error", {
- cwd: folder,
- });
- }
- }
- export interface Metadata {
- $schema: `https://schemas.premid.app/metadata/${number}.${number}`;
- author: Contributor;
- contributors?: Contributor[];
- service: string;
- altnames?: string[];
- description: { [lang: string]: string };
- url: `${string}.${string}` | `${string}.${string}`[];
- regExp?: string;
- version: `${number}.${number}.${number}`;
- apiVersion: number;
- logo: `https://i.imgur.com/${string}.${ImageTypes}`;
- thumbnail: `https://i.imgur.com/${string}.${ImageTypes}`;
- color: `#${string}`;
- tags: string | string[];
- category: string;
- iframe?: boolean;
- iFrameRegExp?: string;
- readLogs?: boolean;
- settings?:
- | Setting
- | MultiLanguageSetting
- | StringSetting
- | ValueSetting
- | ValuesSetting;
- }
- interface Contributor {
- name: string;
- id: `${bigint}`;
- }
- type ImageTypes = "png" | "jpeg" | "jpg" | "gif";
- interface BaseSetting {
- id: string;
- }
- interface MultiLanguageSetting extends BaseSetting {
- multiLanguage: true;
- }
- interface Setting extends BaseSetting {
- title: string;
- icon: string;
- if?: {
- [key: string]: Value;
- };
- }
- interface StringSetting extends Setting {
- value: string;
- placeholder: string;
- }
- interface ValueSetting extends Setting {
- value: boolean;
- }
- interface ValuesSetting extends Setting {
- value: number;
- values: Value[];
- }
- type Value = string | number | boolean;
|