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
JavaScript
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