nstantpage-agent 0.5.26 → 0.5.28
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/agentSync.d.ts +10 -0
- package/dist/agentSync.js +49 -4
- package/dist/cli.js +1 -1
- package/dist/commands/start.js +18 -3
- package/dist/localServer.d.ts +1 -0
- package/dist/localServer.js +16 -0
- package/package.json +1 -1
package/dist/agentSync.d.ts
CHANGED
|
@@ -81,5 +81,15 @@ export declare class AgentSync {
|
|
|
81
81
|
private snapshotBaseline;
|
|
82
82
|
/** Read a single file from disk (for diff view) */
|
|
83
83
|
readDiskFile(filePath: string): Promise<string | null>;
|
|
84
|
+
/**
|
|
85
|
+
* Pull files from the backend DB and write them to disk.
|
|
86
|
+
* This fetches ALL project files and overwrites local versions.
|
|
87
|
+
* Files that exist only on disk (not in DB) are left untouched.
|
|
88
|
+
*/
|
|
89
|
+
pullFromBackend(): Promise<{
|
|
90
|
+
success: boolean;
|
|
91
|
+
filesWritten: number;
|
|
92
|
+
versionId?: string;
|
|
93
|
+
}>;
|
|
84
94
|
destroy(): void;
|
|
85
95
|
}
|
package/dist/agentSync.js
CHANGED
|
@@ -410,12 +410,14 @@ export class AgentSync {
|
|
|
410
410
|
const backendResult = await this.fetchBackendChecksums();
|
|
411
411
|
if (!backendResult)
|
|
412
412
|
return [];
|
|
413
|
+
// Use filtered backend checksums (same skip rules as disk scan) for consistency with getSyncStatus
|
|
414
|
+
const filteredBackend = this.filterBackendChecksums(backendResult.checksums);
|
|
413
415
|
const baseline = this.syncedBaselineChecksums;
|
|
414
416
|
const result = [];
|
|
415
417
|
if (baseline) {
|
|
416
418
|
for (const [filePath, baselineSha] of baseline) {
|
|
417
419
|
const currentDiskSha = diskComputed.checksums.get(filePath);
|
|
418
|
-
const backendSha =
|
|
420
|
+
const backendSha = filteredBackend.get(filePath);
|
|
419
421
|
if (!currentDiskSha) {
|
|
420
422
|
if (backendSha) {
|
|
421
423
|
result.push({ path: filePath, status: 'modified', backendSha });
|
|
@@ -431,7 +433,7 @@ export class AgentSync {
|
|
|
431
433
|
}
|
|
432
434
|
}
|
|
433
435
|
if (this.lastSyncTimestamp && this.lastSyncTimestamp.versionId !== backendResult.versionId) {
|
|
434
|
-
for (const [filePath, backendSha] of
|
|
436
|
+
for (const [filePath, backendSha] of filteredBackend) {
|
|
435
437
|
if (!baseline.has(filePath) && !diskComputed.checksums.has(filePath)) {
|
|
436
438
|
result.push({ path: filePath, status: 'backend-only', backendSha });
|
|
437
439
|
}
|
|
@@ -439,7 +441,7 @@ export class AgentSync {
|
|
|
439
441
|
}
|
|
440
442
|
}
|
|
441
443
|
else {
|
|
442
|
-
const sortedBackend = [...
|
|
444
|
+
const sortedBackend = [...filteredBackend.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
443
445
|
for (const [filePath, backendSha] of sortedBackend) {
|
|
444
446
|
const diskSha = diskComputed.checksums.get(filePath);
|
|
445
447
|
if (diskSha) {
|
|
@@ -456,7 +458,7 @@ export class AgentSync {
|
|
|
456
458
|
}
|
|
457
459
|
const sortedDisk = [...diskComputed.checksums.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
458
460
|
for (const [filePath, diskSha] of sortedDisk) {
|
|
459
|
-
if (!
|
|
461
|
+
if (!filteredBackend.has(filePath) && isUserCreatedFile(filePath)) {
|
|
460
462
|
result.push({ path: filePath, status: 'disk-only', diskSha });
|
|
461
463
|
}
|
|
462
464
|
}
|
|
@@ -578,6 +580,49 @@ export class AgentSync {
|
|
|
578
580
|
return null;
|
|
579
581
|
}
|
|
580
582
|
}
|
|
583
|
+
/**
|
|
584
|
+
* Pull files from the backend DB and write them to disk.
|
|
585
|
+
* This fetches ALL project files and overwrites local versions.
|
|
586
|
+
* Files that exist only on disk (not in DB) are left untouched.
|
|
587
|
+
*/
|
|
588
|
+
async pullFromBackend() {
|
|
589
|
+
const url = `${this.backendUrl}/api/sandbox/files?projectId=${this.projectId}`;
|
|
590
|
+
const response = await fetch(url);
|
|
591
|
+
if (!response.ok) {
|
|
592
|
+
throw new Error(`Failed to fetch files from backend: ${response.status}`);
|
|
593
|
+
}
|
|
594
|
+
const data = await response.json();
|
|
595
|
+
if (!data.files || data.files.length === 0) {
|
|
596
|
+
return { success: true, filesWritten: 0 };
|
|
597
|
+
}
|
|
598
|
+
// Ensure directories exist
|
|
599
|
+
const dirsToCreate = new Set();
|
|
600
|
+
for (const file of data.files) {
|
|
601
|
+
const filePath = path.join(this.projectDir, file.path);
|
|
602
|
+
dirsToCreate.add(path.dirname(filePath));
|
|
603
|
+
}
|
|
604
|
+
for (const dir of dirsToCreate) {
|
|
605
|
+
await fs.mkdir(dir, { recursive: true }).catch(() => { });
|
|
606
|
+
}
|
|
607
|
+
// Write files in parallel batches
|
|
608
|
+
const BATCH_SIZE = 50;
|
|
609
|
+
let written = 0;
|
|
610
|
+
for (let i = 0; i < data.files.length; i += BATCH_SIZE) {
|
|
611
|
+
const batch = data.files.slice(i, i + BATCH_SIZE);
|
|
612
|
+
await Promise.all(batch.map(async (file) => {
|
|
613
|
+
const filePath = path.join(this.projectDir, file.path);
|
|
614
|
+
await fs.writeFile(filePath, file.content, 'utf-8');
|
|
615
|
+
}));
|
|
616
|
+
written += batch.length;
|
|
617
|
+
}
|
|
618
|
+
// Invalidate caches and re-establish baseline
|
|
619
|
+
this.diskChecksumCache = null;
|
|
620
|
+
this.backendChecksumCache = null;
|
|
621
|
+
const versionId = String(data.versionId);
|
|
622
|
+
this.markSynced(versionId);
|
|
623
|
+
console.log(` [AgentSync] Pulled ${written} files from backend (version ${versionId})`);
|
|
624
|
+
return { success: true, filesWritten: written, versionId };
|
|
625
|
+
}
|
|
581
626
|
// ─── Cleanup ──────────────────────────────────────────────
|
|
582
627
|
destroy() {
|
|
583
628
|
this.stopFileWatcher();
|
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.28');
|
|
29
29
|
program
|
|
30
30
|
.command('login')
|
|
31
31
|
.description('Authenticate with nstantpage.com')
|
package/dist/commands/start.js
CHANGED
|
@@ -26,7 +26,7 @@ import { LocalServer } from '../localServer.js';
|
|
|
26
26
|
import { PackageInstaller } from '../packageInstaller.js';
|
|
27
27
|
import { probeLocalPostgres, ensureLocalProjectDb, closeAdminPool, writeDatabaseUrlToEnv } from '../projectDb.js';
|
|
28
28
|
import { StatusServer } from '../statusServer.js';
|
|
29
|
-
const VERSION = '0.5.
|
|
29
|
+
const VERSION = '0.5.28';
|
|
30
30
|
/**
|
|
31
31
|
* Resolve the backend API base URL.
|
|
32
32
|
* - If --backend is passed, use it
|
|
@@ -685,12 +685,21 @@ async function startAdditionalProject(projectId, opts) {
|
|
|
685
685
|
const serverEnv = {};
|
|
686
686
|
if (databaseUrl)
|
|
687
687
|
serverEnv['DATABASE_URL'] = databaseUrl;
|
|
688
|
+
// Note: tunnel is created after localServer but before start(),
|
|
689
|
+
// so the onSyncDirty callback captures it via closure (late-binding).
|
|
690
|
+
let tunnel;
|
|
688
691
|
const localServer = new LocalServer({
|
|
689
692
|
projectDir, projectId,
|
|
690
693
|
apiPort: allocated.apiPort, devPort: allocated.devPort,
|
|
691
694
|
env: serverEnv,
|
|
695
|
+
backendUrl: opts.backendUrl,
|
|
696
|
+
onSyncDirty: (pid) => {
|
|
697
|
+
if (tunnel) {
|
|
698
|
+
tunnel.sendSyncDirty(pid);
|
|
699
|
+
}
|
|
700
|
+
},
|
|
692
701
|
});
|
|
693
|
-
|
|
702
|
+
tunnel = new TunnelClient({
|
|
694
703
|
gatewayUrl: opts.gatewayUrl,
|
|
695
704
|
token: opts.token,
|
|
696
705
|
projectId,
|
|
@@ -714,8 +723,10 @@ async function startAdditionalProject(projectId, opts) {
|
|
|
714
723
|
}
|
|
715
724
|
})();
|
|
716
725
|
const fileStart = Date.now();
|
|
726
|
+
let fetchedVersionId;
|
|
717
727
|
try {
|
|
718
|
-
await fetchProjectFiles(opts.backendUrl, projectId, projectDir, opts.token);
|
|
728
|
+
const { versionId } = await fetchProjectFiles(opts.backendUrl, projectId, projectDir, opts.token);
|
|
729
|
+
fetchedVersionId = versionId;
|
|
719
730
|
timings['fetch-files'] = Date.now() - fileStart;
|
|
720
731
|
progress('fetching-files', `Files downloaded (${timings['fetch-files']}ms)`);
|
|
721
732
|
}
|
|
@@ -730,6 +741,10 @@ async function startAdditionalProject(projectId, opts) {
|
|
|
730
741
|
// Wait for API server before proceeding (tunnel can keep connecting in background)
|
|
731
742
|
await apiServerPromise;
|
|
732
743
|
progress('starting-server', `API server ready`);
|
|
744
|
+
// Set sync baseline so file watcher knows what "clean" looks like
|
|
745
|
+
if (fetchedVersionId) {
|
|
746
|
+
localServer.markSynced(fetchedVersionId);
|
|
747
|
+
}
|
|
733
748
|
// ── Phase 2: Install deps (if needed) ────────────────────────────
|
|
734
749
|
const installer = new PackageInstaller({ projectDir });
|
|
735
750
|
const needsInstall = !installer.areDependenciesInstalled() && fs.existsSync(path.join(projectDir, 'package.json'));
|
package/dist/localServer.d.ts
CHANGED
package/dist/localServer.js
CHANGED
|
@@ -282,6 +282,7 @@ export class LocalServer {
|
|
|
282
282
|
'/live/sync-status': this.handleSyncStatus,
|
|
283
283
|
'/live/sync-diff': this.handleSyncDiff,
|
|
284
284
|
'/live/push-to-backend': this.handlePushToBackend,
|
|
285
|
+
'/live/pull-from-backend': this.handlePullFromBackend,
|
|
285
286
|
'/live/disk-file': this.handleDiskFile,
|
|
286
287
|
'/health': this.handleHealth,
|
|
287
288
|
};
|
|
@@ -1102,6 +1103,21 @@ export class LocalServer {
|
|
|
1102
1103
|
this.json(res, { success: false, error: error.message }, 500);
|
|
1103
1104
|
}
|
|
1104
1105
|
}
|
|
1106
|
+
// ─── /live/pull-from-backend ──────────────────────────────────
|
|
1107
|
+
async handlePullFromBackend(_req, res) {
|
|
1108
|
+
if (!this.agentSync) {
|
|
1109
|
+
this.json(res, { success: false, error: 'Sync not available (no backendUrl configured)' }, 503);
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
try {
|
|
1113
|
+
const result = await this.agentSync.pullFromBackend();
|
|
1114
|
+
this.json(res, result);
|
|
1115
|
+
}
|
|
1116
|
+
catch (error) {
|
|
1117
|
+
console.error(` [LocalServer] pull-from-backend error:`, error.message);
|
|
1118
|
+
this.json(res, { success: false, error: error.message }, 500);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1105
1121
|
// ─── /live/disk-file ─────────────────────────────────────────
|
|
1106
1122
|
async handleDiskFile(_req, res, _body, url) {
|
|
1107
1123
|
const filePath = url.searchParams.get('path');
|
package/package.json
CHANGED