@zapier/zapier-sdk 0.10.0 → 0.11.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 +6 -0
- package/README.md +1 -1
- package/dist/index.cjs +130 -15
- package/dist/index.d.mts +121 -15
- package/dist/index.mjs +130 -15
- package/dist/plugins/listInputFields/index.d.ts +4 -4
- package/dist/plugins/listInputFields/index.d.ts.map +1 -1
- package/dist/plugins/listInputFields/index.js +61 -7
- package/dist/plugins/listInputFields/schemas.d.ts +3 -3
- package/dist/plugins/listInputFields/schemas.d.ts.map +1 -1
- package/dist/resolvers/inputFieldKey.d.ts.map +1 -1
- package/dist/resolvers/inputFieldKey.js +25 -1
- package/dist/resolvers/inputs.d.ts +12 -8
- package/dist/resolvers/inputs.d.ts.map +1 -1
- package/dist/resolvers/inputs.js +23 -4
- package/dist/schemas/Field.d.ts +109 -1
- package/dist/schemas/Field.d.ts.map +1 -1
- package/dist/schemas/Field.js +41 -2
- package/dist/types/domain.d.ts +17 -1
- package/dist/types/domain.d.ts.map +1 -1
- package/dist/utils/string-utils.d.ts +12 -0
- package/dist/utils/string-utils.d.ts.map +1 -0
- package/dist/utils/string-utils.js +23 -0
- package/dist/utils/string-utils.test.d.ts +2 -0
- package/dist/utils/string-utils.test.d.ts.map +1 -0
- package/dist/utils/string-utils.test.js +36 -0
- package/package.json +1 -1
- package/src/plugins/listInputFields/index.ts +73 -13
- package/src/plugins/listInputFields/schemas.ts +6 -3
- package/src/resolvers/inputFieldKey.ts +33 -1
- package/src/resolvers/inputs.ts +38 -12
- package/src/schemas/Field.ts +75 -5
- package/src/types/domain.ts +24 -1
- package/src/utils/string-utils.test.ts +45 -0
- package/src/utils/string-utils.ts +26 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { Plugin, GetSdkType } from "../../types/plugin";
|
|
2
2
|
import type { ApiClient } from "../../api";
|
|
3
3
|
import type { NeedsRequest, NeedsResponse, Need } from "../../api/types";
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
InputFieldItem,
|
|
6
|
+
InfoFieldItem,
|
|
7
|
+
FieldsetItem,
|
|
8
|
+
RootFieldItem,
|
|
9
|
+
} from "../../types/domain";
|
|
5
10
|
import {
|
|
6
11
|
ListInputFieldsSchema,
|
|
7
12
|
type ListInputFieldsOptions,
|
|
@@ -18,7 +23,8 @@ import {
|
|
|
18
23
|
authenticationIdResolver,
|
|
19
24
|
inputsAllOptionalResolver,
|
|
20
25
|
} from "../../resolvers";
|
|
21
|
-
import {
|
|
26
|
+
import { RootFieldItemSchema } from "../../schemas/Field";
|
|
27
|
+
import { toTitleCase } from "../../utils/string-utils";
|
|
22
28
|
|
|
23
29
|
// Enums for input field transformation
|
|
24
30
|
enum InputFieldType {
|
|
@@ -101,7 +107,7 @@ function transformNeedToInputFieldItem(need: Need): InputFieldItem {
|
|
|
101
107
|
const itemsType = getItemsTypeFromNeed(need);
|
|
102
108
|
|
|
103
109
|
return {
|
|
104
|
-
|
|
110
|
+
type: "input_field",
|
|
105
111
|
key: need.key,
|
|
106
112
|
default_value: need.default || "",
|
|
107
113
|
depends_on: need.depends_on || [],
|
|
@@ -116,12 +122,68 @@ function transformNeedToInputFieldItem(need: Need): InputFieldItem {
|
|
|
116
122
|
};
|
|
117
123
|
}
|
|
118
124
|
|
|
125
|
+
// Transform Need to InfoFieldItem (for copy fields)
|
|
126
|
+
function transformNeedToInfoFieldItem(need: Need): InfoFieldItem {
|
|
127
|
+
return {
|
|
128
|
+
type: "info_field",
|
|
129
|
+
key: need.key,
|
|
130
|
+
description: need.help_text || "",
|
|
131
|
+
title: need.label,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Transform needs to fields (similar to partnerapi compute_root_fieldset_from_needs)
|
|
136
|
+
function transformNeedsToFields(needs: Need[]): RootFieldItem[] {
|
|
137
|
+
const rootFields: (InputFieldItem | InfoFieldItem | FieldsetItem)[] = [];
|
|
138
|
+
const fieldsetMap = new Map<string, FieldsetItem>();
|
|
139
|
+
|
|
140
|
+
for (const need of needs) {
|
|
141
|
+
if (need.computed) {
|
|
142
|
+
// Computed needs (i.e zap_id for CodeAPI steps) are not shown by the
|
|
143
|
+
// editor, hide them here too.
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// If parent_key is set, we need to ensure that a corresponding
|
|
148
|
+
// fieldset exists, since we'll be adding this need to that fieldset
|
|
149
|
+
if (need.parent_key) {
|
|
150
|
+
let fieldset = fieldsetMap.get(need.parent_key);
|
|
151
|
+
if (!fieldset) {
|
|
152
|
+
fieldset = {
|
|
153
|
+
type: "fieldset",
|
|
154
|
+
key: need.parent_key,
|
|
155
|
+
title: toTitleCase(need.parent_key),
|
|
156
|
+
fields: [],
|
|
157
|
+
};
|
|
158
|
+
fieldsetMap.set(need.parent_key, fieldset);
|
|
159
|
+
rootFields.push(fieldset);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Add field to fieldset
|
|
163
|
+
if (need.type === "copy" && need.help_text) {
|
|
164
|
+
fieldset.fields.push(transformNeedToInfoFieldItem(need));
|
|
165
|
+
} else {
|
|
166
|
+
fieldset.fields.push(transformNeedToInputFieldItem(need));
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
// Add field to root
|
|
170
|
+
if (need.type === "copy" && need.help_text) {
|
|
171
|
+
rootFields.push(transformNeedToInfoFieldItem(need));
|
|
172
|
+
} else {
|
|
173
|
+
rootFields.push(transformNeedToInputFieldItem(need));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return rootFields;
|
|
179
|
+
}
|
|
180
|
+
|
|
119
181
|
export interface ListInputFieldsPluginProvides {
|
|
120
182
|
listInputFields: (options?: ListInputFieldsOptions) => Promise<{
|
|
121
|
-
data:
|
|
183
|
+
data: RootFieldItem[];
|
|
122
184
|
}> &
|
|
123
|
-
AsyncIterable<{ data:
|
|
124
|
-
items(): AsyncIterable<InputFieldItem>;
|
|
185
|
+
AsyncIterable<{ data: RootFieldItem[]; nextCursor?: string }> & {
|
|
186
|
+
items(): AsyncIterable<InputFieldItem | InfoFieldItem | FieldsetItem>;
|
|
125
187
|
};
|
|
126
188
|
context: {
|
|
127
189
|
meta: {
|
|
@@ -189,13 +251,11 @@ export const listInputFieldsPlugin: Plugin<
|
|
|
189
251
|
);
|
|
190
252
|
}
|
|
191
253
|
|
|
192
|
-
// Transform Need objects to
|
|
193
|
-
const
|
|
194
|
-
transformNeedToInputFieldItem,
|
|
195
|
-
);
|
|
254
|
+
// Transform Need objects to Root Fieldset with proper nesting
|
|
255
|
+
const rootFieldset = transformNeedsToFields(needsData.needs || []);
|
|
196
256
|
|
|
197
257
|
return {
|
|
198
|
-
data:
|
|
258
|
+
data: rootFieldset,
|
|
199
259
|
nextCursor: undefined, // No pagination needed since we return all input fields
|
|
200
260
|
};
|
|
201
261
|
},
|
|
@@ -209,9 +269,9 @@ export const listInputFieldsPlugin: Plugin<
|
|
|
209
269
|
listInputFields: {
|
|
210
270
|
categories: ["action"],
|
|
211
271
|
type: "list",
|
|
212
|
-
itemType: "
|
|
272
|
+
itemType: "RootFieldItem",
|
|
213
273
|
inputSchema: ListInputFieldsSchema,
|
|
214
|
-
outputSchema:
|
|
274
|
+
outputSchema: RootFieldItemSchema,
|
|
215
275
|
resolvers: {
|
|
216
276
|
appKey: appKeyResolver,
|
|
217
277
|
actionType: actionTypeResolver,
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
AuthenticationIdPropertySchema,
|
|
7
7
|
InputsPropertySchema,
|
|
8
8
|
} from "../../types/properties";
|
|
9
|
-
import type {
|
|
9
|
+
import type { RootFieldItem } from "../../types/domain";
|
|
10
10
|
import type { PaginatedSdkFunction } from "../../types/functions";
|
|
11
11
|
import type {
|
|
12
12
|
ZapierConfigurationError,
|
|
@@ -54,11 +54,14 @@ export type ListInputFieldsError =
|
|
|
54
54
|
|
|
55
55
|
// Page result structure
|
|
56
56
|
export interface ListInputFieldsPage {
|
|
57
|
-
data:
|
|
57
|
+
data: RootFieldItem[];
|
|
58
58
|
nextCursor?: string;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
// SDK function interface
|
|
62
62
|
export interface ListInputFieldsSdkFunction {
|
|
63
|
-
listInputFields: PaginatedSdkFunction<
|
|
63
|
+
listInputFields: PaginatedSdkFunction<
|
|
64
|
+
ListInputFieldsOptions,
|
|
65
|
+
RootFieldItem[]
|
|
66
|
+
>;
|
|
64
67
|
}
|
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
import type { DynamicResolver } from "../utils/schema-utils";
|
|
2
2
|
import type { InputFieldItem } from "../schemas/Field";
|
|
3
3
|
import type { ActionTypeProperty } from "../types/properties";
|
|
4
|
+
import type {
|
|
5
|
+
RootFieldItem,
|
|
6
|
+
InfoFieldItem,
|
|
7
|
+
FieldsetItem,
|
|
8
|
+
} from "../types/domain";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Helper function to flatten fieldsets into a flat array of input fields.
|
|
12
|
+
* Used to extract all input fields from nested fieldset structure for field selection.
|
|
13
|
+
*/
|
|
14
|
+
function flattenRootFieldset(rootFieldset: RootFieldItem[]): InputFieldItem[] {
|
|
15
|
+
const result: InputFieldItem[] = [];
|
|
16
|
+
|
|
17
|
+
function processItem(item: InputFieldItem | InfoFieldItem | FieldsetItem) {
|
|
18
|
+
if (item.type === "input_field") {
|
|
19
|
+
result.push(item);
|
|
20
|
+
} else if (item.type === "fieldset") {
|
|
21
|
+
// Recursively process fields in the fieldset
|
|
22
|
+
for (const field of item.fields) {
|
|
23
|
+
processItem(field);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Skip info fields as they're not input fields
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (const item of rootFieldset) {
|
|
30
|
+
processItem(item);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
4
35
|
|
|
5
36
|
export const inputFieldKeyResolver: DynamicResolver<
|
|
6
37
|
InputFieldItem,
|
|
@@ -22,7 +53,8 @@ export const inputFieldKeyResolver: DynamicResolver<
|
|
|
22
53
|
authenticationId: resolvedParams.authenticationId,
|
|
23
54
|
inputs: resolvedParams.inputs, // Pass along currently resolved inputs
|
|
24
55
|
});
|
|
25
|
-
|
|
56
|
+
// Flatten the fieldset structure for field selection
|
|
57
|
+
return flattenRootFieldset(fieldsResponse.data);
|
|
26
58
|
},
|
|
27
59
|
prompt: (fields) => ({
|
|
28
60
|
type: "list",
|
package/src/resolvers/inputs.ts
CHANGED
|
@@ -1,13 +1,41 @@
|
|
|
1
|
-
import type { FieldsResolver } from "../utils/schema-utils";
|
|
2
1
|
import type { ActionTypeProperty } from "../types/properties";
|
|
2
|
+
import type { RootFieldItem } from "../types/domain";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
// Helper function to recursively make all input fields optional while preserving structure
|
|
5
|
+
function makeFieldsOptional(fields: RootFieldItem[]): RootFieldItem[] {
|
|
6
|
+
return fields.map((field) => {
|
|
7
|
+
if (field.type === "input_field") {
|
|
8
|
+
return {
|
|
9
|
+
...field,
|
|
10
|
+
is_required: false,
|
|
11
|
+
};
|
|
12
|
+
} else if (field.type === "fieldset") {
|
|
13
|
+
return {
|
|
14
|
+
...field,
|
|
15
|
+
fields: makeFieldsOptional(field.fields),
|
|
16
|
+
};
|
|
17
|
+
} else {
|
|
18
|
+
// Info fields remain unchanged
|
|
19
|
+
return field;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Custom resolver type for fieldsets - returns the nested structure
|
|
25
|
+
type InputsResolver = {
|
|
26
|
+
type: "fields";
|
|
27
|
+
depends: readonly string[];
|
|
28
|
+
fetch: (
|
|
29
|
+
sdk: any,
|
|
30
|
+
resolvedParams: {
|
|
31
|
+
appKey: string;
|
|
32
|
+
actionKey: string;
|
|
33
|
+
actionType: ActionTypeProperty;
|
|
34
|
+
authenticationId: number;
|
|
35
|
+
inputs?: Record<string, unknown>;
|
|
36
|
+
},
|
|
37
|
+
) => Promise<RootFieldItem[]>;
|
|
38
|
+
};
|
|
11
39
|
|
|
12
40
|
export const inputsResolver: InputsResolver = {
|
|
13
41
|
type: "fields",
|
|
@@ -35,9 +63,7 @@ export const inputsAllOptionalResolver: InputsResolver = {
|
|
|
35
63
|
authenticationId: resolvedParams.authenticationId,
|
|
36
64
|
inputs: resolvedParams.inputs, // Pass along currently resolved inputs
|
|
37
65
|
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
is_required: false,
|
|
41
|
-
}));
|
|
66
|
+
// Return nested structure with all fields marked as optional.
|
|
67
|
+
return makeFieldsOptional(fieldsResponse.data);
|
|
42
68
|
},
|
|
43
69
|
};
|
package/src/schemas/Field.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { withFormatter } from "../utils/schema-utils";
|
|
3
3
|
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Base Field Schema
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
export const BaseFieldItemSchema = z.object({
|
|
9
|
+
type: z.string(), // "input_field", "info_field", or "fieldset"
|
|
10
|
+
key: z.string(), // From need.key
|
|
11
|
+
});
|
|
12
|
+
|
|
4
13
|
// ============================================================================
|
|
5
14
|
// Input Field Item Schema (extends API Need schema with computed fields and formatting)
|
|
6
15
|
// ============================================================================
|
|
7
16
|
|
|
8
17
|
export const InputFieldItemSchema = withFormatter(
|
|
9
|
-
|
|
10
|
-
|
|
18
|
+
BaseFieldItemSchema.extend({
|
|
19
|
+
type: z.literal("input_field"),
|
|
11
20
|
default_value: z.string(), // Mapped from 'default' with fallback to ""
|
|
12
21
|
depends_on: z.array(z.string()), // Mapped from 'depends_on' with fallback to []
|
|
13
22
|
description: z.string(), // Mapped from 'help_text' with fallback to ""
|
|
@@ -89,11 +98,72 @@ export const InputFieldItemSchema = withFormatter(
|
|
|
89
98
|
},
|
|
90
99
|
);
|
|
91
100
|
|
|
101
|
+
// ============================================================================
|
|
102
|
+
// Info Field Item Schema (for help text and copy fields)
|
|
103
|
+
// ============================================================================
|
|
104
|
+
|
|
105
|
+
export const InfoFieldItemSchema = withFormatter(
|
|
106
|
+
BaseFieldItemSchema.extend({
|
|
107
|
+
type: z.literal("info_field"),
|
|
108
|
+
description: z.string(), // From need.help_text
|
|
109
|
+
title: z.string().optional(), // Optional title
|
|
110
|
+
}),
|
|
111
|
+
{
|
|
112
|
+
format: (item) => ({
|
|
113
|
+
title: item.title || "Info",
|
|
114
|
+
key: item.key,
|
|
115
|
+
description: item.description,
|
|
116
|
+
details: [{ text: item.description, style: "normal" as const }],
|
|
117
|
+
}),
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// ============================================================================
|
|
122
|
+
// Fieldset Schema (for grouping fields)
|
|
123
|
+
// ============================================================================
|
|
124
|
+
|
|
125
|
+
// Forward reference for recursive type
|
|
126
|
+
type FieldsetItemType =
|
|
127
|
+
| z.infer<typeof InputFieldItemSchema>
|
|
128
|
+
| z.infer<typeof InfoFieldItemSchema>
|
|
129
|
+
| FieldsetItem;
|
|
130
|
+
|
|
131
|
+
export interface FieldsetItem {
|
|
132
|
+
type: "fieldset";
|
|
133
|
+
key: string;
|
|
134
|
+
title: string;
|
|
135
|
+
fields: FieldsetItemType[];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const FieldsetItemSchema: z.ZodType<FieldsetItem> =
|
|
139
|
+
BaseFieldItemSchema.extend({
|
|
140
|
+
type: z.literal("fieldset"),
|
|
141
|
+
title: z.string(),
|
|
142
|
+
fields: z.lazy(() =>
|
|
143
|
+
z.array(
|
|
144
|
+
z.union([
|
|
145
|
+
InputFieldItemSchema,
|
|
146
|
+
InfoFieldItemSchema,
|
|
147
|
+
FieldsetItemSchema,
|
|
148
|
+
]),
|
|
149
|
+
),
|
|
150
|
+
),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// ============================================================================
|
|
154
|
+
// Root Field Item Schema (union of all possible field types)
|
|
155
|
+
// ============================================================================
|
|
156
|
+
|
|
157
|
+
export const RootFieldItemSchema = z.union([
|
|
158
|
+
InputFieldItemSchema,
|
|
159
|
+
InfoFieldItemSchema,
|
|
160
|
+
FieldsetItemSchema,
|
|
161
|
+
]);
|
|
162
|
+
|
|
92
163
|
// ============================================================================
|
|
93
164
|
// Type Exports
|
|
94
165
|
// ============================================================================
|
|
95
166
|
|
|
96
167
|
export type InputFieldItem = z.infer<typeof InputFieldItemSchema>;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
export type FieldItem = InputFieldItem;
|
|
168
|
+
export type InfoFieldItem = z.infer<typeof InfoFieldItemSchema>;
|
|
169
|
+
export type RootFieldItem = z.infer<typeof RootFieldItemSchema>;
|
package/src/types/domain.ts
CHANGED
|
@@ -9,8 +9,13 @@ import type { z } from "zod";
|
|
|
9
9
|
import type { AppItemSchema } from "../schemas/App";
|
|
10
10
|
import type { AuthenticationItemSchema } from "../schemas/Auth";
|
|
11
11
|
import type { ActionItemSchema } from "../schemas/Action";
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
InputFieldItemSchema,
|
|
14
|
+
InfoFieldItemSchema,
|
|
15
|
+
RootFieldItemSchema,
|
|
16
|
+
} from "../schemas/Field";
|
|
13
17
|
import type { UserProfileItemSchema } from "../schemas/UserProfile";
|
|
18
|
+
import type { FieldsetItem } from "../schemas/Field";
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
21
|
* Represents an app item returned by getApp and listApps functions
|
|
@@ -36,6 +41,24 @@ export type ActionItem = z.infer<typeof ActionItemSchema>;
|
|
|
36
41
|
*/
|
|
37
42
|
export type InputFieldItem = z.infer<typeof InputFieldItemSchema>;
|
|
38
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Represents an info field item (help text/copy) returned by listInputFields functions
|
|
46
|
+
* Inferred from InfoFieldItemSchema
|
|
47
|
+
*/
|
|
48
|
+
export type InfoFieldItem = z.infer<typeof InfoFieldItemSchema>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Represents a fieldset (group of fields) returned by listInputFields functions
|
|
52
|
+
* This is defined as an interface rather than inferred due to recursive nature
|
|
53
|
+
*/
|
|
54
|
+
export type { FieldsetItem };
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Represents a root field item (any field/fieldset type) returned by listInputFields
|
|
58
|
+
* Inferred from RootFieldItemSchema - use as RootFieldItem[]
|
|
59
|
+
*/
|
|
60
|
+
export type RootFieldItem = z.infer<typeof RootFieldItemSchema>;
|
|
61
|
+
|
|
39
62
|
/**
|
|
40
63
|
* Represents a user profile item returned by getProfile function
|
|
41
64
|
* Inferred from UserProfileItemSchema which extends the base UserProfile API schema
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { toTitleCase } from "./string-utils";
|
|
3
|
+
|
|
4
|
+
describe("toTitleCase", () => {
|
|
5
|
+
it("converts snake_case to title case", () => {
|
|
6
|
+
expect(toTitleCase("first_name")).toBe("First Name");
|
|
7
|
+
expect(toTitleCase("sender_settings")).toBe("Sender Settings");
|
|
8
|
+
expect(toTitleCase("api_key_config")).toBe("Api Key Config");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("converts camelCase to title case", () => {
|
|
12
|
+
expect(toTitleCase("firstName")).toBe("First Name");
|
|
13
|
+
expect(toTitleCase("senderSettings")).toBe("Sender Settings");
|
|
14
|
+
expect(toTitleCase("apiKeyConfig")).toBe("Api Key Config");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("converts kebab-case to title case", () => {
|
|
18
|
+
expect(toTitleCase("first-name")).toBe("First Name");
|
|
19
|
+
expect(toTitleCase("sender-settings")).toBe("Sender Settings");
|
|
20
|
+
expect(toTitleCase("api-key-config")).toBe("Api Key Config");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("handles mixed formats", () => {
|
|
24
|
+
expect(toTitleCase("first_name-value")).toBe("First Name Value");
|
|
25
|
+
expect(toTitleCase("sender_settings-config")).toBe(
|
|
26
|
+
"Sender Settings Config",
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("handles single words", () => {
|
|
31
|
+
expect(toTitleCase("name")).toBe("Name");
|
|
32
|
+
expect(toTitleCase("settings")).toBe("Settings");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("handles multiple spaces and trims", () => {
|
|
36
|
+
expect(toTitleCase(" first name ")).toBe("First Name");
|
|
37
|
+
expect(toTitleCase("sender__settings")).toBe("Sender Settings");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("handles empty and edge cases", () => {
|
|
41
|
+
expect(toTitleCase("")).toBe("");
|
|
42
|
+
expect(toTitleCase("a")).toBe("A");
|
|
43
|
+
expect(toTitleCase("_")).toBe("");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String utility functions for the Zapier SDK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Converts a string to title case, handling various input formats:
|
|
7
|
+
* - camelCase: "firstName" → "First Name"
|
|
8
|
+
* - snake_case: "first_name" → "First Name"
|
|
9
|
+
* - kebab-case: "first-name" → "First Name"
|
|
10
|
+
* - mixed formats: "first_name-value" → "First Name Value"
|
|
11
|
+
*/
|
|
12
|
+
export function toTitleCase(input: string): string {
|
|
13
|
+
return (
|
|
14
|
+
input
|
|
15
|
+
// insert a space before capital letters (handles camelCase)
|
|
16
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
17
|
+
// replace delimiters (underscore, dash, multiple spaces) with single space
|
|
18
|
+
.replace(/[_\-]+/g, " ")
|
|
19
|
+
.replace(/\s+/g, " ")
|
|
20
|
+
.trim()
|
|
21
|
+
// split and capitalize each word
|
|
22
|
+
.split(" ")
|
|
23
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
24
|
+
.join(" ")
|
|
25
|
+
);
|
|
26
|
+
}
|