@zapier/zapier-sdk-cli 0.55.4 → 0.55.6

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.
@@ -74,6 +74,15 @@ export function resolveAvailableCredentialName({ baseName, baseUrl, }) {
74
74
  .map((e) => e.name));
75
75
  return firstAvailableCredentialName({ baseName, taken });
76
76
  }
77
+ /**
78
+ * Whether a credential with this exact (name, baseUrl) is already stored
79
+ * locally. Used to reject an explicitly-supplied login name that would
80
+ * otherwise silently overwrite a dormant credential of the same name.
81
+ */
82
+ export function credentialNameExists({ name, baseUrl, }) {
83
+ const resolvedBaseUrl = normalizeBaseUrl(baseUrl);
84
+ return readRegistry().some((e) => e.name === name && e.baseUrl === resolvedBaseUrl);
85
+ }
77
86
  export async function storeClientCredentials({ name, clientId, clientSecret, scopes, baseUrl, }) {
78
87
  if (!name || typeof name !== "string") {
79
88
  throw new Error("storeClientCredentials: name is required");
@@ -9,6 +9,7 @@ export declare const loginPlugin: (sdk: {
9
9
  };
10
10
  }) => {
11
11
  login: (options?: {
12
+ name?: string | undefined;
12
13
  timeout?: string | undefined;
13
14
  useApprovals?: boolean | undefined;
14
15
  nonInteractive?: boolean | undefined;
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  export declare const LoginSchema: z.ZodObject<{
3
+ name: z.ZodOptional<z.ZodString>;
3
4
  timeout: z.ZodOptional<z.ZodString>;
4
5
  useApprovals: z.ZodOptional<z.ZodBoolean>;
5
6
  nonInteractive: z.ZodOptional<z.ZodBoolean>;
@@ -2,6 +2,10 @@ import { z } from "zod";
2
2
  // Login schema
3
3
  export const LoginSchema = z
4
4
  .object({
5
+ name: z
6
+ .string()
7
+ .optional()
8
+ .describe("Name to identify these credentials (defaults to <email>@<hostname>). Provide this to set a custom name without the interactive prompt."),
5
9
  timeout: z
6
10
  .string()
7
11
  .optional()
@@ -1,6 +1,7 @@
1
1
  import { type ApiClient, type EventEmissionProvides, type ResolvedCredentials } from "@zapier/zapier-sdk";
2
2
  export type AccountAuthEntryPoint = "login" | "signup";
3
3
  export interface AccountAuthOptions {
4
+ name?: string;
4
5
  timeout?: string;
5
6
  useApprovals?: boolean;
6
7
  nonInteractive?: boolean;
@@ -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, resolveAvailableCredentialName, } from "../../login/credentials-store";
5
+ import { credentialNameExists, 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";
@@ -61,6 +61,19 @@ async function promptCredentialsName({ email, promptMessage, }) {
61
61
  function resolveDefaultCredentialsName({ email, }) {
62
62
  return validateCredentialsName(defaultCredentialsName(email));
63
63
  }
64
+ async function resolveCredentialName({ email, interactive, baseUrl, entryPoint, }) {
65
+ if (interactive) {
66
+ return promptCredentialsName({
67
+ email,
68
+ promptMessage: getCredentialsPromptMessage(entryPoint),
69
+ });
70
+ }
71
+ // Non-interactive login can't prompt for a new name, so a default name that
72
+ // already exists locally would silently overwrite that credential. Suffix it
73
+ // instead.
74
+ const baseName = resolveDefaultCredentialsName({ email });
75
+ return resolveAvailableCredentialName({ baseName, baseUrl });
76
+ }
64
77
  function toPkceCredentials(credentials) {
65
78
  if (credentials &&
66
79
  isCredentialsObject(credentials) &&
@@ -218,6 +231,27 @@ export async function runAccountAuth({ sdk, options, entryPoint, }) {
218
231
  ...sdk.context,
219
232
  resolvedCredentials,
220
233
  });
234
+ // An explicitly-supplied name is validated and collision-checked up front,
235
+ // before we tear down the existing session or run OAuth. Otherwise a blank or
236
+ // already-taken name would only surface after revoking the active credential
237
+ // and completing the browser flow, leaving the user logged out for nothing.
238
+ // The name is trimmed but otherwise honored as given — never auto-suffixed —
239
+ // so a collision is rejected rather than silently overwriting an existing
240
+ // credential.
241
+ const providedName = options.name !== undefined
242
+ ? validateCredentialsName(options.name)
243
+ : undefined;
244
+ if (providedName !== undefined) {
245
+ const activeCredentials = getActiveCredentials({
246
+ baseUrl: credentialsBaseUrl,
247
+ });
248
+ // Re-login under the active credential's own name is a rotation, not a
249
+ // collision — clearExistingAuthState frees it below.
250
+ if (activeCredentials?.name !== providedName &&
251
+ credentialNameExists({ name: providedName, baseUrl: credentialsBaseUrl })) {
252
+ throw new ZapierCliValidationError(`A credential named "${providedName}" already exists. Choose a different --name.`);
253
+ }
254
+ }
221
255
  if (!(await clearExistingAuthState({
222
256
  sdk,
223
257
  baseUrl: credentialsBaseUrl,
@@ -242,18 +276,13 @@ export async function runAccountAuth({ sdk, options, entryPoint, }) {
242
276
  const profile = await getProfile(scopedApi);
243
277
  console.log(getProfileMessage(entryPoint, profile.email));
244
278
  console.log("\nGenerating credentials so this machine can make authenticated requests on your behalf.");
245
- const baseName = interactive
246
- ? await promptCredentialsName({
279
+ const credentialName = providedName ??
280
+ (await resolveCredentialName({
247
281
  email: profile.email,
248
- promptMessage: getCredentialsPromptMessage(entryPoint),
249
- })
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 });
282
+ interactive,
283
+ baseUrl: credentialsBaseUrl,
284
+ entryPoint,
285
+ }));
257
286
  const useApprovals = options.useApprovals === true;
258
287
  await saveClientCredentials({
259
288
  api: scopedApi,