From 2b0cb6bc9d0b277304d505a4488f3138d7a2f629 Mon Sep 17 00:00:00 2001 From: Gabriel Vinhaes Date: Wed, 25 Feb 2026 19:45:04 -0300 Subject: [PATCH] chore(env): enforce openclaw smoke env contract --- orbio-openclaw-plugin/.env.smoke.example | 2 + orbio-openclaw-plugin/package.json | 3 +- .../scripts/check-env-contract.mjs | 87 +++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 orbio-openclaw-plugin/scripts/check-env-contract.mjs diff --git a/orbio-openclaw-plugin/.env.smoke.example b/orbio-openclaw-plugin/.env.smoke.example index 064d5bf..1a5c909 100644 --- a/orbio-openclaw-plugin/.env.smoke.example +++ b/orbio-openclaw-plugin/.env.smoke.example @@ -1,5 +1,7 @@ ORBIO_BASE_URL=https://api.orbioapi.com.br ORBIO_API_KEY=orbio_sandbox_key_here ORBIO_WORKSPACE_ID=openclaw-smoke +ORBIO_CHANNEL=chat +ORBIO_SEND_EXECUTION_CONTEXT=true ORBIO_SMOKE_QUERY=software b2b em sao paulo ORBIO_SMOKE_LIMIT=3 diff --git a/orbio-openclaw-plugin/package.json b/orbio-openclaw-plugin/package.json index e7e2e1c..4810783 100644 --- a/orbio-openclaw-plugin/package.json +++ b/orbio-openclaw-plugin/package.json @@ -44,8 +44,9 @@ "lint": "eslint \"{src,tests}/**/*.ts\" --max-warnings=0", "test": "vitest run", "coverage": "vitest run --coverage", + "env:audit": "node scripts/check-env-contract.mjs", "smoke:live": "pnpm build && node scripts/live-smoke.mjs", - "quality": "pnpm lint && pnpm typecheck && pnpm coverage", + "quality": "pnpm lint && pnpm typecheck && pnpm coverage && pnpm env:audit", "typecheck": "tsc --noEmit", "verify": "pnpm quality && pnpm build", "prepack": "pnpm build" diff --git a/orbio-openclaw-plugin/scripts/check-env-contract.mjs b/orbio-openclaw-plugin/scripts/check-env-contract.mjs new file mode 100644 index 0000000..540f131 --- /dev/null +++ b/orbio-openclaw-plugin/scripts/check-env-contract.mjs @@ -0,0 +1,87 @@ +import fs from "node:fs"; +import path from "node:path"; + +const ROOT = process.cwd(); +const ENV_TEMPLATE_PATH = path.join(ROOT, ".env.smoke.example"); +const INDEX_PATH = path.join(ROOT, "src/index.ts"); +const LIVE_SMOKE_PATH = path.join(ROOT, "scripts/live-smoke.mjs"); + +function readEnvKeys(filePath) { + const content = fs.readFileSync(filePath, "utf-8"); + const keys = new Set(); + for (const rawLine of content.split(/\r?\n/u)) { + const line = rawLine.trim(); + if (!line || line.startsWith("#")) { + continue; + } + const eqIndex = line.indexOf("="); + if (eqIndex <= 0) { + continue; + } + keys.add(line.slice(0, eqIndex).trim()); + } + return keys; +} + +function extractMatches(content, pattern, groupIndex = 1) { + const keys = new Set(); + for (const match of content.matchAll(pattern)) { + const key = match[groupIndex]; + if (key) { + keys.add(key); + } + } + return keys; +} + +function difference(left, right) { + return [...left].filter((value) => !right.has(value)).sort(); +} + +function main() { + if (!fs.existsSync(ENV_TEMPLATE_PATH)) { + throw new Error(`Missing env template: ${ENV_TEMPLATE_PATH}`); + } + if (!fs.existsSync(INDEX_PATH)) { + throw new Error(`Missing plugin source: ${INDEX_PATH}`); + } + if (!fs.existsSync(LIVE_SMOKE_PATH)) { + throw new Error(`Missing smoke script: ${LIVE_SMOKE_PATH}`); + } + + const envKeys = readEnvKeys(ENV_TEMPLATE_PATH); + const indexContent = fs.readFileSync(INDEX_PATH, "utf-8"); + const smokeContent = fs.readFileSync(LIVE_SMOKE_PATH, "utf-8"); + + const pluginEnvRefs = extractMatches(indexContent, /env\.([A-Z0-9_]+)/gu); + const smokeEnvRefs = extractMatches( + smokeContent, + /(requiredEnv|optionalEnv)\("([A-Z0-9_]+)"/gu, + 2, + ); + + const expectedKeys = new Set([...pluginEnvRefs, ...smokeEnvRefs]); + const missing = difference(expectedKeys, envKeys); + const unused = difference(envKeys, expectedKeys); + + const issues = []; + if (missing.length > 0) { + issues.push(`missing keys in .env.smoke.example: ${missing.join(", ")}`); + } + if (unused.length > 0) { + issues.push(`unused keys in .env.smoke.example: ${unused.join(", ")}`); + } + + if (issues.length > 0) { + console.error("env contract audit failed:"); + for (const issue of issues) { + console.error(`- ${issue}`); + } + process.exit(1); + } + + console.log("env contract audit passed"); + console.log(`expected_keys=${expectedKeys.size}`); +} + +main();