@zapier/zapier-sdk-cli 0.4.1 → 0.4.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +16 -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
@@ -0,0 +1,387 @@
1
+ import inquirer from "inquirer";
2
+ import chalk from "chalk";
3
+ import { z } from "zod";
4
+ import { getResolver, hasResolver, getResolutionOrderForParams, } from "@zapier/zapier-sdk";
5
+ // ============================================================================
6
+ // Schema Parameter Resolver
7
+ // ============================================================================
8
+ export class SchemaParameterResolver {
9
+ async resolveParameters(schema, providedParams, sdk) {
10
+ // 1. Try to parse with current parameters
11
+ const parseResult = schema.safeParse(providedParams);
12
+ // Get all schema parameters to check which ones have resolvers
13
+ const allParams = this.extractParametersFromSchema(schema);
14
+ const resolvableParams = allParams.filter((param) => hasResolver(param.name));
15
+ // Get all missing parameters that have resolvers
16
+ const missingResolvable = resolvableParams.filter((param) => {
17
+ const hasValue = this.getNestedValue(providedParams, param.path) !== undefined;
18
+ return !hasValue;
19
+ });
20
+ // Determine parameter resolution categories:
21
+ // - functionally required: must be provided (inputs)
22
+ // - always prompt: should be prompted for but can be skipped (authenticationId)
23
+ // - truly optional: only ask if user wants to be prompted
24
+ const functionallyRequired = missingResolvable.filter((param) => {
25
+ // Schema-required parameters are always functionally required
26
+ if (param.isRequired)
27
+ return true;
28
+ // Only inputs is functionally required for run-action
29
+ if (param.name === "inputs") {
30
+ return true;
31
+ }
32
+ return false;
33
+ });
34
+ // Parameters that should always be prompted for directly, but can be skipped
35
+ const alwaysPrompt = missingResolvable.filter((param) => {
36
+ if (functionallyRequired.includes(param))
37
+ return false;
38
+ // authenticationId should always be prompted for (since it's usually needed)
39
+ // but can be skipped with "Continue without authentication"
40
+ if (param.name === "authenticationId") {
41
+ return true;
42
+ }
43
+ return false;
44
+ });
45
+ const trulyOptional = missingResolvable.filter((param) => !functionallyRequired.includes(param) && !alwaysPrompt.includes(param));
46
+ if (parseResult.success &&
47
+ functionallyRequired.length === 0 &&
48
+ alwaysPrompt.length === 0) {
49
+ return parseResult.data;
50
+ }
51
+ if (functionallyRequired.length === 0 && alwaysPrompt.length === 0) {
52
+ // No functionally required parameters missing, but check if we can parse
53
+ if (!parseResult.success) {
54
+ throw parseResult.error;
55
+ }
56
+ return parseResult.data;
57
+ }
58
+ // 2. Resolve functionally required parameters and their dependencies first
59
+ const resolvedParams = { ...providedParams };
60
+ const context = {
61
+ sdk,
62
+ currentParams: providedParams,
63
+ resolvedParams,
64
+ };
65
+ if (functionallyRequired.length > 0) {
66
+ const requiredParamNames = functionallyRequired.map((p) => p.name);
67
+ const requiredResolutionOrder = getResolutionOrderForParams(requiredParamNames);
68
+ // Find all parameters that need to be resolved (including dependencies)
69
+ // from the available resolvable parameters
70
+ const orderedRequiredParams = requiredResolutionOrder
71
+ .map((paramName) => {
72
+ // First try to find in functionally required
73
+ let param = functionallyRequired.find((p) => p.name === paramName);
74
+ // If not found, try always prompt (for dependencies like authenticationId)
75
+ if (!param) {
76
+ param = alwaysPrompt.find((p) => p.name === paramName);
77
+ }
78
+ // If not found, try truly optional (for other dependencies)
79
+ if (!param) {
80
+ param = trulyOptional.find((p) => p.name === paramName);
81
+ }
82
+ return param;
83
+ })
84
+ .filter((param) => param !== undefined);
85
+ for (const param of orderedRequiredParams) {
86
+ try {
87
+ const value = await this.resolveParameter(param, context);
88
+ this.setNestedValue(resolvedParams, param.path, value);
89
+ // Update context with newly resolved value
90
+ context.resolvedParams = resolvedParams;
91
+ }
92
+ catch (error) {
93
+ if (this.isUserCancellation(error)) {
94
+ console.log(chalk.yellow("\n\nOperation cancelled by user"));
95
+ process.exit(0);
96
+ }
97
+ throw error;
98
+ }
99
+ }
100
+ // Remove resolved dependencies from other categories to avoid double-prompting
101
+ const resolvedParamNames = new Set(orderedRequiredParams.map((p) => p.name));
102
+ alwaysPrompt.splice(0, alwaysPrompt.length, ...alwaysPrompt.filter((p) => !resolvedParamNames.has(p.name)));
103
+ trulyOptional.splice(0, trulyOptional.length, ...trulyOptional.filter((p) => !resolvedParamNames.has(p.name)));
104
+ }
105
+ // 3. Resolve parameters that should always be prompted for (but can be skipped)
106
+ if (alwaysPrompt.length > 0) {
107
+ const alwaysPromptNames = alwaysPrompt.map((p) => p.name);
108
+ const alwaysPromptResolutionOrder = getResolutionOrderForParams(alwaysPromptNames);
109
+ const orderedAlwaysPromptParams = alwaysPromptResolutionOrder
110
+ .map((paramName) => alwaysPrompt.find((p) => p.name === paramName))
111
+ .filter((param) => param !== undefined);
112
+ for (const param of orderedAlwaysPromptParams) {
113
+ try {
114
+ const value = await this.resolveParameter(param, context);
115
+ this.setNestedValue(resolvedParams, param.path, value);
116
+ // Update context with newly resolved value
117
+ context.resolvedParams = resolvedParams;
118
+ }
119
+ catch (error) {
120
+ if (this.isUserCancellation(error)) {
121
+ console.log(chalk.yellow("\n\nOperation cancelled by user"));
122
+ process.exit(0);
123
+ }
124
+ throw error;
125
+ }
126
+ }
127
+ }
128
+ // 4. Ask user if they want to resolve truly optional parameters
129
+ if (trulyOptional.length > 0) {
130
+ const optionalNames = trulyOptional.map((p) => p.name).join(", ");
131
+ const shouldResolveOptional = await inquirer.prompt([
132
+ {
133
+ type: "confirm",
134
+ name: "resolveOptional",
135
+ message: `Would you like to be prompted for optional parameters (${optionalNames})?`,
136
+ default: false,
137
+ },
138
+ ]);
139
+ if (shouldResolveOptional.resolveOptional) {
140
+ // Resolve optional parameters using their resolvers
141
+ const optionalParamNames = trulyOptional.map((p) => p.name);
142
+ const optionalResolutionOrder = getResolutionOrderForParams(optionalParamNames);
143
+ const orderedOptionalParams = optionalResolutionOrder
144
+ .map((paramName) => trulyOptional.find((p) => p.name === paramName))
145
+ .filter((param) => param !== undefined);
146
+ for (const param of orderedOptionalParams) {
147
+ try {
148
+ const value = await this.resolveParameter(param, context);
149
+ this.setNestedValue(resolvedParams, param.path, value);
150
+ // Update context with newly resolved value
151
+ context.resolvedParams = resolvedParams;
152
+ }
153
+ catch (error) {
154
+ if (this.isUserCancellation(error)) {
155
+ console.log(chalk.yellow("\n\nOperation cancelled by user"));
156
+ process.exit(0);
157
+ }
158
+ throw error;
159
+ }
160
+ }
161
+ }
162
+ }
163
+ // 3. Validate final parameters
164
+ const finalResult = schema.safeParse(resolvedParams);
165
+ if (!finalResult.success) {
166
+ console.error(chalk.red("❌ Parameter validation failed after resolution:"));
167
+ throw finalResult.error;
168
+ }
169
+ return finalResult.data;
170
+ }
171
+ extractParametersFromSchema(schema) {
172
+ const parameters = [];
173
+ // Only handle ZodObject at the top level
174
+ if (schema instanceof z.ZodObject) {
175
+ const shape = schema.shape;
176
+ for (const [key, fieldSchema] of Object.entries(shape)) {
177
+ const param = this.analyzeFieldSchema(key, fieldSchema);
178
+ if (param) {
179
+ parameters.push(param);
180
+ }
181
+ }
182
+ }
183
+ return parameters;
184
+ }
185
+ analyzeFieldSchema(fieldName, fieldSchema) {
186
+ let baseSchema = fieldSchema;
187
+ let isRequired = true;
188
+ // Check if field is optional or has default
189
+ if (baseSchema instanceof z.ZodOptional) {
190
+ isRequired = false;
191
+ baseSchema = baseSchema._def.innerType;
192
+ }
193
+ if (baseSchema instanceof z.ZodDefault) {
194
+ isRequired = false;
195
+ baseSchema = baseSchema._def.innerType;
196
+ }
197
+ return this.createResolvableParameter([fieldName], baseSchema, isRequired);
198
+ }
199
+ createResolvableParameter(path, schema, isRequired) {
200
+ if (path.length === 0)
201
+ return null;
202
+ const name = path[path.length - 1];
203
+ return {
204
+ name,
205
+ path,
206
+ schema,
207
+ description: schema.description,
208
+ isRequired,
209
+ };
210
+ }
211
+ async resolveParameter(param, context) {
212
+ const resolver = getResolver(param.name);
213
+ if (!resolver) {
214
+ throw new Error(`No resolver found for parameter: ${param.name}`);
215
+ }
216
+ console.log(chalk.blue(`\n🔍 Resolving ${param.name}...`));
217
+ if (resolver.type === "static") {
218
+ // Static resolver - just prompt for input
219
+ const promptConfig = {
220
+ type: resolver.inputType === "password" ? "password" : "input",
221
+ name: param.name,
222
+ message: `Enter ${param.name}:`,
223
+ ...(resolver.placeholder && { default: resolver.placeholder }),
224
+ };
225
+ const answers = await inquirer.prompt([promptConfig]);
226
+ return answers[param.name];
227
+ }
228
+ else if (resolver.type === "dynamic") {
229
+ // Dynamic resolver - fetch options and prompt for selection
230
+ try {
231
+ // Only show "Fetching..." for required parameters that typically have many options
232
+ if (param.isRequired && param.name !== "authenticationId") {
233
+ console.log(chalk.gray(`Fetching options for ${param.name}...`));
234
+ }
235
+ const items = await resolver.fetch(context.sdk, context.resolvedParams);
236
+ // Let the resolver's prompt handle empty lists (e.g., authenticationId can show "skip authentication")
237
+ const safeItems = items || [];
238
+ const promptConfig = resolver.prompt(safeItems, context.resolvedParams);
239
+ const answers = await inquirer.prompt([promptConfig]);
240
+ return answers[param.name];
241
+ }
242
+ catch (error) {
243
+ // Let the main CLI error handler display user-friendly errors
244
+ throw error;
245
+ }
246
+ }
247
+ else if (resolver.type === "fields") {
248
+ // Fields resolver - fetch field definitions and prompt for each input with recursive field resolution
249
+ return await this.resolveFieldsRecursively(resolver, context, param);
250
+ }
251
+ throw new Error(`Unknown resolver type for ${param.name}`);
252
+ }
253
+ async resolveFieldsRecursively(resolver, context, param) {
254
+ const inputs = {};
255
+ let processedFieldKeys = new Set();
256
+ let iteration = 0;
257
+ const maxIterations = 5; // Prevent infinite loops
258
+ while (iteration < maxIterations) {
259
+ iteration++;
260
+ // Update context with current inputs so they're passed to listInputFields
261
+ const updatedContext = {
262
+ ...context,
263
+ resolvedParams: {
264
+ ...context.resolvedParams,
265
+ inputs,
266
+ },
267
+ };
268
+ console.log(chalk.gray(`Fetching input fields for ${param.name}${iteration > 1 ? ` (iteration ${iteration})` : ""}...`));
269
+ const fields = await resolver.fetch(updatedContext.sdk, updatedContext.resolvedParams);
270
+ if (!fields || fields.length === 0) {
271
+ if (iteration === 1) {
272
+ console.log(chalk.yellow(`No input fields required for this action.`));
273
+ }
274
+ break;
275
+ }
276
+ // Find new fields that we haven't processed yet
277
+ const newFields = fields.filter((field) => !processedFieldKeys.has(field.key));
278
+ if (newFields.length === 0) {
279
+ // No new fields, we're done
280
+ break;
281
+ }
282
+ // Separate new required and optional fields
283
+ const newRequiredFields = newFields.filter((field) => field.required);
284
+ const newOptionalFields = newFields.filter((field) => !field.required);
285
+ // Prompt for new required fields
286
+ if (newRequiredFields.length > 0) {
287
+ console.log(chalk.blue(`\n📝 Please provide values for the following ${iteration === 1 ? "" : "additional "}input fields:`));
288
+ for (const field of newRequiredFields) {
289
+ await this.promptForField(field, inputs);
290
+ processedFieldKeys.add(field.key);
291
+ }
292
+ }
293
+ // Ask about optional fields
294
+ let shouldConfigureOptional = { configure: false };
295
+ if (newOptionalFields.length > 0) {
296
+ console.log(chalk.gray(`\nThere are ${newOptionalFields.length} ${iteration === 1 ? "" : "additional "}optional field(s) available.`));
297
+ try {
298
+ shouldConfigureOptional = await inquirer.prompt([
299
+ {
300
+ type: "confirm",
301
+ name: "configure",
302
+ message: `Would you like to configure ${iteration === 1 ? "" : "these additional "}optional fields?`,
303
+ default: false,
304
+ },
305
+ ]);
306
+ }
307
+ catch (error) {
308
+ if (this.isUserCancellation(error)) {
309
+ console.log(chalk.yellow("\n\nOperation cancelled by user"));
310
+ process.exit(0);
311
+ }
312
+ throw error;
313
+ }
314
+ if (shouldConfigureOptional.configure) {
315
+ console.log(chalk.cyan(`\nOptional fields:`));
316
+ for (const field of newOptionalFields) {
317
+ await this.promptForField(field, inputs);
318
+ processedFieldKeys.add(field.key);
319
+ }
320
+ }
321
+ else {
322
+ // Mark these fields as processed even if skipped to avoid re-asking
323
+ newOptionalFields.forEach((field) => processedFieldKeys.add(field.key));
324
+ }
325
+ }
326
+ // If we only processed optional fields and skipped them, no need to re-fetch
327
+ if (newRequiredFields.length === 0 &&
328
+ (!newOptionalFields.length || !shouldConfigureOptional.configure)) {
329
+ break;
330
+ }
331
+ }
332
+ if (iteration >= maxIterations) {
333
+ console.log(chalk.yellow(`\n⚠️ Maximum field resolution iterations reached. Some dynamic fields may not have been discovered.`));
334
+ }
335
+ return inputs;
336
+ }
337
+ getNestedValue(obj, path) {
338
+ return path.reduce((current, key) => current?.[key], obj);
339
+ }
340
+ setNestedValue(obj, path, value) {
341
+ const lastKey = path[path.length - 1];
342
+ const parent = path.slice(0, -1).reduce((current, key) => {
343
+ if (!(key in current)) {
344
+ current[key] = {};
345
+ }
346
+ return current[key];
347
+ }, obj);
348
+ parent[lastKey] = value;
349
+ }
350
+ async promptForField(field, inputs) {
351
+ const fieldPrompt = {
352
+ type: field.type === "boolean" ? "confirm" : "input",
353
+ name: field.key,
354
+ message: `${field.label || field.key}${field.required ? " (required)" : " (optional)"}:`,
355
+ ...(field.helpText && { prefix: chalk.gray(`ℹ ${field.helpText}\n`) }),
356
+ ...(field.default && { default: field.default }),
357
+ };
358
+ if (field.choices && field.choices.length > 0) {
359
+ fieldPrompt.type = "list";
360
+ fieldPrompt.choices = field.choices.map((choice) => ({
361
+ name: choice.label || choice.value,
362
+ value: choice.value,
363
+ }));
364
+ }
365
+ try {
366
+ const answer = await inquirer.prompt([fieldPrompt]);
367
+ if (answer[field.key] !== undefined && answer[field.key] !== "") {
368
+ inputs[field.key] = answer[field.key];
369
+ }
370
+ else if (field.required) {
371
+ throw new Error(`Required field ${field.key} cannot be empty`);
372
+ }
373
+ }
374
+ catch (error) {
375
+ if (this.isUserCancellation(error)) {
376
+ console.log(chalk.yellow("\n\nOperation cancelled by user"));
377
+ process.exit(0);
378
+ }
379
+ throw error;
380
+ }
381
+ }
382
+ isUserCancellation(error) {
383
+ return (error?.name === "ExitPromptError" ||
384
+ error?.message?.includes("User force closed") ||
385
+ error?.isTTYError);
386
+ }
387
+ }
@@ -0,0 +1,2 @@
1
+ import { z } from "zod";
2
+ export declare function formatItemsFromSchema(inputSchema: z.ZodType, items: any[]): void;
@@ -0,0 +1,71 @@
1
+ import chalk from "chalk";
2
+ function getFormatMetadata(schema) {
3
+ return schema?._def?.formatMeta;
4
+ }
5
+ function getOutputSchema(schema) {
6
+ return schema?._def?.outputSchema;
7
+ }
8
+ // ============================================================================
9
+ // Generic Schema-Driven Formatter
10
+ // ============================================================================
11
+ export function formatItemsFromSchema(inputSchema, items) {
12
+ // Get the output schema and its format metadata
13
+ const outputSchema = getOutputSchema(inputSchema);
14
+ if (!outputSchema) {
15
+ // Fallback to generic formatting if no output schema
16
+ formatItemsGeneric(items);
17
+ return;
18
+ }
19
+ const formatMeta = getFormatMetadata(outputSchema);
20
+ if (!formatMeta) {
21
+ // Fallback to generic formatting if no format metadata
22
+ formatItemsGeneric(items);
23
+ return;
24
+ }
25
+ // Format each item using the schema metadata
26
+ items.forEach((item, index) => {
27
+ formatSingleItem(item, index, formatMeta);
28
+ });
29
+ }
30
+ function formatSingleItem(item, index, formatMeta) {
31
+ // Get the formatted item from the format function
32
+ const formatted = formatMeta.format(item);
33
+ // Build the main title line
34
+ let titleLine = `${chalk.gray(`${index + 1}.`)} ${chalk.cyan(formatted.title)}`;
35
+ if (formatted.subtitle) {
36
+ titleLine += ` ${chalk.gray(formatted.subtitle)}`;
37
+ }
38
+ console.log(titleLine);
39
+ // Format detail lines
40
+ for (const detail of formatted.details) {
41
+ const styledText = applyStyle(detail.text, detail.style);
42
+ console.log(` ${styledText}`);
43
+ }
44
+ console.log(); // Empty line between items
45
+ }
46
+ function applyStyle(value, style) {
47
+ switch (style) {
48
+ case "dim":
49
+ return chalk.dim(value);
50
+ case "accent":
51
+ return chalk.magenta(value);
52
+ case "warning":
53
+ return chalk.red(value);
54
+ case "success":
55
+ return chalk.green(value);
56
+ case "normal":
57
+ default:
58
+ return chalk.blue(value);
59
+ }
60
+ }
61
+ function formatItemsGeneric(items) {
62
+ // Fallback formatting for items without schema metadata
63
+ items.forEach((item, index) => {
64
+ const name = item.title || item.name || item.key || item.id || "Item";
65
+ console.log(`${chalk.gray(`${index + 1}.`)} ${chalk.cyan(name)}`);
66
+ if (item.description) {
67
+ console.log(` ${chalk.dim(item.description)}`);
68
+ }
69
+ console.log();
70
+ });
71
+ }
@@ -0,0 +1,2 @@
1
+ declare const serializeAsync: <R, A extends []>(fn: (...args: A) => Promise<R>) => ((...args: A) => Promise<R>);
2
+ export default serializeAsync;
@@ -0,0 +1,16 @@
1
+ import { createHash } from "node:crypto";
2
+ const promises = {};
3
+ const serializeAsync = (fn) => async (...args) => {
4
+ const hash = createHash("sha256")
5
+ .update(fn.toString(), "utf8")
6
+ .digest()
7
+ .toString("hex");
8
+ const promise = promises[hash];
9
+ if (!promise) {
10
+ promises[hash] = fn(...args).finally(() => {
11
+ delete promises[hash];
12
+ });
13
+ }
14
+ return promises[hash];
15
+ };
16
+ export default serializeAsync;
@@ -0,0 +1 @@
1
+ export declare const spinPromise: <T>(promise: Promise<T>, text: string) => Promise<T>;
@@ -0,0 +1,13 @@
1
+ import ora from "ora";
2
+ export const spinPromise = async (promise, text) => {
3
+ const spinner = ora(text).start();
4
+ try {
5
+ const result = await promise;
6
+ spinner.succeed();
7
+ return result;
8
+ }
9
+ catch (error) {
10
+ spinner.fail();
11
+ throw error;
12
+ }
13
+ };