Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
},
"metadata": {
"description": "Codex plugins to use in Claude Code for delegation and code review.",
"version": "1.0.2"
"version": "1.0.3"
},
"plugins": [
{
"name": "codex",
"description": "Use Codex from Claude Code to review code or delegate tasks.",
"version": "1.0.2",
"version": "1.0.3",
"author": {
"name": "OpenAI"
},
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openai/codex-plugin-cc",
"version": "1.0.2",
"version": "1.0.3",
"private": true,
"type": "module",
"description": "Use Codex from Claude Code to review code or delegate tasks.",
Expand All @@ -9,6 +9,8 @@
"node": ">=18.18.0"
},
"scripts": {
"bump-version": "node scripts/bump-version.mjs",
"check-version": "node scripts/bump-version.mjs --check",
"prebuild": "mkdir -p plugins/codex/.generated/app-server-types && codex app-server generate-ts --out plugins/codex/.generated/app-server-types",
"build": "tsc -p tsconfig.app-server.json",
"test": "node --test tests/*.test.mjs"
Expand Down
2 changes: 1 addition & 1 deletion plugins/codex/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codex",
"version": "1.0.2",
"version": "1.0.3",
"description": "Use Codex from Claude Code to review code or delegate tasks.",
"author": {
"name": "OpenAI"
Expand Down
227 changes: 227 additions & 0 deletions scripts/bump-version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
import process from "node:process";

const VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;

const TARGETS = [
{
file: "package.json",
values: [
{
label: "version",
get: (json) => json.version,
set: (json, version) => {
json.version = version;
}
}
]
},
{
file: "package-lock.json",
values: [
{
label: "version",
get: (json) => json.version,
set: (json, version) => {
json.version = version;
}
},
{
label: "packages[\"\"].version",
get: (json) => json.packages?.[""]?.version,
set: (json, version) => {
requireObject(json.packages?.[""], "package-lock.json packages[\"\"]");
json.packages[""].version = version;
}
}
]
},
{
file: "plugins/codex/.claude-plugin/plugin.json",
values: [
{
label: "version",
get: (json) => json.version,
set: (json, version) => {
json.version = version;
}
}
]
},
{
file: ".claude-plugin/marketplace.json",
values: [
{
label: "metadata.version",
get: (json) => json.metadata?.version,
set: (json, version) => {
requireObject(json.metadata, ".claude-plugin/marketplace.json metadata");
json.metadata.version = version;
}
},
{
label: "plugins[codex].version",
get: (json) => findMarketplacePlugin(json).version,
set: (json, version) => {
findMarketplacePlugin(json).version = version;
}
}
]
}
];

function usage() {
return [
"Usage:",
" node scripts/bump-version.mjs <version>",
" node scripts/bump-version.mjs --check [version]",
"",
"Options:",
" --check Verify manifest versions. Uses package.json when version is omitted.",
" --root <dir> Run against a different repository root.",
" --help Print this help."
].join("\n");
}

function parseArgs(argv) {
const options = {
check: false,
root: process.cwd(),
version: null
};

for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];

if (arg === "--check") {
options.check = true;
} else if (arg === "--root") {
const root = argv[i + 1];
if (!root) {
throw new Error("--root requires a directory.");
}
options.root = root;
i += 1;
} else if (arg === "--help" || arg === "-h") {
options.help = true;
} else if (arg.startsWith("-")) {
throw new Error(`Unknown option: ${arg}`);
} else if (options.version) {
throw new Error(`Unexpected extra argument: ${arg}`);
} else {
options.version = arg;
}
}

options.root = path.resolve(options.root);
return options;
}

function validateVersion(version) {
if (!VERSION_PATTERN.test(version)) {
throw new Error(`Expected a semver-like version such as 1.0.3, got: ${version}`);
}
}

function requireObject(value, label) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new Error(`Expected ${label} to be an object.`);
}
}

function findMarketplacePlugin(json) {
const plugin = json.plugins?.find((entry) => entry?.name === "codex");
requireObject(plugin, ".claude-plugin/marketplace.json plugins[codex]");
return plugin;
}

function readJson(root, file) {
const filePath = path.join(root, file);
return JSON.parse(fs.readFileSync(filePath, "utf8"));
}

function writeJson(root, file, json) {
const filePath = path.join(root, file);
fs.writeFileSync(filePath, `${JSON.stringify(json, null, 2)}\n`);
}

function readPackageVersion(root) {
const packageJson = readJson(root, "package.json");
if (typeof packageJson.version !== "string") {
throw new Error("package.json version must be a string.");
}
validateVersion(packageJson.version);
return packageJson.version;
}

function checkVersions(root, expectedVersion) {
const mismatches = [];

for (const target of TARGETS) {
const json = readJson(root, target.file);
for (const value of target.values) {
const actual = value.get(json);
if (actual !== expectedVersion) {
mismatches.push(`${target.file} ${value.label}: expected ${expectedVersion}, found ${actual ?? "<missing>"}`);
}
}
}

return mismatches;
}

function bumpVersion(root, version) {
const changedFiles = [];

for (const target of TARGETS) {
const json = readJson(root, target.file);
const before = JSON.stringify(json);

for (const value of target.values) {
value.set(json, version);
}

if (JSON.stringify(json) !== before) {
writeJson(root, target.file, json);
changedFiles.push(target.file);
}
}

return changedFiles;
}

function main() {
const options = parseArgs(process.argv.slice(2));
if (options.help) {
console.log(usage());
return;
}

const version = options.version ?? (options.check ? readPackageVersion(options.root) : null);
if (!version) {
throw new Error(`Missing version.\n\n${usage()}`);
}
validateVersion(version);

if (options.check) {
const mismatches = checkVersions(options.root, version);
if (mismatches.length > 0) {
throw new Error(`Version metadata is out of sync:\n${mismatches.join("\n")}`);
}
console.log(`All version metadata matches ${version}.`);
return;
}

const changedFiles = bumpVersion(options.root, version);
const touched = changedFiles.length > 0 ? changedFiles.join(", ") : "no files changed";
console.log(`Set version metadata to ${version}: ${touched}.`);
}

try {
main();
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
}
88 changes: 88 additions & 0 deletions tests/bump-version.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import fs from "node:fs";
import path from "node:path";
import test from "node:test";
import assert from "node:assert/strict";
import { fileURLToPath } from "node:url";

import { makeTempDir, run } from "./helpers.mjs";

const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const SCRIPT = path.join(ROOT, "scripts", "bump-version.mjs");

function writeJson(filePath, json) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, `${JSON.stringify(json, null, 2)}\n`);
}

function readJson(filePath) {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
}

function makeVersionFixture() {
const root = makeTempDir();

writeJson(path.join(root, "package.json"), {
name: "@openai/codex-plugin-cc",
version: "1.0.2"
});
writeJson(path.join(root, "package-lock.json"), {
name: "@openai/codex-plugin-cc",
version: "1.0.2",
lockfileVersion: 3,
packages: {
"": {
name: "@openai/codex-plugin-cc",
version: "1.0.2"
}
}
});
writeJson(path.join(root, "plugins", "codex", ".claude-plugin", "plugin.json"), {
name: "codex",
version: "1.0.2"
});
writeJson(path.join(root, ".claude-plugin", "marketplace.json"), {
metadata: {
version: "1.0.2"
},
plugins: [
{
name: "codex",
version: "1.0.2"
}
]
});

return root;
}

test("bump-version updates every release manifest", () => {
const root = makeVersionFixture();

const result = run("node", [SCRIPT, "--root", root, "1.2.3"], {
cwd: ROOT
});

assert.equal(result.status, 0, result.stderr);
assert.equal(readJson(path.join(root, "package.json")).version, "1.2.3");
assert.equal(readJson(path.join(root, "package-lock.json")).version, "1.2.3");
assert.equal(readJson(path.join(root, "package-lock.json")).packages[""].version, "1.2.3");
assert.equal(readJson(path.join(root, "plugins", "codex", ".claude-plugin", "plugin.json")).version, "1.2.3");
assert.equal(readJson(path.join(root, ".claude-plugin", "marketplace.json")).metadata.version, "1.2.3");
assert.equal(readJson(path.join(root, ".claude-plugin", "marketplace.json")).plugins[0].version, "1.2.3");
});

test("bump-version check mode reports stale metadata", () => {
const root = makeVersionFixture();
writeJson(path.join(root, "package.json"), {
name: "@openai/codex-plugin-cc",
version: "1.0.3"
});

const result = run("node", [SCRIPT, "--root", root, "--check"], {
cwd: ROOT
});

assert.notEqual(result.status, 0);
assert.match(result.stderr, /plugins\/codex\/\.claude-plugin\/plugin\.json version/);
assert.match(result.stderr, /\.claude-plugin\/marketplace\.json metadata\.version/);
});