@zapier/zapier-sdk-cli 0.60.0 → 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.
package/dist/cli.cjs CHANGED
@@ -102,12 +102,18 @@ var ZapierCliExitError = class extends ZapierCliError {
102
102
  this.exitCode = exitCode;
103
103
  }
104
104
  };
105
- var ZapierCliValidationError = class extends ZapierCliError {
106
- constructor(message) {
105
+ var ZapierCliValidationError = class _ZapierCliValidationError extends ZapierCliError {
106
+ constructor(message, options = {}) {
107
107
  super(message);
108
- this.name = "ZapierCliValidationError";
109
- this.code = "ZAPIER_CLI_VALIDATION_ERROR";
110
108
  this.exitCode = 1;
109
+ this.name = options.name ?? "ZapierCliValidationError";
110
+ this.code = options.code ?? "ZAPIER_CLI_VALIDATION_ERROR";
111
+ }
112
+ withMessage(message) {
113
+ return new _ZapierCliValidationError(message, {
114
+ name: this.name,
115
+ code: this.code
116
+ });
111
117
  }
112
118
  };
113
119
  var ZapierCliMissingParametersError = class extends ZapierCliError {
@@ -1579,7 +1585,7 @@ var SHARED_COMMAND_CLI_OPTIONS = [
1579
1585
 
1580
1586
  // package.json
1581
1587
  var package_default = {
1582
- version: "0.60.0"};
1588
+ version: "0.61.0"};
1583
1589
 
1584
1590
  // src/telemetry/builders.ts
1585
1591
  function createCliBaseEvent(context = {}) {
@@ -3765,41 +3771,165 @@ var spinPromise = async (promise, text) => {
3765
3771
  }
3766
3772
  };
3767
3773
 
3774
+ // src/utils/auth/oauth-errors.ts
3775
+ var OauthFlowTimeoutError = class _OauthFlowTimeoutError extends ZapierCliValidationError {
3776
+ constructor({
3777
+ timeoutMs,
3778
+ message = "OAuth flow timed out"
3779
+ }) {
3780
+ super(message, {
3781
+ name: "OauthFlowTimeoutError",
3782
+ code: "ZAPIER_OAUTH_FLOW_TIMEOUT"
3783
+ });
3784
+ this.timeoutMs = timeoutMs;
3785
+ }
3786
+ withMessage(message) {
3787
+ return new _OauthFlowTimeoutError({ timeoutMs: this.timeoutMs, message });
3788
+ }
3789
+ };
3790
+ var OauthAuthorizationDeniedError = class _OauthAuthorizationDeniedError extends ZapierCliValidationError {
3791
+ constructor({
3792
+ reason,
3793
+ message = "OAuth authorization denied"
3794
+ }) {
3795
+ super(message, {
3796
+ name: "OauthAuthorizationDeniedError",
3797
+ code: "ZAPIER_OAUTH_AUTHORIZATION_DENIED"
3798
+ });
3799
+ this.reason = reason;
3800
+ }
3801
+ withMessage(message) {
3802
+ return new _OauthAuthorizationDeniedError({
3803
+ reason: this.reason,
3804
+ message
3805
+ });
3806
+ }
3807
+ };
3808
+ var OauthFlowError = class _OauthFlowError extends ZapierCliValidationError {
3809
+ constructor({ message }) {
3810
+ super(message, {
3811
+ name: "OauthFlowError",
3812
+ code: "ZAPIER_OAUTH_FLOW"
3813
+ });
3814
+ }
3815
+ withMessage(message) {
3816
+ return new _OauthFlowError({ message });
3817
+ }
3818
+ };
3819
+ var OauthCallbackError = class _OauthCallbackError extends ZapierCliValidationError {
3820
+ constructor({ kind, message }) {
3821
+ super(message, {
3822
+ name: "OauthCallbackError",
3823
+ code: "ZAPIER_OAUTH_CALLBACK"
3824
+ });
3825
+ this.kind = kind;
3826
+ }
3827
+ withMessage(message) {
3828
+ return new _OauthCallbackError({ kind: this.kind, message });
3829
+ }
3830
+ };
3831
+ var OauthTokenExchangeError = class _OauthTokenExchangeError extends ZapierCliValidationError {
3832
+ constructor({ message }) {
3833
+ super(message, {
3834
+ name: "OauthTokenExchangeError",
3835
+ code: "ZAPIER_OAUTH_TOKEN_EXCHANGE"
3836
+ });
3837
+ }
3838
+ withMessage(message) {
3839
+ return new _OauthTokenExchangeError({ message });
3840
+ }
3841
+ };
3842
+ var SENSITIVE_OAUTH_FIELDS = [
3843
+ "access_token",
3844
+ "refresh_token",
3845
+ "id_token",
3846
+ "client_secret",
3847
+ "code_verifier",
3848
+ "code_challenge"
3849
+ ];
3850
+ function getErrorMessage(error) {
3851
+ return error instanceof Error ? error.message : String(error);
3852
+ }
3853
+ function toCamelCase(field) {
3854
+ return field.replace(
3855
+ /_([a-z])/g,
3856
+ (_match, letter) => letter.toUpperCase()
3857
+ );
3858
+ }
3859
+ function escapeRegExp(value) {
3860
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3861
+ }
3862
+ var sensitiveOauthFieldPattern = Array.from(
3863
+ new Set(
3864
+ SENSITIVE_OAUTH_FIELDS.flatMap((field) => [field, toCamelCase(field)])
3865
+ )
3866
+ ).map(escapeRegExp).join("|");
3867
+ var sensitiveQueryParamPattern = new RegExp(
3868
+ `([?&])(${sensitiveOauthFieldPattern})(=)[^&#\\s"'<>]*`,
3869
+ "gi"
3870
+ );
3871
+ function redactSensitiveOauthErrorMessage(message) {
3872
+ return message.replace(
3873
+ sensitiveQueryParamPattern,
3874
+ (_match, prefix, key, separator) => `${prefix}${key}${separator}[REDACTED]`
3875
+ ).replace(
3876
+ new RegExp(`"(${sensitiveOauthFieldPattern})"(\\s*:\\s*)"[^"]*"`, "g"),
3877
+ (_match, key, separator) => `"${key}"${separator}"[REDACTED]"`
3878
+ );
3879
+ }
3880
+ function toRedactedOauthError(error) {
3881
+ const message = redactSensitiveOauthErrorMessage(getErrorMessage(error));
3882
+ if (error instanceof ZapierCliValidationError) {
3883
+ return error.withMessage(message);
3884
+ }
3885
+ return new OauthFlowError({ message });
3886
+ }
3887
+ function toOauthTokenExchangeError(error) {
3888
+ return new OauthTokenExchangeError({
3889
+ message: redactSensitiveOauthErrorMessage(getErrorMessage(error))
3890
+ });
3891
+ }
3892
+
3768
3893
  // src/utils/auth/oauth-callback.ts
3769
3894
  function getCallbackCode({
3770
3895
  callbackUrl,
3771
- transaction,
3772
- recoveryMessage
3896
+ transaction
3773
3897
  }) {
3774
3898
  let parsed;
3775
3899
  try {
3776
3900
  parsed = new URL(callbackUrl.trim());
3777
3901
  } catch {
3778
- throw new ZapierCliValidationError(
3779
- "Paste the final OAuth callback URL from your browser."
3780
- );
3902
+ throw new OauthCallbackError({
3903
+ kind: "invalid_url",
3904
+ message: "Paste the final OAuth callback URL from your browser."
3905
+ });
3781
3906
  }
3782
3907
  const expected = new URL(transaction.redirectUri);
3783
3908
  if (parsed.protocol !== "http:" || parsed.hostname !== expected.hostname || parsed.pathname !== expected.pathname || parsed.port !== expected.port) {
3784
- throw new ZapierCliValidationError(
3785
- `Expected the final OAuth callback URL to start with ${transaction.redirectUri}.`
3786
- );
3909
+ throw new OauthCallbackError({
3910
+ kind: "redirect_mismatch",
3911
+ message: `Expected the final OAuth callback URL to start with ${transaction.redirectUri}.`
3912
+ });
3787
3913
  }
3788
3914
  if (parsed.searchParams.get("state") !== transaction.state) {
3789
- throw new ZapierCliValidationError(
3790
- `OAuth state mismatch.${recoveryMessage ? ` ${recoveryMessage}` : ""}`
3791
- );
3915
+ throw new OauthCallbackError({
3916
+ kind: "state_mismatch",
3917
+ message: "OAuth state mismatch."
3918
+ });
3792
3919
  }
3793
3920
  if (parsed.searchParams.has("error")) {
3794
- throw new ZapierCliValidationError(
3795
- `Authorization denied: ${parsed.searchParams.get("error_description") ?? parsed.searchParams.get("error")}.${recoveryMessage ? ` ${recoveryMessage}` : ""}`
3796
- );
3921
+ throw new OauthAuthorizationDeniedError({
3922
+ reason: String(
3923
+ parsed.searchParams.get("error_description") ?? parsed.searchParams.get("error")
3924
+ )
3925
+ });
3797
3926
  }
3798
3927
  const code = parsed.searchParams.get("code");
3799
3928
  if (!code) {
3800
- throw new ZapierCliValidationError(
3801
- "No authorization code found in the pasted callback URL."
3802
- );
3929
+ throw new OauthCallbackError({
3930
+ kind: "missing_code",
3931
+ message: "No authorization code found in the pasted callback URL."
3932
+ });
3803
3933
  }
3804
3934
  return code;
3805
3935
  }
@@ -3914,7 +4044,9 @@ async function exchangeOauthCode({
3914
4044
  "Content-Type": "application/x-www-form-urlencoded"
3915
4045
  }
3916
4046
  }
3917
- );
4047
+ ).catch((error) => {
4048
+ throw toOauthTokenExchangeError(error);
4049
+ });
3918
4050
  return {
3919
4051
  accessToken: data.access_token,
3920
4052
  refreshToken: data.refresh_token,
@@ -3923,19 +4055,15 @@ async function exchangeOauthCode({
3923
4055
  }
3924
4056
 
3925
4057
  // src/utils/auth/oauth-flow.ts
3926
- var OauthFlowTimeoutError = class extends Error {
3927
- constructor(timeoutMs) {
3928
- super("OAuth flow timed out");
3929
- this.timeoutMs = timeoutMs;
3930
- this.name = "OauthFlowTimeoutError";
3931
- }
4058
+ var LOGIN_OAUTH_COPY = {
4059
+ flowName: "Login",
4060
+ action: "log in",
4061
+ urlLabel: "login URL"
3932
4062
  };
3933
- var OauthAuthorizationDeniedError = class extends Error {
3934
- constructor(reason) {
3935
- super("OAuth authorization denied");
3936
- this.reason = reason;
3937
- this.name = "OauthAuthorizationDeniedError";
3938
- }
4063
+ var SIGNUP_OAUTH_COPY = {
4064
+ flowName: "Signup",
4065
+ action: "sign up",
4066
+ urlLabel: "signup URL"
3939
4067
  };
3940
4068
  function findAvailablePort() {
3941
4069
  return new Promise((resolve4, reject) => {
@@ -3950,70 +4078,86 @@ function findAvailablePort() {
3950
4078
  tryPort(LOGIN_PORTS[portIndex++]);
3951
4079
  } else if (err.code === "EADDRINUSE") {
3952
4080
  reject(
3953
- new Error(
3954
- `All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`
3955
- )
4081
+ new OauthFlowError({
4082
+ message: `All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`
4083
+ })
3956
4084
  );
3957
4085
  } else {
3958
4086
  reject(err);
3959
4087
  }
3960
4088
  });
3961
4089
  };
3962
- if (LOGIN_PORTS.length > 0) tryPort(LOGIN_PORTS[portIndex++]);
3963
- else reject(new Error("No OAuth callback ports configured"));
4090
+ if (LOGIN_PORTS.length > 0) {
4091
+ tryPort(LOGIN_PORTS[portIndex++]);
4092
+ return;
4093
+ }
4094
+ reject(
4095
+ new OauthFlowError({ message: "No OAuth callback ports configured" })
4096
+ );
3964
4097
  });
3965
4098
  }
3966
4099
  async function runLoginOauthFlow(options) {
3967
- return runOauthFlowEntryPoint({
3968
- ...options,
4100
+ return runOauthFlowForEntryPoint({
4101
+ options,
3969
4102
  entryPoint: "login",
3970
- authAction: "log in",
3971
- flowName: "Login"
4103
+ copy: LOGIN_OAUTH_COPY
3972
4104
  });
3973
4105
  }
3974
4106
  async function runSignupOauthFlow(options) {
4107
+ return runOauthFlowForEntryPoint({
4108
+ options,
4109
+ entryPoint: "signup",
4110
+ copy: SIGNUP_OAUTH_COPY
4111
+ });
4112
+ }
4113
+ function runOauthFlowForEntryPoint({
4114
+ options,
4115
+ entryPoint,
4116
+ copy
4117
+ }) {
3975
4118
  if (options.headless) {
3976
4119
  return runOauthFlowEntryPoint({
3977
4120
  ...options,
3978
- entryPoint: "signup",
3979
- authAction: "sign up",
3980
- flowName: "Signup",
3981
- headless: true
4121
+ entryPoint,
4122
+ headless: true,
4123
+ copy
3982
4124
  });
3983
4125
  }
3984
4126
  return runOauthFlowEntryPoint({
3985
4127
  ...options,
3986
- entryPoint: "signup",
3987
- authAction: "sign up",
3988
- flowName: "Signup"
4128
+ entryPoint,
4129
+ copy,
4130
+ headless: false
3989
4131
  });
3990
4132
  }
3991
- async function runOauthFlowEntryPoint({
3992
- flowName,
3993
- ...options
3994
- }) {
4133
+ async function runOauthFlowEntryPoint(options) {
3995
4134
  try {
3996
- return options.headless ? await runHeadlessSignupOauthFlow(options) : await runOauthFlow(options);
4135
+ return options.headless ? await runHeadlessOauthFlow(options) : await runOauthFlow(options);
3997
4136
  } catch (error) {
3998
4137
  if (error instanceof OauthFlowTimeoutError) {
3999
- throw new Error(
4138
+ throw error.withMessage(
4000
4139
  withRecoveryMessage(
4001
- `${flowName} timed out after ${Math.round(error.timeoutMs / 1e3)} seconds.`,
4140
+ `${options.copy.flowName} timed out after ${Math.round(error.timeoutMs / 1e3)} seconds.`,
4002
4141
  options.recoveryMessage
4003
4142
  )
4004
4143
  );
4005
4144
  }
4006
4145
  if (error instanceof OauthAuthorizationDeniedError) {
4007
- throw new Error(
4146
+ throw error.withMessage(
4008
4147
  withRecoveryMessage(
4009
4148
  `Authorization denied: ${error.reason}.`,
4010
4149
  options.recoveryMessage
4011
4150
  )
4012
4151
  );
4013
4152
  }
4153
+ if (error instanceof OauthCallbackError && options.recoveryMessage) {
4154
+ throw error.withMessage(
4155
+ withRecoveryMessage(error.message, options.recoveryMessage)
4156
+ );
4157
+ }
4014
4158
  if (error instanceof ZapierCliUserCancellationError && !options.silent) {
4015
4159
  log_default.info(`
4016
- \u274C ${flowName} cancelled by user`);
4160
+ \u274C ${options.copy.flowName} cancelled by user`);
4017
4161
  }
4018
4162
  throw error;
4019
4163
  }
@@ -4021,12 +4165,22 @@ async function runOauthFlowEntryPoint({
4021
4165
  function withRecoveryMessage(message, recoveryMessage) {
4022
4166
  return recoveryMessage ? `${message} ${recoveryMessage}` : message;
4023
4167
  }
4168
+ function createMissingHeadlessCallbackUrlError() {
4169
+ return new OauthCallbackError({
4170
+ kind: "missing_callback_url",
4171
+ message: "Paste the final OAuth callback URL from your browser."
4172
+ });
4173
+ }
4174
+ function writeHeadlessOauthStatus(message) {
4175
+ process.stderr.write(`${message}
4176
+ `);
4177
+ }
4024
4178
  async function runOauthFlow({
4025
4179
  timeoutMs = LOGIN_TIMEOUT_MS,
4026
4180
  pkceCredentials,
4027
4181
  baseUrl: baseUrl2,
4028
4182
  entryPoint,
4029
- authAction,
4183
+ copy,
4030
4184
  silent = false,
4031
4185
  onProgress
4032
4186
  }) {
@@ -4041,7 +4195,7 @@ async function runOauthFlow({
4041
4195
  const code = await collectLocalCallbackCode({
4042
4196
  transaction,
4043
4197
  timeoutMs,
4044
- authAction,
4198
+ action: copy.action,
4045
4199
  silent,
4046
4200
  onProgress
4047
4201
  });
@@ -4055,92 +4209,104 @@ async function runOauthFlow({
4055
4209
  }
4056
4210
  async function readHeadlessCallbackUrl({
4057
4211
  timeoutMs,
4058
- interactive,
4059
- recoveryMessage
4212
+ interactive
4060
4213
  }) {
4061
- const timeoutMessage = withRecoveryMessage(
4062
- `Signup timed out after ${Math.round(timeoutMs / 1e3)} seconds.`,
4063
- recoveryMessage
4064
- );
4065
- const missingCallbackUrlMessage = withRecoveryMessage(
4066
- "Paste the final OAuth callback URL from your browser.",
4067
- recoveryMessage
4068
- );
4069
4214
  const rl = promises$1.createInterface({ input: process.stdin, output: process.stderr });
4070
4215
  const abortController = new AbortController();
4071
4216
  const timeoutTimer = setTimeout(() => abortController.abort(), timeoutMs);
4072
- const readUrl = interactive ? rl.question("Paste the final OAuth callback URL: ", {
4073
- signal: abortController.signal
4074
- }) : new Promise((resolve4, reject) => {
4217
+ const readUrl = new Promise((resolve4, reject) => {
4075
4218
  let settled = false;
4076
- const settleResolve = (value) => {
4219
+ const handleLine = (value) => settleResolve(value);
4220
+ const handleAbort = () => settleReject(new OauthFlowTimeoutError({ timeoutMs }));
4221
+ const handleClose = () => settleReject(createMissingHeadlessCallbackUrlError());
4222
+ const handleError = (error) => settleReject(error);
4223
+ const handleCancellation = () => settleReject(new ZapierCliUserCancellationError());
4224
+ const cleanupListeners = () => {
4225
+ abortController.signal.removeEventListener("abort", handleAbort);
4226
+ rl.off("line", handleLine);
4227
+ rl.off("close", handleClose);
4228
+ rl.off("error", handleError);
4229
+ rl.off("SIGINT", handleCancellation);
4230
+ process.off("SIGINT", handleCancellation);
4231
+ process.off("SIGTERM", handleCancellation);
4232
+ };
4233
+ function settleResolve(value) {
4234
+ if (settled) return;
4077
4235
  settled = true;
4236
+ cleanupListeners();
4078
4237
  resolve4(value);
4079
- };
4080
- const settleReject = (error) => {
4238
+ }
4239
+ function settleReject(error) {
4081
4240
  if (settled) return;
4082
4241
  settled = true;
4242
+ cleanupListeners();
4083
4243
  reject(error);
4084
- };
4085
- abortController.signal.addEventListener(
4086
- "abort",
4087
- () => settleReject(new Error(timeoutMessage)),
4088
- { once: true }
4089
- );
4090
- rl.once("line", settleResolve);
4091
- rl.once(
4092
- "close",
4093
- () => settleReject(new ZapierCliValidationError(missingCallbackUrlMessage))
4094
- );
4095
- rl.once("error", settleReject);
4244
+ }
4245
+ abortController.signal.addEventListener("abort", handleAbort, {
4246
+ once: true
4247
+ });
4248
+ rl.once("close", handleClose);
4249
+ rl.once("error", handleError);
4250
+ rl.once("SIGINT", handleCancellation);
4251
+ process.once("SIGINT", handleCancellation);
4252
+ process.once("SIGTERM", handleCancellation);
4253
+ if (interactive) {
4254
+ void rl.question("Paste the final OAuth callback URL: ", {
4255
+ signal: abortController.signal
4256
+ }).then(settleResolve).catch((error) => {
4257
+ if (error instanceof Error && error.name === "AbortError") {
4258
+ settleReject(new OauthFlowTimeoutError({ timeoutMs }));
4259
+ return;
4260
+ }
4261
+ settleReject(error);
4262
+ });
4263
+ return;
4264
+ }
4265
+ rl.once("line", handleLine);
4096
4266
  });
4097
4267
  try {
4098
- return await readUrl.catch((error) => {
4099
- if (error instanceof Error && error.name === "AbortError") {
4100
- throw new Error(timeoutMessage);
4101
- }
4102
- throw error;
4103
- });
4268
+ return await readUrl;
4104
4269
  } finally {
4105
4270
  clearTimeout(timeoutTimer);
4106
4271
  rl.close();
4107
4272
  }
4108
4273
  }
4109
- async function runHeadlessSignupOauthFlow({
4274
+ async function runHeadlessOauthFlow({
4110
4275
  timeoutMs = LOGIN_TIMEOUT_MS,
4111
4276
  pkceCredentials,
4112
4277
  baseUrl: baseUrl2,
4278
+ entryPoint,
4113
4279
  interactive = true,
4114
4280
  onProgress,
4115
- recoveryMessage
4281
+ copy
4116
4282
  }) {
4117
4283
  const port = LOGIN_PORTS[0];
4118
4284
  const transaction = await prepareOauthTransaction({
4119
4285
  pkceCredentials,
4120
4286
  baseUrl: baseUrl2,
4121
4287
  redirectUri: `http://${OAUTH_LOOPBACK_HOST}:${port}/oauth`,
4122
- entryPoint: "signup"
4288
+ entryPoint
4123
4289
  });
4124
- console.log(
4125
- "Use this mode when signing up from a machine that has no browser."
4290
+ writeHeadlessOauthStatus(
4291
+ `Use this mode to ${copy.action} from a machine that has no browser.`
4292
+ );
4293
+ writeHeadlessOauthStatus(
4294
+ `Open this ${copy.urlLabel} in a browser on another machine:`
4126
4295
  );
4127
- console.log("Open this signup URL in a browser on another machine:");
4128
4296
  console.log(transaction.browserAuthUrl);
4129
- console.log(
4297
+ writeHeadlessOauthStatus(
4130
4298
  `When the browser lands on ${transaction.redirectUri} and cannot connect, paste the full final URL back here.`
4131
4299
  );
4132
4300
  const callbackUrl = await readHeadlessCallbackUrl({
4133
4301
  timeoutMs,
4134
- interactive,
4135
- recoveryMessage
4302
+ interactive
4136
4303
  });
4137
4304
  const code = getCallbackCode({
4138
4305
  callbackUrl,
4139
- transaction,
4140
- recoveryMessage
4306
+ transaction
4141
4307
  });
4142
4308
  onProgress?.({ type: "callback_accepted" });
4143
- console.log("Exchanging authorization code for tokens...");
4309
+ writeHeadlessOauthStatus("Exchanging authorization code for tokens...");
4144
4310
  onProgress?.({ type: "token_exchange_started" });
4145
4311
  const tokens = await exchangeOauthCode({ ...transaction, code });
4146
4312
  onProgress?.({ type: "token_exchange_completed" });
@@ -4149,7 +4315,7 @@ async function runHeadlessSignupOauthFlow({
4149
4315
  async function collectLocalCallbackCode({
4150
4316
  transaction,
4151
4317
  timeoutMs,
4152
- authAction,
4318
+ action,
4153
4319
  silent,
4154
4320
  onProgress
4155
4321
  }) {
@@ -4157,21 +4323,26 @@ async function collectLocalCallbackCode({
4157
4323
  const app = express__default.default();
4158
4324
  app.get("/oauth", (req, res) => {
4159
4325
  res.setHeader("Connection", "close");
4160
- if (req.query.state !== transaction.state) {
4161
- res.status(400).end("Invalid state. You can close this tab.");
4162
- } else if (req.query.error) {
4163
- reject(
4164
- new OauthAuthorizationDeniedError(
4165
- String(req.query.error_description ?? req.query.error)
4166
- )
4167
- );
4168
- res.end("Authorization was denied. You can close this tab.");
4169
- } else if (!req.query.code) {
4170
- reject(new Error("No authorization code received"));
4171
- res.end("No authorization code received. You can close this tab.");
4172
- } else {
4173
- resolve4(String(req.query.code));
4326
+ try {
4327
+ const code = getCallbackCode({
4328
+ callbackUrl: new URL(
4329
+ req.originalUrl,
4330
+ transaction.redirectUri
4331
+ ).toString(),
4332
+ transaction
4333
+ });
4334
+ resolve4(code);
4174
4335
  res.end("You can now close this tab and return to the CLI.");
4336
+ } catch (error) {
4337
+ if (error instanceof OauthCallbackError && error.kind === "state_mismatch") {
4338
+ res.status(400).end("Invalid state. You can close this tab.");
4339
+ } else if (error instanceof OauthAuthorizationDeniedError) {
4340
+ reject(error);
4341
+ res.end("Authorization was denied. You can close this tab.");
4342
+ } else {
4343
+ reject(error);
4344
+ res.end("No authorization code received. You can close this tab.");
4345
+ }
4175
4346
  }
4176
4347
  });
4177
4348
  const server = app.listen(
@@ -4192,19 +4363,19 @@ async function collectLocalCallbackCode({
4192
4363
  let timeoutTimer;
4193
4364
  try {
4194
4365
  await waitForServerListening(server);
4195
- await openBrowser({ transaction, authAction, silent, onProgress });
4366
+ await openBrowser({ transaction, action, silent, onProgress });
4196
4367
  const waitForCode = Promise.race([
4197
4368
  promise,
4198
4369
  new Promise((_resolve, rejectTimeout) => {
4199
4370
  timeoutTimer = setTimeout(() => {
4200
- rejectTimeout(new OauthFlowTimeoutError(timeoutMs));
4371
+ rejectTimeout(new OauthFlowTimeoutError({ timeoutMs }));
4201
4372
  }, timeoutMs);
4202
4373
  })
4203
4374
  ]);
4204
4375
  onProgress?.({ type: "callback_waiting" });
4205
4376
  return silent ? await waitForCode : await spinPromise(
4206
4377
  waitForCode,
4207
- `Waiting for you to ${authAction} and authorize`
4378
+ `Waiting for you to ${action} and authorize`
4208
4379
  );
4209
4380
  } finally {
4210
4381
  if (timeoutTimer) clearTimeout(timeoutTimer);
@@ -4234,12 +4405,12 @@ async function waitForServerListening(server) {
4234
4405
  }
4235
4406
  async function openBrowser({
4236
4407
  transaction,
4237
- authAction,
4408
+ action,
4238
4409
  silent,
4239
4410
  onProgress
4240
4411
  }) {
4241
4412
  if (!silent) {
4242
- log_default.info(`Opening your browser to ${authAction}.`);
4413
+ log_default.info(`Opening your browser to ${action}.`);
4243
4414
  log_default.info("If it doesn't open, visit:", transaction.browserAuthUrl);
4244
4415
  }
4245
4416
  onProgress?.({ type: "browser_opening", url: transaction.browserAuthUrl });
@@ -4249,9 +4420,7 @@ async function openBrowser({
4249
4420
  } catch (err) {
4250
4421
  const reason = err instanceof Error ? err.message : String(err);
4251
4422
  if (!silent) {
4252
- log_default.info(
4253
- `Browser did not open automatically to ${authAction}: ${reason}`
4254
- );
4423
+ log_default.info(`Browser did not open automatically to ${action}: ${reason}`);
4255
4424
  log_default.info("Visit this URL manually:", transaction.browserAuthUrl);
4256
4425
  }
4257
4426
  onProgress?.({
@@ -4280,61 +4449,10 @@ async function closeServer({
4280
4449
  });
4281
4450
  }
4282
4451
 
4283
- // src/utils/auth/oauth-errors.ts
4284
- var SENSITIVE_OAUTH_FIELDS = [
4285
- "access_token",
4286
- "refresh_token",
4287
- "id_token",
4288
- "client_secret",
4289
- "code_verifier",
4290
- "code_challenge"
4291
- ];
4292
- function getErrorMessage(error) {
4293
- return error instanceof Error ? error.message : String(error);
4294
- }
4295
- function toCamelCase(field) {
4296
- return field.replace(
4297
- /_([a-z])/g,
4298
- (_match, letter) => letter.toUpperCase()
4299
- );
4300
- }
4301
- function escapeRegExp(value) {
4302
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4303
- }
4304
- var sensitiveOauthFieldPattern = Array.from(
4305
- new Set(
4306
- SENSITIVE_OAUTH_FIELDS.flatMap((field) => [field, toCamelCase(field)])
4307
- )
4308
- ).map(escapeRegExp).join("|");
4309
- var sensitiveQueryParamPattern = new RegExp(
4310
- `([?&])(${sensitiveOauthFieldPattern})(=)[^&#\\s"'<>]*`,
4311
- "gi"
4312
- );
4313
- function redactSensitiveOauthErrorMessage(message) {
4314
- return message.replace(
4315
- sensitiveQueryParamPattern,
4316
- (_match, prefix, key, separator) => `${prefix}${key}${separator}[REDACTED]`
4317
- ).replace(
4318
- new RegExp(`"(${sensitiveOauthFieldPattern})"(\\s*:\\s*)"[^"]*"`, "g"),
4319
- (_match, key, separator) => `"${key}"${separator}"[REDACTED]"`
4320
- );
4321
- }
4322
- function toRedactedOauthError(error) {
4323
- const message = redactSensitiveOauthErrorMessage(getErrorMessage(error));
4324
- if (error instanceof ZapierCliValidationError) {
4325
- return new ZapierCliValidationError(message);
4326
- }
4327
- if (error instanceof Error) {
4328
- const redactedError = new Error(message);
4329
- redactedError.name = error.name;
4330
- return redactedError;
4331
- }
4332
- return new ZapierCliValidationError(message);
4333
- }
4334
-
4335
4452
  // src/utils/auth/account-auth.ts
4336
4453
  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?";
4337
4454
  var SIGNUP_RECOVERY_MESSAGE = "Restart `zapier-sdk signup` to generate a fresh signup URL and try again.";
4455
+ var HEADLESS_LOGIN_RECOVERY_MESSAGE = "Restart `zapier-sdk login --headless` to generate a fresh login URL and try again.";
4338
4456
  var HEADLESS_SIGNUP_RECOVERY_MESSAGE = "Restart `zapier-sdk signup --headless` to generate a fresh signup URL and try again.";
4339
4457
  function getEntryPointLabel(entryPoint) {
4340
4458
  return entryPoint === "signup" ? "Signup" : "Login";
@@ -4454,7 +4572,8 @@ Logging out will delete these credentials and may interrupt other Zapier SDK or
4454
4572
  Log out and ${getActiveCredentialsAction(entryPoint)}?`
4455
4573
  }) : promptlessCredentialResetError(activeCredentials);
4456
4574
  if (!confirmed) {
4457
- console.log(`${flowLabel} cancelled.`);
4575
+ process.stderr.write(`${flowLabel} cancelled.
4576
+ `);
4458
4577
  return false;
4459
4578
  }
4460
4579
  try {
@@ -4473,7 +4592,8 @@ Log out and ${getActiveCredentialsAction(entryPoint)}?`
4473
4592
  message: `${flowLabel} cleanup failed. Reset local session state and continue?`
4474
4593
  });
4475
4594
  if (!reset) {
4476
- console.log(`${flowLabel} cancelled.`);
4595
+ process.stderr.write(`${flowLabel} cancelled.
4596
+ `);
4477
4597
  return false;
4478
4598
  }
4479
4599
  await deleteStoredClientCredentials({
@@ -4487,7 +4607,8 @@ Log out and ${getActiveCredentialsAction(entryPoint)}?`
4487
4607
  message: LEGACY_JWT_UPGRADE_PROMPT
4488
4608
  }) : promptlessLegacyJwtUpgradeError();
4489
4609
  if (!confirmed) {
4490
- console.log(`${flowLabel} cancelled.`);
4610
+ process.stderr.write(`${flowLabel} cancelled.
4611
+ `);
4491
4612
  return false;
4492
4613
  }
4493
4614
  }
@@ -4582,7 +4703,14 @@ async function runOauthForEntryPoint({
4582
4703
  );
4583
4704
  }
4584
4705
  return runOauthWithRedaction(
4585
- () => runLoginOauthFlow({ timeoutMs, pkceCredentials, baseUrl: baseUrl2 })
4706
+ () => runLoginOauthFlow({
4707
+ timeoutMs,
4708
+ pkceCredentials,
4709
+ baseUrl: baseUrl2,
4710
+ headless,
4711
+ interactive,
4712
+ recoveryMessage: headless ? HEADLESS_LOGIN_RECOVERY_MESSAGE : void 0
4713
+ })
4586
4714
  );
4587
4715
  }
4588
4716
  async function runAccountAuth({
@@ -4594,6 +4722,7 @@ async function runAccountAuth({
4594
4722
  const interactive = !resolveNonInteractive(options);
4595
4723
  const resolvedCredentials = await sdk.context.resolveCredentials();
4596
4724
  const pkceCredentials = toPkceCredentials(resolvedCredentials);
4725
+ const headless = options.headless === true;
4597
4726
  const credentialsBaseUrl2 = await resolveCredentialsBaseUrl({
4598
4727
  ...sdk.context,
4599
4728
  resolvedCredentials
@@ -4623,7 +4752,7 @@ async function runAccountAuth({
4623
4752
  timeoutMs: timeoutSeconds * 1e3,
4624
4753
  pkceCredentials,
4625
4754
  baseUrl: credentialsBaseUrl2,
4626
- headless: options.headless === true,
4755
+ headless,
4627
4756
  interactive
4628
4757
  });
4629
4758
  const scopedApi = zapierSdk.getOrCreateApiClient({
@@ -4631,9 +4760,10 @@ async function runAccountAuth({
4631
4760
  baseUrl: credentialsBaseUrl2
4632
4761
  });
4633
4762
  const profile = await getProfile(scopedApi);
4634
- console.log(getProfileMessage(entryPoint, profile.email));
4635
- console.log(
4636
- "\nGenerating credentials so this machine can make authenticated requests on your behalf."
4763
+ process.stderr.write(`${getProfileMessage(entryPoint, profile.email)}
4764
+ `);
4765
+ process.stderr.write(
4766
+ "\nGenerating credentials so this machine can make authenticated requests on your behalf.\n"
4637
4767
  );
4638
4768
  const credentialName = providedName ?? await resolveCredentialName({
4639
4769
  email: profile.email,
@@ -4649,11 +4779,12 @@ async function runAccountAuth({
4649
4779
  useApprovals,
4650
4780
  cleanupLogPrefix: entryPoint
4651
4781
  });
4652
- console.log(
4653
- `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.`
4782
+ process.stderr.write(
4783
+ `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.
4784
+ `
4654
4785
  );
4655
4786
  if (useApprovals) {
4656
- console.log("\u{1F510} Approvals are enabled for these credentials.");
4787
+ process.stderr.write("\u{1F510} Approvals are enabled for these credentials.\n");
4657
4788
  }
4658
4789
  emitAccountAuthSuccess({ sdk, profile, clientId });
4659
4790
  }
@@ -4672,7 +4803,10 @@ var LoginSchema = zod.z.object({
4672
4803
  skipPrompts: zod.z.boolean().optional().meta({
4673
4804
  deprecated: true,
4674
4805
  deprecationMessage: "Use --non-interactive instead."
4675
- })
4806
+ }),
4807
+ headless: zod.z.boolean().optional().describe(
4808
+ "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."
4809
+ )
4676
4810
  }).describe("Log in to Zapier to access your account");
4677
4811
 
4678
4812
  // src/plugins/login/index.ts
@@ -7304,7 +7438,7 @@ function buildBoxLines(message) {
7304
7438
  // package.json with { type: 'json' }
7305
7439
  var package_default2 = {
7306
7440
  name: "@zapier/zapier-sdk-cli",
7307
- version: "0.60.0"};
7441
+ version: "0.61.0"};
7308
7442
 
7309
7443
  // src/sdk.ts
7310
7444
  zapierSdk.injectCliLogin(login_exports);