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.
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_supervisor.js +1 -1
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
- package/generator/review/elm-stuff/tests-0.19.1/js/node_supervisor.js +1 -1
- package/generator/src/build.js +6 -6
- package/generator/src/cli.js +111 -8
- package/generator/src/codegen.js +1 -1
- package/generator/src/copy-dir.js +2 -2
- package/generator/src/dev-server.js +52 -40
- package/generator/src/dir-helpers.js +3 -4
- package/generator/src/error-formatter.js +0 -3
- package/generator/src/file-helpers.js +1 -1
- package/generator/src/render-worker.js +7 -4
- package/generator/src/render.js +32 -16
- package/generator/src/request-cache.js +18 -7
- package/generator/src/rewrite-client-elm-json.js +1 -1
- package/generator/src/rewrite-elm-json.js +1 -1
- package/package.json +2 -2
- package/src/BackendTask/Random.elm +79 -0
- package/src/BackendTask/Time.elm +47 -0
- package/src/Pages/Internal/Form.elm +8 -1
- package/src/Server/Request.elm +10 -10
|
Binary file
|
|
@@ -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:
|
|
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
|
|
Binary file
|
|
@@ -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:
|
|
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
|
package/generator/src/build.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
628
|
+
portsFilePath: "./.elm-pages/compiled-ports/custom-backend-task.mjs",
|
|
629
629
|
htmlTemplate: processedIndexTemplate,
|
|
630
630
|
});
|
|
631
631
|
console.log("Success - Adapter script complete");
|
package/generator/src/cli.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
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();
|
package/generator/src/codegen.js
CHANGED
|
@@ -143,10 +143,11 @@ export async function start(options) {
|
|
|
143
143
|
merge_vite_configs(
|
|
144
144
|
{
|
|
145
145
|
server: {
|
|
146
|
-
middlewareMode:
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
204
|
+
} else {
|
|
205
|
+
global.portsFilePath = null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
187
209
|
},
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
@@ -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,
|
package/generator/src/render.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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 (
|
|
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",
|
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.
|
|
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.
|
|
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 :
|
|
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
|
package/src/Server/Request.elm
CHANGED
|
@@ -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
|
-
|
|
914
|
+
_ ->
|
|
914
915
|
Err
|
|
915
916
|
(Pages.Internal.Form.Response
|
|
916
917
|
{ fields = rawFormData_
|
|
917
|
-
, errors =
|
|
918
|
-
|
|
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 []
|