nstantpage-agent 0.8.12 → 0.8.14

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.
@@ -572,18 +572,21 @@ async function startStandbyMode(token, options, backendUrl, deviceId) {
572
572
  devPort: 0,
573
573
  onStartProject: async (pid, opts) => {
574
574
  const isClean = opts?.clean === true;
575
- // If clean reset requested and project is already running, stop it first
576
- if (isClean && activeProjects.has(pid)) {
577
- console.log(chalk.yellow(` Stopping existing project ${pid} for clean reset...`));
575
+ const isLocalRepo = !!opts?.localFolderPath;
576
+ // If clean reset or LocalRepo re-start requested and project is already running, stop it first
577
+ if ((isClean || isLocalRepo) && activeProjects.has(pid)) {
578
+ console.log(chalk.yellow(` Stopping existing project ${pid} for ${isClean ? 'clean reset' : 'LocalRepo restart'}...`));
578
579
  const existing = activeProjects.get(pid);
579
580
  existing.tunnel.disconnect();
580
581
  await existing.localServer.stop();
581
582
  activeProjects.delete(pid);
582
- // Wipe the project directory
583
- const projectDir = resolveProjectDir('.', pid);
584
- if (fs.existsSync(projectDir)) {
585
- console.log(chalk.gray(` Wiping project directory: ${projectDir}`));
586
- fs.rmSync(projectDir, { recursive: true, force: true });
583
+ // Wipe the project directory (only for clean non-LocalRepo)
584
+ if (isClean && !isLocalRepo) {
585
+ const projectDir = resolveProjectDir('.', pid);
586
+ if (fs.existsSync(projectDir)) {
587
+ console.log(chalk.gray(` Wiping project directory: ${projectDir}`));
588
+ fs.rmSync(projectDir, { recursive: true, force: true });
589
+ }
587
590
  }
588
591
  }
589
592
  else if (activeProjects.has(pid)) {
@@ -592,6 +595,7 @@ async function startStandbyMode(token, options, backendUrl, deviceId) {
592
595
  }
593
596
  const result = await startAdditionalProject(pid, {
594
597
  token, backendUrl, gatewayUrl: options.gateway, deviceId, noDev: options.noDev,
598
+ localFolderPath: opts?.localFolderPath,
595
599
  onProgress: (phase, message) => {
596
600
  standbyTunnel.sendSetupProgress(pid, phase, message);
597
601
  },
@@ -678,6 +682,7 @@ async function startStandbyMode(token, options, backendUrl, deviceId) {
678
682
  }
679
683
  const result = await startAdditionalProject(projectId, {
680
684
  token, backendUrl, gatewayUrl: options.gateway, deviceId, noDev: options.noDev,
685
+ localFolderPath: opts?.localFolderPath,
681
686
  onProgress: (phase, message) => {
682
687
  standbyTunnel.sendSetupProgress(projectId, phase, message);
683
688
  },
@@ -736,29 +741,37 @@ async function startAdditionalProject(projectId, opts) {
736
741
  const progress = opts.onProgress || (() => { });
737
742
  const timings = {};
738
743
  const t0 = Date.now();
739
- console.log(chalk.blue(`\n 📦 Starting project ${projectId}...`));
744
+ const isLocalRepo = !!opts.localFolderPath;
745
+ console.log(chalk.blue(`\n 📦 Starting project ${projectId}${isLocalRepo ? ' (LocalRepo)' : ''}...`));
740
746
  try {
741
747
  const allocated = allocatePortsForProject(projectId);
742
- const projectDir = resolveProjectDir('.', projectId);
748
+ const projectDir = isLocalRepo ? opts.localFolderPath : resolveProjectDir('.', projectId);
743
749
  if (!fs.existsSync(projectDir))
744
750
  fs.mkdirSync(projectDir, { recursive: true });
745
751
  // Ensure ports are clear for this project before starting
746
752
  cleanupPreviousAgent(projectId, allocated.apiPort, allocated.devPort);
747
753
  await new Promise(r => setTimeout(r, 50));
748
754
  // ── Phase 1: API server + file fetch + tunnel connect (all parallel) ──
749
- progress('fetching-files', 'Fetching project files...');
750
- // Provision database if PostgreSQL is available
755
+ if (!isLocalRepo) {
756
+ progress('fetching-files', 'Fetching project files...');
757
+ }
758
+ else {
759
+ progress('starting-server', 'Starting local server...');
760
+ }
761
+ // Provision database if PostgreSQL is available (skip for LocalRepo)
751
762
  let databaseUrl = null;
752
- try {
753
- const hasPg = await probeLocalPostgres();
754
- if (hasPg) {
755
- databaseUrl = await ensureLocalProjectDb(projectId);
756
- if (databaseUrl) {
757
- console.log(chalk.green(` ✓ Database ready: project_${projectId}`));
763
+ if (!isLocalRepo) {
764
+ try {
765
+ const hasPg = await probeLocalPostgres();
766
+ if (hasPg) {
767
+ databaseUrl = await ensureLocalProjectDb(projectId);
768
+ if (databaseUrl) {
769
+ console.log(chalk.green(` ✓ Database ready: project_${projectId}`));
770
+ }
758
771
  }
759
772
  }
773
+ catch { }
760
774
  }
761
- catch { }
762
775
  const serverEnv = {};
763
776
  if (databaseUrl)
764
777
  serverEnv['DATABASE_URL'] = databaseUrl;
@@ -799,17 +812,20 @@ async function startAdditionalProject(projectId, opts) {
799
812
  tunnel.startBackgroundReconnect();
800
813
  }
801
814
  })();
815
+ // Fetch project files from backend (skip for LocalRepo — files are on disk)
802
816
  const fileStart = Date.now();
803
817
  let fetchedVersionId;
804
- try {
805
- const { versionId } = await fetchProjectFiles(opts.backendUrl, projectId, projectDir, opts.token);
806
- fetchedVersionId = versionId;
807
- timings['fetch-files'] = Date.now() - fileStart;
808
- progress('fetching-files', `Files downloaded (${timings['fetch-files']}ms)`);
809
- }
810
- catch (err) {
811
- console.log(chalk.yellow(` ⚠ Could not fetch files: ${err.message}`));
812
- progress('fetching-files', `Warning: ${err.message}`);
818
+ if (!isLocalRepo) {
819
+ try {
820
+ const { versionId } = await fetchProjectFiles(opts.backendUrl, projectId, projectDir, opts.token);
821
+ fetchedVersionId = versionId;
822
+ timings['fetch-files'] = Date.now() - fileStart;
823
+ progress('fetching-files', `Files downloaded (${timings['fetch-files']}ms)`);
824
+ }
825
+ catch (err) {
826
+ console.log(chalk.yellow(` ⚠ Could not fetch files: ${err.message}`));
827
+ progress('fetching-files', `Warning: ${err.message}`);
828
+ }
813
829
  }
814
830
  // Write DATABASE_URL to .env AFTER file fetch (so it doesn't get overwritten)
815
831
  if (databaseUrl) {
@@ -822,49 +838,51 @@ async function startAdditionalProject(projectId, opts) {
822
838
  if (fetchedVersionId) {
823
839
  localServer.markSynced(fetchedVersionId);
824
840
  }
825
- // ── Phase 2: Install deps (if needed) ────────────────────────────
826
- const installer = new PackageInstaller({ projectDir });
827
- const needsInstall = !installer.areDependenciesInstalled() && fs.existsSync(path.join(projectDir, 'package.json'));
828
- if (needsInstall) {
829
- const installStart = Date.now();
830
- console.log(chalk.gray(` Installing dependencies...`));
831
- progress('installing-deps', 'Installing npm dependencies...');
832
- try {
833
- await installer.ensureDependencies();
834
- timings['install'] = Date.now() - installStart;
835
- console.log(chalk.green(` ✓ Dependencies installed (${timings['install']}ms)`));
836
- progress('installing-deps', `Dependencies installed (${timings['install']}ms)`);
837
- }
838
- catch (err) {
839
- timings['install'] = Date.now() - installStart;
840
- console.log(chalk.red(` ✗ Install failed: ${err.message}`));
841
- console.log(chalk.yellow(` ⚠ Dev server may fail — retrying install on first request`));
842
- progress('installing-deps', `Install failed: ${err.message}`);
843
- }
844
- }
845
- else {
846
- progress('installing-deps', 'Dependencies already installed (cached)');
847
- }
848
- // ── Phase 3: Start dev server ────────────────────────────────────
849
- if (!opts.noDev) {
850
- if (installer.areDependenciesInstalled()) {
851
- progress('starting-dev', 'Starting dev server...');
841
+ // ── Phase 2: Install deps (if needed) — skip for LocalRepo ──
842
+ if (!isLocalRepo) {
843
+ const installer = new PackageInstaller({ projectDir });
844
+ const needsInstall = !installer.areDependenciesInstalled() && fs.existsSync(path.join(projectDir, 'package.json'));
845
+ if (needsInstall) {
846
+ const installStart = Date.now();
847
+ console.log(chalk.gray(` Installing dependencies...`));
848
+ progress('installing-deps', 'Installing npm dependencies...');
852
849
  try {
853
- const devStart = Date.now();
854
- await localServer.getDevServer().start();
855
- timings['dev-server'] = Date.now() - devStart;
856
- console.log(chalk.green(` Dev server on port ${allocated.devPort} (${timings['dev-server']}ms)`));
857
- progress('starting-dev', `Dev server on port ${allocated.devPort}`);
850
+ await installer.ensureDependencies();
851
+ timings['install'] = Date.now() - installStart;
852
+ console.log(chalk.green(` ✓ Dependencies installed (${timings['install']}ms)`));
853
+ progress('installing-deps', `Dependencies installed (${timings['install']}ms)`);
858
854
  }
859
855
  catch (err) {
860
- console.log(chalk.yellow(` ⚠ Dev server: ${err.message}`));
861
- progress('starting-dev', `Warning: ${err.message}`);
856
+ timings['install'] = Date.now() - installStart;
857
+ console.log(chalk.red(` ✗ Install failed: ${err.message}`));
858
+ console.log(chalk.yellow(` ⚠ Dev server may fail — retrying install on first request`));
859
+ progress('installing-deps', `Install failed: ${err.message}`);
862
860
  }
863
861
  }
864
862
  else {
865
- console.log(chalk.yellow(` ⚠ Skipping dev server — dependencies not installed`));
866
- console.log(chalk.gray(` Dev server will start when browser opens the project`));
867
- progress('starting-dev', 'Skippeddependencies not installed');
863
+ progress('installing-deps', 'Dependencies already installed (cached)');
864
+ }
865
+ // ── Phase 3: Start dev serverskip for LocalRepo ──
866
+ if (!opts.noDev) {
867
+ if (installer.areDependenciesInstalled()) {
868
+ progress('starting-dev', 'Starting dev server...');
869
+ try {
870
+ const devStart = Date.now();
871
+ await localServer.getDevServer().start();
872
+ timings['dev-server'] = Date.now() - devStart;
873
+ console.log(chalk.green(` ✓ Dev server on port ${allocated.devPort} (${timings['dev-server']}ms)`));
874
+ progress('starting-dev', `Dev server on port ${allocated.devPort}`);
875
+ }
876
+ catch (err) {
877
+ console.log(chalk.yellow(` ⚠ Dev server: ${err.message}`));
878
+ progress('starting-dev', `Warning: ${err.message}`);
879
+ }
880
+ }
881
+ else {
882
+ console.log(chalk.yellow(` ⚠ Skipping dev server — dependencies not installed`));
883
+ console.log(chalk.gray(` Dev server will start when browser opens the project`));
884
+ progress('starting-dev', 'Skipped — dependencies not installed');
885
+ }
868
886
  }
869
887
  }
870
888
  // Ensure tunnel is connected before declaring ready
package/dist/tunnel.d.ts CHANGED
@@ -27,6 +27,7 @@ interface TunnelClientOptions {
27
27
  /** Callback when gateway requests starting a new project on this device */
28
28
  onStartProject?: (projectId: string, options?: {
29
29
  clean?: boolean;
30
+ localFolderPath?: string;
30
31
  }) => Promise<void>;
31
32
  /** Callback for setup progress — lets the standby tunnel relay phases to gateway */
32
33
  onSetupProgress?: (projectId: string, phase: string, message: string) => void;
package/dist/tunnel.js CHANGED
@@ -403,8 +403,8 @@ export class TunnelClient {
403
403
  * Handle start-project: gateway wants this agent to start serving a new project.
404
404
  * Called when user clicks "Connect" in the web editor's Cloud panel.
405
405
  */
406
- async handleStartProject(projectId, clean) {
407
- console.log(` [Tunnel] Received start-project command for ${projectId}${clean ? ' (clean)' : ''}`);
406
+ async handleStartProject(projectId, clean, localFolderPath) {
407
+ console.log(` [Tunnel] Received start-project command for ${projectId}${clean ? ' (clean)' : ''}${localFolderPath ? ` (local: ${localFolderPath})` : ''}`);
408
408
  if (!projectId) {
409
409
  this.send({ type: 'start-project-result', success: false, error: 'Missing projectId' });
410
410
  return;
@@ -413,7 +413,7 @@ export class TunnelClient {
413
413
  try {
414
414
  // Send initial progress
415
415
  this.sendSetupProgress(projectId, 'starting', 'Starting project setup...');
416
- await this.options.onStartProject(projectId, clean ? { clean: true } : undefined);
416
+ await this.options.onStartProject(projectId, { ...(clean ? { clean: true } : {}), ...(localFolderPath ? { localFolderPath } : {}) });
417
417
  this.sendSetupProgress(projectId, 'ready', 'Project is live!');
418
418
  this.send({ type: 'start-project-result', projectId, success: true });
419
419
  }
@@ -474,7 +474,7 @@ export class TunnelClient {
474
474
  this.handleWsClose(msg.wsId);
475
475
  break;
476
476
  case 'start-project':
477
- this.handleStartProject(msg.projectId, msg.clean);
477
+ this.handleStartProject(msg.projectId, msg.clean, msg.localFolderPath);
478
478
  break;
479
479
  case 'force-disconnect':
480
480
  console.log(' [Tunnel] Received force-disconnect — will not reconnect');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nstantpage-agent",
3
- "version": "0.8.12",
3
+ "version": "0.8.14",
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": {