nstantpage-agent 0.5.2 → 0.5.4
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/dist/cli.js +1 -1
- package/dist/commands/start.js +45 -29
- package/dist/localServer.d.ts +10 -1
- package/dist/localServer.js +102 -36
- package/dist/packageInstaller.d.ts +8 -0
- package/dist/packageInstaller.js +46 -9
- package/dist/tunnel.js +4 -7
- package/package.json +4 -1
package/dist/cli.js
CHANGED
|
@@ -25,7 +25,7 @@ const program = new Command();
|
|
|
25
25
|
program
|
|
26
26
|
.name('nstantpage')
|
|
27
27
|
.description('Local development agent for nstantpage.com — run projects on your machine, preview in the cloud')
|
|
28
|
-
.version('0.5.
|
|
28
|
+
.version('0.5.4');
|
|
29
29
|
program
|
|
30
30
|
.command('login')
|
|
31
31
|
.description('Authenticate with nstantpage.com')
|
package/dist/commands/start.js
CHANGED
|
@@ -24,7 +24,7 @@ import { getConfig, getProjectConfig, setProjectConfig, clearProjectConfig, getD
|
|
|
24
24
|
import { TunnelClient } from '../tunnel.js';
|
|
25
25
|
import { LocalServer } from '../localServer.js';
|
|
26
26
|
import { PackageInstaller } from '../packageInstaller.js';
|
|
27
|
-
const VERSION = '0.5.
|
|
27
|
+
const VERSION = '0.5.4';
|
|
28
28
|
/**
|
|
29
29
|
* Resolve the backend API base URL.
|
|
30
30
|
* - If --backend is passed, use it
|
|
@@ -226,20 +226,20 @@ export async function startCommand(directory, options) {
|
|
|
226
226
|
console.log(chalk.gray(` Gateway: ${options.gateway}`));
|
|
227
227
|
console.log(chalk.gray(` Backend: ${backendUrl}\n`));
|
|
228
228
|
// 1. Fetch project files from the backend
|
|
229
|
+
const installer = new PackageInstaller({ projectDir });
|
|
229
230
|
try {
|
|
230
231
|
const { fileCount, isNew } = await fetchProjectFiles(backendUrl, projectId, projectDir, token);
|
|
231
|
-
// 2. Install dependencies if
|
|
232
|
-
|
|
233
|
-
if (isNew || !hasNodeModules) {
|
|
232
|
+
// 2. Install dependencies if needed (verifies actual packages, not just folder)
|
|
233
|
+
if (!installer.areDependenciesInstalled()) {
|
|
234
234
|
if (fs.existsSync(path.join(projectDir, 'package.json'))) {
|
|
235
235
|
console.log(chalk.gray(' Installing dependencies...'));
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (result.success) {
|
|
236
|
+
try {
|
|
237
|
+
await installer.ensureDependencies();
|
|
239
238
|
console.log(chalk.green(` ✓ Dependencies installed`));
|
|
240
239
|
}
|
|
241
|
-
|
|
242
|
-
console.log(chalk.yellow(` ⚠
|
|
240
|
+
catch (err) {
|
|
241
|
+
console.log(chalk.yellow(` ⚠ Install failed: ${err.message?.slice(0, 200)}`));
|
|
242
|
+
console.log(chalk.gray(` Will retry when dev server starts`));
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
245
|
}
|
|
@@ -376,15 +376,21 @@ export async function startCommand(directory, options) {
|
|
|
376
376
|
console.log(chalk.green(` ✓ API server on port ${apiPort}`));
|
|
377
377
|
// Start dev server unless --no-dev flag
|
|
378
378
|
if (!options.noDev) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
379
|
+
if (installer.areDependenciesInstalled()) {
|
|
380
|
+
console.log(chalk.gray(' Starting dev server...'));
|
|
381
|
+
try {
|
|
382
|
+
const devServer = localServer.getDevServer();
|
|
383
|
+
await devServer.start();
|
|
384
|
+
console.log(chalk.green(` ✓ Dev server on port ${devPort}`));
|
|
385
|
+
}
|
|
386
|
+
catch (err) {
|
|
387
|
+
console.log(chalk.yellow(` ⚠ Dev server failed to start: ${err.message}`));
|
|
388
|
+
console.log(chalk.gray(' You can start it later from the editor'));
|
|
389
|
+
}
|
|
384
390
|
}
|
|
385
|
-
|
|
386
|
-
console.log(chalk.yellow(` ⚠
|
|
387
|
-
console.log(chalk.gray('
|
|
391
|
+
else {
|
|
392
|
+
console.log(chalk.yellow(` ⚠ Skipping dev server — dependencies not fully installed`));
|
|
393
|
+
console.log(chalk.gray(' Dev server will start when browser opens the project'));
|
|
388
394
|
}
|
|
389
395
|
}
|
|
390
396
|
else {
|
|
@@ -557,14 +563,18 @@ async function startAdditionalProject(projectId, opts) {
|
|
|
557
563
|
catch (err) {
|
|
558
564
|
console.log(chalk.yellow(` ⚠ Could not fetch files: ${err.message}`));
|
|
559
565
|
}
|
|
560
|
-
// Install dependencies
|
|
561
|
-
const
|
|
562
|
-
if (!
|
|
566
|
+
// Install dependencies (must complete before dev server can start)
|
|
567
|
+
const installer = new PackageInstaller({ projectDir });
|
|
568
|
+
if (!installer.areDependenciesInstalled() && fs.existsSync(path.join(projectDir, 'package.json'))) {
|
|
563
569
|
console.log(chalk.gray(` Installing dependencies...`));
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (result.success)
|
|
570
|
+
try {
|
|
571
|
+
await installer.ensureDependencies();
|
|
567
572
|
console.log(chalk.green(` ✓ Dependencies installed`));
|
|
573
|
+
}
|
|
574
|
+
catch (err) {
|
|
575
|
+
console.log(chalk.red(` ✗ Install failed: ${err.message}`));
|
|
576
|
+
console.log(chalk.yellow(` ⚠ Dev server may fail — retrying install on first request`));
|
|
577
|
+
}
|
|
568
578
|
}
|
|
569
579
|
// Start local server
|
|
570
580
|
const localServer = new LocalServer({
|
|
@@ -573,14 +583,20 @@ async function startAdditionalProject(projectId, opts) {
|
|
|
573
583
|
});
|
|
574
584
|
await localServer.start();
|
|
575
585
|
console.log(chalk.green(` ✓ API server on port ${allocated.apiPort}`));
|
|
576
|
-
// Start dev server
|
|
586
|
+
// Start dev server (only if dependencies are installed)
|
|
577
587
|
if (!opts.noDev) {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
588
|
+
if (installer.areDependenciesInstalled()) {
|
|
589
|
+
try {
|
|
590
|
+
await localServer.getDevServer().start();
|
|
591
|
+
console.log(chalk.green(` ✓ Dev server on port ${allocated.devPort}`));
|
|
592
|
+
}
|
|
593
|
+
catch (err) {
|
|
594
|
+
console.log(chalk.yellow(` ⚠ Dev server: ${err.message}`));
|
|
595
|
+
}
|
|
581
596
|
}
|
|
582
|
-
|
|
583
|
-
console.log(chalk.yellow(` ⚠
|
|
597
|
+
else {
|
|
598
|
+
console.log(chalk.yellow(` ⚠ Skipping dev server — dependencies not installed`));
|
|
599
|
+
console.log(chalk.gray(` Dev server will start when browser opens the project`));
|
|
584
600
|
}
|
|
585
601
|
}
|
|
586
602
|
// Connect project tunnel
|
package/dist/localServer.d.ts
CHANGED
|
@@ -17,7 +17,8 @@ import { DevServer } from './devServer.js';
|
|
|
17
17
|
interface TerminalSession {
|
|
18
18
|
id: string;
|
|
19
19
|
projectId: string;
|
|
20
|
-
shell: ChildProcess;
|
|
20
|
+
shell: ChildProcess | null;
|
|
21
|
+
ptyProcess: any | null;
|
|
21
22
|
outputBuffer: string[];
|
|
22
23
|
createdAt: number;
|
|
23
24
|
lastActivity: number;
|
|
@@ -35,6 +36,14 @@ interface TerminalSession {
|
|
|
35
36
|
* Get a terminal session by ID (used by WS relay in tunnel).
|
|
36
37
|
*/
|
|
37
38
|
export declare function getTerminalSession(sessionId: string): TerminalSession | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Write data to a terminal session (handles both node-pty and child_process).
|
|
41
|
+
*/
|
|
42
|
+
export declare function writeToTerminalSession(session: TerminalSession, data: string): void;
|
|
43
|
+
/**
|
|
44
|
+
* Resize a terminal session (only works with node-pty).
|
|
45
|
+
*/
|
|
46
|
+
export declare function resizeTerminalSession(session: TerminalSession, cols: number, rows: number): void;
|
|
38
47
|
/**
|
|
39
48
|
* Attach a real-time listener to a terminal session (for WebSocket relay).
|
|
40
49
|
* Returns a cleanup function to detach.
|
package/dist/localServer.js
CHANGED
|
@@ -14,12 +14,22 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import http from 'http';
|
|
16
16
|
import os from 'os';
|
|
17
|
+
import { createRequire } from 'module';
|
|
17
18
|
import { spawn } from 'child_process';
|
|
18
19
|
import { DevServer } from './devServer.js';
|
|
19
20
|
import { FileManager } from './fileManager.js';
|
|
20
21
|
import { Checker } from './checker.js';
|
|
21
22
|
import { ErrorStore, structuredErrorToString } from './errorStore.js';
|
|
22
23
|
import { PackageInstaller } from './packageInstaller.js';
|
|
24
|
+
// ─── Try to load node-pty for real PTY support ─────────────────
|
|
25
|
+
let ptyModule = null;
|
|
26
|
+
try {
|
|
27
|
+
const _require = createRequire(import.meta.url);
|
|
28
|
+
ptyModule = _require('node-pty');
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// node-pty not available — will fall back to 'script' PTY wrapper
|
|
32
|
+
}
|
|
23
33
|
const terminalSessions = new Map();
|
|
24
34
|
let sessionCounter = 0;
|
|
25
35
|
function generateSessionId() {
|
|
@@ -31,6 +41,30 @@ function generateSessionId() {
|
|
|
31
41
|
export function getTerminalSession(sessionId) {
|
|
32
42
|
return terminalSessions.get(sessionId);
|
|
33
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Write data to a terminal session (handles both node-pty and child_process).
|
|
46
|
+
*/
|
|
47
|
+
export function writeToTerminalSession(session, data) {
|
|
48
|
+
if (session.ptyProcess) {
|
|
49
|
+
session.ptyProcess.write(data);
|
|
50
|
+
}
|
|
51
|
+
else if (session.shell) {
|
|
52
|
+
session.shell.stdin?.write(data);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Resize a terminal session (only works with node-pty).
|
|
57
|
+
*/
|
|
58
|
+
export function resizeTerminalSession(session, cols, rows) {
|
|
59
|
+
session.cols = cols;
|
|
60
|
+
session.rows = rows;
|
|
61
|
+
if (session.ptyProcess) {
|
|
62
|
+
try {
|
|
63
|
+
session.ptyProcess.resize(cols, rows);
|
|
64
|
+
}
|
|
65
|
+
catch { }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
34
68
|
/**
|
|
35
69
|
* Attach a real-time listener to a terminal session (for WebSocket relay).
|
|
36
70
|
* Returns a cleanup function to detach.
|
|
@@ -366,17 +400,50 @@ export class LocalServer {
|
|
|
366
400
|
const isAiSession = parsed.isAiSession || false;
|
|
367
401
|
// Determine shell
|
|
368
402
|
const shellCmd = process.platform === 'win32' ? 'cmd.exe' : (process.env.SHELL || '/bin/bash');
|
|
369
|
-
const
|
|
370
|
-
cwd: this.options.projectDir,
|
|
371
|
-
env: { ...process.env, TERM: 'xterm-256color', COLUMNS: String(cols), LINES: String(rows) },
|
|
372
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
373
|
-
});
|
|
403
|
+
const shellEnv = { ...process.env, TERM: 'xterm-256color', COLUMNS: String(cols), LINES: String(rows) };
|
|
374
404
|
sessionCounter++;
|
|
375
405
|
const label = parsed.label || `Terminal ${sessionCounter}`;
|
|
406
|
+
let shell = null;
|
|
407
|
+
let ptyProcess = null;
|
|
408
|
+
// Prefer node-pty for real PTY (interactive shell, echo, prompt, resize)
|
|
409
|
+
if (ptyModule) {
|
|
410
|
+
ptyProcess = ptyModule.spawn(shellCmd, [], {
|
|
411
|
+
name: 'xterm-256color',
|
|
412
|
+
cols,
|
|
413
|
+
rows,
|
|
414
|
+
cwd: this.options.projectDir,
|
|
415
|
+
env: shellEnv,
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
else if (process.platform === 'darwin') {
|
|
419
|
+
// macOS fallback: use 'script' to allocate a PTY
|
|
420
|
+
shell = spawn('script', ['-q', '/dev/null', shellCmd], {
|
|
421
|
+
cwd: this.options.projectDir,
|
|
422
|
+
env: shellEnv,
|
|
423
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
else if (process.platform === 'linux') {
|
|
427
|
+
// Linux fallback: 'script' with -c flag
|
|
428
|
+
shell = spawn('script', ['-qc', shellCmd, '/dev/null'], {
|
|
429
|
+
cwd: this.options.projectDir,
|
|
430
|
+
env: shellEnv,
|
|
431
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
// Windows / other: raw spawn (limited interactivity)
|
|
436
|
+
shell = spawn(shellCmd, [], {
|
|
437
|
+
cwd: this.options.projectDir,
|
|
438
|
+
env: shellEnv,
|
|
439
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
440
|
+
});
|
|
441
|
+
}
|
|
376
442
|
const session = {
|
|
377
443
|
id: sessionId,
|
|
378
444
|
projectId: this.options.projectId,
|
|
379
445
|
shell,
|
|
446
|
+
ptyProcess,
|
|
380
447
|
outputBuffer: [],
|
|
381
448
|
createdAt: Date.now(),
|
|
382
449
|
lastActivity: Date.now(),
|
|
@@ -389,39 +456,22 @@ export class LocalServer {
|
|
|
389
456
|
dataListeners: new Set(),
|
|
390
457
|
exitListeners: new Set(),
|
|
391
458
|
};
|
|
392
|
-
|
|
393
|
-
|
|
459
|
+
// Helper to push output to buffer and notify listeners
|
|
460
|
+
const pushOutput = (str) => {
|
|
394
461
|
session.outputBuffer.push(str);
|
|
395
462
|
session.lastActivity = Date.now();
|
|
396
|
-
// Keep buffer capped at ~100KB
|
|
397
463
|
while (session.outputBuffer.length > 500)
|
|
398
464
|
session.outputBuffer.shift();
|
|
399
|
-
// Notify real-time listeners (WebSocket relay)
|
|
400
465
|
for (const listener of session.dataListeners) {
|
|
401
466
|
try {
|
|
402
467
|
listener(str);
|
|
403
468
|
}
|
|
404
469
|
catch { }
|
|
405
470
|
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
const str = data.toString('utf-8');
|
|
409
|
-
session.outputBuffer.push(str);
|
|
410
|
-
session.lastActivity = Date.now();
|
|
411
|
-
while (session.outputBuffer.length > 500)
|
|
412
|
-
session.outputBuffer.shift();
|
|
413
|
-
// Notify real-time listeners
|
|
414
|
-
for (const listener of session.dataListeners) {
|
|
415
|
-
try {
|
|
416
|
-
listener(str);
|
|
417
|
-
}
|
|
418
|
-
catch { }
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
shell.on('exit', (code) => {
|
|
471
|
+
};
|
|
472
|
+
const handleExit = (code) => {
|
|
422
473
|
session.exited = true;
|
|
423
474
|
session.exitCode = code;
|
|
424
|
-
// Notify exit listeners
|
|
425
475
|
for (const listener of session.exitListeners) {
|
|
426
476
|
try {
|
|
427
477
|
listener(code);
|
|
@@ -429,7 +479,17 @@ export class LocalServer {
|
|
|
429
479
|
catch { }
|
|
430
480
|
}
|
|
431
481
|
terminalSessions.delete(sessionId);
|
|
432
|
-
}
|
|
482
|
+
};
|
|
483
|
+
if (ptyProcess) {
|
|
484
|
+
// node-pty: single onData for combined stdout+stderr
|
|
485
|
+
ptyProcess.onData((data) => pushOutput(data));
|
|
486
|
+
ptyProcess.onExit(({ exitCode }) => handleExit(exitCode));
|
|
487
|
+
}
|
|
488
|
+
else if (shell) {
|
|
489
|
+
shell.stdout?.on('data', (data) => pushOutput(data.toString('utf-8')));
|
|
490
|
+
shell.stderr?.on('data', (data) => pushOutput(data.toString('utf-8')));
|
|
491
|
+
shell.on('exit', (code) => handleExit(code));
|
|
492
|
+
}
|
|
433
493
|
terminalSessions.set(sessionId, session);
|
|
434
494
|
// Return format matching frontend TerminalSessionInfo
|
|
435
495
|
this.json(res, {
|
|
@@ -467,7 +527,7 @@ export class LocalServer {
|
|
|
467
527
|
this.json(res, { success: false, error: 'Session not found' });
|
|
468
528
|
return;
|
|
469
529
|
}
|
|
470
|
-
session
|
|
530
|
+
writeToTerminalSession(session, inputData);
|
|
471
531
|
session.lastActivity = Date.now();
|
|
472
532
|
this.json(res, { success: true });
|
|
473
533
|
}
|
|
@@ -490,10 +550,7 @@ export class LocalServer {
|
|
|
490
550
|
this.json(res, { success: false, error: 'Session not found' });
|
|
491
551
|
return;
|
|
492
552
|
}
|
|
493
|
-
session
|
|
494
|
-
session.rows = rows || session.rows;
|
|
495
|
-
// For real pty we'd call pty.resize(cols, rows), but for child_process
|
|
496
|
-
// we just update the env (takes effect on next data)
|
|
553
|
+
resizeTerminalSession(session, cols || session.cols, rows || session.rows);
|
|
497
554
|
this.json(res, { success: true, cols: session.cols, rows: session.rows });
|
|
498
555
|
}
|
|
499
556
|
// ─── /live/agent-status ──────────────────────────────────────
|
|
@@ -502,7 +559,7 @@ export class LocalServer {
|
|
|
502
559
|
connected: true,
|
|
503
560
|
projectId: this.options.projectId,
|
|
504
561
|
agent: {
|
|
505
|
-
version: '0.5.
|
|
562
|
+
version: '0.5.4',
|
|
506
563
|
hostname: os.hostname(),
|
|
507
564
|
platform: `${os.platform()} ${os.arch()}`,
|
|
508
565
|
},
|
|
@@ -625,9 +682,18 @@ export class LocalServer {
|
|
|
625
682
|
}
|
|
626
683
|
// ─── /live/close ─────────────────────────────────────────────
|
|
627
684
|
async handleClose(_req, res) {
|
|
628
|
-
//
|
|
629
|
-
//
|
|
630
|
-
|
|
685
|
+
// Stop the dev server when user explicitly clicks Stop in the UI.
|
|
686
|
+
// The frontend only calls /live/close from the Stop button, not on navigation.
|
|
687
|
+
try {
|
|
688
|
+
if (this.devServer.isRunning) {
|
|
689
|
+
await this.devServer.stop();
|
|
690
|
+
}
|
|
691
|
+
this.json(res, { success: true, message: 'Dev server stopped' });
|
|
692
|
+
}
|
|
693
|
+
catch (err) {
|
|
694
|
+
res.statusCode = 500;
|
|
695
|
+
this.json(res, { success: false, error: err.message });
|
|
696
|
+
}
|
|
631
697
|
}
|
|
632
698
|
// ─── /live/heartbeat ────────────────────────────────────────
|
|
633
699
|
async handleHeartbeat(_req, res) {
|
|
@@ -7,6 +7,8 @@ export interface PackageInstallerOptions {
|
|
|
7
7
|
}
|
|
8
8
|
export declare class PackageInstaller {
|
|
9
9
|
private projectDir;
|
|
10
|
+
/** Serializes concurrent install calls */
|
|
11
|
+
private installLock;
|
|
10
12
|
constructor(options: PackageInstallerOptions);
|
|
11
13
|
/**
|
|
12
14
|
* Install packages into the project.
|
|
@@ -18,8 +20,14 @@ export declare class PackageInstaller {
|
|
|
18
20
|
}>;
|
|
19
21
|
/**
|
|
20
22
|
* Ensure all project dependencies are installed.
|
|
23
|
+
* Uses a lock to prevent concurrent installs.
|
|
21
24
|
*/
|
|
22
25
|
ensureDependencies(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Check if dependencies are actually installed (not just that node_modules/ exists).
|
|
28
|
+
* Verifies that at least one key dependency from package.json is present.
|
|
29
|
+
*/
|
|
30
|
+
areDependenciesInstalled(): boolean;
|
|
23
31
|
/**
|
|
24
32
|
* Detect which package manager is used (pnpm, yarn, npm).
|
|
25
33
|
*/
|
package/dist/packageInstaller.js
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { spawn } from 'child_process';
|
|
6
6
|
import path from 'path';
|
|
7
|
-
import { existsSync } from 'fs';
|
|
7
|
+
import fs, { existsSync } from 'fs';
|
|
8
8
|
export class PackageInstaller {
|
|
9
9
|
projectDir;
|
|
10
|
+
/** Serializes concurrent install calls */
|
|
11
|
+
installLock = Promise.resolve();
|
|
10
12
|
constructor(options) {
|
|
11
13
|
this.projectDir = options.projectDir;
|
|
12
14
|
}
|
|
@@ -18,7 +20,7 @@ export class PackageInstaller {
|
|
|
18
20
|
const args = this.buildInstallArgs(pm, packages, dev);
|
|
19
21
|
console.log(` [Installer] ${pm} ${args.join(' ')}`);
|
|
20
22
|
try {
|
|
21
|
-
const timeout = packages.length === 0 ?
|
|
23
|
+
const timeout = packages.length === 0 ? 300_000 : 120_000;
|
|
22
24
|
const output = await this.runCommand(pm, args, timeout);
|
|
23
25
|
return {
|
|
24
26
|
success: true,
|
|
@@ -36,16 +38,51 @@ export class PackageInstaller {
|
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
40
|
* Ensure all project dependencies are installed.
|
|
41
|
+
* Uses a lock to prevent concurrent installs.
|
|
39
42
|
*/
|
|
40
43
|
async ensureDependencies() {
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
// Serialize with any in-flight install
|
|
45
|
+
await this.installLock;
|
|
46
|
+
if (this.areDependenciesInstalled())
|
|
43
47
|
return;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
// Acquire lock for this install
|
|
49
|
+
let releaseLock;
|
|
50
|
+
this.installLock = new Promise(resolve => { releaseLock = resolve; });
|
|
51
|
+
try {
|
|
52
|
+
console.log(` [Installer] Installing project dependencies...`);
|
|
53
|
+
const pm = this.detectPackageManager();
|
|
54
|
+
const args = pm === 'pnpm' ? ['install'] : ['install'];
|
|
55
|
+
await this.runCommand(pm, args, 300_000);
|
|
56
|
+
console.log(` [Installer] Dependencies installed`);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
releaseLock();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if dependencies are actually installed (not just that node_modules/ exists).
|
|
64
|
+
* Verifies that at least one key dependency from package.json is present.
|
|
65
|
+
*/
|
|
66
|
+
areDependenciesInstalled() {
|
|
67
|
+
const nodeModules = path.join(this.projectDir, 'node_modules');
|
|
68
|
+
if (!existsSync(nodeModules))
|
|
69
|
+
return false;
|
|
70
|
+
// Check that at least one real dependency is installed
|
|
71
|
+
try {
|
|
72
|
+
const pkgPath = path.join(this.projectDir, 'package.json');
|
|
73
|
+
if (!existsSync(pkgPath))
|
|
74
|
+
return true; // no package.json → nothing to install
|
|
75
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
76
|
+
const deps = Object.keys(pkg.dependencies || {});
|
|
77
|
+
if (deps.length === 0)
|
|
78
|
+
return true; // no deps → nothing needed
|
|
79
|
+
// Check if the first listed dependency has its folder in node_modules
|
|
80
|
+
const firstDep = deps[0];
|
|
81
|
+
return existsSync(path.join(nodeModules, firstDep));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return existsSync(nodeModules);
|
|
85
|
+
}
|
|
49
86
|
}
|
|
50
87
|
/**
|
|
51
88
|
* Detect which package manager is used (pnpm, yarn, npm).
|
package/dist/tunnel.js
CHANGED
|
@@ -20,7 +20,7 @@ import http from 'http';
|
|
|
20
20
|
import os from 'os';
|
|
21
21
|
import chalk from 'chalk';
|
|
22
22
|
import { getDeviceId } from './config.js';
|
|
23
|
-
import { getTerminalSession, attachTerminalClient } from './localServer.js';
|
|
23
|
+
import { getTerminalSession, attachTerminalClient, writeToTerminalSession, resizeTerminalSession } from './localServer.js';
|
|
24
24
|
export class TunnelClient {
|
|
25
25
|
ws = null;
|
|
26
26
|
options;
|
|
@@ -63,7 +63,7 @@ export class TunnelClient {
|
|
|
63
63
|
// Send enhanced agent info with capabilities and deviceId
|
|
64
64
|
this.send({
|
|
65
65
|
type: 'agent-info',
|
|
66
|
-
version: '0.5.
|
|
66
|
+
version: '0.5.4',
|
|
67
67
|
hostname: os.hostname(),
|
|
68
68
|
platform: `${os.platform()} ${os.arch()}`,
|
|
69
69
|
deviceId: getDeviceId(),
|
|
@@ -215,16 +215,13 @@ export class TunnelClient {
|
|
|
215
215
|
switch (msg.type) {
|
|
216
216
|
case 'input':
|
|
217
217
|
if (typeof msg.data === 'string') {
|
|
218
|
-
session
|
|
218
|
+
writeToTerminalSession(session, msg.data);
|
|
219
219
|
session.lastActivity = Date.now();
|
|
220
220
|
}
|
|
221
221
|
break;
|
|
222
222
|
case 'resize':
|
|
223
223
|
if (typeof msg.cols === 'number' && typeof msg.rows === 'number') {
|
|
224
|
-
session.cols
|
|
225
|
-
session.rows = msg.rows;
|
|
226
|
-
// Note: child_process.spawn doesn't support resize natively
|
|
227
|
-
// (only node-pty does). We update the stored size for future use.
|
|
224
|
+
resizeTerminalSession(session, msg.cols, msg.rows);
|
|
228
225
|
}
|
|
229
226
|
break;
|
|
230
227
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nstantpage-agent",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Local development agent for nstantpage.com — run your projects locally, preview in the cloud. Replaces cloud containers for faster builds.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -58,5 +58,8 @@
|
|
|
58
58
|
"@types/ws": "^8.5.10",
|
|
59
59
|
"tsx": "^4.19.0",
|
|
60
60
|
"typescript": "^5.5.0"
|
|
61
|
+
},
|
|
62
|
+
"optionalDependencies": {
|
|
63
|
+
"node-pty": "^1.1.0"
|
|
61
64
|
}
|
|
62
65
|
}
|