@zapier/zapier-sdk-cli 0.52.10 → 0.53.0

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 (71) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +39 -1
  3. package/dist/cli.cjs +972 -473
  4. package/dist/cli.mjs +973 -474
  5. package/dist/experimental.cjs +914 -424
  6. package/dist/experimental.d.mts +1 -1
  7. package/dist/experimental.d.ts +1 -1
  8. package/dist/experimental.mjs +910 -420
  9. package/dist/index.cjs +914 -424
  10. package/dist/index.d.mts +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.mjs +910 -420
  13. package/dist/login.cjs +8 -10
  14. package/dist/login.d.mts +2 -10
  15. package/dist/login.d.ts +2 -10
  16. package/dist/login.mjs +5 -9
  17. package/dist/package.json +1 -1
  18. package/dist/{sdk-B3nKAZdN.d.mts → sdk-SOLizjno.d.mts} +54 -16
  19. package/dist/{sdk-B3nKAZdN.d.ts → sdk-SOLizjno.d.ts} +54 -16
  20. package/dist/src/experimental.js +30 -27
  21. package/dist/src/login/index.d.ts +1 -9
  22. package/dist/src/login/index.js +12 -14
  23. package/dist/src/plugins/add/index.d.ts +15 -15
  24. package/dist/src/plugins/add/index.js +1 -1
  25. package/dist/src/plugins/buildManifest/index.d.ts +2 -2
  26. package/dist/src/plugins/bundleCode/index.d.ts +1 -1
  27. package/dist/src/plugins/bundleCode/index.js +2 -1
  28. package/dist/src/plugins/cliOverrides/index.d.ts +5 -10
  29. package/dist/src/plugins/cliOverrides/index.js +2 -6
  30. package/dist/src/plugins/curl/index.d.ts +2 -2
  31. package/dist/src/plugins/curl/schemas.d.ts +2 -2
  32. package/dist/src/plugins/feedback/index.d.ts +1 -1
  33. package/dist/src/plugins/generateAppTypes/index.d.ts +11 -11
  34. package/dist/src/plugins/getLoginConfigPath/index.d.ts +1 -1
  35. package/dist/src/plugins/index.d.ts +2 -1
  36. package/dist/src/plugins/index.js +2 -1
  37. package/dist/src/plugins/init/index.d.ts +1 -1
  38. package/dist/src/plugins/login/index.d.ts +3 -16
  39. package/dist/src/plugins/login/index.js +3 -191
  40. package/dist/src/plugins/logout/index.d.ts +1 -1
  41. package/dist/src/plugins/mcp/index.d.ts +1 -1
  42. package/dist/src/plugins/signup/index.d.ts +25 -0
  43. package/dist/src/plugins/signup/index.js +12 -0
  44. package/dist/src/plugins/signup/schemas.d.ts +9 -0
  45. package/dist/src/plugins/signup/schemas.js +26 -0
  46. package/dist/src/plugins/signup/test-harness.d.ts +34 -0
  47. package/dist/src/plugins/signup/test-harness.js +74 -0
  48. package/dist/src/sdk.js +32 -20
  49. package/dist/src/types/sdk.d.ts +2 -1
  50. package/dist/src/utils/auth/account-auth.d.ts +32 -0
  51. package/dist/src/utils/auth/account-auth.js +265 -0
  52. package/dist/src/utils/auth/oauth-callback.d.ts +6 -0
  53. package/dist/src/utils/auth/oauth-callback.js +28 -0
  54. package/dist/src/utils/auth/oauth-errors.d.ts +2 -0
  55. package/dist/src/utils/auth/oauth-errors.js +39 -0
  56. package/dist/src/utils/auth/oauth-flow.d.ts +31 -6
  57. package/dist/src/utils/auth/oauth-flow.js +258 -106
  58. package/dist/src/utils/auth/oauth-transaction.d.ts +35 -0
  59. package/dist/src/utils/auth/oauth-transaction.js +69 -0
  60. package/dist/src/utils/cli-generator.js +14 -7
  61. package/dist/src/utils/cli-renderer.d.ts +13 -3
  62. package/dist/src/utils/cli-renderer.js +27 -20
  63. package/dist/src/utils/log.js +9 -4
  64. package/dist/src/utils/non-interactive.d.ts +5 -4
  65. package/dist/src/utils/non-interactive.js +6 -5
  66. package/dist/src/utils/parameter-resolver.js +3 -1
  67. package/dist/src/utils/schema-formatter.d.ts +2 -2
  68. package/dist/src/utils/schema-formatter.js +4 -30
  69. package/dist/src/utils/version-checker.js +8 -3
  70. package/dist/tsconfig.tsbuildinfo +1 -1
  71. package/package.json +3 -3
@@ -1,12 +1,37 @@
1
1
  import { type PkceCredentials } from "../../login";
2
- export interface OauthTokens {
3
- accessToken: string;
4
- refreshToken: string;
5
- expiresIn: number;
6
- }
2
+ import { buildBrowserAuthUrl, OAUTH_LOOPBACK_HOST, type OauthTokens } from "./oauth-transaction";
3
+ export { buildBrowserAuthUrl, OAUTH_LOOPBACK_HOST };
4
+ export type { OauthFlowEntryPoint, OauthTokens } from "./oauth-transaction";
5
+ export type OauthFlowProgressEvent = {
6
+ type: "browser_opening";
7
+ url: string;
8
+ } | {
9
+ type: "browser_opened";
10
+ url: string;
11
+ } | {
12
+ type: "browser_open_failed";
13
+ url: string;
14
+ reason: string;
15
+ } | {
16
+ type: "callback_waiting";
17
+ } | {
18
+ type: "callback_accepted";
19
+ } | {
20
+ type: "token_exchange_started";
21
+ } | {
22
+ type: "token_exchange_completed";
23
+ };
7
24
  export interface RunOauthFlowOptions {
8
25
  timeoutMs?: number;
9
26
  pkceCredentials?: PkceCredentials;
10
27
  baseUrl?: string;
28
+ silent?: boolean;
29
+ onProgress?: (event: OauthFlowProgressEvent) => void;
30
+ recoveryMessage?: string;
31
+ }
32
+ interface RunSignupOauthFlowOptions extends RunOauthFlowOptions {
33
+ headless?: boolean;
34
+ interactive?: boolean;
11
35
  }
12
- export declare function runOauthFlow({ timeoutMs, pkceCredentials, baseUrl, }: RunOauthFlowOptions): Promise<OauthTokens>;
36
+ export declare function runLoginOauthFlow(options: RunOauthFlowOptions): Promise<OauthTokens>;
37
+ export declare function runSignupOauthFlow(options: RunSignupOauthFlowOptions): Promise<OauthTokens>;
@@ -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
+ }
@@ -472,14 +472,20 @@ function createCommandConfig(cliCommandName, functionInfo, sdk) {
472
472
  const startTime = Date.now();
473
473
  let success = true;
474
474
  let errorMessage = null;
475
- let resolvedParams = {};
475
+ // `resolvedParams` is populated below either by `resolveParameters` or
476
+ // by copying `rawParams`. The renderer captures this object by
477
+ // reference and reads from it inside formatter callbacks, so we
478
+ // mutate it in place (via `Object.assign`) once params are resolved
479
+ // rather than reassigning the binding, which would leave the
480
+ // renderer holding the empty initial object.
481
+ const resolvedParams = {};
476
482
  // The last argument is always the command object with parsed options
477
483
  const commandObj = args[args.length - 1];
478
484
  const options = commandObj.opts();
479
485
  // interactiveMode is true by default; --json disables it
480
486
  const interactiveMode = !options.json;
481
487
  const renderer = interactiveMode
482
- ? createInteractiveRenderer()
488
+ ? createInteractiveRenderer({ sdk, params: resolvedParams })
483
489
  : createJsonRenderer();
484
490
  try {
485
491
  emitDeprecationWarning({
@@ -509,15 +515,16 @@ function createCommandConfig(cliCommandName, functionInfo, sdk) {
509
515
  }
510
516
  if (schema && !usesInputParameters) {
511
517
  const resolver = new SchemaParameterResolver();
512
- resolvedParams = (await resolver.resolveParameters(schema, rawParams, sdk, functionInfo.name, {
518
+ const resolved = (await resolver.resolveParameters(schema, rawParams, sdk, functionInfo.name, {
513
519
  interactiveMode,
514
520
  debug: !!options.debug ||
515
521
  process.env.DEBUG === "true" ||
516
522
  process.argv.includes("--debug"),
517
523
  }));
524
+ Object.assign(resolvedParams, resolved);
518
525
  }
519
526
  else {
520
- resolvedParams = rawParams;
527
+ Object.assign(resolvedParams, rawParams);
521
528
  }
522
529
  const confirm = functionInfo.confirm;
523
530
  let confirmMessageAfter;
@@ -543,7 +550,7 @@ function createCommandConfig(cliCommandName, functionInfo, sdk) {
543
550
  sdkResult,
544
551
  maxItems,
545
552
  });
546
- renderer.renderCollectedList(allItems, {
553
+ await renderer.renderCollectedList(allItems, {
547
554
  maxItems,
548
555
  userSpecifiedMaxItems: hasUserSpecifiedMaxItems,
549
556
  functionInfo,
@@ -575,7 +582,7 @@ function createCommandConfig(cliCommandName, functionInfo, sdk) {
575
582
  await renderer.renderResponse(normalizedResult.value);
576
583
  }
577
584
  else if (normalizedResult.kind === "list") {
578
- renderer.renderCollectedList(normalizedResult.data, {
585
+ await renderer.renderCollectedList(normalizedResult.data, {
579
586
  maxItems: resolvedParams.maxItems,
580
587
  userSpecifiedMaxItems: hasUserSpecifiedMaxItems,
581
588
  functionInfo,
@@ -635,7 +642,7 @@ function createCommandConfig(cliCommandName, functionInfo, sdk) {
635
642
  description,
636
643
  parameters,
637
644
  handler,
638
- hidden: functionInfo.categories?.includes("deprecated") ?? false,
645
+ hidden: !!functionInfo.deprecation,
639
646
  aliases: functionInfo.aliases,
640
647
  supportsJsonOutput: functionInfo.supportsJsonOutput,
641
648
  };