instant-cli 1.0.23 → 1.0.24-branch-codex-cli-args-combinators.25405829034.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 (43) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/__tests__/args.test.ts +358 -0
  3. package/__tests__/authClientAddApple.test.ts +9 -10
  4. package/__tests__/authClientAddClerk.test.ts +6 -5
  5. package/__tests__/authClientAddGithub.test.ts +6 -5
  6. package/__tests__/authClientAddGoogle.test.ts +5 -4
  7. package/__tests__/authClientAddLinkedin.test.ts +6 -5
  8. package/__tests__/oauthMock.ts +8 -9
  9. package/dist/commands/auth/client/add.d.ts.map +1 -1
  10. package/dist/commands/auth/client/add.js +53 -167
  11. package/dist/commands/auth/client/add.js.map +1 -1
  12. package/dist/commands/auth/client/shared.d.ts +0 -4
  13. package/dist/commands/auth/client/shared.d.ts.map +1 -1
  14. package/dist/commands/auth/client/shared.js +0 -4
  15. package/dist/commands/auth/client/shared.js.map +1 -1
  16. package/dist/commands/auth/client/update.d.ts +1 -1
  17. package/dist/commands/auth/client/update.d.ts.map +1 -1
  18. package/dist/commands/auth/client/update.js +25 -112
  19. package/dist/commands/auth/client/update.js.map +1 -1
  20. package/dist/commands/auth/origin/add.d.ts.map +1 -1
  21. package/dist/commands/auth/origin/add.js +37 -59
  22. package/dist/commands/auth/origin/add.js.map +1 -1
  23. package/dist/lib/args.d.ts +212 -0
  24. package/dist/lib/args.d.ts.map +1 -0
  25. package/dist/lib/args.js +313 -0
  26. package/dist/lib/args.js.map +1 -0
  27. package/dist/lib/oauth.d.ts +2 -2
  28. package/dist/lib/oauth.d.ts.map +1 -1
  29. package/dist/lib/oauth.js +13 -17
  30. package/dist/lib/oauth.js.map +1 -1
  31. package/dist/lib/ui.d.ts +0 -18
  32. package/dist/lib/ui.d.ts.map +1 -1
  33. package/dist/lib/ui.js +0 -76
  34. package/dist/lib/ui.js.map +1 -1
  35. package/package.json +4 -4
  36. package/src/commands/auth/client/add.ts +114 -180
  37. package/src/commands/auth/client/shared.ts +0 -12
  38. package/src/commands/auth/client/update.ts +85 -139
  39. package/src/commands/auth/origin/add.ts +25 -36
  40. package/src/lib/args.ts +453 -0
  41. package/src/lib/oauth.ts +8 -14
  42. package/src/lib/ui.ts +0 -127
  43. package/__tests__/optOrPrompt.test.ts +0 -229
@@ -0,0 +1,453 @@
1
+ /**
2
+ * Args lets commands read CLI flags as a small pipeline:
3
+ *
4
+ * 1. Parse a value from opts
5
+ * 2. Gate it: when is this flag available?
6
+ * 3. Prompt for a missing value, when interactive
7
+ * 4. Validate and finish as required or optional
8
+ *
9
+ * This keeps the "can this flag be used here?" logic next to the value it
10
+ * controls. For example, a Google client secret is only meaningful when using
11
+ * custom web credentials:
12
+ *
13
+ * const clientSecret = yield* Args.text(opts, 'client-secret').pipe(
14
+ * Args.availableWhen(usesCustomWebCredentials),
15
+ * Args.prompt(clientSecretPrompt({ providerUrl })),
16
+ * Args.required(),
17
+ * );
18
+ *
19
+ * The key argument to text/bool/has is the exact opts lookup key. If the
20
+ * user-facing flag name differs, pass simpleName for errors:
21
+ *
22
+ * Args.text(opts, 'customRedirectUri', { simpleName: '--custom-redirect-uri' })
23
+ */
24
+ import { Effect } from 'effect';
25
+ import { pipeArguments, type Pipeable } from 'effect/Pipeable';
26
+ import { BadArgsError } from '../errors.ts';
27
+ import { GlobalOpts } from '../context/globalOpts.ts';
28
+ import { UI } from '../ui/index.ts';
29
+ import { runUIEffect } from './ui.ts';
30
+
31
+ type ActiveArg<A, E, R> = {
32
+ readonly _tag: 'Active';
33
+ readonly provided: boolean;
34
+ readonly value: Effect.Effect<A | undefined, E, R>;
35
+ };
36
+
37
+ type InactiveArg = {
38
+ readonly _tag: 'Inactive';
39
+ };
40
+
41
+ type ArgState<A, E, R, CanBeInactive extends boolean> =
42
+ | ActiveArg<A, E, R>
43
+ | (CanBeInactive extends true ? InactiveArg : never);
44
+
45
+ /**
46
+ * A parsed CLI arg that can be composed with Args helpers.
47
+ *
48
+ * Commands usually create one with text() or bool(), then finish with
49
+ * required() or optional().
50
+ *
51
+ * @example
52
+ * const name = yield* Args.text(opts, 'name').pipe(
53
+ * Args.prompt({ prompt: 'Client Name:' }),
54
+ * Args.required(),
55
+ * );
56
+ */
57
+ export interface Arg<
58
+ A,
59
+ E = never,
60
+ R = never,
61
+ CanBeInactive extends boolean = false,
62
+ > extends Pipeable {
63
+ readonly flag: string;
64
+ readonly state: Effect.Effect<ArgState<A, E, R, CanBeInactive>, E, R>;
65
+ }
66
+
67
+ export type ArgOptions = {
68
+ /**
69
+ * Use when the opts key is not the user-facing flag name.
70
+ *
71
+ * @example
72
+ * Args.text(opts, 'customRedirectUri', {
73
+ * simpleName: '--custom-redirect-uri',
74
+ * })
75
+ */
76
+ simpleName?: string;
77
+ };
78
+
79
+ const makeArg = <A, E, R, CanBeInactive extends boolean>(
80
+ flag: string,
81
+ state: Effect.Effect<ArgState<A, E, R, CanBeInactive>, E, R>,
82
+ ): Arg<A, E, R, CanBeInactive> => {
83
+ const arg: Arg<A, E, R, CanBeInactive> = {
84
+ flag,
85
+ state,
86
+ pipe() {
87
+ return pipeArguments(this, arguments);
88
+ },
89
+ };
90
+ return arg;
91
+ };
92
+
93
+ const active = <A, E, R>(
94
+ value: Effect.Effect<A | undefined, E, R>,
95
+ provided: boolean,
96
+ ): ActiveArg<A, E, R> => ({ _tag: 'Active', provided, value });
97
+
98
+ const inactive: InactiveArg = { _tag: 'Inactive' };
99
+
100
+ const missingMessage = (flag: string) => `Missing required value for ${flag}`;
101
+
102
+ type MissingOptions = {
103
+ message?: string;
104
+ };
105
+
106
+ type UnavailableOptions = {
107
+ message?: string;
108
+ };
109
+
110
+ /**
111
+ * @example
112
+ * if (Args.has(opts, 'custom-redirect-uri')) {
113
+ * // The user explicitly passed --custom-redirect-uri.
114
+ * }
115
+ */
116
+ function has(opts: Record<string, unknown>, key: string) {
117
+ return Object.prototype.hasOwnProperty.call(opts, key);
118
+ }
119
+
120
+ /**
121
+ * @example
122
+ * const configureWeb = Args.hasAny(opts, [
123
+ * 'team-id',
124
+ * 'key-id',
125
+ * 'private-key-file',
126
+ * ]);
127
+ */
128
+ function hasAny(opts: Record<string, unknown>, keys: string[]) {
129
+ return keys.some((key) => has(opts, key));
130
+ }
131
+
132
+ /**
133
+ * Returns true only for boolean true or the string "true".
134
+ *
135
+ * @example
136
+ * const useDevCredentials = Args.isTrue(opts, 'dev-credentials');
137
+ */
138
+ function isTrue(opts: Record<string, unknown>, key: string) {
139
+ const value = opts[key];
140
+ return value === true || value === 'true';
141
+ }
142
+
143
+ /**
144
+ * Starts a string arg pipeline. Strings are trimmed, numbers are stringified,
145
+ * empty values become missing values, and other types produce BadArgsError.
146
+ *
147
+ * @example
148
+ * const clientId = yield* Args.text(opts, 'client-id').pipe(
149
+ * Args.prompt(clientIdPrompt({ providerUrl })),
150
+ * Args.required(),
151
+ * );
152
+ */
153
+ function text(
154
+ opts: Record<string, unknown>,
155
+ key: string,
156
+ options?: ArgOptions,
157
+ ): Arg<string, BadArgsError> {
158
+ const flag = options?.simpleName ?? `--${key}`;
159
+ const provided = Object.prototype.hasOwnProperty.call(opts, key);
160
+
161
+ return makeArg(
162
+ flag,
163
+ Effect.succeed(active(readTextValue(opts[key], flag), provided)),
164
+ );
165
+ }
166
+
167
+ function readTextValue(value: unknown, flag: string) {
168
+ return Effect.gen(function* readTextValue() {
169
+ if (value === undefined || value === null) return undefined;
170
+
171
+ if (typeof value === 'string' || typeof value === 'number') {
172
+ const trimmed = String(value).trim();
173
+ return trimmed.length > 0 ? trimmed : undefined;
174
+ }
175
+
176
+ return yield* BadArgsError.make({
177
+ message: `Invalid value for ${flag}`,
178
+ });
179
+ });
180
+ }
181
+
182
+ /**
183
+ * Starts a boolean arg pipeline. Accepts booleans and the strings "true" or
184
+ * "false"; other supplied values produce BadArgsError.
185
+ *
186
+ * @example
187
+ * const configureWeb = yield* Args.bool(opts, 'configure-web').pipe(
188
+ * Args.confirm({ promptText: 'Configure web redirect flow?' }),
189
+ * Args.required(),
190
+ * );
191
+ */
192
+ function bool(
193
+ opts: Record<string, unknown>,
194
+ key: string,
195
+ options?: ArgOptions,
196
+ ): Arg<boolean, BadArgsError> {
197
+ const flag = options?.simpleName ?? `--${key}`;
198
+ const provided = Object.prototype.hasOwnProperty.call(opts, key);
199
+
200
+ return makeArg(
201
+ flag,
202
+ Effect.succeed(active(readBooleanValue(opts[key], flag), provided)),
203
+ );
204
+ }
205
+
206
+ function readBooleanValue(value: unknown, flag: string) {
207
+ return Effect.gen(function* readBooleanValue() {
208
+ if (value === undefined || value === null) return undefined;
209
+ if (value === true || value === 'true') return true;
210
+ if (value === false || value === 'false') return false;
211
+
212
+ return yield* BadArgsError.make({
213
+ message: `Invalid value for ${flag}`,
214
+ });
215
+ });
216
+ }
217
+
218
+ function uiErrorToBadArgs(e: { message: string }) {
219
+ return BadArgsError.make({ message: `UI error: ${e.message}` });
220
+ }
221
+
222
+ function mapActive<A, B, E, R, E2, R2, CanBeInactive extends boolean>(
223
+ wrapper: Arg<A, E, R, CanBeInactive>,
224
+ mapValue: (
225
+ value: Effect.Effect<A | undefined, E, R>,
226
+ ) => Effect.Effect<B | undefined, E | E2, R | R2>,
227
+ ): Arg<B, E | E2, R | R2, CanBeInactive> {
228
+ return makeArg(
229
+ wrapper.flag,
230
+ Effect.gen(function* mapArgState() {
231
+ const state = yield* wrapper.state;
232
+ if (state._tag === 'Inactive') return inactive;
233
+ return active(mapValue(state.value), state.provided);
234
+ }) as Effect.Effect<
235
+ ArgState<B, E | E2, R | R2, CanBeInactive>,
236
+ E | E2,
237
+ R | R2
238
+ >,
239
+ );
240
+ }
241
+
242
+ /**
243
+ * Gates an arg behind a mode or earlier choice.
244
+ *
245
+ * If the condition is false and the user supplied the flag, this returns an
246
+ * error.
247
+ *
248
+ * @example
249
+ * const clientSecret = yield* Args.text(opts, 'client-secret').pipe(
250
+ * Args.availableWhen(appType === 'web'),
251
+ * Args.prompt(clientSecretPrompt({ providerUrl })),
252
+ * Args.required(),
253
+ * );
254
+ */
255
+ function availableWhen(condition: boolean, options?: UnavailableOptions) {
256
+ return function availableWhenArg<A, E, R, CanBeInactive extends boolean>(
257
+ arg: Arg<A, E, R, CanBeInactive>,
258
+ ) {
259
+ return makeArg<A, E | BadArgsError, R, true>(
260
+ arg.flag,
261
+ Effect.gen(function* gateArgAvailability() {
262
+ const state = yield* arg.state;
263
+ if (condition || state._tag === 'Inactive') return state;
264
+ if (state.provided) {
265
+ return yield* BadArgsError.make({
266
+ message:
267
+ options?.message ??
268
+ `${arg.flag} is not compatible with other options`,
269
+ });
270
+ }
271
+ return inactive;
272
+ }),
273
+ );
274
+ };
275
+ }
276
+
277
+ /**
278
+ * Prompts for a missing text value.
279
+ *
280
+ * With --yes, the prompt is skipped. If inputProps.defaultValue is set, that
281
+ * value is used. Otherwise the value stays missing.
282
+ *
283
+ * @example
284
+ * const clientName = yield* Args.text(opts, 'name').pipe(
285
+ * Args.prompt({
286
+ * prompt: 'Client Name:',
287
+ * defaultValue: suggestedClientName,
288
+ * }),
289
+ * Args.required(),
290
+ * );
291
+ */
292
+ function prompt(inputProps: UI.TextInputProps) {
293
+ return function promptArg<E, R, CanBeInactive extends boolean>(
294
+ arg: Arg<string, E, R, CanBeInactive>,
295
+ ): Arg<string, E | BadArgsError, R | GlobalOpts, CanBeInactive> {
296
+ return mapActive(arg, (valueEffect) =>
297
+ Effect.gen(function* promptWhenMissing() {
298
+ const value = yield* valueEffect;
299
+ if (value !== undefined) return value;
300
+
301
+ const { yes } = yield* GlobalOpts;
302
+ const result = yes
303
+ ? inputProps.defaultValue
304
+ : yield* runUIEffect(new UI.TextInput(inputProps)).pipe(
305
+ Effect.catchTag('UIError', uiErrorToBadArgs),
306
+ );
307
+ if (result === undefined) return undefined;
308
+
309
+ const trimmed = result.trim();
310
+ return trimmed.length > 0 ? trimmed : undefined;
311
+ }),
312
+ );
313
+ };
314
+ }
315
+
316
+ /**
317
+ * Prompts for a missing boolean value.
318
+ *
319
+ * With --yes, the prompt is skipped and confirmationProps.defaultValue is used.
320
+ *
321
+ * @example
322
+ * const configureWeb = yield* Args.bool(opts, 'configure-web').pipe(
323
+ * Args.confirm({
324
+ * promptText: 'Configure web redirect flow?',
325
+ * defaultValue: false,
326
+ * }),
327
+ * Args.required(),
328
+ * );
329
+ */
330
+ function confirm(confirmationProps: UI.ConfirmationProps) {
331
+ return function confirmArg<E, R, CanBeInactive extends boolean>(
332
+ arg: Arg<boolean, E, R, CanBeInactive>,
333
+ ): Arg<boolean, E | BadArgsError, R | GlobalOpts, CanBeInactive> {
334
+ return mapActive(arg, (valueEffect) =>
335
+ Effect.gen(function* confirmWhenMissing() {
336
+ const value = yield* valueEffect;
337
+ if (value !== undefined) return value;
338
+
339
+ const { yes } = yield* GlobalOpts;
340
+ if (yes) return confirmationProps.defaultValue;
341
+
342
+ return yield* runUIEffect(new UI.Confirmation(confirmationProps)).pipe(
343
+ Effect.catchTag('UIError', uiErrorToBadArgs),
344
+ );
345
+ }),
346
+ );
347
+ };
348
+ }
349
+
350
+ /**
351
+ * Validates a present value. Return an error message to fail, or undefined to
352
+ * accept the value. Missing and inactive args pass through unchanged.
353
+ *
354
+ * @example
355
+ * const projectId = yield* Args.text(opts, 'project-id').pipe(
356
+ * Args.prompt(firebaseProjectIdPrompt({})),
357
+ * Args.validate(validateFirebaseProjectId),
358
+ * Args.required(),
359
+ * );
360
+ */
361
+ function validate<A>(validator: (value: A) => string | undefined) {
362
+ return function validateArg<E, R, CanBeInactive extends boolean>(
363
+ arg: Arg<A, E, R, CanBeInactive>,
364
+ ): Arg<A, E | BadArgsError, R, CanBeInactive> {
365
+ return mapActive(arg, (valueEffect) =>
366
+ Effect.gen(function* validateValue() {
367
+ const value = yield* valueEffect;
368
+ if (value === undefined) return undefined;
369
+
370
+ const message = validator(value);
371
+ if (!message) return value;
372
+
373
+ return yield* BadArgsError.make({ message });
374
+ }),
375
+ );
376
+ };
377
+ }
378
+
379
+ /**
380
+ * Finishes a pipeline with a required value.
381
+ *
382
+ * Missing active args produce BadArgsError. Inactive args return undefined, so
383
+ * callers can skip downstream work for unavailable flags.
384
+ *
385
+ * @example
386
+ * const clientId = yield* Args.text(opts, 'client-id').pipe(
387
+ * Args.prompt(clientIdPrompt({ providerUrl })),
388
+ * Args.required(),
389
+ * );
390
+ */
391
+ function required(options?: MissingOptions) {
392
+ return function requiredArg<A, E, R, CanBeInactive extends boolean>(
393
+ arg: Arg<A, E, R, CanBeInactive>,
394
+ ): Effect.Effect<
395
+ CanBeInactive extends true ? A | undefined : A,
396
+ E | BadArgsError,
397
+ R
398
+ > {
399
+ return Effect.gen(function* requireValue() {
400
+ const state = yield* arg.state;
401
+ if (state._tag === 'Inactive') return undefined;
402
+
403
+ const value = yield* state.value;
404
+ if (value !== undefined) return value;
405
+
406
+ return yield* BadArgsError.make({
407
+ message: options?.message ?? missingMessage(arg.flag),
408
+ });
409
+ }) as Effect.Effect<
410
+ CanBeInactive extends true ? A | undefined : A,
411
+ E | BadArgsError,
412
+ R
413
+ >;
414
+ };
415
+ }
416
+
417
+ /**
418
+ * Finishes a pipeline with an optional value.
419
+ *
420
+ * Missing and inactive args return undefined.
421
+ *
422
+ * @example
423
+ * const customRedirectUri = yield* Args.text(opts, 'custom-redirect-uri').pipe(
424
+ * Args.prompt(optionalRedirectPrompt),
425
+ * Args.optional(),
426
+ * );
427
+ */
428
+ function optional() {
429
+ return function optionalArg<A, E, R, CanBeInactive extends boolean>(
430
+ arg: Arg<A, E, R, CanBeInactive>,
431
+ ) {
432
+ return Effect.gen(function* optionalValue() {
433
+ const state = yield* arg.state;
434
+ if (state._tag === 'Inactive') return undefined;
435
+
436
+ return yield* state.value;
437
+ });
438
+ };
439
+ }
440
+
441
+ export const Args = {
442
+ text,
443
+ bool,
444
+ has,
445
+ hasAny,
446
+ isTrue,
447
+ availableWhen,
448
+ prompt,
449
+ confirm,
450
+ validate,
451
+ required,
452
+ optional,
453
+ };
package/src/lib/oauth.ts CHANGED
@@ -3,16 +3,12 @@ import { Effect, Schema } from 'effect';
3
3
  import { CurrentApp } from '../context/currentApp.ts';
4
4
  import { InstantHttpAuthed, withCommand } from './http.ts';
5
5
  import chalk from 'chalk';
6
- import {
7
- optOrPrompt,
8
- runUIEffect,
9
- stripFirstBlankLine,
10
- validateRequired,
11
- } from './ui.ts';
6
+ import { runUIEffect, stripFirstBlankLine, validateRequired } from './ui.ts';
12
7
  import { UI } from '../ui/index.ts';
13
8
  import { BadArgsError } from '../errors.ts';
14
9
  import { link } from '../logging.ts';
15
10
  import type { ClientTypeSchema } from '../commands/auth/client/add.ts';
11
+ import { Args } from './args.ts';
16
12
 
17
13
  export const AuthorizedOriginService = Schema.Literal(
18
14
  'generic',
@@ -263,11 +259,8 @@ export const getClientNameAndProvider = Effect.fn(function* (
263
259
  );
264
260
  const suggestedClientName = findName(providerType, usedClientNames);
265
261
 
266
- const clientName = yield* optOrPrompt(opts.name, {
267
- simpleName: '--name',
268
- required: true,
269
- skipIf: false,
270
- prompt: {
262
+ const clientName = yield* Args.text(opts, 'name').pipe(
263
+ Args.prompt({
271
264
  prompt: 'Client Name:',
272
265
  defaultValue: suggestedClientName,
273
266
  placeholder: suggestedClientName,
@@ -276,10 +269,11 @@ export const getClientNameAndProvider = Effect.fn(function* (
276
269
  UI.modifiers.topPadding,
277
270
  UI.modifiers.dimOnComplete,
278
271
  ]),
279
- },
280
- });
272
+ }),
273
+ Args.required(),
274
+ );
281
275
 
282
- if (usedClientNames.has(clientName || '')) {
276
+ if (usedClientNames.has(clientName)) {
283
277
  return yield* BadArgsError.make({
284
278
  message: `The unique name '${clientName}' is already in use.`,
285
279
  });
package/src/lib/ui.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Data, Effect } from 'effect';
2
- import { BadArgsError } from '../errors.ts';
3
2
  import { GlobalOpts } from '../context/globalOpts.ts';
4
3
  import { Prompt, renderUnwrap } from '../ui/lib.ts';
5
4
  import { UI } from '../ui/index.ts';
@@ -37,25 +36,6 @@ export const runUIEffect = <P>(prompt: Prompt<P>) =>
37
36
  }),
38
37
  });
39
38
 
40
- export const invalidFlagError = (flag: string, message: string) =>
41
- BadArgsError.make({ message: `Invalid ${flag}: ${message}` });
42
-
43
- export const getOptionalStringFlag = Effect.fn(function* (
44
- value: unknown,
45
- flag: string,
46
- ) {
47
- if (value === undefined || value === null || value === false) {
48
- return undefined;
49
- }
50
-
51
- if (typeof value !== 'string') {
52
- return yield* invalidFlagError(flag, 'expected a string value');
53
- }
54
-
55
- const trimmed = value.trim();
56
- return trimmed.length > 0 ? trimmed : undefined;
57
- });
58
-
59
39
  export const stripFirstBlankLine = (str: string): string => {
60
40
  const lines = str.split('\n');
61
41
  const firstBlankIndex = lines.findIndex((line) => line.trim() === '');
@@ -64,112 +44,5 @@ export const stripFirstBlankLine = (str: string): string => {
64
44
  return lines.join('\n');
65
45
  };
66
46
 
67
- const coerceValue = (value: unknown, simpleName: string) =>
68
- Effect.gen(function* () {
69
- if (value === undefined || value === null) return undefined;
70
- if (typeof value === 'string' || typeof value === 'number') {
71
- return String(value).trim();
72
- }
73
- return yield* BadArgsError.make({
74
- message: `Invalid value for ${simpleName}`,
75
- });
76
- });
77
-
78
- const resolveOrPrompt = (prompt: UI.TextInputProps, yes: boolean) =>
79
- Effect.gen(function* () {
80
- if (yes) return undefined;
81
- const result = yield* runUIEffect(new UI.TextInput(prompt));
82
- return result.trim() || undefined;
83
- });
84
-
85
- const requireOrReturn = (
86
- resolved: string | undefined,
87
- params: {
88
- required: boolean;
89
- simpleName: string;
90
- customMissingMessage?: string;
91
- },
92
- ) =>
93
- Effect.gen(function* () {
94
- if (resolved) return resolved;
95
- if (params.required) {
96
- return yield* BadArgsError.make({
97
- message:
98
- params.customMissingMessage ??
99
- `Missing required value for ${params.simpleName}`,
100
- });
101
- }
102
- return undefined;
103
- });
104
-
105
- export const optOrPrompt = (
106
- value: unknown,
107
- params: {
108
- required: boolean;
109
- skipMessage?: string;
110
- customMissingMessage?: string;
111
- simpleName: string;
112
- skipIf: boolean;
113
- prompt: UI.TextInputProps;
114
- },
115
- ) =>
116
- Effect.gen(function* () {
117
- if (params.skipIf) {
118
- if (value) {
119
- return yield* BadArgsError.make({
120
- message:
121
- params.skipMessage ??
122
- `${params.simpleName} is not compatible with other options`,
123
- });
124
- }
125
- return undefined;
126
- }
127
-
128
- const { yes } = yield* GlobalOpts;
129
- const coerced = yield* coerceValue(value, params.simpleName);
130
- const resolved = coerced ?? (yield* resolveOrPrompt(params.prompt, yes));
131
- return yield* requireOrReturn(resolved, params);
132
- });
133
-
134
47
  export const validateRequired = (input: string) =>
135
48
  input.trim().length > 0 ? undefined : 'Value is required';
136
-
137
- export const optOrPromptBoolean = (
138
- value: unknown,
139
- params: {
140
- prompt: ConstructorParameters<typeof UI.Confirmation>[0];
141
- required: boolean;
142
- skipMessage?: string;
143
- simpleName: string;
144
- skipIf: boolean;
145
- },
146
- ) =>
147
- Effect.gen(function* () {
148
- if (params.skipIf && value !== undefined) {
149
- return yield* BadArgsError.make({
150
- message:
151
- params.skipMessage ??
152
- `${params.simpleName} is not compatible with other options`,
153
- });
154
- }
155
- if (params.skipIf) {
156
- return false;
157
- }
158
- const { yes } = yield* GlobalOpts;
159
-
160
- if (yes) {
161
- return Boolean(value);
162
- }
163
-
164
- if (value === true) {
165
- return value;
166
- }
167
-
168
- const response = yield* runUIEffect(
169
- new UI.Confirmation({
170
- ...params.prompt,
171
- }),
172
- );
173
-
174
- return response;
175
- });