deckide 3.5.10 → 3.5.12
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/bin/deckide.js +4 -3
- package/dist/routes/terminals.js +1 -101
- package/dist/server.js +3 -6
- package/dist/utils/database.js +6 -0
- package/dist/websocket.js +5 -2
- package/package.json +1 -1
package/bin/deckide.js
CHANGED
|
@@ -317,8 +317,8 @@ if (command === 'auth') {
|
|
|
317
317
|
const genUser = user || 'admin';
|
|
318
318
|
const genPassword = password || crypto.randomBytes(16).toString('base64url');
|
|
319
319
|
|
|
320
|
-
if (password && password.length <
|
|
321
|
-
console.error('Error: password must be at least
|
|
320
|
+
if (password && password.length < 12) {
|
|
321
|
+
console.error('Error: password must be at least 12 characters.');
|
|
322
322
|
process.exit(1);
|
|
323
323
|
}
|
|
324
324
|
|
|
@@ -370,13 +370,14 @@ if (command === 'logs') {
|
|
|
370
370
|
if (follow) {
|
|
371
371
|
const tail = spawn('tail', ['-f', logFile], { stdio: 'inherit' });
|
|
372
372
|
tail.on('exit', () => process.exit(0));
|
|
373
|
+
await new Promise(() => {}); // Block until tail exits
|
|
373
374
|
} else {
|
|
374
375
|
const lines = fs.readFileSync(logFile, 'utf-8');
|
|
375
376
|
// Show last 50 lines
|
|
376
377
|
const arr = lines.split('\n');
|
|
377
378
|
console.log(arr.slice(-51).join('\n'));
|
|
379
|
+
process.exit(0);
|
|
378
380
|
}
|
|
379
|
-
if (!args.includes('-f') && !args.includes('--follow')) process.exit(0);
|
|
380
381
|
}
|
|
381
382
|
|
|
382
383
|
// ── deckide stop ──
|
package/dist/routes/terminals.js
CHANGED
|
@@ -4,7 +4,7 @@ import { spawn } from 'node-pty';
|
|
|
4
4
|
import { TERMINAL_BUFFER_LIMIT } from '../config.js';
|
|
5
5
|
import { createHttpError, handleError, readJson } from '../utils/error.js';
|
|
6
6
|
import { getDefaultShell } from '../utils/shell.js';
|
|
7
|
-
import { saveTerminal, deleteTerminal as deleteTerminalFromDb
|
|
7
|
+
import { saveTerminal, deleteTerminal as deleteTerminalFromDb } from '../utils/database.js';
|
|
8
8
|
// Track terminal index per deck for unique naming
|
|
9
9
|
const deckTerminalCounters = new Map();
|
|
10
10
|
export function createTerminalRouter(db, decks, terminals) {
|
|
@@ -143,106 +143,6 @@ export function createTerminalRouter(db, decks, terminals) {
|
|
|
143
143
|
terminals.set(id, session);
|
|
144
144
|
return session;
|
|
145
145
|
}
|
|
146
|
-
// Restore persisted terminals from database (re-spawn PTY processes)
|
|
147
|
-
function restoreTerminals() {
|
|
148
|
-
const saved = loadTerminals(db);
|
|
149
|
-
for (const row of saved) {
|
|
150
|
-
if (!decks.has(row.deckId)) {
|
|
151
|
-
// Deck no longer exists, clean up
|
|
152
|
-
deleteTerminalFromDb(db, row.id);
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
const deck = decks.get(row.deckId);
|
|
156
|
-
let shell;
|
|
157
|
-
let shellArgs = [];
|
|
158
|
-
if (row.command) {
|
|
159
|
-
shell = getDefaultShell();
|
|
160
|
-
if (process.platform === 'win32') {
|
|
161
|
-
if (shell.toLowerCase().includes('powershell')) {
|
|
162
|
-
shellArgs = ['-NoExit', '-Command', row.command];
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
shellArgs = ['/K', row.command];
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
shellArgs = ['-c', row.command];
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
shell = getDefaultShell();
|
|
174
|
-
}
|
|
175
|
-
const env = {};
|
|
176
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
177
|
-
if (value !== undefined)
|
|
178
|
-
env[key] = value;
|
|
179
|
-
}
|
|
180
|
-
env.TERM = env.TERM || 'xterm-256color';
|
|
181
|
-
env.COLORTERM = 'truecolor';
|
|
182
|
-
env.TERM_PROGRAM = 'xterm.js';
|
|
183
|
-
env.TERM_PROGRAM_VERSION = '5.0.0';
|
|
184
|
-
env.LANG = env.LANG || 'en_US.UTF-8';
|
|
185
|
-
env.LC_ALL = env.LC_ALL || 'en_US.UTF-8';
|
|
186
|
-
env.LC_CTYPE = env.LC_CTYPE || 'en_US.UTF-8';
|
|
187
|
-
try {
|
|
188
|
-
const isWindows = process.platform === 'win32';
|
|
189
|
-
const term = spawn(shell, shellArgs, {
|
|
190
|
-
cwd: deck.root,
|
|
191
|
-
cols: 120,
|
|
192
|
-
rows: 32,
|
|
193
|
-
env,
|
|
194
|
-
encoding: 'utf8',
|
|
195
|
-
...(isWindows ? { useConpty: true } : {}),
|
|
196
|
-
});
|
|
197
|
-
const session = {
|
|
198
|
-
id: row.id,
|
|
199
|
-
deckId: row.deckId,
|
|
200
|
-
title: row.title,
|
|
201
|
-
command: row.command,
|
|
202
|
-
createdAt: row.createdAt,
|
|
203
|
-
sockets: new Set(),
|
|
204
|
-
buffer: row.buffer,
|
|
205
|
-
lastActive: Date.now(),
|
|
206
|
-
write: (data) => { try {
|
|
207
|
-
term.write(data);
|
|
208
|
-
}
|
|
209
|
-
catch { /* terminal may be dying */ } },
|
|
210
|
-
resize: (cols, rows) => { try {
|
|
211
|
-
term.resize(cols, rows);
|
|
212
|
-
}
|
|
213
|
-
catch { /* terminal may be dying */ } },
|
|
214
|
-
kill: () => { try {
|
|
215
|
-
term.kill();
|
|
216
|
-
}
|
|
217
|
-
catch { /* already dead */ } },
|
|
218
|
-
};
|
|
219
|
-
term.onData((data) => {
|
|
220
|
-
appendToTerminalBuffer(session, data);
|
|
221
|
-
session.lastActive = Date.now();
|
|
222
|
-
broadcastToSockets(session, data);
|
|
223
|
-
});
|
|
224
|
-
term.onExit(() => {
|
|
225
|
-
handleTerminalExit(row.id);
|
|
226
|
-
});
|
|
227
|
-
terminals.set(row.id, session);
|
|
228
|
-
console.log(`[TERMINAL] Restored terminal ${row.id}: shell=${shell}, cwd=${deck.root}, pid=${term.pid}`);
|
|
229
|
-
}
|
|
230
|
-
catch (error) {
|
|
231
|
-
console.error(`[TERMINAL] Failed to restore terminal ${row.id}:`, error);
|
|
232
|
-
deleteTerminalFromDb(db, row.id);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
restoreTerminals();
|
|
237
|
-
// Periodically save terminal buffers to database
|
|
238
|
-
const bufferSaveInterval = setInterval(() => {
|
|
239
|
-
terminals.forEach((session) => {
|
|
240
|
-
try {
|
|
241
|
-
updateTerminalBuffer(db, session.id, session.buffer);
|
|
242
|
-
}
|
|
243
|
-
catch { /* ignore */ }
|
|
244
|
-
});
|
|
245
|
-
}, 10_000); // Every 10 seconds
|
|
246
146
|
router.get('/', (c) => {
|
|
247
147
|
const deckId = c.req.query('deckId');
|
|
248
148
|
if (!deckId) {
|
package/dist/server.js
CHANGED
|
@@ -11,7 +11,7 @@ import { PORT, HOST, NODE_ENV, BASIC_AUTH_USER, BASIC_AUTH_PASSWORD, CORS_ORIGIN
|
|
|
11
11
|
import { securityHeaders } from './middleware/security.js';
|
|
12
12
|
import { corsMiddleware } from './middleware/cors.js';
|
|
13
13
|
import { basicAuthMiddleware, generateWsToken, isBasicAuthEnabled } from './middleware/auth.js';
|
|
14
|
-
import { checkDatabaseIntegrity, handleDatabaseCorruption, initializeDatabase, loadPersistedState,
|
|
14
|
+
import { checkDatabaseIntegrity, handleDatabaseCorruption, initializeDatabase, loadPersistedState, clearOrphanedTerminals, } from './utils/database.js';
|
|
15
15
|
import { createWorkspaceRouter, getConfigHandler } from './routes/workspaces.js';
|
|
16
16
|
import { createDeckRouter } from './routes/decks.js';
|
|
17
17
|
import { createFileRouter } from './routes/files.js';
|
|
@@ -42,6 +42,7 @@ export async function createServer() {
|
|
|
42
42
|
}
|
|
43
43
|
const db = new DatabaseSync(dbPath);
|
|
44
44
|
initializeDatabase(db);
|
|
45
|
+
clearOrphanedTerminals(db);
|
|
45
46
|
// Initialize state
|
|
46
47
|
const workspaces = new Map();
|
|
47
48
|
const workspacePathIndex = new Map();
|
|
@@ -118,12 +119,8 @@ export async function createServer() {
|
|
|
118
119
|
if (shutdownPromise)
|
|
119
120
|
return shutdownPromise;
|
|
120
121
|
shutdownPromise = (async () => {
|
|
121
|
-
//
|
|
122
|
+
// Kill all terminals
|
|
122
123
|
terminals.forEach((session) => {
|
|
123
|
-
try {
|
|
124
|
-
updateTerminalBuffer(db, session.id, session.buffer);
|
|
125
|
-
}
|
|
126
|
-
catch { /* ignore */ }
|
|
127
124
|
session.sockets.forEach((socket) => {
|
|
128
125
|
try {
|
|
129
126
|
socket.close(1000, 'Server shutting down');
|
package/dist/utils/database.js
CHANGED
|
@@ -67,6 +67,12 @@ export function initializeDatabase(db) {
|
|
|
67
67
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_decks_workspace_id ON decks(workspace_id);`);
|
|
68
68
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_terminals_deck_id ON terminals(deck_id);`);
|
|
69
69
|
}
|
|
70
|
+
export function clearOrphanedTerminals(db) {
|
|
71
|
+
try {
|
|
72
|
+
db.exec('DELETE FROM terminals');
|
|
73
|
+
}
|
|
74
|
+
catch { /* table may not exist yet */ }
|
|
75
|
+
}
|
|
70
76
|
export function loadPersistedState(db, workspaces, workspacePathIndex, decks) {
|
|
71
77
|
const workspaceRows = db
|
|
72
78
|
.prepare('SELECT id, name, path, created_at FROM workspaces ORDER BY created_at ASC')
|
package/dist/websocket.js
CHANGED
|
@@ -88,9 +88,12 @@ export function setupWebSocketServer(server, terminals) {
|
|
|
88
88
|
const wss = new WebSocketServer({ server });
|
|
89
89
|
const WS_ALLOWED_ORIGINS = new Set([
|
|
90
90
|
`http://localhost:${PORT}`,
|
|
91
|
-
'http://localhost:5173',
|
|
92
|
-
'http://localhost:3000',
|
|
93
91
|
]);
|
|
92
|
+
// Add dev origins only in development mode
|
|
93
|
+
if (NODE_ENV !== 'production') {
|
|
94
|
+
WS_ALLOWED_ORIGINS.add('http://localhost:5173');
|
|
95
|
+
WS_ALLOWED_ORIGINS.add('http://localhost:3000');
|
|
96
|
+
}
|
|
94
97
|
// Allow configured CORS origin for WebSocket too
|
|
95
98
|
if (CORS_ORIGIN && CORS_ORIGIN !== '*') {
|
|
96
99
|
WS_ALLOWED_ORIGINS.add(CORS_ORIGIN);
|