@zapier/zapier-sdk-cli 0.52.12 → 0.53.1

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.
Files changed (45) hide show
  1. package/AGENTS.md +326 -0
  2. package/CHANGELOG.md +22 -0
  3. package/CLAUDE.md +3 -324
  4. package/README.md +20 -0
  5. package/dist/cli.cjs +896 -395
  6. package/dist/cli.mjs +897 -396
  7. package/dist/experimental.cjs +897 -398
  8. package/dist/experimental.d.mts +1 -1
  9. package/dist/experimental.d.ts +1 -1
  10. package/dist/experimental.mjs +896 -397
  11. package/dist/index.cjs +898 -399
  12. package/dist/index.d.mts +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.mjs +897 -398
  15. package/dist/package.json +2 -1
  16. package/dist/{sdk-Sa1HjzUj.d.mts → sdk-SOLizjno.d.mts} +40 -2
  17. package/dist/{sdk-Sa1HjzUj.d.ts → sdk-SOLizjno.d.ts} +40 -2
  18. package/dist/src/experimental.js +2 -1
  19. package/dist/src/plugins/index.d.ts +1 -0
  20. package/dist/src/plugins/index.js +1 -0
  21. package/dist/src/plugins/login/index.d.ts +2 -15
  22. package/dist/src/plugins/login/index.js +3 -191
  23. package/dist/src/plugins/signup/index.d.ts +25 -0
  24. package/dist/src/plugins/signup/index.js +12 -0
  25. package/dist/src/plugins/signup/schemas.d.ts +9 -0
  26. package/dist/src/plugins/signup/schemas.js +26 -0
  27. package/dist/src/plugins/signup/test-harness.d.ts +34 -0
  28. package/dist/src/plugins/signup/test-harness.js +74 -0
  29. package/dist/src/sdk.js +2 -1
  30. package/dist/src/types/sdk.d.ts +2 -1
  31. package/dist/src/utils/auth/account-auth.d.ts +32 -0
  32. package/dist/src/utils/auth/account-auth.js +265 -0
  33. package/dist/src/utils/auth/oauth-callback.d.ts +6 -0
  34. package/dist/src/utils/auth/oauth-callback.js +28 -0
  35. package/dist/src/utils/auth/oauth-errors.d.ts +2 -0
  36. package/dist/src/utils/auth/oauth-errors.js +39 -0
  37. package/dist/src/utils/auth/oauth-flow.d.ts +31 -6
  38. package/dist/src/utils/auth/oauth-flow.js +258 -106
  39. package/dist/src/utils/auth/oauth-transaction.d.ts +35 -0
  40. package/dist/src/utils/auth/oauth-transaction.js +69 -0
  41. package/dist/src/utils/non-interactive.d.ts +5 -4
  42. package/dist/src/utils/non-interactive.js +6 -5
  43. package/dist/tsconfig.tsbuildinfo +1 -1
  44. package/package.json +4 -3
  45. package/templates/basic/AGENTS.md.hbs +2 -2
@@ -1,90 +1,223 @@
1
- import open from "open";
2
- import crypto from "node:crypto";
3
1
  import express from "express";
4
- import pkceChallenge from "pkce-challenge";
5
- import { AUTH_MODE_HEADER, LOGIN_PORTS, LOGIN_TIMEOUT_MS } from "../constants";
6
- import { spinPromise } from "../spinner";
7
- import log from "../log";
8
- import api from "../api/client";
2
+ import { createInterface } from "node:readline/promises";
3
+ import open from "open";
4
+ import { LOGIN_PORTS, LOGIN_TIMEOUT_MS } from "../constants";
5
+ import { ZapierCliUserCancellationError, ZapierCliValidationError, } from "../errors";
9
6
  import getCallablePromise from "../getCallablePromise";
10
- import { getPkceLoginConfig } from "../../login";
11
- import { ZapierCliUserCancellationError } from "../errors";
12
- const findAvailablePort = () => {
7
+ import log from "../log";
8
+ import { spinPromise } from "../spinner";
9
+ import { getCallbackCode } from "./oauth-callback";
10
+ import { buildBrowserAuthUrl, exchangeOauthCode, OAUTH_LOOPBACK_HOST, prepareOauthTransaction, } from "./oauth-transaction";
11
+ export { buildBrowserAuthUrl, OAUTH_LOOPBACK_HOST };
12
+ class OauthFlowTimeoutError extends Error {
13
+ constructor(timeoutMs) {
14
+ super("OAuth flow timed out");
15
+ this.timeoutMs = timeoutMs;
16
+ this.name = "OauthFlowTimeoutError";
17
+ }
18
+ }
19
+ class OauthAuthorizationDeniedError extends Error {
20
+ constructor(reason) {
21
+ super("OAuth authorization denied");
22
+ this.reason = reason;
23
+ this.name = "OauthAuthorizationDeniedError";
24
+ }
25
+ }
26
+ function findAvailablePort() {
13
27
  return new Promise((resolve, reject) => {
14
28
  let portIndex = 0;
15
29
  const tryPort = (port) => {
16
- const server = express().listen(port, () => {
30
+ const server = express().listen(port, OAUTH_LOOPBACK_HOST, () => {
17
31
  server.close();
18
32
  resolve(port);
19
33
  });
20
34
  server.on("error", (err) => {
21
- if (err.code === "EADDRINUSE") {
22
- if (portIndex < LOGIN_PORTS.length) {
23
- tryPort(LOGIN_PORTS[portIndex++]);
24
- }
25
- else {
26
- reject(new Error(`All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`));
27
- }
35
+ if (err.code === "EADDRINUSE" && portIndex < LOGIN_PORTS.length) {
36
+ tryPort(LOGIN_PORTS[portIndex++]);
37
+ }
38
+ else if (err.code === "EADDRINUSE") {
39
+ reject(new Error(`All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`));
28
40
  }
29
41
  else {
30
42
  reject(err);
31
43
  }
32
44
  });
33
45
  };
34
- if (LOGIN_PORTS.length > 0) {
46
+ if (LOGIN_PORTS.length > 0)
35
47
  tryPort(LOGIN_PORTS[portIndex++]);
36
- }
37
- else {
48
+ else
38
49
  reject(new Error("No OAuth callback ports configured"));
50
+ });
51
+ }
52
+ export async function runLoginOauthFlow(options) {
53
+ return runOauthFlowEntryPoint({
54
+ ...options,
55
+ entryPoint: "login",
56
+ authAction: "log in",
57
+ flowName: "Login",
58
+ });
59
+ }
60
+ export async function runSignupOauthFlow(options) {
61
+ if (options.headless) {
62
+ return runOauthFlowEntryPoint({
63
+ ...options,
64
+ entryPoint: "signup",
65
+ authAction: "sign up",
66
+ flowName: "Signup",
67
+ headless: true,
68
+ });
69
+ }
70
+ return runOauthFlowEntryPoint({
71
+ ...options,
72
+ entryPoint: "signup",
73
+ authAction: "sign up",
74
+ flowName: "Signup",
75
+ });
76
+ }
77
+ async function runOauthFlowEntryPoint({ flowName, ...options }) {
78
+ try {
79
+ return options.headless
80
+ ? await runHeadlessSignupOauthFlow(options)
81
+ : await runOauthFlow(options);
82
+ }
83
+ catch (error) {
84
+ if (error instanceof OauthFlowTimeoutError) {
85
+ throw new Error(withRecoveryMessage(`${flowName} timed out after ${Math.round(error.timeoutMs / 1000)} seconds.`, options.recoveryMessage));
39
86
  }
87
+ if (error instanceof OauthAuthorizationDeniedError) {
88
+ throw new Error(withRecoveryMessage(`Authorization denied: ${error.reason}.`, options.recoveryMessage));
89
+ }
90
+ if (error instanceof ZapierCliUserCancellationError && !options.silent) {
91
+ log.info(`\n❌ ${flowName} cancelled by user`);
92
+ }
93
+ throw error;
94
+ }
95
+ }
96
+ function withRecoveryMessage(message, recoveryMessage) {
97
+ return recoveryMessage ? `${message} ${recoveryMessage}` : message;
98
+ }
99
+ async function runOauthFlow({ timeoutMs = LOGIN_TIMEOUT_MS, pkceCredentials, baseUrl, entryPoint, authAction, silent = false, onProgress, }) {
100
+ const port = await findAvailablePort();
101
+ if (!silent)
102
+ log.info(`Using port ${port} for OAuth callback`);
103
+ const transaction = await prepareOauthTransaction({
104
+ pkceCredentials,
105
+ baseUrl,
106
+ redirectUri: `http://${OAUTH_LOOPBACK_HOST}:${port}/oauth`,
107
+ entryPoint,
108
+ });
109
+ const code = await collectLocalCallbackCode({
110
+ transaction,
111
+ timeoutMs,
112
+ authAction,
113
+ silent,
114
+ onProgress,
40
115
  });
41
- };
42
- const generateRandomString = () => {
43
- const array = new Uint32Array(28);
44
- crypto.getRandomValues(array);
45
- return Array.from(array, (dec) => ("0" + dec.toString(16)).slice(-2)).join("");
46
- };
47
- function ensureOfflineAccess(scope) {
48
- if (scope.includes("offline_access")) {
49
- return scope;
116
+ onProgress?.({ type: "callback_accepted" });
117
+ if (!silent)
118
+ log.info("Exchanging authorization code for tokens...");
119
+ onProgress?.({ type: "token_exchange_started" });
120
+ const tokens = await exchangeOauthCode({ ...transaction, code });
121
+ if (!silent)
122
+ log.info("Token exchange completed successfully");
123
+ onProgress?.({ type: "token_exchange_completed" });
124
+ return tokens;
125
+ }
126
+ async function readHeadlessCallbackUrl({ timeoutMs, interactive, recoveryMessage, }) {
127
+ const timeoutMessage = withRecoveryMessage(`Signup timed out after ${Math.round(timeoutMs / 1000)} seconds.`, recoveryMessage);
128
+ const missingCallbackUrlMessage = withRecoveryMessage("Paste the final OAuth callback URL from your browser.", recoveryMessage);
129
+ // Keep TTY echo enabled for paste feedback; the PKCE code is single-use, verifier-protected, and exchanged immediately.
130
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
131
+ const abortController = new AbortController();
132
+ const timeoutTimer = setTimeout(() => abortController.abort(), timeoutMs);
133
+ const readUrl = interactive
134
+ ? rl.question("Paste the final OAuth callback URL: ", {
135
+ signal: abortController.signal,
136
+ })
137
+ : new Promise((resolve, reject) => {
138
+ let settled = false;
139
+ const settleResolve = (value) => {
140
+ settled = true;
141
+ resolve(value);
142
+ };
143
+ const settleReject = (error) => {
144
+ if (settled)
145
+ return;
146
+ settled = true;
147
+ reject(error);
148
+ };
149
+ abortController.signal.addEventListener("abort", () => settleReject(new Error(timeoutMessage)), { once: true });
150
+ rl.once("line", settleResolve);
151
+ rl.once("close", () => settleReject(new ZapierCliValidationError(missingCallbackUrlMessage)));
152
+ rl.once("error", settleReject);
153
+ });
154
+ try {
155
+ return await readUrl.catch((error) => {
156
+ if (error instanceof Error && error.name === "AbortError") {
157
+ throw new Error(timeoutMessage);
158
+ }
159
+ throw error;
160
+ });
161
+ }
162
+ finally {
163
+ clearTimeout(timeoutTimer);
164
+ rl.close();
50
165
  }
51
- return `${scope} offline_access`;
52
166
  }
53
- // Does not persist anything caller decides what to do with the tokens.
54
- export async function runOauthFlow({ timeoutMs = LOGIN_TIMEOUT_MS, pkceCredentials, baseUrl, }) {
55
- const { clientId, tokenUrl, authorizeUrl } = getPkceLoginConfig({
56
- credentials: pkceCredentials,
167
+ async function runHeadlessSignupOauthFlow({ timeoutMs = LOGIN_TIMEOUT_MS, pkceCredentials, baseUrl, interactive = true, onProgress, recoveryMessage, }) {
168
+ const port = LOGIN_PORTS[0];
169
+ if (port === undefined) {
170
+ throw new Error("No OAuth callback ports configured");
171
+ }
172
+ const transaction = await prepareOauthTransaction({
173
+ pkceCredentials,
57
174
  baseUrl,
175
+ redirectUri: `http://${OAUTH_LOOPBACK_HOST}:${port}/oauth`,
176
+ entryPoint: "signup",
177
+ });
178
+ console.log("Use this mode when signing up from a machine that has no browser.");
179
+ console.log("Open this signup URL in a browser on another machine:");
180
+ console.log(transaction.browserAuthUrl);
181
+ console.log(`When the browser lands on ${transaction.redirectUri} and cannot connect, paste the full final URL back here.`);
182
+ const callbackUrl = await readHeadlessCallbackUrl({
183
+ timeoutMs,
184
+ interactive,
185
+ recoveryMessage,
58
186
  });
59
- const scope = ensureOfflineAccess(pkceCredentials?.scope || "internal credentials");
60
- const availablePort = await findAvailablePort();
61
- const redirectUri = `http://localhost:${availablePort}/oauth`;
62
- log.info(`Using port ${availablePort} for OAuth callback`);
63
- const { promise: promisedCode, resolve: setCode, reject: rejectCode, } = getCallablePromise();
64
- const oauthState = generateRandomString();
65
- const expressApp = express();
66
- expressApp.get("/oauth", (req, res) => {
187
+ const code = getCallbackCode({
188
+ callbackUrl,
189
+ transaction,
190
+ recoveryMessage,
191
+ });
192
+ onProgress?.({ type: "callback_accepted" });
193
+ console.log("Exchanging authorization code for tokens...");
194
+ onProgress?.({ type: "token_exchange_started" });
195
+ const tokens = await exchangeOauthCode({ ...transaction, code });
196
+ onProgress?.({ type: "token_exchange_completed" });
197
+ return tokens;
198
+ }
199
+ async function collectLocalCallbackCode({ transaction, timeoutMs, authAction, silent, onProgress, }) {
200
+ const { promise, resolve, reject } = getCallablePromise();
201
+ const app = express();
202
+ app.get("/oauth", (req, res) => {
67
203
  res.setHeader("Connection", "close");
68
- if (req.query.state !== oauthState) {
69
- rejectCode(new Error("OAuth state mismatch — possible CSRF"));
204
+ if (req.query.state !== transaction.state) {
70
205
  res.status(400).end("Invalid state. You can close this tab.");
71
- return;
72
206
  }
73
- if (req.query.error) {
74
- const desc = req.query.error_description ?? req.query.error;
75
- rejectCode(new Error(`Authorization denied: ${desc}`));
207
+ else if (req.query.error) {
208
+ reject(new OauthAuthorizationDeniedError(String(req.query.error_description ?? req.query.error)));
76
209
  res.end("Authorization was denied. You can close this tab.");
77
- return;
78
210
  }
79
- if (!req.query.code) {
80
- rejectCode(new Error("No authorization code received"));
211
+ else if (!req.query.code) {
212
+ reject(new Error("No authorization code received"));
81
213
  res.end("No authorization code received. You can close this tab.");
82
- return;
83
214
  }
84
- setCode(String(req.query.code));
85
- res.end("You can now close this tab and return to the CLI.");
215
+ else {
216
+ resolve(String(req.query.code));
217
+ res.end("You can now close this tab and return to the CLI.");
218
+ }
86
219
  });
87
- const server = expressApp.listen(availablePort);
220
+ const server = app.listen(Number(new URL(transaction.redirectUri).port), OAUTH_LOOPBACK_HOST);
88
221
  const connections = new Set();
89
222
  server.on("connection", (conn) => {
90
223
  connections.add(conn);
@@ -92,70 +225,89 @@ export async function runOauthFlow({ timeoutMs = LOGIN_TIMEOUT_MS, pkceCredentia
92
225
  });
93
226
  const cleanup = () => {
94
227
  server.close();
95
- log.info("\n❌ Login cancelled by user");
96
- rejectCode(new ZapierCliUserCancellationError());
228
+ reject(new ZapierCliUserCancellationError());
97
229
  };
98
230
  process.on("SIGINT", cleanup);
99
231
  process.on("SIGTERM", cleanup);
100
- const { code_verifier: codeVerifier, code_challenge: codeChallenge } = await pkceChallenge();
101
- const authUrl = `${authorizeUrl}?${new URLSearchParams({
102
- response_type: "code",
103
- client_id: clientId,
104
- redirect_uri: redirectUri,
105
- scope,
106
- state: oauthState,
107
- code_challenge: codeChallenge,
108
- code_challenge_method: "S256",
109
- }).toString()}`;
110
- log.info("Opening your browser to log in.");
111
- log.info("If it doesn't open, visit:", authUrl);
112
- open(authUrl);
113
232
  let timeoutTimer;
114
233
  try {
115
- await spinPromise(Promise.race([
116
- promisedCode,
117
- new Promise((_resolve, reject) => {
234
+ await waitForServerListening(server);
235
+ await openBrowser({ transaction, authAction, silent, onProgress });
236
+ const waitForCode = Promise.race([
237
+ promise,
238
+ new Promise((_resolve, rejectTimeout) => {
118
239
  timeoutTimer = setTimeout(() => {
119
- reject(new Error(`Login timed out after ${Math.round(timeoutMs / 1000)} seconds.`));
240
+ rejectTimeout(new OauthFlowTimeoutError(timeoutMs));
120
241
  }, timeoutMs);
121
242
  }),
122
- ]), "Waiting for you to login and authorize");
243
+ ]);
244
+ onProgress?.({ type: "callback_waiting" });
245
+ return silent
246
+ ? await waitForCode
247
+ : await spinPromise(waitForCode, `Waiting for you to ${authAction} and authorize`);
123
248
  }
124
249
  finally {
125
- if (timeoutTimer) {
250
+ if (timeoutTimer)
126
251
  clearTimeout(timeoutTimer);
127
- }
128
252
  process.off("SIGINT", cleanup);
129
253
  process.off("SIGTERM", cleanup);
130
- await new Promise((resolve) => {
131
- const timeout = setTimeout(() => {
132
- log.info("Server close timed out, forcing connection shutdown...");
133
- connections.forEach((conn) => conn.destroy());
134
- resolve();
135
- }, 1000);
136
- server.close(() => {
137
- clearTimeout(timeout);
138
- resolve();
139
- });
254
+ await closeServer({ server, connections, silent });
255
+ }
256
+ }
257
+ async function waitForServerListening(server) {
258
+ if (server.listening)
259
+ return;
260
+ await new Promise((resolve, reject) => {
261
+ const cleanup = () => {
262
+ server.off("listening", handleListening);
263
+ server.off("error", handleError);
264
+ };
265
+ const handleListening = () => {
266
+ cleanup();
267
+ resolve();
268
+ };
269
+ const handleError = (error) => {
270
+ cleanup();
271
+ reject(error);
272
+ };
273
+ server.once("listening", handleListening);
274
+ server.once("error", handleError);
275
+ });
276
+ }
277
+ async function openBrowser({ transaction, authAction, silent, onProgress, }) {
278
+ if (!silent) {
279
+ log.info(`Opening your browser to ${authAction}.`);
280
+ log.info("If it doesn't open, visit:", transaction.browserAuthUrl);
281
+ }
282
+ onProgress?.({ type: "browser_opening", url: transaction.browserAuthUrl });
283
+ try {
284
+ await open(transaction.browserAuthUrl);
285
+ onProgress?.({ type: "browser_opened", url: transaction.browserAuthUrl });
286
+ }
287
+ catch (err) {
288
+ const reason = err instanceof Error ? err.message : String(err);
289
+ if (!silent) {
290
+ log.info(`Browser did not open automatically to ${authAction}: ${reason}`);
291
+ log.info("Visit this URL manually:", transaction.browserAuthUrl);
292
+ }
293
+ onProgress?.({
294
+ type: "browser_open_failed",
295
+ url: transaction.browserAuthUrl,
296
+ reason,
140
297
  });
141
298
  }
142
- log.info("Exchanging authorization code for tokens...");
143
- const { data } = await api.post(tokenUrl, {
144
- grant_type: "authorization_code",
145
- code: await promisedCode,
146
- redirect_uri: redirectUri,
147
- client_id: clientId,
148
- code_verifier: codeVerifier,
149
- }, {
150
- headers: {
151
- [AUTH_MODE_HEADER]: "no",
152
- "Content-Type": "application/x-www-form-urlencoded",
153
- },
299
+ }
300
+ async function closeServer({ server, connections, silent, }) {
301
+ await new Promise((resolve) => {
302
+ const timeout = setTimeout(() => {
303
+ if (!silent)
304
+ log.info("Server close timed out, forcing connection shutdown...");
305
+ connections.forEach((conn) => conn.destroy());
306
+ resolve();
307
+ }, 1000);
308
+ server.close(() => {
309
+ clearTimeout(timeout);
310
+ resolve();
311
+ });
154
312
  });
155
- log.info("Token exchange completed successfully");
156
- return {
157
- accessToken: data.access_token,
158
- refreshToken: data.refresh_token,
159
- expiresIn: data.expires_in,
160
- };
161
313
  }
@@ -0,0 +1,35 @@
1
+ import { type PkceCredentials } from "../../login";
2
+ export declare const OAUTH_LOOPBACK_HOST = "localhost";
3
+ export interface OauthTokens {
4
+ accessToken: string;
5
+ refreshToken: string;
6
+ expiresIn: number;
7
+ }
8
+ export type OauthFlowEntryPoint = "login" | "signup";
9
+ interface PrepareOauthTransactionOptions {
10
+ pkceCredentials?: PkceCredentials;
11
+ baseUrl?: string;
12
+ redirectUri: string;
13
+ entryPoint?: OauthFlowEntryPoint;
14
+ }
15
+ export interface OauthTransaction {
16
+ browserAuthUrl: string;
17
+ clientId: string;
18
+ codeVerifier: string;
19
+ redirectUri: string;
20
+ state: string;
21
+ tokenUrl: string;
22
+ }
23
+ export declare function buildBrowserAuthUrl({ authorizeUrl, entryPoint, }: {
24
+ authorizeUrl: string;
25
+ entryPoint?: OauthFlowEntryPoint;
26
+ }): string;
27
+ export declare function prepareOauthTransaction({ pkceCredentials, baseUrl, redirectUri, entryPoint, }: PrepareOauthTransactionOptions): Promise<OauthTransaction>;
28
+ export declare function exchangeOauthCode({ tokenUrl, code, redirectUri, clientId, codeVerifier, }: {
29
+ tokenUrl: string;
30
+ code: string;
31
+ redirectUri: string;
32
+ clientId: string;
33
+ codeVerifier: string;
34
+ }): Promise<OauthTokens>;
35
+ export {};
@@ -0,0 +1,69 @@
1
+ import crypto from "node:crypto";
2
+ import pkceChallenge from "pkce-challenge";
3
+ import { AUTH_MODE_HEADER } from "../constants";
4
+ import api from "../api/client";
5
+ import { getPkceLoginConfig } from "../../login";
6
+ export const OAUTH_LOOPBACK_HOST = "localhost";
7
+ export function buildBrowserAuthUrl({ authorizeUrl, entryPoint = "login", }) {
8
+ if (entryPoint === "login")
9
+ return authorizeUrl;
10
+ const parsedAuthorizeUrl = new URL(authorizeUrl);
11
+ const signupUrl = new URL("/sign-up", parsedAuthorizeUrl);
12
+ signupUrl.searchParams.set("skipOnboarding", "true");
13
+ signupUrl.searchParams.set("next", `${parsedAuthorizeUrl.pathname}${parsedAuthorizeUrl.search}`);
14
+ return signupUrl.toString();
15
+ }
16
+ function generateRandomString() {
17
+ const array = new Uint32Array(28);
18
+ crypto.getRandomValues(array);
19
+ return Array.from(array, (dec) => ("0" + dec.toString(16)).slice(-2)).join("");
20
+ }
21
+ function ensureOfflineAccess(scope) {
22
+ if (scope.includes("offline_access"))
23
+ return scope;
24
+ return `${scope} offline_access`;
25
+ }
26
+ export async function prepareOauthTransaction({ pkceCredentials, baseUrl, redirectUri, entryPoint = "login", }) {
27
+ const { clientId, tokenUrl, authorizeUrl } = getPkceLoginConfig({
28
+ credentials: pkceCredentials,
29
+ baseUrl,
30
+ });
31
+ const { code_verifier: codeVerifier, code_challenge: codeChallenge } = await pkceChallenge();
32
+ const state = generateRandomString();
33
+ const authUrl = `${authorizeUrl}?${new URLSearchParams({
34
+ response_type: "code",
35
+ client_id: clientId,
36
+ redirect_uri: redirectUri,
37
+ scope: ensureOfflineAccess(pkceCredentials?.scope || "internal credentials"),
38
+ state,
39
+ code_challenge: codeChallenge,
40
+ code_challenge_method: "S256",
41
+ }).toString()}`;
42
+ return {
43
+ browserAuthUrl: buildBrowserAuthUrl({ authorizeUrl: authUrl, entryPoint }),
44
+ clientId,
45
+ codeVerifier,
46
+ redirectUri,
47
+ state,
48
+ tokenUrl,
49
+ };
50
+ }
51
+ export async function exchangeOauthCode({ tokenUrl, code, redirectUri, clientId, codeVerifier, }) {
52
+ const { data } = await api.post(tokenUrl, {
53
+ grant_type: "authorization_code",
54
+ code,
55
+ redirect_uri: redirectUri,
56
+ client_id: clientId,
57
+ code_verifier: codeVerifier,
58
+ }, {
59
+ headers: {
60
+ [AUTH_MODE_HEADER]: "no",
61
+ "Content-Type": "application/x-www-form-urlencoded",
62
+ },
63
+ });
64
+ return {
65
+ accessToken: data.access_token,
66
+ refreshToken: data.refresh_token,
67
+ expiresIn: data.expires_in,
68
+ };
69
+ }
@@ -2,12 +2,13 @@
2
2
  * Resolve whether a CLI invocation should run non-interactively.
3
3
  *
4
4
  * A command is non-interactive when the user explicitly opts out via
5
- * `--non-interactive` / `--skip-prompts`, OR when stdin or stdout isn't a
6
- * TTY (CI, piped output, etc.). The TTY check matters because prompting
7
- * against a non-TTY stream hangs or crashes — every plugin that gates
8
- * inquirer prompts on this flag needs the same answer.
5
+ * `--non-interactive`, OR when stdin or stdout isn't a TTY (CI, piped output,
6
+ * etc.). The TTY check matters because prompting against a non-TTY stream
7
+ * hangs or crashes — every plugin that gates inquirer prompts on this flag
8
+ * needs the same answer.
9
9
  */
10
10
  export declare function resolveNonInteractive(options: {
11
11
  nonInteractive?: boolean;
12
+ /** @deprecated Use `nonInteractive` instead. */
12
13
  skipPrompts?: boolean;
13
14
  }): boolean;
@@ -2,13 +2,14 @@
2
2
  * Resolve whether a CLI invocation should run non-interactively.
3
3
  *
4
4
  * A command is non-interactive when the user explicitly opts out via
5
- * `--non-interactive` / `--skip-prompts`, OR when stdin or stdout isn't a
6
- * TTY (CI, piped output, etc.). The TTY check matters because prompting
7
- * against a non-TTY stream hangs or crashes — every plugin that gates
8
- * inquirer prompts on this flag needs the same answer.
5
+ * `--non-interactive`, OR when stdin or stdout isn't a TTY (CI, piped output,
6
+ * etc.). The TTY check matters because prompting against a non-TTY stream
7
+ * hangs or crashes — every plugin that gates inquirer prompts on this flag
8
+ * needs the same answer.
9
9
  */
10
10
  export function resolveNonInteractive(options) {
11
- return ((options.nonInteractive ?? options.skipPrompts) === true ||
11
+ return (options.nonInteractive === true ||
12
+ options.skipPrompts === true ||
12
13
  !process.stdin.isTTY ||
13
14
  !process.stdout.isTTY);
14
15
  }