mirror of
https://github.com/Heretek-AI/openclaw.git
synced 2026-07-19 17:33:35 -04:00
a9ae1a6778
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.
267 lines
8.0 KiB
JavaScript
Executable File
267 lines
8.0 KiB
JavaScript
Executable File
#!/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();
|