11#!/usr/bin/env node
2+ import { execSync } from 'node:child_process'
23import { existsSync , mkdirSync , readFileSync , realpathSync , writeFileSync } from 'node:fs'
34import { homedir } from 'node:os'
45import { dirname , join , resolve } from 'node:path'
@@ -14,22 +15,45 @@ import { serve } from './src/serve'
1415import type { RemobiConfig , RemobiConfigOverrides } from './src/types'
1516import { readStdin } from './src/util/node-compat'
1617
17- // Walk up from module location to find package.json — works from both source and dist/
18- function loadPackageVersion ( ) : string {
18+ // Walk up from module location to find project root — works from both source and dist/
19+ function findProjectRoot ( ) : string {
1920 let dir = import . meta. dirname
2021 for ( let i = 0 ; i < 5 ; i ++ ) {
21- try {
22- const content = readFileSync ( resolve ( dir , 'package.json' ) , 'utf-8' )
23- // oxlint-disable-next-line typescript/consistent-type-assertions -- JSON.parse returns unknown
24- return ( JSON . parse ( content ) as { version : string } ) . version
25- } catch {
26- dir = dirname ( dir )
27- }
22+ if ( existsSync ( resolve ( dir , 'package.json' ) ) ) return dir
23+ dir = dirname ( dir )
24+ }
25+ return import . meta. dirname
26+ }
27+
28+ function loadPackageVersion ( root : string ) : string {
29+ try {
30+ const content = readFileSync ( resolve ( root , 'package.json' ) , 'utf-8' )
31+ // oxlint-disable-next-line typescript/consistent-type-assertions -- JSON.parse returns unknown
32+ return ( JSON . parse ( content ) as { version : string } ) . version
33+ } catch {
34+ return '0.0.0'
35+ }
36+ }
37+
38+ // Source checkout has src/index.ts (not published to npm per files array in package.json).
39+ // For dev builds, append git short hash so local vs npm is obvious.
40+ function resolveVersion ( ) : string {
41+ const root = findProjectRoot ( )
42+ const pkgVersion = loadPackageVersion ( root )
43+ if ( ! existsSync ( resolve ( root , 'src/index.ts' ) ) ) return pkgVersion
44+ try {
45+ const hash = execSync ( 'git rev-parse --short HEAD' , {
46+ cwd : root ,
47+ encoding : 'utf-8' ,
48+ stdio : [ 'ignore' , 'pipe' , 'ignore' ] ,
49+ } ) . trim ( )
50+ return `${ pkgVersion } -dev+${ hash } `
51+ } catch {
52+ return pkgVersion
2853 }
29- return '0.0.0'
3054}
3155
32- const VERSION : string = loadPackageVersion ( )
56+ const VERSION : string = resolveVersion ( )
3357
3458function usage ( ) : void {
3559 console . log ( `remobi v${ VERSION } — mobile-friendly terminal overlay for ttyd + tmux
@@ -212,7 +236,14 @@ async function main(): Promise<void> {
212236 switch ( command ) {
213237 case 'serve' : {
214238 const loaded = await loadConfig ( configPath )
215- await serve ( loaded . config , port , command_ . length > 0 ? command_ : undefined , noSleep , host )
239+ await serve (
240+ loaded . config ,
241+ port ,
242+ command_ . length > 0 ? command_ : undefined ,
243+ noSleep ,
244+ host ,
245+ VERSION ,
246+ )
216247 break
217248 }
218249
@@ -233,7 +264,7 @@ async function main(): Promise<void> {
233264 // Ensure output directory exists
234265 mkdirSync ( dirname ( targetPath ) , { recursive : true } )
235266
236- await build ( loaded . config , targetPath )
267+ await build ( loaded . config , targetPath , VERSION )
237268 console . log ( `Built: ${ targetPath } ` )
238269 break
239270 }
@@ -254,7 +285,7 @@ async function main(): Promise<void> {
254285 }
255286
256287 ensureInjectInputMode ( 'remobi inject' )
257- const result = await injectFromStdin ( loaded . config )
288+ const result = await injectFromStdin ( loaded . config , VERSION )
258289 process . stdout . write ( result )
259290 break
260291 }
0 commit comments