Skip to content

Commit

Permalink
feat: use properties of object entries as custom field (#194)
Browse files Browse the repository at this point in the history
Example: use custom field 'repository.url'.
  • Loading branch information
BePo65 authored Sep 13, 2024
1 parent d20c4a4 commit d9519b2
Show file tree
Hide file tree
Showing 15 changed files with 432 additions and 33 deletions.
10 changes: 10 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@
],
"program": "${workspaceFolder}\\index.js",
"args": ["--package=${workspaceFolder}\\test\\fixture\\all-fields\\package.json", "--config=${workspaceFolder}\\test\\fixture\\local-packages\\license-report-config.json"]
},
{
"name": "Debug with custom fields",
"type": "node",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}\\index.js",
"args": ["--package=${workspaceFolder}\\test\\fixture\\all-fields\\package.json", "--fields=name", "--fields=repository.url", "--repository.url.label=", "--repository.url.value=n/a", "--fields=nonexist", "--nonexist.label=NonExist", "--nonexist.value=n/a"]
}
]
}
26 changes: 22 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,20 +163,38 @@ license-report --output=csv --config license-report-config.json
}
```

### Custom fields

Besides the 'build-in' fields ("department", "name", "installedVersion", "author" "comment", "licensePeriod", "licenseType", "link", "material", "relatedTo"), any field allowed in a package.json can be used in the fields array (as "custom" field).

When using "custom" field, an element named like the "custom" field with 2 properties must be added: "value" - the default value for this field - and "label - the "heading" for generated columns. Here is an example for adding the 'homepage' field:
When using a "custom" field, an element named like the "custom" field with 2 properties must be added: "value" - the default value for this field - and "label - the "heading" for generated columns.

When the selected field is an object (e.g. the repository field is often an object) the object will be in the json output. If the output is of type table (or any of the other text based formats) the result of the toString function is used (resulting in "[object Object]" as default).

Here is an example for adding the 'homepage' and the repository field:

```
"fields": [
"name",
"installedVersion",
"homepage"
"homepage",
"repository",
"repository.url",
],
"homepage": {
"value": 'n/a',
"label": 'Homepage'
}
},
"repository": {
"value": 'n/a',
"label": 'Repo'
},
"repository": {
"url": {
"value": 'n/a',
"label": 'RepoURL'
}
},
```

### Exclude packages:
Expand All @@ -197,7 +215,7 @@ license-report --excludeRegex=@mycompany\/.*

### Markdown Options

If you want to change the default markdown table look and feel, e.g. center-align the text, you have to use a custom config file (`--config=license-report-config.json`) and in the config file use the `tablemarkConfig` property.
If you want to change the default markdown table look and feel, e.g. center-align the text, you have to use a custom config file )`--config=license-report-config.json`) and in the config file use the `tablemarkConfig` property.

Example config for markdown table with center-aligned content:

Expand Down
10 changes: 6 additions & 4 deletions lib/addLocalPackageData.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import createDebugMessages from 'debug';
import semver from 'semver';
import { extractAuthor } from './extractAuthor.js';
import { extractLicense } from './extractLicense.js';
import { readJson } from './util.js';
import { nestedPropertyValue, readJson } from './util.js';

const debug = createDebugMessages('license-report:addLocalPackageData');

Expand Down Expand Up @@ -59,10 +59,12 @@ export async function addLocalPackageData(element, projectRootPath, fields) {
fields.forEach((fieldName) => {
if (
element[fieldName] === undefined &&
!(fieldName in exclusionList) &&
packageJSONContent[fieldName] !== undefined
!(fieldName in exclusionList)
) {
element[fieldName] = packageJSONContent[fieldName];
const value = nestedPropertyValue(packageJSONContent, fieldName);
if (value !== undefined) {
element[fieldName] = value;
}
}
});
break;
Expand Down
17 changes: 14 additions & 3 deletions lib/getFormatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fs from 'node:fs';
import table from 'text-table';
import tableify from '@kessler/tableify';
import tablemark from 'tablemark';
import { isNullOrUndefined } from './util.js';
import { isNullOrUndefined, nestedPropertyValue } from './util.js';

/**
* Formats package information as json string.
Expand Down Expand Up @@ -32,7 +32,12 @@ function formatAsTable(dataAsArray, config) {
// create a labels array and a lines array
// the lines will be the same length as the label's
for (let i = 0; i < fieldsList.length; i++) {
let label = config[fieldsList[i]].label;
let label = fieldsList[i];
const fieldDefinition = nestedPropertyValue(config, fieldsList[i]);
if (fieldDefinition?.label !== undefined) {
label = fieldDefinition.label;
}

labels.push(label);
lines.push('-'.repeat(label.length));
}
Expand Down Expand Up @@ -205,7 +210,13 @@ function renameRowsProperties(row, config) {
let renamedRow = row;
for (let i = fieldsList.length - 1; i >= 0; i--) {
const fieldname = fieldsList[i];
renamedRow = renameProp(fieldname, config[fieldname].label, renamedRow);
let newFieldname = fieldname;
const fieldDefinition = nestedPropertyValue(config, fieldname);
if (fieldDefinition?.label !== undefined) {
newFieldname = fieldDefinition.label;
}

renamedRow = renameProp(fieldname, newFieldname, renamedRow);
}
return renamedRow;
}
Expand Down
10 changes: 9 additions & 1 deletion lib/packageDataToReportData.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { nestedPropertyValue } from './util.js';

/**
* Create object with all fields listed in config.fields with current values about one package.
* Only fields from the field list will be returned in the generated object.
Expand All @@ -18,7 +20,13 @@ export function packageDataToReportData(packageData, config) {
if (fieldName in packageData) {
finalData[fieldName] = packageData[fieldName];
} else {
finalData[fieldName] = config[fieldName].value;
let value = 'n/a';
const fieldDefinition = nestedPropertyValue(config, fieldName);
if (fieldDefinition?.value !== undefined) {
value = fieldDefinition.value;
}

finalData[fieldName] = value;
}
});

Expand Down
24 changes: 24 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,30 @@ export function isNullOrUndefined(element) {
return element === undefined || element === null;
}

/**
* Get a nested object property by pathname (a dotted string).
* Examples:
* getNestedPropertyFromKeyString(data, ['foo.bar']); // returns value of 'data.foo.bar'.
* getNestedPropertyFromKeyString(data, ['foo.bar[2].baz']); // returns value of 'data.foo.bar[2].baz',
* if data.foo.bar is an array with at least 3 elements.
* @param {object} obj - object containing the requested property
* @param {string} keyPath - key used to address the property (e.g.'foo.bar.baz')
* @returns {any} value of the requested property or undefined
*/
export function nestedPropertyValue(obj, keyPath) {
let result = undefined;
if (!keyPath.includes('.')) {
result = obj[keyPath];
} else {
const keys = keyPath
.replace(/\[([^[\]]*)\]/g, '.$1.') // change array index to dot separators
.split('.')
.filter(t => t !== ''); // remove empty entries
result = keys.reduce((prevValue, currKey) => prevValue?.[currKey] ?? undefined, obj);
}
return result;
}

export const helpText = `Generate a detailed report of the licenses of all projects dependencies.
Usage: license-report [options]
Expand Down
135 changes: 114 additions & 21 deletions test/addLocalPackageData.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,57 @@ describe('addLocalPackageData', () => {
assert.ok(depsIndexElement.installedVersion);
assert.strictEqual(depsIndexElement.installedVersion, 'n/a');
});

it('adds package data for custom field', async () => {
const depsIndexElement = {
fullName: 'my-local-package',
alias: 'my-local-package',
name: 'my-local-package',
version: 'file:local-libs/my-local-package',
};
const customFields = [
'author',
'description',
];
await addLocalPackageData(depsIndexElement, projectRootPath, customFields);

assert.ok(depsIndexElement.description);
assert.strictEqual(depsIndexElement.description, 'A list of color names and its values');
});

it('adds package data for object property of custom field', async () => {
const depsIndexElement = {
fullName: 'my-local-package',
alias: 'my-local-package',
name: 'my-local-package',
version: 'file:local-libs/my-local-package',
};
const customFields = [
'author',
'repository',
];
await addLocalPackageData(depsIndexElement, projectRootPath, customFields);

assert.ok(depsIndexElement.repository);
assert.deepStrictEqual(depsIndexElement.repository, {type: "git",url: "git@github.com:colors/my-local-package.git"});
});

it('adds package data for nested property of custom field', async () => {
const depsIndexElement = {
fullName: 'my-local-package',
alias: 'my-local-package',
name: 'my-local-package',
version: 'file:local-libs/my-local-package',
};
const customFields = [
'name',
'repository.url',
];
await addLocalPackageData(depsIndexElement, projectRootPath, customFields);

assert.ok(depsIndexElement['repository.url']);
assert.strictEqual(depsIndexElement['repository.url'], 'git@github.com:colors/my-local-package.git');
});
});

describe('addLocalPackageData with monorepo', () => {
Expand Down Expand Up @@ -182,44 +233,86 @@ describe('addLocalPackageData', () => {

describe('addLocalPackageData with custom fields', () => {
let projectRootPath;
const fields = [
'name',
'material',
'licenseType',
'homepage',
'definedVersion',
'author',
'bugs',
];

beforeEach(() => {
projectRootPath = path
.resolve(__dirname, 'fixture', 'add-local-data')
.replace(/(\s+)/g, '\\$1');
});

it('adds package data for package in root level', async () => {
it('adds package data for default and custom fields', async () => {
const fields = [
'name',
'material',
'licenseType',
'homepage',
'definedVersion',
'author',
'bugs',
];

const depsIndexElement = {
fullName: '@kessler/tableify',
alias: '',
name: 'tableify',
version: '^1.0.2',
scope: '@kessler',
};

const expectedResult = {
fullName: "@kessler/tableify",
alias: "",
name: "tableify",
version: "^1.0.2",
scope: "@kessler",
installedVersion: "1.0.2",
author: "Dan VerWeire, Yaniv Kessler",
licenseType: "MIT",
homepage: "https://github.com/kessler/node-tableify",
bugs: {
url: "https://github.com/kessler/node-tableify/issues",
},
};

await addLocalPackageData(depsIndexElement, projectRootPath, fields);

assert.ok(depsIndexElement.installedVersion);
assert.strictEqual(depsIndexElement.installedVersion, '1.0.2');
assert.ok(depsIndexElement.homepage);
assert.strictEqual(
depsIndexElement.homepage,
'https://github.com/kessler/node-tableify',
);
assert.ok(depsIndexElement.bugs);
const expectedBugs = {
url: 'https://github.com/kessler/node-tableify/issues',
assert.deepStrictEqual(depsIndexElement, expectedResult);
});

it('adds package data for for nested custom field', async () => {
const fields = [
'name',
'repository',
'repository.url',
];

const depsIndexElement = {
fullName: '@kessler/tableify',
alias: '',
name: 'tableify',
version: '^1.0.2',
scope: '@kessler',
};

const expectedResult = {
fullName: "@kessler/tableify",
alias: "",
name: "tableify",
version: "^1.0.2",
scope: "@kessler",
installedVersion: "1.0.2",
author: "Dan VerWeire, Yaniv Kessler",
licenseType: "MIT",
repository: {
type: "git",
url: "https://github.com/kessler/node-tableify.git",
},
"repository.url": "https://github.com/kessler/node-tableify.git",
};
assert.deepStrictEqual(depsIndexElement.bugs, expectedBugs);

await addLocalPackageData(depsIndexElement, projectRootPath, fields);

assert.deepStrictEqual(depsIndexElement, expectedResult);
});
});
});
Loading

0 comments on commit d9519b2

Please sign in to comment.