Files
trezor-suite/scripts/ci/connect-bump-versions.ts
Tomas Martykan 7e0a549871 ci(connect): always bump all packages at once
ci(github): simplify connect-bump versions

We are moving to release all npm packages under same version as connect,
so all of them are going to be released every time, that means we can
get rid of all the code that was checking what packages required release
and instead release all of them which is simpler.

Co-authored-by: karliatto <yo@karliatto.com>
2026-01-23 11:11:12 +01:00

313 lines
11 KiB
TypeScript

import path from 'node:path';
import fs from 'node:fs';
import { promisify } from 'node:util';
import {
getPackageDependencies,
gettingNpmDistributionTags,
exec,
commit,
comment,
} from './helpers';
const readFile = promisify(fs.readFile);
const existsDirectory = promisify(fs.exists);
const writeFile = promisify(fs.writeFile);
const args = process.argv.slice(2);
if (args.length < 1) {
throw new Error('Check npm dependencies requires 1 parameter: semver');
}
const [semver] = args;
/**
* The release version or type. Can be one of the following:
- minor: Increase minor version (if version 9.5.0-beta.3 --> 9.5.0)
- patch: Increase patch version (if version 9.4.2-beta.3 --> 9.4.2)
- preminor: Increase preminor version, pre-release (if version 9.4.1 --> 9.5.0-beta.1)
- prepatch: Increase prepatch version, pre-release (if version 9.4.1 --> 9.4.2-beta.1)
- prerelease: Increase prerelease version (if version 9.4.1-beta.1 --> 9.4.1-beta.2)
*/
const allowedSemvers = ['patch', 'prepatch', 'minor', 'preminor', 'prerelease'];
if (!allowedSemvers.includes(semver)) {
throw new Error(`provided semver: ${semver} must be one of ${allowedSemvers.join(', ')}`);
}
const deploymentType = ['prepatch', 'preminor', 'prerelease'].includes(semver)
? 'canary'
: 'stable';
const ROOT = path.join(import.meta.dirname, '..', '..');
const getGitCommitByPackageName = (packageName: string, maxCount = 10) =>
exec('git', [
'log',
'--oneline',
'--max-count',
`${maxCount}`,
'--pretty=tformat:"- %s (%h)"',
'--',
`./packages/${packageName}`,
]);
const splitByNewlines = (input: string) => input.split('\n');
const findIndexByCommit = (commitArr: string[], searchString: string) =>
commitArr.findIndex(commit => commit.includes(searchString));
type ConnectVersionMatrix = {
package: string;
stable: string;
canary: string;
};
const tableToMarkdown = (table: ConnectVersionMatrix[], type: 'Package' | 'Deployment') => {
let markdown = `| ${type} | Stable | Canary |\n`;
markdown += '| :----: | :----: | :----:|\n';
table.forEach(row => {
markdown += `| ${row.package} | ${row.stable} | ${row.canary} |\n`;
});
return markdown;
};
const updateConnectChangelog = async (
connectChangelogPath: string,
stableVersion: string,
canaryVersion: string,
) => {
try {
const stable = stableVersion;
const canary = canaryVersion;
const changelogContent = await readFile(connectChangelogPath, 'utf-8');
const lines = changelogContent.split('\n');
const oldContent = lines.slice(10).join('\n');
const npmTable = [
{ package: 'npm @trezor/connect', stable, canary },
{ package: 'npm @trezor/connect-web', stable, canary },
{
package: 'npm @trezor/connect-webextension',
stable,
canary,
},
{
package: 'npm @trezor/connect-mobile',
stable,
canary,
},
];
const connectExplorerTable = [{ package: 'connect.trezor.io/', stable, canary }];
const markdownNpmTable = tableToMarkdown(npmTable, 'Package');
const markdownConnectExplorerTable = tableToMarkdown(connectExplorerTable, 'Deployment');
const updatedContent =
markdownNpmTable + '\n' + markdownConnectExplorerTable + '\n' + oldContent;
await writeFile(connectChangelogPath, updatedContent, 'utf-8');
} catch (error) {
console.error('Error updating CHANGELOG.md:', error);
}
};
const bumpConnect = async () => {
try {
const mainPackages = [
'connect-plugin-ethereum',
'connect-plugin-stellar',
'connect-webextension',
'connect-mobile',
'connect-web',
'connect',
];
const results = await Promise.all(
mainPackages.map(async pkg => {
const result = await getPackageDependencies(pkg);
console.log(`${pkg} dependencies to update:`, result);
return result.update;
}),
);
// We remove from the list the `mainPackages` so they are added later on in correct order,
// so the commits for `mainPackages` are at the end of the changelog.
const connectDependencies = results.flat().filter(dep => !mainPackages.includes(dep));
// Remove connect since it is going to be bumped later using script packages/connect/script/bump-version.ts
const mainPackagesWithoutConnect = mainPackages.filter(pkg => pkg !== 'connect');
// We deduplicate and add the connect packages themselves to the list of packages to update.
const allUniquePackagesToUpdate = [
...new Set([...connectDependencies, ...mainPackagesWithoutConnect]),
];
console.log('allUniquePackagesToUpdate', allUniquePackagesToUpdate);
for (const packageName of allUniquePackagesToUpdate) {
const PACKAGE_PATH = path.join(ROOT, 'packages', packageName);
const PACKAGE_JSON_PATH = path.join(PACKAGE_PATH, 'package.json');
// This uses dependency version-bump-prompt.
await exec('yarn', ['bump', semver, `./packages/${packageName}/package.json`]);
const rawPackageJSON = await readFile(PACKAGE_JSON_PATH, 'utf-8');
const packageJSON = JSON.parse(rawPackageJSON);
const { version } = packageJSON;
const packageGitLog = await getGitCommitByPackageName(packageName, 1000);
const commitsArr = packageGitLog.stdout.split('\n');
const newCommits: string[] = [];
for (const commit of commitsArr) {
// Here we check commits utils last stable release
if (commit.includes(`npm-release: @trezor/${packageName}`)) {
break;
}
newCommits.push(commit.replaceAll('"', ''));
}
// In Connect dependencies packages we only update CHANGELOG when doing a stable release (patch or minor).
// We do that so we can generate the complete CHANGELOG automatically when doing stable release.
if (newCommits.length && deploymentType === 'stable') {
const CHANGELOG_PATH = path.join(PACKAGE_PATH, 'CHANGELOG.md');
if (!(await existsDirectory(CHANGELOG_PATH))) {
await writeFile(CHANGELOG_PATH, '');
}
let changelog = await readFile(CHANGELOG_PATH, 'utf-8');
changelog = `# ${version}\n\n${newCommits.join('\n')}\n\n${changelog}`;
await writeFile(CHANGELOG_PATH, changelog, 'utf-8');
await exec('yarn', ['prettier', '--write', CHANGELOG_PATH]);
}
await commit({
path: PACKAGE_PATH,
// We only use `npm-release` when doing stable release, so the part of the script above can check commits to include in CHANGELOG.
message: `npm-${deploymentType === 'canary' ? 'prerelease' : 'release'}: @trezor/${packageName} ${version}`,
});
}
const CONNECT_PACKAGE_PATH = path.join(ROOT, 'packages', 'connect');
const CONNECT_PACKAGE_JSON_PATH = path.join(CONNECT_PACKAGE_PATH, 'package.json');
const CONNECT_CHANGELOG_PATH = path.join(CONNECT_PACKAGE_PATH, 'CHANGELOG.md');
const preBumpRawPackageJSON = await readFile(CONNECT_PACKAGE_JSON_PATH, 'utf-8');
const preBumpPackageJSON = JSON.parse(preBumpRawPackageJSON);
const { version: preBumpVersion } = preBumpPackageJSON;
// Uses script packages/connect/script/bump-version.ts to increase version in multiple files.
await exec('yarn', ['workspace', '@trezor/connect', `version:${semver}`]);
const rawPackageJSON = await readFile(CONNECT_PACKAGE_JSON_PATH, 'utf-8');
const packageJSON = JSON.parse(rawPackageJSON);
const { version } = packageJSON;
if (deploymentType === 'stable') {
await updateConnectChangelog(CONNECT_CHANGELOG_PATH, version, '-');
} else {
const distributionTags = await gettingNpmDistributionTags('@trezor/connect');
await updateConnectChangelog(CONNECT_CHANGELOG_PATH, distributionTags.latest, version);
}
await exec('yarn', ['prettier', '--write', CONNECT_CHANGELOG_PATH]);
const commitMessage = `npm-release: @trezor/connect ${version}`;
const branchName = `bump-versions/connect-${version}`;
// Check if branch exists and if so, delete it.
const branchExists = (await exec('git', ['branch', '--list', branchName])).stdout;
if (branchExists) {
throw new Error(
`Branch ${branchName} already exists, delete it and call script again.`,
);
}
await exec('git', ['checkout', '-b', branchName]);
await commit({
path: ROOT,
message: commitMessage,
});
await exec('git', ['push', 'origin', branchName]);
const ghPrCreateResult = await exec('gh', [
'pr',
'create',
'--repo',
'trezor/trezor-suite',
'--title',
`${commitMessage}`,
'--body-file',
'scripts/templates/connect-bump-version.md',
'--base',
'develop',
'--head',
branchName,
]);
const prNumber = ghPrCreateResult.stdout
.replaceAll('\n', '')
.replace('https://github.com/trezor/trezor-suite/pull/', '');
const depsChecklist = allUniquePackagesToUpdate.reduce(
(acc, packageName) =>
`${acc}\n- [ ] [![NPM](https://img.shields.io/npm/v/@trezor/${packageName}.svg)](https://www.npmjs.org/package/@trezor/${packageName}) @trezor/${packageName}`,
'',
);
if (depsChecklist) {
await comment({
prNumber,
body: depsChecklist,
});
}
// Adding list of commits form the connect* packages to help creating and checking connect CHANGELOG.
const connectGitLog = await getGitCommitByPackageName('connect*', 1000);
const [_npmReleaseConnect, ...connectGitLogArr] = splitByNewlines(connectGitLog.stdout);
const connectGitLogIndex = findIndexByCommit(
connectGitLogArr,
`npm-release: @trezor/connect ${preBumpVersion}`,
);
// Creating a comment only if there are commits to add since last connect release.
if (connectGitLogIndex !== -1) {
connectGitLogArr.splice(
connectGitLogIndex,
connectGitLogArr.length - connectGitLogIndex,
);
// In array `connectGitLogArr` each item string contains " at the beginning and " at the end, let's remove those characters.
const cleanConnectGitLogArr = connectGitLogArr.map(line => line.slice(1, -1));
const connectGitLogText = cleanConnectGitLogArr.reduce(
(acc, line) => `${acc}\n${line}`,
'',
);
if (!connectGitLogText) {
console.info('no changelog for @trezor/connect');
return;
}
await comment({
prNumber,
body: connectGitLogText,
});
}
} catch (error) {
console.info('error:', error);
}
};
bumpConnect();