diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3e8abb..03b4d9b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,4 +21,12 @@ jobs: cache: npm - run: npm ci - run: npm run build + - name: CLI --version matches package.json + run: | + PKG=$(node -p "require('./package.json').version") + CLI=$(node dist/index.js --version) + if [ "$PKG" != "$CLI" ]; then + echo "Version drift: package.json=$PKG, CLI=$CLI" + exit 1 + fi - run: npm test diff --git a/package-lock.json b/package-lock.json index f36fc92..8160ba6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@switchbot/openapi-cli", - "version": "1.3.1", + "version": "1.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@switchbot/openapi-cli", - "version": "1.3.1", + "version": "1.3.2", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.29.0", diff --git a/package.json b/package.json index 10180d1..c981272 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@switchbot/openapi-cli", - "version": "1.3.1", + "version": "1.3.2", "description": "Command-line interface for SwitchBot API v1.1", "keywords": [ "switchbot", diff --git a/src/index.ts b/src/index.ts index bbb3b40..e335388 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ #!/usr/bin/env node import { Command, CommanderError } from 'commander'; +import { createRequire } from 'node:module'; import { registerConfigCommand } from './commands/config.js'; import { registerDevicesCommand } from './commands/devices.js'; import { registerScenesCommand } from './commands/scenes.js'; @@ -16,12 +17,15 @@ import { registerHistoryCommand } from './commands/history.js'; import { registerPlanCommand } from './commands/plan.js'; import { registerCapabilitiesCommand } from './commands/capabilities.js'; +const require = createRequire(import.meta.url); +const { version: pkgVersion } = require('../package.json') as { version: string }; + const program = new Command(); program .name('switchbot') .description('Command-line tool for SwitchBot API v1.1') - .version('2.1.0') + .version(pkgVersion) .option('--json', 'Output raw JSON response (disables tables; useful for pipes/scripts)') .option('--format ', 'Output format: table (default), json, jsonl, tsv, yaml, id') .option('--fields ', 'Comma-separated list of columns to include (e.g. --fields=id,name,type)') diff --git a/tests/commands/capabilities.test.ts b/tests/commands/capabilities.test.ts index eb8d2f1..632d348 100644 --- a/tests/commands/capabilities.test.ts +++ b/tests/commands/capabilities.test.ts @@ -5,7 +5,7 @@ import { registerCapabilitiesCommand } from '../../src/commands/capabilities.js' /** Build a representative program that mirrors the real CLI structure. */ function makeProgram(): Command { const p = new Command(); - p.name('switchbot').version('2.1.0'); + p.name('switchbot').version('0.0.0-test'); p.option('--json', 'Output raw JSON response'); p.option('--format ', 'Output format'); p.option('--fields ', 'Column filter'); diff --git a/tests/globalSetup.ts b/tests/globalSetup.ts new file mode 100644 index 0000000..e6e744d --- /dev/null +++ b/tests/globalSetup.ts @@ -0,0 +1,8 @@ +import { execSync } from 'node:child_process'; + +export default function setup(): void { + // Build once before the test run so tests that exercise the compiled CLI + // (e.g. version.test.ts spawning `node dist/index.js --version`) see the + // latest source. Runs once per vitest invocation, not per file. + execSync('npm run build', { stdio: 'inherit' }); +} diff --git a/tests/version.test.ts b/tests/version.test.ts new file mode 100644 index 0000000..fef53f3 --- /dev/null +++ b/tests/version.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import { execFileSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; + +// Read the real package.json (NOT an import — keeps this decoupled from tsc's +// JSON import assertion setup and mirrors what publish.yml does in CI). +const here = path.dirname(fileURLToPath(import.meta.url)); +const pkg = JSON.parse( + readFileSync(path.join(here, '..', 'package.json'), 'utf-8'), +) as { version: string }; + +describe('CLI --version', () => { + it('matches package.json version', () => { + // Regression guard for the v1.3.1 bug where src/index.ts hardcoded a + // stale version string. execFileSync + process.execPath avoids shell + // quoting and PATH lookup issues on Windows/macOS/Linux. + const out = execFileSync( + process.execPath, + ['dist/index.js', '--version'], + { encoding: 'utf-8' }, + ).trim(); + expect(out).toBe(pkg.version); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts index 89a5291..84276a4 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,6 +4,7 @@ export default defineConfig({ test: { environment: 'node', include: ['tests/**/*.test.ts'], + globalSetup: ['./tests/globalSetup.ts'], coverage: { provider: 'v8', include: ['src/**/*.ts'],