From fcdd92186e5b658ceda650466baab8b82254b98b Mon Sep 17 00:00:00 2001 From: jinku Date: Sun, 5 Apr 2026 10:35:52 -0700 Subject: [PATCH 1/2] chore: enforce version sync in release workflows --- .github/workflows/ci.yml | 1 + .github/workflows/publish.yml | 1 + package.json | 8 +++- scripts/check-version-sync.mjs | 14 ++++++ scripts/lib/version-sync.mjs | 69 ++++++++++++++++++++++++++++ scripts/sync-plugin-version.mjs | 16 +++++++ tests/version-sync.test.mjs | 79 +++++++++++++++++++++++++++++++++ 7 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 scripts/check-version-sync.mjs create mode 100644 scripts/lib/version-sync.mjs create mode 100644 scripts/sync-plugin-version.mjs create mode 100644 tests/version-sync.test.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ab6215..4b62026 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ jobs: node-version: 24 cache: npm - run: npm ci + - run: npm run check:version-sync - run: npm run lint - run: npm run typecheck - run: npm run test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 861bbd2..e6a5987 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -27,6 +27,7 @@ jobs: cache: npm registry-url: "https://registry.npmjs.org" - run: npm ci + - run: npm run check:version-sync - run: npm run check - run: npm pack --dry-run - run: npm publish diff --git a/package.json b/package.json index 0dbf872..8d16b35 100644 --- a/package.json +++ b/package.json @@ -43,14 +43,18 @@ "scripts": { "lint": "eslint .", "typecheck": "tsc -p tsconfig.json", - "check": "npm run lint && npm run typecheck && npm run test && npm run test:integration && npm run test:e2e", + "check:version-sync": "node scripts/check-version-sync.mjs", + "sync:plugin-version": "node scripts/sync-plugin-version.mjs", + "check": "npm run check:version-sync && npm run lint && npm run typecheck && npm run test && npm run test:integration && npm run test:e2e", "install:codex": "node scripts/installer-cli.mjs install", + "prepack": "npm run check:version-sync", "setup:git-hooks": "node scripts/setup-git-hooks.mjs", "test": "node --test tests/*.test.mjs", "test:integration": "node --test tests/integration/*.test.mjs", "test:e2e": "node --test tests/e2e/*.test.mjs", "uninstall:codex": "node scripts/installer-cli.mjs uninstall", - "update:codex": "node scripts/installer-cli.mjs update" + "update:codex": "node scripts/installer-cli.mjs update", + "version": "npm run sync:plugin-version && npm run check:version-sync" }, "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/scripts/check-version-sync.mjs b/scripts/check-version-sync.mjs new file mode 100644 index 0000000..e8d5085 --- /dev/null +++ b/scripts/check-version-sync.mjs @@ -0,0 +1,14 @@ +#!/usr/bin/env node + +import { assertVersionsMatch } from "./lib/version-sync.mjs"; + +try { + const version = assertVersionsMatch(); + process.stdout.write( + `Version sync OK: package.json and .codex-plugin/plugin.json are both ${version}.\n` + ); +} catch (error) { + const message = error instanceof Error ? error.message : String(error); + process.stderr.write(`${message}\n`); + process.exitCode = 1; +} diff --git a/scripts/lib/version-sync.mjs b/scripts/lib/version-sync.mjs new file mode 100644 index 0000000..24d2886 --- /dev/null +++ b/scripts/lib/version-sync.mjs @@ -0,0 +1,69 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const ROOT_DIR = path.resolve(fileURLToPath(new URL("../..", import.meta.url))); + +export function resolveVersionSyncPaths(rootDir = ROOT_DIR) { + return { + rootDir, + packageJsonPath: path.join(rootDir, "package.json"), + pluginJsonPath: path.join(rootDir, ".codex-plugin", "plugin.json"), + }; +} + +function readJson(filePath) { + return JSON.parse(fs.readFileSync(filePath, "utf8")); +} + +function writeJson(filePath, value) { + fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8"); +} + +export function readVersionPair(rootDir = ROOT_DIR) { + const { packageJsonPath, pluginJsonPath } = resolveVersionSyncPaths(rootDir); + const packageJson = readJson(packageJsonPath); + const pluginJson = readJson(pluginJsonPath); + return { + packageJson, + pluginJson, + packageVersion: String(packageJson.version ?? ""), + pluginVersion: String(pluginJson.version ?? ""), + }; +} + +export function assertVersionsMatch(rootDir = ROOT_DIR) { + const { packageVersion, pluginVersion } = readVersionPair(rootDir); + if (!packageVersion || !pluginVersion) { + throw new Error( + "Both package.json and .codex-plugin/plugin.json must declare a version." + ); + } + if (packageVersion !== pluginVersion) { + throw new Error( + `Version mismatch: package.json is ${packageVersion} but .codex-plugin/plugin.json is ${pluginVersion}.` + ); + } + return packageVersion; +} + +export function syncPluginVersionFromPackage(rootDir = ROOT_DIR) { + const { pluginJsonPath } = resolveVersionSyncPaths(rootDir); + const { packageJson, pluginJson, packageVersion, pluginVersion } = + readVersionPair(rootDir); + + if (!packageVersion) { + throw new Error("package.json is missing a version."); + } + + if (packageVersion === pluginVersion) { + return { changed: false, version: packageVersion }; + } + + const nextPluginJson = { + ...pluginJson, + version: packageJson.version, + }; + writeJson(pluginJsonPath, nextPluginJson); + return { changed: true, version: packageVersion }; +} diff --git a/scripts/sync-plugin-version.mjs b/scripts/sync-plugin-version.mjs new file mode 100644 index 0000000..0cbd9b5 --- /dev/null +++ b/scripts/sync-plugin-version.mjs @@ -0,0 +1,16 @@ +#!/usr/bin/env node + +import { syncPluginVersionFromPackage } from "./lib/version-sync.mjs"; + +try { + const result = syncPluginVersionFromPackage(); + process.stdout.write( + result.changed + ? `Updated .codex-plugin/plugin.json to version ${result.version}.\n` + : `.codex-plugin/plugin.json already matches package.json at ${result.version}.\n` + ); +} catch (error) { + const message = error instanceof Error ? error.message : String(error); + process.stderr.write(`${message}\n`); + process.exitCode = 1; +} diff --git a/tests/version-sync.test.mjs b/tests/version-sync.test.mjs new file mode 100644 index 0000000..9b3bb1e --- /dev/null +++ b/tests/version-sync.test.mjs @@ -0,0 +1,79 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +import { + assertVersionsMatch, + readVersionPair, + syncPluginVersionFromPackage, +} from "../scripts/lib/version-sync.mjs"; + +function writeJson(filePath, value) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8"); +} + +function createTempRepo({ packageVersion, pluginVersion }) { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "cc-version-sync-")); + writeJson(path.join(dir, "package.json"), { + name: "cc-plugin-codex", + version: packageVersion, + }); + writeJson(path.join(dir, ".codex-plugin", "plugin.json"), { + name: "cc", + version: pluginVersion, + }); + return dir; +} + +describe("version sync", () => { + it("asserts the live repo versions match", () => { + assert.equal(assertVersionsMatch(), "1.0.0"); + }); + + it("detects mismatched versions", () => { + const dir = createTempRepo({ + packageVersion: "1.2.3", + pluginVersion: "1.2.2", + }); + try { + assert.throws( + () => assertVersionsMatch(dir), + /Version mismatch: package\.json is 1\.2\.3 but \.codex-plugin\/plugin\.json is 1\.2\.2\./ + ); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); + + it("syncs plugin.json from package.json", () => { + const dir = createTempRepo({ + packageVersion: "2.0.0", + pluginVersion: "1.0.0", + }); + try { + const result = syncPluginVersionFromPackage(dir); + assert.deepEqual(result, { changed: true, version: "2.0.0" }); + const { packageVersion, pluginVersion } = readVersionPair(dir); + assert.equal(packageVersion, "2.0.0"); + assert.equal(pluginVersion, "2.0.0"); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); + + it("does nothing when versions already match", () => { + const dir = createTempRepo({ + packageVersion: "3.1.4", + pluginVersion: "3.1.4", + }); + try { + const result = syncPluginVersionFromPackage(dir); + assert.deepEqual(result, { changed: false, version: "3.1.4" }); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); +}); From 4f9217c38da1bfa64a368e8d1dea61d427f25c6e Mon Sep 17 00:00:00 2001 From: jinku Date: Sun, 5 Apr 2026 18:16:05 -0700 Subject: [PATCH 2/2] chore: address plugin scanner lint findings --- .codexignore | 7 +++++++ .github/dependabot.yml | 13 +++++++++++++ .github/workflows/ci.yml | 4 ++-- .github/workflows/publish.yml | 4 ++-- SECURITY.md | 25 +++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 .codexignore create mode 100644 .github/dependabot.yml create mode 100644 SECURITY.md diff --git a/.codexignore b/.codexignore new file mode 100644 index 0000000..2e7270b --- /dev/null +++ b/.codexignore @@ -0,0 +1,7 @@ +# Local dependencies and generated packaging output +node_modules/ +*.tgz + +# Local scratch and notes +tasks/ +.DS_Store diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c931040 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b62026..fe20508 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,8 +10,8 @@ jobs: ci: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: node-version: 24 cache: npm diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e6a5987..d36dad3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,10 +18,10 @@ jobs: contents: read id-token: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ github.event.release.tag_name || inputs.ref || github.ref_name }} - - uses: actions/setup-node@v6 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: node-version: 24 cache: npm diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..0d70c6d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,25 @@ +# Security Policy + +## Supported Versions + +Security fixes are applied to the latest release on `main`. + +| Version | Supported | +| --- | --- | +| Latest release | Yes | +| Older releases | No | + +## Reporting a Vulnerability + +Please do not report security vulnerabilities in public GitHub issues. + +Use one of the following channels instead: + +1. Open a private vulnerability report through GitHub Security Advisories for this repository, if that option is available to you. +2. Email [jin.ku@sendbird.com](mailto:jin.ku@sendbird.com) with: + - a description of the issue + - affected files or flows + - reproduction steps or proof of concept + - any suggested mitigation + +Sendbird will review the report and respond as quickly as possible. When a report is confirmed, fixes will be developed on the latest supported version and coordinated for disclosure.