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.
431 lines
12 KiB
JavaScript
Executable file
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);
|
|
}
|
|
});
|