Skip to content

Commit 0b4bf2d

Browse files
committed
Add support loading package plugins
1 parent a2cc8b4 commit 0b4bf2d

File tree

11 files changed

+157
-35
lines changed

11 files changed

+157
-35
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
/node_modules
22
/dist/**/*.js
3+
!/dist/install.js
4+
!/dist/logger.js
35
/server

dist/install.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
exports.install = exports.downloadArchive = void 0;
4+
const promises_1 = require("fs/promises");
5+
const download = require("download");
6+
const extract = require("extract-zip");
7+
const path_1 = require("path");
8+
const logger_1 = require("./logger");
9+
function downloadArchive() {
10+
if (process.platform === "win32")
11+
return download("https://github.com/dev2alert/node-samp/releases/download/1.0.0/server-win32.zip");
12+
else
13+
return download("https://github.com/dev2alert/node-samp/releases/download/1.0.0/server-linux.zip");
14+
}
15+
exports.downloadArchive = downloadArchive;
16+
async function install() {
17+
(0, logger_1.log)("Downloading...");
18+
const tmpPath = (0, path_1.join)(__dirname, "../tmp");
19+
const archivePath = (0, path_1.join)(tmpPath, "./server.zip");
20+
const serverPath = (0, path_1.join)(__dirname, "../server");
21+
await (0, promises_1.mkdir)(tmpPath);
22+
const archive = await downloadArchive();
23+
await (0, promises_1.writeFile)(archivePath, archive);
24+
(0, logger_1.log)("Extracting...");
25+
await extract(archivePath, { dir: serverPath });
26+
await (0, promises_1.unlink)(archivePath);
27+
await (0, promises_1.rmdir)(tmpPath);
28+
if (process.platform !== "win32") {
29+
await (0, promises_1.chmod)((0, path_1.join)(serverPath, "./samp03svr"), 0o777);
30+
await (0, promises_1.chmod)((0, path_1.join)(serverPath, "./announce"), 0o777);
31+
}
32+
(0, logger_1.log)("Installed.");
33+
}
34+
exports.install = install;
35+
install();

dist/logger.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
exports.config = exports.error = exports.log = void 0;
4+
const chalk = require("chalk");
5+
function log(content) {
6+
return console.log(chalk.yellow.bold("[NodeSamp]"), content);
7+
}
8+
exports.log = log;
9+
function error(error) {
10+
return console.log(chalk.yellow.bold("[NodeSamp]") + chalk.red.bold("[Error]"), error.message);
11+
}
12+
exports.error = error;
13+
var config;
14+
(function (config) {
15+
function error(error) {
16+
return console.log(chalk.yellow.bold("[Config]") + chalk.red.bold("[Error]"), error.message);
17+
}
18+
config.error = error;
19+
})(config = exports.config || (exports.config = {}));

package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sa-mp/cli",
3-
"version": "1.0.3",
3+
"version": "1.1.0",
44
"description": "Command Line Interface (CLI) for NodeSamp.",
55
"keywords": [
66
"nodesamp",
@@ -19,9 +19,8 @@
1919
"scripts": {
2020
"start": "cross-env NODE_ENV=production node . -h",
2121
"compile": "tsc",
22-
"dev": "cross-env NODE_ENV=development tsc-watch --onSuccess \"node . ../starter\"",
23-
"postinstall": "cross-env NODE_ENV=production node ./dist/install.js",
24-
"install-dev": "cross-env NODE_ENV=development tsc-watch --onSuccess \"node ./dist/install.js\""
22+
"dev": "cross-env NODE_ENV=development tsc-watch --onSuccess \"node . -h\"",
23+
"postinstall": "cross-env NODE_ENV=production node ./dist/install.js"
2524
},
2625
"author": "dev2alert",
2726
"license": "MIT",

src/actions/root/config-params.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import {Type} from "class-transformer";
2-
import {IsArray, IsBoolean, IsInt, IsOptional, IsString, IsUrl, ValidateNested} from "class-validator";
3-
import {PluginParams} from "./plugin-params";
1+
import {IsBoolean, IsInt, IsOptional, IsString, IsUrl} from "class-validator";
2+
import {IsPluginParams, PluginParams} from "./plugin-params";
43

54
export class ConfigParams {
65
@IsOptional()
@@ -36,14 +35,12 @@ export class ConfigParams {
3635
public rconPassword?: string;
3736

3837
@IsOptional()
39-
@IsArray()
40-
@ValidateNested({each: true})
41-
@Type(() => PluginParams)
42-
public plugins?: PluginParams[];
38+
@IsPluginParams()
39+
public plugins: (string | PluginParams)[] = [];
4340

4441
@IsOptional()
4542
@IsString()
46-
public pluginsPath: string = ".";
43+
public pluginsPath: string = "./plugins";
4744

4845
@IsOptional()
4946
@IsString()

src/actions/root/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class Config {
3434
return Config.parsers[info.mimeType] ?? null;
3535
}
3636

37-
private static throwValidationErrors(errors: ValidationError[]): void {
37+
public static throwValidationErrors(errors: ValidationError[]): void {
3838
for(const error of errors) {
3939
if(error.constraints) {
4040
for(const message of Object.values(error.constraints))

src/actions/root/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ export class Root {
3131
} else this.config = await Config.get(this, ConfigPathTypes.PACKAGE, this.getPackageFullPath());
3232
const params: ConfigParams = this.config.getParams();
3333
await removePlugins(this);
34-
if(params.plugins)
35-
this.plugins = await createPlugins(this, params.plugins);
36-
else this.plugins = [];
34+
this.plugins = await createPlugins(this, params.plugins);
3735
await this.config.createServerConfig();
3836
await this.createPluginConfig();
3937
const serverPath: string = this.getServerPath();

src/actions/root/package-params.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {Type} from "class-transformer";
2+
import {IsOptional, IsString, ValidateNested} from "class-validator";
3+
import {IsPluginParams, PluginParams} from "./plugin-params";
4+
5+
export class PackageConfigSampParams {
6+
@IsOptional()
7+
@IsPluginParams()
8+
public plugins: (string | PluginParams)[] = [];
9+
10+
@IsOptional()
11+
@IsString()
12+
public pluginsPath: string = "./plugins";
13+
}
14+
15+
export class PackageConfigParams {
16+
@IsOptional()
17+
@ValidateNested()
18+
@Type(() => PackageConfigSampParams)
19+
public samp: PackageConfigSampParams = new PackageConfigSampParams;
20+
}
21+
22+
export class PackageParams {
23+
@IsOptional()
24+
@ValidateNested()
25+
@Type(() => PackageConfigParams)
26+
public config: PackageConfigParams = new PackageConfigParams;
27+
}

src/actions/root/plugin-params.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1-
import {IsString} from "class-validator";
1+
import {Validate, ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface} from "class-validator";
22

3-
export class PluginParams {
4-
@IsString()
5-
public platform: string;
3+
export interface PluginParams {
4+
platform: string;
5+
path: string;
6+
}
67

7-
@IsString()
8-
public path: string;
8+
@ValidatorConstraint({name: "isPluginParams", async: false})
9+
export class PluginParamsValidator implements ValidatorConstraintInterface {
10+
public validate(value: unknown): boolean {
11+
return value instanceof Array && value.every((value) => typeof value === "string" || (typeof value === "object" && typeof value.platform === "string" && typeof value.path === "string"));
12+
}
13+
14+
public defaultMessage({property}: ValidationArguments): string {
15+
return `each value in ${property} must be an {platform: string, path: string} or string`;
16+
}
17+
}
18+
19+
export function IsPluginParams(): PropertyDecorator {
20+
return Validate(PluginParamsValidator);
921
}

src/actions/root/plugin.ts

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
1+
import {plainToClass} from "class-transformer";
2+
import {validate} from "class-validator";
13
import * as fs from "fs";
2-
import {access, unlink, link, readdir} from "fs/promises";
4+
import {access, unlink, link, readdir, readFile} from "fs/promises";
35
import {join, parse, basename} from "path";
46
import {Root} from ".";
57
import {Config} from "./config";
68
import {ConfigParams} from "./config-params";
9+
import {PackageParams} from "./package-params";
710
import {PluginParams} from "./plugin-params";
811

912
export class Plugin {
10-
public static async create(root: Root, params: PluginParams): Promise<Plugin> {
11-
const plugin = new Plugin(root, params);
13+
public static async create(root: Root, pluginsPath: string, params: PluginParams): Promise<Plugin> {
14+
const plugin = new Plugin(root, pluginsPath, params);
1215
await plugin.create();
1316
return plugin;
1417
}
1518

16-
constructor(public readonly root: Root, public readonly params: PluginParams) {}
19+
constructor(public readonly root: Root, public readonly pluginsPath: string, public readonly params: PluginParams) {}
1720

1821
public name?: string;
1922
public path?: string;
@@ -26,11 +29,7 @@ export class Plugin {
2629
public async create(): Promise<void> {
2730
if(!this.isSupported())
2831
return;
29-
const config: Config | null = this.root.getConfig();
30-
if(config === null)
31-
return;
32-
const configParams: ConfigParams = config.getParams();
33-
this.path = join(this.root.getPackageFullPath(), configParams.pluginsPath, this.params.path);
32+
this.path = join(this.pluginsPath, this.params.path);
3433
this.name = parse(this.getPath()).name;
3534
try {
3635
await access(this.getPath(), fs.constants.F_OK);
@@ -64,9 +63,42 @@ export async function removePlugins(root: Root): Promise<void> {
6463
}
6564
}
6665

67-
export async function createPlugins(root: Root, paramsList: PluginParams[]): Promise<Plugin[]> {
66+
export async function createPlugins(root: Root, plugins: (string | PluginParams)[]): Promise<Plugin[]> {
67+
const result: Plugin[] = [];
68+
for(const params of plugins) {
69+
if(typeof params === "string")
70+
result.push(...await createPackagePlugins(root, params));
71+
else {
72+
const config: Config | null = root.getConfig();
73+
if(config === null)
74+
break;
75+
const configParams: ConfigParams = config.getParams();
76+
const pluginsPath: string = join(root.getPackageFullPath(), configParams.pluginsPath);
77+
result.push(await Plugin.create(root, pluginsPath, params));
78+
}
79+
}
80+
return result;
81+
}
82+
83+
export async function createPackagePlugins(root: Root, packageName: string): Promise<Plugin[]> {
84+
const packagePath: string = join(root.getPackageFullPath(), "./node_modules", packageName);
85+
let packageContent: unknown;
86+
try {
87+
packageContent = JSON.parse(String(await readFile(join(packagePath, "./package.json"))));
88+
} catch(error) {
89+
throw new Error(`Package ${JSON.stringify(packageName)} not found.`);
90+
}
91+
const packageParams: PackageParams = plainToClass(PackageParams, packageContent);
92+
Config.throwValidationErrors(await validate(packageParams));
93+
const plugins: (string | PluginParams)[] = packageParams.config.samp.plugins;
6894
const result: Plugin[] = [];
69-
for(const params of paramsList)
70-
result.push(await Plugin.create(root, params));
95+
for(const params of plugins) {
96+
if(typeof params === "string")
97+
result.push(...await createPackagePlugins(root, params));
98+
else {
99+
const pluginsPath: string = join(packagePath, packageParams.config.samp.pluginsPath);
100+
result.push(await Plugin.create(root, pluginsPath, params));
101+
}
102+
}
71103
return result;
72104
}

0 commit comments

Comments
 (0)