@zapier/zapier-sdk-cli 0.54.0 → 0.54.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/dist/index.mjs CHANGED
@@ -7,7 +7,7 @@ import crypto, { createHash } from 'crypto';
7
7
  import * as path from 'path';
8
8
  import { resolve, join, dirname, basename, relative, extname } from 'path';
9
9
  import * as lockfile from 'proper-lockfile';
10
- import { definePlugin, createPluginMethod, OutputPropertySchema, ZapierBundleError, DEFAULT_CONFIG_PATH, ZapierValidationError, ZapierUnknownError, ZapierReleaseTriggerMessageSignal, injectCliLogin, getOrCreateApiClient, invalidateCachedToken, batch, toSnakeCase, ZapierAbortDrainSignal, isCredentialsObject, buildApplicationLifecycleEvent, ZapierAuthenticationError, ZapierError, createZapierSdkStack, addPlugin, getOsInfo, getPlatformVersions, getCiPlatform, isCi, getReleaseId, getCurrentTimestamp, generateEventId } from '@zapier/zapier-sdk';
10
+ import { definePlugin, createPluginMethod, OutputPropertySchema, ZapierBundleError, DEFAULT_CONFIG_PATH, ZapierValidationError, ZapierUnknownError, ZapierReleaseTriggerMessageSignal, injectCliLogin, getOrCreateApiClient, isPermanentHttpError, invalidateCachedToken, batch, toSnakeCase, ZapierAbortDrainSignal, isCredentialsObject, buildApplicationLifecycleEvent, ZapierAuthenticationError, ZapierError, createZapierSdkStack, addPlugin, getOsInfo, getPlatformVersions, getCiPlatform, isCi, getReleaseId, getCurrentTimestamp, generateEventId } from '@zapier/zapier-sdk';
11
11
  import { z } from 'zod';
12
12
  import { hostname } from 'os';
13
13
  import inquirer from 'inquirer';
@@ -777,7 +777,8 @@ function sleep(ms) {
777
777
  async function withRetry({
778
778
  action,
779
779
  attempts = 3,
780
- initialDelayMs = 100
780
+ initialDelayMs = 100,
781
+ shouldRetry = () => true
781
782
  }) {
782
783
  if (attempts <= 0) {
783
784
  throw new Error("withRetry: attempts must be greater than 0");
@@ -788,9 +789,8 @@ async function withRetry({
788
789
  return await action();
789
790
  } catch (err) {
790
791
  lastError = err;
791
- if (i < attempts - 1) {
792
- await sleep(initialDelayMs * 2 ** i);
793
- }
792
+ if (!shouldRetry(err) || i >= attempts - 1) break;
793
+ await sleep(initialDelayMs * 2 ** i);
794
794
  }
795
795
  }
796
796
  throw lastError;
@@ -810,32 +810,50 @@ function getStatusCode(err) {
810
810
  }
811
811
  return void 0;
812
812
  }
813
+ var REVOKE_WARNING_TAIL = "Local state will be cleared, but the credential may still be active. Verify or revoke via `list-client-credentials` or `delete-client-credentials`.";
814
+ var REVOKE_REJECTED_WARNING = "Could not revoke the credential on the server: the request was rejected (your current credentials may lack permission to revoke it). Local state has been cleared, but the credential may still be active on the server \u2014 run `login` to re-authenticate, then `delete-client-credentials` to remove it.";
813
815
  async function revokeCredentials({
814
816
  api: api2,
815
817
  credentials,
816
- onEvent
818
+ onEvent,
819
+ alwaysClearLocalState = false
817
820
  }) {
818
- await withRetry({
819
- action: async () => {
820
- try {
821
- await api2.delete(
822
- `/api/v0/client-credentials/${credentials.clientId}`,
823
- void 0,
824
- { authRequired: true, requiredScopes: ["credentials"] }
825
- );
826
- } catch (err) {
827
- const status = getStatusCode(err);
828
- if (status === 404) return;
829
- if (status === 401) {
830
- console.warn(
831
- "Could not revoke credentials on the server (unauthorized). Local state will be cleared, but the credential may still be active. Verify or revoke via `list-client-credentials` or `delete-client-credentials`."
821
+ try {
822
+ await withRetry({
823
+ // Permanent errors (403/400) won't change on retry — fail fast instead of
824
+ // burning the backoff budget. The outer catch then rethrows (re-login) or
825
+ // swallows and clears local state (logout) per `alwaysClearLocalState`.
826
+ shouldRetry: (err) => !isPermanentHttpError(err),
827
+ action: async () => {
828
+ try {
829
+ await api2.delete(
830
+ `/api/v0/client-credentials/${credentials.clientId}`,
831
+ void 0,
832
+ { authRequired: true, requiredScopes: ["credentials"] }
832
833
  );
833
- return;
834
+ } catch (err) {
835
+ const status = getStatusCode(err);
836
+ if (status === 404) return;
837
+ if (status === 401) {
838
+ console.warn(
839
+ "Could not revoke credentials on the server (unauthorized). " + REVOKE_WARNING_TAIL
840
+ );
841
+ return;
842
+ }
843
+ throw err;
834
844
  }
835
- throw err;
836
845
  }
846
+ });
847
+ } catch (err) {
848
+ if (!alwaysClearLocalState) throw err;
849
+ if (isPermanentHttpError(err)) {
850
+ console.warn(REVOKE_REJECTED_WARNING);
851
+ } else {
852
+ console.warn(
853
+ "Could not revoke credentials on the server. " + REVOKE_WARNING_TAIL
854
+ );
837
855
  }
838
- });
856
+ }
839
857
  try {
840
858
  await deleteStoredClientCredentials({
841
859
  name: credentials.name,
@@ -1961,7 +1979,8 @@ var logoutPlugin = definePlugin(
1961
1979
  await revokeCredentials({
1962
1980
  api: sdk2.context.api,
1963
1981
  credentials: activeCredentials,
1964
- onEvent
1982
+ onEvent,
1983
+ alwaysClearLocalState: true
1965
1984
  });
1966
1985
  console.log("\u2705 Successfully logged out");
1967
1986
  }
@@ -4469,7 +4488,7 @@ definePlugin(
4469
4488
  // package.json with { type: 'json' }
4470
4489
  var package_default = {
4471
4490
  name: "@zapier/zapier-sdk-cli",
4472
- version: "0.54.0"};
4491
+ version: "0.54.2"};
4473
4492
 
4474
4493
  // src/sdk.ts
4475
4494
  injectCliLogin(login_exports);
@@ -4497,7 +4516,7 @@ function createZapierCliSdk(options = {}) {
4497
4516
 
4498
4517
  // package.json
4499
4518
  var package_default2 = {
4500
- version: "0.54.0"};
4519
+ version: "0.54.2"};
4501
4520
 
4502
4521
  // src/telemetry/builders.ts
4503
4522
  function createCliBaseEvent(context = {}) {
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zapier/zapier-sdk-cli",
3
- "version": "0.54.0",
3
+ "version": "0.54.2",
4
4
  "description": "Command line interface for Zapier SDK",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",
@@ -6,8 +6,9 @@ export type LogoutEventEmitter = (event: {
6
6
  timestamp: number;
7
7
  }) => void;
8
8
  export declare function emitAuthLogout(onEvent: LogoutEventEmitter | undefined): void;
9
- export declare function revokeCredentials({ api, credentials, onEvent, }: {
9
+ export declare function revokeCredentials({ api, credentials, onEvent, alwaysClearLocalState, }: {
10
10
  api: ApiClient;
11
11
  credentials: CredentialsEntry;
12
12
  onEvent?: LogoutEventEmitter;
13
+ alwaysClearLocalState?: boolean;
13
14
  }): Promise<void>;
@@ -1,4 +1,4 @@
1
- import { invalidateCachedToken } from "@zapier/zapier-sdk";
1
+ import { invalidateCachedToken, isPermanentHttpError, } from "@zapier/zapier-sdk";
2
2
  import { clearLegacyJwtState } from "./legacy-jwt";
3
3
  import { deleteStoredClientCredentials, } from "./credentials-store";
4
4
  import { withRetry } from "../utils/retry";
@@ -15,27 +15,67 @@ function getStatusCode(err) {
15
15
  }
16
16
  return undefined;
17
17
  }
18
+ const REVOKE_WARNING_TAIL = "Local state will be cleared, but the credential may still be active. " +
19
+ "Verify or revoke via `list-client-credentials` or `delete-client-credentials`.";
20
+ // Shown only when `logout` tears down local state after the server *rejected*
21
+ // revocation (a permanent 4xx). Retrying won't help, and `list-`/`delete-client-
22
+ // credentials` would hit the same refusal until the user re-authenticates — so we
23
+ // send them through `login`, which restores the ability to manage credentials.
24
+ const REVOKE_REJECTED_WARNING = "Could not revoke the credential on the server: the request was rejected " +
25
+ "(your current credentials may lack permission to revoke it). Local state has " +
26
+ "been cleared, but the credential may still be active on the server — run " +
27
+ "`login` to re-authenticate, then `delete-client-credentials` to remove it.";
18
28
  // Re-login flows pass no emitter so credential rotation stays silent.
19
- export async function revokeCredentials({ api, credentials, onEvent, }) {
20
- await withRetry({
21
- action: async () => {
22
- try {
23
- await api.delete(`/api/v0/client-credentials/${credentials.clientId}`, undefined, { authRequired: true, requiredScopes: ["credentials"] });
24
- }
25
- catch (err) {
26
- const status = getStatusCode(err);
27
- if (status === 404)
28
- return;
29
- if (status === 401) {
30
- console.warn("Could not revoke credentials on the server (unauthorized). " +
31
- "Local state will be cleared, but the credential may still be active. " +
32
- "Verify or revoke via `list-client-credentials` or `delete-client-credentials`.");
33
- return;
29
+ export async function revokeCredentials({ api, credentials, onEvent, alwaysClearLocalState = false, }) {
30
+ try {
31
+ await withRetry({
32
+ // Permanent errors (403/400) won't change on retry — fail fast instead of
33
+ // burning the backoff budget. The outer catch then rethrows (re-login) or
34
+ // swallows and clears local state (logout) per `alwaysClearLocalState`.
35
+ shouldRetry: (err) => !isPermanentHttpError(err),
36
+ action: async () => {
37
+ try {
38
+ await api.delete(`/api/v0/client-credentials/${credentials.clientId}`, undefined, { authRequired: true, requiredScopes: ["credentials"] });
34
39
  }
35
- throw err;
36
- }
37
- },
38
- });
40
+ catch (err) {
41
+ const status = getStatusCode(err);
42
+ if (status === 404)
43
+ return;
44
+ if (status === 401) {
45
+ console.warn("Could not revoke credentials on the server (unauthorized). " +
46
+ REVOKE_WARNING_TAIL);
47
+ return;
48
+ }
49
+ throw err;
50
+ }
51
+ },
52
+ });
53
+ }
54
+ catch (err) {
55
+ // Re-login leaves `alwaysClearLocalState` false because it wants the throw:
56
+ // any revoke failure (permanent OR transient) must surface so it can confirm
57
+ // before replacing creds (see `account-auth.ts`). Only `logout` sets the flag
58
+ // — it is teardown, so it swallows the failure and clears local state rather
59
+ // than strand the user behind a revoke they can never complete.
60
+ if (!alwaysClearLocalState)
61
+ throw err;
62
+ if (isPermanentHttpError(err)) {
63
+ // A permanent client rejection (4xx except the 401/404 the action handles
64
+ // above — e.g. a 403 when the token lacks the `credentials` scope) is the
65
+ // server's definitive "you may not revoke this". Retrying won't help, and
66
+ // there's no dashboard fallback; `delete-client-credentials` needs the same
67
+ // refused `credentials` scope, so the message routes the user through `login`
68
+ // (which restores that scope) first, then `delete-client-credentials`.
69
+ console.warn(REVOKE_REJECTED_WARNING);
70
+ }
71
+ else {
72
+ // Transient 5xx/429, a network failure, or an auth-resolution throw that
73
+ // never built the request (e.g. a missing keychain secret): revocation never
74
+ // reached a verdict, so a later `delete-client-credentials` could still
75
+ // succeed once things recover.
76
+ console.warn("Could not revoke credentials on the server. " + REVOKE_WARNING_TAIL);
77
+ }
78
+ }
39
79
  try {
40
80
  await deleteStoredClientCredentials({
41
81
  name: credentials.name,
@@ -20,10 +20,13 @@ export const logoutPlugin = definePlugin((sdk) => createPluginMethod(sdk, {
20
20
  console.log("✅ Successfully logged out");
21
21
  return;
22
22
  }
23
+ // Logout is teardown: clear local state even if the server refuses to
24
+ // revoke the credential, so a permanent 4xx can't strand the user.
23
25
  await revokeCredentials({
24
26
  api: sdk.context.api,
25
27
  credentials: activeCredentials,
26
28
  onEvent,
29
+ alwaysClearLocalState: true,
27
30
  });
28
31
  console.log("✅ Successfully logged out");
29
32
  },
@@ -1,5 +1,6 @@
1
- export declare function withRetry<T>({ action, attempts, initialDelayMs, }: {
1
+ export declare function withRetry<T>({ action, attempts, initialDelayMs, shouldRetry, }: {
2
2
  action: () => Promise<T>;
3
3
  attempts?: number;
4
4
  initialDelayMs?: number;
5
+ shouldRetry?: (err: unknown) => boolean;
5
6
  }): Promise<T>;
@@ -1,7 +1,7 @@
1
1
  function sleep(ms) {
2
2
  return new Promise((resolve) => setTimeout(resolve, ms));
3
3
  }
4
- export async function withRetry({ action, attempts = 3, initialDelayMs = 100, }) {
4
+ export async function withRetry({ action, attempts = 3, initialDelayMs = 100, shouldRetry = () => true, }) {
5
5
  if (attempts <= 0) {
6
6
  throw new Error("withRetry: attempts must be greater than 0");
7
7
  }
@@ -12,9 +12,9 @@ export async function withRetry({ action, attempts = 3, initialDelayMs = 100, })
12
12
  }
13
13
  catch (err) {
14
14
  lastError = err;
15
- if (i < attempts - 1) {
16
- await sleep(initialDelayMs * 2 ** i);
17
- }
15
+ if (!shouldRetry(err) || i >= attempts - 1)
16
+ break;
17
+ await sleep(initialDelayMs * 2 ** i);
18
18
  }
19
19
  }
20
20
  throw lastError;