mirror of
https://github.com/andrewboldi/obsidian-vault-sync.git
synced 2026-07-22 06:53:35 +00:00
Address ObsidianReviewBot feedback for community plugin submission
- settings: replace HTML heading with Setting().setHeading() pattern; sentence-case "GitHub personal access token" - push: read configDir from Vault instead of hardcoding ".obsidian" - pull/push/seed/main: drop "Vault Sync:" prefix from Notice strings (sentence case + Obsidian style) - pull/seed: omit unused catch params Bumps version to 1.0.3.
This commit is contained in:
parent
29baa452eb
commit
5f5b8d4c74
7 changed files with 55 additions and 58 deletions
2
main.js
2
main.js
File diff suppressed because one or more lines are too long
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "vault-sync-rest",
|
||||
"name": "Vault Sync (REST)",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"minAppVersion": "1.4.0",
|
||||
"description": "Two-way sync with a GitHub repo via REST API. Works on iOS for vaults of any size including images, where git-protocol plugins crash from WebView memory limits.",
|
||||
"author": "Andrew Boldi",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export default class VaultSyncPlugin extends Plugin {
|
|||
this.statusBar = this.addStatusBarItem();
|
||||
this.updateStatusBar("idle");
|
||||
|
||||
this.addRibbonIcon("refresh-cw", "Vault Sync: Sync now", () => {
|
||||
this.addRibbonIcon("refresh-cw", "Sync vault now", () => {
|
||||
void this.runWithErrorNotice("sync", () => this.runSync(false));
|
||||
});
|
||||
|
||||
|
|
|
|||
26
src/pull.ts
26
src/pull.ts
|
|
@ -18,7 +18,7 @@ async function ensureDir(plugin: VaultSyncPlugin, dirPath: string): Promise<void
|
|||
if (!dirPath || dirPath === "/" || dirPath === ".") return;
|
||||
try {
|
||||
await plugin.app.vault.adapter.mkdir(dirPath);
|
||||
} catch (_e) {
|
||||
} catch {
|
||||
/* exists */
|
||||
}
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ async function ensureParentDirs(
|
|||
async function safeRemove(plugin: VaultSyncPlugin, path: string): Promise<void> {
|
||||
try {
|
||||
await plugin.app.vault.adapter.remove(path);
|
||||
} catch (_e) {
|
||||
} catch {
|
||||
/* already gone */
|
||||
}
|
||||
}
|
||||
|
|
@ -78,35 +78,35 @@ export async function pullFromGitHub(
|
|||
if (!repoUrl) throw new Error("Set a repo URL in Vault Sync settings.");
|
||||
if (!lastCommitSha) {
|
||||
throw new Error(
|
||||
"No baseline commit recorded. Run 'Vault Sync: Seed from GitHub' first."
|
||||
"No baseline commit recorded. Run 'Seed from GitHub' first."
|
||||
);
|
||||
}
|
||||
|
||||
const ref = parseRepoUrl(repoUrl);
|
||||
const notice = opts.silent
|
||||
? null
|
||||
: new Notice("Vault Sync: checking remote…", 0);
|
||||
: new Notice("Checking remote…", 0);
|
||||
|
||||
const headSha = await getBranchSha(ref, branch || "main", pat);
|
||||
if (headSha === lastCommitSha) {
|
||||
if (notice) {
|
||||
notice?.setMessage("Vault Sync: already up to date");
|
||||
notice?.setMessage("Already up to date");
|
||||
setTimeout(() => notice?.hide(), 4000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
notice?.setMessage(`Vault Sync: comparing ${lastCommitSha.slice(0, 7)}…${headSha.slice(0, 7)}`);
|
||||
notice?.setMessage(`Comparing ${lastCommitSha.slice(0, 7)}…${headSha.slice(0, 7)}`);
|
||||
|
||||
const cmp = await compareCommits(ref, lastCommitSha, headSha, pat);
|
||||
|
||||
if (cmp.status === "diverged") {
|
||||
notice?.setMessage(
|
||||
"Vault Sync: remote diverged from local baseline. Manual resolution needed (Phase 3 push will detect this)."
|
||||
"Remote diverged from local baseline. Manual resolution needed (push will detect this)."
|
||||
);
|
||||
setTimeout(() => notice?.hide(), 10000);
|
||||
throw new Error(
|
||||
`Remote and local have diverged (ahead ${cmp.ahead_by}, behind ${cmp.behind_by}). Vault Sync can only fast-forward in Phase 2.`
|
||||
`Remote and local have diverged (ahead ${cmp.ahead_by}, behind ${cmp.behind_by}). Pull only supports fast-forward updates.`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -114,14 +114,14 @@ export async function pullFromGitHub(
|
|||
if (files.length === 0) {
|
||||
plugin.settings.lastCommitSha = headSha;
|
||||
await plugin.saveSettings();
|
||||
notice?.setMessage("Vault Sync: pull complete (no file changes)");
|
||||
notice?.setMessage("Pull complete (no file changes)");
|
||||
setTimeout(() => notice?.hide(), 4000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (files.length === 300) {
|
||||
new Notice(
|
||||
"Vault Sync: compare returned exactly 300 files (GitHub's per-call cap). Some changes may be missing — re-seed if pull seems incomplete.",
|
||||
"Compare returned exactly 300 files (GitHub's per-call cap). Some changes may be missing — re-seed if pull seems incomplete.",
|
||||
10000
|
||||
);
|
||||
}
|
||||
|
|
@ -150,7 +150,7 @@ export async function pullFromGitHub(
|
|||
}
|
||||
|
||||
notice?.setMessage(
|
||||
`Vault Sync: ${toFetch.length} to fetch, ${toRemove.length} to remove…`
|
||||
`${toFetch.length} to fetch, ${toRemove.length} to remove…`
|
||||
);
|
||||
|
||||
// Apply removals first (cheap, sequential).
|
||||
|
|
@ -171,7 +171,7 @@ export async function pullFromGitHub(
|
|||
bytes += content.byteLength;
|
||||
if (done % 5 === 0) {
|
||||
notice?.setMessage(
|
||||
`Vault Sync: ${done}/${toFetch.length} files, ${formatBytes(bytes)}`
|
||||
`${done}/${toFetch.length} files, ${formatBytes(bytes)}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
@ -181,7 +181,7 @@ export async function pullFromGitHub(
|
|||
await plugin.saveSettings();
|
||||
|
||||
notice?.setMessage(
|
||||
`Vault Sync: pull complete — ${toFetch.length} updated, ${toRemove.length} removed @ ${headSha.slice(0, 7)}`
|
||||
`Pull complete — ${toFetch.length} updated, ${toRemove.length} removed @ ${headSha.slice(0, 7)}`
|
||||
);
|
||||
setTimeout(() => notice?.hide(), 6000);
|
||||
}
|
||||
|
|
|
|||
63
src/push.ts
63
src/push.ts
|
|
@ -11,30 +11,28 @@ import {
|
|||
type TreeUpdateEntry,
|
||||
} from "./github";
|
||||
|
||||
const SKIP_PATHS = new Set<string>([
|
||||
".obsidian/workspace.json",
|
||||
".obsidian/workspace-mobile.json",
|
||||
".obsidian/cache",
|
||||
".obsidian/appearance.json",
|
||||
".DS_Store",
|
||||
"_GIT-DEBUG-ERROR.md",
|
||||
]);
|
||||
|
||||
const SKIP_PREFIXES = [
|
||||
".trash/",
|
||||
function buildSkipFn(configDir: string): (path: string) => boolean {
|
||||
const skipPaths = new Set<string>([
|
||||
`${configDir}/workspace.json`,
|
||||
`${configDir}/workspace-mobile.json`,
|
||||
`${configDir}/cache`,
|
||||
`${configDir}/appearance.json`,
|
||||
".DS_Store",
|
||||
"_GIT-DEBUG-ERROR.md",
|
||||
]);
|
||||
// Plugin installs are per-device. Each Obsidian install pulls plugin code
|
||||
// from its own source (community store, BRAT, etc). Syncing plugin folders
|
||||
// bloats the repo and risks leaking credentials stored in plugin data.json.
|
||||
// The list of *which* plugins to enable still syncs via .obsidian/community-plugins.json.
|
||||
".obsidian/plugins/",
|
||||
];
|
||||
|
||||
function shouldSkip(path: string): boolean {
|
||||
if (SKIP_PATHS.has(path)) return true;
|
||||
if (path.endsWith(".tmp")) return true;
|
||||
for (const p of SKIP_PREFIXES) if (path.startsWith(p)) return true;
|
||||
if (path.startsWith(".obsidian/workspace")) return true;
|
||||
return false;
|
||||
// The list of *which* plugins to enable still syncs via community-plugins.json.
|
||||
const skipPrefixes = [".trash/", `${configDir}/plugins/`];
|
||||
const workspacePrefix = `${configDir}/workspace`;
|
||||
return (path: string) => {
|
||||
if (skipPaths.has(path)) return true;
|
||||
if (path.endsWith(".tmp")) return true;
|
||||
for (const p of skipPrefixes) if (path.startsWith(p)) return true;
|
||||
if (path.startsWith(workspacePrefix)) return true;
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
async function gitBlobSha(content: ArrayBuffer): Promise<string> {
|
||||
|
|
@ -86,23 +84,24 @@ export async function pushToGitHub(
|
|||
const ref = parseRepoUrl(repoUrl);
|
||||
const notice = opts.silent
|
||||
? null
|
||||
: new Notice("Vault Sync: scanning local changes…", 0);
|
||||
: new Notice("Scanning local changes…", 0);
|
||||
|
||||
// 1. Detect divergence — refuse if remote moved.
|
||||
const remoteHead = await getBranchSha(ref, branch || "main", pat);
|
||||
if (remoteHead !== lastCommitSha) {
|
||||
notice?.hide();
|
||||
throw new Error(
|
||||
`Remote advanced (${remoteHead.slice(0, 7)} vs local baseline ${lastCommitSha.slice(0, 7)}). Run 'Vault Sync: Pull from GitHub' first.`
|
||||
`Remote advanced (${remoteHead.slice(0, 7)} vs local baseline ${lastCommitSha.slice(0, 7)}). Run 'Pull from GitHub' first.`
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Walk vault, hash every non-skipped file, compare to fileShaMap.
|
||||
const shouldSkip = buildSkipFn(plugin.app.vault.configDir);
|
||||
const allPaths = await listAllVaultFiles(plugin);
|
||||
const localFiles = allPaths.filter((p) => !shouldSkip(p));
|
||||
const localSet = new Set(localFiles);
|
||||
|
||||
notice?.setMessage(`Vault Sync: hashing ${localFiles.length} files…`);
|
||||
notice?.setMessage(`Hashing ${localFiles.length} files…`);
|
||||
|
||||
type Change = { path: string; kind: "add" | "modify"; content: ArrayBuffer };
|
||||
const changes: Change[] = [];
|
||||
|
|
@ -121,7 +120,7 @@ export async function pushToGitHub(
|
|||
scanned++;
|
||||
if (scanned % 50 === 0) {
|
||||
notice?.setMessage(
|
||||
`Vault Sync: hashed ${scanned}/${localFiles.length}…`
|
||||
`Hashed ${scanned}/${localFiles.length}…`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -131,14 +130,14 @@ export async function pushToGitHub(
|
|||
}
|
||||
|
||||
if (changes.length === 0 && deletions.length === 0) {
|
||||
notice?.setMessage("Vault Sync: nothing to push");
|
||||
notice?.setMessage("Nothing to push");
|
||||
setTimeout(() => notice?.hide(), 4000);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Upload blobs for changed files (sequential to keep memory bounded for large files).
|
||||
notice?.setMessage(
|
||||
`Vault Sync: uploading ${changes.length} blobs (${deletions.length} deletions)…`
|
||||
`Uploading ${changes.length} blobs (${deletions.length} deletions)…`
|
||||
);
|
||||
|
||||
const treeEntries: TreeUpdateEntry[] = [];
|
||||
|
|
@ -156,7 +155,7 @@ export async function pushToGitHub(
|
|||
bytes += c.content.byteLength;
|
||||
if (uploaded % 5 === 0) {
|
||||
notice?.setMessage(
|
||||
`Vault Sync: uploaded ${uploaded}/${changes.length} (${formatBytes(bytes)})`
|
||||
`Uploaded ${uploaded}/${changes.length} (${formatBytes(bytes)})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -166,12 +165,12 @@ export async function pushToGitHub(
|
|||
}
|
||||
|
||||
// 4. Build new tree on top of the last commit's tree.
|
||||
notice?.setMessage("Vault Sync: creating tree…");
|
||||
notice?.setMessage("Creating tree…");
|
||||
const baseTreeSha = await getCommitTreeSha(ref, lastCommitSha, pat);
|
||||
const newTreeSha = await createTree(ref, baseTreeSha, treeEntries, pat);
|
||||
|
||||
// 5. Create commit.
|
||||
notice?.setMessage("Vault Sync: creating commit…");
|
||||
notice?.setMessage("Creating commit…");
|
||||
const message = `Vault Sync: ${changes.length} changed, ${deletions.length} deleted from mobile`;
|
||||
const newCommitSha = await createCommit(
|
||||
ref,
|
||||
|
|
@ -182,7 +181,7 @@ export async function pushToGitHub(
|
|||
);
|
||||
|
||||
// 6. Update branch ref.
|
||||
notice?.setMessage("Vault Sync: updating branch…");
|
||||
notice?.setMessage("Updating branch…");
|
||||
const upd = await updateRef(ref, branch || "main", newCommitSha, pat);
|
||||
if (!upd.ok) {
|
||||
notice?.hide();
|
||||
|
|
@ -204,7 +203,7 @@ export async function pushToGitHub(
|
|||
await plugin.saveSettings();
|
||||
|
||||
notice?.setMessage(
|
||||
`Vault Sync: pushed ${changes.length} changes (${formatBytes(bytes)}) → ${newCommitSha.slice(0, 7)}`
|
||||
`Pushed ${changes.length} changes (${formatBytes(bytes)}) → ${newCommitSha.slice(0, 7)}`
|
||||
);
|
||||
setTimeout(() => notice?.hide(), 6000);
|
||||
}
|
||||
|
|
|
|||
12
src/seed.ts
12
src/seed.ts
|
|
@ -19,7 +19,7 @@ async function ensureDir(plugin: VaultSyncPlugin, dirPath: string): Promise<void
|
|||
if (!dirPath || dirPath === "/" || dirPath === ".") return;
|
||||
try {
|
||||
await plugin.app.vault.adapter.mkdir(dirPath);
|
||||
} catch (_e) {
|
||||
} catch {
|
||||
// already exists
|
||||
}
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ export async function seedFromGitHub(plugin: VaultSyncPlugin): Promise<void> {
|
|||
if (!repoUrl) throw new Error("Set a repo URL in Vault Sync settings.");
|
||||
|
||||
const ref = parseRepoUrl(repoUrl);
|
||||
const notice = new Notice("Vault Sync: resolving branch…", 0);
|
||||
const notice = new Notice("Resolving branch…", 0);
|
||||
|
||||
let sha: string;
|
||||
try {
|
||||
|
|
@ -83,7 +83,7 @@ export async function seedFromGitHub(plugin: VaultSyncPlugin): Promise<void> {
|
|||
throw e;
|
||||
}
|
||||
|
||||
notice.setMessage(`Vault Sync: listing ${ref.owner}/${ref.repo}@${sha.slice(0, 7)}…`);
|
||||
notice.setMessage(`Listing ${ref.owner}/${ref.repo}@${sha.slice(0, 7)}…`);
|
||||
|
||||
let tree: TreeEntry[];
|
||||
try {
|
||||
|
|
@ -97,7 +97,7 @@ export async function seedFromGitHub(plugin: VaultSyncPlugin): Promise<void> {
|
|||
const blobs = tree.filter((e) => e.type === "blob");
|
||||
|
||||
notice.setMessage(
|
||||
`Vault Sync: ${blobs.length} files, ${dirs.length} dirs — preparing…`
|
||||
`${blobs.length} files, ${dirs.length} dirs — preparing…`
|
||||
);
|
||||
|
||||
// Pre-create all directories sequentially (cheap, prevents race conditions).
|
||||
|
|
@ -118,7 +118,7 @@ export async function seedFromGitHub(plugin: VaultSyncPlugin): Promise<void> {
|
|||
byteCount += content.byteLength;
|
||||
if (fileCount % PROGRESS_EVERY === 0) {
|
||||
notice.setMessage(
|
||||
`Vault Sync: ${fileCount}/${blobs.length} files, ${formatBytes(byteCount)}`
|
||||
`${fileCount}/${blobs.length} files, ${formatBytes(byteCount)}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
@ -128,7 +128,7 @@ export async function seedFromGitHub(plugin: VaultSyncPlugin): Promise<void> {
|
|||
await plugin.saveSettings();
|
||||
|
||||
notice.setMessage(
|
||||
`Vault Sync: seed complete — ${fileCount} files, ${formatBytes(byteCount)} @ ${sha.slice(0, 7)}`
|
||||
`Seed complete — ${fileCount} files, ${formatBytes(byteCount)} @ ${sha.slice(0, 7)}`
|
||||
);
|
||||
setTimeout(() => notice.hide(), 6000);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,10 +31,8 @@ export class VaultSyncSettingTab extends PluginSettingTab {
|
|||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl("h2", { text: "Vault Sync (REST)" });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("GitHub Personal Access Token")
|
||||
.setName("GitHub personal access token")
|
||||
.setDesc(
|
||||
"Fine-grained or classic PAT with repo (contents: read/write) scope on the target repo."
|
||||
)
|
||||
|
|
@ -50,7 +48,7 @@ export class VaultSyncSettingTab extends PluginSettingTab {
|
|||
|
||||
new Setting(containerEl)
|
||||
.setName("Repository URL")
|
||||
.setDesc("e.g. https://github.com/owner/repo")
|
||||
.setDesc("For example, https://github.com/owner/repo")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("https://github.com/owner/repo")
|
||||
|
|
|
|||
Loading…
Reference in a new issue