@momentumcms/plugins-core 0.1.0 → 0.1.2

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/CHANGELOG.md +16 -0
  2. package/index.js +499 -0
  3. package/package.json +31 -31
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## 0.1.2 (2026-02-16)
2
+
3
+ ### 🩹 Fixes
4
+
5
+ - **release:** centralize manifestRootsToUpdate to update both source and dist ([2b8f832](https://github.com/DonaldMurillo/momentum-cms/commit/2b8f832))
6
+ - **create-app:** fix Angular SSR, Analog builds, and CJS/ESM compatibility ([28d4d0a](https://github.com/DonaldMurillo/momentum-cms/commit/28d4d0a))
7
+
8
+ ### ❤️ Thank You
9
+
10
+ - Claude Opus 4.6
11
+ - Donald Murillo @DonaldMurillo
12
+
13
+ ## 0.1.1 (2026-02-16)
14
+
15
+ This was a version bump only for plugins-core to align it with other projects, there were no code changes.
16
+
1
17
  ## 0.1.0 (2026-02-16)
2
18
 
3
19
  This was a version bump only for plugins-core to align it with other projects, there were no code changes.
package/index.js ADDED
@@ -0,0 +1,499 @@
1
+ // libs/logger/src/lib/log-level.ts
2
+ var LOG_LEVEL_VALUES = {
3
+ debug: 0,
4
+ info: 1,
5
+ warn: 2,
6
+ error: 3,
7
+ fatal: 4,
8
+ silent: 5
9
+ };
10
+ function shouldLog(messageLevel, configuredLevel) {
11
+ return LOG_LEVEL_VALUES[messageLevel] >= LOG_LEVEL_VALUES[configuredLevel];
12
+ }
13
+
14
+ // libs/logger/src/lib/ansi-colors.ts
15
+ var ANSI = {
16
+ reset: "\x1B[0m",
17
+ bold: "\x1B[1m",
18
+ dim: "\x1B[2m",
19
+ // Foreground colors
20
+ red: "\x1B[31m",
21
+ green: "\x1B[32m",
22
+ yellow: "\x1B[33m",
23
+ blue: "\x1B[34m",
24
+ magenta: "\x1B[35m",
25
+ cyan: "\x1B[36m",
26
+ white: "\x1B[37m",
27
+ gray: "\x1B[90m",
28
+ // Background colors
29
+ bgRed: "\x1B[41m",
30
+ bgYellow: "\x1B[43m"
31
+ };
32
+ function colorize(text, ...codes) {
33
+ if (codes.length === 0)
34
+ return text;
35
+ return `${codes.join("")}${text}${ANSI.reset}`;
36
+ }
37
+ function supportsColor() {
38
+ if (process.env["FORCE_COLOR"] === "1")
39
+ return true;
40
+ if (process.env["NO_COLOR"] !== void 0)
41
+ return false;
42
+ if (process.env["TERM"] === "dumb")
43
+ return false;
44
+ return process.stdout.isTTY === true;
45
+ }
46
+
47
+ // libs/logger/src/lib/formatters.ts
48
+ var LEVEL_COLORS = {
49
+ debug: [ANSI.dim, ANSI.gray],
50
+ info: [ANSI.cyan],
51
+ warn: [ANSI.yellow],
52
+ error: [ANSI.red],
53
+ fatal: [ANSI.bold, ANSI.white, ANSI.bgRed]
54
+ };
55
+ function padLevel(level) {
56
+ return level.toUpperCase().padEnd(5);
57
+ }
58
+ function formatTimestamp(date) {
59
+ const y = date.getFullYear();
60
+ const mo = String(date.getMonth() + 1).padStart(2, "0");
61
+ const d = String(date.getDate()).padStart(2, "0");
62
+ const h = String(date.getHours()).padStart(2, "0");
63
+ const mi = String(date.getMinutes()).padStart(2, "0");
64
+ const s = String(date.getSeconds()).padStart(2, "0");
65
+ const ms = String(date.getMilliseconds()).padStart(3, "0");
66
+ return `${y}-${mo}-${d} ${h}:${mi}:${s}.${ms}`;
67
+ }
68
+ function formatData(data) {
69
+ const entries = Object.entries(data);
70
+ if (entries.length === 0)
71
+ return "";
72
+ return " " + entries.map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`).join(" ");
73
+ }
74
+ function prettyFormatter(entry) {
75
+ const useColor = supportsColor();
76
+ const level = entry.level;
77
+ const ts = formatTimestamp(entry.timestamp);
78
+ const levelStr = padLevel(entry.level);
79
+ const ctx = `[${entry.context}]`;
80
+ const msg = entry.message;
81
+ const enrichmentStr = entry.enrichments ? formatData(entry.enrichments) : "";
82
+ const dataStr = entry.data ? formatData(entry.data) : "";
83
+ const extra = `${enrichmentStr}${dataStr}`;
84
+ if (useColor) {
85
+ const colors = LEVEL_COLORS[level];
86
+ const coloredLevel = colorize(levelStr, ...colors);
87
+ const coloredCtx = colorize(ctx, ANSI.magenta);
88
+ const coloredTs = colorize(ts, ANSI.gray);
89
+ return `${coloredTs} ${coloredLevel} ${coloredCtx} ${msg}${extra}
90
+ `;
91
+ }
92
+ return `${ts} ${levelStr} ${ctx} ${msg}${extra}
93
+ `;
94
+ }
95
+ function jsonFormatter(entry) {
96
+ const output = {
97
+ timestamp: entry.timestamp.toISOString(),
98
+ level: entry.level,
99
+ context: entry.context,
100
+ message: entry.message
101
+ };
102
+ if (entry.enrichments && Object.keys(entry.enrichments).length > 0) {
103
+ Object.assign(output, entry.enrichments);
104
+ }
105
+ if (entry.data && Object.keys(entry.data).length > 0) {
106
+ output["data"] = entry.data;
107
+ }
108
+ return JSON.stringify(output) + "\n";
109
+ }
110
+
111
+ // libs/logger/src/lib/logger-config.types.ts
112
+ function resolveLoggingConfig(config) {
113
+ return {
114
+ level: config?.level ?? "info",
115
+ format: config?.format ?? "pretty",
116
+ timestamps: config?.timestamps ?? true,
117
+ output: config?.output ?? ((msg) => {
118
+ process.stdout.write(msg);
119
+ }),
120
+ errorOutput: config?.errorOutput ?? ((msg) => {
121
+ process.stderr.write(msg);
122
+ })
123
+ };
124
+ }
125
+
126
+ // libs/logger/src/lib/logger.ts
127
+ var ERROR_LEVELS = /* @__PURE__ */ new Set(["warn", "error", "fatal"]);
128
+ var MomentumLogger = class _MomentumLogger {
129
+ static {
130
+ this.enrichers = [];
131
+ }
132
+ constructor(context, config) {
133
+ this.context = context;
134
+ this.config = isResolvedConfig(config) ? config : resolveLoggingConfig(config);
135
+ this.formatter = this.config.format === "json" ? jsonFormatter : prettyFormatter;
136
+ }
137
+ debug(message, data) {
138
+ this.log("debug", message, data);
139
+ }
140
+ info(message, data) {
141
+ this.log("info", message, data);
142
+ }
143
+ warn(message, data) {
144
+ this.log("warn", message, data);
145
+ }
146
+ error(message, data) {
147
+ this.log("error", message, data);
148
+ }
149
+ fatal(message, data) {
150
+ this.log("fatal", message, data);
151
+ }
152
+ /**
153
+ * Creates a child logger with a sub-context.
154
+ * e.g., `Momentum:DB` → `Momentum:DB:Migrate`
155
+ */
156
+ child(subContext) {
157
+ return new _MomentumLogger(`${this.context}:${subContext}`, this.config);
158
+ }
159
+ /**
160
+ * Registers a global enricher that adds extra fields to all log entries.
161
+ */
162
+ static registerEnricher(enricher) {
163
+ _MomentumLogger.enrichers.push(enricher);
164
+ }
165
+ /**
166
+ * Removes a previously registered enricher.
167
+ */
168
+ static removeEnricher(enricher) {
169
+ const index = _MomentumLogger.enrichers.indexOf(enricher);
170
+ if (index >= 0) {
171
+ _MomentumLogger.enrichers.splice(index, 1);
172
+ }
173
+ }
174
+ /**
175
+ * Clears all registered enrichers. Primarily for testing.
176
+ */
177
+ static clearEnrichers() {
178
+ _MomentumLogger.enrichers.length = 0;
179
+ }
180
+ log(level, message, data) {
181
+ if (!shouldLog(level, this.config.level))
182
+ return;
183
+ const enrichments = this.collectEnrichments();
184
+ const entry = {
185
+ timestamp: /* @__PURE__ */ new Date(),
186
+ level,
187
+ context: this.context,
188
+ message,
189
+ data,
190
+ enrichments: Object.keys(enrichments).length > 0 ? enrichments : void 0
191
+ };
192
+ const formatted = this.formatter(entry);
193
+ if (ERROR_LEVELS.has(level)) {
194
+ this.config.errorOutput(formatted);
195
+ } else {
196
+ this.config.output(formatted);
197
+ }
198
+ }
199
+ collectEnrichments() {
200
+ const result = {};
201
+ for (const enricher of _MomentumLogger.enrichers) {
202
+ Object.assign(result, enricher.enrich());
203
+ }
204
+ return result;
205
+ }
206
+ };
207
+ function isResolvedConfig(config) {
208
+ if (!config)
209
+ return false;
210
+ return typeof config.level === "string" && typeof config.format === "string" && typeof config.timestamps === "boolean" && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- type guard narrows union
211
+ typeof config.output === "function" && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- type guard narrows union
212
+ typeof config.errorOutput === "function";
213
+ }
214
+
215
+ // libs/logger/src/lib/logger-singleton.ts
216
+ var loggerInstance = null;
217
+ var ROOT_CONTEXT = "Momentum";
218
+ function getMomentumLogger() {
219
+ if (!loggerInstance) {
220
+ loggerInstance = new MomentumLogger(ROOT_CONTEXT);
221
+ }
222
+ return loggerInstance;
223
+ }
224
+ function createLogger(context) {
225
+ return getMomentumLogger().child(context);
226
+ }
227
+
228
+ // libs/plugins/core/src/lib/plugin-fatal-error.ts
229
+ var PluginFatalError = class extends Error {
230
+ constructor(pluginName, message) {
231
+ super(`[Plugin:${pluginName}] Fatal: ${message}`);
232
+ this.name = "PluginFatalError";
233
+ this.pluginName = pluginName;
234
+ }
235
+ };
236
+
237
+ // libs/plugins/core/src/lib/plugin-runner.ts
238
+ var PluginRunner = class {
239
+ constructor(options) {
240
+ this.middlewareDescriptors = [];
241
+ this.providerDescriptors = [];
242
+ this.initialized = false;
243
+ this.ready = false;
244
+ this.plugins = options.plugins;
245
+ this.config = options.config;
246
+ this.collections = options.collections;
247
+ this.logger = createLogger("Plugins");
248
+ }
249
+ /**
250
+ * Create a plugin-scoped context.
251
+ */
252
+ createContext(plugin) {
253
+ return {
254
+ config: this.config,
255
+ collections: this.collections,
256
+ logger: this.logger.child(plugin.name),
257
+ registerMiddleware: (descriptor) => {
258
+ this.middlewareDescriptors.push(descriptor);
259
+ },
260
+ registerProvider: (descriptor) => {
261
+ this.providerDescriptors.push(descriptor);
262
+ }
263
+ };
264
+ }
265
+ /**
266
+ * Run onInit for all plugins (in order).
267
+ * Call this before API initialization.
268
+ */
269
+ async runInit() {
270
+ if (this.initialized) {
271
+ this.logger.warn("Plugins already initialized");
272
+ return;
273
+ }
274
+ for (const plugin of this.plugins) {
275
+ try {
276
+ if (plugin.onInit) {
277
+ this.logger.debug(`Initializing plugin: ${plugin.name}`);
278
+ await plugin.onInit(this.createContext(plugin));
279
+ }
280
+ } catch (error) {
281
+ if (error instanceof PluginFatalError) {
282
+ throw error;
283
+ }
284
+ const message = error instanceof Error ? error.message : String(error);
285
+ this.logger.error(`Plugin "${plugin.name}" failed during onInit: ${message}`);
286
+ }
287
+ }
288
+ this.initialized = true;
289
+ }
290
+ /**
291
+ * Run onReady for all plugins (in order).
292
+ * Call this after API + seeding complete.
293
+ */
294
+ async runReady(api) {
295
+ if (this.ready) {
296
+ this.logger.warn("Plugins already marked ready");
297
+ return;
298
+ }
299
+ for (const plugin of this.plugins) {
300
+ try {
301
+ if (plugin.onReady) {
302
+ this.logger.debug(`Plugin ready: ${plugin.name}`);
303
+ const context = {
304
+ ...this.createContext(plugin),
305
+ api
306
+ };
307
+ await plugin.onReady(context);
308
+ }
309
+ } catch (error) {
310
+ if (error instanceof PluginFatalError) {
311
+ throw error;
312
+ }
313
+ const message = error instanceof Error ? error.message : String(error);
314
+ this.logger.error(`Plugin "${plugin.name}" failed during onReady: ${message}`);
315
+ }
316
+ }
317
+ this.ready = true;
318
+ }
319
+ /**
320
+ * Run onShutdown for all plugins (in reverse order).
321
+ * Call this during graceful shutdown.
322
+ */
323
+ async runShutdown() {
324
+ const reversed = [...this.plugins].reverse();
325
+ for (const plugin of reversed) {
326
+ try {
327
+ if (plugin.onShutdown) {
328
+ this.logger.debug(`Shutting down plugin: ${plugin.name}`);
329
+ await plugin.onShutdown(this.createContext(plugin));
330
+ }
331
+ } catch (error) {
332
+ const message = error instanceof Error ? error.message : String(error);
333
+ this.logger.error(`Plugin "${plugin.name}" failed during onShutdown: ${message}`);
334
+ }
335
+ }
336
+ }
337
+ /**
338
+ * Get the list of registered plugin names.
339
+ */
340
+ getPluginNames() {
341
+ return this.plugins.map((p) => p.name);
342
+ }
343
+ /**
344
+ * Get middleware descriptors registered by plugins during onInit.
345
+ * Returns descriptors in the order they were registered.
346
+ */
347
+ getMiddleware() {
348
+ return [...this.middlewareDescriptors];
349
+ }
350
+ /**
351
+ * Get provider descriptors registered by plugins during onInit.
352
+ * Returns descriptors in the order they were registered.
353
+ */
354
+ getProviders() {
355
+ return [...this.providerDescriptors];
356
+ }
357
+ };
358
+
359
+ // libs/plugins/core/src/lib/hook-injector.ts
360
+ function injectCollectionEventHooks(collections, listener) {
361
+ for (const collection of collections) {
362
+ collection.hooks = collection.hooks ?? {};
363
+ const afterChangeHook = (args) => {
364
+ const operation = args.operation ?? "create";
365
+ const eventType = operation === "create" ? "afterChange" : "afterChange";
366
+ const event = {
367
+ collection: collection.slug,
368
+ event: eventType,
369
+ operation,
370
+ doc: args.doc ?? args.data,
371
+ previousDoc: args.originalDoc,
372
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
373
+ };
374
+ listener(event);
375
+ };
376
+ const existingAfterChange = collection.hooks.afterChange ?? [];
377
+ collection.hooks.afterChange = [...existingAfterChange, afterChangeHook];
378
+ const afterDeleteHook = (args) => {
379
+ const event = {
380
+ collection: collection.slug,
381
+ event: "afterDelete",
382
+ operation: "delete",
383
+ doc: args.doc,
384
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
385
+ };
386
+ listener(event);
387
+ };
388
+ const existingAfterDelete = collection.hooks.afterDelete ?? [];
389
+ collection.hooks.afterDelete = [...existingAfterDelete, afterDeleteHook];
390
+ }
391
+ }
392
+
393
+ // libs/plugins/core/src/lib/event-bus/event-bus.ts
394
+ var MomentumEventBus = class {
395
+ constructor() {
396
+ this.subscriptions = [];
397
+ }
398
+ /**
399
+ * Subscribe to events matching a pattern.
400
+ *
401
+ * @param pattern - Pattern string: "collection:event", "*:event", "collection:*", or "*"
402
+ * @param handler - Callback invoked when a matching event occurs
403
+ * @returns Unsubscribe function
404
+ *
405
+ * @example
406
+ * ```typescript
407
+ * bus.on('posts:afterChange', (event) => { ... });
408
+ * bus.on('*:afterDelete', (event) => { ... });
409
+ * const unsub = bus.on('*', (event) => { ... });
410
+ * unsub(); // remove subscription
411
+ * ```
412
+ */
413
+ on(pattern, handler) {
414
+ const subscription = { pattern, handler };
415
+ this.subscriptions.push(subscription);
416
+ return () => {
417
+ const index = this.subscriptions.indexOf(subscription);
418
+ if (index !== -1) {
419
+ this.subscriptions.splice(index, 1);
420
+ }
421
+ };
422
+ }
423
+ /**
424
+ * Subscribe to all events for a specific collection.
425
+ * Shorthand for `on("collection:*", handler)`.
426
+ */
427
+ onCollection(collection, handler) {
428
+ return this.on(`${collection}:*`, handler);
429
+ }
430
+ /**
431
+ * Subscribe to a specific event type across all collections.
432
+ * Shorthand for `on("*:event", handler)`.
433
+ */
434
+ onEvent(event, handler) {
435
+ return this.on(`*:${event}`, handler);
436
+ }
437
+ /**
438
+ * Emit a collection event to all matching subscribers.
439
+ */
440
+ emit(event) {
441
+ for (const sub of this.subscriptions) {
442
+ if (matchesPattern(sub.pattern, event.collection, event.event)) {
443
+ sub.handler(event);
444
+ }
445
+ }
446
+ }
447
+ /**
448
+ * Remove all subscriptions.
449
+ */
450
+ clear() {
451
+ this.subscriptions = [];
452
+ }
453
+ /**
454
+ * Get the current number of subscriptions.
455
+ */
456
+ get size() {
457
+ return this.subscriptions.length;
458
+ }
459
+ };
460
+ function matchesPattern(pattern, collection, event) {
461
+ if (pattern === "*") {
462
+ return true;
463
+ }
464
+ const parts = pattern.split(":");
465
+ if (parts.length !== 2) {
466
+ return false;
467
+ }
468
+ const [patternCollection, patternEvent] = parts;
469
+ const collectionMatch = patternCollection === "*" || patternCollection === collection;
470
+ const eventMatch = patternEvent === "*" || patternEvent === event;
471
+ return collectionMatch && eventMatch;
472
+ }
473
+
474
+ // libs/plugins/core/src/lib/event-bus/event-bus-plugin.ts
475
+ function eventBusPlugin() {
476
+ const bus = new MomentumEventBus();
477
+ return {
478
+ name: "event-bus",
479
+ bus,
480
+ onInit({ collections, logger }) {
481
+ logger.info("Injecting event hooks into collections");
482
+ injectCollectionEventHooks(collections, (event) => {
483
+ bus.emit(event);
484
+ });
485
+ logger.info(`Event hooks injected into ${collections.length} collections`);
486
+ },
487
+ onShutdown({ logger }) {
488
+ logger.info("Clearing event bus subscriptions");
489
+ bus.clear();
490
+ }
491
+ };
492
+ }
493
+ export {
494
+ MomentumEventBus,
495
+ PluginFatalError,
496
+ PluginRunner,
497
+ eventBusPlugin,
498
+ injectCollectionEventHooks
499
+ };
package/package.json CHANGED
@@ -1,32 +1,32 @@
1
1
  {
2
- "name": "@momentumcms/plugins-core",
3
- "version": "0.1.0",
4
- "description": "Plugin system core and event bus for Momentum CMS",
5
- "license": "MIT",
6
- "author": "Momentum CMS Contributors",
7
- "repository": {
8
- "type": "git",
9
- "url": "https://github.com/momentum-cms/momentum-cms.git",
10
- "directory": "libs/plugins/core"
11
- },
12
- "homepage": "https://github.com/momentum-cms/momentum-cms#readme",
13
- "bugs": {
14
- "url": "https://github.com/momentum-cms/momentum-cms/issues"
15
- },
16
- "keywords": [
17
- "cms",
18
- "momentum-cms",
19
- "plugins",
20
- "event-bus"
21
- ],
22
- "engines": {
23
- "node": ">=18"
24
- },
25
- "type": "commonjs",
26
- "main": "./index.cjs",
27
- "types": "./src/index.d.ts",
28
- "dependencies": {
29
- "@momentumcms/core": "0.1.0",
30
- "@momentumcms/logger": "0.1.0"
31
- }
32
- }
2
+ "name": "@momentumcms/plugins-core",
3
+ "version": "0.1.2",
4
+ "description": "Plugin system core and event bus for Momentum CMS",
5
+ "license": "MIT",
6
+ "author": "Momentum CMS Contributors",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/momentum-cms/momentum-cms.git",
10
+ "directory": "libs/plugins/core"
11
+ },
12
+ "homepage": "https://github.com/momentum-cms/momentum-cms#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/momentum-cms/momentum-cms/issues"
15
+ },
16
+ "keywords": [
17
+ "cms",
18
+ "momentum-cms",
19
+ "plugins",
20
+ "event-bus"
21
+ ],
22
+ "engines": {
23
+ "node": ">=18"
24
+ },
25
+ "main": "./index.cjs",
26
+ "types": "./src/index.d.ts",
27
+ "dependencies": {
28
+ "@momentumcms/core": "0.1.2",
29
+ "@momentumcms/logger": "0.1.2"
30
+ },
31
+ "module": "./index.js"
32
+ }