UNPKG

hackmud-script-manager

Version:

Script manager for game hackmud, with minification, TypeScript support, and player script type definition generation.

221 lines (220 loc) 7.94 kB
import { AutoMap } from "@samual/lib/AutoMap" import { assert } from "@samual/lib/assert" import { countHackmudCharacters } from "@samual/lib/countHackmudCharacters" import { readDirectoryWithStats } from "@samual/lib/readDirectoryWithStats" import { writeFilePersistent } from "@samual/lib/writeFilePersistent" import { watch as watch$1 } from "chokidar" import { stat, readFile, writeFile } from "fs/promises" import { extname, basename, resolve } from "path" import { supportedExtensions } from "./constants.js" import { generateTypeDeclaration } from "./generateTypeDeclaration.js" import { processScript } from "./processScript/index.js" import "@babel/generator" import "@babel/parser" import "@babel/plugin-proposal-decorators" import "@babel/plugin-proposal-destructuring-private" import "@babel/plugin-proposal-explicit-resource-management" import "@babel/plugin-transform-class-properties" import "@babel/plugin-transform-class-static-block" import "@babel/plugin-transform-exponentiation-operator" import "@babel/plugin-transform-json-strings" import "@babel/plugin-transform-logical-assignment-operators" import "@babel/plugin-transform-nullish-coalescing-operator" import "@babel/plugin-transform-numeric-separator" import "@babel/plugin-transform-object-rest-spread" import "@babel/plugin-transform-optional-catch-binding" import "@babel/plugin-transform-optional-chaining" import "@babel/plugin-transform-private-property-in-object" import "@babel/plugin-transform-unicode-sets-regex" import "@babel/traverse" import "@babel/types" import "@rollup/plugin-alias" import "@rollup/plugin-babel" import "@rollup/plugin-commonjs" import "@rollup/plugin-json" import "@rollup/plugin-node-resolve" import "prettier" import "rollup" import "./processScript/minify.js" import "@samual/lib/spliceString" import "acorn" import "terser" import "./processScript/shared.js" import "./processScript/postprocess.js" import "./processScript/preprocess.js" import "import-meta-resolve" import "./processScript/transform.js" import "@samual/lib/clearObject" async function watch( sourceDirectory, hackmudDirectory, { scripts = ["*.*"], onPush, minify = !0, mangleNames = !1, typeDeclarationPath: typeDeclarationPath_, onReady, forceQuineCheats, rootFolderPath } = {} ) { if (!scripts.length) throw Error("scripts option was an empty array") if (!(await stat(sourceDirectory)).isDirectory()) throw Error("Target folder must be a folder") const scriptNamesToUsers = new AutoMap(_scriptName => new Set()), wildScriptUsers = new Set(), wildUserScripts = new Set() let pushEverything = !1 for (const fullScriptName of scripts) { const [user, scriptName] = fullScriptName.split(".") user && "*" != user ? scriptName && "*" != scriptName ? scriptNamesToUsers.get(scriptName).add(user) : wildScriptUsers.add(user) : scriptName && "*" != scriptName ? wildUserScripts.add(scriptName) : (pushEverything = !0) } const watcher = watch$1(".", { cwd: sourceDirectory, awaitWriteFinish: { stabilityThreshold: 100 }, ignored: (path, stats) => !!stats?.isFile() && !(path.endsWith(".js") || (path.endsWith(".ts") && !path.endsWith(".d.ts"))) }).on("change", async path => { if (path.endsWith(".d.ts")) return const extension = extname(path) if (!supportedExtensions.includes(extension)) return const scriptName = basename(path, extension) if (path == basename(path)) { if ( !( pushEverything || wildScriptUsers.size || wildUserScripts.has(scriptName) || scriptNamesToUsers.has(scriptName) ) ) return const scriptNamesToUsersToSkip = new AutoMap(_scriptName => []) await Promise.all( (await readDirectoryWithStats(sourceDirectory)).map(async ({ stats, name, path }) => { if (stats.isDirectory()) for (const child of await readDirectoryWithStats(path)) if (child.stats.isFile()) { const fileExtension = extname(child.name) supportedExtensions.includes(fileExtension) && scriptNamesToUsersToSkip.get(basename(child.name, fileExtension)).push(name) } }) ) const usersToPushToSet = new Set() if (pushEverything || wildUserScripts.has(scriptName)) { for (const { stats, name } of await readDirectoryWithStats(sourceDirectory)) stats.isDirectory() && usersToPushToSet.add(name) for (const { stats, name } of await readDirectoryWithStats(hackmudDirectory)) stats.isDirectory() ? usersToPushToSet.add(name) : stats.isFile() && name.endsWith(".key") && usersToPushToSet.add(name.slice(0, -4)) for (const users of scriptNamesToUsers.values()) for (const user of users) usersToPushToSet.add(user) } for (const user of wildScriptUsers) usersToPushToSet.add(user) for (const user of scriptNamesToUsers.get(scriptName)) usersToPushToSet.add(user) const usersToPushTo = [...usersToPushToSet].filter(user => !scriptNamesToUsersToSkip.has(user)) if (!usersToPushTo.length) { onPush?.({ path, users: [], characterCount: 0, error: Error("no users to push to"), warnings: [] }) return } const uniqueId = Math.floor(Math.random() * 2 ** 52) .toString(36) .padStart(11, "0"), filePath = resolve(sourceDirectory, path) let minifiedCode, warnings try { ;({ script: minifiedCode, warnings } = await processScript( await readFile(filePath, { encoding: "utf8" }), { minify, scriptUser: !0, scriptName, uniqueId, filePath, mangleNames, forceQuineCheats, rootFolderPath } )) } catch (error) { assert(error instanceof Error, "src/watch.ts:160:36") onPush?.({ path, users: [], characterCount: 0, error, warnings: [] }) return } await Promise.all( usersToPushTo.map(user => writeFilePersistent( resolve(hackmudDirectory, user, `scripts/${scriptName}.js`), minifiedCode .replace(RegExp(`\\$${uniqueId}\\$SCRIPT_USER\\$`, "g"), user) .replace(RegExp(`\\$${uniqueId}\\$FULL_SCRIPT_NAME\\$`, "g"), `${user}.${scriptName}`) ) ) ) onPush?.({ path, users: usersToPushTo, characterCount: countHackmudCharacters(minifiedCode), error: void 0, warnings }) return } const user = basename(resolve(path, "..")) if ( !( pushEverything || wildScriptUsers.size || wildUserScripts.has(scriptName) || scriptNamesToUsers.get(scriptName).has(user) ) ) return const sourceDirectoryResolved = resolve(sourceDirectory), filePath = resolve(sourceDirectoryResolved, path), sourceCode = await readFile(filePath, { encoding: "utf8" }) let script, warnings try { ;({ script, warnings } = await processScript(sourceCode, { minify, scriptUser: user, scriptName, filePath, mangleNames, forceQuineCheats, rootFolderPath })) } catch (error) { assert(error instanceof Error, "src/watch.ts:207:35") onPush?.({ path, users: [], characterCount: 0, error, warnings: [] }) return } await writeFilePersistent(resolve(hackmudDirectory, user, "scripts", scriptName + ".js"), script) onPush?.({ path, users: [user], characterCount: countHackmudCharacters(script), error: void 0, warnings }) }) onReady && watcher.on("ready", onReady) if (!typeDeclarationPath_) return let typeDeclarationPath = typeDeclarationPath_ const writeTypeDeclaration = async () => { const typeDeclaration = await generateTypeDeclaration(sourceDirectory, hackmudDirectory) try { await writeFile(typeDeclarationPath, typeDeclaration) } catch (error) { assert(error instanceof Error, "src/watch.ts:240:35") if ("EISDIR" != error.code) throw error typeDeclarationPath = resolve(typeDeclarationPath, "player.d.ts") await writeFile(typeDeclarationPath, typeDeclaration) } } await writeTypeDeclaration() watcher.on("add", writeTypeDeclaration) watcher.on("unlink", writeTypeDeclaration) } export { watch }