Skip to main content
Glama
release-package.js7.11 kB
#!/usr/bin/env node /* eslint-disable no-process-exit */ /* eslint-disable no-undef */ /** * Release script for individual packages in the monorepo * Usage: node scripts/release-package.js <package-name> [version] [--dry-run] * * Package names: * - plugin: @hypothesi/tauri-plugin-mcp-bridge (Cargo + NPM) * - server: @hypothesi/tauri-mcp-server (NPM only) */ import { execSync } from 'child_process'; import { readFileSync, writeFileSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const rootDir = join(__dirname, '..'); const packages = { plugin: { name: '@hypothesi/tauri-plugin-mcp-bridge', path: 'packages/tauri-plugin-mcp-bridge', cargo: true, npm: true, githubTag: 'tauri-plugin-mcp-bridge', }, server: { name: '@hypothesi/tauri-mcp-server', path: 'packages/mcp-server', cargo: false, npm: true, githubTag: 'tauri-mcp-server', }, }; function exec(command, cwd = rootDir) { console.log(`\n> ${command}`); return execSync(command, { cwd, stdio: 'inherit', encoding: 'utf-8' }); } function readJson(path) { return JSON.parse(readFileSync(path, 'utf-8')); } function writeJson(path, data) { writeFileSync(path, JSON.stringify(data, null, 2) + '\n'); } function readToml(path) { return readFileSync(path, 'utf-8'); } function writeToml(path, content) { writeFileSync(path, content); } function getCurrentVersion(pkg) { if (pkg.cargo) { const cargoToml = readToml(join(rootDir, pkg.path, 'Cargo.toml')); const match = cargoToml.match(/^version = "([^"]+)"/m); return match ? match[1] : null; } const packageJson = readJson(join(rootDir, pkg.path, 'package.json')); return packageJson.version; } function updateVersion(pkg, newVersion) { if (pkg.cargo) { const cargoPath = join(rootDir, pkg.path, 'Cargo.toml'); let cargoToml = readToml(cargoPath); cargoToml = cargoToml.replace(/^version = "[^"]+"/m, `version = "${newVersion}"`); writeToml(cargoPath, cargoToml); } if (pkg.npm) { const packagePath = join(rootDir, pkg.path, 'package.json'); const packageJson = readJson(packagePath); packageJson.version = newVersion; writeJson(packagePath, packageJson); } } function buildPackage(pkg) { console.log(`\n[BUILD] Building ${pkg.name}...`); if (pkg.cargo) { exec('cargo build --release', join(rootDir, pkg.path)); } if (pkg.npm) { exec('npm run build', join(rootDir, pkg.path)); } } function publishCargo(pkg, dryRun) { console.log(`\n[PUBLISH] Publishing ${pkg.name} to crates.io...`); const cwd = join(rootDir, pkg.path); if (dryRun) { exec('cargo package --dry-run', cwd); exec('cargo publish --dry-run', cwd); } else { exec('cargo package', cwd); exec('cargo publish', cwd); } } function publishNpm(pkg, dryRun) { console.log(`\n[PUBLISH] Publishing ${pkg.name} to npm...`); const cwd = join(rootDir, pkg.path); if (dryRun) { exec('npm publish --dry-run', cwd); } else { exec('npm publish', cwd); } } function checkGitStatus() { try { const status = execSync('git status --porcelain', { cwd: rootDir, encoding: 'utf-8', stdio: 'pipe' }); if (status.trim()) { console.error('\n[ERROR] You have uncommitted changes. Please commit or stash them first.'); process.exit(1); } } catch(error) { // Git might not be initialized, that's okay for dry runs } } function createGitTag(pkg, version, dryRun) { const tag = `${pkg.githubTag}/v${version}`; console.log(`\n[TAG] Creating git tag: ${tag}`); if (dryRun) { console.log(`[DRY RUN] Would create tag: ${tag}`); } else { checkGitStatus(); exec(`git add ${pkg.path}/Cargo.toml ${pkg.path}/package.json ${pkg.path}/CHANGELOG.md`, rootDir); try { exec(`git commit -m "chore(${pkg.githubTag}): release v${version}"`, rootDir); } catch(error) { // Commit might fail if nothing changed, that's okay console.log('No changes to commit (versions may already be updated)'); } exec(`git tag -a ${tag} -m "Release ${pkg.name} v${version}"`, rootDir); } return tag; } // GitHub releases are created automatically by GitHub Actions function bumpVersion(currentVersion, type) { const [ major, minor, patch ] = currentVersion.split('.').map(Number); switch (type) { case 'major': { return `${major + 1}.0.0`; } case 'minor': { return `${major}.${minor + 1}.0`; } case 'patch': { return `${major}.${minor}.${patch + 1}`; } default: { return null; } } } function main() { const args = process.argv.slice(2); const packageKey = args[0]; let version = args[1]; const dryRun = args.includes('--dry-run'); if (!packageKey || !packages[packageKey]) { console.error('Usage: node scripts/release-package.js <package-name> [version|patch|minor|major] [--dry-run]'); console.error('\nAvailable packages:'); Object.keys(packages).forEach((key) => { const pkg = packages[key]; console.error(` ${key}: ${pkg.name} (Cargo: ${pkg.cargo}, NPM: ${pkg.npm})`); }); process.exit(1); } const pkg = packages[packageKey]; const currentVersion = getCurrentVersion(pkg); if (!version) { console.error(`Current version: ${currentVersion}`); console.error('Please specify a new version (e.g., 0.1.1, 0.2.0, 1.0.0)'); console.error('Or use: patch, minor, or major to auto-bump'); process.exit(1); } // Auto-bump if patch/minor/major specified if ([ 'patch', 'minor', 'major' ].includes(version)) { version = bumpVersion(currentVersion, version); if (!version) { console.error(`Invalid current version format: ${currentVersion}`); process.exit(1); } console.log(`Auto-bumping ${args[1]} version: ${currentVersion} -> ${version}`); } console.log(`\n[RELEASE] Releasing ${pkg.name}`); console.log(` Current version: ${currentVersion}`); console.log(` New version: ${version}`); console.log(` Dry run: ${dryRun ? 'YES' : 'NO'}`); // Update versions console.log(`\n[UPDATE] Updating version to ${version}...`); updateVersion(pkg, version); // Build buildPackage(pkg); // Publish if (pkg.cargo) { publishCargo(pkg, dryRun); } if (pkg.npm) { publishNpm(pkg, dryRun); } // Create git tag const tag = createGitTag(pkg, version, dryRun); console.log('\n[SUCCESS] Release complete!'); console.log(` Tag: ${tag}`); if (!dryRun) { console.log('\n Next steps:'); console.log(` 1. Push the tag: git push origin ${tag}`); console.log(' 2. Push commits: git push origin HEAD'); console.log(' 3. GitHub Actions will create the release automatically'); } } main();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/hypothesi/mcp-server-tauri'

If you have feedback or need assistance with the MCP directory API, please join our Discord server