@serenityjs/plugins 0.2.4 → 0.3.0-beta-20240604012528

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +27 -69
  2. package/dist/index.js +143 -233
  3. package/package.json +6 -6
package/dist/index.d.ts CHANGED
@@ -1,59 +1,30 @@
1
1
  import { Logger } from '@serenityjs/logger';
2
2
  import Emitter from '@serenityjs/emitter';
3
3
 
4
- /**
5
- * Represents a base plugin that can be loaded into the Serenity server.
6
- */
7
- declare abstract class BasePlugin<T = unknown> {
8
- /**
9
- * The serenity instance.
10
- */
11
- readonly serenity: T;
12
- /**
13
- * The logger instance.
14
- */
15
- readonly logger: Logger;
16
- /**
17
- * Constructs a new base plugin instance.
18
- *
19
- * @param serenity - The serenity instance.
20
- * @param logger - The logger instance.
21
- */
22
- constructor(serenity: T, logger: Logger);
23
- /**
24
- * Called when the plugin is started.
25
- */
26
- startup(): void;
27
- /**
28
- * Called when the plugin is stopped.
29
- */
30
- shutdown(): void;
31
- }
32
-
33
- interface PluginPackage {
34
- name: string;
35
- version: string;
36
- main: string;
37
- development: boolean;
38
- scripts: {
39
- build: string;
40
- };
41
- }
42
4
  interface Plugin {
43
- instance: BasePlugin;
44
- typescript: boolean;
45
- package: PluginPackage;
46
- path: string;
47
- plugin: unknown;
5
+ logger: Logger;
6
+ config: PluginConfig;
7
+ module: PluginModule;
48
8
  }
49
9
  interface PluginEvents {
50
10
  register: [Plugin];
11
+ ready: [];
51
12
  }
52
- declare class Plugins<T> extends Emitter<PluginEvents> {
53
- /**
54
- * The serenity instance.
55
- */
56
- protected readonly serenity: T;
13
+ interface PluginModule {
14
+ onRegister?(plugin: Plugin): void;
15
+ onStartup?(...arguments_: Array<unknown>): void;
16
+ onShutdown?(...arguments_: Array<unknown>): void;
17
+ }
18
+ interface PluginConfig {
19
+ name: string;
20
+ version: string;
21
+ mode: "development" | "production";
22
+ color: string;
23
+ entry: string;
24
+ node?: boolean;
25
+ typescript?: boolean;
26
+ }
27
+ declare class Plugins extends Emitter<PluginEvents> {
57
28
  /**
58
29
  * The logger instance.
59
30
  */
@@ -67,30 +38,17 @@ declare class Plugins<T> extends Emitter<PluginEvents> {
67
38
  */
68
39
  readonly entries: Map<string, Plugin>;
69
40
  /**
70
- * Constructs a new plugins instance.
71
- *
72
- * @param serenity - The serenity instance.
41
+ * Whether the plugins are ready.
73
42
  */
74
- constructor(serenity: T, path: string, enabled?: boolean);
43
+ ready: boolean;
75
44
  /**
76
- * Get a plugin from the plugins map.
45
+ * Constructs a new plugins instance.
77
46
  *
78
- * @param name - The name of the plugin.
79
- * @param version - The version of the plugin.
80
- * @returns The plugin instance.
81
- */
82
- get<T = BasePlugin>(name: string, version?: string): T;
83
- /**
84
- * Get all the plugins from the plugins map.
85
- */
86
- getAll(): Array<BasePlugin>;
87
- reload(name: string): void;
88
- /**
89
- * Start loading the plugins.
47
+ * @param serenity - The serenity instance.
90
48
  */
91
- private start;
92
- private validatePackage;
93
- private validateTsconfig;
49
+ constructor(path: string, enabled?: boolean);
50
+ private load;
51
+ import<T = PluginModule>(name: string): T;
94
52
  }
95
53
 
96
- export { BasePlugin, Plugins };
54
+ export { type Plugin, Plugins };
package/dist/index.js CHANGED
@@ -30,44 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
- BasePlugin: () => BasePlugin,
34
33
  Plugins: () => Plugins
35
34
  });
36
35
  module.exports = __toCommonJS(src_exports);
37
36
 
38
- // src/base.ts
39
- var BasePlugin = class {
40
- /**
41
- * The serenity instance.
42
- */
43
- serenity;
44
- /**
45
- * The logger instance.
46
- */
47
- logger;
48
- /**
49
- * Constructs a new base plugin instance.
50
- *
51
- * @param serenity - The serenity instance.
52
- * @param logger - The logger instance.
53
- */
54
- constructor(serenity, logger) {
55
- this.serenity = serenity;
56
- this.logger = logger;
57
- this.startup();
58
- }
59
- /**
60
- * Called when the plugin is started.
61
- */
62
- startup() {
63
- }
64
- /**
65
- * Called when the plugin is stopped.
66
- */
67
- shutdown() {
68
- }
69
- };
70
-
71
37
  // src/plugins.ts
72
38
  var import_node_child_process = require("child_process");
73
39
  var import_node_fs = require("fs");
@@ -75,11 +41,40 @@ var import_node_path = require("path");
75
41
  var import_node_process = __toESM(require("process"));
76
42
  var import_logger = require("@serenityjs/logger");
77
43
  var import_emitter = __toESM(require("@serenityjs/emitter"));
44
+
45
+ // src/templates/plugin-json.ts
46
+ var PLUGIN_TEMPLATE = (
47
+ /* json */
48
+ `{
49
+ "name": "plugin-name",
50
+ "version": "1.0.0",
51
+ "mode": "development",
52
+ "color": "blue",
53
+ "entry": "index.js",
54
+ "node": false,
55
+ "typescript": false
56
+ }
57
+ `
58
+ );
59
+
60
+ // src/templates/tsconfig-json.ts
61
+ var TSCONFIG_TEMPLATE = (
62
+ /* json */
63
+ `{
64
+ "extends": "@serenityjs/typescript-config/base.json",
65
+ "compilerOptions": {
66
+ "lib": ["es2022"],
67
+ "outDir": "./dist",
68
+ "types": ["node"]
69
+ },
70
+ "include": ["."],
71
+ "exclude": ["node_modules", "dist"],
72
+ }
73
+ `
74
+ );
75
+
76
+ // src/plugins.ts
78
77
  var Plugins = class extends import_emitter.default {
79
- /**
80
- * The serenity instance.
81
- */
82
- serenity;
83
78
  /**
84
79
  * The logger instance.
85
80
  */
@@ -92,230 +87,145 @@ var Plugins = class extends import_emitter.default {
92
87
  * A collection registry of all plugins.
93
88
  */
94
89
  entries;
90
+ /**
91
+ * Whether the plugins are ready.
92
+ */
93
+ ready = false;
95
94
  /**
96
95
  * Constructs a new plugins instance.
97
96
  *
98
97
  * @param serenity - The serenity instance.
99
98
  */
100
- constructor(serenity, path, enabled = true) {
99
+ constructor(path, enabled = true) {
101
100
  super();
102
- this.serenity = serenity;
103
101
  this.logger = new import_logger.Logger("Plugins", import_logger.LoggerColors.CyanBright);
104
102
  this.path = (0, import_node_path.resolve)(import_node_process.default.cwd(), path);
105
103
  this.entries = /* @__PURE__ */ new Map();
106
104
  if (enabled) {
107
105
  if (!(0, import_node_fs.existsSync)(this.path)) {
108
- (0, import_node_fs.mkdirSync)(this.path);
109
- this.logger.success(`Created plugins folder at "${this.path}"`);
110
- }
111
- this.logger.info(`Attempting to load plugins from "${this.path}"`);
112
- void this.start();
113
- }
114
- }
115
- /**
116
- * Get a plugin from the plugins map.
117
- *
118
- * @param name - The name of the plugin.
119
- * @param version - The version of the plugin.
120
- * @returns The plugin instance.
121
- */
122
- get(name, version) {
123
- const filtered = [...this.entries.values()].filter((plugin2) => {
124
- return plugin2.package.name === name;
125
- });
126
- const plugin = filtered.find((plugin2) => {
127
- return version ? plugin2.package.version === version : filtered[0];
128
- });
129
- if (!plugin) {
130
- throw new Error(
131
- `Plugin "${name}" with version "${version}" does not exist.`
132
- );
133
- }
134
- return plugin.instance;
135
- }
136
- /**
137
- * Get all the plugins from the plugins map.
138
- */
139
- getAll() {
140
- const plugins = [...this.entries.values()].map((plugin) => {
141
- return plugin.instance;
142
- });
143
- return plugins;
144
- }
145
- reload(name) {
146
- const plugin = this.entries.get(name);
147
- if (!plugin) {
148
- throw new Error(`Plugin "${name}" does not exist.`);
149
- }
150
- plugin.instance.shutdown();
151
- if (plugin.typescript) {
152
- try {
153
- if (plugin.package.scripts.build) {
154
- (0, import_node_child_process.execSync)("npm run build", { cwd: plugin.path });
155
- } else {
156
- (0, import_node_child_process.execSync)("tsc", { cwd: plugin.path });
106
+ try {
107
+ (0, import_node_fs.mkdirSync)(this.path);
108
+ } catch (reason) {
109
+ this.logger.error(`Failed to create plugins directory!`, reason);
157
110
  }
158
- } catch (reason) {
159
- this.logger.error(
160
- `Failed to compile plugin "${name}" before reloading.`,
161
- reason
162
- );
163
- return;
164
111
  }
165
- }
166
- const path = require.resolve(plugin.path);
167
- if (path) {
168
- delete require.cache[path];
169
- }
170
- const pluginMain = require(`${(0, import_node_path.resolve)(plugin.path, plugin.package.main)}`);
171
- if (!pluginMain.default) {
172
- throw new Error(`Plugin "${name}" does not have a default export.`);
173
- }
174
- const pluginInstance = new pluginMain.default(
175
- this.serenity,
176
- new import_logger.Logger(`${name}@${plugin.package.version}`, import_logger.LoggerColors.Blue)
177
- );
178
- plugin.instance = pluginInstance;
179
- this.emit("register", plugin);
180
- this.logger.success(`Reloaded plugin "${name}".`);
181
- }
182
- /**
183
- * Start loading the plugins.
184
- */
185
- async start() {
186
- const files = (0, import_node_fs.readdirSync)(this.path, { withFileTypes: true });
187
- for (const file of files) {
188
- if (!file.isDirectory())
189
- continue;
190
- const files2 = (0, import_node_fs.readdirSync)((0, import_node_path.resolve)(file.path, file.name), {
112
+ const directories = (0, import_node_fs.readdirSync)(this.path, {
191
113
  withFileTypes: true
192
- });
193
- const packExsist = files2.find((file2) => file2.name === "package.json");
194
- if (!packExsist) {
195
- this.logger.error(
196
- `Plugin "${file.name}" does not contain a package.json file.`
197
- );
198
- continue;
199
- }
200
- const pack = JSON.parse(
201
- (0, import_node_fs.readFileSync)((0, import_node_path.resolve)(packExsist.path, packExsist.name), "utf8")
202
- );
203
- if (!this.validatePackage(file.name, pack))
204
- continue;
205
- const needModules = (0, import_node_fs.existsSync)(
206
- (0, import_node_path.resolve)(file.path, file.name, "node_modules")
207
- );
208
- if (!needModules) {
209
- try {
210
- (0, import_node_child_process.execSync)("npm install", { cwd: import_node_process.default.cwd() });
211
- } catch (reason) {
212
- this.logger.error(
213
- `Failed to install node_modules for plugin "${file.name}".`,
214
- reason
215
- );
216
- continue;
114
+ }).filter((dirent) => dirent.isDirectory());
115
+ for (const directory of directories) {
116
+ const path2 = (0, import_node_path.resolve)(this.path, directory.name);
117
+ if (!(0, import_node_fs.existsSync)((0, import_node_path.resolve)(path2, "plugin.json"))) {
118
+ try {
119
+ const config2 = PLUGIN_TEMPLATE.replace(
120
+ "plugin-name",
121
+ directory.name
122
+ );
123
+ (0, import_node_fs.writeFileSync)((0, import_node_path.resolve)(path2, "plugin.json"), config2);
124
+ } catch {
125
+ this.logger.error(
126
+ `Failed to create plugin.json for ${directory.name}`
127
+ );
128
+ }
217
129
  }
218
- this.logger.success(
219
- `Installed node_modules for plugin "${file.name}".`
130
+ const config = JSON.parse(
131
+ (0, import_node_fs.readFileSync)((0, import_node_path.resolve)(path2, "plugin.json"), "utf8")
220
132
  );
221
- }
222
- const typescript = files2.find((file2) => file2.name === "tsconfig.json");
223
- const tsconfig = typescript ? JSON.parse(
224
- (0, import_node_fs.readFileSync)((0, import_node_path.resolve)(typescript.path, typescript.name), "utf8")
225
- ) : null;
226
- if (typescript && !this.validateTsconfig(file.name, tsconfig))
227
- continue;
228
- const needToCompile = Boolean(
229
- typescript && !(0, import_node_fs.existsSync)(
230
- (0, import_node_path.resolve)(file.path, file.name, tsconfig.compilerOptions.outDir)
231
- ) || Boolean(pack?.development ?? false) === true
232
- );
233
- if (needToCompile) {
234
- try {
235
- if (pack?.scripts?.build) {
236
- (0, import_node_child_process.execSync)("npm run build", { cwd: (0, import_node_path.resolve)(file.path, file.name) });
237
- } else {
238
- (0, import_node_child_process.execSync)("tsc", { cwd: (0, import_node_path.resolve)(file.path, file.name) });
133
+ if (config.node === true) {
134
+ if (!(0, import_node_fs.existsSync)((0, import_node_path.resolve)(path2, "package.json"))) {
135
+ try {
136
+ (0, import_node_child_process.execSync)("npm init -y", { stdio: "ignore", cwd: path2 });
137
+ } catch (reason) {
138
+ this.logger.error(
139
+ `Failed to create package.json for ${config.name}@${config.version}!`,
140
+ reason
141
+ );
142
+ }
143
+ }
144
+ if (!(0, import_node_fs.existsSync)((0, import_node_path.resolve)(path2, "package-lock.json"))) {
145
+ try {
146
+ this.logger.info(
147
+ `Installing dependencies for ${config.name}@${config.version}...`
148
+ );
149
+ (0, import_node_child_process.execSync)("npm install", { stdio: "ignore", cwd: path2 });
150
+ } catch (reason) {
151
+ this.logger.error(
152
+ `Failed to install dependencies for ${config.name}@${config.version}!`,
153
+ reason
154
+ );
155
+ }
156
+ }
157
+ }
158
+ if (config.typescript === true) {
159
+ if (!(0, import_node_fs.existsSync)((0, import_node_path.resolve)(path2, "tsconfig.json"))) {
160
+ try {
161
+ (0, import_node_fs.writeFileSync)((0, import_node_path.resolve)(path2, "tsconfig.json"), TSCONFIG_TEMPLATE);
162
+ (0, import_node_child_process.execSync)("npm pkg set scripts.build=tsc", {
163
+ stdio: "ignore",
164
+ cwd: path2
165
+ });
166
+ } catch (reason) {
167
+ this.logger.error(
168
+ `Failed to create tsconfig.json for ${config.name}@${config.version}`,
169
+ reason
170
+ );
171
+ }
172
+ }
173
+ if (!(0, import_node_fs.existsSync)((0, import_node_path.resolve)(path2, "dist")) || config.mode === "development") {
174
+ try {
175
+ this.logger.info(
176
+ `Building typescript files for ${config.name}@${config.version}...`
177
+ );
178
+ (0, import_node_child_process.execSync)("npm run build", { stdio: "ignore", cwd: path2 });
179
+ } catch (reason) {
180
+ this.logger.error(
181
+ `Failed to build typescript files for ${config.name}@${config.version}!`,
182
+ reason
183
+ );
184
+ }
239
185
  }
240
- this.logger.success(`Compiled plugin "${file.name}".`);
241
- } catch (reason) {
242
- this.logger.error(`Failed to compile plugin "${file.name}".`, reason);
243
- continue;
244
186
  }
245
187
  }
188
+ void this.load(directories);
189
+ }
190
+ }
191
+ async load(directories) {
192
+ for await (const directory of directories) {
246
193
  try {
247
- const plugin = require(`${(0, import_node_path.resolve)(file.path, file.name, pack.main)}`);
248
- if (!plugin.default)
249
- continue;
250
- const instance = new plugin.default(
251
- this.serenity,
252
- new import_logger.Logger(`${file.name}@${pack.version}`, import_logger.LoggerColors.Blue)
194
+ const path = (0, import_node_path.resolve)(this.path, directory.name);
195
+ const config = JSON.parse(
196
+ (0, import_node_fs.readFileSync)((0, import_node_path.resolve)(path, "plugin.json"), "utf8")
253
197
  );
254
- const pluginEntry = {
255
- instance,
256
- typescript: Boolean(typescript),
257
- package: pack,
258
- path: (0, import_node_path.resolve)(file.path, file.name),
259
- plugin
260
- };
261
- this.entries.set(file.name, pluginEntry);
262
- this.emit("register", pluginEntry);
198
+ const instance = await import(`file://${(0, import_node_path.resolve)(path, config.entry)}`);
199
+ const module2 = instance["default"];
200
+ const adjustedColor = config.color.split("_").map((part) => {
201
+ return part.charAt(0).toUpperCase() + part.slice(1);
202
+ }).join("");
203
+ const color = import_logger.LoggerColors[adjustedColor] ?? import_logger.LoggerColors.White;
204
+ const logger = new import_logger.Logger(`${config.name}@${config.version}`, color);
205
+ const plugin = { config, module: module2, logger };
206
+ this.entries.set(config.name, plugin);
207
+ logger.success(`Successfully loaded at ${path}`);
208
+ try {
209
+ module2.onRegister?.(plugin);
210
+ } catch {
211
+ }
212
+ this.emit("register", plugin);
263
213
  } catch (reason) {
264
- this.logger.error(`Failed to import plugin "${file.name}".`, reason);
265
- continue;
214
+ this.logger.error(`Failed to load plugin! ${directory.name}`, reason);
266
215
  }
267
216
  }
217
+ this.ready = true;
218
+ this.emit("ready");
268
219
  }
269
- validatePackage(path, pack) {
270
- if (!pack?.name) {
271
- this.logger.error(
272
- `Plugin package.json "${path}" does not contain a "name" property.`
273
- );
274
- return false;
275
- }
276
- if (!pack?.version) {
277
- this.logger.error(
278
- `Plugin package.json "${path}" does not contain a "version" property.`
279
- );
280
- return false;
281
- }
282
- if (!pack?.type) {
283
- this.logger.error(
284
- `Plugin package.json "${path}" does not contain a "type" property.`
285
- );
286
- return false;
287
- } else if (pack.type !== "commonjs") {
288
- this.logger.error(
289
- `Plugin package.json "${path}" type is not set to "commonjs".`
290
- );
291
- return false;
292
- }
293
- if (!pack?.main) {
294
- this.logger.error(
295
- `Plugin package.json "${path}" does not contain a "main" property.`
296
- );
297
- return false;
298
- }
299
- return true;
300
- }
301
- validateTsconfig(path, tsconfig) {
302
- if (!tsconfig?.compilerOptions) {
303
- this.logger.error(
304
- `Plugin tsconfig.json "${path}" does not contain a "compilerOptions" property.`
305
- );
306
- return false;
307
- }
308
- if (!tsconfig?.compilerOptions?.outDir) {
309
- this.logger.error(
310
- `Plugin tsconfig.json "${path}" does not contain a "outDir" property.`
311
- );
312
- return false;
220
+ import(name) {
221
+ const plugin = this.entries.get(name);
222
+ if (!plugin) {
223
+ throw new Error(`Plugin ${name} not found!`);
313
224
  }
314
- return true;
225
+ return plugin.module;
315
226
  }
316
227
  };
317
228
  // Annotate the CommonJS export names for ESM import in node:
318
229
  0 && (module.exports = {
319
- BasePlugin,
320
230
  Plugins
321
231
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serenityjs/plugins",
3
- "version": "0.2.4",
3
+ "version": "0.3.0-beta-20240604012528",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "repository": "https://github.com/SerenityJS/serenity",
@@ -18,9 +18,9 @@
18
18
  "preset": "@serenityjs/jest-presets/jest/node"
19
19
  },
20
20
  "devDependencies": {
21
- "@serenityjs/eslint-config": "*",
22
- "@serenityjs/jest-presets": "*",
23
- "@serenityjs/typescript-config": "*",
21
+ "@serenityjs/eslint-config": "0.3.0-beta-20240604012528",
22
+ "@serenityjs/jest-presets": "0.3.0-beta-20240604012528",
23
+ "@serenityjs/typescript-config": "0.3.0-beta-20240604012528",
24
24
  "@types/jest": "^29.5.12",
25
25
  "@types/node": "^20.11.24",
26
26
  "jest": "^29.7.0",
@@ -28,7 +28,7 @@
28
28
  "typescript": "^5.4.2"
29
29
  },
30
30
  "dependencies": {
31
- "@serenityjs/emitter": "*",
32
- "@serenityjs/logger": "*"
31
+ "@serenityjs/emitter": "0.3.0-beta-20240604012528",
32
+ "@serenityjs/logger": "0.3.0-beta-20240604012528"
33
33
  }
34
34
  }