import { editor, space, system } from "@silverbulletmd/silverbullet/syscalls"; import { listFilesCached, readFile } from "./federation.ts"; import { parsePageRef } from "@silverbulletmd/silverbullet/lib/page_ref"; import { federatedPathToLocalPath, wildcardPathToRegex } from "./util.ts"; import type { LibraryDef } from "@silverbulletmd/silverbullet/type/config"; export async function updateLibrariesCommand() { if ( await editor.confirm( "Are you sure you want to update all libraries?", ) ) { await editor.flashNotification("Updating all libraries..."); const updateStats: UpdateStats = await system.invokeFunction( "federation.updateLibraries", ); await editor.reloadConfigAndCommands(); await editor.flashNotification( `Updated ${updateStats.libraries} libraries containing a total of ${updateStats.items} items.`, ); } } type UpdateStats = { libraries: number; items: number; }; // Run on the server for efficiency and CORS avoidance export async function updateLibraries(): Promise { const updateStats: UpdateStats = { libraries: 0, items: 0 }; const libraries = ((await system.reloadConfig())?.libraries || []) as LibraryDef[]; console.log("Libraries", await system.getSpaceConfig()); for (const lib of libraries) { // Handle deprecated 'source' field if (lib.source) { lib.import = lib.source; } if (!lib.import) { console.warn("Library source not set, skipping", lib); continue; } const pageUri = parsePageRef(lib.import).page; if (!pageUri.startsWith("!")) { console.warn( "Library source must be a federated page, skipping", pageUri, ); continue; } console.log("Now updating library", pageUri); const localLibraryPath = federatedPathToLocalPath(pageUri); // Sanity check the `source` pattern to avoid disaster (like wiping out the whole space) if (!/^Library\/.+/.test(localLibraryPath)) { console.warn( "Skipping library", pageUri, "as it does not start with Library/", ); continue; } // Fetch new list of pages let newPages = await listFilesCached(pageUri, true); console.log("All pages", newPages.length); if (lib.exclude) { for (const exclude of lib.exclude) { const excludeUri = parsePageRef(exclude).page; const excludeRegex = wildcardPathToRegex(excludeUri + ".md"); newPages = newPages.filter((p) => { if (excludeRegex.test(p.name)) { console.info("Excluding", p.name); return false; } return true; }); } } // Compile existing page list in local space (to be removed) const localPages = await space.listPages(); const localSourceRegex = wildcardPathToRegex(localLibraryPath); // Remove pages that match the source pattern, but in their "local" form const pagesToRemove = localPages.filter((p) => localSourceRegex.test(p.name) ); console.log("Pages to remove", pagesToRemove.length); for (const page of pagesToRemove) { console.info("Deleting", page.name); await space.deletePage(page.name); } // Import the new pages for (const page of newPages) { console.info("Importing", page.name); // Fetch the file const buf = (await readFile(page.name)).data; // Write to local space await space.writeFile(federatedPathToLocalPath(page.name), buf); updateStats.items++; } updateStats.libraries++; console.log("Done with library", pageUri); } return updateStats; }