@zapier/zapier-sdk-cli 0.4.0 → 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
@@ -0,0 +1,25 @@
1
+ import { Command } from "commander";
2
+ import login from "../utils/auth/login";
3
+ import { getLoggedInUser } from "@zapier/zapier-sdk-cli-login";
4
+ export function createLoginCommand() {
5
+ return new Command("login")
6
+ .description("Log in to Zapier to access your account")
7
+ .option("--timeout <seconds>", "Login timeout in seconds (default: 300)", "300")
8
+ .action(async (options) => {
9
+ try {
10
+ const timeoutSeconds = parseInt(options.timeout, 10);
11
+ if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
12
+ throw new Error("Timeout must be a positive number");
13
+ }
14
+ await login(timeoutSeconds * 1000); // Convert to milliseconds
15
+ const user = await getLoggedInUser();
16
+ console.log(`✅ Successfully logged in as ${user.email}`);
17
+ // Force immediate exit to prevent hanging (especially in development with tsx)
18
+ setTimeout(() => process.exit(0), 100);
19
+ }
20
+ catch (error) {
21
+ console.error("❌ Login failed:", error instanceof Error ? error.message : "Unknown error");
22
+ process.exit(1);
23
+ }
24
+ });
25
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createLogoutCommand(): Command;
@@ -0,0 +1,16 @@
1
+ import { Command } from "commander";
2
+ import { logout } from "@zapier/zapier-sdk-cli-login";
3
+ export function createLogoutCommand() {
4
+ return new Command("logout")
5
+ .description("Log out of your Zapier account")
6
+ .action(async () => {
7
+ try {
8
+ logout();
9
+ console.log("✅ Successfully logged out");
10
+ }
11
+ catch (error) {
12
+ console.error("❌ Logout failed:", error instanceof Error ? error.message : "Unknown error");
13
+ process.exit(1);
14
+ }
15
+ });
16
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createMcpCommand(): Command;
@@ -0,0 +1,11 @@
1
+ import { Command } from "commander";
2
+ import { startMcpServerAsProcess } from "@zapier/zapier-sdk-mcp";
3
+ export function createMcpCommand() {
4
+ const command = new Command("mcp");
5
+ command
6
+ .description("Start MCP server for Zapier SDK")
7
+ .option("--debug", "Enable debug logging")
8
+ .option("--port <port>", "Port to listen on (for future HTTP transport)")
9
+ .action(startMcpServerAsProcess);
10
+ return command;
11
+ }
File without changes
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // Main exports for the CLI package
3
+ // All CLI functionality is now schema-driven via generateCLICommands
@@ -0,0 +1,15 @@
1
+ export interface ApiResponse<T = any> {
2
+ data: T;
3
+ status: number;
4
+ }
5
+ export declare const createApiClient: () => {
6
+ post: <T = any>(url: string, data: any, options?: {
7
+ headers?: Record<string, string>;
8
+ }) => Promise<ApiResponse<T>>;
9
+ };
10
+ declare const api: {
11
+ post: <T = any>(url: string, data: any, options?: {
12
+ headers?: Record<string, string>;
13
+ }) => Promise<ApiResponse<T>>;
14
+ };
15
+ export default api;
@@ -0,0 +1,27 @@
1
+ export const createApiClient = () => {
2
+ const post = async (url, data, options = {}) => {
3
+ const { headers = {} } = options;
4
+ const response = await fetch(url, {
5
+ method: "POST",
6
+ headers: {
7
+ "Content-Type": "application/x-www-form-urlencoded",
8
+ Connection: "close",
9
+ ...headers,
10
+ },
11
+ body: new URLSearchParams(data),
12
+ });
13
+ if (!response.ok) {
14
+ throw new Error(`${response.status} ${response.statusText}`);
15
+ }
16
+ const responseData = await response.json();
17
+ return {
18
+ data: responseData,
19
+ status: response.status,
20
+ };
21
+ };
22
+ return {
23
+ post,
24
+ };
25
+ };
26
+ const api = createApiClient();
27
+ export default api;
@@ -0,0 +1,2 @@
1
+ declare const login: (timeoutMs?: number) => Promise<string>;
2
+ export default login;
@@ -0,0 +1,134 @@
1
+ import open from "open";
2
+ import crypto from "node:crypto";
3
+ import express from "express";
4
+ import pkceChallenge from "pkce-challenge";
5
+ import { AUTH_MODE_HEADER, LOGIN_CLIENT_ID, LOGIN_PORTS, LOGIN_TIMEOUT_MS, ZAPIER_BASE, } from "../constants";
6
+ import { spinPromise } from "../spinner";
7
+ import log from "../log";
8
+ import api from "../api/client";
9
+ import getCallablePromise from "../getCallablePromise";
10
+ import { updateLogin, logout } from "@zapier/zapier-sdk-cli-login";
11
+ const findAvailablePort = () => {
12
+ return new Promise((resolve, reject) => {
13
+ let portIndex = 0;
14
+ const tryPort = (port) => {
15
+ const server = express().listen(port, () => {
16
+ server.close();
17
+ resolve(port);
18
+ });
19
+ server.on("error", (err) => {
20
+ if (err.code === "EADDRINUSE") {
21
+ if (portIndex < LOGIN_PORTS.length) {
22
+ // Try next predefined port
23
+ tryPort(LOGIN_PORTS[portIndex++]);
24
+ }
25
+ else {
26
+ // All configured ports are busy
27
+ reject(new Error(`All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`));
28
+ }
29
+ }
30
+ else {
31
+ reject(err);
32
+ }
33
+ });
34
+ };
35
+ if (LOGIN_PORTS.length > 0) {
36
+ tryPort(LOGIN_PORTS[portIndex++]);
37
+ }
38
+ else {
39
+ reject(new Error("No OAuth callback ports configured"));
40
+ }
41
+ });
42
+ };
43
+ const generateRandomString = () => {
44
+ const array = new Uint32Array(28);
45
+ crypto.getRandomValues(array);
46
+ return Array.from(array, (dec) => ("0" + dec.toString(16)).substring(-2)).join("");
47
+ };
48
+ const login = async (timeoutMs = LOGIN_TIMEOUT_MS) => {
49
+ // Force logout
50
+ logout();
51
+ // Find an available port
52
+ const availablePort = await findAvailablePort();
53
+ const redirectUri = `http://localhost:${availablePort}/oauth`;
54
+ log.info(`Using port ${availablePort} for OAuth callback`);
55
+ const { promise: promisedCode, resolve: setCode } = getCallablePromise();
56
+ const app = express();
57
+ app.get("/oauth", (req, res) => {
58
+ setCode(String(req.query.code));
59
+ // Set headers to prevent keep-alive
60
+ res.setHeader("Connection", "close");
61
+ res.end("You can now close this tab and return to the CLI.");
62
+ });
63
+ const server = app.listen(availablePort);
64
+ // Track connections to force close them if needed
65
+ const connections = new Set();
66
+ server.on("connection", (conn) => {
67
+ connections.add(conn);
68
+ conn.on("close", () => connections.delete(conn));
69
+ });
70
+ // Set up signal handlers for graceful shutdown
71
+ const cleanup = () => {
72
+ server.close();
73
+ log.info("\n❌ Login cancelled by user");
74
+ process.exit(0);
75
+ };
76
+ process.on("SIGINT", cleanup);
77
+ process.on("SIGTERM", cleanup);
78
+ const { code_verifier: codeVerifier, code_challenge: codeChallenge } = await pkceChallenge();
79
+ const authUrl = `${ZAPIER_BASE}/oauth/authorize/?${new URLSearchParams({
80
+ response_type: "code",
81
+ client_id: LOGIN_CLIENT_ID,
82
+ redirect_uri: redirectUri,
83
+ scope: "internal offline_access",
84
+ state: generateRandomString(),
85
+ code_challenge: codeChallenge,
86
+ code_challenge_method: "S256",
87
+ }).toString()}`;
88
+ log.info("Opening your browser to log in.");
89
+ log.info("If it doesn't open, visit:", authUrl);
90
+ open(authUrl);
91
+ try {
92
+ await spinPromise(Promise.race([
93
+ promisedCode,
94
+ new Promise((_resolve, reject) => setTimeout(() => {
95
+ reject(new Error(`Login timed out after ${Math.round(timeoutMs / 1000)} seconds.`));
96
+ }, timeoutMs)),
97
+ ]), "Waiting for you to login and authorize");
98
+ }
99
+ finally {
100
+ // Remove signal handlers
101
+ process.off("SIGINT", cleanup);
102
+ process.off("SIGTERM", cleanup);
103
+ // Close server with timeout and force-close connections if needed
104
+ await new Promise((resolve) => {
105
+ const timeout = setTimeout(() => {
106
+ log.info("Server close timed out, forcing connection shutdown...");
107
+ // Force close all connections
108
+ connections.forEach((conn) => conn.destroy());
109
+ resolve();
110
+ }, 1000); // 1 second timeout
111
+ server.close(() => {
112
+ clearTimeout(timeout);
113
+ resolve();
114
+ });
115
+ });
116
+ }
117
+ log.info("Exchanging authorization code for tokens...");
118
+ const { data } = await api.post(`${ZAPIER_BASE}/oauth/token/`, {
119
+ grant_type: "authorization_code",
120
+ code: await promisedCode,
121
+ redirect_uri: redirectUri,
122
+ client_id: LOGIN_CLIENT_ID,
123
+ code_verifier: codeVerifier,
124
+ }, {
125
+ headers: {
126
+ [AUTH_MODE_HEADER]: "no",
127
+ "Content-Type": "application/x-www-form-urlencoded",
128
+ },
129
+ });
130
+ updateLogin(data);
131
+ log.info("Token exchange completed successfully");
132
+ return data.access_token;
133
+ };
134
+ export default login;
@@ -0,0 +1,13 @@
1
+ import { z } from "zod";
2
+ export interface CliParameter {
3
+ name: string;
4
+ type: "string" | "number" | "boolean" | "array";
5
+ required: boolean;
6
+ description?: string;
7
+ default?: any;
8
+ choices?: string[];
9
+ hasResolver?: boolean;
10
+ isPositional?: boolean;
11
+ }
12
+ export declare function analyzeZodSchema(schema: z.ZodSchema): CliParameter[];
13
+ export declare function convertCliArgsToSdkParams(parameters: CliParameter[], positionalArgs: any[], options: Record<string, any>): Record<string, any>;
@@ -0,0 +1,116 @@
1
+ import { z } from "zod";
2
+ import { hasResolver, isPositional } from "@zapier/zapier-sdk";
3
+ // ============================================================================
4
+ // Schema Analysis
5
+ // ============================================================================
6
+ export function analyzeZodSchema(schema) {
7
+ const parameters = [];
8
+ if (schema instanceof z.ZodObject) {
9
+ const shape = schema.shape;
10
+ for (const [key, fieldSchema] of Object.entries(shape)) {
11
+ const param = analyzeZodField(key, fieldSchema);
12
+ if (param) {
13
+ parameters.push(param);
14
+ }
15
+ }
16
+ }
17
+ return parameters;
18
+ }
19
+ function analyzeZodField(name, schema) {
20
+ let baseSchema = schema;
21
+ let required = true;
22
+ let defaultValue = undefined;
23
+ // Unwrap optional and default wrappers
24
+ if (baseSchema instanceof z.ZodOptional) {
25
+ required = false;
26
+ baseSchema = baseSchema._def.innerType;
27
+ }
28
+ if (baseSchema instanceof z.ZodDefault) {
29
+ required = false;
30
+ defaultValue = baseSchema._def.defaultValue();
31
+ baseSchema = baseSchema._def.innerType;
32
+ }
33
+ // Determine parameter type
34
+ let paramType = "string";
35
+ let choices;
36
+ if (baseSchema instanceof z.ZodString) {
37
+ paramType = "string";
38
+ }
39
+ else if (baseSchema instanceof z.ZodNumber) {
40
+ paramType = "number";
41
+ }
42
+ else if (baseSchema instanceof z.ZodBoolean) {
43
+ paramType = "boolean";
44
+ }
45
+ else if (baseSchema instanceof z.ZodArray) {
46
+ paramType = "array";
47
+ }
48
+ else if (baseSchema instanceof z.ZodEnum) {
49
+ paramType = "string";
50
+ choices = baseSchema._def.values;
51
+ }
52
+ else if (baseSchema instanceof z.ZodRecord) {
53
+ // Handle Record<string, any> as JSON string input
54
+ paramType = "string";
55
+ }
56
+ // Extract resolver metadata
57
+ return {
58
+ name,
59
+ type: paramType,
60
+ required,
61
+ description: schema.description,
62
+ default: defaultValue,
63
+ choices,
64
+ hasResolver: hasResolver(name),
65
+ isPositional: isPositional(schema),
66
+ };
67
+ }
68
+ // ============================================================================
69
+ // Parameter Conversion
70
+ // ============================================================================
71
+ export function convertCliArgsToSdkParams(parameters, positionalArgs, options) {
72
+ const sdkParams = {};
73
+ // Handle positional arguments (required parameters or optional positional parameters)
74
+ let argIndex = 0;
75
+ parameters.forEach((param) => {
76
+ if ((param.required || param.isPositional) &&
77
+ argIndex < positionalArgs.length) {
78
+ // Use the original camelCase parameter name for the SDK
79
+ sdkParams[param.name] = convertValue(positionalArgs[argIndex], param.type);
80
+ argIndex++;
81
+ }
82
+ });
83
+ // Handle option flags
84
+ Object.entries(options).forEach(([key, value]) => {
85
+ // Convert kebab-case back to camelCase
86
+ const camelKey = key.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
87
+ const param = parameters.find((p) => p.name === camelKey);
88
+ if (param && value !== undefined) {
89
+ sdkParams[camelKey] = convertValue(value, param.type);
90
+ }
91
+ });
92
+ return sdkParams;
93
+ }
94
+ function convertValue(value, type) {
95
+ switch (type) {
96
+ case "number":
97
+ return Number(value);
98
+ case "boolean":
99
+ return Boolean(value);
100
+ case "array":
101
+ return Array.isArray(value) ? value : [value];
102
+ case "string":
103
+ default:
104
+ // Handle JSON string for objects
105
+ if (typeof value === "string" &&
106
+ (value.startsWith("{") || value.startsWith("["))) {
107
+ try {
108
+ return JSON.parse(value);
109
+ }
110
+ catch {
111
+ return value;
112
+ }
113
+ }
114
+ return value;
115
+ }
116
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ import { ZapierSdk } from "@zapier/zapier-sdk";
3
+ export declare function generateCliCommands(program: Command, sdk: ZapierSdk): void;