properties-comparator 1.0.4 → 1.0.5

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.
package/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require("fs");
4
- const path = require("path");
5
- const yaml = require("js-yaml");
3
+ import chalk from "chalk";
4
+ import fs from "fs";
5
+ import path from "path";
6
+ import yaml from "js-yaml";
6
7
 
7
8
  /**
8
9
  * Parses a .properties file into an object.
@@ -182,34 +183,238 @@ function getMismatchFields(filePaths) {
182
183
  }
183
184
 
184
185
  /**
185
- * CLI function: compares properties/keys across multiple files,
186
- * prints details to the console, and provides a summary.
186
+ * Generates an HTML report for the comparison results.
187
187
  *
188
- * @param {string[]} filePaths - Array of file paths.
188
+ * @param {Array} filePaths - Array of file paths that were compared
189
+ * @param {Object} comparisonData - The output from compareFileData function
190
+ * @returns {string} - HTML document as string
189
191
  */
190
- function compareFiles(filePaths) {
191
- console.log("Comparing properties/keys across files:\n");
192
+ function generateHtmlReport(filePaths, comparisonData) {
193
+ const { mismatchCount, mismatchDetails } = comparisonData;
194
+ const fileNames = filePaths.map((fp) => path.basename(fp));
195
+
196
+ // Start HTML document
197
+ let html = `<!DOCTYPE html>
198
+ <html lang="en">
199
+ <head>
200
+ <meta charset="UTF-8">
201
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
202
+ <title>Properties Comparison Report</title>
203
+ <style>
204
+ body { font-family: Arial, sans-serif; margin: 20px; line-height: 1.6; color: #333; }
205
+ h1, h2 { color: #0066cc; }
206
+ table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }
207
+ th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
208
+ th { background-color: #f2f2f2; }
209
+ tr:nth-child(even) { background-color: #f9f9f9; }
210
+ tr:hover { background-color: #f2f2f2; }
211
+ .mismatch { background-color: #ffe6e6; }
212
+ .matched { background-color: #e6ffe6; }
213
+ .value-mismatch { color: #cc0000; font-weight: bold; }
214
+ .summary { margin: 20px 0; padding: 15px; border-radius: 5px; }
215
+ .summary.success { background-color: #e6ffe6; border: 1px solid #99cc99; }
216
+ .summary.error { background-color: #ffe6e6; border: 1px solid #cc9999; }
217
+ .file-list { margin-bottom: 20px; }
218
+ </style>
219
+ </head>
220
+ <body>
221
+ <h1>Properties Comparison Report</h1>
222
+
223
+ <div class="file-list">
224
+ <h2>Files Compared:</h2>
225
+ <ol>
226
+ ${fileNames
227
+ .map(
228
+ (name, idx) => `<li>${name} <small>(${filePaths[idx]})</small></li>`
229
+ )
230
+ .join("\n ")}
231
+ </ol>
232
+ </div>
192
233
 
193
- const { mismatchCount, mismatchDetails } = compareFileData(filePaths);
234
+ <h2>Comparison Results</h2>
235
+ <table>
236
+ <tr>
237
+ <th>Key</th>
238
+ <th>Matched</th>
239
+ ${fileNames
240
+ .map((name, idx) => `<th>File ${idx + 1}: ${name}</th>`)
241
+ .join("\n ")}
242
+ </tr>`;
194
243
 
195
- // Print detail for each key
244
+ // Add table rows for each key
196
245
  mismatchDetails.forEach(({ key, values, matched }) => {
197
- if (matched) {
198
- console.log(`Key: ${key} - Values match: '${values[0]}'`);
199
- } else {
200
- console.log(`Key: ${key} - Mismatched values:`);
201
- values.forEach((value, idx) => {
202
- console.log(` File ${idx + 1} (${filePaths[idx]}): ${value}`);
203
- });
204
- }
246
+ html += `\n <tr class="${matched ? "matched" : "mismatch"}">
247
+ <td>${key}</td>
248
+ <td>${matched ? "Yes" : "No"}</td>`;
249
+
250
+ // Add values from each file
251
+ values.forEach((value, idx) => {
252
+ const cellClass = matched ? "" : "value-mismatch";
253
+ html += `\n <td class="${cellClass}">${
254
+ value === "N/A" ? "<em>N/A</em>" : value
255
+ }</td>`;
256
+ });
257
+
258
+ html += `\n </tr>`;
259
+ });
260
+
261
+ html += `\n </table>
262
+
263
+ <div class="summary ${mismatchCount === 0 ? "success" : "error"}">
264
+ <h2>Summary</h2>`;
265
+
266
+ if (mismatchCount === 0) {
267
+ html += `\n <p>All properties match across all files!</p>`;
268
+ } else {
269
+ html += `\n <p>${mismatchCount} key(s) have mismatched values.</p>
270
+ <p><strong>Mismatched keys:</strong> ${mismatchDetails
271
+ .filter((detail) => !detail.matched)
272
+ .map((detail) => detail.key)
273
+ .join(", ")}</p>`;
274
+ }
275
+
276
+ html += `\n </div>
277
+ </body>
278
+ </html>`;
279
+
280
+ return html;
281
+ }
282
+
283
+ /**
284
+ * Generates a Markdown report for the comparison results.
285
+ *
286
+ * @param {Array} filePaths - Array of file paths that were compared
287
+ * @param {Object} comparisonData - The output from compareFileData function
288
+ * @returns {string} - Markdown document as string
289
+ */
290
+ function generateMarkdownReport(filePaths, comparisonData) {
291
+ const { mismatchCount, mismatchDetails } = comparisonData;
292
+ const fileNames = filePaths.map((fp) => path.basename(fp));
293
+
294
+ let markdown = `# Properties Comparison Report\n\n`;
295
+
296
+ // Files compared
297
+ markdown += `## Files Compared\n\n`;
298
+ filePaths.forEach((fp, idx) => {
299
+ markdown += `${idx + 1}. ${fileNames[idx]} (${fp})\n`;
300
+ });
301
+
302
+ // Comparison results table
303
+ markdown += `\n## Comparison Results\n\n`;
304
+
305
+ // Table header
306
+ markdown += `| Key | Matched | ${fileNames
307
+ .map((name, idx) => `File ${idx + 1}: ${name}`)
308
+ .join(" | ")} |\n`;
309
+ markdown += `| --- | --- | ${fileNames.map(() => "---").join(" | ")} |\n`;
310
+
311
+ // Table content
312
+ mismatchDetails.forEach(({ key, values, matched }) => {
313
+ markdown += `| ${key} | ${matched ? "Yes" : "No"} | ${values
314
+ .map((v) => (v === "N/A" ? "*N/A*" : v))
315
+ .join(" | ")} |\n`;
205
316
  });
206
317
 
207
318
  // Summary
208
- console.log("\n=== Summary ===");
319
+ markdown += `\n## Summary\n\n`;
209
320
  if (mismatchCount === 0) {
210
- console.log("All properties match across all files!");
321
+ markdown += `✅ All properties match across all files!\n`;
211
322
  } else {
212
- console.log(`${mismatchCount} key(s) have mismatched values.`);
323
+ markdown += `❌ ${mismatchCount} key(s) have mismatched values.\n\n`;
324
+ markdown += `**Mismatched keys:** ${mismatchDetails
325
+ .filter((detail) => !detail.matched)
326
+ .map((detail) => detail.key)
327
+ .join(", ")}\n`;
328
+ }
329
+
330
+ return markdown;
331
+ }
332
+
333
+ /**
334
+ * CLI function: compares properties/keys across multiple files,
335
+ * prints details to the console in a tabular format, and provides a summary.
336
+ *
337
+ * @param {string[]} filePaths - Array of file paths.
338
+ * @param {Object} options - Options for the comparison.
339
+ * @param {string} [options.format] - Output format ('console', 'html', or 'markdown').
340
+ * @param {string} [options.outputFile] - Path to save the report (for html and markdown).
341
+ */
342
+ function compareFiles(filePaths, options = {}) {
343
+ const format = options.format || "console";
344
+ const outputFile = options.outputFile;
345
+
346
+ const comparisonData = compareFileData(filePaths);
347
+
348
+ if (format === "console") {
349
+ console.log("Comparing properties/keys across files:\n");
350
+
351
+ // Prepare data for tabular output
352
+ const tableData = comparisonData.mismatchDetails.map(
353
+ ({ key, values, matched }) => {
354
+ const valueColumns = values.reduce((acc, value, idx) => {
355
+ acc[`File ${idx + 1}`] = value;
356
+ return acc;
357
+ }, {});
358
+ return {
359
+ Key: key,
360
+ Matched: matched ? "Yes" : "No",
361
+ ...valueColumns,
362
+ };
363
+ }
364
+ );
365
+
366
+ // Print the table
367
+ console.table(tableData);
368
+
369
+ // Custom print for mismatched rows
370
+ console.log("\n=== Highlighted Mismatched Rows ===");
371
+ comparisonData.mismatchDetails.forEach(({ key, values, matched }) => {
372
+ if (!matched) {
373
+ const coloredValues = values.map((value, idx) =>
374
+ chalk.red(`File ${idx + 1}: ${value}`)
375
+ );
376
+ console.log(
377
+ chalk.yellow(`Key: ${key}`),
378
+ "|",
379
+ coloredValues.join(" | ")
380
+ );
381
+ }
382
+ });
383
+
384
+ // Summary
385
+ console.log("\n=== Summary ===");
386
+ if (comparisonData.mismatchCount === 0) {
387
+ console.log("All properties match across all files!");
388
+ } else {
389
+ console.log(
390
+ `${comparisonData.mismatchCount} key(s) have mismatched values.`
391
+ );
392
+ const mismatchedKeys = comparisonData.mismatchDetails
393
+ .filter((detail) => !detail.matched)
394
+ .map((detail) => detail.key);
395
+ console.log("Mismatched keys:", mismatchedKeys.join(", "));
396
+ }
397
+ } else if (format === "html") {
398
+ const htmlReport = generateHtmlReport(filePaths, comparisonData);
399
+ if (outputFile) {
400
+ fs.writeFileSync(outputFile, htmlReport);
401
+ console.log(`HTML report saved to: ${outputFile}`);
402
+ } else {
403
+ console.log(htmlReport);
404
+ }
405
+ } else if (format === "markdown") {
406
+ const markdownReport = generateMarkdownReport(filePaths, comparisonData);
407
+ if (outputFile) {
408
+ fs.writeFileSync(outputFile, markdownReport);
409
+ console.log(`Markdown report saved to: ${outputFile}`);
410
+ } else {
411
+ console.log(markdownReport);
412
+ }
413
+ } else {
414
+ console.error(
415
+ `Unsupported format: ${format}. Using console output instead.`
416
+ );
417
+ compareFiles(filePaths); // Fallback to console output
213
418
  }
214
419
  }
215
420
 
@@ -217,26 +422,90 @@ function compareFiles(filePaths) {
217
422
  * CLI entry point for comparing .properties and .yml/.yaml files.
218
423
  */
219
424
  function run() {
220
- const filePaths = process.argv.slice(2);
425
+ const args = process.argv.slice(2);
426
+ const options = {
427
+ format: "console",
428
+ outputFile: null,
429
+ };
430
+
431
+ // Parse arguments for format and output file
432
+ const filePaths = [];
433
+ for (let i = 0; i < args.length; i++) {
434
+ if (args[i] === "--format" || args[i] === "-f") {
435
+ if (i + 1 < args.length) {
436
+ options.format = args[i + 1].toLowerCase();
437
+ i++; // Skip the next argument as it's the format value
438
+ }
439
+ } else if (args[i] === "--output" || args[i] === "-o") {
440
+ if (i + 1 < args.length) {
441
+ options.outputFile = args[i + 1];
442
+ i++; // Skip the next argument as it's the output file path
443
+ }
444
+ } else {
445
+ // Not an option, treat as file path
446
+ filePaths.push(path.resolve(args[i]));
447
+ }
448
+ }
221
449
 
222
450
  if (filePaths.length === 0) {
223
451
  console.error("Please provide file paths as command-line arguments.");
452
+ console.error(
453
+ "Usage: properties-comparator [options] file1 file2 [file3...]"
454
+ );
455
+ console.error("Options:");
456
+ console.error(
457
+ " --format, -f <format> Output format: console, html, or markdown"
458
+ );
459
+ console.error(
460
+ " --output, -o <file> Output file for html or markdown reports"
461
+ );
462
+ process.exit(1);
463
+ } else if (filePaths.length === 1) {
464
+ console.error("Please provide at least two file paths for comparison.");
224
465
  process.exit(1);
225
466
  }
226
467
 
227
- // Optionally, check if all files exist
228
468
  const missing = filePaths.filter((fp) => !fs.existsSync(fp));
229
469
  if (missing.length > 0) {
230
470
  console.error(`The following file(s) do not exist: ${missing.join(", ")}`);
231
- // We continue anyway, or we can decide to exit.
232
- // For now, let's exit to avoid unexpected comparisons:
233
471
  process.exit(1);
234
472
  }
235
473
 
236
- compareFiles(filePaths);
474
+ compareFiles(filePaths, options);
475
+ }
476
+
477
+ /**
478
+ * API function to compare properties between two files.
479
+ * @param {string} file1 - Path to the first file
480
+ * @param {string} file2 - Path to the second file
481
+ * @param {Object} options - Comparison options
482
+ * @returns {Object} Comparison results in a structured format
483
+ */
484
+ async function compareProperties(file1, file2, options = {}) {
485
+ const filePaths = [file1, file2];
486
+ const comparisonData = compareFileData(filePaths);
487
+
488
+ // Process the output based on options
489
+ if (options.output) {
490
+ if (options.json) {
491
+ fs.writeFileSync(options.output, JSON.stringify(comparisonData, null, 2));
492
+ } else {
493
+ const format = path.extname(options.output).toLowerCase() === '.md' ? 'markdown' : 'html';
494
+ const report = format === 'markdown'
495
+ ? generateMarkdownReport(filePaths, comparisonData)
496
+ : generateHtmlReport(filePaths, comparisonData);
497
+ fs.writeFileSync(options.output, report);
498
+ }
499
+
500
+ if (options.verbose) {
501
+ console.log(`Comparison report saved to ${options.output}`);
502
+ }
503
+ }
504
+
505
+ return comparisonData;
237
506
  }
238
507
 
239
- module.exports = {
508
+ export {
240
509
  parsePropertiesFile,
241
510
  parseYamlFile,
242
511
  parseFile,
@@ -244,10 +513,12 @@ module.exports = {
244
513
  checkIfAllValuesMatch,
245
514
  getMismatchFields,
246
515
  compareFiles,
247
- run,
516
+ generateHtmlReport,
517
+ generateMarkdownReport,
518
+ compareProperties, // Add the new function to exports
248
519
  };
249
520
 
250
521
  // If the script is executed directly, run the CLI
251
- if (require.main === module) {
522
+ if (import.meta.url === `file://${process.argv[1]}`) {
252
523
  run();
253
524
  }
package/jest.config.js CHANGED
@@ -1,22 +1,9 @@
1
- // jest.config.js
2
- module.exports = {
3
- // Tell Jest to collect coverage information
4
- collectCoverage: true,
5
- // Or specify which files to collect coverage from
6
- collectCoverageFrom: ['src/**/*.js'], // adjust for your source folder
7
-
8
- // Optionally set coverage thresholds
9
- coverageThreshold: {
10
- global: {
11
- branches: 80,
12
- functions: 80,
13
- lines: 80,
14
- statements: 80,
15
- },
16
- },
17
-
18
- // Customize output directory or coverage reporters if desired
19
- coverageDirectory: 'coverage',
20
- coverageReporters: ['json', 'lcov', 'text', 'clover'],
21
- };
22
-
1
+ export default {
2
+ testEnvironment: "node", // Use Node.js environment
3
+ transform: {
4
+ "^.+\\.js$": "babel-jest", // Transform ES modules with Babel
5
+ },
6
+ transformIgnorePatterns: [
7
+ "/node_modules/(?!(chalk|ansi-styles|supports-color|strip-ansi|ansi-regex)/)", // Allow specific ES module packages to be transformed
8
+ ],
9
+ };
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "properties-comparator",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "This utility provides functionality to parse and compare properties files in the format of key-value pairs. It reads properties files, compares the values for each key across multiple files, and logs the results.",
5
+ "type": "module",
5
6
  "main": "index.js",
6
7
  "bin": {
7
- "properties-comparator": "./index.js"
8
+ "properties-comparator": "./cli.js"
8
9
  },
9
10
  "scripts": {
10
11
  "test": "jest",
@@ -16,7 +17,9 @@
16
17
  },
17
18
  "keywords": [
18
19
  "properties",
19
- "comparator"
20
+ "compare",
21
+ "yaml",
22
+ "configuration"
20
23
  ],
21
24
  "author": "Zack Dawood",
22
25
  "license": "MIT",
@@ -25,9 +28,15 @@
25
28
  },
26
29
  "homepage": "https://github.com/zackria/properties-comparator#readme",
27
30
  "dependencies": {
31
+ "chalk": "^5.4.1",
32
+ "commander": "^13.1.0",
28
33
  "js-yaml": "^4.1.0"
29
34
  },
30
35
  "devDependencies": {
31
- "jest": "^29.7.0"
36
+ "@babel/core": "^7.26.10",
37
+ "@babel/preset-env": "^7.26.9",
38
+ "babel-jest": "^29.7.0",
39
+ "jest": "^29.7.0",
40
+ "jest-environment-node": "^29.7.0"
32
41
  }
33
42
  }