@slates/cli 1.0.0-rc.10

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.
@@ -0,0 +1,451 @@
1
+ import { confirm, select } from '@inquirer/prompts';
2
+ import {
3
+ normalizeMicrosoftRedirectUri,
4
+ normalizeMicrosoftRedirectUriForIntegration
5
+ } from '@slates/oauth-microsoft';
6
+ import type { SlatesOAuthCredentialRecord, SlatesStoredAuth } from '@slates/profiles';
7
+ import {
8
+ chooseAuthMethod,
9
+ createClientContext,
10
+ createIntegrationClientContext,
11
+ openIntegrationStore
12
+ } from '../lib/context';
13
+ import { chooseScopes, createOAuthCallbackListener, printBrowserUrl } from '../lib/oauth';
14
+ import {
15
+ parseJsonObject,
16
+ parseList,
17
+ promptForObjectSchema,
18
+ promptForString
19
+ } from '../lib/prompts';
20
+ import type { JsonInput, WithProfile } from '../lib/types';
21
+
22
+ type JsonObject = Record<string, any>;
23
+ let NOTION_INTEGRATION_KEY = 'notion';
24
+ let SALESFORCE_INTEGRATION_KEY = 'salesforce';
25
+ let INTERCOM_INTEGRATION_KEY = 'intercom';
26
+ let TYPEFORM_INTEGRATION_KEY = 'typeform';
27
+ let XERO_INTEGRATION_KEY = 'xero';
28
+ let ZENDESK_INTEGRATION_KEY = 'zendesk';
29
+ let HUBSPOT_INTEGRATION_KEY = 'hubspot';
30
+ let HUBSPOT_DEVELOPER_PLATFORM_OAUTH_METHOD_ID = 'developer_platform_oauth';
31
+ let LOOPBACK_REDIRECT_NORMALIZED_INTEGRATIONS = new Set([
32
+ INTERCOM_INTEGRATION_KEY,
33
+ NOTION_INTEGRATION_KEY,
34
+ SALESFORCE_INTEGRATION_KEY,
35
+ TYPEFORM_INTEGRATION_KEY,
36
+ XERO_INTEGRATION_KEY,
37
+ ZENDESK_INTEGRATION_KEY
38
+ ]);
39
+
40
+ type AuthSetupOptions = WithProfile &
41
+ JsonInput & {
42
+ authMethodId?: string;
43
+ clientId?: string;
44
+ clientSecret?: string;
45
+ oauthCredential?: string;
46
+ scopes?: string;
47
+ };
48
+
49
+ export let normalizeCallbackRedirectUriForIntegration = (
50
+ integration: string,
51
+ redirectUri: string,
52
+ authMethodId?: string
53
+ ) => {
54
+ if (
55
+ LOOPBACK_REDIRECT_NORMALIZED_INTEGRATIONS.has(integration) ||
56
+ (integration === HUBSPOT_INTEGRATION_KEY &&
57
+ authMethodId === HUBSPOT_DEVELOPER_PLATFORM_OAUTH_METHOD_ID)
58
+ ) {
59
+ return normalizeMicrosoftRedirectUri(redirectUri);
60
+ }
61
+
62
+ return normalizeMicrosoftRedirectUriForIntegration(integration, redirectUri);
63
+ };
64
+
65
+ export let listAuth = async (opts: WithProfile) => {
66
+ let { store, profile } = await createClientContext(opts);
67
+ return store.listAuth(profile.id);
68
+ };
69
+
70
+ export let getAuth = async (opts: WithProfile & { authMethodId?: string }) => {
71
+ let { store, profile } = await createClientContext(opts);
72
+ return store.getAuth(profile.id, opts.authMethodId);
73
+ };
74
+
75
+ export let listOAuthCredentials = async (
76
+ opts: Pick<WithProfile, 'integration'> & { authMethodId?: string }
77
+ ) => {
78
+ let { store } = await openIntegrationStore(opts.integration);
79
+ return store.listOAuthCredentials(opts.authMethodId).map(credential => ({
80
+ id: credential.id,
81
+ name: credential.name,
82
+ authMethodId: credential.authMethodId,
83
+ clientId: credential.clientId
84
+ }));
85
+ };
86
+
87
+ export let addOAuthCredentials = async (
88
+ opts: Pick<WithProfile, 'integration'> & {
89
+ authMethodId?: string;
90
+ name?: string;
91
+ clientId?: string;
92
+ clientSecret?: string;
93
+ }
94
+ ): Promise<SlatesOAuthCredentialRecord> => {
95
+ let { store, client } = await createIntegrationClientContext({
96
+ integration: opts.integration
97
+ });
98
+ let authMethod = await chooseAuthMethod({
99
+ client,
100
+ authMethodId: opts.authMethodId,
101
+ forcePrompt: !opts.authMethodId
102
+ });
103
+
104
+ if (authMethod.type !== 'auth.oauth') {
105
+ throw new Error(`Authentication method ${authMethod.id} is not OAuth.`);
106
+ }
107
+
108
+ let clientId = opts.clientId ?? (await promptForString({ message: 'OAuth client ID' }));
109
+ let clientSecret =
110
+ opts.clientSecret ??
111
+ (await promptForString({ message: 'OAuth client secret', secret: true }));
112
+ let name =
113
+ opts.name ??
114
+ (await promptForString({
115
+ message: 'Credential name',
116
+ defaultValue: `${authMethod.name} credentials`
117
+ }));
118
+
119
+ let credential = store.upsertOAuthCredential({
120
+ name,
121
+ authMethodId: authMethod.id,
122
+ clientId,
123
+ clientSecret
124
+ });
125
+ await store.save();
126
+ return credential;
127
+ };
128
+
129
+ let createOAuthCredentialInteractive = async (opts: {
130
+ store: Awaited<ReturnType<typeof createClientContext>>['store'];
131
+ authMethod: { id: string; name: string; type: string };
132
+ clientId?: string;
133
+ clientSecret?: string;
134
+ }) => {
135
+ let clientId = opts.clientId ?? (await promptForString({ message: 'OAuth client ID' }));
136
+ let clientSecret =
137
+ opts.clientSecret ??
138
+ (await promptForString({ message: 'OAuth client secret', secret: true }));
139
+ let name = await promptForString({
140
+ message: 'Credential name',
141
+ defaultValue: `${opts.authMethod.name} credentials`
142
+ });
143
+
144
+ let credential = opts.store.upsertOAuthCredential({
145
+ name,
146
+ authMethodId: opts.authMethod.id,
147
+ clientId,
148
+ clientSecret
149
+ });
150
+ await opts.store.save();
151
+ return credential;
152
+ };
153
+
154
+ let chooseOAuthCredentialsForSetup = async (opts: {
155
+ store: Awaited<ReturnType<typeof createClientContext>>['store'];
156
+ authMethod: { id: string; name: string; type: string };
157
+ clientId?: string;
158
+ clientSecret?: string;
159
+ oauthCredential?: string;
160
+ }) => {
161
+ if (opts.authMethod.type !== 'auth.oauth') {
162
+ return null;
163
+ }
164
+
165
+ if (opts.clientId || opts.clientSecret) {
166
+ let credential = await createOAuthCredentialInteractive({
167
+ store: opts.store,
168
+ authMethod: opts.authMethod,
169
+ clientId: opts.clientId,
170
+ clientSecret: opts.clientSecret
171
+ });
172
+ return {
173
+ credential,
174
+ clientId: credential.clientId,
175
+ clientSecret: credential.clientSecret
176
+ };
177
+ }
178
+
179
+ if (opts.oauthCredential) {
180
+ let credential = opts.store.getOAuthCredential(opts.oauthCredential, opts.authMethod.id);
181
+ if (!credential) {
182
+ throw new Error(`Unknown OAuth credentials: ${opts.oauthCredential}`);
183
+ }
184
+
185
+ return {
186
+ credential,
187
+ clientId: credential.clientId,
188
+ clientSecret: credential.clientSecret
189
+ };
190
+ }
191
+
192
+ let credentials = opts.store.listOAuthCredentials(opts.authMethod.id);
193
+ if (credentials.length === 0) {
194
+ let credential = await createOAuthCredentialInteractive({
195
+ store: opts.store,
196
+ authMethod: opts.authMethod
197
+ });
198
+ return {
199
+ credential,
200
+ clientId: credential.clientId,
201
+ clientSecret: credential.clientSecret
202
+ };
203
+ }
204
+
205
+ if (credentials.length === 1) {
206
+ let useExisting = await confirm({
207
+ message: `Use saved OAuth credentials "${credentials[0]!.name}"?`,
208
+ default: true
209
+ });
210
+
211
+ if (useExisting) {
212
+ let credential = credentials[0]!;
213
+ return {
214
+ credential,
215
+ clientId: credential.clientId,
216
+ clientSecret: credential.clientSecret
217
+ };
218
+ }
219
+
220
+ let credential = await createOAuthCredentialInteractive({
221
+ store: opts.store,
222
+ authMethod: opts.authMethod
223
+ });
224
+ return {
225
+ credential,
226
+ clientId: credential.clientId,
227
+ clientSecret: credential.clientSecret
228
+ };
229
+ }
230
+
231
+ let selected = await select({
232
+ message: 'Choose OAuth credentials',
233
+ choices: [
234
+ ...credentials.map(credential => ({
235
+ name: `${credential.name} (${credential.clientId})`,
236
+ value: credential.id
237
+ })),
238
+ {
239
+ name: 'Create new OAuth credentials',
240
+ value: '__new__'
241
+ }
242
+ ]
243
+ });
244
+
245
+ if (selected === '__new__') {
246
+ let credential = await createOAuthCredentialInteractive({
247
+ store: opts.store,
248
+ authMethod: opts.authMethod
249
+ });
250
+ return {
251
+ credential,
252
+ clientId: credential.clientId,
253
+ clientSecret: credential.clientSecret
254
+ };
255
+ }
256
+
257
+ let credential = opts.store.getOAuthCredential(selected, opts.authMethod.id);
258
+ if (!credential) {
259
+ throw new Error(`Unknown OAuth credentials: ${selected}`);
260
+ }
261
+
262
+ return {
263
+ credential,
264
+ clientId: credential.clientId,
265
+ clientSecret: credential.clientSecret
266
+ };
267
+ };
268
+
269
+ let runAuthSetup = async (opts: AuthSetupOptions): Promise<SlatesStoredAuth> => {
270
+ let { store, profile, client } = await createClientContext({
271
+ ...opts,
272
+ autoRefresh: false
273
+ });
274
+ client.clearAuth();
275
+ let authMethod = await chooseAuthMethod({
276
+ client,
277
+ authMethodId: opts.authMethodId,
278
+ forcePrompt: !opts.authMethodId
279
+ });
280
+
281
+ let defaultInput = authMethod.capabilities.getDefaultInput?.enabled
282
+ ? ((await client.getDefaultAuthInput(authMethod.id)).input ?? {})
283
+ : {};
284
+ let authInput =
285
+ parseJsonObject(opts.input, 'auth input') ??
286
+ (await promptForObjectSchema(authMethod.inputSchema, defaultInput));
287
+
288
+ if (authMethod.capabilities.handleChangedInput?.enabled) {
289
+ authInput =
290
+ (
291
+ await client.updateAuthInput({
292
+ authenticationMethodId: authMethod.id,
293
+ previousInput: null,
294
+ newInput: authInput
295
+ })
296
+ ).input ?? authInput;
297
+ }
298
+
299
+ let output: JsonObject;
300
+ let finalInput = authInput;
301
+ let callbackState: JsonObject | null = null;
302
+ let scopes = parseList(opts.scopes);
303
+
304
+ if (authMethod.type === 'auth.oauth') {
305
+ let callback = await createOAuthCallbackListener();
306
+ let redirectUri = normalizeCallbackRedirectUriForIntegration(
307
+ opts.integration,
308
+ callback.redirectUri,
309
+ authMethod.id
310
+ );
311
+ console.log(`OAuth redirect URL: ${redirectUri}`);
312
+
313
+ let resolvedOAuthCredentials = await chooseOAuthCredentialsForSetup({
314
+ store,
315
+ authMethod,
316
+ clientId: opts.clientId,
317
+ clientSecret: opts.clientSecret,
318
+ oauthCredential: opts.oauthCredential
319
+ });
320
+ if (!resolvedOAuthCredentials) {
321
+ throw new Error(`Authentication method ${authMethod.id} is not OAuth.`);
322
+ }
323
+
324
+ let clientId = resolvedOAuthCredentials.clientId;
325
+ let clientSecret = resolvedOAuthCredentials.clientSecret;
326
+ scopes = await chooseScopes(authMethod, scopes);
327
+
328
+ let authorizationUrl = await client.getAuthorizationUrl({
329
+ authenticationMethodId: authMethod.id,
330
+ redirectUri,
331
+ state: callback.state,
332
+ input: authInput,
333
+ clientId,
334
+ clientSecret,
335
+ scopes
336
+ });
337
+
338
+ callbackState = authorizationUrl.callbackState ?? null;
339
+ finalInput = authorizationUrl.input ?? authInput;
340
+
341
+ printBrowserUrl(authorizationUrl.authorizationUrl);
342
+ let callbackResult = await callback.wait();
343
+ if (callbackResult.state !== callback.state) {
344
+ throw new Error('OAuth state mismatch.');
345
+ }
346
+
347
+ let authOutput = await client.handleAuthorizationCallback({
348
+ authenticationMethodId: authMethod.id,
349
+ code: callbackResult.code,
350
+ state: callbackResult.state,
351
+ redirectUri,
352
+ input: finalInput,
353
+ clientId,
354
+ clientSecret,
355
+ scopes,
356
+ callbackParams: callbackResult.callbackParams,
357
+ callbackState: callbackState ?? undefined
358
+ });
359
+
360
+ output = authOutput.output;
361
+ finalInput = authOutput.input ?? finalInput;
362
+ scopes = authOutput.scopes ?? scopes;
363
+
364
+ let profileInfo = authMethod.capabilities.getProfile?.enabled
365
+ ? await client.getAuthProfile({
366
+ authenticationMethodId: authMethod.id,
367
+ output,
368
+ input: finalInput,
369
+ scopes
370
+ })
371
+ : null;
372
+
373
+ let stored = store.upsertAuth(profile.id, {
374
+ authMethodId: authMethod.id,
375
+ authMethodName: authMethod.name,
376
+ authType: authMethod.type,
377
+ input: finalInput,
378
+ output,
379
+ oauthCredentialId: resolvedOAuthCredentials.credential?.id,
380
+ scopes,
381
+ clientId,
382
+ clientSecret,
383
+ callbackState,
384
+ profile: profileInfo?.profile ?? null
385
+ });
386
+
387
+ await store.save();
388
+ return stored;
389
+ }
390
+
391
+ output = (
392
+ await client.getAuthOutput({
393
+ authenticationMethodId: authMethod.id,
394
+ input: authInput
395
+ })
396
+ ).output;
397
+
398
+ let profileInfo = authMethod.capabilities.getProfile?.enabled
399
+ ? await client.getAuthProfile({
400
+ authenticationMethodId: authMethod.id,
401
+ output,
402
+ input: finalInput,
403
+ scopes
404
+ })
405
+ : null;
406
+
407
+ let stored = store.upsertAuth(profile.id, {
408
+ authMethodId: authMethod.id,
409
+ authMethodName: authMethod.name,
410
+ authType: authMethod.type,
411
+ input: finalInput,
412
+ output,
413
+ scopes,
414
+ profile: profileInfo?.profile ?? null
415
+ });
416
+
417
+ await store.save();
418
+ return stored;
419
+ };
420
+
421
+ export let setupAuth = async (opts: AuthSetupOptions) => runAuthSetup(opts);
422
+
423
+ export let refreshAuth = async (opts: WithProfile & { authMethodId?: string }) => {
424
+ let { store, profile, client } = await createClientContext(opts);
425
+ let storedAuth = store.getAuth(profile.id, opts.authMethodId);
426
+ if (!storedAuth) {
427
+ throw new Error('No stored authentication was found for this profile.');
428
+ }
429
+
430
+ let authMethod = await client.getAuthMethod(storedAuth.authMethodId);
431
+ if (!authMethod.authenticationMethod.capabilities.handleTokenRefresh?.enabled) {
432
+ throw new Error('This authentication method does not support token refresh.');
433
+ }
434
+
435
+ let refreshed = await client.refreshToken({
436
+ authenticationMethodId: storedAuth.authMethodId,
437
+ output: storedAuth.output,
438
+ input: storedAuth.input,
439
+ clientId: storedAuth.clientId ?? '',
440
+ clientSecret: storedAuth.clientSecret ?? '',
441
+ scopes: storedAuth.scopes
442
+ });
443
+
444
+ let updated = store.upsertAuth(profile.id, {
445
+ ...storedAuth,
446
+ input: refreshed.input ?? storedAuth.input,
447
+ output: refreshed.output
448
+ });
449
+ await store.save();
450
+ return updated;
451
+ };
@@ -0,0 +1,31 @@
1
+ import { createClientContext } from '../lib/context';
2
+ import { parseJsonObject, promptForObjectSchema } from '../lib/prompts';
3
+ import type { JsonInput, WithProfile } from '../lib/types';
4
+
5
+ export let getConfig = async (opts: WithProfile) => {
6
+ let { profile } = await createClientContext(opts);
7
+ return profile.config;
8
+ };
9
+
10
+ export let getConfigSchema = async (opts: WithProfile) => {
11
+ let { client } = await createClientContext(opts);
12
+ return client.getConfigSchema();
13
+ };
14
+
15
+ export let setConfig = async (opts: WithProfile & JsonInput) => {
16
+ let { store, profile, client } = await createClientContext(opts);
17
+ let previousConfig = profile.config;
18
+ let schema = (await client.getConfigSchema()).schema;
19
+ let defaultConfig = (await client.getDefaultConfig()).config ?? {};
20
+ let desiredConfig =
21
+ parseJsonObject(opts.input, 'config input') ??
22
+ (await promptForObjectSchema(schema, previousConfig ?? defaultConfig));
23
+ let result = await client.updateConfig(previousConfig, desiredConfig);
24
+ let finalConfig = result.config ?? desiredConfig;
25
+ store.setProfileConfig(profile.id, finalConfig);
26
+ await store.save();
27
+ return {
28
+ ...result,
29
+ config: finalConfig
30
+ };
31
+ };
@@ -0,0 +1,6 @@
1
+ export * from './auth';
2
+ export * from './config';
3
+ export * from './profiles';
4
+ export * from './repl';
5
+ export * from './test';
6
+ export * from './tools';
@@ -0,0 +1,151 @@
1
+ import { input } from '@inquirer/prompts';
2
+ import { createSlatesClientFromProfile, type openSlatesCliStore } from '@slates/profiles';
3
+ import path from 'path';
4
+ import { chooseProfile, openIntegrationStore, syncProfileMetadata } from '../lib/context';
5
+ import type { WithProfile } from '../lib/types';
6
+
7
+ let normalizeEntry = (rootDir: string, entry: string) => {
8
+ let absolute = path.isAbsolute(entry) ? entry : path.resolve(process.cwd(), entry);
9
+ let relative = path.relative(rootDir, absolute);
10
+ return relative && !relative.startsWith('..') && !path.isAbsolute(relative)
11
+ ? relative
12
+ : absolute;
13
+ };
14
+
15
+ let getNextSetupProfileName = async (
16
+ store: Awaited<ReturnType<typeof openSlatesCliStore>>
17
+ ) => {
18
+ let names = new Set(store.listProfiles().map(profile => profile.name));
19
+ if (!names.has('default')) {
20
+ return 'default';
21
+ }
22
+
23
+ let suffix = 2;
24
+ while (names.has(`default-${suffix}`)) {
25
+ suffix += 1;
26
+ }
27
+
28
+ return `default-${suffix}`;
29
+ };
30
+
31
+ let createProfile = async (
32
+ opts: WithProfile & {
33
+ name?: string;
34
+ entry?: string;
35
+ exportName?: string;
36
+ useAsDefault?: boolean;
37
+ initializeConfig?: boolean;
38
+ interactive?: boolean;
39
+ }
40
+ ) => {
41
+ let { integration, store } = await openIntegrationStore(opts.integration);
42
+ let interactive = opts.interactive ?? true;
43
+
44
+ let defaultName =
45
+ opts.name ??
46
+ (opts.initializeConfig
47
+ ? await getNextSetupProfileName(store)
48
+ : `profile-${store.listProfiles().length + 1}`);
49
+ let name =
50
+ opts.name ??
51
+ (interactive
52
+ ? await input({ message: 'Profile name', default: defaultName })
53
+ : defaultName);
54
+ let defaultEntry = opts.entry ?? integration.entry;
55
+ let entry =
56
+ opts.entry ??
57
+ (interactive
58
+ ? await input({
59
+ message: 'Local slate entry file',
60
+ default: defaultEntry
61
+ })
62
+ : defaultEntry);
63
+ let exportName =
64
+ opts.exportName ??
65
+ (interactive
66
+ ? await input({ message: 'Export name (optional)', default: 'provider' })
67
+ : 'provider');
68
+
69
+ let profile = store.upsertProfile({
70
+ name,
71
+ target: {
72
+ type: 'local',
73
+ entry: normalizeEntry(store.rootDir, entry),
74
+ exportName: exportName.trim() ? exportName.trim() : undefined
75
+ }
76
+ });
77
+
78
+ let client = await createSlatesClientFromProfile(profile);
79
+ await syncProfileMetadata({ store, profile, client });
80
+
81
+ if (opts.initializeConfig) {
82
+ let defaultConfig = (await client.getDefaultConfig()).config ?? {};
83
+ let result = await client.updateConfig(null, defaultConfig);
84
+ store.setProfileConfig(profile.id, result.config ?? defaultConfig);
85
+ }
86
+
87
+ if (opts.useAsDefault ?? store.listProfiles().length === 1) {
88
+ store.setCurrentProfile(profile.id);
89
+ }
90
+
91
+ await store.save();
92
+
93
+ return profile;
94
+ };
95
+
96
+ export let addProfile = async (
97
+ opts: WithProfile & {
98
+ name?: string;
99
+ entry?: string;
100
+ exportName?: string;
101
+ useAsDefault?: boolean;
102
+ }
103
+ ) =>
104
+ createProfile({
105
+ ...opts,
106
+ interactive: true
107
+ });
108
+
109
+ export let setupIntegration = async (
110
+ opts: WithProfile & {
111
+ name?: string;
112
+ exportName?: string;
113
+ }
114
+ ) =>
115
+ createProfile({
116
+ ...opts,
117
+ useAsDefault: true,
118
+ initializeConfig: true,
119
+ interactive: false
120
+ });
121
+
122
+ export let listProfiles = async (opts: Pick<WithProfile, 'integration'>) => {
123
+ let { store } = await openIntegrationStore(opts.integration);
124
+ let current = store.getProfile();
125
+ return store.listProfiles().map(profile => ({
126
+ name: profile.name,
127
+ id: profile.id,
128
+ current: profile.id === current?.id,
129
+ entry: profile.target.type === 'local' ? profile.target.entry : null,
130
+ authMethods: Object.keys(profile.auth)
131
+ }));
132
+ };
133
+
134
+ export let getProfile = async (opts: WithProfile) => {
135
+ let { store } = await openIntegrationStore(opts.integration);
136
+ return store.requireProfile(opts.profile);
137
+ };
138
+
139
+ export let useProfile = async (opts: WithProfile) => {
140
+ let { store, profile } = await chooseProfile(opts);
141
+ store.setCurrentProfile(profile.id);
142
+ await store.save();
143
+ return profile;
144
+ };
145
+
146
+ export let removeProfile = async (opts: WithProfile) => {
147
+ let { store, profile } = await chooseProfile(opts);
148
+ store.removeProfile(profile.id);
149
+ await store.save();
150
+ return profile;
151
+ };