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
@@ -1,4 +1,4 @@
1
1
 
2
- > instant-cli@1.0.23 build /home/runner/work/instant/instant/client/packages/cli
2
+ > instant-cli@1.0.24-branch-codex-cli-args-combinators.25405829034.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,358 @@
1
+ import { test, expect, describe, expectTypeOf, 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
+ import { BadArgsError } from '../src/errors.ts';
6
+
7
+ const run = <A>(effect: Effect.Effect<A, any, GlobalOpts>, yes: boolean) =>
8
+ Effect.runPromise(
9
+ effect.pipe(Effect.provide(Layer.succeed(GlobalOpts, { yes }))),
10
+ );
11
+
12
+ const runFail = <A>(effect: Effect.Effect<A, any, GlobalOpts>, yes: boolean) =>
13
+ Effect.runPromise(
14
+ effect.pipe(
15
+ Effect.flip,
16
+ Effect.provide(Layer.succeed(GlobalOpts, { yes })),
17
+ ),
18
+ );
19
+
20
+ let mockPromptReturn: unknown = '';
21
+ vi.mock('../src/ui/lib.ts', async (importOriginal) => {
22
+ const orig: any = await importOriginal();
23
+ return {
24
+ ...orig,
25
+ renderUnwrap: () => Promise.resolve(mockPromptReturn),
26
+ };
27
+ });
28
+
29
+ const basePrompt = { prompt: 'Client ID:' } as any;
30
+
31
+ describe('types', () => {
32
+ test('required return type accounts for inactive args', () => {
33
+ const requiredValue = Args.text(
34
+ { 'client-id': 'abc123' },
35
+ 'client-id',
36
+ ).pipe(Args.required());
37
+
38
+ expectTypeOf(requiredValue).toEqualTypeOf<
39
+ Effect.Effect<string, BadArgsError>
40
+ >();
41
+
42
+ const conditionallyRequiredValue = Args.text({}, 'client-id').pipe(
43
+ Args.availableWhen(false),
44
+ Args.required(),
45
+ );
46
+
47
+ expectTypeOf(conditionallyRequiredValue).toEqualTypeOf<
48
+ Effect.Effect<string | undefined, BadArgsError>
49
+ >();
50
+ });
51
+ });
52
+
53
+ describe('availableWhen', () => {
54
+ test('unavailable + value provided -> error', async () => {
55
+ const err = await runFail(
56
+ Args.text(
57
+ {
58
+ 'custom-redirect-uri': 'https://mysite.com/callback',
59
+ },
60
+ 'custom-redirect-uri',
61
+ ).pipe(
62
+ Args.availableWhen(false, {
63
+ message: 'Provided custom redirect URI when not using web app type.',
64
+ }),
65
+ Args.optional(),
66
+ ),
67
+ false,
68
+ );
69
+
70
+ expect(err.message).toBe(
71
+ 'Provided custom redirect URI when not using web app type.',
72
+ );
73
+ });
74
+
75
+ test('unavailable + empty value provided -> error', async () => {
76
+ const err = await runFail(
77
+ Args.text({ 'custom-redirect-uri': '' }, 'custom-redirect-uri').pipe(
78
+ Args.availableWhen(false, {
79
+ message: 'Provided custom redirect URI when not using web app type.',
80
+ }),
81
+ Args.optional(),
82
+ ),
83
+ false,
84
+ );
85
+
86
+ expect(err.message).toBe(
87
+ 'Provided custom redirect URI when not using web app type.',
88
+ );
89
+ });
90
+
91
+ test('unavailable + no value -> undefined', async () => {
92
+ const result = await run(
93
+ Args.text({}, 'custom-redirect-uri').pipe(
94
+ Args.availableWhen(false),
95
+ Args.optional(),
96
+ ),
97
+ false,
98
+ );
99
+
100
+ expect(result).toBeUndefined();
101
+ });
102
+ });
103
+
104
+ describe('validate', () => {
105
+ test('valid value -> passes through', async () => {
106
+ const result = await run(
107
+ Args.text({ 'project-id': 'my-project' }, 'project-id').pipe(
108
+ Args.validate((value) =>
109
+ value.includes('_')
110
+ ? 'Project ID cannot include underscores.'
111
+ : undefined,
112
+ ),
113
+ Args.required(),
114
+ ),
115
+ true,
116
+ );
117
+
118
+ expect(result).toBe('my-project');
119
+ });
120
+
121
+ test('invalid value -> error', async () => {
122
+ const err = await runFail(
123
+ Args.text({ 'project-id': 'my_project' }, 'project-id').pipe(
124
+ Args.validate((value) =>
125
+ value.includes('_')
126
+ ? 'Project ID cannot include underscores.'
127
+ : undefined,
128
+ ),
129
+ Args.required(),
130
+ ),
131
+ true,
132
+ );
133
+
134
+ expect(err.message).toBe('Project ID cannot include underscores.');
135
+ });
136
+ });
137
+
138
+ describe('non-interactive', () => {
139
+ test('required missing value -> error', async () => {
140
+ const err = await runFail(
141
+ Args.text({}, 'client-id').pipe(Args.prompt(basePrompt), Args.required()),
142
+ true,
143
+ );
144
+
145
+ expect(err.message).toBe('Missing required value for --client-id');
146
+ });
147
+
148
+ test('required missing value with prompt default -> default value', async () => {
149
+ const result = await run(
150
+ Args.text({}, 'name').pipe(
151
+ Args.prompt({ prompt: 'Client Name:', defaultValue: 'github' }),
152
+ Args.required(),
153
+ ),
154
+ true,
155
+ );
156
+
157
+ expect(result).toBe('github');
158
+ });
159
+
160
+ test('number value -> stringified', async () => {
161
+ const result = await run(
162
+ Args.text({ 'client-id': 42 }, 'client-id').pipe(Args.required()),
163
+ true,
164
+ );
165
+
166
+ expect(result).toBe('42');
167
+ });
168
+
169
+ test('non-string/number value -> error', async () => {
170
+ const err = await runFail(
171
+ Args.text({ 'client-id': true }, 'client-id').pipe(Args.required()),
172
+ true,
173
+ );
174
+
175
+ expect(err.message).toBe('Invalid value for --client-id');
176
+ });
177
+
178
+ test('valid string -> trimmed value', async () => {
179
+ const result = await run(
180
+ Args.text(
181
+ { 'client-id': ' abc123.apps.googleusercontent.com ' },
182
+ 'client-id',
183
+ ).pipe(Args.required()),
184
+ true,
185
+ );
186
+
187
+ expect(result).toBe('abc123.apps.googleusercontent.com');
188
+ });
189
+
190
+ test('optional missing value -> undefined', async () => {
191
+ const result = await run(
192
+ Args.text({}, 'custom-redirect-uri').pipe(Args.optional()),
193
+ true,
194
+ );
195
+
196
+ expect(result).toBeUndefined();
197
+ });
198
+
199
+ test('simpleName overrides display flag without changing lookup key', async () => {
200
+ const result = await run(
201
+ Args.text(
202
+ { customRedirectUri: 'https://example.com/callback' },
203
+ 'customRedirectUri',
204
+ { simpleName: '--custom-redirect-uri' },
205
+ ).pipe(Args.required()),
206
+ true,
207
+ );
208
+
209
+ expect(result).toBe('https://example.com/callback');
210
+
211
+ const err = await runFail(
212
+ Args.text({}, 'customRedirectUri', {
213
+ simpleName: '--custom-redirect-uri',
214
+ }).pipe(Args.required()),
215
+ true,
216
+ );
217
+
218
+ expect(err.message).toBe(
219
+ 'Missing required value for --custom-redirect-uri',
220
+ );
221
+ });
222
+
223
+ test('does not fall back to other key shapes', async () => {
224
+ const err = await runFail(
225
+ Args.text(
226
+ { 'custom-redirect-uri': 'https://example.com/callback' },
227
+ 'customRedirectUri',
228
+ { simpleName: '--custom-redirect-uri' },
229
+ ).pipe(Args.required()),
230
+ true,
231
+ );
232
+
233
+ expect(err.message).toBe(
234
+ 'Missing required value for --custom-redirect-uri',
235
+ );
236
+ });
237
+
238
+ test('boolean value -> parsed', async () => {
239
+ const result = await run(
240
+ Args.bool({ 'configure-web': true }, 'configure-web').pipe(
241
+ Args.optional(),
242
+ ),
243
+ true,
244
+ );
245
+
246
+ expect(result).toBe(true);
247
+ });
248
+
249
+ test('boolean string value -> parsed', async () => {
250
+ const result = await run(
251
+ Args.bool({ 'configure-web': 'false' }, 'configure-web').pipe(
252
+ Args.optional(),
253
+ ),
254
+ true,
255
+ );
256
+
257
+ expect(result).toBe(false);
258
+ });
259
+
260
+ test('invalid boolean value -> error', async () => {
261
+ const err = await runFail(
262
+ Args.bool({ 'configure-web': 'sometimes' }, 'configure-web').pipe(
263
+ Args.optional(),
264
+ ),
265
+ true,
266
+ );
267
+
268
+ expect(err.message).toBe('Invalid value for --configure-web');
269
+ });
270
+
271
+ test('missing confirmation returns default value', async () => {
272
+ const result = await run(
273
+ Args.bool({}, 'configure-web').pipe(
274
+ Args.confirm({
275
+ promptText: 'Configure web redirect flow?',
276
+ defaultValue: false,
277
+ }),
278
+ Args.required(),
279
+ ),
280
+ true,
281
+ );
282
+
283
+ expect(result).toBe(false);
284
+ });
285
+ });
286
+
287
+ describe('interactive', () => {
288
+ test('value already provided -> use it directly', async () => {
289
+ const result = await run(
290
+ Args.text(
291
+ { 'client-id': ' abc123.apps.googleusercontent.com ' },
292
+ 'client-id',
293
+ ).pipe(Args.prompt(basePrompt), Args.required()),
294
+ false,
295
+ );
296
+
297
+ expect(result).toBe('abc123.apps.googleusercontent.com');
298
+ });
299
+
300
+ test('no value + user types a value -> trimmed result', async () => {
301
+ mockPromptReturn = ' GOCSPX-secret123 ';
302
+
303
+ const result = await run(
304
+ Args.text({}, 'client-secret').pipe(
305
+ Args.prompt(basePrompt),
306
+ Args.required(),
307
+ ),
308
+ false,
309
+ );
310
+
311
+ expect(result).toBe('GOCSPX-secret123');
312
+ });
313
+
314
+ test('no value + user enters empty + required -> error', async () => {
315
+ mockPromptReturn = '';
316
+
317
+ const err = await runFail(
318
+ Args.text({}, 'client-secret').pipe(
319
+ Args.prompt(basePrompt),
320
+ Args.required(),
321
+ ),
322
+ false,
323
+ );
324
+
325
+ expect(err.message).toBe('Missing required value for --client-secret');
326
+ });
327
+
328
+ test('no value + user enters empty + optional -> undefined', async () => {
329
+ mockPromptReturn = '';
330
+
331
+ const result = await run(
332
+ Args.text({}, 'custom-redirect-uri').pipe(
333
+ Args.prompt(basePrompt),
334
+ Args.optional(),
335
+ ),
336
+ false,
337
+ );
338
+
339
+ expect(result).toBeUndefined();
340
+ });
341
+
342
+ test('no boolean value + user confirms -> result', async () => {
343
+ mockPromptReturn = true;
344
+
345
+ const result = await run(
346
+ Args.bool({}, 'configure-web').pipe(
347
+ Args.confirm({
348
+ promptText: 'Configure web redirect flow?',
349
+ defaultValue: false,
350
+ }),
351
+ Args.optional(),
352
+ ),
353
+ false,
354
+ );
355
+
356
+ expect(result).toBe(true);
357
+ });
358
+ });
@@ -141,17 +141,18 @@ const webFlags = new Map([
141
141
 
142
142
  // -- native-only (no web flow): build-up with --yes --
143
143
 
144
- describe('native: --yes errors on each missing required flag', () => {
144
+ describe('native: --yes handles missing values', () => {
145
145
  test('missing --type', async () => {
146
146
  await run(without(nativeFlags, 'type'), { yes: true });
147
147
  expect(logs.join('\n')).toContain('Missing required value for --type');
148
148
  expect(addedClients).toHaveLength(0);
149
149
  });
150
150
 
151
- test('missing --name', async () => {
151
+ test('missing --name uses suggested default', async () => {
152
152
  await run(without(nativeFlags, 'name'), { yes: true });
153
- expect(logs.join('\n')).toContain('Missing required value for --name');
154
- expect(addedClients).toHaveLength(0);
153
+ expect(prompts).toHaveLength(0);
154
+ expect(addedClients).toHaveLength(1);
155
+ expect(addedClients[0].clientName).toBe('apple');
155
156
  });
156
157
 
157
158
  test('missing --services-id', async () => {
@@ -361,17 +362,15 @@ describe('private key file errors', () => {
361
362
  });
362
363
  });
363
364
 
364
- // -- native-only: stray web-only flag rejection --
365
+ // -- web-flow flag detection --
365
366
 
366
- describe('native: web-only flags rejected when web flow not configured', () => {
367
- test('--custom-redirect-uri without web flow error', async () => {
367
+ describe('web-flow flag detection', () => {
368
+ test('--custom-redirect-uri enables web flow and requires web credentials', async () => {
368
369
  await run(
369
370
  withEntry(nativeFlags, 'custom-redirect-uri', 'https://example.com'),
370
371
  { yes: true },
371
372
  );
372
- expect(logs.join('\n')).toContain(
373
- '--custom-redirect-uri requires configuring the web redirect flow',
374
- );
373
+ expect(logs.join('\n')).toContain('Missing required value for --team-id');
375
374
  expect(addedClients).toHaveLength(0);
376
375
  });
377
376
  });
@@ -116,19 +116,20 @@ const webFlags = new Map([
116
116
  ],
117
117
  ]);
118
118
 
119
- // -- --yes: build-up errors on each missing required flag --
119
+ // -- --yes: build-up behavior --
120
120
 
121
- describe('--yes errors on each missing required flag', () => {
121
+ describe('--yes handles missing values', () => {
122
122
  test('missing --type', async () => {
123
123
  await run(without(webFlags, 'type'), { yes: true });
124
124
  expect(logs.join('\n')).toContain('Missing required value for --type');
125
125
  expect(addedClients).toHaveLength(0);
126
126
  });
127
127
 
128
- test('missing --name', async () => {
128
+ test('missing --name uses suggested default', async () => {
129
129
  await run(without(webFlags, 'name'), { yes: true });
130
- expect(logs.join('\n')).toContain('Missing required value for --name');
131
- expect(addedClients).toHaveLength(0);
130
+ expect(prompts).toHaveLength(0);
131
+ expect(addedClients).toHaveLength(1);
132
+ expect(addedClients[0].clientName).toBe('clerk');
132
133
  });
133
134
 
134
135
  test('missing --publishable-key', async () => {
@@ -105,19 +105,20 @@ const webFlags = new Map([
105
105
  ['client-secret', 'ghs_abc123'],
106
106
  ]);
107
107
 
108
- // -- --yes: build-up errors on each missing required flag --
108
+ // -- --yes: build-up behavior --
109
109
 
110
- describe('--yes errors on each missing required flag', () => {
110
+ describe('--yes handles missing values', () => {
111
111
  test('missing --type', async () => {
112
112
  await run(without(webFlags, 'type'), { yes: true });
113
113
  expect(logs.join('\n')).toContain('Missing required value for --type');
114
114
  expect(addedClients).toHaveLength(0);
115
115
  });
116
116
 
117
- test('missing --name', async () => {
117
+ test('missing --name uses suggested default', async () => {
118
118
  await run(without(webFlags, 'name'), { yes: true });
119
- expect(logs.join('\n')).toContain('Missing required value for --name');
120
- expect(addedClients).toHaveLength(0);
119
+ expect(prompts).toHaveLength(0);
120
+ expect(addedClients).toHaveLength(1);
121
+ expect(addedClients[0].clientName).toBe('github');
121
122
  });
122
123
 
123
124
  test('missing --client-id', async () => {
@@ -115,7 +115,7 @@ const iosFlags = new Map([
115
115
 
116
116
  // -- web: build-up with --yes --
117
117
 
118
- describe('web: --yes errors on each missing required flag', () => {
118
+ describe('web: --yes handles missing values', () => {
119
119
  test('missing --type', async () => {
120
120
  await run(without(webFlags, 'type'), { yes: true });
121
121
  expect(logs.join('\n')).toContain('Missing required value for --type');
@@ -128,10 +128,11 @@ describe('web: --yes errors on each missing required flag', () => {
128
128
  expect(addedClients).toHaveLength(0);
129
129
  });
130
130
 
131
- test('missing --name', async () => {
131
+ test('missing --name uses suggested default', async () => {
132
132
  await run(without(webFlags, 'name'), { yes: true });
133
- expect(logs.join('\n')).toContain('Missing required value for --name');
134
- expect(addedClients).toHaveLength(0);
133
+ expect(prompts).toHaveLength(0);
134
+ expect(addedClients).toHaveLength(1);
135
+ expect(addedClients[0].clientName).toBe('google-web');
135
136
  });
136
137
 
137
138
  test('missing --client-id', async () => {
@@ -113,19 +113,20 @@ const webFlags = new Map([
113
113
  ['client-secret', 'secret123'],
114
114
  ]);
115
115
 
116
- // -- --yes: build-up errors on each missing required flag --
116
+ // -- --yes: build-up behavior --
117
117
 
118
- describe('--yes errors on each missing required flag', () => {
118
+ describe('--yes handles missing values', () => {
119
119
  test('missing --type', async () => {
120
120
  await run(without(webFlags, 'type'), { yes: true });
121
121
  expect(logs.join('\n')).toContain('Missing required value for --type');
122
122
  expect(addedClients).toHaveLength(0);
123
123
  });
124
124
 
125
- test('missing --name', async () => {
125
+ test('missing --name uses suggested default', async () => {
126
126
  await run(without(webFlags, 'name'), { yes: true });
127
- expect(logs.join('\n')).toContain('Missing required value for --name');
128
- expect(addedClients).toHaveLength(0);
127
+ expect(prompts).toHaveLength(0);
128
+ expect(addedClients).toHaveLength(1);
129
+ expect(addedClients[0].clientName).toBe('linkedin');
129
130
  });
130
131
 
131
132
  test('missing --client-id', async () => {
@@ -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.prompt({
52
50
  prompt: 'Client Name:',
53
51
  defaultValue: suggested,
54
52
  placeholder: suggested,
@@ -57,9 +55,10 @@ export const makeOAuthMock = (mocks: {
57
55
  UI.modifiers.topPadding,
58
56
  UI.modifiers.dimOnComplete,
59
57
  ]),
60
- },
61
- });
62
- if (used.has(clientName || '')) {
58
+ }),
59
+ Args.required(),
60
+ );
61
+ if (used.has(clientName)) {
63
62
  return yield* BadArgsError.make({
64
63
  message: `The unique name '${clientName}' is already in use.`,
65
64
  });
@@ -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;AA4C5D,eAAO,MAAM,gBAAgB,gFAO5B,CAAC;AAmmBF,eAAO,MAAM,gBAAgB;;;;wgBAwD5B,CAAC"}