instant-cli 1.0.22 → 1.0.23-branch-cli-codex-update.25390498340.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/.turbo/turbo-build.log +1 -1
- package/__tests__/authClientAddGoogle.test.ts +92 -0
- package/__tests__/authClientList.test.ts +90 -0
- package/__tests__/authClientUpdate.test.ts +583 -0
- package/__tests__/oauthMock.ts +9 -1
- package/__tests__/redirectUriPrompt.test.ts +26 -0
- package/dist/commands/auth/client/add.d.ts +1 -2
- package/dist/commands/auth/client/add.d.ts.map +1 -1
- package/dist/commands/auth/client/add.js +173 -276
- package/dist/commands/auth/client/add.js.map +1 -1
- package/dist/commands/auth/client/delete.d.ts +1 -2
- package/dist/commands/auth/client/delete.d.ts.map +1 -1
- package/dist/commands/auth/client/delete.js +8 -18
- package/dist/commands/auth/client/delete.js.map +1 -1
- package/dist/commands/auth/client/list.d.ts.map +1 -1
- package/dist/commands/auth/client/list.js +11 -2
- package/dist/commands/auth/client/list.js.map +1 -1
- package/dist/commands/auth/client/shared.d.ts +72 -0
- package/dist/commands/auth/client/shared.d.ts.map +1 -0
- package/dist/commands/auth/client/shared.js +145 -0
- package/dist/commands/auth/client/shared.js.map +1 -0
- package/dist/commands/auth/client/update.d.ts +8 -0
- package/dist/commands/auth/client/update.d.ts.map +1 -0
- package/dist/commands/auth/client/update.js +515 -0
- package/dist/commands/auth/client/update.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +60 -13
- package/dist/index.js.map +1 -1
- package/dist/lib/oauth.d.ts +114 -7
- package/dist/lib/oauth.d.ts.map +1 -1
- package/dist/lib/oauth.js +51 -1
- package/dist/lib/oauth.js.map +1 -1
- package/package.json +4 -4
- package/src/commands/auth/client/add.ts +251 -330
- package/src/commands/auth/client/delete.ts +8 -20
- package/src/commands/auth/client/list.ts +21 -2
- package/src/commands/auth/client/shared.ts +195 -0
- package/src/commands/auth/client/update.ts +853 -0
- package/src/index.ts +74 -13
- package/src/lib/oauth.ts +83 -1
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { Effect, Match, Option, Schema } from 'effect';
|
|
2
|
-
import { FileSystem } from '@effect/platform';
|
|
3
2
|
import { BadArgsError } from "../../../errors.js";
|
|
4
3
|
import { GlobalOpts } from "../../../context/globalOpts.js";
|
|
5
|
-
import { optOrPrompt, optOrPromptBoolean, runUIEffect,
|
|
6
|
-
import { addOAuthClient, findName, getClientNameAndProvider, getOrCreateProvider, } from "../../../lib/oauth.js";
|
|
7
|
-
import { DEFAULT_OAUTH_CALLBACK_URL, GOOGLE_AUTHORIZATION_ENDPOINT, GOOGLE_DISCOVERY_ENDPOINT, GOOGLE_TOKEN_ENDPOINT, APPLE_AUTHORIZATION_ENDPOINT, APPLE_DISCOVERY_ENDPOINT, APPLE_TOKEN_ENDPOINT, LINKEDIN_AUTHORIZATION_ENDPOINT, LINKEDIN_DISCOVERY_ENDPOINT, LINKEDIN_TOKEN_ENDPOINT, } from '@instantdb/platform';
|
|
4
|
+
import { optOrPrompt, optOrPromptBoolean, runUIEffect, validateRequired, } from "../../../lib/ui.js";
|
|
5
|
+
import { addOAuthClient, findName, getClientNameAndProvider, getOrCreateProvider, GoogleAppTypeSchema, OAuthClient, } from "../../../lib/oauth.js";
|
|
6
|
+
import { DEFAULT_OAUTH_CALLBACK_URL, GOOGLE_AUTHORIZATION_ENDPOINT, GOOGLE_DISCOVERY_ENDPOINT, GOOGLE_TOKEN_ENDPOINT, APPLE_AUTHORIZATION_ENDPOINT, APPLE_DISCOVERY_ENDPOINT, APPLE_TOKEN_ENDPOINT, clerkDomainFromPublishableKey, LINKEDIN_AUTHORIZATION_ENDPOINT, LINKEDIN_DISCOVERY_ENDPOINT, LINKEDIN_TOKEN_ENDPOINT, } from '@instantdb/platform';
|
|
8
7
|
import { UI } from "../../../ui/index.js";
|
|
9
8
|
import chalk from 'chalk';
|
|
10
9
|
import boxen from 'boxen';
|
|
11
10
|
import { link } from "../../../logging.js";
|
|
11
|
+
import { appleKeyIdPrompt, applePrivateKeyFilePrompt, appleServicesIdPrompt, appleTeamIdPrompt, clerkPublishableKeyPrompt, clientIdPrompt, clientSecretPrompt, firebaseDiscoveryEndpoint, firebaseProjectIdPrompt, getFlag, hasAnyFlag, isTrueFlag, readPrivateKeyFile, redirectSetupMessages, redirectUriPrompt, validateFirebaseProjectId, } from "./shared.js";
|
|
12
12
|
export const ClientTypeSchema = Schema.Literal('google', 'github', 'apple', 'linkedin', 'clerk', 'firebase');
|
|
13
|
-
const
|
|
13
|
+
const googleConsoleUrl = 'https://console.developers.google.com/apis/credentials';
|
|
14
|
+
const githubDeveloperUrl = 'https://github.com/settings/developers';
|
|
15
|
+
const linkedinDeveloperUrl = 'https://www.linkedin.com/developers/apps';
|
|
16
|
+
const optionalRedirectPrompt = redirectUriPrompt({
|
|
17
|
+
heading: 'Custom redirect URI (optional):',
|
|
18
|
+
});
|
|
14
19
|
const selectGoogleAppType = (value) => Effect.gen(function* () {
|
|
15
20
|
const { yes } = yield* GlobalOpts;
|
|
16
21
|
return yield* Option.fromNullable(value).pipe(Effect.catchTag('NoSuchElementException', () => {
|
|
@@ -40,10 +45,100 @@ const selectGoogleAppType = (value) => Effect.gen(function* () {
|
|
|
40
45
|
message: 'Invalid app-type, must be one of: web, ios, android, button-for-web',
|
|
41
46
|
})));
|
|
42
47
|
});
|
|
48
|
+
const selectGoogleCredentialMode = Effect.fn(function* () {
|
|
49
|
+
return yield* runUIEffect(new UI.Select({
|
|
50
|
+
options: [
|
|
51
|
+
{
|
|
52
|
+
label: 'Use dev credentials' +
|
|
53
|
+
chalk.dim(' (works on localhost and Expo, no Google setup)'),
|
|
54
|
+
value: 'dev',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
label: 'Use my own credentials' +
|
|
58
|
+
chalk.dim(' (client ID and secret from Google Console)'),
|
|
59
|
+
value: 'custom',
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
promptText: 'Select Google credential mode:',
|
|
63
|
+
modifyOutput: UI.modifiers.piped([
|
|
64
|
+
UI.modifiers.topPadding,
|
|
65
|
+
UI.modifiers.dimOnComplete,
|
|
66
|
+
]),
|
|
67
|
+
defaultValue: 'dev',
|
|
68
|
+
})).pipe(Effect.catchTag('UIError', (e) => BadArgsError.make({ message: `UI error: ${e.message}` })));
|
|
69
|
+
});
|
|
70
|
+
const resolveGoogleCredentialMode = Effect.fn(function* ({ appType, opts, }) {
|
|
71
|
+
const { yes } = yield* GlobalOpts;
|
|
72
|
+
const devCredentialsFlag = isTrueFlag(getFlag(opts, 'dev-credentials'));
|
|
73
|
+
const hasProvidedSomeCustomCredentials = hasAnyFlag(opts, [
|
|
74
|
+
'client-id',
|
|
75
|
+
'client-secret',
|
|
76
|
+
'custom-redirect-uri',
|
|
77
|
+
]);
|
|
78
|
+
if (devCredentialsFlag && appType !== 'web') {
|
|
79
|
+
return yield* BadArgsError.make({
|
|
80
|
+
message: '--dev-credentials is only supported for --app-type web. Native Google clients need credentials from Google.',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (devCredentialsFlag && hasProvidedSomeCustomCredentials) {
|
|
84
|
+
return yield* BadArgsError.make({
|
|
85
|
+
message: '--dev-credentials cannot be combined with --client-id, --client-secret, or --custom-redirect-uri.',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (appType !== 'web') {
|
|
89
|
+
return 'custom';
|
|
90
|
+
}
|
|
91
|
+
if (hasProvidedSomeCustomCredentials) {
|
|
92
|
+
return 'custom';
|
|
93
|
+
}
|
|
94
|
+
if (devCredentialsFlag) {
|
|
95
|
+
return 'dev';
|
|
96
|
+
}
|
|
97
|
+
if (yes) {
|
|
98
|
+
return 'dev';
|
|
99
|
+
}
|
|
100
|
+
return yield* selectGoogleCredentialMode();
|
|
101
|
+
});
|
|
102
|
+
const printGoogleDevCredentialsClient = Effect.fn(function* ({ appType, client, }) {
|
|
103
|
+
yield* Effect.log(boxen([
|
|
104
|
+
`Google OAuth client created: ${client.client_name}`,
|
|
105
|
+
`App type: ${appType}`,
|
|
106
|
+
`Credentials: Instant dev credentials`,
|
|
107
|
+
`ID: ${client.id}`,
|
|
108
|
+
'',
|
|
109
|
+
'No Google Console setup required.',
|
|
110
|
+
'Works on localhost and Expo during development.',
|
|
111
|
+
'',
|
|
112
|
+
chalk.bold('Ready for production? Run:'),
|
|
113
|
+
` instant-cli auth client update --name ${client.client_name} --client-id <id> --client-secret <secret>`,
|
|
114
|
+
].join('\n'), { dimBorder: true, padding: { right: 1, left: 1 } }));
|
|
115
|
+
});
|
|
116
|
+
const printGoogleCustomCredentialsClient = Effect.fn(function* ({ appType, client, clientId, customRedirectUri, redirectUri, }) {
|
|
117
|
+
const redirectMessages = [];
|
|
118
|
+
if (appType === 'web' && redirectUri) {
|
|
119
|
+
redirectMessages.push(...redirectSetupMessages({
|
|
120
|
+
prompt: 'Add this redirect URI in Google Console',
|
|
121
|
+
redirectUri,
|
|
122
|
+
showCustomRedirectInstructions: Boolean(customRedirectUri),
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
yield* Effect.log(boxen([
|
|
126
|
+
`Google OAuth client created: ${client.client_name}`,
|
|
127
|
+
`App type: ${appType}`,
|
|
128
|
+
`ID: ${client.id}`,
|
|
129
|
+
`Google Client ID: ${client.client_id ?? clientId}`,
|
|
130
|
+
...redirectMessages,
|
|
131
|
+
].join('\n'), { dimBorder: true, padding: { right: 1, left: 1 } }));
|
|
132
|
+
});
|
|
43
133
|
const handleGoogleClient = Effect.fn(function* (opts) {
|
|
44
134
|
// This one requires special logic for getting client name
|
|
45
135
|
// because the suggested name includes the app type
|
|
46
136
|
const appType = yield* selectGoogleAppType(opts['app-type']);
|
|
137
|
+
const credentialMode = yield* resolveGoogleCredentialMode({
|
|
138
|
+
appType,
|
|
139
|
+
opts,
|
|
140
|
+
});
|
|
141
|
+
const useSharedCredentials = credentialMode === 'dev';
|
|
47
142
|
const { auth, provider } = yield* getOrCreateProvider('google');
|
|
48
143
|
const usedClientNames = new Set((auth.oauth_clients ?? []).map((client) => client.client_name));
|
|
49
144
|
const suggestedClientName = findName(`google-${appType}`, usedClientNames);
|
|
@@ -56,7 +151,10 @@ const handleGoogleClient = Effect.fn(function* (opts) {
|
|
|
56
151
|
defaultValue: suggestedClientName,
|
|
57
152
|
placeholder: suggestedClientName,
|
|
58
153
|
validate: validateRequired,
|
|
59
|
-
modifyOutput: UI.modifiers.piped([
|
|
154
|
+
modifyOutput: UI.modifiers.piped([
|
|
155
|
+
UI.modifiers.topPadding,
|
|
156
|
+
UI.modifiers.dimOnComplete,
|
|
157
|
+
]),
|
|
60
158
|
},
|
|
61
159
|
});
|
|
62
160
|
if (usedClientNames.has(clientName || '')) {
|
|
@@ -66,62 +164,41 @@ const handleGoogleClient = Effect.fn(function* (opts) {
|
|
|
66
164
|
}
|
|
67
165
|
const clientId = yield* optOrPrompt(opts['client-id'], {
|
|
68
166
|
simpleName: '--client-id',
|
|
69
|
-
required:
|
|
70
|
-
skipIf:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
modifyOutput: UI.modifiers.piped([
|
|
74
|
-
UI.modifiers.topPadding,
|
|
75
|
-
UI.modifiers.dimOnComplete,
|
|
76
|
-
]),
|
|
77
|
-
validate: validateRequired,
|
|
78
|
-
},
|
|
167
|
+
required: !useSharedCredentials,
|
|
168
|
+
skipIf: useSharedCredentials,
|
|
169
|
+
skipMessage: '--client-id is not compatible with --dev-credentials. Drop one or the other.',
|
|
170
|
+
prompt: clientIdPrompt({ providerUrl: googleConsoleUrl }),
|
|
79
171
|
});
|
|
172
|
+
const usesCustomWebCredentials = !useSharedCredentials && appType === 'web';
|
|
80
173
|
const clientSecret = yield* optOrPrompt(opts['client-secret'], {
|
|
81
|
-
required:
|
|
82
|
-
skipIf:
|
|
174
|
+
required: usesCustomWebCredentials,
|
|
175
|
+
skipIf: !usesCustomWebCredentials,
|
|
83
176
|
simpleName: '--client-secret',
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
modifyOutput: UI.modifiers.piped([
|
|
89
|
-
UI.modifiers.topPadding,
|
|
90
|
-
UI.modifiers.dimOnComplete,
|
|
91
|
-
]),
|
|
92
|
-
},
|
|
177
|
+
skipMessage: useSharedCredentials
|
|
178
|
+
? '--client-secret is not compatible with --dev-credentials. Drop one or the other.'
|
|
179
|
+
: undefined,
|
|
180
|
+
prompt: clientSecretPrompt({ providerUrl: googleConsoleUrl }),
|
|
93
181
|
});
|
|
94
182
|
const customRedirectUri = yield* optOrPrompt(opts['custom-redirect-uri'], {
|
|
95
183
|
required: false,
|
|
96
|
-
prompt:
|
|
97
|
-
prompt: '',
|
|
98
|
-
placeholder: 'https://yoursite.com/oauth/callback',
|
|
99
|
-
modifyOutput: UI.modifiers.piped([
|
|
100
|
-
(output, status) => {
|
|
101
|
-
if (status === 'idle') {
|
|
102
|
-
return (`\nCustom redirect URI (optional):
|
|
103
|
-
${chalk.dim('With a custom redirect URI, users will see "Redirecting to yoursite.com..." for a more branded experience.')}
|
|
104
|
-
${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all query parameters preserved.`)}\n\n` +
|
|
105
|
-
stripFirstBlankLine(output));
|
|
106
|
-
}
|
|
107
|
-
return `\nCustom redirect URI (optional):\n${stripFirstBlankLine(output)}`;
|
|
108
|
-
},
|
|
109
|
-
UI.modifiers.dimOnComplete,
|
|
110
|
-
]),
|
|
111
|
-
},
|
|
184
|
+
prompt: optionalRedirectPrompt,
|
|
112
185
|
simpleName: '--custom-redirect-uri',
|
|
113
|
-
skipIf:
|
|
114
|
-
skipMessage:
|
|
186
|
+
skipIf: !usesCustomWebCredentials,
|
|
187
|
+
skipMessage: useSharedCredentials
|
|
188
|
+
? '--custom-redirect-uri is not compatible with --dev-credentials.'
|
|
189
|
+
: 'Provided custom redirect URI when not using web app type.',
|
|
115
190
|
});
|
|
116
191
|
if (!clientName) {
|
|
117
192
|
return yield* BadArgsError.make({ message: 'Client name is required.' }); // Should never reach this
|
|
118
193
|
}
|
|
119
|
-
const redirectUri =
|
|
194
|
+
const redirectUri = useSharedCredentials
|
|
195
|
+
? undefined
|
|
196
|
+
: customRedirectUri || DEFAULT_OAUTH_CALLBACK_URL;
|
|
120
197
|
const response = yield* addOAuthClient({
|
|
121
198
|
providerId: provider.id,
|
|
122
199
|
clientName,
|
|
123
|
-
clientId,
|
|
124
|
-
clientSecret: clientSecret,
|
|
200
|
+
clientId: useSharedCredentials ? undefined : clientId,
|
|
201
|
+
clientSecret: useSharedCredentials ? undefined : clientSecret,
|
|
125
202
|
authorizationEndpoint: GOOGLE_AUTHORIZATION_ENDPOINT,
|
|
126
203
|
tokenEndpoint: GOOGLE_TOKEN_ENDPOINT,
|
|
127
204
|
discoveryEndpoint: GOOGLE_DISCOVERY_ENDPOINT,
|
|
@@ -130,22 +207,22 @@ ${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all que
|
|
|
130
207
|
appType,
|
|
131
208
|
skipNonceChecks: true,
|
|
132
209
|
},
|
|
210
|
+
useSharedCredentials,
|
|
133
211
|
});
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
212
|
+
if (useSharedCredentials) {
|
|
213
|
+
yield* printGoogleDevCredentialsClient({
|
|
214
|
+
appType,
|
|
215
|
+
client: response.client,
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
141
218
|
}
|
|
142
|
-
yield*
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
219
|
+
yield* printGoogleCustomCredentialsClient({
|
|
220
|
+
appType,
|
|
221
|
+
client: response.client,
|
|
222
|
+
clientId,
|
|
223
|
+
customRedirectUri,
|
|
224
|
+
redirectUri,
|
|
225
|
+
});
|
|
149
226
|
});
|
|
150
227
|
const handleGithubClient = Effect.fn(function* (opts) {
|
|
151
228
|
const { clientName, provider } = yield* getClientNameAndProvider('github', opts);
|
|
@@ -153,49 +230,19 @@ const handleGithubClient = Effect.fn(function* (opts) {
|
|
|
153
230
|
simpleName: '--client-id',
|
|
154
231
|
required: true,
|
|
155
232
|
skipIf: false,
|
|
156
|
-
prompt: {
|
|
157
|
-
prompt: `Client ID ${chalk.dim(`(from ${link('https://github.com/settings/developers')})`)}`,
|
|
158
|
-
modifyOutput: UI.modifiers.piped([
|
|
159
|
-
UI.modifiers.topPadding,
|
|
160
|
-
UI.modifiers.dimOnComplete,
|
|
161
|
-
]),
|
|
162
|
-
validate: validateRequired,
|
|
163
|
-
},
|
|
233
|
+
prompt: clientIdPrompt({ providerUrl: githubDeveloperUrl }),
|
|
164
234
|
});
|
|
165
235
|
const clientSecret = yield* optOrPrompt(opts['client-secret'], {
|
|
166
236
|
required: true,
|
|
167
237
|
skipIf: false,
|
|
168
238
|
simpleName: '--client-secret',
|
|
169
|
-
prompt: {
|
|
170
|
-
prompt: `Client Secret: ${chalk.dim(`(from ${link('https://github.com/settings/developers')})`)}`,
|
|
171
|
-
validate: validateRequired,
|
|
172
|
-
sensitive: true,
|
|
173
|
-
modifyOutput: UI.modifiers.piped([
|
|
174
|
-
UI.modifiers.topPadding,
|
|
175
|
-
UI.modifiers.dimOnComplete,
|
|
176
|
-
]),
|
|
177
|
-
},
|
|
239
|
+
prompt: clientSecretPrompt({ providerUrl: githubDeveloperUrl }),
|
|
178
240
|
});
|
|
179
241
|
const customRedirectUri = yield* optOrPrompt(opts['custom-redirect-uri'], {
|
|
180
242
|
required: false,
|
|
181
243
|
simpleName: '--custom-redirect-uri',
|
|
182
244
|
skipIf: false,
|
|
183
|
-
prompt:
|
|
184
|
-
prompt: '',
|
|
185
|
-
placeholder: 'https://yoursite.com/oauth/callback',
|
|
186
|
-
modifyOutput: UI.modifiers.piped([
|
|
187
|
-
(output, status) => {
|
|
188
|
-
if (status === 'idle') {
|
|
189
|
-
return (`\nCustom redirect URI (optional):
|
|
190
|
-
${chalk.dim('With a custom redirect URI, users will see "Redirecting to yoursite.com..." for a more branded experience.')}
|
|
191
|
-
${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all query parameters preserved.`)}\n\n` +
|
|
192
|
-
stripFirstBlankLine(output));
|
|
193
|
-
}
|
|
194
|
-
return `\nCustom redirect URI (optional):\n${stripFirstBlankLine(output)}`;
|
|
195
|
-
},
|
|
196
|
-
UI.modifiers.dimOnComplete,
|
|
197
|
-
]),
|
|
198
|
-
},
|
|
245
|
+
prompt: optionalRedirectPrompt,
|
|
199
246
|
});
|
|
200
247
|
if (!clientName) {
|
|
201
248
|
return yield* BadArgsError.make({ message: 'Client name is required.' });
|
|
@@ -211,13 +258,11 @@ ${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all que
|
|
|
211
258
|
redirectTo: redirectUri,
|
|
212
259
|
meta: { providerName: 'github' },
|
|
213
260
|
});
|
|
214
|
-
const redirectMessages =
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
redirectMessages.push(`You can test it by visiting: ${chalk.bold(redirectUri + '?test-redirect=true')}`);
|
|
220
|
-
}
|
|
261
|
+
const redirectMessages = redirectSetupMessages({
|
|
262
|
+
prompt: 'Add this callback URL in your GitHub OAuth App settings',
|
|
263
|
+
redirectUri,
|
|
264
|
+
showCustomRedirectInstructions: Boolean(customRedirectUri),
|
|
265
|
+
});
|
|
221
266
|
yield* Effect.log(boxen([
|
|
222
267
|
`GitHub OAuth client created: ${response.client.client_name}`,
|
|
223
268
|
`ID: ${response.client.id}`,
|
|
@@ -231,49 +276,19 @@ const handleLinkedInClient = Effect.fn(function* (opts) {
|
|
|
231
276
|
simpleName: '--client-id',
|
|
232
277
|
required: true,
|
|
233
278
|
skipIf: false,
|
|
234
|
-
prompt: {
|
|
235
|
-
prompt: `Client ID: ${chalk.dim(`(from ${link('https://www.linkedin.com/developers/apps')})`)}`,
|
|
236
|
-
modifyOutput: UI.modifiers.piped([
|
|
237
|
-
UI.modifiers.topPadding,
|
|
238
|
-
UI.modifiers.dimOnComplete,
|
|
239
|
-
]),
|
|
240
|
-
validate: validateRequired,
|
|
241
|
-
},
|
|
279
|
+
prompt: clientIdPrompt({ providerUrl: linkedinDeveloperUrl }),
|
|
242
280
|
});
|
|
243
281
|
const clientSecret = yield* optOrPrompt(opts['client-secret'], {
|
|
244
282
|
required: true,
|
|
245
283
|
skipIf: false,
|
|
246
284
|
simpleName: '--client-secret',
|
|
247
|
-
prompt: {
|
|
248
|
-
prompt: `Client Secret: ${chalk.dim(`(from ${link('https://www.linkedin.com/developers/apps')})`)}`,
|
|
249
|
-
validate: validateRequired,
|
|
250
|
-
sensitive: true,
|
|
251
|
-
modifyOutput: UI.modifiers.piped([
|
|
252
|
-
UI.modifiers.topPadding,
|
|
253
|
-
UI.modifiers.dimOnComplete,
|
|
254
|
-
]),
|
|
255
|
-
},
|
|
285
|
+
prompt: clientSecretPrompt({ providerUrl: linkedinDeveloperUrl }),
|
|
256
286
|
});
|
|
257
287
|
const customRedirectUri = yield* optOrPrompt(opts['custom-redirect-uri'], {
|
|
258
288
|
required: false,
|
|
259
289
|
simpleName: '--custom-redirect-uri',
|
|
260
290
|
skipIf: false,
|
|
261
|
-
prompt:
|
|
262
|
-
prompt: '',
|
|
263
|
-
placeholder: 'https://yoursite.com/oauth/callback',
|
|
264
|
-
modifyOutput: UI.modifiers.piped([
|
|
265
|
-
(output, status) => {
|
|
266
|
-
if (status === 'idle') {
|
|
267
|
-
return (`\nCustom redirect URI (optional):
|
|
268
|
-
${chalk.dim('With a custom redirect URI, users will see "Redirecting to yoursite.com..." for a more branded experience.')}
|
|
269
|
-
${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all query parameters preserved.`)}\n\n` +
|
|
270
|
-
stripFirstBlankLine(output));
|
|
271
|
-
}
|
|
272
|
-
return `\nCustom redirect URI (optional):\n${stripFirstBlankLine(output)}`;
|
|
273
|
-
},
|
|
274
|
-
UI.modifiers.dimOnComplete,
|
|
275
|
-
]),
|
|
276
|
-
},
|
|
291
|
+
prompt: optionalRedirectPrompt,
|
|
277
292
|
});
|
|
278
293
|
if (!clientName) {
|
|
279
294
|
return yield* BadArgsError.make({ message: 'Client name is required.' });
|
|
@@ -289,13 +304,11 @@ ${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all que
|
|
|
289
304
|
discoveryEndpoint: LINKEDIN_DISCOVERY_ENDPOINT,
|
|
290
305
|
redirectTo: redirectUri,
|
|
291
306
|
});
|
|
292
|
-
const redirectMessages =
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
redirectMessages.push(`You can test it by visiting: ${chalk.bold(redirectUri + '?test-redirect=true')}`);
|
|
298
|
-
}
|
|
307
|
+
const redirectMessages = redirectSetupMessages({
|
|
308
|
+
prompt: 'Add this redirect URI in your LinkedIn app settings',
|
|
309
|
+
redirectUri,
|
|
310
|
+
showCustomRedirectInstructions: Boolean(customRedirectUri),
|
|
311
|
+
});
|
|
299
312
|
yield* Effect.log(boxen([
|
|
300
313
|
`LinkedIn OAuth client created: ${response.client.client_name}`,
|
|
301
314
|
`ID: ${response.client.id}`,
|
|
@@ -303,22 +316,6 @@ ${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all que
|
|
|
303
316
|
...redirectMessages,
|
|
304
317
|
].join('\n'), { dimBorder: true, padding: { right: 1, left: 1 } }));
|
|
305
318
|
});
|
|
306
|
-
const readPrivateKeyFile = Effect.fn('readPrivateKeyFile')(function* (path) {
|
|
307
|
-
const fs = yield* FileSystem.FileSystem;
|
|
308
|
-
// Strip shell-escape backslashes so paths like "file\ (2).p8" resolve correctly.
|
|
309
|
-
// Only on POSIX — Windows uses backslashes as path separators.
|
|
310
|
-
const normalizedPath = process.platform === 'win32' ? path : path.replace(/\\(.)/g, '$1');
|
|
311
|
-
const contents = yield* fs.readFileString(normalizedPath, 'utf8').pipe(Effect.mapError((e) => new BadArgsError({
|
|
312
|
-
message: `Could not read private key file at ${normalizedPath}: ${e.message}`,
|
|
313
|
-
})));
|
|
314
|
-
const trimmed = contents.trim();
|
|
315
|
-
if (!trimmed) {
|
|
316
|
-
return yield* BadArgsError.make({
|
|
317
|
-
message: `Private key file at ${normalizedPath} is empty.`,
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
return trimmed;
|
|
321
|
-
});
|
|
322
319
|
const handleAppleClient = Effect.fn(function* (opts) {
|
|
323
320
|
const { yes } = yield* GlobalOpts;
|
|
324
321
|
const { clientName, provider } = yield* getClientNameAndProvider('apple', opts);
|
|
@@ -326,14 +323,7 @@ const handleAppleClient = Effect.fn(function* (opts) {
|
|
|
326
323
|
simpleName: '--services-id',
|
|
327
324
|
required: true,
|
|
328
325
|
skipIf: false,
|
|
329
|
-
prompt: {
|
|
330
|
-
prompt: `Services ID ${chalk.dim(`(from ${link('https://developer.apple.com/account/resources/identifiers/list/serviceId')})`)}`,
|
|
331
|
-
modifyOutput: UI.modifiers.piped([
|
|
332
|
-
UI.modifiers.topPadding,
|
|
333
|
-
UI.modifiers.dimOnComplete,
|
|
334
|
-
]),
|
|
335
|
-
validate: validateRequired,
|
|
336
|
-
},
|
|
326
|
+
prompt: appleServicesIdPrompt({}),
|
|
337
327
|
});
|
|
338
328
|
// If any web-flow flag is provided, enable web flow; otherwise ask
|
|
339
329
|
// (non-interactively with --yes we default to native-only).
|
|
@@ -359,42 +349,21 @@ const handleAppleClient = Effect.fn(function* (opts) {
|
|
|
359
349
|
required: true,
|
|
360
350
|
skipIf: skipWeb,
|
|
361
351
|
skipMessage: `--team-id ${webSkipMessage}`,
|
|
362
|
-
prompt: {
|
|
363
|
-
prompt: `Team ID ${chalk.dim(`(from ${link('https://developer.apple.com/account#MembershipDetailsCard')})`)}`,
|
|
364
|
-
validate: validateRequired,
|
|
365
|
-
modifyOutput: UI.modifiers.piped([
|
|
366
|
-
UI.modifiers.topPadding,
|
|
367
|
-
UI.modifiers.dimOnComplete,
|
|
368
|
-
]),
|
|
369
|
-
},
|
|
352
|
+
prompt: appleTeamIdPrompt({}),
|
|
370
353
|
});
|
|
371
354
|
const keyId = yield* optOrPrompt(opts['key-id'], {
|
|
372
355
|
simpleName: '--key-id',
|
|
373
356
|
required: true,
|
|
374
357
|
skipIf: skipWeb,
|
|
375
358
|
skipMessage: `--key-id ${webSkipMessage}`,
|
|
376
|
-
prompt: {
|
|
377
|
-
prompt: `Key ID ${chalk.dim(`(from ${link('https://developer.apple.com/account/resources/authkeys/list')})`)}`,
|
|
378
|
-
validate: validateRequired,
|
|
379
|
-
modifyOutput: UI.modifiers.piped([
|
|
380
|
-
UI.modifiers.topPadding,
|
|
381
|
-
UI.modifiers.dimOnComplete,
|
|
382
|
-
]),
|
|
383
|
-
},
|
|
359
|
+
prompt: appleKeyIdPrompt({}),
|
|
384
360
|
});
|
|
385
361
|
const privateKeyPath = yield* optOrPrompt(opts['private-key-file'], {
|
|
386
362
|
simpleName: '--private-key-file',
|
|
387
363
|
required: true,
|
|
388
364
|
skipIf: skipWeb,
|
|
389
365
|
skipMessage: `--private-key-file ${webSkipMessage}`,
|
|
390
|
-
prompt: {
|
|
391
|
-
prompt: `Path to .p8 private key file ${chalk.dim('(downloaded from Apple)')}`,
|
|
392
|
-
validate: validateRequired,
|
|
393
|
-
modifyOutput: UI.modifiers.piped([
|
|
394
|
-
UI.modifiers.topPadding,
|
|
395
|
-
UI.modifiers.dimOnComplete,
|
|
396
|
-
]),
|
|
397
|
-
},
|
|
366
|
+
prompt: applePrivateKeyFilePrompt({}),
|
|
398
367
|
});
|
|
399
368
|
const privateKey = privateKeyPath
|
|
400
369
|
? yield* readPrivateKeyFile(privateKeyPath)
|
|
@@ -404,22 +373,7 @@ const handleAppleClient = Effect.fn(function* (opts) {
|
|
|
404
373
|
simpleName: '--custom-redirect-uri',
|
|
405
374
|
skipIf: skipWeb,
|
|
406
375
|
skipMessage: `--custom-redirect-uri ${webSkipMessage}`,
|
|
407
|
-
prompt:
|
|
408
|
-
prompt: '',
|
|
409
|
-
placeholder: 'https://yoursite.com/oauth/callback',
|
|
410
|
-
modifyOutput: UI.modifiers.piped([
|
|
411
|
-
(output, status) => {
|
|
412
|
-
if (status === 'idle') {
|
|
413
|
-
return (`\nCustom redirect URI (optional):
|
|
414
|
-
${chalk.dim('With a custom redirect URI, users will see "Redirecting to yoursite.com..." for a more branded experience.')}
|
|
415
|
-
${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all query parameters preserved.`)}\n\n` +
|
|
416
|
-
stripFirstBlankLine(output));
|
|
417
|
-
}
|
|
418
|
-
return `\nCustom redirect URI (optional):\n${stripFirstBlankLine(output)}`;
|
|
419
|
-
},
|
|
420
|
-
UI.modifiers.dimOnComplete,
|
|
421
|
-
]),
|
|
422
|
-
},
|
|
376
|
+
prompt: optionalRedirectPrompt,
|
|
423
377
|
});
|
|
424
378
|
if (!clientName) {
|
|
425
379
|
return yield* BadArgsError.make({ message: 'Client name is required.' });
|
|
@@ -448,14 +402,14 @@ ${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all que
|
|
|
448
402
|
`ID: ${response.client.id}`,
|
|
449
403
|
`Services ID: ${response.client.client_id ?? servicesId}`,
|
|
450
404
|
];
|
|
451
|
-
if (privateKey) {
|
|
405
|
+
if (privateKey && redirectUri) {
|
|
452
406
|
summaryLines.push(`Team ID: ${teamId}`);
|
|
453
407
|
summaryLines.push(`Key ID: ${keyId}`);
|
|
454
|
-
summaryLines.push(
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
}
|
|
408
|
+
summaryLines.push(...redirectSetupMessages({
|
|
409
|
+
prompt: `Add this return URL under your Services ID on ${link('https://developer.apple.com', 'developer.apple.com')}`,
|
|
410
|
+
redirectUri,
|
|
411
|
+
showCustomRedirectInstructions: Boolean(customRedirectUri),
|
|
412
|
+
}));
|
|
459
413
|
}
|
|
460
414
|
yield* Effect.log(boxen(summaryLines.join('\n'), {
|
|
461
415
|
dimBorder: true,
|
|
@@ -468,22 +422,7 @@ const handleClerkClient = Effect.fn(function* (opts) {
|
|
|
468
422
|
simpleName: '--publishable-key',
|
|
469
423
|
required: true,
|
|
470
424
|
skipIf: false,
|
|
471
|
-
prompt: {
|
|
472
|
-
prompt: `Clerk publishable key ${chalk.dim(`(from ${link('https://dashboard.clerk.com/last-active?path=api-keys')})`)}`,
|
|
473
|
-
placeholder: 'pk_********************************************************',
|
|
474
|
-
modifyOutput: UI.modifiers.piped([
|
|
475
|
-
UI.modifiers.topPadding,
|
|
476
|
-
UI.modifiers.dimOnComplete,
|
|
477
|
-
]),
|
|
478
|
-
validate: (val) => {
|
|
479
|
-
if (!val) {
|
|
480
|
-
return 'Publishable key is required';
|
|
481
|
-
}
|
|
482
|
-
if (!val.startsWith('pk_')) {
|
|
483
|
-
return 'Invalid publishable key. It should start with "pk_".';
|
|
484
|
-
}
|
|
485
|
-
},
|
|
486
|
-
},
|
|
425
|
+
prompt: clerkPublishableKeyPrompt({}),
|
|
487
426
|
});
|
|
488
427
|
if (!clientName) {
|
|
489
428
|
return yield* BadArgsError.make({ message: 'Client name is required.' });
|
|
@@ -493,7 +432,7 @@ const handleClerkClient = Effect.fn(function* (opts) {
|
|
|
493
432
|
message: 'Publishable key is required.',
|
|
494
433
|
});
|
|
495
434
|
}
|
|
496
|
-
const domain =
|
|
435
|
+
const domain = clerkDomainFromPublishableKey(publishableKey);
|
|
497
436
|
if (!domain) {
|
|
498
437
|
return yield* BadArgsError.make({
|
|
499
438
|
message: 'Invalid publishable key. Could not extract domain.',
|
|
@@ -520,27 +459,11 @@ const handleClerkClient = Effect.fn(function* (opts) {
|
|
|
520
459
|
});
|
|
521
460
|
const handleFirebaseClient = Effect.fn(function* (opts) {
|
|
522
461
|
const { clientName, provider } = yield* getClientNameAndProvider('firebase', opts);
|
|
523
|
-
const firebaseProjectIdRegex = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
|
|
524
462
|
const projectId = yield* optOrPrompt(opts['project-id'], {
|
|
525
463
|
simpleName: '--project-id',
|
|
526
464
|
required: true,
|
|
527
465
|
skipIf: false,
|
|
528
|
-
prompt: {
|
|
529
|
-
prompt: `Firebase project ID: (From Project Settings page on ${link('https://console.firebase.google.com/')})`,
|
|
530
|
-
placeholder: '',
|
|
531
|
-
modifyOutput: UI.modifiers.piped([
|
|
532
|
-
UI.modifiers.topPadding,
|
|
533
|
-
UI.modifiers.dimOnComplete,
|
|
534
|
-
]),
|
|
535
|
-
validate: (val) => {
|
|
536
|
-
if (!val) {
|
|
537
|
-
return 'Project ID is required';
|
|
538
|
-
}
|
|
539
|
-
if (!firebaseProjectIdRegex.test(val)) {
|
|
540
|
-
return 'Invalid Firebase project ID. It must be 6-30 characters, start with a lowercase letter, contain only lowercase letters, digits, and hyphens, and not end with a hyphen.';
|
|
541
|
-
}
|
|
542
|
-
},
|
|
543
|
-
},
|
|
466
|
+
prompt: firebaseProjectIdPrompt({}),
|
|
544
467
|
});
|
|
545
468
|
// typeguard
|
|
546
469
|
if (!clientName || !projectId) {
|
|
@@ -548,15 +471,14 @@ const handleFirebaseClient = Effect.fn(function* (opts) {
|
|
|
548
471
|
message: 'Missing required arguments',
|
|
549
472
|
});
|
|
550
473
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
});
|
|
474
|
+
const validationError = validateFirebaseProjectId(projectId);
|
|
475
|
+
if (validationError) {
|
|
476
|
+
return yield* BadArgsError.make({ message: validationError });
|
|
555
477
|
}
|
|
556
478
|
const response = yield* addOAuthClient({
|
|
557
479
|
providerId: provider.id,
|
|
558
480
|
clientName,
|
|
559
|
-
discoveryEndpoint:
|
|
481
|
+
discoveryEndpoint: firebaseDiscoveryEndpoint(projectId),
|
|
560
482
|
});
|
|
561
483
|
yield* Effect.log(boxen([
|
|
562
484
|
`Firebase OAuth client created: ${response.client.client_name}`,
|
|
@@ -590,29 +512,4 @@ export const authClientAddCmd = Effect.fn(function* (opts) {
|
|
|
590
512
|
yield* Effect.logError(e.message);
|
|
591
513
|
yield* Effect.log(chalk.dim('hint: run `instant-cli auth client add --help` for the list of available arguments'));
|
|
592
514
|
})));
|
|
593
|
-
function domainFromClerkKey(key) {
|
|
594
|
-
try {
|
|
595
|
-
const parts = key.split('_');
|
|
596
|
-
const domainPartB64 = parts[parts.length - 1];
|
|
597
|
-
const domainPart = base64Decode(domainPartB64);
|
|
598
|
-
return domainPart.replace('$', '');
|
|
599
|
-
}
|
|
600
|
-
catch (e) {
|
|
601
|
-
console.error('Error getting domain from clerk key', e);
|
|
602
|
-
return null;
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
// Base64 decode, switching to url-safe decode if we hit an error
|
|
606
|
-
// Can't be sure which method Clerk uses because you can't generate
|
|
607
|
-
// `+` or `/` with characters that go in a normal host. Urls with
|
|
608
|
-
// chinese characters exist, they might encode to `+` or `/`, and
|
|
609
|
-
// Clerk might support them, so we'll be safe and do both.
|
|
610
|
-
function base64Decode(s) {
|
|
611
|
-
try {
|
|
612
|
-
return Buffer.from(s, 'base64').toString('utf-8');
|
|
613
|
-
}
|
|
614
|
-
catch (e) {
|
|
615
|
-
return Buffer.from(s, 'base64url').toString('utf-8');
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
515
|
//# sourceMappingURL=add.js.map
|