2023-12-14 14:28:02 +08:00
|
|
|
import fs from 'node:fs';
|
|
|
|
import process from 'node:process';
|
|
|
|
import * as url from 'node:url';
|
|
|
|
import path from 'node:path';
|
2023-03-30 21:16:04 +08:00
|
|
|
import minimist from 'minimist';
|
2022-04-07 17:07:13 +08:00
|
|
|
import chalk from 'chalk';
|
|
|
|
import semver from 'semver';
|
|
|
|
import enquirer from 'enquirer';
|
|
|
|
import { execa } from 'execa';
|
|
|
|
|
|
|
|
import buildConfig from '../build.config.js';
|
|
|
|
|
|
|
|
const { prompt } = enquirer;
|
|
|
|
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
|
|
|
|
2023-04-01 15:59:37 +08:00
|
|
|
const { preid: preId, dry: isDryRun } = minimist(process.argv.slice(2));
|
2022-04-07 17:07:13 +08:00
|
|
|
const packages = buildConfig.pkgs;
|
|
|
|
|
2022-05-16 17:46:53 +08:00
|
|
|
const versionIncrements = ['patch', 'minor', 'major', 'prepatch', 'preminor', 'premajor', 'prerelease'];
|
2022-04-07 17:07:13 +08:00
|
|
|
|
2023-12-14 14:28:02 +08:00
|
|
|
function incVersion(version, i) {
|
2023-04-01 15:59:37 +08:00
|
|
|
let _preId = preId || semver.prerelease(version)?.[0];
|
2023-12-14 14:28:02 +08:00
|
|
|
if (!_preId && /pre/.test(i))
|
2023-04-01 15:59:37 +08:00
|
|
|
_preId = 'beta';
|
2023-12-14 14:28:02 +08:00
|
|
|
|
2023-04-01 15:59:37 +08:00
|
|
|
return semver.inc(version, i, _preId);
|
2023-12-14 14:28:02 +08:00
|
|
|
}
|
|
|
|
function autoIncVersion(version) {
|
|
|
|
if (version.includes('-'))
|
2022-08-22 16:00:42 +08:00
|
|
|
return semver.inc(version, 'prerelease');
|
2023-12-14 14:28:02 +08:00
|
|
|
|
2022-08-22 16:00:42 +08:00
|
|
|
return semver.inc(version, 'patch');
|
2023-12-14 14:28:02 +08:00
|
|
|
}
|
2022-08-22 16:00:42 +08:00
|
|
|
|
2022-04-07 17:07:13 +08:00
|
|
|
const run = (bin, args, opts = {}) => execa(bin, args, { stdio: 'inherit', ...opts });
|
|
|
|
const dryRun = (bin, args, opts = {}) => console.log(chalk.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts);
|
|
|
|
const runIfNotDry = isDryRun ? dryRun : run;
|
2023-12-14 14:28:02 +08:00
|
|
|
const getPkgRoot = pkg => path.resolve(__dirname, `../packages/${pkg}`);
|
|
|
|
const step = msg => console.log(chalk.cyan(msg));
|
|
|
|
function arrToObj(arr, key) {
|
|
|
|
return arr.reduce((acc, cur) => {
|
2022-08-22 16:00:42 +08:00
|
|
|
acc[cur[key]] = cur;
|
|
|
|
return acc;
|
|
|
|
}, {});
|
2023-12-14 14:28:02 +08:00
|
|
|
}
|
2022-04-07 17:07:13 +08:00
|
|
|
|
|
|
|
async function publishPackage(pkg, runIfNotDry) {
|
|
|
|
step(`Publishing ${pkg.name}...`);
|
|
|
|
try {
|
2023-03-31 18:19:35 +08:00
|
|
|
let _releaseTag;
|
2023-12-14 14:28:02 +08:00
|
|
|
if (pkg.newVersion.includes('-'))
|
2023-03-31 18:19:35 +08:00
|
|
|
_releaseTag = 'next';
|
2023-12-14 14:28:02 +08:00
|
|
|
|
2022-04-07 17:07:13 +08:00
|
|
|
await runIfNotDry(
|
2023-03-30 21:16:04 +08:00
|
|
|
// note: use of pnpm is intentional here as we rely on its publishing
|
2022-04-07 17:07:13 +08:00
|
|
|
// behavior.
|
2022-05-16 19:06:48 +08:00
|
|
|
'npm',
|
2023-03-31 18:19:35 +08:00
|
|
|
['publish', ...(_releaseTag ? ['--tag', _releaseTag] : []), '--access', 'public', '--registry', 'https://registry.npmjs.org'],
|
2022-04-07 17:07:13 +08:00
|
|
|
{
|
|
|
|
cwd: getPkgRoot(pkg.dirName),
|
|
|
|
stdio: 'pipe',
|
|
|
|
},
|
|
|
|
);
|
2022-08-17 10:53:59 +08:00
|
|
|
console.log('Successfully published :', chalk.green(`${pkg.name}@${pkg.newVersion}`));
|
2023-12-14 14:28:02 +08:00
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
if (e.stderr.match(/previously published/))
|
2022-04-07 17:07:13 +08:00
|
|
|
console.log(chalk.red(`Skipping already published: ${pkg.name}`));
|
2023-12-14 14:28:02 +08:00
|
|
|
|
|
|
|
else
|
2022-04-07 17:07:13 +08:00
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-22 16:00:42 +08:00
|
|
|
function readPackageJson(pkg) {
|
|
|
|
const pkgPath = getPkgRoot(pkg);
|
2022-08-23 17:49:37 +08:00
|
|
|
return JSON.parse(fs.readFileSync(path.join(pkgPath, 'package.json'), 'utf-8'));
|
2022-08-22 16:00:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function writePackageJson(pkg, content) {
|
|
|
|
const pkgPath = getPkgRoot(pkg);
|
2023-12-14 14:28:02 +08:00
|
|
|
fs.writeFileSync(path.join(pkgPath, 'package.json'), `${JSON.stringify(content, null, 4)}\n`);
|
2022-04-07 17:07:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function readPackageVersionAndName(pkg) {
|
2022-08-22 16:00:42 +08:00
|
|
|
const { version, name } = readPackageJson(pkg);
|
2022-04-07 17:07:13 +08:00
|
|
|
return {
|
|
|
|
version,
|
|
|
|
name,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-08-22 16:00:42 +08:00
|
|
|
function updatePackage(pkgName, version, pkgs) {
|
|
|
|
const pkgJson = readPackageJson(pkgName);
|
|
|
|
pkgJson.version = version;
|
2023-12-14 14:28:02 +08:00
|
|
|
pkgJson.dependencies
|
|
|
|
&& Object.keys(pkgJson.dependencies).forEach((npmName) => {
|
|
|
|
if (pkgs[npmName])
|
|
|
|
pkgJson.dependencies[npmName] = `^${pkgs[npmName].newVersion}`;
|
|
|
|
});
|
|
|
|
pkgJson.peerDependencies
|
|
|
|
&& Object.keys(pkgJson.peerDependencies).forEach((npmName) => {
|
|
|
|
if (pkgs[npmName])
|
|
|
|
pkgJson.peerDependencies[npmName] = `^${pkgs[npmName].newVersion}`;
|
|
|
|
});
|
2022-08-22 16:00:42 +08:00
|
|
|
writePackageJson(pkgName, pkgJson);
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateRootVersion(newRootVersion) {
|
|
|
|
const pkgPath = path.resolve(path.resolve(__dirname, '..'), 'package.json');
|
2022-04-07 17:07:13 +08:00
|
|
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
2022-08-22 16:00:42 +08:00
|
|
|
pkg.version = newRootVersion;
|
2023-12-14 14:28:02 +08:00
|
|
|
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 4)}\n`);
|
2022-04-07 17:07:13 +08:00
|
|
|
}
|
|
|
|
|
2022-08-22 16:00:42 +08:00
|
|
|
function updateVersions(packagesVersion) {
|
|
|
|
const pkgs = arrToObj(packagesVersion, 'name');
|
2023-12-14 14:28:02 +08:00
|
|
|
packagesVersion.forEach(p => updatePackage(p.dirName, p.newVersion, pkgs));
|
2022-04-07 17:07:13 +08:00
|
|
|
}
|
|
|
|
|
2023-12-14 14:28:02 +08:00
|
|
|
async function isChangeInCurrentTag(pkg, newestTag) {
|
2022-04-07 17:07:13 +08:00
|
|
|
const { stdout: pkgDiffContent } = await run('git', ['diff', newestTag, `packages/${pkg}`], { stdio: 'pipe' });
|
|
|
|
return !!pkgDiffContent;
|
2023-12-14 14:28:02 +08:00
|
|
|
}
|
2022-04-07 17:07:13 +08:00
|
|
|
|
2023-12-14 14:28:02 +08:00
|
|
|
async function filterChangedPackages() {
|
2022-04-07 17:07:13 +08:00
|
|
|
const { stdout: newestTag } = await run('git', ['describe', '--abbrev=0', '--tags'], { stdio: 'pipe' });
|
|
|
|
|
2022-05-17 19:19:43 +08:00
|
|
|
const results = await Promise.all(
|
|
|
|
packages.map(async (pkg) => {
|
|
|
|
const result = await isChangeInCurrentTag(pkg, newestTag);
|
|
|
|
return result;
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
return packages.filter((_v, index) => results[index]);
|
2023-12-14 14:28:02 +08:00
|
|
|
}
|
2022-04-07 17:07:13 +08:00
|
|
|
|
2023-04-01 15:45:51 +08:00
|
|
|
async function createPackageNewVersion(name, version) {
|
2022-04-07 17:07:13 +08:00
|
|
|
// no explicit version, offer suggestions
|
|
|
|
const { release } = await prompt({
|
|
|
|
type: 'select',
|
|
|
|
name: 'release',
|
|
|
|
message: `Select release type: ${name}`,
|
2023-12-14 14:28:02 +08:00
|
|
|
choices: versionIncrements.map(i => `${i} (${incVersion(version, i)})`).concat(['custom']),
|
2022-04-07 17:07:13 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
let newVersion;
|
|
|
|
if (release === 'custom') {
|
|
|
|
newVersion = (
|
|
|
|
await prompt({
|
|
|
|
type: 'input',
|
|
|
|
name: 'version',
|
|
|
|
message: `Input custom version: ${name}`,
|
|
|
|
initial: version,
|
|
|
|
})
|
|
|
|
).version;
|
2023-12-14 14:28:02 +08:00
|
|
|
}
|
|
|
|
else {
|
2022-04-07 17:07:13 +08:00
|
|
|
newVersion = release.match(/\((.*)\)/)[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!semver.valid(newVersion)) {
|
|
|
|
console.log(`invalid target version: ${newVersion}, please again.`);
|
2023-04-01 15:45:51 +08:00
|
|
|
return createPackageNewVersion(name, version);
|
2022-04-07 17:07:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return newVersion;
|
|
|
|
}
|
|
|
|
|
2023-04-01 15:45:51 +08:00
|
|
|
async function genRootPackageVersion() {
|
|
|
|
const pkgPath = path.resolve(path.resolve(__dirname, '..'), 'package.json');
|
|
|
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
|
|
const newVersion = await createPackageNewVersion(pkg.name, pkg.version);
|
|
|
|
return newVersion;
|
|
|
|
}
|
|
|
|
|
2022-08-22 16:00:42 +08:00
|
|
|
function genOtherPkgsVersion(packagesVersion) {
|
2023-12-14 14:28:02 +08:00
|
|
|
const noChangedPkgs = packages.filter(name => !packagesVersion.find(item => item.dirName === name));
|
2022-08-22 16:00:42 +08:00
|
|
|
const pkgs = arrToObj(packagesVersion, 'name');
|
|
|
|
const result = [];
|
|
|
|
noChangedPkgs.forEach((currentPkg) => {
|
|
|
|
const pkgJson = readPackageJson(currentPkg);
|
|
|
|
let isUpdated = false;
|
2022-08-23 17:49:37 +08:00
|
|
|
|
|
|
|
if (pkgJson.dependencies) {
|
|
|
|
Object.keys(pkgJson.dependencies).forEach((npmName) => {
|
|
|
|
if (pkgs[npmName]) {
|
|
|
|
isUpdated = true;
|
|
|
|
pkgJson.dependencies[npmName] = pkgs[npmName].newVersion;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2022-08-22 16:00:42 +08:00
|
|
|
|
|
|
|
if (isUpdated) {
|
|
|
|
const oldVersion = pkgJson.version;
|
|
|
|
pkgJson.version = autoIncVersion(oldVersion);
|
|
|
|
result.push({
|
|
|
|
dirName: currentPkg,
|
|
|
|
version: oldVersion,
|
|
|
|
newVersion: pkgJson.version,
|
|
|
|
name: pkgJson.name,
|
|
|
|
});
|
|
|
|
writePackageJson(currentPkg, pkgJson);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-04-07 17:07:13 +08:00
|
|
|
async function main() {
|
|
|
|
const changedPackages = await filterChangedPackages();
|
|
|
|
|
|
|
|
if (!changedPackages.length) {
|
|
|
|
console.log(chalk.yellow(`No changes to commit.`));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-08-22 16:00:42 +08:00
|
|
|
const updatedPkgs = [];
|
2022-04-07 17:07:13 +08:00
|
|
|
for (const pkg of changedPackages) {
|
2023-04-01 15:45:51 +08:00
|
|
|
const { name, version } = readPackageVersionAndName(pkg);
|
|
|
|
const newVersion = await createPackageNewVersion(name, version);
|
2022-08-22 16:00:42 +08:00
|
|
|
updatedPkgs.push({
|
2022-04-07 17:07:13 +08:00
|
|
|
dirName: pkg,
|
|
|
|
newVersion,
|
|
|
|
...readPackageVersionAndName(pkg),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-08-22 16:00:42 +08:00
|
|
|
const passiveUpdatePkgs = genOtherPkgsVersion(updatedPkgs);
|
|
|
|
const packagesVersion = passiveUpdatePkgs.concat(updatedPkgs);
|
|
|
|
|
2022-04-07 17:07:13 +08:00
|
|
|
const { yes } = await prompt({
|
|
|
|
type: 'confirm',
|
|
|
|
name: 'yes',
|
|
|
|
message: `These packages will be released: \n${packagesVersion
|
2023-12-14 14:28:02 +08:00
|
|
|
.map(pkg => `${chalk.magenta(pkg.name)}: v${pkg.version} > ${chalk.green(`v${pkg.newVersion}`)}`)
|
2022-04-07 17:07:13 +08:00
|
|
|
.join('\n')}\nConfirm?`,
|
|
|
|
});
|
|
|
|
|
2023-12-14 14:28:02 +08:00
|
|
|
if (!yes)
|
2022-04-07 17:07:13 +08:00
|
|
|
return;
|
|
|
|
|
2023-04-01 15:45:51 +08:00
|
|
|
const newRootVersion = await genRootPackageVersion();
|
2022-04-07 17:07:13 +08:00
|
|
|
|
|
|
|
// update all package versions and inter-dependencies
|
|
|
|
step('\nUpdating cross dependencies...');
|
2022-08-22 16:00:42 +08:00
|
|
|
updateRootVersion(newRootVersion);
|
|
|
|
updateVersions(packagesVersion);
|
2022-04-07 17:07:13 +08:00
|
|
|
|
2022-08-22 16:00:42 +08:00
|
|
|
// update lock
|
2023-04-01 15:21:28 +08:00
|
|
|
await run('pnpm', ['i']);
|
2022-04-07 17:07:13 +08:00
|
|
|
// // build all packages with types
|
|
|
|
step('\nBuilding all packages...');
|
2023-12-14 14:28:02 +08:00
|
|
|
if (!isDryRun)
|
2023-03-30 21:16:04 +08:00
|
|
|
await run('pnpm', ['build']);
|
2023-12-14 14:28:02 +08:00
|
|
|
|
|
|
|
else
|
2022-04-07 17:07:13 +08:00
|
|
|
console.log(`(skipped build)`);
|
|
|
|
|
|
|
|
// generate changelog
|
|
|
|
step('\nGenerating changelog...');
|
2023-03-30 21:16:04 +08:00
|
|
|
await run(`pnpm`, ['changelog']);
|
2022-04-07 17:07:13 +08:00
|
|
|
|
|
|
|
const { stdout } = await run('git', ['diff'], { stdio: 'pipe' });
|
|
|
|
if (stdout) {
|
|
|
|
step('\nCommitting changes...');
|
|
|
|
await runIfNotDry('git', ['add', '-A']);
|
2022-05-17 14:36:40 +08:00
|
|
|
await runIfNotDry('git', ['commit', '-m', `chore: v${newRootVersion}`]);
|
2023-12-14 14:28:02 +08:00
|
|
|
}
|
|
|
|
else {
|
2022-04-07 17:07:13 +08:00
|
|
|
console.log('No changes to commit.');
|
|
|
|
}
|
|
|
|
|
|
|
|
// publish packages
|
|
|
|
step('\nPublishing packages...');
|
2023-12-14 14:28:02 +08:00
|
|
|
for (const pkg of packagesVersion)
|
2022-04-07 17:07:13 +08:00
|
|
|
await publishPackage(pkg, runIfNotDry);
|
|
|
|
|
|
|
|
// push to GitHub
|
|
|
|
step('\nPushing to GitHub...');
|
|
|
|
await runIfNotDry('git', ['tag', `v${newRootVersion}`]);
|
|
|
|
await runIfNotDry('git', ['push', 'origin', `refs/tags/v${newRootVersion}`]);
|
|
|
|
await runIfNotDry('git', ['push']);
|
|
|
|
|
2023-12-14 14:28:02 +08:00
|
|
|
if (isDryRun)
|
2022-04-07 17:07:13 +08:00
|
|
|
console.log(`\nDry run finished - run git diff to see package changes.`);
|
2023-12-14 14:28:02 +08:00
|
|
|
|
2022-04-07 17:07:13 +08:00
|
|
|
console.log();
|
|
|
|
}
|
|
|
|
|
|
|
|
main().catch((err) => {
|
|
|
|
console.error(err);
|
|
|
|
});
|