Skip to content

Commit

Permalink
feat(cli): add support for manifest and packaging of Legacy Script En…
Browse files Browse the repository at this point in the history
…gine plugins
  • Loading branch information
leoweyr committed Jan 25, 2025
1 parent 58be2a4 commit 2933e01
Show file tree
Hide file tree
Showing 17 changed files with 397 additions and 18 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
# Legacy Script Engine Scaffold

A utility for assisting in the development of Legacy Script Engine plugins, supporting a native development experience on the Node.js platform.

> Only TypeScript projects are supported at the moment.
## 📦 Prepare

It is a non-intrusive tool, meaning it does not require any mandatory files to be kept in your project. However, it is recommended to add it as a development dependency to your environment for convenient usage:

```bash
npm install legacy-script-engine-scaffold --save-dev
```

## 🚀 Usage

Generate manifest.json for the Legacy Script Engine plugin:

```bash
npx lses manifest
```

Package the Legacy Script Engine plugin:

```bash
npx lses pack
```
19 changes: 15 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
{
"name": "legacy-script-engine-scaffold",
"version": "0.0.0",
"description": "A utility for assisting in the development of Legacy Script Engine plugins, supporting a native development experience on the Node.js platform.",
"version": "0.1.0",
"description": "A utility for assisting in the development of Legacy Script Engine plugins.",
"bugs": "https://github.com/leoweyr/LSEScaffold/issues",
"bin": {
"lses": "dist/cli/index.js"
},
"files": [
"dist"
],
"scripts": {
"clean": "tsc --build --clean",
"build": "npm run clean & tsc"
"compile": "tsc",
"build": "npm run clean && npm run compile",
"package": "npm run build && npm pack",
"deploy": "npm run package && npm publish"
},
"keywords": [
"levilamina",
"minecraft",
"bedrock-dedicated-server",
"utility",
"scaffold",
"cli",
"npx"
],
"author": "leoweyr <contact@leoweyr.com>",
Expand All @@ -23,7 +33,8 @@
},
"dependencies": {
"typescript": "^5.7.3",
"archiver": "^7.0.1"
"archiver": "^7.0.1",
"commander": "^13.1.0"
},
"repository": {
"type": "git",
Expand Down
41 changes: 41 additions & 0 deletions src/cli/CliLogger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { CliLoggableError } from "./CliLoggableError";


export class CliLogger {
private static TOOL_NAME: string = "legacy-script-engine-scaffold";

private readonly methodName: string;

public constructor(methodName: string) {
this.methodName = methodName;
}

public success(msg: string): void {
console.log(
`✅ ${CliLogger.TOOL_NAME}::${this.methodName}: ${msg}`
)
}

public error(error: CliLoggableError): void {
let suggestionString: string = "";

if (error.getSuggestion().length === 1) {
suggestionString += `Suggestion: ${error.getSuggestion()[0]}`;
} else {
suggestionString += "Suggestions:\n";

let solutionIndex: number = 1;

for (const solution of error.getSuggestion()) {
suggestionString += ` ${solutionIndex}. ${solution}\n`;
solutionIndex++;
}

suggestionString = suggestionString.slice(0, -2); // Remove the last newline.
}

console.error(
`❌ ${CliLogger.TOOL_NAME}::${this.methodName}: ${error.constructor.name} - ${error.getMessage()}\n ${suggestionString}`
);
}
}
55 changes: 55 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env node


import { program } from "commander";

import { CliLogger } from "./CliLogger";
import { TypeScriptProject } from "../project/TypeScriptProject";
import { CliLoggableError } from "./CliLoggableError";
import { Packager } from "../packager/Packager";


program
.name("lses")
.version("0.1.0")
.description("A utility for assisting in the development of Legacy Script Engine plugins.");

program
.command("manifest")
.description("generate manifest.json for the Legacy Script Engine plugin")
.action((): void => {
const logger: CliLogger = new CliLogger("manifest");

try {
const project: TypeScriptProject = TypeScriptProject.getInstance();

const successMessage: string = project.getManifest().generate();
logger.success(successMessage);
} catch (error) {
logger.error(error as CliLoggableError);
}
});

program
.command("pack")
.description("package the Legacy Script Engine plugin")
.action(async (): Promise<void> => {
const logger = new CliLogger("pack");

try {
const project: TypeScriptProject = TypeScriptProject.getInstance();
const packager: Packager = new Packager(project);

const successMessage: string = await packager.package();
logger.success(successMessage);
} catch (error) {
logger.error(error as CliLoggableError);
}
});

program.on("command:*", (): void => {
console.error(`Error: Invalid command lses ${program.args.join(" ")}`);
program.help();
});

program.parse(process.argv);
31 changes: 27 additions & 4 deletions src/nodejs/NodeJsConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ import * as Path from "path";
import * as File from "fs";

import { Project } from "../project/Project";
import { NodeJsConfigurationFileNotFoundError } from "./NodeJsConfigurationFileNotFoundError";
import { NodeJsConfigurationMissingError } from "./NodeJsConfigurationMissingError";


export class NodeJsConfiguration {
private readonly filePath: string;

public constructor(project: Project) {
this.filePath = Path.join(project.getPath(), "package.json");
const projectPath: string = project.getPath();

this.filePath = Path.join(projectPath, "package.json");

if (!File.existsSync(this.filePath)) {
throw new NodeJsConfigurationFileNotFoundError(projectPath);
}
}

public getPath(): string {
Expand All @@ -17,19 +25,34 @@ export class NodeJsConfiguration {

public getName(): string {
const configuration: any = JSON.parse(File.readFileSync(this.filePath, "utf-8"));
const name: string = configuration.name;

if (!name) {
throw new NodeJsConfigurationMissingError(this.filePath, "name");
}

return configuration.name;
return name;
}

public getVersion(): string {
const configuration: any = JSON.parse(File.readFileSync(this.filePath, "utf-8"));
const version: string = configuration.version;

return configuration.version;
if (!version) {
throw new NodeJsConfigurationMissingError(this.filePath, "version");
}

return version;
}

public getMainEntry(): string {
const configuration: any = JSON.parse(File.readFileSync(this.filePath, "utf-8"));
const mainEntry: string = configuration.main;

if (!mainEntry) {
throw new NodeJsConfigurationMissingError(this.filePath, "main");
}

return configuration.main;
return mainEntry;
}
}
25 changes: 25 additions & 0 deletions src/nodejs/NodeJsConfigurationFileNotFoundError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { CliLoggableError } from "../cli/CliLoggableError";


export class NodeJsConfigurationFileNotFoundError extends Error implements CliLoggableError {
private readonly msg: string;

public constructor(fileDirectory: string) {
const message: string = `Could not find package.json in ${fileDirectory}.`;

super(message);

this.msg = message;
}

public getMessage(): string {
return this.msg;
}

public getSuggestion(): Array<string> {
const suggestion: Array<string> = new Array<string>();
suggestion.push("Try `npm init` to initialize the project.");

return suggestion;
}
}
27 changes: 27 additions & 0 deletions src/nodejs/NodeJsConfigurationMissingError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { CliLoggableError } from "../cli/CliLoggableError";


export class NodeJsConfigurationMissingError extends Error implements CliLoggableError {
private readonly msg: string;
private readonly missingProperty: string;

public constructor(filePath: string, missingProperty: string) {
const message: string = `${filePath} is missing the required property \`${missingProperty}\`.`;

super(message);

this.msg = message;
this.missingProperty = missingProperty;
}

public getMessage(): string {
return this.msg;
}

public getSuggestion(): Array<string> {
const suggestion: Array<string> = new Array<string>();
suggestion.push(`Try checking if package.json includes the \`${this.missingProperty}\` configuration.`);

return suggestion;
}
}
14 changes: 11 additions & 3 deletions src/nodejs/TypeScriptConfiguration.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import * as Path from "path";
import * as File from "fs";

import ts, { ParsedCommandLine, Program } from "typescript";

import { Project } from "../project/Project";
import { TypeScriptConfigurationParseError } from "./TypeScriptConfigurationParseError";
import { TypeScriptConfigurationFileNotFoundError } from "./TypeScriptConfigurationFileNotFoundError";
import { TypeScriptConfigurationMissingError } from "./TypeScriptConfigurationMissingError";


export class TypeScriptConfiguration {
private static parseFile(filePath: string): ParsedCommandLine {
const file: any = ts.readConfigFile(filePath, ts.sys.readFile);

if (file.error) {
throw new Error("Error reading tsconfig.json.");
throw new TypeScriptConfigurationParseError(filePath);
}

const parsedCommandLine: ParsedCommandLine = ts.parseJsonConfigFileContent(
Expand All @@ -20,7 +24,7 @@ export class TypeScriptConfiguration {
);

if (parsedCommandLine.errors.length > 0) {
throw new Error(`Error parsing tsconfig.json: ${JSON.stringify(parsedCommandLine.errors)}.`);
throw new TypeScriptConfigurationParseError(filePath, parsedCommandLine.errors);
}

return parsedCommandLine;
Expand All @@ -30,14 +34,18 @@ export class TypeScriptConfiguration {

public constructor(project: Project) {
this.filePath = Path.join(project.getPath(), "tsconfig.json");

if (!File.existsSync(this.filePath)) {
throw new TypeScriptConfigurationFileNotFoundError(project.getPath());
}
}

public getEmittedDirectory(): string {
const parsedCommandLine: ParsedCommandLine = TypeScriptConfiguration.parseFile(this.filePath);
const emittedDirectory: string | undefined = parsedCommandLine.options.outDir;

if (!emittedDirectory) {
throw new Error("No `outDir` configuration in tsconfig.json.");
throw new TypeScriptConfigurationMissingError(this.filePath, "outDir");
}

return emittedDirectory;
Expand Down
25 changes: 25 additions & 0 deletions src/nodejs/TypeScriptConfigurationFileNotFoundError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { CliLoggableError } from "../cli/CliLoggableError";


export class TypeScriptConfigurationFileNotFoundError extends Error implements CliLoggableError {
private readonly msg: string;

public constructor(fileDirectory: string) {
const message: string = `Could not find tsconfig.json in ${fileDirectory}.`;

super(message);

this.msg = message;
}

public getMessage(): string {
return this.msg;
}

public getSuggestion(): Array<string> {
const suggestion: Array<string> = new Array<string>();
suggestion.push("Try `npx tsc --init` to initialize the TypeScript project.");

return suggestion;
}
}
27 changes: 27 additions & 0 deletions src/nodejs/TypeScriptConfigurationMissingError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { CliLoggableError } from "../cli/CliLoggableError";


export class TypeScriptConfigurationMissingError extends Error implements CliLoggableError {
private readonly msg: string;
private readonly missingProperty: string;

public constructor(filePath: string, missingProperty: string) {
const message: string = `${filePath} is missing the required property \`${missingProperty}\`.`;

super(message);

this.msg = message;
this.missingProperty = missingProperty;
}

public getMessage(): string {
return this.msg;
}

public getSuggestion(): Array<string> {
const suggestion: Array<string> = new Array<string>();
suggestion.push(`Try checking if tsconfig.json includes the \`${this.missingProperty}\` configuration.`);

return suggestion;
}
}
Loading

0 comments on commit 2933e01

Please # to comment.