zapier-platform-cli 18.3.0 → 18.5.0
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.
- package/package.json +2 -2
- package/src/app-templates.js +1 -0
- package/src/generators/index.js +3 -2
- package/src/generators/templates/README.template.md +9 -9
- package/src/generators/templates/line-items/README.md +16 -0
- package/src/generators/templates/line-items/creates/order.js +67 -0
- package/src/generators/templates/line-items/index.js +12 -0
- package/src/generators/templates/line-items/test/creates.test.js +30 -0
- package/src/oclif/commands/invoke/action.js +4 -2
- package/src/oclif/commands/invoke/index.js +9 -2
- package/src/oclif/commands/invoke/input-types.js +10 -2
- package/src/oclif/commands/invoke/prompts.js +238 -11
- package/src/oclif/commands/invoke/remote.js +41 -15
- package/src/oclif/commands/versions.js +7 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zapier-platform-cli",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.5.0",
|
|
4
4
|
"description": "The CLI for managing integrations in Zapier Developer Platform.",
|
|
5
5
|
"repository": "zapier/zapier-platform",
|
|
6
6
|
"homepage": "https://platform.zapier.com/",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"ignore": "7.0.5",
|
|
42
42
|
"inquirer": "8.2.5",
|
|
43
43
|
"jscodeshift": "^17.3.0",
|
|
44
|
-
"lodash": "4.
|
|
44
|
+
"lodash": "4.18.1",
|
|
45
45
|
"luxon": "3.7.1",
|
|
46
46
|
"marked": "15.0.12",
|
|
47
47
|
"marked-terminal": "7.3.0",
|
package/src/app-templates.js
CHANGED
package/src/generators/index.js
CHANGED
|
@@ -217,6 +217,7 @@ const TEMPLATE_ROUTES = {
|
|
|
217
217
|
'digest-auth': writeForAuthTemplate,
|
|
218
218
|
'dynamic-dropdown': writeForStandaloneTemplate,
|
|
219
219
|
files: writeForStandaloneTemplate,
|
|
220
|
+
'line-items': writeForStandaloneTemplate,
|
|
220
221
|
minimal: writeForMinimalTemplate,
|
|
221
222
|
'oauth1-trello': writeForAuthTemplate,
|
|
222
223
|
oauth2: writeForAuthTemplate,
|
|
@@ -246,12 +247,12 @@ const ProjectGeneratorPromise = createGeneratorClass((Generator) => {
|
|
|
246
247
|
this.destinationRoot(path.resolve(this.options.path));
|
|
247
248
|
|
|
248
249
|
const jsFilter = filter(['*.js', '*.json', '*.ts'], { restore: true });
|
|
249
|
-
this.queueTransformStream(
|
|
250
|
+
this.queueTransformStream(
|
|
250
251
|
{ disabled: true },
|
|
251
252
|
jsFilter,
|
|
252
253
|
prettier({ singleQuote: true }),
|
|
253
254
|
jsFilter.restore,
|
|
254
|
-
|
|
255
|
+
);
|
|
255
256
|
}
|
|
256
257
|
|
|
257
258
|
async prompting() {
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
# <%= name %>
|
|
2
2
|
|
|
3
|
-
This Zapier integration project is generated by the `zapier init` CLI command.
|
|
3
|
+
This Zapier integration project is generated by the `zapier-platform init` CLI command.
|
|
4
4
|
|
|
5
5
|
These are what you normally do next:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
# Install dependencies
|
|
9
|
-
npm install # or you can use pnpm or yarn
|
|
9
|
+
npm install --ignore-scripts # or you can use pnpm or yarn
|
|
10
10
|
|
|
11
11
|
# Run tests
|
|
12
|
-
zapier test
|
|
12
|
+
zapier-platform test
|
|
13
13
|
|
|
14
14
|
# Register the integration on Zapier if you haven't
|
|
15
|
-
zapier register "App Title"
|
|
15
|
+
zapier-platform register "App Title"
|
|
16
16
|
|
|
17
17
|
# Or you can link to an existing integration on Zapier
|
|
18
|
-
zapier link
|
|
18
|
+
zapier-platform link
|
|
19
19
|
|
|
20
20
|
# Push it to Zapier
|
|
21
|
-
zapier push
|
|
21
|
+
zapier-platform push
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Then, to add more features, you can use the `zapier scaffold` command, for example:
|
|
24
|
+
Then, to add more features, you can use the `zapier-platform scaffold` command, for example:
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
27
|
# Add a trigger
|
|
28
|
-
zapier scaffold trigger contact
|
|
28
|
+
zapier-platform scaffold trigger contact
|
|
29
29
|
|
|
30
30
|
# Add an action
|
|
31
|
-
zapier scaffold create contact
|
|
31
|
+
zapier-platform scaffold create contact
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
Find out more on the latest docs: https://docs.zapier.com/platform
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# line-items
|
|
2
|
+
|
|
3
|
+
An example integration demonstrating line item support. Line items are fields
|
|
4
|
+
with a `children` property that represent structured, repeating data — like rows
|
|
5
|
+
in a spreadsheet or items in an order.
|
|
6
|
+
|
|
7
|
+
## Testing with `zapier-platform invoke`
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Non-interactive with JSON input
|
|
11
|
+
zapier-platform invoke create order --non-interactive \
|
|
12
|
+
-i '{"name": "My Order", "line_items": [{"product_name": "Pens", "quantity": "12", "price": "1.50"}]}'
|
|
13
|
+
|
|
14
|
+
# Interactive mode — use the line item editing UI
|
|
15
|
+
zapier-platform invoke create order -i '{"name": "My Order"}'
|
|
16
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const perform = async (z, bundle) => {
|
|
2
|
+
const response = await z.request({
|
|
3
|
+
url: 'https://httpbin.zapier-tooling.com/post',
|
|
4
|
+
method: 'POST',
|
|
5
|
+
body: {
|
|
6
|
+
name: bundle.inputData.name,
|
|
7
|
+
line_items: bundle.inputData.line_items,
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
return response.data;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
key: 'order',
|
|
16
|
+
noun: 'Order',
|
|
17
|
+
display: {
|
|
18
|
+
label: 'Create Order',
|
|
19
|
+
description: 'Creates a new order with line items.',
|
|
20
|
+
},
|
|
21
|
+
operation: {
|
|
22
|
+
inputFields: [
|
|
23
|
+
{ key: 'name', required: true, type: 'string', label: 'Order Name' },
|
|
24
|
+
{
|
|
25
|
+
key: 'line_items',
|
|
26
|
+
label: 'Line Items',
|
|
27
|
+
children: [
|
|
28
|
+
{
|
|
29
|
+
key: 'product_name',
|
|
30
|
+
type: 'string',
|
|
31
|
+
label: 'Product Name',
|
|
32
|
+
required: true,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: 'quantity',
|
|
36
|
+
type: 'integer',
|
|
37
|
+
label: 'Quantity',
|
|
38
|
+
required: true,
|
|
39
|
+
},
|
|
40
|
+
{ key: 'price', type: 'number', label: 'Unit Price' },
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
perform,
|
|
45
|
+
sample: {
|
|
46
|
+
id: 1,
|
|
47
|
+
name: 'Stationery Order',
|
|
48
|
+
line_items: [
|
|
49
|
+
{ product_name: 'Pens', quantity: 12, price: 1.5 },
|
|
50
|
+
{ product_name: 'Notebooks', quantity: 3, price: 8.99 },
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
outputFields: [
|
|
54
|
+
{ key: 'id', label: 'ID' },
|
|
55
|
+
{ key: 'name', label: 'Order Name' },
|
|
56
|
+
{
|
|
57
|
+
key: 'line_items',
|
|
58
|
+
label: 'Line Items',
|
|
59
|
+
children: [
|
|
60
|
+
{ key: 'product_name', label: 'Product Name' },
|
|
61
|
+
{ key: 'quantity', label: 'Quantity' },
|
|
62
|
+
{ key: 'price', label: 'Unit Price' },
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* globals describe, expect, test */
|
|
2
|
+
|
|
3
|
+
const zapier = require('zapier-platform-core');
|
|
4
|
+
|
|
5
|
+
const App = require('../index');
|
|
6
|
+
const appTester = zapier.createAppTester(App);
|
|
7
|
+
zapier.tools.env.inject();
|
|
8
|
+
|
|
9
|
+
describe('creates', () => {
|
|
10
|
+
test('create order with line items', async () => {
|
|
11
|
+
const bundle = {
|
|
12
|
+
inputData: {
|
|
13
|
+
name: 'Test Order',
|
|
14
|
+
line_items: [
|
|
15
|
+
{ product_name: 'Pens', quantity: 12, price: 1.5 },
|
|
16
|
+
{ product_name: 'Notebooks', quantity: 3, price: 8.99 },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
const result = await appTester(
|
|
21
|
+
App.creates.order.operation.perform,
|
|
22
|
+
bundle,
|
|
23
|
+
);
|
|
24
|
+
const body = JSON.parse(result.data);
|
|
25
|
+
expect(body.name).toBe('Test Order');
|
|
26
|
+
expect(body.line_items).toHaveLength(2);
|
|
27
|
+
expect(body.line_items[0].product_name).toBe('Pens');
|
|
28
|
+
expect(body.line_items[1].quantity).toBe(3);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const debug = require('debug')('zapier:invoke');
|
|
2
|
+
const _ = require('lodash');
|
|
2
3
|
|
|
3
4
|
const { startSpinner, endSpinner } = require('../../../utils/display');
|
|
4
5
|
const { customLogger } = require('./logger');
|
|
@@ -71,8 +72,9 @@ const invokeAction = async (command, context) => {
|
|
|
71
72
|
await promptForFields(command, context, inputFields, invokeAction);
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
// Preserve original inputData as inputDataRaw before type resolution
|
|
75
|
-
|
|
75
|
+
// Preserve original inputData as inputDataRaw before type resolution (deep
|
|
76
|
+
// copy needed because resolveInputDataTypes mutates nested objects in-place)
|
|
77
|
+
const inputDataRaw = _.cloneDeep(context.inputData);
|
|
76
78
|
let inputData;
|
|
77
79
|
if (context.remote) {
|
|
78
80
|
// Let the remote server resolve input data types
|
|
@@ -146,10 +146,12 @@ class InvokeCommand extends BaseCommand {
|
|
|
146
146
|
context.appId = (await getLinkedAppConfig(null, false))?.id;
|
|
147
147
|
context.deployKey = (await readCredentials(false))[AUTH_KEY];
|
|
148
148
|
|
|
149
|
+
const hasAuth = Boolean(context.appDefinition.authentication);
|
|
150
|
+
|
|
149
151
|
if (
|
|
150
152
|
context.authId === '-' ||
|
|
151
153
|
context.authId === '' ||
|
|
152
|
-
(context.remote && !context.authId)
|
|
154
|
+
(context.remote && !context.authId && hasAuth)
|
|
153
155
|
) {
|
|
154
156
|
if (context.nonInteractive) {
|
|
155
157
|
throw new Error(
|
|
@@ -159,6 +161,12 @@ class InvokeCommand extends BaseCommand {
|
|
|
159
161
|
context.authId = (await promptForAuthentication(this)).toString();
|
|
160
162
|
}
|
|
161
163
|
|
|
164
|
+
if (context.remote && !context.authId && !hasAuth) {
|
|
165
|
+
// The remote invoke API requires authentication_id in the POST body,
|
|
166
|
+
// but the server accepts 0 for apps without authentication configured.
|
|
167
|
+
context.authId = '0';
|
|
168
|
+
}
|
|
169
|
+
|
|
162
170
|
if (context.authId) {
|
|
163
171
|
context.authId = parseInt(context.authId);
|
|
164
172
|
if (isNaN(context.authId)) {
|
|
@@ -482,7 +490,6 @@ The \`--debug\` flag will show you the HTTP request logs and any console logs yo
|
|
|
482
490
|
The following is a non-exhaustive list of current limitations in local and relay mode. We may support them in the future.
|
|
483
491
|
|
|
484
492
|
- Hook triggers, including REST hook subscribe/unsubscribe
|
|
485
|
-
- Line items
|
|
486
493
|
- Output hydration
|
|
487
494
|
- File upload
|
|
488
495
|
- Function-based connection label
|
|
@@ -57,7 +57,8 @@ const parseDecimal = (s) => {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
const cleaned = chars.join('').replace(/[.,-]$/, '');
|
|
60
|
-
|
|
60
|
+
const result = parseFloat(cleaned);
|
|
61
|
+
return isNaN(result) ? 0 : result;
|
|
61
62
|
};
|
|
62
63
|
|
|
63
64
|
/**
|
|
@@ -232,7 +233,14 @@ const resolveInputDataTypes = (inputData, inputFields, timezone) => {
|
|
|
232
233
|
}
|
|
233
234
|
}
|
|
234
235
|
|
|
235
|
-
//
|
|
236
|
+
// Handle line items (fields with "children")
|
|
237
|
+
for (const field of inputFields) {
|
|
238
|
+
if (field.children && field.children.length && Array.isArray(inputData[field.key])) {
|
|
239
|
+
for (const item of inputData[field.key]) {
|
|
240
|
+
resolveInputDataTypes(item, field.children, timezone);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
236
244
|
|
|
237
245
|
return inputData;
|
|
238
246
|
};
|
|
@@ -71,10 +71,43 @@ const getLabelForDynamicDropdown = (obj, preferredKey, fallbackKey) => {
|
|
|
71
71
|
*/
|
|
72
72
|
const getMissingRequiredInputFields = (inputData, inputFields) => {
|
|
73
73
|
return inputFields.filter(
|
|
74
|
-
(f) =>
|
|
74
|
+
(f) =>
|
|
75
|
+
f.required &&
|
|
76
|
+
!f.default &&
|
|
77
|
+
(inputData[f.key] == null || inputData[f.key] === ''),
|
|
75
78
|
);
|
|
76
79
|
};
|
|
77
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Finds required child fields (line items) that are missing values in any row.
|
|
83
|
+
* @param {Object} inputData - The current input data
|
|
84
|
+
* @param {Array<Object>} inputFields - Array of field definitions
|
|
85
|
+
* @returns {Array<Object>} Array of required child fields missing in at least one row
|
|
86
|
+
*/
|
|
87
|
+
const getMissingRequiredChildFields = (inputData, inputFields) => {
|
|
88
|
+
const missing = [];
|
|
89
|
+
for (const field of inputFields) {
|
|
90
|
+
if (!field.children || !field.children.length) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const items = inputData[field.key];
|
|
94
|
+
if (!Array.isArray(items)) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const requiredChildren = field.children.filter(
|
|
98
|
+
(c) => c.required && c.type !== 'copy' && !c.default,
|
|
99
|
+
);
|
|
100
|
+
for (const item of items) {
|
|
101
|
+
for (const c of requiredChildren) {
|
|
102
|
+
if (item[c.key] == null || item[c.key] === '') {
|
|
103
|
+
missing.push(c);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return missing;
|
|
109
|
+
};
|
|
110
|
+
|
|
78
111
|
/**
|
|
79
112
|
* Fetches choices for a dynamic dropdown field.
|
|
80
113
|
* @param {import('../../ZapierBaseCommand')} command - The command instance for prompting
|
|
@@ -324,7 +357,8 @@ const promptForField = async (command, context, field, invokeAction) => {
|
|
|
324
357
|
let nextPagingToken = null;
|
|
325
358
|
|
|
326
359
|
while (
|
|
327
|
-
|
|
360
|
+
answer == null ||
|
|
361
|
+
answer === '' ||
|
|
328
362
|
answer === '__next_page__' ||
|
|
329
363
|
answer === '__prev_page__'
|
|
330
364
|
) {
|
|
@@ -378,8 +412,12 @@ const promptForField = async (command, context, field, invokeAction) => {
|
|
|
378
412
|
}
|
|
379
413
|
return answer;
|
|
380
414
|
} else if (field.type === 'boolean') {
|
|
381
|
-
|
|
382
|
-
|
|
415
|
+
if (field.required) {
|
|
416
|
+
const yes = await command.confirm(message, false, false, true);
|
|
417
|
+
return yes ? 'yes' : 'no';
|
|
418
|
+
} else {
|
|
419
|
+
return await command.prompt(message + ' (yes/no)', { useStderr: true });
|
|
420
|
+
}
|
|
383
421
|
} else {
|
|
384
422
|
return await command.prompt(message, { useStderr: true });
|
|
385
423
|
}
|
|
@@ -400,6 +438,7 @@ const promptOrErrorForRequiredInputFields = async (
|
|
|
400
438
|
inputFields,
|
|
401
439
|
invokeAction,
|
|
402
440
|
) => {
|
|
441
|
+
// Check top-level required fields
|
|
403
442
|
const missingFields = getMissingRequiredInputFields(
|
|
404
443
|
context.inputData,
|
|
405
444
|
inputFields,
|
|
@@ -420,6 +459,49 @@ const promptOrErrorForRequiredInputFields = async (
|
|
|
420
459
|
);
|
|
421
460
|
}
|
|
422
461
|
}
|
|
462
|
+
|
|
463
|
+
// Check required child fields (line items) per row
|
|
464
|
+
const missingChildFields = getMissingRequiredChildFields(
|
|
465
|
+
context.inputData,
|
|
466
|
+
inputFields,
|
|
467
|
+
);
|
|
468
|
+
if (missingChildFields.length) {
|
|
469
|
+
if (context.nonInteractive || context.meta.isFillingDynamicDropdown) {
|
|
470
|
+
throw new Error(
|
|
471
|
+
"You're in non-interactive mode, so you must at least specify these required fields with --inputData: \n" +
|
|
472
|
+
missingChildFields
|
|
473
|
+
.map((f) => '* ' + formatFieldDisplay(f))
|
|
474
|
+
.join('\n'),
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
// Prompt per row for missing child fields
|
|
478
|
+
for (const field of inputFields) {
|
|
479
|
+
if (!field.children || !field.children.length) {
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
const items = context.inputData[field.key];
|
|
483
|
+
if (!Array.isArray(items)) {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const requiredChildren = field.children.filter(
|
|
487
|
+
(c) => c.required && c.type !== 'copy' && !c.default,
|
|
488
|
+
);
|
|
489
|
+
for (let i = 0; i < items.length; i++) {
|
|
490
|
+
for (const c of requiredChildren) {
|
|
491
|
+
if (items[i][c.key] == null || items[i][c.key] === '') {
|
|
492
|
+
const label = field.label || field.key;
|
|
493
|
+
console.error(`\n${label} (row ${i + 1}):`);
|
|
494
|
+
items[i][c.key] = await promptForField(
|
|
495
|
+
command,
|
|
496
|
+
context,
|
|
497
|
+
c,
|
|
498
|
+
invokeAction,
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
423
505
|
};
|
|
424
506
|
|
|
425
507
|
/**
|
|
@@ -451,8 +533,22 @@ const promptForInputFieldEdit = async (
|
|
|
451
533
|
} else {
|
|
452
534
|
name = f.key;
|
|
453
535
|
}
|
|
454
|
-
|
|
455
|
-
|
|
536
|
+
const currentValue = context.inputData[f.key];
|
|
537
|
+
if (currentValue != null && currentValue !== '') {
|
|
538
|
+
if (Array.isArray(currentValue)) {
|
|
539
|
+
const MAX_LEN = 60;
|
|
540
|
+
const csv = currentValue
|
|
541
|
+
.map((item) => `{${Object.values(item).join(',')}}`)
|
|
542
|
+
.join(', ');
|
|
543
|
+
const count = `(${currentValue.length} ${currentValue.length === 1 ? 'item' : 'items'})`;
|
|
544
|
+
if (csv.length <= MAX_LEN) {
|
|
545
|
+
name += ` [${csv}] ${count}`;
|
|
546
|
+
} else {
|
|
547
|
+
name += ` [${csv.slice(0, MAX_LEN)}...] ${count}`;
|
|
548
|
+
}
|
|
549
|
+
} else {
|
|
550
|
+
name += ` [current: "${currentValue}"]`;
|
|
551
|
+
}
|
|
456
552
|
} else if (f.default) {
|
|
457
553
|
name += ` [default: "${f.default}"]`;
|
|
458
554
|
}
|
|
@@ -479,12 +575,143 @@ const promptForInputFieldEdit = async (
|
|
|
479
575
|
}
|
|
480
576
|
|
|
481
577
|
const field = inputFields.find((f) => f.key === fieldKey);
|
|
482
|
-
|
|
483
|
-
command,
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
578
|
+
if (field.children && field.children.length) {
|
|
579
|
+
await promptForLineItemEdit(command, context, field, invokeAction);
|
|
580
|
+
} else {
|
|
581
|
+
context.inputData[fieldKey] = await promptForField(
|
|
582
|
+
command,
|
|
583
|
+
context,
|
|
584
|
+
field,
|
|
585
|
+
invokeAction,
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Prompts the user to add a new line item row by prompting for each child field.
|
|
593
|
+
* @param {import('../../ZapierBaseCommand')} command - The command instance
|
|
594
|
+
* @param {Object} context - The execution context
|
|
595
|
+
* @param {Object} field - The parent field definition with children
|
|
596
|
+
* @param {Function} invokeAction - Function to invoke actions
|
|
597
|
+
* @returns {Promise<Object>} The new row object
|
|
598
|
+
*/
|
|
599
|
+
const promptForNewLineItemRow = async (
|
|
600
|
+
command,
|
|
601
|
+
context,
|
|
602
|
+
field,
|
|
603
|
+
invokeAction,
|
|
604
|
+
) => {
|
|
605
|
+
const row = {};
|
|
606
|
+
for (const child of field.children) {
|
|
607
|
+
if (child.default) {
|
|
608
|
+
row[child.key] = child.default;
|
|
609
|
+
}
|
|
610
|
+
if (child.required) {
|
|
611
|
+
row[child.key] = await promptForField(
|
|
612
|
+
command,
|
|
613
|
+
context,
|
|
614
|
+
child,
|
|
615
|
+
invokeAction,
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return row;
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Sub-menu for editing line item rows: add, edit, remove rows.
|
|
624
|
+
* @param {import('../../ZapierBaseCommand')} command - The command instance
|
|
625
|
+
* @param {Object} context - The execution context (inputData will be mutated)
|
|
626
|
+
* @param {Object} field - The parent field definition with children
|
|
627
|
+
* @param {Function} invokeAction - Function to invoke actions
|
|
628
|
+
* @returns {Promise<void>}
|
|
629
|
+
*/
|
|
630
|
+
const promptForLineItemEdit = async (command, context, field, invokeAction) => {
|
|
631
|
+
if (!Array.isArray(context.inputData[field.key])) {
|
|
632
|
+
context.inputData[field.key] = [];
|
|
633
|
+
}
|
|
634
|
+
const items = context.inputData[field.key];
|
|
635
|
+
const label = field.label || field.key;
|
|
636
|
+
|
|
637
|
+
while (true) {
|
|
638
|
+
const choices = [
|
|
639
|
+
{ name: '>>> BACK <<<', short: 'BACK', value: '__back__' },
|
|
640
|
+
{ name: '>>> ADD ITEM <<<', value: '__add__' },
|
|
641
|
+
];
|
|
642
|
+
for (let i = 0; i < items.length; i++) {
|
|
643
|
+
const parts = Object.entries(items[i])
|
|
644
|
+
.map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
|
|
645
|
+
.join(', ');
|
|
646
|
+
choices.push({
|
|
647
|
+
name: parts ? `[${i}] {${parts}}` : `[${i}] Empty item - select to edit`,
|
|
648
|
+
value: `__select_${i}`,
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const action = await command.promptWithList(
|
|
653
|
+
`${label} [${items.length} ${items.length === 1 ? 'item' : 'items'}]:`,
|
|
654
|
+
choices,
|
|
655
|
+
{ useStderr: true },
|
|
487
656
|
);
|
|
657
|
+
|
|
658
|
+
if (action === '__back__') {
|
|
659
|
+
break;
|
|
660
|
+
} else if (action === '__add__') {
|
|
661
|
+
const row = await promptForNewLineItemRow(
|
|
662
|
+
command,
|
|
663
|
+
context,
|
|
664
|
+
field,
|
|
665
|
+
invokeAction,
|
|
666
|
+
);
|
|
667
|
+
items.push(row);
|
|
668
|
+
} else if (action.startsWith('__select_')) {
|
|
669
|
+
const idx = parseInt(action.slice(9));
|
|
670
|
+
// Sub-menu loop for editing/deleting the selected item
|
|
671
|
+
while (true) {
|
|
672
|
+
const editChoices = [
|
|
673
|
+
{ name: '>>> BACK <<<', short: 'BACK', value: '__back__' },
|
|
674
|
+
];
|
|
675
|
+
for (const child of field.children) {
|
|
676
|
+
const current = items[idx] && items[idx][child.key];
|
|
677
|
+
let choiceName;
|
|
678
|
+
if (child.label) {
|
|
679
|
+
choiceName = `${child.label} (${child.key})`;
|
|
680
|
+
} else {
|
|
681
|
+
choiceName = child.key;
|
|
682
|
+
}
|
|
683
|
+
if (current != null) {
|
|
684
|
+
choiceName += ` [current: "${current}"]`;
|
|
685
|
+
}
|
|
686
|
+
editChoices.push({ name: choiceName, value: child.key });
|
|
687
|
+
}
|
|
688
|
+
editChoices.push({
|
|
689
|
+
name: '>>> DELETE ITEM <<<',
|
|
690
|
+
value: '__delete__',
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
const editAction = await command.promptWithList(
|
|
694
|
+
'Edit or delete the item?',
|
|
695
|
+
editChoices,
|
|
696
|
+
{ useStderr: true },
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
if (editAction === '__back__') {
|
|
700
|
+
break;
|
|
701
|
+
} else if (editAction === '__delete__') {
|
|
702
|
+
items.splice(idx, 1);
|
|
703
|
+
break;
|
|
704
|
+
} else {
|
|
705
|
+
const child = field.children.find((c) => c.key === editAction);
|
|
706
|
+
items[idx][editAction] = await promptForField(
|
|
707
|
+
command,
|
|
708
|
+
context,
|
|
709
|
+
child,
|
|
710
|
+
invokeAction,
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
488
715
|
}
|
|
489
716
|
};
|
|
490
717
|
|
|
@@ -37,22 +37,48 @@ const fetchInputFields = async (context) => {
|
|
|
37
37
|
},
|
|
38
38
|
},
|
|
39
39
|
);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
alterDynamicFields: need.alter_dynamic_fields ?? false,
|
|
54
|
-
};
|
|
40
|
+
const mapNeed = (need) => ({
|
|
41
|
+
key: need.key,
|
|
42
|
+
type: FIELD_TYPE_MAP[need.type] || need.type,
|
|
43
|
+
required: need.required,
|
|
44
|
+
default: need.default,
|
|
45
|
+
choices: need.choices,
|
|
46
|
+
label: need.label,
|
|
47
|
+
helpText: need.help_text,
|
|
48
|
+
inputFormat: need.input_format,
|
|
49
|
+
dynamic: need.prefill,
|
|
50
|
+
list: need.list,
|
|
51
|
+
placeholder: need.placeholder,
|
|
52
|
+
alterDynamicFields: need.alter_dynamic_fields ?? false,
|
|
55
53
|
});
|
|
54
|
+
|
|
55
|
+
// Group child fields (those with parent_key) under their parent
|
|
56
|
+
const parentKeys = new Set(
|
|
57
|
+
responseData.needs.filter((n) => n.parent_key).map((n) => n.parent_key),
|
|
58
|
+
);
|
|
59
|
+
const fields = [];
|
|
60
|
+
const childrenByParent = {};
|
|
61
|
+
for (const need of responseData.needs) {
|
|
62
|
+
if (need.parent_key) {
|
|
63
|
+
if (!childrenByParent[need.parent_key]) {
|
|
64
|
+
childrenByParent[need.parent_key] = [];
|
|
65
|
+
}
|
|
66
|
+
childrenByParent[need.parent_key].push(mapNeed(need));
|
|
67
|
+
} else {
|
|
68
|
+
fields.push(mapNeed(need));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Add parent fields for any parent_key that doesn't have a corresponding
|
|
72
|
+
// top-level field in the response, then attach children
|
|
73
|
+
for (const parentKey of parentKeys) {
|
|
74
|
+
let parent = fields.find((f) => f.key === parentKey);
|
|
75
|
+
if (!parent) {
|
|
76
|
+
parent = { key: parentKey };
|
|
77
|
+
fields.push(parent);
|
|
78
|
+
}
|
|
79
|
+
parent.children = childrenByParent[parentKey];
|
|
80
|
+
}
|
|
81
|
+
return fields;
|
|
56
82
|
};
|
|
57
83
|
|
|
58
84
|
const fetchChoices = async (context, inputFieldKey) => {
|
|
@@ -4,7 +4,7 @@ const { buildFlags } = require('../buildFlags');
|
|
|
4
4
|
|
|
5
5
|
const { listVersions } = require('../../utils/api');
|
|
6
6
|
|
|
7
|
-
class
|
|
7
|
+
class VersionsCommand extends BaseCommand {
|
|
8
8
|
async perform() {
|
|
9
9
|
this.startSpinner('Loading versions');
|
|
10
10
|
const { versions } = await listVersions();
|
|
@@ -27,7 +27,8 @@ class VersionCommand extends BaseCommand {
|
|
|
27
27
|
['State', 'state'],
|
|
28
28
|
['Legacy Date', 'legacy_date'],
|
|
29
29
|
['Deprecation Date', 'deprecation_date'],
|
|
30
|
-
['
|
|
30
|
+
['Created at', 'date'],
|
|
31
|
+
['Updated at', 'last_changed'],
|
|
31
32
|
],
|
|
32
33
|
emptyMessage:
|
|
33
34
|
'No versions to show. Try adding one with the `zapier push` command',
|
|
@@ -41,8 +42,8 @@ class VersionCommand extends BaseCommand {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
VersionsCommand.skipValidInstallCheck = true;
|
|
46
|
+
VersionsCommand.flags = buildFlags({
|
|
46
47
|
commandFlags: {
|
|
47
48
|
all: Flags.boolean({
|
|
48
49
|
char: 'a',
|
|
@@ -51,6 +52,6 @@ VersionCommand.flags = buildFlags({
|
|
|
51
52
|
},
|
|
52
53
|
opts: { format: true },
|
|
53
54
|
});
|
|
54
|
-
|
|
55
|
+
VersionsCommand.description = `List the versions of your integration available for use in Zapier automations.`;
|
|
55
56
|
|
|
56
|
-
module.exports =
|
|
57
|
+
module.exports = VersionsCommand;
|