instant-cli 1.0.21-branch-cli-codex-update.25195790564.1 → 1.0.21-branch-cli-codex.25196009658.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 +3 -4
- package/dist/commands/auth/client/add.d.ts +2 -1
- package/dist/commands/auth/client/add.d.ts.map +1 -1
- package/dist/commands/auth/client/add.js +260 -58
- package/dist/commands/auth/client/add.js.map +1 -1
- package/dist/commands/auth/client/delete.d.ts +2 -1
- package/dist/commands/auth/client/delete.d.ts.map +1 -1
- package/dist/commands/auth/client/delete.js +18 -8
- package/dist/commands/auth/client/delete.js.map +1 -1
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -46
- package/dist/index.js.map +1 -1
- package/dist/lib/oauth.d.ts +0 -75
- package/dist/lib/oauth.d.ts.map +1 -1
- package/dist/lib/oauth.js +0 -40
- package/dist/lib/oauth.js.map +1 -1
- package/package.json +4 -4
- package/src/commands/auth/client/add.ts +306 -78
- package/src/commands/auth/client/delete.ts +20 -8
- package/src/index.ts +0 -59
- package/src/lib/oauth.ts +0 -60
- package/__tests__/authClientUpdate.test.ts +0 -523
- package/dist/commands/auth/client/shared.d.ts +0 -79
- package/dist/commands/auth/client/shared.d.ts.map +0 -1
- package/dist/commands/auth/client/shared.js +0 -171
- package/dist/commands/auth/client/shared.js.map +0 -1
- package/dist/commands/auth/client/update.d.ts +0 -8
- package/dist/commands/auth/client/update.d.ts.map +0 -1
- package/dist/commands/auth/client/update.js +0 -479
- package/dist/commands/auth/client/update.js.map +0 -1
- package/src/commands/auth/client/shared.ts +0 -234
- package/src/commands/auth/client/update.ts +0 -783
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
> instant-cli@1.0.21-branch-cli-codex
|
|
2
|
+
> instant-cli@1.0.21-branch-cli-codex.25196009658.1 build /home/runner/work/instant/instant/client/packages/cli
|
|
3
3
|
> rm -rf dist; tsc -p tsconfig.build.json
|
|
4
4
|
|
|
@@ -267,9 +267,7 @@ describe('web: dev credentials', () => {
|
|
|
267
267
|
expect(output).toContain(
|
|
268
268
|
'For production, use your own Google OAuth credentials.',
|
|
269
269
|
);
|
|
270
|
-
expect(output).toContain(
|
|
271
|
-
'instant-cli auth client update --name google-web',
|
|
272
|
-
);
|
|
270
|
+
expect(output).not.toContain('instant-cli auth client update');
|
|
273
271
|
expect(output).not.toContain('Add this redirect URI in Google Console');
|
|
274
272
|
});
|
|
275
273
|
|
|
@@ -289,8 +287,9 @@ describe('web: dev credentials', () => {
|
|
|
289
287
|
expect(output).toContain('Credentials: Instant dev credentials');
|
|
290
288
|
expect(output).toContain('No Google Console setup required');
|
|
291
289
|
expect(output).toContain(
|
|
292
|
-
'
|
|
290
|
+
'For production, use your own Google OAuth credentials.',
|
|
293
291
|
);
|
|
292
|
+
expect(output).not.toContain('instant-cli auth client update');
|
|
294
293
|
expect(output).not.toContain('Add this redirect URI in Google Console');
|
|
295
294
|
});
|
|
296
295
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Effect, Schema } from 'effect';
|
|
2
|
+
import { FileSystem } from '@effect/platform';
|
|
2
3
|
import { GlobalOpts } from '../../../context/globalOpts.ts';
|
|
3
4
|
export declare const ClientTypeSchema: Schema.Literal<["google", "github", "apple", "linkedin", "clerk", "firebase"]>;
|
|
4
5
|
export declare const authClientAddCmd: (opts: {
|
|
5
6
|
type?: string | undefined;
|
|
6
7
|
name?: string | undefined;
|
|
7
8
|
app?: string | undefined;
|
|
8
|
-
} & Record<string, unknown>) => Effect.Effect<void | undefined, import("../../../lib/ui.ts").UIError | import("../../../lib/http.ts").InstantHttpError | import("effect/Cause").TimeoutException | import("@effect/platform/HttpClientError").RequestError | import("effect/ParseResult").ParseError | import("@effect/platform/HttpClientError").ResponseError, GlobalOpts |
|
|
9
|
+
} & Record<string, unknown>) => Effect.Effect<void | undefined, import("../../../lib/ui.ts").UIError | import("../../../lib/http.ts").InstantHttpError | import("effect/Cause").TimeoutException | import("@effect/platform/HttpClientError").RequestError | import("effect/ParseResult").ParseError | import("@effect/platform/HttpClientError").ResponseError, GlobalOpts | FileSystem.FileSystem | import("../../../lib/http.ts").InstantHttpAuthed | import("../../../context/currentApp.ts").CurrentApp>;
|
|
9
10
|
//# sourceMappingURL=add.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../../../src/commands/auth/client/add.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAiB,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../../../src/commands/auth/client/add.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAiB,MAAM,EAAE,MAAM,QAAQ,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAiC5D,eAAO,MAAM,gBAAgB,gFAO5B,CAAC;AA63BF,eAAO,MAAM,gBAAgB;;;;6eAwD5B,CAAC"}
|
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
import { Effect, Match, Option, Schema } from 'effect';
|
|
2
|
+
import { FileSystem } from '@effect/platform';
|
|
2
3
|
import { BadArgsError } from "../../../errors.js";
|
|
3
4
|
import { GlobalOpts } from "../../../context/globalOpts.js";
|
|
4
|
-
import { optOrPrompt, optOrPromptBoolean, runUIEffect, validateRequired, } from "../../../lib/ui.js";
|
|
5
|
+
import { optOrPrompt, optOrPromptBoolean, runUIEffect, stripFirstBlankLine, validateRequired, } from "../../../lib/ui.js";
|
|
5
6
|
import { addOAuthClient, findName, getClientNameAndProvider, getOrCreateProvider, GoogleAppTypeSchema, OAuthClient, } from "../../../lib/oauth.js";
|
|
6
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';
|
|
7
8
|
import { UI } from "../../../ui/index.js";
|
|
8
9
|
import chalk from 'chalk';
|
|
9
10
|
import boxen from 'boxen';
|
|
10
11
|
import { link } from "../../../logging.js";
|
|
11
|
-
import { appleKeyIdPrompt, applePrivateKeyFilePrompt, appleServicesIdPrompt, appleTeamIdPrompt, clerkPublishableKeyPrompt, clientIdPrompt, clientSecretPrompt, domainFromClerkKey, 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
|
|
14
|
-
const
|
|
15
|
-
const linkedinDeveloperUrl = 'https://www.linkedin.com/developers/apps';
|
|
16
|
-
const optionalRedirectPrompt = redirectUriPrompt({
|
|
17
|
-
heading: 'Custom redirect URI (optional):',
|
|
18
|
-
});
|
|
13
|
+
const isTrueFlag = (value) => value === true || value === 'true';
|
|
14
|
+
const hasFlag = (opts, flag) => flag in opts;
|
|
19
15
|
const selectGoogleAppType = (value) => Effect.gen(function* () {
|
|
20
16
|
const { yes } = yield* GlobalOpts;
|
|
21
17
|
return yield* Option.fromNullable(value).pipe(Effect.catchTag('NoSuchElementException', () => {
|
|
@@ -69,12 +65,10 @@ const selectGoogleCredentialMode = Effect.fn(function* () {
|
|
|
69
65
|
});
|
|
70
66
|
const resolveGoogleCredentialMode = Effect.fn(function* ({ appType, opts, }) {
|
|
71
67
|
const { yes } = yield* GlobalOpts;
|
|
72
|
-
const devCredentialsFlag = isTrueFlag(
|
|
73
|
-
const hasProvidedSomeCustomCredentials =
|
|
74
|
-
'client-
|
|
75
|
-
'
|
|
76
|
-
'custom-redirect-uri',
|
|
77
|
-
]);
|
|
68
|
+
const devCredentialsFlag = isTrueFlag(opts['dev-credentials']);
|
|
69
|
+
const hasProvidedSomeCustomCredentials = hasFlag(opts, 'client-id') ||
|
|
70
|
+
hasFlag(opts, 'client-secret') ||
|
|
71
|
+
hasFlag(opts, 'custom-redirect-uri');
|
|
78
72
|
if (devCredentialsFlag && appType !== 'web') {
|
|
79
73
|
return yield* BadArgsError.make({
|
|
80
74
|
message: '--dev-credentials is only supported for --app-type web. Native Google clients need credentials from Google.',
|
|
@@ -109,19 +103,17 @@ const printGoogleDevCredentialsClient = Effect.fn(function* ({ appType, client,
|
|
|
109
103
|
'No Google Console setup required.',
|
|
110
104
|
'Works on localhost and Expo during development.',
|
|
111
105
|
'',
|
|
112
|
-
chalk.bold
|
|
113
|
-
'Ready for production? Run:',
|
|
114
|
-
` instant-cli auth client update --name ${client.client_name} --client-id <id> --client-secret <secret>`,
|
|
106
|
+
chalk.bold('For production, use your own Google OAuth credentials.'),
|
|
115
107
|
].join('\n'), { dimBorder: true, padding: { right: 1, left: 1 } }));
|
|
116
108
|
});
|
|
117
109
|
const printGoogleCustomCredentialsClient = Effect.fn(function* ({ appType, client, clientId, customRedirectUri, redirectUri, }) {
|
|
118
110
|
const redirectMessages = [];
|
|
119
111
|
if (appType === 'web' && redirectUri) {
|
|
120
|
-
redirectMessages.push(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
112
|
+
redirectMessages.push('', chalk.bold('Add this redirect URI in Google Console:'), chalk.bold(redirectUri));
|
|
113
|
+
if (customRedirectUri) {
|
|
114
|
+
redirectMessages.push('', `Your custom redirect must forward to ${chalk.bold(DEFAULT_OAUTH_CALLBACK_URL)} with all query parameters preserved.`);
|
|
115
|
+
redirectMessages.push(`You can test it by visiting: ${chalk.bold(redirectUri + '?test-redirect=true')}`);
|
|
116
|
+
}
|
|
125
117
|
}
|
|
126
118
|
yield* Effect.log(boxen([
|
|
127
119
|
`Google OAuth client created: ${client.client_name}`,
|
|
@@ -168,7 +160,14 @@ const handleGoogleClient = Effect.fn(function* (opts) {
|
|
|
168
160
|
required: !useSharedCredentials,
|
|
169
161
|
skipIf: useSharedCredentials,
|
|
170
162
|
skipMessage: '--client-id is not compatible with --dev-credentials. Drop one or the other.',
|
|
171
|
-
prompt:
|
|
163
|
+
prompt: {
|
|
164
|
+
prompt: `Client ID: ${chalk.dim(`(from ${link('https://console.developers.google.com/apis/credentials')})`)}`,
|
|
165
|
+
modifyOutput: UI.modifiers.piped([
|
|
166
|
+
UI.modifiers.topPadding,
|
|
167
|
+
UI.modifiers.dimOnComplete,
|
|
168
|
+
]),
|
|
169
|
+
validate: validateRequired,
|
|
170
|
+
},
|
|
172
171
|
});
|
|
173
172
|
const usesCustomWebCredentials = !useSharedCredentials && appType === 'web';
|
|
174
173
|
const clientSecret = yield* optOrPrompt(opts['client-secret'], {
|
|
@@ -178,11 +177,34 @@ const handleGoogleClient = Effect.fn(function* (opts) {
|
|
|
178
177
|
skipMessage: useSharedCredentials
|
|
179
178
|
? '--client-secret is not compatible with --dev-credentials. Drop one or the other.'
|
|
180
179
|
: undefined,
|
|
181
|
-
prompt:
|
|
180
|
+
prompt: {
|
|
181
|
+
prompt: `Client Secret: ${chalk.dim(`(from ${link('https://console.developers.google.com/apis/credentials')})`)}`,
|
|
182
|
+
validate: validateRequired,
|
|
183
|
+
sensitive: true,
|
|
184
|
+
modifyOutput: UI.modifiers.piped([
|
|
185
|
+
UI.modifiers.topPadding,
|
|
186
|
+
UI.modifiers.dimOnComplete,
|
|
187
|
+
]),
|
|
188
|
+
},
|
|
182
189
|
});
|
|
183
190
|
const customRedirectUri = yield* optOrPrompt(opts['custom-redirect-uri'], {
|
|
184
191
|
required: false,
|
|
185
|
-
prompt:
|
|
192
|
+
prompt: {
|
|
193
|
+
prompt: '',
|
|
194
|
+
placeholder: 'https://yoursite.com/oauth/callback',
|
|
195
|
+
modifyOutput: UI.modifiers.piped([
|
|
196
|
+
(output, status) => {
|
|
197
|
+
if (status === 'idle') {
|
|
198
|
+
return (`\nCustom redirect URI (optional):
|
|
199
|
+
${chalk.dim('With a custom redirect URI, users will see "Redirecting to yoursite.com..." for a more branded experience.')}
|
|
200
|
+
${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all query parameters preserved.`)}\n\n` +
|
|
201
|
+
stripFirstBlankLine(output));
|
|
202
|
+
}
|
|
203
|
+
return `\nCustom redirect URI (optional):\n${stripFirstBlankLine(output)}`;
|
|
204
|
+
},
|
|
205
|
+
UI.modifiers.dimOnComplete,
|
|
206
|
+
]),
|
|
207
|
+
},
|
|
186
208
|
simpleName: '--custom-redirect-uri',
|
|
187
209
|
skipIf: !usesCustomWebCredentials,
|
|
188
210
|
skipMessage: useSharedCredentials
|
|
@@ -231,19 +253,49 @@ const handleGithubClient = Effect.fn(function* (opts) {
|
|
|
231
253
|
simpleName: '--client-id',
|
|
232
254
|
required: true,
|
|
233
255
|
skipIf: false,
|
|
234
|
-
prompt:
|
|
256
|
+
prompt: {
|
|
257
|
+
prompt: `Client ID ${chalk.dim(`(from ${link('https://github.com/settings/developers')})`)}`,
|
|
258
|
+
modifyOutput: UI.modifiers.piped([
|
|
259
|
+
UI.modifiers.topPadding,
|
|
260
|
+
UI.modifiers.dimOnComplete,
|
|
261
|
+
]),
|
|
262
|
+
validate: validateRequired,
|
|
263
|
+
},
|
|
235
264
|
});
|
|
236
265
|
const clientSecret = yield* optOrPrompt(opts['client-secret'], {
|
|
237
266
|
required: true,
|
|
238
267
|
skipIf: false,
|
|
239
268
|
simpleName: '--client-secret',
|
|
240
|
-
prompt:
|
|
269
|
+
prompt: {
|
|
270
|
+
prompt: `Client Secret: ${chalk.dim(`(from ${link('https://github.com/settings/developers')})`)}`,
|
|
271
|
+
validate: validateRequired,
|
|
272
|
+
sensitive: true,
|
|
273
|
+
modifyOutput: UI.modifiers.piped([
|
|
274
|
+
UI.modifiers.topPadding,
|
|
275
|
+
UI.modifiers.dimOnComplete,
|
|
276
|
+
]),
|
|
277
|
+
},
|
|
241
278
|
});
|
|
242
279
|
const customRedirectUri = yield* optOrPrompt(opts['custom-redirect-uri'], {
|
|
243
280
|
required: false,
|
|
244
281
|
simpleName: '--custom-redirect-uri',
|
|
245
282
|
skipIf: false,
|
|
246
|
-
prompt:
|
|
283
|
+
prompt: {
|
|
284
|
+
prompt: '',
|
|
285
|
+
placeholder: 'https://yoursite.com/oauth/callback',
|
|
286
|
+
modifyOutput: UI.modifiers.piped([
|
|
287
|
+
(output, status) => {
|
|
288
|
+
if (status === 'idle') {
|
|
289
|
+
return (`\nCustom redirect URI (optional):
|
|
290
|
+
${chalk.dim('With a custom redirect URI, users will see "Redirecting to yoursite.com..." for a more branded experience.')}
|
|
291
|
+
${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all query parameters preserved.`)}\n\n` +
|
|
292
|
+
stripFirstBlankLine(output));
|
|
293
|
+
}
|
|
294
|
+
return `\nCustom redirect URI (optional):\n${stripFirstBlankLine(output)}`;
|
|
295
|
+
},
|
|
296
|
+
UI.modifiers.dimOnComplete,
|
|
297
|
+
]),
|
|
298
|
+
},
|
|
247
299
|
});
|
|
248
300
|
if (!clientName) {
|
|
249
301
|
return yield* BadArgsError.make({ message: 'Client name is required.' });
|
|
@@ -259,11 +311,13 @@ const handleGithubClient = Effect.fn(function* (opts) {
|
|
|
259
311
|
redirectTo: redirectUri,
|
|
260
312
|
meta: { providerName: 'github' },
|
|
261
313
|
});
|
|
262
|
-
const redirectMessages =
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
314
|
+
const redirectMessages = [
|
|
315
|
+
chalk.bold(`\nAdd this callback URL in your GitHub OAuth App settings:\n${redirectUri}\n`),
|
|
316
|
+
];
|
|
317
|
+
if (customRedirectUri) {
|
|
318
|
+
redirectMessages.push(`Your custom redirect must forward to ${chalk.bold(DEFAULT_OAUTH_CALLBACK_URL)} with all query parameters preserved.`);
|
|
319
|
+
redirectMessages.push(`You can test it by visiting: ${chalk.bold(redirectUri + '?test-redirect=true')}`);
|
|
320
|
+
}
|
|
267
321
|
yield* Effect.log(boxen([
|
|
268
322
|
`GitHub OAuth client created: ${response.client.client_name}`,
|
|
269
323
|
`ID: ${response.client.id}`,
|
|
@@ -277,19 +331,49 @@ const handleLinkedInClient = Effect.fn(function* (opts) {
|
|
|
277
331
|
simpleName: '--client-id',
|
|
278
332
|
required: true,
|
|
279
333
|
skipIf: false,
|
|
280
|
-
prompt:
|
|
334
|
+
prompt: {
|
|
335
|
+
prompt: `Client ID: ${chalk.dim(`(from ${link('https://www.linkedin.com/developers/apps')})`)}`,
|
|
336
|
+
modifyOutput: UI.modifiers.piped([
|
|
337
|
+
UI.modifiers.topPadding,
|
|
338
|
+
UI.modifiers.dimOnComplete,
|
|
339
|
+
]),
|
|
340
|
+
validate: validateRequired,
|
|
341
|
+
},
|
|
281
342
|
});
|
|
282
343
|
const clientSecret = yield* optOrPrompt(opts['client-secret'], {
|
|
283
344
|
required: true,
|
|
284
345
|
skipIf: false,
|
|
285
346
|
simpleName: '--client-secret',
|
|
286
|
-
prompt:
|
|
347
|
+
prompt: {
|
|
348
|
+
prompt: `Client Secret: ${chalk.dim(`(from ${link('https://www.linkedin.com/developers/apps')})`)}`,
|
|
349
|
+
validate: validateRequired,
|
|
350
|
+
sensitive: true,
|
|
351
|
+
modifyOutput: UI.modifiers.piped([
|
|
352
|
+
UI.modifiers.topPadding,
|
|
353
|
+
UI.modifiers.dimOnComplete,
|
|
354
|
+
]),
|
|
355
|
+
},
|
|
287
356
|
});
|
|
288
357
|
const customRedirectUri = yield* optOrPrompt(opts['custom-redirect-uri'], {
|
|
289
358
|
required: false,
|
|
290
359
|
simpleName: '--custom-redirect-uri',
|
|
291
360
|
skipIf: false,
|
|
292
|
-
prompt:
|
|
361
|
+
prompt: {
|
|
362
|
+
prompt: '',
|
|
363
|
+
placeholder: 'https://yoursite.com/oauth/callback',
|
|
364
|
+
modifyOutput: UI.modifiers.piped([
|
|
365
|
+
(output, status) => {
|
|
366
|
+
if (status === 'idle') {
|
|
367
|
+
return (`\nCustom redirect URI (optional):
|
|
368
|
+
${chalk.dim('With a custom redirect URI, users will see "Redirecting to yoursite.com..." for a more branded experience.')}
|
|
369
|
+
${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all query parameters preserved.`)}\n\n` +
|
|
370
|
+
stripFirstBlankLine(output));
|
|
371
|
+
}
|
|
372
|
+
return `\nCustom redirect URI (optional):\n${stripFirstBlankLine(output)}`;
|
|
373
|
+
},
|
|
374
|
+
UI.modifiers.dimOnComplete,
|
|
375
|
+
]),
|
|
376
|
+
},
|
|
293
377
|
});
|
|
294
378
|
if (!clientName) {
|
|
295
379
|
return yield* BadArgsError.make({ message: 'Client name is required.' });
|
|
@@ -305,11 +389,13 @@ const handleLinkedInClient = Effect.fn(function* (opts) {
|
|
|
305
389
|
discoveryEndpoint: LINKEDIN_DISCOVERY_ENDPOINT,
|
|
306
390
|
redirectTo: redirectUri,
|
|
307
391
|
});
|
|
308
|
-
const redirectMessages =
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
392
|
+
const redirectMessages = [
|
|
393
|
+
chalk.bold(`\nAdd this redirect URI in your LinkedIn app settings:\n${redirectUri}\n`),
|
|
394
|
+
];
|
|
395
|
+
if (customRedirectUri) {
|
|
396
|
+
redirectMessages.push(`Your custom redirect must forward to ${chalk.bold(DEFAULT_OAUTH_CALLBACK_URL)} with all query parameters preserved.`);
|
|
397
|
+
redirectMessages.push(`You can test it by visiting: ${chalk.bold(redirectUri + '?test-redirect=true')}`);
|
|
398
|
+
}
|
|
313
399
|
yield* Effect.log(boxen([
|
|
314
400
|
`LinkedIn OAuth client created: ${response.client.client_name}`,
|
|
315
401
|
`ID: ${response.client.id}`,
|
|
@@ -317,6 +403,22 @@ const handleLinkedInClient = Effect.fn(function* (opts) {
|
|
|
317
403
|
...redirectMessages,
|
|
318
404
|
].join('\n'), { dimBorder: true, padding: { right: 1, left: 1 } }));
|
|
319
405
|
});
|
|
406
|
+
const readPrivateKeyFile = Effect.fn('readPrivateKeyFile')(function* (path) {
|
|
407
|
+
const fs = yield* FileSystem.FileSystem;
|
|
408
|
+
// Strip shell-escape backslashes so paths like "file\ (2).p8" resolve correctly.
|
|
409
|
+
// Only on POSIX — Windows uses backslashes as path separators.
|
|
410
|
+
const normalizedPath = process.platform === 'win32' ? path : path.replace(/\\(.)/g, '$1');
|
|
411
|
+
const contents = yield* fs.readFileString(normalizedPath, 'utf8').pipe(Effect.mapError((e) => new BadArgsError({
|
|
412
|
+
message: `Could not read private key file at ${normalizedPath}: ${e.message}`,
|
|
413
|
+
})));
|
|
414
|
+
const trimmed = contents.trim();
|
|
415
|
+
if (!trimmed) {
|
|
416
|
+
return yield* BadArgsError.make({
|
|
417
|
+
message: `Private key file at ${normalizedPath} is empty.`,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
return trimmed;
|
|
421
|
+
});
|
|
320
422
|
const handleAppleClient = Effect.fn(function* (opts) {
|
|
321
423
|
const { yes } = yield* GlobalOpts;
|
|
322
424
|
const { clientName, provider } = yield* getClientNameAndProvider('apple', opts);
|
|
@@ -324,7 +426,14 @@ const handleAppleClient = Effect.fn(function* (opts) {
|
|
|
324
426
|
simpleName: '--services-id',
|
|
325
427
|
required: true,
|
|
326
428
|
skipIf: false,
|
|
327
|
-
prompt:
|
|
429
|
+
prompt: {
|
|
430
|
+
prompt: `Services ID ${chalk.dim(`(from ${link('https://developer.apple.com/account/resources/identifiers/list/serviceId')})`)}`,
|
|
431
|
+
modifyOutput: UI.modifiers.piped([
|
|
432
|
+
UI.modifiers.topPadding,
|
|
433
|
+
UI.modifiers.dimOnComplete,
|
|
434
|
+
]),
|
|
435
|
+
validate: validateRequired,
|
|
436
|
+
},
|
|
328
437
|
});
|
|
329
438
|
// If any web-flow flag is provided, enable web flow; otherwise ask
|
|
330
439
|
// (non-interactively with --yes we default to native-only).
|
|
@@ -350,21 +459,42 @@ const handleAppleClient = Effect.fn(function* (opts) {
|
|
|
350
459
|
required: true,
|
|
351
460
|
skipIf: skipWeb,
|
|
352
461
|
skipMessage: `--team-id ${webSkipMessage}`,
|
|
353
|
-
prompt:
|
|
462
|
+
prompt: {
|
|
463
|
+
prompt: `Team ID ${chalk.dim(`(from ${link('https://developer.apple.com/account#MembershipDetailsCard')})`)}`,
|
|
464
|
+
validate: validateRequired,
|
|
465
|
+
modifyOutput: UI.modifiers.piped([
|
|
466
|
+
UI.modifiers.topPadding,
|
|
467
|
+
UI.modifiers.dimOnComplete,
|
|
468
|
+
]),
|
|
469
|
+
},
|
|
354
470
|
});
|
|
355
471
|
const keyId = yield* optOrPrompt(opts['key-id'], {
|
|
356
472
|
simpleName: '--key-id',
|
|
357
473
|
required: true,
|
|
358
474
|
skipIf: skipWeb,
|
|
359
475
|
skipMessage: `--key-id ${webSkipMessage}`,
|
|
360
|
-
prompt:
|
|
476
|
+
prompt: {
|
|
477
|
+
prompt: `Key ID ${chalk.dim(`(from ${link('https://developer.apple.com/account/resources/authkeys/list')})`)}`,
|
|
478
|
+
validate: validateRequired,
|
|
479
|
+
modifyOutput: UI.modifiers.piped([
|
|
480
|
+
UI.modifiers.topPadding,
|
|
481
|
+
UI.modifiers.dimOnComplete,
|
|
482
|
+
]),
|
|
483
|
+
},
|
|
361
484
|
});
|
|
362
485
|
const privateKeyPath = yield* optOrPrompt(opts['private-key-file'], {
|
|
363
486
|
simpleName: '--private-key-file',
|
|
364
487
|
required: true,
|
|
365
488
|
skipIf: skipWeb,
|
|
366
489
|
skipMessage: `--private-key-file ${webSkipMessage}`,
|
|
367
|
-
prompt:
|
|
490
|
+
prompt: {
|
|
491
|
+
prompt: `Path to .p8 private key file ${chalk.dim('(downloaded from Apple)')}`,
|
|
492
|
+
validate: validateRequired,
|
|
493
|
+
modifyOutput: UI.modifiers.piped([
|
|
494
|
+
UI.modifiers.topPadding,
|
|
495
|
+
UI.modifiers.dimOnComplete,
|
|
496
|
+
]),
|
|
497
|
+
},
|
|
368
498
|
});
|
|
369
499
|
const privateKey = privateKeyPath
|
|
370
500
|
? yield* readPrivateKeyFile(privateKeyPath)
|
|
@@ -374,7 +504,22 @@ const handleAppleClient = Effect.fn(function* (opts) {
|
|
|
374
504
|
simpleName: '--custom-redirect-uri',
|
|
375
505
|
skipIf: skipWeb,
|
|
376
506
|
skipMessage: `--custom-redirect-uri ${webSkipMessage}`,
|
|
377
|
-
prompt:
|
|
507
|
+
prompt: {
|
|
508
|
+
prompt: '',
|
|
509
|
+
placeholder: 'https://yoursite.com/oauth/callback',
|
|
510
|
+
modifyOutput: UI.modifiers.piped([
|
|
511
|
+
(output, status) => {
|
|
512
|
+
if (status === 'idle') {
|
|
513
|
+
return (`\nCustom redirect URI (optional):
|
|
514
|
+
${chalk.dim('With a custom redirect URI, users will see "Redirecting to yoursite.com..." for a more branded experience.')}
|
|
515
|
+
${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all query parameters preserved.`)}\n\n` +
|
|
516
|
+
stripFirstBlankLine(output));
|
|
517
|
+
}
|
|
518
|
+
return `\nCustom redirect URI (optional):\n${stripFirstBlankLine(output)}`;
|
|
519
|
+
},
|
|
520
|
+
UI.modifiers.dimOnComplete,
|
|
521
|
+
]),
|
|
522
|
+
},
|
|
378
523
|
});
|
|
379
524
|
if (!clientName) {
|
|
380
525
|
return yield* BadArgsError.make({ message: 'Client name is required.' });
|
|
@@ -403,14 +548,14 @@ const handleAppleClient = Effect.fn(function* (opts) {
|
|
|
403
548
|
`ID: ${response.client.id}`,
|
|
404
549
|
`Services ID: ${response.client.client_id ?? servicesId}`,
|
|
405
550
|
];
|
|
406
|
-
if (privateKey
|
|
551
|
+
if (privateKey) {
|
|
407
552
|
summaryLines.push(`Team ID: ${teamId}`);
|
|
408
553
|
summaryLines.push(`Key ID: ${keyId}`);
|
|
409
|
-
summaryLines.push(
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
554
|
+
summaryLines.push(chalk.bold(`\nAdd this return URL under your Services ID on ${link('https://developer.apple.com', 'developer.apple.com')}:\n${redirectUri}\n`));
|
|
555
|
+
if (customRedirectUri) {
|
|
556
|
+
summaryLines.push(`Your custom redirect must forward to ${chalk.bold(DEFAULT_OAUTH_CALLBACK_URL)} with all query parameters preserved.`);
|
|
557
|
+
summaryLines.push(`You can test it by visiting: ${chalk.bold(redirectUri + '?test-redirect=true')}`);
|
|
558
|
+
}
|
|
414
559
|
}
|
|
415
560
|
yield* Effect.log(boxen(summaryLines.join('\n'), {
|
|
416
561
|
dimBorder: true,
|
|
@@ -423,7 +568,22 @@ const handleClerkClient = Effect.fn(function* (opts) {
|
|
|
423
568
|
simpleName: '--publishable-key',
|
|
424
569
|
required: true,
|
|
425
570
|
skipIf: false,
|
|
426
|
-
prompt:
|
|
571
|
+
prompt: {
|
|
572
|
+
prompt: `Clerk publishable key ${chalk.dim(`(from ${link('https://dashboard.clerk.com/last-active?path=api-keys')})`)}`,
|
|
573
|
+
placeholder: 'pk_********************************************************',
|
|
574
|
+
modifyOutput: UI.modifiers.piped([
|
|
575
|
+
UI.modifiers.topPadding,
|
|
576
|
+
UI.modifiers.dimOnComplete,
|
|
577
|
+
]),
|
|
578
|
+
validate: (val) => {
|
|
579
|
+
if (!val) {
|
|
580
|
+
return 'Publishable key is required';
|
|
581
|
+
}
|
|
582
|
+
if (!val.startsWith('pk_')) {
|
|
583
|
+
return 'Invalid publishable key. It should start with "pk_".';
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
},
|
|
427
587
|
});
|
|
428
588
|
if (!clientName) {
|
|
429
589
|
return yield* BadArgsError.make({ message: 'Client name is required.' });
|
|
@@ -460,11 +620,27 @@ const handleClerkClient = Effect.fn(function* (opts) {
|
|
|
460
620
|
});
|
|
461
621
|
const handleFirebaseClient = Effect.fn(function* (opts) {
|
|
462
622
|
const { clientName, provider } = yield* getClientNameAndProvider('firebase', opts);
|
|
623
|
+
const firebaseProjectIdRegex = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
|
|
463
624
|
const projectId = yield* optOrPrompt(opts['project-id'], {
|
|
464
625
|
simpleName: '--project-id',
|
|
465
626
|
required: true,
|
|
466
627
|
skipIf: false,
|
|
467
|
-
prompt:
|
|
628
|
+
prompt: {
|
|
629
|
+
prompt: `Firebase project ID: (From Project Settings page on ${link('https://console.firebase.google.com/')})`,
|
|
630
|
+
placeholder: '',
|
|
631
|
+
modifyOutput: UI.modifiers.piped([
|
|
632
|
+
UI.modifiers.topPadding,
|
|
633
|
+
UI.modifiers.dimOnComplete,
|
|
634
|
+
]),
|
|
635
|
+
validate: (val) => {
|
|
636
|
+
if (!val) {
|
|
637
|
+
return 'Project ID is required';
|
|
638
|
+
}
|
|
639
|
+
if (!firebaseProjectIdRegex.test(val)) {
|
|
640
|
+
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.';
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
},
|
|
468
644
|
});
|
|
469
645
|
// typeguard
|
|
470
646
|
if (!clientName || !projectId) {
|
|
@@ -472,14 +648,15 @@ const handleFirebaseClient = Effect.fn(function* (opts) {
|
|
|
472
648
|
message: 'Missing required arguments',
|
|
473
649
|
});
|
|
474
650
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
651
|
+
if (!firebaseProjectIdRegex.test(projectId)) {
|
|
652
|
+
return yield* BadArgsError.make({
|
|
653
|
+
message: '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.',
|
|
654
|
+
});
|
|
478
655
|
}
|
|
479
656
|
const response = yield* addOAuthClient({
|
|
480
657
|
providerId: provider.id,
|
|
481
658
|
clientName,
|
|
482
|
-
discoveryEndpoint:
|
|
659
|
+
discoveryEndpoint: `https://securetoken.google.com/${encodeURIComponent(projectId)}/.well-known/openid-configuration`,
|
|
483
660
|
});
|
|
484
661
|
yield* Effect.log(boxen([
|
|
485
662
|
`Firebase OAuth client created: ${response.client.client_name}`,
|
|
@@ -513,4 +690,29 @@ export const authClientAddCmd = Effect.fn(function* (opts) {
|
|
|
513
690
|
yield* Effect.logError(e.message);
|
|
514
691
|
yield* Effect.log(chalk.dim('hint: run `instant-cli auth client add --help` for the list of available arguments'));
|
|
515
692
|
})));
|
|
693
|
+
function domainFromClerkKey(key) {
|
|
694
|
+
try {
|
|
695
|
+
const parts = key.split('_');
|
|
696
|
+
const domainPartB64 = parts[parts.length - 1];
|
|
697
|
+
const domainPart = base64Decode(domainPartB64);
|
|
698
|
+
return domainPart.replace('$', '');
|
|
699
|
+
}
|
|
700
|
+
catch (e) {
|
|
701
|
+
console.error('Error getting domain from clerk key', e);
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
// Base64 decode, switching to url-safe decode if we hit an error
|
|
706
|
+
// Can't be sure which method Clerk uses because you can't generate
|
|
707
|
+
// `+` or `/` with characters that go in a normal host. Urls with
|
|
708
|
+
// chinese characters exist, they might encode to `+` or `/`, and
|
|
709
|
+
// Clerk might support them, so we'll be safe and do both.
|
|
710
|
+
function base64Decode(s) {
|
|
711
|
+
try {
|
|
712
|
+
return Buffer.from(s, 'base64').toString('utf-8');
|
|
713
|
+
}
|
|
714
|
+
catch (e) {
|
|
715
|
+
return Buffer.from(s, 'base64url').toString('utf-8');
|
|
716
|
+
}
|
|
717
|
+
}
|
|
516
718
|
//# sourceMappingURL=add.js.map
|