@zapier/zapier-sdk-cli 0.54.1 → 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/CHANGELOG.md +9 -0
- package/dist/cli.cjs +44 -25
- package/dist/cli.mjs +45 -26
- package/dist/experimental.cjs +43 -24
- package/dist/experimental.mjs +44 -25
- package/dist/index.cjs +44 -25
- package/dist/index.mjs +45 -26
- package/dist/package.json +1 -1
- package/dist/src/login/credentials-revoke.d.ts +2 -1
- package/dist/src/login/credentials-revoke.js +60 -20
- package/dist/src/plugins/logout/index.js +3 -0
- package/dist/src/utils/retry.d.ts +2 -1
- package/dist/src/utils/retry.js +4 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
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
|
|
792
|
-
|
|
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
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
4519
|
+
version: "0.54.2"};
|
|
4501
4520
|
|
|
4502
4521
|
// src/telemetry/builders.ts
|
|
4503
4522
|
function createCliBaseEvent(context = {}) {
|
package/dist/package.json
CHANGED
|
@@ -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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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>;
|
package/dist/src/utils/retry.js
CHANGED
|
@@ -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
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
if (!shouldRetry(err) || i >= attempts - 1)
|
|
16
|
+
break;
|
|
17
|
+
await sleep(initialDelayMs * 2 ** i);
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
throw lastError;
|