// @ts-check import { rollup } from 'rollup'; import { readFileSync, writeFileSync, mkdirSync, unlinkSync, cpSync, createWriteStream } from 'fs'; import archiver from 'archiver'; // Path constants const BUILD_CACHE_PATH = "./build-cache.json"; const SRC_DIR = "./src"; const SPRITES_DIR = "./sprites"; const IMAGES_DIR = "./images"; const FONTS_DIR = "./fonts"; const DIST_DIR = "./dist"; const BROWSER_MANIFEST = "./platform-specific/extension/manifest.json"; const OBSIDIAN_MANIFEST = "./platform-specific/obsidian/manifest.json"; const USERSCRIPT_HEADER = "./platform-specific/userscript/header.txt"; const OBSIDIAN_WRAPPER = "./platform-specific/obsidian/wrapper.js"; const VENCORD_WRAPPER = "./platform-specific/vencord/wrapper.js"; const USERSCRIPT_DIR = DIST_DIR + "/userscript"; const EXTENSION_DIR = DIST_DIR + "/extension"; const OBSIDIAN_DIR = DIST_DIR + "/obsidian"; const VENCORD_DIR = DIST_DIR + "/vencord"; const STYLESHEET_PATH = SRC_DIR + "/stylesheet.css"; const WEB_ENTRY = SRC_DIR + "/platforms/web.js"; const USERSCRIPT_ENTRY = SRC_DIR + "/platforms/userscript.js"; const BROWSER_EXTENSION_ENTRY = SRC_DIR + "/platforms/extension.js"; const OBSIDIAN_ENTRY = SRC_DIR + "/platforms/obsidian.js"; const VENCORD_ENTRY = SRC_DIR + "/platforms/vencord.js"; const BUNDLED_OUTPUT = DIST_DIR + "/birb.bundled.js"; const BIRB_OUTPUT = DIST_DIR + "/birb.js"; const MONOCRAFT_URL = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf"; const VERSION_KEY = "__VERSION__"; const STYLESHEET_KEY = "___STYLESHEET___"; const MONOCRAFT_SRC_KEY = "__MONOCRAFT_SRC__"; const CODE_KEY = "__CODE__"; const CONTEXT_KEY = "__CONTEXT__"; const spriteSheets = [ { key: "__SPRITE_SHEET__", path: SPRITES_DIR + "/birb.png" }, { key: "__FEATHER_SPRITE_SHEET__", path: SPRITES_DIR + "/feather.png" } ]; /** @type {Record} */ let buildCache = {}; try { const cacheContent = readFileSync(BUILD_CACHE_PATH, 'utf8'); buildCache = JSON.parse(cacheContent); } catch (e) { console.warn("No build cache found, starting fresh"); } const now = new Date(); const versionDate = `${now.getFullYear()}.${now.getMonth() + 1}.${now.getDate()}`; // Get current build number from the build cache let buildNumber = 0; if (buildCache.version && buildCache.version.startsWith(versionDate)) { // Same day, increment build number const parts = buildCache.version.split('.'); if (parts.length === 4) { buildNumber = parseInt(parts[3], 10) + 1; } } // const version = `${versionDate}.${buildNumber}`; const version = `${versionDate}`; // Disable build number for now // Update build cache buildCache.version = version; writeFileSync(BUILD_CACHE_PATH, JSON.stringify(buildCache), 'utf8'); /** * @param {string} entryPoint * @param {boolean} [embedFont] * @returns {Promise} */ async function generateCode(entryPoint, embedFont = false) { // Bundle with rollup const bundle = await rollup({ input: entryPoint, }); await bundle.write({ file: BUNDLED_OUTPUT, format: 'iife', }); await bundle.close(); let birbJs = readFileSync(BUNDLED_OUTPUT, 'utf8'); // Delete bundled file unlinkSync(BUNDLED_OUTPUT); // Replace version placeholder birbJs = birbJs.replaceAll(VERSION_KEY, version); // Compile and insert sprite sheets for (const spriteSheet of spriteSheets) { const dataUri = readFileSync(spriteSheet.path, 'base64'); birbJs = birbJs.replaceAll(spriteSheet.key, `data:image/png;base64,${dataUri}`); } // Insert stylesheet const stylesheetContent = readFileSync(STYLESHEET_PATH, 'utf8'); birbJs = birbJs.replace(STYLESHEET_KEY, stylesheetContent); if (embedFont) { // Encode font to data URI const monocraftFontData = readFileSync(FONTS_DIR + '/Monocraft.otf', 'base64'); const monocraftDataUri = `data:font/otf;base64,${monocraftFontData}`; birbJs = birbJs.replaceAll(MONOCRAFT_SRC_KEY, monocraftDataUri); } else { birbJs = birbJs.replaceAll(MONOCRAFT_SRC_KEY, MONOCRAFT_URL); } return birbJs; } // ============================================= // Build JavaScript function // ============================================= async function buildWeb() { const birbJs = await generateCode(WEB_ENTRY); writeFileSync(BIRB_OUTPUT, birbJs); } // Bundle with rollup // const bundle = await rollup({ // input: APPLICATION_ENTRY, // }); // await bundle.write({ // file: BUNDLED_OUTPUT, // format: 'iife', // }); // await bundle.close(); // let birbJs = readFileSync(BUNDLED_OUTPUT, 'utf8'); // // Delete bundled file // unlinkSync(BUNDLED_OUTPUT); // // Replace version placeholder // birbJs = birbJs.replaceAll(VERSION_KEY, version); // // Compile and insert sprite sheets // for (const spriteSheet of spriteSheets) { // const dataUri = readFileSync(spriteSheet.path, 'base64'); // birbJs = birbJs.replaceAll(spriteSheet.key, `data:image/png;base64,${dataUri}`); // } // // Insert stylesheet // const stylesheetContent = readFileSync(STYLESHEET_PATH, 'utf8'); // birbJs = birbJs.replace(STYLESHEET_KEY, stylesheetContent).replace(MONOCRAFT_SRC_KEY, MONOCRAFT_URL); // // Write bundled JavaScript function // writeFileSync(BIRB_OUTPUT, birbJs); // ============================================= // Build userscript // ============================================= // Get userscript header // const userScriptHeader = readFileSync(USERSCRIPT_HEADER, 'utf8').replaceAll(VERSION_KEY, version); // mkdirSync(USERSCRIPT_DIR, { recursive: true }); // const userScript = userScriptHeader + "\n" + birbJs; // writeFileSync(USERSCRIPT_DIR + '/birb.user.js', userScript); async function buildUserscript() { const birbJs = await generateCode(USERSCRIPT_ENTRY); // Get userscript header const userScriptHeader = readFileSync(USERSCRIPT_HEADER, 'utf8').replaceAll(VERSION_KEY, version); mkdirSync(USERSCRIPT_DIR, { recursive: true }); const userScript = userScriptHeader + "\n" + birbJs; writeFileSync(USERSCRIPT_DIR + '/birb.user.js', userScript); } // ============================================= // Build browser extension // ============================================= // mkdirSync(EXTENSION_DIR, { recursive: true }); // // Copy birb.js // writeFileSync(EXTENSION_DIR + '/birb.js', birbJs); // // Copy manifest.json // let browserManifest = readFileSync(BROWSER_MANIFEST, 'utf8'); // browserManifest = browserManifest.replace(VERSION_KEY, version); // writeFileSync(EXTENSION_DIR + '/manifest.json', browserManifest); // // Copy icons folder // mkdirSync(EXTENSION_DIR + '/images/icons', { recursive: true }); // cpSync(IMAGES_DIR + '/icons/transparent', EXTENSION_DIR + '/images/icons/transparent', { recursive: true }); // // Copy fonts folder // mkdirSync(EXTENSION_DIR + '/fonts', { recursive: true }); // cpSync(FONTS_DIR, EXTENSION_DIR + '/fonts', { recursive: true }); // // Compress extension folder into zip // const output = createWriteStream(DIST_DIR + "/extension.zip"); // const archive = archiver('zip'); // output.on('close', () => { // console.log(`Created zip file: ${archive.pointer()} total bytes`); // }); // archive.on('error', (err) => { // throw err; // }); // archive.pipe(output); // archive.directory(EXTENSION_DIR + '/', false); // archive.finalize(); async function buildExtension() { const birbJs = await generateCode(BROWSER_EXTENSION_ENTRY); mkdirSync(EXTENSION_DIR, { recursive: true }); // Copy birb.js writeFileSync(EXTENSION_DIR + '/birb.js', birbJs); // Copy manifest.json let browserManifest = readFileSync(BROWSER_MANIFEST, 'utf8'); browserManifest = browserManifest.replace(VERSION_KEY, version); writeFileSync(EXTENSION_DIR + '/manifest.json', browserManifest); // Copy icons folder mkdirSync(EXTENSION_DIR + '/images/icons', { recursive: true }); cpSync(IMAGES_DIR + '/icons/transparent', EXTENSION_DIR + '/images/icons/transparent', { recursive: true }); // Copy fonts folder mkdirSync(EXTENSION_DIR + '/fonts', { recursive: true }); cpSync(FONTS_DIR, EXTENSION_DIR + '/fonts', { recursive: true }); // Compress extension folder into zip const output = createWriteStream(DIST_DIR + "/extension.zip"); const archive = archiver('zip'); output.on('close', () => { console.log(`Created zip file: ${archive.pointer()} total bytes`); }); archive.on('error', (err) => { throw err; }); archive.pipe(output); archive.directory(EXTENSION_DIR + '/', false); archive.finalize(); } // ============================================= // Build Obsidian plugin // ============================================= // mkdirSync(OBSIDIAN_DIR, { recursive: true }); // // Wrap birb.js with plugin boilerplate // let obsidianPlugin = readFileSync(OBSIDIAN_WRAPPER, 'utf8').replace(VERSION_KEY, version).replace(CODE_KEY, birbJs); // // Encode font to data URI since Obsidian plugins can't have external font files // const monocraftFontData = readFileSync(FONTS_DIR + '/Monocraft.otf', 'base64'); // const monocraftDataUri = `data:font/otf;base64,${monocraftFontData}`; // obsidianPlugin = obsidianPlugin.replace(MONOCRAFT_URL, monocraftDataUri); // // Create main.js with plugin code // writeFileSync(OBSIDIAN_DIR + '/main.js', obsidianPlugin); // // Copy manifest.json // let obsidianManifest = readFileSync(OBSIDIAN_MANIFEST, 'utf8'); // obsidianManifest = obsidianManifest.replace(/"version":\s*".*"/, `"version": "${version}"`); // writeFileSync(OBSIDIAN_DIR + '/manifest.json', obsidianManifest); async function buildObsidian() { const birbJs = await generateCode(OBSIDIAN_ENTRY, true); mkdirSync(OBSIDIAN_DIR, { recursive: true }); // Wrap birb.js with plugin boilerplate let obsidianPlugin = readFileSync(OBSIDIAN_WRAPPER, 'utf8').replace(VERSION_KEY, version).replace(CODE_KEY, birbJs); // Create main.js with plugin code writeFileSync(OBSIDIAN_DIR + '/main.js', obsidianPlugin); // Copy manifest.json let obsidianManifest = readFileSync(OBSIDIAN_MANIFEST, 'utf8'); obsidianManifest = obsidianManifest.replace(/"version":\s*".*"/, `"version": "${version}"`); writeFileSync(OBSIDIAN_DIR + '/manifest.json', obsidianManifest); } // ============================================= // Build Vencord plugin // ============================================= // mkdirSync(VENCORD_DIR, { recursive: true }); // // Wrap birb.js with plugin boilerplate // let vencordPlugin = readFileSync(VENCORD_WRAPPER, 'utf8').replace(CODE_KEY, birbJs); // // Set context to "local" // vencordPlugin = vencordPlugin.replace(CONTEXT_KEY, "local"); // // Create exported birb function // writeFileSync(VENCORD_DIR + '/birb.export.js', vencordPlugin); // console.log(`Build complete: ${version}`); async function buildVencord() { const birbJs = await generateCode(VENCORD_ENTRY); mkdirSync(VENCORD_DIR, { recursive: true }); // Wrap birb.js with plugin boilerplate let vencordPlugin = readFileSync(VENCORD_WRAPPER, 'utf8').replace(CODE_KEY, birbJs); // Set context to "local" vencordPlugin = vencordPlugin.replace(CONTEXT_KEY, "local"); // Create exported birb function writeFileSync(VENCORD_DIR + '/birb.export.js', vencordPlugin); } console.log("Starting build..."); await buildWeb(); await buildUserscript(); await buildExtension(); await buildObsidian(); await buildVencord();