build system

This commit is contained in:
Asaki Yuki 2026-02-02 15:12:31 +07:00
parent 33a13cb1cc
commit 5730c94552
10 changed files with 32023 additions and 19272 deletions

5
config.d.ts vendored
View file

@ -3,7 +3,10 @@ import { Variable } from "./src/types/properties/value.ts"
export interface Config { export interface Config {
compiler?: { compiler?: {
enabled?: boolean enabled?: boolean
linked?: boolean autoImport?: boolean
importToPreview?: boolean
autoEnable?: boolean
gdkUserId?: string
} }
packinfo?: { packinfo?: {
name?: string name?: string

View file

@ -4,11 +4,14 @@
*/ */
export const config = { export const config = {
packinfo: { packinfo: {
name: "AsaJS", name: "AsaJS - Installer Test",
description: "Create your Minecraft JSON-UI resource packs using JavaScript.", description: "Create your Minecraft JSON-UI resource packs using JavaScript.",
version: [4, 0, 0],
}, },
compiler: { compiler: {
enabled: true, enabled: true,
linked: false, autoImport: true,
autoEnable: true,
importToPreview: false,
}, },
} }

View file

@ -8,15 +8,15 @@ function toCamelCase(str: string) {
const intelliSense: string[] = [ const intelliSense: string[] = [
'import { Type as T } from "../enums/Type.js"\n', 'import { Type as T } from "../enums/Type.js"\n',
"export type Namespace = keyof IntelliSense;", "export type Namespace = keyof VanillaUI;",
"export type Element<T extends Namespace> = Extract<keyof IntelliSense[T], string>", "export type Element<T extends Namespace> = Extract<keyof VanillaUI[T], string>",
"export type VanillaElementInfo<T extends Namespace, K extends Element<T>> = IntelliSense[T][K]", "export type VanillaElementInfo<T extends Namespace, K extends Element<T>> = VanillaUI[T][K]",
"// @ts-ignore", "// @ts-ignore",
'export type VanillaType<T extends Namespace, K extends Element<T>> = VanillaElementInfo<T, K>["type"]', 'export type VanillaType<T extends Namespace, K extends Element<T>> = VanillaElementInfo<T, K>["type"]',
"// @ts-ignore", "// @ts-ignore",
'export type VanillaElementChilds<T extends Namespace, K extends Element<T>> = VanillaElementInfo<T, K>["children"]', 'export type VanillaElementChilds<T extends Namespace, K extends Element<T>> = VanillaElementInfo<T, K>["children"]',
"\n", "\n",
"export type IntelliSense = {", "export type VanillaUI = {",
] ]
const intelliSenseTypeEachNamespace: string[] = [] const intelliSenseTypeEachNamespace: string[] = []

View file

@ -14,8 +14,8 @@ if (!fs.existsSync(".gitignore")) {
export const config: Config = require(path.resolve(process.cwd(), "asajs.config.cjs")).config export const config: Config = require(path.resolve(process.cwd(), "asajs.config.cjs")).config
export let isBuildMode = config.compiler?.enabled ?? false export let isBuildMode = config.compiler?.enabled ?? false
export let isLinkMode = config.compiler?.linked ?? false export let isLinkMode = config.compiler?.autoImport ?? false
export let unLinked = !(config.compiler?.linked ?? true) export let unLinked = !(config.compiler?.autoImport ?? true)
for (const arg of process.argv) { for (const arg of process.argv) {
if (arg === "--build") isBuildMode = true if (arg === "--build") isBuildMode = true

View file

@ -17,3 +17,24 @@ Map.prototype.toJSON = function () {
Array.prototype.lastItem = function () { Array.prototype.lastItem = function () {
return this[this.length - 1] return this[this.length - 1]
} }
const now = performance.now()
type LogType = "INFO"
function TypeHighlight(type: LogType) {
switch (type) {
case "INFO":
return `\x1b[32mINFO\x1b[0m`
default:
return type satisfies never
}
}
export function Log(type: LogType, message: string) {
console.log(
`\x1b[90m[${(performance.now() - now).toFixed(2)}ms]\x1b[0m`,
`[${TypeHighlight(type)}]`,
message,
"\x1b[0m",
)
}

View file

@ -1,11 +1,14 @@
import { config, isBuildMode, isLinkMode, unLinked } from "../Configuration.js" import { config, isBuildMode, isLinkMode, unLinked } from "../Configuration.js"
import { Memory } from "../Memory.js" import { Memory } from "../Memory.js"
import { createBuildFolder, linkToGame, unlink } from "./linker.js" import { createBuildFolder, gamePath, getBuildFolderName, linkToGame, unlink } from "./linker.js"
import { genManifest } from "./manifest.js" import { genManifest, version } from "./manifest.js"
import { UI } from "../../components/UI.js" import { UI } from "../../components/UI.js"
import { Type } from "../../types/enums/Type.js" import { Type } from "../../types/enums/Type.js"
import fs from "fs/promises" import fs from "fs/promises"
import { BuildCache } from "./buildcache.js" import { BuildCache } from "./buildcache.js"
import { disableRSP, enableRSP } from "./installer.js"
import { Log } from "../PreCompile.js"
import path from "path"
async function buildUI() { async function buildUI() {
const build = Memory.build() const build = Memory.build()
@ -23,30 +26,38 @@ async function buildUI() {
.stat(outFile.split(/\\|\//g).slice(0, -1).join("/")) .stat(outFile.split(/\\|\//g).slice(0, -1).join("/"))
.catch(async () => await fs.mkdir(outFile.split(/\\|\//g).slice(0, -1).join("/"), { recursive: true })) .catch(async () => await fs.mkdir(outFile.split(/\\|\//g).slice(0, -1).join("/"), { recursive: true }))
await fs.writeFile( await fs
outFile, .writeFile(
JSON.stringify( outFile,
Object.fromEntries( JSON.stringify(
Object.entries(value).map(([key, value]: [string, any]) => { Object.fromEntries(
const extend = (value as UI<Type>).extend Object.entries(value).map(([key, value]: [string, any]) => {
return [extend ? key + String(extend) : key, value] const extend = (value as UI<Type>).extend
}), return [extend ? key + String(extend) : key, value]
}),
),
), ),
), "utf-8",
"utf-8", )
) .then(() => Log("INFO", `${outFile} with ${Object.keys(value).length} elements created!`))
build.delete(file) build.delete(file)
return file return file
}), }),
) )
await Promise.all([ await Promise.all([
fs.writeFile("build/manifest.json", await genManifest(), "utf-8"), fs
fs.writeFile("build/.gitignore", [...out, "manifest.json"].join("\n"), "utf-8"), .writeFile("build/manifest.json", await genManifest(), "utf-8")
BuildCache.set("build-files", [...out, "manifest.json"]), .then(() => Log("INFO", "build/manifest.json created!")),
fs
.writeFile("build/.gitignore", [...out, "manifest.json"].join("\n"), "utf-8")
.then(() => Log("INFO", "build/.gitignore created!")),
BuildCache.set("build-files", [...out, "manifest.json"]).then(() => Log("INFO", "build-files set!")),
BuildCache.set("version", version).then(() => Log("INFO", "version set!")),
fs fs
.stat("build/pack_icon.png") .stat("build/pack_icon.png")
.catch(() => fs.copyFile("node_modules/asajs/resources/pack_icon.png", "build/pack_icon.png")), .catch(() => fs.copyFile("node_modules/asajs/resources/pack_icon.png", "build/pack_icon.png"))
.then(() => Log("INFO", "build/pack_icon.png copied!")),
]) ])
return out.length return out.length
@ -59,6 +70,20 @@ if (isBuildMode) {
await createBuildFolder() await createBuildFolder()
await buildUI() await buildUI()
if (isLinkMode) await linkToGame() if (isLinkMode) await linkToGame()
if (config.compiler?.autoEnable) await enableRSP()
else await disableRSP()
Log("INFO", "Build completed!")
console.log("========================= PACK INFO =========================")
console.log("Name:", `\x1b[32m${config.packinfo?.name || "MyPack"}\x1b[0m`)
console.log(
"Description:",
`\x1b[32m${config.packinfo?.description || "Create your Minecraft JSON-UI resource packs using JavaScript."}\x1b[0m`,
)
console.log("Version:", config.packinfo?.version || [4, 0, 0])
console.log("UUID:", await BuildCache.get<[string, string]>("uuid"))
if (gamePath)
console.log("Install Path:", `\x1b[32m"${path.join(gamePath, await getBuildFolderName())}"\x1b[0m`)
console.log("=============================================================")
} }
first = false first = false
}) })

View file

@ -1,17 +1,180 @@
import os from "os" import os from "os"
import path from "path" import path from "path"
import fs from "fs"
import fsp from "fs/promises"
import { config } from "../Configuration.js"
import { BuildCache } from "./buildcache.js"
import { version } from "./manifest.js"
import { Log } from "../PreCompile.js"
interface PackInfo {
pack_id: string
subpack?: string
version: [number, number, number]
}
export interface PathInfo<Platform = NodeJS.Platform> {
os: Platform
isGdk: Platform extends "win32" ? boolean : never
gamepath: string | null
}
export const pathinfo: PathInfo = {
os: os.platform(),
isGdk: false,
gamepath: null,
}
function getGlobalResourcePackFile(gamepath: string) {
return path.join(gamepath, "minecraftpe/global_resource_packs.json")
}
async function readGlobalRspFile(filepath: string) {
try {
if (await fsp.stat(filepath)) {
return JSON.parse(await fsp.readFile(filepath, "utf-8")) as PackInfo[]
}
} catch (error) {
return null
}
}
async function writeGlobalRspFile(filepath: string, data: PackInfo[]) {
try {
await fsp.writeFile(filepath, JSON.stringify(data), "utf-8")
} catch (error) {
return null
}
}
export async function enable(uuid: string, version: [number, number, number], filepath: string) {
try {
const globalRsp = await readGlobalRspFile(filepath)
if (!globalRsp) return null
const index = globalRsp.findIndex(data => data.pack_id === uuid)
if (index === -1) {
globalRsp.push({ pack_id: uuid, version })
await writeGlobalRspFile(filepath, globalRsp)
return true
} else if (globalRsp[index].version.join(".") !== version.join(".")) {
globalRsp[index].version = version
await writeGlobalRspFile(filepath, globalRsp)
return true
}
return false
} catch (error) {
return null
}
}
export async function disable(uuid: string, filepath: string) {
try {
let globalRsp = await readGlobalRspFile(filepath)
if (!globalRsp) return null
let isWrite = false
globalRsp = globalRsp.filter(data => {
if (data.pack_id === uuid) isWrite = true
return data.pack_id !== uuid
})
if (isWrite) {
await writeGlobalRspFile(filepath, globalRsp)
return true
} else return false
} catch (error) {
return null
}
}
export async function enableRSP() {
if (pathinfo.isGdk && pathinfo.gamepath) {
const ids: string[] = [],
gamepath = path.join(pathinfo.gamepath, "../../..")
if (config.compiler?.gdkUserId && /^\d+$/.test(config.compiler.gdkUserId)) ids.push(config.compiler.gdkUserId)
else
ids.push(...(await fsp.readdir(gamepath, { withFileTypes: false })).filter((id: string) => id !== "Shared"))
const [uuid] = await Promise.all([BuildCache.get<[string, string]>("uuid")])
if (!uuid) return
await Promise.all(
ids.map(async (id: string) =>
enable(uuid[0]!, version, getGlobalResourcePackFile(path.join(gamepath, id, "games/com.mojang"))).then(
v => {
if (v) {
Log("INFO", "Resource Pack enabled automaticly for " + id)
}
},
),
),
)
} else if (pathinfo.gamepath) {
const [uuid] = await Promise.all([BuildCache.get<[string, string]>("uuid")])
if (!uuid) return
await enable(uuid[0]!, version, getGlobalResourcePackFile(pathinfo.gamepath)).then(v => {
if (v) {
Log("INFO", "Resource Pack enabled automaticly")
}
})
}
}
export async function disableRSP() {
if (pathinfo.isGdk && pathinfo.gamepath) {
const gamepath = path.join(pathinfo.gamepath, "../../..")
const [uuid] = await Promise.all([BuildCache.get<[string, string]>("uuid")])
if (!uuid) return
await Promise.all(
(await fsp.readdir(gamepath, { withFileTypes: false })).map(async (id: string) =>
disable(uuid[0]!, getGlobalResourcePackFile(path.join(gamepath, id, "games/com.mojang"))).then(v => {
if (v) {
Log("INFO", "Resource Pack disabled automaticly for " + id)
}
}),
),
)
} else if (pathinfo.gamepath) {
const [uuid] = await Promise.all([BuildCache.get<[string, string]>("uuid")])
if (!uuid) return
await disable(uuid[0]!, getGlobalResourcePackFile(pathinfo.gamepath)).then(v => {
if (v) {
Log("INFO", "Resource Pack disabled automaticly")
}
})
}
}
export function getGamedataPath() { export function getGamedataPath() {
switch (os.platform()) { switch (pathinfo.os) {
case "win32": { case "win32": {
if (/Windows (10|11)/.test(os.version())) { if (/Windows (10|11)/.test(os.version())) {
return path.join(process.env.APPDATA!, "Minecraft Bedrock\\Users\\Shared\\games\\com.mojang") let gamedata = path.join(
process.env.APPDATA!,
config.compiler?.importToPreview ? "Minecraft Bedrock Preview" : "Minecraft Bedrock",
"\\Users\\Shared\\games\\com.mojang",
)
if (fs.existsSync(gamedata)) {
pathinfo.isGdk = true
return (pathinfo.gamepath = gamedata)
}
gamedata = path.join(
process.env.LOCALAPPDATA!,
"Packages",
config.compiler?.importToPreview
? "Microsoft.MinecraftWindowsBeta_8wekyb3d8bbwe"
: "Microsoft.MinecraftUWP_8wekyb3d8bbwe",
"LocalState\\games\\com.mojang",
)
if (fs.existsSync(gamedata)) {
return (pathinfo.gamepath = gamedata)
}
} }
} }
default: { default: {
console.error(`Your platform is not supported the install feature yet! \nYour OS version: ${os.version()}`) console.warn(`Your platform is not supported the install feature yet! \nYour OS version: ${os.version()}`)
process.exit(1)
} }
} }
} }

View file

@ -2,7 +2,8 @@ import fs from "fs/promises"
import { BuildCache } from "./buildcache.js" import { BuildCache } from "./buildcache.js"
import { RandomString } from "../../components/Utils.js" import { RandomString } from "../../components/Utils.js"
import path from "path" import path from "path"
import { getGamedataPath } from "./installer.js" import { getGamedataPath, PathInfo, pathinfo } from "./installer.js"
import { Log } from "../PreCompile.js"
const HEX: string[] = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0")) const HEX: string[] = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"))
@ -25,13 +26,17 @@ function genUUID(): string {
export async function clearBuild() { export async function clearBuild() {
const files: string[] = (await BuildCache.get("build-files")) || [] const files: string[] = (await BuildCache.get("build-files")) || []
await Promise.all(files.map(file => fs.rm(`build/${file}`).catch(() => null))) await Promise.all(files.map(file => fs.rm(`build/${file}`).catch(() => null))).then(() => {
if (files.length) {
Log("INFO", `Build folder cleared (${files.length} files removed)!`)
}
})
} }
export async function createBuildFolder() { export async function createBuildFolder() {
return fs return fs
.stat("build") .stat("build")
.catch(() => fs.mkdir("build")) .catch(() => fs.mkdir("build").then(() => Log("INFO", "Build folder created!")))
.then(() => clearBuild()) .then(() => clearBuild())
} }
@ -39,18 +44,28 @@ export async function getBuildFolderName() {
return await BuildCache.getWithSetDefault("build-key", () => RandomString(16)) return await BuildCache.getWithSetDefault("build-key", () => RandomString(16))
} }
export let gamePath: string | null = null
export async function linkToGame() { export async function linkToGame() {
const sourcePath = path.resolve("build") const gamedataPath = getGamedataPath()
const targetPath = path.resolve(getGamedataPath(), "development_resource_packs", await getBuildFolderName()) await BuildCache.set("last-gamedata-path", pathinfo)
await fs.stat(targetPath).catch(async () => { if (gamedataPath) {
await fs.symlink(sourcePath, targetPath, "junction") const sourcePath = path.resolve("build")
}) const targetPath = path.resolve(gamedataPath, "development_resource_packs", await getBuildFolderName())
await fs.stat(targetPath).catch(async () => {
await fs
.symlink(sourcePath, targetPath, "junction")
.then(() => Log("INFO", "Linked build folder to gamedata!"))
})
gamePath = gamedataPath
} else console.warn("No gamedata path found, cannot link!")
} }
export async function unlink() { export async function unlink() {
const targetPath = path.resolve(getGamedataPath(), "development_resource_packs", await getBuildFolderName()) const gamedataPath = await BuildCache.get<PathInfo>("last-gamedata-path")
if (!gamedataPath?.gamepath) return
const targetPath = path.resolve(gamedataPath.gamepath, "development_resource_packs", await getBuildFolderName())
try { try {
await fs.unlink(targetPath) await fs.unlink(targetPath).then(() => Log("INFO", "Unlinked build folder from gamedata!"))
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} }

View file

@ -1,6 +1,8 @@
import { config } from "../Configuration.js" import { config } from "../Configuration.js"
import { getUUID } from "./linker.js" import { getUUID } from "./linker.js"
export const version = config.packinfo?.version || [4, 0, 0]
export async function genManifest() { export async function genManifest() {
const [uuid1, uuid2] = await getUUID() const [uuid1, uuid2] = await getUUID()
return JSON.stringify({ return JSON.stringify({
@ -10,7 +12,7 @@ export async function genManifest() {
description: description:
config.packinfo?.description || "Create your Minecraft JSON-UI resource packs using JavaScript.", config.packinfo?.description || "Create your Minecraft JSON-UI resource packs using JavaScript.",
uuid: uuid1, uuid: uuid1,
version: config.packinfo?.version || [4, 0, 0], version,
min_engine_version: [1, 21, 80], min_engine_version: [1, 21, 80],
}, },
modules: [ modules: [
@ -18,7 +20,7 @@ export async function genManifest() {
description: "This resource pack generate by AsaJS.", description: "This resource pack generate by AsaJS.",
type: "resources", type: "resources",
uuid: uuid2, uuid: uuid2,
version: [4, 0, 0], version: version,
}, },
], ],
subpacks: config.packinfo?.subpacks, subpacks: config.packinfo?.subpacks,

File diff suppressed because it is too large Load diff