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
2 changes: 2 additions & 0 deletions orbio-openclaw-plugin/.env.smoke.example
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion orbio-openclaw-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
87 changes: 87 additions & 0 deletions orbio-openclaw-plugin/scripts/check-env-contract.mjs
Original file line number Diff line number Diff line change
@@ -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();