@@ -11,18 +11,27 @@ import {
11
11
generateSamplePayloadFromMapping ,
12
12
generateDestinationSettings ,
13
13
generateAudienceSettings ,
14
- addAudienceSettingsToPayload
14
+ addAudienceSettingsToPayload ,
15
+ generateSampleFromSchema
15
16
} from '../lib/payload-generator/payload'
17
+ import {
18
+ API_ENDPOINTS ,
19
+ ApiEndpoint ,
20
+ getApiEndpointByName ,
21
+ getFormattedPath
22
+ } from '../lib/payload-generator/api-definitions'
16
23
17
24
export default class GenerateTestPayload extends Command {
18
25
private spinner : ora . Ora = ora ( )
19
26
20
- static description = `Generates sample test payload curl commands for a cloud mode destination.`
27
+ static description = `Generates sample test payload curl commands for different APIs in a cloud mode destination.`
21
28
22
29
static examples = [
23
30
`$ ./bin/run generate-test-payload` ,
24
31
`$ ./bin/run generate-test-payload --destination=slack` ,
25
- `$ ./bin/run generate-test-payload --destination=slack --action=postToChannel`
32
+ `$ ./bin/run generate-test-payload --destination=slack --action=postToChannel` ,
33
+ `$ ./bin/run generate-test-payload --destination=slack --api="Execute Action"` ,
34
+ `$ ./bin/run generate-test-payload --destination=slack --api="Create Audience"`
26
35
]
27
36
28
37
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -36,6 +45,9 @@ export default class GenerateTestPayload extends Command {
36
45
char : 'a' ,
37
46
description : 'specific action to generate test payload for'
38
47
} ) ,
48
+ api : flags . boolean ( {
49
+ description : 'prompt for API Selection'
50
+ } ) ,
39
51
directory : flags . string ( {
40
52
char : 'p' ,
41
53
description : 'destination actions directory path' ,
@@ -53,6 +65,29 @@ export default class GenerateTestPayload extends Command {
53
65
const { flags } = this . parse ( GenerateTestPayload )
54
66
let destinationName = flags . destination
55
67
const isBrowser = ! ! flags . browser
68
+ let selectedApiEndpoint : ApiEndpoint | undefined = getApiEndpointByName ( 'Execute Action' )
69
+
70
+ // Get the API endpoint based on the flag if provided
71
+ if ( flags . api ) {
72
+ // Prompt for API selection instead of exiting
73
+ const { selectedApi } = await autoPrompt < { selectedApi : string } > ( flags , {
74
+ type : 'select' ,
75
+ name : 'selectedApi' ,
76
+ message : 'Please select a valid API endpoint:' ,
77
+ choices : API_ENDPOINTS . map ( ( api ) => ( {
78
+ title : api . name === 'Execute Action' ? 'Execute Action (default)' : api . name ,
79
+ value : api . name
80
+ } ) )
81
+ } )
82
+ selectedApiEndpoint = getApiEndpointByName ( selectedApi )
83
+ }
84
+
85
+ if ( ! selectedApiEndpoint ) {
86
+ this . warn ( 'No valid API endpoint selected. Exiting.' )
87
+ this . exit ( 1 )
88
+ }
89
+
90
+ this . spinner . info ( `Generating test payload for API: ${ selectedApiEndpoint . name } ` )
56
91
57
92
if ( ! destinationName ) {
58
93
const integrationsGlob = `${ flags . directory } /*`
@@ -97,49 +132,57 @@ export default class GenerateTestPayload extends Command {
97
132
this . error ( `Failed to load destination: ${ destinationName } ` )
98
133
}
99
134
100
- const actions = Object . entries ( destination . actions )
135
+ this . spinner . succeed ( `Successfully loaded destination: ${ destinationName } ` )
101
136
102
- if ( actions . length === 0 ) {
103
- this . warn ( 'No actions found for this destination.' )
104
- this . exit ( )
105
- }
137
+ // If Execute Action API is selected, proceed with action selection
138
+ if ( selectedApiEndpoint . name === 'Execute Action' ) {
139
+ const actions = Object . entries ( destination . actions )
106
140
107
- let actionToGenerate = flags . action
141
+ if ( actions . length === 0 ) {
142
+ this . warn ( 'No actions found for this destination.' )
143
+ this . exit ( )
144
+ }
108
145
109
- if ( ! actionToGenerate && actions . length > 1 ) {
110
- const { selectedAction } = await autoPrompt < { selectedAction : string } > ( flags , {
111
- type : 'select' ,
112
- name : 'selectedAction' ,
113
- message : 'Which action to generate test payload for?' ,
114
- choices : [
115
- { title : 'All actions' , value : 'all' } ,
116
- ...actions . map ( ( [ slug , action ] ) => ( {
117
- title : action . title || slug ,
118
- value : slug
119
- } ) )
120
- ]
121
- } )
146
+ let actionToGenerate = flags . action
147
+
148
+ if ( ! actionToGenerate && actions . length > 1 ) {
149
+ const { selectedAction } = await autoPrompt < { selectedAction : string } > ( flags , {
150
+ type : 'select' ,
151
+ name : 'selectedAction' ,
152
+ message : 'Which action to generate test payload for?' ,
153
+ choices : [
154
+ { title : 'All actions' , value : 'all' } ,
155
+ ...actions . map ( ( [ slug , action ] ) => ( {
156
+ title : action . title || slug ,
157
+ value : slug
158
+ } ) )
159
+ ]
160
+ } )
122
161
123
- actionToGenerate = selectedAction
124
- } else if ( ! actionToGenerate ) {
125
- actionToGenerate = 'all'
126
- }
162
+ actionToGenerate = selectedAction
163
+ } else if ( ! actionToGenerate ) {
164
+ actionToGenerate = 'all'
165
+ }
127
166
128
- this . log ( chalk . bold ( '\nTest Payload curl commands:' ) )
167
+ this . log ( chalk . bold ( '\nTest Payload curl commands:' ) )
168
+
169
+ if ( actionToGenerate === 'all' ) {
170
+ for ( const [ slug , action ] of actions ) {
171
+ await this . generatePayloadForAction ( destination , slug , action )
172
+ }
173
+ } else {
174
+ const action = actions . find ( ( [ slug ] ) => slug === actionToGenerate )
175
+ if ( ! action ) {
176
+ this . warn ( `Action "${ actionToGenerate } " not found. Exiting.` )
177
+ this . exit ( 1 )
178
+ return
179
+ }
129
180
130
- if ( actionToGenerate === 'all' ) {
131
- for ( const [ slug , action ] of actions ) {
132
- await this . generatePayloadForAction ( destination , slug , action )
181
+ await this . generatePayloadForAction ( destination , action [ 0 ] , action [ 1 ] )
133
182
}
134
183
} else {
135
- const action = actions . find ( ( [ slug ] ) => slug === actionToGenerate )
136
- if ( ! action ) {
137
- this . warn ( `Action "${ actionToGenerate } " not found. Exiting.` )
138
- this . exit ( 1 )
139
- return
140
- }
141
-
142
- await this . generatePayloadForAction ( destination , action [ 0 ] , action [ 1 ] )
184
+ // Generate payload for selected API endpoint
185
+ await this . generatePayloadForApi ( destination , selectedApiEndpoint )
143
186
}
144
187
145
188
this . log ( chalk . green ( `\nDone generating test payloads! 🎉` ) )
@@ -221,6 +264,77 @@ export default class GenerateTestPayload extends Command {
221
264
}
222
265
}
223
266
267
+ /**
268
+ * Generate payload for a specific API endpoint
269
+ */
270
+ async generatePayloadForApi ( destination : DestinationDefinition , apiEndpoint : ApiEndpoint ) {
271
+ this . spinner . start ( `Generating test payload for API: ${ apiEndpoint . name } ` )
272
+
273
+ try {
274
+ // Generate destination settings and auth
275
+ const { settings, auth } = generateDestinationSettings ( destination )
276
+
277
+ // Get audience settings if needed
278
+ const audienceSettings = generateAudienceSettings ( destination )
279
+ const audienceSettingsValues =
280
+ Object . keys ( audienceSettings ) . length > 0 ? generateSampleFromSchema ( audienceSettings || { } ) : { }
281
+
282
+ // Start with the template payload from the API definition
283
+ const request = { ...apiEndpoint . requestTemplate }
284
+
285
+ // Fill in settings and auth if the payload has those fields
286
+ if ( 'settings' in request && ! Object . keys ( request . settings ) . length ) {
287
+ request . settings = settings
288
+ }
289
+
290
+ if ( 'auth' in request && ! Object . keys ( request . auth || { } ) . length && auth ) {
291
+ request . auth = auth
292
+ }
293
+
294
+ // Fill in audience settings if applicable
295
+ if ( 'audienceSettings' in request && ! Object . keys ( request . audienceSettings || { } ) . length ) {
296
+ request . audienceSettings = audienceSettingsValues
297
+ }
298
+
299
+ if ( 'payload' in request ) {
300
+ const baseEvent = generateSamplePayloadFromMapping ( { } , { } )
301
+ const isDeleteHandler = apiEndpoint . name === 'Invoke Delete Handler'
302
+ request . payload = {
303
+ ...baseEvent ,
304
+ type : isDeleteHandler ? 'delete' : 'track' ,
305
+ traits : { }
306
+ }
307
+ }
308
+
309
+ // Handle path parameters if needed
310
+ const pathParams : Record < string , string > = { }
311
+ if ( apiEndpoint . pathParams ) {
312
+ // For now we'll use placeholders, but in the future we could prompt for these values
313
+ for ( const param of apiEndpoint . pathParams ) {
314
+ pathParams [ param . key ] = param . placeholder
315
+ }
316
+ }
317
+
318
+ // Format the path with any parameters
319
+ const formattedPath = getFormattedPath ( apiEndpoint , pathParams )
320
+
321
+ this . spinner . succeed ( `Generated test payload for API: ${ apiEndpoint . name } ` )
322
+
323
+ // Print the curl command to the terminal
324
+ this . log ( chalk . cyan ( `\n# Test payload for ${ chalk . bold ( destination . name ) } - ${ chalk . bold ( apiEndpoint . name ) } ` ) )
325
+
326
+ if ( apiEndpoint . method === 'GET' ) {
327
+ this . log ( chalk . yellow ( `curl -X GET http://localhost:3000${ formattedPath } ` ) )
328
+ } else {
329
+ this . log ( chalk . yellow ( `curl -X ${ apiEndpoint . method } http://localhost:3000${ formattedPath } \\` ) )
330
+ this . log ( chalk . yellow ( ` -H "Content-Type: application/json" \\` ) )
331
+ this . log ( chalk . yellow ( ` -d '${ JSON . stringify ( request ) . replace ( / ' / g, "\\'" ) } '` ) )
332
+ }
333
+ } catch ( error ) {
334
+ this . spinner . fail ( `Failed to generate payload for API ${ apiEndpoint . name } : ${ ( error as Error ) . message } ` )
335
+ }
336
+ }
337
+
224
338
async catch ( error : unknown ) {
225
339
if ( this . spinner ?. isSpinning ) {
226
340
this . spinner . fail ( )
0 commit comments