effect-start 0.17.2 → 0.19.0

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 (80) hide show
  1. package/dist/Development.d.ts +7 -2
  2. package/dist/Development.js +12 -6
  3. package/dist/PlatformRuntime.d.ts +4 -0
  4. package/dist/PlatformRuntime.js +9 -0
  5. package/dist/Route.d.ts +6 -2
  6. package/dist/Route.js +22 -0
  7. package/dist/RouteHttp.d.ts +1 -1
  8. package/dist/RouteHttp.js +12 -19
  9. package/dist/RouteMount.d.ts +2 -1
  10. package/dist/Start.d.ts +1 -5
  11. package/dist/Start.js +1 -8
  12. package/dist/Unique.d.ts +50 -0
  13. package/dist/Unique.js +187 -0
  14. package/dist/bun/BunHttpServer.js +5 -6
  15. package/dist/bun/BunRoute.d.ts +1 -1
  16. package/dist/bun/BunRoute.js +2 -2
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +1 -0
  19. package/dist/node/Effectify.d.ts +209 -0
  20. package/dist/node/Effectify.js +19 -0
  21. package/dist/node/FileSystem.d.ts +3 -5
  22. package/dist/node/FileSystem.js +42 -62
  23. package/dist/node/PlatformError.d.ts +46 -0
  24. package/dist/node/PlatformError.js +43 -0
  25. package/dist/testing/TestLogger.js +1 -1
  26. package/package.json +10 -5
  27. package/src/Development.ts +13 -18
  28. package/src/PlatformRuntime.ts +11 -0
  29. package/src/Route.ts +31 -2
  30. package/src/RouteHttp.ts +15 -31
  31. package/src/RouteMount.ts +1 -1
  32. package/src/Start.ts +1 -15
  33. package/src/Unique.ts +232 -0
  34. package/src/bun/BunHttpServer.ts +6 -9
  35. package/src/bun/BunRoute.ts +3 -3
  36. package/src/index.ts +1 -0
  37. package/src/node/Effectify.ts +262 -0
  38. package/src/node/FileSystem.ts +59 -97
  39. package/src/node/PlatformError.ts +102 -0
  40. package/src/testing/TestLogger.ts +1 -1
  41. package/dist/Random.d.ts +0 -5
  42. package/dist/Random.js +0 -49
  43. package/src/Commander.test.ts +0 -1639
  44. package/src/ContentNegotiation.test.ts +0 -603
  45. package/src/Development.test.ts +0 -119
  46. package/src/Entity.test.ts +0 -592
  47. package/src/FileRouterPattern.test.ts +0 -147
  48. package/src/FileRouter_files.test.ts +0 -64
  49. package/src/FileRouter_path.test.ts +0 -145
  50. package/src/FileRouter_tree.test.ts +0 -132
  51. package/src/Http.test.ts +0 -319
  52. package/src/HttpAppExtra.test.ts +0 -103
  53. package/src/HttpUtils.test.ts +0 -85
  54. package/src/PathPattern.test.ts +0 -648
  55. package/src/Random.ts +0 -59
  56. package/src/RouteBody.test.ts +0 -232
  57. package/src/RouteHook.test.ts +0 -40
  58. package/src/RouteHttp.test.ts +0 -2909
  59. package/src/RouteMount.test.ts +0 -481
  60. package/src/RouteSchema.test.ts +0 -427
  61. package/src/RouteSse.test.ts +0 -249
  62. package/src/RouteTree.test.ts +0 -494
  63. package/src/RouteTrie.test.ts +0 -322
  64. package/src/RouterPattern.test.ts +0 -676
  65. package/src/Values.test.ts +0 -263
  66. package/src/bun/BunBundle.test.ts +0 -268
  67. package/src/bun/BunBundle_imports.test.ts +0 -48
  68. package/src/bun/BunHttpServer.test.ts +0 -251
  69. package/src/bun/BunImportTrackerPlugin.test.ts +0 -77
  70. package/src/bun/BunRoute.test.ts +0 -162
  71. package/src/bundler/BundleHttp.test.ts +0 -132
  72. package/src/effect/HttpRouter.test.ts +0 -548
  73. package/src/experimental/EncryptedCookies.test.ts +0 -488
  74. package/src/hyper/HyperHtml.test.ts +0 -209
  75. package/src/hyper/HyperRoute.test.tsx +0 -197
  76. package/src/middlewares/BasicAuthMiddleware.test.ts +0 -84
  77. package/src/testing/TestHttpClient.test.ts +0 -83
  78. package/src/testing/TestLogger.test.ts +0 -51
  79. package/src/x/datastar/Datastar.test.ts +0 -266
  80. package/src/x/tailwind/TailwindPlugin.test.ts +0 -333
@@ -1,1639 +0,0 @@
1
- import * as test from "bun:test"
2
- import * as Effect from "effect/Effect"
3
- import * as Schema from "effect/Schema"
4
- import * as assert from "node:assert"
5
- import * as Commander from "./Commander.ts"
6
-
7
- test.describe(`${Commander.make.name}`, () => {
8
- test.it("should create a basic command", () => {
9
- const cmd = Commander.make({
10
- name: "test-app",
11
- description: "A test application",
12
- })
13
- test
14
- .expect(cmd.name)
15
- .toBe("test-app")
16
- test
17
- .expect(cmd.description)
18
- .toBe("A test application")
19
- })
20
- })
21
-
22
- test.describe(`${Commander.option.name} - nested builder API`, () => {
23
- test.it("should add an option with schema", () => {
24
- const cmd = Commander
25
- .make({ name: "app" })
26
- .option(
27
- Commander
28
- .option("--output", "-o")
29
- .schema(Schema.String),
30
- )
31
-
32
- test
33
- .expect(cmd.options.output)
34
- .toBeDefined()
35
- test
36
- .expect(cmd.options.output.long)
37
- .toBe("--output")
38
- test
39
- .expect(cmd.options.output.short)
40
- .toBe("o")
41
- })
42
-
43
- test.it("should add option with description", () => {
44
- const cmd = Commander
45
- .make({ name: "app" })
46
- .option(
47
- Commander
48
- .option("--output", "-o")
49
- .description("Output file")
50
- .schema(Schema.String),
51
- )
52
-
53
- test
54
- .expect(cmd.options.output.description)
55
- .toBe("Output file")
56
- })
57
-
58
- test.it("should add option with default value", () => {
59
- const cmd = Commander
60
- .make({ name: "app" })
61
- .option(
62
- Commander
63
- .option("--count", "-c")
64
- .default(10)
65
- .schema(Commander.NumberFromString),
66
- )
67
-
68
- test
69
- .expect(cmd.options.count.defaultValue)
70
- .toBe(10)
71
- })
72
-
73
- test.it("should chain multiple options", () => {
74
- const cmd = Commander
75
- .make({ name: "app" })
76
- .option(
77
- Commander
78
- .option("--input", "-i")
79
- .schema(Schema.String),
80
- )
81
- .option(
82
- Commander
83
- .option("--output", "-o")
84
- .schema(Schema.String),
85
- )
86
-
87
- test
88
- .expect(cmd.options.input)
89
- .toBeDefined()
90
- test
91
- .expect(cmd.options.output)
92
- .toBeDefined()
93
- test
94
- .expect(cmd.options.input.long)
95
- .toBe("--input")
96
- test
97
- .expect(cmd.options.output.long)
98
- .toBe("--output")
99
- })
100
- })
101
-
102
- test.describe(`${Commander.parse.name} - kebab-to-camel conversion`, () => {
103
- test.it("should convert kebab-case to camelCase", async () => {
104
- const cmd = Commander
105
- .make({ name: "app" })
106
- .option(
107
- Commander
108
- .option("--input-file")
109
- .schema(Schema.String),
110
- )
111
-
112
- const result = await Effect.runPromise(
113
- Commander.parse(cmd, ["--input-file", "test.txt"]),
114
- )
115
-
116
- test
117
- .expect(result.inputFile)
118
- .toBe("test.txt")
119
- })
120
-
121
- test.it("should handle single word options", async () => {
122
- const cmd = Commander
123
- .make({ name: "app" })
124
- .option(
125
- Commander
126
- .option("--port")
127
- .schema(Commander.NumberFromString),
128
- )
129
-
130
- const result = await Effect.runPromise(
131
- Commander.parse(cmd, ["--port", "3000"]),
132
- )
133
-
134
- test
135
- .expect(result.port)
136
- .toBe(3000)
137
- })
138
-
139
- test.it("should parse short options", async () => {
140
- const cmd = Commander
141
- .make({ name: "app" })
142
- .option(
143
- Commander
144
- .option("--port", "-p")
145
- .schema(Commander.NumberFromString),
146
- )
147
-
148
- const result = await Effect.runPromise(
149
- Commander.parse(cmd, ["-p", "8080"]),
150
- )
151
-
152
- test
153
- .expect(result.port)
154
- .toBe(8080)
155
- })
156
-
157
- test.it("should parse multiple options", async () => {
158
- const cmd = Commander
159
- .make({ name: "app" })
160
- .option(
161
- Commander
162
- .option("--host", "-h")
163
- .schema(Schema.String),
164
- )
165
- .option(
166
- Commander
167
- .option("--port", "-p")
168
- .schema(Commander.NumberFromString),
169
- )
170
-
171
- const result = await Effect.runPromise(
172
- Commander.parse(cmd, ["--host", "localhost", "--port", "3000"]),
173
- )
174
-
175
- test
176
- .expect(result.host)
177
- .toBe("localhost")
178
- test
179
- .expect(result.port)
180
- .toBe(3000)
181
- })
182
-
183
- test.it("should handle options with equals syntax", async () => {
184
- const cmd = Commander
185
- .make({ name: "app" })
186
- .option(
187
- Commander
188
- .option("--port")
189
- .schema(Commander.NumberFromString),
190
- )
191
-
192
- const result = await Effect.runPromise(
193
- Commander.parse(cmd, ["--port=3000"]),
194
- )
195
-
196
- test
197
- .expect(result.port)
198
- .toBe(3000)
199
- })
200
-
201
- test.it("should use default value when option not provided", async () => {
202
- const cmd = Commander
203
- .make({ name: "app" })
204
- .option(
205
- Commander
206
- .option("--port", "-p")
207
- .default(3000)
208
- .schema(Commander.NumberFromString),
209
- )
210
-
211
- const result = await Effect.runPromise(
212
- Commander.parse(cmd, []),
213
- )
214
-
215
- test
216
- .expect(result.port)
217
- .toBe(3000)
218
- })
219
-
220
- test.it("should override default when option specified", async () => {
221
- const cmd = Commander
222
- .make({ name: "app" })
223
- .option(
224
- Commander
225
- .option("--port", "-p")
226
- .default(3000)
227
- .schema(Commander.NumberFromString),
228
- )
229
-
230
- const result = await Effect.runPromise(
231
- Commander.parse(cmd, ["--port", "8080"]),
232
- )
233
-
234
- test
235
- .expect(result.port)
236
- .toBe(8080)
237
- })
238
- })
239
-
240
- test.describe(`${Commander.optionHelp.name}`, () => {
241
- test.it("should add help option", () => {
242
- const cmd = Commander
243
- .make({ name: "app" })
244
- .optionHelp()
245
-
246
- test
247
- .expect(cmd.options.help)
248
- .toBeDefined()
249
- test
250
- .expect(cmd.options.help.long)
251
- .toBe("--help")
252
- test
253
- .expect(cmd.options.help.short)
254
- .toBe("h")
255
- })
256
- })
257
-
258
- test.describe(`${Commander.optionVersion.name}`, () => {
259
- test.it("should add version option", () => {
260
- const cmd = Commander
261
- .make({ name: "app", version: "1.0.0" })
262
- .optionVersion()
263
-
264
- test
265
- .expect(cmd.options.version)
266
- .toBeDefined()
267
- test
268
- .expect(cmd.options.version.long)
269
- .toBe("--version")
270
- test
271
- .expect(cmd.options.version.short)
272
- .toBe("V")
273
- })
274
- })
275
-
276
- test.describe(`${Commander.handle.name}`, () => {
277
- test.it("should mark command as handled", () => {
278
- const handled = Commander
279
- .make({ name: "app" })
280
- .option(
281
- Commander
282
- .option("--name", "-n")
283
- .schema(Schema.String),
284
- )
285
- .handle((opts) => Effect.void)
286
-
287
- const unhandled = Commander
288
- .make({ name: "app2" })
289
- .option(
290
- Commander
291
- .option("--name", "-n")
292
- .schema(Schema.String),
293
- )
294
-
295
- test
296
- .expect(handled.handler)
297
- .toBeDefined()
298
- test
299
- .expect(unhandled.handler)
300
- .toBeUndefined()
301
- })
302
- })
303
-
304
- test.describe(`${Commander.subcommand.name}`, () => {
305
- test.it("should add subcommand", () => {
306
- const subCmd = Commander
307
- .make({ name: "format" })
308
- .option(
309
- Commander
310
- .option("--style")
311
- .schema(Schema.String),
312
- )
313
- .handle((opts) => Effect.void)
314
-
315
- const main = Commander
316
- .make({ name: "main" })
317
- .subcommand(subCmd)
318
-
319
- test
320
- .expect(main.subcommands.length)
321
- .toBe(1)
322
- test
323
- .expect(main.subcommands[0]!.command.name)
324
- .toBe("format")
325
- })
326
- })
327
-
328
- test.describe(`${Commander.help.name}`, () => {
329
- test.it("should generate help text", () => {
330
- const cmd = Commander
331
- .make({
332
- name: "myapp",
333
- description: "My awesome application",
334
- version: "1.0.0",
335
- })
336
- .option(
337
- Commander
338
- .option("--output", "-o")
339
- .description("Output file")
340
- .schema(Schema.String),
341
- )
342
- .optionHelp()
343
-
344
- const helpText = Commander.help(cmd)
345
-
346
- test
347
- .expect(helpText)
348
- .toContain("My awesome application")
349
- test
350
- .expect(helpText)
351
- .toContain("Usage: myapp [options]")
352
- test
353
- .expect(helpText)
354
- .toContain("--output")
355
- test
356
- .expect(helpText)
357
- .toContain("-o,")
358
- test
359
- .expect(helpText)
360
- .toContain("Output file")
361
- test
362
- .expect(helpText)
363
- .toContain("--help")
364
- })
365
- })
366
-
367
- test.describe("BooleanFromString", () => {
368
- test.it("should decode true value", async () => {
369
- const result = await Effect.runPromise(
370
- Schema.decode(Schema.BooleanFromString)("true"),
371
- )
372
- test
373
- .expect(result)
374
- .toBe(true)
375
- })
376
-
377
- test.it("should decode false value", async () => {
378
- const result = await Effect.runPromise(
379
- Schema.decode(Schema.BooleanFromString)("false"),
380
- )
381
- test
382
- .expect(result)
383
- .toBe(false)
384
- })
385
- })
386
-
387
- test.describe(`${Commander.choice.name}`, () => {
388
- test.it("should accept valid choice", async () => {
389
- const ColorSchema = Schema.compose(
390
- Schema.String,
391
- Schema.Literal("red", "green", "blue"),
392
- )
393
-
394
- const result = await Effect.runPromise(
395
- Schema.decode(ColorSchema)("red"),
396
- )
397
-
398
- test
399
- .expect(result)
400
- .toBe("red")
401
- })
402
-
403
- test.it("should fail on invalid choice", async () => {
404
- const ColorSchema = Schema.compose(
405
- Schema.String,
406
- Schema.Literal("red", "green", "blue"),
407
- )
408
-
409
- const result = await Effect.runPromise(
410
- Effect.either(Schema.decode(ColorSchema)("yellow")),
411
- )
412
-
413
- test
414
- .expect(result._tag)
415
- .toBe("Left")
416
- })
417
- })
418
-
419
- test.describe(`${Commander.repeatable.name}`, () => {
420
- test.it("should parse comma-separated values", async () => {
421
- const schema = Commander.repeatable(Schema.String)
422
-
423
- const result = await Effect.runPromise(
424
- Schema.decode(schema)("foo,bar,baz"),
425
- )
426
-
427
- test
428
- .expect(result)
429
- .toEqual(["foo", "bar", "baz"])
430
- })
431
-
432
- test.it("should parse comma-separated numbers", async () => {
433
- const schema = Commander.repeatable(Commander.NumberFromString)
434
-
435
- const result = await Effect.runPromise(
436
- Schema.decode(schema)("1,2,3,4,5"),
437
- )
438
-
439
- test
440
- .expect(result)
441
- .toEqual([1, 2, 3, 4, 5])
442
- })
443
-
444
- test.it("should trim whitespace", async () => {
445
- const schema = Commander.repeatable(Schema.String)
446
-
447
- const result = await Effect.runPromise(
448
- Schema.decode(schema)("foo, bar , baz"),
449
- )
450
-
451
- test
452
- .expect(result)
453
- .toEqual(["foo", "bar", "baz"])
454
- })
455
-
456
- test.it("should encode back to string", async () => {
457
- const schema = Commander.repeatable(Schema.String)
458
-
459
- const result = await Effect.runPromise(
460
- Schema.encode(schema)(["foo", "bar", "baz"]),
461
- )
462
-
463
- test
464
- .expect(result)
465
- .toBe("foo,bar,baz")
466
- })
467
- })
468
-
469
- test.describe("integration", () => {
470
- test.it("should work with builder pattern", async () => {
471
- const cmd = Commander
472
- .make({
473
- name: "converter",
474
- description: "Convert files",
475
- version: "2.1.0",
476
- })
477
- .option(
478
- Commander
479
- .option("--input", "-i")
480
- .description("Input file")
481
- .schema(Schema.String),
482
- )
483
- .option(
484
- Commander
485
- .option("--output", "-o")
486
- .description("Output file")
487
- .default("output.txt")
488
- .schema(Schema.String),
489
- )
490
- .option(
491
- Commander
492
- .option("--format", "-f")
493
- .default("json")
494
- .schema(
495
- Schema.compose(
496
- Schema.String,
497
- Schema.Literal("json", "xml", "yaml"),
498
- ),
499
- ),
500
- )
501
- .optionHelp()
502
-
503
- const result = await Effect.runPromise(
504
- Commander.parse(cmd, [
505
- "--input",
506
- "input.txt",
507
- "-f",
508
- "yaml",
509
- ]),
510
- )
511
-
512
- test
513
- .expect(result.input)
514
- .toBe("input.txt")
515
- test
516
- .expect(result.output)
517
- .toBe("output.txt")
518
- test
519
- .expect(result.format)
520
- .toBe("yaml")
521
- test
522
- .expect(result.help)
523
- .toBe(false)
524
- })
525
-
526
- test.it("should handle kebab-case option names", async () => {
527
- const cmd = Commander
528
- .make({ name: "app" })
529
- .option(
530
- Commander
531
- .option("--dry-run")
532
- .default(false)
533
- .schema(Schema.BooleanFromString),
534
- )
535
- .option(
536
- Commander
537
- .option("--cache-dir")
538
- .schema(Schema.String),
539
- )
540
-
541
- const result = await Effect.runPromise(
542
- Commander.parse(cmd, ["--dry-run", "true", "--cache-dir", "/tmp/cache"]),
543
- )
544
-
545
- test
546
- .expect(result.dryRun)
547
- .toBe(true)
548
- test
549
- .expect(result.cacheDir)
550
- .toBe("/tmp/cache")
551
- })
552
- })
553
-
554
- test.describe(`${Commander.parse.name} - comprehensive`, () => {
555
- test.it("should parse with explicit args", async () => {
556
- const cmd = Commander
557
- .make({ name: "app" })
558
- .option(
559
- Commander
560
- .option("--port", "-p")
561
- .schema(Commander.NumberFromString),
562
- )
563
-
564
- const result = await Effect.runPromise(
565
- Commander.parse(cmd, ["--port", "3000"]),
566
- )
567
-
568
- test
569
- .expect(result.port)
570
- .toBe(3000)
571
- })
572
-
573
- test.it("should parse short options", async () => {
574
- const cmd = Commander
575
- .make({ name: "app" })
576
- .option(
577
- Commander
578
- .option("--port", "-p")
579
- .schema(Commander.NumberFromString),
580
- )
581
-
582
- const result = await Effect.runPromise(
583
- Commander.parse(cmd, ["-p", "8080"]),
584
- )
585
-
586
- test
587
- .expect(result.port)
588
- .toBe(8080)
589
- })
590
-
591
- test.it("should parse multiple options", async () => {
592
- const cmd = Commander
593
- .make({ name: "app" })
594
- .option(
595
- Commander
596
- .option("--host", "-h")
597
- .schema(Schema.String),
598
- )
599
- .option(
600
- Commander
601
- .option("--port", "-p")
602
- .schema(Commander.NumberFromString),
603
- )
604
-
605
- const result = await Effect.runPromise(
606
- Commander.parse(cmd, ["--host", "localhost", "--port", "3000"]),
607
- )
608
-
609
- test
610
- .expect(result.host)
611
- .toBe("localhost")
612
- test
613
- .expect(result.port)
614
- .toBe(3000)
615
- })
616
-
617
- test.it("should handle options with equals syntax", async () => {
618
- const cmd = Commander
619
- .make({ name: "app" })
620
- .option(
621
- Commander
622
- .option("--port")
623
- .schema(Commander.NumberFromString),
624
- )
625
-
626
- const result = await Effect.runPromise(
627
- Commander.parse(cmd, ["--port=3000"]),
628
- )
629
-
630
- test
631
- .expect(result.port)
632
- .toBe(3000)
633
- })
634
-
635
- test.it("should parse combined short flags", async () => {
636
- const cmd = Commander
637
- .make({ name: "app" })
638
- .option(
639
- Commander
640
- .option("--verbose", "-v")
641
- .default(false)
642
- .schema(Schema.BooleanFromString),
643
- )
644
- .option(
645
- Commander
646
- .option("--debug", "-d")
647
- .default(false)
648
- .schema(Schema.BooleanFromString),
649
- )
650
-
651
- const result = await Effect.runPromise(
652
- Commander.parse(cmd, []),
653
- )
654
-
655
- test
656
- .expect(result.verbose)
657
- .toBe(false)
658
- test
659
- .expect(result.debug)
660
- .toBe(false)
661
- })
662
- })
663
-
664
- test.describe("boolean options", () => {
665
- test.it("should return false for boolean flag when not specified", async () => {
666
- const cmd = Commander
667
- .make({ name: "app" })
668
- .option(
669
- Commander
670
- .option("--verbose", "-v")
671
- .default(false)
672
- .schema(Schema.BooleanFromString),
673
- )
674
-
675
- const result = await Effect.runPromise(
676
- Commander.parse(cmd, []),
677
- )
678
-
679
- test
680
- .expect(result.verbose)
681
- .toBe(false)
682
- })
683
-
684
- test.it("should return true for boolean flag when specified", async () => {
685
- const cmd = Commander
686
- .make({ name: "app" })
687
- .option(
688
- Commander
689
- .option("--verbose", "-v")
690
- .default(false)
691
- .schema(Schema.BooleanFromString),
692
- )
693
-
694
- const result = await Effect.runPromise(
695
- Commander.parse(cmd, ["--verbose", "true"]),
696
- )
697
-
698
- test
699
- .expect(result.verbose)
700
- .toBe(true)
701
- })
702
-
703
- test.it("should handle boolean with custom default", async () => {
704
- const cmd = Commander
705
- .make({ name: "app" })
706
- .option(
707
- Commander
708
- .option("--color")
709
- .default("auto")
710
- .schema(Schema.String),
711
- )
712
-
713
- const result = await Effect.runPromise(
714
- Commander.parse(cmd, []),
715
- )
716
-
717
- test
718
- .expect(result.color)
719
- .toBe("auto")
720
- })
721
- })
722
-
723
- test.describe("options with choices", () => {
724
- test.it("should accept valid choice", async () => {
725
- const cmd = Commander
726
- .make({ name: "app" })
727
- .option(
728
- Commander
729
- .option("--color", "-c")
730
- .schema(
731
- Schema.compose(
732
- Schema.String,
733
- Schema.Literal("red", "green", "blue"),
734
- ),
735
- ),
736
- )
737
-
738
- const result = await Effect.runPromise(
739
- Commander.parse(cmd, ["--color", "red"]),
740
- )
741
-
742
- test
743
- .expect(result.color)
744
- .toBe("red")
745
- })
746
-
747
- test.it("should reject invalid choice", async () => {
748
- const cmd = Commander
749
- .make({ name: "app" })
750
- .option(
751
- Commander
752
- .option("--color", "-c")
753
- .schema(
754
- Schema.compose(
755
- Schema.String,
756
- Schema.Literal("red", "green", "blue"),
757
- ),
758
- ),
759
- )
760
-
761
- const result = await Effect.runPromise(
762
- Effect.either(Commander.parse(cmd, ["--color", "yellow"])),
763
- )
764
-
765
- test
766
- .expect(result._tag)
767
- .toBe("Left")
768
- })
769
-
770
- test.it("should handle multiple choice options", async () => {
771
- const cmd = Commander
772
- .make({ name: "app" })
773
- .option(
774
- Commander
775
- .option("--format", "-f")
776
- .default("json")
777
- .schema(
778
- Schema.compose(
779
- Schema.String,
780
- Schema.Literal("json", "xml", "yaml"),
781
- ),
782
- ),
783
- )
784
- .option(
785
- Commander
786
- .option("--level", "-l")
787
- .default("info")
788
- .schema(
789
- Schema.compose(
790
- Schema.String,
791
- Schema.Literal("debug", "info", "warn", "error"),
792
- ),
793
- ),
794
- )
795
-
796
- const result = await Effect.runPromise(
797
- Commander.parse(cmd, ["--format", "xml", "--level", "debug"]),
798
- )
799
-
800
- test
801
- .expect(result.format)
802
- .toBe("xml")
803
- test
804
- .expect(result.level)
805
- .toBe("debug")
806
- })
807
- })
808
-
809
- test.describe("options with defaults", () => {
810
- test.it("should use default when option not specified", async () => {
811
- const cmd = Commander
812
- .make({ name: "app" })
813
- .option(
814
- Commander
815
- .option("--port", "-p")
816
- .default(3000)
817
- .schema(Commander.NumberFromString),
818
- )
819
-
820
- const result = await Effect.runPromise(
821
- Commander.parse(cmd, []),
822
- )
823
-
824
- test
825
- .expect(result.port)
826
- .toBe(3000)
827
- })
828
-
829
- test.it("should override default when option specified", async () => {
830
- const cmd = Commander
831
- .make({ name: "app" })
832
- .option(
833
- Commander
834
- .option("--port", "-p")
835
- .default(3000)
836
- .schema(Commander.NumberFromString),
837
- )
838
-
839
- const result = await Effect.runPromise(
840
- Commander.parse(cmd, ["--port", "8080"]),
841
- )
842
-
843
- test
844
- .expect(result.port)
845
- .toBe(8080)
846
- })
847
-
848
- test.it("should handle multiple defaults", async () => {
849
- const cmd = Commander
850
- .make({ name: "app" })
851
- .option(
852
- Commander
853
- .option("--host")
854
- .default("localhost")
855
- .schema(Schema.String),
856
- )
857
- .option(
858
- Commander
859
- .option("--port")
860
- .default(3000)
861
- .schema(Commander.NumberFromString),
862
- )
863
- .option(
864
- Commander
865
- .option("--debug")
866
- .default(false)
867
- .schema(Schema.BooleanFromString),
868
- )
869
-
870
- const result = await Effect.runPromise(
871
- Commander.parse(cmd, []),
872
- )
873
-
874
- test
875
- .expect(result.host)
876
- .toBe("localhost")
877
- test
878
- .expect(result.port)
879
- .toBe(3000)
880
- test
881
- .expect(result.debug)
882
- .toBe(false)
883
- })
884
-
885
- test.it("should use default for string option", async () => {
886
- const cmd = Commander
887
- .make({ name: "app" })
888
- .option(
889
- Commander
890
- .option("--output", "-o")
891
- .default("output.txt")
892
- .schema(Schema.String),
893
- )
894
-
895
- const result = await Effect.runPromise(
896
- Commander.parse(cmd, []),
897
- )
898
-
899
- test
900
- .expect(result.output)
901
- .toBe("output.txt")
902
- })
903
- })
904
-
905
- test.describe("action/handler", () => {
906
- test.it("should invoke handler with parsed options", async () => {
907
- let capturedOptions: any = null
908
-
909
- const cmd = Commander
910
- .make({ name: "app" })
911
- .option(
912
- Commander
913
- .option("--name", "-n")
914
- .schema(Schema.String),
915
- )
916
- .handle((opts) =>
917
- Effect.sync(() => {
918
- capturedOptions = opts
919
- })
920
- )
921
-
922
- const parsed = await Effect.runPromise(
923
- Commander.parse(cmd, ["--name", "test"]),
924
- )
925
-
926
- test
927
- .expect(parsed.name)
928
- .toBe("test")
929
- })
930
-
931
- test.it("should support async handlers", async () => {
932
- let executed = false
933
-
934
- const cmd = Commander
935
- .make({ name: "app" })
936
- .option(
937
- Commander
938
- .option("--delay")
939
- .default(0)
940
- .schema(Commander.NumberFromString),
941
- )
942
- .handle((opts) =>
943
- Effect.gen(function*() {
944
- yield* Effect.sleep(opts.delay)
945
- executed = true
946
- })
947
- )
948
-
949
- await Effect.runPromise(
950
- Commander.parse(cmd, ["--delay", "10"]),
951
- )
952
-
953
- test
954
- .expect(executed)
955
- .toBe(false)
956
- })
957
-
958
- test.it("should pass all options to handler", async () => {
959
- let capturedOpts: any = null
960
-
961
- const cmd = Commander
962
- .make({ name: "app" })
963
- .option(
964
- Commander
965
- .option("--input", "-i")
966
- .schema(Schema.String),
967
- )
968
- .option(
969
- Commander
970
- .option("--output", "-o")
971
- .schema(Schema.String),
972
- )
973
- .option(
974
- Commander
975
- .option("--verbose", "-v")
976
- .default(false)
977
- .schema(Schema.BooleanFromString),
978
- )
979
- .handle((opts) =>
980
- Effect.sync(() => {
981
- capturedOpts = opts
982
- })
983
- )
984
-
985
- await Effect.runPromise(
986
- Commander.parse(cmd, ["-i", "in.txt", "-o", "out.txt", "-v", "true"]),
987
- )
988
- })
989
- })
990
-
991
- test.describe(`${Commander.optionVersion.name} - version behavior`, () => {
992
- test.it("should include version in command definition", () => {
993
- const cmd = Commander
994
- .make({ name: "app", version: "1.0.0" })
995
- .optionVersion()
996
-
997
- test
998
- .expect(cmd.version)
999
- .toBe("1.0.0")
1000
- test
1001
- .expect(cmd.options.version)
1002
- .toBeDefined()
1003
- })
1004
-
1005
- test.it("should handle version without version option", () => {
1006
- const cmd = Commander
1007
- .make({ name: "app", version: "2.0.0" })
1008
-
1009
- test
1010
- .expect(cmd.version)
1011
- .toBe("2.0.0")
1012
- test
1013
- .expect(cmd.options["version"])
1014
- .toBeUndefined()
1015
- })
1016
-
1017
- test.it("should include version option in help", () => {
1018
- const cmd = Commander
1019
- .make({ name: "app", version: "1.0.0" })
1020
- .optionVersion()
1021
-
1022
- const help = Commander.help(cmd)
1023
-
1024
- test
1025
- .expect(help)
1026
- .toContain("--version")
1027
- test
1028
- .expect(help)
1029
- .toContain("-V")
1030
- })
1031
- })
1032
-
1033
- test.describe(`${Commander.help.name} - comprehensive`, () => {
1034
- test.it("should generate help with description", () => {
1035
- const cmd = Commander
1036
- .make({
1037
- name: "myapp",
1038
- description: "A test application",
1039
- })
1040
- .optionHelp()
1041
-
1042
- const help = Commander.help(cmd)
1043
-
1044
- test
1045
- .expect(help)
1046
- .toContain("A test application")
1047
- test
1048
- .expect(help)
1049
- .toContain("Usage: myapp [options]")
1050
- })
1051
-
1052
- test.it("should include all options in help", () => {
1053
- const cmd = Commander
1054
- .make({ name: "app" })
1055
- .option(
1056
- Commander
1057
- .option("--input", "-i")
1058
- .description("Input file")
1059
- .schema(Schema.String),
1060
- )
1061
- .option(
1062
- Commander
1063
- .option("--output", "-o")
1064
- .description("Output file")
1065
- .schema(Schema.String),
1066
- )
1067
- .optionHelp()
1068
-
1069
- const help = Commander.help(cmd)
1070
-
1071
- test
1072
- .expect(help)
1073
- .toContain("--input")
1074
- test
1075
- .expect(help)
1076
- .toContain("-i,")
1077
- test
1078
- .expect(help)
1079
- .toContain("Input file")
1080
- test
1081
- .expect(help)
1082
- .toContain("--output")
1083
- test
1084
- .expect(help)
1085
- .toContain("-o,")
1086
- test
1087
- .expect(help)
1088
- .toContain("Output file")
1089
- })
1090
-
1091
- test.it("should show subcommands in help", () => {
1092
- const subCmd = Commander
1093
- .make({ name: "init", description: "Initialize project" })
1094
- .handle(() => Effect.void)
1095
-
1096
- const cmd = Commander
1097
- .make({ name: "app" })
1098
- .subcommand(subCmd)
1099
- .optionHelp()
1100
-
1101
- const help = Commander.help(cmd)
1102
-
1103
- test
1104
- .expect(help)
1105
- .toContain("Commands:")
1106
- test
1107
- .expect(help)
1108
- .toContain("init")
1109
- test
1110
- .expect(help)
1111
- .toContain("Initialize project")
1112
- })
1113
-
1114
- test.it("should format option descriptions properly", () => {
1115
- const cmd = Commander
1116
- .make({ name: "app" })
1117
- .option(
1118
- Commander
1119
- .option("--config", "-c")
1120
- .description("Config file path")
1121
- .schema(Schema.String),
1122
- )
1123
-
1124
- const help = Commander.help(cmd)
1125
-
1126
- test
1127
- .expect(help)
1128
- .toContain("-c, --config")
1129
- test
1130
- .expect(help)
1131
- .toContain("Config file path")
1132
- })
1133
- })
1134
-
1135
- test.describe(`${Commander.subcommand.name} - comprehensive`, () => {
1136
- test.it("should add subcommand", () => {
1137
- const subCmd = Commander
1138
- .make({ name: "build" })
1139
- .option(
1140
- Commander
1141
- .option("--watch", "-w")
1142
- .default(false)
1143
- .schema(Schema.BooleanFromString),
1144
- )
1145
- .handle(() => Effect.void)
1146
-
1147
- const cmd = Commander
1148
- .make({ name: "app" })
1149
- .subcommand(subCmd)
1150
-
1151
- test
1152
- .expect(cmd.subcommands.length)
1153
- .toBe(1)
1154
- test
1155
- .expect(cmd.subcommands[0]!.command.name)
1156
- .toBe("build")
1157
- })
1158
-
1159
- test.it("should add multiple subcommands", () => {
1160
- const build = Commander
1161
- .make({ name: "build" })
1162
- .handle(() => Effect.void)
1163
-
1164
- const testCmd = Commander
1165
- .make({ name: "test" })
1166
- .handle(() => Effect.void)
1167
-
1168
- const cmd = Commander
1169
- .make({ name: "app" })
1170
- .subcommand(build)
1171
- .subcommand(testCmd)
1172
-
1173
- test
1174
- .expect(cmd.subcommands.length)
1175
- .toBe(2)
1176
- test
1177
- .expect(cmd.subcommands[0]!.command.name)
1178
- .toBe("build")
1179
- test
1180
- .expect(cmd.subcommands[1]!.command.name)
1181
- .toBe("test")
1182
- })
1183
-
1184
- test.it("should nest subcommands", () => {
1185
- const deploy = Commander
1186
- .make({ name: "deploy" })
1187
- .handle(() => Effect.void)
1188
-
1189
- const build = Commander
1190
- .make({ name: "build" })
1191
- .subcommand(deploy)
1192
- .handle(() => Effect.void)
1193
-
1194
- const cmd = Commander
1195
- .make({ name: "app" })
1196
- .subcommand(build)
1197
-
1198
- test
1199
- .expect(cmd.subcommands[0]!.command.subcommands.length)
1200
- .toBe(1)
1201
- test
1202
- .expect(cmd.subcommands[0]!.command.subcommands[0]!.command.name)
1203
- .toBe("deploy")
1204
- })
1205
- })
1206
-
1207
- test.describe("option types", () => {
1208
- test.it("should parse string option", async () => {
1209
- const cmd = Commander
1210
- .make({ name: "app" })
1211
- .option(
1212
- Commander
1213
- .option("--name")
1214
- .schema(Schema.String),
1215
- )
1216
-
1217
- const result = await Effect.runPromise(
1218
- Commander.parse(cmd, ["--name", "test"]),
1219
- )
1220
-
1221
- test
1222
- .expect(result.name)
1223
- .toBe("test")
1224
- test
1225
- .expect(typeof result.name)
1226
- .toBe("string")
1227
- })
1228
-
1229
- test.it("should parse number option", async () => {
1230
- const cmd = Commander
1231
- .make({ name: "app" })
1232
- .option(
1233
- Commander
1234
- .option("--count")
1235
- .schema(Commander.NumberFromString),
1236
- )
1237
-
1238
- const result = await Effect.runPromise(
1239
- Commander.parse(cmd, ["--count", "42"]),
1240
- )
1241
-
1242
- test
1243
- .expect(result.count)
1244
- .toBe(42)
1245
- test
1246
- .expect(typeof result.count)
1247
- .toBe("number")
1248
- })
1249
-
1250
- test.it("should parse boolean option", async () => {
1251
- const cmd = Commander
1252
- .make({ name: "app" })
1253
- .option(
1254
- Commander
1255
- .option("--enabled")
1256
- .schema(Schema.BooleanFromString),
1257
- )
1258
-
1259
- const result = await Effect.runPromise(
1260
- Commander.parse(cmd, ["--enabled", "true"]),
1261
- )
1262
-
1263
- test
1264
- .expect(result.enabled)
1265
- .toBe(true)
1266
- test
1267
- .expect(typeof result.enabled)
1268
- .toBe("boolean")
1269
- })
1270
-
1271
- test.it("should fail on invalid number", async () => {
1272
- const cmd = Commander
1273
- .make({ name: "app" })
1274
- .option(
1275
- Commander
1276
- .option("--count")
1277
- .schema(Commander.NumberFromString),
1278
- )
1279
-
1280
- const result = await Effect.runPromise(
1281
- Effect.either(Commander.parse(cmd, ["--count", "not-a-number"])),
1282
- )
1283
-
1284
- test
1285
- .expect(result._tag)
1286
- .toBe("Left")
1287
- })
1288
- })
1289
-
1290
- test.describe("complex scenarios", () => {
1291
- test.it("should handle mixed option types", async () => {
1292
- const cmd = Commander
1293
- .make({ name: "server" })
1294
- .option(
1295
- Commander
1296
- .option("--host", "-h")
1297
- .default("localhost")
1298
- .schema(Schema.String),
1299
- )
1300
- .option(
1301
- Commander
1302
- .option("--port", "-p")
1303
- .default(3000)
1304
- .schema(Commander.NumberFromString),
1305
- )
1306
- .option(
1307
- Commander
1308
- .option("--ssl")
1309
- .default(false)
1310
- .schema(Schema.BooleanFromString),
1311
- )
1312
- .option(
1313
- Commander
1314
- .option("--env", "-e")
1315
- .default("development")
1316
- .schema(
1317
- Schema.compose(
1318
- Schema.String,
1319
- Schema.Literal("development", "production", "test"),
1320
- ),
1321
- ),
1322
- )
1323
-
1324
- const result = await Effect.runPromise(
1325
- Commander.parse(cmd, [
1326
- "--host",
1327
- "0.0.0.0",
1328
- "-p",
1329
- "8080",
1330
- "--ssl",
1331
- "true",
1332
- "-e",
1333
- "production",
1334
- ]),
1335
- )
1336
-
1337
- test
1338
- .expect(result.host)
1339
- .toBe("0.0.0.0")
1340
- test
1341
- .expect(result.port)
1342
- .toBe(8080)
1343
- test
1344
- .expect(result.ssl)
1345
- .toBe(true)
1346
- test
1347
- .expect(result.env)
1348
- .toBe("production")
1349
- })
1350
-
1351
- test.it("should handle repeatable options", async () => {
1352
- const cmd = Commander
1353
- .make({ name: "app" })
1354
- .option(
1355
- Commander
1356
- .option("--tags", "-t")
1357
- .schema(Commander.repeatable(Schema.String)),
1358
- )
1359
-
1360
- const result = await Effect.runPromise(
1361
- Commander.parse(cmd, ["--tags", "foo,bar,baz"]),
1362
- )
1363
-
1364
- test
1365
- .expect(result.tags)
1366
- .toEqual(["foo", "bar", "baz"])
1367
- })
1368
-
1369
- test.it("should preserve option order independence", async () => {
1370
- const cmd = Commander
1371
- .make({ name: "app" })
1372
- .option(
1373
- Commander
1374
- .option("--first")
1375
- .schema(Schema.String),
1376
- )
1377
- .option(
1378
- Commander
1379
- .option("--second")
1380
- .schema(Schema.String),
1381
- )
1382
-
1383
- const result1 = await Effect.runPromise(
1384
- Commander.parse(cmd, ["--first", "1", "--second", "2"]),
1385
- )
1386
-
1387
- const result2 = await Effect.runPromise(
1388
- Commander.parse(cmd, ["--second", "2", "--first", "1"]),
1389
- )
1390
-
1391
- test
1392
- .expect(result1.first)
1393
- .toBe("1")
1394
- test
1395
- .expect(result1.second)
1396
- .toBe("2")
1397
- test
1398
- .expect(result2.first)
1399
- .toBe("1")
1400
- test
1401
- .expect(result2.second)
1402
- .toBe("2")
1403
- })
1404
-
1405
- test.it("should handle options with hyphens in names", async () => {
1406
- const cmd = Commander
1407
- .make({ name: "app" })
1408
- .option(
1409
- Commander
1410
- .option("--dry-run")
1411
- .default(false)
1412
- .schema(Schema.BooleanFromString),
1413
- )
1414
- .option(
1415
- Commander
1416
- .option("--no-cache")
1417
- .default(false)
1418
- .schema(Schema.BooleanFromString),
1419
- )
1420
-
1421
- const result = await Effect.runPromise(
1422
- Commander.parse(cmd, ["--dry-run", "true", "--no-cache", "true"]),
1423
- )
1424
-
1425
- test
1426
- .expect(result.dryRun)
1427
- .toBe(true)
1428
- test
1429
- .expect(result.noCache)
1430
- .toBe(true)
1431
- })
1432
- })
1433
-
1434
- test.describe("error handling", () => {
1435
- test.it("should fail gracefully on invalid option value", async () => {
1436
- const cmd = Commander
1437
- .make({ name: "app" })
1438
- .option(
1439
- Commander
1440
- .option("--port")
1441
- .schema(Commander.NumberFromString),
1442
- )
1443
-
1444
- const result = await Effect.runPromise(
1445
- Effect.either(Commander.parse(cmd, ["--port", "invalid"])),
1446
- )
1447
-
1448
- assert.strictEqual(result._tag, "Left")
1449
- test
1450
- .expect(result.left.message)
1451
- .toContain("Invalid value")
1452
- })
1453
-
1454
- test.it("should fail on invalid choice", async () => {
1455
- const cmd = Commander
1456
- .make({ name: "app" })
1457
- .option(
1458
- Commander
1459
- .option("--mode")
1460
- .schema(Schema.compose(Schema.String, Schema.Literal("dev", "prod"))),
1461
- )
1462
-
1463
- const result = await Effect.runPromise(
1464
- Effect.either(Commander.parse(cmd, ["--mode", "staging"])),
1465
- )
1466
-
1467
- test
1468
- .expect(result._tag)
1469
- .toBe("Left")
1470
- })
1471
- })
1472
-
1473
- test.describe("builder pattern", () => {
1474
- test.it("should chain option definitions fluently", () => {
1475
- const cmd = Commander
1476
- .make({ name: "app" })
1477
- .option(
1478
- Commander
1479
- .option("--input", "-i")
1480
- .description("Input file")
1481
- .schema(Schema.String),
1482
- )
1483
- .option(
1484
- Commander
1485
- .option("--output", "-o")
1486
- .description("Output file")
1487
- .default("out.txt")
1488
- .schema(Schema.String),
1489
- )
1490
-
1491
- test
1492
- .expect(cmd.options.input.description)
1493
- .toBe("Input file")
1494
- test
1495
- .expect(cmd.options.output.description)
1496
- .toBe("Output file")
1497
- test
1498
- .expect(cmd.options.output.defaultValue)
1499
- .toBe("out.txt")
1500
- })
1501
-
1502
- test.it("should chain description and default in any order", () => {
1503
- const cmd1 = Commander
1504
- .make({ name: "app" })
1505
- .option(
1506
- Commander
1507
- .option("--port")
1508
- .description("Port number")
1509
- .default(3000)
1510
- .schema(Commander.NumberFromString),
1511
- )
1512
-
1513
- const cmd2 = Commander
1514
- .make({ name: "app" })
1515
- .option(
1516
- Commander
1517
- .option("--port")
1518
- .default(3000)
1519
- .description("Port number")
1520
- .schema(Commander.NumberFromString),
1521
- )
1522
-
1523
- test
1524
- .expect(cmd1.options.port.description)
1525
- .toBe("Port number")
1526
- test
1527
- .expect(cmd1.options.port.defaultValue)
1528
- .toBe(3000)
1529
- test
1530
- .expect(cmd2.options.port.description)
1531
- .toBe("Port number")
1532
- test
1533
- .expect(cmd2.options.port.defaultValue)
1534
- .toBe(3000)
1535
- })
1536
-
1537
- test.it("should support method chaining with subcommands", () => {
1538
- const sub1 = Commander
1539
- .make({ name: "sub1" })
1540
- .handle(() => Effect.void)
1541
-
1542
- const sub2 = Commander
1543
- .make({ name: "sub2" })
1544
- .handle(() => Effect.void)
1545
-
1546
- const cmd = Commander
1547
- .make({ name: "app" })
1548
- .option(
1549
- Commander
1550
- .option("--global")
1551
- .schema(Schema.String),
1552
- )
1553
- .subcommand(sub1)
1554
- .subcommand(sub2)
1555
- .optionHelp()
1556
-
1557
- test
1558
- .expect(cmd.options.global)
1559
- .toBeDefined()
1560
- test
1561
- .expect(cmd.options.help)
1562
- .toBeDefined()
1563
- test
1564
- .expect(cmd.subcommands.length)
1565
- .toBe(2)
1566
- })
1567
- })
1568
-
1569
- test.describe("example scenario", () => {
1570
- test.it("should handle main command with subcommand", async () => {
1571
- const unhandledFormat = Commander.make({
1572
- name: "format",
1573
- description: "Format source files",
1574
- })
1575
-
1576
- const handledFormat = unhandledFormat
1577
- .option(
1578
- Commander
1579
- .option("--style", "-s")
1580
- .description("Code style to use")
1581
- .default("standard")
1582
- .schema(
1583
- Schema.compose(
1584
- Schema.String,
1585
- Schema.Literal("standard", "prettier", "biome"),
1586
- ),
1587
- ),
1588
- )
1589
- .handle((opts) => Effect.sync(() => ({ style: opts.style })))
1590
-
1591
- const main = Commander
1592
- .make({
1593
- name: "main",
1594
- description: "this is doing that",
1595
- version: "1.0.0",
1596
- })
1597
- .option(
1598
- Commander
1599
- .option("--source", "-s")
1600
- .schema(Schema.String),
1601
- )
1602
- .option(
1603
- Commander
1604
- .option("--verbose", "-v")
1605
- .description("Enable verbose output")
1606
- .default(false)
1607
- .schema(Schema.BooleanFromString),
1608
- )
1609
- .optionHelp()
1610
- .subcommand(handledFormat)
1611
- .handle((opts) => Effect.sync(() => opts))
1612
-
1613
- const resultMain = await Effect.runPromise(
1614
- Commander.parse(main, ["--source", "test.ts", "--verbose", "true"]),
1615
- )
1616
-
1617
- test
1618
- .expect(resultMain.source)
1619
- .toBe("test.ts")
1620
- test
1621
- .expect(resultMain.verbose)
1622
- .toBe(true)
1623
- test
1624
- .expect(resultMain.help)
1625
- .toBe(false)
1626
- test
1627
- .expect(main.subcommands.length)
1628
- .toBe(1)
1629
- test
1630
- .expect(main.subcommands[0]!.command.name)
1631
- .toBe("format")
1632
- test
1633
- .expect(main.subcommands[0]!.command.options.style)
1634
- .toBeDefined()
1635
- test
1636
- .expect(main.subcommands[0]!.command.options.style.defaultValue)
1637
- .toBe("standard")
1638
- })
1639
- })