master-zapier-plan-draft/vendor/zapier-platform/scripts/bump.js
George Lambert b4150c8250 Milestone 0: import zappier billing, Verae middleware, and Zapier research
Compose-ready workspace: packages/zappier (rate card, portal, Stripe),
packages/verae-zapier-middleware (timestamp + NATS), packages/verae-zapier
(CLI app), vendor/zapier-platform, and research/zapier vendor corpus.

Gate 0 structure checks pass. Product code and research are not yet wired.
2026-09-09 02:37:36 -04:00

431 lines
12 KiB
JavaScript
Executable file

#!/usr/bin/env node
/* eslint-disable array-callback-return */
const fs = require('fs');
const path = require('path');
const { bold, underline } = require('chalk');
const { spawnSync } = require('child_process');
const inquirer = require('inquirer');
const semver = require('semver');
const REPO_DIR = path.dirname(__dirname);
// Interactive mode for humans:
// node bump.js
//
// Non-interactive mode for AI agents:
// node bump.js <new_version> — bumps cli, core, schema (default)
// node bump.js <package_name> <new_version> — bumps specified package
//
// package_name:
// - "cli", "core", or "schema" (all three are bumped together)
// - "legacy-scripting-runner"
//
// new_version:
// - a semver version like "15.9.1"
// - or a bump type: "patch", "minor", "major"
const args = process.argv.slice(2);
if (['--help', '-h', 'help'].includes(args[0])) {
console.log(`Usage:
pnpm bump Interactive mode (for humans)
pnpm bump <new_version> Bump cli, core, schema (default)
pnpm bump <package_name> <new_version> Bump specified package
package_name:
cli | core | schema Any one of these bumps all three together
legacy-scripting-runner Bumped independently
new_version:
patch, minor, major Bump type
X.Y.Z Exact semver version (e.g. 15.9.1)`);
process.exit(0);
}
const nonInteractive = args.length > 0;
const MAIN_PACKAGES = ['cli', 'core', 'schema'];
const resolvePackageArg = (arg) => {
if (MAIN_PACKAGES.includes(arg)) {
return 'cli, core, schema';
}
if (arg === 'legacy-scripting-runner') {
return arg;
}
throw new Error(
`Unknown package "${arg}". Valid values: ${MAIN_PACKAGES.join(', ')}, legacy-scripting-runner`,
);
};
const resolveVersionArg = (arg, currentVersion) => {
if (['patch', 'minor', 'major'].includes(arg)) {
return semver.inc(currentVersion, arg);
}
if (semver.valid(arg)) {
return arg;
}
throw new Error(
`Invalid version "${arg}". Use a semver version (e.g. 15.9.1) or a bump type (patch, minor, major).`,
);
};
const readJson = (path) => {
return JSON.parse(fs.readFileSync(path, { encoding: 'utf8' }));
};
const writeJson = (path, obj) => {
const content = JSON.stringify(obj, null, 2) + '\n';
fs.writeFileSync(path, content, { encoding: 'utf8' });
};
const PACKAGE_ORIG_VERSIONS = [
'cli',
'core',
'schema',
'legacy-scripting-runner',
].reduce((result, packageName) => {
const packageJsonPath = path.join(
REPO_DIR,
'packages',
packageName,
'package.json',
);
const packageJson = readJson(packageJsonPath);
return { ...result, [packageName]: packageJson.version };
}, {});
const ensureMainPackageVersionsAreSame = () => {
if (
!(
PACKAGE_ORIG_VERSIONS.cli === PACKAGE_ORIG_VERSIONS.core &&
PACKAGE_ORIG_VERSIONS.core === PACKAGE_ORIG_VERSIONS.schema
)
) {
throw new Error(
'Packages must have the same version number.\nInstead, we got ' +
JSON.stringify(PACKAGE_ORIG_VERSIONS),
);
}
};
const confirmIfMasterBranch = async () => {
const result = spawnSync('git', ['branch', '--show-current'], {
encoding: 'utf8',
});
const branch = (result.stdout || '').trim();
if (branch === 'master' || branch === 'main') {
if (nonInteractive) {
throw new Error(
`This was supposed to be run on a feature branch, but you're on ${branch}.`,
);
}
const answer = await inquirer.prompt([
{
type: 'confirm',
name: 'continue',
message:
'This was supposed to be run on a feature branch. ' +
`You want to proceed with branch ${underline(branch)} anyway?`,
default: false,
},
]);
if (!answer.continue) {
throw new Error('Canceled.');
}
}
};
const ensureNoUncommittedChanges = () => {
const result = spawnSync(
'git',
['status', '--untracked-files=no', '--porcelain'],
{ encoding: 'utf8' },
);
const lines = result.stdout
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line);
if (lines.length > 0) {
throw new Error(
`${bold.underline('git status')} shows you have uncommitted changes. ` +
'Commit or discard those before you try again.',
);
}
};
const promptPackagesToBump = async () => {
const answer = await inquirer.prompt([
{
type: 'list',
message: 'What package(s) you want to bump?',
name: 'package',
choices: [
{
name: `cli, core, schema (currently ${PACKAGE_ORIG_VERSIONS.cli})`,
value: 'cli, core, schema',
},
{
name: `legacy-scripting-runner (currently ${PACKAGE_ORIG_VERSIONS['legacy-scripting-runner']})`,
value: 'legacy-scripting-runner',
},
],
},
]);
return [answer.package];
};
const promptVersionToBump = async (packageName) => {
const currentVersion = PACKAGE_ORIG_VERSIONS[packageName];
const choices = ['patch', 'minor', 'major'].map((bumpType) => {
const version = semver.inc(currentVersion, bumpType);
return {
name: `${version} (${bumpType})`,
value: version,
};
});
const answer = await inquirer.prompt([
{
type: 'list',
name: 'versionToBump',
message: `Version to bump for ${underline(packageName)}?`,
choices,
},
]);
return answer.versionToBump;
};
const bumpMainPackagesForExampleApps = (versionToBump) => {
const examplesDir = path.join(REPO_DIR, 'example-apps');
fs.readdirSync(examplesDir, { withFileTypes: true }).map((item) => {
if (item.isDirectory()) {
const packageJsonPath = path.join(examplesDir, item.name, 'package.json');
const packageJson = readJson(packageJsonPath);
['cli', 'core', 'schema'].map((packageName) => {
const packageFullName = `zapier-platform-${packageName}`;
if (packageJson.dependencies) {
const depVersion = packageJson.dependencies[packageFullName];
if (depVersion) {
console.log(
`${item.name}'s dependency ${packageName} ${depVersion} -> ${versionToBump}`,
);
packageJson.dependencies[packageFullName] = versionToBump;
}
}
});
writeJson(packageJsonPath, packageJson);
}
});
};
// Main packages are cli, core, schema
const bumpMainPackages = (versionToBump) => {
const PACKAGES = ['cli', 'core', 'schema'];
PACKAGES.map((packageName) => {
const packageJsonPath = path.join(
REPO_DIR,
'packages',
packageName,
'package.json',
);
const packageJson = readJson(packageJsonPath);
console.log(
`${packageName} ${PACKAGE_ORIG_VERSIONS[packageName]} -> ${versionToBump}`,
);
packageJson.version = versionToBump;
PACKAGES.map((depName) => {
const depFullName = `zapier-platform-${depName}`;
const depVersion = packageJson.dependencies[depFullName];
if (depVersion && !depVersion.startsWith('workspace:')) {
console.log(
`${packageName}'s dependency ${depName} ${depVersion} -> ${versionToBump}`,
);
packageJson.dependencies[depFullName] = versionToBump;
}
});
writeJson(packageJsonPath, packageJson);
});
};
// "Extension package", such as legacy-scripting-runner
const bumpExtensionPackage = (packageName, versionToBump) => {
const packageJsonPath = path.join(
REPO_DIR,
'packages',
packageName,
'package.json',
);
const packageJson = readJson(packageJsonPath);
console.log(`${packageName} ${packageJson.version} -> ${versionToBump}`);
packageJson.version = versionToBump;
writeJson(packageJsonPath, packageJson);
};
const bumpPackages = (packageName, versionToBump) => {
if (packageName === 'cli, core, schema') {
bumpMainPackages(versionToBump);
bumpMainPackagesForExampleApps(versionToBump);
} else {
bumpExtensionPackage(packageName, versionToBump);
}
};
const gitAdd = () => {
const result = spawnSync(
'git',
['add', 'packages/*/package.json', 'example-apps/*/package.json'],
{
stdio: [0, 1, 2],
},
);
if (result.status !== 0) {
throw new Error('git-add failed');
}
};
const buildCommitMessage = (versionsToBump) => {
const messageParts = Object.keys(versionsToBump).map((packageName) => {
const fromVersion = PACKAGE_ORIG_VERSIONS[packageName];
const toVersion = versionsToBump[packageName];
return `${packageName} ${fromVersion} -> ${toVersion}`;
});
return 'Bump ' + messageParts.join(', ');
};
const gitCommit = (message) => {
const result = spawnSync('git', ['commit', '-m', message], {
stdio: [0, 1, 2],
});
if (result.status !== 0) {
throw new Error('git-commit failed');
}
};
const gitTag = (versionsToBump) => {
const toVersions = Object.keys(versionsToBump).reduce(
(result, packageName) => {
const toVersion = versionsToBump[packageName];
if (packageName === 'cli, core, schema') {
result.cli = result.core = result.schema = toVersion;
} else {
result[packageName] = toVersion;
}
return result;
},
{},
);
Object.keys(toVersions).map((packageName) => {
const version = toVersions[packageName];
const tag = `zapier-platform-${packageName}@${version}`;
const result = spawnSync('git', ['tag', '-a', tag, '-m', tag], {
stdio: [0, 1, 2],
});
if (result.status !== 0) {
throw new Error('git-tag failed');
}
});
};
const main = async () => {
try {
ensureMainPackageVersionsAreSame();
await confirmIfMasterBranch();
ensureNoUncommittedChanges();
} catch (err) {
console.error(err.message);
return 1;
}
// Currently we bump cli, core, schema together
PACKAGE_ORIG_VERSIONS['cli, core, schema'] = PACKAGE_ORIG_VERSIONS.cli;
let packagesToBump;
const versionsToBump = {};
if (nonInteractive) {
try {
let packageName, versionArg;
if (args.length === 1) {
packageName = 'cli, core, schema';
versionArg = args[0];
} else {
packageName = resolvePackageArg(args[0]);
versionArg = args[1];
}
const currentVersion = PACKAGE_ORIG_VERSIONS[packageName];
const version = resolveVersionArg(versionArg, currentVersion);
packagesToBump = [packageName];
versionsToBump[packageName] = version;
} catch (err) {
console.error(err.message);
return 1;
}
} else {
packagesToBump = await promptPackagesToBump();
if (packagesToBump.length === 0) {
console.error('No packages selected, nothing to do here.');
return 1;
}
for (const packageName of packagesToBump) {
versionsToBump[packageName] = await promptVersionToBump(packageName);
}
}
Object.keys(versionsToBump).map((packageName) => {
bumpPackages(packageName, versionsToBump[packageName]);
});
try {
gitAdd();
gitCommit(buildCommitMessage(versionsToBump));
gitTag(versionsToBump);
} catch (err) {
// TODO: Roll back
console.error(err.message);
console.error(
`Now you may have to use ${bold.underline('git reset --hard HEAD~1')} and ` +
`${bold.underline('git tag -d <tags>')} to roll back the changes.`,
);
return 1;
}
console.log(
`\nDone! Review the change with ${bold.underline(
'git diff HEAD~1..HEAD',
)} then ${bold.underline('git push origin HEAD --tags')}.`,
);
const tags = Object.keys(versionsToBump).flatMap((packageName) => {
const version = versionsToBump[packageName];
if (packageName === 'cli, core, schema') {
return ['cli', 'core', 'schema'].map(
(p) => `zapier-platform-${p}@${version}`,
);
}
return [`zapier-platform-${packageName}@${version}`];
});
const tagDeleteCmd = `git tag -d ${tags.join(' ')}`;
console.log(
`To revert, run ${bold.underline('git reset --hard HEAD~1')} and ${bold.underline(tagDeleteCmd)}.`,
);
return 0;
};
main().then((exitCode) => {
if (exitCode) {
process.exit(exitCode);
}
});