Files
openclaw/scripts/prepare-release.ts
T
Tabula Myriad a9ae1a6778 feat: Triad development iteration complete
Matrix Protocol:
- docker-compose.matrix.yml: Dendrite homeserver + PostgreSQL + Nginx TLS
- src/channels/plugins/matrix-channel.ts: OpenClaw plugin implementation
- docs/matrix-triad-setup.md: Setup guide with auth scheme (@tm1-4:triad.local)

MCP Server Integration:
- docs/mcp-triad-integration.md: SearXNG, Playwright, GitHub MCP configs
- docs/mcp-curiosity-mapping.md: Gap-to-capability mapping

Node Sync Architecture:
- src/services/node-sync-service.ts: WebSocket peer sync + presence detection
- src/services/node-sync-service.test.ts: Unit tests
- docs/node-sync-architecture.md: Architecture docs

Triad Resilience:
- scripts/triad-corruption-check.mjs: SQLite + log + config + git integrity
- docs/triad-resilience.md: Recovery procedures
- .secure/deployment-logs/README.md: Schema v2
- skills/triad-heartbeat/SKILL.md: Corruption check integration

NPM Publish Workflow:
- scripts/npm-publish.mjs: version, changelog, validate, publish, rollback
- .github/workflows/npm-publish.yml: GitHub Actions with provenance
- docs/npm-publish-guide.md: Complete documentation

All deliverables tested in Docker before production.
2026-03-24 00:44:50 -04:00

267 lines
8.0 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env -S node --import tsx
/**
* Prepares a release by synchronizing version across all files and optionally creating a tag.
*
* This script handles the version synchronization that often causes CI failures:
* - Updates package.json version
* - Regenerates src/config/schema.base.generated.ts (which embeds the version)
* - Creates a git commit and tag
* - Optionally pushes to origin
*
* Usage:
* node --import tsx scripts/prepare-release.ts [options]
*
* Options:
* --version <version> Specify version directly (e.g., 2026.3.23 or 2026.3.23-abc1234)
* --tag Create a git tag for the release
* --push Push the commit and tag to origin
* --dry-run Show what would be done without making changes
* --check Check if versions are in sync (exit 1 if not)
*
* Examples:
* # Auto-generate version from today's date + current git hash
* node --import tsx scripts/prepare-release.ts --tag --push
*
* # Specify a specific version
* node --import tsx scripts/prepare-release.ts --version 2026.3.24 --tag --push
*
* # Check if versions are in sync (useful for CI)
* node --import tsx scripts/prepare-release.ts --check
*/
import { execFileSync, execSync } from "node:child_process";
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
function getCurrentVersion(): string {
const pkg = JSON.parse(readFileSync("package.json", "utf8"));
return pkg.version;
}
function getGitShortHash(): string {
return execSync("git rev-parse --short HEAD", { encoding: "utf8" }).trim();
}
function parseVersionInput(input: string): { baseVersion: string; hash?: string } {
// If input is just a hash (7+ hex chars), combine with current date version
if (/^[a-f0-9]{7,}$/.test(input)) {
const today = new Date();
const year = today.getFullYear();
const month = today.getMonth() + 1;
const day = today.getDate();
const baseVersion = `${year}.${month}.${day}`;
return { baseVersion, hash: input.slice(0, 7) };
}
// If input is a full version with hash (e.g., 2026.3.23-abc1234)
const match = input.match(/^(\d{4}\.\d+\.\d+)(?:-([a-f0-9]{7,}))?$/);
if (match) {
return { baseVersion: match[1], hash: match[2]?.slice(0, 7) };
}
// If input is just a base version (e.g., 2026.3.23)
if (/^\d{4}\.\d+\.\d+$/.test(input)) {
return { baseVersion: input };
}
throw new Error(`Invalid version format: ${input}`);
}
function generateNewVersion(specifiedVersion?: string): string {
if (specifiedVersion) {
const { baseVersion, hash } = parseVersionInput(specifiedVersion);
if (hash) {
return `${baseVersion}-${hash}`;
}
return baseVersion;
}
// Auto-generate: use today's date + current git hash
const today = new Date();
const year = today.getFullYear();
const month = today.getMonth() + 1;
const day = today.getDate();
const baseVersion = `${year}.${month}.${day}`;
const hash = getGitShortHash();
return `${baseVersion}-${hash}`;
}
function updatePackageJson(version: string, dryRun: boolean): boolean {
const filePath = resolve("package.json");
const content = readFileSync(filePath, "utf8");
const pkg = JSON.parse(content);
if (pkg.version === version) {
console.log(` ✓ package.json already at ${version}`);
return false;
}
if (dryRun) {
console.log(` 📝 Would update package.json: ${pkg.version}${version}`);
return true;
}
pkg.version = version;
writeFileSync(filePath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
console.log(` ✓ Updated package.json: ${pkg.version}${version}`);
return true;
}
function regenerateSchema(dryRun: boolean): boolean {
if (dryRun) {
console.log(" 📝 Would regenerate schema.base.generated.ts");
return true;
}
console.log(" 🔄 Regenerating schema.base.generated.ts...");
try {
execFileSync("node", ["--import", "tsx", "scripts/generate-base-config-schema.ts"], {
stdio: "inherit",
});
console.log(" ✓ Schema regenerated");
return true;
} catch (error) {
console.error(" ❌ Failed to regenerate schema:", error);
throw error;
}
}
function checkVersionSync(): void {
const pkgVersion = getCurrentVersion();
const schemaFile = resolve("src/config/schema.base.generated.ts");
if (!existsSync(schemaFile)) {
console.error("❌ schema.base.generated.ts not found");
process.exit(1);
}
const schemaContent = readFileSync(schemaFile, "utf8");
const versionMatch = schemaContent.match(/version:\s*"([^"]+)"/);
if (!versionMatch) {
console.error("❌ Could not find version in schema.base.generated.ts");
process.exit(1);
}
const schemaVersion = versionMatch[1];
if (pkgVersion !== schemaVersion) {
console.error(`❌ Version mismatch:`);
console.error(` package.json: ${pkgVersion}`);
console.error(` schema: ${schemaVersion}`);
console.error(`\n💡 Run: node --import tsx scripts/prepare-release.ts`);
process.exit(1);
}
console.log(`✅ Versions in sync: ${pkgVersion}`);
}
function createCommit(version: string, dryRun: boolean): void {
if (dryRun) {
console.log(`\n📝 Would create commit: chore: prepare release ${version}`);
return;
}
console.log(`\n📝 Creating commit...`);
try {
execSync("git add package.json src/config/schema.base.generated.ts", { encoding: "utf8" });
execSync(`git commit -m "chore: prepare release ${version}"`, { encoding: "utf8" });
console.log(" ✓ Commit created");
} catch {
console.log(" ️ No changes to commit");
}
}
function createTag(version: string, dryRun: boolean): void {
const tagName = `v${version}`;
if (dryRun) {
console.log(`\n🏷️ Would create tag: ${tagName}`);
return;
}
console.log(`\n🏷️ Creating tag ${tagName}...`);
try {
execSync(`git tag ${tagName}`, { encoding: "utf8" });
console.log(" ✓ Tag created");
} catch {
console.log(" ️ Tag may already exist");
}
}
function pushChanges(version: string, dryRun: boolean): void {
if (dryRun) {
console.log("\n🚀 Would push commit and tag to origin");
return;
}
console.log("\n🚀 Pushing to origin...");
try {
execSync("git push origin main", { encoding: "utf8" });
execSync(`git push origin v${version}`, { encoding: "utf8" });
console.log(" ✓ Pushed to origin");
} catch (error) {
console.error(" ❌ Failed to push:", error);
throw error;
}
}
function main(): void {
const args = process.argv.slice(2);
const dryRun = args.includes("--dry-run");
const checkOnly = args.includes("--check");
const createTagFlag = args.includes("--tag");
const pushFlag = args.includes("--push");
const versionIndex = args.indexOf("--version");
const specifiedVersion = versionIndex !== -1 ? args[versionIndex + 1] : undefined;
console.log("🔧 OpenClaw Release Preparation");
if (checkOnly) {
checkVersionSync();
return;
}
if (dryRun) {
console.log("📋 DRY RUN - No changes will be made");
}
const currentVersion = getCurrentVersion();
console.log(`\n📌 Current version: ${currentVersion}`);
const newVersion = generateNewVersion(specifiedVersion);
console.log(`📌 New version: ${newVersion}`);
if (newVersion === currentVersion && !dryRun) {
console.log("\n✅ Version is already up to date.");
// Still regenerate schema to ensure sync
regenerateSchema(false);
return;
}
console.log("\n📦 Updating files:");
const pkgChanged = updatePackageJson(newVersion, dryRun);
const schemaChanged = regenerateSchema(dryRun);
if (pkgChanged || schemaChanged) {
createCommit(newVersion, dryRun);
}
if (createTagFlag) {
createTag(newVersion, dryRun);
}
if (pushFlag) {
pushChanges(newVersion, dryRun);
}
console.log("\n✅ Release preparation complete!");
console.log(`\n📌 Version: ${newVersion}`);
console.log(`🏷️ Tag: v${newVersion}`);
if (!pushFlag && !dryRun) {
console.log(`\n💡 To push changes: git push origin main && git push origin v${newVersion}`);
}
}
main();