From 13da1825083f91d0b7fed1ffc5f56ccc3159695f Mon Sep 17 00:00:00 2001 From: leoweyr Date: Thu, 23 Jan 2025 20:53:43 +0800 Subject: [PATCH 01/16] feat: add package.json parsing capability --- package.json | 3 +++ src/nodejs/PackageConfiguration.ts | 35 ++++++++++++++++++++++++++++++ src/project/Project.ts | 3 +++ 3 files changed, 41 insertions(+) create mode 100644 src/nodejs/PackageConfiguration.ts create mode 100644 src/project/Project.ts diff --git a/package.json b/package.json index 0daafe3..9f0f5af 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,9 @@ ], "author": "leoweyr ", "license": "MIT", + "devDependencies": { + "@types/node": "^22.10.7" + }, "dependencies": { "typescript": "^5.7.3" }, diff --git a/src/nodejs/PackageConfiguration.ts b/src/nodejs/PackageConfiguration.ts new file mode 100644 index 0000000..3336005 --- /dev/null +++ b/src/nodejs/PackageConfiguration.ts @@ -0,0 +1,35 @@ +import * as Path from "path"; +import * as File from "fs"; + +import { Project } from "../project/Project"; + + +export class PackageConfiguration { + private readonly filePath: string; + + public constructor(project: Project) { + this.filePath = Path.join(project.getPath(), "package.json"); + } + + public getPath(): string { + return this.filePath; + } + + public getName(): string { + const packageJson: any = JSON.parse(File.readFileSync(this.filePath, "utf-8")); + + return packageJson.name; + } + + public getVersion(): string { + const packageJson: any = JSON.parse(File.readFileSync(this.filePath, "utf-8")); + + return packageJson.version; + } + + public getMainEntry(): string { + const packageJson: any = JSON.parse(File.readFileSync(this.filePath, "utf-8")); + + return packageJson.main; + } +} diff --git a/src/project/Project.ts b/src/project/Project.ts new file mode 100644 index 0000000..9f94886 --- /dev/null +++ b/src/project/Project.ts @@ -0,0 +1,3 @@ +export interface Project { + getPath(): string; +} From fa8f3b90d73de8d234e87790f9922771f6636a65 Mon Sep 17 00:00:00 2001 From: leoweyr Date: Thu, 23 Jan 2025 21:19:18 +0800 Subject: [PATCH 02/16] feat: add tsconfig.json parsing capability --- src/nodejs/TypeScriptConfiguration.ts | 65 +++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/nodejs/TypeScriptConfiguration.ts diff --git a/src/nodejs/TypeScriptConfiguration.ts b/src/nodejs/TypeScriptConfiguration.ts new file mode 100644 index 0000000..d7f15fc --- /dev/null +++ b/src/nodejs/TypeScriptConfiguration.ts @@ -0,0 +1,65 @@ +import * as Path from "path"; + +import ts, { ParsedCommandLine, Program } from "typescript"; + +import { Project } from "../project/Project"; + + +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."); + } + + const parsedCommandLine: ParsedCommandLine = ts.parseJsonConfigFileContent( + file.config, + ts.sys, + Path.dirname(filePath) + ); + + if (parsedCommandLine.errors.length > 0) { + throw new Error(`Error parsing tsconfig.json: ${JSON.stringify(parsedCommandLine.errors)}.`); + } + + return parsedCommandLine; + } + + private readonly filePath: string; + + public constructor(project: Project) { + this.filePath = Path.join(project.getPath(), "tsconfig.json"); + } + + 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."); + } + + return emittedDirectory; + } + + public getCompiledDirectoryStructure(): Array { + const parsedCommandLine: ParsedCommandLine = TypeScriptConfiguration.parseFile(this.filePath); + + const program: Program = ts.createProgram({ + rootNames: parsedCommandLine.fileNames, + options: parsedCommandLine.options, + }); + + const emittedFilePaths: Array = new Array(); + const emittedDirectory: string = this.getEmittedDirectory(); + + program.emit(undefined, (fileName: string): void => { + emittedFilePaths.push(fileName); + }); + + return emittedFilePaths.map((filePath: string): string => { + return Path.relative(emittedDirectory, filePath).replace(/\\/g, "/") + }); + } +} From 57b8ba1c108e89181572e72cf5bc47c4b8af7169 Mon Sep 17 00:00:00 2001 From: leoweyr Date: Thu, 23 Jan 2025 21:39:27 +0800 Subject: [PATCH 03/16] feat: add manifest.json handling for Legacy Script Engine plugin package --- src/packager/Manifest.ts | 54 ++++++++++++++++++++++++++++++++++++++++ src/project/Project.ts | 10 ++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/packager/Manifest.ts diff --git a/src/packager/Manifest.ts b/src/packager/Manifest.ts new file mode 100644 index 0000000..d53e681 --- /dev/null +++ b/src/packager/Manifest.ts @@ -0,0 +1,54 @@ +import * as Path from "path"; +import * as File from "fs"; + +import { Project } from "../project/Project"; +import { PackageConfiguration } from "../nodejs/PackageConfiguration"; + + +export class Manifest { + private project: Project; + + public constructor(project: Project) { + this.project= project; + } + + public getPath(): string { + return Path.join(this.project.getBuiltPath(), "manifest.json"); + } + + public generate(): void { + const packageConfiguration: PackageConfiguration = this.project.getPackageConfiguration(); + const name: string = packageConfiguration.getName(); + const version: string = packageConfiguration.getVersion(); + const entry: string = packageConfiguration.getMainEntry(); + + const manifest = { + "name": name, + "version": version, + "type": "lse-nodejs", + "entry": entry, + "dependencies": [{"name": "legacy-script-engine-nodejs"}] + }; + + File.writeFileSync(this.getPath(), JSON.stringify(manifest, null, 2), "utf-8"); + } + + public static isValid(filePath: string): boolean { + if (!File.existsSync(filePath)) { + return false; + } + + try { + const data: string = File.readFileSync(filePath, "utf-8"); + const manifest: any = JSON.parse(data); + + return manifest.hasOwnProperty("name") && + manifest.hasOwnProperty("version") && + manifest.hasOwnProperty("type") && + manifest.hasOwnProperty("entry") && + manifest.hasOwnProperty("dependencies"); + } catch (error) { + return false; + } + } +} diff --git a/src/project/Project.ts b/src/project/Project.ts index 9f94886..e0e14e2 100644 --- a/src/project/Project.ts +++ b/src/project/Project.ts @@ -1,3 +1,13 @@ +import { PackageConfiguration } from "../nodejs/PackageConfiguration"; +import { Manifest } from "../packager/Manifest"; + + export interface Project { + getName(): string; getPath(): string; + getBuiltPath(): string; + getPackageConfiguration(): PackageConfiguration; + getManifest(): Manifest; + isBuilt(): boolean; + isManifest(): boolean; } From 3265ace38012aab5cf531ae33b2d8f8cd8e65ab4 Mon Sep 17 00:00:00 2001 From: leoweyr Date: Thu, 23 Jan 2025 23:11:31 +0800 Subject: [PATCH 04/16] feat: add support for TypeScript projects --- ...onfiguration.ts => NodeJsConfiguration.ts} | 14 +-- src/packager/Manifest.ts | 4 +- src/project/Project.ts | 6 +- src/project/TypeScriptProject.ts | 88 +++++++++++++++++++ 4 files changed, 99 insertions(+), 13 deletions(-) rename src/nodejs/{PackageConfiguration.ts => NodeJsConfiguration.ts} (52%) create mode 100644 src/project/TypeScriptProject.ts diff --git a/src/nodejs/PackageConfiguration.ts b/src/nodejs/NodeJsConfiguration.ts similarity index 52% rename from src/nodejs/PackageConfiguration.ts rename to src/nodejs/NodeJsConfiguration.ts index 3336005..347bcbf 100644 --- a/src/nodejs/PackageConfiguration.ts +++ b/src/nodejs/NodeJsConfiguration.ts @@ -4,7 +4,7 @@ import * as File from "fs"; import { Project } from "../project/Project"; -export class PackageConfiguration { +export class NodeJsConfiguration { private readonly filePath: string; public constructor(project: Project) { @@ -16,20 +16,20 @@ export class PackageConfiguration { } public getName(): string { - const packageJson: any = JSON.parse(File.readFileSync(this.filePath, "utf-8")); + const configuration: any = JSON.parse(File.readFileSync(this.filePath, "utf-8")); - return packageJson.name; + return configuration.name; } public getVersion(): string { - const packageJson: any = JSON.parse(File.readFileSync(this.filePath, "utf-8")); + const configuration: any = JSON.parse(File.readFileSync(this.filePath, "utf-8")); - return packageJson.version; + return configuration.version; } public getMainEntry(): string { - const packageJson: any = JSON.parse(File.readFileSync(this.filePath, "utf-8")); + const configuration: any = JSON.parse(File.readFileSync(this.filePath, "utf-8")); - return packageJson.main; + return configuration.main; } } diff --git a/src/packager/Manifest.ts b/src/packager/Manifest.ts index d53e681..f35cd77 100644 --- a/src/packager/Manifest.ts +++ b/src/packager/Manifest.ts @@ -2,7 +2,7 @@ import * as Path from "path"; import * as File from "fs"; import { Project } from "../project/Project"; -import { PackageConfiguration } from "../nodejs/PackageConfiguration"; +import { NodeJsConfiguration } from "../nodejs/NodeJsConfiguration"; export class Manifest { @@ -17,7 +17,7 @@ export class Manifest { } public generate(): void { - const packageConfiguration: PackageConfiguration = this.project.getPackageConfiguration(); + const packageConfiguration: NodeJsConfiguration = this.project.getNodeJsConfiguration(); const name: string = packageConfiguration.getName(); const version: string = packageConfiguration.getVersion(); const entry: string = packageConfiguration.getMainEntry(); diff --git a/src/project/Project.ts b/src/project/Project.ts index e0e14e2..d8a2e57 100644 --- a/src/project/Project.ts +++ b/src/project/Project.ts @@ -1,13 +1,11 @@ -import { PackageConfiguration } from "../nodejs/PackageConfiguration"; -import { Manifest } from "../packager/Manifest"; +import { NodeJsConfiguration } from "../nodejs/NodeJsConfiguration"; export interface Project { getName(): string; getPath(): string; getBuiltPath(): string; - getPackageConfiguration(): PackageConfiguration; - getManifest(): Manifest; + getNodeJsConfiguration(): NodeJsConfiguration; isBuilt(): boolean; isManifest(): boolean; } diff --git a/src/project/TypeScriptProject.ts b/src/project/TypeScriptProject.ts new file mode 100644 index 0000000..4b5c700 --- /dev/null +++ b/src/project/TypeScriptProject.ts @@ -0,0 +1,88 @@ +import { Dirent } from "node:fs"; +import * as File from "fs"; +import * as Path from "path"; + +import { Project } from "./Project"; +import { NodeJsConfiguration } from "../nodejs/NodeJsConfiguration"; +import { TypeScriptConfiguration } from "../nodejs/TypeScriptConfiguration"; +import { Manifest } from "../packager/Manifest"; + + +export class TypeScriptProject implements Project { + private static instance: TypeScriptProject; + + private static getDirectoryStructure(baseDirectory: string): Array { + const filePaths: Array = new Array(); + const directories: Array = new Array(); + directories.push(baseDirectory); + + while (directories.length > 0) { + const currentDirectory: string = directories.pop()!; + const directoryEntries: Array = File.readdirSync(currentDirectory, {withFileTypes: true}); + + for (const directoryEntry of directoryEntries) { + const absolutePath: string = Path.join(currentDirectory, directoryEntry.name); + + if (directoryEntry.isDirectory()) { + directories.push(absolutePath); + } else { + filePaths.push(Path.relative(baseDirectory, absolutePath).replace(/\\/g, "/")); + } + } + } + + return filePaths; + } + + public static getInstance(): TypeScriptProject { + if (!TypeScriptProject.instance) { + const currentWordDirectory: string = process.cwd(); + TypeScriptProject.instance = new TypeScriptProject(currentWordDirectory); + } + + return TypeScriptProject.instance; + } + + private readonly path: string; + private readonly nodeJsConfiguration: NodeJsConfiguration; + private readonly typeScriptConfiguration: TypeScriptConfiguration; + private readonly manifest: Manifest; + + private constructor(path: string) { + this.path = path; + this.nodeJsConfiguration = new NodeJsConfiguration(this); + this.typeScriptConfiguration = new TypeScriptConfiguration(this); + this.manifest = new Manifest(this); + } + + public getName(): string { + return this.nodeJsConfiguration.getName(); + } + + public getPath(): string { + return this.path; + } + + public getBuiltPath(): string { + return this.typeScriptConfiguration.getEmittedDirectory(); + } + + public getNodeJsConfiguration(): NodeJsConfiguration { + return this.nodeJsConfiguration; + } + + public isBuilt(): boolean { + const emittedDirectoryStructure: Array = TypeScriptProject.getDirectoryStructure( + this.typeScriptConfiguration.getEmittedDirectory() + ); + const compiledDirectoryStructure: Array = this.typeScriptConfiguration.getCompiledDirectoryStructure(); + + return compiledDirectoryStructure.every((filePath: string): boolean => { + return emittedDirectoryStructure.includes(filePath) + }); + } + + public isManifest(): boolean { + return Manifest.isValid(this.manifest.getPath()); + } +} From 58be2a4c75317a4991bd455c44ed5f2c0e616db9 Mon Sep 17 00:00:00 2001 From: leoweyr Date: Fri, 24 Jan 2025 11:11:40 +0800 Subject: [PATCH 05/16] feat: add packaging functionality for Legacy Script Engine plugin --- package.json | 6 ++- src/cli/CliLoggableError.ts | 4 ++ src/packager/Packager.ts | 46 +++++++++++++++++++++++ src/packager/ProjectNotBuiltError.ts | 24 ++++++++++++ src/packager/ProjectNotManifestedError.ts | 22 +++++++++++ 5 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 src/cli/CliLoggableError.ts create mode 100644 src/packager/Packager.ts create mode 100644 src/packager/ProjectNotBuiltError.ts create mode 100644 src/packager/ProjectNotManifestedError.ts diff --git a/package.json b/package.json index 9f0f5af..97abefc 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,12 @@ "author": "leoweyr ", "license": "MIT", "devDependencies": { - "@types/node": "^22.10.7" + "@types/node": "^22.10.7", + "@types/archiver": "^6.0.3" }, "dependencies": { - "typescript": "^5.7.3" + "typescript": "^5.7.3", + "archiver": "^7.0.1" }, "repository": { "type": "git", diff --git a/src/cli/CliLoggableError.ts b/src/cli/CliLoggableError.ts new file mode 100644 index 0000000..60c74c1 --- /dev/null +++ b/src/cli/CliLoggableError.ts @@ -0,0 +1,4 @@ +export interface CliLoggableError { + getMessage(): string; + getSuggestion(): Array; +} diff --git a/src/packager/Packager.ts b/src/packager/Packager.ts new file mode 100644 index 0000000..101a539 --- /dev/null +++ b/src/packager/Packager.ts @@ -0,0 +1,46 @@ +import * as File from "fs"; +import * as Path from "path"; + +import archiver, { Archiver, ArchiverError } from "archiver"; + +import { Project } from "../project/Project"; +import { ProjectNotBuiltError } from "./ProjectNotBuiltError"; +import { ProjectNotManifestedError } from "./ProjectNotManifestedError"; + + +export class Packager { + private project: Project; + + public constructor(project: Project) { + this.project = project; + } + + public async package(): Promise { + if (!this.project.isBuilt()) { + throw new ProjectNotBuiltError(); + } + + if (!this.project.isManifest()) { + throw new ProjectNotManifestedError(); + } + + File.copyFileSync( + this.project.getNodeJsConfiguration().getPath(), + Path.join(this.project.getBuiltPath(), "package.json") + ); + + const outputStream: File.WriteStream = File.createWriteStream( + Path.join(this.project.getPath(), `${this.project.getName()}.zip`) + ); + + const archive: Archiver = archiver("zip", {zlib: {level: 9}}); + + archive.on("error", (error: ArchiverError): never => { + throw error; + }); + + archive.pipe(outputStream); + archive.directory(this.project.getBuiltPath(), false); + await archive.finalize(); + } +} diff --git a/src/packager/ProjectNotBuiltError.ts b/src/packager/ProjectNotBuiltError.ts new file mode 100644 index 0000000..513a646 --- /dev/null +++ b/src/packager/ProjectNotBuiltError.ts @@ -0,0 +1,24 @@ +import { CliLoggableError } from "../cli/CliLoggableError"; + + +export class ProjectNotBuiltError extends Error implements CliLoggableError { + private static MESSAGE: string = "Project is not built."; + + public constructor() { + super(ProjectNotBuiltError.MESSAGE); + } + + getMessage(): string { + return ProjectNotBuiltError.MESSAGE; + } + + getSuggestion(): Array { + const suggestion: Array = new Array(); + + suggestion.push("Try checking if tsconfig.json includes the `include` configuration."); + suggestion.push("Try checking if tsconfig.json includes the `outDir` configuration."); + suggestion.push("Try `npx tsc` to build the project if it is a TypeScript project."); + + return suggestion; + } +} diff --git a/src/packager/ProjectNotManifestedError.ts b/src/packager/ProjectNotManifestedError.ts new file mode 100644 index 0000000..a5ad317 --- /dev/null +++ b/src/packager/ProjectNotManifestedError.ts @@ -0,0 +1,22 @@ +import { CliLoggableError } from "../cli/CliLoggableError"; + + +export class ProjectNotManifestedError extends Error implements CliLoggableError { + private static MESSAGE: string = "Project is not manifested."; + + public constructor() { + super(ProjectNotManifestedError.MESSAGE); + } + + getMessage(): string { + return ProjectNotManifestedError.MESSAGE; + } + + getSuggestion(): Array { + const suggestion: Array = new Array(); + + suggestion.push("Try `npx lses manifest` to manifest the project."); + + return suggestion; + } +} From 2933e0100765d7785f87472e113c077112a46e9a Mon Sep 17 00:00:00 2001 From: leoweyr Date: Fri, 24 Jan 2025 15:57:15 +0800 Subject: [PATCH 06/16] feat(cli): add support for manifest and packaging of Legacy Script Engine plugins --- README.md | 24 ++++++++ package.json | 19 +++++-- src/cli/CliLogger.ts | 41 ++++++++++++++ src/cli/index.ts | 55 +++++++++++++++++++ src/nodejs/NodeJsConfiguration.ts | 31 +++++++++-- .../NodeJsConfigurationFileNotFoundError.ts | 25 +++++++++ src/nodejs/NodeJsConfigurationMissingError.ts | 27 +++++++++ src/nodejs/TypeScriptConfiguration.ts | 14 ++++- ...ypeScriptConfigurationFileNotFoundError.ts | 25 +++++++++ .../TypeScriptConfigurationMissingError.ts | 27 +++++++++ .../TypeScriptConfigurationParseError.ts | 34 ++++++++++++ src/packager/Manifest.ts | 29 ++++++++-- .../ManifestConfigurationMissingError.ts | 27 +++++++++ src/packager/ManifestFileNotFoundError.ts | 25 +++++++++ src/packager/Packager.ts | 4 +- src/project/Project.ts | 2 + src/project/TypeScriptProject.ts | 6 +- 17 files changed, 397 insertions(+), 18 deletions(-) create mode 100644 src/cli/CliLogger.ts create mode 100644 src/cli/index.ts create mode 100644 src/nodejs/NodeJsConfigurationFileNotFoundError.ts create mode 100644 src/nodejs/NodeJsConfigurationMissingError.ts create mode 100644 src/nodejs/TypeScriptConfigurationFileNotFoundError.ts create mode 100644 src/nodejs/TypeScriptConfigurationMissingError.ts create mode 100644 src/nodejs/TypeScriptConfigurationParseError.ts create mode 100644 src/packager/ManifestConfigurationMissingError.ts create mode 100644 src/packager/ManifestFileNotFoundError.ts diff --git a/README.md b/README.md index 4ec2a24..2a0ed74 100644 --- a/README.md +++ b/README.md @@ -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 +``` diff --git a/package.json b/package.json index 97abefc..aba4f72 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,20 @@ { "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", @@ -13,6 +22,7 @@ "bedrock-dedicated-server", "utility", "scaffold", + "cli", "npx" ], "author": "leoweyr ", @@ -23,7 +33,8 @@ }, "dependencies": { "typescript": "^5.7.3", - "archiver": "^7.0.1" + "archiver": "^7.0.1", + "commander": "^13.1.0" }, "repository": { "type": "git", diff --git a/src/cli/CliLogger.ts b/src/cli/CliLogger.ts new file mode 100644 index 0000000..5055f53 --- /dev/null +++ b/src/cli/CliLogger.ts @@ -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}` + ); + } +} diff --git a/src/cli/index.ts b/src/cli/index.ts new file mode 100644 index 0000000..bdfac58 --- /dev/null +++ b/src/cli/index.ts @@ -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 => { + 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); diff --git a/src/nodejs/NodeJsConfiguration.ts b/src/nodejs/NodeJsConfiguration.ts index 347bcbf..3899a84 100644 --- a/src/nodejs/NodeJsConfiguration.ts +++ b/src/nodejs/NodeJsConfiguration.ts @@ -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 { @@ -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; } } diff --git a/src/nodejs/NodeJsConfigurationFileNotFoundError.ts b/src/nodejs/NodeJsConfigurationFileNotFoundError.ts new file mode 100644 index 0000000..45c7e79 --- /dev/null +++ b/src/nodejs/NodeJsConfigurationFileNotFoundError.ts @@ -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 { + const suggestion: Array = new Array(); + suggestion.push("Try `npm init` to initialize the project."); + + return suggestion; + } +} diff --git a/src/nodejs/NodeJsConfigurationMissingError.ts b/src/nodejs/NodeJsConfigurationMissingError.ts new file mode 100644 index 0000000..c6faf17 --- /dev/null +++ b/src/nodejs/NodeJsConfigurationMissingError.ts @@ -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 { + const suggestion: Array = new Array(); + suggestion.push(`Try checking if package.json includes the \`${this.missingProperty}\` configuration.`); + + return suggestion; + } +} diff --git a/src/nodejs/TypeScriptConfiguration.ts b/src/nodejs/TypeScriptConfiguration.ts index d7f15fc..2aca32d 100644 --- a/src/nodejs/TypeScriptConfiguration.ts +++ b/src/nodejs/TypeScriptConfiguration.ts @@ -1,8 +1,12 @@ 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 { @@ -10,7 +14,7 @@ export class TypeScriptConfiguration { 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( @@ -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; @@ -30,6 +34,10 @@ 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 { @@ -37,7 +45,7 @@ export class TypeScriptConfiguration { 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; diff --git a/src/nodejs/TypeScriptConfigurationFileNotFoundError.ts b/src/nodejs/TypeScriptConfigurationFileNotFoundError.ts new file mode 100644 index 0000000..4fb0d48 --- /dev/null +++ b/src/nodejs/TypeScriptConfigurationFileNotFoundError.ts @@ -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 { + const suggestion: Array = new Array(); + suggestion.push("Try `npx tsc --init` to initialize the TypeScript project."); + + return suggestion; + } +} diff --git a/src/nodejs/TypeScriptConfigurationMissingError.ts b/src/nodejs/TypeScriptConfigurationMissingError.ts new file mode 100644 index 0000000..f8b12cf --- /dev/null +++ b/src/nodejs/TypeScriptConfigurationMissingError.ts @@ -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 { + const suggestion: Array = new Array(); + suggestion.push(`Try checking if tsconfig.json includes the \`${this.missingProperty}\` configuration.`); + + return suggestion; + } +} diff --git a/src/nodejs/TypeScriptConfigurationParseError.ts b/src/nodejs/TypeScriptConfigurationParseError.ts new file mode 100644 index 0000000..628c2dc --- /dev/null +++ b/src/nodejs/TypeScriptConfigurationParseError.ts @@ -0,0 +1,34 @@ +import { CliLoggableError } from "../cli/CliLoggableError"; +import { Diagnostic } from "typescript"; + + +export class TypeScriptConfigurationParseError extends Error implements CliLoggableError { + private readonly msg: string; + private readonly diagnostics: Array | null; + + public constructor(filePath: string, diagnostics: Array | null = null) { + const message: string = `Failed to parse ${filePath}.`; + + super(message); + + this.msg = message; + this.diagnostics = diagnostics; + } + + public getMessage(): string { + return this.msg; + } + + public getSuggestion(): Array { + const suggestion: Array = new Array(); + suggestion.push("Try checking the tsconfig.json."); + + if (this.diagnostics !== null) { + suggestion.push( + `Try to resolve the issue based on the diagnostic information from the TypeScript compiler: ${JSON.stringify(this.diagnostics)}` + ); + } + + return suggestion; + } +} diff --git a/src/packager/Manifest.ts b/src/packager/Manifest.ts index f35cd77..dc742b3 100644 --- a/src/packager/Manifest.ts +++ b/src/packager/Manifest.ts @@ -3,6 +3,8 @@ import * as File from "fs"; import { Project } from "../project/Project"; import { NodeJsConfiguration } from "../nodejs/NodeJsConfiguration"; +import { ManifestFileNotFoundError } from "./ManifestFileNotFoundError"; +import { ManifestConfigurationMissingError } from "./ManifestConfigurationMissingError"; export class Manifest { @@ -16,11 +18,26 @@ export class Manifest { return Path.join(this.project.getBuiltPath(), "manifest.json"); } - public generate(): void { - const packageConfiguration: NodeJsConfiguration = this.project.getNodeJsConfiguration(); - const name: string = packageConfiguration.getName(); - const version: string = packageConfiguration.getVersion(); - const entry: string = packageConfiguration.getMainEntry(); + public getName(): string { + if (!File.existsSync(this.getPath())) { + throw new ManifestFileNotFoundError(this.project.getBuiltPath()); + } + + const manifest: any = JSON.parse(File.readFileSync(this.getPath(), "utf-8")); + const name: string = manifest.name; + + if (!name) { + throw new ManifestConfigurationMissingError(this.getPath(), "name"); + } + + return name; + } + + public generate(): string { + const nodeJsConfiguration: NodeJsConfiguration = this.project.getNodeJsConfiguration(); + const name: string = nodeJsConfiguration.getName(); + const version: string = nodeJsConfiguration.getVersion(); + const entry: string = nodeJsConfiguration.getMainEntry(); const manifest = { "name": name, @@ -31,6 +48,8 @@ export class Manifest { }; File.writeFileSync(this.getPath(), JSON.stringify(manifest, null, 2), "utf-8"); + + return `The manifest has been generated at ${this.getPath()}.`; } public static isValid(filePath: string): boolean { diff --git a/src/packager/ManifestConfigurationMissingError.ts b/src/packager/ManifestConfigurationMissingError.ts new file mode 100644 index 0000000..0f5096d --- /dev/null +++ b/src/packager/ManifestConfigurationMissingError.ts @@ -0,0 +1,27 @@ +import { CliLoggableError } from "../cli/CliLoggableError"; + + +export class ManifestConfigurationMissingError 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 { + const suggestion: Array = new Array(); + suggestion.push(`Try checking if manifest.json includes the \`${this.missingProperty}\` configuration.`); + + return suggestion; + } +} diff --git a/src/packager/ManifestFileNotFoundError.ts b/src/packager/ManifestFileNotFoundError.ts new file mode 100644 index 0000000..8462687 --- /dev/null +++ b/src/packager/ManifestFileNotFoundError.ts @@ -0,0 +1,25 @@ +import { CliLoggableError } from "../cli/CliLoggableError"; + + +export class ManifestFileNotFoundError extends Error implements CliLoggableError{ + private readonly msg: string; + + public constructor(fileDirectory: string) { + const message: string = `Could not find manifest.json in ${fileDirectory}.`; + + super(message); + + this.msg = message; + } + + public getMessage(): string { + return this.msg; + } + + public getSuggestion(): Array { + const suggestion: Array = new Array(); + suggestion.push("Try `npx lses manifest` to manifest the project."); + + return suggestion; + } +} diff --git a/src/packager/Packager.ts b/src/packager/Packager.ts index 101a539..01d7a5c 100644 --- a/src/packager/Packager.ts +++ b/src/packager/Packager.ts @@ -15,7 +15,7 @@ export class Packager { this.project = project; } - public async package(): Promise { + public async package(): Promise { if (!this.project.isBuilt()) { throw new ProjectNotBuiltError(); } @@ -42,5 +42,7 @@ export class Packager { archive.pipe(outputStream); archive.directory(this.project.getBuiltPath(), false); await archive.finalize(); + + return `The package has been generated at ${outputStream.path}.`; } } diff --git a/src/project/Project.ts b/src/project/Project.ts index d8a2e57..933f5c2 100644 --- a/src/project/Project.ts +++ b/src/project/Project.ts @@ -1,4 +1,5 @@ import { NodeJsConfiguration } from "../nodejs/NodeJsConfiguration"; +import { Manifest } from "../packager/Manifest"; export interface Project { @@ -6,6 +7,7 @@ export interface Project { getPath(): string; getBuiltPath(): string; getNodeJsConfiguration(): NodeJsConfiguration; + getManifest(): Manifest; isBuilt(): boolean; isManifest(): boolean; } diff --git a/src/project/TypeScriptProject.ts b/src/project/TypeScriptProject.ts index 4b5c700..8341685 100644 --- a/src/project/TypeScriptProject.ts +++ b/src/project/TypeScriptProject.ts @@ -56,7 +56,7 @@ export class TypeScriptProject implements Project { } public getName(): string { - return this.nodeJsConfiguration.getName(); + return this.manifest.getName(); } public getPath(): string { @@ -71,6 +71,10 @@ export class TypeScriptProject implements Project { return this.nodeJsConfiguration; } + public getManifest(): Manifest { + return this.manifest; + } + public isBuilt(): boolean { const emittedDirectoryStructure: Array = TypeScriptProject.getDirectoryStructure( this.typeScriptConfiguration.getEmittedDirectory() From e51a1691c39bf3c8ee515fc2bdc072e89d8ff0de Mon Sep 17 00:00:00 2001 From: leoweyr Date: Sat, 25 Jan 2025 23:42:07 +0800 Subject: [PATCH 07/16] feat: introduce handling for the project-generated plugin package --- package.json | 6 ++-- src/packager/Packager.ts | 11 ++++++- src/packager/PluginPackage.ts | 35 ++++++++++++++++++++++ src/packager/PluginPackageNotFoundError.ts | 22 ++++++++++++++ 4 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 src/packager/PluginPackage.ts create mode 100644 src/packager/PluginPackageNotFoundError.ts diff --git a/package.json b/package.json index aba4f72..953c819 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,14 @@ "license": "MIT", "devDependencies": { "@types/node": "^22.10.7", - "@types/archiver": "^6.0.3" + "@types/archiver": "^6.0.3", + "@types/unzipper": "^0.10.10" }, "dependencies": { "typescript": "^5.7.3", "archiver": "^7.0.1", - "commander": "^13.1.0" + "commander": "^13.1.0", + "unzipper": "^0.12.3" }, "repository": { "type": "git", diff --git a/src/packager/Packager.ts b/src/packager/Packager.ts index 01d7a5c..1e2ac66 100644 --- a/src/packager/Packager.ts +++ b/src/packager/Packager.ts @@ -4,15 +4,20 @@ import * as Path from "path"; import archiver, { Archiver, ArchiverError } from "archiver"; import { Project } from "../project/Project"; +import { PluginPackage } from "./PluginPackage"; import { ProjectNotBuiltError } from "./ProjectNotBuiltError"; import { ProjectNotManifestedError } from "./ProjectNotManifestedError"; export class Packager { private project: Project; + private readonly pluginPackage: PluginPackage; public constructor(project: Project) { this.project = project; + + const packagePath: string = Path.join(this.project.getPath(), `${this.project.getName()}.zip`); + this.pluginPackage = new PluginPackage(this.project.getName(), packagePath); } public async package(): Promise { @@ -30,7 +35,7 @@ export class Packager { ); const outputStream: File.WriteStream = File.createWriteStream( - Path.join(this.project.getPath(), `${this.project.getName()}.zip`) + this.pluginPackage.getPath() ); const archive: Archiver = archiver("zip", {zlib: {level: 9}}); @@ -45,4 +50,8 @@ export class Packager { return `The package has been generated at ${outputStream.path}.`; } + + public getPluginPackage(): PluginPackage { + return this.pluginPackage; + } } diff --git a/src/packager/PluginPackage.ts b/src/packager/PluginPackage.ts new file mode 100644 index 0000000..88d3232 --- /dev/null +++ b/src/packager/PluginPackage.ts @@ -0,0 +1,35 @@ +import * as File from "fs"; +import * as Path from "path"; + +import * as Unzipper from "unzipper"; + +import { PluginPackageNotFoundError } from "./PluginPackageNotFoundError"; + + +export class PluginPackage { + private readonly name: string; + private readonly filePath: string; + + public constructor(pluginName: string, filePath: string) { + this.name = pluginName; + this.filePath = filePath; + } + + public getName(): string { + return this.name; + } + + public getPath(): string { + return this.filePath; + } + + public async expand(destinationPath: string): Promise { + if (!File.existsSync(this.filePath)) { + throw new PluginPackageNotFoundError(); + } + + const pluginPackage: Unzipper.CentralDirectory = await Unzipper.Open.file(this.filePath); + const extractedPath: string = Path.join(destinationPath, this.name); + await pluginPackage.extract({path: extractedPath}); + } +} diff --git a/src/packager/PluginPackageNotFoundError.ts b/src/packager/PluginPackageNotFoundError.ts new file mode 100644 index 0000000..9d2ce59 --- /dev/null +++ b/src/packager/PluginPackageNotFoundError.ts @@ -0,0 +1,22 @@ +import { CliLoggableError } from "../cli/CliLoggableError"; + + +export class PluginPackageNotFoundError extends Error implements CliLoggableError { + private static MESSAGE: string = "Could not find the plugin package."; + + public constructor() { + super(PluginPackageNotFoundError.MESSAGE); + } + + public getMessage(): string { + return PluginPackageNotFoundError.MESSAGE; + } + + public getSuggestion(): Array { + const suggestion: Array = new Array(); + + suggestion.push("Try `npx lses pack` to pack the project."); + + return suggestion; + } +} From 6aeec36daa2653f3563c00a06e53880b2293d024 Mon Sep 17 00:00:00 2001 From: leoweyr Date: Sat, 25 Jan 2025 23:55:22 +0800 Subject: [PATCH 08/16] feat: enable deployment of plugins to local Levilamina server --- .../LevilaminaPluginNotFoundError.ts | 5 ++ src/deployment/LevilaminaServer.ts | 46 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 src/deployment/LevilaminaPluginNotFoundError.ts create mode 100644 src/deployment/LevilaminaServer.ts diff --git a/src/deployment/LevilaminaPluginNotFoundError.ts b/src/deployment/LevilaminaPluginNotFoundError.ts new file mode 100644 index 0000000..11d3eeb --- /dev/null +++ b/src/deployment/LevilaminaPluginNotFoundError.ts @@ -0,0 +1,5 @@ +export class LevilaminaPluginNotFoundError extends Error { + public constructor(pluginName: string) { + super(`Levilamina plugin ${pluginName} does not found.`); + } +} diff --git a/src/deployment/LevilaminaServer.ts b/src/deployment/LevilaminaServer.ts new file mode 100644 index 0000000..3b1acc1 --- /dev/null +++ b/src/deployment/LevilaminaServer.ts @@ -0,0 +1,46 @@ +import * as File from "fs"; +import * as Path from "path"; + +import { LevilaminaPluginNotFoundError } from "./LevilaminaPluginNotFoundError"; +import { PluginPackage } from "../packager/PluginPackage"; + + +export class LevilaminaServer { + private static deleteDirectory(basePath: string): void { + if (File.existsSync(basePath)) { + File.readdirSync(basePath).forEach((file: string): void => { + const currentPath: string = Path.join(basePath, file); + + if (File.lstatSync(currentPath).isDirectory()) { + LevilaminaServer.deleteDirectory(currentPath); + } else { + File.unlinkSync(currentPath); + } + }); + + File.rmdirSync(basePath); + } + } + + private readonly pluginDirectory: string; + + public constructor(path: string) { + this.pluginDirectory = Path.join(path, "plugins"); + } + + public removePlugin(pluginName: string): void { + const pluginPath: string = Path.join(this.pluginDirectory, pluginName); + + if (File.existsSync(pluginPath)) { + LevilaminaServer.deleteDirectory(pluginPath); + } else { + throw new LevilaminaPluginNotFoundError(pluginName); + } + } + + public async importPlugin(pluginPackage: PluginPackage): Promise { + await pluginPackage.expand(this.pluginDirectory); + + return `Plugin ${pluginPackage.getName()} has been imported to ${this.pluginDirectory}.`; + } +} From 64df3509243b0b53c895db7ca6854e60f496812f Mon Sep 17 00:00:00 2001 From: leoweyr Date: Sun, 26 Jan 2025 00:13:31 +0800 Subject: [PATCH 09/16] chore(scripts): add local deployment to package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 953c819..943ed45 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "clean": "tsc --build --clean", "compile": "tsc", "build": "npm run clean && npm run compile", + "install": "npm run build && npm link", "package": "npm run build && npm pack", "deploy": "npm run package && npm publish" }, From ea9ef70701d127b2b114f503102359dbffbea673 Mon Sep 17 00:00:00 2001 From: leoweyr Date: Sun, 26 Jan 2025 00:43:09 +0800 Subject: [PATCH 10/16] feat(cli): add support for local deployment of Legacy Script Engine plugins --- README.md | 10 ++++++++++ package.json | 2 +- src/cli/index.ts | 29 +++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a0ed74..3b477a8 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,13 @@ Package the Legacy Script Engine plugin: ```bash npx lses pack ``` + +Deploy the Legacy Script Engine plugin package to the local Levilamina server: + +```bash +npx lses deploy +``` + +| Argument | Description | Type | +| -------- | --------------------------------------------- | ------ | +| `` | Specific Levilamina server working directory. | String | diff --git a/package.json b/package.json index 943ed45..69cb7f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "legacy-script-engine-scaffold", - "version": "0.1.0", + "version": "0.2.0", "description": "A utility for assisting in the development of Legacy Script Engine plugins.", "bugs": "https://github.com/leoweyr/LSEScaffold/issues", "bin": { diff --git a/src/cli/index.ts b/src/cli/index.ts index bdfac58..bb2ecf5 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -7,6 +7,8 @@ import { CliLogger } from "./CliLogger"; import { TypeScriptProject } from "../project/TypeScriptProject"; import { CliLoggableError } from "./CliLoggableError"; import { Packager } from "../packager/Packager"; +import { LevilaminaServer } from "../deployment/LevilaminaServer"; +import { PluginPackage } from "../packager/PluginPackage"; program @@ -47,6 +49,33 @@ program } }); +program + .command("deploy") + .description("deploy the Legacy Script Engine plugin package to the local Levilamina server") + .argument("", "specific Levilamina server working directory") + .action(async (path: string): Promise => { + const logger = new CliLogger("deploy"); + + try { + const project: TypeScriptProject = TypeScriptProject.getInstance(); + const packager: Packager = new Packager(project); + const levilaminaServer: LevilaminaServer = new LevilaminaServer(path); + const pluginPackage: PluginPackage = packager.getPluginPackage(); + + try { + levilaminaServer.removePlugin(project.getName()); + } catch (error) { + // Do nothing if the plugin does not exist. + } + + const successMessage: string = await levilaminaServer.importPlugin(pluginPackage); + + 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(); From ea61ebd52e655a95d34c9080ceb0e82f9cfdaaa1 Mon Sep 17 00:00:00 2001 From: leoweyr Date: Mon, 27 Jan 2025 22:33:00 +0800 Subject: [PATCH 11/16] fix(scripts): correct local deployment to prevent npm install override --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 69cb7f4..170aacb 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "clean": "tsc --build --clean", "compile": "tsc", "build": "npm run clean && npm run compile", - "install": "npm run build && npm link", + "local-deploy": "npm run build && npm link", "package": "npm run build && npm pack", "deploy": "npm run package && npm publish" }, From d597babf6a6d50e68f9100873f9ef08460c7d678 Mon Sep 17 00:00:00 2001 From: leoweyr Date: Tue, 28 Jan 2025 00:22:22 +0800 Subject: [PATCH 12/16] fix: correct entry resolution in manifest.json handling based on package.json --- README.md | 12 ++++++++++-- package.json | 2 +- src/nodejs/NodeJsConfigurationMainError.ts | 22 ++++++++++++++++++++++ src/packager/Manifest.ts | 10 ++++++++-- src/project/TypeScriptProject.ts | 2 +- 5 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 src/nodejs/NodeJsConfigurationMainError.ts diff --git a/README.md b/README.md index 3b477a8..85988c3 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Package the Legacy Script Engine plugin: npx lses pack ``` -Deploy the Legacy Script Engine plugin package to the local Levilamina server: +Deploy the Legacy Script Engine plugin package to the local LeviLamina server: ```bash npx lses deploy @@ -34,4 +34,12 @@ npx lses deploy | Argument | Description | Type | | -------- | --------------------------------------------- | ------ | -| `` | Specific Levilamina server working directory. | String | +| `` | Specific LeviLamina server working directory. | String | + +## ❗ Important + +The `main` configuration entry file in package.json should be relative to the project's working directory, not the directory of the Legacy Script Engine plugin package. + +For example, in a TypeScript project where index.ts is defined as the entry point in source code and the TypeScript compiler is configured via tsconfig.json to emit to the build directory named dist, you should set the `main` field in package.json to `dist/index.js`. + +This ensures that the `entry` field in the manifest.json generated by `npx lses manifest` can be correctly identified and located by LeviLamina. diff --git a/package.json b/package.json index 170aacb..d2bd0b0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "legacy-script-engine-scaffold", - "version": "0.2.0", + "version": "0.2.1", "description": "A utility for assisting in the development of Legacy Script Engine plugins.", "bugs": "https://github.com/leoweyr/LSEScaffold/issues", "bin": { diff --git a/src/nodejs/NodeJsConfigurationMainError.ts b/src/nodejs/NodeJsConfigurationMainError.ts new file mode 100644 index 0000000..f390998 --- /dev/null +++ b/src/nodejs/NodeJsConfigurationMainError.ts @@ -0,0 +1,22 @@ +import { CliLoggableError } from "../cli/CliLoggableError"; + + +export class NodeJsConfigurationMainError extends Error implements CliLoggableError { + private static MESSAGE: string = "The `main` configuration in package.json is incorrect."; + + public constructor() { + super(NodeJsConfigurationMainError.MESSAGE); + } + + public getMessage(): string { + return NodeJsConfigurationMainError.MESSAGE; + } + + public getSuggestion(): Array { + const suggestion: Array = new Array(); + + suggestion.push("Ensure the `main` field is set to be relative to the project's working directory."); + + return suggestion; + } +} diff --git a/src/packager/Manifest.ts b/src/packager/Manifest.ts index dc742b3..57a3b14 100644 --- a/src/packager/Manifest.ts +++ b/src/packager/Manifest.ts @@ -5,6 +5,7 @@ import { Project } from "../project/Project"; import { NodeJsConfiguration } from "../nodejs/NodeJsConfiguration"; import { ManifestFileNotFoundError } from "./ManifestFileNotFoundError"; import { ManifestConfigurationMissingError } from "./ManifestConfigurationMissingError"; +import { NodeJsConfigurationMainError } from "../nodejs/NodeJsConfigurationMainError"; export class Manifest { @@ -37,13 +38,18 @@ export class Manifest { const nodeJsConfiguration: NodeJsConfiguration = this.project.getNodeJsConfiguration(); const name: string = nodeJsConfiguration.getName(); const version: string = nodeJsConfiguration.getVersion(); - const entry: string = nodeJsConfiguration.getMainEntry(); + const absoluteEntry: string = Path.join(this.project.getPath(), nodeJsConfiguration.getMainEntry()); + const relativeEntry: string = absoluteEntry.split(`${this.project.getBuiltPath()}\\`).join(""); + + if (relativeEntry === absoluteEntry) { + throw new NodeJsConfigurationMainError(); + } const manifest = { "name": name, "version": version, "type": "lse-nodejs", - "entry": entry, + "entry": relativeEntry, "dependencies": [{"name": "legacy-script-engine-nodejs"}] }; diff --git a/src/project/TypeScriptProject.ts b/src/project/TypeScriptProject.ts index 8341685..c780179 100644 --- a/src/project/TypeScriptProject.ts +++ b/src/project/TypeScriptProject.ts @@ -64,7 +64,7 @@ export class TypeScriptProject implements Project { } public getBuiltPath(): string { - return this.typeScriptConfiguration.getEmittedDirectory(); + return Path.join(this.typeScriptConfiguration.getEmittedDirectory()); } public getNodeJsConfiguration(): NodeJsConfiguration { From 7f95b9ba5a5625e5bc5131746f050d525b6a0186 Mon Sep 17 00:00:00 2001 From: leoweyr Date: Sun, 15 Jun 2025 21:46:45 +0800 Subject: [PATCH 13/16] fix: enable proper handling of multi-repo projects --- package.json | 2 +- src/cli/index.ts | 6 +++--- src/deployment/LevilaminaServer.ts | 2 +- src/packager/Manifest.ts | 2 +- src/packager/Packager.ts | 2 +- src/packager/PluginPackage.ts | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index d2bd0b0..71ff3fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "legacy-script-engine-scaffold", - "version": "0.2.1", + "version": "0.2.2", "description": "A utility for assisting in the development of Legacy Script Engine plugins.", "bugs": "https://github.com/leoweyr/LSEScaffold/issues", "bin": { diff --git a/src/cli/index.ts b/src/cli/index.ts index bb2ecf5..63e1e2f 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -59,16 +59,16 @@ program try { const project: TypeScriptProject = TypeScriptProject.getInstance(); const packager: Packager = new Packager(project); - const levilaminaServer: LevilaminaServer = new LevilaminaServer(path); + const leviLaminaServer: LevilaminaServer = new LevilaminaServer(path); const pluginPackage: PluginPackage = packager.getPluginPackage(); try { - levilaminaServer.removePlugin(project.getName()); + leviLaminaServer.removePlugin(project.getName()); } catch (error) { // Do nothing if the plugin does not exist. } - const successMessage: string = await levilaminaServer.importPlugin(pluginPackage); + const successMessage: string = await leviLaminaServer.importPlugin(pluginPackage); logger.success(successMessage); } catch (error) { diff --git a/src/deployment/LevilaminaServer.ts b/src/deployment/LevilaminaServer.ts index 3b1acc1..6062671 100644 --- a/src/deployment/LevilaminaServer.ts +++ b/src/deployment/LevilaminaServer.ts @@ -29,7 +29,7 @@ export class LevilaminaServer { } public removePlugin(pluginName: string): void { - const pluginPath: string = Path.join(this.pluginDirectory, pluginName); + const pluginPath: string = Path.join(this.pluginDirectory, pluginName.replace("/", "-").replace("@","")); if (File.existsSync(pluginPath)) { LevilaminaServer.deleteDirectory(pluginPath); diff --git a/src/packager/Manifest.ts b/src/packager/Manifest.ts index 57a3b14..d478c9e 100644 --- a/src/packager/Manifest.ts +++ b/src/packager/Manifest.ts @@ -36,7 +36,7 @@ export class Manifest { public generate(): string { const nodeJsConfiguration: NodeJsConfiguration = this.project.getNodeJsConfiguration(); - const name: string = nodeJsConfiguration.getName(); + const name: string = nodeJsConfiguration.getName().replace("/", "-").replace("@", ""); const version: string = nodeJsConfiguration.getVersion(); const absoluteEntry: string = Path.join(this.project.getPath(), nodeJsConfiguration.getMainEntry()); const relativeEntry: string = absoluteEntry.split(`${this.project.getBuiltPath()}\\`).join(""); diff --git a/src/packager/Packager.ts b/src/packager/Packager.ts index 1e2ac66..5b9916c 100644 --- a/src/packager/Packager.ts +++ b/src/packager/Packager.ts @@ -16,7 +16,7 @@ export class Packager { public constructor(project: Project) { this.project = project; - const packagePath: string = Path.join(this.project.getPath(), `${this.project.getName()}.zip`); + const packagePath: string = Path.join(this.project.getPath(), `${this.project.getName().replace("/", "-").replace("@", "")}.zip`); this.pluginPackage = new PluginPackage(this.project.getName(), packagePath); } diff --git a/src/packager/PluginPackage.ts b/src/packager/PluginPackage.ts index 88d3232..a3ef835 100644 --- a/src/packager/PluginPackage.ts +++ b/src/packager/PluginPackage.ts @@ -29,7 +29,7 @@ export class PluginPackage { } const pluginPackage: Unzipper.CentralDirectory = await Unzipper.Open.file(this.filePath); - const extractedPath: string = Path.join(destinationPath, this.name); + const extractedPath: string = Path.join(destinationPath, this.name.replace("/", "-").replace("@", "")); await pluginPackage.extract({path: extractedPath}); } } From 2c12eb84ac4b4ddc86b86a9d99253c9804bbb387 Mon Sep 17 00:00:00 2001 From: leoweyr Date: Sat, 15 Mar 2025 16:28:35 +0800 Subject: [PATCH 14/16] build: clean with rimraf (cherry picked from commit b912840b7b5cee9266fdf97f7caae99e4db09d0d) --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 71ff3fb..d79869c 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "dist" ], "scripts": { - "clean": "tsc --build --clean", + "clean": "rimraf dist", "compile": "tsc", "build": "npm run clean && npm run compile", "local-deploy": "npm run build && npm link", @@ -29,6 +29,7 @@ "author": "leoweyr ", "license": "MIT", "devDependencies": { + "rimraf": "^6.0.1", "@types/node": "^22.10.7", "@types/archiver": "^6.0.3", "@types/unzipper": "^0.10.10" From a0f4c6230d27a922906b49d2653366c00b2ca303 Mon Sep 17 00:00:00 2001 From: leoweyr Date: Sat, 15 Mar 2025 16:43:20 +0800 Subject: [PATCH 15/16] chore: migrate package to scoped namespace levilamina (cherry picked from commit 25a6adb06e4ae654059cb83c5d999d9552d683a4) --- README.md | 2 +- package.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 85988c3..7b6aa54 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A utility for assisting in the development of Legacy Script Engine plugins, supp 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 +npm install @levilamina/scaffold --save-dev ``` ## 🚀 Usage diff --git a/package.json b/package.json index d79869c..1e09bf2 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "legacy-script-engine-scaffold", + "name": "@levilamina/scaffold", "version": "0.2.2", "description": "A utility for assisting in the development of Legacy Script Engine plugins.", - "bugs": "https://github.com/leoweyr/LSEScaffold/issues", + "bugs": "https://github.com/leoweyr/LegacyScriptEngine_Scaffold/issues", "bin": { "lses": "dist/cli/index.js" }, @@ -15,7 +15,7 @@ "build": "npm run clean && npm run compile", "local-deploy": "npm run build && npm link", "package": "npm run build && npm pack", - "deploy": "npm run package && npm publish" + "deploy": "npm run package && npm publish --access=public" }, "keywords": [ "levilamina", @@ -42,6 +42,6 @@ }, "repository": { "type": "git", - "url": "https://github.com/leoweyr/LSEScaffold" + "url": "https://github.com/leoweyr/LegacyScriptEngine_Scaffold" } } From 8b2146da61baef81b0740f6aa05e181243392ead Mon Sep 17 00:00:00 2001 From: leoweyr Date: Sun, 16 Mar 2025 22:02:54 +0800 Subject: [PATCH 16/16] chore: migrate package to scoped namespace levimc-lse (cherry picked from commit 9ee044e3f253184429bdade4b63fac5555840851) --- README.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7b6aa54..52ef876 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A utility for assisting in the development of Legacy Script Engine plugins, supp 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 @levilamina/scaffold --save-dev +npm install @levimc-lse/scaffold --save-dev ``` ## 🚀 Usage diff --git a/package.json b/package.json index 1e09bf2..459e8fe 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@levilamina/scaffold", + "name": "@levimc-lse/scaffold", "version": "0.2.2", "description": "A utility for assisting in the development of Legacy Script Engine plugins.", "bugs": "https://github.com/leoweyr/LegacyScriptEngine_Scaffold/issues",