@zapier/zapier-sdk-cli 0.13.15 → 0.14.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.
@@ -20,8 +20,6 @@ const loginWithSdk = createFunction(async (options) => {
20
20
  });
21
21
  const user = await getLoggedInUser();
22
22
  console.log(`✅ Successfully logged in as ${user.email}`);
23
- // Force immediate exit to prevent hanging (especially in development with tsx)
24
- setTimeout(() => process.exit(0), 100);
25
23
  });
26
24
  export const loginPlugin = ({ context }) => {
27
25
  // Wrap the login function to emit telemetry events
@@ -8,6 +8,7 @@ import log from "../log";
8
8
  import api from "../api/client";
9
9
  import getCallablePromise from "../getCallablePromise";
10
10
  import { updateLogin, logout, getAuthTokenUrl, getAuthAuthorizeUrl, } from "@zapier/zapier-sdk-cli-login";
11
+ import { ZapierCliUserCancellationError } from "../errors";
11
12
  const findAvailablePort = () => {
12
13
  return new Promise((resolve, reject) => {
13
14
  let portIndex = 0;
@@ -55,7 +56,7 @@ const login = async ({ timeoutMs = LOGIN_TIMEOUT_MS, baseUrl, authBaseUrl, authC
55
56
  const availablePort = await findAvailablePort();
56
57
  const redirectUri = `http://localhost:${availablePort}/oauth`;
57
58
  log.info(`Using port ${availablePort} for OAuth callback`);
58
- const { promise: promisedCode, resolve: setCode } = getCallablePromise();
59
+ const { promise: promisedCode, resolve: setCode, reject: rejectCode, } = getCallablePromise();
59
60
  const app = express();
60
61
  app.get("/oauth", (req, res) => {
61
62
  setCode(String(req.query.code));
@@ -74,7 +75,7 @@ const login = async ({ timeoutMs = LOGIN_TIMEOUT_MS, baseUrl, authBaseUrl, authC
74
75
  const cleanup = () => {
75
76
  server.close();
76
77
  log.info("\n❌ Login cancelled by user");
77
- process.exit(0);
78
+ rejectCode(new ZapierCliUserCancellationError());
78
79
  };
79
80
  process.on("SIGINT", cleanup);
80
81
  process.on("SIGTERM", cleanup);
@@ -4,6 +4,7 @@ import { SchemaParameterResolver } from "./parameter-resolver";
4
4
  import { formatItemsFromSchema, formatJsonOutput } from "./schema-formatter";
5
5
  import chalk from "chalk";
6
6
  import inquirer from "inquirer";
7
+ import { ZapierCliError, ZapierCliExitError } from "./errors";
7
8
  // ============================================================================
8
9
  // Schema Analysis
9
10
  // ============================================================================
@@ -254,22 +255,29 @@ function createCommandConfig(cliCommandName, functionInfo, sdk) {
254
255
  console.error(chalk.yellow(` • ${field}: ${errorObj?.message || "Unknown error"}`));
255
256
  });
256
257
  console.error("\n" + chalk.dim(`Use --help to see available options`));
258
+ throw new ZapierCliExitError("Validation failed", 1);
257
259
  }
258
260
  catch {
259
- console.error(chalk.red("Error:"), error.message);
261
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
262
+ throw new ZapierCliExitError(error instanceof Error ? error.message : String(error), 1);
260
263
  }
261
264
  }
265
+ else if (error instanceof ZapierCliError) {
266
+ // Re-throw all CLI errors as-is
267
+ throw error;
268
+ }
262
269
  else if (error instanceof ZapierError) {
263
270
  // Handle SDK errors with clean, user-friendly messages using our formatter
264
271
  const formattedMessage = formatErrorMessage(error);
265
272
  console.error(chalk.red("❌ Error:"), formattedMessage);
273
+ throw new ZapierCliExitError(formattedMessage, 1);
266
274
  }
267
275
  else {
268
276
  // Handle other errors
269
277
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
270
278
  console.error(chalk.red("❌ Error:"), errorMessage);
279
+ throw new ZapierCliExitError(errorMessage, 1);
271
280
  }
272
- process.exit(1);
273
281
  }
274
282
  };
275
283
  return {
@@ -0,0 +1,16 @@
1
+ import { ZapierError } from "@zapier/zapier-sdk";
2
+ export declare abstract class ZapierCliError extends ZapierError {
3
+ abstract readonly exitCode: number;
4
+ }
5
+ export declare class ZapierCliUserCancellationError extends ZapierCliError {
6
+ readonly name = "ZapierCliUserCancellationError";
7
+ readonly code = "ZAPIER_CLI_USER_CANCELLATION";
8
+ readonly exitCode = 0;
9
+ constructor(message?: string);
10
+ }
11
+ export declare class ZapierCliExitError extends ZapierCliError {
12
+ readonly name = "ZapierCliExitError";
13
+ readonly code = "ZAPIER_CLI_EXIT";
14
+ readonly exitCode: number;
15
+ constructor(message: string, exitCode?: number);
16
+ }
@@ -0,0 +1,19 @@
1
+ import { ZapierError } from "@zapier/zapier-sdk";
2
+ export class ZapierCliError extends ZapierError {
3
+ }
4
+ export class ZapierCliUserCancellationError extends ZapierCliError {
5
+ constructor(message = "Operation cancelled by user") {
6
+ super(message);
7
+ this.name = "ZapierCliUserCancellationError";
8
+ this.code = "ZAPIER_CLI_USER_CANCELLATION";
9
+ this.exitCode = 0;
10
+ }
11
+ }
12
+ export class ZapierCliExitError extends ZapierCliError {
13
+ constructor(message, exitCode = 1) {
14
+ super(message);
15
+ this.name = "ZapierCliExitError";
16
+ this.code = "ZAPIER_CLI_EXIT";
17
+ this.exitCode = exitCode;
18
+ }
19
+ }
@@ -3,5 +3,6 @@ declare const log: {
3
3
  error: (message: string, ...args: unknown[]) => void;
4
4
  success: (message: string, ...args: unknown[]) => void;
5
5
  warn: (message: string, ...args: unknown[]) => void;
6
+ debug: (message: string, ...args: unknown[]) => void;
6
7
  };
7
8
  export default log;
@@ -12,5 +12,10 @@ const log = {
12
12
  warn: (message, ...args) => {
13
13
  console.log(chalk.yellow("⚠"), message, ...args);
14
14
  },
15
+ debug: (message, ...args) => {
16
+ if (process.env.DEBUG === "true" || process.argv.includes("--debug")) {
17
+ console.log(chalk.gray("🐛"), message, ...args);
18
+ }
19
+ },
15
20
  };
16
21
  export default log;
@@ -0,0 +1,16 @@
1
+ export interface PackageManagerInfo {
2
+ name: "npm" | "yarn" | "pnpm" | "bun" | "unknown";
3
+ source: "runtime" | "lockfile" | "fallback";
4
+ }
5
+ /**
6
+ * Detect which package manager is being used or configured for this project.
7
+ *
8
+ * Returns an object like:
9
+ * { name: 'npm' | 'yarn' | 'pnpm' | 'bun' | 'unknown', source: 'runtime' | 'lockfile' | 'fallback' }
10
+ */
11
+ export declare function detectPackageManager(cwd?: string): PackageManagerInfo;
12
+ /**
13
+ * Get the appropriate update command for the detected package manager.
14
+ * Returns global update commands if globally installed, local commands if locally installed.
15
+ */
16
+ export declare function getUpdateCommand(packageName: string): string;
@@ -0,0 +1,77 @@
1
+ import { existsSync } from "fs";
2
+ import { join } from "path";
3
+ import isInstalledGlobally from "is-installed-globally";
4
+ /**
5
+ * Detect which package manager is being used or configured for this project.
6
+ *
7
+ * Returns an object like:
8
+ * { name: 'npm' | 'yarn' | 'pnpm' | 'bun' | 'unknown', source: 'runtime' | 'lockfile' | 'fallback' }
9
+ */
10
+ export function detectPackageManager(cwd = process.cwd()) {
11
+ // --- 1. Runtime detection (based on env)
12
+ const ua = process.env.npm_config_user_agent;
13
+ if (ua) {
14
+ if (ua.includes("yarn"))
15
+ return { name: "yarn", source: "runtime" };
16
+ if (ua.includes("pnpm"))
17
+ return { name: "pnpm", source: "runtime" };
18
+ if (ua.includes("bun"))
19
+ return { name: "bun", source: "runtime" };
20
+ if (ua.includes("npm"))
21
+ return { name: "npm", source: "runtime" };
22
+ }
23
+ // --- 2. Lockfile detection (based on files in cwd)
24
+ const files = [
25
+ ["pnpm-lock.yaml", "pnpm"],
26
+ ["yarn.lock", "yarn"],
27
+ ["bun.lockb", "bun"],
28
+ ["package-lock.json", "npm"],
29
+ ];
30
+ for (const [file, name] of files) {
31
+ if (existsSync(join(cwd, file))) {
32
+ return { name, source: "lockfile" };
33
+ }
34
+ }
35
+ // --- 3. Fallback
36
+ return { name: "unknown", source: "fallback" };
37
+ }
38
+ /**
39
+ * Get the appropriate update command for the detected package manager.
40
+ * Returns global update commands if globally installed, local commands if locally installed.
41
+ */
42
+ export function getUpdateCommand(packageName) {
43
+ const pm = detectPackageManager();
44
+ const isGlobal = isInstalledGlobally;
45
+ if (isGlobal) {
46
+ // Global update commands
47
+ switch (pm.name) {
48
+ case "yarn":
49
+ return `yarn global upgrade ${packageName}`;
50
+ case "pnpm":
51
+ return `pnpm update -g ${packageName}`;
52
+ case "bun":
53
+ return `bun update -g ${packageName}`;
54
+ case "npm":
55
+ return `npm update -g ${packageName}`;
56
+ case "unknown":
57
+ // Default to npm since it's most widely supported
58
+ return `npm update -g ${packageName}`;
59
+ }
60
+ }
61
+ else {
62
+ // Local update commands
63
+ switch (pm.name) {
64
+ case "yarn":
65
+ return `yarn upgrade ${packageName}`;
66
+ case "pnpm":
67
+ return `pnpm update ${packageName}`;
68
+ case "bun":
69
+ return `bun update ${packageName}`;
70
+ case "npm":
71
+ return `npm update ${packageName}`;
72
+ case "unknown":
73
+ // Default to npm since it's most widely supported
74
+ return `npm update ${packageName}`;
75
+ }
76
+ }
77
+ }
@@ -1,6 +1,7 @@
1
1
  import inquirer from "inquirer";
2
2
  import chalk from "chalk";
3
3
  import { z } from "zod";
4
+ import { ZapierCliUserCancellationError } from "./errors";
4
5
  // ============================================================================
5
6
  // Local Resolution Helper Functions
6
7
  // ============================================================================
@@ -137,7 +138,7 @@ export class SchemaParameterResolver {
137
138
  catch (error) {
138
139
  if (this.isUserCancellation(error)) {
139
140
  console.log(chalk.yellow("\n\nOperation cancelled by user"));
140
- process.exit(0);
141
+ throw new ZapierCliUserCancellationError();
141
142
  }
142
143
  throw error;
143
144
  }
@@ -164,7 +165,7 @@ export class SchemaParameterResolver {
164
165
  catch (error) {
165
166
  if (this.isUserCancellation(error)) {
166
167
  console.log(chalk.yellow("\n\nOperation cancelled by user"));
167
- process.exit(0);
168
+ throw new ZapierCliUserCancellationError();
168
169
  }
169
170
  throw error;
170
171
  }
@@ -198,7 +199,7 @@ export class SchemaParameterResolver {
198
199
  catch (error) {
199
200
  if (this.isUserCancellation(error)) {
200
201
  console.log(chalk.yellow("\n\nOperation cancelled by user"));
201
- process.exit(0);
202
+ throw new ZapierCliUserCancellationError();
202
203
  }
203
204
  throw error;
204
205
  }
@@ -431,7 +432,7 @@ export class SchemaParameterResolver {
431
432
  catch (error) {
432
433
  if (this.isUserCancellation(error)) {
433
434
  console.log(chalk.yellow("\n\nOperation cancelled by user"));
434
- process.exit(0);
435
+ throw new ZapierCliUserCancellationError();
435
436
  }
436
437
  throw error;
437
438
  }
@@ -492,7 +493,7 @@ export class SchemaParameterResolver {
492
493
  catch (error) {
493
494
  if (this.isUserCancellation(error)) {
494
495
  console.log(chalk.yellow("\n\nOperation cancelled by user"));
495
- process.exit(0);
496
+ throw new ZapierCliUserCancellationError();
496
497
  }
497
498
  throw error;
498
499
  }
@@ -1,4 +1,5 @@
1
1
  import ora from "ora";
2
+ import { ZapierCliUserCancellationError } from "./errors";
2
3
  export const spinPromise = async (promise, text) => {
3
4
  const spinner = ora(text).start();
4
5
  try {
@@ -7,7 +8,14 @@ export const spinPromise = async (promise, text) => {
7
8
  return result;
8
9
  }
9
10
  catch (error) {
10
- spinner.fail();
11
+ if (error instanceof ZapierCliUserCancellationError) {
12
+ // For user cancellation, just stop the spinner without showing failure
13
+ spinner.stop();
14
+ }
15
+ else {
16
+ // For actual errors, show failure
17
+ spinner.fail();
18
+ }
11
19
  throw error;
12
20
  }
13
21
  };
@@ -0,0 +1,17 @@
1
+ interface VersionInfo {
2
+ hasUpdate: boolean;
3
+ latestVersion?: string;
4
+ currentVersion: string;
5
+ isDeprecated: boolean;
6
+ deprecationMessage?: string;
7
+ }
8
+ export declare function checkForUpdates({ packageName, currentVersion, }: {
9
+ packageName: string;
10
+ currentVersion: string;
11
+ }): Promise<VersionInfo>;
12
+ export declare function displayUpdateNotification(versionInfo: VersionInfo, packageName: string): void;
13
+ export declare function checkAndNotifyUpdates({ packageName, currentVersion, }: {
14
+ packageName: string;
15
+ currentVersion: string;
16
+ }): Promise<void>;
17
+ export {};
@@ -0,0 +1,154 @@
1
+ import packageJsonLib from "package-json";
2
+ import chalk from "chalk";
3
+ import log from "./log";
4
+ import Conf from "conf";
5
+ import { getUpdateCommand } from "./package-manager-detector";
6
+ let config = null;
7
+ function getConfig() {
8
+ if (!config) {
9
+ config = new Conf({ projectName: "zapier-sdk-cli" });
10
+ }
11
+ return config;
12
+ }
13
+ const ONE_DAY_MS = 24 * 60 * 60 * 1000;
14
+ const CACHE_RESET_INTERVAL_MS = (() => {
15
+ const { ZAPIER_SDK_UPDATE_CHECK_INTERVAL_MS = `${ONE_DAY_MS}` } = process.env;
16
+ const interval = parseInt(ZAPIER_SDK_UPDATE_CHECK_INTERVAL_MS);
17
+ if (isNaN(interval) || interval < 0) {
18
+ return -1;
19
+ }
20
+ return interval;
21
+ })();
22
+ function getVersionCache() {
23
+ try {
24
+ const cache = getConfig().get("version_cache");
25
+ const now = Date.now();
26
+ // If no cache, missing timestamp, or it's been more than a day, reset the cache
27
+ if (!cache ||
28
+ !cache.last_reset_timestamp ||
29
+ now - cache.last_reset_timestamp >= CACHE_RESET_INTERVAL_MS) {
30
+ const newCache = {
31
+ last_reset_timestamp: now,
32
+ packages: {},
33
+ };
34
+ getConfig().set("version_cache", newCache);
35
+ return newCache;
36
+ }
37
+ return cache;
38
+ }
39
+ catch (error) {
40
+ log.debug(`Failed to read version cache: ${error}`);
41
+ return {
42
+ last_reset_timestamp: Date.now(),
43
+ packages: {},
44
+ };
45
+ }
46
+ }
47
+ function setCachedPackageInfo(packageName, version, info) {
48
+ try {
49
+ const cache = getVersionCache();
50
+ if (!cache.packages[packageName]) {
51
+ cache.packages[packageName] = {};
52
+ }
53
+ cache.packages[packageName][version] = info;
54
+ getConfig().set("version_cache", cache);
55
+ }
56
+ catch (error) {
57
+ log.debug(`Failed to cache package info: ${error}`);
58
+ }
59
+ }
60
+ function getCachedPackageInfo(packageName, version) {
61
+ try {
62
+ const cache = getVersionCache();
63
+ return cache.packages[packageName]?.[version];
64
+ }
65
+ catch (error) {
66
+ log.debug(`Failed to get cached package info: ${error}`);
67
+ return undefined;
68
+ }
69
+ }
70
+ async function fetchCachedPackageInfo(packageName, version) {
71
+ const cacheKey = version || "latest";
72
+ // Try cache first
73
+ let cachedInfo = getCachedPackageInfo(packageName, cacheKey);
74
+ if (cachedInfo) {
75
+ return cachedInfo;
76
+ }
77
+ // Not in cache, fetch from npm
78
+ const packageInfo = await packageJsonLib(packageName, {
79
+ version,
80
+ fullMetadata: true,
81
+ });
82
+ const info = {
83
+ version: packageInfo.version,
84
+ deprecated: packageInfo.deprecated,
85
+ fetched_at: new Date().toISOString(),
86
+ };
87
+ // Cache for next time
88
+ setCachedPackageInfo(packageName, cacheKey, info);
89
+ return info;
90
+ }
91
+ export async function checkForUpdates({ packageName, currentVersion, }) {
92
+ try {
93
+ // Get latest version info (with caching)
94
+ const latestPackageInfo = await fetchCachedPackageInfo(packageName);
95
+ const latestVersion = latestPackageInfo.version;
96
+ const hasUpdate = currentVersion !== latestVersion;
97
+ // Check deprecation status of the current version (with caching)
98
+ let currentPackageInfo;
99
+ try {
100
+ currentPackageInfo = await fetchCachedPackageInfo(packageName, currentVersion);
101
+ }
102
+ catch (error) {
103
+ // If we can't fetch the current version info, use the latest version info
104
+ log.debug(`Failed to check deprecation for current version: ${error}`);
105
+ currentPackageInfo = latestPackageInfo;
106
+ }
107
+ const isDeprecated = Boolean(currentPackageInfo.deprecated);
108
+ const deprecationMessage = isDeprecated
109
+ ? String(currentPackageInfo.deprecated)
110
+ : undefined;
111
+ return {
112
+ hasUpdate,
113
+ latestVersion,
114
+ currentVersion,
115
+ isDeprecated,
116
+ deprecationMessage,
117
+ };
118
+ }
119
+ catch (error) {
120
+ // If we can't check for updates (network issues, etc.), fail silently
121
+ log.debug(`Failed to check for updates: ${error}`);
122
+ return {
123
+ hasUpdate: false,
124
+ currentVersion,
125
+ isDeprecated: false,
126
+ };
127
+ }
128
+ }
129
+ export function displayUpdateNotification(versionInfo, packageName) {
130
+ if (versionInfo.isDeprecated) {
131
+ console.error();
132
+ console.error(chalk.red.bold("⚠️ DEPRECATION WARNING") +
133
+ chalk.red(` - ${packageName} v${versionInfo.currentVersion} is deprecated.`));
134
+ if (versionInfo.deprecationMessage) {
135
+ console.error(chalk.red(` ${versionInfo.deprecationMessage}`));
136
+ }
137
+ console.error(chalk.red(` Please update to the latest version.`));
138
+ console.error();
139
+ }
140
+ if (versionInfo.hasUpdate) {
141
+ console.log();
142
+ console.log(chalk.yellow.bold("📦 Update available!") +
143
+ chalk.yellow(` ${packageName} v${versionInfo.currentVersion} → v${versionInfo.latestVersion}`));
144
+ console.log(chalk.yellow(` Run ${chalk.bold(getUpdateCommand(packageName))} to update.`));
145
+ console.log();
146
+ }
147
+ }
148
+ export async function checkAndNotifyUpdates({ packageName, currentVersion, }) {
149
+ if (CACHE_RESET_INTERVAL_MS < 0) {
150
+ return;
151
+ }
152
+ const versionInfo = await checkForUpdates({ packageName, currentVersion });
153
+ displayUpdateNotification(versionInfo, packageName);
154
+ }