diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..d6d808a --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,31 @@ +name: Check + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + check: + name: check + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 24.x + + - name: Check plugin package + run: pnpm check diff --git a/.gitignore b/.gitignore index 5ca0973..646ac51 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ .DS_Store - +node_modules/ diff --git a/README.md b/README.md index dad6695..b94a157 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ Start a new Codex thread after installing so the plugin skill and MCP server are ```text .claude-plugin/marketplace.json Claude Code marketplace catalog .agents/plugins/marketplace.json Codex marketplace catalog +package.json Package metadata and check command plugins/samebase/ Shared Claude Code and Codex plugin ``` @@ -99,5 +100,5 @@ declaration, icon, and skill tree, so the workflow cannot drift between Codex an Validate the complete package with: ```bash -node scripts/check.mjs +pnpm check ``` diff --git a/package.json b/package.json new file mode 100644 index 0000000..9562672 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "samebase-agent-plugins", + "version": "0.2.0", + "private": true, + "type": "module", + "engines": { + "node": ">=24" + }, + "packageManager": "pnpm@11.0.8", + "scripts": { + "check": "node ./scripts/check.ts" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..9b60ae1 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,9 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} diff --git a/scripts/check.mjs b/scripts/check.ts similarity index 51% rename from scripts/check.mjs rename to scripts/check.ts index 27592b9..5ef2ba3 100644 --- a/scripts/check.mjs +++ b/scripts/check.ts @@ -1,51 +1,81 @@ -import { existsSync, readFileSync } from "node:fs"; +import { existsSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import codexMarketplace from "../.agents/plugins/marketplace.json" with { type: "json" }; +import claudeMarketplace from "../.claude-plugin/marketplace.json" with { type: "json" }; +import packageMetadata from "../package.json" with { type: "json" }; +import claudeManifest from "../plugins/samebase/.claude-plugin/plugin.json" with { + type: "json", +}; +import codexManifest from "../plugins/samebase/.codex-plugin/plugin.json" with { + type: "json", +}; +import mcp from "../plugins/samebase/.mcp.json" with { type: "json" }; + const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const pluginRoot = resolve(root, "plugins/samebase"); -function readJson(path) { - return JSON.parse(readFileSync(path, "utf8")); -} - -function check(condition, message) { +function check(condition: unknown, message: string): asserts condition { if (!condition) { throw new Error(message); } } -function checkRelativePath(pluginRoot, relativePath, label) { +function checkRelativePath( + baseRoot: string, + relativePath: unknown, + label: string, +): asserts relativePath is string { check(typeof relativePath === "string", `${label} must be a relative path.`); check( - existsSync(resolve(pluginRoot, relativePath)), + existsSync(resolve(baseRoot, relativePath)), `${label} does not resolve: ${relativePath}`, ); } -const codexManifest = readJson(resolve(pluginRoot, ".codex-plugin/plugin.json")); -const claudeManifest = readJson(resolve(pluginRoot, ".claude-plugin/plugin.json")); -const codexMarketplace = readJson(resolve(root, ".agents/plugins/marketplace.json")); -const claudeMarketplace = readJson(resolve(root, ".claude-plugin/marketplace.json")); -const mcp = readJson(resolve(pluginRoot, ".mcp.json")); - -check(codexManifest.version === claudeManifest.version, "Codex and Claude versions must match."); +check( + packageMetadata.version === codexManifest.version, + "The package version must match the Codex manifest.", +); +check( + packageMetadata.version === claudeManifest.version, + "The package version must match the Claude manifest.", +); +check( + packageMetadata.version === claudeMarketplace.plugins[0]?.version, + "The package version must match the Claude marketplace.", +); +check( + codexManifest.version === claudeManifest.version, + "Codex and Claude versions must match.", +); check( claudeMarketplace.plugins[0]?.version === claudeManifest.version, "The Claude marketplace version must match the plugin manifests.", ); -check(mcp.mcpServers?.samebase?.url === "https://api.samebase.com/mcp", "Unexpected MCP endpoint."); +check( + mcp.mcpServers?.samebase?.url === "https://api.samebase.com/mcp", + "Unexpected MCP endpoint.", +); const codexMarketplaceSource = codexMarketplace.plugins[0]?.source; -check(codexMarketplaceSource?.source === "local", "The Codex marketplace source must be local."); +check( + codexMarketplaceSource?.source === "local", + "The Codex marketplace source must be local.", +); checkRelativePath(root, codexMarketplaceSource.path, "Codex marketplace source"); -checkRelativePath(root, claudeMarketplace.plugins[0]?.source, "Claude marketplace source"); +checkRelativePath( + root, + claudeMarketplace.plugins[0]?.source, + "Claude marketplace source", +); check( resolve(root, codexMarketplaceSource.path) === pluginRoot, "The Codex marketplace must install the shared plugin directory.", ); check( - resolve(root, claudeMarketplace.plugins[0]?.source) === pluginRoot, + resolve(root, claudeMarketplace.plugins[0].source) === pluginRoot, "The Claude marketplace must install the shared plugin directory.", ); checkRelativePath(pluginRoot, codexManifest.skills, "Codex skills path");