nstantpage-agent 0.5.27 → 0.5.29
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 +60 -33
- package/dist/cli.js +1 -1
- package/dist/commands/start.js +1 -1
- 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
|
@@ -278,6 +278,13 @@ export class AgentSync {
|
|
|
278
278
|
continue;
|
|
279
279
|
if (SKIP_EXTENSIONS.has(ext))
|
|
280
280
|
continue;
|
|
281
|
+
// Match disk scan: skip files inside SKIP_DIRS directories
|
|
282
|
+
const parts = filePath.split('/');
|
|
283
|
+
if (parts.some(p => SKIP_DIRS.has(p)))
|
|
284
|
+
continue;
|
|
285
|
+
// Match disk scan: skip dist/ and build/ top-level paths
|
|
286
|
+
if (filePath.startsWith('dist/') || filePath.startsWith('build/'))
|
|
287
|
+
continue;
|
|
281
288
|
filtered.set(filePath, sha);
|
|
282
289
|
}
|
|
283
290
|
return filtered;
|
|
@@ -358,36 +365,11 @@ export class AgentSync {
|
|
|
358
365
|
}
|
|
359
366
|
}
|
|
360
367
|
// Determine direction
|
|
368
|
+
// Disk is always the source of truth. DB only changes via push or AI coding agent
|
|
369
|
+
// (which updates both disk+DB simultaneously). So direction is only ever
|
|
370
|
+
// 'in-sync' or 'disk-ahead'. backendOnlyFiles are stale DB remnants, not "newer" files.
|
|
361
371
|
const inSync = modifiedFiles.length === 0 && diskOnlyFiles.length === 0 && backendOnlyFiles.length === 0;
|
|
362
|
-
|
|
363
|
-
if (!inSync) {
|
|
364
|
-
const hasDiskEdits = modifiedFiles.length > 0 || diskOnlyFiles.length > 0;
|
|
365
|
-
const hasBackendEdits = backendOnlyFiles.length > 0;
|
|
366
|
-
let modifiedFromBackend = false;
|
|
367
|
-
if (modifiedFiles.length > 0 && baseline) {
|
|
368
|
-
for (const filePath of modifiedFiles) {
|
|
369
|
-
const baselineSha = baseline.get(filePath);
|
|
370
|
-
const diskSha = diskResult.checksums.get(filePath);
|
|
371
|
-
const backendSha = filteredBackend.get(filePath);
|
|
372
|
-
if (baselineSha && diskSha === baselineSha && backendSha && backendSha !== baselineSha) {
|
|
373
|
-
modifiedFromBackend = true;
|
|
374
|
-
break;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
if ((hasDiskEdits && !modifiedFromBackend) && hasBackendEdits) {
|
|
379
|
-
direction = 'diverged';
|
|
380
|
-
}
|
|
381
|
-
else if (hasDiskEdits && !modifiedFromBackend && !hasBackendEdits) {
|
|
382
|
-
direction = 'disk-ahead';
|
|
383
|
-
}
|
|
384
|
-
else if (hasBackendEdits || modifiedFromBackend) {
|
|
385
|
-
direction = 'backend-ahead';
|
|
386
|
-
}
|
|
387
|
-
else {
|
|
388
|
-
direction = 'disk-ahead';
|
|
389
|
-
}
|
|
390
|
-
}
|
|
372
|
+
const direction = inSync ? 'in-sync' : 'disk-ahead';
|
|
391
373
|
return {
|
|
392
374
|
inSync,
|
|
393
375
|
direction,
|
|
@@ -410,12 +392,14 @@ export class AgentSync {
|
|
|
410
392
|
const backendResult = await this.fetchBackendChecksums();
|
|
411
393
|
if (!backendResult)
|
|
412
394
|
return [];
|
|
395
|
+
// Use filtered backend checksums (same skip rules as disk scan) for consistency with getSyncStatus
|
|
396
|
+
const filteredBackend = this.filterBackendChecksums(backendResult.checksums);
|
|
413
397
|
const baseline = this.syncedBaselineChecksums;
|
|
414
398
|
const result = [];
|
|
415
399
|
if (baseline) {
|
|
416
400
|
for (const [filePath, baselineSha] of baseline) {
|
|
417
401
|
const currentDiskSha = diskComputed.checksums.get(filePath);
|
|
418
|
-
const backendSha =
|
|
402
|
+
const backendSha = filteredBackend.get(filePath);
|
|
419
403
|
if (!currentDiskSha) {
|
|
420
404
|
if (backendSha) {
|
|
421
405
|
result.push({ path: filePath, status: 'modified', backendSha });
|
|
@@ -431,7 +415,7 @@ export class AgentSync {
|
|
|
431
415
|
}
|
|
432
416
|
}
|
|
433
417
|
if (this.lastSyncTimestamp && this.lastSyncTimestamp.versionId !== backendResult.versionId) {
|
|
434
|
-
for (const [filePath, backendSha] of
|
|
418
|
+
for (const [filePath, backendSha] of filteredBackend) {
|
|
435
419
|
if (!baseline.has(filePath) && !diskComputed.checksums.has(filePath)) {
|
|
436
420
|
result.push({ path: filePath, status: 'backend-only', backendSha });
|
|
437
421
|
}
|
|
@@ -439,7 +423,7 @@ export class AgentSync {
|
|
|
439
423
|
}
|
|
440
424
|
}
|
|
441
425
|
else {
|
|
442
|
-
const sortedBackend = [...
|
|
426
|
+
const sortedBackend = [...filteredBackend.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
443
427
|
for (const [filePath, backendSha] of sortedBackend) {
|
|
444
428
|
const diskSha = diskComputed.checksums.get(filePath);
|
|
445
429
|
if (diskSha) {
|
|
@@ -456,7 +440,7 @@ export class AgentSync {
|
|
|
456
440
|
}
|
|
457
441
|
const sortedDisk = [...diskComputed.checksums.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
458
442
|
for (const [filePath, diskSha] of sortedDisk) {
|
|
459
|
-
if (!
|
|
443
|
+
if (!filteredBackend.has(filePath) && isUserCreatedFile(filePath)) {
|
|
460
444
|
result.push({ path: filePath, status: 'disk-only', diskSha });
|
|
461
445
|
}
|
|
462
446
|
}
|
|
@@ -578,6 +562,49 @@ export class AgentSync {
|
|
|
578
562
|
return null;
|
|
579
563
|
}
|
|
580
564
|
}
|
|
565
|
+
/**
|
|
566
|
+
* Pull files from the backend DB and write them to disk.
|
|
567
|
+
* This fetches ALL project files and overwrites local versions.
|
|
568
|
+
* Files that exist only on disk (not in DB) are left untouched.
|
|
569
|
+
*/
|
|
570
|
+
async pullFromBackend() {
|
|
571
|
+
const url = `${this.backendUrl}/api/sandbox/files?projectId=${this.projectId}`;
|
|
572
|
+
const response = await fetch(url);
|
|
573
|
+
if (!response.ok) {
|
|
574
|
+
throw new Error(`Failed to fetch files from backend: ${response.status}`);
|
|
575
|
+
}
|
|
576
|
+
const data = await response.json();
|
|
577
|
+
if (!data.files || data.files.length === 0) {
|
|
578
|
+
return { success: true, filesWritten: 0 };
|
|
579
|
+
}
|
|
580
|
+
// Ensure directories exist
|
|
581
|
+
const dirsToCreate = new Set();
|
|
582
|
+
for (const file of data.files) {
|
|
583
|
+
const filePath = path.join(this.projectDir, file.path);
|
|
584
|
+
dirsToCreate.add(path.dirname(filePath));
|
|
585
|
+
}
|
|
586
|
+
for (const dir of dirsToCreate) {
|
|
587
|
+
await fs.mkdir(dir, { recursive: true }).catch(() => { });
|
|
588
|
+
}
|
|
589
|
+
// Write files in parallel batches
|
|
590
|
+
const BATCH_SIZE = 50;
|
|
591
|
+
let written = 0;
|
|
592
|
+
for (let i = 0; i < data.files.length; i += BATCH_SIZE) {
|
|
593
|
+
const batch = data.files.slice(i, i + BATCH_SIZE);
|
|
594
|
+
await Promise.all(batch.map(async (file) => {
|
|
595
|
+
const filePath = path.join(this.projectDir, file.path);
|
|
596
|
+
await fs.writeFile(filePath, file.content, 'utf-8');
|
|
597
|
+
}));
|
|
598
|
+
written += batch.length;
|
|
599
|
+
}
|
|
600
|
+
// Invalidate caches and re-establish baseline
|
|
601
|
+
this.diskChecksumCache = null;
|
|
602
|
+
this.backendChecksumCache = null;
|
|
603
|
+
const versionId = String(data.versionId);
|
|
604
|
+
this.markSynced(versionId);
|
|
605
|
+
console.log(` [AgentSync] Pulled ${written} files from backend (version ${versionId})`);
|
|
606
|
+
return { success: true, filesWritten: written, versionId };
|
|
607
|
+
}
|
|
581
608
|
// ─── Cleanup ──────────────────────────────────────────────
|
|
582
609
|
destroy() {
|
|
583
610
|
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.29');
|
|
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.29';
|
|
30
30
|
/**
|
|
31
31
|
* Resolve the backend API base URL.
|
|
32
32
|
* - If --backend is passed, use it
|
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