termbeam 1.17.1 → 1.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termbeam",
3
- "version": "1.17.1",
3
+ "version": "1.17.2",
4
4
  "description": "Beam your terminal to any device — mobile-optimized web terminal with multi-session support",
5
5
  "main": "src/server/index.js",
6
6
  "bin": {
@@ -101,12 +101,12 @@ function setupRoutes(app, { auth, sessions, config, state, pushManager }) {
101
101
 
102
102
  try {
103
103
  const info = await checkForUpdate({ currentVersion: config.version, force });
104
- const { installCmd, installArgs, ...publicInstallInfo } = detectInstallMethod();
104
+ const { installCmd, installArgs, cwd, ...publicInstallInfo } = detectInstallMethod();
105
105
  state.updateInfo = { ...info, ...publicInstallInfo };
106
106
  res.json(state.updateInfo);
107
107
  } catch (err) {
108
108
  log.warn(`Update check failed: ${err.message}`);
109
- const { installCmd, installArgs, ...publicInstallInfo } = detectInstallMethod();
109
+ const { installCmd, installArgs, cwd, ...publicInstallInfo } = detectInstallMethod();
110
110
  const fallback = {
111
111
  current: config.version,
112
112
  latest: null,
@@ -226,6 +226,7 @@ function setupRoutes(app, { auth, sessions, config, state, pushManager }) {
226
226
  restartStrategy: installInfo.restartStrategy,
227
227
  onProgress: broadcastProgress,
228
228
  performRestart,
229
+ cwd: installInfo.cwd,
229
230
  }).catch((err) => {
230
231
  log.error(`Update execution error: ${err.message}`);
231
232
  });
@@ -292,14 +292,31 @@ function detectInstallMethod() {
292
292
  // Check before Docker: a git checkout running inside a container (CI/devcontainers)
293
293
  // should be treated as source, not Docker
294
294
  if (isRunningFromSource()) {
295
+ const sourceRoot = getSourceRoot();
296
+ const baseCmd = 'git pull && npm install && npm run build:frontend';
297
+
298
+ if (isPm2) {
299
+ log.debug('Install method: source (PM2)');
300
+ return {
301
+ method: 'source',
302
+ command: `${baseCmd} && pm2 restart termbeam`,
303
+ installCmd: process.platform === 'win32' ? process.env.COMSPEC || 'cmd.exe' : 'sh',
304
+ installArgs: process.platform === 'win32' ? ['/c', baseCmd] : ['-c', baseCmd],
305
+ canAutoUpdate: true,
306
+ restartStrategy: 'pm2',
307
+ cwd: sourceRoot,
308
+ };
309
+ }
310
+
295
311
  log.debug('Install method: source');
296
312
  return {
297
313
  method: 'source',
298
- command: 'git pull && npm install && npm run build:frontend',
314
+ command: baseCmd,
299
315
  installCmd: null,
300
316
  installArgs: null,
301
317
  canAutoUpdate: false,
302
318
  restartStrategy: 'none',
319
+ cwd: sourceRoot,
303
320
  };
304
321
  }
305
322
 
@@ -346,6 +363,26 @@ function isRunningInDocker() {
346
363
  return false;
347
364
  }
348
365
 
366
+ /**
367
+ * Find the root of the source checkout by walking up from __dirname.
368
+ * Returns the absolute path to the repo root, or null if not found.
369
+ */
370
+ function getSourceRoot() {
371
+ if (__dirname.includes('node_modules')) return null;
372
+ try {
373
+ let currentDir = __dirname;
374
+ for (let i = 0; i < 10; i++) {
375
+ if (fs.existsSync(path.join(currentDir, '.git'))) return currentDir;
376
+ const parentDir = path.dirname(currentDir);
377
+ if (!parentDir || parentDir === currentDir) break;
378
+ currentDir = parentDir;
379
+ }
380
+ } catch {
381
+ // ignore
382
+ }
383
+ return null;
384
+ }
385
+
349
386
  /**
350
387
  * Detect if running from a git source checkout (not installed as a package).
351
388
  * Walks upward from __dirname looking for .git to avoid fragile fixed-depth assumptions.
@@ -387,4 +424,5 @@ module.exports = {
387
424
  isRunningInDocker,
388
425
  isRunningFromSource,
389
426
  isRunningUnderPm2,
427
+ getSourceRoot,
390
428
  };
@@ -71,6 +71,16 @@ function resetState() {
71
71
  * Returns { canUpdate, reason } — if canUpdate is false, reason explains why.
72
72
  */
73
73
  async function checkPermissions(method) {
74
+ // Source installs use git, not a package manager
75
+ if (method === 'source') {
76
+ try {
77
+ await execFilePromise('git', ['--version'], { timeout: VERIFY_TIMEOUT_MS });
78
+ } catch {
79
+ return { canUpdate: false, reason: 'git not found on PATH' };
80
+ }
81
+ return { canUpdate: true, reason: null };
82
+ }
83
+
74
84
  const cmd = method === 'yarn' ? 'yarn' : method === 'pnpm' ? 'pnpm' : 'npm';
75
85
 
76
86
  // Check if the package manager is available by running it directly
@@ -124,6 +134,7 @@ async function executeUpdate({
124
134
  restartStrategy,
125
135
  onProgress,
126
136
  performRestart,
137
+ cwd,
127
138
  }) {
128
139
  if (updateState.status !== 'idle' && updateState.status !== 'failed') {
129
140
  return { ...updateState, error: 'Update already in progress' };
@@ -169,6 +180,7 @@ async function executeUpdate({
169
180
  timeout: INSTALL_TIMEOUT_MS,
170
181
  maxBuffer: 10 * 1024 * 1024, // 10 MB — package manager installs can be verbose
171
182
  env: { ...process.env, NO_UPDATE_NOTIFIER: '1' },
183
+ cwd: cwd || undefined,
172
184
  });
173
185
 
174
186
  const output = (stdout + '\n' + stderr).trim();
@@ -178,7 +190,7 @@ async function executeUpdate({
178
190
  // Step 3: Verify
179
191
  notify({ status: 'verifying', phase: 'Verifying update...' });
180
192
 
181
- const newVersion = await verifyInstalledVersion(method);
193
+ const newVersion = await verifyInstalledVersion(method, cwd);
182
194
  if (!newVersion) {
183
195
  notify({
184
196
  status: 'failed',
@@ -230,7 +242,22 @@ async function executeUpdate({
230
242
 
231
243
  // ── Version Verification ─────────────────────────────────────────────────────
232
244
 
233
- async function verifyInstalledVersion(method) {
245
+ async function verifyInstalledVersion(method, cwd) {
246
+ // Source installs: read version from the repo's package.json after git pull
247
+ if (method === 'source') {
248
+ try {
249
+ const pkgPath = cwd
250
+ ? path.join(cwd, 'package.json')
251
+ : path.resolve(__dirname, '../../package.json');
252
+ const content = await fs.promises.readFile(pkgPath, 'utf8');
253
+ const pkg = JSON.parse(content);
254
+ return pkg.version || null;
255
+ } catch (err) {
256
+ log.debug(`Version verification via package.json failed: ${err.message}`);
257
+ }
258
+ return null;
259
+ }
260
+
234
261
  const cmd = method === 'yarn' ? 'yarn' : method === 'pnpm' ? 'pnpm' : 'npm';
235
262
  try {
236
263
  // Use npm/yarn/pnpm to read the installed version