elm-pages 3.0.24 → 3.0.26

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.
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  - [elm-pages Docs Site](https://elm-pages.com/docs)
13
13
  - [elm-pages site showcase](https://elm-pages.com/showcase/)
14
- - [elm-pages Elm API Docs](https://package.elm-lang.org/packages/dillonkearns/elm-pages/10.2.1/)
14
+ - [elm-pages Elm API Docs](https://package.elm-lang.org/packages/dillonkearns/elm-pages/10.2.2/)
15
15
  - [Quick start repo](https://github.com/dillonkearns/elm-pages-starter) [(live site hosted here)](https://elm-pages-starter.netlify.com)
16
16
  - [Introducing `elm-pages` blog post](https://elm-pages.com/blog/introducing-elm-pages)
17
17
  - [`examples` folder](https://github.com/dillonkearns/elm-pages/blob/master/examples/) (includes https://elm-pages.com site source) Use `git clone --recurse-submodules https://github.com/dillonkearns/elm-pages.git` so that there aren't missing files when you try to build the examples.
@@ -741,7 +741,9 @@ async function runAdapter(adaptFn, processedIndexTemplate) {
741
741
  * @param {string} file
742
742
  */
743
743
  function defaultPreloadForFile(file) {
744
- if (file.endsWith(".js")) {
744
+ if (/\/elm\.[a-f0-9]+\.js$/.test(file)) {
745
+ return `<link rel="preload" as="script" href="${file}">`;
746
+ } else if (file.endsWith(".js")) {
745
747
  return `<link rel="modulepreload" crossorigin href="${file}">`;
746
748
  } else if (file.endsWith(".css")) {
747
749
  return `<link rel="preload" href="${file}" as="style">`;
@@ -162,8 +162,7 @@ async function main() {
162
162
  moduleName
163
163
  );
164
164
  } catch (error) {
165
- console.trace(error);
166
- console.log(restoreColorSafe(error));
165
+ printCaughtError(error);
167
166
  process.exit(1);
168
167
  }
169
168
  });
@@ -283,7 +282,7 @@ await(async()=>{let{dirname:e}=await import("path"),{fileURLToPath:i}=await impo
283
282
  });
284
283
  // await runTerser(path.resolve(cwd, options.output));
285
284
  } catch (error) {
286
- console.log(restoreColorSafe(error));
285
+ printCaughtError(error);
287
286
  process.exit(1);
288
287
  }
289
288
  });
@@ -327,6 +326,17 @@ function safeSubscribe(program, portName, subscribeFunction) {
327
326
  program.ports[portName].subscribe(subscribeFunction);
328
327
  }
329
328
 
329
+ /**
330
+ * @param {Error|string|any[]} error - Thing that was thrown and caught.
331
+ */
332
+ function printCaughtError(error) {
333
+ if (typeof error === "string" || Array.isArray(error)) {
334
+ console.log(restoreColorSafe(error));
335
+ } else {
336
+ console.trace(error);
337
+ }
338
+ }
339
+
330
340
  /**
331
341
  * @param {string} rawPagePath
332
342
  */
@@ -404,7 +414,13 @@ async function compileElmForScript(elmModulePath) {
404
414
  // await codegen.generate("");
405
415
  ensureDirSync(path.join(process.cwd(), ".elm-pages", "http-response-cache"));
406
416
  if (fs.existsSync("./codegen/") && process.env.SKIP_ELM_CODEGEN !== "true") {
407
- await runElmCodegenInstall();
417
+ const result = await runElmCodegenInstall();
418
+ if (!result.success) {
419
+ console.error(`Warning: ${result.message}. This may cause stale generated code or missing module errors.\n`);
420
+ if (result.error) {
421
+ console.error(result.error);
422
+ }
423
+ }
408
424
  }
409
425
 
410
426
  ensureDirSync(`${projectDirectory}/elm-stuff`);
@@ -1,3 +1,3 @@
1
1
  export const compatibilityKey = 22;
2
2
 
3
- export const packageVersion = "3.0.24";
3
+ export const packageVersion = "3.0.26";
@@ -133,6 +133,12 @@ async function compileElm(options, elmEntrypointPath, outputPath, cwd) {
133
133
 
134
134
  function spawnElmMake(options, elmEntrypointPath, outputPath, cwd) {
135
135
  return new Promise(async (resolve, reject) => {
136
+ try {
137
+ await fsPromises.unlink(outputPath);
138
+ } catch (e) {
139
+ /* File may not exist, so ignore errors */
140
+ }
141
+
136
142
  const subprocess = spawnCallback(
137
143
  `lamdera`,
138
144
  [
@@ -152,13 +158,6 @@ function spawnElmMake(options, elmEntrypointPath, outputPath, cwd) {
152
158
  cwd: cwd,
153
159
  }
154
160
  );
155
- if (await fsHelpers.fileExists(outputPath)) {
156
- try {
157
- await fsPromises.unlink(outputPath, {
158
- force: true /* ignore errors if file doesn't exist */,
159
- });
160
- } catch (e) {}
161
- }
162
161
  let commandOutput = "";
163
162
 
164
163
  subprocess.stderr.on("data", function (data) {
@@ -1,35 +1,39 @@
1
1
  import { spawn as spawnCallback } from "cross-spawn";
2
+ import which from "which";
2
3
 
3
- export function runElmCodegenInstall() {
4
- return new Promise(async (resolve, reject) => {
5
- const subprocess = spawnCallback(`elm-codegen`, ["install"], {
6
- // ignore stdout
7
- // stdio: ["inherit", "ignore", "inherit"],
8
- // cwd: cwd,
9
- });
10
- // if (await fsHelpers.fileExists(outputPath)) {
11
- // await fsPromises.unlink(outputPath, {
12
- // force: true /* ignore errors if file doesn't exist */,
13
- // });
14
- // }
15
- let commandOutput = "";
4
+ /**
5
+ * @returns {Promise<{ success: true } | { success: false; message: string; error?: Error }>}
6
+ */
7
+ export async function runElmCodegenInstall() {
8
+ try {
9
+ await which("elm-codegen");
10
+ } catch (error) {
11
+ return { success: false, message: "Unable to find elm-codegen on the PATH" };
12
+ }
16
13
 
14
+ return new Promise((resolve) => {
15
+ const subprocess = spawnCallback("elm-codegen", ["install"]);
16
+
17
+ let commandOutput = "";
17
18
  subprocess.stderr.on("data", function (data) {
18
19
  commandOutput += data;
19
20
  });
20
21
  subprocess.stdout.on("data", function (data) {
21
22
  commandOutput += data;
22
23
  });
23
- subprocess.on("error", function () {
24
- reject(commandOutput);
24
+ subprocess.on("error", function (error) {
25
+ resolve({ success: false, message: "Failed to run elm-codegen", error });
25
26
  });
26
27
 
27
- subprocess.on("close", async (code) => {
28
- if (code == 0) {
29
- resolve();
30
- } else {
31
- reject(commandOutput);
28
+ subprocess.on("close", (code) => {
29
+ if (code === 0) {
30
+ return resolve({ success: true });
32
31
  }
32
+ resolve({
33
+ success: false,
34
+ message: `elm-codegen exited with code ${code}`,
35
+ error: commandOutput.length > 0 ? new Error(commandOutput) : undefined
36
+ });
33
37
  });
34
38
  });
35
39
  }
@@ -107,7 +107,7 @@ export const restoreColor = (error) => {
107
107
  };
108
108
 
109
109
  /**
110
- * @param {string} error
110
+ * @param {string|RootObject[]} error
111
111
  * @returns {string}
112
112
  */
113
113
  export function restoreColorSafe(error) {
@@ -65,8 +65,7 @@ export async function render(
65
65
  mode,
66
66
  path,
67
67
  request,
68
- addBackendTaskWatcher,
69
- hasFsAccess
68
+ addBackendTaskWatcher
70
69
  );
71
70
  return result;
72
71
  }
@@ -102,7 +101,6 @@ export async function runGenerator(
102
101
  scriptModuleName,
103
102
  "production",
104
103
  "",
105
- true,
106
104
  versionMessage
107
105
  );
108
106
  return result;
@@ -120,7 +118,6 @@ export async function runGenerator(
120
118
  * @param {string[]} cliOptions
121
119
  * @param {any} portsFile
122
120
  * @param {typeof import("fs") | import("memfs").IFs} fs
123
- * @param {boolean} hasFsAccess
124
121
  * @param {string} scriptModuleName
125
122
  * @param {string} versionMessage
126
123
  */
@@ -132,7 +129,6 @@ function runGeneratorAppHelp(
132
129
  scriptModuleName,
133
130
  mode,
134
131
  pagePath,
135
- hasFsAccess,
136
132
  versionMessage
137
133
  ) {
138
134
  const isDevServer = mode !== "build";
@@ -208,9 +204,7 @@ function runGeneratorAppHelp(
208
204
  return runInternalJob(
209
205
  requestHash,
210
206
  app,
211
- mode,
212
207
  requestToPerform,
213
- hasFsAccess,
214
208
  patternsToWatch,
215
209
  portsFile
216
210
  );
@@ -218,10 +212,7 @@ function runGeneratorAppHelp(
218
212
  return runHttpJob(
219
213
  requestHash,
220
214
  portsFile,
221
- app,
222
215
  mode,
223
- requestToPerform,
224
- hasFsAccess,
225
216
  requestToPerform
226
217
  );
227
218
  }
@@ -262,8 +253,7 @@ function runElmApp(
262
253
  mode,
263
254
  pagePath,
264
255
  request,
265
- addBackendTaskWatcher,
266
- hasFsAccess
256
+ addBackendTaskWatcher
267
257
  ) {
268
258
  const isDevServer = mode !== "build";
269
259
  let patternsToWatch = new Set();
@@ -347,9 +337,7 @@ function runElmApp(
347
337
  return runInternalJob(
348
338
  requestHash,
349
339
  app,
350
- mode,
351
340
  requestToPerform,
352
- hasFsAccess,
353
341
  patternsToWatch,
354
342
  portsFile
355
343
  );
@@ -357,10 +345,7 @@ function runElmApp(
357
345
  return runHttpJob(
358
346
  requestHash,
359
347
  portsFile,
360
- app,
361
348
  mode,
362
- requestToPerform,
363
- hasFsAccess,
364
349
  requestToPerform
365
350
  );
366
351
  }
@@ -427,22 +412,12 @@ async function outputString(
427
412
 
428
413
  /** @typedef { { head: any[]; errors: any[]; contentJson: any[]; html: string; route: string; title: string; } } Arg */
429
414
 
430
- async function runHttpJob(
431
- requestHash,
432
- portsFile,
433
- app,
434
- mode,
435
- requestToPerform,
436
- hasFsAccess,
437
- useCache
438
- ) {
415
+ async function runHttpJob(requestHash, portsFile, mode, requestToPerform) {
439
416
  try {
440
417
  const lookupResponse = await lookupOrPerform(
441
418
  portsFile,
442
419
  mode,
443
- requestToPerform,
444
- hasFsAccess,
445
- useCache
420
+ requestToPerform
446
421
  );
447
422
 
448
423
  if (lookupResponse.kind === "cache-response-path") {
@@ -485,29 +460,43 @@ function jsonResponse(request, json) {
485
460
  response: { bodyKind: "json", body: json },
486
461
  };
487
462
  }
463
+ /**
464
+ * @param {any} request
465
+ * @param {WithImplicitCoercion<ArrayBuffer | SharedArrayBuffer>} buffer
466
+ */
467
+ function bytesResponse(request, buffer) {
468
+ return {
469
+ request,
470
+ response: {
471
+ bodyKind: "bytes",
472
+ body: Buffer.from(buffer).toString("base64"),
473
+ },
474
+ };
475
+ }
488
476
 
477
+ /**
478
+ * @param {{ url: string; body: { args: any[] } }} requestToPerform
479
+ */
489
480
  async function runInternalJob(
490
481
  requestHash,
491
482
  app,
492
- mode,
493
483
  requestToPerform,
494
- hasFsAccess,
495
484
  patternsToWatch,
496
485
  portsFile
497
486
  ) {
498
487
  try {
499
- const cwd = path.resolve(...requestToPerform.dir);
500
- const quiet = requestToPerform.quiet;
501
- const env = { ...process.env, ...requestToPerform.env };
502
-
503
- const context = { cwd, quiet, env };
504
488
  switch (requestToPerform.url) {
505
489
  case "elm-pages-internal://log":
506
490
  return [requestHash, await runLogJob(requestToPerform)];
507
491
  case "elm-pages-internal://read-file":
508
492
  return [
509
493
  requestHash,
510
- await readFileJobNew(requestToPerform, patternsToWatch, context),
494
+ await readFileJobNew(requestToPerform, patternsToWatch),
495
+ ];
496
+ case "elm-pages-internal://read-file-binary":
497
+ return [
498
+ requestHash,
499
+ await readFileBinaryJobNew(requestToPerform, patternsToWatch),
511
500
  ];
512
501
  case "elm-pages-internal://glob":
513
502
  return [
@@ -540,7 +529,7 @@ async function runInternalJob(
540
529
  await runDecryptJob(requestToPerform, patternsToWatch),
541
530
  ];
542
531
  case "elm-pages-internal://write-file":
543
- return [requestHash, await runWriteFileJob(requestToPerform, context)];
532
+ return [requestHash, await runWriteFileJob(requestToPerform)];
544
533
  case "elm-pages-internal://sleep":
545
534
  return [requestHash, await runSleep(requestToPerform)];
546
535
  case "elm-pages-internal://which":
@@ -550,10 +539,7 @@ async function runInternalJob(
550
539
  case "elm-pages-internal://shell":
551
540
  return [requestHash, await runShell(requestToPerform)];
552
541
  case "elm-pages-internal://stream":
553
- return [
554
- requestHash,
555
- await runStream(requestToPerform, portsFile, context),
556
- ];
542
+ return [requestHash, await runStream(requestToPerform, portsFile)];
557
543
  case "elm-pages-internal://start-spinner":
558
544
  return [requestHash, runStartSpinner(requestToPerform)];
559
545
  case "elm-pages-internal://stop-spinner":
@@ -568,7 +554,19 @@ async function runInternalJob(
568
554
  }
569
555
  }
570
556
 
571
- async function readFileJobNew(req, patternsToWatch, { cwd }) {
557
+ /**
558
+ * @param {{ dir: string[]; quiet: boolean; env: { [key:string]: string; }; }} requestToPerform
559
+ */
560
+ function getContext(requestToPerform) {
561
+ const cwd = path.resolve(...requestToPerform.dir);
562
+ const quiet = requestToPerform.quiet;
563
+ const env = { ...process.env, ...requestToPerform.env };
564
+
565
+ return { cwd, quiet, env };
566
+ }
567
+
568
+ async function readFileJobNew(req, patternsToWatch) {
569
+ const { cwd } = getContext(req);
572
570
  // TODO use cwd
573
571
  const filePath = path.resolve(cwd, req.body.args[1]);
574
572
  try {
@@ -590,6 +588,34 @@ async function readFileJobNew(req, patternsToWatch, { cwd }) {
590
588
  }
591
589
  }
592
590
 
591
+ /**
592
+ * @param {{ url: string; body: { args: any[] } }} req
593
+ * @param {{ add: (arg0: string) => void; }} patternsToWatch
594
+ */
595
+ async function readFileBinaryJobNew(req, patternsToWatch) {
596
+ const filePath = req.body.args[1];
597
+ try {
598
+ patternsToWatch.add(filePath);
599
+
600
+ const fileContents = await fsPromises.readFile(filePath);
601
+ // It's safe to use allocUnsafe here because we're going to overwrite it immediately anyway
602
+ const buffer = new Uint8Array(4 + fileContents.length);
603
+ const view = new DataView(
604
+ buffer.buffer,
605
+ buffer.byteOffset,
606
+ buffer.byteLength
607
+ );
608
+ view.setInt32(0, fileContents.length);
609
+ fileContents.copy(buffer, 4);
610
+
611
+ return bytesResponse(req, buffer);
612
+ } catch (error) {
613
+ const buffer = new Int32Array(1);
614
+ buffer[0] = -1;
615
+ return bytesResponse(req, buffer);
616
+ }
617
+ }
618
+
593
619
  function runSleep(req) {
594
620
  const { milliseconds } = req.body.args[0];
595
621
  return new Promise((resolve) => {
@@ -612,8 +638,9 @@ async function runQuestion(req) {
612
638
  return jsonResponse(req, await question(req.body.args[0]));
613
639
  }
614
640
 
615
- function runStream(req, portsFile, context) {
641
+ function runStream(req, portsFile) {
616
642
  return new Promise(async (resolve) => {
643
+ const context = getContext(req);
617
644
  let metadataResponse = null;
618
645
  let lastStream = null;
619
646
  try {
@@ -1126,7 +1153,8 @@ export async function question({ prompt }) {
1126
1153
  });
1127
1154
  }
1128
1155
 
1129
- async function runWriteFileJob(req, { cwd }) {
1156
+ async function runWriteFileJob(req) {
1157
+ const { cwd } = getContext(req);
1130
1158
  const data = req.body.args[0];
1131
1159
  const filePath = path.resolve(cwd, data.path);
1132
1160
  try {
@@ -11,15 +11,12 @@ const defaultHttpCachePath = "./.elm-pages/http-cache";
11
11
  * @param {string} mode
12
12
  * @param {{url: string;headers: {[x: string]: string;};method: string;body: Body; }} rawRequest
13
13
  * @param {Record<string, unknown>} portsFile
14
- * @param {boolean} hasFsAccess
15
14
  * @returns {Promise<Response>}
16
15
  */
17
16
  export function lookupOrPerform(
18
17
  portsFile,
19
18
  mode,
20
- rawRequest,
21
- hasFsAccess,
22
- useCache
19
+ rawRequest
23
20
  ) {
24
21
  const uniqueTimeId =
25
22
  Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
@@ -14,7 +14,7 @@
14
14
  "dillonkearns/elm-bcp47-language-tag": "2.0.0",
15
15
  "dillonkearns/elm-form": "3.0.1",
16
16
  "dillonkearns/elm-markdown": "7.0.1",
17
- "dillonkearns/elm-pages": "10.2.1",
17
+ "dillonkearns/elm-pages": "10.2.2",
18
18
  "elm/browser": "1.0.2",
19
19
  "elm/bytes": "1.0.8",
20
20
  "elm/core": "1.0.5",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "elm-pages",
3
3
  "type": "module",
4
- "version": "3.0.24",
4
+ "version": "3.0.26",
5
5
  "homepage": "https://elm-pages.com",
6
6
  "moduleResolution": "node",
7
7
  "description": "Hybrid Elm framework with full-stack and static routes.",
@@ -126,6 +126,7 @@ import Date
126
126
  import FatalError exposing (FatalError)
127
127
  import Json.Decode as Decode exposing (Decoder)
128
128
  import Json.Encode as Encode
129
+ import Pages.StaticHttpRequest
129
130
  import TerminalText
130
131
  import Time
131
132
 
@@ -331,7 +332,6 @@ request :
331
332
  }
332
333
  -> BackendTask { fatal : FatalError, recoverable : Error } a
333
334
  request { body, expect } =
334
- -- elm-review: known-unoptimized-recursion
335
335
  BackendTask.Http.request
336
336
  { url = "elm-pages-internal://port"
337
337
  , method = "GET"
@@ -343,8 +343,6 @@ request { body, expect } =
343
343
  expect
344
344
  |> BackendTask.onError
345
345
  (\error ->
346
- -- TODO avoid crash here, this should be handled as an internal error
347
- --request params
348
346
  case error.recoverable of
349
347
  BackendTask.Http.BadBody (Just jsonError) _ ->
350
348
  { recoverable = DecodeError jsonError
@@ -353,8 +351,5 @@ request { body, expect } =
353
351
  |> BackendTask.fail
354
352
 
355
353
  _ ->
356
- { recoverable = Error
357
- , fatal = error.fatal
358
- }
359
- |> BackendTask.fail
354
+ Pages.StaticHttpRequest.InternalError error.fatal
360
355
  )
@@ -1,6 +1,6 @@
1
1
  module BackendTask.File exposing
2
2
  ( bodyWithFrontmatter, bodyWithoutFrontmatter, onlyFrontmatter
3
- , jsonFile, rawFile
3
+ , jsonFile, rawFile, binaryFile
4
4
  , FileReadError(..)
5
5
  )
6
6
 
@@ -40,7 +40,7 @@ plain old JSON in Elm.
40
40
 
41
41
  ## Reading Files Without Frontmatter
42
42
 
43
- @docs jsonFile, rawFile
43
+ @docs jsonFile, rawFile, binaryFile
44
44
 
45
45
 
46
46
  ## FatalErrors
@@ -52,6 +52,8 @@ plain old JSON in Elm.
52
52
  import BackendTask exposing (BackendTask)
53
53
  import BackendTask.Http
54
54
  import BackendTask.Internal.Request
55
+ import Bytes exposing (Bytes)
56
+ import Bytes.Decode
55
57
  import FatalError exposing (FatalError)
56
58
  import Json.Decode as Decode exposing (Decoder)
57
59
  import TerminalText
@@ -337,6 +339,39 @@ rawFile filePath =
337
339
  read filePath (Decode.field "rawFile" Decode.string)
338
340
 
339
341
 
342
+ {-| Get the raw file content as `Bytes`.
343
+
344
+ You could read a file called `hello.jpg` in your root project directory like this:
345
+
346
+ import BackendTask exposing (BackendTask)
347
+ import BackendTask.File as File
348
+ import Bytes exposing (Bytes)
349
+
350
+ elmBinaryFile : BackendTask Bytes
351
+ elmBinaryFile =
352
+ File.binaryFile "hello.jpg"
353
+
354
+ -}
355
+ binaryFile : String -> BackendTask { fatal : FatalError, recoverable : FileReadError decoderError } Bytes
356
+ binaryFile filePath =
357
+ BackendTask.Internal.Request.request
358
+ { name = "read-file-binary"
359
+ , body = BackendTask.Http.stringBody "" filePath
360
+ , expect =
361
+ Bytes.Decode.signedInt32 Bytes.BE
362
+ |> Bytes.Decode.andThen
363
+ (\length ->
364
+ if length < 0 then
365
+ Bytes.Decode.fail
366
+
367
+ else
368
+ Bytes.Decode.bytes length
369
+ )
370
+ |> BackendTask.Http.expectBytes
371
+ }
372
+ |> BackendTask.mapError (\_ -> fileNotFound filePath)
373
+
374
+
340
375
  {-| Read a file as JSON.
341
376
 
342
377
  The Decode will strip off any unused JSON data.
@@ -410,23 +445,25 @@ read filePath decoder =
410
445
  |> BackendTask.andThen BackendTask.fromResult
411
446
 
412
447
 
413
- errorDecoder :
448
+ errorDecoder : String -> Decoder { fatal : FatalError, recoverable : FileReadError decoding }
449
+ errorDecoder filePath =
450
+ Decode.succeed (fileNotFound filePath)
451
+
452
+
453
+ fileNotFound :
414
454
  String
415
455
  ->
416
- Decoder
417
- { fatal : FatalError
418
- , recoverable : FileReadError decoding
419
- }
420
- errorDecoder filePath =
421
- Decode.succeed
422
- (FatalError.recoverable
423
- { title = "File Doesn't Exist"
424
- , body =
425
- [ TerminalText.text "Couldn't find file at path `"
426
- , TerminalText.yellow filePath
427
- , TerminalText.text "`"
428
- ]
429
- |> TerminalText.toString
430
- }
431
- FileDoesntExist
432
- )
456
+ { fatal : FatalError
457
+ , recoverable : FileReadError decoding
458
+ }
459
+ fileNotFound filePath =
460
+ FatalError.recoverable
461
+ { title = "File Doesn't Exist"
462
+ , body =
463
+ [ TerminalText.text "Couldn't find file at path `"
464
+ , TerminalText.yellow filePath
465
+ , TerminalText.text "`"
466
+ ]
467
+ |> TerminalText.toString
468
+ }
469
+ FileDoesntExist
@@ -4,6 +4,7 @@ import BackendTask exposing (BackendTask)
4
4
  import BackendTask.Http exposing (Body, Expect)
5
5
  import Json.Decode exposing (Decoder)
6
6
  import Json.Encode as Encode
7
+ import Pages.StaticHttpRequest
7
8
 
8
9
 
9
10
  request :
@@ -12,8 +13,7 @@ request :
12
13
  , expect : Expect a
13
14
  }
14
15
  -> BackendTask error a
15
- request ({ name, body, expect } as params) =
16
- -- elm-review: known-unoptimized-recursion
16
+ request { name, body, expect } =
17
17
  BackendTask.Http.request
18
18
  { url = "elm-pages-internal://" ++ name
19
19
  , method = "GET"
@@ -24,9 +24,8 @@ request ({ name, body, expect } as params) =
24
24
  }
25
25
  expect
26
26
  |> BackendTask.onError
27
- (\_ ->
28
- -- TODO avoid crash here, this should be handled as an internal error
29
- request params
27
+ (\err ->
28
+ Pages.StaticHttpRequest.InternalError err.fatal
30
29
  )
31
30
 
32
31
 
@@ -143,6 +143,9 @@ but mapping allows you to change the resulting values by applying functions to t
143
143
  map : (a -> b) -> BackendTask error a -> BackendTask error b
144
144
  map fn requestInfo =
145
145
  case requestInfo of
146
+ InternalError err ->
147
+ InternalError err
148
+
146
149
  ApiRoute value ->
147
150
  ApiRoute (Result.map fn value)
148
151
 
@@ -221,6 +224,9 @@ inDir dir backendTask =
221
224
  -- elm-review: known-unoptimized-recursion
222
225
  -- TODO try to find a way to optimize tail-call recursion here
223
226
  case backendTask of
227
+ InternalError _ ->
228
+ backendTask
229
+
224
230
  ApiRoute _ ->
225
231
  backendTask
226
232
 
@@ -243,6 +249,9 @@ quiet backendTask =
243
249
  -- elm-review: known-unoptimized-recursion
244
250
  -- TODO try to find a way to optimize tail-call recursion here
245
251
  case backendTask of
252
+ InternalError _ ->
253
+ backendTask
254
+
246
255
  ApiRoute _ ->
247
256
  backendTask
248
257
 
@@ -260,6 +269,9 @@ withEnv key value backendTask =
260
269
  -- elm-review: known-unoptimized-recursion
261
270
  -- TODO try to find a way to optimize tail-call recursion here
262
271
  case backendTask of
272
+ InternalError _ ->
273
+ backendTask
274
+
263
275
  ApiRoute _ ->
264
276
  backendTask
265
277
 
@@ -422,6 +434,12 @@ map2 fn request1 request2 =
422
434
  -- elm-review: known-unoptimized-recursion
423
435
  -- TODO try to find a way to optimize tail-call recursion here
424
436
  case ( request1, request2 ) of
437
+ ( InternalError err1, _ ) ->
438
+ InternalError err1
439
+
440
+ ( _, InternalError err2 ) ->
441
+ InternalError err2
442
+
425
443
  ( ApiRoute value1, ApiRoute value2 ) ->
426
444
  ApiRoute (Result.map2 fn value1 value2)
427
445
 
@@ -478,6 +496,9 @@ andThen fn requestInfo =
478
496
  -- elm-review: known-unoptimized-recursion
479
497
  -- TODO try to find a way to optimize recursion here
480
498
  case requestInfo of
499
+ InternalError errA ->
500
+ InternalError errA
501
+
481
502
  ApiRoute a ->
482
503
  case a of
483
504
  Ok okA ->
@@ -503,6 +524,9 @@ onError : (error -> BackendTask mappedError value) -> BackendTask error value ->
503
524
  onError fromError backendTask =
504
525
  -- elm-review: known-unoptimized-recursion
505
526
  case backendTask of
527
+ InternalError err ->
528
+ InternalError err
529
+
506
530
  ApiRoute a ->
507
531
  case a of
508
532
  Ok okA ->
@@ -569,6 +593,9 @@ fromResult result =
569
593
  mapError : (error -> errorMapped) -> BackendTask error value -> BackendTask errorMapped value
570
594
  mapError mapFn requestInfo =
571
595
  case requestInfo of
596
+ InternalError internal ->
597
+ InternalError internal
598
+
572
599
  ApiRoute value ->
573
600
  ApiRoute (Result.mapError mapFn value)
574
601
 
@@ -84,7 +84,15 @@ mainView config model =
84
84
  { path = ContentCache.pathForUrl urls |> UrlPath.join
85
85
  , route = config.urlToRoute { currentUrl | path = model.currentPath }
86
86
  }
87
- Nothing
87
+ (Just
88
+ { protocol = currentUrl.protocol
89
+ , host = currentUrl.host
90
+ , port_ = currentUrl.port_
91
+ , path = ContentCache.pathForUrl urls
92
+ , query = currentUrl.query |> Maybe.map QueryParams.fromString |> Maybe.withDefault Dict.empty
93
+ , fragment = currentUrl.fragment
94
+ }
95
+ )
88
96
  pageData.sharedData
89
97
  pageData.pageData
90
98
  pageData.actionData
@@ -1,7 +1,9 @@
1
1
  module Pages.StaticHttpRequest exposing (Error(..), MockResolver, RawRequest(..), Status(..), cacheRequestResolution, mockResolve, toBuildError)
2
2
 
3
3
  import BuildError exposing (BuildError)
4
+ import FatalError exposing (FatalError)
4
5
  import Json.Encode
6
+ import Pages.Internal.FatalError
5
7
  import Pages.StaticHttp.Request
6
8
  import RequestsAndPending exposing (RequestsAndPending)
7
9
  import TerminalText as Terminal
@@ -15,11 +17,13 @@ type alias MockResolver =
15
17
  type RawRequest error value
16
18
  = Request (List Pages.StaticHttp.Request.Request) (Maybe MockResolver -> RequestsAndPending -> RawRequest error value)
17
19
  | ApiRoute (Result error value)
20
+ | InternalError FatalError
18
21
 
19
22
 
20
23
  type Error
21
24
  = DecoderError String
22
25
  | UserCalledStaticHttpFail String
26
+ | InternalFailure FatalError
23
27
 
24
28
 
25
29
  toBuildError : String -> Error -> BuildError
@@ -43,18 +47,36 @@ toBuildError path error =
43
47
  , fatal = True
44
48
  }
45
49
 
50
+ InternalFailure (Pages.Internal.FatalError.FatalError buildError) ->
51
+ { title = "Internal error"
52
+ , message =
53
+ [ Terminal.text <| "Please report this error!"
54
+ , Terminal.text ""
55
+ , Terminal.text ""
56
+ , Terminal.text buildError.body
57
+ ]
58
+ , path = path
59
+ , fatal = True
60
+ }
61
+
46
62
 
47
- mockResolve : RawRequest error value -> MockResolver -> Result error value
48
- mockResolve request mockResolver =
63
+ mockResolve : (FatalError -> error) -> RawRequest error value -> MockResolver -> Result error value
64
+ mockResolve onInternalError request mockResolver =
49
65
  case request of
50
66
  Request _ lookupFn ->
51
- case lookupFn (Just mockResolver) (Json.Encode.object []) of
52
- nextRequest ->
53
- mockResolve nextRequest mockResolver
67
+ let
68
+ nextRequest : RawRequest error value
69
+ nextRequest =
70
+ lookupFn (Just mockResolver) (Json.Encode.object [])
71
+ in
72
+ mockResolve onInternalError nextRequest mockResolver
54
73
 
55
74
  ApiRoute value ->
56
75
  value
57
76
 
77
+ InternalError err ->
78
+ Err (onInternalError err)
79
+
58
80
 
59
81
  cacheRequestResolution :
60
82
  RawRequest error value
@@ -72,6 +94,9 @@ cacheRequestResolution request rawResponses =
72
94
  ApiRoute value ->
73
95
  Complete value
74
96
 
97
+ InternalError err ->
98
+ HasPermanentError (InternalFailure err)
99
+
75
100
 
76
101
  type Status error value
77
102
  = Incomplete (List Pages.StaticHttp.Request.Request) (RawRequest error value)
@@ -36,10 +36,12 @@ bodyDecoder =
36
36
  Decode.string
37
37
  |> Decode.andThen
38
38
  (\base64String ->
39
- base64String
40
- |> Base64.toBytes
41
- |> Maybe.map (BytesBody >> Decode.succeed)
42
- |> Maybe.withDefault (Decode.fail "Couldn't parse base64 string into Bytes.")
39
+ case Base64.toBytes base64String of
40
+ Just bytes ->
41
+ Decode.succeed (BytesBody bytes)
42
+
43
+ Nothing ->
44
+ Decode.fail "Couldn't parse base64 string into Bytes."
43
45
  )
44
46
 
45
47
  "string" ->