nstantpage-agent 0.5.3 → 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 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.3');
28
+ .version('0.5.4');
29
29
  program
30
30
  .command('login')
31
31
  .description('Authenticate with nstantpage.com')
@@ -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.3';
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 this is a new download or node_modules is missing
232
- const hasNodeModules = fs.existsSync(path.join(projectDir, 'node_modules'));
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
- const installer = new PackageInstaller({ projectDir });
237
- const result = await installer.install([], false);
238
- if (result.success) {
236
+ try {
237
+ await installer.ensureDependencies();
239
238
  console.log(chalk.green(` ✓ Dependencies installed`));
240
239
  }
241
- else {
242
- console.log(chalk.yellow(` ⚠ Dependency installation had issues: ${result.output.slice(0, 200)}`));
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
- console.log(chalk.gray(' Starting dev server...'));
380
- try {
381
- const devServer = localServer.getDevServer();
382
- await devServer.start();
383
- console.log(chalk.green(` ✓ Dev server on port ${devPort}`));
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
- catch (err) {
386
- console.log(chalk.yellow(` ⚠ Dev server failed to start: ${err.message}`));
387
- console.log(chalk.gray(' You can start it later from the editor'));
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 hasNodeModules = fs.existsSync(path.join(projectDir, 'node_modules'));
562
- if (!hasNodeModules && fs.existsSync(path.join(projectDir, 'package.json'))) {
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
- const installer = new PackageInstaller({ projectDir });
565
- const result = await installer.install([], false);
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
- try {
579
- await localServer.getDevServer().start();
580
- console.log(chalk.green(` ✓ Dev server on port ${allocated.devPort}`));
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
- catch (err) {
583
- console.log(chalk.yellow(` ⚠ Dev server: ${err.message}`));
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
@@ -559,7 +559,7 @@ export class LocalServer {
559
559
  connected: true,
560
560
  projectId: this.options.projectId,
561
561
  agent: {
562
- version: '0.5.3',
562
+ version: '0.5.4',
563
563
  hostname: os.hostname(),
564
564
  platform: `${os.platform()} ${os.arch()}`,
565
565
  },
@@ -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
  */
@@ -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 ? 120_000 : 60_000;
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
- const nodeModules = path.join(this.projectDir, 'node_modules');
42
- if (existsSync(nodeModules))
44
+ // Serialize with any in-flight install
45
+ await this.installLock;
46
+ if (this.areDependenciesInstalled())
43
47
  return;
44
- console.log(` [Installer] Installing project dependencies...`);
45
- const pm = this.detectPackageManager();
46
- const args = pm === 'pnpm' ? ['install'] : ['install'];
47
- await this.runCommand(pm, args, 120_000);
48
- console.log(` [Installer] Dependencies installed`);
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
@@ -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.3',
66
+ version: '0.5.4',
67
67
  hostname: os.hostname(),
68
68
  platform: `${os.platform()} ${os.arch()}`,
69
69
  deviceId: getDeviceId(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nstantpage-agent",
3
- "version": "0.5.3",
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": {