@zapier/zapier-sdk-cli 0.4.1 → 0.4.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cli.js +961 -284
  3. package/dist/src/cli.d.ts +2 -0
  4. package/dist/src/cli.js +28 -0
  5. package/dist/src/commands/bundle-code/cli.d.ts +2 -0
  6. package/dist/src/commands/bundle-code/cli.js +77 -0
  7. package/dist/src/commands/bundle-code/index.d.ts +5 -0
  8. package/dist/src/commands/bundle-code/index.js +62 -0
  9. package/dist/src/commands/bundle-code/schemas.d.ts +24 -0
  10. package/dist/src/commands/bundle-code/schemas.js +19 -0
  11. package/dist/src/commands/configPath.d.ts +2 -0
  12. package/dist/src/commands/configPath.js +9 -0
  13. package/dist/src/commands/generate-types/cli.d.ts +2 -0
  14. package/dist/src/commands/generate-types/cli.js +73 -0
  15. package/dist/src/commands/generate-types/index.d.ts +8 -0
  16. package/dist/src/commands/generate-types/index.js +273 -0
  17. package/dist/src/commands/generate-types/schemas.d.ts +18 -0
  18. package/dist/src/commands/generate-types/schemas.js +11 -0
  19. package/dist/src/commands/index.d.ts +6 -0
  20. package/dist/src/commands/index.js +6 -0
  21. package/dist/src/commands/login.d.ts +2 -0
  22. package/dist/src/commands/login.js +25 -0
  23. package/dist/src/commands/logout.d.ts +2 -0
  24. package/dist/src/commands/logout.js +16 -0
  25. package/dist/src/commands/mcp.d.ts +2 -0
  26. package/dist/src/commands/mcp.js +11 -0
  27. package/dist/src/index.d.ts +0 -0
  28. package/dist/src/index.js +3 -0
  29. package/dist/src/utils/api/client.d.ts +15 -0
  30. package/dist/src/utils/api/client.js +27 -0
  31. package/dist/src/utils/auth/login.d.ts +2 -0
  32. package/dist/src/utils/auth/login.js +134 -0
  33. package/dist/src/utils/cli-generator-utils.d.ts +13 -0
  34. package/dist/src/utils/cli-generator-utils.js +116 -0
  35. package/dist/src/utils/cli-generator.d.ts +3 -0
  36. package/dist/src/utils/cli-generator.js +443 -0
  37. package/dist/src/utils/constants.d.ts +5 -0
  38. package/dist/src/utils/constants.js +6 -0
  39. package/dist/src/utils/getCallablePromise.d.ts +6 -0
  40. package/dist/src/utils/getCallablePromise.js +14 -0
  41. package/dist/src/utils/log.d.ts +7 -0
  42. package/dist/src/utils/log.js +16 -0
  43. package/dist/src/utils/parameter-resolver.d.ts +14 -0
  44. package/dist/src/utils/parameter-resolver.js +387 -0
  45. package/dist/src/utils/schema-formatter.d.ts +2 -0
  46. package/dist/src/utils/schema-formatter.js +71 -0
  47. package/dist/src/utils/serializeAsync.d.ts +2 -0
  48. package/dist/src/utils/serializeAsync.js +16 -0
  49. package/dist/src/utils/spinner.d.ts +1 -0
  50. package/dist/src/utils/spinner.js +13 -0
  51. package/dist/tsconfig.tsbuildinfo +1 -0
  52. package/package.json +5 -3
  53. package/src/cli.test.ts +15 -0
  54. package/src/cli.ts +9 -3
  55. package/src/commands/bundle-code/cli.ts +103 -0
  56. package/src/commands/bundle-code/index.ts +91 -0
  57. package/src/commands/bundle-code/schemas.ts +24 -0
  58. package/src/commands/generate-types/cli.ts +110 -0
  59. package/src/commands/generate-types/index.ts +365 -0
  60. package/src/commands/generate-types/schemas.ts +23 -0
  61. package/src/commands/index.ts +3 -1
  62. package/src/commands/mcp.ts +14 -0
  63. package/src/utils/cli-generator-utils.ts +157 -0
  64. package/src/utils/cli-generator.ts +148 -91
  65. package/src/utils/parameter-resolver.ts +217 -85
  66. package/src/utils/schema-formatter.ts +1 -1
  67. package/tsconfig.json +3 -5
  68. package/src/commands/whoami.ts +0 -25
  69. package/src/utils/pager.ts +0 -202
  70. package/test/cli.test.ts +0 -46
@@ -52,16 +52,29 @@ export class SchemaParameterResolver {
52
52
  return !hasValue;
53
53
  });
54
54
 
55
- // Determine which parameters are "functionally required" vs truly optional
56
- // For run-action: appKey, actionType, actionKey are schema-required
57
- // authenticationId and inputs are schema-optional but functionally needed
55
+ // Determine parameter resolution categories:
56
+ // - functionally required: must be provided (inputs)
57
+ // - always prompt: should be prompted for but can be skipped (authenticationId)
58
+ // - truly optional: only ask if user wants to be prompted
58
59
  const functionallyRequired = missingResolvable.filter((param) => {
59
60
  // Schema-required parameters are always functionally required
60
61
  if (param.isRequired) return true;
61
62
 
62
- // For run-action, inputs and authenticationId are functionally required
63
- // even though schema-optional
64
- if (param.name === "inputs" || param.name === "authenticationId") {
63
+ // Only inputs is functionally required for run-action
64
+ if (param.name === "inputs") {
65
+ return true;
66
+ }
67
+
68
+ return false;
69
+ });
70
+
71
+ // Parameters that should always be prompted for directly, but can be skipped
72
+ const alwaysPrompt = missingResolvable.filter((param) => {
73
+ if (functionallyRequired.includes(param)) return false;
74
+
75
+ // authenticationId should always be prompted for (since it's usually needed)
76
+ // but can be skipped with "Continue without authentication"
77
+ if (param.name === "authenticationId") {
65
78
  return true;
66
79
  }
67
80
 
@@ -69,14 +82,19 @@ export class SchemaParameterResolver {
69
82
  });
70
83
 
71
84
  const trulyOptional = missingResolvable.filter(
72
- (param) => !functionallyRequired.includes(param),
85
+ (param) =>
86
+ !functionallyRequired.includes(param) && !alwaysPrompt.includes(param),
73
87
  );
74
88
 
75
- if (parseResult.success && functionallyRequired.length === 0) {
89
+ if (
90
+ parseResult.success &&
91
+ functionallyRequired.length === 0 &&
92
+ alwaysPrompt.length === 0
93
+ ) {
76
94
  return parseResult.data;
77
95
  }
78
96
 
79
- if (functionallyRequired.length === 0) {
97
+ if (functionallyRequired.length === 0 && alwaysPrompt.length === 0) {
80
98
  // No functionally required parameters missing, but check if we can parse
81
99
  if (!parseResult.success) {
82
100
  throw parseResult.error;
@@ -84,7 +102,7 @@ export class SchemaParameterResolver {
84
102
  return parseResult.data;
85
103
  }
86
104
 
87
- // 2. Resolve functionally required parameters first
105
+ // 2. Resolve functionally required parameters and their dependencies first
88
106
  const resolvedParams = { ...providedParams };
89
107
  const context: ResolverContext = {
90
108
  sdk,
@@ -97,10 +115,22 @@ export class SchemaParameterResolver {
97
115
  const requiredResolutionOrder =
98
116
  getResolutionOrderForParams(requiredParamNames);
99
117
 
118
+ // Find all parameters that need to be resolved (including dependencies)
119
+ // from the available resolvable parameters
100
120
  const orderedRequiredParams = requiredResolutionOrder
101
- .map((paramName) =>
102
- functionallyRequired.find((p) => p.name === paramName),
103
- )
121
+ .map((paramName) => {
122
+ // First try to find in functionally required
123
+ let param = functionallyRequired.find((p) => p.name === paramName);
124
+ // If not found, try always prompt (for dependencies like authenticationId)
125
+ if (!param) {
126
+ param = alwaysPrompt.find((p) => p.name === paramName);
127
+ }
128
+ // If not found, try truly optional (for other dependencies)
129
+ if (!param) {
130
+ param = trulyOptional.find((p) => p.name === paramName);
131
+ }
132
+ return param;
133
+ })
104
134
  .filter((param): param is ResolvableParameter => param !== undefined);
105
135
 
106
136
  for (const param of orderedRequiredParams) {
@@ -115,13 +145,54 @@ export class SchemaParameterResolver {
115
145
  console.log(chalk.yellow("\n\nOperation cancelled by user"));
116
146
  process.exit(0);
117
147
  }
118
- console.error(chalk.red(`Failed to resolve ${param.name}:`), error);
148
+ throw error;
149
+ }
150
+ }
151
+
152
+ // Remove resolved dependencies from other categories to avoid double-prompting
153
+ const resolvedParamNames = new Set(
154
+ orderedRequiredParams.map((p) => p.name),
155
+ );
156
+ alwaysPrompt.splice(
157
+ 0,
158
+ alwaysPrompt.length,
159
+ ...alwaysPrompt.filter((p) => !resolvedParamNames.has(p.name)),
160
+ );
161
+ trulyOptional.splice(
162
+ 0,
163
+ trulyOptional.length,
164
+ ...trulyOptional.filter((p) => !resolvedParamNames.has(p.name)),
165
+ );
166
+ }
167
+
168
+ // 3. Resolve parameters that should always be prompted for (but can be skipped)
169
+ if (alwaysPrompt.length > 0) {
170
+ const alwaysPromptNames = alwaysPrompt.map((p) => p.name);
171
+ const alwaysPromptResolutionOrder =
172
+ getResolutionOrderForParams(alwaysPromptNames);
173
+
174
+ const orderedAlwaysPromptParams = alwaysPromptResolutionOrder
175
+ .map((paramName) => alwaysPrompt.find((p) => p.name === paramName))
176
+ .filter((param): param is ResolvableParameter => param !== undefined);
177
+
178
+ for (const param of orderedAlwaysPromptParams) {
179
+ try {
180
+ const value = await this.resolveParameter(param, context);
181
+ this.setNestedValue(resolvedParams, param.path, value);
182
+
183
+ // Update context with newly resolved value
184
+ context.resolvedParams = resolvedParams;
185
+ } catch (error) {
186
+ if (this.isUserCancellation(error)) {
187
+ console.log(chalk.yellow("\n\nOperation cancelled by user"));
188
+ process.exit(0);
189
+ }
119
190
  throw error;
120
191
  }
121
192
  }
122
193
  }
123
194
 
124
- // 3. Ask user if they want to resolve truly optional parameters
195
+ // 4. Ask user if they want to resolve truly optional parameters
125
196
  if (trulyOptional.length > 0) {
126
197
  const optionalNames = trulyOptional.map((p) => p.name).join(", ");
127
198
  const shouldResolveOptional = await inquirer.prompt([
@@ -155,7 +226,6 @@ export class SchemaParameterResolver {
155
226
  console.log(chalk.yellow("\n\nOperation cancelled by user"));
156
227
  process.exit(0);
157
228
  }
158
- console.error(chalk.red(`Failed to resolve ${param.name}:`), error);
159
229
  throw error;
160
230
  }
161
231
  }
@@ -258,102 +328,164 @@ export class SchemaParameterResolver {
258
328
  } else if (resolver.type === "dynamic") {
259
329
  // Dynamic resolver - fetch options and prompt for selection
260
330
  try {
261
- console.log(chalk.gray(`Fetching options for ${param.name}...`));
262
- const items = await resolver.fetch(context.sdk, context.resolvedParams);
263
-
264
- if (!items || items.length === 0) {
265
- throw new Error(`No options available for ${param.name}`);
331
+ // Only show "Fetching..." for required parameters that typically have many options
332
+ if (param.isRequired && param.name !== "authenticationId") {
333
+ console.log(chalk.gray(`Fetching options for ${param.name}...`));
266
334
  }
335
+ const items = await resolver.fetch(context.sdk, context.resolvedParams);
267
336
 
268
- const promptConfig = resolver.prompt(items, context.resolvedParams);
337
+ // Let the resolver's prompt handle empty lists (e.g., authenticationId can show "skip authentication")
338
+ const safeItems = items || [];
339
+ const promptConfig = resolver.prompt(safeItems, context.resolvedParams);
269
340
  const answers = await inquirer.prompt([promptConfig as any]);
270
341
  return answers[param.name];
271
342
  } catch (error) {
272
- console.error(
273
- chalk.red(`Failed to fetch options for ${param.name}:`),
274
- error instanceof Error ? error.message : error,
275
- );
343
+ // Let the main CLI error handler display user-friendly errors
276
344
  throw error;
277
345
  }
278
346
  } else if ((resolver as any).type === "fields") {
279
- // Fields resolver - fetch field definitions and prompt for each input
280
- try {
281
- console.log(chalk.gray(`Fetching input fields for ${param.name}...`));
282
- const fields = await (resolver as any).fetch(
283
- context.sdk,
284
- context.resolvedParams,
285
- );
347
+ // Fields resolver - fetch field definitions and prompt for each input with recursive field resolution
348
+ return await this.resolveFieldsRecursively(
349
+ resolver as any,
350
+ context,
351
+ param,
352
+ );
353
+ }
354
+
355
+ throw new Error(`Unknown resolver type for ${param.name}`);
356
+ }
357
+
358
+ private async resolveFieldsRecursively(
359
+ resolver: any,
360
+ context: ResolverContext,
361
+ param: ResolvableParameter,
362
+ ): Promise<Record<string, any>> {
363
+ const inputs: Record<string, any> = {};
364
+ let processedFieldKeys = new Set<string>();
365
+ let iteration = 0;
366
+ const maxIterations = 5; // Prevent infinite loops
367
+
368
+ while (iteration < maxIterations) {
369
+ iteration++;
370
+
371
+ // Update context with current inputs so they're passed to listInputFields
372
+ const updatedContext = {
373
+ ...context,
374
+ resolvedParams: {
375
+ ...context.resolvedParams,
376
+ inputs,
377
+ },
378
+ };
379
+
380
+ console.log(
381
+ chalk.gray(
382
+ `Fetching input fields for ${param.name}${iteration > 1 ? ` (iteration ${iteration})` : ""}...`,
383
+ ),
384
+ );
385
+
386
+ const fields = await resolver.fetch(
387
+ updatedContext.sdk,
388
+ updatedContext.resolvedParams,
389
+ );
286
390
 
287
- if (!fields || fields.length === 0) {
391
+ if (!fields || fields.length === 0) {
392
+ if (iteration === 1) {
288
393
  console.log(
289
394
  chalk.yellow(`No input fields required for this action.`),
290
395
  );
291
- return {};
292
396
  }
397
+ break;
398
+ }
293
399
 
294
- const inputs: Record<string, any> = {};
400
+ // Find new fields that we haven't processed yet
401
+ const newFields = fields.filter(
402
+ (field: any) => !processedFieldKeys.has(field.key),
403
+ );
295
404
 
296
- // Separate required and optional fields
297
- const requiredFields = fields.filter((field: any) => field.required);
298
- const optionalFields = fields.filter((field: any) => !field.required);
405
+ if (newFields.length === 0) {
406
+ // No new fields, we're done
407
+ break;
408
+ }
299
409
 
300
- // First, prompt for all required fields
301
- if (requiredFields.length > 0) {
302
- console.log(
303
- chalk.blue(
304
- `\nšŸ“ Please provide values for the following input fields:`,
305
- ),
306
- );
307
- for (const field of requiredFields) {
308
- await this.promptForField(field, inputs);
309
- }
410
+ // Separate new required and optional fields
411
+ const newRequiredFields = newFields.filter(
412
+ (field: any) => field.required,
413
+ );
414
+ const newOptionalFields = newFields.filter(
415
+ (field: any) => !field.required,
416
+ );
417
+
418
+ // Prompt for new required fields
419
+ if (newRequiredFields.length > 0) {
420
+ console.log(
421
+ chalk.blue(
422
+ `\nšŸ“ Please provide values for the following ${iteration === 1 ? "" : "additional "}input fields:`,
423
+ ),
424
+ );
425
+ for (const field of newRequiredFields) {
426
+ await this.promptForField(field, inputs);
427
+ processedFieldKeys.add(field.key);
310
428
  }
429
+ }
311
430
 
312
- // Then ask if user wants to configure optional fields
313
- if (optionalFields.length > 0) {
314
- console.log(
315
- chalk.gray(
316
- `\nThere are ${optionalFields.length} optional field(s) available.`,
317
- ),
318
- );
431
+ // Ask about optional fields
432
+ let shouldConfigureOptional = { configure: false };
433
+ if (newOptionalFields.length > 0) {
434
+ console.log(
435
+ chalk.gray(
436
+ `\nThere are ${newOptionalFields.length} ${iteration === 1 ? "" : "additional "}optional field(s) available.`,
437
+ ),
438
+ );
319
439
 
320
- let shouldConfigureOptional;
321
- try {
322
- shouldConfigureOptional = await inquirer.prompt([
323
- {
324
- type: "confirm",
325
- name: "configure",
326
- message: "Would you like to configure optional fields?",
327
- default: false,
328
- },
329
- ]);
330
- } catch (error) {
331
- if (this.isUserCancellation(error)) {
332
- console.log(chalk.yellow("\n\nOperation cancelled by user"));
333
- process.exit(0);
334
- }
335
- throw error;
440
+ try {
441
+ shouldConfigureOptional = await inquirer.prompt([
442
+ {
443
+ type: "confirm",
444
+ name: "configure",
445
+ message: `Would you like to configure ${iteration === 1 ? "" : "these additional "}optional fields?`,
446
+ default: false,
447
+ },
448
+ ]);
449
+ } catch (error) {
450
+ if (this.isUserCancellation(error)) {
451
+ console.log(chalk.yellow("\n\nOperation cancelled by user"));
452
+ process.exit(0);
336
453
  }
454
+ throw error;
455
+ }
337
456
 
338
- if (shouldConfigureOptional.configure) {
339
- console.log(chalk.cyan(`\nOptional fields:`));
340
- for (const field of optionalFields) {
341
- await this.promptForField(field, inputs);
342
- }
457
+ if (shouldConfigureOptional.configure) {
458
+ console.log(chalk.cyan(`\nOptional fields:`));
459
+ for (const field of newOptionalFields) {
460
+ await this.promptForField(field, inputs);
461
+ processedFieldKeys.add(field.key);
343
462
  }
463
+ } else {
464
+ // Mark these fields as processed even if skipped to avoid re-asking
465
+ newOptionalFields.forEach((field: any) =>
466
+ processedFieldKeys.add(field.key),
467
+ );
344
468
  }
469
+ }
345
470
 
346
- return inputs;
347
- } catch (error) {
348
- console.error(
349
- chalk.red(`Failed to fetch fields for ${param.name}:`),
350
- error instanceof Error ? error.message : error,
351
- );
352
- throw error;
471
+ // If we only processed optional fields and skipped them, no need to re-fetch
472
+ if (
473
+ newRequiredFields.length === 0 &&
474
+ (!newOptionalFields.length || !shouldConfigureOptional.configure)
475
+ ) {
476
+ break;
353
477
  }
354
478
  }
355
479
 
356
- throw new Error(`Unknown resolver type for ${param.name}`);
480
+ if (iteration >= maxIterations) {
481
+ console.log(
482
+ chalk.yellow(
483
+ `\nāš ļø Maximum field resolution iterations reached. Some dynamic fields may not have been discovered.`,
484
+ ),
485
+ );
486
+ }
487
+
488
+ return inputs;
357
489
  }
358
490
 
359
491
  private getNestedValue(obj: any, path: string[]): any {
@@ -96,7 +96,7 @@ function applyStyle(value: string, style: string): string {
96
96
  function formatItemsGeneric(items: any[]): void {
97
97
  // Fallback formatting for items without schema metadata
98
98
  items.forEach((item, index) => {
99
- const name = item.name || item.key || item.id || "Item";
99
+ const name = item.title || item.name || item.key || item.id || "Item";
100
100
  console.log(`${chalk.gray(`${index + 1}.`)} ${chalk.cyan(name)}`);
101
101
  if (item.description) {
102
102
  console.log(` ${chalk.dim(item.description)}`);
package/tsconfig.json CHANGED
@@ -11,11 +11,9 @@
11
11
  "forceConsistentCasingInFileNames": true,
12
12
  "declaration": true,
13
13
  "outDir": "./dist",
14
- "baseUrl": ".",
15
- "paths": {
16
- "@zapier/zapier-sdk": ["../zapier-sdk/dist/index.mjs"]
17
- }
14
+ "composite": true
18
15
  },
19
16
  "include": ["src/**/*"],
20
- "exclude": ["node_modules", "dist", "**/*.test.ts"]
17
+ "exclude": ["node_modules", "dist", "**/*.test.ts"],
18
+ "references": [{ "path": "../zapier-sdk" }]
21
19
  }
@@ -1,25 +0,0 @@
1
- import { Command } from "commander";
2
- import { getLoggedInUser } from "@zapier/zapier-sdk-cli-login";
3
- import { spinPromise } from "../utils/spinner";
4
-
5
- export function createWhoamiCommand(): Command {
6
- return new Command("whoami")
7
- .description("Show current login status and user information")
8
- .action(async () => {
9
- try {
10
- const user = await spinPromise(
11
- getLoggedInUser(),
12
- "Checking login status...",
13
- );
14
-
15
- console.log(
16
- `āœ… Logged in as ${user.email} (Account ID: ${user.accountId})`,
17
- );
18
- } catch {
19
- console.log(
20
- "āŒ Not logged in. Use 'zapier-sdk login' to authenticate.",
21
- );
22
- process.exit(1);
23
- }
24
- });
25
- }
@@ -1,202 +0,0 @@
1
- import inquirer from "inquirer";
2
- import chalk from "chalk";
3
-
4
- export interface PagerOptions {
5
- pageSize?: number;
6
- showPrompt?: boolean;
7
- itemName?: string;
8
- }
9
-
10
- export interface PaginatedResult<T> {
11
- items: T[];
12
- hasMore: boolean;
13
- totalShown: number;
14
- }
15
-
16
- /**
17
- * Generic pager that handles pagination for any data source
18
- * Supports interactive "load more" functionality
19
- */
20
- export class Pager<T> {
21
- private pageSize: number;
22
- private showPrompt: boolean;
23
- private itemName: string;
24
- private allItems: T[] = [];
25
- private currentOffset = 0;
26
-
27
- constructor(options: PagerOptions = {}) {
28
- this.pageSize = options.pageSize || 20;
29
- this.showPrompt = options.showPrompt !== false;
30
- this.itemName = options.itemName || "items";
31
- }
32
-
33
- /**
34
- * Fetch and display paginated results with interactive loading
35
- */
36
- async paginate<TParams>(
37
- fetchFunction: (
38
- params: TParams & { limit: number; offset?: number },
39
- ) => Promise<T[]>,
40
- baseParams: TParams,
41
- displayFunction: (
42
- items: T[],
43
- totalShown: number,
44
- totalAvailable?: number,
45
- ) => void,
46
- ): Promise<PaginatedResult<T>> {
47
- let hasMore = true;
48
- let totalAvailable: number | undefined;
49
-
50
- while (hasMore) {
51
- // Fetch next page
52
- const params = {
53
- ...baseParams,
54
- limit: this.pageSize,
55
- offset: this.currentOffset,
56
- };
57
-
58
- try {
59
- const items = await fetchFunction(params);
60
-
61
- if (items.length === 0) {
62
- // Still call display function to show "No items found" message
63
- displayFunction(this.allItems, this.allItems.length, totalAvailable);
64
- hasMore = false;
65
- break;
66
- }
67
-
68
- // Check for pagination metadata to get total count
69
- const pagination = (items as any).__pagination;
70
- if (pagination && pagination.count) {
71
- totalAvailable = pagination.count;
72
- hasMore = pagination.hasNext;
73
- } else {
74
- // Fallback: check if we got fewer items than requested
75
- hasMore = items.length >= this.pageSize;
76
- }
77
-
78
- // Add to our collection
79
- this.allItems.push(...items);
80
- this.currentOffset += items.length;
81
-
82
- // Display current batch
83
- displayFunction(this.allItems, this.allItems.length, totalAvailable);
84
-
85
- // If prompting is disabled, continue automatically
86
- if (!this.showPrompt) {
87
- continue;
88
- }
89
-
90
- // If we have total count info, show more detailed prompt
91
- const totalInfo = totalAvailable
92
- ? ` of ${totalAvailable.toLocaleString()}`
93
- : "";
94
- const message = `Load more ${this.itemName}? (${this.allItems.length}${totalInfo} shown so far)`;
95
-
96
- // Ask user if they want to load more
97
- const { loadMore } = await inquirer.prompt([
98
- {
99
- type: "confirm",
100
- name: "loadMore",
101
- message,
102
- default: true,
103
- },
104
- ]);
105
-
106
- if (!loadMore) {
107
- hasMore = false;
108
- }
109
- } catch (error) {
110
- // Re-throw the error to be handled by the caller
111
- throw error;
112
- }
113
- }
114
-
115
- return {
116
- items: this.allItems,
117
- hasMore: this.currentOffset > 0 && hasMore,
118
- totalShown: this.allItems.length,
119
- };
120
- }
121
-
122
- /**
123
- * Reset the pager state
124
- */
125
- reset(): void {
126
- this.allItems = [];
127
- this.currentOffset = 0;
128
- }
129
- }
130
-
131
- /**
132
- * Convenience function to create a pager for a specific use case
133
- */
134
- export function createPager<T>(options: PagerOptions = {}): Pager<T> {
135
- return new Pager<T>(options);
136
- }
137
-
138
- /**
139
- * Simple pagination without interactivity - just fetches all results up to a limit
140
- */
141
- export async function fetchAllPages<T, TParams>(
142
- fetchFunction: (
143
- params: TParams & { limit: number; offset?: number },
144
- ) => Promise<T[]>,
145
- baseParams: TParams,
146
- maxResults: number = 100,
147
- ): Promise<T[]> {
148
- const pageSize = Math.min(maxResults, 50); // Reasonable page size
149
- const allItems: T[] = [];
150
- let offset = 0;
151
-
152
- while (allItems.length < maxResults) {
153
- const remainingItems = maxResults - allItems.length;
154
- const currentPageSize = Math.min(pageSize, remainingItems);
155
-
156
- const params = {
157
- ...baseParams,
158
- limit: currentPageSize,
159
- offset,
160
- };
161
-
162
- const items = await fetchFunction(params);
163
-
164
- if (items.length === 0) {
165
- break; // No more data
166
- }
167
-
168
- allItems.push(...items);
169
- offset += items.length;
170
-
171
- // If we got fewer items than requested, we've reached the end
172
- if (items.length < currentPageSize) {
173
- break;
174
- }
175
- }
176
-
177
- return allItems;
178
- }
179
-
180
- /**
181
- * Display helper for showing pagination status
182
- */
183
- export function showPaginationStatus(
184
- currentCount: number,
185
- requestedLimit: number,
186
- itemName: string = "items",
187
- ): void {
188
- if (currentCount >= requestedLimit) {
189
- console.log(
190
- chalk.yellow(
191
- `\nšŸ“„ Showing first ${currentCount} ${itemName} (limit: ${requestedLimit})`,
192
- ),
193
- );
194
- console.log(
195
- chalk.gray(
196
- `Use --limit to increase the limit, or add paging with --page`,
197
- ),
198
- );
199
- } else {
200
- console.log(chalk.gray(`\nšŸ“„ Showing all ${currentCount} ${itemName}`));
201
- }
202
- }