hackmud-script-manager
Version:
Script manager for game hackmud, with minification, TypeScript support, and player script type definition generation.
165 lines (164 loc) • 6.88 kB
JavaScript
import { AutoMap } from "@samual/lib/AutoMap"
import { ensure, assert } from "@samual/lib/assert"
import { countHackmudCharacters } from "@samual/lib/countHackmudCharacters"
import { readDirectoryWithStats } from "@samual/lib/readDirectoryWithStats"
import { writeFilePersistent } from "@samual/lib/writeFilePersistent"
import { readFile } from "fs/promises"
import { basename, resolve } from "path"
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 "./constants.js"
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"
class MissingSourceFolderError extends Error {}
Object.defineProperty(MissingSourceFolderError.prototype, "name", { value: "MissingSourceFolderError" })
class MissingHackmudFolderError extends Error {}
Object.defineProperty(MissingHackmudFolderError.prototype, "name", { value: "MissingHackmudFolderError" })
class NoUsersError extends Error {}
Object.defineProperty(NoUsersError.prototype, "name", { value: "NoUsersError" })
class NoScriptsError extends Error {}
Object.defineProperty(NoScriptsError.prototype, "name", { value: "NoScriptsError" })
async function push(
sourcePath,
hackmudPath,
{ scripts = ["*.*"], onPush = () => {}, minify = !0, mangleNames = !1, forceQuineCheats, rootFolderPath } = {}
) {
const [sourceFolder, hackmudFolder] = await Promise.all([
readDirectoryWithStats(sourcePath).catch(error => {
if (error && "ENOENT" == error.code)
return new MissingSourceFolderError("There is no folder at " + sourcePath)
throw error
}),
readDirectoryWithStats(hackmudPath).catch(error => {
if (error && "ENOENT" == error.code)
return new MissingHackmudFolderError("There is no folder at " + hackmudPath)
throw error
})
])
if (sourceFolder instanceof Error) return sourceFolder
if (hackmudFolder instanceof Error) return hackmudFolder
const sourceFolderFolders = sourceFolder.filter(
({ name, stats }) => stats.isDirectory() && /^[a-z_][a-z\d_]{0,24}$/.test(name)
),
allUsers = new Set([
...scripts
.map(scriptName => ensure(scriptName.split(".")[0], "src/push.ts:85:65"))
.filter(name => "*" != name),
...sourceFolderFolders.map(({ name }) => name),
...hackmudFolder.filter(({ stats }) => stats.isDirectory()).map(({ name }) => name),
...hackmudFolder
.filter(({ stats, name }) => stats.isFile() && name.endsWith(".key"))
.map(({ name }) => name.slice(0, -4))
])
if (!allUsers.size)
return new NoUsersError(
"Could not find any users. Either provide the names of your users or log into a user in hackmud"
)
const usersToScriptsToPush = new AutoMap(_user => new Map()),
scriptNamesToUsers = new AutoMap(_scriptName => new Set())
for (const script of scripts) {
const [user, scriptName] = script.split(".")
assert(user, "src/push.ts:108:16")
assert(scriptName, "src/push.ts:109:22")
"*" == user ? scriptNamesToUsers.set(scriptName, allUsers) : scriptNamesToUsers.get(scriptName).add(user)
}
const sourceFolderFiles = sourceFolder.filter(({ stats }) => stats.isFile()),
wildScriptUsers_ = scriptNamesToUsers.get("*")
scriptNamesToUsers.delete("*")
for (const { name, path } of [
...sourceFolderFiles.filter(({ name }) => name.endsWith(".js")),
...sourceFolderFiles.filter(({ name }) => name.endsWith(".ts"))
]) {
const scriptName = name.slice(0, -3)
for (const user of [...wildScriptUsers_, ...scriptNamesToUsers.get(scriptName)])
usersToScriptsToPush.get(user).set(scriptName, path)
}
await Promise.all(
sourceFolderFolders.map(async ({ name: user, path }) => {
const files = (await readDirectoryWithStats(path)).filter(({ stats }) => stats.isFile()),
scriptFiles = [
...files.filter(({ name }) => name.endsWith(".js")),
...files.filter(({ name }) => name.endsWith(".ts"))
]
for (const { name, path } of scriptFiles) {
const scriptName = name.slice(0, -3)
;[...wildScriptUsers_, ...scriptNamesToUsers.get(scriptName)].includes(user) &&
usersToScriptsToPush.get(user).set(scriptName, path)
}
})
)
for (const [scriptName, users] of scriptNamesToUsers)
for (const user of users)
if (!usersToScriptsToPush.get(user).has(scriptName))
return new NoScriptsError(`Could not find script ${user}.${scriptName} to push`)
const pathsToUsers = new AutoMap(_path => new Set())
for (const [user, scriptsToPush] of usersToScriptsToPush)
for (const path of scriptsToPush.values()) pathsToUsers.get(path).add(user)
const allInfo = []
await Promise.all(
[...pathsToUsers].map(async ([path, [...users]]) => {
const scriptName = basename(path.slice(0, -3)),
uniqueId = Math.floor(Math.random() * 2 ** 52)
.toString(36)
.padStart(11, "0"),
{ script: minifiedCode, warnings } = await processScript(await readFile(path, { encoding: "utf8" }), {
minify,
scriptUser: !0,
scriptName,
uniqueId,
filePath: path,
mangleNames,
forceQuineCheats,
rootFolderPath
}),
info = { path, users, characterCount: countHackmudCharacters(minifiedCode), error: void 0, warnings }
await Promise.all(
users.map(user =>
writeFilePersistent(
resolve(hackmudPath, user, `scripts/${scriptName}.js`),
minifiedCode
.replace(RegExp(`\\$${uniqueId}\\$SCRIPT_USER\\$`, "g"), user)
.replace(RegExp(`\\$${uniqueId}\\$FULL_SCRIPT_NAME\\$`, "g"), `${user}.${scriptName}`)
)
)
)
allInfo.push(info)
onPush(info)
})
)
return allInfo
}
export { MissingHackmudFolderError, MissingSourceFolderError, NoScriptsError, NoUsersError, push }