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,5 +1,4 @@
|
|
|
1
1
|
import { Effect, Match, Option, Schema } from 'effect';
|
|
2
|
-
import { FileSystem } from '@effect/platform';
|
|
3
2
|
import type { authClientAddDef, OptsFromCommand } from '../../../index.ts';
|
|
4
3
|
import { BadArgsError } from '../../../errors.ts';
|
|
5
4
|
import { GlobalOpts } from '../../../context/globalOpts.ts';
|
|
@@ -7,7 +6,6 @@ import {
|
|
|
7
6
|
optOrPrompt,
|
|
8
7
|
optOrPromptBoolean,
|
|
9
8
|
runUIEffect,
|
|
10
|
-
stripFirstBlankLine,
|
|
11
9
|
validateRequired,
|
|
12
10
|
} from '../../../lib/ui.ts';
|
|
13
11
|
import {
|
|
@@ -15,6 +13,8 @@ import {
|
|
|
15
13
|
findName,
|
|
16
14
|
getClientNameAndProvider,
|
|
17
15
|
getOrCreateProvider,
|
|
16
|
+
GoogleAppTypeSchema,
|
|
17
|
+
OAuthClient,
|
|
18
18
|
} from '../../../lib/oauth.ts';
|
|
19
19
|
import {
|
|
20
20
|
DEFAULT_OAUTH_CALLBACK_URL,
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
APPLE_AUTHORIZATION_ENDPOINT,
|
|
25
25
|
APPLE_DISCOVERY_ENDPOINT,
|
|
26
26
|
APPLE_TOKEN_ENDPOINT,
|
|
27
|
+
clerkDomainFromPublishableKey,
|
|
27
28
|
LINKEDIN_AUTHORIZATION_ENDPOINT,
|
|
28
29
|
LINKEDIN_DISCOVERY_ENDPOINT,
|
|
29
30
|
LINKEDIN_TOKEN_ENDPOINT,
|
|
@@ -32,6 +33,24 @@ import { UI } from '../../../ui/index.ts';
|
|
|
32
33
|
import chalk from 'chalk';
|
|
33
34
|
import boxen from 'boxen';
|
|
34
35
|
import { link } from '../../../logging.ts';
|
|
36
|
+
import {
|
|
37
|
+
appleKeyIdPrompt,
|
|
38
|
+
applePrivateKeyFilePrompt,
|
|
39
|
+
appleServicesIdPrompt,
|
|
40
|
+
appleTeamIdPrompt,
|
|
41
|
+
clerkPublishableKeyPrompt,
|
|
42
|
+
clientIdPrompt,
|
|
43
|
+
clientSecretPrompt,
|
|
44
|
+
firebaseDiscoveryEndpoint,
|
|
45
|
+
firebaseProjectIdPrompt,
|
|
46
|
+
getFlag,
|
|
47
|
+
hasAnyFlag,
|
|
48
|
+
isTrueFlag,
|
|
49
|
+
readPrivateKeyFile,
|
|
50
|
+
redirectSetupMessages,
|
|
51
|
+
redirectUriPrompt,
|
|
52
|
+
validateFirebaseProjectId,
|
|
53
|
+
} from './shared.ts';
|
|
35
54
|
|
|
36
55
|
export const ClientTypeSchema = Schema.Literal(
|
|
37
56
|
'google',
|
|
@@ -42,12 +61,13 @@ export const ClientTypeSchema = Schema.Literal(
|
|
|
42
61
|
'firebase',
|
|
43
62
|
);
|
|
44
63
|
|
|
45
|
-
const
|
|
46
|
-
'
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
)
|
|
64
|
+
const googleConsoleUrl =
|
|
65
|
+
'https://console.developers.google.com/apis/credentials';
|
|
66
|
+
const githubDeveloperUrl = 'https://github.com/settings/developers';
|
|
67
|
+
const linkedinDeveloperUrl = 'https://www.linkedin.com/developers/apps';
|
|
68
|
+
const optionalRedirectPrompt = redirectUriPrompt({
|
|
69
|
+
heading: 'Custom redirect URI (optional):',
|
|
70
|
+
});
|
|
51
71
|
|
|
52
72
|
const selectGoogleAppType = (value: unknown) =>
|
|
53
73
|
Effect.gen(function* () {
|
|
@@ -96,10 +116,159 @@ const selectGoogleAppType = (value: unknown) =>
|
|
|
96
116
|
);
|
|
97
117
|
});
|
|
98
118
|
|
|
119
|
+
const selectGoogleCredentialMode = Effect.fn(function* () {
|
|
120
|
+
return yield* runUIEffect(
|
|
121
|
+
new UI.Select({
|
|
122
|
+
options: [
|
|
123
|
+
{
|
|
124
|
+
label:
|
|
125
|
+
'Use dev credentials' +
|
|
126
|
+
chalk.dim(' (works on localhost and Expo, no Google setup)'),
|
|
127
|
+
value: 'dev' as const,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
label:
|
|
131
|
+
'Use my own credentials' +
|
|
132
|
+
chalk.dim(' (client ID and secret from Google Console)'),
|
|
133
|
+
value: 'custom' as const,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
promptText: 'Select Google credential mode:',
|
|
137
|
+
modifyOutput: UI.modifiers.piped([
|
|
138
|
+
UI.modifiers.topPadding,
|
|
139
|
+
UI.modifiers.dimOnComplete,
|
|
140
|
+
]),
|
|
141
|
+
defaultValue: 'dev' as const,
|
|
142
|
+
}),
|
|
143
|
+
).pipe(
|
|
144
|
+
Effect.catchTag('UIError', (e) =>
|
|
145
|
+
BadArgsError.make({ message: `UI error: ${e.message}` }),
|
|
146
|
+
),
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const resolveGoogleCredentialMode = Effect.fn(function* ({
|
|
151
|
+
appType,
|
|
152
|
+
opts,
|
|
153
|
+
}: {
|
|
154
|
+
appType: typeof GoogleAppTypeSchema.Type;
|
|
155
|
+
opts: Record<string, unknown>;
|
|
156
|
+
}): Effect.fn.Return<'custom' | 'dev', BadArgsError, GlobalOpts> {
|
|
157
|
+
const { yes } = yield* GlobalOpts;
|
|
158
|
+
const devCredentialsFlag = isTrueFlag(getFlag(opts, 'dev-credentials'));
|
|
159
|
+
const hasProvidedSomeCustomCredentials = hasAnyFlag(opts, [
|
|
160
|
+
'client-id',
|
|
161
|
+
'client-secret',
|
|
162
|
+
'custom-redirect-uri',
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
if (devCredentialsFlag && appType !== 'web') {
|
|
166
|
+
return yield* BadArgsError.make({
|
|
167
|
+
message:
|
|
168
|
+
'--dev-credentials is only supported for --app-type web. Native Google clients need credentials from Google.',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (devCredentialsFlag && hasProvidedSomeCustomCredentials) {
|
|
173
|
+
return yield* BadArgsError.make({
|
|
174
|
+
message:
|
|
175
|
+
'--dev-credentials cannot be combined with --client-id, --client-secret, or --custom-redirect-uri.',
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (appType !== 'web') {
|
|
180
|
+
return 'custom';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (hasProvidedSomeCustomCredentials) {
|
|
184
|
+
return 'custom';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (devCredentialsFlag) {
|
|
188
|
+
return 'dev';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (yes) {
|
|
192
|
+
return 'dev';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return yield* selectGoogleCredentialMode();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const printGoogleDevCredentialsClient = Effect.fn(function* ({
|
|
199
|
+
appType,
|
|
200
|
+
client,
|
|
201
|
+
}: {
|
|
202
|
+
appType: typeof GoogleAppTypeSchema.Type;
|
|
203
|
+
client: typeof OAuthClient.Type;
|
|
204
|
+
}) {
|
|
205
|
+
yield* Effect.log(
|
|
206
|
+
boxen(
|
|
207
|
+
[
|
|
208
|
+
`Google OAuth client created: ${client.client_name}`,
|
|
209
|
+
`App type: ${appType}`,
|
|
210
|
+
`Credentials: Instant dev credentials`,
|
|
211
|
+
`ID: ${client.id}`,
|
|
212
|
+
'',
|
|
213
|
+
'No Google Console setup required.',
|
|
214
|
+
'Works on localhost and Expo during development.',
|
|
215
|
+
'',
|
|
216
|
+
chalk.bold('Ready for production? Run:'),
|
|
217
|
+
` instant-cli auth client update --name ${client.client_name} --client-id <id> --client-secret <secret>`,
|
|
218
|
+
].join('\n'),
|
|
219
|
+
{ dimBorder: true, padding: { right: 1, left: 1 } },
|
|
220
|
+
),
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const printGoogleCustomCredentialsClient = Effect.fn(function* ({
|
|
225
|
+
appType,
|
|
226
|
+
client,
|
|
227
|
+
clientId,
|
|
228
|
+
customRedirectUri,
|
|
229
|
+
redirectUri,
|
|
230
|
+
}: {
|
|
231
|
+
appType: typeof GoogleAppTypeSchema.Type;
|
|
232
|
+
client: typeof OAuthClient.Type;
|
|
233
|
+
clientId: string | undefined;
|
|
234
|
+
customRedirectUri: string | undefined;
|
|
235
|
+
redirectUri: string | undefined;
|
|
236
|
+
}) {
|
|
237
|
+
const redirectMessages: string[] = [];
|
|
238
|
+
if (appType === 'web' && redirectUri) {
|
|
239
|
+
redirectMessages.push(
|
|
240
|
+
...redirectSetupMessages({
|
|
241
|
+
prompt: 'Add this redirect URI in Google Console',
|
|
242
|
+
redirectUri,
|
|
243
|
+
showCustomRedirectInstructions: Boolean(customRedirectUri),
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
yield* Effect.log(
|
|
249
|
+
boxen(
|
|
250
|
+
[
|
|
251
|
+
`Google OAuth client created: ${client.client_name}`,
|
|
252
|
+
`App type: ${appType}`,
|
|
253
|
+
`ID: ${client.id}`,
|
|
254
|
+
`Google Client ID: ${client.client_id ?? clientId}`,
|
|
255
|
+
...redirectMessages,
|
|
256
|
+
].join('\n'),
|
|
257
|
+
{ dimBorder: true, padding: { right: 1, left: 1 } },
|
|
258
|
+
),
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
|
|
99
262
|
const handleGoogleClient = Effect.fn(function* (opts: Record<string, unknown>) {
|
|
100
263
|
// This one requires special logic for getting client name
|
|
101
264
|
// because the suggested name includes the app type
|
|
102
265
|
const appType = yield* selectGoogleAppType(opts['app-type']);
|
|
266
|
+
const credentialMode = yield* resolveGoogleCredentialMode({
|
|
267
|
+
appType,
|
|
268
|
+
opts,
|
|
269
|
+
});
|
|
270
|
+
const useSharedCredentials = credentialMode === 'dev';
|
|
271
|
+
|
|
103
272
|
const { auth, provider } = yield* getOrCreateProvider('google');
|
|
104
273
|
const usedClientNames = new Set(
|
|
105
274
|
(auth.oauth_clients ?? []).map((client) => client.client_name),
|
|
@@ -115,7 +284,10 @@ const handleGoogleClient = Effect.fn(function* (opts: Record<string, unknown>) {
|
|
|
115
284
|
defaultValue: suggestedClientName,
|
|
116
285
|
placeholder: suggestedClientName,
|
|
117
286
|
validate: validateRequired,
|
|
118
|
-
modifyOutput: UI.modifiers.piped([
|
|
287
|
+
modifyOutput: UI.modifiers.piped([
|
|
288
|
+
UI.modifiers.topPadding,
|
|
289
|
+
UI.modifiers.dimOnComplete,
|
|
290
|
+
]),
|
|
119
291
|
},
|
|
120
292
|
});
|
|
121
293
|
|
|
@@ -127,68 +299,46 @@ const handleGoogleClient = Effect.fn(function* (opts: Record<string, unknown>) {
|
|
|
127
299
|
|
|
128
300
|
const clientId = yield* optOrPrompt(opts['client-id'], {
|
|
129
301
|
simpleName: '--client-id',
|
|
130
|
-
required:
|
|
131
|
-
skipIf:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
UI.modifiers.topPadding,
|
|
136
|
-
UI.modifiers.dimOnComplete,
|
|
137
|
-
]),
|
|
138
|
-
validate: validateRequired,
|
|
139
|
-
},
|
|
302
|
+
required: !useSharedCredentials,
|
|
303
|
+
skipIf: useSharedCredentials,
|
|
304
|
+
skipMessage:
|
|
305
|
+
'--client-id is not compatible with --dev-credentials. Drop one or the other.',
|
|
306
|
+
prompt: clientIdPrompt({ providerUrl: googleConsoleUrl }),
|
|
140
307
|
});
|
|
141
308
|
|
|
309
|
+
const usesCustomWebCredentials = !useSharedCredentials && appType === 'web';
|
|
142
310
|
const clientSecret = yield* optOrPrompt(opts['client-secret'], {
|
|
143
|
-
required:
|
|
144
|
-
skipIf:
|
|
311
|
+
required: usesCustomWebCredentials,
|
|
312
|
+
skipIf: !usesCustomWebCredentials,
|
|
145
313
|
simpleName: '--client-secret',
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
modifyOutput: UI.modifiers.piped([
|
|
151
|
-
UI.modifiers.topPadding,
|
|
152
|
-
UI.modifiers.dimOnComplete,
|
|
153
|
-
]),
|
|
154
|
-
},
|
|
314
|
+
skipMessage: useSharedCredentials
|
|
315
|
+
? '--client-secret is not compatible with --dev-credentials. Drop one or the other.'
|
|
316
|
+
: undefined,
|
|
317
|
+
prompt: clientSecretPrompt({ providerUrl: googleConsoleUrl }),
|
|
155
318
|
});
|
|
156
319
|
|
|
157
320
|
const customRedirectUri = yield* optOrPrompt(opts['custom-redirect-uri'], {
|
|
158
321
|
required: false,
|
|
159
|
-
prompt:
|
|
160
|
-
prompt: '',
|
|
161
|
-
placeholder: 'https://yoursite.com/oauth/callback',
|
|
162
|
-
modifyOutput: UI.modifiers.piped([
|
|
163
|
-
(output, status) => {
|
|
164
|
-
if (status === 'idle') {
|
|
165
|
-
return (
|
|
166
|
-
`\nCustom redirect URI (optional):
|
|
167
|
-
${chalk.dim('With a custom redirect URI, users will see "Redirecting to yoursite.com..." for a more branded experience.')}
|
|
168
|
-
${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all query parameters preserved.`)}\n\n` +
|
|
169
|
-
stripFirstBlankLine(output)
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
return `\nCustom redirect URI (optional):\n${stripFirstBlankLine(output)}`;
|
|
173
|
-
},
|
|
174
|
-
UI.modifiers.dimOnComplete,
|
|
175
|
-
]),
|
|
176
|
-
},
|
|
322
|
+
prompt: optionalRedirectPrompt,
|
|
177
323
|
simpleName: '--custom-redirect-uri',
|
|
178
|
-
skipIf:
|
|
179
|
-
skipMessage:
|
|
324
|
+
skipIf: !usesCustomWebCredentials,
|
|
325
|
+
skipMessage: useSharedCredentials
|
|
326
|
+
? '--custom-redirect-uri is not compatible with --dev-credentials.'
|
|
327
|
+
: 'Provided custom redirect URI when not using web app type.',
|
|
180
328
|
});
|
|
181
329
|
|
|
182
330
|
if (!clientName) {
|
|
183
331
|
return yield* BadArgsError.make({ message: 'Client name is required.' }); // Should never reach this
|
|
184
332
|
}
|
|
185
|
-
const redirectUri =
|
|
333
|
+
const redirectUri = useSharedCredentials
|
|
334
|
+
? undefined
|
|
335
|
+
: customRedirectUri || DEFAULT_OAUTH_CALLBACK_URL;
|
|
186
336
|
|
|
187
337
|
const response = yield* addOAuthClient({
|
|
188
338
|
providerId: provider.id,
|
|
189
339
|
clientName,
|
|
190
|
-
clientId,
|
|
191
|
-
clientSecret: clientSecret,
|
|
340
|
+
clientId: useSharedCredentials ? undefined : clientId,
|
|
341
|
+
clientSecret: useSharedCredentials ? undefined : clientSecret,
|
|
192
342
|
authorizationEndpoint: GOOGLE_AUTHORIZATION_ENDPOINT,
|
|
193
343
|
tokenEndpoint: GOOGLE_TOKEN_ENDPOINT,
|
|
194
344
|
discoveryEndpoint: GOOGLE_DISCOVERY_ENDPOINT,
|
|
@@ -197,37 +347,24 @@ ${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all que
|
|
|
197
347
|
appType,
|
|
198
348
|
skipNonceChecks: true,
|
|
199
349
|
},
|
|
350
|
+
useSharedCredentials,
|
|
200
351
|
});
|
|
201
352
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
);
|
|
209
|
-
if (customRedirectUri) {
|
|
210
|
-
redirectMessages.push(
|
|
211
|
-
`Your custom redirect must forward to ${chalk.bold(DEFAULT_OAUTH_CALLBACK_URL)} with all query parameters preserved.`,
|
|
212
|
-
);
|
|
213
|
-
redirectMessages.push(
|
|
214
|
-
`You can test it by visiting: ${chalk.bold(redirectUri + '?test-redirect=true')}`,
|
|
215
|
-
);
|
|
216
|
-
}
|
|
353
|
+
if (useSharedCredentials) {
|
|
354
|
+
yield* printGoogleDevCredentialsClient({
|
|
355
|
+
appType,
|
|
356
|
+
client: response.client,
|
|
357
|
+
});
|
|
358
|
+
return;
|
|
217
359
|
}
|
|
218
360
|
|
|
219
|
-
yield*
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
...redirectMessages,
|
|
227
|
-
].join('\n'),
|
|
228
|
-
{ dimBorder: true, padding: { right: 1, left: 1 } },
|
|
229
|
-
),
|
|
230
|
-
);
|
|
361
|
+
yield* printGoogleCustomCredentialsClient({
|
|
362
|
+
appType,
|
|
363
|
+
client: response.client,
|
|
364
|
+
clientId,
|
|
365
|
+
customRedirectUri,
|
|
366
|
+
redirectUri,
|
|
367
|
+
});
|
|
231
368
|
});
|
|
232
369
|
|
|
233
370
|
const handleGithubClient = Effect.fn(function* (opts: Record<string, unknown>) {
|
|
@@ -240,53 +377,21 @@ const handleGithubClient = Effect.fn(function* (opts: Record<string, unknown>) {
|
|
|
240
377
|
simpleName: '--client-id',
|
|
241
378
|
required: true,
|
|
242
379
|
skipIf: false,
|
|
243
|
-
prompt: {
|
|
244
|
-
prompt: `Client ID ${chalk.dim(`(from ${link('https://github.com/settings/developers')})`)}`,
|
|
245
|
-
modifyOutput: UI.modifiers.piped([
|
|
246
|
-
UI.modifiers.topPadding,
|
|
247
|
-
UI.modifiers.dimOnComplete,
|
|
248
|
-
]),
|
|
249
|
-
validate: validateRequired,
|
|
250
|
-
},
|
|
380
|
+
prompt: clientIdPrompt({ providerUrl: githubDeveloperUrl }),
|
|
251
381
|
});
|
|
252
382
|
|
|
253
383
|
const clientSecret = yield* optOrPrompt(opts['client-secret'], {
|
|
254
384
|
required: true,
|
|
255
385
|
skipIf: false,
|
|
256
386
|
simpleName: '--client-secret',
|
|
257
|
-
prompt: {
|
|
258
|
-
prompt: `Client Secret: ${chalk.dim(`(from ${link('https://github.com/settings/developers')})`)}`,
|
|
259
|
-
validate: validateRequired,
|
|
260
|
-
sensitive: true,
|
|
261
|
-
modifyOutput: UI.modifiers.piped([
|
|
262
|
-
UI.modifiers.topPadding,
|
|
263
|
-
UI.modifiers.dimOnComplete,
|
|
264
|
-
]),
|
|
265
|
-
},
|
|
387
|
+
prompt: clientSecretPrompt({ providerUrl: githubDeveloperUrl }),
|
|
266
388
|
});
|
|
267
389
|
|
|
268
390
|
const customRedirectUri = yield* optOrPrompt(opts['custom-redirect-uri'], {
|
|
269
391
|
required: false,
|
|
270
392
|
simpleName: '--custom-redirect-uri',
|
|
271
393
|
skipIf: false,
|
|
272
|
-
prompt:
|
|
273
|
-
prompt: '',
|
|
274
|
-
placeholder: 'https://yoursite.com/oauth/callback',
|
|
275
|
-
modifyOutput: UI.modifiers.piped([
|
|
276
|
-
(output, status) => {
|
|
277
|
-
if (status === 'idle') {
|
|
278
|
-
return (
|
|
279
|
-
`\nCustom redirect URI (optional):
|
|
280
|
-
${chalk.dim('With a custom redirect URI, users will see "Redirecting to yoursite.com..." for a more branded experience.')}
|
|
281
|
-
${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all query parameters preserved.`)}\n\n` +
|
|
282
|
-
stripFirstBlankLine(output)
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
return `\nCustom redirect URI (optional):\n${stripFirstBlankLine(output)}`;
|
|
286
|
-
},
|
|
287
|
-
UI.modifiers.dimOnComplete,
|
|
288
|
-
]),
|
|
289
|
-
},
|
|
394
|
+
prompt: optionalRedirectPrompt,
|
|
290
395
|
});
|
|
291
396
|
|
|
292
397
|
if (!clientName) {
|
|
@@ -306,19 +411,11 @@ ${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all que
|
|
|
306
411
|
meta: { providerName: 'github' },
|
|
307
412
|
});
|
|
308
413
|
|
|
309
|
-
const redirectMessages
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
),
|
|
313
|
-
|
|
314
|
-
if (customRedirectUri) {
|
|
315
|
-
redirectMessages.push(
|
|
316
|
-
`Your custom redirect must forward to ${chalk.bold(DEFAULT_OAUTH_CALLBACK_URL)} with all query parameters preserved.`,
|
|
317
|
-
);
|
|
318
|
-
redirectMessages.push(
|
|
319
|
-
`You can test it by visiting: ${chalk.bold(redirectUri + '?test-redirect=true')}`,
|
|
320
|
-
);
|
|
321
|
-
}
|
|
414
|
+
const redirectMessages = redirectSetupMessages({
|
|
415
|
+
prompt: 'Add this callback URL in your GitHub OAuth App settings',
|
|
416
|
+
redirectUri,
|
|
417
|
+
showCustomRedirectInstructions: Boolean(customRedirectUri),
|
|
418
|
+
});
|
|
322
419
|
|
|
323
420
|
yield* Effect.log(
|
|
324
421
|
boxen(
|
|
@@ -345,53 +442,21 @@ const handleLinkedInClient = Effect.fn(function* (
|
|
|
345
442
|
simpleName: '--client-id',
|
|
346
443
|
required: true,
|
|
347
444
|
skipIf: false,
|
|
348
|
-
prompt: {
|
|
349
|
-
prompt: `Client ID: ${chalk.dim(`(from ${link('https://www.linkedin.com/developers/apps')})`)}`,
|
|
350
|
-
modifyOutput: UI.modifiers.piped([
|
|
351
|
-
UI.modifiers.topPadding,
|
|
352
|
-
UI.modifiers.dimOnComplete,
|
|
353
|
-
]),
|
|
354
|
-
validate: validateRequired,
|
|
355
|
-
},
|
|
445
|
+
prompt: clientIdPrompt({ providerUrl: linkedinDeveloperUrl }),
|
|
356
446
|
});
|
|
357
447
|
|
|
358
448
|
const clientSecret = yield* optOrPrompt(opts['client-secret'], {
|
|
359
449
|
required: true,
|
|
360
450
|
skipIf: false,
|
|
361
451
|
simpleName: '--client-secret',
|
|
362
|
-
prompt: {
|
|
363
|
-
prompt: `Client Secret: ${chalk.dim(`(from ${link('https://www.linkedin.com/developers/apps')})`)}`,
|
|
364
|
-
validate: validateRequired,
|
|
365
|
-
sensitive: true,
|
|
366
|
-
modifyOutput: UI.modifiers.piped([
|
|
367
|
-
UI.modifiers.topPadding,
|
|
368
|
-
UI.modifiers.dimOnComplete,
|
|
369
|
-
]),
|
|
370
|
-
},
|
|
452
|
+
prompt: clientSecretPrompt({ providerUrl: linkedinDeveloperUrl }),
|
|
371
453
|
});
|
|
372
454
|
|
|
373
455
|
const customRedirectUri = yield* optOrPrompt(opts['custom-redirect-uri'], {
|
|
374
456
|
required: false,
|
|
375
457
|
simpleName: '--custom-redirect-uri',
|
|
376
458
|
skipIf: false,
|
|
377
|
-
prompt:
|
|
378
|
-
prompt: '',
|
|
379
|
-
placeholder: 'https://yoursite.com/oauth/callback',
|
|
380
|
-
modifyOutput: UI.modifiers.piped([
|
|
381
|
-
(output, status) => {
|
|
382
|
-
if (status === 'idle') {
|
|
383
|
-
return (
|
|
384
|
-
`\nCustom redirect URI (optional):
|
|
385
|
-
${chalk.dim('With a custom redirect URI, users will see "Redirecting to yoursite.com..." for a more branded experience.')}
|
|
386
|
-
${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all query parameters preserved.`)}\n\n` +
|
|
387
|
-
stripFirstBlankLine(output)
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
return `\nCustom redirect URI (optional):\n${stripFirstBlankLine(output)}`;
|
|
391
|
-
},
|
|
392
|
-
UI.modifiers.dimOnComplete,
|
|
393
|
-
]),
|
|
394
|
-
},
|
|
459
|
+
prompt: optionalRedirectPrompt,
|
|
395
460
|
});
|
|
396
461
|
|
|
397
462
|
if (!clientName) {
|
|
@@ -411,19 +476,11 @@ ${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all que
|
|
|
411
476
|
redirectTo: redirectUri,
|
|
412
477
|
});
|
|
413
478
|
|
|
414
|
-
const redirectMessages
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
),
|
|
418
|
-
|
|
419
|
-
if (customRedirectUri) {
|
|
420
|
-
redirectMessages.push(
|
|
421
|
-
`Your custom redirect must forward to ${chalk.bold(DEFAULT_OAUTH_CALLBACK_URL)} with all query parameters preserved.`,
|
|
422
|
-
);
|
|
423
|
-
redirectMessages.push(
|
|
424
|
-
`You can test it by visiting: ${chalk.bold(redirectUri + '?test-redirect=true')}`,
|
|
425
|
-
);
|
|
426
|
-
}
|
|
479
|
+
const redirectMessages = redirectSetupMessages({
|
|
480
|
+
prompt: 'Add this redirect URI in your LinkedIn app settings',
|
|
481
|
+
redirectUri,
|
|
482
|
+
showCustomRedirectInstructions: Boolean(customRedirectUri),
|
|
483
|
+
});
|
|
427
484
|
|
|
428
485
|
yield* Effect.log(
|
|
429
486
|
boxen(
|
|
@@ -438,32 +495,6 @@ ${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all que
|
|
|
438
495
|
);
|
|
439
496
|
});
|
|
440
497
|
|
|
441
|
-
const readPrivateKeyFile = Effect.fn('readPrivateKeyFile')(function* (
|
|
442
|
-
path: string,
|
|
443
|
-
) {
|
|
444
|
-
const fs = yield* FileSystem.FileSystem;
|
|
445
|
-
// Strip shell-escape backslashes so paths like "file\ (2).p8" resolve correctly.
|
|
446
|
-
// Only on POSIX — Windows uses backslashes as path separators.
|
|
447
|
-
const normalizedPath =
|
|
448
|
-
process.platform === 'win32' ? path : path.replace(/\\(.)/g, '$1');
|
|
449
|
-
const contents = yield* fs.readFileString(normalizedPath, 'utf8').pipe(
|
|
450
|
-
Effect.mapError(
|
|
451
|
-
(e) =>
|
|
452
|
-
new BadArgsError({
|
|
453
|
-
message: `Could not read private key file at ${normalizedPath}: ${e.message}`,
|
|
454
|
-
}),
|
|
455
|
-
),
|
|
456
|
-
);
|
|
457
|
-
|
|
458
|
-
const trimmed = contents.trim();
|
|
459
|
-
if (!trimmed) {
|
|
460
|
-
return yield* BadArgsError.make({
|
|
461
|
-
message: `Private key file at ${normalizedPath} is empty.`,
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
return trimmed;
|
|
465
|
-
});
|
|
466
|
-
|
|
467
498
|
const handleAppleClient = Effect.fn(function* (opts: Record<string, unknown>) {
|
|
468
499
|
const { yes } = yield* GlobalOpts;
|
|
469
500
|
const { clientName, provider } = yield* getClientNameAndProvider(
|
|
@@ -475,14 +506,7 @@ const handleAppleClient = Effect.fn(function* (opts: Record<string, unknown>) {
|
|
|
475
506
|
simpleName: '--services-id',
|
|
476
507
|
required: true,
|
|
477
508
|
skipIf: false,
|
|
478
|
-
prompt: {
|
|
479
|
-
prompt: `Services ID ${chalk.dim(`(from ${link('https://developer.apple.com/account/resources/identifiers/list/serviceId')})`)}`,
|
|
480
|
-
modifyOutput: UI.modifiers.piped([
|
|
481
|
-
UI.modifiers.topPadding,
|
|
482
|
-
UI.modifiers.dimOnComplete,
|
|
483
|
-
]),
|
|
484
|
-
validate: validateRequired,
|
|
485
|
-
},
|
|
509
|
+
prompt: appleServicesIdPrompt({}),
|
|
486
510
|
});
|
|
487
511
|
|
|
488
512
|
// If any web-flow flag is provided, enable web flow; otherwise ask
|
|
@@ -518,14 +542,7 @@ const handleAppleClient = Effect.fn(function* (opts: Record<string, unknown>) {
|
|
|
518
542
|
required: true,
|
|
519
543
|
skipIf: skipWeb,
|
|
520
544
|
skipMessage: `--team-id ${webSkipMessage}`,
|
|
521
|
-
prompt: {
|
|
522
|
-
prompt: `Team ID ${chalk.dim(`(from ${link('https://developer.apple.com/account#MembershipDetailsCard')})`)}`,
|
|
523
|
-
validate: validateRequired,
|
|
524
|
-
modifyOutput: UI.modifiers.piped([
|
|
525
|
-
UI.modifiers.topPadding,
|
|
526
|
-
UI.modifiers.dimOnComplete,
|
|
527
|
-
]),
|
|
528
|
-
},
|
|
545
|
+
prompt: appleTeamIdPrompt({}),
|
|
529
546
|
});
|
|
530
547
|
|
|
531
548
|
const keyId = yield* optOrPrompt(opts['key-id'], {
|
|
@@ -533,14 +550,7 @@ const handleAppleClient = Effect.fn(function* (opts: Record<string, unknown>) {
|
|
|
533
550
|
required: true,
|
|
534
551
|
skipIf: skipWeb,
|
|
535
552
|
skipMessage: `--key-id ${webSkipMessage}`,
|
|
536
|
-
prompt: {
|
|
537
|
-
prompt: `Key ID ${chalk.dim(`(from ${link('https://developer.apple.com/account/resources/authkeys/list')})`)}`,
|
|
538
|
-
validate: validateRequired,
|
|
539
|
-
modifyOutput: UI.modifiers.piped([
|
|
540
|
-
UI.modifiers.topPadding,
|
|
541
|
-
UI.modifiers.dimOnComplete,
|
|
542
|
-
]),
|
|
543
|
-
},
|
|
553
|
+
prompt: appleKeyIdPrompt({}),
|
|
544
554
|
});
|
|
545
555
|
|
|
546
556
|
const privateKeyPath = yield* optOrPrompt(opts['private-key-file'], {
|
|
@@ -548,14 +558,7 @@ const handleAppleClient = Effect.fn(function* (opts: Record<string, unknown>) {
|
|
|
548
558
|
required: true,
|
|
549
559
|
skipIf: skipWeb,
|
|
550
560
|
skipMessage: `--private-key-file ${webSkipMessage}`,
|
|
551
|
-
prompt: {
|
|
552
|
-
prompt: `Path to .p8 private key file ${chalk.dim('(downloaded from Apple)')}`,
|
|
553
|
-
validate: validateRequired,
|
|
554
|
-
modifyOutput: UI.modifiers.piped([
|
|
555
|
-
UI.modifiers.topPadding,
|
|
556
|
-
UI.modifiers.dimOnComplete,
|
|
557
|
-
]),
|
|
558
|
-
},
|
|
561
|
+
prompt: applePrivateKeyFilePrompt({}),
|
|
559
562
|
});
|
|
560
563
|
|
|
561
564
|
const privateKey = privateKeyPath
|
|
@@ -567,24 +570,7 @@ const handleAppleClient = Effect.fn(function* (opts: Record<string, unknown>) {
|
|
|
567
570
|
simpleName: '--custom-redirect-uri',
|
|
568
571
|
skipIf: skipWeb,
|
|
569
572
|
skipMessage: `--custom-redirect-uri ${webSkipMessage}`,
|
|
570
|
-
prompt:
|
|
571
|
-
prompt: '',
|
|
572
|
-
placeholder: 'https://yoursite.com/oauth/callback',
|
|
573
|
-
modifyOutput: UI.modifiers.piped([
|
|
574
|
-
(output, status) => {
|
|
575
|
-
if (status === 'idle') {
|
|
576
|
-
return (
|
|
577
|
-
`\nCustom redirect URI (optional):
|
|
578
|
-
${chalk.dim('With a custom redirect URI, users will see "Redirecting to yoursite.com..." for a more branded experience.')}
|
|
579
|
-
${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all query parameters preserved.`)}\n\n` +
|
|
580
|
-
stripFirstBlankLine(output)
|
|
581
|
-
);
|
|
582
|
-
}
|
|
583
|
-
return `\nCustom redirect URI (optional):\n${stripFirstBlankLine(output)}`;
|
|
584
|
-
},
|
|
585
|
-
UI.modifiers.dimOnComplete,
|
|
586
|
-
]),
|
|
587
|
-
},
|
|
573
|
+
prompt: optionalRedirectPrompt,
|
|
588
574
|
});
|
|
589
575
|
|
|
590
576
|
if (!clientName) {
|
|
@@ -617,22 +603,16 @@ ${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all que
|
|
|
617
603
|
`Services ID: ${response.client.client_id ?? servicesId}`,
|
|
618
604
|
];
|
|
619
605
|
|
|
620
|
-
if (privateKey) {
|
|
606
|
+
if (privateKey && redirectUri) {
|
|
621
607
|
summaryLines.push(`Team ID: ${teamId}`);
|
|
622
608
|
summaryLines.push(`Key ID: ${keyId}`);
|
|
623
609
|
summaryLines.push(
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
610
|
+
...redirectSetupMessages({
|
|
611
|
+
prompt: `Add this return URL under your Services ID on ${link('https://developer.apple.com', 'developer.apple.com')}`,
|
|
612
|
+
redirectUri,
|
|
613
|
+
showCustomRedirectInstructions: Boolean(customRedirectUri),
|
|
614
|
+
}),
|
|
627
615
|
);
|
|
628
|
-
if (customRedirectUri) {
|
|
629
|
-
summaryLines.push(
|
|
630
|
-
`Your custom redirect must forward to ${chalk.bold(DEFAULT_OAUTH_CALLBACK_URL)} with all query parameters preserved.`,
|
|
631
|
-
);
|
|
632
|
-
summaryLines.push(
|
|
633
|
-
`You can test it by visiting: ${chalk.bold(redirectUri + '?test-redirect=true')}`,
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
616
|
}
|
|
637
617
|
yield* Effect.log(
|
|
638
618
|
boxen(summaryLines.join('\n'), {
|
|
@@ -652,23 +632,7 @@ const handleClerkClient = Effect.fn(function* (opts: Record<string, unknown>) {
|
|
|
652
632
|
simpleName: '--publishable-key',
|
|
653
633
|
required: true,
|
|
654
634
|
skipIf: false,
|
|
655
|
-
prompt: {
|
|
656
|
-
prompt: `Clerk publishable key ${chalk.dim(`(from ${link('https://dashboard.clerk.com/last-active?path=api-keys')})`)}`,
|
|
657
|
-
placeholder:
|
|
658
|
-
'pk_********************************************************',
|
|
659
|
-
modifyOutput: UI.modifiers.piped([
|
|
660
|
-
UI.modifiers.topPadding,
|
|
661
|
-
UI.modifiers.dimOnComplete,
|
|
662
|
-
]),
|
|
663
|
-
validate: (val) => {
|
|
664
|
-
if (!val) {
|
|
665
|
-
return 'Publishable key is required';
|
|
666
|
-
}
|
|
667
|
-
if (!val.startsWith('pk_')) {
|
|
668
|
-
return 'Invalid publishable key. It should start with "pk_".';
|
|
669
|
-
}
|
|
670
|
-
},
|
|
671
|
-
},
|
|
635
|
+
prompt: clerkPublishableKeyPrompt({}),
|
|
672
636
|
});
|
|
673
637
|
|
|
674
638
|
if (!clientName) {
|
|
@@ -680,7 +644,7 @@ const handleClerkClient = Effect.fn(function* (opts: Record<string, unknown>) {
|
|
|
680
644
|
});
|
|
681
645
|
}
|
|
682
646
|
|
|
683
|
-
const domain =
|
|
647
|
+
const domain = clerkDomainFromPublishableKey(publishableKey);
|
|
684
648
|
if (!domain) {
|
|
685
649
|
return yield* BadArgsError.make({
|
|
686
650
|
message: 'Invalid publishable key. Could not extract domain.',
|
|
@@ -731,27 +695,11 @@ const handleFirebaseClient = Effect.fn(function* (
|
|
|
731
695
|
opts,
|
|
732
696
|
);
|
|
733
697
|
|
|
734
|
-
const firebaseProjectIdRegex = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
|
|
735
698
|
const projectId = yield* optOrPrompt(opts['project-id'], {
|
|
736
699
|
simpleName: '--project-id',
|
|
737
700
|
required: true,
|
|
738
701
|
skipIf: false,
|
|
739
|
-
prompt: {
|
|
740
|
-
prompt: `Firebase project ID: (From Project Settings page on ${link('https://console.firebase.google.com/')})`,
|
|
741
|
-
placeholder: '',
|
|
742
|
-
modifyOutput: UI.modifiers.piped([
|
|
743
|
-
UI.modifiers.topPadding,
|
|
744
|
-
UI.modifiers.dimOnComplete,
|
|
745
|
-
]),
|
|
746
|
-
validate: (val) => {
|
|
747
|
-
if (!val) {
|
|
748
|
-
return 'Project ID is required';
|
|
749
|
-
}
|
|
750
|
-
if (!firebaseProjectIdRegex.test(val)) {
|
|
751
|
-
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.';
|
|
752
|
-
}
|
|
753
|
-
},
|
|
754
|
-
},
|
|
702
|
+
prompt: firebaseProjectIdPrompt({}),
|
|
755
703
|
});
|
|
756
704
|
// typeguard
|
|
757
705
|
if (!clientName || !projectId) {
|
|
@@ -759,16 +707,14 @@ const handleFirebaseClient = Effect.fn(function* (
|
|
|
759
707
|
message: 'Missing required arguments',
|
|
760
708
|
});
|
|
761
709
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
'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.',
|
|
766
|
-
});
|
|
710
|
+
const validationError = validateFirebaseProjectId(projectId);
|
|
711
|
+
if (validationError) {
|
|
712
|
+
return yield* BadArgsError.make({ message: validationError });
|
|
767
713
|
}
|
|
768
714
|
const response = yield* addOAuthClient({
|
|
769
715
|
providerId: provider.id,
|
|
770
716
|
clientName,
|
|
771
|
-
discoveryEndpoint:
|
|
717
|
+
discoveryEndpoint: firebaseDiscoveryEndpoint(projectId),
|
|
772
718
|
});
|
|
773
719
|
|
|
774
720
|
yield* Effect.log(
|
|
@@ -840,28 +786,3 @@ export const authClientAddCmd = Effect.fn(
|
|
|
840
786
|
}),
|
|
841
787
|
),
|
|
842
788
|
);
|
|
843
|
-
|
|
844
|
-
function domainFromClerkKey(key: string): string | null {
|
|
845
|
-
try {
|
|
846
|
-
const parts = key.split('_');
|
|
847
|
-
const domainPartB64 = parts[parts.length - 1];
|
|
848
|
-
const domainPart = base64Decode(domainPartB64);
|
|
849
|
-
return domainPart.replace('$', '');
|
|
850
|
-
} catch (e) {
|
|
851
|
-
console.error('Error getting domain from clerk key', e);
|
|
852
|
-
return null;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
// Base64 decode, switching to url-safe decode if we hit an error
|
|
857
|
-
// Can't be sure which method Clerk uses because you can't generate
|
|
858
|
-
// `+` or `/` with characters that go in a normal host. Urls with
|
|
859
|
-
// chinese characters exist, they might encode to `+` or `/`, and
|
|
860
|
-
// Clerk might support them, so we'll be safe and do both.
|
|
861
|
-
function base64Decode(s: string) {
|
|
862
|
-
try {
|
|
863
|
-
return Buffer.from(s, 'base64').toString('utf-8');
|
|
864
|
-
} catch (e) {
|
|
865
|
-
return Buffer.from(s, 'base64url').toString('utf-8');
|
|
866
|
-
}
|
|
867
|
-
}
|