@zapier/zapier-sdk-cli 0.15.1 → 0.15.3

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/dist/index.cjs CHANGED
@@ -291,7 +291,7 @@ var LoginSchema = zod.z.object({
291
291
 
292
292
  // package.json
293
293
  var package_default = {
294
- version: "0.15.1"};
294
+ version: "0.15.3"};
295
295
 
296
296
  // src/telemetry/builders.ts
297
297
  function createCliBaseEvent(context = {}) {
@@ -370,6 +370,8 @@ var loginPlugin = ({ context }) => {
370
370
  const startTime = Date.now();
371
371
  let success = false;
372
372
  let errorMessage = null;
373
+ let accountId = null;
374
+ let customUserId = null;
373
375
  try {
374
376
  await loginWithSdk({
375
377
  ...options,
@@ -378,6 +380,10 @@ var loginPlugin = ({ context }) => {
378
380
  authClientId: context.options?.authClientId
379
381
  });
380
382
  success = true;
383
+ try {
384
+ ({ accountId, customUserId } = await zapierSdkCliLogin.getLoggedInUser());
385
+ } catch {
386
+ }
381
387
  } catch (error) {
382
388
  success = false;
383
389
  errorMessage = error instanceof Error ? error.message : "Login failed";
@@ -401,7 +407,9 @@ var loginPlugin = ({ context }) => {
401
407
  session_id: context.session_id,
402
408
  selected_api: context.selected_api,
403
409
  app_id: context.app_id,
404
- app_version_id: context.app_version_id
410
+ app_version_id: context.app_version_id,
411
+ customuser_id: customUserId,
412
+ account_id: accountId
405
413
  },
406
414
  cliVersion: package_default.version
407
415
  });
package/dist/index.mjs CHANGED
@@ -260,7 +260,7 @@ var LoginSchema = z.object({
260
260
 
261
261
  // package.json
262
262
  var package_default = {
263
- version: "0.15.1"};
263
+ version: "0.15.3"};
264
264
 
265
265
  // src/telemetry/builders.ts
266
266
  function createCliBaseEvent(context = {}) {
@@ -339,6 +339,8 @@ var loginPlugin = ({ context }) => {
339
339
  const startTime = Date.now();
340
340
  let success = false;
341
341
  let errorMessage = null;
342
+ let accountId = null;
343
+ let customUserId = null;
342
344
  try {
343
345
  await loginWithSdk({
344
346
  ...options,
@@ -347,6 +349,10 @@ var loginPlugin = ({ context }) => {
347
349
  authClientId: context.options?.authClientId
348
350
  });
349
351
  success = true;
352
+ try {
353
+ ({ accountId, customUserId } = await getLoggedInUser());
354
+ } catch {
355
+ }
350
356
  } catch (error) {
351
357
  success = false;
352
358
  errorMessage = error instanceof Error ? error.message : "Login failed";
@@ -370,7 +376,9 @@ var loginPlugin = ({ context }) => {
370
376
  session_id: context.session_id,
371
377
  selected_api: context.selected_api,
372
378
  app_id: context.app_id,
373
- app_version_id: context.app_version_id
379
+ app_version_id: context.app_version_id,
380
+ customuser_id: customUserId,
381
+ account_id: accountId
374
382
  },
375
383
  cliVersion: package_default.version
376
384
  });
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zapier/zapier-sdk-cli",
3
- "version": "0.15.1",
3
+ "version": "0.15.3",
4
4
  "description": "Command line interface for Zapier SDK",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",
@@ -27,6 +27,8 @@ export const loginPlugin = ({ context }) => {
27
27
  const startTime = Date.now();
28
28
  let success = false;
29
29
  let errorMessage = null;
30
+ let accountId = null;
31
+ let customUserId = null;
30
32
  try {
31
33
  await loginWithSdk({
32
34
  ...options,
@@ -35,6 +37,13 @@ export const loginPlugin = ({ context }) => {
35
37
  authClientId: context.options?.authClientId,
36
38
  });
37
39
  success = true;
40
+ // Extract user IDs after successful login
41
+ try {
42
+ ({ accountId, customUserId } = await getLoggedInUser());
43
+ }
44
+ catch {
45
+ // If we can't get user info, continue without it
46
+ }
38
47
  }
39
48
  catch (error) {
40
49
  success = false;
@@ -62,6 +71,8 @@ export const loginPlugin = ({ context }) => {
62
71
  selected_api: context.selected_api,
63
72
  app_id: context.app_id,
64
73
  app_version_id: context.app_version_id,
74
+ customuser_id: customUserId,
75
+ account_id: accountId,
65
76
  },
66
77
  cliVersion: cliPackageJson.version,
67
78
  });
@@ -14,6 +14,26 @@ export declare class SchemaParameterResolver {
14
14
  private processFieldItems;
15
15
  private getNestedValue;
16
16
  private setNestedValue;
17
+ /**
18
+ * Extract and normalize field metadata from raw field object
19
+ */
20
+ private extractFieldMetadata;
21
+ /**
22
+ * Fetch a page of choices for a dropdown field
23
+ */
24
+ private fetchChoices;
25
+ /**
26
+ * Prompt user with choices (handles both single and multi-select with pagination)
27
+ */
28
+ private promptWithChoices;
29
+ /**
30
+ * Prompt user for free-form input (text or boolean)
31
+ */
32
+ private promptFreeForm;
33
+ /**
34
+ * Store field value in inputs object with validation
35
+ */
36
+ private storeFieldValue;
17
37
  private promptForField;
18
38
  private isUserCancellation;
19
39
  private hasResolver;
@@ -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
- async promptForField(field, inputs) {
461
+ /**
462
+ * Extract and normalize field metadata from raw field object
463
+ */
464
+ extractFieldMetadata(field) {
462
465
  const fieldObj = field;
463
- const fieldPrompt = {
464
- type: fieldObj.type === "boolean" ? "confirm" : "input",
465
- name: fieldObj.key,
466
- message: `${fieldObj.label || fieldObj.key}${fieldObj.is_required ? " (required)" : " (optional)"}:`,
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
- if (fieldObj.helpText) {
469
- fieldPrompt.prefix = chalk.gray(`ℹ ${fieldObj.helpText}\n`);
470
- }
471
- if (fieldObj.default !== undefined) {
472
- fieldPrompt.default = fieldObj.default;
473
- }
474
- if (fieldObj.choices && fieldObj.choices.length > 0) {
475
- fieldPrompt.type = "list";
476
- fieldPrompt.choices = fieldObj.choices.map((choice) => {
477
- const choiceObj = choice;
478
- return {
479
- name: choiceObj.label || choiceObj.value,
480
- value: choiceObj.value,
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([fieldPrompt]);
486
- if (answer[fieldObj.key] !== undefined && answer[fieldObj.key] !== "") {
487
- inputs[fieldObj.key] = answer[fieldObj.key];
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
- else if (fieldObj.is_required) {
490
- throw new Error(`Required field ${fieldObj.key} cannot be empty`);
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" ||