diff options
Diffstat (limited to 'apps/cli/src/commands/migrate.ts')
| -rw-r--r-- | apps/cli/src/commands/migrate.ts | 281 |
1 files changed, 166 insertions, 115 deletions
diff --git a/apps/cli/src/commands/migrate.ts b/apps/cli/src/commands/migrate.ts index 48343992..750daf61 100644 --- a/apps/cli/src/commands/migrate.ts +++ b/apps/cli/src/commands/migrate.ts @@ -71,6 +71,15 @@ export const migrateCmd = new Command() ) .requiredOption("--dest-api-key <key>", "API key for the destination server") .option("-y, --yes", "skip confirmation prompt") + .option("--exclude-assets", "exclude assets (skip asset bookmarks)") + .option("--exclude-lists", "exclude lists and list membership") + .option("--exclude-ai-prompts", "exclude AI prompts") + .option("--exclude-rules", "exclude rule engine rules") + .option("--exclude-feeds", "exclude RSS feeds") + .option("--exclude-webhooks", "exclude webhooks") + .option("--exclude-bookmarks", "exclude bookmarks migration") + .option("--exclude-tags", "exclude tags migration") + .option("--exclude-user-settings", "exclude user settings migration") .option( "--batch-size <n>", `number of bookmarks per page (max ${MAX_NUM_BOOKMARKS_PER_PAGE})`, @@ -118,130 +127,163 @@ export const migrateCmd = new Command() } // 1) User settings - stepStart("Migrating user settings"); - await migrateUserSettings(src, dest); - stepEndSuccess(); + if (!opts.excludeUserSettings) { + stepStart("Migrating user settings"); + await migrateUserSettings(src, dest); + stepEndSuccess(); + } // 2) Lists (and mapping) - stepStart("Migrating lists"); - const listsStart = Date.now(); - const { - lists, - listIdMap, - createdCount: listsCreated, - } = await migrateLists(src, dest, (created, alreadyExists, total) => { - progressUpdate("Lists (created)", created + alreadyExists, total); - }); - progressDone(); - stepEndSuccess( - `${listsCreated} created in ${Math.round((Date.now() - listsStart) / 1000)}s`, - ); + let lists: ZBookmarkList[] = []; + let listIdMap = new Map<string, string>(); + if (!opts.excludeLists) { + stepStart("Migrating lists"); + const listsStart = Date.now(); + const listsRes = await migrateLists( + src, + dest, + (created, alreadyExists, total) => { + progressUpdate("Lists (created)", created + alreadyExists, total); + }, + ); + lists = listsRes.lists; + listIdMap = listsRes.listIdMap; + progressDone(); + stepEndSuccess( + `${listsRes.createdCount} created in ${Math.round((Date.now() - listsStart) / 1000)}s`, + ); + } // 3) Feeds - stepStart("Migrating feeds"); - const feedsStart = Date.now(); - const { idMap: feedIdMap, count: feedsCount } = await migrateFeeds( - src, - dest, - (created, total) => { + let feedIdMap = new Map<string, string>(); + if (!opts.excludeFeeds) { + stepStart("Migrating feeds"); + const feedsStart = Date.now(); + const res = await migrateFeeds(src, dest, (created, total) => { progressUpdate("Feeds", created, total); - }, - ); - progressDone(); - stepEndSuccess( - `${feedsCount} migrated in ${Math.round((Date.now() - feedsStart) / 1000)}s`, - ); + }); + feedIdMap = res.idMap; + progressDone(); + stepEndSuccess( + `${res.count} migrated in ${Math.round((Date.now() - feedsStart) / 1000)}s`, + ); + } // 4) AI settings (custom prompts) - stepStart("Migrating AI prompts"); - const promptsStart = Date.now(); - const promptsCount = await migratePrompts(src, dest, (created, total) => { - progressUpdate("Prompts", created, total); - }); - progressDone(); - stepEndSuccess( - `${promptsCount} migrated in ${Math.round((Date.now() - promptsStart) / 1000)}s`, - ); + if (!opts.excludeAiPrompts) { + stepStart("Migrating AI prompts"); + const promptsStart = Date.now(); + const promptsCount = await migratePrompts( + src, + dest, + (created, total) => { + progressUpdate("Prompts", created, total); + }, + ); + progressDone(); + stepEndSuccess( + `${promptsCount} migrated in ${Math.round((Date.now() - promptsStart) / 1000)}s`, + ); + } // 5) Webhooks (tokens cannot be read; created without token) - stepStart("Migrating webhooks"); - const webhooksStart = Date.now(); - const webhooksCount = await migrateWebhooks( - src, - dest, - (created, total) => { - progressUpdate("Webhooks", created, total); - }, - ); - progressDone(); - stepEndSuccess( - `${webhooksCount} migrated in ${Math.round((Date.now() - webhooksStart) / 1000)}s`, - ); + if (!opts.excludeWebhooks) { + stepStart("Migrating webhooks"); + const webhooksStart = Date.now(); + const webhooksCount = await migrateWebhooks( + src, + dest, + (created, total) => { + progressUpdate("Webhooks", created, total); + }, + ); + progressDone(); + stepEndSuccess( + `${webhooksCount} migrated in ${Math.round((Date.now() - webhooksStart) / 1000)}s`, + ); + } // 6) Tags (build id map for rules) - stepStart("Ensuring tags on destination"); - const tagsStart = Date.now(); - const { idMap: tagIdMap, count: tagsCount } = await migrateTags( - src, - dest, - (ensured, total) => { + let tagIdMap = new Map<string, string>(); + if (!opts.excludeTags) { + stepStart("Ensuring tags on destination"); + const tagsStart = Date.now(); + const res = await migrateTags(src, dest, (ensured, total) => { progressUpdate("Tags", ensured, total); - }, - ); - progressDone(); - stepEndSuccess( - `${tagsCount} ensured in ${Math.round((Date.now() - tagsStart) / 1000)}s`, - ); + }); + tagIdMap = res.idMap; + progressDone(); + stepEndSuccess( + `${res.count} ensured in ${Math.round((Date.now() - tagsStart) / 1000)}s`, + ); + } // 7) Rules (requires tag/list/feed id maps) - stepStart("Migrating rule engine rules"); - const rulesStart = Date.now(); - const rulesCount = await migrateRules( - src, - dest, - { tagIdMap, listIdMap, feedIdMap }, - (created, total) => { - progressUpdate("Rules", created, total); - }, - ); - progressDone(); - stepEndSuccess( - `${rulesCount} migrated in ${Math.round((Date.now() - rulesStart) / 1000)}s`, - ); + if ( + !opts.excludeRules && + !opts.excludeLists && + !opts.excludeFeeds && + !opts.excludeTags + ) { + stepStart("Migrating rule engine rules"); + const rulesStart = Date.now(); + const rulesCount = await migrateRules( + src, + dest, + { tagIdMap, listIdMap, feedIdMap }, + (created, total) => { + progressUpdate("Rules", created, total); + }, + ); + progressDone(); + stepEndSuccess( + `${rulesCount} migrated in ${Math.round((Date.now() - rulesStart) / 1000)}s`, + ); + } // 8) Bookmarks (with list membership + tags) - stepStart("Building list membership for bookmarks"); - const blmStart = Date.now(); - const { bookmarkListsMap, scannedLists } = - await buildBookmarkListMembership(src, lists, (processed, total) => { - progressUpdate("Scanning lists", processed, total); + let bookmarkListsMap = new Map<string, string[]>(); + if (!opts.excludeLists && !opts.excludeBookmarks) { + stepStart("Building list membership for bookmarks"); + const blmStart = Date.now(); + const res = await buildBookmarkListMembership( + src, + lists, + (processed, total) => { + progressUpdate("Scanning lists", processed, total); + }, + ); + bookmarkListsMap = res.bookmarkListsMap; + progressDone(); + stepEndSuccess( + `${res.scannedLists} lists scanned in ${Math.round((Date.now() - blmStart) / 1000)}s`, + ); + } + if (!opts.excludeBookmarks) { + stepStart("Migrating bookmarks"); + const bmStart = Date.now(); + const res = await migrateBookmarks(src, dest, { + pageSize: Number(opts.batchSize) || 50, + listIdMap, + bookmarkListsMap, + total: totalBookmarks, + onProgress: (migrated, skipped, total) => { + const suffix = + skipped > 0 ? `(skipped ${skipped} assets)` : undefined; + progressUpdate("Bookmarks", migrated, total, suffix); + }, + srcServer: globals.serverAddr, + srcApiKey: globals.apiKey, + destServer: opts.destServer, + destApiKey: opts.destApiKey, + excludeAssets: !!opts.excludeAssets, + excludeLists: !!opts.excludeLists, }); - progressDone(); - stepEndSuccess( - `${scannedLists} lists scanned in ${Math.round((Date.now() - blmStart) / 1000)}s`, - ); - - stepStart("Migrating bookmarks"); - const bmStart = Date.now(); - const res = await migrateBookmarks(src, dest, { - pageSize: Number(opts.batchSize) || 50, - listIdMap, - bookmarkListsMap, - total: totalBookmarks, - onProgress: (migrated, skipped, total) => { - const suffix = - skipped > 0 ? `(skipped ${skipped} assets)` : undefined; - progressUpdate("Bookmarks", migrated, total, suffix); - }, - srcServer: globals.serverAddr, - srcApiKey: globals.apiKey, - destServer: opts.destServer, - destApiKey: opts.destApiKey, - }); - progressDone(); - stepEndSuccess( - `${res.migrated} migrated${res.skippedAssets ? `, ${res.skippedAssets} skipped` : ""} in ${Math.round((Date.now() - bmStart) / 1000)}s`, - ); + progressDone(); + stepEndSuccess( + `${res.migrated} migrated${res.skippedAssets ? `, ${res.skippedAssets} skipped` : ""} in ${Math.round((Date.now() - bmStart) / 1000)}s`, + ); + } printStatusMessage(true, "Migration completed successfully"); } catch (error) { @@ -629,6 +671,8 @@ async function migrateBookmarks( srcApiKey: string; destServer: string; destApiKey: string; + excludeAssets: boolean; + excludeLists: boolean; }, ) { let cursor: ZCursor | null = null; @@ -674,6 +718,11 @@ async function migrateBookmarks( break; } case BookmarkTypes.ASSET: { + if (opts.excludeAssets) { + // Skip migrating asset bookmarks when excluded + skippedAssets++; + continue; + } // Download from source and re-upload to destination try { const downloadResp = await fetch( @@ -746,14 +795,16 @@ async function migrateBookmarks( } // Add to lists (map src -> dest list ids) - const srcListIds = opts.bookmarkListsMap.get(b.id) ?? []; - for (const srcListId of srcListIds) { - const destListId = opts.listIdMap.get(srcListId); - if (destListId) { - await dest.lists.addToList.mutate({ - listId: destListId, - bookmarkId: createdId!, - }); + if (!opts.excludeLists) { + const srcListIds = opts.bookmarkListsMap.get(b.id) ?? []; + for (const srcListId of srcListIds) { + const destListId = opts.listIdMap.get(srcListId); + if (destListId) { + await dest.lists.addToList.mutate({ + listId: destListId, + bookmarkId: createdId!, + }); + } } } migrated++; |
