@zapier/zapier-sdk-cli 0.15.1 ā 0.15.2
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/CHANGELOG.md +6 -0
- package/dist/cli.cjs +196 -33
- package/dist/cli.mjs +196 -33
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/package.json +1 -1
- package/dist/src/utils/parameter-resolver.d.ts +20 -0
- package/dist/src/utils/parameter-resolver.js +189 -29
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/utils/parameter-resolver.ts +275 -32
|
@@ -324,7 +324,7 @@ export class SchemaParameterResolver {
|
|
|
324
324
|
break;
|
|
325
325
|
}
|
|
326
326
|
// Process fields recursively, maintaining fieldset structure
|
|
327
|
-
const fieldStats = await this.processFieldItems(rootFieldItems, inputs, processedFieldKeys, [], iteration);
|
|
327
|
+
const fieldStats = await this.processFieldItems(rootFieldItems, inputs, processedFieldKeys, [], iteration, updatedContext);
|
|
328
328
|
// If no new fields were processed, we're done
|
|
329
329
|
if (fieldStats.newRequired === 0 && fieldStats.newOptional === 0) {
|
|
330
330
|
break;
|
|
@@ -343,7 +343,7 @@ export class SchemaParameterResolver {
|
|
|
343
343
|
* Recursively processes fieldsets and their fields, maintaining natural structure
|
|
344
344
|
* and creating nested inputs as needed (e.g., fieldset "foo" becomes inputs.foo = [{}])
|
|
345
345
|
*/
|
|
346
|
-
async processFieldItems(items, targetInputs, processedFieldKeys, fieldsetPath = [], iteration = 1) {
|
|
346
|
+
async processFieldItems(items, targetInputs, processedFieldKeys, fieldsetPath = [], iteration = 1, context) {
|
|
347
347
|
let newRequiredCount = 0;
|
|
348
348
|
let newOptionalCount = 0;
|
|
349
349
|
let optionalSkipped = false;
|
|
@@ -361,7 +361,7 @@ export class SchemaParameterResolver {
|
|
|
361
361
|
// Process fields within this fieldset recursively
|
|
362
362
|
const fieldsetTarget = targetInputs[typedItem.key][0];
|
|
363
363
|
const nestedPath = [...fieldsetPath, fieldsetTitle];
|
|
364
|
-
const nestedStats = await this.processFieldItems(typedItem.fields, fieldsetTarget, processedFieldKeys, nestedPath, iteration);
|
|
364
|
+
const nestedStats = await this.processFieldItems(typedItem.fields, fieldsetTarget, processedFieldKeys, nestedPath, iteration, context);
|
|
365
365
|
newRequiredCount += nestedStats.newRequired;
|
|
366
366
|
newOptionalCount += nestedStats.newOptional;
|
|
367
367
|
if (nestedStats.optionalSkipped) {
|
|
@@ -381,7 +381,7 @@ export class SchemaParameterResolver {
|
|
|
381
381
|
// Only show this message once at root level
|
|
382
382
|
console.log(chalk.blue(`\nš Please provide values for the following ${iteration === 1 ? "" : "additional "}input fields:`));
|
|
383
383
|
}
|
|
384
|
-
await this.promptForField(typedItem, targetInputs);
|
|
384
|
+
await this.promptForField(typedItem, targetInputs, context);
|
|
385
385
|
processedFieldKeys.add(typedItem.key);
|
|
386
386
|
}
|
|
387
387
|
else {
|
|
@@ -415,7 +415,7 @@ export class SchemaParameterResolver {
|
|
|
415
415
|
if (shouldConfigureOptional.configure) {
|
|
416
416
|
console.log(chalk.cyan(`\nOptional fields${pathContext}:`));
|
|
417
417
|
for (const field of optionalFields) {
|
|
418
|
-
await this.promptForField(field, targetInputs);
|
|
418
|
+
await this.promptForField(field, targetInputs, context);
|
|
419
419
|
const typedField = field;
|
|
420
420
|
processedFieldKeys.add(typedField.key);
|
|
421
421
|
}
|
|
@@ -458,36 +458,169 @@ export class SchemaParameterResolver {
|
|
|
458
458
|
}, obj);
|
|
459
459
|
parent[lastKey] = value;
|
|
460
460
|
}
|
|
461
|
-
|
|
461
|
+
/**
|
|
462
|
+
* Extract and normalize field metadata from raw field object
|
|
463
|
+
*/
|
|
464
|
+
extractFieldMetadata(field) {
|
|
462
465
|
const fieldObj = field;
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
466
|
+
const valueType = fieldObj.value_type || "string";
|
|
467
|
+
return {
|
|
468
|
+
key: fieldObj.key,
|
|
469
|
+
title: fieldObj.title || fieldObj.label || fieldObj.key,
|
|
470
|
+
description: fieldObj.description || fieldObj.helpText,
|
|
471
|
+
isRequired: fieldObj.is_required || false,
|
|
472
|
+
defaultValue: fieldObj.default_value ?? fieldObj.default,
|
|
473
|
+
valueType,
|
|
474
|
+
hasDropdown: fieldObj.format === "SELECT",
|
|
475
|
+
isMultiSelect: Boolean(valueType === "array" ||
|
|
476
|
+
(fieldObj.items && fieldObj.items.type !== undefined)),
|
|
467
477
|
};
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Fetch a page of choices for a dropdown field
|
|
481
|
+
*/
|
|
482
|
+
async fetchChoices(fieldMeta, inputs, context, cursor) {
|
|
483
|
+
try {
|
|
484
|
+
console.log(chalk.gray(cursor
|
|
485
|
+
? ` Fetching more choices...`
|
|
486
|
+
: ` Fetching choices for ${fieldMeta.title}...`));
|
|
487
|
+
const page = await context.sdk.listInputFieldChoices({
|
|
488
|
+
appKey: context.resolvedParams.appKey,
|
|
489
|
+
actionKey: context.resolvedParams.actionKey,
|
|
490
|
+
actionType: context.resolvedParams.actionType,
|
|
491
|
+
authenticationId: context.resolvedParams.authenticationId,
|
|
492
|
+
inputFieldKey: fieldMeta.key,
|
|
493
|
+
inputs,
|
|
494
|
+
...(cursor && { cursor }),
|
|
482
495
|
});
|
|
496
|
+
const choices = page.data.map((choice) => ({
|
|
497
|
+
label: choice.label || choice.key || String(choice.value),
|
|
498
|
+
value: choice.value ?? choice.key,
|
|
499
|
+
}));
|
|
500
|
+
if (choices.length === 0 && !cursor) {
|
|
501
|
+
console.log(chalk.yellow(` No choices available for ${fieldMeta.title}`));
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
choices,
|
|
505
|
+
nextCursor: page.nextCursor,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
catch (error) {
|
|
509
|
+
console.warn(chalk.yellow(` ā ļø Failed to fetch choices for ${fieldMeta.title}:`), error);
|
|
510
|
+
return { choices: [] };
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Prompt user with choices (handles both single and multi-select with pagination)
|
|
515
|
+
*/
|
|
516
|
+
async promptWithChoices({ fieldMeta, choices: initialChoices, nextCursor: initialCursor, inputs, context, }) {
|
|
517
|
+
const choices = [...initialChoices];
|
|
518
|
+
let nextCursor = initialCursor;
|
|
519
|
+
const LOAD_MORE_SENTINEL = Symbol("LOAD_MORE");
|
|
520
|
+
// Progressive loading loop
|
|
521
|
+
while (true) {
|
|
522
|
+
const promptChoices = choices.map((choice) => ({
|
|
523
|
+
name: choice.label,
|
|
524
|
+
value: choice.value,
|
|
525
|
+
}));
|
|
526
|
+
// Add "(Load more...)" option if there are more pages
|
|
527
|
+
if (nextCursor) {
|
|
528
|
+
promptChoices.push({
|
|
529
|
+
name: chalk.dim("(Load more...)"),
|
|
530
|
+
value: LOAD_MORE_SENTINEL,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
// Add skip option for optional fields (single-select only)
|
|
534
|
+
if (!fieldMeta.isRequired && !fieldMeta.isMultiSelect) {
|
|
535
|
+
promptChoices.push({ name: "(Skip)", value: undefined });
|
|
536
|
+
}
|
|
537
|
+
const promptConfig = {
|
|
538
|
+
type: fieldMeta.isMultiSelect ? "checkbox" : "list",
|
|
539
|
+
name: fieldMeta.key,
|
|
540
|
+
message: `${fieldMeta.title}${fieldMeta.isRequired ? " (required)" : " (optional)"}:`,
|
|
541
|
+
choices: promptChoices,
|
|
542
|
+
...(fieldMeta.isMultiSelect && {
|
|
543
|
+
validate: (input) => {
|
|
544
|
+
if (fieldMeta.isRequired && (!input || input.length === 0)) {
|
|
545
|
+
return "At least one selection is required";
|
|
546
|
+
}
|
|
547
|
+
return true;
|
|
548
|
+
},
|
|
549
|
+
}),
|
|
550
|
+
};
|
|
551
|
+
const answer = await inquirer.prompt([promptConfig]);
|
|
552
|
+
let selectedValue = answer[fieldMeta.key];
|
|
553
|
+
// Check if user selected "Load more..."
|
|
554
|
+
const wantsMore = fieldMeta.isMultiSelect
|
|
555
|
+
? Array.isArray(selectedValue) &&
|
|
556
|
+
selectedValue.includes(LOAD_MORE_SENTINEL)
|
|
557
|
+
: selectedValue === LOAD_MORE_SENTINEL;
|
|
558
|
+
if (wantsMore && nextCursor && context) {
|
|
559
|
+
// Remove sentinel from multi-select
|
|
560
|
+
if (fieldMeta.isMultiSelect && Array.isArray(selectedValue)) {
|
|
561
|
+
selectedValue = selectedValue.filter((v) => v !== LOAD_MORE_SENTINEL);
|
|
562
|
+
}
|
|
563
|
+
// Fetch next page
|
|
564
|
+
const result = await this.fetchChoices(fieldMeta, inputs, context, nextCursor);
|
|
565
|
+
choices.push(...result.choices);
|
|
566
|
+
nextCursor = result.nextCursor;
|
|
567
|
+
// Re-prompt with updated choices
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
return selectedValue;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Prompt user for free-form input (text or boolean)
|
|
575
|
+
*/
|
|
576
|
+
async promptFreeForm(fieldMeta) {
|
|
577
|
+
const promptConfig = {
|
|
578
|
+
name: fieldMeta.key,
|
|
579
|
+
message: `${fieldMeta.title}${fieldMeta.isRequired ? " (required)" : " (optional)"}:`,
|
|
580
|
+
};
|
|
581
|
+
if (fieldMeta.valueType === "boolean") {
|
|
582
|
+
promptConfig.type = "confirm";
|
|
583
|
+
promptConfig.default =
|
|
584
|
+
fieldMeta.defaultValue !== undefined
|
|
585
|
+
? Boolean(fieldMeta.defaultValue)
|
|
586
|
+
: undefined;
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
promptConfig.type = "input";
|
|
590
|
+
promptConfig.default = fieldMeta.defaultValue;
|
|
591
|
+
promptConfig.validate = (input) => {
|
|
592
|
+
if (fieldMeta.isRequired && !input) {
|
|
593
|
+
return "This field is required";
|
|
594
|
+
}
|
|
595
|
+
return true;
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
// Add help text if available
|
|
599
|
+
if (fieldMeta.description) {
|
|
600
|
+
promptConfig.prefix = chalk.gray(`ā¹ ${fieldMeta.description}\n`);
|
|
483
601
|
}
|
|
484
602
|
try {
|
|
485
|
-
const answer = await inquirer.prompt([
|
|
486
|
-
|
|
487
|
-
|
|
603
|
+
const answer = await inquirer.prompt([promptConfig]);
|
|
604
|
+
return answer[fieldMeta.key];
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
if (this.isUserCancellation(error)) {
|
|
608
|
+
console.log(chalk.yellow("\n\nOperation cancelled by user"));
|
|
609
|
+
throw new ZapierCliUserCancellationError();
|
|
488
610
|
}
|
|
489
|
-
|
|
490
|
-
|
|
611
|
+
throw error;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Store field value in inputs object with validation
|
|
616
|
+
*/
|
|
617
|
+
storeFieldValue(inputs, key, value, isRequired) {
|
|
618
|
+
try {
|
|
619
|
+
if (value !== undefined && value !== "") {
|
|
620
|
+
inputs[key] = value;
|
|
621
|
+
}
|
|
622
|
+
else if (isRequired) {
|
|
623
|
+
throw new Error(`Required field ${key} cannot be empty`);
|
|
491
624
|
}
|
|
492
625
|
}
|
|
493
626
|
catch (error) {
|
|
@@ -498,6 +631,33 @@ export class SchemaParameterResolver {
|
|
|
498
631
|
throw error;
|
|
499
632
|
}
|
|
500
633
|
}
|
|
634
|
+
async promptForField(field, inputs, context) {
|
|
635
|
+
const fieldMeta = this.extractFieldMetadata(field);
|
|
636
|
+
// Fetch choices if field has dropdown
|
|
637
|
+
let choices = [];
|
|
638
|
+
let nextCursor;
|
|
639
|
+
if (fieldMeta.hasDropdown && context) {
|
|
640
|
+
const result = await this.fetchChoices(fieldMeta, inputs, context);
|
|
641
|
+
choices = result.choices;
|
|
642
|
+
nextCursor = result.nextCursor;
|
|
643
|
+
}
|
|
644
|
+
// Prompt user based on field type
|
|
645
|
+
let selectedValue;
|
|
646
|
+
if (choices.length > 0) {
|
|
647
|
+
selectedValue = await this.promptWithChoices({
|
|
648
|
+
fieldMeta,
|
|
649
|
+
choices,
|
|
650
|
+
nextCursor,
|
|
651
|
+
inputs,
|
|
652
|
+
context,
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
selectedValue = await this.promptFreeForm(fieldMeta);
|
|
657
|
+
}
|
|
658
|
+
// Store result
|
|
659
|
+
this.storeFieldValue(inputs, fieldMeta.key, selectedValue, fieldMeta.isRequired);
|
|
660
|
+
}
|
|
501
661
|
isUserCancellation(error) {
|
|
502
662
|
const errorObj = error;
|
|
503
663
|
return (errorObj?.name === "ExitPromptError" ||
|