gdrive-syncer 3.1.1 → 3.1.3
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/package.json +1 -1
- package/src/envSync.js +75 -62
package/package.json
CHANGED
package/src/envSync.js
CHANGED
|
@@ -1242,54 +1242,19 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
|
|
|
1242
1242
|
}
|
|
1243
1243
|
}
|
|
1244
1244
|
|
|
1245
|
-
// Get local files
|
|
1245
|
+
// Get local files
|
|
1246
1246
|
fs.ensureDirSync(envDir);
|
|
1247
1247
|
const localItems = listLocalFilesRecursive(envDir, pattern, ignore, '', true);
|
|
1248
1248
|
const localFiles = localItems.filter((f) => !f.isFolder);
|
|
1249
1249
|
const localFolders = localItems.filter((f) => f.isFolder);
|
|
1250
1250
|
|
|
1251
|
-
//
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
// Delta sync: Determine which files need to be downloaded for comparison
|
|
1258
|
-
// Skip files where metadata indicates no change (same size AND local is not older)
|
|
1259
|
-
s.message('Analyzing file changes (delta sync)...');
|
|
1260
|
-
const filesToDownload = [];
|
|
1261
|
-
const unchangedFiles = [];
|
|
1262
|
-
|
|
1263
|
-
for (const driveFile of driveFiles) {
|
|
1264
|
-
const localFile = localFileMap.get(driveFile.relativePath);
|
|
1265
|
-
|
|
1266
|
-
if (!localFile) {
|
|
1267
|
-
// File only exists on Drive - need to download for diff
|
|
1268
|
-
filesToDownload.push(driveFile);
|
|
1269
|
-
} else {
|
|
1270
|
-
// File exists in both places - check if metadata indicates change
|
|
1271
|
-
const sizeMatch = driveFile.sizeBytes === localFile.sizeBytes;
|
|
1272
|
-
const driveTime = driveFile.modifiedTime ? driveFile.modifiedTime.getTime() : 0;
|
|
1273
|
-
const localTime = localFile.modifiedTime ? localFile.modifiedTime.getTime() : 0;
|
|
1274
|
-
|
|
1275
|
-
// Consider unchanged if: same size AND Drive is not newer
|
|
1276
|
-
// (allow 1 second tolerance for time comparison)
|
|
1277
|
-
const timeMatch = driveTime <= localTime + 1000;
|
|
1278
|
-
|
|
1279
|
-
if (sizeMatch && timeMatch) {
|
|
1280
|
-
// Metadata suggests unchanged - skip download
|
|
1281
|
-
unchangedFiles.push(driveFile.relativePath);
|
|
1282
|
-
} else {
|
|
1283
|
-
// Metadata differs - need to download for accurate comparison
|
|
1284
|
-
filesToDownload.push(driveFile);
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1251
|
+
// Always download ALL Drive files for content-based comparison (no delta sync heuristics)
|
|
1252
|
+
// Accuracy is paramount - metadata (size/time) comparisons are unreliable because:
|
|
1253
|
+
// - Drive sizes from CLI output are human-readable approximations (lossy)
|
|
1254
|
+
// - Local mtime vs Drive modifiedTime are not comparable across systems
|
|
1255
|
+
const filesToDownload = [...driveFiles];
|
|
1288
1256
|
|
|
1289
|
-
|
|
1290
|
-
s.message(
|
|
1291
|
-
`Downloading ${filesToDownload.length} file(s) for comparison (${unchangedFiles.length} unchanged)...`
|
|
1292
|
-
);
|
|
1257
|
+
s.message(`Downloading ${filesToDownload.length} file(s) for comparison...`);
|
|
1293
1258
|
|
|
1294
1259
|
// Prepare destination directories first
|
|
1295
1260
|
for (const file of filesToDownload) {
|
|
@@ -1304,14 +1269,60 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
|
|
|
1304
1269
|
destination: path.join(tempDir, path.dirname(file.relativePath)),
|
|
1305
1270
|
overwrite: true,
|
|
1306
1271
|
},
|
|
1272
|
+
relativePath: file.relativePath,
|
|
1307
1273
|
}));
|
|
1274
|
+
let downloadResults = [];
|
|
1308
1275
|
if (downloads.length > 0) {
|
|
1309
|
-
await gdrive.downloadParallel(downloads, 5, (completed, total) => {
|
|
1276
|
+
downloadResults = await gdrive.downloadParallel(downloads, 5, (completed, total) => {
|
|
1310
1277
|
s.message(`Downloading files for comparison... (${completed}/${total})`);
|
|
1311
1278
|
});
|
|
1312
1279
|
}
|
|
1313
1280
|
|
|
1314
|
-
|
|
1281
|
+
// Check for failed downloads and retry them individually
|
|
1282
|
+
const failedDownloads = [];
|
|
1283
|
+
for (let i = 0; i < downloadResults.length; i++) {
|
|
1284
|
+
if (downloadResults[i].code !== 0) {
|
|
1285
|
+
failedDownloads.push(downloads[i]);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
if (failedDownloads.length > 0) {
|
|
1290
|
+
s.message(`Retrying ${failedDownloads.length} failed download(s)...`);
|
|
1291
|
+
for (const dl of failedDownloads) {
|
|
1292
|
+
const retryResult = await gdrive.downloadAsync(dl.fileId, dl.options);
|
|
1293
|
+
if (retryResult.code !== 0) {
|
|
1294
|
+
// Verify file actually exists on disk despite error code
|
|
1295
|
+
const expectedPath = path.join(tempDir, dl.relativePath);
|
|
1296
|
+
if (!fs.existsSync(expectedPath)) {
|
|
1297
|
+
s.stop(color.red('Download failures detected'));
|
|
1298
|
+
log.error(`Failed to download: ${dl.relativePath}`);
|
|
1299
|
+
if (retryResult.stderr) log.error(retryResult.stderr);
|
|
1300
|
+
log.error('Diff results would be incomplete. Aborting to avoid showing incorrect diff.');
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// Final verification: ensure every file we expected to download actually exists
|
|
1308
|
+
const missingFiles = [];
|
|
1309
|
+
for (const file of filesToDownload) {
|
|
1310
|
+
const expectedPath = path.join(tempDir, file.relativePath);
|
|
1311
|
+
if (!fs.existsSync(expectedPath)) {
|
|
1312
|
+
missingFiles.push(file.relativePath);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
if (missingFiles.length > 0) {
|
|
1317
|
+
s.stop(color.red('Some files could not be downloaded'));
|
|
1318
|
+
for (const f of missingFiles) {
|
|
1319
|
+
log.error(`Missing: ${f}`);
|
|
1320
|
+
}
|
|
1321
|
+
log.error('Diff results would be incomplete. Aborting to avoid showing incorrect diff.');
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
s.stop(color.green(`Found ${driveFiles.length} file(s) on Drive, downloaded all for comparison`));
|
|
1315
1326
|
|
|
1316
1327
|
// Compare files using relativePath
|
|
1317
1328
|
const changes = {
|
|
@@ -1325,26 +1336,19 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
|
|
|
1325
1336
|
const driveFilePaths = new Set(driveFiles.map((f) => f.relativePath));
|
|
1326
1337
|
const localFilePaths = new Set(localFiles.map((f) => f.relativePath));
|
|
1327
1338
|
const localFolderPaths = new Set(localFolders.map((f) => f.relativePath));
|
|
1328
|
-
const unchangedSet = new Set(unchangedFiles);
|
|
1329
1339
|
|
|
1330
|
-
// Check local files
|
|
1340
|
+
// Check local files - always compare content for accuracy
|
|
1341
|
+
// All Drive files are guaranteed to exist in tempDir (verified above)
|
|
1331
1342
|
for (const localFile of localFiles) {
|
|
1332
1343
|
const localFilePath = path.join(envDir, localFile.relativePath);
|
|
1333
1344
|
const driveFilePath = path.join(tempDir, localFile.relativePath);
|
|
1334
1345
|
|
|
1335
1346
|
if (driveFilePaths.has(localFile.relativePath)) {
|
|
1336
|
-
// File exists on both
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
// Check for modifications (only for files we downloaded)
|
|
1342
|
-
if (fs.existsSync(driveFilePath)) {
|
|
1343
|
-
const localContent = fs.readFileSync(localFilePath, 'utf-8');
|
|
1344
|
-
const driveContent = fs.readFileSync(driveFilePath, 'utf-8');
|
|
1345
|
-
if (localContent !== driveContent) {
|
|
1346
|
-
changes.modified.push(localFile.relativePath);
|
|
1347
|
-
}
|
|
1347
|
+
// File exists on both - compare actual content
|
|
1348
|
+
const localContent = fs.readFileSync(localFilePath, 'utf-8');
|
|
1349
|
+
const driveContent = fs.readFileSync(driveFilePath, 'utf-8');
|
|
1350
|
+
if (localContent !== driveContent) {
|
|
1351
|
+
changes.modified.push(localFile.relativePath);
|
|
1348
1352
|
}
|
|
1349
1353
|
} else {
|
|
1350
1354
|
changes.localOnly.push(localFile.relativePath);
|
|
@@ -1519,11 +1523,13 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
|
|
|
1519
1523
|
}
|
|
1520
1524
|
|
|
1521
1525
|
const downloadSpinner = spinner();
|
|
1522
|
-
|
|
1523
|
-
|
|
1526
|
+
const totalToDownload = changes.modified.length + changes.driveOnly.length;
|
|
1524
1527
|
let downloaded = 0;
|
|
1525
1528
|
|
|
1529
|
+
downloadSpinner.start(`Downloading files... (0/${totalToDownload})`);
|
|
1530
|
+
|
|
1526
1531
|
for (const relativePath of changes.modified) {
|
|
1532
|
+
downloadSpinner.message(`Downloading (${downloaded + 1}/${totalToDownload}): ${relativePath}`);
|
|
1527
1533
|
const srcPath = path.join(tempDir, relativePath);
|
|
1528
1534
|
const destPath = path.join(envDir, relativePath);
|
|
1529
1535
|
fs.ensureDirSync(path.dirname(destPath));
|
|
@@ -1532,6 +1538,7 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
|
|
|
1532
1538
|
}
|
|
1533
1539
|
|
|
1534
1540
|
for (const relativePath of changes.driveOnly) {
|
|
1541
|
+
downloadSpinner.message(`Downloading (${downloaded + 1}/${totalToDownload}): ${relativePath}`);
|
|
1535
1542
|
const srcPath = path.join(tempDir, relativePath);
|
|
1536
1543
|
const destPath = path.join(envDir, relativePath);
|
|
1537
1544
|
fs.ensureDirSync(path.dirname(destPath));
|
|
@@ -1542,11 +1549,13 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
|
|
|
1542
1549
|
downloadSpinner.stop(color.green(`Downloaded ${downloaded} file(s)`));
|
|
1543
1550
|
} else if (action === 'upload') {
|
|
1544
1551
|
const uploadSpinner = spinner();
|
|
1545
|
-
|
|
1546
|
-
|
|
1552
|
+
const totalToUpload = changes.modified.length + changes.localOnly.length;
|
|
1553
|
+
let completed = 0;
|
|
1547
1554
|
let uploaded = 0;
|
|
1548
1555
|
let replaced = 0;
|
|
1549
1556
|
|
|
1557
|
+
uploadSpinner.start(`Uploading files... (0/${totalToUpload})`);
|
|
1558
|
+
|
|
1550
1559
|
// Build a map of relativePath -> driveFile for quick lookup
|
|
1551
1560
|
const driveFileMap = new Map();
|
|
1552
1561
|
for (const df of driveFiles) {
|
|
@@ -1554,14 +1563,17 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
|
|
|
1554
1563
|
}
|
|
1555
1564
|
|
|
1556
1565
|
for (const relativePath of changes.modified) {
|
|
1566
|
+
uploadSpinner.message(`Replacing (${completed + 1}/${totalToUpload}): ${relativePath}`);
|
|
1557
1567
|
const driveFile = driveFileMap.get(relativePath);
|
|
1558
1568
|
if (driveFile) {
|
|
1559
1569
|
gdrive.update(driveFile.id, path.join(envDir, relativePath));
|
|
1560
1570
|
replaced++;
|
|
1571
|
+
completed++;
|
|
1561
1572
|
}
|
|
1562
1573
|
}
|
|
1563
1574
|
|
|
1564
1575
|
for (const relativePath of changes.localOnly) {
|
|
1576
|
+
uploadSpinner.message(`Uploading (${completed + 1}/${totalToUpload}): ${relativePath}`);
|
|
1565
1577
|
const localFilePath = path.join(envDir, relativePath);
|
|
1566
1578
|
const folderPath = path.dirname(relativePath);
|
|
1567
1579
|
|
|
@@ -1573,6 +1585,7 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
|
|
|
1573
1585
|
|
|
1574
1586
|
gdrive.upload(localFilePath, { parent: parentId });
|
|
1575
1587
|
uploaded++;
|
|
1588
|
+
completed++;
|
|
1576
1589
|
}
|
|
1577
1590
|
|
|
1578
1591
|
uploadSpinner.stop(color.green(`Replaced: ${replaced}, New: ${uploaded}`));
|