@zapier/zapier-sdk-cli 0.44.1 → 0.46.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/CHANGELOG.md +18 -0
- package/README.md +381 -29
- package/bin/zapier-sdk-experimental.mjs +14 -0
- package/dist/cli.cjs +608 -38
- package/dist/cli.mjs +607 -37
- package/dist/experimental.cjs +3519 -0
- package/dist/experimental.d.mts +39 -0
- package/dist/experimental.d.ts +39 -0
- package/dist/experimental.mjs +3483 -0
- package/dist/index.cjs +507 -26
- package/dist/index.d.mts +3 -514
- package/dist/index.d.ts +3 -514
- package/dist/index.mjs +505 -24
- package/dist/package.json +14 -2
- package/dist/sdk-B3nKAZdN.d.mts +516 -0
- package/dist/sdk-B3nKAZdN.d.ts +516 -0
- package/dist/src/cli.js +26 -2
- package/dist/src/experimental.d.ts +33 -0
- package/dist/src/experimental.js +83 -0
- package/dist/src/generators/ast-generator.d.ts +2 -2
- package/dist/src/generators/ast-generator.js +1 -1
- package/dist/src/plugins/add/index.d.ts +2 -2
- package/dist/src/plugins/bundleCode/index.js +3 -12
- package/dist/src/plugins/curl/index.js +2 -2
- package/dist/src/plugins/curl/utils.d.ts +11 -1
- package/dist/src/plugins/curl/utils.js +14 -5
- package/dist/src/plugins/drainTriggerInbox/index.d.ts +46 -0
- package/dist/src/plugins/drainTriggerInbox/index.js +178 -0
- package/dist/src/plugins/generateAppTypes/index.d.ts +2 -2
- package/dist/src/plugins/index.d.ts +2 -0
- package/dist/src/plugins/index.js +2 -0
- package/dist/src/plugins/mcp/index.d.ts +1 -0
- package/dist/src/plugins/mcp/index.js +5 -1
- package/dist/src/plugins/watchTriggerInbox/index.d.ts +45 -0
- package/dist/src/plugins/watchTriggerInbox/index.js +157 -0
- package/dist/src/sdk.js +5 -1
- package/dist/src/utils/cli-generator.js +18 -1
- package/dist/src/utils/cli-renderer.d.ts +12 -0
- package/dist/src/utils/cli-renderer.js +22 -1
- package/dist/src/utils/parameter-resolver.d.ts +1 -0
- package/dist/src/utils/parameter-resolver.js +81 -10
- package/dist/src/utils/triggerDrain.d.ts +144 -0
- package/dist/src/utils/triggerDrain.js +351 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -4
|
@@ -27,7 +27,7 @@ export class AstTypeGenerator {
|
|
|
27
27
|
// Fetch all input fields with concurrency limiting for better reliability
|
|
28
28
|
// Using batch() instead of Promise.allSettled() prevents overwhelming the API
|
|
29
29
|
// and triggering rate limits when apps have many actions
|
|
30
|
-
const inputFieldsTasks = actions.map((action) => () => sdk.
|
|
30
|
+
const inputFieldsTasks = actions.map((action) => () => sdk.listActionInputFields({
|
|
31
31
|
appKey: app.implementation_id,
|
|
32
32
|
actionKey: action.key,
|
|
33
33
|
actionType: action.action_type,
|
|
@@ -105,7 +105,7 @@ export declare const addPlugin: (sdk: {
|
|
|
105
105
|
};
|
|
106
106
|
};
|
|
107
107
|
} & {
|
|
108
|
-
|
|
108
|
+
listActionInputFields: (options?: (({
|
|
109
109
|
app: string;
|
|
110
110
|
actionType: "filter" | "read" | "read_bulk" | "run" | "search" | "search_and_write" | "search_or_write" | "write";
|
|
111
111
|
action: string;
|
|
@@ -155,7 +155,7 @@ export declare const addPlugin: (sdk: {
|
|
|
155
155
|
} & {
|
|
156
156
|
context: {
|
|
157
157
|
meta: {
|
|
158
|
-
|
|
158
|
+
listActionInputFields: import("@zapier/zapier-sdk").PluginMeta;
|
|
159
159
|
};
|
|
160
160
|
};
|
|
161
161
|
} & {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BundleCodeSchema } from "./schemas";
|
|
2
|
-
import { createPluginMethod, definePlugin, } from "@zapier/zapier-sdk";
|
|
2
|
+
import { createPluginMethod, definePlugin, ZapierBundleError, } from "@zapier/zapier-sdk";
|
|
3
3
|
import { buildSync } from "esbuild";
|
|
4
4
|
import * as fs from "fs";
|
|
5
5
|
import * as path from "path";
|
|
@@ -9,15 +9,6 @@ export const bundleCodePlugin = definePlugin((sdk) => createPluginMethod(sdk, {
|
|
|
9
9
|
inputSchema: BundleCodeSchema,
|
|
10
10
|
handler: async ({ options }) => bundleCode(options),
|
|
11
11
|
}));
|
|
12
|
-
class ZapierBundleError extends Error {
|
|
13
|
-
constructor(message, details, originalError) {
|
|
14
|
-
super(message);
|
|
15
|
-
this.code = "ZAPIER_BUNDLE_ERROR";
|
|
16
|
-
this.name = "ZapierBundleError";
|
|
17
|
-
this.details = details;
|
|
18
|
-
this.originalError = originalError;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
12
|
/**
|
|
22
13
|
* Bundle TypeScript code into executable JavaScript (CLI version)
|
|
23
14
|
*/
|
|
@@ -42,7 +33,7 @@ async function bundleCode(options) {
|
|
|
42
33
|
});
|
|
43
34
|
if (result.errors.length > 0) {
|
|
44
35
|
const errorMessages = result.errors.map((e) => e.text);
|
|
45
|
-
throw new ZapierBundleError(`Bundle failed: ${errorMessages.join(", ")}`, errorMessages);
|
|
36
|
+
throw new ZapierBundleError(`Bundle failed: ${errorMessages.join(", ")}`, { buildErrors: errorMessages });
|
|
46
37
|
}
|
|
47
38
|
const bundledCode = result.outputFiles?.[0]?.text;
|
|
48
39
|
if (!bundledCode) {
|
|
@@ -65,6 +56,6 @@ async function bundleCode(options) {
|
|
|
65
56
|
if (error instanceof ZapierBundleError) {
|
|
66
57
|
throw error;
|
|
67
58
|
}
|
|
68
|
-
throw new ZapierBundleError(`Bundle failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
59
|
+
throw new ZapierBundleError(`Bundle failed: ${error instanceof Error ? error.message : "Unknown error"}`, { cause: error instanceof Error ? error : undefined });
|
|
69
60
|
}
|
|
70
61
|
}
|
|
@@ -3,7 +3,7 @@ import { createWriteStream } from "fs";
|
|
|
3
3
|
import { promises as fs } from "fs";
|
|
4
4
|
import { dirname } from "path";
|
|
5
5
|
import { CurlSchema } from "./schemas";
|
|
6
|
-
import {
|
|
6
|
+
import { ZapierCurlExitError, parseHeaderLine, basicAuthHeader, deriveRemoteFilename, formatWriteOut, appendQueryParams, readAllStdin, resolveDataArgText, resolveDataArgBinary, buildFormData, } from "./utils";
|
|
7
7
|
export const curlPlugin = definePlugin((sdk) => {
|
|
8
8
|
async function curl(options) {
|
|
9
9
|
const { url: rawUrl, request, header = [], data = [], dataRaw = [], dataAscii = [], dataBinary = [], dataUrlencode = [], json, form = [], formString = [], get: forceGet, head: forceHead, location, include, output, remoteName, verbose, silent, showError, fail, failWithBody, writeOut, maxTime, user, compressed, connection: connectionParam, connectionId, } = options;
|
|
@@ -195,7 +195,7 @@ export const curlPlugin = definePlugin((sdk) => {
|
|
|
195
195
|
if (!silent || showError) {
|
|
196
196
|
process.stderr.write(`curl: (22) The requested URL returned error: ${response.status}\n`);
|
|
197
197
|
}
|
|
198
|
-
throw new
|
|
198
|
+
throw new ZapierCurlExitError("HTTP request failed", 22);
|
|
199
199
|
}
|
|
200
200
|
return undefined;
|
|
201
201
|
}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
import { ZapierError } from "@zapier/zapier-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a curl-style invocation fails — captures the exit code so
|
|
4
|
+
* the CLI can surface it (mirrors curl's own non-zero exit codes for
|
|
5
|
+
* different failure modes).
|
|
6
|
+
*/
|
|
7
|
+
export declare class ZapierCurlExitError extends ZapierError {
|
|
8
|
+
readonly name = "ZapierCurlExitError";
|
|
9
|
+
readonly code: "ZAPIER_CURL_EXIT_ERROR";
|
|
2
10
|
exitCode: number;
|
|
3
11
|
constructor(message: string, exitCode: number);
|
|
4
12
|
}
|
|
13
|
+
/** @deprecated Use {@link ZapierCurlExitError} instead. */
|
|
14
|
+
export declare const CurlExitError: typeof ZapierCurlExitError;
|
|
5
15
|
export declare function parseHeaderLine(input: string): {
|
|
6
16
|
key: string;
|
|
7
17
|
value: string;
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { createHash } from "crypto";
|
|
2
2
|
import { promises as fs } from "fs";
|
|
3
3
|
import { basename } from "path";
|
|
4
|
-
|
|
4
|
+
import { ZapierError } from "@zapier/zapier-sdk";
|
|
5
|
+
/**
|
|
6
|
+
* Thrown when a curl-style invocation fails — captures the exit code so
|
|
7
|
+
* the CLI can surface it (mirrors curl's own non-zero exit codes for
|
|
8
|
+
* different failure modes).
|
|
9
|
+
*/
|
|
10
|
+
export class ZapierCurlExitError extends ZapierError {
|
|
5
11
|
constructor(message, exitCode) {
|
|
6
12
|
super(message);
|
|
13
|
+
this.name = "ZapierCurlExitError";
|
|
14
|
+
this.code = "ZAPIER_CURL_EXIT_ERROR";
|
|
7
15
|
this.exitCode = exitCode;
|
|
8
|
-
this.name = "CurlExitError";
|
|
9
16
|
}
|
|
10
17
|
}
|
|
18
|
+
/** @deprecated Use {@link ZapierCurlExitError} instead. */
|
|
19
|
+
export const CurlExitError = ZapierCurlExitError;
|
|
11
20
|
export function parseHeaderLine(input) {
|
|
12
21
|
const idx = input.indexOf(":");
|
|
13
22
|
if (idx === -1) {
|
|
@@ -102,18 +111,18 @@ export async function resolveDataArgBinary(raw) {
|
|
|
102
111
|
}
|
|
103
112
|
export async function buildFormData(formArgs, formStringArgs) {
|
|
104
113
|
if (typeof FormData === "undefined") {
|
|
105
|
-
throw new
|
|
114
|
+
throw new ZapierCurlExitError("FormData is not available in this runtime; cannot use --form.", 2);
|
|
106
115
|
}
|
|
107
116
|
const fd = new FormData();
|
|
108
117
|
const addField = async (item, forceString) => {
|
|
109
118
|
const idx = item.indexOf("=");
|
|
110
119
|
if (idx === -1) {
|
|
111
|
-
throw new
|
|
120
|
+
throw new ZapierCurlExitError(`Invalid form field: '${item}'. Expected 'name=value' or 'name=@file'.`, 2);
|
|
112
121
|
}
|
|
113
122
|
const name = item.slice(0, idx);
|
|
114
123
|
const value = item.slice(idx + 1);
|
|
115
124
|
if (!name) {
|
|
116
|
-
throw new
|
|
125
|
+
throw new ZapierCurlExitError(`Invalid form field: '${item}'. Field name cannot be empty.`, 2);
|
|
117
126
|
}
|
|
118
127
|
if (!forceString && value.startsWith("@")) {
|
|
119
128
|
const filePath = value.slice(1);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ZapierSdk } from "@zapier/zapier-sdk/experimental";
|
|
3
|
+
type SdkWithDrainTriggerInbox = {
|
|
4
|
+
drainTriggerInbox: ZapierSdk["drainTriggerInbox"];
|
|
5
|
+
context: {
|
|
6
|
+
meta: {
|
|
7
|
+
drainTriggerInbox: {
|
|
8
|
+
inputSchema?: z.ZodObject;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* CLI command for `drain-trigger-inbox`. `--continue-on-error` flows
|
|
16
|
+
* through from the auto-generated SDK schema flag; without it, the
|
|
17
|
+
* first handler failure rejects (fail-fast). For exec / exec-shell
|
|
18
|
+
* and interactive modes, errors print live to stderr via `onError` so
|
|
19
|
+
* they appear next to the message that failed (rather than in a
|
|
20
|
+
* deferred dump at the end).
|
|
21
|
+
*
|
|
22
|
+
* `--json` mode collects messages into a `data` array and prints
|
|
23
|
+
* `{ data, errors }` once at the end — that's its contract. We
|
|
24
|
+
* always pass `continueOnError: true` in JSON mode so partial
|
|
25
|
+
* failures don't truncate the envelope.
|
|
26
|
+
*/
|
|
27
|
+
export declare const drainTriggerInboxCliPlugin: (sdk: SdkWithDrainTriggerInbox & {
|
|
28
|
+
context: {
|
|
29
|
+
meta: Record<string, import("@zapier/zapier-sdk").PluginMeta>;
|
|
30
|
+
};
|
|
31
|
+
}) => {
|
|
32
|
+
drainTriggerInbox: ZapierSdk["drainTriggerInbox"];
|
|
33
|
+
context: {
|
|
34
|
+
meta: {
|
|
35
|
+
drainTriggerInbox: {
|
|
36
|
+
inputSchema: z.ZodObject<{
|
|
37
|
+
exec: z.ZodOptional<z.ZodString>;
|
|
38
|
+
execShell: z.ZodOptional<z.ZodString>;
|
|
39
|
+
json: z.ZodOptional<z.ZodBoolean>;
|
|
40
|
+
}, z.core.$strip>;
|
|
41
|
+
packages: undefined;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { definePlugin, ZapierReleaseTriggerMessageSignal, } from "@zapier/zapier-sdk";
|
|
3
|
+
import { jsonReplacer } from "../../utils/cli-renderer";
|
|
4
|
+
import { CliSkipLeaseExpireError, combineSignals, createInteractiveCallback, getPostDashArgs, printDrainError, printDrainSummary, rejectExecJsonMutex, requireInteractiveTty, runExecCommand, runShellCommand, warnInteractiveContinueOnErrorOverride, } from "../../utils/triggerDrain";
|
|
5
|
+
const JsonProperty = z
|
|
6
|
+
.boolean()
|
|
7
|
+
.optional()
|
|
8
|
+
.describe("Format the drained result as a JSON object on stdout: { data, errors }. Use for scripts or piping. Mutually exclusive with --exec / --exec-shell and the interactive default.");
|
|
9
|
+
// Both `--exec` and `--exec-shell` are CLI-only: the SDK exposes only
|
|
10
|
+
// `onMessage`, and the CLI plugin wraps these flags into an onMessage
|
|
11
|
+
// before delegating. On the CLI, the binary is the flag value and any
|
|
12
|
+
// argv after a literal `--` is appended (xargs / npm-run convention).
|
|
13
|
+
const ExecCliProperty = z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Run a binary per message with no shell interpretation. Message JSON is piped to stdin; exit code 0 acks, non-zero records the error per the same rules as a thrown handler. Pass extra argv after `--` (e.g. `--exec ./handler -- --verbose`). Mutually exclusive with --exec-shell and --json.");
|
|
17
|
+
const ExecShellCliProperty = z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Run a shell command per message. Message JSON is piped to the subprocess on stdin; exit code 0 acks, non-zero records the error per the same rules as a thrown handler. Interpreted by the platform's default shell (sh on POSIX, cmd.exe on Windows). Mutually exclusive with --exec and --json.");
|
|
21
|
+
/**
|
|
22
|
+
* CLI command for `drain-trigger-inbox`. `--continue-on-error` flows
|
|
23
|
+
* through from the auto-generated SDK schema flag; without it, the
|
|
24
|
+
* first handler failure rejects (fail-fast). For exec / exec-shell
|
|
25
|
+
* and interactive modes, errors print live to stderr via `onError` so
|
|
26
|
+
* they appear next to the message that failed (rather than in a
|
|
27
|
+
* deferred dump at the end).
|
|
28
|
+
*
|
|
29
|
+
* `--json` mode collects messages into a `data` array and prints
|
|
30
|
+
* `{ data, errors }` once at the end — that's its contract. We
|
|
31
|
+
* always pass `continueOnError: true` in JSON mode so partial
|
|
32
|
+
* failures don't truncate the envelope.
|
|
33
|
+
*/
|
|
34
|
+
export const drainTriggerInboxCliPlugin = definePlugin((sdk) => {
|
|
35
|
+
const original = sdk.drainTriggerInbox;
|
|
36
|
+
const existingMeta = sdk.context.meta.drainTriggerInbox;
|
|
37
|
+
const baseInputSchema = existingMeta.inputSchema;
|
|
38
|
+
// `.extend` overrides `exec` from the SDK's array form to the CLI's
|
|
39
|
+
// single-string form. The post-`--` argv is reattached by the
|
|
40
|
+
// handler below before `runExecCommand`.
|
|
41
|
+
const extendedInputSchema = baseInputSchema
|
|
42
|
+
? baseInputSchema.extend({
|
|
43
|
+
exec: ExecCliProperty,
|
|
44
|
+
execShell: ExecShellCliProperty,
|
|
45
|
+
json: JsonProperty,
|
|
46
|
+
})
|
|
47
|
+
: z.object({
|
|
48
|
+
exec: ExecCliProperty,
|
|
49
|
+
execShell: ExecShellCliProperty,
|
|
50
|
+
json: JsonProperty,
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
drainTriggerInbox: (async (options) => {
|
|
54
|
+
const { json, exec, execShell, ...sdkArgs } = options;
|
|
55
|
+
rejectExecJsonMutex({ exec, execShell, json });
|
|
56
|
+
// Validate TTY before installing the SIGINT handler / finally,
|
|
57
|
+
// so a preflight failure doesn't print a misleading
|
|
58
|
+
// "Processed 0 messages" summary on its way out.
|
|
59
|
+
if (!exec && !execShell && !json) {
|
|
60
|
+
requireInteractiveTty("drain-trigger-inbox");
|
|
61
|
+
}
|
|
62
|
+
// SIGINT in non-interactive modes → kill subprocesses cleanly.
|
|
63
|
+
const sigintController = new AbortController();
|
|
64
|
+
const onSigint = () => sigintController.abort();
|
|
65
|
+
process.on("SIGINT", onSigint);
|
|
66
|
+
const combined = combineSignals(sdkArgs.signal, sigintController.signal);
|
|
67
|
+
let fulfilled = 0;
|
|
68
|
+
let rejected = 0;
|
|
69
|
+
let skipped = 0;
|
|
70
|
+
const liveOnError = (reason, message) => {
|
|
71
|
+
rejected++;
|
|
72
|
+
printDrainError(reason, message);
|
|
73
|
+
};
|
|
74
|
+
try {
|
|
75
|
+
// --exec: direct binary invocation (no shell). Append any
|
|
76
|
+
// argv after a literal `--` so users can pass flags to the
|
|
77
|
+
// exec'd binary without quoting tricks.
|
|
78
|
+
if (exec) {
|
|
79
|
+
const execArgv = [exec, ...getPostDashArgs()];
|
|
80
|
+
await original({
|
|
81
|
+
...sdkArgs,
|
|
82
|
+
signal: combined.signal,
|
|
83
|
+
onMessage: async (message) => {
|
|
84
|
+
await runExecCommand(execArgv, message, combined.signal);
|
|
85
|
+
fulfilled++;
|
|
86
|
+
},
|
|
87
|
+
onError: liveOnError,
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// --exec-shell: shell handler. Errors print live; user's
|
|
92
|
+
// --continue-on-error decides whether to keep going.
|
|
93
|
+
if (execShell) {
|
|
94
|
+
await original({
|
|
95
|
+
...sdkArgs,
|
|
96
|
+
signal: combined.signal,
|
|
97
|
+
onMessage: async (message) => {
|
|
98
|
+
await runShellCommand(execShell, message, combined.signal);
|
|
99
|
+
fulfilled++;
|
|
100
|
+
},
|
|
101
|
+
onError: liveOnError,
|
|
102
|
+
});
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// --json: collect into envelope. Always continue-on-error
|
|
106
|
+
// because partial-result truncation isn't useful here.
|
|
107
|
+
if (json) {
|
|
108
|
+
const data = [];
|
|
109
|
+
const errors = [];
|
|
110
|
+
await original({
|
|
111
|
+
...sdkArgs,
|
|
112
|
+
signal: combined.signal,
|
|
113
|
+
continueOnError: true,
|
|
114
|
+
onMessage: (message) => {
|
|
115
|
+
data.push(message);
|
|
116
|
+
},
|
|
117
|
+
onError: (reason, message) => {
|
|
118
|
+
errors.push({ reason, message });
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
process.stdout.write(JSON.stringify({ data, errors }, jsonReplacer, 2) + "\n");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Default: interactive triage. continueOnError forced true
|
|
125
|
+
// so "skip (let lease expire)" doesn't tear down the loop.
|
|
126
|
+
// No onError — user-driven skips aren't errors worth
|
|
127
|
+
// reporting, and real SDK failures still propagate. TTY
|
|
128
|
+
// validation already ran above the try block.
|
|
129
|
+
if (sdkArgs.continueOnError === false) {
|
|
130
|
+
warnInteractiveContinueOnErrorOverride();
|
|
131
|
+
}
|
|
132
|
+
const interactive = createInteractiveCallback();
|
|
133
|
+
await original({
|
|
134
|
+
...sdkArgs,
|
|
135
|
+
signal: combined.signal,
|
|
136
|
+
concurrency: 1,
|
|
137
|
+
continueOnError: true,
|
|
138
|
+
onMessage: async (message) => {
|
|
139
|
+
try {
|
|
140
|
+
await interactive(message);
|
|
141
|
+
fulfilled++;
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
// Count both skip flavors (release-after-draining +
|
|
145
|
+
// let-lease-expire) toward `skipped` so the final
|
|
146
|
+
// summary's total matches what the user actually saw.
|
|
147
|
+
// Quit (ZapierAbortDrainSignal) and unknown errors
|
|
148
|
+
// fall through uncounted — quit ends the drain and
|
|
149
|
+
// unknown errors are pre-existing silently-swallowed
|
|
150
|
+
// behavior in interactive mode.
|
|
151
|
+
if (err instanceof ZapierReleaseTriggerMessageSignal ||
|
|
152
|
+
err instanceof CliSkipLeaseExpireError) {
|
|
153
|
+
skipped++;
|
|
154
|
+
}
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
process.off("SIGINT", onSigint);
|
|
162
|
+
combined.dispose();
|
|
163
|
+
if (!json) {
|
|
164
|
+
printDrainSummary({ fulfilled, rejected, skipped });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}),
|
|
168
|
+
context: {
|
|
169
|
+
meta: {
|
|
170
|
+
drainTriggerInbox: {
|
|
171
|
+
...existingMeta,
|
|
172
|
+
inputSchema: extendedInputSchema,
|
|
173
|
+
packages: undefined,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
});
|
|
@@ -106,7 +106,7 @@ export declare const generateAppTypesPlugin: (sdk: {
|
|
|
106
106
|
};
|
|
107
107
|
};
|
|
108
108
|
} & {
|
|
109
|
-
|
|
109
|
+
listActionInputFields: (options?: (({
|
|
110
110
|
app: string;
|
|
111
111
|
actionType: "filter" | "read" | "read_bulk" | "run" | "search" | "search_and_write" | "search_or_write" | "write";
|
|
112
112
|
action: string;
|
|
@@ -156,7 +156,7 @@ export declare const generateAppTypesPlugin: (sdk: {
|
|
|
156
156
|
} & {
|
|
157
157
|
context: {
|
|
158
158
|
meta: {
|
|
159
|
-
|
|
159
|
+
listActionInputFields: import("@zapier/zapier-sdk").PluginMeta;
|
|
160
160
|
};
|
|
161
161
|
};
|
|
162
162
|
} & {
|
|
@@ -10,3 +10,5 @@ export { feedbackPlugin } from "./feedback";
|
|
|
10
10
|
export { curlPlugin } from "./curl";
|
|
11
11
|
export { cliOverridesPlugin } from "./cliOverrides";
|
|
12
12
|
export { initPlugin } from "./init";
|
|
13
|
+
export { drainTriggerInboxCliPlugin } from "./drainTriggerInbox";
|
|
14
|
+
export { watchTriggerInboxCliPlugin } from "./watchTriggerInbox";
|
|
@@ -10,3 +10,5 @@ export { feedbackPlugin } from "./feedback";
|
|
|
10
10
|
export { curlPlugin } from "./curl";
|
|
11
11
|
export { cliOverridesPlugin } from "./cliOverrides";
|
|
12
12
|
export { initPlugin } from "./init";
|
|
13
|
+
export { drainTriggerInboxCliPlugin } from "./drainTriggerInbox";
|
|
14
|
+
export { watchTriggerInboxCliPlugin } from "./watchTriggerInbox";
|
|
@@ -9,11 +9,15 @@ export const mcpPlugin = definePlugin((sdk) => createPluginMethod(sdk, {
|
|
|
9
9
|
// Forward debug + the extensions resolved at CLI startup so the MCP
|
|
10
10
|
// server's registry includes extension functions as tools. Without
|
|
11
11
|
// this forward, MCP would build a vanilla SDK and the CLI/MCP
|
|
12
|
-
// surfaces would diverge.
|
|
12
|
+
// surfaces would diverge. The `experimental` flag (set by the
|
|
13
|
+
// experimental CLI factory) tells the MCP server to build against
|
|
14
|
+
// `@zapier/zapier-sdk/experimental` so experimental tools are
|
|
15
|
+
// exposed.
|
|
13
16
|
await startMcpServer({
|
|
14
17
|
...options,
|
|
15
18
|
debug: sdk.context.options?.debug,
|
|
16
19
|
extensions: sdk.context.extensions,
|
|
20
|
+
experimental: sdk.context.experimental,
|
|
17
21
|
});
|
|
18
22
|
},
|
|
19
23
|
}));
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ZapierSdk } from "@zapier/zapier-sdk/experimental";
|
|
3
|
+
type SdkWithWatchTriggerInbox = {
|
|
4
|
+
watchTriggerInbox: ZapierSdk["watchTriggerInbox"];
|
|
5
|
+
context: {
|
|
6
|
+
meta: {
|
|
7
|
+
watchTriggerInbox: {
|
|
8
|
+
inputSchema?: z.ZodObject;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* CLI command for `watch-trigger-inbox`. Same flag dispatch as
|
|
16
|
+
* drain-trigger-inbox. `--continue-on-error` flows through from the
|
|
17
|
+
* auto-generated SDK schema flag; without it, the first handler
|
|
18
|
+
* failure rejects (fail-fast). Errors print live to stderr via
|
|
19
|
+
* `onError`, not buffered until the end — for a forever loop "the
|
|
20
|
+
* end" is Ctrl-C, which is too late.
|
|
21
|
+
*
|
|
22
|
+
* `--json` for watch is NDJSON streaming (one record per line)
|
|
23
|
+
* rather than a final envelope. The watch loop has no terminal
|
|
24
|
+
* aggregate to format.
|
|
25
|
+
*/
|
|
26
|
+
export declare const watchTriggerInboxCliPlugin: (sdk: SdkWithWatchTriggerInbox & {
|
|
27
|
+
context: {
|
|
28
|
+
meta: Record<string, import("@zapier/zapier-sdk").PluginMeta>;
|
|
29
|
+
};
|
|
30
|
+
}) => {
|
|
31
|
+
watchTriggerInbox: ZapierSdk["watchTriggerInbox"];
|
|
32
|
+
context: {
|
|
33
|
+
meta: {
|
|
34
|
+
watchTriggerInbox: {
|
|
35
|
+
inputSchema: z.ZodObject<{
|
|
36
|
+
exec: z.ZodOptional<z.ZodString>;
|
|
37
|
+
execShell: z.ZodOptional<z.ZodString>;
|
|
38
|
+
json: z.ZodOptional<z.ZodBoolean>;
|
|
39
|
+
}, z.core.$strip>;
|
|
40
|
+
packages: undefined;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { definePlugin, ZapierReleaseTriggerMessageSignal, } from "@zapier/zapier-sdk";
|
|
3
|
+
import { CliSkipLeaseExpireError, combineSignals, createInteractiveCallback, createNdjsonCallback, getPostDashArgs, printDrainError, printDrainSummary, rejectExecJsonMutex, requireInteractiveTty, runExecCommand, runShellCommand, warnInteractiveContinueOnErrorOverride, } from "../../utils/triggerDrain";
|
|
4
|
+
const JsonProperty = z
|
|
5
|
+
.boolean()
|
|
6
|
+
.optional()
|
|
7
|
+
.describe("Stream each message as JSON to stdout (one record per line, NDJSON), acking as each write completes. Use for piping to other tools. Mutually exclusive with --exec / --exec-shell and the interactive default.");
|
|
8
|
+
// Both `--exec` and `--exec-shell` are CLI-only — see drainTriggerInbox/index.ts.
|
|
9
|
+
const ExecCliProperty = z
|
|
10
|
+
.string()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Run a binary per message with no shell interpretation. Message JSON is piped to stdin; exit code 0 acks, non-zero records the error per the same rules as a thrown handler. Pass extra argv after `--` (e.g. `--exec ./handler -- --verbose`). Mutually exclusive with --exec-shell and --json.");
|
|
13
|
+
const ExecShellCliProperty = z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Run a shell command per message. Message JSON is piped to the subprocess on stdin; exit code 0 acks, non-zero records the error per the same rules as a thrown handler. Interpreted by the platform's default shell (sh on POSIX, cmd.exe on Windows). Mutually exclusive with --exec and --json.");
|
|
17
|
+
/**
|
|
18
|
+
* CLI command for `watch-trigger-inbox`. Same flag dispatch as
|
|
19
|
+
* drain-trigger-inbox. `--continue-on-error` flows through from the
|
|
20
|
+
* auto-generated SDK schema flag; without it, the first handler
|
|
21
|
+
* failure rejects (fail-fast). Errors print live to stderr via
|
|
22
|
+
* `onError`, not buffered until the end — for a forever loop "the
|
|
23
|
+
* end" is Ctrl-C, which is too late.
|
|
24
|
+
*
|
|
25
|
+
* `--json` for watch is NDJSON streaming (one record per line)
|
|
26
|
+
* rather than a final envelope. The watch loop has no terminal
|
|
27
|
+
* aggregate to format.
|
|
28
|
+
*/
|
|
29
|
+
export const watchTriggerInboxCliPlugin = definePlugin((sdk) => {
|
|
30
|
+
const original = sdk.watchTriggerInbox;
|
|
31
|
+
const existingMeta = sdk.context.meta.watchTriggerInbox;
|
|
32
|
+
const baseInputSchema = existingMeta.inputSchema;
|
|
33
|
+
const extendedInputSchema = baseInputSchema
|
|
34
|
+
? baseInputSchema.extend({
|
|
35
|
+
exec: ExecCliProperty,
|
|
36
|
+
execShell: ExecShellCliProperty,
|
|
37
|
+
json: JsonProperty,
|
|
38
|
+
})
|
|
39
|
+
: z.object({
|
|
40
|
+
exec: ExecCliProperty,
|
|
41
|
+
execShell: ExecShellCliProperty,
|
|
42
|
+
json: JsonProperty,
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
watchTriggerInbox: (async (options) => {
|
|
46
|
+
const { json, exec, execShell, ...sdkArgs } = options;
|
|
47
|
+
rejectExecJsonMutex({ exec, execShell, json });
|
|
48
|
+
// Validate TTY before installing the SIGINT handler / finally,
|
|
49
|
+
// so a preflight failure doesn't print a misleading
|
|
50
|
+
// "Processed 0 messages" summary on its way out.
|
|
51
|
+
if (!exec && !execShell && !json) {
|
|
52
|
+
requireInteractiveTty("watch-trigger-inbox");
|
|
53
|
+
}
|
|
54
|
+
// SIGINT + user signal → combined.
|
|
55
|
+
const sigintController = new AbortController();
|
|
56
|
+
const onSigint = () => sigintController.abort();
|
|
57
|
+
process.on("SIGINT", onSigint);
|
|
58
|
+
const combined = combineSignals(sdkArgs.signal, sigintController.signal);
|
|
59
|
+
let fulfilled = 0;
|
|
60
|
+
let rejected = 0;
|
|
61
|
+
let skipped = 0;
|
|
62
|
+
const liveOnError = (reason, message) => {
|
|
63
|
+
rejected++;
|
|
64
|
+
printDrainError(reason, message);
|
|
65
|
+
};
|
|
66
|
+
try {
|
|
67
|
+
if (exec) {
|
|
68
|
+
const execArgv = [exec, ...getPostDashArgs()];
|
|
69
|
+
await original({
|
|
70
|
+
...sdkArgs,
|
|
71
|
+
signal: combined.signal,
|
|
72
|
+
onMessage: async (message) => {
|
|
73
|
+
await runExecCommand(execArgv, message, combined.signal);
|
|
74
|
+
fulfilled++;
|
|
75
|
+
},
|
|
76
|
+
onError: liveOnError,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else if (execShell) {
|
|
80
|
+
await original({
|
|
81
|
+
...sdkArgs,
|
|
82
|
+
signal: combined.signal,
|
|
83
|
+
onMessage: async (message) => {
|
|
84
|
+
await runShellCommand(execShell, message, combined.signal);
|
|
85
|
+
fulfilled++;
|
|
86
|
+
},
|
|
87
|
+
onError: liveOnError,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
else if (json) {
|
|
91
|
+
const ndjson = createNdjsonCallback();
|
|
92
|
+
await original({
|
|
93
|
+
...sdkArgs,
|
|
94
|
+
signal: combined.signal,
|
|
95
|
+
onMessage: async (message) => {
|
|
96
|
+
await ndjson(message);
|
|
97
|
+
fulfilled++;
|
|
98
|
+
},
|
|
99
|
+
onError: liveOnError,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// Interactive: continueOnError is forced true so the user's
|
|
104
|
+
// "skip (let lease expire)" choice (which throws a plain
|
|
105
|
+
// Error) doesn't tear down the loop. We don't pass onError
|
|
106
|
+
// here — user-driven skips aren't errors worth reporting,
|
|
107
|
+
// and SDK-level failures still propagate as a rejection.
|
|
108
|
+
// TTY validation already ran above the try block.
|
|
109
|
+
if (sdkArgs.continueOnError === false) {
|
|
110
|
+
warnInteractiveContinueOnErrorOverride();
|
|
111
|
+
}
|
|
112
|
+
const interactive = createInteractiveCallback();
|
|
113
|
+
await original({
|
|
114
|
+
...sdkArgs,
|
|
115
|
+
signal: combined.signal,
|
|
116
|
+
concurrency: 1,
|
|
117
|
+
continueOnError: true,
|
|
118
|
+
onMessage: async (message) => {
|
|
119
|
+
try {
|
|
120
|
+
await interactive(message);
|
|
121
|
+
fulfilled++;
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
// See drainTriggerInbox for the counting policy: both
|
|
125
|
+
// skip flavors land in `skipped`; quit and unknown
|
|
126
|
+
// errors fall through uncounted.
|
|
127
|
+
if (err instanceof ZapierReleaseTriggerMessageSignal ||
|
|
128
|
+
err instanceof CliSkipLeaseExpireError) {
|
|
129
|
+
skipped++;
|
|
130
|
+
}
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
process.off("SIGINT", onSigint);
|
|
139
|
+
combined.dispose();
|
|
140
|
+
// NDJSON mode emitted everything per-line; printing a
|
|
141
|
+
// summary on top would mix with the data stream.
|
|
142
|
+
if (!json) {
|
|
143
|
+
printDrainSummary({ fulfilled, rejected, skipped });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}),
|
|
147
|
+
context: {
|
|
148
|
+
meta: {
|
|
149
|
+
watchTriggerInbox: {
|
|
150
|
+
...existingMeta,
|
|
151
|
+
inputSchema: extendedInputSchema,
|
|
152
|
+
packages: undefined,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
});
|
package/dist/src/sdk.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import * as cliLogin from "./login";
|
|
2
2
|
import { createZapierSdk, injectCliLogin, } from "@zapier/zapier-sdk";
|
|
3
|
-
import { loginPlugin, logoutPlugin, mcpPlugin, bundleCodePlugin, getLoginConfigPathPlugin, addPlugin, generateAppTypesPlugin, buildManifestPlugin, feedbackPlugin, curlPlugin, cliOverridesPlugin, initPlugin,
|
|
3
|
+
import { loginPlugin, logoutPlugin, mcpPlugin, bundleCodePlugin, getLoginConfigPathPlugin, addPlugin, generateAppTypesPlugin, buildManifestPlugin, feedbackPlugin, curlPlugin, cliOverridesPlugin, initPlugin,
|
|
4
|
+
// drainTriggerInboxForeverCliPlugin is experimental — registered only
|
|
5
|
+
// in `./experimental.ts`. See the "Experimental gating" section in
|
|
6
|
+
// `docs/design/2026-05-01-triggers-in-sdk.md`.
|
|
7
|
+
} from "./plugins/index";
|
|
4
8
|
import packageJson from "../package.json" with { type: "json" };
|
|
5
9
|
injectCliLogin(cliLogin);
|
|
6
10
|
/**
|