instant-cli 1.0.23-branch-cli-codex-update.25390498340.1 → 1.0.23-branch-codex-cli-args-combinators.25393207850.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.
@@ -1,4 +1,4 @@
1
1
 
2
- > instant-cli@1.0.23-branch-cli-codex-update.25390498340.1 build /home/runner/work/instant/instant/client/packages/cli
2
+ > instant-cli@1.0.23-branch-codex-cli-args-combinators.25393207850.1 build /home/runner/work/instant/instant/client/packages/cli
3
3
  > rm -rf dist; tsc -p tsconfig.build.json
4
4
 
@@ -0,0 +1,203 @@
1
+ import { test, expect, describe, vi } from 'vitest';
2
+ import { Effect, Layer } from 'effect';
3
+ import { Args } from '../src/lib/args.ts';
4
+ import { GlobalOpts } from '../src/context/globalOpts.ts';
5
+
6
+ const run = <A>(effect: Effect.Effect<A, any, GlobalOpts>, yes: boolean) =>
7
+ Effect.runPromise(
8
+ effect.pipe(Effect.provide(Layer.succeed(GlobalOpts, { yes }))),
9
+ );
10
+
11
+ const runFail = <A>(effect: Effect.Effect<A, any, GlobalOpts>, yes: boolean) =>
12
+ Effect.runPromise(
13
+ effect.pipe(
14
+ Effect.flip,
15
+ Effect.provide(Layer.succeed(GlobalOpts, { yes })),
16
+ ),
17
+ );
18
+
19
+ let mockPromptReturn: unknown = '';
20
+ vi.mock('../src/ui/lib.ts', async (importOriginal) => {
21
+ const orig: any = await importOriginal();
22
+ return {
23
+ ...orig,
24
+ renderUnwrap: () => Promise.resolve(mockPromptReturn),
25
+ };
26
+ });
27
+
28
+ const basePrompt = { prompt: 'Client ID:' } as any;
29
+
30
+ describe('forbidIf', () => {
31
+ test('condition=true + value provided -> error', async () => {
32
+ const err = await runFail(
33
+ Args.text(
34
+ { 'custom-redirect-uri': 'https://mysite.com/callback' },
35
+ 'custom-redirect-uri',
36
+ ).pipe(
37
+ Args.forbidIf(true, {
38
+ message: 'Provided custom redirect URI when not using web app type.',
39
+ }),
40
+ ),
41
+ false,
42
+ );
43
+
44
+ expect(err.message).toBe(
45
+ 'Provided custom redirect URI when not using web app type.',
46
+ );
47
+ });
48
+
49
+ test('condition=true + no value -> undefined', async () => {
50
+ const result = await run(
51
+ Args.text({}, 'custom-redirect-uri').pipe(Args.forbidIf(true)),
52
+ false,
53
+ );
54
+
55
+ expect(result).toBeUndefined();
56
+ });
57
+ });
58
+
59
+ describe('non-interactive', () => {
60
+ test('required missing value -> error', async () => {
61
+ const err = await runFail(
62
+ Args.text({}, 'client-id').pipe(
63
+ Args.orPrompt(basePrompt),
64
+ Args.required(),
65
+ ),
66
+ true,
67
+ );
68
+
69
+ expect(err.message).toBe('Missing required value for --client-id');
70
+ });
71
+
72
+ test('number value -> stringified', async () => {
73
+ const result = await run(
74
+ Args.text({ 'client-id': 42 }, 'client-id').pipe(Args.required()),
75
+ true,
76
+ );
77
+
78
+ expect(result).toBe('42');
79
+ });
80
+
81
+ test('non-string/number value -> error', async () => {
82
+ const err = await runFail(
83
+ Args.text({ 'client-id': true }, 'client-id').pipe(Args.required()),
84
+ true,
85
+ );
86
+
87
+ expect(err.message).toBe('Invalid value for --client-id');
88
+ });
89
+
90
+ test('valid string -> trimmed value', async () => {
91
+ const result = await run(
92
+ Args.text(
93
+ { 'client-id': ' abc123.apps.googleusercontent.com ' },
94
+ 'client-id',
95
+ ).pipe(Args.required()),
96
+ true,
97
+ );
98
+
99
+ expect(result).toBe('abc123.apps.googleusercontent.com');
100
+ });
101
+
102
+ test('optional missing value -> undefined', async () => {
103
+ const result = await run(Args.text({}, 'custom-redirect-uri'), true);
104
+
105
+ expect(result).toBeUndefined();
106
+ });
107
+
108
+ test('boolean value -> parsed', async () => {
109
+ const result = await run(
110
+ Args.bool({ 'configure-web': true }, 'configure-web'),
111
+ true,
112
+ );
113
+
114
+ expect(result).toBe(true);
115
+ });
116
+
117
+ test('boolean string value -> parsed', async () => {
118
+ const result = await run(
119
+ Args.bool({ 'configure-web': 'false' }, 'configure-web'),
120
+ true,
121
+ );
122
+
123
+ expect(result).toBe(false);
124
+ });
125
+
126
+ test('invalid boolean value -> error', async () => {
127
+ const err = await runFail(
128
+ Args.bool({ 'configure-web': 'sometimes' }, 'configure-web'),
129
+ true,
130
+ );
131
+
132
+ expect(err.message).toBe('Invalid value for --configure-web');
133
+ });
134
+ });
135
+
136
+ describe('interactive', () => {
137
+ test('value already provided -> use it directly', async () => {
138
+ const result = await run(
139
+ Args.text(
140
+ { 'client-id': ' abc123.apps.googleusercontent.com ' },
141
+ 'client-id',
142
+ ).pipe(Args.orPrompt(basePrompt), Args.required()),
143
+ false,
144
+ );
145
+
146
+ expect(result).toBe('abc123.apps.googleusercontent.com');
147
+ });
148
+
149
+ test('no value + user types a value -> trimmed result', async () => {
150
+ mockPromptReturn = ' GOCSPX-secret123 ';
151
+
152
+ const result = await run(
153
+ Args.text({}, 'client-secret').pipe(
154
+ Args.orPrompt(basePrompt),
155
+ Args.required(),
156
+ ),
157
+ false,
158
+ );
159
+
160
+ expect(result).toBe('GOCSPX-secret123');
161
+ });
162
+
163
+ test('no value + user enters empty + required -> error', async () => {
164
+ mockPromptReturn = '';
165
+
166
+ const err = await runFail(
167
+ Args.text({}, 'client-secret').pipe(
168
+ Args.orPrompt(basePrompt),
169
+ Args.required(),
170
+ ),
171
+ false,
172
+ );
173
+
174
+ expect(err.message).toBe('Missing required value for --client-secret');
175
+ });
176
+
177
+ test('no value + user enters empty + optional -> undefined', async () => {
178
+ mockPromptReturn = '';
179
+
180
+ const result = await run(
181
+ Args.text({}, 'custom-redirect-uri').pipe(Args.orPrompt(basePrompt)),
182
+ false,
183
+ );
184
+
185
+ expect(result).toBeUndefined();
186
+ });
187
+
188
+ test('no boolean value + user confirms -> result', async () => {
189
+ mockPromptReturn = true;
190
+
191
+ const result = await run(
192
+ Args.bool({}, 'configure-web').pipe(
193
+ Args.orConfirm({
194
+ promptText: 'Configure web redirect flow?',
195
+ defaultValue: false,
196
+ }),
197
+ ),
198
+ false,
199
+ );
200
+
201
+ expect(result).toBe(true);
202
+ });
203
+ });
@@ -1,5 +1,6 @@
1
1
  import { Effect, Schema } from 'effect';
2
- import { optOrPrompt, validateRequired } from '../src/lib/ui.ts';
2
+ import { Args } from '../src/lib/args.ts';
3
+ import { validateRequired } from '../src/lib/ui.ts';
3
4
  import { UI } from '../src/ui/index.ts';
4
5
  import { BadArgsError } from '../src/errors.ts';
5
6
 
@@ -44,11 +45,8 @@ export const makeOAuthMock = (mocks: {
44
45
  (auth.oauth_clients ?? []).map((c: any) => c.client_name),
45
46
  );
46
47
  const suggested = findName(providerType, used);
47
- const clientName = yield* optOrPrompt(opts.name, {
48
- simpleName: '--name',
49
- required: true,
50
- skipIf: false,
51
- prompt: {
48
+ const clientName = yield* Args.text(opts, 'name').pipe(
49
+ Args.orPrompt({
52
50
  prompt: 'Client Name:',
53
51
  defaultValue: suggested,
54
52
  placeholder: suggested,
@@ -57,8 +55,9 @@ export const makeOAuthMock = (mocks: {
57
55
  UI.modifiers.topPadding,
58
56
  UI.modifiers.dimOnComplete,
59
57
  ]),
60
- },
61
- });
58
+ }),
59
+ Args.required(),
60
+ );
62
61
  if (used.has(clientName || '')) {
63
62
  return yield* BadArgsError.make({
64
63
  message: `The unique name '${clientName}' is already in use.`,
@@ -3,7 +3,9 @@ import stripAnsi from 'strip-ansi';
3
3
  import { redirectUriPrompt } from '../src/commands/auth/client/shared.ts';
4
4
 
5
5
  test('redirectUriPrompt shows skipped when submitted empty', () => {
6
- const prompt = redirectUriPrompt({ heading: 'Custom redirect URI (optional):' });
6
+ const prompt = redirectUriPrompt({
7
+ heading: 'Custom redirect URI (optional):',
8
+ });
7
9
 
8
10
  const output = stripAnsi(prompt.modifyOutput!('\n', 'submitted'));
9
11
 
@@ -11,13 +13,12 @@ test('redirectUriPrompt shows skipped when submitted empty', () => {
11
13
  });
12
14
 
13
15
  test('redirectUriPrompt shows submitted custom redirect URI', () => {
14
- const prompt = redirectUriPrompt({ heading: 'Custom redirect URI (optional):' });
16
+ const prompt = redirectUriPrompt({
17
+ heading: 'Custom redirect URI (optional):',
18
+ });
15
19
 
16
20
  const output = stripAnsi(
17
- prompt.modifyOutput!(
18
- '\nhttps://example.com/oauth/callback',
19
- 'submitted',
20
- ),
21
+ prompt.modifyOutput!('\nhttps://example.com/oauth/callback', 'submitted'),
21
22
  );
22
23
 
23
24
  expect(output).toContain(
@@ -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;AA8pBF,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;AAGvD,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AA+C5D,eAAO,MAAM,gBAAgB,gFAO5B,CAAC;AAuoBF,eAAO,MAAM,gBAAgB;;;;wgBAwD5B,CAAC"}
@@ -1,7 +1,8 @@
1
1
  import { Effect, Match, Option, Schema } from 'effect';
2
2
  import { BadArgsError } from "../../../errors.js";
3
3
  import { GlobalOpts } from "../../../context/globalOpts.js";
4
- import { optOrPrompt, optOrPromptBoolean, runUIEffect, validateRequired, } from "../../../lib/ui.js";
4
+ import { Args } from "../../../lib/args.js";
5
+ import { runUIEffect, 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, clerkDomainFromPublishableKey, LINKEDIN_AUTHORIZATION_ENDPOINT, LINKEDIN_DISCOVERY_ENDPOINT, LINKEDIN_TOKEN_ENDPOINT, } from '@instantdb/platform';
7
8
  import { UI } from "../../../ui/index.js";
@@ -142,52 +143,35 @@ const handleGoogleClient = Effect.fn(function* (opts) {
142
143
  const { auth, provider } = yield* getOrCreateProvider('google');
143
144
  const usedClientNames = new Set((auth.oauth_clients ?? []).map((client) => client.client_name));
144
145
  const suggestedClientName = findName(`google-${appType}`, usedClientNames);
145
- const clientName = yield* optOrPrompt(opts.name, {
146
- simpleName: '--name',
147
- required: true,
148
- skipIf: false,
149
- prompt: {
150
- prompt: 'Client Name:',
151
- defaultValue: suggestedClientName,
152
- placeholder: suggestedClientName,
153
- validate: validateRequired,
154
- modifyOutput: UI.modifiers.piped([
155
- UI.modifiers.topPadding,
156
- UI.modifiers.dimOnComplete,
157
- ]),
158
- },
159
- });
146
+ const clientName = yield* Args.text(opts, 'name').pipe(Args.orPrompt({
147
+ prompt: 'Client Name:',
148
+ defaultValue: suggestedClientName,
149
+ placeholder: suggestedClientName,
150
+ validate: validateRequired,
151
+ modifyOutput: UI.modifiers.piped([
152
+ UI.modifiers.topPadding,
153
+ UI.modifiers.dimOnComplete,
154
+ ]),
155
+ }), Args.required());
160
156
  if (usedClientNames.has(clientName || '')) {
161
157
  return yield* BadArgsError.make({
162
158
  message: `The unique name '${clientName}' is already in use.`,
163
159
  });
164
160
  }
165
- const clientId = yield* optOrPrompt(opts['client-id'], {
166
- simpleName: '--client-id',
167
- required: !useSharedCredentials,
168
- skipIf: useSharedCredentials,
169
- skipMessage: '--client-id is not compatible with --dev-credentials. Drop one or the other.',
170
- prompt: clientIdPrompt({ providerUrl: googleConsoleUrl }),
171
- });
161
+ const clientId = yield* Args.text(opts, 'client-id').pipe(Args.forbidIf(useSharedCredentials, {
162
+ message: '--client-id is not compatible with --dev-credentials. Drop one or the other.',
163
+ }), Args.orPromptIf(!useSharedCredentials, clientIdPrompt({ providerUrl: googleConsoleUrl })), Args.requiredIf(!useSharedCredentials));
172
164
  const usesCustomWebCredentials = !useSharedCredentials && appType === 'web';
173
- const clientSecret = yield* optOrPrompt(opts['client-secret'], {
174
- required: usesCustomWebCredentials,
175
- skipIf: !usesCustomWebCredentials,
176
- simpleName: '--client-secret',
177
- skipMessage: useSharedCredentials
165
+ const clientSecret = yield* Args.text(opts, 'client-secret').pipe(Args.forbidIf(!usesCustomWebCredentials, {
166
+ message: useSharedCredentials
178
167
  ? '--client-secret is not compatible with --dev-credentials. Drop one or the other.'
179
168
  : undefined,
180
- prompt: clientSecretPrompt({ providerUrl: googleConsoleUrl }),
181
- });
182
- const customRedirectUri = yield* optOrPrompt(opts['custom-redirect-uri'], {
183
- required: false,
184
- prompt: optionalRedirectPrompt,
185
- simpleName: '--custom-redirect-uri',
186
- skipIf: !usesCustomWebCredentials,
187
- skipMessage: useSharedCredentials
169
+ }), Args.orPromptIf(usesCustomWebCredentials, clientSecretPrompt({ providerUrl: googleConsoleUrl })), Args.requiredIf(usesCustomWebCredentials));
170
+ const customRedirectUri = yield* Args.text(opts, 'custom-redirect-uri').pipe(Args.forbidIf(!usesCustomWebCredentials, {
171
+ message: useSharedCredentials
188
172
  ? '--custom-redirect-uri is not compatible with --dev-credentials.'
189
173
  : 'Provided custom redirect URI when not using web app type.',
190
- });
174
+ }), Args.orPromptIf(usesCustomWebCredentials, optionalRedirectPrompt));
191
175
  if (!clientName) {
192
176
  return yield* BadArgsError.make({ message: 'Client name is required.' }); // Should never reach this
193
177
  }
@@ -226,24 +210,9 @@ const handleGoogleClient = Effect.fn(function* (opts) {
226
210
  });
227
211
  const handleGithubClient = Effect.fn(function* (opts) {
228
212
  const { clientName, provider } = yield* getClientNameAndProvider('github', opts);
229
- const clientId = yield* optOrPrompt(opts['client-id'], {
230
- simpleName: '--client-id',
231
- required: true,
232
- skipIf: false,
233
- prompt: clientIdPrompt({ providerUrl: githubDeveloperUrl }),
234
- });
235
- const clientSecret = yield* optOrPrompt(opts['client-secret'], {
236
- required: true,
237
- skipIf: false,
238
- simpleName: '--client-secret',
239
- prompt: clientSecretPrompt({ providerUrl: githubDeveloperUrl }),
240
- });
241
- const customRedirectUri = yield* optOrPrompt(opts['custom-redirect-uri'], {
242
- required: false,
243
- simpleName: '--custom-redirect-uri',
244
- skipIf: false,
245
- prompt: optionalRedirectPrompt,
246
- });
213
+ const clientId = yield* Args.text(opts, 'client-id').pipe(Args.orPrompt(clientIdPrompt({ providerUrl: githubDeveloperUrl })), Args.required());
214
+ const clientSecret = yield* Args.text(opts, 'client-secret').pipe(Args.orPrompt(clientSecretPrompt({ providerUrl: githubDeveloperUrl })), Args.required());
215
+ const customRedirectUri = yield* Args.text(opts, 'custom-redirect-uri').pipe(Args.orPrompt(optionalRedirectPrompt));
247
216
  if (!clientName) {
248
217
  return yield* BadArgsError.make({ message: 'Client name is required.' });
249
218
  }
@@ -272,24 +241,9 @@ const handleGithubClient = Effect.fn(function* (opts) {
272
241
  });
273
242
  const handleLinkedInClient = Effect.fn(function* (opts) {
274
243
  const { clientName, provider } = yield* getClientNameAndProvider('linkedin', opts);
275
- const clientId = yield* optOrPrompt(opts['client-id'], {
276
- simpleName: '--client-id',
277
- required: true,
278
- skipIf: false,
279
- prompt: clientIdPrompt({ providerUrl: linkedinDeveloperUrl }),
280
- });
281
- const clientSecret = yield* optOrPrompt(opts['client-secret'], {
282
- required: true,
283
- skipIf: false,
284
- simpleName: '--client-secret',
285
- prompt: clientSecretPrompt({ providerUrl: linkedinDeveloperUrl }),
286
- });
287
- const customRedirectUri = yield* optOrPrompt(opts['custom-redirect-uri'], {
288
- required: false,
289
- simpleName: '--custom-redirect-uri',
290
- skipIf: false,
291
- prompt: optionalRedirectPrompt,
292
- });
244
+ const clientId = yield* Args.text(opts, 'client-id').pipe(Args.orPrompt(clientIdPrompt({ providerUrl: linkedinDeveloperUrl })), Args.required());
245
+ const clientSecret = yield* Args.text(opts, 'client-secret').pipe(Args.orPrompt(clientSecretPrompt({ providerUrl: linkedinDeveloperUrl })), Args.required());
246
+ const customRedirectUri = yield* Args.text(opts, 'custom-redirect-uri').pipe(Args.orPrompt(optionalRedirectPrompt));
293
247
  if (!clientName) {
294
248
  return yield* BadArgsError.make({ message: 'Client name is required.' });
295
249
  }
@@ -319,12 +273,7 @@ const handleLinkedInClient = Effect.fn(function* (opts) {
319
273
  const handleAppleClient = Effect.fn(function* (opts) {
320
274
  const { yes } = yield* GlobalOpts;
321
275
  const { clientName, provider } = yield* getClientNameAndProvider('apple', opts);
322
- const servicesId = yield* optOrPrompt(opts['services-id'], {
323
- simpleName: '--services-id',
324
- required: true,
325
- skipIf: false,
326
- prompt: appleServicesIdPrompt({}),
327
- });
276
+ const servicesId = yield* Args.text(opts, 'services-id').pipe(Args.orPrompt(appleServicesIdPrompt({})), Args.required());
328
277
  // If any web-flow flag is provided, enable web flow; otherwise ask
329
278
  // (non-interactively with --yes we default to native-only).
330
279
  const anyWebFlagProvided = Boolean(opts['team-id'] || opts['key-id'] || opts['private-key-file']);
@@ -332,49 +281,24 @@ const handleAppleClient = Effect.fn(function* (opts) {
332
281
  ? true
333
282
  : yes
334
283
  ? false
335
- : yield* optOrPromptBoolean(undefined, {
336
- simpleName: '--configure-web',
337
- required: false,
338
- skipIf: false,
339
- prompt: {
340
- promptText: 'Configure web redirect flow? ' +
341
- chalk.dim('(requires Team ID, Key ID, and a .p8 private key from Apple)'),
342
- defaultValue: false,
343
- },
344
- });
284
+ : Boolean(yield* Args.bool(opts, 'configure-web').pipe(Args.orConfirm({
285
+ promptText: 'Configure web redirect flow? ' +
286
+ chalk.dim('(requires Team ID, Key ID, and a .p8 private key from Apple)'),
287
+ defaultValue: false,
288
+ })));
345
289
  const skipWeb = !configureWeb;
346
290
  const webSkipMessage = 'requires configuring the web redirect flow (also provide --team-id, --key-id, and --private-key-file).';
347
- const teamId = yield* optOrPrompt(opts['team-id'], {
348
- simpleName: '--team-id',
349
- required: true,
350
- skipIf: skipWeb,
351
- skipMessage: `--team-id ${webSkipMessage}`,
352
- prompt: appleTeamIdPrompt({}),
353
- });
354
- const keyId = yield* optOrPrompt(opts['key-id'], {
355
- simpleName: '--key-id',
356
- required: true,
357
- skipIf: skipWeb,
358
- skipMessage: `--key-id ${webSkipMessage}`,
359
- prompt: appleKeyIdPrompt({}),
360
- });
361
- const privateKeyPath = yield* optOrPrompt(opts['private-key-file'], {
362
- simpleName: '--private-key-file',
363
- required: true,
364
- skipIf: skipWeb,
365
- skipMessage: `--private-key-file ${webSkipMessage}`,
366
- prompt: applePrivateKeyFilePrompt({}),
367
- });
291
+ const teamId = yield* Args.text(opts, 'team-id').pipe(Args.forbidIf(skipWeb, { message: `--team-id ${webSkipMessage}` }), Args.orPromptIf(!skipWeb, appleTeamIdPrompt({})), Args.requiredIf(!skipWeb));
292
+ const keyId = yield* Args.text(opts, 'key-id').pipe(Args.forbidIf(skipWeb, { message: `--key-id ${webSkipMessage}` }), Args.orPromptIf(!skipWeb, appleKeyIdPrompt({})), Args.requiredIf(!skipWeb));
293
+ const privateKeyPath = yield* Args.text(opts, 'private-key-file').pipe(Args.forbidIf(skipWeb, {
294
+ message: `--private-key-file ${webSkipMessage}`,
295
+ }), Args.orPromptIf(!skipWeb, applePrivateKeyFilePrompt({})), Args.requiredIf(!skipWeb));
368
296
  const privateKey = privateKeyPath
369
297
  ? yield* readPrivateKeyFile(privateKeyPath)
370
298
  : undefined;
371
- const customRedirectUri = yield* optOrPrompt(opts['custom-redirect-uri'], {
372
- required: false,
373
- simpleName: '--custom-redirect-uri',
374
- skipIf: skipWeb,
375
- skipMessage: `--custom-redirect-uri ${webSkipMessage}`,
376
- prompt: optionalRedirectPrompt,
377
- });
299
+ const customRedirectUri = yield* Args.text(opts, 'custom-redirect-uri').pipe(Args.forbidIf(skipWeb, {
300
+ message: `--custom-redirect-uri ${webSkipMessage}`,
301
+ }), Args.orPromptIf(!skipWeb, optionalRedirectPrompt));
378
302
  if (!clientName) {
379
303
  return yield* BadArgsError.make({ message: 'Client name is required.' });
380
304
  }
@@ -418,12 +342,7 @@ const handleAppleClient = Effect.fn(function* (opts) {
418
342
  });
419
343
  const handleClerkClient = Effect.fn(function* (opts) {
420
344
  const { clientName, provider } = yield* getClientNameAndProvider('clerk', opts);
421
- const publishableKey = yield* optOrPrompt(opts['publishable-key'], {
422
- simpleName: '--publishable-key',
423
- required: true,
424
- skipIf: false,
425
- prompt: clerkPublishableKeyPrompt({}),
426
- });
345
+ const publishableKey = yield* Args.text(opts, 'publishable-key').pipe(Args.orPrompt(clerkPublishableKeyPrompt({})), Args.required());
427
346
  if (!clientName) {
428
347
  return yield* BadArgsError.make({ message: 'Client name is required.' });
429
348
  }
@@ -459,12 +378,7 @@ const handleClerkClient = Effect.fn(function* (opts) {
459
378
  });
460
379
  const handleFirebaseClient = Effect.fn(function* (opts) {
461
380
  const { clientName, provider } = yield* getClientNameAndProvider('firebase', opts);
462
- const projectId = yield* optOrPrompt(opts['project-id'], {
463
- simpleName: '--project-id',
464
- required: true,
465
- skipIf: false,
466
- prompt: firebaseProjectIdPrompt({}),
467
- });
381
+ const projectId = yield* Args.text(opts, 'project-id').pipe(Args.orPrompt(firebaseProjectIdPrompt({})), Args.required());
468
382
  // typeguard
469
383
  if (!clientName || !projectId) {
470
384
  return yield* BadArgsError.make({