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.
Files changed (34) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/__tests__/authClientAddGoogle.test.ts +3 -4
  3. package/dist/commands/auth/client/add.d.ts +2 -1
  4. package/dist/commands/auth/client/add.d.ts.map +1 -1
  5. package/dist/commands/auth/client/add.js +260 -58
  6. package/dist/commands/auth/client/add.js.map +1 -1
  7. package/dist/commands/auth/client/delete.d.ts +2 -1
  8. package/dist/commands/auth/client/delete.d.ts.map +1 -1
  9. package/dist/commands/auth/client/delete.js +18 -8
  10. package/dist/commands/auth/client/delete.js.map +1 -1
  11. package/dist/index.d.ts +0 -5
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +0 -46
  14. package/dist/index.js.map +1 -1
  15. package/dist/lib/oauth.d.ts +0 -75
  16. package/dist/lib/oauth.d.ts.map +1 -1
  17. package/dist/lib/oauth.js +0 -40
  18. package/dist/lib/oauth.js.map +1 -1
  19. package/package.json +4 -4
  20. package/src/commands/auth/client/add.ts +306 -78
  21. package/src/commands/auth/client/delete.ts +20 -8
  22. package/src/index.ts +0 -59
  23. package/src/lib/oauth.ts +0 -60
  24. package/__tests__/authClientUpdate.test.ts +0 -523
  25. package/dist/commands/auth/client/shared.d.ts +0 -79
  26. package/dist/commands/auth/client/shared.d.ts.map +0 -1
  27. package/dist/commands/auth/client/shared.js +0 -171
  28. package/dist/commands/auth/client/shared.js.map +0 -1
  29. package/dist/commands/auth/client/update.d.ts +0 -8
  30. package/dist/commands/auth/client/update.d.ts.map +0 -1
  31. package/dist/commands/auth/client/update.js +0 -479
  32. package/dist/commands/auth/client/update.js.map +0 -1
  33. package/src/commands/auth/client/shared.ts +0 -234
  34. package/src/commands/auth/client/update.ts +0 -783
@@ -1,4 +1,4 @@
1
1
 
2
- > instant-cli@1.0.21-branch-cli-codex-update.25195790564.1 build /home/runner/work/instant/instant/client/packages/cli
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
- 'instant-cli auth client update --name google-web',
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 | import("@effect/platform/FileSystem").FileSystem | import("../../../lib/http.ts").InstantHttpAuthed | import("../../../context/currentApp.ts").CurrentApp>;
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;AAGvD,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAmD5D,eAAO,MAAM,gBAAgB,gFAO5B,CAAC;AAiqBF,eAAO,MAAM,gBAAgB;;;;wgBAwD5B,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 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
- });
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(getFlag(opts, 'dev-credentials'));
73
- const hasProvidedSomeCustomCredentials = hasAnyFlag(opts, [
74
- 'client-id',
75
- 'client-secret',
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.underline('For production, use your own Google OAuth credentials.'),
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(...redirectSetupMessages({
121
- prompt: 'Add this redirect URI in Google Console',
122
- redirectUri,
123
- showCustomRedirectInstructions: Boolean(customRedirectUri),
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: clientIdPrompt({ providerUrl: googleConsoleUrl }),
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: clientSecretPrompt({ providerUrl: googleConsoleUrl }),
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: optionalRedirectPrompt,
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: clientIdPrompt({ providerUrl: githubDeveloperUrl }),
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: clientSecretPrompt({ providerUrl: githubDeveloperUrl }),
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: optionalRedirectPrompt,
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 = redirectSetupMessages({
263
- prompt: 'Add this callback URL in your GitHub OAuth App settings',
264
- redirectUri,
265
- showCustomRedirectInstructions: Boolean(customRedirectUri),
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: clientIdPrompt({ providerUrl: linkedinDeveloperUrl }),
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: clientSecretPrompt({ providerUrl: linkedinDeveloperUrl }),
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: optionalRedirectPrompt,
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 = redirectSetupMessages({
309
- prompt: 'Add this redirect URI in your LinkedIn app settings',
310
- redirectUri,
311
- showCustomRedirectInstructions: Boolean(customRedirectUri),
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: appleServicesIdPrompt({}),
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: appleTeamIdPrompt({}),
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: appleKeyIdPrompt({}),
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: applePrivateKeyFilePrompt({}),
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: optionalRedirectPrompt,
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 && redirectUri) {
551
+ if (privateKey) {
407
552
  summaryLines.push(`Team ID: ${teamId}`);
408
553
  summaryLines.push(`Key ID: ${keyId}`);
409
- summaryLines.push(...redirectSetupMessages({
410
- prompt: `Add this return URL under your Services ID on ${link('https://developer.apple.com', 'developer.apple.com')}`,
411
- redirectUri,
412
- showCustomRedirectInstructions: Boolean(customRedirectUri),
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: clerkPublishableKeyPrompt({}),
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: firebaseProjectIdPrompt({}),
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
- const validationError = validateFirebaseProjectId(projectId);
476
- if (validationError) {
477
- return yield* BadArgsError.make({ message: validationError });
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: firebaseDiscoveryEndpoint(projectId),
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