properties-comparator 1.0.5 → 1.0.6

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,75 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+
6
+ # This workflow helps you trigger a SonarCloud analysis of your code and populates
7
+ # GitHub Code Scanning alerts with the vulnerabilities found.
8
+ # Free for open source project.
9
+
10
+ # 1. Login to SonarCloud.io using your GitHub account
11
+
12
+ # 2. Import your project on SonarCloud
13
+ # * Add your GitHub organization first, then add your repository as a new project.
14
+ # * Please note that many languages are eligible for automatic analysis,
15
+ # which means that the analysis will start automatically without the need to set up GitHub Actions.
16
+ # * This behavior can be changed in Administration > Analysis Method.
17
+ #
18
+ # 3. Follow the SonarCloud in-product tutorial
19
+ # * a. Copy/paste the Project Key and the Organization Key into the args parameter below
20
+ # (You'll find this information in SonarCloud. Click on "Information" at the bottom left)
21
+ #
22
+ # * b. Generate a new token and add it to your Github repository's secrets using the name SONAR_TOKEN
23
+ # (On SonarCloud, click on your avatar on top-right > My account > Security
24
+ # or go directly to https://sonarcloud.io/account/security/)
25
+
26
+ # Feel free to take a look at our documentation (https://docs.sonarcloud.io/getting-started/github/)
27
+ # or reach out to our community forum if you need some help (https://community.sonarsource.com/c/help/sc/9)
28
+
29
+ name: SonarCloud analysis
30
+
31
+ on:
32
+ push:
33
+ branches: ["main"]
34
+ pull_request:
35
+ branches: ["main"]
36
+ workflow_dispatch:
37
+
38
+ permissions:
39
+ pull-requests: read # allows SonarCloud to decorate PRs with analysis results
40
+
41
+ jobs:
42
+ Analysis:
43
+ runs-on: ubuntu-latest
44
+
45
+ steps:
46
+ - name: Checkout repository
47
+ uses: actions/checkout@v4
48
+ with:
49
+ fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
50
+
51
+ - name: Setup Node.js
52
+ uses: actions/setup-node@v4
53
+ with:
54
+ node-version: "20"
55
+
56
+ - name: Install dependencies
57
+ run: npm install
58
+
59
+ - name: Run tests with coverage
60
+ run: npm test -- --coverage
61
+
62
+ - name: Analyze with SonarCloud
63
+ uses: SonarSource/sonarcloud-github-action@4006f663ecaf1f8093e8e4abb9227f6041f52216
64
+ env:
65
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
66
+ with:
67
+ args: -Dsonar.projectKey=zackria_properties-comparator
68
+ -Dsonar.organization=zackria
69
+ -Dsonar.sources=index.js,cli.js,src
70
+ -Dsonar.tests=test
71
+ -Dsonar.test.inclusions=test/**/*.test.js
72
+ -Dsonar.exclusions=node_modules/**,coverage/**,index.js
73
+ -Dsonar.cpd.exclusions=index.js
74
+ -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info
75
+ projectBaseDir: .
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A powerful utility for parsing and comparing **.properties** and **.yml/.yaml** files. This tool reads files as key-value pairs, compares values across multiple files, and generates detailed comparison reports in various formats.
4
4
 
5
- [![NPM Package](https://img.shields.io/npm/v/properties-comparator.svg)](https://www.npmjs.com/package/properties-comparator)
5
+ [![NPM Package](https://img.shields.io/npm/v/properties-comparator.svg)](https://www.npmjs.com/package/properties-comparator) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=zackria_properties-comparator&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=zackria_properties-comparator)
6
6
 
7
7
  ## Features
8
8
 
@@ -79,8 +79,8 @@ This package is thoroughly tested with over 90% code coverage to ensure reliabil
79
79
  ## Compatibility
80
80
 
81
81
  Developed and tested with:
82
- - npm v11.1.0
83
- - Node.js v22.13.0
82
+ - npm v11.7.0
83
+ - Node.js v25.2.1
84
84
 
85
85
  ## Documentation
86
86
 
package/cli.js CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  import { program } from 'commander';
4
4
  import { compareFiles } from './index.js';
5
- import { readFileSync } from 'fs';
6
- import path from 'path';
5
+ import { readFileSync } from 'node:fs';
6
+ import path from 'node:path';
7
7
 
8
8
  // Get version from package.json
9
9
  let version = '1.0.0';
@@ -13,6 +13,7 @@ try {
13
13
  version = packageJson.version;
14
14
  } catch (err) {
15
15
  // Use default version if package.json can't be read
16
+ console.warn(`Warning: Could not read version from package.json (${err.message}). Using default 1.0.0`);
16
17
  }
17
18
 
18
19
  // Check if no arguments were provided
@@ -40,7 +41,7 @@ program
40
41
  try {
41
42
  // Resolve all file paths
42
43
  const resolvedPaths = files.map(file => path.resolve(file));
43
-
44
+
44
45
  // Prepare options for compareFiles
45
46
  const comparisonOptions = {
46
47
  format: options.format || 'console',
@@ -50,7 +51,7 @@ program
50
51
 
51
52
  // Run the comparison
52
53
  compareFiles(resolvedPaths, comparisonOptions);
53
-
54
+
54
55
  } catch (error) {
55
56
  console.error('Error:', error.message);
56
57
  process.exit(1);
package/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import chalk from "chalk";
4
- import fs from "fs";
5
- import path from "path";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
6
  import yaml from "js-yaml";
7
7
 
8
8
  /**
@@ -115,7 +115,7 @@ function parseFile(filePath) {
115
115
  default:
116
116
  console.error(
117
117
  `Warning: Unsupported file extension "${ext}" for file "${filePath}". ` +
118
- `Only .properties, .yml, or .yaml are supported. This file will be treated as empty.`
118
+ `Only .properties, .yml, or .yaml are supported. This file will be treated as empty.`
119
119
  );
120
120
  return {};
121
121
  }
@@ -147,7 +147,7 @@ function compareFileData(filePaths) {
147
147
  // Compare values for each key across files
148
148
  allKeys.forEach((key) => {
149
149
  const values = parsedObjects.map(
150
- (obj) => obj[key]?.replace(/\s+/g, "") || "N/A"
150
+ (obj) => obj[key]?.replaceAll(/\s+/g, "") || "N/A"
151
151
  );
152
152
  const matched = values.every((value) => value === values[0]);
153
153
  mismatchDetails.push({ key, values, matched });
@@ -224,10 +224,10 @@ function generateHtmlReport(filePaths, comparisonData) {
224
224
  <h2>Files Compared:</h2>
225
225
  <ol>
226
226
  ${fileNames
227
- .map(
228
- (name, idx) => `<li>${name} <small>(${filePaths[idx]})</small></li>`
229
- )
230
- .join("\n ")}
227
+ .map(
228
+ (name, idx) => `<li>${name} <small>(${filePaths[idx]})</small></li>`
229
+ )
230
+ .join("\n ")}
231
231
  </ol>
232
232
  </div>
233
233
 
@@ -237,8 +237,8 @@ function generateHtmlReport(filePaths, comparisonData) {
237
237
  <th>Key</th>
238
238
  <th>Matched</th>
239
239
  ${fileNames
240
- .map((name, idx) => `<th>File ${idx + 1}: ${name}</th>`)
241
- .join("\n ")}
240
+ .map((name, idx) => `<th>File ${idx + 1}: ${name}</th>`)
241
+ .join("\n ")}
242
242
  </tr>`;
243
243
 
244
244
  // Add table rows for each key
@@ -250,9 +250,8 @@ function generateHtmlReport(filePaths, comparisonData) {
250
250
  // Add values from each file
251
251
  values.forEach((value, idx) => {
252
252
  const cellClass = matched ? "" : "value-mismatch";
253
- html += `\n <td class="${cellClass}">${
254
- value === "N/A" ? "<em>N/A</em>" : value
255
- }</td>`;
253
+ html += `\n <td class="${cellClass}">${value === "N/A" ? "<em>N/A</em>" : value
254
+ }</td>`;
256
255
  });
257
256
 
258
257
  html += `\n </tr>`;
@@ -268,9 +267,9 @@ function generateHtmlReport(filePaths, comparisonData) {
268
267
  } else {
269
268
  html += `\n <p>${mismatchCount} key(s) have mismatched values.</p>
270
269
  <p><strong>Mismatched keys:</strong> ${mismatchDetails
271
- .filter((detail) => !detail.matched)
272
- .map((detail) => detail.key)
273
- .join(", ")}</p>`;
270
+ .filter((detail) => !detail.matched)
271
+ .map((detail) => detail.key)
272
+ .join(", ")}</p>`;
274
273
  }
275
274
 
276
275
  html += `\n </div>
@@ -419,46 +418,67 @@ function compareFiles(filePaths, options = {}) {
419
418
  }
420
419
 
421
420
  /**
422
- * CLI entry point for comparing .properties and .yml/.yaml files.
421
+ * Prints the usage information for the CLI.
423
422
  */
424
- function run() {
425
- const args = process.argv.slice(2);
423
+ function printUsage() {
424
+ console.error("Please provide file paths as command-line arguments.");
425
+ console.error(
426
+ "Usage: properties-comparator [options] file1 file2 [file3...]"
427
+ );
428
+ console.error("Options:");
429
+ console.error(
430
+ " --format, -f <format> Output format: console, html, or markdown"
431
+ );
432
+ console.error(
433
+ " --output, -o <file> Output file for html or markdown reports"
434
+ );
435
+ }
436
+
437
+ /**
438
+ * Parses command-line arguments.
439
+ * @param {string[]} args - Array of command-line arguments.
440
+ * @returns {{ filePaths: string[], options: Object }} - Parsed paths and options.
441
+ */
442
+ function parseArgs(args) {
426
443
  const options = {
427
444
  format: "console",
428
445
  outputFile: null,
429
446
  };
430
-
431
- // Parse arguments for format and output file
432
447
  const filePaths = [];
433
- for (let i = 0; i < args.length; i++) {
448
+
449
+ let i = 0;
450
+ while (i < args.length) {
434
451
  if (args[i] === "--format" || args[i] === "-f") {
435
452
  if (i + 1 < args.length) {
436
453
  options.format = args[i + 1].toLowerCase();
437
- i++; // Skip the next argument as it's the format value
454
+ i += 2;
455
+ } else {
456
+ i++;
438
457
  }
439
458
  } else if (args[i] === "--output" || args[i] === "-o") {
440
459
  if (i + 1 < args.length) {
441
460
  options.outputFile = args[i + 1];
442
- i++; // Skip the next argument as it's the output file path
461
+ i += 2;
462
+ } else {
463
+ i++;
443
464
  }
444
465
  } else {
445
- // Not an option, treat as file path
446
466
  filePaths.push(path.resolve(args[i]));
467
+ i++;
447
468
  }
448
469
  }
449
470
 
471
+ return { filePaths, options };
472
+ }
473
+
474
+ /**
475
+ * CLI entry point for comparing .properties and .yml/.yaml files.
476
+ */
477
+ function run() {
478
+ const { filePaths, options } = parseArgs(process.argv.slice(2));
479
+
450
480
  if (filePaths.length === 0) {
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
- );
481
+ printUsage();
462
482
  process.exit(1);
463
483
  } else if (filePaths.length === 1) {
464
484
  console.error("Please provide at least two file paths for comparison.");
@@ -481,27 +501,27 @@ function run() {
481
501
  * @param {Object} options - Comparison options
482
502
  * @returns {Object} Comparison results in a structured format
483
503
  */
484
- async function compareProperties(file1, file2, options = {}) {
504
+ function compareProperties(file1, file2, options = {}) {
485
505
  const filePaths = [file1, file2];
486
506
  const comparisonData = compareFileData(filePaths);
487
-
507
+
488
508
  // Process the output based on options
489
509
  if (options.output) {
490
510
  if (options.json) {
491
511
  fs.writeFileSync(options.output, JSON.stringify(comparisonData, null, 2));
492
512
  } else {
493
513
  const format = path.extname(options.output).toLowerCase() === '.md' ? 'markdown' : 'html';
494
- const report = format === 'markdown'
495
- ? generateMarkdownReport(filePaths, comparisonData)
514
+ const report = format === 'markdown'
515
+ ? generateMarkdownReport(filePaths, comparisonData)
496
516
  : generateHtmlReport(filePaths, comparisonData);
497
517
  fs.writeFileSync(options.output, report);
498
518
  }
499
-
519
+
500
520
  if (options.verbose) {
501
521
  console.log(`Comparison report saved to ${options.output}`);
502
522
  }
503
523
  }
504
-
524
+
505
525
  return comparisonData;
506
526
  }
507
527
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "properties-comparator",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
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
5
  "type": "module",
6
6
  "main": "index.js",
@@ -9,7 +9,8 @@
9
9
  },
10
10
  "scripts": {
11
11
  "test": "jest",
12
- "run": "node index.js"
12
+ "run": "node index.js",
13
+ "clean": "rm -rf node_modules coverage"
13
14
  },
14
15
  "repository": {
15
16
  "type": "git",
@@ -28,15 +29,18 @@
28
29
  },
29
30
  "homepage": "https://github.com/zackria/properties-comparator#readme",
30
31
  "dependencies": {
31
- "chalk": "^5.4.1",
32
- "commander": "^13.1.0",
33
- "js-yaml": "^4.1.0"
32
+ "chalk": "^5.6.2",
33
+ "commander": "^14.0.2",
34
+ "js-yaml": "^4.1.1"
34
35
  },
35
36
  "devDependencies": {
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"
37
+ "@babel/core": "^7.28.5",
38
+ "@babel/preset-env": "^7.28.5",
39
+ "babel-jest": "^30.2.0",
40
+ "jest": "^30.2.0",
41
+ "jest-environment-node": "^30.2.0"
42
+ },
43
+ "overrides": {
44
+ "test-exclude": "^7.0.1"
41
45
  }
42
- }
46
+ }
@@ -0,0 +1,19 @@
1
+ sonar.projectKey=zackria_properties-comparator
2
+ sonar.organization=zackria
3
+
4
+ # This is the name and version displayed in the SonarCloud UI.
5
+ #sonar.projectName=properties-comparator
6
+ #sonar.projectVersion=1.0.6
7
+
8
+ # Path is relative to the sonarproject.properties file. Replace "\" by "/" on Windows.
9
+ sonar.sources=index.js,cli.js,src
10
+ sonar.tests=test
11
+ sonar.test.inclusions=test/**/*.test.js
12
+
13
+ # Encoding of the source code. Default is default system encoding
14
+ sonar.sourceEncoding=UTF-8
15
+
16
+ # JavaScript-specific configurations
17
+ sonar.javascript.lcov.reportPaths=coverage/lcov.info
18
+ sonar.exclusions=node_modules/**, coverage/**, index.js
19
+ sonar.cpd.exclusions=index.js
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import chalk from "chalk";
4
- import fs from "fs";
5
- import path from "path";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
6
  import yaml from "js-yaml";
7
7
 
8
8
  /**
@@ -115,7 +115,7 @@ function parseFile(filePath) {
115
115
  default:
116
116
  console.error(
117
117
  `Warning: Unsupported file extension "${ext}" for file "${filePath}". ` +
118
- `Only .properties, .yml, or .yaml are supported. This file will be treated as empty.`
118
+ `Only .properties, .yml, or .yaml are supported. This file will be treated as empty.`
119
119
  );
120
120
  return {};
121
121
  }
@@ -147,7 +147,7 @@ function compareFileData(filePaths) {
147
147
  // Compare values for each key across files
148
148
  allKeys.forEach((key) => {
149
149
  const values = parsedObjects.map(
150
- (obj) => obj[key]?.replace(/\s+/g, "") || "N/A"
150
+ (obj) => obj[key]?.replaceAll(/\s+/g, "") || "N/A"
151
151
  );
152
152
  const matched = values.every((value) => value === values[0]);
153
153
  mismatchDetails.push({ key, values, matched });
@@ -224,10 +224,10 @@ function generateHtmlReport(filePaths, comparisonData) {
224
224
  <h2>Files Compared:</h2>
225
225
  <ol>
226
226
  ${fileNames
227
- .map(
228
- (name, idx) => `<li>${name} <small>(${filePaths[idx]})</small></li>`
229
- )
230
- .join("\n ")}
227
+ .map(
228
+ (name, idx) => `<li>${name} <small>(${filePaths[idx]})</small></li>`
229
+ )
230
+ .join("\n ")}
231
231
  </ol>
232
232
  </div>
233
233
 
@@ -237,8 +237,8 @@ function generateHtmlReport(filePaths, comparisonData) {
237
237
  <th>Key</th>
238
238
  <th>Matched</th>
239
239
  ${fileNames
240
- .map((name, idx) => `<th>File ${idx + 1}: ${name}</th>`)
241
- .join("\n ")}
240
+ .map((name, idx) => `<th>File ${idx + 1}: ${name}</th>`)
241
+ .join("\n ")}
242
242
  </tr>`;
243
243
 
244
244
  // Add table rows for each key
@@ -250,9 +250,8 @@ function generateHtmlReport(filePaths, comparisonData) {
250
250
  // Add values from each file
251
251
  values.forEach((value, idx) => {
252
252
  const cellClass = matched ? "" : "value-mismatch";
253
- html += `\n <td class="${cellClass}">${
254
- value === "N/A" ? "<em>N/A</em>" : value
255
- }</td>`;
253
+ html += `\n <td class="${cellClass}">${value === "N/A" ? "<em>N/A</em>" : value
254
+ }</td>`;
256
255
  });
257
256
 
258
257
  html += `\n </tr>`;
@@ -268,9 +267,9 @@ function generateHtmlReport(filePaths, comparisonData) {
268
267
  } else {
269
268
  html += `\n <p>${mismatchCount} key(s) have mismatched values.</p>
270
269
  <p><strong>Mismatched keys:</strong> ${mismatchDetails
271
- .filter((detail) => !detail.matched)
272
- .map((detail) => detail.key)
273
- .join(", ")}</p>`;
270
+ .filter((detail) => !detail.matched)
271
+ .map((detail) => detail.key)
272
+ .join(", ")}</p>`;
274
273
  }
275
274
 
276
275
  html += `\n </div>
@@ -419,46 +418,67 @@ function compareFiles(filePaths, options = {}) {
419
418
  }
420
419
 
421
420
  /**
422
- * CLI entry point for comparing .properties and .yml/.yaml files.
421
+ * Prints the usage information for the CLI.
423
422
  */
424
- function run() {
425
- const args = process.argv.slice(2);
423
+ function printUsage() {
424
+ console.error("Please provide file paths as command-line arguments.");
425
+ console.error(
426
+ "Usage: properties-comparator [options] file1 file2 [file3...]"
427
+ );
428
+ console.error("Options:");
429
+ console.error(
430
+ " --format, -f <format> Output format: console, html, or markdown"
431
+ );
432
+ console.error(
433
+ " --output, -o <file> Output file for html or markdown reports"
434
+ );
435
+ }
436
+
437
+ /**
438
+ * Parses command-line arguments.
439
+ * @param {string[]} args - Array of command-line arguments.
440
+ * @returns {{ filePaths: string[], options: Object }} - Parsed paths and options.
441
+ */
442
+ function parseArgs(args) {
426
443
  const options = {
427
444
  format: "console",
428
445
  outputFile: null,
429
446
  };
430
-
431
- // Parse arguments for format and output file
432
447
  const filePaths = [];
433
- for (let i = 0; i < args.length; i++) {
448
+
449
+ let i = 0;
450
+ while (i < args.length) {
434
451
  if (args[i] === "--format" || args[i] === "-f") {
435
452
  if (i + 1 < args.length) {
436
453
  options.format = args[i + 1].toLowerCase();
437
- i++; // Skip the next argument as it's the format value
454
+ i += 2;
455
+ } else {
456
+ i++;
438
457
  }
439
458
  } else if (args[i] === "--output" || args[i] === "-o") {
440
459
  if (i + 1 < args.length) {
441
460
  options.outputFile = args[i + 1];
442
- i++; // Skip the next argument as it's the output file path
461
+ i += 2;
462
+ } else {
463
+ i++;
443
464
  }
444
465
  } else {
445
- // Not an option, treat as file path
446
466
  filePaths.push(path.resolve(args[i]));
467
+ i++;
447
468
  }
448
469
  }
449
470
 
471
+ return { filePaths, options };
472
+ }
473
+
474
+ /**
475
+ * CLI entry point for comparing .properties and .yml/.yaml files.
476
+ */
477
+ function run() {
478
+ const { filePaths, options } = parseArgs(process.argv.slice(2));
479
+
450
480
  if (filePaths.length === 0) {
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
- );
481
+ printUsage();
462
482
  process.exit(1);
463
483
  } else if (filePaths.length === 1) {
464
484
  console.error("Please provide at least two file paths for comparison.");
@@ -481,27 +501,27 @@ function run() {
481
501
  * @param {Object} options - Comparison options
482
502
  * @returns {Object} Comparison results in a structured format
483
503
  */
484
- async function compareProperties(file1, file2, options = {}) {
504
+ function compareProperties(file1, file2, options = {}) {
485
505
  const filePaths = [file1, file2];
486
506
  const comparisonData = compareFileData(filePaths);
487
-
507
+
488
508
  // Process the output based on options
489
509
  if (options.output) {
490
510
  if (options.json) {
491
511
  fs.writeFileSync(options.output, JSON.stringify(comparisonData, null, 2));
492
512
  } else {
493
513
  const format = path.extname(options.output).toLowerCase() === '.md' ? 'markdown' : 'html';
494
- const report = format === 'markdown'
495
- ? generateMarkdownReport(filePaths, comparisonData)
514
+ const report = format === 'markdown'
515
+ ? generateMarkdownReport(filePaths, comparisonData)
496
516
  : generateHtmlReport(filePaths, comparisonData);
497
517
  fs.writeFileSync(options.output, report);
498
518
  }
499
-
519
+
500
520
  if (options.verbose) {
501
521
  console.log(`Comparison report saved to ${options.output}`);
502
522
  }
503
523
  }
504
-
524
+
505
525
  return comparisonData;
506
526
  }
507
527