fix: enforce release metadata consistency (#297)
This commit is contained in:
1
.github/workflows/release-pr.yml
vendored
1
.github/workflows/release-pr.yml
vendored
@@ -44,6 +44,7 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
config-file: .github/release-please-config.json
|
||||
manifest-file: .github/.release-please-manifest.json
|
||||
skip-labeling: true
|
||||
|
||||
publish-cli:
|
||||
needs: release-pr
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "compound-engineering",
|
||||
"displayName": "Compound Engineering",
|
||||
"version": "2.33.0",
|
||||
"version": "2.42.0",
|
||||
"description": "AI-powered development tools. 29 agents, 44 skills, 1 MCP server for code review, research, design, and workflow automation.",
|
||||
"author": {
|
||||
"name": "Kieran Klaassen",
|
||||
|
||||
@@ -41,6 +41,13 @@ export type MetadataSyncResult = {
|
||||
updates: FileUpdate[]
|
||||
}
|
||||
|
||||
function resolveExpectedVersion(
|
||||
explicitVersion: string | undefined,
|
||||
fallbackVersion: string,
|
||||
): string {
|
||||
return explicitVersion ?? fallbackVersion
|
||||
}
|
||||
|
||||
export async function countMarkdownFiles(root: string): Promise<number> {
|
||||
const entries = await fs.readdir(root, { withFileTypes: true })
|
||||
let total = 0
|
||||
@@ -110,10 +117,18 @@ export async function syncReleaseMetadata(options: SyncOptions = {}): Promise<Me
|
||||
const codingTutorClaude = await readJson<ClaudePluginManifest>(codingTutorClaudePath)
|
||||
const codingTutorCursor = await readJson<CursorPluginManifest>(codingTutorCursorPath)
|
||||
const marketplaceClaude = await readJson<MarketplaceManifest>(marketplaceClaudePath)
|
||||
const expectedCompoundVersion = resolveExpectedVersion(
|
||||
versions["compound-engineering"],
|
||||
compoundClaude.version,
|
||||
)
|
||||
const expectedCodingTutorVersion = resolveExpectedVersion(
|
||||
versions["coding-tutor"],
|
||||
codingTutorClaude.version,
|
||||
)
|
||||
|
||||
let changed = false
|
||||
if (versions["compound-engineering"] && compoundClaude.version !== versions["compound-engineering"]) {
|
||||
compoundClaude.version = versions["compound-engineering"]
|
||||
if (compoundClaude.version !== expectedCompoundVersion) {
|
||||
compoundClaude.version = expectedCompoundVersion
|
||||
changed = true
|
||||
}
|
||||
if (compoundClaude.description !== compoundDescription) {
|
||||
@@ -124,8 +139,8 @@ export async function syncReleaseMetadata(options: SyncOptions = {}): Promise<Me
|
||||
if (write && changed) await writeJson(compoundClaudePath, compoundClaude)
|
||||
|
||||
changed = false
|
||||
if (versions["compound-engineering"] && compoundCursor.version !== versions["compound-engineering"]) {
|
||||
compoundCursor.version = versions["compound-engineering"]
|
||||
if (compoundCursor.version !== expectedCompoundVersion) {
|
||||
compoundCursor.version = expectedCompoundVersion
|
||||
changed = true
|
||||
}
|
||||
if (compoundCursor.description !== compoundDescription) {
|
||||
@@ -136,16 +151,16 @@ export async function syncReleaseMetadata(options: SyncOptions = {}): Promise<Me
|
||||
if (write && changed) await writeJson(compoundCursorPath, compoundCursor)
|
||||
|
||||
changed = false
|
||||
if (versions["coding-tutor"] && codingTutorClaude.version !== versions["coding-tutor"]) {
|
||||
codingTutorClaude.version = versions["coding-tutor"]
|
||||
if (codingTutorClaude.version !== expectedCodingTutorVersion) {
|
||||
codingTutorClaude.version = expectedCodingTutorVersion
|
||||
changed = true
|
||||
}
|
||||
updates.push({ path: codingTutorClaudePath, changed })
|
||||
if (write && changed) await writeJson(codingTutorClaudePath, codingTutorClaude)
|
||||
|
||||
changed = false
|
||||
if (versions["coding-tutor"] && codingTutorCursor.version !== versions["coding-tutor"]) {
|
||||
codingTutorCursor.version = versions["coding-tutor"]
|
||||
if (codingTutorCursor.version !== expectedCodingTutorVersion) {
|
||||
codingTutorCursor.version = expectedCodingTutorVersion
|
||||
changed = true
|
||||
}
|
||||
updates.push({ path: codingTutorCursorPath, changed })
|
||||
@@ -159,8 +174,8 @@ export async function syncReleaseMetadata(options: SyncOptions = {}): Promise<Me
|
||||
|
||||
for (const plugin of marketplaceClaude.plugins) {
|
||||
if (plugin.name === "compound-engineering") {
|
||||
if (versions["compound-engineering"] && plugin.version !== versions["compound-engineering"]) {
|
||||
plugin.version = versions["compound-engineering"]
|
||||
if (plugin.version !== expectedCompoundVersion) {
|
||||
plugin.version = expectedCompoundVersion
|
||||
changed = true
|
||||
}
|
||||
if (plugin.description !== `AI-powered development tools that get smarter with every use. Make each unit of engineering work easier than the last. Includes ${await countMarkdownFiles(path.join(root, "plugins", "compound-engineering", "agents"))} specialized agents and ${await countSkillDirectories(path.join(root, "plugins", "compound-engineering", "skills"))} skills.`) {
|
||||
@@ -169,8 +184,8 @@ export async function syncReleaseMetadata(options: SyncOptions = {}): Promise<Me
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.name === "coding-tutor" && versions["coding-tutor"] && plugin.version !== versions["coding-tutor"]) {
|
||||
plugin.version = versions["coding-tutor"]
|
||||
if (plugin.name === "coding-tutor" && plugin.version !== expectedCodingTutorVersion) {
|
||||
plugin.version = expectedCodingTutorVersion
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,86 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { buildCompoundEngineeringDescription } from "../src/release/metadata"
|
||||
import { mkdtemp, mkdir, writeFile } from "fs/promises"
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import { afterEach, describe, expect, test } from "bun:test"
|
||||
import { buildCompoundEngineeringDescription, syncReleaseMetadata } from "../src/release/metadata"
|
||||
|
||||
const tempRoots: string[] = []
|
||||
|
||||
afterEach(async () => {
|
||||
for (const root of tempRoots.splice(0, tempRoots.length)) {
|
||||
await Bun.$`rm -rf ${root}`.quiet()
|
||||
}
|
||||
})
|
||||
|
||||
async function makeFixtureRoot(): Promise<string> {
|
||||
const root = await mkdtemp(path.join(os.tmpdir(), "release-metadata-"))
|
||||
tempRoots.push(root)
|
||||
|
||||
await mkdir(path.join(root, "plugins", "compound-engineering", "agents", "review"), {
|
||||
recursive: true,
|
||||
})
|
||||
await mkdir(path.join(root, "plugins", "compound-engineering", "skills", "ce-plan"), {
|
||||
recursive: true,
|
||||
})
|
||||
await mkdir(path.join(root, "plugins", "compound-engineering", ".claude-plugin"), {
|
||||
recursive: true,
|
||||
})
|
||||
await mkdir(path.join(root, "plugins", "compound-engineering", ".cursor-plugin"), {
|
||||
recursive: true,
|
||||
})
|
||||
await mkdir(path.join(root, "plugins", "coding-tutor", ".claude-plugin"), {
|
||||
recursive: true,
|
||||
})
|
||||
await mkdir(path.join(root, "plugins", "coding-tutor", ".cursor-plugin"), {
|
||||
recursive: true,
|
||||
})
|
||||
await mkdir(path.join(root, ".claude-plugin"), { recursive: true })
|
||||
|
||||
await writeFile(
|
||||
path.join(root, "plugins", "compound-engineering", "agents", "review", "agent.md"),
|
||||
"# Review Agent\n",
|
||||
)
|
||||
await writeFile(
|
||||
path.join(root, "plugins", "compound-engineering", "skills", "ce-plan", "SKILL.md"),
|
||||
"# ce:plan\n",
|
||||
)
|
||||
await writeFile(
|
||||
path.join(root, "plugins", "compound-engineering", ".mcp.json"),
|
||||
JSON.stringify({ mcpServers: { context7: { command: "ctx7" } } }, null, 2),
|
||||
)
|
||||
await writeFile(
|
||||
path.join(root, "plugins", "compound-engineering", ".claude-plugin", "plugin.json"),
|
||||
JSON.stringify({ version: "2.42.0", description: "old" }, null, 2),
|
||||
)
|
||||
await writeFile(
|
||||
path.join(root, "plugins", "compound-engineering", ".cursor-plugin", "plugin.json"),
|
||||
JSON.stringify({ version: "2.33.0", description: "old" }, null, 2),
|
||||
)
|
||||
await writeFile(
|
||||
path.join(root, "plugins", "coding-tutor", ".claude-plugin", "plugin.json"),
|
||||
JSON.stringify({ version: "1.2.1" }, null, 2),
|
||||
)
|
||||
await writeFile(
|
||||
path.join(root, "plugins", "coding-tutor", ".cursor-plugin", "plugin.json"),
|
||||
JSON.stringify({ version: "1.2.1" }, null, 2),
|
||||
)
|
||||
await writeFile(
|
||||
path.join(root, ".claude-plugin", "marketplace.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
metadata: { version: "1.0.0", description: "marketplace" },
|
||||
plugins: [
|
||||
{ name: "compound-engineering", version: "2.41.0", description: "old" },
|
||||
{ name: "coding-tutor", version: "1.2.0", description: "old" },
|
||||
],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
)
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
describe("release metadata", () => {
|
||||
test("builds the current compound-engineering manifest description from repo counts", async () => {
|
||||
@@ -8,4 +89,13 @@ describe("release metadata", () => {
|
||||
"AI-powered development tools. 29 agents, 44 skills, 1 MCP server for code review, research, design, and workflow automation.",
|
||||
)
|
||||
})
|
||||
|
||||
test("detects cross-surface version drift even without explicit override versions", async () => {
|
||||
const root = await makeFixtureRoot()
|
||||
const result = await syncReleaseMetadata({ root, write: false })
|
||||
const changedPaths = result.updates.filter((update) => update.changed).map((update) => update.path)
|
||||
|
||||
expect(changedPaths).toContain(path.join(root, "plugins", "compound-engineering", ".cursor-plugin", "plugin.json"))
|
||||
expect(changedPaths).toContain(path.join(root, ".claude-plugin", "marketplace.json"))
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user