@memberjunction/metadata-sync 2.50.0 → 2.51.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.
@@ -0,0 +1,561 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FormattingService = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ class FormattingService {
9
+ /**
10
+ * Format validation result as JSON
11
+ */
12
+ formatValidationResultAsJson(result) {
13
+ const output = {
14
+ isValid: result.isValid,
15
+ summary: {
16
+ totalFiles: result.summary.totalFiles,
17
+ totalEntities: result.summary.totalEntities,
18
+ totalErrors: result.summary.totalErrors,
19
+ totalWarnings: result.summary.totalWarnings,
20
+ errorsByType: this.getErrorsByType(result.errors),
21
+ warningsByType: this.getWarningsByType(result.warnings)
22
+ },
23
+ errors: result.errors.map(e => ({
24
+ type: e.type,
25
+ entity: e.entity,
26
+ field: e.field,
27
+ file: e.file,
28
+ message: e.message,
29
+ suggestion: e.suggestion
30
+ })),
31
+ warnings: result.warnings.map(w => ({
32
+ type: w.type,
33
+ entity: w.entity,
34
+ field: w.field,
35
+ file: w.file,
36
+ message: w.message,
37
+ suggestion: w.suggestion
38
+ }))
39
+ };
40
+ return JSON.stringify(output, null, 2);
41
+ }
42
+ symbols = {
43
+ success: '✓',
44
+ error: '✗',
45
+ warning: '⚠',
46
+ info: 'ℹ',
47
+ arrow: '→',
48
+ bullet: '•',
49
+ box: {
50
+ topLeft: '┌',
51
+ topRight: '┐',
52
+ bottomLeft: '└',
53
+ bottomRight: '┘',
54
+ horizontal: '─',
55
+ vertical: '│',
56
+ cross: '┼'
57
+ }
58
+ };
59
+ /**
60
+ * Format validation result for terminal output
61
+ */
62
+ formatValidationResult(result, verbose = false) {
63
+ const lines = [];
64
+ // Header
65
+ lines.push(this.formatHeader('Validation Report'));
66
+ lines.push('');
67
+ // Summary box
68
+ lines.push(this.formatSummaryBox(result));
69
+ lines.push('');
70
+ // File results
71
+ if (result.summary.fileResults.size > 0) {
72
+ lines.push(this.formatSectionHeader('File Results'));
73
+ lines.push('');
74
+ for (const [file, fileResult] of result.summary.fileResults) {
75
+ const hasIssues = fileResult.errors.length > 0 || fileResult.warnings.length > 0;
76
+ if (!verbose && !hasIssues)
77
+ continue;
78
+ lines.push(this.formatFileResult(file, fileResult, verbose));
79
+ }
80
+ }
81
+ // Detailed errors
82
+ if (result.errors.length > 0) {
83
+ lines.push('');
84
+ lines.push(this.formatSectionHeader('Errors'));
85
+ lines.push('');
86
+ result.errors.forEach((error, index) => {
87
+ lines.push(this.formatError(error, index + 1));
88
+ });
89
+ }
90
+ // Detailed warnings
91
+ if (result.warnings.length > 0) {
92
+ lines.push('');
93
+ lines.push(this.formatSectionHeader('Warnings'));
94
+ lines.push('');
95
+ result.warnings.forEach((warning, index) => {
96
+ lines.push(this.formatWarning(warning, index + 1));
97
+ });
98
+ }
99
+ // Footer
100
+ lines.push('');
101
+ lines.push(this.formatFooter(result));
102
+ return lines.join('\n');
103
+ }
104
+ /**
105
+ * Format push/pull summary report
106
+ */
107
+ formatSyncSummary(operation, stats) {
108
+ const lines = [];
109
+ lines.push(this.formatHeader(`${operation.charAt(0).toUpperCase() + operation.slice(1)} Summary`));
110
+ lines.push('');
111
+ const total = stats.created + stats.updated + stats.deleted + stats.skipped;
112
+ lines.push(chalk_1.default.bold('Operation Statistics:'));
113
+ lines.push('');
114
+ lines.push(` ${chalk_1.default.green(this.symbols.success)} Created: ${chalk_1.default.green(stats.created)}`);
115
+ lines.push(` ${chalk_1.default.blue(this.symbols.info)} Updated: ${chalk_1.default.blue(stats.updated)}`);
116
+ lines.push(` ${chalk_1.default.red(this.symbols.error)} Deleted: ${chalk_1.default.red(stats.deleted)}`);
117
+ lines.push(` ${chalk_1.default.gray('-')} Skipped: ${chalk_1.default.gray(stats.skipped)}`);
118
+ lines.push('');
119
+ lines.push(` Total Records: ${chalk_1.default.bold(total)}`);
120
+ lines.push(` Duration: ${chalk_1.default.cyan(this.formatDuration(stats.duration))}`);
121
+ if (stats.errors > 0) {
122
+ lines.push('');
123
+ lines.push(chalk_1.default.red(` ${this.symbols.error} Errors: ${stats.errors}`));
124
+ }
125
+ return lines.join('\n');
126
+ }
127
+ formatHeader(title) {
128
+ const width = 60;
129
+ const line = '═'.repeat(width);
130
+ // MemberJunction branding
131
+ const brandingText = 'MemberJunction Metadata Sync';
132
+ const brandingPadding = Math.floor((width - brandingText.length - 2) / 2);
133
+ // Title
134
+ const titlePadding = Math.floor((width - title.length - 2) / 2);
135
+ return chalk_1.default.blue([
136
+ line,
137
+ '║' + ' '.repeat(brandingPadding) + brandingText + ' '.repeat(width - brandingPadding - brandingText.length - 2) + '║',
138
+ '║' + ' '.repeat(titlePadding) + title + ' '.repeat(width - titlePadding - title.length - 2) + '║',
139
+ line
140
+ ].join('\n'));
141
+ }
142
+ formatSectionHeader(title) {
143
+ return chalk_1.default.bold.underline(title);
144
+ }
145
+ formatSummaryBox(result) {
146
+ const lines = [];
147
+ const width = 50;
148
+ lines.push(chalk_1.default.gray('┌' + '─'.repeat(width - 2) + '┐'));
149
+ // Basic stats
150
+ const items = [
151
+ ['Files:', result.summary.totalFiles],
152
+ ['Entities:', result.summary.totalEntities],
153
+ ['Errors:', result.summary.totalErrors],
154
+ ['Warnings:', result.summary.totalWarnings]
155
+ ];
156
+ items.forEach(([label, value]) => {
157
+ const numValue = Number(value);
158
+ const color = label === 'Errors:' && numValue > 0 ? chalk_1.default.red :
159
+ label === 'Warnings:' && numValue > 0 ? chalk_1.default.yellow :
160
+ chalk_1.default.white;
161
+ const line = `${String(label).padEnd(15)} ${color(String(value))}`;
162
+ lines.push(chalk_1.default.gray('│ ') + line.padEnd(width - 4) + chalk_1.default.gray(' │'));
163
+ });
164
+ // Add separator
165
+ if (result.errors.length > 0 || result.warnings.length > 0) {
166
+ lines.push(chalk_1.default.gray('├' + '─'.repeat(width - 2) + '┤'));
167
+ }
168
+ // Error breakdown by type
169
+ if (result.errors.length > 0) {
170
+ lines.push(chalk_1.default.gray('│ ') + chalk_1.default.bold('Errors by Type:').padEnd(width - 4) + chalk_1.default.gray(' │'));
171
+ const errorsByType = this.getErrorsByType(result.errors);
172
+ for (const [type, count] of Object.entries(errorsByType)) {
173
+ const typeText = ` ${type}:`;
174
+ const countText = chalk_1.default.red(count.toString());
175
+ const spaceBetween = width - 4 - typeText.length - count.toString().length;
176
+ const line = typeText + ' '.repeat(spaceBetween) + countText;
177
+ lines.push(chalk_1.default.gray('│ ') + line + chalk_1.default.gray(' │'));
178
+ }
179
+ }
180
+ // Warning breakdown by type
181
+ if (result.warnings.length > 0) {
182
+ if (result.errors.length > 0) {
183
+ lines.push(chalk_1.default.gray('├' + '─'.repeat(width - 2) + '┤'));
184
+ }
185
+ lines.push(chalk_1.default.gray('│ ') + chalk_1.default.bold('Warnings by Type:').padEnd(width - 4) + chalk_1.default.gray(' │'));
186
+ const warningsByType = this.getWarningsByType(result.warnings);
187
+ for (const [type, count] of Object.entries(warningsByType)) {
188
+ const typeText = ` ${type}:`;
189
+ const countText = chalk_1.default.yellow(count.toString());
190
+ const spaceBetween = width - 4 - typeText.length - count.toString().length;
191
+ const line = typeText + ' '.repeat(spaceBetween) + countText;
192
+ lines.push(chalk_1.default.gray('│ ') + line + chalk_1.default.gray(' │'));
193
+ }
194
+ }
195
+ lines.push(chalk_1.default.gray('└' + '─'.repeat(width - 2) + '┘'));
196
+ return lines.join('\n');
197
+ }
198
+ formatFileResult(file, result, verbose) {
199
+ const lines = [];
200
+ const hasErrors = result.errors.length > 0;
201
+ const hasWarnings = result.warnings.length > 0;
202
+ const icon = hasErrors ? chalk_1.default.red(this.symbols.error) :
203
+ hasWarnings ? chalk_1.default.yellow(this.symbols.warning) :
204
+ chalk_1.default.green(this.symbols.success);
205
+ const shortPath = this.shortenPath(file);
206
+ lines.push(`${icon} ${chalk_1.default.bold(shortPath)}`);
207
+ if (verbose || hasErrors || hasWarnings) {
208
+ lines.push(` ${chalk_1.default.gray(`Entities: ${result.entityCount}`)}`);
209
+ if (hasErrors) {
210
+ lines.push(` ${chalk_1.default.red(`Errors: ${result.errors.length}`)}`);
211
+ }
212
+ if (hasWarnings) {
213
+ lines.push(` ${chalk_1.default.yellow(`Warnings: ${result.warnings.length}`)}`);
214
+ }
215
+ }
216
+ lines.push('');
217
+ return lines.join('\n');
218
+ }
219
+ formatError(error, index) {
220
+ const lines = [];
221
+ lines.push(chalk_1.default.red(`${index}. ${error.message}`));
222
+ if (error.entity) {
223
+ lines.push(chalk_1.default.gray(` Entity: ${error.entity}`));
224
+ }
225
+ if (error.field) {
226
+ lines.push(chalk_1.default.gray(` Field: ${error.field}`));
227
+ }
228
+ lines.push(chalk_1.default.gray(` File: ${this.shortenPath(error.file)}`));
229
+ if (error.suggestion) {
230
+ lines.push(chalk_1.default.cyan(` ${this.symbols.arrow} Suggestion: ${error.suggestion}`));
231
+ }
232
+ lines.push('');
233
+ return lines.join('\n');
234
+ }
235
+ formatWarning(warning, index) {
236
+ const lines = [];
237
+ lines.push(chalk_1.default.yellow(`${index}. ${warning.message}`));
238
+ if (warning.entity) {
239
+ lines.push(chalk_1.default.gray(` Entity: ${warning.entity}`));
240
+ }
241
+ if (warning.field) {
242
+ lines.push(chalk_1.default.gray(` Field: ${warning.field}`));
243
+ }
244
+ lines.push(chalk_1.default.gray(` File: ${this.shortenPath(warning.file)}`));
245
+ if (warning.suggestion) {
246
+ lines.push(chalk_1.default.cyan(` ${this.symbols.arrow} Suggestion: ${warning.suggestion}`));
247
+ }
248
+ lines.push('');
249
+ return lines.join('\n');
250
+ }
251
+ formatFooter(result) {
252
+ const lines = [];
253
+ if (result.isValid) {
254
+ lines.push(chalk_1.default.green.bold(`${this.symbols.success} Validation passed!`));
255
+ }
256
+ else {
257
+ lines.push(chalk_1.default.red.bold(`${this.symbols.error} Validation failed with ${result.errors.length} error(s)`));
258
+ }
259
+ // Add documentation link if there are any issues
260
+ if (result.errors.length > 0 || result.warnings.length > 0) {
261
+ lines.push('');
262
+ lines.push(chalk_1.default.gray('For help resolving issues, see:'));
263
+ lines.push(chalk_1.default.cyan('https://github.com/MemberJunction/MJ/tree/next/packages/MetadataSync'));
264
+ }
265
+ return lines.join('\n');
266
+ }
267
+ shortenPath(filePath) {
268
+ const cwd = process.cwd();
269
+ if (filePath.startsWith(cwd)) {
270
+ return '.' + filePath.slice(cwd.length);
271
+ }
272
+ return filePath;
273
+ }
274
+ formatDuration(ms) {
275
+ if (ms < 1000)
276
+ return `${ms}ms`;
277
+ if (ms < 60000)
278
+ return `${(ms / 1000).toFixed(1)}s`;
279
+ return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;
280
+ }
281
+ /**
282
+ * Get count of errors by type
283
+ */
284
+ getErrorsByType(errors) {
285
+ const counts = {};
286
+ for (const error of errors) {
287
+ counts[error.type] = (counts[error.type] || 0) + 1;
288
+ }
289
+ return counts;
290
+ }
291
+ /**
292
+ * Get count of warnings by type
293
+ */
294
+ getWarningsByType(warnings) {
295
+ const counts = {};
296
+ for (const warning of warnings) {
297
+ counts[warning.type] = (counts[warning.type] || 0) + 1;
298
+ }
299
+ return counts;
300
+ }
301
+ /**
302
+ * Format validation result as markdown
303
+ */
304
+ formatValidationResultAsMarkdown(result) {
305
+ const lines = [];
306
+ const timestamp = new Date();
307
+ const dateStr = timestamp.toLocaleDateString('en-US', {
308
+ weekday: 'long',
309
+ year: 'numeric',
310
+ month: 'long',
311
+ day: 'numeric'
312
+ });
313
+ const timeStr = timestamp.toLocaleTimeString('en-US', {
314
+ hour: '2-digit',
315
+ minute: '2-digit',
316
+ second: '2-digit'
317
+ });
318
+ // Header with branding
319
+ lines.push('# 🚀 MemberJunction Metadata Sync');
320
+ lines.push('## Validation Report');
321
+ lines.push('');
322
+ lines.push(`📅 **Date:** ${dateStr} `);
323
+ lines.push(`🕐 **Time:** ${timeStr} `);
324
+ lines.push(`📍 **Directory:** \`${process.cwd()}\` `);
325
+ lines.push('');
326
+ // Table of Contents
327
+ lines.push('## 📑 Table of Contents');
328
+ lines.push('');
329
+ lines.push('- [Executive Summary](#executive-summary)');
330
+ lines.push('- [Validation Results](#validation-results)');
331
+ if (result.errors.length > 0 || result.warnings.length > 0) {
332
+ lines.push('- [Issue Analysis](#issue-analysis)');
333
+ }
334
+ lines.push('- [File-by-File Breakdown](#file-by-file-breakdown)');
335
+ if (result.errors.length > 0) {
336
+ lines.push('- [Error Details](#error-details)');
337
+ }
338
+ if (result.warnings.length > 0) {
339
+ lines.push('- [Warning Details](#warning-details)');
340
+ }
341
+ lines.push('- [Next Steps](#next-steps)');
342
+ lines.push('- [Resources](#resources)');
343
+ lines.push('');
344
+ // Executive Summary
345
+ lines.push('## 📊 Executive Summary');
346
+ lines.push('');
347
+ const statusEmoji = result.isValid ? '✅' : '❌';
348
+ const statusText = result.isValid ? 'PASSED' : 'FAILED';
349
+ const statusColor = result.isValid ? 'green' : 'red';
350
+ lines.push(`### Overall Status: ${statusEmoji} **${statusText}**`);
351
+ lines.push('');
352
+ if (result.isValid) {
353
+ lines.push('> 🎉 **Congratulations!** Your metadata validation passed with no errors.');
354
+ }
355
+ else {
356
+ lines.push(`> ⚠️ **Action Required:** ${result.errors.length} error(s) need to be resolved before proceeding.`);
357
+ }
358
+ lines.push('');
359
+ // Quick Stats
360
+ lines.push('### 📈 Quick Statistics');
361
+ lines.push('');
362
+ lines.push(`**📁 Files Validated:** ${result.summary.totalFiles} `);
363
+ lines.push(`**📦 Entities Processed:** ${result.summary.totalEntities} `);
364
+ lines.push(`**❌ Errors Found:** ${result.summary.totalErrors} `);
365
+ lines.push(`**⚠️ Warnings Found:** ${result.summary.totalWarnings} `);
366
+ lines.push('');
367
+ // Validation Results
368
+ lines.push('## 🔍 Validation Results');
369
+ lines.push('');
370
+ // Issue Analysis
371
+ if (result.errors.length > 0 || result.warnings.length > 0) {
372
+ lines.push('## 📊 Issue Analysis');
373
+ lines.push('');
374
+ if (result.errors.length > 0) {
375
+ lines.push('### ❌ Error Distribution');
376
+ lines.push('');
377
+ const errorsByType = this.getErrorsByType(result.errors);
378
+ lines.push('<details>');
379
+ lines.push('<summary>Click to expand error breakdown</summary>');
380
+ lines.push('');
381
+ for (const [type, count] of Object.entries(errorsByType)) {
382
+ const percentage = ((count / result.errors.length) * 100).toFixed(1);
383
+ lines.push(`- **${type}**: ${count} errors (${percentage}%)`);
384
+ }
385
+ lines.push('');
386
+ lines.push('</details>');
387
+ lines.push('');
388
+ }
389
+ if (result.warnings.length > 0) {
390
+ lines.push('### ⚠️ Warning Distribution');
391
+ lines.push('');
392
+ const warningsByType = this.getWarningsByType(result.warnings);
393
+ lines.push('<details>');
394
+ lines.push('<summary>Click to expand warning breakdown</summary>');
395
+ lines.push('');
396
+ for (const [type, count] of Object.entries(warningsByType)) {
397
+ const percentage = ((count / result.warnings.length) * 100).toFixed(1);
398
+ lines.push(`- **${type}**: ${count} warnings (${percentage}%)`);
399
+ }
400
+ lines.push('');
401
+ lines.push('</details>');
402
+ lines.push('');
403
+ }
404
+ }
405
+ // File Results
406
+ lines.push('## 📁 File-by-File Breakdown');
407
+ lines.push('');
408
+ const sortedFiles = Array.from(result.summary.fileResults.entries())
409
+ .sort(([a], [b]) => {
410
+ // Sort by error count (descending), then warning count, then name
411
+ const aResult = result.summary.fileResults.get(a);
412
+ const bResult = result.summary.fileResults.get(b);
413
+ if (aResult.errors.length !== bResult.errors.length) {
414
+ return bResult.errors.length - aResult.errors.length;
415
+ }
416
+ if (aResult.warnings.length !== bResult.warnings.length) {
417
+ return bResult.warnings.length - aResult.warnings.length;
418
+ }
419
+ return a.localeCompare(b);
420
+ });
421
+ for (const [file, fileResult] of sortedFiles) {
422
+ const shortPath = this.shortenPath(file);
423
+ const hasErrors = fileResult.errors.length > 0;
424
+ const hasWarnings = fileResult.warnings.length > 0;
425
+ const icon = hasErrors ? '❌' : hasWarnings ? '⚠️' : '✅';
426
+ const status = hasErrors ? 'Has Errors' : hasWarnings ? 'Has Warnings' : 'Clean';
427
+ lines.push(`<details>`);
428
+ lines.push(`<summary>${icon} <strong>${shortPath}</strong> - ${status}</summary>`);
429
+ lines.push('');
430
+ lines.push('#### File Statistics');
431
+ lines.push(`- **Entities:** ${fileResult.entityCount}`);
432
+ lines.push(`- **Errors:** ${fileResult.errors.length}`);
433
+ lines.push(`- **Warnings:** ${fileResult.warnings.length}`);
434
+ if (hasErrors) {
435
+ lines.push('');
436
+ lines.push('#### Errors in this file:');
437
+ fileResult.errors.forEach((error, idx) => {
438
+ lines.push(`${idx + 1}. ${error.message}`);
439
+ });
440
+ }
441
+ if (hasWarnings) {
442
+ lines.push('');
443
+ lines.push('#### Warnings in this file:');
444
+ fileResult.warnings.forEach((warning, idx) => {
445
+ lines.push(`${idx + 1}. ${warning.message}`);
446
+ });
447
+ }
448
+ lines.push('');
449
+ lines.push('</details>');
450
+ lines.push('');
451
+ }
452
+ // Detailed Errors
453
+ if (result.errors.length > 0) {
454
+ lines.push('## ❌ Error Details');
455
+ lines.push('');
456
+ lines.push(`> Found ${result.errors.length} error(s) that must be fixed.`);
457
+ lines.push('');
458
+ result.errors.forEach((error, index) => {
459
+ lines.push(`### Error ${index + 1}: ${error.message}`);
460
+ lines.push('');
461
+ lines.push(`**Type:** \`${error.type}\` `);
462
+ if (error.entity)
463
+ lines.push(`**Entity:** ${error.entity} `);
464
+ if (error.field)
465
+ lines.push(`**Field:** \`${error.field}\` `);
466
+ lines.push(`**File:** \`${this.shortenPath(error.file)}\` `);
467
+ lines.push(`**Severity:** ${error.severity} `);
468
+ if (error.suggestion) {
469
+ lines.push('');
470
+ lines.push('> 💡 **Suggestion:** ' + error.suggestion);
471
+ }
472
+ lines.push('');
473
+ lines.push('---');
474
+ lines.push('');
475
+ });
476
+ }
477
+ // Detailed Warnings
478
+ if (result.warnings.length > 0) {
479
+ lines.push('## ⚠️ Warning Details');
480
+ lines.push('');
481
+ lines.push(`> Found ${result.warnings.length} warning(s) for your review.`);
482
+ lines.push('');
483
+ // Group warnings by type for better organization
484
+ const warningsByType = new Map();
485
+ result.warnings.forEach(warning => {
486
+ if (!warningsByType.has(warning.type)) {
487
+ warningsByType.set(warning.type, []);
488
+ }
489
+ warningsByType.get(warning.type).push(warning);
490
+ });
491
+ for (const [type, warnings] of warningsByType) {
492
+ lines.push(`### Warning Type: \`${type}\``);
493
+ lines.push('');
494
+ warnings.forEach((warning, index) => {
495
+ lines.push(`#### ${index + 1}. ${warning.message}`);
496
+ lines.push('');
497
+ if (warning.entity)
498
+ lines.push(`**Entity:** ${warning.entity} `);
499
+ if (warning.field)
500
+ lines.push(`**Field:** \`${warning.field}\` `);
501
+ lines.push(`**File:** \`${this.shortenPath(warning.file)}\` `);
502
+ if (warning.suggestion) {
503
+ lines.push('');
504
+ lines.push('> 💡 **Suggestion:** ' + warning.suggestion);
505
+ }
506
+ lines.push('');
507
+ });
508
+ }
509
+ }
510
+ // Next Steps
511
+ lines.push('## 🚀 Next Steps');
512
+ lines.push('');
513
+ if (result.errors.length > 0) {
514
+ lines.push('### To fix errors:');
515
+ lines.push('');
516
+ lines.push('1. Review each error in the [Error Details](#error-details) section');
517
+ lines.push('2. Follow the suggestions provided for each error');
518
+ lines.push('3. Run validation again after making changes');
519
+ lines.push('4. Repeat until all errors are resolved');
520
+ lines.push('');
521
+ }
522
+ if (result.warnings.length > 0) {
523
+ lines.push('### To address warnings:');
524
+ lines.push('');
525
+ lines.push('1. Review warnings in the [Warning Details](#warning-details) section');
526
+ lines.push('2. Determine which warnings are relevant to your use case');
527
+ lines.push('3. Apply suggested fixes where appropriate');
528
+ lines.push('');
529
+ }
530
+ if (result.isValid) {
531
+ lines.push('Your metadata is valid and ready to sync! 🎉');
532
+ lines.push('');
533
+ lines.push('```bash');
534
+ lines.push('# Push your metadata to the database');
535
+ lines.push('mj-sync push');
536
+ lines.push('```');
537
+ }
538
+ // Resources
539
+ lines.push('');
540
+ lines.push('## 📚 Resources');
541
+ lines.push('');
542
+ lines.push('- 📖 [MetadataSync Documentation](https://github.com/MemberJunction/MJ/tree/next/packages/MetadataSync)');
543
+ lines.push('- 🐛 [Report Issues](https://github.com/MemberJunction/MJ/issues)');
544
+ lines.push('- 💬 [MemberJunction Community](https://memberjunction.org)');
545
+ lines.push('- 📝 [Validation Rules Guide](https://github.com/MemberJunction/MJ/tree/next/packages/MetadataSync#validation-features)');
546
+ lines.push('');
547
+ // Footer
548
+ lines.push('---');
549
+ lines.push('');
550
+ lines.push('<div align="center">');
551
+ lines.push('');
552
+ lines.push('**Generated by [MemberJunction](https://memberjunction.org) Metadata Sync**');
553
+ lines.push('');
554
+ lines.push(`<sub>${timestamp.toISOString()}</sub>`);
555
+ lines.push('');
556
+ lines.push('</div>');
557
+ return lines.join('\n');
558
+ }
559
+ }
560
+ exports.FormattingService = FormattingService;
561
+ //# sourceMappingURL=FormattingService.js.map