diff --git a/lib/cli/README.md b/lib/cli/README.md index bd64d8f82c..442a43668c 100644 --- a/lib/cli/README.md +++ b/lib/cli/README.md @@ -42,6 +42,7 @@ In develop mode, the CLI takes the prebuilt scripts from the dist folder. | **Commands** |**Description** | |--- |--- | +| changelog | Generate changelog report for two branches of git repository | |check-cs-env |Check cs env is up | |check-ps-env |Check ps env is up | |check-plugin-env |Check plugin status | @@ -61,6 +62,61 @@ In develop mode, the CLI takes the prebuilt scripts from the dist folder. ## Examples +### Changelog + +You can get command details by using `--help` argument + +```bash +adf-cli changelog --help +``` + +The format of the command is as following: + +```bash +Usage: adf-cli changelog [options] + +Generate changelog report for two branches of git repository + +Options: + -v, --version output the version number + -r, --range Commit range, e.g. master..develop (default: "master..develop") + -d, --dir Working directory (default: working directory) + -m, --max Limit the number of commits to output + -o, --output Output directory, will use console output if not defined + --skip Skip number commits before starting to show the commit output + -f, --format Output format (md, html) (default: "md") + -e --exclude Exclude authors from the output, comma-delimited list + -h, --help output usage information +``` + +#### Usage examples + +```sh +# show changelog in the console for the current directory +adf-cli changelog -r master..develop -d . + +# show changelog in the console for a specific folder +adf-cli changelog -d ~/github/alfresco-ng2-components + +# generate changelog for specific folder and pipe the console output to a file +adf-cli changelog -d ~/github/alfresco-ng2-components > log.md + +# generate changelog report in the default format as "changelog-X.X.X.md" and save to the current folder +adf-cli changelog -d ~/github/alfresco-ng2-components -o . + +# generate changelog report and save it to a specific folder +adf-cli changelog -d ~/github/alfresco-ng2-components -o ../reports + +# generate changelog report in the HTML format and save to the current folder +adf-cli changelog -d ~/github/alfresco-ng2-components -f html -o . + +# generate report in the default format and save to the current folder, reset all filters (including embedded ones for bots) +adf-cli changelog -d ~/github/alfresco-ng2-components -e "" + +# generate report in the default format excluding commits made by certain authors +adf-cli changelog -d ~/github/alfresco-ng2-components -e "bot,user1,user2" +``` + ### License Check Move in the folder where you have your `package.json` and run the command: diff --git a/lib/cli/bin/adf-cli b/lib/cli/bin/adf-cli index c256a1bd92..af4284f789 100755 --- a/lib/cli/bin/adf-cli +++ b/lib/cli/bin/adf-cli @@ -5,12 +5,18 @@ const minimist = require('minimist'); const path = require('path'); const fs = require('fs'); +function printHelp() { + const pkgData = fs.readFileSync(path.resolve(__dirname, '..', 'package.json')); + const { name, version } = JSON.parse(pkgData); + console.log(`${name} v${version}`); +} + const args = minimist(process.argv.slice(2), { boolean: ['verbose'] }); if (args._.length === 0) { - console.error('Error: no commands provided'); + printHelp(); process.exit(1); } diff --git a/lib/cli/package-lock.json b/lib/cli/package-lock.json index 207237eee1..e5f772f2f2 100644 --- a/lib/cli/package-lock.json +++ b/lib/cli/package-lock.json @@ -14,6 +14,12 @@ "superagent": "^6.0.0" } }, + "@types/ejs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.0.tgz", + "integrity": "sha512-DCg+Ka+uDQ31lJ/UtEXVlaeV3d6t81gifaVWKJy4MYVVgvJttyX/viREy+If7fz+tK/gVxTGMtyrFPnm4gjrVA==", + "dev": true + }, "@types/glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", diff --git a/lib/cli/package.json b/lib/cli/package.json index cefccaa700..ac1632728b 100644 --- a/lib/cli/package.json +++ b/lib/cli/package.json @@ -35,6 +35,7 @@ ], "license": "Apache-2.0", "devDependencies": { + "@types/ejs": "^3.1.0", "@types/shelljs": "^0.8.7", "typescript": "3.9.3" } diff --git a/lib/cli/scripts/changelog.ts b/lib/cli/scripts/changelog.ts new file mode 100644 index 0000000000..5ef7800102 --- /dev/null +++ b/lib/cli/scripts/changelog.ts @@ -0,0 +1,194 @@ +#!/usr/bin/env node + +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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. + */ + +import * as shell from 'shelljs'; +import * as path from 'path'; +import * as program from 'commander'; +import * as fs from 'fs'; +import * as ejs from 'ejs'; + +interface Commit { + hash: string; + author: string; + author_email: string; + date: string; + subject: string; +} + +interface DiffOptions { + /** + * Commit range, e.g. "master..develop" + */ + range: string; + /** + * Working directory + */ + dir: string; + /** + * Max number of commits + */ + max?: number; + /** + * Number of commits to skip from the top + */ + skip?: number; + /** + * Exclude commits by the author + */ + exclude?: string; +} + +/** + * Get the remote URL for the cloned git repository + * @param workingDir Repository directory + * @returns URL pointing to the git remote + */ +function getRemote(workingDir: string): string { + const command = 'git config --get remote.origin.url'; + const remote = shell.exec(command, { cwd: workingDir, silent: true }).toString(); + + return remote.trim(); +} + +/** + * Get the list of commits based on the configuration options + * @param options Logging options + * @returns Collection of Commit objects + */ +function getCommits(options: DiffOptions): Array { + let authorFilter = (options.exclude || '') + .split(',') + .map(str => str.trim()) + .join('\|'); + + if (!authorFilter) { + authorFilter = "bot\|Alfresco Build User"; + } + + + const args = [ + `git`, + `log`, + options.range, + `--no-merges`, + `--first-parent`, + `--invert-grep`, + `--author="${authorFilter}"`, + // this format is needed to allow parsing all characters in the commit message and safely convert to JSON + `--format="{ ^@^hash^@^: ^@^%h^@^, ^@^author^@^: ^@^%an^@^, ^@^author_email^@^: ^@^%ae^@^, ^@^date^@^: ^@^%ad^@^, ^@^subject^@^: ^@^%s^@^ }"` + ]; + + if (options.max !== undefined) { + args.push(`--max-count=${options.max}`); + } + + if (options.skip !== undefined) { + args.push(`--skip=${options.skip}`); + } + + const command = args.join(' '); + + let log = shell.exec(command, { cwd: options.dir, silent: true }).toString(); + + // https://stackoverflow.com/a/13928240/14644447 + log = log.trim().replace(/"/gm, '\\"').replace(/\^@\^/gm, '"'); + if (log.endsWith(',')) { + log = log.substring (0, log.length - 1); + } + + return log.split('\n').map(str => JSON.parse(str) as Commit); +} + +export default function main(_args: string[], workingDir: string) { + program + .description('Generate changelog report for two branches of git repository') + .version('0.0.1', '-v, --version') + .usage('changelog [options]') + .option('-r, --range ', 'Commit range, e.g. master..develop', 'master..develop') + .option('-d, --dir ', 'Working directory (default: working directory)') + .option('-m, --max ', 'Limit the number of commits to output') + .option('-o, --output ', 'Output directory, will use console output if not defined') + .option('--skip ', 'Skip number commits before starting to show the commit output') + .option('-f, --format ', 'Output format (md, html)', 'md') + .option('-e --exclude ', 'Exclude authors from the output, comma-delimited list') + .parse(process.argv); + + if (process.argv.includes('-h') || process.argv.includes('--help')) { + program.outputHelp(); + return; + } + + const dir = path.resolve(program.dir || workingDir); + const { range, skip, max, format, output, exclude } = program; + + const remote = getRemote(dir); + + let repo_url = remote; + if (repo_url.endsWith('.git')) { + repo_url = repo_url.substring(0, repo_url.length - 4); + } + + const commits = getCommits({ + dir, + range, + skip, + max, + exclude + }); + + const packagePath = path.resolve(dir, 'package.json'); + if (!fs.existsSync(packagePath)) { + console.error('The package.json file was not found'); + process.exit(1); + } + + const templatePath = path.resolve(__dirname, `../templates/changelog-${format}.ejs`); + if (!fs.existsSync(templatePath)) { + console.error(`Cannot find the report template: ${templatePath}`); + process.exit(1); + } + + return new Promise((resolve, reject) => { + 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(1); + } else { + 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); + } + }); + }); +} diff --git a/lib/cli/templates/changelog-html.ejs b/lib/cli/templates/changelog-html.ejs new file mode 100644 index 0000000000..f0d283e9e7 --- /dev/null +++ b/lib/cli/templates/changelog-html.ejs @@ -0,0 +1,21 @@ + + + + + + + Changelog for <%= projName %> v<%= projVersion %> + + +

Changelog

+
    + <% for(var idx in commits) { + commit = commits[idx]; + -%> +
  • + [<%= commit.hash %>] <%= commit.subject %> +
  • + <% } %> +
+ + diff --git a/lib/cli/templates/changelog-md.ejs b/lib/cli/templates/changelog-md.ejs new file mode 100644 index 0000000000..f37f350dbb --- /dev/null +++ b/lib/cli/templates/changelog-md.ejs @@ -0,0 +1,11 @@ +--- +Title: Changelog for <%= projName %> v<%= projVersion %> +--- + +# Changelog + +<% for(var idx in commits) { + commit = commits[idx]; +-%> +- [<%= commit.hash %>](<%= repo_url %>/commit/<%= commit.hash %>) <%= commit.subject %> +<% } %>