zapier-platform-cli 15.15.0 → 15.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,845 @@
1
+ const fs = require('node:fs');
2
+
3
+ const _ = require('lodash');
4
+ const { flags } = require('@oclif/command');
5
+ const debug = require('debug')('zapier:invoke');
6
+ const dotenv = require('dotenv');
7
+
8
+ // Datetime related imports
9
+ const chrono = require('chrono-node');
10
+ const { DateTime, IANAZone } = require('luxon');
11
+
12
+ const BaseCommand = require('../ZapierBaseCommand');
13
+ const { buildFlags } = require('../buildFlags');
14
+ const { localAppCommand } = require('../../utils/local');
15
+ const { startSpinner, endSpinner } = require('../../utils/display');
16
+
17
+ const ACTION_TYPE_PLURALS = {
18
+ trigger: 'triggers',
19
+ search: 'searches',
20
+ create: 'creates',
21
+ };
22
+
23
+ const ACTION_TYPES = ['auth', ...Object.keys(ACTION_TYPE_PLURALS)];
24
+
25
+ const AUTH_FIELD_ENV_PREFIX = 'authData_';
26
+
27
+ const NUMBER_CHARSET = '0123456789.-,';
28
+
29
+ const FALSE_STRINGS = new Set([
30
+ 'noo',
31
+ 'no',
32
+ 'n',
33
+ 'false',
34
+ 'nope',
35
+ 'f',
36
+ 'never',
37
+ 'no thanks',
38
+ 'no thank you',
39
+ 'nul',
40
+ '0',
41
+ 'none',
42
+ 'nil',
43
+ 'nill',
44
+ 'null',
45
+ ]);
46
+
47
+ const TRUE_STRINGS = new Set([['yes', 'yeah', 'y', 'true', 't', '1']]);
48
+
49
+ const loadAuthDataFromEnv = () => {
50
+ return Object.entries(process.env)
51
+ .filter(([k, v]) => k.startsWith(AUTH_FIELD_ENV_PREFIX))
52
+ .reduce((authData, [k, v]) => {
53
+ const fieldKey = k.substr(AUTH_FIELD_ENV_PREFIX.length);
54
+ authData[fieldKey] = v;
55
+ return authData;
56
+ }, {});
57
+ };
58
+
59
+ const readStream = async (stream) => {
60
+ const chunks = [];
61
+ for await (const chunk of stream) {
62
+ chunks.push(chunk);
63
+ }
64
+ return chunks.join('');
65
+ };
66
+
67
+ const getMissingRequiredInputFields = (inputData, inputFields) => {
68
+ return inputFields.filter(
69
+ (f) => f.required && !f.default && !inputData[f.key]
70
+ );
71
+ };
72
+
73
+ const parseDecimal = (s) => {
74
+ const chars = [];
75
+ for (const c of s) {
76
+ if (NUMBER_CHARSET.includes(c)) {
77
+ chars.push(c);
78
+ }
79
+ }
80
+ const cleaned = chars.join('').replace(/[.,-]$/, '');
81
+ return parseFloat(cleaned);
82
+ };
83
+
84
+ const parseInteger = (s) => {
85
+ const n = parseInt(s);
86
+ if (!isNaN(n)) {
87
+ return n;
88
+ }
89
+ return Math.floor(parseDecimal(s));
90
+ };
91
+
92
+ const parseBoolean = (s) => {
93
+ s = s.toLowerCase();
94
+ if (TRUE_STRINGS.has(s)) {
95
+ return true;
96
+ }
97
+ if (FALSE_STRINGS.has(s)) {
98
+ return false;
99
+ }
100
+ return Boolean(s);
101
+ };
102
+
103
+ const parsingCompsToString = (parsingComps) => {
104
+ const yyyy = parsingComps.get('year');
105
+ const mm = String(parsingComps.get('month')).padStart(2, '0');
106
+ const dd = String(parsingComps.get('day')).padStart(2, '0');
107
+ const hh = String(parsingComps.get('hour')).padStart(2, '0');
108
+ const ii = String(parsingComps.get('minute')).padStart(2, '0');
109
+ const ss = String(parsingComps.get('second')).padStart(2, '0');
110
+ return `${yyyy}-${mm}-${dd}T${hh}:${ii}:${ss}`;
111
+ };
112
+
113
+ const hasTimeInfo = (parsingComps) => {
114
+ const tags = [...parsingComps.tags()];
115
+ for (const tag of tags) {
116
+ if (tag.includes('ISOFormat') || tag.includes('Time')) {
117
+ return true;
118
+ }
119
+ }
120
+ return false;
121
+ };
122
+
123
+ const maybeImplyTimeInfo = (parsingComps) => {
124
+ if (!hasTimeInfo(parsingComps)) {
125
+ parsingComps.imply('hour', 9);
126
+ parsingComps.imply('minute', 0);
127
+ parsingComps.imply('second', 0);
128
+ parsingComps.imply('millisecond', 0);
129
+ }
130
+ return parsingComps;
131
+ };
132
+
133
+ const parseTimestamp = (dtString, tzName) => {
134
+ const match = dtString.match(/-?\d{10,14}/);
135
+ if (!match) {
136
+ return null;
137
+ }
138
+
139
+ dtString = match[0];
140
+ let timestamp = parseInt(dtString);
141
+ if (dtString.length <= 12) {
142
+ timestamp *= 1000;
143
+ }
144
+
145
+ return DateTime.fromMillis(timestamp, { zone: tzName }).toFormat(
146
+ "yyyy-MM-dd'T'HH:mm:ssZZ"
147
+ );
148
+ };
149
+
150
+ const parseDatetime = (dtString, tzName, now) => {
151
+ const timestampResult = parseTimestamp(dtString, tzName);
152
+ if (timestampResult) {
153
+ return timestampResult;
154
+ }
155
+
156
+ const offset = IANAZone.create(tzName).offset(now.getTime());
157
+ const results = chrono.parse(dtString, {
158
+ instant: now,
159
+ timezone: offset,
160
+ });
161
+
162
+ let isoString;
163
+ if (results.length) {
164
+ const parsingComps = results[0].start;
165
+ if (parsingComps.get('timezoneOffset') == null) {
166
+ // No timezone info in the input string => interpret the datetime string
167
+ // exactly as it is and append the timezone
168
+ isoString = parsingCompsToString(maybeImplyTimeInfo(parsingComps));
169
+ } else {
170
+ // Timezone info is present or implied in the input string => convert the
171
+ // datetime to the specified timezone
172
+ isoString = maybeImplyTimeInfo(parsingComps).date().toISOString();
173
+ }
174
+ } else {
175
+ // No datetime info in the input string => just return the current time
176
+ isoString = now.toISOString();
177
+ }
178
+
179
+ return DateTime.fromISO(isoString, { zone: tzName }).toFormat(
180
+ "yyyy-MM-dd'T'HH:mm:ssZZ"
181
+ );
182
+ };
183
+
184
+ const resolveInputDataTypes = (inputData, inputFields, timezone) => {
185
+ const fieldsWithDefault = inputFields.filter((f) => f.default);
186
+ for (const f of fieldsWithDefault) {
187
+ if (!inputData[f.key]) {
188
+ inputData[f.key] = f.default;
189
+ }
190
+ }
191
+
192
+ const inputFieldsByKey = _.keyBy(inputFields, 'key');
193
+ for (const [k, v] of Object.entries(inputData)) {
194
+ const inputField = inputFieldsByKey[k];
195
+ if (!inputField) {
196
+ continue;
197
+ }
198
+
199
+ switch (inputField.type) {
200
+ case 'integer':
201
+ inputData[k] = parseInteger(v);
202
+ break;
203
+ case 'number':
204
+ inputData[k] = parseDecimal(v);
205
+ break;
206
+ case 'boolean':
207
+ inputData[k] = parseBoolean(v);
208
+ break;
209
+ case 'datetime':
210
+ inputData[k] = parseDatetime(v, timezone, new Date());
211
+ break;
212
+ case 'file':
213
+ // TODO: How to handle a file field?
214
+ break;
215
+ // TODO: Handle 'list' and 'dict' types?
216
+ default:
217
+ // No need to do anything with 'string' type
218
+ break;
219
+ }
220
+ }
221
+
222
+ // TODO: Handle line items (fields with "children")
223
+
224
+ return inputData;
225
+ };
226
+
227
+ const testAuth = async (authData, meta, zcacheTestObj) => {
228
+ startSpinner('Invoking authentication.test');
229
+ const result = await localAppCommand({
230
+ command: 'execute',
231
+ method: 'authentication.test',
232
+ bundle: {
233
+ authData,
234
+ meta,
235
+ },
236
+ zcacheTestObj,
237
+ customLogger,
238
+ });
239
+ endSpinner();
240
+ return result;
241
+ };
242
+
243
+ const getAuthLabel = async (labelTemplate, authData, meta, zcacheTestObj) => {
244
+ const testResult = await testAuth(authData, meta, zcacheTestObj);
245
+ labelTemplate = labelTemplate.replace('__', '.');
246
+ const tpl = _.template(labelTemplate, { interpolate: /{{([\s\S]+?)}}/g });
247
+ return tpl(testResult);
248
+ };
249
+
250
+ const getLabelForDynamicDropdown = (obj, preferredKey, fallbackKey) => {
251
+ const keys = [
252
+ 'name',
253
+ 'Name',
254
+ 'display',
255
+ 'Display',
256
+ 'title',
257
+ 'Title',
258
+ 'subject',
259
+ 'Subject',
260
+ ];
261
+ if (preferredKey) {
262
+ keys.unshift(preferredKey.split('__'));
263
+ }
264
+ if (fallbackKey) {
265
+ keys.push(fallbackKey.split('__'));
266
+ }
267
+ for (const key of keys) {
268
+ const label = _.get(obj, key);
269
+ if (label) {
270
+ return label;
271
+ }
272
+ }
273
+ return '';
274
+ };
275
+
276
+ const findNonStringPrimitives = (data, path = 'inputData') => {
277
+ if (typeof data === 'number' || typeof data === 'boolean' || data === null) {
278
+ return [{ path, value: data }];
279
+ } else if (typeof data === 'string') {
280
+ return [];
281
+ } else if (Array.isArray(data)) {
282
+ const paths = [];
283
+ for (let i = 0; i < data.length; i++) {
284
+ paths.push(...findNonStringPrimitives(data[i], `${path}[${i}]`));
285
+ }
286
+ return paths;
287
+ } else if (_.isPlainObject(data)) {
288
+ const paths = [];
289
+ for (const [k, v] of Object.entries(data)) {
290
+ paths.push(...findNonStringPrimitives(v, `${path}.${k}`));
291
+ }
292
+ return paths;
293
+ } else {
294
+ throw new Error('Unexpected data type');
295
+ }
296
+ };
297
+
298
+ const customLogger = (message, data) => {
299
+ debug(message);
300
+ debug(data);
301
+ };
302
+
303
+ class InvokeCommand extends BaseCommand {
304
+ async promptForField(
305
+ field,
306
+ appDefinition,
307
+ inputData,
308
+ authData,
309
+ timezone,
310
+ zcacheTestObj,
311
+ cursorTestObj
312
+ ) {
313
+ const ftype = field.type || 'string';
314
+
315
+ let message;
316
+ if (field.label) {
317
+ message = `${field.label} | ${field.key} | ${ftype}`;
318
+ } else {
319
+ message = `${field.key} | ${ftype}`;
320
+ }
321
+ if (field.required) {
322
+ message += ' | required';
323
+ }
324
+ message += ':';
325
+
326
+ if (field.dynamic) {
327
+ // Dyanmic dropdown
328
+ const [triggerKey, idField, labelField] = field.dynamic.split('.');
329
+ const trigger = appDefinition.triggers[triggerKey];
330
+ const meta = {
331
+ isLoadingSample: false,
332
+ isFillingDynamicDropdown: true,
333
+ isPopulatingDedupe: false,
334
+ limit: -1,
335
+ page: 0,
336
+ isTestingAuth: false,
337
+ };
338
+ const choices = await this.invokeAction(
339
+ appDefinition,
340
+ 'triggers',
341
+ trigger,
342
+ inputData,
343
+ authData,
344
+ meta,
345
+ timezone,
346
+ zcacheTestObj,
347
+ cursorTestObj
348
+ );
349
+ return this.promptWithList(
350
+ message,
351
+ choices.map((c) => {
352
+ const id = c[idField] || 'null';
353
+ const label = getLabelForDynamicDropdown(c, labelField, idField);
354
+ return {
355
+ name: `${label} (${id})`,
356
+ value: id,
357
+ };
358
+ }),
359
+ { useStderr: true }
360
+ );
361
+ } else if (ftype === 'boolean') {
362
+ const yes = await this.confirm(message, false, !field.required, true);
363
+ return yes ? 'yes' : 'no';
364
+ } else {
365
+ return this.prompt(message, { useStderr: true });
366
+ }
367
+ }
368
+
369
+ async promptOrErrorForRequiredInputFields(
370
+ inputData,
371
+ inputFields,
372
+ appDefinition,
373
+ authData,
374
+ meta,
375
+ timezone,
376
+ zcacheTestObj,
377
+ cursorTestObj
378
+ ) {
379
+ const missingFields = getMissingRequiredInputFields(inputData, inputFields);
380
+ if (missingFields.length) {
381
+ if (!process.stdin.isTTY || meta.isFillingDynamicDropdown) {
382
+ throw new Error(
383
+ 'You must specify these required fields in --inputData at least when stdin is not a TTY: \n' +
384
+ missingFields.map((f) => `* ${f.key}`).join('\n')
385
+ );
386
+ }
387
+ for (const f of missingFields) {
388
+ inputData[f.key] = await this.promptForField(
389
+ f,
390
+ appDefinition,
391
+ inputData,
392
+ authData,
393
+ timezone,
394
+ zcacheTestObj,
395
+ cursorTestObj
396
+ );
397
+ }
398
+ }
399
+ }
400
+
401
+ async promptForInputFieldEdit(
402
+ inputData,
403
+ inputFields,
404
+ appDefinition,
405
+ authData,
406
+ timezone,
407
+ zcacheTestObj,
408
+ cursorTestObj
409
+ ) {
410
+ inputFields = inputFields.filter((f) => f.key);
411
+ if (!inputFields.length) {
412
+ return;
413
+ }
414
+
415
+ // Let user select which field to fill/edit
416
+ while (true) {
417
+ let fieldChoices = inputFields.map((f) => {
418
+ let name;
419
+ if (f.label) {
420
+ name = `${f.label} (${f.key})`;
421
+ } else {
422
+ name = f.key;
423
+ }
424
+ if (inputData[f.key]) {
425
+ name += ` [current: "${inputData[f.key]}"]`;
426
+ } else if (f.default) {
427
+ name += ` [default: "${f.default}"]`;
428
+ }
429
+ return {
430
+ name,
431
+ value: f.key,
432
+ };
433
+ });
434
+ fieldChoices = [
435
+ {
436
+ name: '>>> DONE <<<',
437
+ short: 'DONE',
438
+ value: null,
439
+ },
440
+ ...fieldChoices,
441
+ ];
442
+ const fieldKey = await this.promptWithList(
443
+ 'Would you like to edit any of these input fields? Select "DONE" when you are all set.',
444
+ fieldChoices,
445
+ { useStderr: true }
446
+ );
447
+ if (!fieldKey) {
448
+ break;
449
+ }
450
+
451
+ const field = inputFields.find((f) => f.key === fieldKey);
452
+ inputData[fieldKey] = await this.promptForField(
453
+ field,
454
+ appDefinition,
455
+ inputData,
456
+ authData,
457
+ timezone,
458
+ zcacheTestObj,
459
+ cursorTestObj
460
+ );
461
+ }
462
+ }
463
+
464
+ async promptForFields(
465
+ inputData,
466
+ inputFields,
467
+ appDefinition,
468
+ authData,
469
+ meta,
470
+ timezone,
471
+ zcacheTestObj,
472
+ cursorTestObj
473
+ ) {
474
+ await this.promptOrErrorForRequiredInputFields(
475
+ inputData,
476
+ inputFields,
477
+ appDefinition,
478
+ authData,
479
+ meta,
480
+ timezone,
481
+ zcacheTestObj,
482
+ cursorTestObj
483
+ );
484
+ if (process.stdin.isTTY && !meta.isFillingDynamicDropdown) {
485
+ await this.promptForInputFieldEdit(
486
+ inputData,
487
+ inputFields,
488
+ appDefinition,
489
+ authData,
490
+ timezone,
491
+ zcacheTestObj,
492
+ cursorTestObj
493
+ );
494
+ }
495
+ }
496
+
497
+ async invokeAction(
498
+ appDefinition,
499
+ actionTypePlural,
500
+ action,
501
+ inputData,
502
+ authData,
503
+ meta,
504
+ timezone,
505
+ zcacheTestObj,
506
+ cursorTestObj
507
+ ) {
508
+ // Do these in order:
509
+ // 1. Prompt for static input fields that alter dynamic fields
510
+ // 2. {actionTypePlural}.{actionKey}.operation.inputFields
511
+ // 3. Prompt for input fields again
512
+ // 4. {actionTypePlural}.{actionKey}.operation.perform
513
+
514
+ const staticInputFields = (action.operation.inputFields || []).filter(
515
+ (f) => f.key
516
+ );
517
+ debug('staticInputFields:', staticInputFields);
518
+
519
+ await this.promptForFields(
520
+ inputData,
521
+ staticInputFields,
522
+ appDefinition,
523
+ authData,
524
+ meta,
525
+ timezone,
526
+ zcacheTestObj,
527
+ cursorTestObj
528
+ );
529
+
530
+ let methodName = `${actionTypePlural}.${action.key}.operation.inputFields`;
531
+ startSpinner(`Invoking ${methodName}`);
532
+
533
+ const inputFields = await localAppCommand({
534
+ command: 'execute',
535
+ method: methodName,
536
+ bundle: {
537
+ inputData,
538
+ authData,
539
+ meta,
540
+ },
541
+ zcacheTestObj,
542
+ cursorTestObj,
543
+ customLogger,
544
+ });
545
+ endSpinner();
546
+
547
+ debug('inputFields:', inputFields);
548
+
549
+ if (inputFields.length !== staticInputFields.length) {
550
+ await this.promptForFields(
551
+ inputData,
552
+ inputFields,
553
+ appDefinition,
554
+ authData,
555
+ meta,
556
+ timezone,
557
+ zcacheTestObj,
558
+ cursorTestObj
559
+ );
560
+ }
561
+
562
+ inputData = resolveInputDataTypes(inputData, inputFields, timezone);
563
+ methodName = `${actionTypePlural}.${action.key}.operation.perform`;
564
+
565
+ startSpinner(`Invoking ${methodName}`);
566
+ const output = await localAppCommand({
567
+ command: 'execute',
568
+ method: methodName,
569
+ bundle: {
570
+ inputData,
571
+ authData,
572
+ meta,
573
+ },
574
+ zcacheTestObj,
575
+ cursorTestObj,
576
+ customLogger,
577
+ });
578
+ endSpinner();
579
+
580
+ return output;
581
+ }
582
+
583
+ async perform() {
584
+ dotenv.config({ override: true });
585
+
586
+ let { actionType, actionKey } = this.args;
587
+
588
+ if (!actionType) {
589
+ if (!process.stdin.isTTY) {
590
+ throw new Error(
591
+ 'You must specify ACTIONTYPE and ACTIONKEY when stdin is not a TTY.'
592
+ );
593
+ }
594
+ actionType = await this.promptWithList(
595
+ 'Which action type would you like to invoke?',
596
+ ACTION_TYPES,
597
+ { useStderr: true }
598
+ );
599
+ }
600
+
601
+ const actionTypePlural = ACTION_TYPE_PLURALS[actionType];
602
+ const appDefinition = await localAppCommand({ command: 'definition' });
603
+
604
+ if (!actionKey) {
605
+ if (!process.stdin.isTTY) {
606
+ throw new Error('You must specify ACTIONKEY when stdin is not a TTY.');
607
+ }
608
+ if (actionType === 'auth') {
609
+ const actionKeys = ['label', 'test'];
610
+ actionKey = await this.promptWithList(
611
+ 'Which auth operation would you like to invoke?',
612
+ actionKeys,
613
+ { useStderr: true }
614
+ );
615
+ } else {
616
+ const actionKeys = Object.keys(
617
+ appDefinition[actionTypePlural] || {}
618
+ ).sort();
619
+ if (!actionKeys.length) {
620
+ throw new Error(
621
+ `No "${actionTypePlural}" found in your integration.`
622
+ );
623
+ }
624
+
625
+ actionKey = await this.promptWithList(
626
+ `Which "${actionType}" key would you like to invoke?`,
627
+ actionKeys,
628
+ { useStderr: true }
629
+ );
630
+ }
631
+ }
632
+
633
+ const authData = loadAuthDataFromEnv();
634
+ const zcacheTestObj = {};
635
+ const cursorTestObj = {};
636
+
637
+ if (actionType === 'auth') {
638
+ const meta = {
639
+ isLoadingSample: false,
640
+ isFillingDynamicDropdown: false,
641
+ isPopulatingDedupe: false,
642
+ limit: -1,
643
+ page: 0,
644
+ isTestingAuth: true,
645
+ };
646
+ switch (actionKey) {
647
+ // TODO: Add 'start' and 'refresh' commands
648
+ case 'test': {
649
+ const output = await testAuth(authData, meta, zcacheTestObj);
650
+ console.log(JSON.stringify(output, null, 2));
651
+ return;
652
+ }
653
+ case 'label': {
654
+ const labelTemplate = appDefinition.authentication.connectionLabel;
655
+ if (labelTemplate && labelTemplate.startsWith('$func$')) {
656
+ console.warn(
657
+ 'Function-based connection label is currently not supported; will print auth test result instead.'
658
+ );
659
+ const output = await testAuth(authData, meta, zcacheTestObj);
660
+ console.log(JSON.stringify(output, null, 2));
661
+ } else {
662
+ const output = await getAuthLabel(
663
+ labelTemplate,
664
+ authData,
665
+ meta,
666
+ zcacheTestObj
667
+ );
668
+ if (output) {
669
+ console.log(output);
670
+ } else {
671
+ console.warn('Connection label is empty.');
672
+ }
673
+ }
674
+ return;
675
+ }
676
+ default:
677
+ throw new Error(`Unknown auth action: ${actionKey}`);
678
+ }
679
+ } else {
680
+ const action = appDefinition[actionTypePlural][actionKey];
681
+ if (!action) {
682
+ throw new Error(`No "${actionType}" found with key "${actionKey}".`);
683
+ }
684
+
685
+ debug('Action type:', actionType);
686
+ debug('Action key:', actionKey);
687
+ debug('Action label:', action.display.label);
688
+
689
+ let { inputData } = this.flags;
690
+ if (inputData) {
691
+ if (inputData.startsWith('@')) {
692
+ const filePath = inputData.substr(1);
693
+ let inputStream;
694
+ if (filePath === '-') {
695
+ inputStream = process.stdin;
696
+ } else {
697
+ inputStream = fs.createReadStream(filePath, { encoding: 'utf8' });
698
+ }
699
+ inputData = await readStream(inputStream);
700
+ }
701
+ inputData = JSON.parse(inputData);
702
+ } else {
703
+ inputData = {};
704
+ }
705
+
706
+ // inputData should only contain strings
707
+ const nonStringPrimitives = findNonStringPrimitives(inputData);
708
+ if (nonStringPrimitives.length) {
709
+ throw new Error(
710
+ 'All primitive values in --inputData must be strings. Found non-string values in these paths:\n' +
711
+ nonStringPrimitives
712
+ .map(({ path, value }) => `* ${value} at ${path}`)
713
+ .join('\n')
714
+ );
715
+ }
716
+
717
+ const { timezone } = this.flags;
718
+ const meta = {
719
+ isLoadingSample: this.flags.isLoadingSample,
720
+ isFillingDynamicDropdown: this.flags.isFillingDynamicDropdown,
721
+ isPopulatingDedupe: this.flags.isPopulatingDedupe,
722
+ limit: this.flags.limit,
723
+ page: this.flags.page,
724
+ isTestingAuth: false, // legacy property
725
+ };
726
+ const output = await this.invokeAction(
727
+ appDefinition,
728
+ actionTypePlural,
729
+ action,
730
+ inputData,
731
+ authData,
732
+ meta,
733
+ timezone,
734
+ zcacheTestObj,
735
+ cursorTestObj
736
+ );
737
+ console.log(JSON.stringify(output, null, 2));
738
+ }
739
+ }
740
+ }
741
+
742
+ InvokeCommand.flags = buildFlags({
743
+ commandFlags: {
744
+ inputData: flags.string({
745
+ char: 'i',
746
+ description:
747
+ 'The input data to pass to the action. Must be a JSON-encoded object. The data can be passed from the command directly like \'{"key": "value"}\', read from a file like @file.json, or read from stdin like @-.',
748
+ }),
749
+ isLoadingSample: flags.boolean({
750
+ description:
751
+ 'Set bundle.meta.isLoadingSample to true. When true in production, this run is initiated by the user in the Zap editor trying to pull a sample.',
752
+ default: false,
753
+ }),
754
+ isFillingDynamicDropdown: flags.boolean({
755
+ description:
756
+ 'Set bundle.meta.isFillingDynamicDropdown to true. Only makes sense for a polling trigger. When true in production, this poll is being used to populate a dynamic dropdown.',
757
+ default: false,
758
+ }),
759
+ isPopulatingDedupe: flags.boolean({
760
+ description:
761
+ 'Set bundle.meta.isPopulatingDedupe to true. Only makes sense for a polling trigger. When true in production, the results of this poll will be used initialize the deduplication list rather than trigger a Zap. This happens when a user enables a Zap.',
762
+ default: false,
763
+ }),
764
+ limit: flags.integer({
765
+ description:
766
+ 'Set bundle.meta.limit. Only makes sense for a trigger. When used in production, this indicates the number of items you should fetch. -1 means no limit.',
767
+ default: -1,
768
+ }),
769
+ page: flags.integer({
770
+ char: 'p',
771
+ description:
772
+ 'Set bundle.meta.page. Only makes sense for a trigger. When used in production, this indicates which page of items you should fetch. First page is 0.',
773
+ default: 0,
774
+ }),
775
+ timezone: flags.string({
776
+ char: 'z',
777
+ description:
778
+ 'Set the default timezone for datetime fields. If not set, defaults to America/Chicago, which matches Zapier production behavior. Find the list timezone names at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones.',
779
+ default: 'America/Chicago',
780
+ }),
781
+ },
782
+ });
783
+
784
+ InvokeCommand.args = [
785
+ {
786
+ name: 'actionType',
787
+ description: 'The action type you want to invoke.',
788
+ options: ACTION_TYPES,
789
+ },
790
+ {
791
+ name: 'actionKey',
792
+ description:
793
+ 'The trigger/action key you want to invoke. If ACTIONTYPE is "auth", this can be "test" or "label".',
794
+ },
795
+ ];
796
+
797
+ InvokeCommand.skipValidInstallCheck = true;
798
+ InvokeCommand.examples = [
799
+ 'zapier invoke',
800
+ 'zapier invoke auth test',
801
+ 'zapier invoke trigger new_recipe',
802
+ `zapier invoke create add_recipe --inputData '{"title": "Pancakes"}'`,
803
+ 'zapier invoke search find_recipe -i @file.json',
804
+ 'cat file.json | zapier invoke trigger new_recipe -i @-',
805
+ ];
806
+ InvokeCommand.description = `Invoke an auth operation, a trigger, or a create/search action locally.
807
+
808
+ This command emulates how Zapier production environment would invoke your integration. It runs code locally, so you can use this command to quickly test your integration without deploying it to Zapier. This is especially useful for debugging and development.
809
+
810
+ This command loads \`authData\` from the \`.env\` file in the current directory. Create a \`.env\` file with the necessary auth data before running this command. Each line in \`.env\` should be in the format \`authData_FIELD_KEY=VALUE\`. For example, an OAuth2 integration might have a \`.env\` file like this:
811
+
812
+ \`\`\`
813
+ authData_access_token=1234567890
814
+ authData_other_auth_field=abcdef
815
+ \`\`\`
816
+
817
+ To test if the auth data is correct, run either one of these:
818
+
819
+ \`\`\`
820
+ zapier invoke auth test # invokes authentication.test method
821
+ zapier invoke auth label # invokes authentication.test and renders connection label
822
+ \`\`\`
823
+
824
+ Then you can test an trigger, a search, or a create action. For example, this is how you invoke a trigger with key \`new_recipe\`:
825
+
826
+ \`\`\`
827
+ zapier invoke trigger new_recipe
828
+ \`\`\`
829
+
830
+ To add input data, use the \`--inputData\` flag. The input data can come from the command directly, a file, or stdin. See **EXAMPLES** below.
831
+
832
+ The following are current limitations and may be supported in the future:
833
+
834
+ - \`zapier invoke auth start\` to help you initialize the auth data in \`.env\`
835
+ - \`zapier invoke auth refresh\` to refresh the auth data in \`.env\`
836
+ - Hook triggers, including REST hook subscribe/unsubscribe
837
+ - Line items
838
+ - Output hydration
839
+ - File upload
840
+ - Dynamic dropdown pagination
841
+ - Function-based connection label
842
+ - Buffered create actions
843
+ `;
844
+
845
+ module.exports = InvokeCommand;