Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"@babel/core": "^7.22.5",
"@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.2",
"@conventional-changelog/git-client": "^2.5.1",
"@eslint/compat": "^1.2.5",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.19.0",
Expand All @@ -81,10 +82,10 @@
"buffer": "^5.5.0",
"chalk": "^5.2.0",
"command-line-args": "^5.2.1",
"conventional-changelog-angular": "^5.0.13",
"conventional-changelog-cli": "^2.2.2",
"conventional-commits-filter": "^2.0.7",
"conventional-commits-parser": "^3.2.4",
"conventional-changelog": "^7.1.0",
"conventional-changelog-angular": "^8.0.0",
"conventional-commits-filter": "^5.0.0",
"conventional-commits-parser": "^6.2.0",
"conventional-recommended-bump": "^6.1.0",
"crypto-browserify": "^3.12.0",
"esbuild": "^0.20.1",
Expand Down
97 changes: 97 additions & 0 deletions scripts/changelog/command.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env node
'use strict';

import commandLineArgs from 'command-line-args';
import {
getInfoBeforeGeneratingChangelog,
generateChangelogAndSave,
generateChangelogAndPrint,
} from '../common/changelog.mjs';
import {
workspacePackages,
packageNamesToPackagesWithInfo,
} from '../common/utils.mjs';

async function generateChangelogForRoot(options) {
const { from, commitsCount } = await getInfoBeforeGeneratingChangelog();

if (commitsCount > 0) {
console.log('from:', from || 'start (first release)');
console.log('commits:', commitsCount);

if (options.save) {
await generateChangelogAndSave();
} else {
generateChangelogAndPrint();
}
} else {
console.log(`No commits found, skipping changelog generation.`);
}
}

async function generateChangelogForWorkspaceMembers(pkgs) {
for (const pkg of pkgs) {
const { from, commitsCount } = await getInfoBeforeGeneratingChangelog(pkg);

if (commitsCount > 0) {
console.log('name:', pkg.name);
console.log('from:', from || 'start (first release)');
console.log('commits:', commitsCount);

if (save) {
await generateChangelogAndSave(pkg);
} else {
generateChangelogAndPrint(pkg);
}
} else {
console.log(
`No commits found for ${pkg.name}, skipping changelog generation.`
);
}
}
}

// NOTE 1: changelog should be run before the tagging proccess for the new release. checkout the steps here: https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog#recommended-workflow
// NOTE 2: we use tags. tags with package@semver format.
// NOTE 3: when don't use any flag, we will last valid tag as starting point.
async function run() {
const optionDefinitions = [
{ name: 'name', type: String },
{ name: 'save', type: Boolean },
{ name: 'all', type: Boolean },
];
const { name, save, all } = commandLineArgs(optionDefinitions);

if (name && all)
throw new Error('One of the --name or --all flag should be given');

if (name || all) {
// Create a list of packages we are going to create changelog for.
const pkgs = [];
if (name) {
const pkgs = await packageNamesToPackagesWithInfo([name]);
if (pkgs.length !== 1)
throw new Error('Your provided package is not found.', { cause: pkgs });

pkgs.push(pkgs[0]);
} else {
const list = await workspacePackages();
list
.filter((pkg) => !pkg.private)
.forEach((pkg) => {
pkgs.push(pkg);
});
}

await generateChangelogForWorkspaceMembers(pkgs);
} else {
await generateChangelogForRoot({
save,
});
}
}

run().catch((e) => {
console.error(e);
process.exit(1);
});
11 changes: 8 additions & 3 deletions scripts/check-conventional-commits/command.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { execa } from 'execa';
import { logAsSection } from '../publish/utils.mjs';
import { detectChannel } from '../common/github.mjs';
import parser from 'conventional-commits-parser';
import filter from 'conventional-commits-filter';
import { CommitParser } from 'conventional-commits-parser';
import { filterRevertedCommitsSync } from 'conventional-commits-filter';

async function run() {
const channel = detectChannel();
Expand All @@ -15,7 +15,12 @@ async function run() {
'--pretty=format:%B__________',
]);
const commits = logs.split('__________').filter(Boolean);
const parsedCommits = filter(commits.map(parser.sync));

const parser = new CommitParser();
const parsedCommits = Array.from(
filterRevertedCommitsSync(commits.map((commit) => parser.parse(commit)))
);

const hasAnyConventionalCommit = parsedCommits.some(
(commit) => !!commit.type
);
Expand Down
182 changes: 145 additions & 37 deletions scripts/common/changelog.mjs
Original file line number Diff line number Diff line change
@@ -1,49 +1,157 @@
import { execa } from 'execa';
import { GenerateChangelogFailedError, YarnError } from './errors.mjs';
import { packageNameWithoutScope } from './utils.mjs';
import { ConventionalChangelog } from 'conventional-changelog';
import { ConventionalGitClient } from '@conventional-changelog/git-client';
import { WriteStream } from 'node:fs';
import fs from 'node:fs';
import path from 'node:path';

// Our tagging is using lerna convention which is package-name@version
// for example for @rango-dev/wallets-core, it will be wallets-core@1.1.0
const TAG_PACKAGE_PREFIX = (pkg) => `${packageNameWithoutScope(pkg.name)}@`;
const TAG_ROOT_PREFIX = /^[^@]+@/;

// TODO: this is not correct assumption that the script will be run from the root.
// I made it a function to make it easier correct behaviour in future.
function rootPath() {
return path.join('.');
}
function rootPackageJson() {
return path.join(rootPath(), 'package.json');
}

/**
* Generate a changelog by using convetional commit format.
* Retrieving some useful information when you are going to generate a changelog
*
* @param {import("./typedefs.mjs").Package} pkg
* @param {Object} options
* @param {boolean} options.saveToFile `true` for using it for creating `pkg/CHANGELOG.com` and `false` for Github Release note.
* @param {import("./typedefs.mjs").Package} [pkg]
*/
export async function generateChangelog(pkg, options) {
const { saveToFile = false } = options || {};

const conventionalChangelogBinPath = await execa('yarn', [
'bin',
'conventional-changelog',
])
.then((result) => result.stdout)
.catch((err) => {
throw new YarnError(`GetBinaryPathFailed: \n${err.stdout}`);
export async function getInfoBeforeGeneratingChangelog(pkg) {
const gitClient = new ConventionalGitClient(process.cwd());

let commitsParams = {
merges: false,
};

let startFromTag = undefined;
if (pkg) {
const tagsParams = {
prefix: TAG_PACKAGE_PREFIX(pkg),
};
const semverTagsStream = gitClient.getSemverTags(tagsParams);

const semverTags = [];
for await (const tag of semverTagsStream) {
semverTags.push(tag);
}
startFromTag = semverTags[0];

commitsParams = {
...commitsParams,
from: startFromTag,
path: pkg.location,
};
} else {
const semverTag = await gitClient.getLastSemverTag({
// HEADS UP:
// The following regex pattern supports the `package@1.1.1` format, which meets our needs for now.
// scoped tags like `@a/b@1.1.1` are not currently supported.
prefix: TAG_ROOT_PREFIX,
});
// If there are no semver tags, null is returned. In that case, we change it undefined to match the `string | undefined` signature.
startFromTag = semverTag || undefined;

const tagName = packageNameWithoutScope(pkg.name);
const command = [
'conventional-changelog',
'-p',
'angular',
'-l',
`${tagName}`,
'-k',
pkg.location,
'--commit-path',
pkg.location,
];

if (saveToFile) {
const changelogPath = `${pkg.location}/CHANGELOG.md`;
command.push('-i', changelogPath, '-s');
if (startFromTag) {
commitsParams = {
...commitsParams,
from: startFromTag,
};
}
}

const result = await execa(conventionalChangelogBinPath, command)
.then((result) => result.stdout)
.catch((err) => {
throw new GenerateChangelogFailedError(err.stdout);
const commitsStream = gitClient.getCommits(commitsParams);

const commits = [];
for await (const commit of commitsStream) {
commits.push(commit);
}

return {
/** Where is considering as starting point, it is genrally a tag. undefined means it's the first release.'*/
from: startFromTag,
/** How many commits this release has. */
commitsCount: commits.length,
};
}

/**
* Create a write stream for the target package's changelog.
*
* @param {import("./typedefs.mjs").Package} pkg
* @returns {WriteStream}
*/
export function changelogFileStream(pkg) {
const changelogPath = path.join(pkg.location, 'CHANGELOG.md');
const file = fs.createWriteStream(changelogPath, {
encoding: 'utf-8',
flags: 'a',
});

return file;
}

/**
*
* Generate a changelog by using convetional commit format.
* It uses tags to identify releases.
*
* @param {import("./typedefs.mjs").Package} [pkg]
* @returns {ReadableStream}
*/
export function generateChangelog(pkg) {
const generator = new ConventionalChangelog(process.cwd());
generator.loadPreset('angular');

if (pkg) {
generator.readPackage(`${pkg.location}/package.json`);
generator.commits({
path: pkg.location,
});

return result;
generator.tags({
prefix: TAG_PACKAGE_PREFIX(pkg),
});
} else {
generator.readPackage(rootPackageJson());
generator.tags({
prefix: TAG_ROOT_PREFIX,
});
}

return generator.writeStream();
}

/**
*
* @param {import("./typedefs.mjs").Package} [pkg]
*/
export async function generateChangelogAndSave(pkg) {
return new Promise((resolve, reject) => {
const changelog = generateChangelog(pkg);

// we only need location for file stream, when pkg is undefined, we will point to root package.json
if (!pkg) pkg = { location: rootPath() };

const writeStream = changelog.pipe(changelogFileStream(pkg));

writeStream.on('finish', resolve);
writeStream.on('error', reject);
});
}

/**
*
* @param {import("./typedefs.mjs").Package} [pkg]
*/
export function generateChangelogAndPrint(pkg) {
const changelog = generateChangelog(pkg);
changelog.pipe(process.stdout);
}
10 changes: 2 additions & 8 deletions scripts/common/errors.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,6 @@ export class IncreaseVersionFailedError extends Error {
}
}

export class GenerateChangelogFailedError extends Error {
name = 'GenerateChangelogFailedError';
constructor(msg) {
super(msg);
}
}

export class UnableToProceedPublishError extends Error {
name = 'UnableToProceedPublishError';
constructor(msg) {
Expand Down Expand Up @@ -110,4 +103,5 @@ export class VercelError extends Error {
constructor(msg) {
super(msg);
}
}
}

8 changes: 5 additions & 3 deletions scripts/common/github.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ export async function getGithubReleaseFor(pkg) {
* @param {import('./typedefs.mjs').Package} pkg
*/
export async function makeGithubRelease(pkg) {
const notes = await generateChangelog(pkg, {
saveToFile: false,
});
const notes = '';
for await (chunk of generateChangelog(pkg)) {
notes += chunk;
}

const tagName = generateTagName(pkg);
const output = await execa('gh', [
'release',
Expand Down
Loading