termbeam 1.15.2 → 1.17.0

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.
Files changed (68) hide show
  1. package/README.md +3 -0
  2. package/package.json +2 -1
  3. package/public/assets/{_basePickBy-BSIbg2Hw.js → _basePickBy-Crmlna7W.js} +1 -1
  4. package/public/assets/{_baseUniq-CYmx81nY.js → _baseUniq-h6HY8nD4.js} +1 -1
  5. package/public/assets/{arc-CDJcNcKc.js → arc-BI4RNUD8.js} +1 -1
  6. package/public/assets/{architectureDiagram-2XIMDMQ5-C1qauSxh.js → architectureDiagram-2XIMDMQ5-C2PAl3D6.js} +1 -1
  7. package/public/assets/{blockDiagram-WCTKOSBZ-nTHCaU6g.js → blockDiagram-WCTKOSBZ-CADYyoNx.js} +1 -1
  8. package/public/assets/{c4Diagram-IC4MRINW-CdGuCZNN.js → c4Diagram-IC4MRINW-CQtNNlqT.js} +1 -1
  9. package/public/assets/channel-DlFJ0YtH.js +1 -0
  10. package/public/assets/{chunk-4BX2VUAB-IxfdQ8zN.js → chunk-4BX2VUAB-BZhBHL2q.js} +1 -1
  11. package/public/assets/{chunk-55IACEB6-mjdLMPLu.js → chunk-55IACEB6-DaOODotQ.js} +1 -1
  12. package/public/assets/{chunk-FMBD7UC4-B-QgE8A5.js → chunk-FMBD7UC4-D7ZUE2Qt.js} +1 -1
  13. package/public/assets/{chunk-JSJVCQXG-BlBtV3cx.js → chunk-JSJVCQXG-Cr7LmD49.js} +1 -1
  14. package/public/assets/{chunk-KX2RTZJC-ByLbXYtr.js → chunk-KX2RTZJC-mSzu7V0i.js} +1 -1
  15. package/public/assets/{chunk-NQ4KR5QH-DdgKg6ac.js → chunk-NQ4KR5QH-UNIo7K3P.js} +1 -1
  16. package/public/assets/{chunk-QZHKN3VN-DK0sNhO7.js → chunk-QZHKN3VN-D8pHtVTR.js} +1 -1
  17. package/public/assets/{chunk-WL4C6EOR-CMhwM8MW.js → chunk-WL4C6EOR-CKtSBmtm.js} +1 -1
  18. package/public/assets/classDiagram-VBA2DB6C-Uh272C_T.js +1 -0
  19. package/public/assets/classDiagram-v2-RAHNMMFH-Uh272C_T.js +1 -0
  20. package/public/assets/clone-BiOpyrvc.js +1 -0
  21. package/public/assets/{cose-bilkent-S5V4N54A-C4J5lbLg.js → cose-bilkent-S5V4N54A-C73dVsDU.js} +1 -1
  22. package/public/assets/{dagre-KLK3FWXG-CmPYo_iW.js → dagre-KLK3FWXG-CGtdO-e6.js} +1 -1
  23. package/public/assets/{diagram-E7M64L7V-BSDHjD_1.js → diagram-E7M64L7V-B3RnL1-2.js} +1 -1
  24. package/public/assets/{diagram-IFDJBPK2-DZFEThmE.js → diagram-IFDJBPK2-BhT13Y--.js} +1 -1
  25. package/public/assets/{diagram-P4PSJMXO-D2vA458R.js → diagram-P4PSJMXO-w4ta5qzj.js} +1 -1
  26. package/public/assets/{erDiagram-INFDFZHY-CqngKW80.js → erDiagram-INFDFZHY-p_XdulXc.js} +1 -1
  27. package/public/assets/{flowDiagram-PKNHOUZH-2ndb8I08.js → flowDiagram-PKNHOUZH-cKD9roCC.js} +1 -1
  28. package/public/assets/{ganttDiagram-A5KZAMGK-DGH9iwxm.js → ganttDiagram-A5KZAMGK-kRLcbnHy.js} +1 -1
  29. package/public/assets/{gitGraphDiagram-K3NZZRJ6-DBszyq19.js → gitGraphDiagram-K3NZZRJ6-CfqReYYJ.js} +1 -1
  30. package/public/assets/{graph-B-VDztTg.js → graph-2Z05uqaC.js} +1 -1
  31. package/public/assets/index-Bpz9aDGB.css +32 -0
  32. package/public/assets/index-Cvxh0Fjh.js +394 -0
  33. package/public/assets/{infoDiagram-LFFYTUFH-BQYostn9.js → infoDiagram-LFFYTUFH-D2bxFvYS.js} +1 -1
  34. package/public/assets/{ishikawaDiagram-PHBUUO56-BF9SDQjL.js → ishikawaDiagram-PHBUUO56-olWTIvNJ.js} +1 -1
  35. package/public/assets/{journeyDiagram-4ABVD52K-BVygcg_3.js → journeyDiagram-4ABVD52K-T_3LhARU.js} +1 -1
  36. package/public/assets/{kanban-definition-K7BYSVSG-C360CZ_M.js → kanban-definition-K7BYSVSG-BCmUNdAK.js} +1 -1
  37. package/public/assets/{layout-D1dS_Xae.js → layout-BuQ9md8V.js} +1 -1
  38. package/public/assets/{linear-DSiHoSbJ.js → linear-BGGATdCH.js} +1 -1
  39. package/public/assets/{mindmap-definition-YRQLILUH-DW7C3qtv.js → mindmap-definition-YRQLILUH-Bz_sgl78.js} +1 -1
  40. package/public/assets/{pieDiagram-SKSYHLDU-C8vfomtz.js → pieDiagram-SKSYHLDU-wxt-R3l5.js} +1 -1
  41. package/public/assets/{quadrantDiagram-337W2JSQ-DXT_qKk-.js → quadrantDiagram-337W2JSQ-0yTHkNo0.js} +1 -1
  42. package/public/assets/{requirementDiagram-Z7DCOOCP-Dj2MzFq3.js → requirementDiagram-Z7DCOOCP-CLqLwKcJ.js} +1 -1
  43. package/public/assets/{sankeyDiagram-WA2Y5GQK-YfmbQXg2.js → sankeyDiagram-WA2Y5GQK-CV2OX87k.js} +1 -1
  44. package/public/assets/{sequenceDiagram-2WXFIKYE-Bp9hgUSv.js → sequenceDiagram-2WXFIKYE-DaQifS2p.js} +1 -1
  45. package/public/assets/{stateDiagram-RAJIS63D-D8VgKzZe.js → stateDiagram-RAJIS63D-Bi5e4H5H.js} +1 -1
  46. package/public/assets/stateDiagram-v2-FVOUBMTO-D2d2wuS-.js +1 -0
  47. package/public/assets/{timeline-definition-YZTLITO2-3ErXxqpK.js → timeline-definition-YZTLITO2-Bu0j_UbL.js} +1 -1
  48. package/public/assets/{treemap-KZPCXAKY-D3-uSz_K.js → treemap-KZPCXAKY-BreHb2Q6.js} +1 -1
  49. package/public/assets/{vennDiagram-LZ73GAT5-D7Isk6A4.js → vennDiagram-LZ73GAT5-C5vHpUCv.js} +1 -1
  50. package/public/assets/{xychartDiagram-JWTSCODW-CpuvGwXM.js → xychartDiagram-JWTSCODW-DEN428FH.js} +1 -1
  51. package/public/index.html +2 -2
  52. package/public/sw.js +2 -2
  53. package/src/server/index.js +35 -4
  54. package/src/server/push.js +118 -0
  55. package/src/server/routes.js +350 -5
  56. package/src/server/sessions.js +144 -0
  57. package/src/server/websocket.js +21 -2
  58. package/src/utils/git.js +338 -1
  59. package/src/utils/update-check.js +139 -9
  60. package/src/utils/update-executor.js +340 -0
  61. package/src/utils/vapid.js +45 -0
  62. package/public/assets/channel-Bh_CZXn-.js +0 -1
  63. package/public/assets/classDiagram-VBA2DB6C-CSkcpaag.js +0 -1
  64. package/public/assets/classDiagram-v2-RAHNMMFH-CSkcpaag.js +0 -1
  65. package/public/assets/clone-BR1se3-G.js +0 -1
  66. package/public/assets/index-BWUfRdC9.js +0 -391
  67. package/public/assets/index-D_1GL6a5.css +0 -32
  68. package/public/assets/stateDiagram-v2-FVOUBMTO-By5luAVT.js +0 -1
@@ -0,0 +1,340 @@
1
+ const { execFile } = require('child_process');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+ const log = require('./logger');
6
+
7
+ const INSTALL_TIMEOUT_MS = 120_000; // 2 minutes for npm install
8
+ const VERIFY_TIMEOUT_MS = 5_000;
9
+
10
+ function getConfigDir() {
11
+ return process.env.TERMBEAM_CONFIG_DIR || path.join(os.homedir(), '.termbeam');
12
+ }
13
+
14
+ function getUpdateResultPath() {
15
+ return path.join(getConfigDir(), 'update-result.json');
16
+ }
17
+
18
+ // ── State Machine ────────────────────────────────────────────────────────────
19
+
20
+ /**
21
+ * @typedef {'idle'|'checking-permissions'|'installing'|'verifying'|'restarting'|'complete'|'failed'} UpdateStatus
22
+ * @typedef {{
23
+ * status: UpdateStatus,
24
+ * phase: string|null,
25
+ * progress: string|null,
26
+ * error: string|null,
27
+ * fromVersion: string|null,
28
+ * toVersion: string|null,
29
+ * startedAt: number|null,
30
+ * restartStrategy: string|null,
31
+ * }} UpdateState
32
+ */
33
+
34
+ /** @type {UpdateState} */
35
+ const updateState = {
36
+ status: 'idle',
37
+ phase: null,
38
+ progress: null,
39
+ error: null,
40
+ fromVersion: null,
41
+ toVersion: null,
42
+ startedAt: null,
43
+ restartStrategy: null,
44
+ };
45
+
46
+ function getUpdateState() {
47
+ return { ...updateState };
48
+ }
49
+
50
+ function setState(updates) {
51
+ Object.assign(updateState, updates);
52
+ }
53
+
54
+ function resetState() {
55
+ setState({
56
+ status: 'idle',
57
+ phase: null,
58
+ progress: null,
59
+ error: null,
60
+ fromVersion: null,
61
+ toVersion: null,
62
+ startedAt: null,
63
+ restartStrategy: null,
64
+ });
65
+ }
66
+
67
+ // ── Permission Check ─────────────────────────────────────────────────────────
68
+
69
+ /**
70
+ * Check if we can write to the npm global prefix directory.
71
+ * Returns { canUpdate, reason } — if canUpdate is false, reason explains why.
72
+ */
73
+ async function checkPermissions(method) {
74
+ const cmd = method === 'yarn' ? 'yarn' : method === 'pnpm' ? 'pnpm' : 'npm';
75
+
76
+ // Check if the package manager is available by running it directly
77
+ try {
78
+ await execFilePromise(cmd, ['--version'], { timeout: VERIFY_TIMEOUT_MS });
79
+ } catch {
80
+ return { canUpdate: false, reason: `${cmd} not found on PATH` };
81
+ }
82
+
83
+ // Check if npm global directory is writable (only for npm — yarn/pnpm have different paths)
84
+ if (cmd === 'npm') {
85
+ try {
86
+ // Use `npm root -g` for the actual global node_modules path (cross-platform)
87
+ const globalRoot = (
88
+ await execFilePromise('npm', ['root', '-g'], { timeout: VERIFY_TIMEOUT_MS })
89
+ ).stdout.trim();
90
+ await fs.promises.access(globalRoot, fs.constants.W_OK);
91
+ } catch {
92
+ return {
93
+ canUpdate: false,
94
+ reason: 'npm global directory is not writable (may need sudo)',
95
+ };
96
+ }
97
+ }
98
+
99
+ return { canUpdate: true, reason: null };
100
+ }
101
+
102
+ // ── Update Execution ─────────────────────────────────────────────────────────
103
+
104
+ /**
105
+ * Execute the update process. Calls onProgress for real-time status updates.
106
+ *
107
+ * @param {object} options
108
+ * @param {string} options.currentVersion - Version before update
109
+ * @param {string} options.installCmd - The executable to run (e.g. "npm")
110
+ * @param {string[]} options.installArgs - Arguments for the install command
111
+ * @param {string} options.command - Display string for the full command
112
+ * @param {string} options.method - Package manager (npm/yarn/pnpm)
113
+ * @param {string} options.restartStrategy - 'pm2' or 'exit'
114
+ * @param {(state: UpdateState) => void} [options.onProgress] - Progress callback
115
+ * @param {() => Promise<void>} [options.performRestart] - Called to execute restart
116
+ * @returns {Promise<UpdateState>}
117
+ */
118
+ async function executeUpdate({
119
+ currentVersion,
120
+ installCmd,
121
+ installArgs,
122
+ command,
123
+ method,
124
+ restartStrategy,
125
+ onProgress,
126
+ performRestart,
127
+ }) {
128
+ if (updateState.status !== 'idle' && updateState.status !== 'failed') {
129
+ return { ...updateState, error: 'Update already in progress' };
130
+ }
131
+
132
+ const notify = (updates) => {
133
+ setState(updates);
134
+ if (onProgress) {
135
+ try {
136
+ onProgress(getUpdateState());
137
+ } catch {
138
+ // Don't let callback errors break the update
139
+ }
140
+ }
141
+ };
142
+
143
+ setState({
144
+ status: 'checking-permissions',
145
+ phase: 'Checking permissions...',
146
+ progress: null,
147
+ error: null,
148
+ fromVersion: currentVersion,
149
+ toVersion: null,
150
+ startedAt: Date.now(),
151
+ restartStrategy,
152
+ });
153
+ notify({});
154
+
155
+ try {
156
+ // Step 1: Permission check
157
+ const { canUpdate, reason } = await checkPermissions(method);
158
+ if (!canUpdate) {
159
+ notify({ status: 'failed', phase: 'Permission check failed', error: reason });
160
+ return getUpdateState();
161
+ }
162
+
163
+ // Step 2: Install
164
+ notify({ status: 'installing', phase: 'Installing update...', progress: '' });
165
+
166
+ log.info(`Executing update: ${command}`);
167
+
168
+ const { stdout, stderr } = await execFilePromise(installCmd, installArgs, {
169
+ timeout: INSTALL_TIMEOUT_MS,
170
+ maxBuffer: 10 * 1024 * 1024, // 10 MB — package manager installs can be verbose
171
+ env: { ...process.env, NO_UPDATE_NOTIFIER: '1' },
172
+ });
173
+
174
+ const output = (stdout + '\n' + stderr).trim();
175
+ notify({ progress: output.slice(-500) }); // Keep last 500 chars
176
+ log.debug(`Install output: ${output.slice(0, 200)}`);
177
+
178
+ // Step 3: Verify
179
+ notify({ status: 'verifying', phase: 'Verifying update...' });
180
+
181
+ const newVersion = await verifyInstalledVersion(method);
182
+ if (!newVersion) {
183
+ notify({
184
+ status: 'failed',
185
+ phase: 'Verification failed',
186
+ error: 'Could not determine new version after install',
187
+ });
188
+ return getUpdateState();
189
+ }
190
+
191
+ const { isNewerVersion } = require('./update-check');
192
+ if (newVersion === currentVersion) {
193
+ // Same version reinstalled (cache, registry delay) — treat as success
194
+ log.info(`Update reinstalled same version (${newVersion})`);
195
+ } else if (!isNewerVersion(currentVersion, newVersion)) {
196
+ notify({
197
+ status: 'failed',
198
+ phase: 'Verification failed',
199
+ error: `Unexpected version after update: ${newVersion} (was ${currentVersion})`,
200
+ });
201
+ return getUpdateState();
202
+ }
203
+
204
+ notify({ toVersion: newVersion });
205
+
206
+ // Step 4: Write result marker for post-restart verification
207
+ writeUpdateResult({ fromVersion: currentVersion, toVersion: newVersion });
208
+
209
+ // Step 5: Restart
210
+ notify({ status: 'restarting', phase: `Update to v${newVersion} complete. Restarting...` });
211
+
212
+ if (performRestart) {
213
+ // Give clients a moment to receive the status update
214
+ await sleep(500);
215
+ await performRestart();
216
+ }
217
+
218
+ notify({ status: 'complete', phase: `Updated to v${newVersion}` });
219
+ return getUpdateState();
220
+ } catch (err) {
221
+ log.error(`Update failed: ${err.message}`);
222
+ notify({
223
+ status: 'failed',
224
+ phase: 'Update failed',
225
+ error: err.message,
226
+ });
227
+ return getUpdateState();
228
+ }
229
+ }
230
+
231
+ // ── Version Verification ─────────────────────────────────────────────────────
232
+
233
+ async function verifyInstalledVersion(method) {
234
+ const cmd = method === 'yarn' ? 'yarn' : method === 'pnpm' ? 'pnpm' : 'npm';
235
+ try {
236
+ // Use npm/yarn/pnpm to read the installed version
237
+ let args;
238
+ if (cmd === 'npm') {
239
+ args = ['ls', '-g', 'termbeam', '--depth=0', '--json'];
240
+ } else if (cmd === 'yarn') {
241
+ args = ['global', 'list', '--json', '--pattern', 'termbeam'];
242
+ } else {
243
+ args = ['list', '-g', 'termbeam', '--json'];
244
+ }
245
+
246
+ const { stdout } = await execFilePromise(cmd, args, { timeout: VERIFY_TIMEOUT_MS });
247
+
248
+ if (cmd === 'npm') {
249
+ const data = JSON.parse(stdout);
250
+ const deps = data.dependencies || {};
251
+ if (deps.termbeam && deps.termbeam.version) {
252
+ return deps.termbeam.version;
253
+ }
254
+ }
255
+
256
+ // Fallback: try parsing version from output
257
+ const match = stdout.match(/termbeam@(\d+\.\d+\.\d+)/);
258
+ if (match) return match[1];
259
+ } catch (err) {
260
+ log.debug(`Version verification via ${cmd} failed: ${err.message}`);
261
+ }
262
+
263
+ // Fallback: try running the new termbeam binary directly
264
+ try {
265
+ const { stdout } = await execFilePromise('termbeam', ['--version'], {
266
+ timeout: VERIFY_TIMEOUT_MS,
267
+ });
268
+ const match = stdout.trim().match(/(\d+\.\d+\.\d+)/);
269
+ if (match) return match[1];
270
+ } catch {
271
+ log.debug('Version verification via termbeam --version failed');
272
+ }
273
+
274
+ return null;
275
+ }
276
+
277
+ // ── Update Result Marker ─────────────────────────────────────────────────────
278
+
279
+ function writeUpdateResult({ fromVersion, toVersion }) {
280
+ try {
281
+ const resultPath = getUpdateResultPath();
282
+ fs.mkdirSync(path.dirname(resultPath), { recursive: true });
283
+ fs.writeFileSync(
284
+ resultPath,
285
+ JSON.stringify({ fromVersion, toVersion, updatedAt: Date.now() }) + '\n',
286
+ { mode: 0o600 },
287
+ );
288
+ } catch (err) {
289
+ log.debug(`Could not write update result: ${err.message}`);
290
+ }
291
+ }
292
+
293
+ function readUpdateResult() {
294
+ try {
295
+ const data = JSON.parse(fs.readFileSync(getUpdateResultPath(), 'utf8'));
296
+ if (data && data.fromVersion && data.toVersion) return data;
297
+ } catch {
298
+ // No result or corrupt
299
+ }
300
+ return null;
301
+ }
302
+
303
+ function clearUpdateResult() {
304
+ try {
305
+ fs.unlinkSync(getUpdateResultPath());
306
+ } catch {
307
+ // Already gone
308
+ }
309
+ }
310
+
311
+ // ── Helpers ──────────────────────────────────────────────────────────────────
312
+
313
+ function execFilePromise(cmd, args, options = {}) {
314
+ return new Promise((resolve, reject) => {
315
+ execFile(cmd, args, { encoding: 'utf8', ...options }, (err, stdout, stderr) => {
316
+ if (err) {
317
+ err.stdout = stdout;
318
+ err.stderr = stderr;
319
+ reject(err);
320
+ } else {
321
+ resolve({ stdout, stderr });
322
+ }
323
+ });
324
+ });
325
+ }
326
+
327
+ function sleep(ms) {
328
+ return new Promise((resolve) => setTimeout(resolve, ms));
329
+ }
330
+
331
+ module.exports = {
332
+ getUpdateState,
333
+ resetState,
334
+ executeUpdate,
335
+ checkPermissions,
336
+ verifyInstalledVersion,
337
+ writeUpdateResult,
338
+ readUpdateResult,
339
+ clearUpdateResult,
340
+ };
@@ -0,0 +1,45 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const webpush = require('web-push');
4
+ const log = require('./logger');
5
+
6
+ /**
7
+ * Load existing VAPID keys from configDir/vapid.json or generate new ones.
8
+ * Keys are persisted so they survive server restarts.
9
+ * @param {string} configDir - Directory to store vapid.json
10
+ * @returns {{ publicKey: string, privateKey: string, subject: string }}
11
+ */
12
+ function getOrCreateVapidKeys(configDir) {
13
+ const vapidPath = path.join(configDir, 'vapid.json');
14
+ const subject = 'https://termbeam.dev';
15
+
16
+ try {
17
+ const raw = fs.readFileSync(vapidPath, 'utf8');
18
+ const keys = JSON.parse(raw);
19
+ if (keys.publicKey && keys.privateKey) {
20
+ log.debug('Loaded existing VAPID keys');
21
+ return { publicKey: keys.publicKey, privateKey: keys.privateKey, subject };
22
+ }
23
+ } catch {
24
+ // File doesn't exist or is invalid — generate new keys
25
+ }
26
+
27
+ log.info('Generating new VAPID keys');
28
+ const keys = webpush.generateVAPIDKeys();
29
+
30
+ try {
31
+ fs.mkdirSync(configDir, { recursive: true });
32
+ fs.writeFileSync(
33
+ vapidPath,
34
+ JSON.stringify({ publicKey: keys.publicKey, privateKey: keys.privateKey }, null, 2),
35
+ { mode: 0o600 },
36
+ );
37
+ log.debug(`VAPID keys saved to ${vapidPath}`);
38
+ } catch (err) {
39
+ log.warn(`Could not save VAPID keys to ${vapidPath}: ${err.message}`);
40
+ }
41
+
42
+ return { publicKey: keys.publicKey, privateKey: keys.privateKey, subject };
43
+ }
44
+
45
+ module.exports = { getOrCreateVapidKeys };
@@ -1 +0,0 @@
1
- import{aq as o,ar as n}from"./index-BWUfRdC9.js";const t=(r,a)=>o.lang.round(n.parse(r)[a]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-WL4C6EOR-CMhwM8MW.js";import{_ as i}from"./index-BWUfRdC9.js";import"./chunk-FMBD7UC4-B-QgE8A5.js";import"./chunk-JSJVCQXG-BlBtV3cx.js";import"./chunk-55IACEB6-mjdLMPLu.js";import"./chunk-KX2RTZJC-ByLbXYtr.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-WL4C6EOR-CMhwM8MW.js";import{_ as i}from"./index-BWUfRdC9.js";import"./chunk-FMBD7UC4-B-QgE8A5.js";import"./chunk-JSJVCQXG-BlBtV3cx.js";import"./chunk-55IACEB6-mjdLMPLu.js";import"./chunk-KX2RTZJC-ByLbXYtr.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -1 +0,0 @@
1
- import{b as r}from"./_baseUniq-CYmx81nY.js";var e=4;function a(o){return r(o,e)}export{a as c};