diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 74ad18a..67c1049 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,6 +23,7 @@ jobs: bun-version: latest - run: bun install + - run: bun run deps:check - run: bun run test:coverage:ci - run: bun run build:openclaw:check diff --git a/deps-baseline.json b/deps-baseline.json new file mode 100644 index 0000000..047731e --- /dev/null +++ b/deps-baseline.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "@modelcontextprotocol/sdk": "^1.26.0", + "zod": "^3.24.0", + "aelf-sdk": "^3.5.1-beta.0" + } +} diff --git a/lib/config.ts b/lib/config.ts index e8daaa5..a10c80d 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -2,6 +2,18 @@ import { homedir } from 'node:os'; import { join } from 'node:path'; import type { NodeProfile } from './types.js'; +export const CHAIN_IDS = { + AELF: 'AELF', + TDVV: 'tDVV', +} as const; + +export const DEFAULT_CHAIN_ID = CHAIN_IDS.AELF; + +export const DEFAULT_RPC_URLS: Record<(typeof CHAIN_IDS)[keyof typeof CHAIN_IDS], string> = { + [CHAIN_IDS.AELF]: 'https://aelf-public-node.aelf.io', + [CHAIN_IDS.TDVV]: 'https://tdvv-public-node.aelf.io', +}; + const DEFAULT_TIMEOUT_MS = 10_000; const DEFAULT_RETRY = 1; const DEFAULT_SDK_INSTANCE_CACHE_MAX = 32; @@ -11,15 +23,15 @@ const DEFAULT_REST_CLIENT_CACHE_MAX = 64; export const DEFAULT_NODES: NodeProfile[] = [ { id: 'default-aelf', - chainId: 'AELF', - rpcUrl: 'https://aelf-public-node.aelf.io', + chainId: CHAIN_IDS.AELF, + rpcUrl: DEFAULT_RPC_URLS[CHAIN_IDS.AELF], enabled: true, source: 'default', }, { id: 'default-tdvv', - chainId: 'tDVV', - rpcUrl: 'https://tdvv-public-node.aelf.io', + chainId: CHAIN_IDS.TDVV, + rpcUrl: DEFAULT_RPC_URLS[CHAIN_IDS.TDVV], enabled: true, source: 'default', }, @@ -52,11 +64,11 @@ export function getRestClientCacheMax(): number { } export function getRegistryPath(): string { - return process.env.AELF_NODE_REGISTRY_PATH || join(homedir(), '.aelf-node-skill', 'nodes.json'); + return process.env.AELF_NODE_REGISTRY_PATH ?? join(homedir(), '.aelf-node-skill', 'nodes.json'); } export function getEoaPrivateKey(override?: string): string | undefined { - return override || process.env.AELF_PRIVATE_KEY; + return override ?? process.env.AELF_PRIVATE_KEY; } export function getEnvOverrideNodes(): NodeProfile[] { @@ -64,7 +76,7 @@ export function getEnvOverrideNodes(): NodeProfile[] { if (process.env.AELF_NODE_AELF_RPC_URL) { result.push({ id: 'env-aelf', - chainId: 'AELF', + chainId: CHAIN_IDS.AELF, rpcUrl: process.env.AELF_NODE_AELF_RPC_URL, enabled: true, source: 'env', @@ -73,7 +85,7 @@ export function getEnvOverrideNodes(): NodeProfile[] { if (process.env.AELF_NODE_TDVV_RPC_URL) { result.push({ id: 'env-tdvv', - chainId: 'tDVV', + chainId: CHAIN_IDS.TDVV, rpcUrl: process.env.AELF_NODE_TDVV_RPC_URL, enabled: true, source: 'env', diff --git a/lib/node-router.ts b/lib/node-router.ts index 73d5a73..a5b77a6 100644 --- a/lib/node-router.ts +++ b/lib/node-router.ts @@ -1,4 +1,4 @@ -import { DEFAULT_NODES, getEnvOverrideNodes } from './config.js'; +import { DEFAULT_CHAIN_ID, DEFAULT_NODES, getEnvOverrideNodes } from './config.js'; import { listImportedNodes } from './node-registry.js'; import { validateChainTargetInput } from './validators.js'; import type { NodeProfile, ResolveNodeInput, ResolveNodeResult } from './types.js'; @@ -21,7 +21,7 @@ export async function resolveNode(input: ResolveNodeInput): Promise item.chainId === chainId); if (!node) { throw new Error(`No available node for chainId: ${chainId}`); diff --git a/package.json b/package.json index 8da9340..845b8f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockchain-forever/aelf-node-skill", - "version": "0.1.0", + "version": "0.1.1", "description": "AElf Node Skill for AI agents: REST for reads, SDK for contract execution, and selective fallback for fee estimate.", "type": "module", "main": "index.ts", @@ -40,7 +40,8 @@ "test:coverage:ci": "COVERAGE_MIN_LINES=85 COVERAGE_MIN_FUNCS=80 bun run test:unit:coverage && bun run coverage:gate", "build:openclaw": "bun run bin/generate-openclaw.ts", "build:openclaw:check": "bun run bin/generate-openclaw.ts --check", - "coverage:badge": "bun run bin/generate-coverage-badge.ts" + "coverage:badge": "bun run bin/generate-coverage-badge.ts", + "deps:check": "bun run scripts/check-deps-baseline.ts" }, "keywords": [ "aelf", @@ -64,7 +65,7 @@ "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.26.0", - "aelf-sdk": "3.5.1-beta.0", + "aelf-sdk": "^3.5.1-beta.0", "commander": "^12.1.0", "zod": "^3.24.0" }, diff --git a/scripts/check-deps-baseline.ts b/scripts/check-deps-baseline.ts new file mode 100644 index 0000000..812c4e9 --- /dev/null +++ b/scripts/check-deps-baseline.ts @@ -0,0 +1,58 @@ +#!/usr/bin/env bun +import { existsSync, readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +type Baseline = { + dependencies: Record; +}; + +function readJson(filePath: string): T { + return JSON.parse(readFileSync(filePath, 'utf8')) as T; +} + +function main() { + const cwd = process.cwd(); + const baselinePath = resolve(cwd, 'deps-baseline.json'); + const packagePath = resolve(cwd, 'package.json'); + + if (!existsSync(baselinePath)) { + console.error(`[deps:check] missing deps-baseline.json at ${baselinePath}`); + process.exit(1); + } + + if (!existsSync(packagePath)) { + console.error(`[deps:check] missing package.json at ${packagePath}`); + process.exit(1); + } + + const baseline = readJson(baselinePath); + const pkg = readJson(packagePath); + const declaredDeps = { + ...(pkg.dependencies || {}), + ...(pkg.devDependencies || {}), + } as Record; + + const failures: string[] = []; + for (const [name, expected] of Object.entries(baseline.dependencies || {})) { + const actual = declaredDeps[name]; + if (!actual) { + failures.push(`${name}: missing (expected ${expected})`); + continue; + } + if (actual !== expected) { + failures.push(`${name}: expected ${expected}, got ${actual}`); + } + } + + if (failures.length > 0) { + console.error('[deps:check] dependency baseline mismatch:'); + for (const failure of failures) { + console.error(`- ${failure}`); + } + process.exit(1); + } + + console.log('[deps:check] passed'); +} + +main(); diff --git a/tsconfig.json b/tsconfig.json index e692ef7..e5c0303 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,12 @@ "forceConsistentCasingInFileNames": true, "esModuleInterop": true, "resolveJsonModule": true, - "types": ["bun-types"] + "types": [ + "bun-types" + ], + "noEmit": true }, - "include": ["**/*.ts"] + "include": [ + "**/*.ts" + ] }