@micropage-sh/cli 2.0.7 → 2.0.8
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/supabase.js +55 -5
package/package.json
CHANGED
package/src/supabase.js
CHANGED
|
@@ -437,6 +437,12 @@ async function streamDeployEventsUntilDone(bearerToken, projectId, buildId, opti
|
|
|
437
437
|
return { terminalEvent };
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
+
function hashFile(filePath) {
|
|
441
|
+
const crypto = require('crypto');
|
|
442
|
+
const fs = require('fs');
|
|
443
|
+
return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
|
|
444
|
+
}
|
|
445
|
+
|
|
440
446
|
/**
|
|
441
447
|
* Upload a single asset file to the project via the upload-file Edge function.
|
|
442
448
|
* Uses a pre-obtained Supabase access token.
|
|
@@ -447,10 +453,12 @@ async function uploadAssetWithToken(accessToken, projectId, filePath, filename)
|
|
|
447
453
|
|
|
448
454
|
const bytes = fs.readFileSync(filePath);
|
|
449
455
|
const mimeType = mime.fromFilename(filename);
|
|
456
|
+
const sha = hashFile(filePath);
|
|
450
457
|
|
|
451
458
|
const formData = new FormData();
|
|
452
459
|
formData.append('file', new Blob([bytes], { type: mimeType }), filename);
|
|
453
460
|
formData.append('project_id', String(projectId));
|
|
461
|
+
formData.append('content_hash', sha);
|
|
454
462
|
|
|
455
463
|
const res = await fetch(`${SUPABASE_URL}/functions/v1/upload-file`, {
|
|
456
464
|
method: 'POST',
|
|
@@ -473,9 +481,33 @@ async function uploadAssetWithToken(accessToken, projectId, filePath, filename)
|
|
|
473
481
|
return data;
|
|
474
482
|
}
|
|
475
483
|
|
|
484
|
+
async function deleteFileWithToken(accessToken, fileId) {
|
|
485
|
+
const res = await fetch(`${SUPABASE_URL}/functions/v1/delete-file`, {
|
|
486
|
+
method: 'POST',
|
|
487
|
+
headers: {
|
|
488
|
+
'apikey': SUPABASE_ANON_KEY,
|
|
489
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
490
|
+
'Content-Type': 'application/json',
|
|
491
|
+
},
|
|
492
|
+
body: JSON.stringify({ file_id: fileId }),
|
|
493
|
+
});
|
|
494
|
+
const text = await res.text();
|
|
495
|
+
let data = null;
|
|
496
|
+
try { data = text ? JSON.parse(text) : null; } catch { data = { raw: text }; }
|
|
497
|
+
if (!res.ok) {
|
|
498
|
+
const msg = (data && (data.error || data.message)) || `HTTP ${res.status}`;
|
|
499
|
+
const err = new Error(msg);
|
|
500
|
+
err.status = res.status;
|
|
501
|
+
throw err;
|
|
502
|
+
}
|
|
503
|
+
return data;
|
|
504
|
+
}
|
|
505
|
+
|
|
476
506
|
/**
|
|
477
507
|
* Upload all assets from the local `assets/` directory for a project.
|
|
478
|
-
*
|
|
508
|
+
* Compares each local file's SHA-256 hash against the server's stored hash.
|
|
509
|
+
* Skips files whose content is unchanged. Deletes and re-uploads files whose
|
|
510
|
+
* content has changed (or whose server hash is NULL from before hashing was added).
|
|
479
511
|
* Returns the count of files uploaded.
|
|
480
512
|
*/
|
|
481
513
|
async function uploadAssetsWithToken(accessToken, projectId, cwd, onProgress) {
|
|
@@ -485,7 +517,9 @@ async function uploadAssetsWithToken(accessToken, projectId, cwd, onProgress) {
|
|
|
485
517
|
const assetFiles = listAssetsFromDir(cwd);
|
|
486
518
|
if (assetFiles.length === 0) return 0;
|
|
487
519
|
|
|
488
|
-
// Fetch existing
|
|
520
|
+
// Fetch existing files so we can skip unchanged ones. We MUST treat a
|
|
521
|
+
// non-OK response as fatal: continuing with an empty list would re-upload
|
|
522
|
+
// every asset and (when filenames collide) leave duplicate user_files rows.
|
|
489
523
|
const listRes = await fetch(
|
|
490
524
|
`${SUPABASE_URL}/functions/v1/list-files?project_id=${encodeURIComponent(projectId)}`,
|
|
491
525
|
{
|
|
@@ -495,13 +529,28 @@ async function uploadAssetsWithToken(accessToken, projectId, cwd, onProgress) {
|
|
|
495
529
|
},
|
|
496
530
|
},
|
|
497
531
|
);
|
|
498
|
-
|
|
499
|
-
|
|
532
|
+
if (!listRes.ok) {
|
|
533
|
+
const text = await listRes.text().catch(() => '');
|
|
534
|
+
const err = new Error(`Failed to list project files: HTTP ${listRes.status}${text ? ` — ${text}` : ''}`);
|
|
535
|
+
err.status = listRes.status;
|
|
536
|
+
throw err;
|
|
537
|
+
}
|
|
538
|
+
const listData = await listRes.json();
|
|
539
|
+
const existingByName = new Map(
|
|
540
|
+
(listData?.files || []).map((f) => [f.filename, { id: f.id, content_hash: f.content_hash }]),
|
|
541
|
+
);
|
|
500
542
|
|
|
501
543
|
let uploaded = 0;
|
|
502
544
|
for (const filename of assetFiles) {
|
|
503
|
-
if (existing.has(filename)) continue;
|
|
504
545
|
const filePath = path.join(cwd, 'assets', filename);
|
|
546
|
+
const localHash = hashFile(filePath);
|
|
547
|
+
const existing = existingByName.get(filename);
|
|
548
|
+
|
|
549
|
+
if (existing && existing.content_hash === localHash) continue;
|
|
550
|
+
|
|
551
|
+
if (existing) {
|
|
552
|
+
await deleteFileWithToken(accessToken, existing.id);
|
|
553
|
+
}
|
|
505
554
|
await uploadAssetWithToken(accessToken, projectId, filePath, filename);
|
|
506
555
|
if (onProgress) onProgress(filename);
|
|
507
556
|
uploaded++;
|
|
@@ -645,4 +694,5 @@ module.exports = {
|
|
|
645
694
|
pushWithToken,
|
|
646
695
|
invokePublishBuild,
|
|
647
696
|
uploadAssetsWithToken,
|
|
697
|
+
hashFile,
|
|
648
698
|
};
|