@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,162 @@
1
+ import { checkbox } from '@inquirer/prompts';
2
+ import { randomUUID } from 'crypto';
3
+ import { createServer } from 'http';
4
+
5
+ let DEFAULT_OAUTH_CALLBACK_PORT = 45873;
6
+
7
+ export let chooseScopes = async (
8
+ authMethod: any,
9
+ initialScopes: string[]
10
+ ): Promise<string[]> => {
11
+ let scopeIds = (authMethod.scopes ?? []).map((scope: any) => scope.id);
12
+ if (scopeIds.length === 0) {
13
+ return initialScopes;
14
+ }
15
+
16
+ return (await checkbox({
17
+ message: 'Choose OAuth scopes',
18
+ choices: authMethod.scopes.map((scope: any) => ({
19
+ name: `${scope.title} (${scope.id})`,
20
+ value: scope.id,
21
+ checked:
22
+ initialScopes.length > 0
23
+ ? initialScopes.includes(scope.id)
24
+ : (scope.defaultChecked ?? true)
25
+ }))
26
+ })) as string[];
27
+ };
28
+
29
+ export let printBrowserUrl = (url: string) => {
30
+ console.log(`Open this URL in your browser:\n${url}`);
31
+ };
32
+
33
+ export let createOAuthCallbackListener = async () => {
34
+ return new Promise<{
35
+ redirectUri: string;
36
+ state: string;
37
+ wait: () => Promise<{
38
+ code: string;
39
+ state: string;
40
+ callbackParams: Record<string, string>;
41
+ }>;
42
+ }>((resolve, reject) => {
43
+ let expectedState = randomUUID();
44
+ let settled = false;
45
+ let callbackPort = Number(process.env.SLATES_OAUTH_PORT ?? DEFAULT_OAUTH_CALLBACK_PORT);
46
+
47
+ let server = createServer((req, res) => {
48
+ try {
49
+ let url = new URL(req.url ?? '/', 'http://127.0.0.1');
50
+ let code = url.searchParams.get('code');
51
+ let state = url.searchParams.get('state');
52
+ let oauthError = url.searchParams.get('error');
53
+ let oauthErrorDescription = url.searchParams.get('error_description');
54
+ let oauthErrorUri = url.searchParams.get('error_uri');
55
+
56
+ if (oauthError) {
57
+ let description = oauthErrorDescription ?? 'No error description was provided.';
58
+ let errorMessage = `OAuth callback returned "${oauthError}": ${description}${
59
+ oauthErrorUri ? ` (${oauthErrorUri})` : ''
60
+ }`;
61
+
62
+ res.statusCode = 400;
63
+ res.end(errorMessage);
64
+ server.close();
65
+ settled = true;
66
+ waiter.reject(new Error(errorMessage));
67
+ return;
68
+ }
69
+
70
+ if (!code || !state) {
71
+ res.statusCode = 400;
72
+ res.end('Missing code or state.');
73
+ server.close();
74
+ settled = true;
75
+ waiter.reject(
76
+ new Error(
77
+ `OAuth callback did not include the required query parameters. Received path: ${url.pathname}${url.search}`
78
+ )
79
+ );
80
+ return;
81
+ }
82
+
83
+ res.end('Authentication complete. You can close this window.');
84
+ server.close();
85
+ settled = true;
86
+ waiter.resolve({
87
+ code,
88
+ state,
89
+ callbackParams: Object.fromEntries(url.searchParams.entries())
90
+ });
91
+ } catch (error) {
92
+ server.close();
93
+ settled = true;
94
+ waiter.reject(error);
95
+ }
96
+ });
97
+
98
+ let waiter = (() => {
99
+ let resolvePromise!: (value: {
100
+ code: string;
101
+ state: string;
102
+ callbackParams: Record<string, string>;
103
+ }) => void;
104
+ let rejectPromise!: (error: unknown) => void;
105
+ let promise = new Promise<{
106
+ code: string;
107
+ state: string;
108
+ callbackParams: Record<string, string>;
109
+ }>((resolveFn, rejectFn) => {
110
+ resolvePromise = resolveFn;
111
+ rejectPromise = rejectFn;
112
+ });
113
+
114
+ return {
115
+ promise,
116
+ resolve: resolvePromise,
117
+ reject: rejectPromise
118
+ };
119
+ })();
120
+
121
+ let timeout = setTimeout(
122
+ () => {
123
+ if (settled) return;
124
+ server.close();
125
+ waiter.reject(new Error('Timed out waiting for the OAuth callback.'));
126
+ },
127
+ 5 * 60 * 1000
128
+ );
129
+
130
+ server.on('close', () => clearTimeout(timeout));
131
+ server.on('error', error => {
132
+ if (settled) return;
133
+ settled = true;
134
+
135
+ let errno = error as NodeJS.ErrnoException;
136
+ if (errno.code === 'EADDRINUSE') {
137
+ reject(
138
+ new Error(
139
+ `OAuth callback port ${callbackPort} is already in use. Free that port or set SLATES_OAUTH_PORT to a different fixed port.`
140
+ )
141
+ );
142
+ return;
143
+ }
144
+
145
+ reject(error);
146
+ });
147
+
148
+ server.listen(callbackPort, '127.0.0.1', () => {
149
+ let address = server.address();
150
+ if (!address || typeof address === 'string') {
151
+ reject(new Error('Could not determine OAuth callback address.'));
152
+ return;
153
+ }
154
+
155
+ resolve({
156
+ redirectUri: `http://127.0.0.1:${address.port}/callback`,
157
+ state: expectedState,
158
+ wait: () => waiter.promise
159
+ });
160
+ });
161
+ });
162
+ };
@@ -0,0 +1,159 @@
1
+ import { checkbox, confirm, input, password, select } from '@inquirer/prompts';
2
+ import type { JsonObject } from './types';
3
+
4
+ export let prettyJson = (value: unknown) => JSON.stringify(value, null, 2);
5
+
6
+ export let parseJsonObject = (value: string | undefined, label: string): JsonObject | null => {
7
+ if (!value) return null;
8
+
9
+ let parsed = JSON.parse(value);
10
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
11
+ throw new Error(`${label} must be a JSON object.`);
12
+ }
13
+
14
+ return parsed;
15
+ };
16
+
17
+ export let parseList = (value: string | undefined) =>
18
+ (value ?? '')
19
+ .split(',')
20
+ .map(item => item.trim())
21
+ .filter(Boolean);
22
+
23
+ export let print = (value: unknown) => {
24
+ console.log(prettyJson(value));
25
+ };
26
+
27
+ export let promptForString = async (d: {
28
+ message: string;
29
+ defaultValue?: string;
30
+ secret?: boolean;
31
+ }) => {
32
+ if (d.secret) {
33
+ return password({
34
+ message: d.message,
35
+ mask: '*'
36
+ });
37
+ }
38
+
39
+ return input({
40
+ message: d.message,
41
+ default: d.defaultValue
42
+ });
43
+ };
44
+
45
+ export let promptForSchemaValue = async (d: {
46
+ key: string;
47
+ schema: any;
48
+ currentValue?: any;
49
+ required?: boolean;
50
+ }): Promise<any> => {
51
+ let schema = d.schema ?? {};
52
+ let label = schema.title ?? d.key;
53
+
54
+ if (schema.enum && Array.isArray(schema.enum) && schema.enum.length > 0) {
55
+ return select({
56
+ message: label,
57
+ default: d.currentValue,
58
+ choices: schema.enum.map((value: string) => ({
59
+ name: value,
60
+ value
61
+ }))
62
+ });
63
+ }
64
+
65
+ if (schema.type === 'boolean') {
66
+ return confirm({
67
+ message: label,
68
+ default: d.currentValue ?? false
69
+ });
70
+ }
71
+
72
+ if (schema.type === 'array') {
73
+ if (Array.isArray(schema.items?.enum) && schema.items.enum.length > 0) {
74
+ return checkbox({
75
+ message: label,
76
+ choices: schema.items.enum.map((value: string) => ({
77
+ name: value,
78
+ value,
79
+ checked: Array.isArray(d.currentValue) ? d.currentValue.includes(value) : false
80
+ }))
81
+ });
82
+ }
83
+
84
+ let raw = await input({
85
+ message: `${label} (JSON array)`,
86
+ default: Array.isArray(d.currentValue) ? prettyJson(d.currentValue) : undefined
87
+ });
88
+
89
+ if (!raw.trim()) return [];
90
+ return JSON.parse(raw);
91
+ }
92
+
93
+ if (schema.type === 'number' || schema.type === 'integer') {
94
+ let raw = await input({
95
+ message: label,
96
+ default: d.currentValue !== undefined ? String(d.currentValue) : undefined
97
+ });
98
+
99
+ if (!raw.trim() && !d.required) return undefined;
100
+ return Number(raw);
101
+ }
102
+
103
+ if (schema.type === 'object') {
104
+ let raw = await input({
105
+ message: `${label} (JSON object)`,
106
+ default: d.currentValue ? prettyJson(d.currentValue) : '{}'
107
+ });
108
+
109
+ if (!raw.trim()) return {};
110
+ return JSON.parse(raw);
111
+ }
112
+
113
+ let isSecret = /secret|token|password/i.test(d.key);
114
+ let raw = await promptForString({
115
+ message: label,
116
+ defaultValue: typeof d.currentValue === 'string' ? d.currentValue : undefined,
117
+ secret: isSecret
118
+ });
119
+
120
+ if (!raw.trim() && !d.required) return undefined;
121
+ return raw;
122
+ };
123
+
124
+ export let promptForObjectSchema = async (schema: any, initialValue: JsonObject = {}) => {
125
+ let properties = schema?.properties ?? {};
126
+ let required = new Set<string>(schema?.required ?? []);
127
+ let keys = Object.keys(properties);
128
+
129
+ if (keys.length === 0) {
130
+ if (Object.keys(initialValue).length === 0) {
131
+ return {};
132
+ }
133
+
134
+ let raw = await input({
135
+ message: 'Enter JSON object',
136
+ default: Object.keys(initialValue).length > 0 ? prettyJson(initialValue) : '{}'
137
+ });
138
+
139
+ if (!raw.trim()) return {};
140
+ return parseJsonObject(raw, 'JSON input') ?? {};
141
+ }
142
+
143
+ let result: JsonObject = { ...initialValue };
144
+
145
+ for (let key of keys) {
146
+ let value = await promptForSchemaValue({
147
+ key,
148
+ schema: properties[key],
149
+ currentValue: initialValue[key],
150
+ required: required.has(key)
151
+ });
152
+
153
+ if (value !== undefined) {
154
+ result[key] = value;
155
+ }
156
+ }
157
+
158
+ return result;
159
+ };
@@ -0,0 +1,10 @@
1
+ export type JsonObject = Record<string, any>;
2
+
3
+ export type WithProfile = {
4
+ integration: string;
5
+ profile?: string;
6
+ };
7
+
8
+ export type JsonInput = {
9
+ input?: string;
10
+ };