@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.
- package/CHANGELOG.md +8 -0
- package/dist/cli.js +961 -284
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +28 -0
- package/dist/src/commands/bundle-code/cli.d.ts +2 -0
- package/dist/src/commands/bundle-code/cli.js +77 -0
- package/dist/src/commands/bundle-code/index.d.ts +5 -0
- package/dist/src/commands/bundle-code/index.js +62 -0
- package/dist/src/commands/bundle-code/schemas.d.ts +24 -0
- package/dist/src/commands/bundle-code/schemas.js +19 -0
- package/dist/src/commands/configPath.d.ts +2 -0
- package/dist/src/commands/configPath.js +9 -0
- package/dist/src/commands/generate-types/cli.d.ts +2 -0
- package/dist/src/commands/generate-types/cli.js +73 -0
- package/dist/src/commands/generate-types/index.d.ts +8 -0
- package/dist/src/commands/generate-types/index.js +273 -0
- package/dist/src/commands/generate-types/schemas.d.ts +18 -0
- package/dist/src/commands/generate-types/schemas.js +11 -0
- package/dist/src/commands/index.d.ts +6 -0
- package/dist/src/commands/index.js +6 -0
- package/dist/src/commands/login.d.ts +2 -0
- package/dist/src/commands/login.js +25 -0
- package/dist/src/commands/logout.d.ts +2 -0
- package/dist/src/commands/logout.js +16 -0
- package/dist/src/commands/mcp.d.ts +2 -0
- package/dist/src/commands/mcp.js +11 -0
- package/dist/src/index.d.ts +0 -0
- package/dist/src/index.js +3 -0
- package/dist/src/utils/api/client.d.ts +15 -0
- package/dist/src/utils/api/client.js +27 -0
- package/dist/src/utils/auth/login.d.ts +2 -0
- package/dist/src/utils/auth/login.js +134 -0
- package/dist/src/utils/cli-generator-utils.d.ts +13 -0
- package/dist/src/utils/cli-generator-utils.js +116 -0
- package/dist/src/utils/cli-generator.d.ts +3 -0
- package/dist/src/utils/cli-generator.js +443 -0
- package/dist/src/utils/constants.d.ts +5 -0
- package/dist/src/utils/constants.js +6 -0
- package/dist/src/utils/getCallablePromise.d.ts +6 -0
- package/dist/src/utils/getCallablePromise.js +14 -0
- package/dist/src/utils/log.d.ts +7 -0
- package/dist/src/utils/log.js +16 -0
- package/dist/src/utils/parameter-resolver.d.ts +14 -0
- package/dist/src/utils/parameter-resolver.js +387 -0
- package/dist/src/utils/schema-formatter.d.ts +2 -0
- package/dist/src/utils/schema-formatter.js +71 -0
- package/dist/src/utils/serializeAsync.d.ts +2 -0
- package/dist/src/utils/serializeAsync.js +16 -0
- package/dist/src/utils/spinner.d.ts +1 -0
- package/dist/src/utils/spinner.js +13 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +5 -3
- package/src/cli.test.ts +15 -0
- package/src/cli.ts +9 -3
- package/src/commands/bundle-code/cli.ts +103 -0
- package/src/commands/bundle-code/index.ts +91 -0
- package/src/commands/bundle-code/schemas.ts +24 -0
- package/src/commands/generate-types/cli.ts +110 -0
- package/src/commands/generate-types/index.ts +365 -0
- package/src/commands/generate-types/schemas.ts +23 -0
- package/src/commands/index.ts +3 -1
- package/src/commands/mcp.ts +14 -0
- package/src/utils/cli-generator-utils.ts +157 -0
- package/src/utils/cli-generator.ts +148 -91
- package/src/utils/parameter-resolver.ts +217 -85
- package/src/utils/schema-formatter.ts +1 -1
- package/tsconfig.json +3 -5
- package/src/commands/whoami.ts +0 -25
- package/src/utils/pager.ts +0 -202
- package/test/cli.test.ts +0 -46
|
@@ -52,16 +52,29 @@ export class SchemaParameterResolver {
|
|
|
52
52
|
return !hasValue;
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
// Determine
|
|
56
|
-
//
|
|
57
|
-
//
|
|
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
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
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) =>
|
|
85
|
+
(param) =>
|
|
86
|
+
!functionallyRequired.includes(param) && !alwaysPrompt.includes(param),
|
|
73
87
|
);
|
|
74
88
|
|
|
75
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
262
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
405
|
+
if (newFields.length === 0) {
|
|
406
|
+
// No new fields, we're done
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
299
409
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
}
|
package/src/commands/whoami.ts
DELETED
|
@@ -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
|
-
}
|
package/src/utils/pager.ts
DELETED
|
@@ -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
|
-
}
|