@zapier/zapier-sdk-cli 0.0.2 → 0.1.0

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.
@@ -1,389 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.generateCLICommands = generateCLICommands;
7
- exports.enhanceCommandHelp = enhanceCommandHelp;
8
- const zod_1 = require("zod");
9
- const schemas_1 = require("@zapier/actions-sdk/dist/schemas");
10
- const parameter_resolver_1 = require("./parameter-resolver");
11
- const pager_1 = require("./pager");
12
- const chalk_1 = __importDefault(require("chalk"));
13
- // ============================================================================
14
- // Schema Analysis
15
- // ============================================================================
16
- function analyzeZodSchema(schema) {
17
- const parameters = [];
18
- if (schema instanceof zod_1.z.ZodObject) {
19
- const shape = schema.shape;
20
- for (const [key, fieldSchema] of Object.entries(shape)) {
21
- const param = analyzeZodField(key, fieldSchema);
22
- if (param) {
23
- parameters.push(param);
24
- }
25
- }
26
- }
27
- return parameters;
28
- }
29
- function analyzeZodField(name, schema) {
30
- let baseSchema = schema;
31
- let required = true;
32
- let defaultValue = undefined;
33
- // Unwrap optional and default wrappers
34
- if (baseSchema instanceof zod_1.z.ZodOptional) {
35
- required = false;
36
- baseSchema = baseSchema._def.innerType;
37
- }
38
- if (baseSchema instanceof zod_1.z.ZodDefault) {
39
- required = false;
40
- defaultValue = baseSchema._def.defaultValue();
41
- baseSchema = baseSchema._def.innerType;
42
- }
43
- // Determine parameter type
44
- let paramType = 'string';
45
- let choices;
46
- if (baseSchema instanceof zod_1.z.ZodString) {
47
- paramType = 'string';
48
- }
49
- else if (baseSchema instanceof zod_1.z.ZodNumber) {
50
- paramType = 'number';
51
- }
52
- else if (baseSchema instanceof zod_1.z.ZodBoolean) {
53
- paramType = 'boolean';
54
- }
55
- else if (baseSchema instanceof zod_1.z.ZodArray) {
56
- paramType = 'array';
57
- }
58
- else if (baseSchema instanceof zod_1.z.ZodEnum) {
59
- paramType = 'string';
60
- choices = baseSchema._def.values;
61
- }
62
- else if (baseSchema instanceof zod_1.z.ZodRecord) {
63
- // Handle Record<string, any> as JSON string input
64
- paramType = 'string';
65
- }
66
- // Extract resolver metadata
67
- const resolverMeta = schema._def.resolverMeta;
68
- return {
69
- name,
70
- type: paramType,
71
- required,
72
- description: schema.description,
73
- default: defaultValue,
74
- choices,
75
- resolverMeta
76
- };
77
- }
78
- // ============================================================================
79
- // CLI Command Generation
80
- // ============================================================================
81
- function generateCLICommands(program, sdk) {
82
- // Check if SDKSchemas is available
83
- if (!schemas_1.SDKSchemas) {
84
- console.error('SDKSchemas not available');
85
- return;
86
- }
87
- // Generate namespace commands (apps, actions, auths, fields)
88
- Object.entries(schemas_1.SDKSchemas).forEach(([namespace, methods]) => {
89
- if (namespace === 'generate' || namespace === 'bundle') {
90
- // Handle root tools separately
91
- return;
92
- }
93
- const namespaceCommand = program.command(namespace).description(`${namespace} management commands`);
94
- if (typeof methods === 'object' && methods !== null) {
95
- Object.entries(methods).forEach(([method, schema]) => {
96
- const config = createCommandConfig(namespace, method, schema, sdk);
97
- addSubCommand(namespaceCommand, method, config);
98
- });
99
- }
100
- });
101
- // Generate root tool commands
102
- if (schemas_1.SDKSchemas.generate) {
103
- const generateConfig = createCommandConfig('', 'generate', schemas_1.SDKSchemas.generate, sdk);
104
- addSubCommand(program, 'generate', generateConfig);
105
- }
106
- if (schemas_1.SDKSchemas.bundle) {
107
- const bundleConfig = createCommandConfig('', 'bundle', schemas_1.SDKSchemas.bundle, sdk);
108
- addSubCommand(program, 'bundle', bundleConfig);
109
- }
110
- }
111
- function createCommandConfig(namespace, method, schema, sdk) {
112
- const parameters = analyzeZodSchema(schema);
113
- const description = schema.description || `${namespace} ${method} command`;
114
- const handler = async (...args) => {
115
- try {
116
- // The last argument is always the command object with parsed options
117
- const command = args[args.length - 1];
118
- const options = command.opts();
119
- // Check if this is a list command with pagination support
120
- const isListCommand = method === 'list';
121
- const hasPaginationParams = parameters.some(p => p.name === 'limit' || p.name === 'offset');
122
- const hasUserSpecifiedLimit = 'limit' in options && options.limit !== undefined;
123
- const shouldUsePaging = isListCommand && hasPaginationParams && !hasUserSpecifiedLimit;
124
- const shouldUseJSON = options.json;
125
- // Convert CLI args to SDK method parameters
126
- const rawParams = convertCLIArgsToSDKParams(parameters, args.slice(0, -1), options);
127
- // NEW: Resolve missing parameters interactively using schema metadata
128
- const resolver = new parameter_resolver_1.SchemaParameterResolver();
129
- const resolvedParams = await resolver.resolveParameters(schema, rawParams, sdk);
130
- if (shouldUsePaging && !shouldUseJSON) {
131
- // Use interactive paging for list commands
132
- await handlePaginatedList(namespace, method, resolvedParams, sdk);
133
- }
134
- else {
135
- // Call the appropriate SDK method with complete, validated parameters
136
- let result;
137
- if (namespace === '') {
138
- // Root tool (generate, bundle)
139
- result = await sdk[method](resolvedParams);
140
- }
141
- else {
142
- // Regular namespace method
143
- result = await sdk[namespace][method](resolvedParams);
144
- }
145
- // Special handling for generate and bundle commands - don't output to console if writing to file
146
- const isRootCommandWithOutput = namespace === '' && (method === 'generate' || method === 'bundle');
147
- const hasOutputFile = isRootCommandWithOutput && resolvedParams.output;
148
- // Output result (JSON or formatted)
149
- if (!hasOutputFile && (shouldUseJSON || !isListCommand)) {
150
- console.log(JSON.stringify(result, null, 2));
151
- }
152
- else if (!hasOutputFile) {
153
- // Format list results nicely (non-paginated)
154
- formatNonPaginatedResults(namespace, result, resolvedParams.limit, hasUserSpecifiedLimit);
155
- }
156
- else if (hasOutputFile) {
157
- // Show success message for file output instead of printing generated content
158
- console.log(chalk_1.default.green(`āœ… ${method} completed successfully!`));
159
- console.log(chalk_1.default.gray(`Output written to: ${resolvedParams.output}`));
160
- }
161
- }
162
- }
163
- catch (error) {
164
- console.error('Error:', error instanceof Error ? error.message : 'Unknown error');
165
- process.exit(1);
166
- }
167
- };
168
- return {
169
- description,
170
- parameters,
171
- handler
172
- };
173
- }
174
- function addSubCommand(parentCommand, name, config) {
175
- const command = parentCommand.command(name).description(config.description);
176
- // Check if this is a list command that should have pagination options
177
- const isListCommand = name === 'list';
178
- const hasPaginationParams = config.parameters.some(p => p.name === 'limit' || p.name === 'offset');
179
- // Add parameters to command
180
- config.parameters.forEach(param => {
181
- if (param.resolverMeta?.resolver && param.required) {
182
- // Required parameters with resolvers become optional positional arguments (resolver handles prompting)
183
- command.argument(`[${param.name}]`, param.description || `${param.name} parameter`);
184
- }
185
- else if (param.required) {
186
- // Required parameters without resolvers become required positional arguments
187
- command.argument(`<${param.name}>`, param.description || `${param.name} parameter`);
188
- }
189
- else {
190
- // Optional parameters become flags (whether they have resolvers or not)
191
- const flags = [`--${param.name.replace(/([A-Z])/g, '-$1').toLowerCase()}`];
192
- if (param.type === 'boolean') {
193
- command.option(flags.join(', '), param.description);
194
- }
195
- else {
196
- const flagSignature = flags.join(', ') + ` <${param.type}>`;
197
- command.option(flagSignature, param.description, param.default);
198
- }
199
- }
200
- });
201
- // Add formatting options for list commands
202
- if (isListCommand) {
203
- command.option('--json', 'Output raw JSON instead of formatted results');
204
- }
205
- command.action(config.handler);
206
- }
207
- // ============================================================================
208
- // Parameter Conversion
209
- // ============================================================================
210
- function convertCLIArgsToSDKParams(parameters, positionalArgs, options) {
211
- const sdkParams = {};
212
- // Handle positional arguments (required parameters only, whether they have resolvers or not)
213
- let argIndex = 0;
214
- parameters.forEach(param => {
215
- if (param.required && argIndex < positionalArgs.length) {
216
- sdkParams[param.name] = convertValue(positionalArgs[argIndex], param.type);
217
- argIndex++;
218
- }
219
- });
220
- // Handle option flags
221
- Object.entries(options).forEach(([key, value]) => {
222
- // Convert kebab-case back to camelCase
223
- const camelKey = key.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
224
- const param = parameters.find(p => p.name === camelKey);
225
- if (param && value !== undefined) {
226
- sdkParams[camelKey] = convertValue(value, param.type);
227
- }
228
- });
229
- return sdkParams;
230
- }
231
- function convertValue(value, type) {
232
- switch (type) {
233
- case 'number':
234
- return Number(value);
235
- case 'boolean':
236
- return Boolean(value);
237
- case 'array':
238
- return Array.isArray(value) ? value : [value];
239
- case 'string':
240
- default:
241
- // Handle JSON string for objects
242
- if (typeof value === 'string' && (value.startsWith('{') || value.startsWith('['))) {
243
- try {
244
- return JSON.parse(value);
245
- }
246
- catch {
247
- return value;
248
- }
249
- }
250
- return value;
251
- }
252
- }
253
- // ============================================================================
254
- // Pagination Handlers
255
- // ============================================================================
256
- async function handlePaginatedList(namespace, method, baseParams, sdk) {
257
- const limit = baseParams.limit || 20;
258
- const itemName = getItemName(namespace);
259
- console.log(chalk_1.default.blue(`šŸ“‹ Fetching ${itemName}...`));
260
- const pager = (0, pager_1.createPager)({
261
- pageSize: Math.min(limit, 20),
262
- itemName
263
- });
264
- const displayFunction = (items, totalShown, totalAvailable) => {
265
- console.clear();
266
- console.log(chalk_1.default.blue(`šŸ“‹ ${getListTitle(namespace)}\n`));
267
- if (items.length === 0) {
268
- console.log(chalk_1.default.yellow(`No ${itemName} found.`));
269
- return;
270
- }
271
- formatItems(namespace, items);
272
- const totalInfo = totalAvailable ? ` of ${totalAvailable.toLocaleString()} total` : '';
273
- console.log(chalk_1.default.green(`\nāœ… Showing ${totalShown}${totalInfo} ${itemName}`));
274
- };
275
- await pager.paginate((params) => sdk[namespace][method]({
276
- ...baseParams,
277
- ...params
278
- }), {}, displayFunction);
279
- }
280
- function formatNonPaginatedResults(namespace, result, requestedLimit, userSpecifiedLimit) {
281
- if (!Array.isArray(result)) {
282
- console.log(JSON.stringify(result, null, 2));
283
- return;
284
- }
285
- const itemName = getItemName(namespace);
286
- if (result.length === 0) {
287
- console.log(chalk_1.default.yellow(`No ${itemName} found.`));
288
- return;
289
- }
290
- console.log(chalk_1.default.green(`\nāœ… Found ${result.length} ${itemName}:\n`));
291
- formatItems(namespace, result);
292
- // Show appropriate status message
293
- if (userSpecifiedLimit && requestedLimit) {
294
- console.log(chalk_1.default.gray(`\nšŸ“„ Showing up to ${requestedLimit} ${itemName} (--limit ${requestedLimit})`));
295
- }
296
- else {
297
- console.log(chalk_1.default.gray(`\nšŸ“„ All available ${itemName} shown`));
298
- }
299
- }
300
- function formatItems(namespace, items) {
301
- switch (namespace) {
302
- case 'apps':
303
- items.forEach((app, index) => {
304
- console.log(`${chalk_1.default.gray(`${index + 1}.`)} ${chalk_1.default.cyan(app.name || app.key)} ${chalk_1.default.gray(`(${app.key})`)}`);
305
- if (app.description) {
306
- console.log(` ${chalk_1.default.dim(app.description)}`);
307
- }
308
- if (app.category) {
309
- console.log(` ${chalk_1.default.magenta(`Category: ${app.category}`)}`);
310
- }
311
- console.log();
312
- });
313
- break;
314
- case 'actions':
315
- items.forEach((action, index) => {
316
- console.log(`${chalk_1.default.gray(`${index + 1}.`)} ${chalk_1.default.cyan(action.name || action.key)} ${chalk_1.default.gray(`(${action.key})`)}`);
317
- console.log(` ${chalk_1.default.magenta(`Type: ${action.type}`)}`);
318
- if (action.appKey) {
319
- console.log(` ${chalk_1.default.blue(`App: ${action.appKey}`)}`);
320
- }
321
- if (action.description) {
322
- console.log(` ${chalk_1.default.dim(action.description)}`);
323
- }
324
- console.log();
325
- });
326
- break;
327
- case 'auths':
328
- items.forEach((auth, index) => {
329
- const title = auth.title || auth.label || `Authentication ${auth.id}`;
330
- console.log(`${chalk_1.default.gray(`${index + 1}.`)} ${chalk_1.default.cyan(title)} ${chalk_1.default.gray(`(ID: ${auth.id})`)}`);
331
- if (auth.identifier) {
332
- console.log(` ${chalk_1.default.magenta(`Identifier: ${auth.identifier}`)}`);
333
- }
334
- if (auth.label && auth.title && auth.label !== auth.title) {
335
- console.log(` ${chalk_1.default.blue(`Label: ${auth.label}`)}`);
336
- }
337
- console.log(` ${chalk_1.default.dim(`Account: ${auth.account_id} | Private: ${auth.is_private} | Shared: ${auth.shared_with_all}`)}`);
338
- if (auth.marked_stale_at) {
339
- console.log(` ${chalk_1.default.red(`āš ļø Marked stale: ${new Date(auth.marked_stale_at).toLocaleDateString()}`)}`);
340
- }
341
- console.log();
342
- });
343
- break;
344
- default:
345
- // Generic formatting for unknown types
346
- items.forEach((item, index) => {
347
- console.log(`${chalk_1.default.gray(`${index + 1}.`)} ${chalk_1.default.cyan(item.name || item.key || item.id || 'Item')}`);
348
- if (item.description) {
349
- console.log(` ${chalk_1.default.dim(item.description)}`);
350
- }
351
- console.log();
352
- });
353
- }
354
- }
355
- function getItemName(namespace) {
356
- switch (namespace) {
357
- case 'apps': return 'apps';
358
- case 'actions': return 'actions';
359
- case 'auths': return 'authentications';
360
- case 'fields': return 'fields';
361
- default: return 'items';
362
- }
363
- }
364
- function getListTitle(namespace) {
365
- switch (namespace) {
366
- case 'apps': return 'Available Apps';
367
- case 'actions': return 'Available Actions';
368
- case 'auths': return 'Available Authentications';
369
- case 'fields': return 'Available Fields';
370
- default: return 'Available Items';
371
- }
372
- }
373
- // ============================================================================
374
- // Help Text Enhancement
375
- // ============================================================================
376
- function enhanceCommandHelp(program) {
377
- // Add custom help that shows schema-driven nature
378
- program.on('--help', () => {
379
- console.log('');
380
- console.log('Commands are automatically generated from SDK schemas.');
381
- console.log('Each command maps directly to an SDK method with the same parameters.');
382
- console.log('');
383
- console.log('Examples:');
384
- console.log(' zapier apps list --category=productivity --limit=10');
385
- console.log(' zapier actions run slack search user_by_email --inputs=\'{"email":"user@example.com"}\'');
386
- console.log(' zapier generate my-app --output=./generated/');
387
- console.log('');
388
- });
389
- }