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/DOCUMENTATION.md +202 -69
- package/PUBLISH.MD +3 -1
- package/README.md +80 -9
- package/TEST.md +48 -1
- package/babel.config.js +3 -0
- package/cli.js +60 -0
- package/index.js +300 -29
- package/jest.config.js +9 -22
- package/package.json +13 -4
- package/src/compareUtility.js +520 -0
package/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
*
|
|
186
|
-
* prints details to the console, and provides a summary.
|
|
186
|
+
* Generates an HTML report for the comparison results.
|
|
187
187
|
*
|
|
188
|
-
* @param {
|
|
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
|
|
191
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
244
|
+
// Add table rows for each key
|
|
196
245
|
mismatchDetails.forEach(({ key, values, matched }) => {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
319
|
+
markdown += `\n## Summary\n\n`;
|
|
209
320
|
if (mismatchCount === 0) {
|
|
210
|
-
|
|
321
|
+
markdown += `✅ All properties match across all files!\n`;
|
|
211
322
|
} else {
|
|
212
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
522
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
252
523
|
run();
|
|
253
524
|
}
|
package/jest.config.js
CHANGED
|
@@ -1,22 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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.
|
|
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": "./
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
}
|