-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.js
More file actions
6 lines (5 loc) · 15.5 KB
/
Copy pathmain.js
File metadata and controls
6 lines (5 loc) · 15.5 KB
1
2
3
4
5
6
/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source visit https://github.com/andrewboldi/obsidian-vault-sync
*/
"use strict";var G=Object.defineProperty;var nt=Object.getOwnPropertyDescriptor;var rt=Object.getOwnPropertyNames;var at=Object.prototype.hasOwnProperty;var it=(t,n)=>{for(var e in n)G(t,e,{get:n[e],enumerable:!0})},ot=(t,n,e,a)=>{if(n&&typeof n=="object"||typeof n=="function")for(let s of rt(n))!at.call(t,s)&&s!==e&&G(t,s,{get:()=>n[s],enumerable:!(a=nt(n,s))||a.enumerable});return t};var ct=t=>ot(G({},"__esModule",{value:!0}),t);var Rt={};it(Rt,{default:()=>M});module.exports=ct(Rt);var E=require("obsidian");var v=require("obsidian"),j={pat:"",repoUrl:"",branch:"main",lastCommitSha:"",fileShaMap:{},autoSyncIntervalMinutes:0},x=class extends v.PluginSettingTab{constructor(n,e){super(n,e),this.plugin=e}display(){let{containerEl:n}=this;n.empty(),new v.Setting(n).setName("GitHub personal access token").setDesc("Fine-grained or classic PAT with repo (contents: read/write) scope on the target repo.").addText(e=>{e.inputEl.type="password",e.setPlaceholder("ghp_\u2026").setValue(this.plugin.settings.pat).onChange(async a=>{this.plugin.settings.pat=a.trim(),await this.plugin.saveSettings()})}),new v.Setting(n).setName("Repository URL").setDesc("For example, https://github.com/owner/repo").addText(e=>e.setPlaceholder("https://github.com/owner/repo").setValue(this.plugin.settings.repoUrl).onChange(async a=>{this.plugin.settings.repoUrl=a.trim(),await this.plugin.saveSettings()})),new v.Setting(n).setName("Branch").setDesc("Branch to sync (default: main)").addText(e=>e.setPlaceholder("main").setValue(this.plugin.settings.branch).onChange(async a=>{this.plugin.settings.branch=a.trim()||"main",await this.plugin.saveSettings()})),new v.Setting(n).setName("Auto-sync interval (minutes)").setDesc("Pull then push every N minutes. 0 disables. Recommended: 1-5.").addText(e=>e.setPlaceholder("0").setValue(String(this.plugin.settings.autoSyncIntervalMinutes??0)).onChange(async a=>{let s=Math.max(0,Math.floor(Number(a)||0));this.plugin.settings.autoSyncIntervalMinutes=s,await this.plugin.saveSettings(),this.plugin.scheduleAutoSync()})),new v.Setting(n).setName("Last commit SHA").setDesc("Set after a successful seed/pull. Read-only \u2014 used as the local sync base.").addText(e=>{e.setValue(this.plugin.settings.lastCommitSha||"(none)").setDisabled(!0)})}};var q=require("obsidian");var f=require("obsidian"),lt="obsidian-vault-sync",S="https://api.github.com";function b(t){return{Authorization:`token ${t}`,"User-Agent":lt,Accept:"application/vnd.github+json","X-GitHub-Api-Version":"2022-11-28"}}function T(t){if(!t)throw new Error("Repository URL is empty");let n=t.trim().replace(/\.git$/,"").replace(/\/+$/,""),e=n.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)$/i);if(e||(e=n.match(/^([^/\s]+)\/([^/\s]+)$/)),!e)throw new Error(`Cannot parse repo URL: ${t}`);return{owner:e[1],repo:e[2]}}async function P(t,n,e){let a=`${S}/repos/${t.owner}/${t.repo}/branches/${encodeURIComponent(n)}`,s=await(0,f.requestUrl)({url:a,method:"GET",headers:b(e),throw:!1});if(s.status<200||s.status>=300)throw new Error(`GitHub branch lookup failed: ${s.status} \u2014 ${s.text.slice(0,300)}`);let i=s.json?.commit?.sha;if(!i)throw new Error("GitHub response missing commit.sha");return i}async function I(t,n,e,a){let s=`${S}/repos/${t.owner}/${t.repo}/compare/${n}...${e}`,r=await(0,f.requestUrl)({url:s,method:"GET",headers:b(a),throw:!1});if(r.status<200||r.status>=300)throw new Error(`Compare failed: ${r.status} \u2014 ${(r.text||"").slice(0,300)}`);return r.json}async function _(t,n,e){let a=`${S}/repos/${t.owner}/${t.repo}/git/trees/${n}?recursive=1`,s=await(0,f.requestUrl)({url:a,method:"GET",headers:b(e),throw:!1});if(s.status<200||s.status>=300)throw new Error(`Tree fetch failed: ${s.status} \u2014 ${(s.text||"").slice(0,300)}`);let r=s.json;if(r.truncated)throw new Error("Repo tree exceeds GitHub's single-request limit (~100k files). Vault Sync needs pagination support \u2014 file an issue.");return r.tree}async function O(t,n,e){let a=`${S}/repos/${t.owner}/${t.repo}/git/commits/${n}`,s=await(0,f.requestUrl)({url:a,method:"GET",headers:b(e),throw:!1});if(s.status<200||s.status>=300)throw new Error(`Commit lookup failed: ${s.status}`);return s.json.tree.sha}async function D(t,n,e){let a=`${S}/repos/${t.owner}/${t.repo}/git/blobs`,s=new Uint8Array(n),r="",i=32768;for(let u=0;u<s.length;u+=i)r+=String.fromCharCode(...s.subarray(u,u+i));let p=btoa(r),o=await(0,f.requestUrl)({url:a,method:"POST",headers:{...b(e),"Content-Type":"application/json"},body:JSON.stringify({content:p,encoding:"base64"}),throw:!1});if(o.status<200||o.status>=300)throw new Error(`Blob create failed: ${o.status} \u2014 ${(o.text||"").slice(0,300)}`);return o.json.sha}async function W(t,n,e,a){let s=`${S}/repos/${t.owner}/${t.repo}/git/trees`,r=await(0,f.requestUrl)({url:s,method:"POST",headers:{...b(a),"Content-Type":"application/json"},body:JSON.stringify({base_tree:n,tree:e}),throw:!1});if(r.status<200||r.status>=300)throw new Error(`Tree create failed: ${r.status} \u2014 ${(r.text||"").slice(0,300)}`);return r.json.sha}async function J(t,n,e,a,s){let r=`${S}/repos/${t.owner}/${t.repo}/git/commits`,i=await(0,f.requestUrl)({url:r,method:"POST",headers:{...b(s),"Content-Type":"application/json"},body:JSON.stringify({message:n,tree:e,parents:a}),throw:!1});if(i.status<200||i.status>=300)throw new Error(`Commit create failed: ${i.status} \u2014 ${(i.text||"").slice(0,300)}`);return i.json.sha}async function K(t,n,e,a){let s=`${S}/repos/${t.owner}/${t.repo}/git/refs/heads/${encodeURIComponent(n)}`,r=await(0,f.requestUrl)({url:s,method:"PATCH",headers:{...b(a),"Content-Type":"application/json"},body:JSON.stringify({sha:e,force:!1}),throw:!1});return{ok:r.status>=200&&r.status<300,status:r.status,message:(r.text||"").slice(0,300)}}async function B(t,n,e,a){let s=`${S}/repos/${t.owner}/${t.repo}/contents/${encodeURIComponent(n).replace(/%2F/g,"/")}?ref=${e}`,r=await(0,f.requestUrl)({url:s,method:"GET",headers:{...b(a),Accept:"application/vnd.github.raw"},throw:!1});if(r.status<200||r.status>=300)throw new Error(`File fetch failed (${n}): ${r.status} \u2014 ${(r.text||"").slice(0,200)}`);return r.arrayBuffer}function Y(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:t<1024*1024*1024?`${(t/(1024*1024)).toFixed(1)} MB`:`${(t/(1024*1024*1024)).toFixed(2)} GB`}async function z(t,n){if(!(!n||n==="/"||n==="."))try{await t.app.vault.adapter.mkdir(n)}catch{}}async function ut(t,n){let e=n.split("/");if(e.pop(),e.length===0)return;let a="";for(let s of e)s&&(a=a?`${a}/${s}`:s,await z(t,a))}async function ht(t,n,e){let a=0,s=[];for(let r=0;r<n;r++)s.push((async()=>{for(;;){let i=a++;if(i>=t.length)return;await e(t[i],i)}})());await Promise.all(s)}var pt=8,mt=10;async function X(t){let{pat:n,repoUrl:e,branch:a}=t.settings;if(!n)throw new Error("Set a GitHub PAT in Vault Sync settings.");if(!e)throw new Error("Set a repo URL in Vault Sync settings.");let s=T(e),r=new q.Notice("Resolving branch\u2026",0),i;try{i=await P(s,a||"main",n)}catch(c){throw r.hide(),c}r.setMessage(`Listing ${s.owner}/${s.repo}@${i.slice(0,7)}\u2026`);let p;try{p=await _(s,i,n)}catch(c){throw r.hide(),c}let o=p.filter(c=>c.type==="tree"),u=p.filter(c=>c.type==="blob");r.setMessage(`${u.length} files, ${o.length} dirs \u2014 preparing\u2026`);for(let c of o)await z(t,c.path);let g=0,$=0,d={};await ht(u,pt,async c=>{let m=await B(s,c.path,i,n);await ut(t,c.path),await t.app.vault.adapter.writeBinary(c.path,m),d[c.path]=c.sha,g++,$+=m.byteLength,g%mt===0&&r.setMessage(`${g}/${u.length} files, ${Y($)}`)}),t.settings.lastCommitSha=i,t.settings.fileShaMap=d,await t.saveSettings(),r.setMessage(`Seed complete \u2014 ${g} files, ${Y($)} @ ${i.slice(0,7)}`),setTimeout(()=>r.hide(),6e3)}var N=require("obsidian");function dt(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(1)} MB`}async function gt(t,n){if(!(!n||n==="/"||n==="."))try{await t.app.vault.adapter.mkdir(n)}catch{}}async function ft(t,n){let e=n.split("/");if(e.pop(),e.length===0)return;let a="";for(let s of e)s&&(a=a?`${a}/${s}`:s,await gt(t,a))}async function yt(t,n){try{await t.app.vault.adapter.remove(n)}catch{}}async function wt(t,n,e){let a=0,s=[];for(let r=0;r<n;r++)s.push((async()=>{for(;;){let i=a++;if(i>=t.length)return;await e(t[i])}})());await Promise.all(s)}var St=8;async function F(t,n={}){let{pat:e,repoUrl:a,branch:s,lastCommitSha:r,fileShaMap:i}=t.settings;if(!e)throw new Error("Set a GitHub PAT in Vault Sync settings.");if(!a)throw new Error("Set a repo URL in Vault Sync settings.");if(!r)throw new Error("No baseline commit recorded. Run 'Seed from GitHub' first.");let p=T(a),o=n.silent?null:new N.Notice("Checking remote\u2026",0),u=await P(p,s||"main",e);if(u===r){o&&(o?.setMessage("Already up to date"),setTimeout(()=>o?.hide(),4e3));return}o?.setMessage(`Comparing ${r.slice(0,7)}\u2026${u.slice(0,7)}`);let g=await I(p,r,u,e);if(g.status==="diverged")throw o?.setMessage("Remote diverged from local baseline. Manual resolution needed (push will detect this)."),setTimeout(()=>o?.hide(),1e4),new Error(`Remote and local have diverged (ahead ${g.ahead_by}, behind ${g.behind_by}). Pull only supports fast-forward updates.`);let $=g.files??[];if($.length===0){t.settings.lastCommitSha=u,await t.saveSettings(),o?.setMessage("Pull complete (no file changes)"),setTimeout(()=>o?.hide(),4e3);return}$.length===300&&new N.Notice("Compare returned exactly 300 files (GitHub's per-call cap). Some changes may be missing \u2014 re-seed if pull seems incomplete.",1e4);let d=[],c=[];for(let h of $)switch(h.status){case"added":case"modified":case"changed":case"copied":d.push(h);break;case"renamed":h.previous_filename&&c.push(h.previous_filename),d.push(h);break;case"removed":c.push(h.filename);break;case"unchanged":break}o?.setMessage(`${d.length} to fetch, ${c.length} to remove\u2026`);for(let h of c)await yt(t,h),delete i[h];let m=0,y=0;await wt(d,St,async h=>{let R=await B(p,h.filename,u,e);await ft(t,h.filename),await t.app.vault.adapter.writeBinary(h.filename,R),i[h.filename]=h.sha,m++,y+=R.byteLength,m%5===0&&o?.setMessage(`${m}/${d.length} files, ${dt(y)}`)}),t.settings.lastCommitSha=u,t.settings.fileShaMap=i,await t.saveSettings(),o?.setMessage(`Pull complete \u2014 ${d.length} updated, ${c.length} removed @ ${u.slice(0,7)}`),setTimeout(()=>o?.hide(),6e3)}var Z=require("obsidian");function bt(t){let n=new Set([`${t}/workspace.json`,`${t}/workspace-mobile.json`,`${t}/cache`,`${t}/appearance.json`,".DS_Store","_GIT-DEBUG-ERROR.md"]),e=[".trash/",`${t}/plugins/`],a=`${t}/workspace`;return s=>{if(n.has(s)||s.endsWith(".tmp"))return!0;for(let r of e)if(s.startsWith(r))return!0;return!!s.startsWith(a)}}async function $t(t){let n=new TextEncoder().encode(`blob ${t.byteLength}\0`),e=new Uint8Array(n.byteLength+t.byteLength);e.set(n,0),e.set(new Uint8Array(t),n.byteLength);let a=await crypto.subtle.digest("SHA-1",e),s=new Uint8Array(a),r="";for(let i of s)r+=i.toString(16).padStart(2,"0");return r}function Q(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(1)} MB`}async function vt(t){let n=[];async function e(a){let s=await t.app.vault.adapter.list(a||"/");for(let r of s.files)n.push(r);for(let r of s.folders)r===".trash"||r===".git"||await e(r)}return await e(""),n}async function H(t,n={}){let{pat:e,repoUrl:a,branch:s,lastCommitSha:r,fileShaMap:i}=t.settings;if(!e)throw new Error("Set a GitHub PAT in Vault Sync settings.");if(!a)throw new Error("Set a repo URL in Vault Sync settings.");if(!r)throw new Error("No baseline commit. Seed first.");let p=T(a),o=n.silent?null:new Z.Notice("Scanning local changes\u2026",0),u=await P(p,s||"main",e);if(u!==r)throw o?.hide(),new Error(`Remote advanced (${u.slice(0,7)} vs local baseline ${r.slice(0,7)}). Run 'Pull from GitHub' first.`);let g=bt(t.app.vault.configDir),d=(await vt(t)).filter(l=>!g(l)),c=new Set(d);o?.setMessage(`Hashing ${d.length} files\u2026`);let m=[],y=[],h=0;for(let l of d){let w=await t.app.vault.adapter.readBinary(l),C=await $t(w),L=i[l];L?L!==C&&m.push({path:l,kind:"modify",content:w}):m.push({path:l,kind:"add",content:w}),h++,h%50===0&&o?.setMessage(`Hashed ${h}/${d.length}\u2026`)}for(let l of Object.keys(i))c.has(l)||y.push(l);if(m.length===0&&y.length===0){o?.setMessage("Nothing to push"),setTimeout(()=>o?.hide(),4e3);return}o?.setMessage(`Uploading ${m.length} blobs (${y.length} deletions)\u2026`);let R=[],U=0,V=0;for(let l of m){let w=await D(p,l.content,e);R.push({path:l.path,mode:"100644",type:"blob",sha:w}),U++,V+=l.content.byteLength,U%5===0&&o?.setMessage(`Uploaded ${U}/${m.length} (${Q(V)})`)}for(let l of y)R.push({path:l,mode:"100644",type:"blob",sha:null});o?.setMessage("Creating tree\u2026");let tt=await O(p,r,e),et=await W(p,tt,R,e);o?.setMessage("Creating commit\u2026");let st=`Vault Sync: ${m.length} changed, ${y.length} deleted from mobile`,k=await J(p,st,et,[r],e);o?.setMessage("Updating branch\u2026");let A=await K(p,s||"main",k,e);if(!A.ok)throw o?.hide(),new Error(`Branch update failed (${A.status}): ${A.message}. Pull and retry.`);for(let l of m){let w=R.find(C=>C.path===l.path&&C.sha!==null);w&&w.sha&&(i[l.path]=w.sha)}for(let l of y)delete i[l];t.settings.lastCommitSha=k,t.settings.fileShaMap=i,await t.saveSettings(),o?.setMessage(`Pushed ${m.length} changes (${Q(V)}) \u2192 ${k.slice(0,7)}`),setTimeout(()=>o?.hide(),6e3)}var M=class extends E.Plugin{constructor(){super(...arguments);this.autoSyncTimer=null;this.autoSyncRunning=!1;this.statusBar=null}async onload(){await this.loadSettings(),this.addSettingTab(new x(this.app,this)),this.statusBar=this.addStatusBarItem(),this.updateStatusBar("idle"),this.addRibbonIcon("refresh-cw","Sync vault now",()=>{this.runWithErrorNotice("sync",()=>this.runSync(!1))}),this.addCommand({id:"seed-from-github",name:"Seed from GitHub",callback:()=>this.runWithErrorNotice("seed",()=>X(this))}),this.addCommand({id:"pull-from-github",name:"Pull from GitHub",callback:()=>this.runWithErrorNotice("pull",()=>F(this))}),this.addCommand({id:"push-to-github",name:"Push to GitHub",callback:()=>this.runWithErrorNotice("push",()=>H(this))}),this.addCommand({id:"sync-now",name:"Sync now (pull then push)",callback:()=>this.runWithErrorNotice("sync",()=>this.runSync(!1))}),this.scheduleAutoSync()}updateStatusBar(e,a){if(!this.statusBar)return;let s=this.settings.lastCommitSha?this.settings.lastCommitSha.slice(0,7):"(unseeded)",r=e==="syncing"?"\u27F3":e==="error"?"\u26A0":"\u2713";this.statusBar.setText(`${r} Vault Sync ${s}${a?` \u2014 ${a}`:""}`)}onunload(){this.autoSyncTimer!==null&&(window.clearInterval(this.autoSyncTimer),this.autoSyncTimer=null)}async runWithErrorNotice(e,a){try{await a()}catch(s){let r=s instanceof Error?s.message:String(s);console.error(`[Vault Sync] ${e} failed`,s),new E.Notice(`Vault Sync ${e} failed: ${r}`,1e4)}}async runSync(e){if(!this.autoSyncRunning){this.autoSyncRunning=!0,this.updateStatusBar("syncing");try{await F(this,{silent:e}),await H(this,{silent:e}),this.updateStatusBar("idle")}catch(a){throw this.updateStatusBar("error",a instanceof Error?a.message.slice(0,40):""),a}finally{this.autoSyncRunning=!1}}}scheduleAutoSync(){this.autoSyncTimer!==null&&(window.clearInterval(this.autoSyncTimer),this.autoSyncTimer=null);let e=this.settings.autoSyncIntervalMinutes??0;if(e<=0)return;let a=e*60*1e3;this.autoSyncTimer=window.setInterval(()=>{this.runSync(!0).catch(s=>{let r=s instanceof Error?s.message:String(s);console.error("[Vault Sync] auto-sync failed",s),new E.Notice(`Vault Sync auto-sync failed: ${r}`,1e4)})},a)}async loadSettings(){let e=await this.loadData()??{};this.settings=Object.assign({},j,e)}async saveSettings(){await this.saveData(this.settings)}};