bedrock-development
Version:
APIs for creating and editing files related to Minecraft Bedrock development.
242 lines (214 loc) • 8.72 kB
text/typescript
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)
});
});
}