chore(cli): replace ejs with native node template rendering (#12109)

* chore(cli): replace ejs with native node template rendering

* chore(cli): add HTML escaping to audit, changelog, and licenses rendering functions

* chore(cli): remove templates directory from dist script in package.json

* chore(cli): remove node-fetch dependency from package.json
This commit is contained in:
Denys Vuika
2026-07-30 13:22:47 +01:00
committed by GitHub
parent 4f32198afd
commit 0c099eafb0
11 changed files with 1217 additions and 1233 deletions
+1 -4
View File
@@ -17,14 +17,12 @@
"scripts": {
"build": "tsc -p tsconfig.json",
"develop": "tsc -p tsconfig.json --watch",
"dist": "rm -rf ../../dist/libs/cli && npm run build && cp -R ./bin ../../dist/libs/cli && cp -R ./resources ../../dist/libs/cli && cp -R ./templates ../../dist/libs/cli && cp ./package.json ../../dist/libs/cli",
"dist": "rm -rf ../../dist/libs/cli && npm run build && cp -R ./bin ../../dist/libs/cli && cp -R ./resources ../../dist/libs/cli && cp ./package.json ../../dist/libs/cli",
"link": "npm run dist && cd ../../dist/libs/cli && npm link",
"unlink": "cd ../../dist/libs/cli && npm unlink"
},
"dependencies": {
"@alfresco/js-api": ">=10.0.0",
"ejs": "^3.1.10",
"node-fetch": "^2.7.0",
"spdx-license-list": "^5.0.0"
},
"keywords": [
@@ -32,7 +30,6 @@
],
"license": "Apache-2.0",
"devDependencies": {
"@types/ejs": "^3.1.2",
"@types/node": "^20.1.7",
"typescript": "^4.9.5"
}
+47 -29
View File
@@ -18,17 +18,56 @@
*/
import { spawnSync } from 'node:child_process';
import * as ejs from 'ejs';
import * as path from 'path';
import * as fs from 'fs';
import { argv, exit } from 'node:process';
import { parseArgs } from 'node:util';
import { escapeHtml } from './utils';
interface AuditCommandArgs {
package?: string;
outDir?: string;
}
function renderAuditPage(jsonAudit: any, projName: string, projVersion: string): string {
const rows: string[] = [];
if (jsonAudit.auditReportVersion >= 2) {
for (const key in jsonAudit.vulnerabilities) {
const v = jsonAudit.vulnerabilities[key];
rows.push(`|${escapeHtml(v.severity)} | ${escapeHtml(v.name)} | ${JSON.stringify(v.range)} |`);
}
} else {
for (const key in jsonAudit.advisories) {
const a = jsonAudit.advisories[key];
rows.push(`|${escapeHtml(a.severity)} | ${escapeHtml(a.module_name)} | ${JSON.stringify(a.vulnerable_versions)} |`);
}
}
return `---
Title: Audit info, ${escapeHtml(projName)} ${escapeHtml(projVersion)}
---
# Audit information for ${escapeHtml(projName)} ${escapeHtml(projVersion)}
This page lists the security audit of the dependencies this project depends on.
## Risks
- Critical risk: ${jsonAudit.metadata.vulnerabilities.critical}
- High risk: ${jsonAudit.metadata.vulnerabilities.high}
- Moderate risk: ${jsonAudit.metadata.vulnerabilities.moderate}
- Low risk: ${jsonAudit.metadata.vulnerabilities.low}
Dependencies analyzed: ${jsonAudit.metadata.totalDependencies}
## Libraries
| Severity | Module | Vulnerable versions |
| --- | --- | --- |
${rows.join('\n')}
`;
}
/**
* Audit report command
*
@@ -82,12 +121,6 @@ Options:
exit(1);
}
const templatePath = path.resolve(__dirname, '../templates/auditPage.ejs');
if (!fs.existsSync(templatePath)) {
console.error(`Cannot find the report template: ${templatePath}`);
exit(1);
}
return new Promise((resolve, reject) => {
// eslint-disable-next-line no-console
console.log(`Running audit on ${packagePath}`);
@@ -133,29 +166,14 @@ Options:
return;
}
ejs.renderFile(
templatePath,
{
jsonAudit,
projVersion: packageJson.version,
projName: packageJson.name
},
{},
(err: any, mdText: string) => {
if (err) {
console.error(err);
reject(err);
} else {
const outputPath = path.resolve(options.outDir || workingDir);
const outputFile = path.join(outputPath, `audit-info-${packageJson.version}.md`);
const mdText = renderAuditPage(jsonAudit, packageJson.name, packageJson.version);
const outputPath = path.resolve(options.outDir || workingDir);
const outputFile = path.join(outputPath, `audit-info-${packageJson.version}.md`);
fs.writeFileSync(outputFile, mdText);
fs.writeFileSync(outputFile, mdText);
// eslint-disable-next-line no-console
console.log(`Report saved as ${outputFile}`);
resolve(0);
}
}
);
// eslint-disable-next-line no-console
console.log(`Report saved as ${outputFile}`);
resolve(0);
});
}
+49 -35
View File
@@ -25,7 +25,7 @@ import { spawnSync } from 'node:child_process';
import * as path from 'path';
import { logger } from './logger';
import * as fs from 'fs';
import * as ejs from 'ejs';
import { escapeHtml } from './utils';
interface Commit {
hash: string;
@@ -167,6 +167,39 @@ function commitAuthorAllowed(commit: Commit, authorFilter: string): boolean {
return !(filterRegex.test(commit.author) || filterRegex.test(commit.author_email));
}
function renderChangelogMd(commits: Commit[], projName: string, projVersion: string, repoUrl: string): string {
const lines = commits.map((c) => `- [${c.hash}](${repoUrl}/commit/${c.hash}) ${escapeHtml(c.subject)}`);
return `---
Title: Changelog for ${escapeHtml(projName)} v${escapeHtml(projVersion)}
---
# Changelog
${lines.join('\n')}
`;
}
function renderChangelogHtml(commits: Commit[], projName: string, projVersion: string, repoUrl: string): string {
const items = commits
.map((c) => ` <li>\n <a href="${repoUrl}/commit/${c.hash}">[${c.hash}]</a> ${escapeHtml(c.subject)}\n </li>`)
.join('\n');
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog for ${escapeHtml(projName)} v${escapeHtml(projVersion)}</title>
</head>
<body>
<h1>Changelog</h1>
<ul>
${items}
</ul>
</body>
</html>`;
}
/**
* Changelog command
*
@@ -265,42 +298,23 @@ Options:
exit(1);
}
const templatePath = path.resolve(__dirname, `../templates/changelog-${format}.ejs`);
if (!fs.existsSync(templatePath)) {
console.error(`Cannot find the report template: ${templatePath}`);
exit(1);
}
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
const packageJson = JSON.parse(fs.readFileSync(packagePath).toString());
ejs.renderFile(
templatePath,
{
remote,
repo_url,
commits,
projVersion: packageJson.version,
projName: packageJson.name
},
{},
(err: any, text: string) => {
if (err) {
console.error(err);
reject(err);
} else {
if (output) {
const outputDir = path.resolve(output);
const outputFile = path.join(outputDir, `changelog-${packageJson.version}.${format}`);
console.log('Writing changelog to', outputFile);
const text =
format === 'html'
? renderChangelogHtml(commits, packageJson.name, packageJson.version, repo_url)
: renderChangelogMd(commits, packageJson.name, packageJson.version, repo_url);
fs.writeFileSync(outputFile, text);
} else {
console.log(text);
}
resolve(0);
}
}
);
if (output) {
const outputDir = path.resolve(output);
const outputFile = path.join(outputDir, `changelog-${packageJson.version}.${format}`);
console.log('Writing changelog to', outputFile);
fs.writeFileSync(outputFile, text);
} else {
console.log(text);
}
resolve(0);
});
}
+34 -29
View File
@@ -22,7 +22,7 @@ import { parseArgs } from 'node:util';
import * as path from 'path';
import * as fs from 'fs';
import * as licenseList from 'spdx-license-list';
import * as ejs from 'ejs';
import { escapeHtml } from './utils';
const { collectProductionLicenses } = require('../resources/license-collector.cjs');
@@ -112,6 +112,32 @@ function toLinkedLicenseExpression(rawExpression: string): string {
});
}
function renderLicensePage(filteredPackages: Record<string, PackageInfoWithMetadata>, projName: string, projVersion: string): string {
const rows = Object.entries(filteredPackages).map(([packageName, pack]) => {
const lastAtSignPos = packageName.lastIndexOf('@');
const name = packageName.substring(0, lastAtSignPos);
const version = packageName.substring(lastAtSignPos + 1);
const licenses = escapeHtml(pack.licenseExp || 'N/A');
const linkedName = pack.repository ? `[${escapeHtml(name)}](${pack.repository})` : escapeHtml(name);
return `| ${linkedName} | ${escapeHtml(version)} | ${licenses} |`;
});
return `---
Title: License info, ${escapeHtml(projName)} ${escapeHtml(projVersion)}
---
# License information for ${escapeHtml(projName)} ${escapeHtml(projVersion)}
This page lists all third party libraries the project depends on.
## Libraries
| Name | Version | License |
| --- | --- | --- |
${rows.join('\n')}
`;
}
/**
* Licenses command
*
@@ -165,12 +191,6 @@ Options:
exit(1);
}
const templatePath = path.resolve(__dirname, '../templates/licensePage.ejs');
if (!fs.existsSync(templatePath)) {
console.error(`Cannot find the report template: ${templatePath}`);
exit(1);
}
return new Promise((resolve, reject) => {
// eslint-disable-next-line no-console
console.info(`Checking ${packagePath}`);
@@ -198,28 +218,13 @@ Options:
const packageJson: PackageInfo = getPackageFile(packagePath);
ejs.renderFile(
templatePath,
{
filteredPackages,
projVersion: packageJson.version,
projName: packageJson.name
},
{},
(ejsError: unknown, mdText: string) => {
if (ejsError) {
console.error(ejsError);
reject(ejsError);
} else {
const outputPath = path.resolve(options.outDir || workingDir);
const outputFile = path.join(outputPath, `license-info-${packageJson.version}.md`);
const mdText = renderLicensePage(filteredPackages, packageJson.name, packageJson.version);
const outputPath = path.resolve(options.outDir || workingDir);
const outputFile = path.join(outputPath, `license-info-${packageJson.version}.md`);
fs.writeFileSync(outputFile, mdText);
// eslint-disable-next-line no-console
console.log(`Report saved as ${outputFile}`);
resolve(0);
}
}
);
fs.writeFileSync(outputFile, mdText);
// eslint-disable-next-line no-console
console.log(`Report saved as ${outputFile}`);
resolve(0);
});
}
+20
View File
@@ -0,0 +1,20 @@
/*!
* @license
* Copyright © 2005-2026 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export function escapeHtml(text: string): string {
return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
-38
View File
@@ -1,38 +0,0 @@
---
Title: Audit info, <%= projName %> <%= projVersion %>
---
# Audit information for <%= projName %> <%= projVersion %>
This page lists the security audit of the dependencies this project depends on.
## Risks
- Critical risk: <%= jsonAudit.metadata.vulnerabilities.critical %>
- High risk: <%= jsonAudit.metadata.vulnerabilities.high %>
- Moderate risk: <%= jsonAudit.metadata.vulnerabilities.moderate %>
- Low risk: <%= jsonAudit.metadata.vulnerabilities.low %>
Dependencies analyzed: <%= jsonAudit.metadata.totalDependencies %>
## Libraries
| Severity | Module | Vulnerable versions |
| --- | --- | --- |
<% if(jsonAudit.auditReportVersion >= 2) {
for(var currentVulnerabilities in jsonAudit.vulnerabilities) {
severity = jsonAudit.vulnerabilities[currentVulnerabilities].severity;
vulnerable_versions = JSON.stringify(jsonAudit.vulnerabilities[currentVulnerabilities].range);
module = jsonAudit.vulnerabilities[currentVulnerabilities].name;
-%>
|<%= severity %> | <%= module %> | <%= vulnerable_versions %> |
<% } %>
<% } else {
for(var currentAdvisories in jsonAudit.advisories) {
severity = jsonAudit.advisories[currentAdvisories].severity;
vulnerable_versions = JSON.stringify(jsonAudit.advisories[currentAdvisories].vulnerable_versions);
module = jsonAudit.advisories[currentAdvisories].module_name;
-%>
|<%= severity %> | <%= module %> | <%= vulnerable_versions %> |
<% } %>
<% } %>
-21
View File
@@ -1,21 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog for <%= projName %> v<%= projVersion %></title>
</head>
<body>
<h1>Changelog</h1>
<ul>
<% for(var idx in commits) {
commit = commits[idx];
-%>
<li>
<a href="<%= repo_url %>/commit/<%= commit.hash %>">[<%= commit.hash %>]</a> <%= commit.subject %>
</li>
<% } %>
</ul>
</body>
</html>
-11
View File
@@ -1,11 +0,0 @@
---
Title: Changelog for <%= projName %> v<%= projVersion %>
---
# Changelog
<% for(var idx in commits) {
commit = commits[idx];
-%>
- [<%= commit.hash %>](<%= repo_url %>/commit/<%= commit.hash %>) <%= commit.subject %>
<% } %>
-28
View File
@@ -1,28 +0,0 @@
---
Title: License info, <%= projName %> <%= projVersion %>
---
# License information for <%= projName %> <%= projVersion %>
This page lists all third party libraries the project depends on.
## Libraries
| Name | Version | License |
| --- | --- | --- |
<% for (var packageName in filteredPackages) {
var lastAtSignPos = packageName.lastIndexOf('@');
var name = packageName.substring(0, lastAtSignPos);
var version = packageName.substring(lastAtSignPos + 1);
var pack = filteredPackages[packageName];
var licenses = pack['licenseExp'] || 'N/A';
var repo = pack['repository'];
var linkedName = name;
if (repo) {
linkedName = `[${name}](${repo})`
}
-%>
| <%= linkedName %> | <%= version %> | <%= licenses %> |
<% } %>
-2
View File
@@ -93,7 +93,6 @@
"@schematics/angular": "20.3.32",
"@storybook/addon-themes": "10.4.0",
"@storybook/angular": "10.4.0",
"@types/ejs": "3.1.5",
"@types/jasmine": "4.0.3",
"@types/jasminewd2": "2.0.13",
"@types/minimatch": "5.1.2",
@@ -105,7 +104,6 @@
"@typescript-eslint/utils": "8.59.4",
"ajv": "8.20.0",
"dotenv": "16.4.7",
"ejs": "3.1.10",
"eslint": "8.57.1",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-ban": "2.0.0",
+1066 -1036
View File
File diff suppressed because it is too large Load Diff