async function sync(dir) {
const jwt = await loginWithSupabase(); // opens browser once
const remote = await fetchManifest(jwt);
const local = await buildLocalMap(dir, remote); // reuse cache
const diff = diffLocalVsRemote(local, remote); // { upload, delete }
if (!diff.upload.length && !diff.delete.length) {
console.log("✓ Already up-to-date");
return;
}
const presigned = await presignBatch(jwt, diff.upload, diff.delete);
await Promise.all(presigned.upload.map(putFile)); // 5–10 concurrency
await commit(jwt, presigned.newManifest, remote.version);
console.log(`↑ ${presigned.upload.length} files, ↓ ${diff.delete.length} removed`);
}
/sites/{username}/{default|...}/manifest.xml
/{friendly-name}-{hash}
GET /sync/manifest: get manifest of current published pages POST /sync/presign-batch: send files that need to be changed, get back presigned urls and updated manifest POST /sync/commit: commits new manifest to server
note: initial manifest will be {"version": 0, "files": {}}
{
"version": 17,
"generatedAt": "2025-04-23T16:02:10Z",
"files": {
"blog/intro.md": "1f3870be274f6c49b3e31a0c6728957f",
"notes/todo.txt": "d174ab98d277d9f5a5611c2c9f419d9f",
…
}
}
- request
POST /sync/presign-batch
Authorization: Bearer <JWT | API key>
Content-Type: application/json
{
"siteId": "blog-42",
"baseVersion": 17,
"uploads": [
{ "path": "posts/hello.md", "md5": "1f38…", "size": 894, "contentType": "text/markdown" },
…
],
"delete": ["drafts/old.md"]
}
- response
{
"upload": [
{
"path": "posts/hello.md",
"url": "https://site-bucket.s3.amazonaws.com/blog-42/posts/hello.md?...",
"headers": {
"Content-MD5": "HzhwvjJPbEmz4xoMZyiVfw==",
"Content-Type": "text/markdown"
}
}
],
"delete": ["drafts/old.md"],
"newVersion": 18,
"expiresIn": 600
}
{
"siteId": "blog-42",
"baseVersion": 18,
"newVersion": 19,
"manifest": {
"posts/hello.md": "1f3870be274f6c49b3e31a0c6728957f",
"index.md": "ab56b4d92b40713acc5af89985d4b786"
}
}