From 41c4d8bdcb1d56340c76dfb564b4c580d939babe Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Mon, 1 Sep 2025 20:56:51 +0200 Subject: [PATCH] CRITICAL FIX: Move hook registration to config phase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Hooks were being registered too late (in onInit) - PayloadCMS doesn't honor hooks registered after initialization - Move hook registration to config phase using applyHooksToCollections() - Use global executor registry to make WorkflowExecutor available to config-phase hooks - Add aggressive debugging to trace hook execution - This should resolve the core issue where hooks were registered but never called ๐Ÿค– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- hook-verification-test.js | 130 ++++++++++++++++++++++++++++++++++++++ src/plugin/index.ts | 101 +++++++++++++++++++++++++++-- 2 files changed, 226 insertions(+), 5 deletions(-) create mode 100644 hook-verification-test.js diff --git a/hook-verification-test.js b/hook-verification-test.js new file mode 100644 index 0000000..6222cba --- /dev/null +++ b/hook-verification-test.js @@ -0,0 +1,130 @@ +// Hook verification test - run this in your PayloadCMS environment +// This will help identify why registered hooks aren't executing + +console.log('๐Ÿ” === HOOK VERIFICATION TEST ===') + +console.log(` +Add this code to your PayloadCMS environment after initialization: + +const verifyHooks = async () => { + console.log('๐Ÿ” === HOOK VERIFICATION DIAGNOSTIC ===') + + // 1. Check if hooks are still registered + const ordersCollection = payload.collections.orders + const hooks = ordersCollection.config.hooks.afterChange || [] + + console.log('Hook count:', hooks.length) + console.log('Hook types:', hooks.map((h, i) => \`#\${i}: \${typeof h}\`)) + + // 2. Check if hooks are actually functions + for (let i = 0; i < hooks.length; i++) { + const hook = hooks[i] + console.log(\`Hook #\${i}:\`) + console.log(\` - Type: \${typeof hook}\`) + console.log(\` - Is Function: \${typeof hook === 'function'}\`) + console.log(\` - Has Name: \${hook.name || 'anonymous'}\`) + console.log(\` - String Preview: \${hook.toString().substring(0, 100)}...\`) + } + + // 3. Try to manually execute each hook + console.log('\\n๐Ÿงช MANUAL HOOK EXECUTION TEST') + + const mockChange = { + collection: { slug: 'orders' }, + operation: 'update', + doc: { + id: 'test-' + Date.now(), + orderName: 'Test Order', + status: 'Paid', + customerEmail: 'test@example.com' + }, + previousDoc: { + id: 'test-' + Date.now(), + orderName: 'Test Order', + status: 'Unpaid', + customerEmail: 'test@example.com' + }, + req: { user: null } // Minimal request object + } + + for (let i = 0; i < hooks.length; i++) { + try { + console.log(\`\\nTesting hook #\${i}...\`) + console.log('About to call hook with mock data') + + const result = await hooks[i](mockChange) + + console.log(\`โœ… Hook #\${i} executed successfully\`) + console.log('Result:', result) + + } catch (error) { + console.log(\`โŒ Hook #\${i} failed:\`) + console.log('Error:', error.message) + console.log('Stack:', error.stack) + } + } + + // 4. Check if hooks are being called during real operations + console.log('\\n๐Ÿ” REAL OPERATION TEST') + console.log('Creating a test order to see if hooks fire...') + + // Add a simple test hook to verify hook execution + const testHook = async (change) => { + console.log('๐Ÿšจ TEST HOOK FIRED! ๐Ÿšจ') + console.log('Collection:', change.collection.slug) + console.log('Operation:', change.operation) + } + + // Add test hook at the beginning + ordersCollection.config.hooks.afterChange.unshift(testHook) + console.log('Added test hook at position 0') + + try { + const testOrder = await payload.create({ + collection: 'orders', + data: { + orderName: 'Hook Verification Test', + status: 'Unpaid', + customerEmail: 'hooktest@example.com', + totalPrice: 1000, + items: [{ name: 'Test Item', quantity: 1, price: 1000 }] + } + }) + + console.log('Test order created:', testOrder.id) + + // Update the order to trigger hooks + const updatedOrder = await payload.update({ + collection: 'orders', + id: testOrder.id, + data: { status: 'Paid' } + }) + + console.log('Test order updated to:', updatedOrder.status) + + } catch (error) { + console.log('Error during test operation:', error.message) + } +} + +// Run after PayloadCMS is initialized +setTimeout(verifyHooks, 3000) +`) + +console.log(` +๐ŸŽฏ Expected Results: + +If you see "๐Ÿšจ TEST HOOK FIRED! ๐Ÿšจ" but NOT the automation plugin messages: +- Hook execution works, but the automation plugin hook has an issue +- Likely problem: Hook function malformed or has runtime error + +If you DON'T see "๐Ÿšจ TEST HOOK FIRED! ๐Ÿšจ": +- Hook execution is completely broken +- PayloadCMS configuration or timing issue + +If hooks execute manually but not during real operations: +- Hook registration timing issue +- PayloadCMS lifecycle problem +`) + +process.exit(0) \ No newline at end of file diff --git a/src/plugin/index.ts b/src/plugin/index.ts index cfe0e1f..cbf70be 100644 --- a/src/plugin/index.ts +++ b/src/plugin/index.ts @@ -15,6 +15,18 @@ import {getConfigLogger, initializeLogger} from './logger.js' export {getLogger} from './logger.js' +// Global executor registry for config-phase hooks +let globalExecutor: WorkflowExecutor | null = null + +const setWorkflowExecutor = (executor: WorkflowExecutor) => { + console.log('๐Ÿšจ SETTING GLOBAL EXECUTOR') + globalExecutor = executor +} + +const getWorkflowExecutor = (): WorkflowExecutor | null => { + return globalExecutor +} + const applyCollectionsConfig = (pluginOptions: WorkflowsPluginConfig, config: Config) => { // Add workflow collections if (!config.collections) { @@ -27,6 +39,82 @@ const applyCollectionsConfig = (pluginOptions: WorkflowsPlugin ) } +const applyHooksToCollections = (pluginOptions: WorkflowsPluginConfig, config: Config) => { + const configLogger = getConfigLogger() + + if (!pluginOptions.collectionTriggers || Object.keys(pluginOptions.collectionTriggers).length === 0) { + configLogger.warn('No collection triggers configured - hooks will not be applied') + return + } + + configLogger.info('Applying hooks to collections during config phase') + + // Apply hooks to each configured collection + for (const [collectionSlug, triggerConfig] of Object.entries(pluginOptions.collectionTriggers)) { + if (!triggerConfig) { + continue + } + + // Find the collection in the config + const collectionConfig = config.collections?.find(c => c.slug === collectionSlug) + if (!collectionConfig) { + configLogger.warn(`Collection '${collectionSlug}' not found in config - cannot apply hooks`) + continue + } + + const crud = triggerConfig === true ? { + create: true, + delete: true, + read: true, + update: true, + } : triggerConfig + + // Initialize hooks if they don't exist + if (!collectionConfig.hooks) { + collectionConfig.hooks = {} + } + + // Apply afterChange hook for create/update operations + if ((crud as any).update || (crud as any).create) { + if (!collectionConfig.hooks.afterChange) { + collectionConfig.hooks.afterChange = [] + } + + // Add our automation hook - this will be called when the executor is ready + collectionConfig.hooks.afterChange.push(async (change) => { + console.log('๐Ÿšจ CONFIG-PHASE AUTOMATION HOOK CALLED! ๐Ÿšจ') + console.log('Collection:', change.collection.slug) + console.log('Operation:', change.operation) + console.log('Doc ID:', change.doc?.id) + + // Get the executor from global registry (set during onInit) + const executor = getWorkflowExecutor() + if (!executor) { + console.log('โŒ No executor available yet - workflow execution skipped') + return + } + + console.log('โœ… Executor found - executing workflows') + + try { + await executor.executeTriggeredWorkflows( + change.collection.slug, + change.operation as 'create' | 'update', + change.doc, + change.previousDoc, + change.req + ) + console.log('๐Ÿšจ executeTriggeredWorkflows completed successfully') + } catch (error) { + console.log('๐Ÿšจ executeTriggeredWorkflows failed:', error) + } + }) + } + + configLogger.info(`Applied hooks to collection: ${collectionSlug}`) + } +} + export const workflowsPlugin = (pluginOptions: WorkflowsPluginConfig) => @@ -37,6 +125,9 @@ export const workflowsPlugin = } applyCollectionsConfig(pluginOptions, config) + + // CRITICAL FIX: Apply hooks during config phase, not onInit + applyHooksToCollections(pluginOptions, config) if (!config.jobs) { config.jobs = {tasks: []} @@ -83,12 +174,12 @@ export const workflowsPlugin = const executor = new WorkflowExecutor(payload, logger) console.log('๐Ÿšจ EXECUTOR CREATED:', typeof executor) console.log('๐Ÿšจ EXECUTOR METHODS:', Object.getOwnPropertyNames(Object.getPrototypeOf(executor))) + + // Register executor globally for config-phase hooks + setWorkflowExecutor(executor) - // Initialize hooks - console.log('๐Ÿšจ INITIALIZING COLLECTION HOOKS') - logger.info('Initializing collection hooks...') - initCollectionHooks(pluginOptions, payload, logger, executor) - console.log('๐Ÿšจ COLLECTION HOOKS INITIALIZATION COMPLETE') + // Note: Collection hooks are now applied during config phase, not here + logger.info('Collection hooks applied during config phase - executor now available for them') logger.info('Initializing global hooks...') initGlobalHooks(payload, logger, executor)