elm-pages 3.0.12 → 3.0.14

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 (75) hide show
  1. package/README.md +2 -26
  2. package/codegen/elm-pages-codegen.cjs +10741 -10302
  3. package/generator/src/build.js +15 -5
  4. package/generator/src/cli.js +3 -5
  5. package/generator/src/compatibility-key.js +2 -2
  6. package/generator/src/dev-server.js +3 -0
  7. package/generator/src/render.js +681 -50
  8. package/generator/src/request-cache.js +13 -6
  9. package/generator/src/spinnies/index.js +200 -0
  10. package/generator/src/spinnies/utils.js +123 -0
  11. package/generator/src/validate-stream.js +25 -0
  12. package/generator/template/elm.json +4 -4
  13. package/generator/template/package.json +6 -6
  14. package/generator/template/script/elm.json +7 -8
  15. package/package.json +4 -3
  16. package/src/BackendTask/Custom.elm +38 -0
  17. package/src/BackendTask/Do.elm +233 -0
  18. package/src/BackendTask/File.elm +24 -9
  19. package/src/BackendTask/Glob.elm +208 -25
  20. package/src/BackendTask/Http.elm +32 -21
  21. package/src/BackendTask/Internal/Glob.elm +16 -4
  22. package/src/BackendTask/Stream.elm +1179 -0
  23. package/src/BackendTask.elm +214 -7
  24. package/src/Pages/Internal/Platform/CompatibilityKey.elm +1 -1
  25. package/src/Pages/Internal/Platform.elm +11 -2
  26. package/src/Pages/Script/Spinner.elm +505 -0
  27. package/src/Pages/Script.elm +199 -2
  28. package/src/Pages/StaticHttp/Request.elm +7 -0
  29. package/src/RequestsAndPending.elm +1 -1
  30. package/src/Scaffold/Form.elm +2 -3
  31. package/src/TerminalText.elm +8 -0
  32. package/src/Vendored/Result/Extra.elm +75 -0
  33. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmi +0 -0
  34. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmo +0 -0
  35. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateDataTest.elmi +0 -0
  36. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateDataTest.elmo +0 -0
  37. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Reporter.elmi +0 -0
  38. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Reporter.elmo +0 -0
  39. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Runner.elmi +0 -0
  40. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Runner.elmo +0 -0
  41. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
  42. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
  43. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/lock +0 -0
  44. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
  45. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm.json +0 -1
  46. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +0 -7900
  47. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Runner.elm.js +0 -28657
  48. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_runner.js +0 -110
  49. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_supervisor.js +0 -187
  50. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/package.json +0 -1
  51. package/generator/dead-code-review/elm-stuff/tests-0.19.1/src/Reporter.elm +0 -26
  52. package/generator/dead-code-review/elm-stuff/tests-0.19.1/src/Runner.elm +0 -62
  53. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Internal-RoutePattern.elmi +0 -0
  54. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Internal-RoutePattern.elmo +0 -0
  55. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolations.elmi +0 -0
  56. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolations.elmo +0 -0
  57. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolationsTest.elmi +0 -0
  58. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolationsTest.elmo +0 -0
  59. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Reporter.elmi +0 -0
  60. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Reporter.elmo +0 -0
  61. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Runner.elmi +0 -0
  62. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Runner.elmo +0 -0
  63. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
  64. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
  65. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/lock +0 -0
  66. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
  67. package/generator/review/elm-stuff/tests-0.19.1/elm.json +0 -1
  68. package/generator/review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +0 -7900
  69. package/generator/review/elm-stuff/tests-0.19.1/js/Runner.elm.js +0 -30511
  70. package/generator/review/elm-stuff/tests-0.19.1/js/node_runner.js +0 -110
  71. package/generator/review/elm-stuff/tests-0.19.1/js/node_supervisor.js +0 -187
  72. package/generator/review/elm-stuff/tests-0.19.1/js/package.json +0 -1
  73. package/generator/review/elm-stuff/tests-0.19.1/src/Reporter.elm +0 -26
  74. package/generator/review/elm-stuff/tests-0.19.1/src/Runner.elm +0 -62
  75. package/src/Result/Extra.elm +0 -26
@@ -0,0 +1,1179 @@
1
+ module BackendTask.Stream exposing
2
+ ( Stream
3
+ , pipe
4
+ , fileRead, fileWrite, fromString, http, httpWithInput, stdin, stdout, stderr
5
+ , read, readJson, readMetadata, run
6
+ , Error(..)
7
+ , command
8
+ , commandWithOptions
9
+ , StderrOutput(..)
10
+ , CommandOptions, defaultCommandOptions, allowNon0Status, withOutput, withTimeout
11
+ , gzip, unzip
12
+ , customRead, customWrite, customDuplex
13
+ , customReadWithMeta, customTransformWithMeta, customWriteWithMeta
14
+ )
15
+
16
+ {-| A `Stream` represents a flow of data through a pipeline.
17
+
18
+ It is typically
19
+
20
+ - An input source, or Readable Stream (`Stream { read : (), write : Never }`)
21
+ - An output destination, or Writable Stream (`Stream { read : Never, write : () }`)
22
+ - And (optionally) a series of transformations in between, or Duplex Streams (`Stream { read : (), write : () }`)
23
+
24
+ For example, you could have a stream that
25
+
26
+ - Reads from a file [`fileRead`](#fileRead)
27
+ - Unzips the contents [`unzip`](#unzip)
28
+ - Runs a shell command on the contents [`command`](#command)
29
+ - And writes the result to a network connection [`httpWithInput`](#httpWithInput)
30
+
31
+ For example,
32
+
33
+ import BackendTask.Stream as Stream exposing (Stream)
34
+
35
+ example =
36
+ Stream.fileRead "data.txt"
37
+ |> Stream.unzip
38
+ |> Stream.command "wc" [ "-l" ]
39
+ |> Stream.httpWithInput
40
+ { url = "http://example.com"
41
+ , method = "POST"
42
+ , headers = []
43
+ , retries = Nothing
44
+ , timeoutInMs = Nothing
45
+ }
46
+ |> Stream.run
47
+
48
+ End example
49
+
50
+ @docs Stream
51
+
52
+ @docs pipe
53
+
54
+ @docs fileRead, fileWrite, fromString, http, httpWithInput, stdin, stdout, stderr
55
+
56
+
57
+ ## Running Streams
58
+
59
+ @docs read, readJson, readMetadata, run
60
+
61
+ @docs Error
62
+
63
+
64
+ ## Shell Commands
65
+
66
+ Note that the commands do not execute through a shell but rather directly executes a child process. That means that
67
+ special shell syntax will have no effect, but instead will be interpreted as literal characters in arguments to the command.
68
+
69
+ So instead of `grep error < log.txt`, you would use
70
+
71
+ module GrepErrors exposing (run)
72
+
73
+ import BackendTask
74
+ import BackendTask.Stream as Stream
75
+ import Pages.Script as Script exposing (Script)
76
+
77
+ run : Script
78
+ run =
79
+ Script.withoutCliOptions
80
+ (Stream.fileRead "log.txt"
81
+ |> Stream.pipe (Stream.command "grep" [ "error" ])
82
+ |> Stream.stdout
83
+ |> Stream.run
84
+ )
85
+
86
+ @docs command
87
+
88
+
89
+ ## Command Options
90
+
91
+ @docs commandWithOptions
92
+
93
+ @docs StderrOutput
94
+
95
+ @docs CommandOptions, defaultCommandOptions, allowNon0Status, withOutput, withTimeout
96
+
97
+
98
+ ## Command Output Strategies
99
+
100
+ There are 3 things that effect the output behavior of a command:
101
+
102
+ - The verbosity of the `BackendTask` context ([`BackendTask.quiet`](BackendTask#quiet))
103
+ - Whether the `Stream` output is ignored ([`Stream.run`](#run)), or read ([`Stream.read`](#read))
104
+ - [`withOutput`](#withOutput) (allows you to use stdout, stderr, or both)
105
+
106
+ With `BackendTask.quiet`, the output of the command will not print as it runs, but you still read it in Elm if you read the `Stream` (instead of using [`Stream.run`](#run)).
107
+
108
+ There are 3 ways to handle the output of a command:
109
+
110
+ 1. Read the output but don't print
111
+ 2. Print the output but don't read
112
+ 3. Ignore the output
113
+
114
+ To read the output (1), use [`Stream.read`](#read) or [`Stream.readJson`](#readJson). This will give you the output as a String or JSON object.
115
+ Regardless of whether you use `BackendTask.quiet`, the output will be read and returned to Elm.
116
+
117
+ To let the output from the command natively print to the console (2), use [`Stream.run`](#run) without setting `BackendTask.quiet`. Based on
118
+ the command's `withOutput` configuration, either stderr, stdout, or both will print to the console. The native output will
119
+ sometimes be treated more like running the command directly in the terminal, for example `elm make` will print progress
120
+ messages which will be cleared and updated in place.
121
+
122
+ To ignore the output (3), use [`Stream.run`](#run) with `BackendTask.quiet`. This will run the command without printing anything to the console.
123
+ You can also use [`Stream.read`](#read) and ignore the captured output, but this is less efficient than using `BackendTask.quiet` with `Stream.run`.
124
+
125
+
126
+ ## Compression Helpers
127
+
128
+ module CompressionDemo exposing (run)
129
+
130
+ import BackendTask
131
+ import BackendTask.Stream as Stream
132
+ import Pages.Script as Script exposing (Script)
133
+
134
+ run : Script
135
+ run =
136
+ Script.withoutCliOptions
137
+ (Stream.fileRead "elm.json"
138
+ |> Stream.pipe Stream.gzip
139
+ |> Stream.pipe (Stream.fileWrite "elm.json.gz")
140
+ |> Stream.run
141
+ |> BackendTask.andThen
142
+ (\_ ->
143
+ Stream.fileRead "elm.json.gz"
144
+ |> Stream.pipe Stream.unzip
145
+ |> Stream.pipe Stream.stdout
146
+ |> Stream.run
147
+ )
148
+ )
149
+
150
+ @docs gzip, unzip
151
+
152
+
153
+ ## Custom Streams
154
+
155
+ [`BackendTask.Custom`](BackendTask-Custom) lets you define custom `BackendTask`s from async NodeJS functions in your `custom-backend-task` file.
156
+ Similarly, you can define custom streams with async functions in your `custom-backend-task` file, returning native NodeJS Streams, and optionally functions to extract metadata.
157
+
158
+ ```js
159
+ import { Writable, Transform, Readable } from "node:stream";
160
+
161
+ export async function upperCaseStream(input, { cwd, env, quiet }) {
162
+ return {
163
+ metadata: () => "Hi! I'm metadata from upperCaseStream!",
164
+ stream: new Transform({
165
+ transform(chunk, encoding, callback) {
166
+ callback(null, chunk.toString().toUpperCase());
167
+ },
168
+ }),
169
+ };
170
+ }
171
+
172
+ export async function customReadStream(input) {
173
+ return new Readable({
174
+ read(size) {
175
+ this.push("Hello from customReadStream!");
176
+ this.push(null);
177
+ },
178
+ });
179
+ }
180
+
181
+ export async function customWriteStream(input, { cwd, env, quiet }) {
182
+ return {
183
+ stream: new Writable({
184
+ write(chunk, encoding, callback) {
185
+ console.error("...received chunk...");
186
+ console.log(chunk.toString());
187
+ callback();
188
+ },
189
+ }),
190
+ metadata: () => {
191
+ return "Hi! I'm metadata from customWriteStream!";
192
+ },
193
+ };
194
+ }
195
+ ```
196
+
197
+ module CustomStreamDemo exposing (run)
198
+
199
+ import BackendTask
200
+ import BackendTask.Stream as Stream
201
+ import Pages.Script as Script exposing (Script)
202
+
203
+ run : Script
204
+ run =
205
+ Script.withoutCliOptions
206
+ (Stream.customRead "customReadStream" Encode.null
207
+ |> Stream.pipe (Stream.customDuplex "upperCaseStream" Encode.null)
208
+ |> Stream.pipe (Stream.customWrite "customWriteStream" Encode.null)
209
+ |> Stream.run
210
+ )
211
+
212
+ To extract the metadata from the custom stream, you can use the `...WithMeta` functions:
213
+
214
+ module CustomStreamDemoWithMeta exposing (run)
215
+
216
+ import BackendTask
217
+ import BackendTask.Stream as Stream
218
+ import Pages.Script as Script exposing (Script)
219
+
220
+ run : Script
221
+ run =
222
+ Script.withoutCliOptions
223
+ (Stream.customReadWithMeta "customReadStream" Encode.null Decode.succeed
224
+ |> Stream.pipe (Stream.customTransformWithMeta "upperCaseStream" Encode.null Decode.succeed)
225
+ |> Stream.readMetadata
226
+ |> BackendTask.allowFatal
227
+ |> BackendTask.andThen
228
+ (\metadata ->
229
+ Script.log ("Metadata: " ++ metadata)
230
+ )
231
+ )
232
+ --> Script.log "Metadata: Hi! I'm metadata from upperCaseStream!"
233
+
234
+ @docs customRead, customWrite, customDuplex
235
+
236
+
237
+ ### With Metadata Decoders
238
+
239
+ @docs customReadWithMeta, customTransformWithMeta, customWriteWithMeta
240
+
241
+ -}
242
+
243
+ import BackendTask exposing (BackendTask)
244
+ import BackendTask.Http exposing (Body)
245
+ import BackendTask.Internal.Request
246
+ import Base64
247
+ import FatalError exposing (FatalError)
248
+ import Json.Decode as Decode exposing (Decoder)
249
+ import Json.Encode as Encode
250
+ import Pages.Internal.StaticHttpBody
251
+ import RequestsAndPending
252
+ import TerminalText
253
+
254
+
255
+ {-| Once you've defined a `Stream`, it can be turned into a `BackendTask` that will run it (and optionally read its output and metadata).
256
+ -}
257
+ type Stream error metadata kind
258
+ = Stream ( String, Decoder (Result (Recoverable error) metadata) ) (List StreamPart)
259
+
260
+
261
+ type alias Recoverable error =
262
+ { fatal : FatalError, recoverable : error }
263
+
264
+
265
+ mapRecoverable : Maybe body -> { a | fatal : b, recoverable : c } -> { fatal : b, recoverable : Error c body }
266
+ mapRecoverable maybeBody { fatal, recoverable } =
267
+ { fatal = fatal
268
+ , recoverable = CustomError recoverable maybeBody
269
+ }
270
+
271
+
272
+ type StreamPart
273
+ = StreamPart String (List ( String, Encode.Value ))
274
+
275
+
276
+ single : ( String, Decoder (Result (Recoverable error) metadata) ) -> String -> List ( String, Encode.Value ) -> Stream error metadata kind
277
+ single decoder inner1 inner2 =
278
+ Stream decoder [ StreamPart inner1 inner2 ]
279
+
280
+
281
+ unit : ( String, Decoder (Result (Recoverable ()) ()) )
282
+ unit =
283
+ ( "unit", Decode.succeed (Ok ()) )
284
+
285
+
286
+ {-| The `stdin` from the process. When you execute an `elm-pages` script, this will be the value that is piped in to it. For example, given this script module:
287
+
288
+ module CountLines exposing (run)
289
+
290
+ import BackendTask
291
+ import BackendTask.Stream as Stream
292
+ import Pages.Script as Script exposing (Script)
293
+
294
+ run : Script
295
+ run =
296
+ Script.withoutCliOptions
297
+ (Stream.stdin
298
+ |> Stream.read
299
+ |> BackendTask.allowFatal
300
+ |> BackendTask.andThen
301
+ (\{ body } ->
302
+ body
303
+ |> String.lines
304
+ |> List.length
305
+ |> String.fromInt
306
+ |> Script.log
307
+ )
308
+ )
309
+
310
+ If you run the script without any stdin, it will wait until stdin is closed.
311
+
312
+ ```shell
313
+ elm-pages run script/src/CountLines.elm
314
+ # pressing ctrl-d (or your platform-specific way of closing stdin) will print the number of lines in the input
315
+ ```
316
+
317
+ Or you can pipe to it and it will read that input:
318
+
319
+ ```shell
320
+ ls | elm-pages run script/src/CountLines.elm
321
+ # prints the number of files in the current directory
322
+ ```
323
+
324
+ -}
325
+ stdin : Stream () () { read : (), write : Never }
326
+ stdin =
327
+ single unit "stdin" []
328
+
329
+
330
+ {-| Streaming through to stdout can be a convenient way to print a pipeline directly without going through to Elm.
331
+
332
+ module UnzipFile exposing (run)
333
+
334
+ import BackendTask
335
+ import BackendTask.Stream as Stream
336
+ import Pages.Script as Script exposing (Script)
337
+
338
+ run : Script
339
+ run =
340
+ Script.withoutCliOptions
341
+ (Stream.fileRead "data.gzip.txt"
342
+ |> Stream.pipe Stream.unzip
343
+ |> Stream.pipe Stream.stdout
344
+ |> Stream.run
345
+ |> BackendTask.allowFatal
346
+ )
347
+
348
+ -}
349
+ stdout : Stream () () { read : Never, write : () }
350
+ stdout =
351
+ single unit "stdout" []
352
+
353
+
354
+ {-| Similar to [`stdout`](#stdout), but writes to `stderr` instead.
355
+ -}
356
+ stderr : Stream () () { read : Never, write : () }
357
+ stderr =
358
+ single unit "stderr" []
359
+
360
+
361
+ {-| Open a file's contents as a Stream.
362
+
363
+ module ReadFile exposing (run)
364
+
365
+ import BackendTask
366
+ import BackendTask.Stream as Stream
367
+ import Pages.Script as Script exposing (Script)
368
+
369
+ run : Script
370
+ run =
371
+ Script.withoutCliOptions
372
+ (Stream.fileRead "elm.json"
373
+ |> Stream.readJson (Decode.field "source-directories" (Decode.list Decode.string))
374
+ |> BackendTask.allowFatal
375
+ |> BackendTask.andThen
376
+ (\{ body } ->
377
+ Script.log
378
+ ("The source directories are: "
379
+ ++ String.join ", " body
380
+ )
381
+ )
382
+ )
383
+
384
+ If you want to read a file but don't need to use any of the other Stream functions, you can use [`BackendTask.File.read`](BackendTask-File#rawFile) instead.
385
+
386
+ -}
387
+ fileRead : String -> Stream () () { read : (), write : Never }
388
+ fileRead path =
389
+ -- TODO revisit the error type instead of ()?
390
+ single unit "fileRead" [ ( "path", Encode.string path ) ]
391
+
392
+
393
+ {-| Write a Stream to a file.
394
+
395
+ module WriteFile exposing (run)
396
+
397
+ import BackendTask
398
+ import BackendTask.Stream as Stream
399
+ import Pages.Script as Script exposing (Script)
400
+
401
+ run : Script
402
+ run =
403
+ Script.withoutCliOptions
404
+ (Stream.fileRead "logs.txt"
405
+ |> Stream.pipe (Stream.command "grep" [ "error" ])
406
+ |> Stream.pipe (Stream.fileWrite "errors.txt")
407
+ )
408
+
409
+ -}
410
+ fileWrite : String -> Stream () () { read : Never, write : () }
411
+ fileWrite path =
412
+ single unit "fileWrite" [ ( "path", Encode.string path ) ]
413
+
414
+
415
+ {-| Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `ReadableStream` it returns.
416
+ -}
417
+ customRead : String -> Encode.Value -> Stream () () { read : (), write : Never }
418
+ customRead name input =
419
+ single unit
420
+ "customRead"
421
+ [ ( "portName", Encode.string name )
422
+ , ( "input", input )
423
+ ]
424
+
425
+
426
+ {-| Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `WritableStream` it returns.
427
+ -}
428
+ customWrite : String -> Encode.Value -> Stream () () { read : Never, write : () }
429
+ customWrite name input =
430
+ single unit
431
+ "customWrite"
432
+ [ ( "portName", Encode.string name )
433
+ , ( "input", input )
434
+ ]
435
+
436
+
437
+ {-| Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `DuplexStream` it returns.
438
+ -}
439
+ customReadWithMeta :
440
+ String
441
+ -> Encode.Value
442
+ -> Decoder (Result { fatal : FatalError, recoverable : error } metadata)
443
+ -> Stream error metadata { read : (), write : Never }
444
+ customReadWithMeta name input decoder =
445
+ single ( "", decoder )
446
+ "customRead"
447
+ [ ( "portName", Encode.string name )
448
+ , ( "input", input )
449
+ ]
450
+
451
+
452
+ {-| Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `WritableStream` and metadata function it returns.
453
+ -}
454
+ customWriteWithMeta :
455
+ String
456
+ -> Encode.Value
457
+ -> Decoder (Result { fatal : FatalError, recoverable : error } metadata)
458
+ -> Stream error metadata { read : Never, write : () }
459
+ customWriteWithMeta name input decoder =
460
+ single ( "", decoder )
461
+ "customWrite"
462
+ [ ( "portName", Encode.string name )
463
+ , ( "input", input )
464
+ ]
465
+
466
+
467
+ {-| Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `DuplexStream` and metadata function it returns.
468
+ -}
469
+ customTransformWithMeta :
470
+ String
471
+ -> Encode.Value
472
+ -> Decoder (Result { fatal : FatalError, recoverable : error } metadata)
473
+ -> Stream error metadata { read : (), write : () }
474
+ customTransformWithMeta name input decoder =
475
+ single ( "", decoder )
476
+ "customDuplex"
477
+ [ ( "portName", Encode.string name )
478
+ , ( "input", input )
479
+ ]
480
+
481
+
482
+ {-| Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `DuplexStream` it returns.
483
+ -}
484
+ customDuplex : String -> Encode.Value -> Stream () () { read : (), write : () }
485
+ customDuplex name input =
486
+ single unit
487
+ "customDuplex"
488
+ [ ( "portName", Encode.string name )
489
+ , ( "input", input )
490
+ ]
491
+
492
+
493
+ {-| Transforms the input with gzip compression.
494
+
495
+ Under the hood this builds a Stream using Node's [`zlib.createGzip`](https://nodejs.org/api/zlib.html#zlibcreategzipoptions).
496
+
497
+ -}
498
+ gzip : Stream () () { read : (), write : () }
499
+ gzip =
500
+ single unit "gzip" []
501
+
502
+
503
+ {-| Transforms the input by auto-detecting the header and decompressing either a Gzip- or Deflate-compressed stream.
504
+
505
+ Under the hood, this builds a Stream using Node's [`zlib.createUnzip`](https://nodejs.org/api/zlib.html#zlibcreateunzip).
506
+
507
+ -}
508
+ unzip : Stream () () { read : (), write : () }
509
+ unzip =
510
+ single unit "unzip" []
511
+
512
+
513
+ {-| Streams the data from the input stream as the body of the HTTP request. The HTTP response body becomes the output stream.
514
+ -}
515
+ httpWithInput :
516
+ { url : String
517
+ , method : String
518
+ , headers : List ( String, String )
519
+ , retries : Maybe Int
520
+ , timeoutInMs : Maybe Int
521
+ }
522
+ -> Stream BackendTask.Http.Error BackendTask.Http.Metadata { read : (), write : () }
523
+ httpWithInput string =
524
+ -- Pages.Internal.StaticHttpBody
525
+ single httpMetadataDecoder
526
+ "httpWrite"
527
+ [ ( "url", Encode.string string.url )
528
+ , ( "method", Encode.string string.method )
529
+ , ( "headers", Encode.list (\( key, value ) -> Encode.object [ ( "key", Encode.string key ), ( "value", Encode.string value ) ]) string.headers )
530
+ , ( "retries", nullable Encode.int string.retries )
531
+ , ( "timeoutInMs", nullable Encode.int string.timeoutInMs )
532
+ ]
533
+
534
+
535
+ {-| Uses a regular HTTP request body (not a `Stream`). Streams the HTTP response body.
536
+
537
+ If you want to pass a stream as the request body, use [`httpWithInput`](#httpWithInput) instead.
538
+
539
+ If you don't need to stream the response body, you can use the functions from [`BackendTask.Http`](BackendTask-Http) instead.
540
+
541
+ -}
542
+ http :
543
+ { url : String
544
+ , method : String
545
+ , headers : List ( String, String )
546
+ , body : Body
547
+ , retries : Maybe Int
548
+ , timeoutInMs : Maybe Int
549
+ }
550
+ -> Stream BackendTask.Http.Error BackendTask.Http.Metadata { read : (), write : Never }
551
+ http request_ =
552
+ single httpMetadataDecoder
553
+ "httpWrite"
554
+ [ ( "url", Encode.string request_.url )
555
+ , ( "method", Encode.string request_.method )
556
+ , ( "headers", Encode.list (\( key, value ) -> Encode.object [ ( "key", Encode.string key ), ( "value", Encode.string value ) ]) request_.headers )
557
+ , ( "body", Pages.Internal.StaticHttpBody.encode request_.body )
558
+ , ( "retries", nullable Encode.int request_.retries )
559
+ , ( "timeoutInMs", nullable Encode.int request_.timeoutInMs )
560
+ ]
561
+
562
+
563
+ httpMetadataDecoder : ( String, Decoder (Result (Recoverable BackendTask.Http.Error) BackendTask.Http.Metadata) )
564
+ httpMetadataDecoder =
565
+ ( "http"
566
+ , RequestsAndPending.responseDecoder
567
+ |> Decode.map
568
+ (\thing ->
569
+ toBadResponse (Just thing) RequestsAndPending.WhateverBody
570
+ |> Maybe.map
571
+ (\httpError ->
572
+ FatalError.recoverable
573
+ (errorToString httpError)
574
+ httpError
575
+ |> Err
576
+ )
577
+ |> Maybe.withDefault (Ok thing)
578
+ )
579
+ )
580
+
581
+
582
+ {-| You can build up a pipeline of streams by using the `pipe` function.
583
+
584
+ The stream you are piping to must be writable (`{ write : () }`),
585
+ and the stream you are piping from must be readable (`{ read : () }`).
586
+
587
+ module HelloWorld exposing (run)
588
+
589
+ import BackendTask
590
+ import BackendTask.Stream as Stream
591
+ import Pages.Script as Script exposing (Script)
592
+
593
+ run : Script
594
+ run =
595
+ Script.withoutCliOptions
596
+ (Stream.fromString "Hello, World!"
597
+ |> Stream.stdout
598
+ |> Stream.run
599
+ )
600
+
601
+ -}
602
+ pipe :
603
+ Stream errorTo metaTo { read : toReadable, write : () }
604
+ -> Stream errorFrom metaFrom { read : (), write : fromWriteable }
605
+ -> Stream errorTo metaTo { read : toReadable, write : fromWriteable }
606
+ pipe (Stream decoderTo to) (Stream _ from) =
607
+ Stream decoderTo (from ++ to)
608
+
609
+
610
+ {-| Gives a `BackendTask` to execute the `Stream`, ignoring its body and metadata.
611
+
612
+ This is useful if you only want the side-effect from the `Stream` and don't need to programmatically use its
613
+ output. For example, if the end result you want is:
614
+
615
+ - Printing to the console
616
+ - Writing to a file
617
+ - Making an HTTP request
618
+
619
+ If you need to read the output of the `Stream`, use [`read`](#read), [`readJson`](#readJson), or [`readMetadata`](#readMetadata) instead.
620
+
621
+ -}
622
+ run : Stream error metadata kind -> BackendTask FatalError ()
623
+ run stream =
624
+ -- TODO give access to recoverable error here instead of just FatalError
625
+ BackendTask.Internal.Request.request
626
+ { name = "stream"
627
+ , body = BackendTask.Http.jsonBody (pipelineEncoder stream "none")
628
+ , expect =
629
+ BackendTask.Http.expectJson
630
+ (Decode.oneOf
631
+ [ Decode.field "error" Decode.string
632
+ |> Decode.andThen
633
+ (\error ->
634
+ Decode.succeed
635
+ (Err
636
+ (FatalError.recoverable
637
+ { title = "Stream Error"
638
+ , body = error
639
+ }
640
+ (StreamError error)
641
+ )
642
+ )
643
+ )
644
+ , Decode.succeed (Ok ())
645
+ ]
646
+ )
647
+ }
648
+ |> BackendTask.andThen BackendTask.fromResult
649
+ |> BackendTask.allowFatal
650
+
651
+
652
+ pipelineEncoder : Stream error metadata kind -> String -> Encode.Value
653
+ pipelineEncoder (Stream _ parts) kind =
654
+ Encode.object
655
+ [ ( "kind", Encode.string kind )
656
+ , ( "parts"
657
+ , Encode.list
658
+ (\(StreamPart name data) ->
659
+ Encode.object (( "name", Encode.string name ) :: data)
660
+ )
661
+ parts
662
+ )
663
+ ]
664
+
665
+
666
+ {-| A handy way to turn either a hardcoded String, or any other value from Elm into a Stream.
667
+
668
+ module HelloWorld exposing (run)
669
+
670
+ import BackendTask
671
+ import BackendTask.Stream as Stream
672
+ import Pages.Script as Script exposing (Script)
673
+
674
+ run : Script
675
+ run =
676
+ Script.withoutCliOptions
677
+ (Stream.fromString "Hello, World!"
678
+ |> Stream.stdout
679
+ |> Stream.run
680
+ |> BackendTask.allowFatal
681
+ )
682
+
683
+ A more programmatic use of `fromString` to use the result of a previous `BackendTask` to a `Stream`:
684
+
685
+ module HelloWorld exposing (run)
686
+
687
+ import BackendTask
688
+ import BackendTask.Stream as Stream
689
+ import Pages.Script as Script exposing (Script)
690
+
691
+ run : Script
692
+ run =
693
+ Script.withoutCliOptions
694
+ (Glob.fromString "src/**/*.elm"
695
+ |> BackendTask.andThen
696
+ (\elmFiles ->
697
+ elmFiles
698
+ |> String.join ", "
699
+ |> Stream.fromString
700
+ |> Stream.pipe Stream.stdout
701
+ |> Stream.run
702
+ )
703
+ )
704
+
705
+ -}
706
+ fromString : String -> Stream () () { read : (), write : Never }
707
+ fromString string =
708
+ single unit "fromString" [ ( "string", Encode.string string ) ]
709
+
710
+
711
+ {-| Running or reading a `Stream` can give one of two kinds of error:
712
+
713
+ - `StreamError String` - when something in the middle of the stream fails
714
+ - `CustomError error body` - when the `Stream` fails with a custom error
715
+
716
+ A `CustomError` can only come from the final part of the stream.
717
+
718
+ You can define your own custom errors by decoding metadata to an `Err` in the `...WithMeta` helpers.
719
+
720
+ -}
721
+ type Error error body
722
+ = StreamError String
723
+ | CustomError error (Maybe body)
724
+
725
+
726
+ {-| Read the body of the `Stream` as text.
727
+ -}
728
+ read :
729
+ Stream error metadata { read : (), write : write }
730
+ -> BackendTask { fatal : FatalError, recoverable : Error error String } { metadata : metadata, body : String }
731
+ read ((Stream ( _, decoder ) _) as stream) =
732
+ BackendTask.Internal.Request.request
733
+ { name = "stream"
734
+
735
+ -- TODO pass in `decoderName` to pipelineEncoder
736
+ , body = BackendTask.Http.jsonBody (pipelineEncoder stream "text")
737
+ , expect =
738
+ BackendTask.Http.expectJson
739
+ (decodeLog
740
+ (Decode.oneOf
741
+ [ Decode.field "error" Decode.string
742
+ |> Decode.andThen
743
+ (\error ->
744
+ Decode.succeed
745
+ (Err
746
+ (FatalError.recoverable
747
+ { title = "Stream Error"
748
+ , body = error
749
+ }
750
+ (StreamError error)
751
+ )
752
+ )
753
+ )
754
+ , decodeLog (Decode.field "metadata" decoder)
755
+ |> Decode.andThen
756
+ (\result ->
757
+ case result of
758
+ Ok metadata ->
759
+ Decode.map
760
+ (\body ->
761
+ Ok
762
+ { metadata = metadata
763
+ , body = body
764
+ }
765
+ )
766
+ (Decode.field "body" Decode.string)
767
+
768
+ Err error ->
769
+ Decode.field "body" Decode.string
770
+ |> Decode.maybe
771
+ |> Decode.map
772
+ (\body ->
773
+ error |> mapRecoverable body |> Err
774
+ )
775
+ )
776
+ , Decode.succeed
777
+ (Err
778
+ (FatalError.recoverable
779
+ { title = "Stream Error", body = "No metadata" }
780
+ (StreamError "No metadata")
781
+ )
782
+ )
783
+ ]
784
+ )
785
+ )
786
+ }
787
+ |> BackendTask.andThen BackendTask.fromResult
788
+
789
+
790
+ {-| Ignore the body of the `Stream`, while capturing the metadata from the final part of the Stream.
791
+ -}
792
+ readMetadata :
793
+ Stream error metadata { read : read, write : write }
794
+ -> BackendTask { fatal : FatalError, recoverable : Error error String } metadata
795
+ readMetadata ((Stream ( _, decoder ) _) as stream) =
796
+ BackendTask.Internal.Request.request
797
+ { name = "stream"
798
+
799
+ -- TODO pass in `decoderName` to pipelineEncoder
800
+ , body = BackendTask.Http.jsonBody (pipelineEncoder stream "none")
801
+ , expect =
802
+ BackendTask.Http.expectJson
803
+ (decodeLog
804
+ (Decode.oneOf
805
+ [ Decode.field "error" Decode.string
806
+ |> Decode.andThen
807
+ (\error ->
808
+ Decode.succeed
809
+ (Err
810
+ (FatalError.recoverable
811
+ { title = "Stream Error"
812
+ , body = error
813
+ }
814
+ (StreamError error)
815
+ )
816
+ )
817
+ )
818
+ , decodeLog (Decode.field "metadata" decoder)
819
+ |> Decode.map
820
+ (\result ->
821
+ case result of
822
+ Ok metadata ->
823
+ Ok metadata
824
+
825
+ Err error ->
826
+ error |> mapRecoverable Nothing |> Err
827
+ )
828
+ , Decode.succeed
829
+ (Err
830
+ (FatalError.recoverable
831
+ { title = "Stream Error", body = "No metadata" }
832
+ (StreamError "No metadata")
833
+ )
834
+ )
835
+ ]
836
+ )
837
+ )
838
+ }
839
+ |> BackendTask.andThen BackendTask.fromResult
840
+
841
+
842
+ decodeLog : Decoder a -> Decoder a
843
+ decodeLog decoder =
844
+ Decode.value
845
+ |> Decode.andThen
846
+ (\_ ->
847
+ --let
848
+ -- _ =
849
+ -- Debug.log "VALUE" (Encode.encode 2 value)
850
+ --in
851
+ decoder
852
+ )
853
+
854
+
855
+ {-| Read the body of the `Stream` as JSON.
856
+
857
+ module ReadJson exposing (run)
858
+
859
+ import BackendTask
860
+ import BackendTask.Stream as Stream
861
+ import Json.Decode as Decode
862
+ import Pages.Script as Script exposing (Script)
863
+
864
+ run : Script
865
+ run =
866
+ Script.withoutCliOptions
867
+ (Stream.fileRead "data.json"
868
+ |> Stream.readJson (Decode.field "name" Decode.string)
869
+ |> BackendTask.allowFatal
870
+ |> BackendTask.andThen
871
+ (\{ body } ->
872
+ Script.log ("The name is: " ++ body)
873
+ )
874
+ )
875
+
876
+ -}
877
+ readJson :
878
+ Decoder value
879
+ -> Stream error metadata { read : (), write : write }
880
+ -> BackendTask { fatal : FatalError, recoverable : Error error value } { metadata : metadata, body : value }
881
+ readJson decoder ((Stream ( _, metadataDecoder ) _) as stream) =
882
+ BackendTask.Internal.Request.request
883
+ { name = "stream"
884
+ , body = BackendTask.Http.jsonBody (pipelineEncoder stream "json")
885
+ , expect =
886
+ BackendTask.Http.expectJson
887
+ (Decode.field "metadata" metadataDecoder
888
+ |> Decode.andThen
889
+ (\result1 ->
890
+ let
891
+ bodyResult : Decoder (Result Decode.Error value)
892
+ bodyResult =
893
+ Decode.field "body" Decode.value
894
+ |> Decode.map
895
+ (\bodyValue ->
896
+ Decode.decodeValue decoder bodyValue
897
+ )
898
+ in
899
+ bodyResult
900
+ |> Decode.map
901
+ (\result ->
902
+ case result1 of
903
+ Ok metadata ->
904
+ case result of
905
+ Ok body ->
906
+ Ok
907
+ { metadata = metadata
908
+ , body = body
909
+ }
910
+
911
+ Err decoderError ->
912
+ FatalError.recoverable
913
+ { title = "Failed to decode body"
914
+ , body = "Failed to decode body"
915
+ }
916
+ (StreamError (Decode.errorToString decoderError))
917
+ |> Err
918
+
919
+ Err error ->
920
+ error
921
+ |> mapRecoverable (Result.toMaybe result)
922
+ |> Err
923
+ )
924
+ )
925
+ )
926
+ }
927
+ |> BackendTask.andThen BackendTask.fromResult
928
+
929
+
930
+ {-| Run a command (or `child_process`). The command's output becomes the body of the `Stream`.
931
+ -}
932
+ command : String -> List String -> Stream Int () { read : read, write : write }
933
+ command command_ args_ =
934
+ commandWithOptions defaultCommandOptions command_ args_
935
+
936
+
937
+ commandDecoder : Bool -> ( String, Decoder (Result (Recoverable Int) ()) )
938
+ commandDecoder allowNon0 =
939
+ ( "command"
940
+ , commandOutputDecoder
941
+ |> Decode.map
942
+ (\exitCode ->
943
+ if exitCode == 0 || allowNon0 || True then
944
+ Ok ()
945
+
946
+ else
947
+ Err
948
+ (FatalError.recoverable
949
+ { title = "Command Failed"
950
+ , body = "Command failed with exit code " ++ String.fromInt exitCode
951
+ }
952
+ exitCode
953
+ )
954
+ )
955
+ )
956
+
957
+
958
+
959
+ -- on error, give CommandOutput as well
960
+
961
+
962
+ {-| Pass in custom [`CommandOptions`](#CommandOptions) to configure the behavior of the command.
963
+
964
+ For example, `grep` will return a non-zero status code if it doesn't find any matches. To ignore the non-zero status code and proceed with
965
+ empty output, you can use `allowNon0Status`.
966
+
967
+ module GrepErrors exposing (run)
968
+
969
+ import BackendTask
970
+ import BackendTask.Stream as Stream
971
+ import Pages.Script as Script exposing (Script)
972
+
973
+ run : Script
974
+ run =
975
+ Script.withoutCliOptions
976
+ (Stream.fileRead "log.txt"
977
+ |> Stream.pipe
978
+ (Stream.commandWithOptions
979
+ (Stream.defaultCommandOptions |> Stream.allowNon0Status)
980
+ "grep"
981
+ [ "error" ]
982
+ )
983
+ |> Stream.pipe Stream.stdout
984
+ |> Stream.run
985
+ )
986
+
987
+ -}
988
+ commandWithOptions : CommandOptions -> String -> List String -> Stream Int () { read : read, write : write }
989
+ commandWithOptions (CommandOptions options) command_ args_ =
990
+ single (commandDecoder options.allowNon0Status)
991
+ "command"
992
+ [ ( "command", Encode.string command_ )
993
+ , ( "args", Encode.list Encode.string args_ )
994
+ , ( "allowNon0Status", Encode.bool options.allowNon0Status )
995
+ , ( "output", encodeChannel options.output )
996
+ , ( "timeoutInMs", nullable Encode.int options.timeoutInMs )
997
+ ]
998
+
999
+
1000
+ nullable : (a -> Encode.Value) -> Maybe a -> Encode.Value
1001
+ nullable encoder maybeValue =
1002
+ case maybeValue of
1003
+ Just value ->
1004
+ encoder value
1005
+
1006
+ Nothing ->
1007
+ Encode.null
1008
+
1009
+
1010
+ {-| Configuration for [`commandWithOptions`](#commandWithOptions).
1011
+ -}
1012
+ type CommandOptions
1013
+ = CommandOptions CommandOptions_
1014
+
1015
+
1016
+ type alias CommandOptions_ =
1017
+ { output : StderrOutput
1018
+ , allowNon0Status : Bool
1019
+ , timeoutInMs : Maybe Int
1020
+ }
1021
+
1022
+
1023
+ {-| The default options that are used for [`command`](#command). Used to build up `CommandOptions`
1024
+ to pass in to [`commandWithOptions`](#commandWithOptions).
1025
+ -}
1026
+ defaultCommandOptions : CommandOptions
1027
+ defaultCommandOptions =
1028
+ CommandOptions
1029
+ { output = PrintStderr
1030
+ , allowNon0Status = False
1031
+ , timeoutInMs = Nothing
1032
+ }
1033
+
1034
+
1035
+ {-| Configure the [`StderrOutput`](#StderrOutput) behavior.
1036
+ -}
1037
+ withOutput : StderrOutput -> CommandOptions -> CommandOptions
1038
+ withOutput output (CommandOptions cmd) =
1039
+ CommandOptions { cmd | output = output }
1040
+
1041
+
1042
+ {-| By default, the `Stream` will halt with an error if a command returns a non-zero status code.
1043
+
1044
+ With `allowNon0Status`, the stream will continue without an error if the command returns a non-zero status code.
1045
+
1046
+ -}
1047
+ allowNon0Status : CommandOptions -> CommandOptions
1048
+ allowNon0Status (CommandOptions cmd) =
1049
+ CommandOptions { cmd | allowNon0Status = True }
1050
+
1051
+
1052
+ {-| By default, commands do not have a timeout. This will set the timeout, in milliseconds, for the given command. If that duration is exceeded,
1053
+ the `Stream` will fail with an error.
1054
+ -}
1055
+ withTimeout : Int -> CommandOptions -> CommandOptions
1056
+ withTimeout timeoutMs (CommandOptions cmd) =
1057
+ CommandOptions { cmd | timeoutInMs = Just timeoutMs }
1058
+
1059
+
1060
+ encodeChannel : StderrOutput -> Encode.Value
1061
+ encodeChannel output =
1062
+ Encode.string
1063
+ (case output of
1064
+ IgnoreStderr ->
1065
+ "Ignore"
1066
+
1067
+ PrintStderr ->
1068
+ "Print"
1069
+
1070
+ MergeStderrAndStdout ->
1071
+ "MergeWithStdout"
1072
+
1073
+ StderrInsteadOfStdout ->
1074
+ "InsteadOfStdout"
1075
+ )
1076
+
1077
+
1078
+ commandOutputDecoder : Decoder Int
1079
+ commandOutputDecoder =
1080
+ Decode.field "exitCode" Decode.int
1081
+
1082
+
1083
+ {-| The output configuration for [`withOutput`](#withOutput). The default is `PrintStderr`.
1084
+
1085
+ - `PrintStderr` - Print (but do not pass along) the `stderr` output of the command. Only `stdout` will be passed along as the body of the stream.
1086
+ - `IgnoreStderr` - Ignore the `stderr` output of the command, only include `stdout`
1087
+ - `MergeStderrAndStdout` - Both `stderr` and `stdout` will be passed along as the body of the stream.
1088
+ - `StderrInsteadOfStdout` - Only `stderr` will be passed along as the body of the stream. `stdout` will be ignored.
1089
+
1090
+ -}
1091
+ type StderrOutput
1092
+ = PrintStderr
1093
+ | IgnoreStderr
1094
+ | MergeStderrAndStdout
1095
+ | StderrInsteadOfStdout
1096
+
1097
+
1098
+ toBadResponse : Maybe BackendTask.Http.Metadata -> RequestsAndPending.ResponseBody -> Maybe BackendTask.Http.Error
1099
+ toBadResponse maybeResponse body =
1100
+ case maybeResponse of
1101
+ Just response ->
1102
+ if not (response.statusCode >= 200 && response.statusCode < 300) then
1103
+ case body of
1104
+ RequestsAndPending.StringBody s ->
1105
+ BackendTask.Http.BadStatus
1106
+ { url = response.url
1107
+ , statusCode = response.statusCode
1108
+ , statusText = response.statusText
1109
+ , headers = response.headers
1110
+ }
1111
+ s
1112
+ |> Just
1113
+
1114
+ RequestsAndPending.BytesBody bytes ->
1115
+ BackendTask.Http.BadStatus
1116
+ { url = response.url
1117
+ , statusCode = response.statusCode
1118
+ , statusText = response.statusText
1119
+ , headers = response.headers
1120
+ }
1121
+ (Base64.fromBytes bytes |> Maybe.withDefault "")
1122
+ |> Just
1123
+
1124
+ RequestsAndPending.JsonBody value ->
1125
+ BackendTask.Http.BadStatus
1126
+ { url = response.url
1127
+ , statusCode = response.statusCode
1128
+ , statusText = response.statusText
1129
+ , headers = response.headers
1130
+ }
1131
+ (Encode.encode 0 value)
1132
+ |> Just
1133
+
1134
+ RequestsAndPending.WhateverBody ->
1135
+ BackendTask.Http.BadStatus
1136
+ { url = response.url
1137
+ , statusCode = response.statusCode
1138
+ , statusText = response.statusText
1139
+ , headers = response.headers
1140
+ }
1141
+ ""
1142
+ |> Just
1143
+
1144
+ else
1145
+ Nothing
1146
+
1147
+ Nothing ->
1148
+ Nothing
1149
+
1150
+
1151
+ errorToString : BackendTask.Http.Error -> { title : String, body : String }
1152
+ errorToString error =
1153
+ { title = "HTTP Error"
1154
+ , body =
1155
+ (case error of
1156
+ BackendTask.Http.BadUrl string ->
1157
+ [ TerminalText.text ("BadUrl " ++ string)
1158
+ ]
1159
+
1160
+ BackendTask.Http.Timeout ->
1161
+ [ TerminalText.text "Timeout"
1162
+ ]
1163
+
1164
+ BackendTask.Http.NetworkError ->
1165
+ [ TerminalText.text "NetworkError"
1166
+ ]
1167
+
1168
+ BackendTask.Http.BadStatus metadata _ ->
1169
+ [ TerminalText.text "BadStatus: "
1170
+ , TerminalText.red (String.fromInt metadata.statusCode)
1171
+ , TerminalText.text (" " ++ metadata.statusText)
1172
+ ]
1173
+
1174
+ BackendTask.Http.BadBody _ string ->
1175
+ [ TerminalText.text ("BadBody: " ++ string)
1176
+ ]
1177
+ )
1178
+ |> TerminalText.toString
1179
+ }