mirror of
https://github.com/xtr-dev/payload-automation.git
synced 2025-12-13 01:53:23 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd85f90ef1 | |||
| 38fbb1922a | |||
| dfcc5c0fce | |||
| 089e12ac7a | |||
| 8ff65ca7c3 | |||
| bdfc311009 | |||
| 3c54f00f57 | |||
| cbb74206e9 | |||
| 41c4d8bdcb |
151
CUSTOMER-DIAGNOSTIC.md
Normal file
151
CUSTOMER-DIAGNOSTIC.md
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# 🔍 CRITICAL DIAGNOSTIC: Why The Plugin Works Locally But Not For You
|
||||||
|
|
||||||
|
## The Key Insight
|
||||||
|
|
||||||
|
Our tests work because we define collections **inline** in the config:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// OUR TEST ENVIRONMENT - WORKS
|
||||||
|
export default buildConfig({
|
||||||
|
collections: [
|
||||||
|
{
|
||||||
|
slug: 'posts',
|
||||||
|
fields: [...],
|
||||||
|
// Collection defined RIGHT HERE
|
||||||
|
}
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
workflowsPlugin({...}) // Plugin can see collections above
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## The Likely Customer Setup
|
||||||
|
|
||||||
|
You probably have collections defined **separately**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// YOUR ENVIRONMENT - LIKELY STRUCTURE
|
||||||
|
import { Orders } from './collections/Orders'
|
||||||
|
import { Users } from './collections/Users'
|
||||||
|
import { Products } from './collections/Products'
|
||||||
|
|
||||||
|
export default buildConfig({
|
||||||
|
collections: [
|
||||||
|
Orders, // Imported from separate file
|
||||||
|
Users, // Imported from separate file
|
||||||
|
Products // Imported from separate file
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
workflowsPlugin({...}) // Plugin runs but collections might be different
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## The Critical Question
|
||||||
|
|
||||||
|
**How are your collections defined?**
|
||||||
|
|
||||||
|
### Option 1: Separate Files (Most Common)
|
||||||
|
```typescript
|
||||||
|
// collections/Orders.ts
|
||||||
|
export const Orders: CollectionConfig = {
|
||||||
|
slug: 'orders',
|
||||||
|
hooks: {
|
||||||
|
// Your existing hooks here
|
||||||
|
},
|
||||||
|
fields: [...]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Factory Functions
|
||||||
|
```typescript
|
||||||
|
// collections/Orders.ts
|
||||||
|
export const Orders = (): CollectionConfig => ({
|
||||||
|
slug: 'orders',
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3: Class-based or Complex Setup
|
||||||
|
```typescript
|
||||||
|
// Something more complex that might not be in config.collections yet
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚨 THE DIAGNOSTIC TEST
|
||||||
|
|
||||||
|
Add this to your payload.config.ts BEFORE the workflowsPlugin:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default buildConfig({
|
||||||
|
collections: [Orders, Users, Products],
|
||||||
|
plugins: [
|
||||||
|
// ADD THIS DIAGNOSTIC PLUGIN FIRST
|
||||||
|
(config) => {
|
||||||
|
console.log('🔍 DIAGNOSTIC: Collections in config:')
|
||||||
|
console.log(' - config.collections exists?', !!config.collections)
|
||||||
|
console.log(' - config.collections length:', config.collections?.length)
|
||||||
|
console.log(' - Collection slugs:', config.collections?.map(c => c.slug))
|
||||||
|
|
||||||
|
// Check if orders collection has hooks already
|
||||||
|
const ordersConfig = config.collections?.find(c => c.slug === 'orders')
|
||||||
|
console.log(' - Orders collection found?', !!ordersConfig)
|
||||||
|
console.log(' - Orders has hooks?', !!ordersConfig?.hooks)
|
||||||
|
console.log(' - Orders afterChange hooks:', ordersConfig?.hooks?.afterChange?.length || 0)
|
||||||
|
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
|
||||||
|
// THEN your automation plugin
|
||||||
|
workflowsPlugin({...})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 What This Will Tell Us
|
||||||
|
|
||||||
|
1. **If collections show up**: The plugin should work with v0.0.20
|
||||||
|
2. **If collections are empty/undefined**: That's why hooks aren't registering
|
||||||
|
3. **If orders already has hooks**: There might be a conflict
|
||||||
|
|
||||||
|
## 💡 The Likely Solution
|
||||||
|
|
||||||
|
If your collections are in separate files, you might need to:
|
||||||
|
|
||||||
|
### Option A: Add hooks directly to collection files
|
||||||
|
```typescript
|
||||||
|
// collections/Orders.ts
|
||||||
|
import { automationHook } from '@xtr-dev/payload-automation/hooks' // We'd need to export this
|
||||||
|
|
||||||
|
export const Orders: CollectionConfig = {
|
||||||
|
slug: 'orders',
|
||||||
|
hooks: {
|
||||||
|
afterChange: [
|
||||||
|
automationHook, // Add directly here
|
||||||
|
// ... your other hooks
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option B: Modify collections before passing to buildConfig
|
||||||
|
```typescript
|
||||||
|
// payload.config.ts
|
||||||
|
import { Orders } from './collections/Orders'
|
||||||
|
import { addAutomationHooks } from '@xtr-dev/payload-automation/utils' // We'd need to create this
|
||||||
|
|
||||||
|
const OrdersWithAutomation = addAutomationHooks(Orders)
|
||||||
|
|
||||||
|
export default buildConfig({
|
||||||
|
collections: [OrdersWithAutomation, Users, Products],
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔑 The Bottom Line
|
||||||
|
|
||||||
|
**The plugin works when collections are defined inline because they exist in `config.collections` when the plugin runs.**
|
||||||
|
|
||||||
|
**If your collections are imported from separate files, they might not be in the right structure for the plugin to modify them.**
|
||||||
|
|
||||||
|
Run the diagnostic above and share the console output - it will tell us exactly why the hooks aren't registering in your environment!
|
||||||
130
hook-verification-test.js
Normal file
130
hook-verification-test.js
Normal file
@@ -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)
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@xtr-dev/payload-workflows",
|
"name": "@xtr-dev/payload-workflows",
|
||||||
"version": "0.0.17",
|
"version": "0.0.21",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@xtr-dev/payload-workflows",
|
"name": "@xtr-dev/payload-workflows",
|
||||||
"version": "0.0.17",
|
"version": "0.0.21",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsonpath-plus": "^10.3.0",
|
"jsonpath-plus": "^10.3.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@xtr-dev/payload-automation",
|
"name": "@xtr-dev/payload-automation",
|
||||||
"version": "0.0.17",
|
"version": "0.0.21",
|
||||||
"description": "PayloadCMS Automation Plugin - Comprehensive workflow automation system with visual workflow building, execution tracking, and step types",
|
"description": "PayloadCMS Automation Plugin - Comprehensive workflow automation system with visual workflow building, execution tracking, and step types",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type {Config} from 'payload'
|
import type {Config} from 'payload'
|
||||||
|
|
||||||
import type {WorkflowsPluginConfig} from "./config-types.js"
|
import type {WorkflowsPluginConfig, CollectionTriggerConfigCrud} from "./config-types.js"
|
||||||
|
|
||||||
import {createWorkflowCollection} from '../collections/Workflow.js'
|
import {createWorkflowCollection} from '../collections/Workflow.js'
|
||||||
import {WorkflowRunsCollection} from '../collections/WorkflowRuns.js'
|
import {WorkflowRunsCollection} from '../collections/WorkflowRuns.js'
|
||||||
@@ -15,6 +15,24 @@ import {getConfigLogger, initializeLogger} from './logger.js'
|
|||||||
|
|
||||||
export {getLogger} 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
|
||||||
|
|
||||||
|
// Also set on global object as fallback
|
||||||
|
if (typeof global !== 'undefined') {
|
||||||
|
(global as any).__workflowExecutor = executor
|
||||||
|
console.log('🚨 EXECUTOR ALSO SET ON GLOBAL OBJECT')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getWorkflowExecutor = (): WorkflowExecutor | null => {
|
||||||
|
return globalExecutor
|
||||||
|
}
|
||||||
|
|
||||||
const applyCollectionsConfig = <T extends string>(pluginOptions: WorkflowsPluginConfig<T>, config: Config) => {
|
const applyCollectionsConfig = <T extends string>(pluginOptions: WorkflowsPluginConfig<T>, config: Config) => {
|
||||||
// Add workflow collections
|
// Add workflow collections
|
||||||
if (!config.collections) {
|
if (!config.collections) {
|
||||||
@@ -27,6 +45,8 @@ const applyCollectionsConfig = <T extends string>(pluginOptions: WorkflowsPlugin
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Removed config-phase hook registration - user collections don't exist during config phase
|
||||||
|
|
||||||
|
|
||||||
export const workflowsPlugin =
|
export const workflowsPlugin =
|
||||||
<TSlug extends string>(pluginOptions: WorkflowsPluginConfig<TSlug>) =>
|
<TSlug extends string>(pluginOptions: WorkflowsPluginConfig<TSlug>) =>
|
||||||
@@ -37,6 +57,92 @@ export const workflowsPlugin =
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyCollectionsConfig<TSlug>(pluginOptions, config)
|
applyCollectionsConfig<TSlug>(pluginOptions, config)
|
||||||
|
|
||||||
|
// CRITICAL: Modify existing collection configs BEFORE PayloadCMS processes them
|
||||||
|
// This is the ONLY time we can add hooks that will actually work
|
||||||
|
const logger = getConfigLogger()
|
||||||
|
logger.info('Attempting to modify collection configs before PayloadCMS initialization...')
|
||||||
|
|
||||||
|
if (config.collections && pluginOptions.collectionTriggers) {
|
||||||
|
for (const [triggerSlug, triggerConfig] of Object.entries(pluginOptions.collectionTriggers)) {
|
||||||
|
if (!triggerConfig) continue
|
||||||
|
|
||||||
|
// Find the collection config that matches
|
||||||
|
const collectionIndex = config.collections.findIndex(c => c.slug === triggerSlug)
|
||||||
|
if (collectionIndex === -1) {
|
||||||
|
logger.warn(`Collection '${triggerSlug}' not found in config.collections`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const collection = config.collections[collectionIndex]
|
||||||
|
logger.info(`Found collection '${triggerSlug}' - modifying its hooks...`)
|
||||||
|
|
||||||
|
// Initialize hooks if needed
|
||||||
|
if (!collection.hooks) {
|
||||||
|
collection.hooks = {}
|
||||||
|
}
|
||||||
|
if (!collection.hooks.afterChange) {
|
||||||
|
collection.hooks.afterChange = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a properly bound hook function that doesn't rely on closures
|
||||||
|
// Use a simple function that PayloadCMS can definitely execute
|
||||||
|
const automationHook = Object.assign(
|
||||||
|
async function payloadAutomationHook(args: any) {
|
||||||
|
try {
|
||||||
|
// Use global console to ensure output
|
||||||
|
global.console.log('🔥🔥🔥 AUTOMATION HOOK EXECUTED! 🔥🔥🔥')
|
||||||
|
global.console.log('Collection:', args?.collection?.slug)
|
||||||
|
global.console.log('Operation:', args?.operation)
|
||||||
|
global.console.log('Doc ID:', args?.doc?.id)
|
||||||
|
|
||||||
|
// Try multiple ways to get the executor
|
||||||
|
let executor = null
|
||||||
|
|
||||||
|
// Method 1: Global registry
|
||||||
|
if (typeof getWorkflowExecutor === 'function') {
|
||||||
|
executor = getWorkflowExecutor()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method 2: Global variable fallback
|
||||||
|
if (!executor && typeof global !== 'undefined' && (global as any).__workflowExecutor) {
|
||||||
|
executor = (global as any).__workflowExecutor
|
||||||
|
global.console.log('Got executor from global variable')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (executor) {
|
||||||
|
global.console.log('✅ Executor found - executing workflows!')
|
||||||
|
await executor.executeTriggeredWorkflows(
|
||||||
|
args.collection.slug,
|
||||||
|
args.operation,
|
||||||
|
args.doc,
|
||||||
|
args.previousDoc,
|
||||||
|
args.req
|
||||||
|
)
|
||||||
|
global.console.log('✅ Workflow execution completed!')
|
||||||
|
} else {
|
||||||
|
global.console.log('⚠️ No executor available')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
global.console.error('❌ Hook execution error:', error)
|
||||||
|
// Don't throw - just log
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always return undefined to match other hooks
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Add metadata to help debugging
|
||||||
|
__isAutomationHook: true,
|
||||||
|
__version: '0.0.21'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add the hook to the collection config
|
||||||
|
collection.hooks.afterChange.push(automationHook)
|
||||||
|
logger.info(`Added automation hook to '${triggerSlug}' - hook count: ${collection.hooks.afterChange.length}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!config.jobs) {
|
if (!config.jobs) {
|
||||||
config.jobs = {tasks: []}
|
config.jobs = {tasks: []}
|
||||||
@@ -83,12 +189,12 @@ export const workflowsPlugin =
|
|||||||
const executor = new WorkflowExecutor(payload, logger)
|
const executor = new WorkflowExecutor(payload, logger)
|
||||||
console.log('🚨 EXECUTOR CREATED:', typeof executor)
|
console.log('🚨 EXECUTOR CREATED:', typeof executor)
|
||||||
console.log('🚨 EXECUTOR METHODS:', Object.getOwnPropertyNames(Object.getPrototypeOf(executor)))
|
console.log('🚨 EXECUTOR METHODS:', Object.getOwnPropertyNames(Object.getPrototypeOf(executor)))
|
||||||
|
|
||||||
|
// Register executor globally
|
||||||
|
setWorkflowExecutor(executor)
|
||||||
|
|
||||||
// Initialize hooks
|
// Hooks are now registered during config phase - just log status
|
||||||
console.log('🚨 INITIALIZING COLLECTION HOOKS')
|
logger.info('Hooks were registered during config phase - executor now available')
|
||||||
logger.info('Initializing collection hooks...')
|
|
||||||
initCollectionHooks(pluginOptions, payload, logger, executor)
|
|
||||||
console.log('🚨 COLLECTION HOOKS INITIALIZATION COMPLETE')
|
|
||||||
|
|
||||||
logger.info('Initializing global hooks...')
|
logger.info('Initializing global hooks...')
|
||||||
initGlobalHooks(payload, logger, executor)
|
initGlobalHooks(payload, logger, executor)
|
||||||
|
|||||||
Reference in New Issue
Block a user