diff --git a/packages/liferay-npm-bundler/src/report/__tests__/__snapshots__/index.test.js.snap b/packages/liferay-npm-bundler/src/report/__tests__/__snapshots__/index.test.js.snap
new file mode 100644
index 00000000..ee03430f
--- /dev/null
+++ b/packages/liferay-npm-bundler/src/report/__tests__/__snapshots__/index.test.js.snap
@@ -0,0 +1,536 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`correctly dumps HTML report 1`] = `
+"
+
+
+
+
+
+ Report of liferay-npm-bundler execution at Thu Jan 01 1970 01:00:00 GMT+0100 (CET)
+
+
+
+
+
+
+ Report of liferay-npm-bundler execution
+
+
+ Executed at: | Thu, 01 Jan 1970 00:00:00 GMT |
+Execution took: | 1s |
+
+
+
+ Warnings
+
+
+
+
+
+ Bundler environment versions
+
+
+ Package |
+Version |
+
+ liferay-npm-bundler |
+ 1.4.2 |
+
+
+ liferay-npm-bundler-plugin-inject-angular |
+ 1.4.2 |
+
+
+
+
+
+ Bundled packages
+
+
+ Package |
+Version |
+Copied files |
+Excluded files |
+Exclusions |
+Linked to |
+
+ package-1 |
+ 1.0.0 |
+ 2 |
+ 1 |
+ b.* |
+ file:../package-1 |
+
+
+ package-2 |
+ 2.0.0 |
+ 0 |
+ 0 |
+ |
+ |
+
+
+
+
+
+ Summary of package transformations
+
+
+
+
+
+ Details of bundler transformations
+
+
+
+ package-1@1.0.0
+
+
+
+ Pre-phase plugin |
+Config |
+Message |
+ |
+
+ a-plugin | {\\"cfgval1\\":1,\\"cfgval2\\":2} |
+
+ |
+
+
+
+
+
+
+
+
+ Details of Babel transformations
+
+
+
+ package-1@1.0.0
+
+
+ Configuration:
+ {\\"cfgval1\\":1,\\"cfgval2\\":2}
+
+
+
+ File |
+Log source |
+Message |
+ |
+
+ a.js |
+ |
+ | |
+
+
+
+
+
+
+
+
+ "
+`;
+
+exports[`when describing the run correctly removes stale linked dependencies 1`] = `
+Report {
+ "_executionDate": 1970-01-01T00:00:00.000Z,
+ "_packages": Object {
+ "package-1@1.0.0": Object {
+ "allFiles": Array [],
+ "copiedFiles": Array [],
+ "exclusions": Array [],
+ "id": "package-1@1.0.0",
+ "link": "file:../package-1",
+ "name": "package-1",
+ "process": Object {
+ "babel": Object {
+ "config": Object {},
+ "files": Object {},
+ },
+ "post": Object {},
+ "pre": Object {},
+ },
+ "version": "1.0.0",
+ },
+ "package-2@2.0.0": Object {
+ "allFiles": Array [],
+ "copiedFiles": Array [],
+ "exclusions": Array [],
+ "id": "package-2@2.0.0",
+ "name": "package-2",
+ "process": Object {
+ "babel": Object {
+ "config": Object {},
+ "files": Object {},
+ },
+ "post": Object {},
+ "pre": Object {},
+ },
+ "version": "2.0.0",
+ },
+ },
+ "_versionsInfo": Object {},
+ "_warnings": Array [],
+}
+`;
+
+exports[`when describing the run correctly stores Babel config 1`] = `
+Report {
+ "_executionDate": 1970-01-01T00:00:00.000Z,
+ "_packages": Object {
+ "package-1@1.0.0": Object {
+ "allFiles": Array [],
+ "copiedFiles": Array [],
+ "exclusions": Array [],
+ "process": Object {
+ "babel": Object {
+ "config": Object {
+ "cfgval1": 1,
+ "cfgval2": 2,
+ },
+ "files": Object {},
+ },
+ "post": Object {},
+ "pre": Object {},
+ },
+ },
+ },
+ "_versionsInfo": Object {},
+ "_warnings": Array [],
+}
+`;
+
+exports[`when describing the run correctly stores Babel file run 1`] = `
+Report {
+ "_executionDate": 1970-01-01T00:00:00.000Z,
+ "_packages": Object {
+ "package-1@1.0.0": Object {
+ "allFiles": Array [],
+ "copiedFiles": Array [],
+ "exclusions": Array [],
+ "process": Object {
+ "babel": Object {
+ "config": Object {},
+ "files": Object {
+ "a.js": Object {
+ "logger": PluginLogger {
+ "_msgs": Array [],
+ },
+ },
+ },
+ },
+ "post": Object {},
+ "pre": Object {},
+ },
+ },
+ },
+ "_versionsInfo": Object {},
+ "_warnings": Array [],
+}
+`;
+
+exports[`when describing the run correctly stores bundler plugin runs 1`] = `
+Report {
+ "_executionDate": 1970-01-01T00:00:00.000Z,
+ "_packages": Object {
+ "package-1@1.0.0": Object {
+ "allFiles": Array [],
+ "copiedFiles": Array [],
+ "exclusions": Array [],
+ "process": Object {
+ "babel": Object {
+ "config": Object {},
+ "files": Object {},
+ },
+ "post": Object {},
+ "pre": Object {
+ "a-plugin": Object {
+ "logger": PluginLogger {
+ "_msgs": Array [],
+ },
+ "plugin": Object {
+ "config": Object {
+ "cfgval1": 1,
+ "cfgval2": 2,
+ },
+ "name": "a-plugin",
+ "run": [Function],
+ },
+ },
+ },
+ },
+ },
+ },
+ "_versionsInfo": Object {},
+ "_warnings": Array [],
+}
+`;
+
+exports[`when describing the run correctly stores dependencies 1`] = `
+Report {
+ "_executionDate": 1970-01-01T00:00:00.000Z,
+ "_packages": Object {
+ "package-1@1.0.0": Object {
+ "allFiles": Array [],
+ "copiedFiles": Array [],
+ "exclusions": Array [],
+ "id": "package-1@1.0.0",
+ "name": "package-1",
+ "process": Object {
+ "babel": Object {
+ "config": Object {},
+ "files": Object {},
+ },
+ "post": Object {},
+ "pre": Object {},
+ },
+ "version": "1.0.0",
+ },
+ "package-2@2.0.0": Object {
+ "allFiles": Array [],
+ "copiedFiles": Array [],
+ "exclusions": Array [],
+ "id": "package-2@2.0.0",
+ "name": "package-2",
+ "process": Object {
+ "babel": Object {
+ "config": Object {},
+ "files": Object {},
+ },
+ "post": Object {},
+ "pre": Object {},
+ },
+ "version": "2.0.0",
+ },
+ },
+ "_versionsInfo": Object {},
+ "_warnings": Array [],
+}
+`;
+
+exports[`when describing the run correctly stores executionTime 1`] = `
+Report {
+ "_executionDate": 1970-01-01T00:00:00.000Z,
+ "_executionTime": Array [
+ 1,
+ 2,
+ ],
+ "_packages": Object {},
+ "_versionsInfo": Object {},
+ "_warnings": Array [],
+}
+`;
+
+exports[`when describing the run correctly stores linked dependencies 1`] = `
+Report {
+ "_executionDate": 1970-01-01T00:00:00.000Z,
+ "_packages": Object {
+ "a-package@1.1.0": Object {
+ "allFiles": Array [],
+ "copiedFiles": Array [],
+ "exclusions": Array [],
+ "link": "file:../a-package",
+ "process": Object {
+ "babel": Object {
+ "config": Object {},
+ "files": Object {},
+ },
+ "post": Object {},
+ "pre": Object {},
+ },
+ },
+ },
+ "_versionsInfo": Object {},
+ "_warnings": Array [],
+}
+`;
+
+exports[`when describing the run correctly stores package copies 1`] = `
+Report {
+ "_executionDate": 1970-01-01T00:00:00.000Z,
+ "_packages": Object {
+ "package-1@1.0.0": Object {
+ "allFiles": Array [
+ "a.js",
+ "b.js",
+ "c.js",
+ ],
+ "copiedFiles": Array [
+ "a.js",
+ "c.js",
+ ],
+ "exclusions": Array [
+ "b.*",
+ ],
+ "process": Object {
+ "babel": Object {
+ "config": Object {},
+ "files": Object {},
+ },
+ "post": Object {},
+ "pre": Object {},
+ },
+ },
+ },
+ "_versionsInfo": Object {},
+ "_warnings": Array [],
+}
+`;
+
+exports[`when describing the run correctly stores unique warnings 1`] = `
+Report {
+ "_executionDate": 1970-01-01T00:00:00.000Z,
+ "_packages": Object {},
+ "_versionsInfo": Object {},
+ "_warnings": Array [
+ "warn",
+ ],
+}
+`;
+
+exports[`when describing the run correctly stores versions info 1`] = `
+Report {
+ "_executionDate": 1970-01-01T00:00:00.000Z,
+ "_packages": Object {},
+ "_versionsInfo": Object {
+ "liferay-npm-bundler": "1.4.2",
+ "liferay-npm-bundler-plugin-inject-angular": "1.4.2",
+ },
+ "_warnings": Array [],
+}
+`;
+
+exports[`when describing the run correctly stores warnings 1`] = `
+Report {
+ "_executionDate": 1970-01-01T00:00:00.000Z,
+ "_packages": Object {},
+ "_versionsInfo": Object {},
+ "_warnings": Array [
+ "warn 1",
+ "warn 2",
+ ],
+}
+`;
diff --git a/packages/liferay-npm-bundler/src/report/__tests__/index.test.js b/packages/liferay-npm-bundler/src/report/__tests__/index.test.js
new file mode 100644
index 00000000..50857cc2
--- /dev/null
+++ b/packages/liferay-npm-bundler/src/report/__tests__/index.test.js
@@ -0,0 +1,144 @@
+import PluginLogger from 'liferay-npm-build-tools-common/lib/plugin-logger';
+import {Report} from '../index';
+
+let report;
+
+beforeEach(() => {
+ report = new Report();
+
+ // Hack to make tests repeatable
+ report._executionDate = new Date(0);
+});
+
+describe('when describing the run', () => {
+ afterEach(() => {
+ expect(report).toMatchSnapshot();
+ });
+
+ it('correctly stores executionTime', () => {
+ report.executionTime([1, 2]);
+ });
+
+ it('correctly stores warnings', () => {
+ report.warn('warn 1');
+ report.warn('warn 2');
+ });
+
+ it('correctly stores unique warnings', () => {
+ report.warn('warn', {unique: true});
+ report.warn('warn', {unique: true});
+ });
+
+ it('correctly stores versions info', () => {
+ report.versionsInfo({
+ 'liferay-npm-bundler': '1.4.2',
+ 'liferay-npm-bundler-plugin-inject-angular': '1.4.2',
+ });
+ });
+
+ it('correctly stores linked dependencies', () => {
+ report.linkedDependency('a-package', 'file:../a-package', '1.1.0');
+ });
+
+ it('correctly stores dependencies', () => {
+ report.dependencies([
+ {id: 'package-1@1.0.0', name: 'package-1', version: '1.0.0'},
+ {id: 'package-2@2.0.0', name: 'package-2', version: '2.0.0'},
+ ]);
+ });
+
+ it('correctly removes stale linked dependencies', () => {
+ report.linkedDependency('a-package', 'file:../a-package', '1.1.0');
+ report.linkedDependency('package-1', 'file:../package-1', '1.0.0');
+ report.dependencies([
+ {id: 'package-1@1.0.0', name: 'package-1', version: '1.0.0'},
+ {id: 'package-2@2.0.0', name: 'package-2', version: '2.0.0'},
+ ]);
+ });
+
+ it('correctly stores package copies', () => {
+ report.packageCopy(
+ {id: 'package-1@1.0.0', name: 'package-1', version: '1.0.0'},
+ ['a.js', 'b.js', 'c.js'],
+ ['a.js', 'c.js'],
+ ['b.*']
+ );
+ });
+
+ it('correctly stores bundler plugin runs', () => {
+ report.packageProcessBundlerPlugin(
+ 'pre',
+ {id: 'package-1@1.0.0', name: 'package-1', version: '1.0.0'},
+ {
+ name: 'a-plugin',
+ config: {cfgval1: 1, cfgval2: 2},
+ run: () => '',
+ },
+ new PluginLogger()
+ );
+ });
+
+ it('correctly stores Babel config', () => {
+ report.packageProcessBabelConfig(
+ {id: 'package-1@1.0.0', name: 'package-1', version: '1.0.0'},
+ {cfgval1: 1, cfgval2: 2}
+ );
+ });
+
+ it('correctly stores Babel file run', () => {
+ report.packageProcessBabelRun(
+ {id: 'package-1@1.0.0', name: 'package-1', version: '1.0.0'},
+ 'a.js',
+ new PluginLogger()
+ );
+ });
+});
+
+it('correctly dumps HTML report', () => {
+ // The goal of this test is to detect unwanted changes in HTML. If you make
+ // changes to the HTML on purpose, just check it visually and update the
+ // snapshot with Jest's -u switch.
+
+ report.executionTime([1, 2]);
+ report.warn('warn 1');
+ report.warn('warn 2');
+ report.warn('warn', {unique: true});
+ report.warn('warn', {unique: true});
+ report.versionsInfo({
+ 'liferay-npm-bundler': '1.4.2',
+ 'liferay-npm-bundler-plugin-inject-angular': '1.4.2',
+ });
+ report.linkedDependency('a-package', 'file:../a-package', '1.1.0');
+ report.linkedDependency('package-1', 'file:../package-1', '1.0.0');
+ report.dependencies([
+ {id: 'package-1@1.0.0', name: 'package-1', version: '1.0.0'},
+ {id: 'package-2@2.0.0', name: 'package-2', version: '2.0.0'},
+ ]);
+ report.packageCopy(
+ {id: 'package-1@1.0.0', name: 'package-1', version: '1.0.0'},
+ ['a.js', 'b.js', 'c.js'],
+ ['a.js', 'c.js'],
+ ['b.*']
+ );
+ report.packageProcessBundlerPlugin(
+ 'pre',
+ {id: 'package-1@1.0.0', name: 'package-1', version: '1.0.0'},
+ {
+ name: 'a-plugin',
+ config: {cfgval1: 1, cfgval2: 2},
+ run: () => '',
+ },
+ new PluginLogger()
+ );
+ report.packageProcessBabelConfig(
+ {id: 'package-1@1.0.0', name: 'package-1', version: '1.0.0'},
+ {cfgval1: 1, cfgval2: 2}
+ );
+ report.packageProcessBabelRun(
+ {id: 'package-1@1.0.0', name: 'package-1', version: '1.0.0'},
+ 'a.js',
+ new PluginLogger()
+ );
+
+ expect(report.toHtml()).toMatchSnapshot();
+});
diff --git a/packages/liferay-npm-bundler/src/report/html.js b/packages/liferay-npm-bundler/src/report/html.js
new file mode 100644
index 00000000..1c6e043a
--- /dev/null
+++ b/packages/liferay-npm-bundler/src/report/html.js
@@ -0,0 +1,465 @@
+/* eslint require-jsdoc: off */
+import pretty from 'pretty-time';
+
+export function htmlDump(report) {
+ const {
+ _executionDate,
+ _executionTime,
+ _warnings,
+ _versionsInfo,
+ _packages,
+ } = report;
+
+ const title = 'Report of liferay-npm-bundler execution
';
+
+ const summary = htmlTable([
+ htmlRow(
+ `Executed at: | ${_executionDate.toUTCString()} | `
+ ),
+ htmlIf(_executionTime, () =>
+ htmlRow(
+ `Execution took: | ${pretty(_executionTime)} | `
+ )
+ ),
+ ]);
+
+ const warnings = htmlIf(_warnings.length > 0, () =>
+ htmlSection('Warnings', htmlList(..._warnings))
+ );
+
+ const versionsInfo = htmlSection(
+ 'Bundler environment versions',
+ htmlTable(
+ 'Package',
+ 'Version',
+ Object.keys(_versionsInfo).map(pkgName =>
+ htmlRow(`
+ ${pkgName} |
+ ${_versionsInfo[pkgName]} |
+ `)
+ )
+ )
+ );
+
+ const dependencies = htmlSection(
+ 'Bundled packages',
+ htmlTable(
+ 'Package',
+ 'Version',
+ 'Copied files',
+ 'Excluded files',
+ 'Exclusions',
+ 'Linked to',
+ Object.keys(_packages)
+ .sort()
+ .map(pkgId => {
+ const {
+ name,
+ version,
+ link,
+ allFiles,
+ copiedFiles,
+ exclusions,
+ } = _packages[pkgId];
+
+ return htmlRow(`
+ ${name} |
+ ${version} |
+ ${copiedFiles.length} |
+ ${allFiles.length - copiedFiles.length} |
+ ${exclusions} |
+ ${link === undefined ? '' : link} |
+ `);
+ })
+ )
+ );
+
+ const packageProcesses = htmlSection(
+ 'Summary of package transformations',
+ htmlTable(
+ 'Package',
+ 'Version',
+ 'Pre-babel phase plugins',
+ 'Post-babel phase plugins',
+ 'Babel phase results',
+ Object.keys(_packages)
+ .sort()
+ .map(pkgId => {
+ const pkg = _packages[pkgId];
+ const {pre, post, babel} = pkg.process;
+ const preKeys = Object.keys(pre);
+ const postKeys = Object.keys(post);
+ const babelKeys = Object.keys(babel.files);
+
+ const preNotice = htmlIf(
+ preKeys.length > 0,
+ () => `${preKeys.length} plugins applied`
+ );
+ const postNotice = htmlIf(
+ postKeys.length > 0,
+ () => `${postKeys.length} plugins applied`
+ );
+ const babelNotice = htmlIf(
+ babelKeys.length > 0,
+ () => `${babelKeys.length} files processed`
+ );
+
+ return htmlRow(`
+ ${pkg.name} |
+ ${pkg.version} |
+
+
+ ${preNotice}
+
+ |
+
+
+ ${postNotice}
+
+ |
+
+
+ ${babelNotice}
+
+ |
+ `);
+ })
+ )
+ );
+
+ const packageProcessesBundlerDetails = htmlSection(
+ 'Details of bundler transformations',
+ ...Object.keys(_packages)
+ .sort()
+ .map(pkgId => {
+ const pkg = _packages[pkgId];
+ const {pre, post} = pkg.process;
+ const preKeys = Object.keys(pre);
+ const postKeys = Object.keys(post);
+
+ return htmlIf(preKeys.length > 0 || postKeys.length > 0, () =>
+ htmlSubsection(
+ `
+
+ ${pkgId}
+ `,
+ htmlIf(preKeys.length > 0, () =>
+ preKeys
+ .sort()
+ .map(pluginName =>
+ htmlLogOutput(
+ ['Pre-phase plugin', 'Config'],
+ [
+ [
+ pluginName,
+ JSON.stringify(
+ pre[pluginName].plugin
+ .config
+ ),
+ ],
+ ],
+ [pre[pluginName].logger],
+ {source: false}
+ )
+ )
+ ),
+ htmlIf(postKeys.length > 0, () =>
+ postKeys
+ .sort()
+ .map(pluginName =>
+ htmlLogOutput(
+ ['Post-phase plugin', 'Config'],
+ [
+ [
+ pluginName,
+ JSON.stringify(
+ post[pluginName].plugin
+ .config
+ ),
+ ],
+ ],
+ [post[pluginName].logger],
+ {source: false}
+ )
+ )
+ )
+ )
+ );
+ })
+ );
+
+ const packageProcessesBabelDetails = htmlSection(
+ 'Details of Babel transformations',
+ ...Object.keys(_packages)
+ .sort()
+ .map(pkgId => {
+ const pkg = _packages[pkgId];
+ const {babel} = pkg.process;
+ const babelKeys = Object.keys(babel.files);
+
+ return htmlIf(babelKeys.length > 0, () =>
+ htmlSubsection(
+ `
+
+ ${pkgId}
+ `,
+ `
+ Configuration:
+ ${JSON.stringify(babel.config)}
+
`,
+ htmlLogOutput(
+ ['File'],
+ babelKeys.sort().map(filePath => [filePath]),
+ babelKeys
+ .sort()
+ .map(filePath => babel.files[filePath].logger)
+ )
+ )
+ );
+ })
+ );
+
+ return `
+
+
+
+
+
+ Report of liferay-npm-bundler execution at ${_executionDate}
+
+
+
+
+
+
+ ${title}
+ ${summary}
+ ${warnings}
+ ${versionsInfo}
+ ${dependencies}
+ ${packageProcesses}
+ ${packageProcessesBundlerDetails}
+ ${packageProcessesBabelDetails}
+
+
+ `;
+}
+
+function htmlIf(condition, contentGenerator) {
+ return condition ? contentGenerator() : '';
+}
+
+function htmlSection(title, ...contents) {
+ return `
+ ${title}
+ ${contents.join('\n')}
+ `;
+}
+
+function htmlSubsection(title, ...contents) {
+ return `
+ ${title}
+ ${contents.join('\n')}
+ `;
+}
+
+function htmlList(...args) {
+ return `
+
+ ${args.map(arg => `- ${arg}
`).join(' ')}
+
+ `;
+}
+
+function htmlTable(...args) {
+ const columns = args.slice(0, args.length - 1);
+ let content = args[args.length - 1];
+
+ if (Array.isArray(content)) {
+ content = content.join('\n');
+ }
+
+ if (columns.length == 0) {
+ return `
+
+ `;
+ } else {
+ return `
+
+ ${htmlRow(columns.map(column => `${column} | `))}
+ ${content}
+
+ `;
+ }
+}
+
+function htmlRow(content) {
+ if (Array.isArray(content)) {
+ content = content.join('\n');
+ }
+
+ return `${content}
`;
+}
+
+/**
+ * Dump a table with the output of a PluginLogger
+ * @param {Array} prefixColumns [description]
+ * @param {Array} prefixCells an array with one array per row containing the prefix cell content
+ * @param {Array} rowLoggers an array with one logger per row in prefixCells
+ * @param {boolean} source whether or not to show 'Log source' column
+ * @return {String} an HTML table
+ */
+function htmlLogOutput(
+ prefixColumns,
+ prefixCells,
+ rowLoggers,
+ {source} = {source: true}
+) {
+ if (prefixCells.length != rowLoggers.length) {
+ throw new Error(
+ 'The length of prefixCells and rowLoggers must be the same'
+ );
+ }
+
+ let logColums = ['Message', ''];
+
+ if (source) {
+ logColums.splice(0, 0, 'Log source');
+ }
+
+ const columns = prefixColumns.concat(logColums);
+
+ let rows = [];
+
+ prefixCells.forEach((cells, i) => {
+ if (cells.length != prefixColumns.length) {
+ throw new Error(
+ `Prefix cells row ${i} has an invalid length: ${cells.length}`
+ );
+ }
+
+ const msgs = rowLoggers[i].messages;
+
+ if (msgs.length == 0) {
+ rows.push(
+ htmlRow(`
+ ${cells.map(cell => `${cell} | `).join(' ')}
+ ${htmlIf(source, () => ` | `)}
+ ${logColums
+ .splice(1)
+ .map(() => ' | ')
+ .join(' ')}
+ `)
+ );
+ } else {
+ const msg0 = msgs[0];
+
+ let sourceCell = htmlIf(
+ source,
+ () => `${msg0.source} | `
+ );
+
+ rows.push(
+ htmlRow(`
+ ${cells.map(cell => `${cell} | `).join(' ')}
+ ${sourceCell}
+ ${msg0.level} |
+ ${msg0.things.join(' ')} |
+ `)
+ );
+
+ for (let i = 1; i < msgs.length; i++) {
+ sourceCell = htmlIf(
+ source,
+ () => `${msgs[i].source} | `
+ );
+
+ rows.push(
+ htmlRow(`
+ ${cells.map(() => ` | `).join(' ')}
+ ${sourceCell}
+ ${msgs[i].level} |
+ ${msgs[i].things.join(' ')} |
+ `)
+ );
+ }
+ }
+ });
+
+ return htmlTable(...columns, rows);
+}
diff --git a/packages/liferay-npm-bundler/src/report/index.js b/packages/liferay-npm-bundler/src/report/index.js
new file mode 100644
index 00000000..87ca0c25
--- /dev/null
+++ b/packages/liferay-npm-bundler/src/report/index.js
@@ -0,0 +1,201 @@
+import {htmlDump} from './html';
+
+/**
+ * A Report holds data describing a execution of the liferay-npm-bundler so that
+ * it can be dump as an HTML report.
+ * @type {Report}
+ */
+export class Report {
+ /**
+ * Constructor
+ */
+ constructor() {
+ this._executionDate = new Date();
+ this._versionsInfo = {};
+ this._packages = {};
+ this._warnings = [];
+ }
+
+ /**
+ * Return an HTML string with the information contained in this report.
+ * @return {String} an HTML string
+ */
+ toHtml() {
+ return htmlDump(this);
+ }
+
+ /**
+ * Register execution time.
+ * @param {Array} hrtime the time it took to execute
+ * @return {void}
+ */
+ executionTime(hrtime) {
+ this._executionTime = hrtime;
+ }
+
+ /**
+ * Register a warning.
+ * @param {String} message the warning message
+ * @param {boolean} unique set to true if you want this warning to be deduped
+ * @return {void}
+ */
+ warn(message, {unique} = {unique: false}) {
+ if (unique && this._warnings.find(item => item === message)) {
+ return;
+ }
+
+ this._warnings.push(message);
+ }
+
+ /**
+ * Register a versions hash describing the packages and versions used by the
+ * build process.
+ * @param {Object} info a hash or (package,version) pairs
+ * @return {void}
+ */
+ versionsInfo(info) {
+ this._versionsInfo = info;
+ }
+
+ /**
+ * Register a linked dependency found in the root package.json.
+ * @param {String} packageName package name
+ * @param {String} packageLink the link to the package
+ * @param {String} packageVersion package version
+ * @return {void}
+ */
+ linkedDependency(packageName, packageLink, packageVersion) {
+ const pkgId = `${packageName}@${packageVersion}`;
+ let pkg = this._getPackage(pkgId);
+
+ pkg.link = packageLink;
+ }
+
+ /**
+ * Register the list of dependencies detected in this build.
+ * @param {Array} deps an array of package descriptors (with id, name and
+ * version fields)
+ * @return {void}
+ */
+ dependencies(deps) {
+ deps.forEach(dep => {
+ let pkg = this._getPackage(dep.id);
+
+ Object.assign(pkg, dep);
+ });
+
+ // Remove all pre-registered packages that are not in the deps array
+ Object.keys(this._packages).forEach(pkgId => {
+ const pkg = this._packages[pkgId];
+
+ if (!pkg.id) {
+ delete this._packages[pkgId];
+ }
+ });
+ }
+
+ /**
+ * Register a package copy action.
+ * @param {Object} pkg a package descriptor
+ * @param {Array} allFiles the list of all files in the package
+ * @param {Array} copiedFiles the list of files copied to the target
+ * @param {Array} exclusions the list of configured file exclusions
+ * @return {void}
+ */
+ packageCopy(pkg, allFiles, copiedFiles, exclusions) {
+ let rpkg = this._getPackage(pkg.id);
+
+ Object.assign(rpkg, {
+ allFiles,
+ copiedFiles,
+ exclusions,
+ });
+ }
+
+ /**
+ * Register a liferay-npm-bundler plugin execution.
+ * @param {String} phase run phase (pre or post)
+ * @param {Object} pkg package descriptor
+ * @param {Object} plugin plugin descriptor (with config and run fields)
+ * @param {PluginLogger} logger the logger cotaining the process messages
+ * @return {void}
+ */
+ packageProcessBundlerPlugin(phase, pkg, plugin, logger) {
+ let pkgProcess = this._getPackageProcess(pkg.id);
+
+ pkgProcess[phase][plugin.name] = {
+ plugin,
+ logger,
+ };
+ }
+
+ /**
+ * Register a Babel execution config.
+ * @param {Object} pkg package descriptor
+ * @param {Object} babelConfig the Babel config object
+ * @return {void}
+ */
+ packageProcessBabelConfig(pkg, babelConfig) {
+ let {babel} = this._getPackageProcess(pkg.id);
+
+ babel.config = babelConfig;
+ }
+
+ /**
+ * Register a Babel file process.
+ * @param {Object} pkg package descriptor
+ * @param {String} filePath the file path
+ * @param {PluginLogger} logger the logger cotaining the process messages
+ * @return {void}
+ */
+ packageProcessBabelRun(pkg, filePath, logger) {
+ let {babel} = this._getPackageProcess(pkg.id);
+
+ babel.files[filePath] = {logger};
+ }
+
+ /**
+ * Get a package slot and create it if missing.
+ * @param {String} pkgId the package id
+ * @return {Object} a package slot
+ */
+ _getPackage(pkgId) {
+ let pkg = this._packages[pkgId];
+
+ if (!pkg) {
+ pkg = this._packages[pkgId] = {
+ allFiles: [],
+ copiedFiles: [],
+ exclusions: [],
+ };
+
+ this._getPackageProcess(pkgId);
+ }
+
+ return pkg;
+ }
+
+ /**
+ * Get a package process slot and create it if missing.
+ * @param {String} pkgId the package id
+ * @return {Object} a package process slot
+ */
+ _getPackageProcess(pkgId) {
+ let rpkg = this._getPackage(pkgId);
+
+ rpkg.process = rpkg.process || {
+ pre: {},
+ babel: {
+ config: {},
+ files: {},
+ },
+ post: {},
+ };
+
+ return rpkg.process;
+ }
+}
+
+const report = new Report();
+
+export default report;