@zapier/zapier-sdk-cli 0.59.3 → 0.61.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.
@@ -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, getOrCreateApiClient, isPermanentHttpError, invalidateCachedToken, batch, toSnakeCase, ZapierAbortDrainSignal, isCredentialsObject, buildApplicationLifecycleEvent, AuthMechanism, ZapierAuthenticationError, ZapierError } from '@zapier/zapier-sdk';
10
+ import { definePlugin, createPluginMethod, OutputPropertySchema, ZapierBundleError, DEFAULT_CONFIG_PATH, ZapierValidationError, ZapierUnknownError, ZapierReleaseTriggerMessageSignal, getOrCreateApiClient, isPermanentHttpError, invalidateCachedToken, batch, toSnakeCase, ZapierAbortDrainSignal, isCredentialsObject, buildApplicationLifecycleEvent, AuthMechanism, ZapierAuthenticationError, ZapierError, DEPRECATION_NOTICE_EVENT } from '@zapier/zapier-sdk';
11
11
  import { z } from 'zod';
12
12
  import { injectCliLogin, createZapierSdkStack, createSdk, defineLegacyMerge, zapierSdkPlugin, addPlugin } from '@zapier/zapier-sdk/experimental';
13
13
  import { hostname } from 'os';
@@ -308,12 +308,18 @@ var ZapierCliExitError = class extends ZapierCliError {
308
308
  this.exitCode = exitCode;
309
309
  }
310
310
  };
311
- var ZapierCliValidationError = class extends ZapierCliError {
312
- constructor(message) {
311
+ var ZapierCliValidationError = class _ZapierCliValidationError extends ZapierCliError {
312
+ constructor(message, options = {}) {
313
313
  super(message);
314
- this.name = "ZapierCliValidationError";
315
- this.code = "ZAPIER_CLI_VALIDATION_ERROR";
316
314
  this.exitCode = 1;
315
+ this.name = options.name ?? "ZapierCliValidationError";
316
+ this.code = options.code ?? "ZAPIER_CLI_VALIDATION_ERROR";
317
+ }
318
+ withMessage(message) {
319
+ return new _ZapierCliValidationError(message, {
320
+ name: this.name,
321
+ code: this.code
322
+ });
317
323
  }
318
324
  };
319
325
 
@@ -1074,41 +1080,165 @@ var spinPromise = async (promise, text) => {
1074
1080
  }
1075
1081
  };
1076
1082
 
1083
+ // src/utils/auth/oauth-errors.ts
1084
+ var OauthFlowTimeoutError = class _OauthFlowTimeoutError extends ZapierCliValidationError {
1085
+ constructor({
1086
+ timeoutMs,
1087
+ message = "OAuth flow timed out"
1088
+ }) {
1089
+ super(message, {
1090
+ name: "OauthFlowTimeoutError",
1091
+ code: "ZAPIER_OAUTH_FLOW_TIMEOUT"
1092
+ });
1093
+ this.timeoutMs = timeoutMs;
1094
+ }
1095
+ withMessage(message) {
1096
+ return new _OauthFlowTimeoutError({ timeoutMs: this.timeoutMs, message });
1097
+ }
1098
+ };
1099
+ var OauthAuthorizationDeniedError = class _OauthAuthorizationDeniedError extends ZapierCliValidationError {
1100
+ constructor({
1101
+ reason,
1102
+ message = "OAuth authorization denied"
1103
+ }) {
1104
+ super(message, {
1105
+ name: "OauthAuthorizationDeniedError",
1106
+ code: "ZAPIER_OAUTH_AUTHORIZATION_DENIED"
1107
+ });
1108
+ this.reason = reason;
1109
+ }
1110
+ withMessage(message) {
1111
+ return new _OauthAuthorizationDeniedError({
1112
+ reason: this.reason,
1113
+ message
1114
+ });
1115
+ }
1116
+ };
1117
+ var OauthFlowError = class _OauthFlowError extends ZapierCliValidationError {
1118
+ constructor({ message }) {
1119
+ super(message, {
1120
+ name: "OauthFlowError",
1121
+ code: "ZAPIER_OAUTH_FLOW"
1122
+ });
1123
+ }
1124
+ withMessage(message) {
1125
+ return new _OauthFlowError({ message });
1126
+ }
1127
+ };
1128
+ var OauthCallbackError = class _OauthCallbackError extends ZapierCliValidationError {
1129
+ constructor({ kind, message }) {
1130
+ super(message, {
1131
+ name: "OauthCallbackError",
1132
+ code: "ZAPIER_OAUTH_CALLBACK"
1133
+ });
1134
+ this.kind = kind;
1135
+ }
1136
+ withMessage(message) {
1137
+ return new _OauthCallbackError({ kind: this.kind, message });
1138
+ }
1139
+ };
1140
+ var OauthTokenExchangeError = class _OauthTokenExchangeError extends ZapierCliValidationError {
1141
+ constructor({ message }) {
1142
+ super(message, {
1143
+ name: "OauthTokenExchangeError",
1144
+ code: "ZAPIER_OAUTH_TOKEN_EXCHANGE"
1145
+ });
1146
+ }
1147
+ withMessage(message) {
1148
+ return new _OauthTokenExchangeError({ message });
1149
+ }
1150
+ };
1151
+ var SENSITIVE_OAUTH_FIELDS = [
1152
+ "access_token",
1153
+ "refresh_token",
1154
+ "id_token",
1155
+ "client_secret",
1156
+ "code_verifier",
1157
+ "code_challenge"
1158
+ ];
1159
+ function getErrorMessage(error) {
1160
+ return error instanceof Error ? error.message : String(error);
1161
+ }
1162
+ function toCamelCase(field) {
1163
+ return field.replace(
1164
+ /_([a-z])/g,
1165
+ (_match, letter) => letter.toUpperCase()
1166
+ );
1167
+ }
1168
+ function escapeRegExp(value) {
1169
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1170
+ }
1171
+ var sensitiveOauthFieldPattern = Array.from(
1172
+ new Set(
1173
+ SENSITIVE_OAUTH_FIELDS.flatMap((field) => [field, toCamelCase(field)])
1174
+ )
1175
+ ).map(escapeRegExp).join("|");
1176
+ var sensitiveQueryParamPattern = new RegExp(
1177
+ `([?&])(${sensitiveOauthFieldPattern})(=)[^&#\\s"'<>]*`,
1178
+ "gi"
1179
+ );
1180
+ function redactSensitiveOauthErrorMessage(message) {
1181
+ return message.replace(
1182
+ sensitiveQueryParamPattern,
1183
+ (_match, prefix, key, separator) => `${prefix}${key}${separator}[REDACTED]`
1184
+ ).replace(
1185
+ new RegExp(`"(${sensitiveOauthFieldPattern})"(\\s*:\\s*)"[^"]*"`, "g"),
1186
+ (_match, key, separator) => `"${key}"${separator}"[REDACTED]"`
1187
+ );
1188
+ }
1189
+ function toRedactedOauthError(error) {
1190
+ const message = redactSensitiveOauthErrorMessage(getErrorMessage(error));
1191
+ if (error instanceof ZapierCliValidationError) {
1192
+ return error.withMessage(message);
1193
+ }
1194
+ return new OauthFlowError({ message });
1195
+ }
1196
+ function toOauthTokenExchangeError(error) {
1197
+ return new OauthTokenExchangeError({
1198
+ message: redactSensitiveOauthErrorMessage(getErrorMessage(error))
1199
+ });
1200
+ }
1201
+
1077
1202
  // src/utils/auth/oauth-callback.ts
1078
1203
  function getCallbackCode({
1079
1204
  callbackUrl,
1080
- transaction,
1081
- recoveryMessage
1205
+ transaction
1082
1206
  }) {
1083
1207
  let parsed;
1084
1208
  try {
1085
1209
  parsed = new URL(callbackUrl.trim());
1086
1210
  } catch {
1087
- throw new ZapierCliValidationError(
1088
- "Paste the final OAuth callback URL from your browser."
1089
- );
1211
+ throw new OauthCallbackError({
1212
+ kind: "invalid_url",
1213
+ message: "Paste the final OAuth callback URL from your browser."
1214
+ });
1090
1215
  }
1091
1216
  const expected = new URL(transaction.redirectUri);
1092
1217
  if (parsed.protocol !== "http:" || parsed.hostname !== expected.hostname || parsed.pathname !== expected.pathname || parsed.port !== expected.port) {
1093
- throw new ZapierCliValidationError(
1094
- `Expected the final OAuth callback URL to start with ${transaction.redirectUri}.`
1095
- );
1218
+ throw new OauthCallbackError({
1219
+ kind: "redirect_mismatch",
1220
+ message: `Expected the final OAuth callback URL to start with ${transaction.redirectUri}.`
1221
+ });
1096
1222
  }
1097
1223
  if (parsed.searchParams.get("state") !== transaction.state) {
1098
- throw new ZapierCliValidationError(
1099
- `OAuth state mismatch.${recoveryMessage ? ` ${recoveryMessage}` : ""}`
1100
- );
1224
+ throw new OauthCallbackError({
1225
+ kind: "state_mismatch",
1226
+ message: "OAuth state mismatch."
1227
+ });
1101
1228
  }
1102
1229
  if (parsed.searchParams.has("error")) {
1103
- throw new ZapierCliValidationError(
1104
- `Authorization denied: ${parsed.searchParams.get("error_description") ?? parsed.searchParams.get("error")}.${recoveryMessage ? ` ${recoveryMessage}` : ""}`
1105
- );
1230
+ throw new OauthAuthorizationDeniedError({
1231
+ reason: String(
1232
+ parsed.searchParams.get("error_description") ?? parsed.searchParams.get("error")
1233
+ )
1234
+ });
1106
1235
  }
1107
1236
  const code = parsed.searchParams.get("code");
1108
1237
  if (!code) {
1109
- throw new ZapierCliValidationError(
1110
- "No authorization code found in the pasted callback URL."
1111
- );
1238
+ throw new OauthCallbackError({
1239
+ kind: "missing_code",
1240
+ message: "No authorization code found in the pasted callback URL."
1241
+ });
1112
1242
  }
1113
1243
  return code;
1114
1244
  }
@@ -1223,7 +1353,9 @@ async function exchangeOauthCode({
1223
1353
  "Content-Type": "application/x-www-form-urlencoded"
1224
1354
  }
1225
1355
  }
1226
- );
1356
+ ).catch((error) => {
1357
+ throw toOauthTokenExchangeError(error);
1358
+ });
1227
1359
  return {
1228
1360
  accessToken: data.access_token,
1229
1361
  refreshToken: data.refresh_token,
@@ -1232,19 +1364,15 @@ async function exchangeOauthCode({
1232
1364
  }
1233
1365
 
1234
1366
  // src/utils/auth/oauth-flow.ts
1235
- var OauthFlowTimeoutError = class extends Error {
1236
- constructor(timeoutMs) {
1237
- super("OAuth flow timed out");
1238
- this.timeoutMs = timeoutMs;
1239
- this.name = "OauthFlowTimeoutError";
1240
- }
1367
+ var LOGIN_OAUTH_COPY = {
1368
+ flowName: "Login",
1369
+ action: "log in",
1370
+ urlLabel: "login URL"
1241
1371
  };
1242
- var OauthAuthorizationDeniedError = class extends Error {
1243
- constructor(reason) {
1244
- super("OAuth authorization denied");
1245
- this.reason = reason;
1246
- this.name = "OauthAuthorizationDeniedError";
1247
- }
1372
+ var SIGNUP_OAUTH_COPY = {
1373
+ flowName: "Signup",
1374
+ action: "sign up",
1375
+ urlLabel: "signup URL"
1248
1376
  };
1249
1377
  function findAvailablePort() {
1250
1378
  return new Promise((resolve4, reject) => {
@@ -1259,70 +1387,86 @@ function findAvailablePort() {
1259
1387
  tryPort(LOGIN_PORTS[portIndex++]);
1260
1388
  } else if (err.code === "EADDRINUSE") {
1261
1389
  reject(
1262
- new Error(
1263
- `All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`
1264
- )
1390
+ new OauthFlowError({
1391
+ message: `All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`
1392
+ })
1265
1393
  );
1266
1394
  } else {
1267
1395
  reject(err);
1268
1396
  }
1269
1397
  });
1270
1398
  };
1271
- if (LOGIN_PORTS.length > 0) tryPort(LOGIN_PORTS[portIndex++]);
1272
- else reject(new Error("No OAuth callback ports configured"));
1399
+ if (LOGIN_PORTS.length > 0) {
1400
+ tryPort(LOGIN_PORTS[portIndex++]);
1401
+ return;
1402
+ }
1403
+ reject(
1404
+ new OauthFlowError({ message: "No OAuth callback ports configured" })
1405
+ );
1273
1406
  });
1274
1407
  }
1275
1408
  async function runLoginOauthFlow(options) {
1276
- return runOauthFlowEntryPoint({
1277
- ...options,
1409
+ return runOauthFlowForEntryPoint({
1410
+ options,
1278
1411
  entryPoint: "login",
1279
- authAction: "log in",
1280
- flowName: "Login"
1412
+ copy: LOGIN_OAUTH_COPY
1281
1413
  });
1282
1414
  }
1283
1415
  async function runSignupOauthFlow(options) {
1416
+ return runOauthFlowForEntryPoint({
1417
+ options,
1418
+ entryPoint: "signup",
1419
+ copy: SIGNUP_OAUTH_COPY
1420
+ });
1421
+ }
1422
+ function runOauthFlowForEntryPoint({
1423
+ options,
1424
+ entryPoint,
1425
+ copy
1426
+ }) {
1284
1427
  if (options.headless) {
1285
1428
  return runOauthFlowEntryPoint({
1286
1429
  ...options,
1287
- entryPoint: "signup",
1288
- authAction: "sign up",
1289
- flowName: "Signup",
1290
- headless: true
1430
+ entryPoint,
1431
+ headless: true,
1432
+ copy
1291
1433
  });
1292
1434
  }
1293
1435
  return runOauthFlowEntryPoint({
1294
1436
  ...options,
1295
- entryPoint: "signup",
1296
- authAction: "sign up",
1297
- flowName: "Signup"
1437
+ entryPoint,
1438
+ copy,
1439
+ headless: false
1298
1440
  });
1299
1441
  }
1300
- async function runOauthFlowEntryPoint({
1301
- flowName,
1302
- ...options
1303
- }) {
1442
+ async function runOauthFlowEntryPoint(options) {
1304
1443
  try {
1305
- return options.headless ? await runHeadlessSignupOauthFlow(options) : await runOauthFlow(options);
1444
+ return options.headless ? await runHeadlessOauthFlow(options) : await runOauthFlow(options);
1306
1445
  } catch (error) {
1307
1446
  if (error instanceof OauthFlowTimeoutError) {
1308
- throw new Error(
1447
+ throw error.withMessage(
1309
1448
  withRecoveryMessage(
1310
- `${flowName} timed out after ${Math.round(error.timeoutMs / 1e3)} seconds.`,
1449
+ `${options.copy.flowName} timed out after ${Math.round(error.timeoutMs / 1e3)} seconds.`,
1311
1450
  options.recoveryMessage
1312
1451
  )
1313
1452
  );
1314
1453
  }
1315
1454
  if (error instanceof OauthAuthorizationDeniedError) {
1316
- throw new Error(
1455
+ throw error.withMessage(
1317
1456
  withRecoveryMessage(
1318
1457
  `Authorization denied: ${error.reason}.`,
1319
1458
  options.recoveryMessage
1320
1459
  )
1321
1460
  );
1322
1461
  }
1462
+ if (error instanceof OauthCallbackError && options.recoveryMessage) {
1463
+ throw error.withMessage(
1464
+ withRecoveryMessage(error.message, options.recoveryMessage)
1465
+ );
1466
+ }
1323
1467
  if (error instanceof ZapierCliUserCancellationError && !options.silent) {
1324
1468
  log_default.info(`
1325
- \u274C ${flowName} cancelled by user`);
1469
+ \u274C ${options.copy.flowName} cancelled by user`);
1326
1470
  }
1327
1471
  throw error;
1328
1472
  }
@@ -1330,12 +1474,22 @@ async function runOauthFlowEntryPoint({
1330
1474
  function withRecoveryMessage(message, recoveryMessage) {
1331
1475
  return recoveryMessage ? `${message} ${recoveryMessage}` : message;
1332
1476
  }
1477
+ function createMissingHeadlessCallbackUrlError() {
1478
+ return new OauthCallbackError({
1479
+ kind: "missing_callback_url",
1480
+ message: "Paste the final OAuth callback URL from your browser."
1481
+ });
1482
+ }
1483
+ function writeHeadlessOauthStatus(message) {
1484
+ process.stderr.write(`${message}
1485
+ `);
1486
+ }
1333
1487
  async function runOauthFlow({
1334
1488
  timeoutMs = LOGIN_TIMEOUT_MS,
1335
1489
  pkceCredentials,
1336
1490
  baseUrl,
1337
1491
  entryPoint,
1338
- authAction,
1492
+ copy,
1339
1493
  silent = false,
1340
1494
  onProgress
1341
1495
  }) {
@@ -1350,7 +1504,7 @@ async function runOauthFlow({
1350
1504
  const code = await collectLocalCallbackCode({
1351
1505
  transaction,
1352
1506
  timeoutMs,
1353
- authAction,
1507
+ action: copy.action,
1354
1508
  silent,
1355
1509
  onProgress
1356
1510
  });
@@ -1364,92 +1518,104 @@ async function runOauthFlow({
1364
1518
  }
1365
1519
  async function readHeadlessCallbackUrl({
1366
1520
  timeoutMs,
1367
- interactive,
1368
- recoveryMessage
1521
+ interactive
1369
1522
  }) {
1370
- const timeoutMessage = withRecoveryMessage(
1371
- `Signup timed out after ${Math.round(timeoutMs / 1e3)} seconds.`,
1372
- recoveryMessage
1373
- );
1374
- const missingCallbackUrlMessage = withRecoveryMessage(
1375
- "Paste the final OAuth callback URL from your browser.",
1376
- recoveryMessage
1377
- );
1378
1523
  const rl = createInterface({ input: process.stdin, output: process.stderr });
1379
1524
  const abortController = new AbortController();
1380
1525
  const timeoutTimer = setTimeout(() => abortController.abort(), timeoutMs);
1381
- const readUrl = interactive ? rl.question("Paste the final OAuth callback URL: ", {
1382
- signal: abortController.signal
1383
- }) : new Promise((resolve4, reject) => {
1526
+ const readUrl = new Promise((resolve4, reject) => {
1384
1527
  let settled = false;
1385
- const settleResolve = (value) => {
1528
+ const handleLine = (value) => settleResolve(value);
1529
+ const handleAbort = () => settleReject(new OauthFlowTimeoutError({ timeoutMs }));
1530
+ const handleClose = () => settleReject(createMissingHeadlessCallbackUrlError());
1531
+ const handleError = (error) => settleReject(error);
1532
+ const handleCancellation = () => settleReject(new ZapierCliUserCancellationError());
1533
+ const cleanupListeners = () => {
1534
+ abortController.signal.removeEventListener("abort", handleAbort);
1535
+ rl.off("line", handleLine);
1536
+ rl.off("close", handleClose);
1537
+ rl.off("error", handleError);
1538
+ rl.off("SIGINT", handleCancellation);
1539
+ process.off("SIGINT", handleCancellation);
1540
+ process.off("SIGTERM", handleCancellation);
1541
+ };
1542
+ function settleResolve(value) {
1543
+ if (settled) return;
1386
1544
  settled = true;
1545
+ cleanupListeners();
1387
1546
  resolve4(value);
1388
- };
1389
- const settleReject = (error) => {
1547
+ }
1548
+ function settleReject(error) {
1390
1549
  if (settled) return;
1391
1550
  settled = true;
1551
+ cleanupListeners();
1392
1552
  reject(error);
1393
- };
1394
- abortController.signal.addEventListener(
1395
- "abort",
1396
- () => settleReject(new Error(timeoutMessage)),
1397
- { once: true }
1398
- );
1399
- rl.once("line", settleResolve);
1400
- rl.once(
1401
- "close",
1402
- () => settleReject(new ZapierCliValidationError(missingCallbackUrlMessage))
1403
- );
1404
- rl.once("error", settleReject);
1553
+ }
1554
+ abortController.signal.addEventListener("abort", handleAbort, {
1555
+ once: true
1556
+ });
1557
+ rl.once("close", handleClose);
1558
+ rl.once("error", handleError);
1559
+ rl.once("SIGINT", handleCancellation);
1560
+ process.once("SIGINT", handleCancellation);
1561
+ process.once("SIGTERM", handleCancellation);
1562
+ if (interactive) {
1563
+ void rl.question("Paste the final OAuth callback URL: ", {
1564
+ signal: abortController.signal
1565
+ }).then(settleResolve).catch((error) => {
1566
+ if (error instanceof Error && error.name === "AbortError") {
1567
+ settleReject(new OauthFlowTimeoutError({ timeoutMs }));
1568
+ return;
1569
+ }
1570
+ settleReject(error);
1571
+ });
1572
+ return;
1573
+ }
1574
+ rl.once("line", handleLine);
1405
1575
  });
1406
1576
  try {
1407
- return await readUrl.catch((error) => {
1408
- if (error instanceof Error && error.name === "AbortError") {
1409
- throw new Error(timeoutMessage);
1410
- }
1411
- throw error;
1412
- });
1577
+ return await readUrl;
1413
1578
  } finally {
1414
1579
  clearTimeout(timeoutTimer);
1415
1580
  rl.close();
1416
1581
  }
1417
1582
  }
1418
- async function runHeadlessSignupOauthFlow({
1583
+ async function runHeadlessOauthFlow({
1419
1584
  timeoutMs = LOGIN_TIMEOUT_MS,
1420
1585
  pkceCredentials,
1421
1586
  baseUrl,
1587
+ entryPoint,
1422
1588
  interactive = true,
1423
1589
  onProgress,
1424
- recoveryMessage
1590
+ copy
1425
1591
  }) {
1426
1592
  const port = LOGIN_PORTS[0];
1427
1593
  const transaction = await prepareOauthTransaction({
1428
1594
  pkceCredentials,
1429
1595
  baseUrl,
1430
1596
  redirectUri: `http://${OAUTH_LOOPBACK_HOST}:${port}/oauth`,
1431
- entryPoint: "signup"
1597
+ entryPoint
1432
1598
  });
1433
- console.log(
1434
- "Use this mode when signing up from a machine that has no browser."
1599
+ writeHeadlessOauthStatus(
1600
+ `Use this mode to ${copy.action} from a machine that has no browser.`
1601
+ );
1602
+ writeHeadlessOauthStatus(
1603
+ `Open this ${copy.urlLabel} in a browser on another machine:`
1435
1604
  );
1436
- console.log("Open this signup URL in a browser on another machine:");
1437
1605
  console.log(transaction.browserAuthUrl);
1438
- console.log(
1606
+ writeHeadlessOauthStatus(
1439
1607
  `When the browser lands on ${transaction.redirectUri} and cannot connect, paste the full final URL back here.`
1440
1608
  );
1441
1609
  const callbackUrl = await readHeadlessCallbackUrl({
1442
1610
  timeoutMs,
1443
- interactive,
1444
- recoveryMessage
1611
+ interactive
1445
1612
  });
1446
1613
  const code = getCallbackCode({
1447
1614
  callbackUrl,
1448
- transaction,
1449
- recoveryMessage
1615
+ transaction
1450
1616
  });
1451
1617
  onProgress?.({ type: "callback_accepted" });
1452
- console.log("Exchanging authorization code for tokens...");
1618
+ writeHeadlessOauthStatus("Exchanging authorization code for tokens...");
1453
1619
  onProgress?.({ type: "token_exchange_started" });
1454
1620
  const tokens = await exchangeOauthCode({ ...transaction, code });
1455
1621
  onProgress?.({ type: "token_exchange_completed" });
@@ -1458,7 +1624,7 @@ async function runHeadlessSignupOauthFlow({
1458
1624
  async function collectLocalCallbackCode({
1459
1625
  transaction,
1460
1626
  timeoutMs,
1461
- authAction,
1627
+ action,
1462
1628
  silent,
1463
1629
  onProgress
1464
1630
  }) {
@@ -1466,21 +1632,26 @@ async function collectLocalCallbackCode({
1466
1632
  const app = express();
1467
1633
  app.get("/oauth", (req, res) => {
1468
1634
  res.setHeader("Connection", "close");
1469
- if (req.query.state !== transaction.state) {
1470
- res.status(400).end("Invalid state. You can close this tab.");
1471
- } else if (req.query.error) {
1472
- reject(
1473
- new OauthAuthorizationDeniedError(
1474
- String(req.query.error_description ?? req.query.error)
1475
- )
1476
- );
1477
- res.end("Authorization was denied. You can close this tab.");
1478
- } else if (!req.query.code) {
1479
- reject(new Error("No authorization code received"));
1480
- res.end("No authorization code received. You can close this tab.");
1481
- } else {
1482
- resolve4(String(req.query.code));
1635
+ try {
1636
+ const code = getCallbackCode({
1637
+ callbackUrl: new URL(
1638
+ req.originalUrl,
1639
+ transaction.redirectUri
1640
+ ).toString(),
1641
+ transaction
1642
+ });
1643
+ resolve4(code);
1483
1644
  res.end("You can now close this tab and return to the CLI.");
1645
+ } catch (error) {
1646
+ if (error instanceof OauthCallbackError && error.kind === "state_mismatch") {
1647
+ res.status(400).end("Invalid state. You can close this tab.");
1648
+ } else if (error instanceof OauthAuthorizationDeniedError) {
1649
+ reject(error);
1650
+ res.end("Authorization was denied. You can close this tab.");
1651
+ } else {
1652
+ reject(error);
1653
+ res.end("No authorization code received. You can close this tab.");
1654
+ }
1484
1655
  }
1485
1656
  });
1486
1657
  const server = app.listen(
@@ -1501,19 +1672,19 @@ async function collectLocalCallbackCode({
1501
1672
  let timeoutTimer;
1502
1673
  try {
1503
1674
  await waitForServerListening(server);
1504
- await openBrowser({ transaction, authAction, silent, onProgress });
1675
+ await openBrowser({ transaction, action, silent, onProgress });
1505
1676
  const waitForCode = Promise.race([
1506
1677
  promise,
1507
1678
  new Promise((_resolve, rejectTimeout) => {
1508
1679
  timeoutTimer = setTimeout(() => {
1509
- rejectTimeout(new OauthFlowTimeoutError(timeoutMs));
1680
+ rejectTimeout(new OauthFlowTimeoutError({ timeoutMs }));
1510
1681
  }, timeoutMs);
1511
1682
  })
1512
1683
  ]);
1513
1684
  onProgress?.({ type: "callback_waiting" });
1514
1685
  return silent ? await waitForCode : await spinPromise(
1515
1686
  waitForCode,
1516
- `Waiting for you to ${authAction} and authorize`
1687
+ `Waiting for you to ${action} and authorize`
1517
1688
  );
1518
1689
  } finally {
1519
1690
  if (timeoutTimer) clearTimeout(timeoutTimer);
@@ -1543,12 +1714,12 @@ async function waitForServerListening(server) {
1543
1714
  }
1544
1715
  async function openBrowser({
1545
1716
  transaction,
1546
- authAction,
1717
+ action,
1547
1718
  silent,
1548
1719
  onProgress
1549
1720
  }) {
1550
1721
  if (!silent) {
1551
- log_default.info(`Opening your browser to ${authAction}.`);
1722
+ log_default.info(`Opening your browser to ${action}.`);
1552
1723
  log_default.info("If it doesn't open, visit:", transaction.browserAuthUrl);
1553
1724
  }
1554
1725
  onProgress?.({ type: "browser_opening", url: transaction.browserAuthUrl });
@@ -1558,9 +1729,7 @@ async function openBrowser({
1558
1729
  } catch (err) {
1559
1730
  const reason = err instanceof Error ? err.message : String(err);
1560
1731
  if (!silent) {
1561
- log_default.info(
1562
- `Browser did not open automatically to ${authAction}: ${reason}`
1563
- );
1732
+ log_default.info(`Browser did not open automatically to ${action}: ${reason}`);
1564
1733
  log_default.info("Visit this URL manually:", transaction.browserAuthUrl);
1565
1734
  }
1566
1735
  onProgress?.({
@@ -1589,61 +1758,10 @@ async function closeServer({
1589
1758
  });
1590
1759
  }
1591
1760
 
1592
- // src/utils/auth/oauth-errors.ts
1593
- var SENSITIVE_OAUTH_FIELDS = [
1594
- "access_token",
1595
- "refresh_token",
1596
- "id_token",
1597
- "client_secret",
1598
- "code_verifier",
1599
- "code_challenge"
1600
- ];
1601
- function getErrorMessage(error) {
1602
- return error instanceof Error ? error.message : String(error);
1603
- }
1604
- function toCamelCase(field) {
1605
- return field.replace(
1606
- /_([a-z])/g,
1607
- (_match, letter) => letter.toUpperCase()
1608
- );
1609
- }
1610
- function escapeRegExp(value) {
1611
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1612
- }
1613
- var sensitiveOauthFieldPattern = Array.from(
1614
- new Set(
1615
- SENSITIVE_OAUTH_FIELDS.flatMap((field) => [field, toCamelCase(field)])
1616
- )
1617
- ).map(escapeRegExp).join("|");
1618
- var sensitiveQueryParamPattern = new RegExp(
1619
- `([?&])(${sensitiveOauthFieldPattern})(=)[^&#\\s"'<>]*`,
1620
- "gi"
1621
- );
1622
- function redactSensitiveOauthErrorMessage(message) {
1623
- return message.replace(
1624
- sensitiveQueryParamPattern,
1625
- (_match, prefix, key, separator) => `${prefix}${key}${separator}[REDACTED]`
1626
- ).replace(
1627
- new RegExp(`"(${sensitiveOauthFieldPattern})"(\\s*:\\s*)"[^"]*"`, "g"),
1628
- (_match, key, separator) => `"${key}"${separator}"[REDACTED]"`
1629
- );
1630
- }
1631
- function toRedactedOauthError(error) {
1632
- const message = redactSensitiveOauthErrorMessage(getErrorMessage(error));
1633
- if (error instanceof ZapierCliValidationError) {
1634
- return new ZapierCliValidationError(message);
1635
- }
1636
- if (error instanceof Error) {
1637
- const redactedError = new Error(message);
1638
- redactedError.name = error.name;
1639
- return redactedError;
1640
- }
1641
- return new ZapierCliValidationError(message);
1642
- }
1643
-
1644
1761
  // src/utils/auth/account-auth.ts
1645
1762
  var LEGACY_JWT_UPGRADE_PROMPT = "We're upgrading your login to client credentials for a simpler, more reliable experience and to support future security controls. Older Zapier SDK/CLI versions on this machine may stop working after the upgrade. Continue?";
1646
1763
  var SIGNUP_RECOVERY_MESSAGE = "Restart `zapier-sdk signup` to generate a fresh signup URL and try again.";
1764
+ var HEADLESS_LOGIN_RECOVERY_MESSAGE = "Restart `zapier-sdk login --headless` to generate a fresh login URL and try again.";
1647
1765
  var HEADLESS_SIGNUP_RECOVERY_MESSAGE = "Restart `zapier-sdk signup --headless` to generate a fresh signup URL and try again.";
1648
1766
  function getEntryPointLabel(entryPoint) {
1649
1767
  return entryPoint === "signup" ? "Signup" : "Login";
@@ -1763,7 +1881,8 @@ Logging out will delete these credentials and may interrupt other Zapier SDK or
1763
1881
  Log out and ${getActiveCredentialsAction(entryPoint)}?`
1764
1882
  }) : promptlessCredentialResetError(activeCredentials);
1765
1883
  if (!confirmed) {
1766
- console.log(`${flowLabel} cancelled.`);
1884
+ process.stderr.write(`${flowLabel} cancelled.
1885
+ `);
1767
1886
  return false;
1768
1887
  }
1769
1888
  try {
@@ -1782,7 +1901,8 @@ Log out and ${getActiveCredentialsAction(entryPoint)}?`
1782
1901
  message: `${flowLabel} cleanup failed. Reset local session state and continue?`
1783
1902
  });
1784
1903
  if (!reset) {
1785
- console.log(`${flowLabel} cancelled.`);
1904
+ process.stderr.write(`${flowLabel} cancelled.
1905
+ `);
1786
1906
  return false;
1787
1907
  }
1788
1908
  await deleteStoredClientCredentials({
@@ -1796,7 +1916,8 @@ Log out and ${getActiveCredentialsAction(entryPoint)}?`
1796
1916
  message: LEGACY_JWT_UPGRADE_PROMPT
1797
1917
  }) : promptlessLegacyJwtUpgradeError();
1798
1918
  if (!confirmed) {
1799
- console.log(`${flowLabel} cancelled.`);
1919
+ process.stderr.write(`${flowLabel} cancelled.
1920
+ `);
1800
1921
  return false;
1801
1922
  }
1802
1923
  }
@@ -1891,7 +2012,14 @@ async function runOauthForEntryPoint({
1891
2012
  );
1892
2013
  }
1893
2014
  return runOauthWithRedaction(
1894
- () => runLoginOauthFlow({ timeoutMs, pkceCredentials, baseUrl })
2015
+ () => runLoginOauthFlow({
2016
+ timeoutMs,
2017
+ pkceCredentials,
2018
+ baseUrl,
2019
+ headless,
2020
+ interactive,
2021
+ recoveryMessage: headless ? HEADLESS_LOGIN_RECOVERY_MESSAGE : void 0
2022
+ })
1895
2023
  );
1896
2024
  }
1897
2025
  async function runAccountAuth({
@@ -1903,6 +2031,7 @@ async function runAccountAuth({
1903
2031
  const interactive = !resolveNonInteractive(options);
1904
2032
  const resolvedCredentials = await sdk.context.resolveCredentials();
1905
2033
  const pkceCredentials = toPkceCredentials(resolvedCredentials);
2034
+ const headless = options.headless === true;
1906
2035
  const credentialsBaseUrl = await resolveCredentialsBaseUrl({
1907
2036
  ...sdk.context,
1908
2037
  resolvedCredentials
@@ -1932,7 +2061,7 @@ async function runAccountAuth({
1932
2061
  timeoutMs: timeoutSeconds * 1e3,
1933
2062
  pkceCredentials,
1934
2063
  baseUrl: credentialsBaseUrl,
1935
- headless: options.headless === true,
2064
+ headless,
1936
2065
  interactive
1937
2066
  });
1938
2067
  const scopedApi = getOrCreateApiClient({
@@ -1940,9 +2069,10 @@ async function runAccountAuth({
1940
2069
  baseUrl: credentialsBaseUrl
1941
2070
  });
1942
2071
  const profile = await getProfile(scopedApi);
1943
- console.log(getProfileMessage(entryPoint, profile.email));
1944
- console.log(
1945
- "\nGenerating credentials so this machine can make authenticated requests on your behalf."
2072
+ process.stderr.write(`${getProfileMessage(entryPoint, profile.email)}
2073
+ `);
2074
+ process.stderr.write(
2075
+ "\nGenerating credentials so this machine can make authenticated requests on your behalf.\n"
1946
2076
  );
1947
2077
  const credentialName = providedName ?? await resolveCredentialName({
1948
2078
  email: profile.email,
@@ -1958,11 +2088,12 @@ async function runAccountAuth({
1958
2088
  useApprovals,
1959
2089
  cleanupLogPrefix: entryPoint
1960
2090
  });
1961
- console.log(
1962
- `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.`
2091
+ process.stderr.write(
2092
+ `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.
2093
+ `
1963
2094
  );
1964
2095
  if (useApprovals) {
1965
- console.log("\u{1F510} Approvals are enabled for these credentials.");
2096
+ process.stderr.write("\u{1F510} Approvals are enabled for these credentials.\n");
1966
2097
  }
1967
2098
  emitAccountAuthSuccess({ sdk, profile, clientId });
1968
2099
  }
@@ -1981,7 +2112,10 @@ var LoginSchema = z.object({
1981
2112
  skipPrompts: z.boolean().optional().meta({
1982
2113
  deprecated: true,
1983
2114
  deprecationMessage: "Use --non-interactive instead."
1984
- })
2115
+ }),
2116
+ headless: z.boolean().optional().describe(
2117
+ "Use when logging in from a machine that has no browser. Prints a login link to open elsewhere, then accepts the pasted loopback callback URL."
2118
+ )
1985
2119
  }).describe("Log in to Zapier to access your account");
1986
2120
 
1987
2121
  // src/plugins/login/index.ts
@@ -4554,11 +4688,23 @@ var watchTriggerInboxCliPlugin = definePlugin(
4554
4688
  };
4555
4689
  }
4556
4690
  );
4691
+ var activeNotices = /* @__PURE__ */ new Map();
4692
+ function collectDeprecationNotice(event) {
4693
+ if (event.type !== DEPRECATION_NOTICE_EVENT) return;
4694
+ const { id, message } = event.payload ?? {};
4695
+ if (typeof id === "string" && typeof message === "string") {
4696
+ activeNotices.set(id, message);
4697
+ }
4698
+ }
4699
+ function collectDeprecationNoticeAndForward(event, onEvent) {
4700
+ collectDeprecationNotice(event);
4701
+ return onEvent?.(event);
4702
+ }
4557
4703
 
4558
4704
  // package.json with { type: 'json' }
4559
4705
  var package_default = {
4560
4706
  name: "@zapier/zapier-sdk-cli",
4561
- version: "0.59.3"};
4707
+ version: "0.61.0"};
4562
4708
 
4563
4709
  // src/experimental.ts
4564
4710
  injectCliLogin(login_exports);
@@ -4573,7 +4719,11 @@ function createZapierCliSdk(options = {}) {
4573
4719
  const stack = createZapierSdkStack({
4574
4720
  ...sdkOptions,
4575
4721
  eventEmission: { ...sdkOptions.eventEmission, callContext: "cli" },
4576
- callerPackage: { name: package_default.name, version: package_default.version }
4722
+ callerPackage: { name: package_default.name, version: package_default.version },
4723
+ // Collected for the boxed stderr warning rendered in the exit path. The
4724
+ // box is additive: the SDK's own inline warn still fires, so the notice
4725
+ // cannot be hidden by any rendering path.
4726
+ onEvent: (event) => collectDeprecationNoticeAndForward(event, sdkOptions.onEvent)
4577
4727
  }).use(extensionsContextPlugin).use(experimentalContextPlugin).use(generateAppTypesPlugin).use(buildManifestPlugin).use(bundleCodePlugin).use(getLoginConfigPathPlugin).use(addAppsPlugin).use(feedbackPlugin).use(curlPlugin).use(initPlugin).use(drainTriggerInboxCliPlugin, { override: true }).use(watchTriggerInboxCliPlugin, { override: true }).use(mcpPlugin).use(loginPlugin).use(signupPlugin).use(logoutPlugin).use(cliOverridesPlugin, { override: true });
4578
4728
  const sdk = createSdk(
4579
4729
  defineLegacyMerge({