@zapier/zapier-sdk 0.13.1 → 0.13.3
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 +12 -0
- package/README.md +14 -2
- package/dist/api/debug.d.ts.map +1 -1
- package/dist/api/debug.js +36 -1
- package/dist/index.cjs +62 -14
- package/dist/index.d.mts +23 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.mjs +61 -15
- package/dist/plugins/getAction/index.d.ts.map +1 -1
- package/dist/plugins/getAction/index.js +3 -2
- package/dist/plugins/listInputFields/index.d.ts +2 -1
- package/dist/plugins/listInputFields/index.d.ts.map +1 -1
- package/dist/plugins/listInputFields/index.js +7 -2
- package/dist/plugins/listInputFields/index.test.js +36 -5
- package/dist/plugins/runAction/index.d.ts.map +1 -1
- package/dist/plugins/runAction/index.js +6 -1
- package/dist/schemas/App.d.ts.map +1 -1
- package/dist/schemas/App.js +11 -1
- package/dist/utils/domain-utils.d.ts.map +1 -1
- package/dist/utils/domain-utils.js +2 -6
- package/dist/utils/string-utils.d.ts +9 -0
- package/dist/utils/string-utils.d.ts.map +1 -1
- package/dist/utils/string-utils.js +25 -0
- package/package.json +1 -1
- package/src/api/debug.ts +44 -1
- package/src/index.ts +3 -0
- package/src/plugins/getAction/index.ts +5 -2
- package/src/plugins/listInputFields/index.test.ts +37 -5
- package/src/plugins/listInputFields/index.ts +10 -3
- package/src/plugins/runAction/index.ts +9 -0
- package/src/schemas/App.ts +12 -1
- package/src/utils/domain-utils.ts +2 -6
- package/src/utils/string-utils.ts +28 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
2
|
import { ZapierValidationError, ZapierConfigurationError, ZapierApiError, } from "../../types/errors";
|
|
3
3
|
import { listInputFieldsPlugin } from "./index";
|
|
4
|
+
import { getActionPlugin } from "../getAction";
|
|
5
|
+
import { getAppPlugin } from "../getApp";
|
|
6
|
+
import { listActionsPlugin } from "../listActions";
|
|
7
|
+
import { listAppsPlugin } from "../listApps";
|
|
4
8
|
import { createSdk } from "../../sdk";
|
|
5
9
|
const mockNeeds = [
|
|
6
10
|
{
|
|
@@ -35,12 +39,34 @@ const mockNeedsResponse = {
|
|
|
35
39
|
success: true,
|
|
36
40
|
needs: mockNeeds,
|
|
37
41
|
};
|
|
42
|
+
const mockActionsResponse = {
|
|
43
|
+
results: [
|
|
44
|
+
{
|
|
45
|
+
slug: "slack",
|
|
46
|
+
selected_api: "slack",
|
|
47
|
+
actions: [
|
|
48
|
+
{
|
|
49
|
+
key: "send_message",
|
|
50
|
+
name: "Send Message",
|
|
51
|
+
description: "Send a message to a channel",
|
|
52
|
+
type_of: "write",
|
|
53
|
+
type: "write",
|
|
54
|
+
id: "core:12345",
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
meta: {
|
|
60
|
+
next_cursor: null,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
38
63
|
describe("listInputFields plugin", () => {
|
|
39
64
|
let mockApiClient;
|
|
40
65
|
let mockGetVersionedImplementationId;
|
|
41
66
|
beforeEach(() => {
|
|
42
67
|
vi.clearAllMocks();
|
|
43
68
|
mockApiClient = {
|
|
69
|
+
get: vi.fn().mockResolvedValue(mockActionsResponse),
|
|
44
70
|
post: vi.fn().mockResolvedValue(mockNeedsResponse),
|
|
45
71
|
};
|
|
46
72
|
mockGetVersionedImplementationId = vi
|
|
@@ -52,7 +78,12 @@ describe("listInputFields plugin", () => {
|
|
|
52
78
|
api: mockApiClient,
|
|
53
79
|
meta: {},
|
|
54
80
|
getVersionedImplementationId: mockGetVersionedImplementationId,
|
|
55
|
-
})
|
|
81
|
+
})
|
|
82
|
+
.addPlugin(listAppsPlugin)
|
|
83
|
+
.addPlugin(listActionsPlugin)
|
|
84
|
+
.addPlugin(getAppPlugin)
|
|
85
|
+
.addPlugin(getActionPlugin)
|
|
86
|
+
.addPlugin(listInputFieldsPlugin);
|
|
56
87
|
}
|
|
57
88
|
describe("schema validation", () => {
|
|
58
89
|
it("should throw validation error for missing required fields", () => {
|
|
@@ -255,19 +286,19 @@ describe("listInputFields plugin", () => {
|
|
|
255
286
|
it("should throw ZapierApiError when API response indicates failure", async () => {
|
|
256
287
|
mockApiClient.post = vi.fn().mockResolvedValue({
|
|
257
288
|
success: false,
|
|
258
|
-
errors: ["
|
|
289
|
+
errors: ["Authentication failed", "Invalid credentials"],
|
|
259
290
|
});
|
|
260
291
|
const sdk = createTestSdk();
|
|
261
292
|
await expect(sdk.listInputFields({
|
|
262
293
|
appKey: "slack",
|
|
263
294
|
actionType: "write",
|
|
264
|
-
actionKey: "
|
|
295
|
+
actionKey: "send_message", // Use valid action so we get to the POST call
|
|
265
296
|
})).rejects.toThrow(ZapierApiError);
|
|
266
297
|
await expect(sdk.listInputFields({
|
|
267
298
|
appKey: "slack",
|
|
268
299
|
actionType: "write",
|
|
269
|
-
actionKey: "
|
|
270
|
-
})).rejects.toThrow("Failed to get action fields:
|
|
300
|
+
actionKey: "send_message", // Use valid action so we get to the POST call
|
|
301
|
+
})).rejects.toThrow("Failed to get action fields: Authentication failed, Invalid credentials");
|
|
271
302
|
});
|
|
272
303
|
it("should handle API errors gracefully", async () => {
|
|
273
304
|
mockApiClient.post = vi
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/runAction/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EACL,eAAe,EACf,KAAK,gBAAgB,EAEtB,MAAM,WAAW,CAAC;AAQnB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAQtD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAExE,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,CAAC,OAAO,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC;QACjD,IAAI,EAAE,GAAG,EAAE,CAAC;QACZ,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC,GACA,aAAa,CAAC;QAAE,IAAI,EAAE,GAAG,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG;QACpD,KAAK,IAAI,aAAa,CAAC,GAAG,CAAC,CAAC;KAC7B,CAAC;IACJ,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,SAAS,EAAE;gBACT,WAAW,EAAE,OAAO,eAAe,CAAC;aACrC,CAAC;SACH,CAAC;KACH,CAAC;CACH;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/runAction/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EACL,eAAe,EACf,KAAK,gBAAgB,EAEtB,MAAM,WAAW,CAAC;AAQnB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAQtD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAExE,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,CAAC,OAAO,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC;QACjD,IAAI,EAAE,GAAG,EAAE,CAAC;QACZ,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC,GACA,aAAa,CAAC;QAAE,IAAI,EAAE,GAAG,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG;QACpD,KAAK,IAAI,aAAa,CAAC,GAAG,CAAC,CAAC;KAC7B,CAAC;IACJ,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,SAAS,EAAE;gBACT,WAAW,EAAE,OAAO,eAAe,CAAC;aACrC,CAAC;SACH,CAAC;KACH,CAAC;CACH;AAgFD,eAAO,MAAM,eAAe,EAAE,MAAM,CAClC,UAAU,CAAC,uBAAuB,GAAG,oBAAoB,CAAC,EAAE,uCAAuC;AACnG;IACE,GAAG,EAAE,SAAS,CAAC;IACf,4BAA4B,EAAE,4BAA4B,CAAC;CAC5D,EAAE,0BAA0B;AAC7B,uBAAuB,CAwFxB,CAAC"}
|
|
@@ -4,7 +4,7 @@ import { ZapierValidationError, ZapierConfigurationError, ZapierActionError, } f
|
|
|
4
4
|
import { createPaginatedFunction } from "../../utils/function-utils";
|
|
5
5
|
import { appKeyResolver, actionTypeResolver, actionKeyResolver, authenticationIdResolver, inputsResolver, } from "../../resolvers";
|
|
6
6
|
async function executeAction(actionOptions) {
|
|
7
|
-
const { api, context, appKey, actionKey, actionType, executionOptions, authenticationId, } = actionOptions;
|
|
7
|
+
const { api, context, appKey, actionId, actionKey, actionType, executionOptions, authenticationId, } = actionOptions;
|
|
8
8
|
// Use the manifest plugin to get the current implementation ID
|
|
9
9
|
const selectedApi = await context.getVersionedImplementationId(appKey);
|
|
10
10
|
if (!selectedApi) {
|
|
@@ -13,6 +13,7 @@ async function executeAction(actionOptions) {
|
|
|
13
13
|
// Step 1: POST to /actions/v1/runs to start execution
|
|
14
14
|
const runRequestData = {
|
|
15
15
|
selected_api: selectedApi,
|
|
16
|
+
action_id: actionId,
|
|
16
17
|
action_key: actionKey,
|
|
17
18
|
action_type: actionType,
|
|
18
19
|
inputs: executionOptions.inputs || {},
|
|
@@ -47,11 +48,15 @@ export const runActionPlugin = ({ sdk, context }) => {
|
|
|
47
48
|
if (actionData.data.action_type !== actionType) {
|
|
48
49
|
throw new ZapierValidationError(`Action type mismatch: expected ${actionType}, got ${actionData.data.action_type}`);
|
|
49
50
|
}
|
|
51
|
+
const actionId = actionData.data.id;
|
|
50
52
|
// Execute the action using the Actions API (supports all action types)
|
|
51
53
|
const result = await executeAction({
|
|
52
54
|
api,
|
|
53
55
|
context,
|
|
54
56
|
appKey,
|
|
57
|
+
// Some actions require the action ID to run them, but technically the ID is not guaranteed to be available when
|
|
58
|
+
// we retrieve actions (probably legacy reasons), so we just pass along all the things!
|
|
59
|
+
actionId,
|
|
55
60
|
actionKey,
|
|
56
61
|
actionType,
|
|
57
62
|
executionOptions: { inputs },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/schemas/App.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/schemas/App.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAMtE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BzB,CAAC;AAMF,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC"}
|
package/dist/schemas/App.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { withFormatter } from "../utils/schema-utils";
|
|
3
3
|
import { ImplementationMetaSchema } from "../api/schemas";
|
|
4
|
+
import { toSnakeCase } from "../utils/string-utils";
|
|
4
5
|
// ============================================================================
|
|
5
6
|
// App Item Schema (extends ImplementationMetaSchema with transformed fields)
|
|
6
7
|
// ============================================================================
|
|
@@ -10,10 +11,19 @@ export const AppItemSchema = withFormatter(ImplementationMetaSchema.omit({ name:
|
|
|
10
11
|
implementation_id: z.string(), // Mapped from id (full versioned ID)
|
|
11
12
|
}), {
|
|
12
13
|
format: (item) => {
|
|
14
|
+
// Create additional keys if slug exists
|
|
15
|
+
const additionalKeys = [];
|
|
16
|
+
if (item.slug && item.slug !== item.key) {
|
|
17
|
+
additionalKeys.push(item.slug);
|
|
18
|
+
const snakeCaseSlug = toSnakeCase(item.slug);
|
|
19
|
+
if (snakeCaseSlug !== item.slug && snakeCaseSlug !== item.key) {
|
|
20
|
+
additionalKeys.push(snakeCaseSlug);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
13
23
|
return {
|
|
14
24
|
title: item.title,
|
|
15
25
|
key: item.key,
|
|
16
|
-
keys: [item.
|
|
26
|
+
keys: [item.key, ...additionalKeys],
|
|
17
27
|
description: item.description,
|
|
18
28
|
details: [],
|
|
19
29
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain-utils.d.ts","sourceRoot":"","sources":["../../src/utils/domain-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE/E;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,GACnB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAQ9B;AAED;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAClD,kBAAkB,EAAE,kBAAkB,GACrC,OAAO,
|
|
1
|
+
{"version":3,"file":"domain-utils.d.ts","sourceRoot":"","sources":["../../src/utils/domain-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE/E;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,GACnB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAQ9B;AAED;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAClD,kBAAkB,EAAE,kBAAkB,GACrC,OAAO,CAYT;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,cAAc,EACpB,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GACvD,kBAAkB,CAqCpB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAsB9D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG;IAC9D,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAgCA;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG;IACrD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAUA;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5C;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOtD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAY1D;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,kBAAmB,SAAQ,UAAU;IACpD,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAI9C;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAkBvD;AAED,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,UAAU,GACrB,UAAU,IAAI,kBAAkB,CAElC;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,kBAAkB,GAAG,MAAM,CAEzE"}
|
|
@@ -27,18 +27,14 @@ export function splitVersionedKey(versionedKey) {
|
|
|
27
27
|
* @returns Normalized AppItem with essential fields only
|
|
28
28
|
*/
|
|
29
29
|
export function normalizeImplementationMetaToAppItem(implementationMeta) {
|
|
30
|
-
// Extract API name and version from the implementation ID
|
|
31
30
|
const [selectedApi, appVersion] = splitVersionedKey(implementationMeta.id);
|
|
32
|
-
// Destructure to exclude id and name before spreading
|
|
33
31
|
const { id, name, ...restOfImplementationMeta } = implementationMeta;
|
|
34
32
|
return {
|
|
35
|
-
// Pass through all ImplementationMeta fields except id and name
|
|
36
33
|
...restOfImplementationMeta,
|
|
37
|
-
// Transform key fields
|
|
38
34
|
title: name,
|
|
39
35
|
key: selectedApi,
|
|
40
|
-
implementation_id: id,
|
|
41
|
-
version: appVersion,
|
|
36
|
+
implementation_id: id,
|
|
37
|
+
version: appVersion,
|
|
42
38
|
};
|
|
43
39
|
}
|
|
44
40
|
/**
|
|
@@ -9,4 +9,13 @@
|
|
|
9
9
|
* - mixed formats: "first_name-value" → "First Name Value"
|
|
10
10
|
*/
|
|
11
11
|
export declare function toTitleCase(input: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Converts a string to snake_case, handling various input formats:
|
|
14
|
+
* - camelCase: "firstName" → "first_name"
|
|
15
|
+
* - kebab-case: "first-name" → "first_name"
|
|
16
|
+
* - title case: "First Name" → "first_name"
|
|
17
|
+
* - mixed formats: "first-Name Value" → "first_name_value"
|
|
18
|
+
* - starts with number: "123abc" → "_123abc"
|
|
19
|
+
*/
|
|
20
|
+
export declare function toSnakeCase(input: string): string;
|
|
12
21
|
//# sourceMappingURL=string-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"string-utils.d.ts","sourceRoot":"","sources":["../../src/utils/string-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAcjD"}
|
|
1
|
+
{"version":3,"file":"string-utils.d.ts","sourceRoot":"","sources":["../../src/utils/string-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAcjD;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBjD"}
|
|
@@ -21,3 +21,28 @@ export function toTitleCase(input) {
|
|
|
21
21
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
22
22
|
.join(" "));
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Converts a string to snake_case, handling various input formats:
|
|
26
|
+
* - camelCase: "firstName" → "first_name"
|
|
27
|
+
* - kebab-case: "first-name" → "first_name"
|
|
28
|
+
* - title case: "First Name" → "first_name"
|
|
29
|
+
* - mixed formats: "first-Name Value" → "first_name_value"
|
|
30
|
+
* - starts with number: "123abc" → "_123abc"
|
|
31
|
+
*/
|
|
32
|
+
export function toSnakeCase(input) {
|
|
33
|
+
let result = input
|
|
34
|
+
// insert underscore before capital letters (handles camelCase)
|
|
35
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
36
|
+
// replace spaces and dashes with underscores
|
|
37
|
+
.replace(/[\s\-]+/g, "_")
|
|
38
|
+
// replace multiple underscores with single underscore
|
|
39
|
+
.replace(/_+/g, "_")
|
|
40
|
+
// remove leading/trailing underscores and convert to lowercase
|
|
41
|
+
.replace(/^_|_$/g, "")
|
|
42
|
+
.toLowerCase();
|
|
43
|
+
// If the result starts with a number, prefix with underscore
|
|
44
|
+
if (/^[0-9]/.test(result)) {
|
|
45
|
+
result = "_" + result;
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
package/package.json
CHANGED
package/src/api/debug.ts
CHANGED
|
@@ -7,13 +7,56 @@
|
|
|
7
7
|
|
|
8
8
|
import type { DebugLogger } from "./types";
|
|
9
9
|
|
|
10
|
+
import type { InspectOptions } from "util";
|
|
11
|
+
|
|
12
|
+
type UtilModule = {
|
|
13
|
+
inspect: (object: unknown, options?: InspectOptions) => string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// We don't want to import util since it might not work in a browser. So we'll lazily load it and cache the result so
|
|
17
|
+
// we can use it synchronously after that. The types above are just to make our linter happy.
|
|
18
|
+
let utilModule: UtilModule | null = null;
|
|
19
|
+
let utilPromise: Promise<UtilModule | null> | null = null;
|
|
20
|
+
|
|
10
21
|
export function createDebugLogger(enabled: boolean): DebugLogger {
|
|
11
22
|
if (!enabled) {
|
|
12
23
|
return () => {}; // No-op function when debug is disabled
|
|
13
24
|
}
|
|
14
25
|
|
|
26
|
+
// Try to load util module on first use.
|
|
27
|
+
if (!utilPromise) {
|
|
28
|
+
utilPromise = import("util")
|
|
29
|
+
.then((util) => {
|
|
30
|
+
utilModule = util;
|
|
31
|
+
return util;
|
|
32
|
+
})
|
|
33
|
+
.catch(() => {
|
|
34
|
+
utilModule = null;
|
|
35
|
+
return null;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
15
39
|
return (message: string, data?: unknown) => {
|
|
16
|
-
|
|
40
|
+
if (data === undefined || data === "") {
|
|
41
|
+
console.log(`[Zapier SDK] ${message}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Use cached module if available for sync logging. Yes, this means if the util module isn't yet loaded, we'll fall
|
|
46
|
+
// back to a plain old console.log, which might mean we don't see proper formatting/nesting. But that's unlikely
|
|
47
|
+
// since this logging happens during async fetching. Alternatively, we could switch to async logging, but this
|
|
48
|
+
// seems fine since we have to have a fallback anyway.
|
|
49
|
+
if (utilModule) {
|
|
50
|
+
const formatted = utilModule.inspect(data, {
|
|
51
|
+
colors: true,
|
|
52
|
+
depth: null,
|
|
53
|
+
breakLength: 80,
|
|
54
|
+
});
|
|
55
|
+
console.log(`[Zapier SDK] ${message}`, formatted);
|
|
56
|
+
} else {
|
|
57
|
+
// Fallback - browser will handle formatting automatically
|
|
58
|
+
console.log(`[Zapier SDK] ${message}`, data);
|
|
59
|
+
}
|
|
17
60
|
};
|
|
18
61
|
}
|
|
19
62
|
|
package/src/index.ts
CHANGED
|
@@ -42,6 +42,9 @@ export { isPositional, PositionalMetadata } from "./utils/schema-utils";
|
|
|
42
42
|
export { createFunction } from "./utils/function-utils";
|
|
43
43
|
export type { FormattedItem, FormatMetadata } from "./utils/schema-utils";
|
|
44
44
|
|
|
45
|
+
// Export string utilities
|
|
46
|
+
export { toSnakeCase, toTitleCase } from "./utils/string-utils";
|
|
47
|
+
|
|
45
48
|
// Export resolver utilities for CLI
|
|
46
49
|
export * from "./resolvers";
|
|
47
50
|
|
|
@@ -36,9 +36,12 @@ export const getActionPlugin: Plugin<
|
|
|
36
36
|
// Use the listActions function from the SDK to search for the specific action
|
|
37
37
|
const actionsResult = await sdk.listActions({ appKey });
|
|
38
38
|
|
|
39
|
-
// Search through all actions to find the matching one
|
|
39
|
+
// Search through all actions to find the matching one (by key or ID)
|
|
40
40
|
for (const action of actionsResult.data) {
|
|
41
|
-
if (
|
|
41
|
+
if (
|
|
42
|
+
(action.key === actionKey || action.id === actionKey) &&
|
|
43
|
+
action.action_type === actionType
|
|
44
|
+
) {
|
|
42
45
|
return { data: action };
|
|
43
46
|
}
|
|
44
47
|
}
|
|
@@ -5,6 +5,10 @@ import {
|
|
|
5
5
|
ZapierApiError,
|
|
6
6
|
} from "../../types/errors";
|
|
7
7
|
import { listInputFieldsPlugin } from "./index";
|
|
8
|
+
import { getActionPlugin } from "../getAction";
|
|
9
|
+
import { getAppPlugin } from "../getApp";
|
|
10
|
+
import { listActionsPlugin } from "../listActions";
|
|
11
|
+
import { listAppsPlugin } from "../listApps";
|
|
8
12
|
import { createSdk } from "../../sdk";
|
|
9
13
|
import type { ApiClient } from "../../api";
|
|
10
14
|
import type { ListInputFieldsOptions } from "./schemas";
|
|
@@ -45,6 +49,28 @@ const mockNeedsResponse: NeedsResponse = {
|
|
|
45
49
|
needs: mockNeeds,
|
|
46
50
|
};
|
|
47
51
|
|
|
52
|
+
const mockActionsResponse = {
|
|
53
|
+
results: [
|
|
54
|
+
{
|
|
55
|
+
slug: "slack",
|
|
56
|
+
selected_api: "slack",
|
|
57
|
+
actions: [
|
|
58
|
+
{
|
|
59
|
+
key: "send_message",
|
|
60
|
+
name: "Send Message",
|
|
61
|
+
description: "Send a message to a channel",
|
|
62
|
+
type_of: "write",
|
|
63
|
+
type: "write",
|
|
64
|
+
id: "core:12345",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
meta: {
|
|
70
|
+
next_cursor: null,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
48
74
|
describe("listInputFields plugin", () => {
|
|
49
75
|
let mockApiClient: ApiClient;
|
|
50
76
|
let mockGetVersionedImplementationId: any;
|
|
@@ -52,6 +78,7 @@ describe("listInputFields plugin", () => {
|
|
|
52
78
|
beforeEach(() => {
|
|
53
79
|
vi.clearAllMocks();
|
|
54
80
|
mockApiClient = {
|
|
81
|
+
get: vi.fn().mockResolvedValue(mockActionsResponse),
|
|
55
82
|
post: vi.fn().mockResolvedValue(mockNeedsResponse),
|
|
56
83
|
} as Partial<ApiClient> as ApiClient;
|
|
57
84
|
|
|
@@ -69,7 +96,12 @@ describe("listInputFields plugin", () => {
|
|
|
69
96
|
meta: {},
|
|
70
97
|
getVersionedImplementationId: mockGetVersionedImplementationId,
|
|
71
98
|
},
|
|
72
|
-
)
|
|
99
|
+
)
|
|
100
|
+
.addPlugin(listAppsPlugin as any)
|
|
101
|
+
.addPlugin(listActionsPlugin as any)
|
|
102
|
+
.addPlugin(getAppPlugin as any)
|
|
103
|
+
.addPlugin(getActionPlugin as any)
|
|
104
|
+
.addPlugin(listInputFieldsPlugin as any);
|
|
73
105
|
}
|
|
74
106
|
|
|
75
107
|
describe("schema validation", () => {
|
|
@@ -319,7 +351,7 @@ describe("listInputFields plugin", () => {
|
|
|
319
351
|
it("should throw ZapierApiError when API response indicates failure", async () => {
|
|
320
352
|
mockApiClient.post = vi.fn().mockResolvedValue({
|
|
321
353
|
success: false,
|
|
322
|
-
errors: ["
|
|
354
|
+
errors: ["Authentication failed", "Invalid credentials"],
|
|
323
355
|
});
|
|
324
356
|
|
|
325
357
|
const sdk = createTestSdk();
|
|
@@ -327,7 +359,7 @@ describe("listInputFields plugin", () => {
|
|
|
327
359
|
sdk.listInputFields({
|
|
328
360
|
appKey: "slack",
|
|
329
361
|
actionType: "write",
|
|
330
|
-
actionKey: "
|
|
362
|
+
actionKey: "send_message", // Use valid action so we get to the POST call
|
|
331
363
|
}),
|
|
332
364
|
).rejects.toThrow(ZapierApiError);
|
|
333
365
|
|
|
@@ -335,10 +367,10 @@ describe("listInputFields plugin", () => {
|
|
|
335
367
|
sdk.listInputFields({
|
|
336
368
|
appKey: "slack",
|
|
337
369
|
actionType: "write",
|
|
338
|
-
actionKey: "
|
|
370
|
+
actionKey: "send_message", // Use valid action so we get to the POST call
|
|
339
371
|
}),
|
|
340
372
|
).rejects.toThrow(
|
|
341
|
-
"Failed to get action fields:
|
|
373
|
+
"Failed to get action fields: Authentication failed, Invalid credentials",
|
|
342
374
|
);
|
|
343
375
|
});
|
|
344
376
|
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { ZapierConfigurationError, ZapierApiError } from "../../types/errors";
|
|
16
16
|
import { createPaginatedFunction } from "../../utils/function-utils";
|
|
17
17
|
import type { GetAppPluginProvides } from "../getApp";
|
|
18
|
+
import type { GetActionPluginProvides } from "../getAction";
|
|
18
19
|
import type { GetVersionedImplementationId } from "../manifest/schemas";
|
|
19
20
|
import {
|
|
20
21
|
appKeyResolver,
|
|
@@ -196,13 +197,13 @@ export interface ListInputFieldsPluginProvides {
|
|
|
196
197
|
}
|
|
197
198
|
|
|
198
199
|
export const listInputFieldsPlugin: Plugin<
|
|
199
|
-
GetSdkType<GetAppPluginProvides>, // requires getApp in SDK
|
|
200
|
+
GetSdkType<GetAppPluginProvides & GetActionPluginProvides>, // requires getApp and getAction in SDK
|
|
200
201
|
{
|
|
201
202
|
api: ApiClient;
|
|
202
203
|
getVersionedImplementationId: GetVersionedImplementationId;
|
|
203
204
|
}, // requires api and getVersionedImplementationId in context
|
|
204
205
|
ListInputFieldsPluginProvides
|
|
205
|
-
> = ({ context }) => {
|
|
206
|
+
> = ({ sdk, context }) => {
|
|
206
207
|
const listInputFields = createPaginatedFunction(
|
|
207
208
|
async function listInputFieldsPage(
|
|
208
209
|
options: ListInputFieldsOptions & { cursor?: string } & {
|
|
@@ -226,10 +227,16 @@ export const listInputFieldsPlugin: Plugin<
|
|
|
226
227
|
);
|
|
227
228
|
}
|
|
228
229
|
|
|
230
|
+
const { data: action } = await sdk.getAction({
|
|
231
|
+
appKey,
|
|
232
|
+
actionType,
|
|
233
|
+
actionKey,
|
|
234
|
+
});
|
|
235
|
+
|
|
229
236
|
// Build needs request
|
|
230
237
|
const needsRequest: NeedsRequest = {
|
|
231
238
|
selected_api: selectedApi,
|
|
232
|
-
action:
|
|
239
|
+
action: action.key,
|
|
233
240
|
type_of: actionType,
|
|
234
241
|
params: inputs || {},
|
|
235
242
|
};
|
|
@@ -46,6 +46,7 @@ interface ExecuteActionOptions {
|
|
|
46
46
|
getVersionedImplementationId: GetVersionedImplementationId;
|
|
47
47
|
};
|
|
48
48
|
appKey: string;
|
|
49
|
+
actionId?: string;
|
|
49
50
|
actionKey: string;
|
|
50
51
|
actionType: string;
|
|
51
52
|
executionOptions: { inputs: Record<string, unknown> };
|
|
@@ -61,6 +62,7 @@ async function executeAction(actionOptions: ExecuteActionOptions): Promise<{
|
|
|
61
62
|
api,
|
|
62
63
|
context,
|
|
63
64
|
appKey,
|
|
65
|
+
actionId,
|
|
64
66
|
actionKey,
|
|
65
67
|
actionType,
|
|
66
68
|
executionOptions,
|
|
@@ -80,12 +82,14 @@ async function executeAction(actionOptions: ExecuteActionOptions): Promise<{
|
|
|
80
82
|
// Step 1: POST to /actions/v1/runs to start execution
|
|
81
83
|
const runRequestData: {
|
|
82
84
|
selected_api: string;
|
|
85
|
+
action_id?: string;
|
|
83
86
|
action_key: string;
|
|
84
87
|
action_type: string;
|
|
85
88
|
inputs: Record<string, unknown>;
|
|
86
89
|
authentication_id?: number | null;
|
|
87
90
|
} = {
|
|
88
91
|
selected_api: selectedApi,
|
|
92
|
+
action_id: actionId,
|
|
89
93
|
action_key: actionKey,
|
|
90
94
|
action_type: actionType,
|
|
91
95
|
inputs: executionOptions.inputs || {},
|
|
@@ -148,11 +152,16 @@ export const runActionPlugin: Plugin<
|
|
|
148
152
|
);
|
|
149
153
|
}
|
|
150
154
|
|
|
155
|
+
const actionId = actionData.data.id;
|
|
156
|
+
|
|
151
157
|
// Execute the action using the Actions API (supports all action types)
|
|
152
158
|
const result = await executeAction({
|
|
153
159
|
api,
|
|
154
160
|
context,
|
|
155
161
|
appKey,
|
|
162
|
+
// Some actions require the action ID to run them, but technically the ID is not guaranteed to be available when
|
|
163
|
+
// we retrieve actions (probably legacy reasons), so we just pass along all the things!
|
|
164
|
+
actionId,
|
|
156
165
|
actionKey,
|
|
157
166
|
actionType,
|
|
158
167
|
executionOptions: { inputs },
|
package/src/schemas/App.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { withFormatter } from "../utils/schema-utils";
|
|
3
3
|
import { ImplementationMetaSchema } from "../api/schemas";
|
|
4
|
+
import { toSnakeCase } from "../utils/string-utils";
|
|
4
5
|
|
|
5
6
|
export { FormattedItem, FormatMetadata } from "../utils/schema-utils";
|
|
6
7
|
|
|
@@ -16,10 +17,20 @@ export const AppItemSchema = withFormatter(
|
|
|
16
17
|
}),
|
|
17
18
|
{
|
|
18
19
|
format: (item) => {
|
|
20
|
+
// Create additional keys if slug exists
|
|
21
|
+
const additionalKeys = [];
|
|
22
|
+
if (item.slug && item.slug !== item.key) {
|
|
23
|
+
additionalKeys.push(item.slug);
|
|
24
|
+
const snakeCaseSlug = toSnakeCase(item.slug);
|
|
25
|
+
if (snakeCaseSlug !== item.slug && snakeCaseSlug !== item.key) {
|
|
26
|
+
additionalKeys.push(snakeCaseSlug);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
19
30
|
return {
|
|
20
31
|
title: item.title,
|
|
21
32
|
key: item.key,
|
|
22
|
-
keys: [item.
|
|
33
|
+
keys: [item.key, ...additionalKeys],
|
|
23
34
|
description: item.description,
|
|
24
35
|
details: [],
|
|
25
36
|
};
|
|
@@ -36,20 +36,16 @@ export function splitVersionedKey(
|
|
|
36
36
|
export function normalizeImplementationMetaToAppItem(
|
|
37
37
|
implementationMeta: ImplementationMeta,
|
|
38
38
|
): AppItem {
|
|
39
|
-
// Extract API name and version from the implementation ID
|
|
40
39
|
const [selectedApi, appVersion] = splitVersionedKey(implementationMeta.id);
|
|
41
40
|
|
|
42
|
-
// Destructure to exclude id and name before spreading
|
|
43
41
|
const { id, name, ...restOfImplementationMeta } = implementationMeta;
|
|
44
42
|
|
|
45
43
|
return {
|
|
46
|
-
// Pass through all ImplementationMeta fields except id and name
|
|
47
44
|
...restOfImplementationMeta,
|
|
48
|
-
// Transform key fields
|
|
49
45
|
title: name,
|
|
50
46
|
key: selectedApi,
|
|
51
|
-
implementation_id: id,
|
|
52
|
-
version: appVersion,
|
|
47
|
+
implementation_id: id,
|
|
48
|
+
version: appVersion,
|
|
53
49
|
};
|
|
54
50
|
}
|
|
55
51
|
|
|
@@ -24,3 +24,31 @@ export function toTitleCase(input: string): string {
|
|
|
24
24
|
.join(" ")
|
|
25
25
|
);
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Converts a string to snake_case, handling various input formats:
|
|
30
|
+
* - camelCase: "firstName" → "first_name"
|
|
31
|
+
* - kebab-case: "first-name" → "first_name"
|
|
32
|
+
* - title case: "First Name" → "first_name"
|
|
33
|
+
* - mixed formats: "first-Name Value" → "first_name_value"
|
|
34
|
+
* - starts with number: "123abc" → "_123abc"
|
|
35
|
+
*/
|
|
36
|
+
export function toSnakeCase(input: string): string {
|
|
37
|
+
let result = input
|
|
38
|
+
// insert underscore before capital letters (handles camelCase)
|
|
39
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
40
|
+
// replace spaces and dashes with underscores
|
|
41
|
+
.replace(/[\s\-]+/g, "_")
|
|
42
|
+
// replace multiple underscores with single underscore
|
|
43
|
+
.replace(/_+/g, "_")
|
|
44
|
+
// remove leading/trailing underscores and convert to lowercase
|
|
45
|
+
.replace(/^_|_$/g, "")
|
|
46
|
+
.toLowerCase();
|
|
47
|
+
|
|
48
|
+
// If the result starts with a number, prefix with underscore
|
|
49
|
+
if (/^[0-9]/.test(result)) {
|
|
50
|
+
result = "_" + result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return result;
|
|
54
|
+
}
|