UNPKG

bedrock-development

Version:

APIs for creating and editing files related to Minecraft Bedrock development.

242 lines (214 loc) 8.72 kB
import * as fs from 'fs'; import * as path from 'path'; import { globSync } from 'glob'; import { chalk } from './utils.js'; import { exec } from "child_process"; import { fileURLToPath } from 'url'; import * as archiver from 'archiver'; /** * @remarks File Representation, contains the file path, file contents, and how to handle existing files. */ export type File = {filePath: string, fileContents: string, handleExisting? : 'overwrite' | 'overwrite_silent'}; /** * @remarks A global class for getting and setting workspace paths. */ export class Directories { private static behavior_path = '**/behavior_packs/*bp/'; private static resource_path = '**/resource_packs/*rp/'; private static addon_path = ''; private static source_path = path.join(path.resolve(path.dirname(fileURLToPath(import.meta.url))), 'src'); private static package_path = path.join(path.resolve(path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url))))), 'package.json'); public static get PACKAGE_PATH() : string { return this.package_path; } /** * @remarks The path to the module's /src */ public static get SOURCE_PATH() : string { return this.source_path + '/'; } /** * @remarks The path to the vanilla behavior pack samples packaged with the module. */ public static get VANILLA_BEHAVIOR_PATH() : string { return Directories.SOURCE_PATH + 'vanilla/behavior_pack/'; } /** * @remarks The path to the vanilla resource pack samples packaged with the module. */ public static get VANILLA_RESOURCE_PATH() : string { return Directories.SOURCE_PATH + 'vanilla/resource_pack/'; } /** * @remarks The behavior pack in the workspace. */ public static get BEHAVIOR_PATH() : string { try { return globSync(this.behavior_path)[0].replace(/\/|\\+/g, '/') + '/'; } catch (error) { throw chalk.red("Cannot Find Behavior Path"); } } /** * @remarks The resource pack in the workspace. */ public static get RESOURCE_PATH() : string { try { return globSync(this.resource_path)[0].replace(/\/|\\+/g, '/') + '/'; } catch (error) { throw chalk.red("Cannot Find Resource Path"); } } /** * @remarks The addon subpath <team>/<project> or an empty string if unspecified. */ public static get ADDON_PATH() : string { return Directories.addon_path; } public static set BEHAVIOR_PATH(v: string) { if (fs.existsSync(v)) { this.behavior_path = v; } else { console.warn(`${chalk.yellow(`Created directory ${v} and assigned to behavior path`)}`); fs.mkdirSync(v, {recursive: true}); this.behavior_path = v; } } public static set RESOURCE_PATH(v: string) { if (fs.existsSync(v)) { this.resource_path = v; } else { console.warn(`${chalk.yellow(`Created directory ${v} and assigned to resource path`)}`); fs.mkdirSync(v, {recursive: true}); this.resource_path = v; } } public static set ADDON_PATH(v : string) { Directories.addon_path = v + "/"; } } /** * @remarks Gets files matching a glob pattern. * @param globPattern The glob pattern to use to find files. * @returns An array of {@link File}s matching the pattern. * @example * ```typescript * let files = getFiles(Directories.RESOURCE_PATH + 'texts/*.lang'); * ``` */ export function getFiles(globPattern: string): File[] { globPattern = globPattern.replace(/\/|\\+/g, '/'); return globSync(globPattern).filter(file => fs.lstatSync(file).isFile()).map(file => { return {filePath: file, fileContents: String(fs.readFileSync(file))} }); } /** * @remarks Writes an array of files. * @param files The array of {@link File}s to write. * @example * ```typescript * let files = getFiles(Directories.RESOURCE_PATH + 'texts/*.lang'); * files.forEach(file => file.fileContents = ':)'); * setFiles(files); * ``` */ export function setFiles(files: File[]) { files.forEach(file => { if (!fs.existsSync(path.dirname(file.filePath))) { fs.mkdirSync(path.dirname(file.filePath), {recursive: true}); console.log(`${chalk.green(`Creating directory at ${path.dirname(file.filePath)}`)}`); } if (fs.existsSync(file.filePath)) { switch (file.handleExisting) { case 'overwrite': console.log(`${chalk.green(`Overwriting file at ${file.filePath}`)}`); fs.writeFileSync(file.filePath, file.fileContents); return; case 'overwrite_silent': fs.writeFileSync(file.filePath, file.fileContents); return; default: console.warn(`${chalk.yellow(`Won't overwrite file at ${file.filePath}`)}`); return; } } console.log(`${chalk.green(`Writing file at ${file.filePath}`)}`); fs.writeFileSync(file.filePath, file.fileContents); }); } /** * @remarks Copies a source file from this module to a destination. * @param sourceFile The filepath to a source file within this module's src. * @param targetPath The filepath to the destination the file should be copied to. * @param handleExisting How to handle existing files. Undefined will not overwrite, 'overwite' replaces the file with this object, * 'overwrite_silent' does the same with no terminal log. */ export function copySourceFile(sourceFile: string, targetPath: string, handleExisting? : 'overwrite' | 'overwrite_silent') { if (!fs.existsSync(path.dirname(targetPath))) { fs.mkdirSync(path.dirname(targetPath), {recursive: true}); console.log(`${chalk.green(`Creating directory at ${path.dirname(targetPath)}`)}`); } if (fs.existsSync(targetPath)) { switch (handleExisting) { case 'overwrite': console.log(`${chalk.green(`Overwriting file at ${targetPath}`)}`); fs.copyFileSync(path.join(Directories.SOURCE_PATH, sourceFile), targetPath); return; case 'overwrite_silent': fs.copyFileSync(path.join(Directories.SOURCE_PATH, sourceFile), targetPath); return; default: console.warn(`${chalk.yellow(`Won't overwrite file at ${targetPath}`)}`); return; } } console.log(`${chalk.green(`Writing file at ${targetPath}`)}`); fs.copyFileSync(path.join(Directories.SOURCE_PATH, sourceFile), targetPath); } /** * @remarks Copies a source directory from this module to a destination. * @param src The path to a source directory within this module's src. * @param dest The filepath to the destination where the directory should be copied to. */ export function copySourceDirectory(src: string, dest: string) { fs.mkdirSync(dest, { recursive: true }) let entries = fs.readdirSync(src, { withFileTypes: true }); for (let entry of entries) { let srcPath = path.join(src, entry.name); let destPath = path.join(dest, entry.name); entry.isDirectory() ? copySourceDirectory(srcPath, destPath) : fs.copyFileSync(srcPath, destPath); } } /** * @remarks Archives a directory, compressing to a .zip or .mcworld for example. * @param dir The directory to archive. * @param zipPath The path the directory should be archived to. * @param callback A callback to run when the directory finishes archiving. */ export function archiveDirectory(dir: string, zipPath: string, callback: Function) { let output = fs.createWriteStream(zipPath); let archive = archiver.default('zip', { zlib: { level: 9 } }); output.on('close', async () => { await callback(); fs.rmSync(dir, {recursive: true, force: true}); }); archive.pipe(output); archive.directory(dir, ''); archive.finalize(); } /** * @remarks Creates a temporary file, opening it in Notepad. The contents of the file will be returned when Notepad is closed. * @returns A promise to a string. */ export function getStringFromTemporaryFile(): Promise<string> { const filename = 'temp-' + Date.now() + '.txt'; fs.writeFileSync(filename, ''); return new Promise<string>(resolve => { exec(`Notepad ${filename}`).on("close", () => { const contents = String(fs.readFileSync(filename)); fs.rmSync(filename); resolve(contents) }); }); }