attio 0.0.1-experimental.20241003.1 → 0.0.1-experimental.20241007.1
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/lib/api/add-connection-definition.js +2 -2
- package/lib/commands/connection/add.js +139 -18
- package/lib/graphql/generate-operations.js +9 -3
- package/lib/machines/add-connection-machine.js +164 -41
- package/lib/util/find-available-port.js +23 -18
- package/lib/util/slugify-extension.js +2 -2
- package/package.json +1 -1
- package/schema.graphql +3 -49
|
@@ -7,11 +7,11 @@ const addConnectionSchema = z.object({
|
|
|
7
7
|
app_id: z.string(),
|
|
8
8
|
connection_definition_id: z.string(),
|
|
9
9
|
});
|
|
10
|
-
export async function addConnectionDefinition({ token, devSlug, appId,
|
|
10
|
+
export async function addConnectionDefinition({ token, devSlug, appId, key, label, description, allowMultiple, global, connectionType, clientId, clientSecret, authorizeUrl, accessTokenUrl, major, scopes, }) {
|
|
11
11
|
const connectionDefinitionId = uuid();
|
|
12
12
|
const body = {
|
|
13
13
|
connection_type: connectionType,
|
|
14
|
-
slug,
|
|
14
|
+
slug: key,
|
|
15
15
|
label,
|
|
16
16
|
description,
|
|
17
17
|
allow_multiple: allowMultiple,
|
|
@@ -11,10 +11,10 @@ import { Select } from "../../components/Select.js";
|
|
|
11
11
|
import { addConnectionMachine, connectionTypes } from "../../machines/add-connection-machine.js";
|
|
12
12
|
export const description = "Create a new connection for your Attio app";
|
|
13
13
|
export const options = z.object({
|
|
14
|
-
|
|
14
|
+
key: z
|
|
15
15
|
.string()
|
|
16
16
|
.optional()
|
|
17
|
-
.describe(option({ description: "A unique
|
|
17
|
+
.describe(option({ description: "A unique key for your connection" })),
|
|
18
18
|
label: z
|
|
19
19
|
.string()
|
|
20
20
|
.optional()
|
|
@@ -29,6 +29,7 @@ export const options = z.object({
|
|
|
29
29
|
.enum(["secret", "oauth2-code"])
|
|
30
30
|
.optional()
|
|
31
31
|
.describe(option({ description: "The type of connection to create" })),
|
|
32
|
+
allowMultiple: z.boolean().optional().default(false),
|
|
32
33
|
authorizeUrl: z
|
|
33
34
|
.string()
|
|
34
35
|
.url("Invalid URL, e.g. https://authorization-server.com/authorize")
|
|
@@ -61,15 +62,16 @@ export const options = z.object({
|
|
|
61
62
|
.default(false)
|
|
62
63
|
.describe(option({ description: "Run in development mode (additional debugging info)" })),
|
|
63
64
|
});
|
|
64
|
-
export default function AddConnection({ options: {
|
|
65
|
+
export default function AddConnection({ options: { key, label, description, type: connectionType, authorizeUrl, accessTokenUrl, allowMultiple, scopes, clientId, clientSecret, dev, }, }) {
|
|
65
66
|
const [snapshot, send] = useMachine(addConnectionMachine, {
|
|
66
67
|
input: {
|
|
67
|
-
|
|
68
|
+
key,
|
|
68
69
|
label,
|
|
69
70
|
description,
|
|
70
71
|
connectionType,
|
|
71
72
|
authorizeUrl,
|
|
72
73
|
accessTokenUrl,
|
|
74
|
+
allowMultiple,
|
|
73
75
|
scopes,
|
|
74
76
|
clientId,
|
|
75
77
|
clientSecret,
|
|
@@ -81,19 +83,107 @@ export default function AddConnection({ options: { slug, label, description, typ
|
|
|
81
83
|
React.createElement(Logo, null),
|
|
82
84
|
React.createElement(Box, { flexDirection: "column" },
|
|
83
85
|
snapshot.matches("Show config instructions") && (React.createElement(InitialInstructions, { reason: snapshot.context.configError })),
|
|
84
|
-
|
|
85
|
-
React.createElement(Box, null,
|
|
86
|
-
React.createElement(Text, null, "
|
|
87
|
-
React.createElement(
|
|
86
|
+
React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
87
|
+
snapshot.context.connectionType && (React.createElement(Box, null,
|
|
88
|
+
React.createElement(Text, null, "Connection Type: "),
|
|
89
|
+
React.createElement(Text, { color: "green" }, snapshot.context.connectionType === "oauth2-code"
|
|
90
|
+
? "OAuth2 Code"
|
|
91
|
+
: "Secret"))),
|
|
92
|
+
snapshot.context.label && !snapshot.matches("Ask for label") && (React.createElement(Box, null,
|
|
93
|
+
React.createElement(Text, null, "Label: "),
|
|
94
|
+
React.createElement(Text, { color: "green" }, snapshot.context.label))),
|
|
95
|
+
snapshot.context.key && !snapshot.matches("Ask for key") && (React.createElement(Box, null,
|
|
96
|
+
React.createElement(Text, null, "Connection Key: "),
|
|
97
|
+
React.createElement(Text, { color: "green" }, snapshot.context.key))),
|
|
98
|
+
snapshot.context.description && !snapshot.matches("Ask for description") && (React.createElement(Box, null,
|
|
99
|
+
React.createElement(Text, null, "Description: "),
|
|
100
|
+
React.createElement(Text, { color: "green" }, snapshot.context.description))),
|
|
101
|
+
snapshot.context.global !== undefined &&
|
|
102
|
+
!snapshot.matches("Ask for global") && (React.createElement(Box, null,
|
|
103
|
+
React.createElement(Text, null,
|
|
104
|
+
snapshot.context.connectionType === "secret"
|
|
105
|
+
? "Secret"
|
|
106
|
+
: "Token",
|
|
107
|
+
" ",
|
|
108
|
+
"will be created for:",
|
|
109
|
+
" "),
|
|
110
|
+
React.createElement(Text, { color: "green" }, snapshot.context.global
|
|
111
|
+
? "The entire workspace"
|
|
112
|
+
: "An individual user"))),
|
|
113
|
+
snapshot.context.connectionType === "oauth2-code" && (React.createElement(React.Fragment, null,
|
|
114
|
+
snapshot.context.authorizeUrl &&
|
|
115
|
+
!snapshot.matches("Ask for Authorize URL") && (React.createElement(Box, null,
|
|
116
|
+
React.createElement(Text, null, "Authorize URL: "),
|
|
117
|
+
React.createElement(Text, { color: "green" }, snapshot.context.authorizeUrl))),
|
|
118
|
+
snapshot.context.accessTokenUrl &&
|
|
119
|
+
!snapshot.matches("Ask for Access Token URL") && (React.createElement(Box, null,
|
|
120
|
+
React.createElement(Text, null, "Access Token URL: "),
|
|
121
|
+
React.createElement(Text, { color: "green" }, snapshot.context.accessTokenUrl))),
|
|
122
|
+
snapshot.context.scopes && !snapshot.matches("Ask for Scopes") && (React.createElement(Box, null,
|
|
123
|
+
React.createElement(Text, null, "Scopes: "),
|
|
124
|
+
React.createElement(Text, { color: "green" }, snapshot.context.scopes))),
|
|
125
|
+
snapshot.context.clientId &&
|
|
126
|
+
!snapshot.matches("Ask for Client ID") && (React.createElement(Box, null,
|
|
127
|
+
React.createElement(Text, null, "Client ID: "),
|
|
128
|
+
React.createElement(Text, { color: "green" }, snapshot.context.clientId)))))),
|
|
129
|
+
snapshot.matches("Ask for key") && (React.createElement(React.Fragment, null,
|
|
130
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
131
|
+
React.createElement(Box, { flexDirection: "column", margin: 1, borderStyle: "round", padding: 1 },
|
|
132
|
+
React.createElement(Text, null,
|
|
133
|
+
"import ",
|
|
134
|
+
"{",
|
|
135
|
+
" connections ",
|
|
136
|
+
"}",
|
|
137
|
+
" from \"attio/server\""),
|
|
138
|
+
React.createElement(Box, { marginY: 1 },
|
|
139
|
+
React.createElement(Text, null, "...")),
|
|
140
|
+
React.createElement(Text, null,
|
|
141
|
+
"const response = await fetch(\"https://api.yourdomain.com/your-endpoint\", ",
|
|
142
|
+
"{"),
|
|
143
|
+
React.createElement(Text, null,
|
|
144
|
+
" ",
|
|
145
|
+
"method: \"GET\","),
|
|
146
|
+
React.createElement(Text, null,
|
|
147
|
+
" ",
|
|
148
|
+
"headers: ",
|
|
149
|
+
"{"),
|
|
150
|
+
React.createElement(Text, null,
|
|
151
|
+
" ",
|
|
152
|
+
"\"Content-Type\": \"application/json\","),
|
|
153
|
+
React.createElement(Box, { flexWrap: "wrap" },
|
|
154
|
+
React.createElement(Text, null,
|
|
155
|
+
" ",
|
|
156
|
+
"Authorization: `Bearer $",
|
|
157
|
+
"{",
|
|
158
|
+
"connections"),
|
|
159
|
+
React.createElement(Text, null, snapshot.context.key?.includes("-") ? '["' : "."),
|
|
160
|
+
React.createElement(Text, { color: "yellowBright" }, snapshot.context.key),
|
|
161
|
+
snapshot.context.key?.includes("-") && React.createElement(Text, null, "\"]"),
|
|
162
|
+
React.createElement(Text, null,
|
|
163
|
+
"}",
|
|
164
|
+
"`,")),
|
|
165
|
+
React.createElement(Box, { marginLeft: 41 + (snapshot.context.key?.includes("-") ? 1 : 0) },
|
|
166
|
+
React.createElement(Text, { color: "cyan" }, "^".repeat(snapshot.context.key?.length ?? 0))),
|
|
167
|
+
React.createElement(Text, null, " }"),
|
|
168
|
+
React.createElement(Text, null,
|
|
169
|
+
"}",
|
|
170
|
+
");")),
|
|
171
|
+
React.createElement(Box, null,
|
|
172
|
+
React.createElement(Text, null, "Unique Connection Key: "),
|
|
173
|
+
React.createElement(TextInput, { value: snapshot.context.key ?? "", onChange: (key) => send({ type: "Update Key", key }), onSubmit: () => send({ type: "Submit" }) }))))),
|
|
88
174
|
snapshot.matches("Ask for label") && (React.createElement(React.Fragment, null,
|
|
89
175
|
React.createElement(Box, null,
|
|
90
|
-
React.createElement(Text, null, "Provide a label for your connection. This
|
|
91
|
-
React.createElement(Box,
|
|
176
|
+
React.createElement(Text, null, "Provide a label for your connection. This is what your users will see.")),
|
|
177
|
+
React.createElement(Box, { marginTop: 1 },
|
|
178
|
+
React.createElement(Text, null, "> "),
|
|
92
179
|
React.createElement(TextInput, { value: snapshot.context.label ?? "", onChange: (label) => send({ type: "Update Label", label }), onSubmit: () => send({ type: "Submit" }) })))),
|
|
93
180
|
snapshot.matches("Ask for description") && (React.createElement(React.Fragment, null,
|
|
94
181
|
React.createElement(Box, null,
|
|
95
|
-
React.createElement(Text, null, "Provide an optional detailed description for your connection.
|
|
96
|
-
React.createElement(Box,
|
|
182
|
+
React.createElement(Text, null, "Provide an optional detailed description for your connection.")),
|
|
183
|
+
React.createElement(Box, { marginTop: 1 },
|
|
184
|
+
React.createElement(Text, null, "This will be displayed to users. (You can edit this later)")),
|
|
185
|
+
React.createElement(Box, { marginTop: 1 },
|
|
186
|
+
React.createElement(Text, null, "> "),
|
|
97
187
|
React.createElement(TextInput, { value: snapshot.context.description ?? "", onChange: (description) => send({ type: "Update Description", description }), onSubmit: () => send({ type: "Submit" }) })))),
|
|
98
188
|
snapshot.matches("Ask for connection type") && (React.createElement(React.Fragment, null,
|
|
99
189
|
React.createElement(Box, null,
|
|
@@ -102,30 +192,61 @@ export default function AddConnection({ options: { slug, label, description, typ
|
|
|
102
192
|
React.createElement(Select, { items: connectionTypes, onSelect: (connectionType) => send({ type: "Choose Connection Type", connectionType }) })))),
|
|
103
193
|
snapshot.matches("Ask for global") && (React.createElement(React.Fragment, null,
|
|
104
194
|
React.createElement(Box, null,
|
|
105
|
-
React.createElement(Text, null,
|
|
195
|
+
React.createElement(Text, null,
|
|
196
|
+
"A",
|
|
197
|
+
" ",
|
|
198
|
+
snapshot.context.connectionType === "secret" ? "secret" : "token",
|
|
199
|
+
" ",
|
|
200
|
+
"will be created for:")),
|
|
106
201
|
React.createElement(Box, null,
|
|
107
202
|
React.createElement(Select, { items: [
|
|
108
|
-
{ value:
|
|
109
|
-
{ value:
|
|
203
|
+
{ value: false, label: "An individual user" },
|
|
204
|
+
{ value: true, label: "The entire workspace" },
|
|
110
205
|
], onSelect: (global) => send({ type: "Choose Global", global }) })))),
|
|
111
206
|
snapshot.matches("Ask for allow multiple") && (React.createElement(React.Fragment, null,
|
|
112
207
|
React.createElement(Box, null,
|
|
113
|
-
React.createElement(Text, null,
|
|
208
|
+
React.createElement(Text, null, snapshot.context.global
|
|
209
|
+
? `${snapshot.context.connectionType === "secret"
|
|
210
|
+
? "Secrets"
|
|
211
|
+
: "Tokens"} should be:`
|
|
212
|
+
: `A user should be able to:`)),
|
|
114
213
|
React.createElement(Box, null,
|
|
115
214
|
React.createElement(Select, { items: [
|
|
116
|
-
{
|
|
117
|
-
|
|
215
|
+
{
|
|
216
|
+
value: false,
|
|
217
|
+
label: snapshot.context.global
|
|
218
|
+
? "Limited to one per workspace"
|
|
219
|
+
: `Create only one ${snapshot.context.connectionType === "secret"
|
|
220
|
+
? "secret"
|
|
221
|
+
: "token"} that remains private`,
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
value: true,
|
|
225
|
+
label: snapshot.context.global
|
|
226
|
+
? `Unlimited; users can add multiple ${snapshot.context.connectionType === "secret"
|
|
227
|
+
? "secrets"
|
|
228
|
+
: "token"} to each workspace`
|
|
229
|
+
: `Create multiple ${snapshot.context.connectionType === "secret"
|
|
230
|
+
? "secrets"
|
|
231
|
+
: "tokens"} and share them with other users`,
|
|
232
|
+
},
|
|
118
233
|
], onSelect: (allowMultiple) => send({ type: "Choose Allow Multiple", allowMultiple }) })))),
|
|
119
234
|
snapshot.matches("Ask for Authorize URL") && (React.createElement(React.Fragment, null,
|
|
120
235
|
React.createElement(Box, null,
|
|
236
|
+
React.createElement(Text, null, "e.g. https://authorization-server.com/oauth/authorize")),
|
|
237
|
+
React.createElement(Box, { marginTop: 1 },
|
|
121
238
|
React.createElement(Text, null, "OAuth Authorize URL: "),
|
|
122
239
|
React.createElement(TextInput, { value: snapshot.context.authorizeUrl ?? "", onChange: (authorizeUrl) => send({ type: "Update Authorize URL", authorizeUrl }), onSubmit: () => send({ type: "Submit" }) })))),
|
|
123
240
|
snapshot.matches("Ask for Access Token URL") && (React.createElement(React.Fragment, null,
|
|
124
241
|
React.createElement(Box, null,
|
|
242
|
+
React.createElement(Text, null, "e.g. https://authorization-server.com/oauth/token")),
|
|
243
|
+
React.createElement(Box, { marginTop: 1 },
|
|
125
244
|
React.createElement(Text, null, "OAuth Access Token URL: "),
|
|
126
245
|
React.createElement(TextInput, { value: snapshot.context.accessTokenUrl ?? "", onChange: (accessTokenUrl) => send({ type: "Update Access Token URL", accessTokenUrl }), onSubmit: () => send({ type: "Submit" }) })))),
|
|
127
246
|
snapshot.matches("Ask for Scopes") && (React.createElement(React.Fragment, null,
|
|
128
247
|
React.createElement(Box, null,
|
|
248
|
+
React.createElement(Text, null, "e.g. profile,email,read,write")),
|
|
249
|
+
React.createElement(Box, { marginTop: 1 },
|
|
129
250
|
React.createElement(Text, null, "OAuth Scopes: (separated by commas) "),
|
|
130
251
|
React.createElement(TextInput, { value: snapshot.context.scopes ?? "", onChange: (scopes) => send({ type: "Update Scopes", scopes }), onSubmit: () => send({ type: "Submit" }) })))),
|
|
131
252
|
snapshot.matches("Ask for Client ID") && (React.createElement(React.Fragment, null,
|
|
@@ -24,7 +24,7 @@ function findGeneratedFiles(dir) {
|
|
|
24
24
|
for (const entry of entries) {
|
|
25
25
|
const fullPath = path.join(dir, entry.name);
|
|
26
26
|
if (entry.isDirectory()) {
|
|
27
|
-
files.push(...
|
|
27
|
+
files.push(...findGeneratedFiles(fullPath));
|
|
28
28
|
}
|
|
29
29
|
else if (entry.isFile() && path.extname(entry.name) === ".graphql.d.ts") {
|
|
30
30
|
files.push(fullPath);
|
|
@@ -44,7 +44,7 @@ function generateTypeDefinition(type) {
|
|
|
44
44
|
return generateTypeDefinition(type.ofType);
|
|
45
45
|
}
|
|
46
46
|
if (isListType(type)) {
|
|
47
|
-
return
|
|
47
|
+
return `(${generateTypeDefinition(type.ofType)})[]`;
|
|
48
48
|
}
|
|
49
49
|
if (isObjectType(type) || isInputObjectType(type)) {
|
|
50
50
|
return type.name;
|
|
@@ -84,12 +84,18 @@ function generateTypeDefinitionFromSelectionSet(selectionSet, parentType) {
|
|
|
84
84
|
if (selection.selectionSet) {
|
|
85
85
|
if (isObjectType(getNamedType(fieldType))) {
|
|
86
86
|
fieldTypeDefinition = generateTypeDefinitionFromSelectionSet(selection.selectionSet, getNamedType(fieldType));
|
|
87
|
+
if (isListType(fieldType)) {
|
|
88
|
+
fieldTypeDefinition = `(${fieldTypeDefinition})[]`;
|
|
89
|
+
}
|
|
87
90
|
}
|
|
88
91
|
}
|
|
89
92
|
else {
|
|
90
93
|
fieldTypeDefinition = generateTypeDefinition(fieldType);
|
|
94
|
+
if (isListType(fieldType) && !isNonNullType(fieldType.ofType)) {
|
|
95
|
+
fieldTypeDefinition = `(${generateTypeDefinition(fieldType.ofType)} | null)[]`;
|
|
96
|
+
}
|
|
91
97
|
}
|
|
92
|
-
return `${selection.name.value}: ${fieldTypeDefinition}${
|
|
98
|
+
return `${selection.name.value}: ${fieldTypeDefinition} ${isNonNullType(fieldType) ? "" : " | null"}`;
|
|
93
99
|
}
|
|
94
100
|
return "";
|
|
95
101
|
})
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import readline from "readline";
|
|
1
2
|
import { assign, setup, fromCallback } from "xstate";
|
|
2
3
|
import { addConnectionDefinition } from "../api/add-connection-definition.js";
|
|
3
4
|
import { fetchConnections } from "../api/fetch-connections.js";
|
|
@@ -16,7 +17,7 @@ export const addConnectionMachine = setup({
|
|
|
16
17
|
input: {},
|
|
17
18
|
},
|
|
18
19
|
actors: {
|
|
19
|
-
createConnectionDefinition: fromCallback(({ sendBack, input: { developer: { token, slug: devSlug }, config, connectionType: connection_type,
|
|
20
|
+
"createConnectionDefinition": fromCallback(({ sendBack, input: { developer: { token, slug: devSlug }, config, connectionType: connection_type, key, label, description, allowMultiple, global, authorizeUrl, accessTokenUrl, clientId, clientSecret, scopes, }, }) => {
|
|
20
21
|
const add = async () => {
|
|
21
22
|
try {
|
|
22
23
|
await addConnectionDefinition({
|
|
@@ -25,7 +26,7 @@ export const addConnectionMachine = setup({
|
|
|
25
26
|
appId: config.id,
|
|
26
27
|
major: config.major,
|
|
27
28
|
connectionType: connection_type,
|
|
28
|
-
|
|
29
|
+
key,
|
|
29
30
|
label,
|
|
30
31
|
description,
|
|
31
32
|
allowMultiple,
|
|
@@ -50,18 +51,45 @@ export const addConnectionMachine = setup({
|
|
|
50
51
|
}),
|
|
51
52
|
loadDeveloperConfig,
|
|
52
53
|
loadAppConfig,
|
|
53
|
-
|
|
54
|
+
"keyboard-navigation": fromCallback(({ sendBack }) => {
|
|
55
|
+
readline.emitKeypressEvents(process.stdin);
|
|
56
|
+
if (process.stdin.isTTY) {
|
|
57
|
+
process.stdin.setRawMode(true);
|
|
58
|
+
}
|
|
59
|
+
const handleKeyPress = (_, key) => {
|
|
60
|
+
if (key.meta) {
|
|
61
|
+
switch (key.name) {
|
|
62
|
+
case "down":
|
|
63
|
+
sendBack({ type: "Next" });
|
|
64
|
+
break;
|
|
65
|
+
case "up":
|
|
66
|
+
sendBack({ type: "Previous" });
|
|
67
|
+
break;
|
|
68
|
+
default:
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
process.stdin.on("keypress", handleKeyPress);
|
|
74
|
+
return () => {
|
|
75
|
+
process.stdin.removeListener("keypress", handleKeyPress);
|
|
76
|
+
if (process.stdin.isTTY) {
|
|
77
|
+
process.stdin.setRawMode(false);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}),
|
|
81
|
+
"validateKey": fromCallback(({ sendBack, input: { key, developer, config } }) => {
|
|
54
82
|
fetchConnections({
|
|
55
83
|
token: developer.token,
|
|
56
84
|
devSlug: developer.slug,
|
|
57
85
|
appId: config.id,
|
|
58
86
|
major: config.major,
|
|
59
87
|
}).then((connections) => {
|
|
60
|
-
if (connections[
|
|
61
|
-
sendBack({ type: "
|
|
88
|
+
if (connections[key]) {
|
|
89
|
+
sendBack({ type: "Key Taken" });
|
|
62
90
|
}
|
|
63
91
|
else {
|
|
64
|
-
sendBack({ type: "Valid
|
|
92
|
+
sendBack({ type: "Valid Key" });
|
|
65
93
|
}
|
|
66
94
|
});
|
|
67
95
|
}),
|
|
@@ -70,6 +98,21 @@ export const addConnectionMachine = setup({
|
|
|
70
98
|
clearError: assign({
|
|
71
99
|
error: () => undefined,
|
|
72
100
|
}),
|
|
101
|
+
initializeLabel: assign({
|
|
102
|
+
label: (_, params) => {
|
|
103
|
+
if (params.label?.trim())
|
|
104
|
+
return params.label;
|
|
105
|
+
switch (params.connectionType) {
|
|
106
|
+
case "oauth2-code":
|
|
107
|
+
return "Connection";
|
|
108
|
+
case "secret":
|
|
109
|
+
case undefined:
|
|
110
|
+
return "Access Token";
|
|
111
|
+
default:
|
|
112
|
+
return params.connectionType;
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
73
116
|
setError: assign({
|
|
74
117
|
error: (_, params) => params.error,
|
|
75
118
|
}),
|
|
@@ -94,8 +137,8 @@ export const addConnectionMachine = setup({
|
|
|
94
137
|
setDeveloperConfig: assign({
|
|
95
138
|
developer: (_, params) => params,
|
|
96
139
|
}),
|
|
97
|
-
|
|
98
|
-
|
|
140
|
+
setKey: assign({
|
|
141
|
+
key: (_, params) => slugifyExtension(params.key, false),
|
|
99
142
|
}),
|
|
100
143
|
setInvalidAuthorizeUrl: assign({
|
|
101
144
|
error: (_, params) => {
|
|
@@ -151,13 +194,16 @@ export const addConnectionMachine = setup({
|
|
|
151
194
|
setAppConfig: assign({
|
|
152
195
|
config: (_, params) => params.config,
|
|
153
196
|
}),
|
|
154
|
-
|
|
155
|
-
error: () => "
|
|
197
|
+
setKeyTaken: assign({
|
|
198
|
+
error: () => "Key taken",
|
|
199
|
+
}),
|
|
200
|
+
slugifyLabel: assign({
|
|
201
|
+
key: (_, params) => slugifyExtension(params.label, true),
|
|
156
202
|
}),
|
|
157
203
|
},
|
|
158
204
|
guards: {
|
|
159
205
|
"have connection type": (_, params) => Boolean(params.connectionType),
|
|
160
|
-
"have
|
|
206
|
+
"have key": (_, params) => Boolean(params.key),
|
|
161
207
|
"have label": (_, params) => Boolean(params.label),
|
|
162
208
|
"have description": (_, params) => Boolean(params.description),
|
|
163
209
|
"have authorize url": (_, params) => params.connectionType === "secret" ||
|
|
@@ -208,12 +254,11 @@ export const addConnectionMachine = setup({
|
|
|
208
254
|
target: "Error",
|
|
209
255
|
actions: { type: "setError", params: ({ event }) => event },
|
|
210
256
|
},
|
|
211
|
-
"App Config Loaded":
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
],
|
|
257
|
+
"App Config Loaded": {
|
|
258
|
+
target: "Do we have a connection type?",
|
|
259
|
+
actions: { type: "setAppConfig", params: ({ event }) => event },
|
|
260
|
+
reenter: true,
|
|
261
|
+
},
|
|
217
262
|
},
|
|
218
263
|
},
|
|
219
264
|
"Error": {
|
|
@@ -235,7 +280,7 @@ export const addConnectionMachine = setup({
|
|
|
235
280
|
allowMultiple: context.allowMultiple,
|
|
236
281
|
label: context.label,
|
|
237
282
|
description: context.description,
|
|
238
|
-
|
|
283
|
+
key: context.key,
|
|
239
284
|
}),
|
|
240
285
|
},
|
|
241
286
|
on: {
|
|
@@ -258,12 +303,18 @@ export const addConnectionMachine = setup({
|
|
|
258
303
|
{
|
|
259
304
|
target: "Do we have an access token url?",
|
|
260
305
|
guard: { type: "have authorize url", params: ({ context }) => context },
|
|
306
|
+
actions: "clearError",
|
|
261
307
|
},
|
|
262
308
|
{
|
|
263
309
|
target: "Ask for Authorize URL",
|
|
264
310
|
actions: { type: "setInvalidAuthorizeUrl", params: ({ context }) => context },
|
|
265
311
|
},
|
|
266
312
|
],
|
|
313
|
+
"Previous": "Ask for allow multiple",
|
|
314
|
+
"Next": {
|
|
315
|
+
target: "Ask for Access Token URL",
|
|
316
|
+
guard: { type: "have authorize url", params: ({ context }) => context },
|
|
317
|
+
},
|
|
267
318
|
},
|
|
268
319
|
},
|
|
269
320
|
"Ask for Access Token URL": {
|
|
@@ -272,6 +323,7 @@ export const addConnectionMachine = setup({
|
|
|
272
323
|
{
|
|
273
324
|
target: "Do we have scopes?",
|
|
274
325
|
guard: { type: "have access token url", params: ({ context }) => context },
|
|
326
|
+
actions: "clearError",
|
|
275
327
|
},
|
|
276
328
|
{
|
|
277
329
|
target: "Ask for Access Token URL",
|
|
@@ -281,6 +333,11 @@ export const addConnectionMachine = setup({
|
|
|
281
333
|
"Update Access Token URL": {
|
|
282
334
|
actions: { type: "setAccessTokenUrl", params: ({ event }) => event },
|
|
283
335
|
},
|
|
336
|
+
"Previous": "Ask for Authorize URL",
|
|
337
|
+
"Next": {
|
|
338
|
+
target: "Ask for Scopes",
|
|
339
|
+
guard: { type: "have access token url", params: ({ context }) => context },
|
|
340
|
+
},
|
|
284
341
|
},
|
|
285
342
|
},
|
|
286
343
|
"Ask for Scopes": {
|
|
@@ -292,12 +349,18 @@ export const addConnectionMachine = setup({
|
|
|
292
349
|
{
|
|
293
350
|
target: "Do we have a client id?",
|
|
294
351
|
guard: { type: "have scopes", params: ({ context }) => context },
|
|
352
|
+
actions: "clearError",
|
|
295
353
|
},
|
|
296
354
|
{
|
|
297
355
|
target: "Ask for Scopes",
|
|
298
356
|
actions: { type: "setInvalidScopes", params: ({ context }) => context },
|
|
299
357
|
},
|
|
300
358
|
],
|
|
359
|
+
"Previous": "Ask for Access Token URL",
|
|
360
|
+
"Next": {
|
|
361
|
+
target: "Ask for Client ID",
|
|
362
|
+
guard: { type: "have scopes", params: ({ context }) => context },
|
|
363
|
+
},
|
|
301
364
|
},
|
|
302
365
|
},
|
|
303
366
|
"Ask for Client ID": {
|
|
@@ -309,12 +372,18 @@ export const addConnectionMachine = setup({
|
|
|
309
372
|
{
|
|
310
373
|
target: "Do we have a client secret?",
|
|
311
374
|
guard: { type: "have client id", params: ({ context }) => context },
|
|
375
|
+
actions: "clearError",
|
|
312
376
|
},
|
|
313
377
|
{
|
|
314
378
|
target: "Ask for Client ID",
|
|
315
379
|
actions: { type: "setInvalidClientId", params: ({ context }) => context },
|
|
316
380
|
},
|
|
317
381
|
],
|
|
382
|
+
"Previous": "Ask for Scopes",
|
|
383
|
+
"Next": {
|
|
384
|
+
target: "Ask for Client Secret",
|
|
385
|
+
guard: { type: "have client id", params: ({ context }) => context },
|
|
386
|
+
},
|
|
318
387
|
},
|
|
319
388
|
},
|
|
320
389
|
"Ask for Client Secret": {
|
|
@@ -326,8 +395,14 @@ export const addConnectionMachine = setup({
|
|
|
326
395
|
{
|
|
327
396
|
target: "Creating connection definition",
|
|
328
397
|
guard: { type: "have client secret", params: ({ context }) => context },
|
|
398
|
+
actions: "clearError",
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
target: "Ask for Client Secret",
|
|
402
|
+
actions: { type: "setInvalidClientSecret", params: ({ context }) => context },
|
|
329
403
|
},
|
|
330
404
|
],
|
|
405
|
+
"Previous": "Ask for Client ID",
|
|
331
406
|
},
|
|
332
407
|
},
|
|
333
408
|
"Do we have an authorize url?": {
|
|
@@ -385,49 +460,58 @@ export const addConnectionMachine = setup({
|
|
|
385
460
|
},
|
|
386
461
|
],
|
|
387
462
|
},
|
|
388
|
-
"Do we have a
|
|
463
|
+
"Do we have a key?": {
|
|
389
464
|
always: [
|
|
390
465
|
{
|
|
391
|
-
target: "Validating
|
|
392
|
-
guard: { type: "have
|
|
466
|
+
target: "Validating Key",
|
|
467
|
+
guard: { type: "have key", params: ({ context }) => context },
|
|
393
468
|
},
|
|
394
469
|
{
|
|
395
|
-
target: "Ask for
|
|
470
|
+
target: "Ask for key",
|
|
471
|
+
actions: {
|
|
472
|
+
type: "slugifyLabel",
|
|
473
|
+
params: ({ context }) => ({ label: context.label }),
|
|
474
|
+
},
|
|
396
475
|
},
|
|
397
476
|
],
|
|
398
477
|
},
|
|
399
|
-
"Validating
|
|
478
|
+
"Validating Key": {
|
|
400
479
|
on: {
|
|
401
|
-
"
|
|
402
|
-
target: "Ask for
|
|
403
|
-
actions: "
|
|
480
|
+
"Key Taken": {
|
|
481
|
+
target: "Ask for key",
|
|
482
|
+
actions: "setKeyTaken",
|
|
404
483
|
},
|
|
405
|
-
"Valid
|
|
406
|
-
target: "Do we have a
|
|
484
|
+
"Valid Key": {
|
|
485
|
+
target: "Do we have a description?",
|
|
486
|
+
reenter: true,
|
|
487
|
+
actions: "clearError",
|
|
407
488
|
},
|
|
408
489
|
},
|
|
409
490
|
invoke: {
|
|
410
|
-
src: "
|
|
491
|
+
src: "validateKey",
|
|
411
492
|
input: ({ context }) => ({
|
|
412
|
-
|
|
493
|
+
key: context.key,
|
|
413
494
|
developer: context.developer,
|
|
414
495
|
config: context.config,
|
|
415
496
|
}),
|
|
416
497
|
},
|
|
417
498
|
},
|
|
418
|
-
"Ask for
|
|
499
|
+
"Ask for key": {
|
|
419
500
|
on: {
|
|
420
|
-
"Update
|
|
421
|
-
actions: { type: "
|
|
501
|
+
"Update Key": {
|
|
502
|
+
actions: { type: "setKey", params: ({ event }) => event },
|
|
422
503
|
},
|
|
423
|
-
"Submit": "Validating
|
|
504
|
+
"Submit": "Validating Key",
|
|
505
|
+
"Previous": "Ask for label",
|
|
506
|
+
"Next": "Validating Key",
|
|
424
507
|
},
|
|
425
508
|
},
|
|
426
509
|
"Do we have a connection type?": {
|
|
427
510
|
always: [
|
|
428
511
|
{
|
|
429
|
-
target: "
|
|
512
|
+
target: "Do we have a label?",
|
|
430
513
|
guard: { type: "have connection type", params: ({ context }) => context },
|
|
514
|
+
reenter: true,
|
|
431
515
|
},
|
|
432
516
|
"Ask for connection type",
|
|
433
517
|
],
|
|
@@ -435,8 +519,13 @@ export const addConnectionMachine = setup({
|
|
|
435
519
|
"Ask for connection type": {
|
|
436
520
|
on: {
|
|
437
521
|
"Choose Connection Type": {
|
|
438
|
-
target: "
|
|
439
|
-
actions: { type: "setConnectionType", params: ({ event }) => event },
|
|
522
|
+
target: "Do we have a label?",
|
|
523
|
+
actions: [{ type: "setConnectionType", params: ({ event }) => event }],
|
|
524
|
+
reenter: true,
|
|
525
|
+
},
|
|
526
|
+
"Next": {
|
|
527
|
+
target: "Ask for label",
|
|
528
|
+
guard: { type: "have connection type", params: ({ context }) => context },
|
|
440
529
|
},
|
|
441
530
|
},
|
|
442
531
|
},
|
|
@@ -454,8 +543,9 @@ export const addConnectionMachine = setup({
|
|
|
454
543
|
"Do we have a label?": {
|
|
455
544
|
always: [
|
|
456
545
|
{
|
|
457
|
-
target: "Do we have a
|
|
546
|
+
target: "Do we have a key?",
|
|
458
547
|
guard: { type: "have label", params: ({ context }) => context },
|
|
548
|
+
reenter: true,
|
|
459
549
|
},
|
|
460
550
|
{
|
|
461
551
|
target: "Ask for label",
|
|
@@ -487,8 +577,9 @@ export const addConnectionMachine = setup({
|
|
|
487
577
|
"Do we have allow multiple?": {
|
|
488
578
|
always: [
|
|
489
579
|
{
|
|
490
|
-
target: "
|
|
580
|
+
target: "Split on connection type",
|
|
491
581
|
guard: { type: "have allow multiple", params: ({ context }) => context },
|
|
582
|
+
reenter: true,
|
|
492
583
|
},
|
|
493
584
|
{
|
|
494
585
|
target: "Ask for allow multiple",
|
|
@@ -498,13 +589,25 @@ export const addConnectionMachine = setup({
|
|
|
498
589
|
"Ask for label": {
|
|
499
590
|
on: {
|
|
500
591
|
"Submit": {
|
|
501
|
-
target: "Do we have a
|
|
592
|
+
target: "Do we have a key?",
|
|
502
593
|
guard: { type: "have label", params: ({ context }) => context },
|
|
594
|
+
reenter: true,
|
|
503
595
|
},
|
|
504
596
|
"Update Label": {
|
|
505
597
|
actions: { type: "setLabel", params: ({ event }) => event },
|
|
506
598
|
},
|
|
599
|
+
"Previous": "Ask for connection type",
|
|
600
|
+
"Next": {
|
|
601
|
+
target: "Ask for key",
|
|
602
|
+
guard: { type: "have label", params: ({ context }) => context },
|
|
603
|
+
},
|
|
507
604
|
},
|
|
605
|
+
entry: [
|
|
606
|
+
{
|
|
607
|
+
type: "initializeLabel",
|
|
608
|
+
params: ({ context }) => context,
|
|
609
|
+
},
|
|
610
|
+
],
|
|
508
611
|
},
|
|
509
612
|
"Ask for description": {
|
|
510
613
|
on: {
|
|
@@ -514,6 +617,12 @@ export const addConnectionMachine = setup({
|
|
|
514
617
|
"Submit": {
|
|
515
618
|
target: "Do we have global?",
|
|
516
619
|
guard: { type: "have description", params: ({ context }) => context },
|
|
620
|
+
actions: "clearError",
|
|
621
|
+
},
|
|
622
|
+
"Previous": "Ask for key",
|
|
623
|
+
"Next": {
|
|
624
|
+
target: "Ask for global",
|
|
625
|
+
guard: { type: "have description", params: ({ context }) => context },
|
|
517
626
|
},
|
|
518
627
|
},
|
|
519
628
|
},
|
|
@@ -523,16 +632,30 @@ export const addConnectionMachine = setup({
|
|
|
523
632
|
target: "Do we have allow multiple?",
|
|
524
633
|
actions: { type: "setGlobal", params: ({ event }) => event },
|
|
525
634
|
},
|
|
635
|
+
"Previous": "Ask for description",
|
|
636
|
+
"Next": {
|
|
637
|
+
target: "Ask for allow multiple",
|
|
638
|
+
guard: { type: "have global", params: ({ context }) => context },
|
|
639
|
+
},
|
|
526
640
|
},
|
|
527
641
|
},
|
|
528
642
|
"Ask for allow multiple": {
|
|
529
643
|
on: {
|
|
530
644
|
"Choose Allow Multiple": {
|
|
531
|
-
target: "
|
|
645
|
+
target: "Split on connection type",
|
|
532
646
|
actions: { type: "setAllowMultiple", params: ({ event }) => event },
|
|
647
|
+
reenter: true,
|
|
648
|
+
},
|
|
649
|
+
"Previous": "Ask for global",
|
|
650
|
+
"Next": {
|
|
651
|
+
target: "Split on connection type",
|
|
652
|
+
guard: { type: "have allow multiple", params: ({ context }) => context },
|
|
533
653
|
},
|
|
534
654
|
},
|
|
535
655
|
},
|
|
536
656
|
},
|
|
537
657
|
initial: "Loading Developer Config",
|
|
658
|
+
invoke: {
|
|
659
|
+
src: "keyboard-navigation",
|
|
660
|
+
},
|
|
538
661
|
});
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import net from "net";
|
|
2
|
-
|
|
3
|
-
return new Promise((resolve
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
server.listen(port);
|
|
18
|
-
};
|
|
19
|
-
tryPort(startPort);
|
|
2
|
+
async function isPortAvailable(port) {
|
|
3
|
+
return new Promise((resolve) => {
|
|
4
|
+
const tester = net.createConnection(port, "127.0.0.1");
|
|
5
|
+
tester.once("error", (err) => {
|
|
6
|
+
if (err.code === "ECONNREFUSED") {
|
|
7
|
+
resolve(true);
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
resolve(false);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
tester.once("connect", () => {
|
|
14
|
+
tester.end();
|
|
15
|
+
resolve(false);
|
|
16
|
+
});
|
|
20
17
|
});
|
|
21
18
|
}
|
|
19
|
+
export async function findAvailablePort(startPort, maxAttempts = 100) {
|
|
20
|
+
for (let port = startPort; port < startPort + maxAttempts; port++) {
|
|
21
|
+
if (await isPortAvailable(port)) {
|
|
22
|
+
return port;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`Could not find an available port after ${maxAttempts} attempts`);
|
|
26
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export
|
|
1
|
+
export function slugifyExtension(string, trim = true) {
|
|
2
2
|
const slug = (trim ? string.trim() : string)
|
|
3
3
|
.toLowerCase()
|
|
4
4
|
.replace(/[\s._@]/g, "-")
|
|
@@ -11,4 +11,4 @@ export const slugifyExtension = (string, trim = true) => {
|
|
|
11
11
|
return slug
|
|
12
12
|
.replace(/^-+/g, "")
|
|
13
13
|
.replace(/(?!-+$)--+/g, "-");
|
|
14
|
-
}
|
|
14
|
+
}
|
package/package.json
CHANGED
package/schema.graphql
CHANGED
|
@@ -88,6 +88,7 @@ type PeopleRecord implements Record {
|
|
|
88
88
|
last_email_interaction: Interaction
|
|
89
89
|
first_interaction: Interaction
|
|
90
90
|
last_interaction: Interaction
|
|
91
|
+
next_interaction: Interaction
|
|
91
92
|
associated_deals: [DealsRecord!]
|
|
92
93
|
associated_users: [UsersRecord!]
|
|
93
94
|
list_entries: [Record!]
|
|
@@ -106,7 +107,7 @@ interface Record {
|
|
|
106
107
|
url: String!
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
union AttributeValue = RecordReferenceValue | MultiRecordReferenceValue | PersonalNameValue |
|
|
110
|
+
union AttributeValue = RecordReferenceValue | MultiRecordReferenceValue | PersonalNameValue | TextValue | DateValue | TimestampValue | NumberValue | MultiEmailAddressValue | DomainValue | MultiDomainValue | LocationValue | InteractionValue | SelectValue | MultiSelectValue | PipelineValue | CheckboxValue | RatingValue | PhoneNumberValue | MultiPhoneNumberValue | CurrencyValue | ActorReferenceValue | MultiActorReferenceValue
|
|
110
111
|
|
|
111
112
|
type RecordReferenceValue {
|
|
112
113
|
value: Record
|
|
@@ -126,46 +127,22 @@ type PersonalName {
|
|
|
126
127
|
full: String
|
|
127
128
|
}
|
|
128
129
|
|
|
129
|
-
type MultiPersonalNameValue {
|
|
130
|
-
values: [PersonalName]
|
|
131
|
-
}
|
|
132
|
-
|
|
133
130
|
type TextValue {
|
|
134
131
|
value: String
|
|
135
132
|
}
|
|
136
133
|
|
|
137
|
-
type MultiTextValue {
|
|
138
|
-
values: [String]
|
|
139
|
-
}
|
|
140
|
-
|
|
141
134
|
type DateValue {
|
|
142
135
|
value: String
|
|
143
136
|
}
|
|
144
137
|
|
|
145
|
-
type MultiDateValue {
|
|
146
|
-
values: [String]
|
|
147
|
-
}
|
|
148
|
-
|
|
149
138
|
type TimestampValue {
|
|
150
139
|
value: String
|
|
151
140
|
}
|
|
152
141
|
|
|
153
|
-
type MultiTimestampValue {
|
|
154
|
-
values: [String]
|
|
155
|
-
}
|
|
156
|
-
|
|
157
142
|
type NumberValue {
|
|
158
143
|
value: Float
|
|
159
144
|
}
|
|
160
145
|
|
|
161
|
-
type MultiNumberValue {
|
|
162
|
-
values: [Float]
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
type EmailAddressValue {
|
|
166
|
-
value: String
|
|
167
|
-
}
|
|
168
|
-
|
|
169
146
|
type MultiEmailAddressValue {
|
|
170
147
|
values: [String]
|
|
171
148
|
}
|
|
@@ -195,10 +172,6 @@ type Location {
|
|
|
195
172
|
longitude: String
|
|
196
173
|
}
|
|
197
174
|
|
|
198
|
-
type MultiLocationValue {
|
|
199
|
-
values: [Location]
|
|
200
|
-
}
|
|
201
|
-
|
|
202
175
|
type InteractionValue {
|
|
203
176
|
value: Interaction
|
|
204
177
|
}
|
|
@@ -207,10 +180,6 @@ type Interaction {
|
|
|
207
180
|
timestamp: String!
|
|
208
181
|
}
|
|
209
182
|
|
|
210
|
-
type MultiInteractionValue {
|
|
211
|
-
values: [Interaction]
|
|
212
|
-
}
|
|
213
|
-
|
|
214
183
|
type SelectValue {
|
|
215
184
|
value: SelectOption
|
|
216
185
|
}
|
|
@@ -233,26 +202,14 @@ type PipelineStage {
|
|
|
233
202
|
id: String!
|
|
234
203
|
}
|
|
235
204
|
|
|
236
|
-
type MultiPipelineValue {
|
|
237
|
-
values: [PipelineStage]
|
|
238
|
-
}
|
|
239
|
-
|
|
240
205
|
type CheckboxValue {
|
|
241
206
|
value: Boolean!
|
|
242
207
|
}
|
|
243
208
|
|
|
244
|
-
type MultiCheckboxValue {
|
|
245
|
-
values: [Boolean!]
|
|
246
|
-
}
|
|
247
|
-
|
|
248
209
|
type RatingValue {
|
|
249
210
|
value: Int!
|
|
250
211
|
}
|
|
251
212
|
|
|
252
|
-
type MultiRatingValue {
|
|
253
|
-
values: [Int!]
|
|
254
|
-
}
|
|
255
|
-
|
|
256
213
|
type PhoneNumberValue {
|
|
257
214
|
value: String
|
|
258
215
|
}
|
|
@@ -270,10 +227,6 @@ type Money {
|
|
|
270
227
|
currency: String!
|
|
271
228
|
}
|
|
272
229
|
|
|
273
|
-
type MultiCurrencyValue {
|
|
274
|
-
values: [Money]
|
|
275
|
-
}
|
|
276
|
-
|
|
277
230
|
type ActorReferenceValue {
|
|
278
231
|
value: Actor
|
|
279
232
|
}
|
|
@@ -328,6 +281,7 @@ type CompaniesRecord implements Record {
|
|
|
328
281
|
last_email_interaction: Interaction
|
|
329
282
|
first_interaction: Interaction
|
|
330
283
|
last_interaction: Interaction
|
|
284
|
+
next_interaction: Interaction
|
|
331
285
|
associated_deals: [DealsRecord!]
|
|
332
286
|
associated_workspaces: [WorkspacesRecord!]
|
|
333
287
|
list_entries: [Record!]
|