diff --git a/src/index.ts b/src/index.ts index a8170b7..6d6d396 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,6 +21,7 @@ import { type EngineMode, resolveEngineMode, shouldUseSweetistics } from './lib/ import { extractTweetId } from './lib/extract-tweet-id.js'; import { SweetisticsClient } from './lib/sweetistics-client.js'; import { type TweetData, TwitterClient } from './lib/twitter-client.js'; +import { getCliVersion } from './lib/version.js'; const program = new Command(); @@ -98,7 +99,7 @@ program.addHelpText( () => `${colors.banner('bird CLI')} ${colors.subtitle('— fast X CLI for tweeting, replying, and reading')}`, ); -program.name('bird').description('Post tweets and replies via Twitter/X GraphQL API').version('0.1.0'); +program.name('bird').description('Post tweets and replies via Twitter/X GraphQL API').version(getCliVersion()); const formatExample = (command: string, description: string) => `${colors.command(` ${command}`)}\n${colors.muted(` ${description}`)}`; diff --git a/src/lib/version.ts b/src/lib/version.ts new file mode 100644 index 0000000..0b9d570 --- /dev/null +++ b/src/lib/version.ts @@ -0,0 +1,45 @@ +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +function readPackageVersionFromJsonFile(path: string): string | undefined { + try { + const raw = readFileSync(path, 'utf8'); + const parsed = JSON.parse(raw) as { version?: unknown }; + return typeof parsed.version === 'string' ? parsed.version : undefined; + } catch { + return undefined; + } +} + +function readVersionFromTextFile(path: string): string | undefined { + try { + const raw = readFileSync(path, 'utf8').trim(); + return raw.length > 0 ? raw : undefined; + } catch { + return undefined; + } +} + +function tryReadVersionFromImportMeta(): string | undefined { + try { + const thisFile = fileURLToPath(import.meta.url); + const baseDir = join(dirname(thisFile), '..', '..'); + return ( + readPackageVersionFromJsonFile(join(baseDir, 'package.json')) ?? readVersionFromTextFile(join(baseDir, 'VERSION')) + ); + } catch { + return undefined; + } +} + +function tryReadVersionNextToExecutable(): string | undefined { + const execDir = dirname(process.argv[0] ?? process.execPath); + return ( + readPackageVersionFromJsonFile(join(execDir, 'package.json')) ?? readVersionFromTextFile(join(execDir, 'VERSION')) + ); +} + +export function getCliVersion(): string { + return tryReadVersionFromImportMeta() ?? tryReadVersionNextToExecutable() ?? 'unknown'; +} diff --git a/tests/version.test.ts b/tests/version.test.ts new file mode 100644 index 0000000..fd8f2ef --- /dev/null +++ b/tests/version.test.ts @@ -0,0 +1,12 @@ +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; +import { getCliVersion } from '../src/lib/version.js'; + +describe('getCliVersion', () => { + it('matches package.json version when available', () => { + const raw = readFileSync(fileURLToPath(new URL('../package.json', import.meta.url)), 'utf8'); + const pkg = JSON.parse(raw) as { version?: unknown }; + expect(getCliVersion()).toBe(pkg.version); + }); +});