mediaguru 1.0.0

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 (48) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/CODE_OF_CONDUCT.md +41 -0
  3. package/CONTRIBUTING.md +50 -0
  4. package/LICENSE +21 -0
  5. package/README.md +193 -0
  6. package/RELEASE.md +38 -0
  7. package/SECURITY.md +24 -0
  8. package/bin/mediaguru.js +2 -0
  9. package/dist/cli/interactive.d.ts +1 -0
  10. package/dist/cli/interactive.js +647 -0
  11. package/dist/core/batch/index.d.ts +16 -0
  12. package/dist/core/batch/index.js +66 -0
  13. package/dist/core/compress/index.d.ts +17 -0
  14. package/dist/core/compress/index.js +96 -0
  15. package/dist/core/config/index.d.ts +14 -0
  16. package/dist/core/config/index.js +56 -0
  17. package/dist/core/export/index.d.ts +12 -0
  18. package/dist/core/export/index.js +82 -0
  19. package/dist/core/image/index.d.ts +44 -0
  20. package/dist/core/image/index.js +206 -0
  21. package/dist/core/ocr/index.d.ts +14 -0
  22. package/dist/core/ocr/index.js +53 -0
  23. package/dist/core/pdf/index.d.ts +34 -0
  24. package/dist/core/pdf/index.js +121 -0
  25. package/dist/core/qr/index.d.ts +12 -0
  26. package/dist/core/qr/index.js +37 -0
  27. package/dist/core/screenshot/index.d.ts +12 -0
  28. package/dist/core/screenshot/index.js +46 -0
  29. package/dist/core/server/index.d.ts +5 -0
  30. package/dist/core/server/index.js +101 -0
  31. package/dist/core/text2img/index.d.ts +14 -0
  32. package/dist/core/text2img/index.js +64 -0
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.js +429 -0
  35. package/dist/plugins/index.d.ts +41 -0
  36. package/dist/plugins/index.js +61 -0
  37. package/dist/tests/test.d.ts +1 -0
  38. package/dist/tests/test.js +108 -0
  39. package/dist/tests/test_playwright.d.ts +1 -0
  40. package/dist/tests/test_playwright.js +60 -0
  41. package/dist/utils/branding.d.ts +6 -0
  42. package/dist/utils/branding.js +21 -0
  43. package/dist/utils/file.d.ts +7 -0
  44. package/dist/utils/file.js +29 -0
  45. package/dist/utils/templates.d.ts +9 -0
  46. package/dist/utils/templates.js +347 -0
  47. package/mediaguru-1.0.0.tgz +0 -0
  48. package/package.json +51 -0
package/dist/index.js ADDED
@@ -0,0 +1,429 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { printHeader, printFooter } from './utils/branding.js';
7
+ import { launchInteractive } from './cli/interactive.js';
8
+ import { PdfEngine } from './core/pdf/index.js';
9
+ import { ImageEngine } from './core/image/index.js';
10
+ import { QrEngine } from './core/qr/index.js';
11
+ import { OcrEngine } from './core/ocr/index.js';
12
+ import { ScreenshotEngine } from './core/screenshot/index.js';
13
+ import { Text2ImgEngine } from './core/text2img/index.js';
14
+ import { CompressEngine } from './core/compress/index.js';
15
+ import { BatchEngine } from './core/batch/index.js';
16
+ import { ExportEngine } from './core/export/index.js';
17
+ import { ConfigManager } from './core/config/index.js';
18
+ import { PluginRegistry } from './plugins/index.js';
19
+ import { formatBytes } from './utils/file.js';
20
+ import { RestApiServer } from './core/server/index.js';
21
+ const program = new Command();
22
+ program
23
+ .name('mediaguru')
24
+ .description('MediaGuru CLI — Universal Document & Media Processing Toolkit')
25
+ .version('1.0.0');
26
+ // Trigger beforeCommand hooks
27
+ program.hook('preAction', async (thisCommand, actionCommand) => {
28
+ await PluginRegistry.triggerBeforeCommand(actionCommand.name(), actionCommand.args);
29
+ });
30
+ // Trigger afterCommand hooks and footer
31
+ program.hook('postAction', async (thisCommand, actionCommand) => {
32
+ await PluginRegistry.triggerAfterCommand(actionCommand.name(), actionCommand.args);
33
+ printFooter();
34
+ });
35
+ // PDF Command group
36
+ const pdf = program.command('pdf').description('PDF manipulation operations');
37
+ pdf
38
+ .command('merge')
39
+ .description('Merge multiple PDFs into a single file')
40
+ .argument('<files...>', 'Paths to PDF files')
41
+ .action(async (files) => {
42
+ const spinner = ora('Merging PDF documents...').start();
43
+ try {
44
+ const out = await PdfEngine.merge(files);
45
+ spinner.succeed(`PDF files merged successfully to: ${chalk.cyan(out)}`);
46
+ }
47
+ catch (e) {
48
+ spinner.fail(`Merge failed: ${e.message}`);
49
+ }
50
+ });
51
+ pdf
52
+ .command('split')
53
+ .description('Split PDF into individual pages')
54
+ .argument('<file>', 'Path to PDF file')
55
+ .action(async (file) => {
56
+ const spinner = ora('Splitting PDF pages...').start();
57
+ try {
58
+ const pages = await PdfEngine.split(file);
59
+ spinner.succeed(`PDF split complete. Extracted ${pages.length} pages:`);
60
+ pages.forEach(p => console.log(` - ${chalk.cyan(p)}`));
61
+ }
62
+ catch (e) {
63
+ spinner.fail(`Split failed: ${e.message}`);
64
+ }
65
+ });
66
+ pdf
67
+ .command('extract')
68
+ .description('Extract plain text from PDF')
69
+ .argument('<file>', 'Path to PDF file')
70
+ .action(async (file) => {
71
+ const spinner = ora('Extracting text from PDF...').start();
72
+ try {
73
+ const text = await PdfEngine.extractText(file);
74
+ spinner.succeed('Extraction completed. Text content:');
75
+ console.log(chalk.dim('\n' + '─'.repeat(40)));
76
+ console.log(text.trim());
77
+ console.log(chalk.dim('─'.repeat(40)));
78
+ }
79
+ catch (e) {
80
+ spinner.fail(`Extraction failed: ${e.message}`);
81
+ }
82
+ });
83
+ pdf
84
+ .command('compress')
85
+ .description('Optimize and compress PDF document size')
86
+ .argument('<file>', 'Path to PDF file')
87
+ .action(async (file) => {
88
+ const spinner = ora('Compressing PDF stream...').start();
89
+ try {
90
+ const out = await PdfEngine.compress(file);
91
+ spinner.succeed(`PDF optimized and saved: ${chalk.cyan(out)}`);
92
+ }
93
+ catch (e) {
94
+ spinner.fail(`Compression failed: ${e.message}`);
95
+ }
96
+ });
97
+ // Root level pdf default actions (markdown/HTML compile)
98
+ pdf
99
+ .argument('<file>', 'Markdown (.md) or HTML (.html) source document')
100
+ .action(async (file) => {
101
+ const ext = path.extname(file).toLowerCase();
102
+ const spinner = ora('Compiling document to PDF...').start();
103
+ try {
104
+ let out = '';
105
+ if (ext === '.md') {
106
+ out = await PdfEngine.markdownToPdf(file);
107
+ spinner.succeed(`Markdown compiled: ${chalk.cyan(out)}`);
108
+ }
109
+ else if (ext === '.html' || ext === '.htm') {
110
+ out = await PdfEngine.htmlToPdf(file);
111
+ spinner.succeed(`HTML compiled: ${chalk.cyan(out)}`);
112
+ }
113
+ else {
114
+ spinner.fail(`Unsupported file type: ${ext}. Please supply .md or .html`);
115
+ }
116
+ }
117
+ catch (e) {
118
+ spinner.fail(`Compilation failed: ${e.message}`);
119
+ }
120
+ });
121
+ // Image Command group
122
+ const image = program.command('image').description('Image manipulation operations');
123
+ image
124
+ .command('resize')
125
+ .description('Resize image dimensions')
126
+ .argument('<file>', 'Path to image file')
127
+ .argument('<size>', 'Resolution dimensions (e.g. 800x600 or 800)')
128
+ .action(async (file, size) => {
129
+ const spinner = ora('Resizing image layout...').start();
130
+ try {
131
+ const out = await ImageEngine.resize(file, size);
132
+ spinner.succeed(`Image resized: ${chalk.cyan(out)}`);
133
+ }
134
+ catch (e) {
135
+ spinner.fail(`Resize failed: ${e.message}`);
136
+ }
137
+ });
138
+ image
139
+ .command('convert')
140
+ .description('Convert image format')
141
+ .argument('<file>', 'Path to image file')
142
+ .argument('<format>', 'Target format (webp, png, jpg, jpeg)')
143
+ .action(async (file, format) => {
144
+ const spinner = ora(`Converting format to ${format}...`).start();
145
+ try {
146
+ const out = await ImageEngine.convert(file, format);
147
+ spinner.succeed(`Image converted: ${chalk.cyan(out)}`);
148
+ }
149
+ catch (e) {
150
+ spinner.fail(`Conversion failed: ${e.message}`);
151
+ }
152
+ });
153
+ image
154
+ .command('watermark')
155
+ .description('Overlay a watermark logo on top of an image')
156
+ .argument('<file>', 'Path to main image file')
157
+ .argument('<watermark>', 'Path to watermark logo image')
158
+ .action(async (file, watermark) => {
159
+ const spinner = ora('Overlaying watermark composite...').start();
160
+ try {
161
+ const out = await ImageEngine.watermark(file, watermark);
162
+ spinner.succeed(`Watermark composite applied: ${chalk.cyan(out)}`);
163
+ }
164
+ catch (e) {
165
+ spinner.fail(`Watermarking failed: ${e.message}`);
166
+ }
167
+ });
168
+ image
169
+ .command('remove-bg')
170
+ .description('Remove background from image')
171
+ .argument('<file>', 'Path to image file')
172
+ .option('--api', 'Force use API background removal engine (requires remove.bg API key)')
173
+ .action(async (file, options) => {
174
+ const engineType = options.api ? 'api' : 'local';
175
+ const spinner = ora(`Removing background (engine: ${engineType})...`).start();
176
+ try {
177
+ const out = await ImageEngine.removeBg(file, engineType);
178
+ spinner.succeed(`Background removed: ${chalk.cyan(out)}`);
179
+ }
180
+ catch (e) {
181
+ spinner.fail(`Background removal failed: ${e.message}`);
182
+ }
183
+ });
184
+ // QR Generator Command
185
+ program
186
+ .command('qr')
187
+ .description('Generate QR Code from text or url')
188
+ .argument('<text>', 'URL or text to encode')
189
+ .option('--svg', 'Output SVG vector path')
190
+ .option('--size <number>', 'Resolution size in px', '300')
191
+ .action(async (text, options) => {
192
+ const spinner = ora('Generating QR Code...').start();
193
+ try {
194
+ const sizeVal = parseInt(options.size, 10) || 300;
195
+ const out = await QrEngine.generate(text, { svg: options.svg, size: sizeVal });
196
+ spinner.succeed(`QR Code saved: ${chalk.cyan(out)}`);
197
+ }
198
+ catch (e) {
199
+ spinner.fail(`Generation failed: ${e.message}`);
200
+ }
201
+ });
202
+ // OCR Command
203
+ program
204
+ .command('ocr')
205
+ .description('Extract text from image (OCR)')
206
+ .argument('<file>', 'Path to image file')
207
+ .option('--export <format>', 'Export result to file format (txt, markdown, json)')
208
+ .action(async (file, options) => {
209
+ const spinner = ora('Parsing image text (OCR)...').start();
210
+ try {
211
+ const exportFormat = options.export ? options.export.toLowerCase() : undefined;
212
+ const res = await OcrEngine.extract(file, { exportFormat });
213
+ spinner.succeed('OCR Extraction completed. Result:');
214
+ console.log(chalk.dim('\n' + '─'.repeat(40)));
215
+ console.log(res.text.trim());
216
+ console.log(chalk.dim('─'.repeat(40)));
217
+ if (res.exportedPath) {
218
+ console.log(`\nExport file written: ${chalk.cyan(res.exportedPath)}`);
219
+ }
220
+ }
221
+ catch (e) {
222
+ spinner.fail(`OCR Extraction failed: ${e.message}`);
223
+ }
224
+ });
225
+ // Website Screenshot Command
226
+ program
227
+ .command('screenshot')
228
+ .description('Capture responsive website screenshot')
229
+ .argument('<url>', 'Webpage URL')
230
+ .option('--full-page', 'Capture full scroll height')
231
+ .option('--mobile', 'Emulate high-end mobile layout')
232
+ .action(async (url, options) => {
233
+ const spinner = ora('Rendering website screenshot...').start();
234
+ try {
235
+ const out = await ScreenshotEngine.capture(url, {
236
+ fullPage: options.fullPage,
237
+ mobile: options.mobile,
238
+ });
239
+ spinner.succeed(`Website screenshot captured: ${chalk.cyan(out)}`);
240
+ }
241
+ catch (e) {
242
+ spinner.fail(`Screenshot failed: ${e.message}`);
243
+ }
244
+ });
245
+ // Text-to-Image (Social Card) Command
246
+ program
247
+ .command('text2img')
248
+ .description('Generate stylized social announcement cards or banner images')
249
+ .argument('<text>', 'Card text body')
250
+ .option('--theme <type>', 'Visual color theme (dark, light, glass)', 'dark')
251
+ .option('--type <layout>', 'Aspect ratio structure (social, quote, poster, banner)', 'social')
252
+ .option('--title <string>', 'Optional category/header title')
253
+ .option('--author <string>', 'Optional author credits')
254
+ .action(async (text, options) => {
255
+ const spinner = ora('Rendering custom card template in browser...').start();
256
+ try {
257
+ const out = await Text2ImgEngine.generate(text, {
258
+ theme: options.theme.toLowerCase(),
259
+ type: options.type.toLowerCase(),
260
+ title: options.title,
261
+ author: options.author,
262
+ });
263
+ spinner.succeed(`Social graphics card generated: ${chalk.cyan(out)}`);
264
+ }
265
+ catch (e) {
266
+ spinner.fail(`Text2img rendering failed: ${e.message}`);
267
+ }
268
+ });
269
+ // Compression Command
270
+ program
271
+ .command('compress')
272
+ .description('Reduce image file size or entire directories recursively')
273
+ .argument('<path>', 'File or folder path')
274
+ .action(async (targetPath) => {
275
+ const stat = fs.statSync(targetPath);
276
+ if (stat.isDirectory()) {
277
+ const spinner = ora('Compressing folder files recursively...').start();
278
+ try {
279
+ const results = await CompressEngine.compressFolder(targetPath);
280
+ spinner.succeed(`Recursive folder compression completed!`);
281
+ let totalOriginal = 0;
282
+ let totalCompressed = 0;
283
+ results.forEach(r => {
284
+ totalOriginal += r.originalSize;
285
+ totalCompressed += r.compressedSize;
286
+ });
287
+ const totalSaved = totalOriginal - totalCompressed;
288
+ const totalPct = totalOriginal > 0 ? (totalSaved / totalOriginal) * 100 : 0;
289
+ console.log(chalk.bold.yellow('\nBatch Compression Statistics:'));
290
+ console.log(`- Files processed: ${chalk.cyan(results.length)}`);
291
+ console.log(`- Total original size: ${chalk.bold(formatBytes(totalOriginal))}`);
292
+ console.log(`- Total compressed size: ${chalk.bold(formatBytes(totalCompressed))}`);
293
+ console.log(`- Total space saved: ${chalk.green.bold(formatBytes(totalSaved))} (${chalk.green.bold(totalPct.toFixed(1))}% reduction)`);
294
+ }
295
+ catch (e) {
296
+ spinner.fail(`Folder compression failed: ${e.message}`);
297
+ }
298
+ }
299
+ else {
300
+ const spinner = ora('Optimizing image stream...').start();
301
+ try {
302
+ const res = await CompressEngine.compressImage(targetPath);
303
+ spinner.succeed(`Image file optimized: ${chalk.cyan(res.filePath)}`);
304
+ console.log(chalk.bold.yellow('\nCompression Statistics:'));
305
+ console.log(`- Original size: ${chalk.bold(formatBytes(res.originalSize))}`);
306
+ console.log(`- Compressed size: ${chalk.bold(formatBytes(res.compressedSize))}`);
307
+ console.log(`- Savings: ${chalk.green.bold(formatBytes(res.savedBytes))} (${chalk.green.bold(res.percentage.toFixed(1))}% reduction)`);
308
+ }
309
+ catch (e) {
310
+ spinner.fail(`Image compression failed: ${e.message}`);
311
+ }
312
+ }
313
+ });
314
+ // Batch Command
315
+ program
316
+ .command('batch')
317
+ .description('Run operations across groups of matching files')
318
+ .argument('<pattern>', 'Glob file search pattern (e.g. "images/*.png")')
319
+ .option('--convert <format>', 'Convert batch to target format (png, jpg, webp)')
320
+ .option('--pdf', 'Compile batch documents into PDF files')
321
+ .action(async (pattern, options) => {
322
+ if (!options.convert && !options.pdf) {
323
+ console.error(chalk.red('Error: You must specify --convert <format> or --pdf.'));
324
+ process.exit(1);
325
+ }
326
+ const spinner = ora('Executing batch processing jobs...').start();
327
+ try {
328
+ let results;
329
+ if (options.convert) {
330
+ results = await BatchEngine.convertImages(pattern, options.convert);
331
+ }
332
+ else {
333
+ results = await BatchEngine.convertToPdf(pattern);
334
+ }
335
+ const succeeded = results.filter(r => r.success);
336
+ const failed = results.filter(r => !r.success);
337
+ spinner.succeed(`Batch operations completed!`);
338
+ console.log(chalk.bold.yellow('\nBatch Statistics:'));
339
+ console.log(`- Total tasks: ${chalk.cyan(results.length)}`);
340
+ console.log(`- Succeeded: ${chalk.green(succeeded.length)}`);
341
+ console.log(`- Failed: ${chalk.red(failed.length)}`);
342
+ if (failed.length > 0) {
343
+ console.log(chalk.bold.red('\nFailures:'));
344
+ failed.forEach(f => console.log(` - ${f.sourceFile}: ${chalk.dim(f.error)}`));
345
+ }
346
+ }
347
+ catch (e) {
348
+ spinner.fail(`Batch processing execution failed: ${e.message}`);
349
+ }
350
+ });
351
+ // Export Command
352
+ program
353
+ .command('export')
354
+ .description('Export configurations or convert JSON datasets to CSV/Markdown')
355
+ .argument('<format>', 'Target output format (json, markdown, csv)')
356
+ .option('--source <file>', 'Path to JSON file to convert (optional)')
357
+ .action(async (format, options) => {
358
+ const fmt = format.toLowerCase();
359
+ if (options.source) {
360
+ const spinner = ora(`Converting JSON file to ${format}...`).start();
361
+ try {
362
+ if (fmt === 'json') {
363
+ spinner.fail('Cannot convert a JSON file into a JSON file.');
364
+ return;
365
+ }
366
+ const out = await ExportEngine.convertJsonData(options.source, fmt);
367
+ spinner.succeed(`Dataset exported: ${chalk.cyan(out)}`);
368
+ }
369
+ catch (e) {
370
+ spinner.fail(`Conversion failed: ${e.message}`);
371
+ }
372
+ }
373
+ else {
374
+ const spinner = ora(`Exporting config parameters as ${format}...`).start();
375
+ try {
376
+ const out = await ExportEngine.exportConfig(fmt);
377
+ spinner.succeed(`Settings exported: ${chalk.cyan(out)}`);
378
+ }
379
+ catch (e) {
380
+ spinner.fail(`Export failed: ${e.message}`);
381
+ }
382
+ }
383
+ });
384
+ // Config Command
385
+ program
386
+ .command('config')
387
+ .description('Print or modify configuration parameters')
388
+ .action(async () => {
389
+ const current = ConfigManager.load();
390
+ console.log(chalk.bold.yellow('\nCurrent MediaGuru Settings:'));
391
+ console.log(`- Default Image Format: ${chalk.cyan(current.defaultImageFormat)}`);
392
+ console.log(`- PDF Rendering Engine: ${chalk.cyan(current.pdfEngine)}`);
393
+ console.log(`- Compression Quality: ${chalk.cyan(current.compressionQuality + '%')}`);
394
+ console.log(`- Screenshot Resolution: ${chalk.cyan(current.screenshotResolution)}`);
395
+ console.log(`- Default Output Folder: ${chalk.cyan(current.outputFolder)}`);
396
+ console.log(chalk.dim('\nTo edit settings, run the CLI without arguments to open interactive mode.'));
397
+ });
398
+ // Standalone REST API Server Command
399
+ program
400
+ .command('server')
401
+ .description('Start a local HTTP REST API server')
402
+ .option('--port <number>', 'Port to listen on', '3000')
403
+ .action(async (options) => {
404
+ const port = parseInt(options.port, 10) || 3000;
405
+ const server = new RestApiServer();
406
+ const spinner = ora('Starting HTTP REST Server...').start();
407
+ try {
408
+ spinner.stop(); // Stop spinner to avoid overlapping text
409
+ await server.start(port);
410
+ }
411
+ catch (e) {
412
+ spinner.fail(`Server failed to start: ${e.message}`);
413
+ }
414
+ });
415
+ // Bootstrap CLI
416
+ async function bootstrap() {
417
+ // Inject plugin custom commands
418
+ PluginRegistry.injectCustomCommands(program);
419
+ // If no arguments, launch interactive mode
420
+ if (process.argv.length <= 2) {
421
+ await launchInteractive();
422
+ }
423
+ else {
424
+ // Print ASCII art header on direct command invocations
425
+ printHeader();
426
+ program.parse(process.argv);
427
+ }
428
+ }
429
+ bootstrap();
@@ -0,0 +1,41 @@
1
+ import { Command } from 'commander';
2
+ export interface IMediaGuruPlugin {
3
+ name: string;
4
+ version: string;
5
+ description?: string;
6
+ /**
7
+ * Executed before any built-in command runs.
8
+ */
9
+ beforeCommand?(commandName: string, args: any[]): Promise<void>;
10
+ /**
11
+ * Executed after any built-in command runs successfully.
12
+ */
13
+ afterCommand?(commandName: string, result: any): Promise<void>;
14
+ /**
15
+ * Allows the plugin to inject brand new subcommands into Commander.js!
16
+ */
17
+ registerCustomCommands?(program: Command): void;
18
+ }
19
+ export declare class PluginRegistry {
20
+ private static plugins;
21
+ /**
22
+ * Registers a plugin instance.
23
+ */
24
+ static registerPlugin(plugin: IMediaGuruPlugin): void;
25
+ /**
26
+ * Retrieves all loaded plugins.
27
+ */
28
+ static getPlugins(): IMediaGuruPlugin[];
29
+ /**
30
+ * Lifecycle trigger: before command execution.
31
+ */
32
+ static triggerBeforeCommand(commandName: string, args: any[]): Promise<void>;
33
+ /**
34
+ * Lifecycle trigger: after command execution.
35
+ */
36
+ static triggerAfterCommand(commandName: string, result: any): Promise<void>;
37
+ /**
38
+ * Scans loaded plugins and lets them inject their custom subcommands.
39
+ */
40
+ static injectCustomCommands(program: Command): void;
41
+ }
@@ -0,0 +1,61 @@
1
+ export class PluginRegistry {
2
+ static plugins = new Map();
3
+ /**
4
+ * Registers a plugin instance.
5
+ */
6
+ static registerPlugin(plugin) {
7
+ this.plugins.set(plugin.name, plugin);
8
+ // console.log(`[PluginRegistry] Registered: ${plugin.name} (v${plugin.version})`);
9
+ }
10
+ /**
11
+ * Retrieves all loaded plugins.
12
+ */
13
+ static getPlugins() {
14
+ return Array.from(this.plugins.values());
15
+ }
16
+ /**
17
+ * Lifecycle trigger: before command execution.
18
+ */
19
+ static async triggerBeforeCommand(commandName, args) {
20
+ for (const plugin of this.plugins.values()) {
21
+ if (plugin.beforeCommand) {
22
+ try {
23
+ await plugin.beforeCommand(commandName, args);
24
+ }
25
+ catch (err) {
26
+ console.warn(`[PluginRegistry] Plugin "${plugin.name}" error in beforeCommand:`, err);
27
+ }
28
+ }
29
+ }
30
+ }
31
+ /**
32
+ * Lifecycle trigger: after command execution.
33
+ */
34
+ static async triggerAfterCommand(commandName, result) {
35
+ for (const plugin of this.plugins.values()) {
36
+ if (plugin.afterCommand) {
37
+ try {
38
+ await plugin.afterCommand(commandName, result);
39
+ }
40
+ catch (err) {
41
+ console.warn(`[PluginRegistry] Plugin "${plugin.name}" error in afterCommand:`, err);
42
+ }
43
+ }
44
+ }
45
+ }
46
+ /**
47
+ * Scans loaded plugins and lets them inject their custom subcommands.
48
+ */
49
+ static injectCustomCommands(program) {
50
+ for (const plugin of this.plugins.values()) {
51
+ if (plugin.registerCustomCommands) {
52
+ try {
53
+ plugin.registerCustomCommands(program);
54
+ }
55
+ catch (err) {
56
+ console.error(`[PluginRegistry] Plugin "${plugin.name}" failed to register custom commands:`, err);
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,108 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import sharp from 'sharp';
5
+ import { ConfigManager } from '../core/config/index.js';
6
+ import { ImageEngine } from '../core/image/index.js';
7
+ import { QrEngine } from '../core/qr/index.js';
8
+ import { ExportEngine } from '../core/export/index.js';
9
+ async function runTests() {
10
+ console.log(chalk.bold.cyan('========================================'));
11
+ console.log(chalk.bold.cyan(' MediaGuru Engine Validation Suite '));
12
+ console.log(chalk.bold.cyan('========================================\n'));
13
+ const testDir = './test_sandbox';
14
+ if (!fs.existsSync(testDir)) {
15
+ fs.mkdirSync(testDir, { recursive: true });
16
+ }
17
+ // Setup Config
18
+ ConfigManager.update({
19
+ outputFolder: testDir,
20
+ compressionQuality: 85,
21
+ defaultImageFormat: 'png',
22
+ });
23
+ console.log(chalk.green('✓ Config Manager initialized and updated.'));
24
+ // Create mock source image (100x100 solid blue box)
25
+ const mockImagePath = path.join(testDir, 'source.png');
26
+ const mockWatermarkPath = path.join(testDir, 'watermark.png');
27
+ await sharp({
28
+ create: {
29
+ width: 200,
30
+ height: 200,
31
+ channels: 4,
32
+ background: { r: 0, g: 0, b: 255, alpha: 1 },
33
+ },
34
+ })
35
+ .png()
36
+ .toFile(mockImagePath);
37
+ // Solid white 40x40 watermark box
38
+ await sharp({
39
+ create: {
40
+ width: 40,
41
+ height: 40,
42
+ channels: 4,
43
+ background: { r: 255, g: 255, b: 255, alpha: 1 },
44
+ },
45
+ })
46
+ .png()
47
+ .toFile(mockWatermarkPath);
48
+ console.log(chalk.green('✓ Mock testing images created successfully.'));
49
+ // Test Image Resize
50
+ console.log(chalk.yellow('\nTesting Image Resizing...'));
51
+ const resizedPath = await ImageEngine.resize(mockImagePath, '100x100');
52
+ if (fs.existsSync(resizedPath)) {
53
+ console.log(chalk.green(`✓ Image resized successfully: ${resizedPath}`));
54
+ }
55
+ else {
56
+ throw new Error('Image resize failed to output file.');
57
+ }
58
+ // Test Image Format Convert
59
+ console.log(chalk.yellow('\nTesting Image Conversion...'));
60
+ const convertedPath = await ImageEngine.convert(mockImagePath, 'webp');
61
+ if (fs.existsSync(convertedPath)) {
62
+ console.log(chalk.green(`✓ Image converted to WebP successfully: ${convertedPath}`));
63
+ }
64
+ else {
65
+ throw new Error('Image conversion failed to output file.');
66
+ }
67
+ // Test Image Watermark
68
+ console.log(chalk.yellow('\nTesting Image Watermarking...'));
69
+ const watermarkedPath = await ImageEngine.watermark(mockImagePath, mockWatermarkPath);
70
+ if (fs.existsSync(watermarkedPath)) {
71
+ console.log(chalk.green(`✓ Watermark overlay successful: ${watermarkedPath}`));
72
+ }
73
+ else {
74
+ throw new Error('Watermark overlay failed to output file.');
75
+ }
76
+ // Test QR Generator
77
+ console.log(chalk.yellow('\nTesting QR Code Generation...'));
78
+ const qrPng = await QrEngine.generate('https://kontyra.com', { svg: false, size: 250 });
79
+ const qrSvg = await QrEngine.generate('https://kontyra.com', { svg: true, size: 250 });
80
+ if (fs.existsSync(qrPng) && fs.existsSync(qrSvg)) {
81
+ console.log(chalk.green(`✓ QR PNG generated successfully: ${qrPng}`));
82
+ console.log(chalk.green(`✓ QR SVG generated successfully: ${qrSvg}`));
83
+ }
84
+ else {
85
+ throw new Error('QR Code generation failed.');
86
+ }
87
+ // Test Exports System
88
+ console.log(chalk.yellow('\nTesting Exports System...'));
89
+ const configJson = await ExportEngine.exportConfig('json', { outputPath: path.join(testDir, 'config.json') });
90
+ const configMd = await ExportEngine.exportConfig('markdown', { outputPath: path.join(testDir, 'config.md') });
91
+ const configCsv = await ExportEngine.exportConfig('csv', { outputPath: path.join(testDir, 'config.csv') });
92
+ if (fs.existsSync(configJson) && fs.existsSync(configMd) && fs.existsSync(configCsv)) {
93
+ console.log(chalk.green('✓ Configurations exported successfully in JSON, MD, and CSV.'));
94
+ }
95
+ else {
96
+ throw new Error('Export config failed.');
97
+ }
98
+ // Clean up mock generated source files
99
+ console.log(chalk.cyan('\nCleaning up mock testing files...'));
100
+ // (we leave other outputs in the sandbox folder for manual inspection if needed)
101
+ console.log(chalk.bold.green('\n========================================'));
102
+ console.log(chalk.bold.green(' ALL CORE ENGINE TESTS PASSED! '));
103
+ console.log(chalk.bold.green('========================================\n'));
104
+ }
105
+ runTests().catch((err) => {
106
+ console.error(chalk.bold.red('\n❌ TEST SUITE FAILED:'), err);
107
+ process.exit(1);
108
+ });
@@ -0,0 +1 @@
1
+ export {};