instant-cli 1.0.23-branch-codex-cli-args-combinators.25394030897.1 → 1.0.23-branch-codex-cli-args-combinators.25395572961.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.
@@ -95,8 +95,9 @@ const addOriginHandler = Effect.fn(function* (
95
95
  const handleGenericOrigin = Effect.fn(function* (
96
96
  opts: Record<string, unknown>,
97
97
  ) {
98
- const url = yield* Args.text(opts, 'url').pipe(
99
- Args.orPrompt({
98
+ const args = Args.from(opts);
99
+ const url = yield* args.text('url').pipe(
100
+ Args.prompt({
100
101
  prompt: 'Website URL:',
101
102
  placeholder: 'example.com',
102
103
  modifyOutput: UI.modifiers.piped([
@@ -120,8 +121,9 @@ const handleGenericOrigin = Effect.fn(function* (
120
121
  });
121
122
 
122
123
  const handleVercelOrigin = Effect.fn(function* (opts: Record<string, unknown>) {
123
- const project = yield* Args.text(opts, 'project').pipe(
124
- Args.orPrompt({
124
+ const args = Args.from(opts);
125
+ const project = yield* args.text('project').pipe(
126
+ Args.prompt({
125
127
  prompt: 'Vercel project name:',
126
128
  placeholder: 'vercel-project-name',
127
129
  modifyOutput: UI.modifiers.piped([
@@ -143,8 +145,9 @@ const handleVercelOrigin = Effect.fn(function* (opts: Record<string, unknown>) {
143
145
  const handleNetlifyOrigin = Effect.fn(function* (
144
146
  opts: Record<string, unknown>,
145
147
  ) {
146
- const site = yield* Args.text(opts, 'site').pipe(
147
- Args.orPrompt({
148
+ const args = Args.from(opts);
149
+ const site = yield* args.text('site').pipe(
150
+ Args.prompt({
148
151
  prompt: 'Netlify site name:',
149
152
  placeholder: 'netlify-site-name',
150
153
  modifyOutput: UI.modifiers.piped([
@@ -166,8 +169,9 @@ const handleNetlifyOrigin = Effect.fn(function* (
166
169
  const handleCustomSchemeOrigin = Effect.fn(function* (
167
170
  opts: Record<string, unknown>,
168
171
  ) {
169
- const scheme = yield* Args.text(opts, 'scheme').pipe(
170
- Args.orPrompt({
172
+ const args = Args.from(opts);
173
+ const scheme = yield* args.text('scheme').pipe(
174
+ Args.prompt({
171
175
  prompt: 'App scheme:',
172
176
  placeholder: 'app-scheme://',
173
177
  modifyOutput: UI.modifiers.piped([
package/src/lib/args.ts CHANGED
@@ -4,30 +4,74 @@ import { GlobalOpts } from '../context/globalOpts.ts';
4
4
  import { UI } from '../ui/index.ts';
5
5
  import { runUIEffect } from './ui.ts';
6
6
 
7
- const ArgFlag: unique symbol = Symbol('ArgFlag');
8
-
9
- /**
10
- * An ArgEffect is just an Effect with the source CLI flag attached to it.
11
- *
12
- * Effect values do not have a natural place to store "this came from
13
- * --client-id", but the prompt/validation combinators need that flag name for
14
- * errors. The symbol keeps the metadata private to this module and prevents
15
- * these helpers from accepting arbitrary Effects.
16
- */
17
- export type ArgEffect<A, E = never, R = never> = Effect.Effect<A, E, R> & {
18
- readonly [ArgFlag]: string;
7
+ type ActiveArg<A, E, R> = {
8
+ readonly _tag: 'Active';
9
+ readonly provided: boolean;
10
+ readonly value: Effect.Effect<A | undefined, E, R>;
19
11
  };
20
12
 
21
- const flagName = (name: string) => (name.startsWith('--') ? name : `--${name}`);
13
+ type InactiveArg = {
14
+ readonly _tag: 'Inactive';
15
+ };
16
+
17
+ type ArgState<A, E, R> = ActiveArg<A, E, R> | InactiveArg;
18
+
19
+ export interface Arg<A, E = never, R = never> {
20
+ readonly flag: string;
21
+ readonly state: Effect.Effect<ArgState<A, E, R>, E, R>;
22
+ pipe(): Arg<A, E, R>;
23
+ pipe<B>(ab: (self: Arg<A, E, R>) => B): B;
24
+ pipe<B, C>(ab: (self: Arg<A, E, R>) => B, bc: (b: B) => C): C;
25
+ pipe<B, C, D>(
26
+ ab: (self: Arg<A, E, R>) => B,
27
+ bc: (b: B) => C,
28
+ cd: (c: C) => D,
29
+ ): D;
30
+ pipe<B, C, D, F>(
31
+ ab: (self: Arg<A, E, R>) => B,
32
+ bc: (b: B) => C,
33
+ cd: (c: C) => D,
34
+ df: (d: D) => F,
35
+ ): F;
36
+ pipe<B, C, D, F, G>(
37
+ ab: (self: Arg<A, E, R>) => B,
38
+ bc: (b: B) => C,
39
+ cd: (c: C) => D,
40
+ df: (d: D) => F,
41
+ fg: (f: F) => G,
42
+ ): G;
43
+ }
44
+
45
+ const keyName = (name: string) => name.replace(/^--/, '');
46
+
47
+ const flagName = (name: string) => {
48
+ const key = keyName(name);
49
+ return key.startsWith('-') ? key : `--${key}`;
50
+ };
22
51
 
23
52
  const makeArg = <A, E, R>(
24
53
  flag: string,
25
- effect: Effect.Effect<A, E, R>,
26
- ): ArgEffect<A, E, R> => {
27
- return Object.assign(effect, { [ArgFlag]: flag }) as ArgEffect<A, E, R>;
54
+ state: Effect.Effect<ArgState<A, E, R>, E, R>,
55
+ ): Arg<A, E, R> => {
56
+ const arg: Arg<A, E, R> = {
57
+ flag,
58
+ state,
59
+ pipe: function pipe(
60
+ this: Arg<A, E, R>,
61
+ ...fns: Array<(value: unknown) => unknown>
62
+ ) {
63
+ return fns.reduce<unknown>((value, fn) => fn(value), this);
64
+ } as Arg<A, E, R>['pipe'],
65
+ };
66
+ return arg;
28
67
  };
29
68
 
30
- const getFlag = (arg: ArgEffect<unknown, unknown, unknown>) => arg[ArgFlag];
69
+ const active = <A, E, R>(
70
+ value: Effect.Effect<A | undefined, E, R>,
71
+ provided: boolean,
72
+ ): ActiveArg<A, E, R> => ({ _tag: 'Active', provided, value });
73
+
74
+ const inactive: InactiveArg = { _tag: 'Inactive' };
31
75
 
32
76
  const missingMessage = (flag: string) => `Missing required value for ${flag}`;
33
77
 
@@ -35,10 +79,47 @@ type MissingOptions = {
35
79
  message?: string;
36
80
  };
37
81
 
38
- function text(opts: Record<string, unknown>, name: string) {
39
- const flag = flagName(name);
82
+ type UnavailableOptions = {
83
+ message?: string;
84
+ otherwise?: string;
85
+ };
86
+
87
+ type ArgsReader = {
88
+ text: (name: string) => Arg<string, BadArgsError>;
89
+ bool: (name: string) => Arg<boolean, BadArgsError>;
90
+ has: (name: string) => boolean;
91
+ hasAny: (names: string[]) => boolean;
92
+ isTrue: (name: string) => boolean;
93
+ raw: (name: string) => unknown;
94
+ };
95
+
96
+ function from(opts: Record<string, unknown>): ArgsReader {
97
+ const raw = (name: string) => opts[keyName(name)];
98
+ const has = (name: string) =>
99
+ Object.prototype.hasOwnProperty.call(opts, keyName(name));
100
+
101
+ return {
102
+ text: (name) => text(opts, name),
103
+ bool: (name) => bool(opts, name),
104
+ has,
105
+ hasAny: (names) => names.some(has),
106
+ isTrue: (name) => {
107
+ const value = raw(name);
108
+ return value === true || value === 'true';
109
+ },
110
+ raw,
111
+ };
112
+ }
40
113
 
41
- return makeArg(flag, readTextValue(opts[name], flag));
114
+ function text(opts: Record<string, unknown>, name: string) {
115
+ const key = keyName(name);
116
+ const flag = flagName(key);
117
+ const provided = Object.prototype.hasOwnProperty.call(opts, key);
118
+
119
+ return makeArg(
120
+ flag,
121
+ Effect.succeed(active(readTextValue(opts[key], flag), provided)),
122
+ );
42
123
  }
43
124
 
44
125
  function readTextValue(value: unknown, flag: string) {
@@ -57,9 +138,14 @@ function readTextValue(value: unknown, flag: string) {
57
138
  }
58
139
 
59
140
  function bool(opts: Record<string, unknown>, name: string) {
60
- const flag = flagName(name);
61
-
62
- return makeArg(flag, readBooleanValue(opts[name], flag));
141
+ const key = keyName(name);
142
+ const flag = flagName(key);
143
+ const provided = Object.prototype.hasOwnProperty.call(opts, key);
144
+
145
+ return makeArg(
146
+ flag,
147
+ Effect.succeed(active(readBooleanValue(opts[key], flag), provided)),
148
+ );
63
149
  }
64
150
 
65
151
  function readBooleanValue(value: unknown, flag: string) {
@@ -78,23 +164,49 @@ function uiErrorToBadArgs(e: { message: string }) {
78
164
  return BadArgsError.make({ message: `UI error: ${e.message}` });
79
165
  }
80
166
 
81
- function orPrompt(prompt: UI.TextInputProps) {
82
- return function orPromptArg<E, R>(arg: ArgEffect<string | undefined, E, R>) {
83
- return orPromptIf(true, prompt)(arg);
84
- };
167
+ function mapActive<A, B, E, R, E2, R2>(
168
+ arg: Arg<A, E, R>,
169
+ mapValue: (
170
+ value: Effect.Effect<A | undefined, E, R>,
171
+ ) => Effect.Effect<B | undefined, E | E2, R | R2>,
172
+ ): Arg<B, E | E2, R | R2> {
173
+ return makeArg(
174
+ arg.flag,
175
+ Effect.gen(function* mapArgState() {
176
+ const state = yield* arg.state;
177
+ if (state._tag === 'Inactive') return inactive;
178
+ return active(mapValue(state.value), state.provided);
179
+ }),
180
+ );
85
181
  }
86
182
 
87
- function orPromptIf(condition: boolean, prompt: UI.TextInputProps) {
88
- return function orPromptIfArg<E, R>(
89
- arg: ArgEffect<string | undefined, E, R>,
90
- ) {
91
- const flag = getFlag(arg);
92
-
183
+ function availableWhen(condition: boolean, options?: UnavailableOptions) {
184
+ return function availableWhenArg<A, E, R>(arg: Arg<A, E, R>) {
93
185
  return makeArg(
94
- flag,
186
+ arg.flag,
187
+ Effect.gen(function* gateArgAvailability() {
188
+ const state = yield* arg.state;
189
+ if (condition || state._tag === 'Inactive') return state;
190
+ if (state.provided) {
191
+ return yield* BadArgsError.make({
192
+ message:
193
+ options?.message ??
194
+ options?.otherwise ??
195
+ `${arg.flag} is not compatible with other options`,
196
+ });
197
+ }
198
+ return inactive;
199
+ }),
200
+ );
201
+ };
202
+ }
203
+
204
+ function prompt(prompt: UI.TextInputProps) {
205
+ return function promptArg<E, R>(arg: Arg<string, E, R>) {
206
+ return mapActive(arg, (valueEffect) =>
95
207
  Effect.gen(function* promptWhenMissing() {
96
- const value = yield* arg;
97
- if (value !== undefined || !condition) return value;
208
+ const value = yield* valueEffect;
209
+ if (value !== undefined) return value;
98
210
 
99
211
  const { yes } = yield* GlobalOpts;
100
212
  if (yes) return undefined;
@@ -109,25 +221,12 @@ function orPromptIf(condition: boolean, prompt: UI.TextInputProps) {
109
221
  };
110
222
  }
111
223
 
112
- function orConfirm(prompt: UI.ConfirmationProps) {
113
- return function orConfirmArg<E, R>(
114
- arg: ArgEffect<boolean | undefined, E, R>,
115
- ) {
116
- return orConfirmIf(true, prompt)(arg);
117
- };
118
- }
119
-
120
- function orConfirmIf(condition: boolean, prompt: UI.ConfirmationProps) {
121
- return function orConfirmIfArg<E, R>(
122
- arg: ArgEffect<boolean | undefined, E, R>,
123
- ) {
124
- const flag = getFlag(arg);
125
-
126
- return makeArg(
127
- flag,
224
+ function confirm(prompt: UI.ConfirmationProps) {
225
+ return function confirmArg<E, R>(arg: Arg<boolean, E, R>) {
226
+ return mapActive(arg, (valueEffect) =>
128
227
  Effect.gen(function* confirmWhenMissing() {
129
- const value = yield* arg;
130
- if (value !== undefined || !condition) return value;
228
+ const value = yield* valueEffect;
229
+ if (value !== undefined) return value;
131
230
 
132
231
  const { yes } = yield* GlobalOpts;
133
232
  if (yes) return undefined;
@@ -141,83 +240,39 @@ function orConfirmIf(condition: boolean, prompt: UI.ConfirmationProps) {
141
240
  }
142
241
 
143
242
  function required(options?: MissingOptions) {
144
- return function requiredArg<A, E, R>(arg: ArgEffect<A | undefined, E, R>) {
145
- const flag = getFlag(arg);
146
-
147
- return makeArg(
148
- flag,
149
- Effect.gen(function* requireValue() {
150
- const value = yield* arg;
151
- if (value !== undefined) return value;
152
-
153
- return yield* BadArgsError.make({
154
- message: options?.message ?? missingMessage(flag),
155
- });
156
- }),
157
- );
158
- };
159
- }
160
-
161
- function requiredIf(condition: boolean, options?: MissingOptions) {
162
- return function requiredIfArg<A, E, R>(
163
- arg: ArgEffect<A | undefined, E, R>,
164
- ): ArgEffect<A | undefined, E | BadArgsError, R> {
165
- return condition ? required(options)(arg) : arg;
166
- };
167
- }
243
+ return function requiredArg<A, E, R>(arg: Arg<A, E, R>) {
244
+ return Effect.gen(function* requireValue() {
245
+ const state = yield* arg.state;
246
+ if (state._tag === 'Inactive') return undefined as A;
168
247
 
169
- function forbidIf(condition: boolean, options?: { message?: string }) {
170
- return function forbidIfArg<A, E, R>(arg: ArgEffect<A | undefined, E, R>) {
171
- const flag = getFlag(arg);
248
+ const value = yield* state.value;
249
+ if (value !== undefined) return value;
172
250
 
173
- return makeArg(
174
- flag,
175
- Effect.gen(function* rejectForbiddenValue() {
176
- const value = yield* arg;
177
- if (condition && value !== undefined) {
178
- return yield* BadArgsError.make({
179
- message:
180
- options?.message ??
181
- `${flag} is not compatible with other options`,
182
- });
183
- }
184
-
185
- return condition ? undefined : value;
186
- }),
187
- );
251
+ return yield* BadArgsError.make({
252
+ message: options?.message ?? missingMessage(arg.flag),
253
+ });
254
+ });
188
255
  };
189
256
  }
190
257
 
191
- function validate<A>(validateValue: (value: A) => string | undefined) {
192
- return function validateArg<E, R>(arg: ArgEffect<A | undefined, E, R>) {
193
- const flag = getFlag(arg);
194
-
195
- return makeArg(
196
- flag,
197
- Effect.gen(function* validateValueIfPresent() {
198
- const value = yield* arg;
199
- if (value === undefined) return undefined;
200
-
201
- const error = validateValue(value);
202
- if (error) {
203
- return yield* BadArgsError.make({ message: error });
204
- }
258
+ function optional() {
259
+ return function optionalArg<A, E, R>(arg: Arg<A, E, R>) {
260
+ return Effect.gen(function* optionalValue() {
261
+ const state = yield* arg.state;
262
+ if (state._tag === 'Inactive') return undefined;
205
263
 
206
- return value;
207
- }),
208
- );
264
+ return yield* state.value;
265
+ });
209
266
  };
210
267
  }
211
268
 
212
269
  export const Args = {
270
+ from,
213
271
  text,
214
272
  bool,
215
- orPrompt,
216
- orPromptIf,
217
- orConfirm,
218
- orConfirmIf,
273
+ availableWhen,
274
+ prompt,
275
+ confirm,
219
276
  required,
220
- requiredIf,
221
- forbidIf,
222
- validate,
277
+ optional,
223
278
  };
package/src/lib/oauth.ts CHANGED
@@ -253,14 +253,15 @@ export const getClientNameAndProvider = Effect.fn(function* (
253
253
  providerType: typeof ClientTypeSchema.Type,
254
254
  opts: Record<string, unknown>,
255
255
  ) {
256
+ const args = Args.from(opts);
256
257
  const { auth, provider } = yield* getOrCreateProvider(providerType);
257
258
  const usedClientNames = new Set(
258
259
  (auth.oauth_clients ?? []).map((client) => client.client_name),
259
260
  );
260
261
  const suggestedClientName = findName(providerType, usedClientNames);
261
262
 
262
- const clientName = yield* Args.text(opts, 'name').pipe(
263
- Args.orPrompt({
263
+ const clientName = yield* args.text('name').pipe(
264
+ Args.prompt({
264
265
  prompt: 'Client Name:',
265
266
  defaultValue: suggestedClientName,
266
267
  placeholder: suggestedClientName,