@zapier/zapier-sdk-cli 0.44.1 → 0.45.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 (45) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +381 -29
  3. package/bin/zapier-sdk-experimental.mjs +14 -0
  4. package/dist/cli.cjs +585 -37
  5. package/dist/cli.mjs +584 -36
  6. package/dist/experimental.cjs +3519 -0
  7. package/dist/experimental.d.mts +39 -0
  8. package/dist/experimental.d.ts +39 -0
  9. package/dist/experimental.mjs +3483 -0
  10. package/dist/index.cjs +507 -26
  11. package/dist/index.d.mts +3 -514
  12. package/dist/index.d.ts +3 -514
  13. package/dist/index.mjs +505 -24
  14. package/dist/package.json +14 -2
  15. package/dist/sdk-B3nKAZdN.d.mts +516 -0
  16. package/dist/sdk-B3nKAZdN.d.ts +516 -0
  17. package/dist/src/cli.js +26 -2
  18. package/dist/src/experimental.d.ts +33 -0
  19. package/dist/src/experimental.js +83 -0
  20. package/dist/src/generators/ast-generator.d.ts +2 -2
  21. package/dist/src/generators/ast-generator.js +1 -1
  22. package/dist/src/plugins/add/index.d.ts +2 -2
  23. package/dist/src/plugins/bundleCode/index.js +3 -12
  24. package/dist/src/plugins/curl/index.js +2 -2
  25. package/dist/src/plugins/curl/utils.d.ts +11 -1
  26. package/dist/src/plugins/curl/utils.js +14 -5
  27. package/dist/src/plugins/drainTriggerInbox/index.d.ts +46 -0
  28. package/dist/src/plugins/drainTriggerInbox/index.js +178 -0
  29. package/dist/src/plugins/generateAppTypes/index.d.ts +2 -2
  30. package/dist/src/plugins/index.d.ts +2 -0
  31. package/dist/src/plugins/index.js +2 -0
  32. package/dist/src/plugins/mcp/index.d.ts +1 -0
  33. package/dist/src/plugins/mcp/index.js +5 -1
  34. package/dist/src/plugins/watchTriggerInbox/index.d.ts +45 -0
  35. package/dist/src/plugins/watchTriggerInbox/index.js +157 -0
  36. package/dist/src/sdk.js +5 -1
  37. package/dist/src/utils/cli-generator.js +18 -1
  38. package/dist/src/utils/cli-renderer.d.ts +12 -0
  39. package/dist/src/utils/cli-renderer.js +22 -1
  40. package/dist/src/utils/parameter-resolver.d.ts +1 -0
  41. package/dist/src/utils/parameter-resolver.js +55 -9
  42. package/dist/src/utils/triggerDrain.d.ts +144 -0
  43. package/dist/src/utils/triggerDrain.js +351 -0
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +16 -4
@@ -224,6 +224,17 @@ function analyzeZodField(name, schema, functionInfo) {
224
224
  break;
225
225
  }
226
226
  }
227
+ // Some schema kinds aren't expressible as CLI flags and would
228
+ // otherwise fall through to a bogus `--name <string>` flag:
229
+ // - z.function() — onMessage / onError-style callbacks
230
+ // - z.custom<AbortSignal>() and other custom predicates — passed
231
+ // programmatically by callers, or filled in by a CLI-side
232
+ // decorator plugin (closure-capture + override).
233
+ // Skip them so the auto-generated CLI surface stays clean.
234
+ const baseSchemaDef = baseSchema._zod?.def;
235
+ if (baseSchema instanceof z.ZodFunction || baseSchemaDef?.type === "custom") {
236
+ return null;
237
+ }
227
238
  // Determine parameter type
228
239
  let paramType = "string";
229
240
  let elementType;
@@ -448,9 +459,15 @@ function createCommandConfig(cliCommandName, functionInfo, sdk) {
448
459
  }
449
460
  }
450
461
  }
451
- const description = functionInfo.description ||
462
+ const baseDescription = functionInfo.description ||
452
463
  schema?.description ||
453
464
  `${cliCommandName} command`;
465
+ // Mark experimental commands clearly in --help so users know what they're
466
+ // touching. The command only reaches here at all when the experimental SDK
467
+ // factory is loaded, so we don't need to gate help visibility separately.
468
+ const description = functionInfo.experimental
469
+ ? `${baseDescription} (experimental)`
470
+ : baseDescription;
454
471
  const handler = async (...args) => {
455
472
  const startTime = Date.now();
456
473
  let success = true;
@@ -25,6 +25,18 @@ export interface CliRenderer {
25
25
  renderError(error: unknown): never;
26
26
  }
27
27
  export declare function buildJsonErrors(error: unknown): JsonErrorEntry[];
28
+ /**
29
+ * Replacer for JSON.stringify that coerces Error instances to a plain
30
+ * object with `{ name, message, stack }`. Without this, `JSON.stringify`
31
+ * renders an Error as `{}` (its useful properties are non-enumerable),
32
+ * which surfaces in any command that returns Error-like values — most
33
+ * notably `drainTriggerInbox` whose `errors[].reason` is typed `unknown`
34
+ * and is usually an Error.
35
+ *
36
+ * Walks recursively because the replacer is invoked for every nested
37
+ * key/value pair, not just the top-level value.
38
+ */
39
+ export declare function jsonReplacer(_key: string, value: unknown): unknown;
28
40
  export declare function createJsonRenderer(): CliRenderer;
29
41
  export declare function createInteractiveRenderer(): CliRenderer;
30
42
  export {};
@@ -50,8 +50,29 @@ async function unwrapHttpResponse(response) {
50
50
  // ============================================================================
51
51
  // JSON renderer
52
52
  // ============================================================================
53
+ /**
54
+ * Replacer for JSON.stringify that coerces Error instances to a plain
55
+ * object with `{ name, message, stack }`. Without this, `JSON.stringify`
56
+ * renders an Error as `{}` (its useful properties are non-enumerable),
57
+ * which surfaces in any command that returns Error-like values — most
58
+ * notably `drainTriggerInbox` whose `errors[].reason` is typed `unknown`
59
+ * and is usually an Error.
60
+ *
61
+ * Walks recursively because the replacer is invoked for every nested
62
+ * key/value pair, not just the top-level value.
63
+ */
64
+ export function jsonReplacer(_key, value) {
65
+ if (value instanceof Error) {
66
+ return {
67
+ name: value.name,
68
+ message: value.message,
69
+ ...(value.stack ? { stack: value.stack } : {}),
70
+ };
71
+ }
72
+ return value;
73
+ }
53
74
  function outputJson(envelope) {
54
- console.log(JSON.stringify(envelope, null, 2));
75
+ console.log(JSON.stringify(envelope, jsonReplacer, 2));
55
76
  }
56
77
  export function createJsonRenderer() {
57
78
  return {
@@ -64,4 +64,5 @@ export declare class SchemaParameterResolver {
64
64
  private hasResolver;
65
65
  private getResolver;
66
66
  private getLocalResolvers;
67
+ private getResolverConstants;
67
68
  }
@@ -24,7 +24,7 @@ function formatZodError(error) {
24
24
  */
25
25
  function getLocalResolutionOrder(paramName, resolvers, resolved = new Set()) {
26
26
  const resolver = resolvers[paramName];
27
- if (!resolver || resolver.type === "static") {
27
+ if (!resolver || resolver.type === "static" || resolver.type === "constant") {
28
28
  return [paramName];
29
29
  }
30
30
  const order = [];
@@ -119,7 +119,16 @@ export class SchemaParameterResolver {
119
119
  return parseResult.data;
120
120
  }
121
121
  // 2. Resolve required parameters
122
- const resolvedParams = { ...providedParams };
122
+ // Seed resolvedParams with constant resolvers — entries in `resolvers`
123
+ // typed `{ type: "constant", value }` for parameters not in the schema.
124
+ // Used when a dependent resolver needs a value the user never sets
125
+ // (e.g., trigger plugins pin actionType="read" so actionKeyResolver
126
+ // and inputsResolver work without actionType being a real input).
127
+ const resolverConstants = this.getResolverConstants(sdk, functionName);
128
+ const resolvedParams = {
129
+ ...resolverConstants,
130
+ ...providedParams,
131
+ };
123
132
  const context = {
124
133
  sdk,
125
134
  currentParams: providedParams,
@@ -263,11 +272,17 @@ export class SchemaParameterResolver {
263
272
  const missingParams = [];
264
273
  for (const param of params) {
265
274
  const resolver = this.getResolver(param.name, context.sdk, functionName);
266
- const autoResolution = resolver?.type === "dynamic"
267
- ? await this.tryAutoResolve(resolver, context)
268
- : null;
269
- if (autoResolution != null) {
270
- this.setNestedValue(resolvedParams, param.path, autoResolution.resolvedValue);
275
+ let autoResolved = null;
276
+ if (resolver?.type === "constant") {
277
+ autoResolved = {
278
+ resolvedValue: resolver.value,
279
+ };
280
+ }
281
+ else if (resolver?.type === "dynamic") {
282
+ autoResolved = await this.tryAutoResolve(resolver, context);
283
+ }
284
+ if (autoResolved != null) {
285
+ this.setNestedValue(resolvedParams, param.path, autoResolved.resolvedValue);
271
286
  context.resolvedParams = resolvedParams;
272
287
  }
273
288
  else {
@@ -299,7 +314,12 @@ export class SchemaParameterResolver {
299
314
  : param.name;
300
315
  const promptName = inArrayContext ? "value" : param.name;
301
316
  this.debugLog(`Resolving ${promptLabel}${isOptional ? " (optional)" : ""}`);
302
- if (resolver.type === "static") {
317
+ if (resolver.type === "constant") {
318
+ const constantResolver = resolver;
319
+ this.stopSpinner();
320
+ return constantResolver.value;
321
+ }
322
+ else if (resolver.type === "static") {
303
323
  const staticResolver = resolver;
304
324
  const promptConfig = {
305
325
  type: staticResolver.inputType === "password" ? "password" : "input",
@@ -370,6 +390,15 @@ export class SchemaParameterResolver {
370
390
  while (true) {
371
391
  const promptConfig = dynamicResolver.prompt(items, context.resolvedParams);
372
392
  promptConfig.name = promptName;
393
+ // Friendlier than inquirer's default "No selectable choices. All
394
+ // choices are disabled." when the resolver returns no items (or
395
+ // filters everything out in `prompt`). Use ZapierCliValidationError
396
+ // so the message renders to the user; ZapierCliExitError exits
397
+ // silently and would be confusing here.
398
+ const hasSelectableChoice = promptConfig.choices?.some((c) => !c.disabled);
399
+ if (!hasSelectableChoice && !hasMore) {
400
+ throw new ZapierCliValidationError(`No ${promptLabel} available to select.`);
401
+ }
373
402
  if (isOptional && promptConfig.choices) {
374
403
  promptConfig.choices.unshift({
375
404
  name: chalk.dim("(Skip)"),
@@ -711,7 +740,7 @@ export class SchemaParameterResolver {
711
740
  ? `Fetching more choices for ${fieldMeta.title}`
712
741
  : `Fetching choices for ${fieldMeta.title}`);
713
742
  this.startSpinner();
714
- const page = await context.sdk.listInputFieldChoices({
743
+ const page = await context.sdk.listActionInputFieldChoices({
715
744
  app: context.resolvedParams.app,
716
745
  action: context.resolvedParams.action,
717
746
  actionType: context.resolvedParams.actionType,
@@ -956,4 +985,21 @@ export class SchemaParameterResolver {
956
985
  const functionInfo = registry.functions.find((f) => f.name === functionName);
957
986
  return functionInfo?.resolvers || {};
958
987
  }
988
+ getResolverConstants(sdk, functionName) {
989
+ if (!functionName || typeof sdk.getRegistry !== "function") {
990
+ return {};
991
+ }
992
+ const registry = sdk.getRegistry();
993
+ const functionInfo = registry.functions.find((f) => f.name === functionName);
994
+ const resolvers = functionInfo?.resolvers ?? {};
995
+ const constants = {};
996
+ for (const [key, resolver] of Object.entries(resolvers)) {
997
+ if (resolver &&
998
+ typeof resolver === "object" &&
999
+ resolver.type === "constant") {
1000
+ constants[key] = resolver.value;
1001
+ }
1002
+ }
1003
+ return constants;
1004
+ }
959
1005
  }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Shared CLI helpers for the `drain-trigger-inbox` and
3
+ * `watch-trigger-inbox` commands. Both wrap the eager SDK callback API
4
+ * and dispatch on the same flag set: interactive default (TTY),
5
+ * `--exec` (binary + argv, no shell), `--exec-shell` (shell handler),
6
+ * `--json` (drain: collect-and-print; watch: NDJSON streaming).
7
+ */
8
+ import { type LeasedTriggerMessageItem } from "@zapier/zapier-sdk";
9
+ export type Callback = (message: LeasedTriggerMessageItem) => Promise<void>;
10
+ /**
11
+ * Marker error thrown by `createInteractiveCallback` when the user
12
+ * picks "Skip (let lease expire)". The SDK treats it like any other
13
+ * Error subclass — leave-leased per default `releaseOnError: false`
14
+ * — so the lease-timeout recovery path is preserved. The CLI uses
15
+ * the marker type to distinguish a deliberate skip from an unhandled
16
+ * exception when counting summary lines. Not a `ZapierSignal`: the
17
+ * SDK's signal-handling auto-releases the message, which is exactly
18
+ * what skip-expire does *not* want.
19
+ */
20
+ export declare class CliSkipLeaseExpireError extends Error {
21
+ readonly name = "CliSkipLeaseExpireError";
22
+ constructor();
23
+ }
24
+ /**
25
+ * Prompts the user per message. Choices:
26
+ *
27
+ * - **Ack**: returns normally so the SDK acks the message.
28
+ * - **Skip (release after draining)**: throws
29
+ * `ZapierReleaseTriggerMessageSignal`. The SDK marks the message for
30
+ * release; the actual release call is deferred until the drain
31
+ * finishes so the same drain doesn't immediately re-lease it. After
32
+ * drain ends, the message returns to `available` for a future drain
33
+ * run or another consumer.
34
+ * - **Skip (let lease expire)**: throws `CliSkipLeaseExpireError` so
35
+ * the SDK leaves the message leased (it's not a `ZapierSignal`,
36
+ * so the SDK's catch-all "leave on error" path applies); the lease
37
+ * timeout (~5 min default) is the recovery path. Use this when you
38
+ * want backpressure on reprocessing. The CLI catches the marker
39
+ * to count it as a skip in the drain summary.
40
+ * - **Quit**: throws `ZapierAbortDrainSignal` so the SDK stops draining
41
+ * after the current batch finishes (in-flight callbacks complete;
42
+ * not-yet-started messages in the batch are released).
43
+ *
44
+ * Ctrl-C during the prompt is translated to `ZapierAbortDrainSignal`
45
+ * (same as Quit) — inquirer raises `ExitPromptError`, which we catch
46
+ * and re-throw as the abort signal.
47
+ */
48
+ export declare function createInteractiveCallback(): Callback;
49
+ /**
50
+ * Writes one JSON object per line to stdout (NDJSON) for piping. Acks
51
+ * each message after the write resolves; if stdout is closed mid-drain
52
+ * (broken pipe), the write throws and the message stays unacked.
53
+ *
54
+ * The write callback alone provides backpressure: it doesn't fire until
55
+ * the chunk is fully committed to the underlying resource, so awaiting
56
+ * it pauses the drain when stdout is busy. No separate `drain` listener
57
+ * needed — and adding one would race with the callback (drain fires at
58
+ * highWaterMark, callback fires after full commit; if drain wins, the
59
+ * later callback's error gets dropped on the floor).
60
+ */
61
+ export declare function createNdjsonCallback(): Callback;
62
+ export declare function runShellCommand(command: string, message: LeasedTriggerMessageItem, signal?: AbortSignal): Promise<void>;
63
+ /**
64
+ * Run a binary per message with no shell interpretation. `argv[0]` is
65
+ * the executable; the rest are passed literally as argv to the child.
66
+ * Otherwise identical to `runShellCommand`: stdin = message JSON, exit
67
+ * code 0 acks, non-zero rejects, abort signal kills the child.
68
+ */
69
+ export declare function runExecCommand(argv: string[], message: LeasedTriggerMessageItem, signal?: AbortSignal): Promise<void>;
70
+ /**
71
+ * Print a single per-message error to stderr. Used as the live
72
+ * `onError` body so failures surface as they happen (vs accumulated
73
+ * and dumped at the end, which is broken for `watch-trigger-inbox`
74
+ * where "the end" is Ctrl-C).
75
+ */
76
+ export declare function printDrainError(reason: unknown, message: LeasedTriggerMessageItem): void;
77
+ /**
78
+ * One-line summary printed when the CLI finished a drain in a mode
79
+ * where the data array is uninformative (interactive, exec, exec-shell).
80
+ * Both the bounded and forever variants use the same format.
81
+ *
82
+ * `skipped` is the interactive-mode count of user-driven Skip choices
83
+ * (release-after-draining + let-lease-expire). Omitted when zero so the
84
+ * subprocess modes — which have no skip semantics — keep their original
85
+ * two-part summary.
86
+ */
87
+ export declare function printDrainSummary(counts: {
88
+ fulfilled: number;
89
+ rejected: number;
90
+ skipped?: number;
91
+ }): void;
92
+ /**
93
+ * Warn on stderr that interactive mode is overriding an explicit
94
+ * `continueOnError: false`. Only fires when the caller (programmatic
95
+ * SDK consumer or future flag form that takes `=false`) opted into
96
+ * fail-fast — bare `--continue-on-error` flag absence leaves it
97
+ * `undefined` and doesn't trigger this. Interactive mode mechanically
98
+ * requires continue-on-error: the "Skip (let lease expire)" choice
99
+ * throws a plain Error to leave the message leased, and a fail-fast
100
+ * drain would tear down on the first skip.
101
+ */
102
+ export declare function warnInteractiveContinueOnErrorOverride(): void;
103
+ /**
104
+ * Throw a CliValidationError if the current process isn't attached to
105
+ * a real terminal on both stdin and stdout. Used by the interactive
106
+ * default to fail fast with a useful message instead of letting
107
+ * inquirer fall over later.
108
+ */
109
+ export declare function requireInteractiveTty(commandName: string): void;
110
+ /**
111
+ * Reject more than one of `--exec`, `--exec-shell`, and `--json`.
112
+ * Each is a non-interactive output path and choosing implicitly is
113
+ * ambiguous.
114
+ */
115
+ export declare function rejectExecJsonMutex(opts: {
116
+ exec?: unknown;
117
+ execShell?: unknown;
118
+ json?: unknown;
119
+ }): void;
120
+ /**
121
+ * Pull argv tokens after the first `--` separator out of `process.argv`.
122
+ * Returns `[]` when there's no `--`. Used by drain/watch to append
123
+ * post-`--` args to the `--exec` binary, mirroring `xargs`, `gh`, and
124
+ * `npm run -- ...` semantics.
125
+ *
126
+ * Read from `process.argv` rather than threading via cli-generator
127
+ * because Commander already consumes `--` at parse time and there's
128
+ * no per-command API for "the args after `--`" exposed through the
129
+ * SDK options object.
130
+ */
131
+ export declare function getPostDashArgs(argv?: string[]): string[];
132
+ /**
133
+ * Combine an optional user signal with an internal one (typically the
134
+ * SIGINT controller) into a single AbortSignal that fires when either
135
+ * source aborts. Returns a `dispose` to detach the listeners; safe to
136
+ * call regardless of whether the combined signal fired.
137
+ *
138
+ * Plain shape rather than the SDK's `combineAbortSignals`, which works
139
+ * over `DisposableAbortSignal` handles and returns a different shape.
140
+ */
141
+ export declare function combineSignals(a: AbortSignal | undefined, b: AbortSignal): {
142
+ signal: AbortSignal;
143
+ dispose: () => void;
144
+ };