pomanalyzer 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.
package/.eslintrc.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "env": {
3
+ "browser": true,
4
+ "es2021": true,
5
+ "node": true
6
+ },
7
+ "extends": "eslint:recommended",
8
+ "parserOptions": {
9
+ "ecmaVersion": 12,
10
+ "sourceType": "module"
11
+ },
12
+ "rules": {
13
+ "indent": ["error", 2],
14
+ "linebreak-style": ["error", "unix"],
15
+ "quotes": ["error", "single"],
16
+ "semi": ["error", "always"],
17
+ "no-console": "warn",
18
+ "eqeqeq": ["error", "always"],
19
+ "curly": "error"
20
+ }
21
+ }
File without changes
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Zack Dawood
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/PUBLISH.md ADDED
@@ -0,0 +1,28 @@
1
+
2
+ ## Link your package locally for testing:
3
+
4
+ ### Test Locally
5
+ `npm link`
6
+
7
+ `npm unlink -g`
8
+
9
+ `npm link pomanalyzer`
10
+
11
+ ## Now you can run the script globally using:
12
+
13
+ ```pomanalyzer <filePath>```
14
+
15
+
16
+ ## Publish the Package
17
+ ### Login to npm (if you haven't already):
18
+ `npm login`
19
+
20
+ ### Publish the package:
21
+ `npm publish`
22
+
23
+
24
+ ### Install the Package Globally
25
+ Once published, you (or anyone) can install the package globally using:
26
+ `npm install -g pomanalyzer`
27
+
28
+ ### Check [Documentation](DOCUMENTATION.md) for more details
package/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # pomanalyzer
2
+
3
+ pomanalyzer is a utility to analyze Apache Maven POM XML files offline. It extracts and formats dependency information, detects duplicate dependencies, and generates reports in various formats.
4
+
5
+ [![NPM Package](https://img.shields.io/npm/v/pomanalyzer.svg)](https://www.npmjs.com/package/pomanalyzer)
6
+
7
+
8
+ ## Features
9
+
10
+ - Analyze Maven POM XML files to extract dependency information.
11
+ - Detect duplicate dependencies and summarize them.
12
+ - Generate reports in:
13
+ - **HTML** format.
14
+ - **Markdown** format.
15
+ - Customizable output folder for generated reports.
16
+
17
+
18
+ ## Installation using NPM
19
+
20
+ ```bash
21
+ # Install globally
22
+ npm install -g pomanalyzer
23
+
24
+ # To uninstall
25
+ npm uninstall -g pomanalyzer
26
+ ```
27
+
28
+ ## Installation in local
29
+
30
+ To install `pomanalyzer`, clone the repository and install the dependencies:
31
+
32
+ ```bash
33
+ git clone https://github.com/zackria/pomanalyzer.git
34
+ cd pomanalyzer
35
+ npm install
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ Run the `pomanalyzer` CLI tool with the following commands:
41
+
42
+ ### Basic Analysis
43
+
44
+ Analyze a POM file and display the dependencies in the console:
45
+
46
+ ```bash
47
+ pomanalyzer <pom.xmlfilepath>
48
+ ```
49
+
50
+ ### Generate HTML Report
51
+
52
+ Generate an HTML report of the dependencies:
53
+
54
+ ```bash
55
+ pomanalyzer --html <pom.xmlfilepath>
56
+ ```
57
+
58
+ ### Generate Markdown Report
59
+
60
+ Generate a Markdown report of the dependencies:
61
+
62
+ ```bash
63
+ pomanalyzer --markdown <pom.xmlfilepath>
64
+ ```
65
+
66
+ ### Generate Both HTML and Markdown Reports
67
+
68
+ Generate both HTML and Markdown reports:
69
+
70
+ ```bash
71
+ pomanalyzer --markdown --html <pom.xmlfilepath>
72
+ ```
73
+
74
+ ### Specify Output Folder
75
+
76
+ Generate reports and save them to a specific output folder:
77
+
78
+ ```bash
79
+ pomanalyzer --markdown --html --output-folder output <pom.xmlfilepath>
80
+ ```
81
+
82
+ ## Example Output
83
+
84
+ ### Console Output
85
+
86
+ ```plaintext
87
+ Analyzing POM file: <pom.xmlfilepath>
88
+ Maven Dependencies:
89
+ | Group ID | Artifact ID | Version |
90
+ |----------------|----------------|----------|
91
+ | org.example | example-lib | 1.0.0 |
92
+ | org.example | another-lib | 2.1.0 |
93
+
94
+ Duplicate Dependencies:
95
+ | Dependency | Versions |
96
+ |--------------------|----------------|
97
+ | org.example:lib | 1.0.0, 1.1.0 |
98
+ ```
99
+
100
+ ### HTML and Markdown Reports
101
+
102
+ Reports are saved in the specified output folder with filenames like:
103
+
104
+ - `Maven_Dependencies_report.html`
105
+ - `Maven_Dependencies_report.md`
106
+
107
+ ## License
108
+
109
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
110
+
111
+ ## Contributing
112
+
113
+ Contributions are welcome! Feel free to open issues or submit pull requests to improve the tool.
114
+
115
+ ## Support
116
+
117
+ For issues or feature requests, please visit the [GitHub Issues](https://github.com/zackria/pomanalyzer/issues) page.
118
+
119
+ ### Example Commands
120
+
121
+ ```bash
122
+ pomanalyzer ./test/Test1.xml
123
+ pomanalyzer --html ./test/Test1.xml
124
+ pomanalyzer --markdown ./test/Test1.xml
125
+ pomanalyzer --markdown --html ./test/Test1.xml
126
+ pomanalyzer --markdown --html --output-folder output ./test/Test1.xml
127
+ ```
128
+
129
+
130
+ ## Report Examples
131
+
132
+ ### Terminal View
133
+ ![Terminal Report](./screenshots/ConsoleOutput.png)
134
+
135
+ ### HTML View
136
+ ![HTML Report](./screenshots/HtmlOutput.png)
137
+
138
+ ### Markdown View
139
+ ![Markdown Report](./screenshots/MarkdownOutput.png)
140
+
141
+ ## Quality Assurance
142
+
143
+ This package is thoroughly tested with over 90% code coverage to ensure reliability.
144
+
145
+ ![Test Coverage](./screenshots/CodeCoverage.png)
146
+
147
+ ## Compatibility
148
+
149
+ Developed and tested with:
150
+ - npm v11.1.0
151
+ - Node.js v22.13.0
152
+
package/TEST.md ADDED
@@ -0,0 +1,43 @@
1
+ # Automated testing with Jest
2
+
3
+
4
+ ## Run Your Tests
5
+ `npm init -y # if you haven't created a package.json yet`
6
+ `npm install --save-dev jest`
7
+
8
+ `npm test`
9
+ or
10
+ `npx jest`
11
+
12
+
13
+ ### Check jest.config.js for testing configuration
14
+
15
+ `npm test -- --coverage`
16
+
17
+
18
+ `node src/index.js ./test/Test1.xml`
19
+
20
+ `node cli.js ./test/Test1.xml`
21
+
22
+ `node cli.js --html ./test/Test1.xml`
23
+
24
+ `node cli.js --markdown ./test/Test1.xml`
25
+
26
+ `node cli.js --markdown --html ./test/Test1.xml`
27
+
28
+ `node cli.js --markdown --html --output-folder output ./test/Test1.xml`
29
+
30
+ ### Test with NPM Link
31
+
32
+ `pomanalyzer ./test/Test1.xml`
33
+
34
+ `pomanalyzer ./test/Test1.xml`
35
+
36
+ `pomanalyzer --html ./test/Test1.xml`
37
+
38
+ `pomanalyzer --markdown ./test/Test1.xml`
39
+
40
+ `pomanalyzer --markdown --html ./test/Test1.xml`
41
+
42
+ `pomanalyzer --markdown --html --output-folder output ./test/Test1.xml`
43
+
@@ -0,0 +1,3 @@
1
+ export default {
2
+ presets: ['@babel/preset-env'],
3
+ };
package/cli.js ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander';
4
+ import { analyzePom } from './src/index.js';
5
+ import { readFileSync } from 'fs';
6
+ import path from 'path';
7
+
8
+ // Get version from package.json
9
+ let version = '1.0.0';
10
+
11
+ try {
12
+ const packageJson = JSON.parse(readFileSync('./package.json', 'utf8'));
13
+ version = packageJson.version;
14
+ } catch (err) {
15
+ // Use default version if package.json can't be read
16
+ }
17
+
18
+ // Check if no arguments were provided
19
+ if (process.argv.length <= 2) {
20
+ console.log(`pomanalyzer v${version}`);
21
+ console.log('Usage: pomanalyzer [options] <pomFile>');
22
+ process.exit(0);
23
+ }
24
+
25
+ program
26
+ .version(version)
27
+ .description('Analyze Maven POM XML files')
28
+ .option('--html', 'Generate HTML report')
29
+ .option('--markdown', 'Generate Markdown report')
30
+ .option('--output-folder <path>', 'Output folder for results')
31
+ .option('-v, --verbose', 'Show verbose output')
32
+ .arguments('<pomFile>')
33
+ .usage('[options] <pomFile>')
34
+ .action(async (pomFile, options) => {
35
+ try {
36
+ // Resolve file path
37
+ const resolvedPath = path.resolve(pomFile);
38
+
39
+ // Prepare options for analysis
40
+ const analysisOptions = {
41
+ html: options.html,
42
+ markdown: options.markdown,
43
+ outputFolder: options.outputFolder,
44
+ verbose: options.verbose
45
+ };
46
+
47
+ // Use the centralized analyzePom function
48
+ await analyzePom(resolvedPath, analysisOptions);
49
+
50
+ } catch (error) {
51
+ console.error('Error:', error.message);
52
+ process.exit(1);
53
+ }
54
+ });
55
+
56
+ program.parse(process.argv);
package/jest.config.js ADDED
@@ -0,0 +1,17 @@
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
+ testMatch: [
10
+ "**/tests/**/*.test.js",
11
+ "**/?(*.)+(spec|test).js"
12
+ ],
13
+ collectCoverage: true,
14
+ coverageDirectory: 'coverage',
15
+ coverageReporters: ['text', 'lcov'],
16
+ verbose: true,
17
+ };
@@ -0,0 +1,84 @@
1
+
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Maven Dependencies Report</title>
6
+ <style>
7
+ body { font-family: Arial, sans-serif; margin: 20px; }
8
+ h2 { color: #333; }
9
+ table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
10
+ th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
11
+ th { background-color: #f2f2f2; }
12
+ .duplicate { background-color: #ffcccc; }
13
+ </style>
14
+ </head>
15
+ <body>
16
+
17
+ <h2>Maven Dependencies</h2>
18
+ <table>
19
+ <thead>
20
+ <tr>
21
+ <th>groupId</th><th>artifactId</th><th>version</th>
22
+ </tr>
23
+ </thead>
24
+ <tbody>
25
+
26
+ <tr>
27
+ <td>org.springframework</td><td>spring-core</td><td>5.3.9</td>
28
+ </tr>
29
+
30
+ <tr>
31
+ <td>org.springframework</td><td>spring-core</td><td>5.3.9</td>
32
+ </tr>
33
+
34
+ <tr>
35
+ <td>org.apache.commons</td><td>commons-lang3</td><td>N/A</td>
36
+ </tr>
37
+
38
+ <tr>
39
+ <td>org.apache.commons</td><td>commons-lang3</td><td>N/A</td>
40
+ </tr>
41
+
42
+ <tr>
43
+ <td>junit</td><td>junit</td><td>4.13.2</td>
44
+ </tr>
45
+
46
+ <tr>
47
+ <td>com.google.guava</td><td>guava</td><td>N/A</td>
48
+ </tr>
49
+
50
+ <tr>
51
+ <td>com.google.guava</td><td>guava</td><td>N/A</td>
52
+ </tr>
53
+
54
+ </tbody>
55
+ </table>
56
+
57
+
58
+ <h2>Duplicate Dependencies</h2>
59
+ <table>
60
+ <thead>
61
+ <tr>
62
+ <th>dependency</th><th>versions</th>
63
+ </tr>
64
+ </thead>
65
+ <tbody>
66
+
67
+ <tr class="duplicate">
68
+ <td>org.springframework:spring-core</td><td>5.3.9, 5.3.9</td>
69
+ </tr>
70
+
71
+ <tr class="duplicate">
72
+ <td>org.apache.commons:commons-lang3</td><td>N/A, N/A</td>
73
+ </tr>
74
+
75
+ <tr class="duplicate">
76
+ <td>com.google.guava:guava</td><td>N/A, N/A</td>
77
+ </tr>
78
+
79
+ </tbody>
80
+ </table>
81
+
82
+ </body>
83
+ </html>
84
+
@@ -0,0 +1,24 @@
1
+ # Report
2
+
3
+
4
+ ## Maven Dependencies
5
+
6
+ | groupId | artifactId | version |
7
+ | --- | --- | --- |
8
+ | org.springframework | spring-core | 5.3.9 |
9
+ | org.springframework | spring-core | 5.3.9 |
10
+ | org.apache.commons | commons-lang3 | N/A |
11
+ | org.apache.commons | commons-lang3 | N/A |
12
+ | junit | junit | 4.13.2 |
13
+ | com.google.guava | guava | N/A |
14
+ | com.google.guava | guava | N/A |
15
+
16
+
17
+
18
+ ## Duplicate Dependencies
19
+
20
+ | dependency | versions |
21
+ | --- | --- |
22
+ | org.springframework:spring-core | 5.3.9, 5.3.9 | <!-- duplicate row -->
23
+ | org.apache.commons:commons-lang3 | N/A, N/A | <!-- duplicate row -->
24
+ | com.google.guava:guava | N/A, N/A | <!-- duplicate row -->
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "pomanalyzer",
3
+ "version": "1.0.0",
4
+ "description": "pomanalyzer is a utility to analyze Apache Maven POM XML file",
5
+ "keywords": [
6
+ "pom",
7
+ "xml",
8
+ "analyze",
9
+ "maven",
10
+ "depedencies"
11
+ ],
12
+ "homepage": "https://github.com/zackria/pomanalyzer#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/zackria/pomanalyzer/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/zackria/pomanalyzer.git"
19
+ },
20
+ "license": "MIT",
21
+ "author": "Zack Dawood",
22
+ "type": "module",
23
+ "main": "index.js",
24
+ "bin": {
25
+ "pomanalyzer": "./cli.js"
26
+ },
27
+ "scripts": {
28
+ "test": "jest",
29
+ "run": "node ./src/index.js"
30
+ },
31
+ "devDependencies": {
32
+ "@babel/core": "^7.26.10",
33
+ "@babel/preset-env": "^7.26.9",
34
+ "babel-jest": "^29.7.0",
35
+ "jest": "^29.7.0",
36
+ "jest-environment-node": "^29.7.0"
37
+ },
38
+ "dependencies": {
39
+ "chalk": "^5.4.1",
40
+ "commander": "^13.1.0",
41
+ "js-yaml": "^4.1.0",
42
+ "xml2js": "^0.6.2"
43
+ },
44
+ "engines": {
45
+ "node": ">=14.0.0"
46
+ }
47
+ }
Binary file
Binary file
Binary file
Binary file
package/src/index.js ADDED
@@ -0,0 +1,50 @@
1
+ import { readPomXml } from "./services/dependencyService.js";
2
+ import { checkForDuplicates } from "./services/duplicateChecker.js";
3
+ import { printTable } from "./utils/outputFormatter.js";
4
+ import { printHTMLTable } from "./utils/printHTMLTable.js";
5
+ import { printMarkdownTable } from "./utils/printMarkdownTable.js";
6
+ import chalk from "chalk"; // added Chalk import
7
+
8
+ export async function analyzePom(pomXmlPath, options = {}) {
9
+ try {
10
+ console.log(`Analyzing POM file: ${pomXmlPath}`);
11
+ const dependencies = await readPomXml(pomXmlPath, options);
12
+
13
+ if (dependencies && dependencies.length > 0) {
14
+ printTable(dependencies, "Maven Dependencies");
15
+
16
+ const duplicateDependencies = checkForDuplicates(dependencies);
17
+ let duplicateSummary = [];
18
+ if (duplicateDependencies.length > 0) {
19
+ duplicateSummary = duplicateDependencies.map(depList => ({
20
+ dependency: `${depList[0].groupId}:${depList[0].artifactId}`,
21
+ versions: depList.map(dep => dep.version).join(", "),
22
+ }));
23
+ }
24
+
25
+ // Generate additional reports if flags are provided.
26
+ if (options.html) {
27
+ printHTMLTable(dependencies, duplicateSummary, "Maven Dependencies", options.outputFolder);
28
+ }
29
+ if (options.markdown) {
30
+ printMarkdownTable(dependencies, duplicateSummary, "Maven Dependencies", options.outputFolder);
31
+ }
32
+
33
+
34
+ if (duplicateSummary.length > 0) {
35
+ console.log(chalk.yellow("Duplicate dependencies found:"));
36
+ duplicateSummary.forEach(dup => {
37
+ console.log(chalk.red(`${dup.dependency} -> ${dup.versions}`));
38
+ });
39
+ } else {
40
+ console.log("No duplicate dependencies found.");
41
+
42
+ }
43
+ } else {
44
+ console.log("No dependencies found in the POM file.");
45
+ }
46
+ } catch (error) {
47
+ console.error("Error analyzing POM file:", error);
48
+ throw error;
49
+ }
50
+ }
@@ -0,0 +1,61 @@
1
+ // src/services/dependencyService.js
2
+ import { readFile } from '../utils/fileUtils.js';
3
+ import { parseXml } from '../utils/xmlParser.js';
4
+ import { resolveVersion } from '../utils/dependencyResolver.js';
5
+
6
+ /**
7
+ * Reads and parses a Maven pom.xml file.
8
+ * @param {string} filePath - The path to the pom.xml file.
9
+ * @returns {Promise<Array>} - A promise that resolves to an array of dependencies.
10
+ */
11
+ export async function readPomXml(filePath) {
12
+ try {
13
+ const xmlContent = await readFile(filePath);
14
+ const result = await parseXml(xmlContent); // Use await here
15
+ const dependencies = extractDependencies(result);
16
+ return dependencies;
17
+ } catch (error) {
18
+ throw new Error(`Error reading pom.xml: ${error.message}`);
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Extracts dependencies from the parsed XML result.
24
+ * @param {Object} result - The parsed XML result.
25
+ * @returns {Array} - An array of dependencies.
26
+ */
27
+ function extractDependencies(result) {
28
+ const dependencies = [];
29
+
30
+ if (!result || !result.project) {
31
+ return dependencies;
32
+ }
33
+
34
+ const properties = result.project.properties ? result.project.properties[0] : {};
35
+
36
+ if (result.project.dependencies && result.project.dependencies[0] && result.project.dependencies[0].dependency) {
37
+ dependencies.push(
38
+ ...result.project.dependencies[0].dependency.map((dep) => ({
39
+ groupId: dep.groupId ? dep.groupId[0] : 'unknown',
40
+ artifactId: dep.artifactId ? dep.artifactId[0] : 'unknown',
41
+ version: resolveVersion(dep.version && dep.version[0] ? dep.version[0] : 'N/A', properties),
42
+ }))
43
+ );
44
+ }
45
+
46
+ if (result.project.dependencyManagement &&
47
+ result.project.dependencyManagement[0] &&
48
+ result.project.dependencyManagement[0].dependencies &&
49
+ result.project.dependencyManagement[0].dependencies[0] &&
50
+ result.project.dependencyManagement[0].dependencies[0].dependency) {
51
+ dependencies.push(
52
+ ...result.project.dependencyManagement[0].dependencies[0].dependency.map((dep) => ({
53
+ groupId: dep.groupId ? dep.groupId[0] : 'unknown',
54
+ artifactId: dep.artifactId ? dep.artifactId[0] : 'unknown',
55
+ version: resolveVersion(dep.version && dep.version[0] ? dep.version[0] : 'N/A', properties),
56
+ }))
57
+ );
58
+ }
59
+
60
+ return dependencies;
61
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Identifies duplicate dependencies based on groupId and artifactId.
3
+ * @param {Array} dependencies - List of dependencies from the POM file.
4
+ * @returns {Array} - List of duplicate dependencies grouped by groupId:artifactId.
5
+ */
6
+ export function checkForDuplicates(dependencies) {
7
+ const duplicates = dependencies.reduce((acc, dep) => {
8
+ const key = `${dep.groupId}:${dep.artifactId}`;
9
+ if (!acc[key]) {
10
+ acc[key] = [];
11
+ }
12
+ acc[key].push(dep);
13
+ return acc;
14
+ }, {});
15
+
16
+ return Object.values(duplicates).filter((depList) => depList.length > 1);
17
+ }
@@ -0,0 +1,28 @@
1
+ // src/utils/dependencyResolver.js
2
+
3
+ /**
4
+ * Resolves a Maven-style property placeholder in a version string.
5
+ *
6
+ * @param {string|null} version - The version string that may contain a property placeholder (e.g., "${project.version}")
7
+ * @param {Object} properties - An object containing property key-value pairs from the Maven POM file
8
+ * @returns {string} The resolved version string or "N/A" if the version couldn't be resolved
9
+ *
10
+ * @example
11
+ * // If properties = { "project.version": ["1.0.0"] }
12
+ * resolveVersion("${project.version}", properties) // returns "1.0.0"
13
+ *
14
+ * @example
15
+ * // If the property doesn't exist
16
+ * resolveVersion("${unknown.property}", properties) // returns "N/A"
17
+ *
18
+ * @example
19
+ * // If version is a regular string
20
+ * resolveVersion("2.1.0", properties) // returns "2.1.0"
21
+ */
22
+ export const resolveVersion = (version, properties) => {
23
+ if (version && version.startsWith('${') && version.endsWith('}')) {
24
+ const propertyName = version.slice(2, -1);
25
+ return properties[propertyName] ? properties[propertyName][0] : 'N/A';
26
+ }
27
+ return version || 'N/A';
28
+ };