@zapier/zapier-sdk-cli 0.55.2 → 0.55.3

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.
@@ -11,6 +11,28 @@ export type CredentialsEntry = z.infer<typeof CredentialsEntrySchema>;
11
11
  export declare function getActiveCredentials(options?: {
12
12
  baseUrl?: string;
13
13
  }): CredentialsEntry | undefined;
14
+ /**
15
+ * Pick the first credential name not already present in `taken`. Returns
16
+ * `baseName` if it is free, otherwise appends an incrementing `-1`, `-2`, …
17
+ * suffix to the literal base — a base ending in a digit is left intact, so
18
+ * `acme-1` collides into `acme-1-1`. Storing a credential under a name that
19
+ * already exists locally silently replaces that entry (and deletes its
20
+ * keychain secret), so non-interactive login uses this to avoid clobbering a
21
+ * dormant credential of the same name.
22
+ */
23
+ export declare function firstAvailableCredentialName({ baseName, taken, }: {
24
+ baseName: string;
25
+ taken: ReadonlySet<string>;
26
+ }): string;
27
+ /**
28
+ * Resolve a locally-unique credential name for the given baseUrl. Local
29
+ * credentials are keyed by (name, baseUrl), so name collisions — and the
30
+ * suffixing that avoids them — are scoped per baseUrl.
31
+ */
32
+ export declare function resolveAvailableCredentialName({ baseName, baseUrl, }: {
33
+ baseName: string;
34
+ baseUrl?: string;
35
+ }): string;
14
36
  export declare function storeClientCredentials({ name, clientId, clientSecret, scopes, baseUrl, }: {
15
37
  name: string;
16
38
  clientId: string;
@@ -3,6 +3,7 @@ import { getPassword, setPassword, deletePassword } from "cross-keychain";
3
3
  import { z } from "zod";
4
4
  import { DEFAULT_AUTH_BASE_URL, getConfig } from "./config";
5
5
  import { enqueue, getBackendInfo } from "./keychain";
6
+ import { ZapierCliValidationError } from "../utils/errors";
6
7
  const SERVICE = "zapier-sdk-cli";
7
8
  const CREDENTIALS_KEY = "credentials";
8
9
  const REGISTRY_KEY = "credentialsRegistry";
@@ -41,6 +42,38 @@ export function getActiveCredentials(options) {
41
42
  return undefined;
42
43
  return findEntry(readRegistry(), name, normalizeBaseUrl(options?.baseUrl));
43
44
  }
45
+ const MAX_CREDENTIAL_NAME_ATTEMPTS = 500;
46
+ /**
47
+ * Pick the first credential name not already present in `taken`. Returns
48
+ * `baseName` if it is free, otherwise appends an incrementing `-1`, `-2`, …
49
+ * suffix to the literal base — a base ending in a digit is left intact, so
50
+ * `acme-1` collides into `acme-1-1`. Storing a credential under a name that
51
+ * already exists locally silently replaces that entry (and deletes its
52
+ * keychain secret), so non-interactive login uses this to avoid clobbering a
53
+ * dormant credential of the same name.
54
+ */
55
+ export function firstAvailableCredentialName({ baseName, taken, }) {
56
+ if (!taken.has(baseName))
57
+ return baseName;
58
+ for (let i = 1; i <= MAX_CREDENTIAL_NAME_ATTEMPTS; i++) {
59
+ const candidate = `${baseName}-${i}`;
60
+ if (!taken.has(candidate))
61
+ return candidate;
62
+ }
63
+ throw new ZapierCliValidationError(`Could not find an available credential name for "${baseName}" after ${MAX_CREDENTIAL_NAME_ATTEMPTS} attempts.`);
64
+ }
65
+ /**
66
+ * Resolve a locally-unique credential name for the given baseUrl. Local
67
+ * credentials are keyed by (name, baseUrl), so name collisions — and the
68
+ * suffixing that avoids them — are scoped per baseUrl.
69
+ */
70
+ export function resolveAvailableCredentialName({ baseName, baseUrl, }) {
71
+ const resolvedBaseUrl = normalizeBaseUrl(baseUrl);
72
+ const taken = new Set(readRegistry()
73
+ .filter((e) => e.baseUrl === resolvedBaseUrl)
74
+ .map((e) => e.name));
75
+ return firstAvailableCredentialName({ baseName, taken });
76
+ }
44
77
  export async function storeClientCredentials({ name, clientId, clientSecret, scopes, baseUrl, }) {
45
78
  if (!name || typeof name !== "string") {
46
79
  throw new Error("storeClientCredentials: name is required");
@@ -2,7 +2,7 @@ import { hostname } from "node:os";
2
2
  import inquirer from "inquirer";
3
3
  import { buildApplicationLifecycleEvent, getOrCreateApiClient, isCredentialsObject, } from "@zapier/zapier-sdk";
4
4
  import { revokeCredentials } from "../../login/credentials-revoke";
5
- import { deleteStoredClientCredentials, getActiveCredentials, } from "../../login/credentials-store";
5
+ import { deleteStoredClientCredentials, getActiveCredentials, resolveAvailableCredentialName, } from "../../login/credentials-store";
6
6
  import { clearLegacyJwtState, hasLegacyJwtConfig, } from "../../login/legacy-jwt";
7
7
  import { resolveCredentialsBaseUrl } from "../../plugins/auth/credentials-base-url";
8
8
  import { resolveNonInteractive } from "../non-interactive";
@@ -242,13 +242,18 @@ export async function runAccountAuth({ sdk, options, entryPoint, }) {
242
242
  const profile = await getProfile(scopedApi);
243
243
  console.log(getProfileMessage(entryPoint, profile.email));
244
244
  console.log("\nGenerating credentials so this machine can make authenticated requests on your behalf.");
245
- const resolveCredentialsName = interactive
246
- ? ({ email }) => promptCredentialsName({
247
- email,
245
+ const baseName = interactive
246
+ ? await promptCredentialsName({
247
+ email: profile.email,
248
248
  promptMessage: getCredentialsPromptMessage(entryPoint),
249
249
  })
250
- : resolveDefaultCredentialsName;
251
- const credentialName = await resolveCredentialsName({ email: profile.email });
250
+ : resolveDefaultCredentialsName({ email: profile.email });
251
+ // Non-interactive login can't prompt for a new name, so a default name that
252
+ // already exists locally would silently overwrite that credential. Suffix it
253
+ // instead. Interactive callers chose the name themselves, so leave it as-is.
254
+ const credentialName = interactive
255
+ ? baseName
256
+ : resolveAvailableCredentialName({ baseName, baseUrl: credentialsBaseUrl });
252
257
  const useApprovals = options.useApprovals === true;
253
258
  await saveClientCredentials({
254
259
  api: scopedApi,