UNPKG

payload-wordpress-migrator

Version:

A PayloadCMS plugin for WordPress migration - migrate and manage WordPress content directly in your Payload admin dashboard

454 lines (453 loc) 18.5 kB
let pluginOptionsGlobal = null; // Simple cache invalidation tracking let lastCacheInvalidation = Date.now(); const invalidateLocalCache = ()=>{ lastCacheInvalidation = Date.now(); }; export const getPluginOptions = ()=>pluginOptionsGlobal; export const getLastCacheInvalidation = ()=>lastCacheInvalidation; // Export utility functions for easier configuration // export { createDefaultSEOFieldMappings } from './utils/contentTransformer.js' // TODO: Implement this function export const payloadWordPressMigrator = (pluginOptions)=>(config)=>{ pluginOptionsGlobal = pluginOptions; if (!config.collections) { config.collections = []; } // Add WordPress Migration collection (migration jobs only - site config handled in dashboard) config.collections.push({ slug: 'wordpress-migration', access: { create: ()=>true, delete: async ({ id, req })=>{ try { // Check if job is running before allowing deletion if (id) { const job = await req.payload.findByID({ id: id, collection: 'wordpress-migration' }); if (job?.status === 'running') { return false; } } return true; } catch (error) { console.error('Error checking job status for deletion:', error); return true // Allow deletion if we can't check status ; } }, read: ()=>true, update: ()=>true }, admin: { components: { beforeList: [ 'payload-wordpress-migrator/rsc#MigrationDashboardServer' ] }, group: 'Migration', useAsTitle: 'jobName' }, fields: [ { name: 'jobName', type: 'text', label: 'Migration Job Name', required: true }, { name: 'contentType', type: 'text', admin: { components: { Field: 'payload-wordpress-migrator/client#ContentTypeSelect' } }, label: 'Content Type to Migrate', required: true }, { name: 'targetCollection', type: 'select', admin: { description: 'The Payload collection where migrated content will be stored' }, label: 'Target Payload Collection', options: (()=>{ const collectionLabelMap = { categories: 'Categories', media: 'Media', pages: 'Pages', posts: 'Posts', tags: 'Tags', users: 'Users' }; return config.collections?.filter((collection)=>{ // Filter out system collections and the wordpress-migration collection itself return collection.slug !== 'wordpress-migration' && collection.slug !== 'payload-preferences' && collection.slug !== 'payload-migrations' && !collection.slug.startsWith('payload-'); }).map((collection)=>({ label: collectionLabelMap[collection.slug] || collection.slug.charAt(0).toUpperCase() + collection.slug.slice(1), value: collection.slug })).sort((a, b)=>a.label.localeCompare(b.label)) || []; })(), required: true }, { name: 'status', type: 'select', admin: { readOnly: true }, defaultValue: 'ready', options: [ { label: 'Ready to Start', value: 'ready' }, { label: 'Running', value: 'running' }, { label: 'Completed', value: 'completed' }, { label: 'Failed', value: 'failed' }, { label: 'Paused', value: 'paused' } ] }, { name: 'progress', type: 'group', admin: { hidden: true }, fields: [ { name: 'totalItems', type: 'number', admin: { readOnly: true }, defaultValue: 0 }, { name: 'processedItems', type: 'number', admin: { readOnly: true }, defaultValue: 0 }, { name: 'successfulItems', type: 'number', admin: { readOnly: true }, defaultValue: 0 }, { name: 'failedItems', type: 'number', admin: { readOnly: true }, defaultValue: 0 }, { name: 'failedItemIds', type: 'array', admin: { readOnly: true }, fields: [ { name: 'wpId', type: 'number' }, { name: 'error', type: 'text' } ] } ] }, { name: 'configuration', type: 'group', fields: [ { name: 'batchSize', type: 'number', defaultValue: 10, label: 'Batch Size', max: 100, min: 1 }, { name: 'enableBlocks', type: 'checkbox', defaultValue: true, label: 'Convert Gutenberg blocks' }, { name: 'fieldMapping', type: 'json', admin: { components: { Field: 'payload-wordpress-migrator/client#SimpleFieldMapping' } }, label: 'Field Mapping' } ] }, { name: 'logs', type: 'array', admin: { hidden: true }, fields: [ { name: 'timestamp', type: 'date', admin: { readOnly: true }, defaultValue: ()=>new Date() }, { name: 'level', type: 'select', options: [ { label: 'Info', value: 'info' }, { label: 'Warning', value: 'warning' }, { label: 'Error', value: 'error' } ] }, { name: 'message', type: 'textarea' } ] } ], hooks: { afterChange: [ ({ doc, operation })=>{ // Invalidate cache when jobs are created, updated, or deleted invalidateLocalCache(); // Notify dashboard through localStorage event try { const event = { type: `migration-job-${operation}`, jobId: doc.id, timestamp: Date.now() }; localStorage.setItem('migration-event', JSON.stringify(event)); // Remove the event after a short delay to trigger storage event setTimeout(()=>{ localStorage.removeItem('migration-event'); }, 100); } catch (error) { // Ignore localStorage errors } } ], afterDelete: [ ({ doc })=>{ // Invalidate cache when jobs are deleted invalidateLocalCache(); // Notify dashboard through localStorage event try { const event = { type: 'migration-job-deleted', jobId: doc.id, timestamp: Date.now() }; localStorage.setItem('migration-event', JSON.stringify(event)); // Remove the event after a short delay to trigger storage event setTimeout(()=>{ localStorage.removeItem('migration-event'); }, 100); } catch (error) { // Ignore localStorage errors } } ] }, labels: { plural: 'WordPress Migrations', singular: 'WordPress Migration' } }); // Add fields to specified collections if (pluginOptions.collections) { for(const collectionSlug in pluginOptions.collections){ const collection = config.collections.find((collection)=>collection.slug === collectionSlug); if (collection) { collection.fields.push({ name: 'migratedFromWordPress', type: 'group', admin: { hidden: true }, fields: [ { name: 'wpPostId', type: 'number', admin: { readOnly: true }, label: 'WordPress Post ID' }, { name: 'wpPostType', type: 'text', admin: { readOnly: true }, label: 'WordPress Post Type' }, { name: 'migrationDate', type: 'date', admin: { readOnly: true }, label: 'Migration Date' } ] }); } } } /** * If the plugin is disabled, we still want to keep added collections/fields so the database schema is consistent which is important for migrations. */ if (pluginOptions.disabled) { return config; } if (!config.endpoints) { config.endpoints = []; } // Add WordPress API endpoints config.endpoints.push({ handler: async (req)=>{ const { wordpressConnectionHandler } = await import('./utils/wordpressApi.js'); return wordpressConnectionHandler(req, pluginOptions); }, method: 'post', path: '/wordpress/test-connection' }); config.endpoints.push({ handler: async (req)=>{ const { migrationSummaryHandler } = await import('./utils/wordpressApi.js'); return migrationSummaryHandler(req, pluginOptions); }, method: 'get', path: '/wordpress/migration-summary' }); config.endpoints.push({ handler: async (req)=>{ const { migrationJobHandler } = await import('./utils/wordpressApi.js'); return migrationJobHandler(req, pluginOptions); }, method: 'get', path: '/wordpress/migration-jobs' }); config.endpoints.push({ handler: async (req)=>{ const { migrationJobHandler } = await import('./utils/wordpressApi.js'); return migrationJobHandler(req, pluginOptions); }, method: 'post', path: '/wordpress/migration-jobs' }); config.endpoints.push({ handler: async (req)=>{ const { migrationJobHandler } = await import('./utils/wordpressApi.js'); return migrationJobHandler(req, pluginOptions); }, method: 'put', path: '/wordpress/migration-jobs' }); config.endpoints.push({ handler: async (req)=>{ const { discoverWordPressContent } = await import('./utils/wordpressApi.js'); return discoverWordPressContent(req, pluginOptions); }, method: 'post', path: '/wordpress/discover-content' }); config.endpoints.push({ handler: async (req)=>{ const { migrationJobHandler } = await import('./utils/wordpressApi.js'); return migrationJobHandler(req, pluginOptions); }, method: 'delete', path: '/wordpress/migration-jobs' }); config.endpoints.push({ handler: async (req)=>{ const { fetchWordPressContentFields } = await import('./utils/wordpressApi.js'); return fetchWordPressContentFields(req, pluginOptions); }, method: 'post', path: '/wordpress/content-fields' }); config.endpoints.push({ handler: async (req)=>{ const { fetchPayloadCollectionFields } = await import('./utils/wordpressApi.js'); return fetchPayloadCollectionFields(req); }, method: 'get', path: '/collections/:slug/fields' }); const incomingOnInit = config.onInit; config.onInit = async (payload)=>{ // Ensure we are executing any existing onInit functions before running our own. if (incomingOnInit) { await incomingOnInit(payload); } // Auto-sync WordPress sites if enabled if (pluginOptions.enableAutoSync) { try { const { docs: readyJobs } = await payload.find({ collection: 'wordpress-migration', limit: 5, where: { status: { equals: 'ready' } } }); if (readyJobs.length > 0) { // Could implement auto-start logic here if desired } } catch (error) { console.error('Error checking for ready migrations:', error); } } }; return config; }; //# sourceMappingURL=index.js.map