elm-pages 3.0.0-beta.19 → 3.0.0-beta.20

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.
@@ -75,7 +75,7 @@ console.elmlog = (str) => logs.push(str + "\n");
75
75
  const { Elm } = require("./Runner.elm.js");
76
76
 
77
77
  // Start the Elm app
78
- const flags = { initialSeed: 3718366887, fuzzRuns: 100, filter: null };
78
+ const flags = { initialSeed: 3129741668, fuzzRuns: 100, filter: null };
79
79
  const app = Elm.Runner.init({ flags: flags });
80
80
 
81
81
  // Record the timing at which we received the last "runTest" message
@@ -82,7 +82,7 @@ const verbosity = 0;
82
82
  // Create a long lived reporter worker
83
83
  const { Elm } = require("./Reporter.elm.js");
84
84
  const flags = {
85
- initialSeed: 3718366887,
85
+ initialSeed: 3129741668,
86
86
  fuzzRuns: 100,
87
87
  mode: "consoleNoColor",
88
88
  verbosity: verbosity,
@@ -75,7 +75,7 @@ console.elmlog = (str) => logs.push(str + "\n");
75
75
  const { Elm } = require("./Runner.elm.js");
76
76
 
77
77
  // Start the Elm app
78
- const flags = { initialSeed: 1328864607, fuzzRuns: 100, filter: null };
78
+ const flags = { initialSeed: 939943801, fuzzRuns: 100, filter: null };
79
79
  const app = Elm.Runner.init({ flags: flags });
80
80
 
81
81
  // Record the timing at which we received the last "runTest" message
@@ -82,7 +82,7 @@ const verbosity = 0;
82
82
  // Create a long lived reporter worker
83
83
  const { Elm } = require("./Reporter.elm.js");
84
84
  const flags = {
85
- initialSeed: 1328864607,
85
+ initialSeed: 939943801,
86
86
  fuzzRuns: 100,
87
87
  mode: "consoleNoColor",
88
88
  verbosity: verbosity,
@@ -145,18 +145,18 @@ export async function run(options) {
145
145
  );
146
146
  await fsPromises.writeFile("dist/template.html", processedIndexTemplate);
147
147
  await fsPromises.unlink(assetManifestPath);
148
-
149
148
  const portBackendTaskCompiled = esbuild
150
149
  .build({
151
150
  entryPoints: ["./custom-backend-task"],
152
151
  platform: "node",
153
- outfile: ".elm-pages/compiled-ports/custom-backend-task.js",
152
+ outfile: ".elm-pages/compiled-ports/custom-backend-task.mjs",
154
153
  assetNames: "[name]-[hash]",
155
154
  chunkNames: "chunks/[name]-[hash]",
156
155
  outExtension: { ".js": ".js" },
157
156
  metafile: true,
158
157
  bundle: true,
159
- watch: false,
158
+ format: "esm",
159
+ packages: "external",
160
160
  logLevel: "silent",
161
161
  })
162
162
  .then((result) => {
@@ -356,7 +356,7 @@ async function fingerprintElmAsset(fullOutputPath, withoutExtension) {
356
356
  return fileHash;
357
357
  }
358
358
 
359
- function elmOptimizeLevel2(outputPath, cwd) {
359
+ export function elmOptimizeLevel2(outputPath, cwd) {
360
360
  return new Promise((resolve, reject) => {
361
361
  const optimizedOutputPath = outputPath + ".opt";
362
362
  const subprocess = spawnCallback(
@@ -618,14 +618,14 @@ function _HtmlAsJson_toJson(html) {
618
618
  async function runAdapter(adaptFn, processedIndexTemplate) {
619
619
  try {
620
620
  await adaptFn({
621
- renderFunctionFilePath: "./elm-stuff/elm-pages/elm.js",
621
+ renderFunctionFilePath: "./elm-stuff/elm-pages/elm.cjs",
622
622
  routePatterns: JSON.parse(
623
623
  await fsPromises.readFile("./dist/route-patterns.json", "utf-8")
624
624
  ),
625
625
  apiRoutePatterns: JSON.parse(
626
626
  await fsPromises.readFile("./dist/api-patterns.json", "utf-8")
627
627
  ),
628
- portsFilePath: "./.elm-pages/compiled-ports/custom-backend-task.js",
628
+ portsFilePath: "./.elm-pages/compiled-ports/custom-backend-task.mjs",
629
629
  htmlTemplate: processedIndexTemplate,
630
630
  });
631
631
  console.log("Success - Adapter script complete");
@@ -5,20 +5,23 @@ import * as build from "./build.js";
5
5
  import * as dev from "./dev-server.js";
6
6
  import * as init from "./init.js";
7
7
  import * as codegen from "./codegen.js";
8
- import * as fs from "fs";
9
- import * as path from "path";
8
+ import * as fs from "node:fs";
9
+ import * as path from "node:path";
10
10
  import { restoreColorSafe } from "./error-formatter.js";
11
11
  import * as renderer from "./render.js";
12
12
  import { globbySync } from "globby";
13
13
  import * as esbuild from "esbuild";
14
14
  import { rewriteElmJson } from "./rewrite-elm-json.js";
15
15
  import { ensureDirSync, deleteIfExists } from "./file-helpers.js";
16
+ import * as url from "url";
16
17
 
17
18
  import * as commander from "commander";
18
19
  import { runElmCodegenInstall } from "./elm-codegen.js";
19
20
 
20
21
  const Argument = commander.Argument;
21
22
  const Option = commander.Option;
23
+ const __filename = url.fileURLToPath(import.meta.url);
24
+ const __dirname = path.dirname(__filename);
22
25
 
23
26
  import * as packageJson from "../../package.json" assert { type: "json" };
24
27
  const packageVersion = packageJson.version;
@@ -135,13 +138,13 @@ async function main() {
135
138
  .build({
136
139
  entryPoints: ["./custom-backend-task"],
137
140
  platform: "node",
138
- outfile: ".elm-pages/compiled-ports/custom-backend-task.js",
141
+ outfile: ".elm-pages/compiled-ports/custom-backend-task.mjs",
139
142
  assetNames: "[name]-[hash]",
140
143
  chunkNames: "chunks/[name]-[hash]",
141
- outExtension: { ".js": ".js" },
142
144
  metafile: true,
143
145
  bundle: true,
144
- watch: false,
146
+ format: "esm",
147
+ packages: "external",
145
148
  logLevel: "silent",
146
149
  })
147
150
  .then((result) => {
@@ -164,8 +167,6 @@ async function main() {
164
167
  }
165
168
  });
166
169
  const portsPath = await portBackendTaskCompiled;
167
- const resolvedPortsPath =
168
- portsPath && path.join(process.cwd(), portsPath);
169
170
 
170
171
  process.chdir("./script");
171
172
  // TODO have option for compiling with --debug or not (maybe allow running with elm-optimize-level-2 as well?)
@@ -177,7 +178,7 @@ async function main() {
177
178
  );
178
179
  await renderer.runGenerator(
179
180
  unprocessedCliOptions,
180
- resolvedPortsPath,
181
+ await import(url.pathToFileURL(path.resolve(portsPath)).href),
181
182
  await requireElm("./script/elm-stuff/elm-pages/elm.cjs"),
182
183
  moduleName
183
184
  );
@@ -187,6 +188,105 @@ async function main() {
187
188
  }
188
189
  });
189
190
 
191
+ program
192
+ .command("bundle-script <moduleName>")
193
+ .description("bundle an elm-pages script")
194
+ .option(
195
+ "--debug",
196
+ "Skip elm-optimize-level-2 and run elm make with --debug"
197
+ )
198
+ .option(
199
+ "--output <path>",
200
+ "Output path for compiled script",
201
+ "./myscript.mjs"
202
+ )
203
+ .option(
204
+ "--external <package-or-pattern>",
205
+ "build site to be served under a base path",
206
+ collect,
207
+ []
208
+ )
209
+ .action(async (moduleName, options, options2) => {
210
+ if (!/^[A-Z][a-zA-Z0-9_]*(\.[A-Z][a-zA-Z0-9_]*)*$/.test(moduleName)) {
211
+ throw `Invalid module name "${moduleName}", must be in the format of an Elm module`;
212
+ }
213
+ const splitModuleName = moduleName.split(".");
214
+ const expectedFilePath = path.join(
215
+ process.cwd(),
216
+ "script/src/",
217
+ `${splitModuleName.join("/")}.elm`
218
+ );
219
+ if (!fs.existsSync(expectedFilePath)) {
220
+ throw `I couldn't find a module named ${expectedFilePath}`;
221
+ }
222
+ try {
223
+ if (fs.existsSync("./codegen/")) {
224
+ await runElmCodegenInstall();
225
+ }
226
+
227
+ ensureDirSync("./script/elm-stuff");
228
+ ensureDirSync("./script/elm-stuff/elm-pages/.elm-pages");
229
+ await fs.promises.writeFile(
230
+ path.join("./script/elm-stuff/elm-pages/.elm-pages/Main.elm"),
231
+ generatorWrapperFile(moduleName)
232
+ );
233
+ await rewriteElmJson(
234
+ "./script/elm.json",
235
+ "./script/elm-stuff/elm-pages/elm.json"
236
+ );
237
+
238
+ process.chdir("./script");
239
+ // TODO have option for compiling with --debug or not (maybe allow running with elm-optimize-level-2 as well?)
240
+ console.log("Compiling...");
241
+ await build.compileCliApp({ debug: options.debug });
242
+ process.chdir("../");
243
+ if (!options.debug) {
244
+ console.log("Running elm-optimize-level-2...");
245
+ await build.elmOptimizeLevel2(
246
+ "./script/elm-stuff/elm-pages/elm.js",
247
+ process.cwd()
248
+ );
249
+ }
250
+ fs.renameSync(
251
+ "./script/elm-stuff/elm-pages/elm.js",
252
+ "./script/elm-stuff/elm-pages/elm.cjs"
253
+ );
254
+ const scriptRunner = `import * as customBackendTask from "${path.resolve(
255
+ "./custom-backend-task.ts"
256
+ )}";
257
+ import * as renderer from "./render.js";
258
+ import { default as Elm } from "${path.resolve(
259
+ "./script/elm-stuff/elm-pages/elm.cjs"
260
+ )}";
261
+
262
+ await renderer.runGenerator(
263
+ [...process.argv].splice(2),
264
+ customBackendTask,
265
+ Elm,
266
+ "${moduleName}"
267
+ );
268
+ `;
269
+ // source: https://github.com/evanw/esbuild/pull/2067#issuecomment-1073039746
270
+ const ESM_REQUIRE_SHIM = `
271
+ await(async()=>{let{dirname:e}=await import("path"),{fileURLToPath:i}=await import("url");if(typeof globalThis.__filename>"u"&&(globalThis.__filename=i(import.meta.url)),typeof globalThis.__dirname>"u"&&(globalThis.__dirname=e(globalThis.__filename)),typeof globalThis.require>"u"){let{default:a}=await import("module");globalThis.require=a.createRequire(import.meta.url)}})();
272
+ `;
273
+
274
+ await esbuild.build({
275
+ format: "esm",
276
+ platform: "node",
277
+ stdin: { contents: scriptRunner, resolveDir: __dirname },
278
+ bundle: true,
279
+ outfile: options.output,
280
+ external: ["node:*", ...options.external],
281
+ minify: true,
282
+ banner: { js: `#!/usr/bin/env node\n\n${ESM_REQUIRE_SHIM}` },
283
+ });
284
+ } catch (error) {
285
+ console.log(restoreColorSafe(error));
286
+ process.exit(1);
287
+ }
288
+ });
289
+
190
290
  program
191
291
  .command("docs")
192
292
  .description("open the docs for locally generated modules")
@@ -311,5 +411,8 @@ port gotBatchSub : (Decode.Value -> msg) -> Sub msg
311
411
  port sendPageData : { oldThing : Encode.Value, binaryPageData : Bytes.Bytes } -> Cmd msg
312
412
  `;
313
413
  }
414
+ function collect(value, previous) {
415
+ return previous.concat([value]);
416
+ }
314
417
 
315
418
  main();
@@ -1,4 +1,4 @@
1
- import * as fs from "fs";
1
+ import * as fs from "node:fs";
2
2
  import * as fsExtra from "fs-extra/esm";
3
3
  import { rewriteElmJson } from "./rewrite-elm-json.js";
4
4
  import { rewriteClientElmJson } from "./rewrite-client-elm-json.js";
@@ -1,5 +1,5 @@
1
- const util = require("util");
2
- const fsSync = require("fs");
1
+ const util = require("node:util");
2
+ const fsSync = require("node:fs");
3
3
  const fs = {
4
4
  writeFile: util.promisify(fsSync.writeFile),
5
5
  mkdir: util.promisify(fsSync.mkdir),
@@ -143,10 +143,11 @@ export async function start(options) {
143
143
  merge_vite_configs(
144
144
  {
145
145
  server: {
146
- middlewareMode: "ssr",
146
+ middlewareMode: true,
147
147
  base: options.base,
148
148
  port: options.port,
149
149
  },
150
+ appType: "custom",
150
151
  configFile: false,
151
152
  root: process.cwd(),
152
153
  base: options.base,
@@ -155,50 +156,61 @@ export async function start(options) {
155
156
  config.vite
156
157
  )
157
158
  );
158
- esbuild
159
- .build({
160
- entryPoints: ["./custom-backend-task"],
161
- platform: "node",
162
- assetNames: "[name]-[hash]",
163
- chunkNames: "chunks/[name]-[hash]",
164
- outExtension: { ".js": ".js" },
165
- metafile: true,
166
- bundle: true,
167
- watch: true,
168
- logLevel: "silent",
169
-
170
- outdir: ".elm-pages/compiled-ports",
171
- entryNames: "[dir]/[name]-[hash]",
172
-
173
- plugins: [
174
- {
175
- name: "example",
176
- setup(build) {
177
- build.onEnd((result) => {
178
- try {
179
- global.portsFilePath = Object.keys(result.metafile.outputs)[0];
159
+
160
+ const ctx = await esbuild.context({
161
+ entryPoints: ["./custom-backend-task"],
162
+ platform: "node",
163
+ assetNames: "[name]-[hash]",
164
+ chunkNames: "chunks/[name]-[hash]",
165
+ outExtension: { ".js": ".mjs" },
166
+ format: "esm",
167
+ metafile: true,
168
+ bundle: true,
169
+ packages: "external",
170
+ logLevel: "silent",
171
+ outdir: ".elm-pages/compiled-ports",
172
+ entryNames: "[dir]/[name]-[hash]",
173
+
174
+ plugins: [
175
+ {
176
+ name: "example",
177
+ setup(build) {
178
+ build.onEnd(async (result) => {
179
+ try {
180
+ global.portsFilePath = Object.keys(result.metafile.outputs)[0];
181
+
182
+ clients.forEach((client) => {
183
+ client.response.write(`data: content.dat\n\n`);
184
+ });
185
+ } catch (e) {
186
+ const portBackendTaskFileFound =
187
+ globbySync("./custom-backend-task.*").length > 0;
188
+ if (portBackendTaskFileFound) {
189
+ // don't present error if there are no files matching custom-backend-task
190
+ // if there are files matching custom-backend-task, warn the user in case something went wrong loading it
191
+ const messages = (
192
+ await esbuild.formatMessages(result.errors, {
193
+ kind: "error",
194
+ color: true,
195
+ })
196
+ ).join("\n");
197
+ global.portsFilePath = {
198
+ __internalElmPagesError: messages,
199
+ };
180
200
 
181
201
  clients.forEach((client) => {
182
202
  client.response.write(`data: content.dat\n\n`);
183
203
  });
184
- } catch (e) {}
185
- });
186
- },
204
+ } else {
205
+ global.portsFilePath = null;
206
+ }
207
+ }
208
+ });
187
209
  },
188
- ],
189
- })
190
- .then((result) => {
191
- console.log("Watching custom-backend-task...");
192
- })
193
- .catch((error) => {
194
- const portBackendTaskFileFound =
195
- globbySync("./custom-backend-task.*").length > 0;
196
- if (portBackendTaskFileFound) {
197
- // don't present error if there are no files matching custom-backend-task
198
- // if there are files matching custom-backend-task, warn the user in case something went wrong loading it
199
- console.error("Failed to start custom-backend-task watcher", error);
200
- }
201
- });
210
+ },
211
+ ],
212
+ });
213
+ await ctx.watch();
202
214
 
203
215
  const app = connect()
204
216
  .use(timeMiddleware())
@@ -1,6 +1,6 @@
1
- import * as util from "util";
2
- import * as fsSync from "fs";
3
- import * as path from "path";
1
+ import * as util from "node:util";
2
+ import * as fsSync from "node:fs";
3
+ import * as path from "node:path";
4
4
 
5
5
  const fs = {
6
6
  writeFile: util.promisify(fsSync.writeFile),
@@ -75,4 +75,3 @@ export async function copyDirNested(src, dest) {
75
75
  fs.copyFile(src, dest);
76
76
  }
77
77
  }
78
-
@@ -58,9 +58,6 @@ function toKleurColor(color) {
58
58
  case "33BBC8": {
59
59
  return "cyan";
60
60
  }
61
- case "33BBC8": {
62
- return "cyan";
63
- }
64
61
  case "FFFF00": {
65
62
  return "yellow";
66
63
  }
@@ -1,4 +1,4 @@
1
- import * as fs from "fs";
1
+ import * as fs from "node:fs";
2
2
 
3
3
  export function ensureDirSync(dirpath) {
4
4
  try {
@@ -1,15 +1,18 @@
1
1
  import * as renderer from "../../generator/src/render.js";
2
- import * as path from "path";
2
+ import * as path from "node:path";
3
3
  import * as fs from "./dir-helpers.js";
4
- import { readFileSync, writeFileSync } from "fs";
4
+ import { readFileSync, writeFileSync } from "node:fs";
5
5
  import { stat } from "fs/promises";
6
- import { parentPort, threadId, workerData } from "worker_threads";
6
+ import { parentPort, threadId, workerData } from "node:worker_threads";
7
+ import * as url from "url";
7
8
 
8
9
  async function run({ mode, pathname, serverRequest, portsFilePath }) {
9
10
  console.time(`${threadId} ${pathname}`);
10
11
  try {
11
12
  const renderResult = await renderer.render(
12
- portsFilePath,
13
+ typeof portsFilePath === "string"
14
+ ? await import(url.pathToFileURL(path.resolve(portsFilePath)).href)
15
+ : portsFilePath,
13
16
  workerData.basePath,
14
17
  await requireElm(mode),
15
18
  mode,
@@ -1,16 +1,18 @@
1
1
  // @ts-check
2
2
 
3
- import * as path from "path";
3
+ import * as path from "node:path";
4
4
  import { default as mm } from "micromatch";
5
5
  import { default as matter } from "gray-matter";
6
- import { globby } from "globby";
7
- import * as fsPromises from "fs/promises";
6
+ import * as globby from "globby";
7
+ import * as fsPromises from "node:fs/promises";
8
8
  import * as preRenderHtml from "./pre-render-html.js";
9
9
  import { lookupOrPerform } from "./request-cache.js";
10
10
  import * as kleur from "kleur/colors";
11
11
  import * as cookie from "cookie-signature";
12
12
  import { compatibilityKey } from "./compatibility-key.js";
13
- import * as fs from "fs";
13
+ import * as fs from "node:fs";
14
+ import * as crypto from "node:crypto";
15
+ import { restoreColorSafe } from "./error-formatter.js";
14
16
 
15
17
  process.on("unhandledRejection", (error) => {
16
18
  console.error(error);
@@ -76,17 +78,21 @@ export async function runGenerator(
76
78
  // since init/update are never called in pre-renders, and BackendTask.Http is called using pure NodeJS HTTP fetching
77
79
  // we can provide a fake HTTP instead of xhr2 (which is otherwise needed for Elm HTTP requests from Node)
78
80
  global.XMLHttpRequest = {};
79
- const result = await runGeneratorAppHelp(
80
- cliOptions,
81
- portsFile,
82
- "",
83
- elmModule,
84
- scriptModuleName,
85
- "production",
86
- "",
87
- true
88
- );
89
- return result;
81
+ try {
82
+ const result = await runGeneratorAppHelp(
83
+ cliOptions,
84
+ portsFile,
85
+ "",
86
+ elmModule,
87
+ scriptModuleName,
88
+ "production",
89
+ "",
90
+ true
91
+ );
92
+ return result;
93
+ } catch (error) {
94
+ console.log(restoreColorSafe(error));
95
+ }
90
96
  }
91
97
  /**
92
98
  * @param {string} basePath
@@ -468,6 +474,16 @@ async function runInternalJob(
468
474
  ];
469
475
  } else if (requestToPerform.url === "elm-pages-internal://glob") {
470
476
  return [requestHash, await runGlobNew(requestToPerform, patternsToWatch)];
477
+ } else if (requestToPerform.url === "elm-pages-internal://randomSeed") {
478
+ return [
479
+ requestHash,
480
+ jsonResponse(
481
+ requestToPerform,
482
+ crypto.getRandomValues(new Uint32Array(1))[0]
483
+ ),
484
+ ];
485
+ } else if (requestToPerform.url === "elm-pages-internal://now") {
486
+ return [requestHash, jsonResponse(requestToPerform, Date.now())];
471
487
  } else if (requestToPerform.url === "elm-pages-internal://env") {
472
488
  return [requestHash, await runEnvJob(requestToPerform, patternsToWatch)];
473
489
  } else if (requestToPerform.url === "elm-pages-internal://encrypt") {
@@ -538,7 +554,7 @@ async function runWriteFileJob(req) {
538
554
  async function runGlobNew(req, patternsToWatch) {
539
555
  try {
540
556
  const { pattern, options } = req.body.args[0];
541
- const matchedPaths = await globby(pattern, options);
557
+ const matchedPaths = await globby.globby(pattern, options);
542
558
  patternsToWatch.add(pattern);
543
559
 
544
560
  return jsonResponse(
@@ -10,7 +10,7 @@ const defaultHttpCachePath = "./.elm-pages/http-cache";
10
10
  /**
11
11
  * @param {string} mode
12
12
  * @param {{url: string;headers: {[x: string]: string;};method: string;body: Body;}} rawRequest
13
- * @param {string} portsFile
13
+ * @param {Record<string, unknown>} portsFile
14
14
  * @param {boolean} hasFsAccess
15
15
  * @returns {Promise<Response>}
16
16
  */
@@ -27,16 +27,12 @@ export function lookupOrPerform(
27
27
  return new Promise(async (resolve, reject) => {
28
28
  const request = toRequest(rawRequest);
29
29
 
30
- let portBackendTask = {};
30
+ let portBackendTask = portsFile;
31
31
  let portBackendTaskImportError = null;
32
32
  try {
33
33
  if (portsFile === undefined) {
34
34
  throw "missing";
35
35
  }
36
- const portBackendTaskPath = path.resolve(portsFile);
37
- // On Windows, we need cannot use paths directly and instead must use a file:// URL.
38
- // portBackendTask = await require(url.pathToFileURL(portBackendTaskPath).href);
39
- portBackendTask = await import(portBackendTaskPath);
40
36
  } catch (e) {
41
37
  portBackendTaskImportError = e;
42
38
  }
@@ -45,7 +41,22 @@ export function lookupOrPerform(
45
41
  try {
46
42
  const { input, portName } = rawRequest.body.args[0];
47
43
 
48
- if (!portBackendTask[portName]) {
44
+ if (portBackendTask === null) {
45
+ resolve({
46
+ kind: "response-json",
47
+ value: jsonResponse({
48
+ "elm-pages-internal-error": "MissingCustomBackendTaskFile",
49
+ }),
50
+ });
51
+ } else if (portBackendTask && portBackendTask.__internalElmPagesError) {
52
+ resolve({
53
+ kind: "response-json",
54
+ value: jsonResponse({
55
+ "elm-pages-internal-error": "ErrorInCustomBackendTaskFile",
56
+ error: portBackendTask.__internalElmPagesError,
57
+ }),
58
+ });
59
+ } else if (portBackendTask && !portBackendTask[portName]) {
49
60
  if (portBackendTaskImportError === null) {
50
61
  resolve({
51
62
  kind: "response-json",
@@ -1,4 +1,4 @@
1
- import * as fs from "fs";
1
+ import * as fs from "node:fs";
2
2
 
3
3
  export async function rewriteClientElmJson() {
4
4
  var elmJson = JSON.parse(
@@ -1,4 +1,4 @@
1
- import * as fs from "fs";
1
+ import * as fs from "node:fs";
2
2
 
3
3
  export async function rewriteElmJson(sourceElmJsonPath, targetElmJsonPath) {
4
4
  var elmJson = JSON.parse(
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "elm-pages",
3
3
  "type": "module",
4
- "version": "3.0.0-beta.19",
4
+ "version": "3.0.0-beta.20",
5
5
  "homepage": "https://elm-pages.com",
6
6
  "moduleResolution": "node",
7
7
  "description": "Type-safe static sites, written in pure elm with your own custom elm-markup syntax.",
@@ -34,7 +34,7 @@
34
34
  "devcert": "^1.2.2",
35
35
  "elm-doc-preview": "^5.0.5",
36
36
  "elm-hot": "^1.1.6",
37
- "esbuild": "^0.16.15",
37
+ "esbuild": "^0.17.5",
38
38
  "fs-extra": "^11.1.0",
39
39
  "globby": "^13.1.3",
40
40
  "gray-matter": "^4.0.3",
@@ -0,0 +1,79 @@
1
+ module BackendTask.Random exposing
2
+ ( generate
3
+ , int32
4
+ )
5
+
6
+ {-|
7
+
8
+ @docs generate
9
+
10
+ @docs int32
11
+
12
+ -}
13
+
14
+ import BackendTask exposing (BackendTask)
15
+ import BackendTask.Http
16
+ import BackendTask.Internal.Request
17
+ import Json.Decode as Decode
18
+ import Json.Encode as Encode
19
+ import Random
20
+
21
+
22
+ {-| Takes an `elm/random` `Random.Generator` and runs it using a randomly generated initial seed.
23
+
24
+ type alias Data =
25
+ { randomData : ( Int, Float )
26
+ }
27
+
28
+ data : BackendTask FatalError Data
29
+ data =
30
+ BackendTask.map Data
31
+ (BackendTask.Random.generate generator)
32
+
33
+ generator : Random.Generator ( Int, Float )
34
+ generator =
35
+ Random.map2 Tuple.pair (Random.int 0 100) (Random.float 0 100)
36
+
37
+ The random initial seed is generated using <https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues>
38
+ to generate a single 32-bit Integer. That 32-bit Integer is then used with `Random.initialSeed` to create an Elm Random.Seed value.
39
+ Then that `Seed` used to run the `Generator`.
40
+
41
+ Note that this is different than `elm/random`'s `Random.generate`. This difference shouldn't be problematic, and in fact the `BackendTask`
42
+ random seed generation is more cryptographically independent because you can't determine the
43
+ random seed based solely on the time at which it is run. Each time you call `BackendTask.generate` it uses a newly
44
+ generated random seed to run the `Random.Generator` that is passed in. In contrast, `elm/random`'s `Random.generate`
45
+ generates an initial seed using `Time.now`, and then continues with that same seed using using [`Random.step`](https://package.elm-lang.org/packages/elm/random/latest/Random#step)
46
+ to get new random values after that. You can [see the implementation here](https://github.com/elm/random/blob/c1c9da4d861363cee1c93382d2687880279ed0dd/src/Random.elm#L865-L896).
47
+ However, `elm/random` is still not suitable in general for cryptographic uses of random because it uses 32-bits for when it
48
+ steps through new seeds while running a single `Random.Generator`.
49
+
50
+ -}
51
+ generate : Random.Generator value -> BackendTask error value
52
+ generate generator =
53
+ int32
54
+ |> BackendTask.map
55
+ (Random.initialSeed
56
+ >> Random.step generator
57
+ >> Tuple.first
58
+ )
59
+
60
+
61
+ {-| Gives a random 32-bit Int. This can be useful if you want to do low-level things with a cryptographically sound
62
+ random 32-bit integer.
63
+
64
+ The value comes from running this code in Node using <https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues>:
65
+
66
+ ```js
67
+ import * as crypto from "node:crypto";
68
+
69
+ crypto.getRandomValues(new Uint32Array(1))[0]
70
+ ```
71
+
72
+ -}
73
+ int32 : BackendTask error Int
74
+ int32 =
75
+ BackendTask.Internal.Request.request
76
+ { name = "randomSeed"
77
+ , body = BackendTask.Http.jsonBody Encode.null
78
+ , expect = BackendTask.Http.expectJson Decode.int
79
+ }
@@ -0,0 +1,47 @@
1
+ module BackendTask.Time exposing (now)
2
+
3
+ {-|
4
+
5
+ @docs now
6
+
7
+ -}
8
+
9
+ import BackendTask exposing (BackendTask)
10
+ import BackendTask.Http
11
+ import BackendTask.Internal.Request
12
+ import Json.Decode as Decode
13
+ import Json.Encode as Encode
14
+ import Time
15
+
16
+
17
+ {-| Gives a `Time.Posix` of when the `BackendTask` executes.
18
+
19
+ type alias Data =
20
+ { time : Time.Posix
21
+ }
22
+
23
+ data : BackendTask FatalError Data
24
+ data =
25
+ BackendTask.map Data
26
+ BackendTask.Time.now
27
+
28
+ It's better to use [`Server.Request.requestTime`](Server-Request#requestTime) or `Pages.builtAt` when those are the semantics
29
+ you are looking for. `requestTime` gives you a single reliable and consistent time for when the incoming HTTP request was received in
30
+ a server-rendered Route or server-rendered API Route. `Pages.builtAt` gives a single reliable and consistent time when the
31
+ site was built.
32
+
33
+ `BackendTask.Time.now` gives you the time that it happened to execute, which might give you what you need, but be
34
+ aware that the time you get is dependent on how BackendTask's are scheduled and executed internally in elm-pages, and
35
+ its best to avoid depending on that variation when possible.
36
+
37
+ -}
38
+ now : BackendTask error Time.Posix
39
+ now =
40
+ BackendTask.Internal.Request.request
41
+ { name = "now"
42
+ , body =
43
+ BackendTask.Http.jsonBody Encode.null
44
+ , expect =
45
+ BackendTask.Http.expectJson
46
+ (Decode.int |> Decode.map Time.millisToPosix)
47
+ }
@@ -22,9 +22,16 @@ type Response error
22
22
  = Response
23
23
  { fields : List ( String, String )
24
24
  , errors : Dict String (List error)
25
+ , clientErrors : Dict String (List error)
25
26
  }
26
27
 
27
28
 
28
- unwrapResponse : Response error -> { fields : List ( String, String ), errors : Dict String (List error) }
29
+ unwrapResponse :
30
+ Response error
31
+ ->
32
+ { fields : List ( String, String )
33
+ , errors : Dict String (List error)
34
+ , clientErrors : Dict String (List error)
35
+ }
29
36
  unwrapResponse (Response response) =
30
37
  response
@@ -906,19 +906,17 @@ formDataWithServerValidation formParsers =
906
906
  ( Pages.Internal.Form.Response
907
907
  { fields = rawFormData_
908
908
  , errors = Dict.empty
909
+ , clientErrors = Dict.empty
909
910
  }
910
911
  , decodedFinal
911
912
  )
912
913
 
913
- ( _, maybeErrors ) ->
914
+ _ ->
914
915
  Err
915
916
  (Pages.Internal.Form.Response
916
917
  { fields = rawFormData_
917
- , errors =
918
- maybeErrors
919
- |> Maybe.map List.NonEmpty.toList
920
- |> Maybe.withDefault []
921
- |> Dict.fromList
918
+ , errors = errors2
919
+ , clientErrors = errors
922
920
  }
923
921
  )
924
922
  )
@@ -933,6 +931,7 @@ formDataWithServerValidation formParsers =
933
931
  |> Maybe.map List.NonEmpty.toList
934
932
  |> Maybe.withDefault []
935
933
  |> Dict.fromList
934
+ , clientErrors = Dict.empty
936
935
  }
937
936
  )
938
937
  |> BackendTask.succeed
@@ -943,7 +942,7 @@ formDataWithServerValidation formParsers =
943
942
  {-| -}
944
943
  formData :
945
944
  Form.ServerForms error combined
946
- -> Parser ( Form.Response error, Result { fields : List ( String, String ), errors : Dict String (List error) } combined )
945
+ -> Parser ( Form.Response error, Result { fields : List ( String, String ), errors : Dict String (List error), clientErrors : Dict String (List error) } combined )
947
946
  formData formParsers =
948
947
  rawFormData
949
948
  |> andThen
@@ -956,17 +955,18 @@ formData formParsers =
956
955
  in
957
956
  case ( maybeDecoded, errors |> Dict.toList |> List.filter (\( _, value ) -> value |> List.isEmpty |> not) |> List.NonEmpty.fromList ) of
958
957
  ( Just decoded, Nothing ) ->
959
- ( Pages.Internal.Form.Response { fields = [], errors = Dict.empty }
958
+ ( Pages.Internal.Form.Response { fields = [], errors = Dict.empty, clientErrors = Dict.empty }
960
959
  , Ok decoded
961
960
  )
962
961
  |> succeed
963
962
 
964
963
  ( _, maybeErrors ) ->
965
964
  let
966
- record : { fields : List ( String, String ), errors : Dict String (List error) }
965
+ record : { fields : List ( String, String ), errors : Dict String (List error), clientErrors : Dict String (List error) }
967
966
  record =
968
967
  { fields = rawFormData_
969
- , errors =
968
+ , errors = Dict.empty
969
+ , clientErrors =
970
970
  maybeErrors
971
971
  |> Maybe.map List.NonEmpty.toList
972
972
  |> Maybe.withDefault []