elm-pages 3.0.0-beta.4 → 3.0.0-beta.40

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 (140) hide show
  1. package/README.md +10 -1
  2. package/adapter/netlify.js +207 -0
  3. package/codegen/{elm-pages-codegen.js → elm-pages-codegen.cjs} +2678 -2725
  4. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmi +0 -0
  5. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmo +0 -0
  6. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateDataTest.elmo +0 -0
  7. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
  8. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
  9. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
  10. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm.json +1 -1
  11. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +1447 -342
  12. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Runner.elm.js +17004 -13817
  13. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
  14. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_supervisor.js +4 -4
  15. package/generator/dead-code-review/elm.json +9 -7
  16. package/generator/dead-code-review/src/Pages/Review/DeadCodeEliminateData.elm +59 -10
  17. package/generator/dead-code-review/tests/Pages/Review/DeadCodeEliminateDataTest.elm +52 -36
  18. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Internal-RoutePattern.elmi +0 -0
  19. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Internal-RoutePattern.elmo +0 -0
  20. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolations.elmi +0 -0
  21. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolations.elmo +0 -0
  22. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
  23. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
  24. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
  25. package/generator/review/elm-stuff/tests-0.19.1/elm.json +1 -1
  26. package/generator/review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +1447 -342
  27. package/generator/review/elm-stuff/tests-0.19.1/js/Runner.elm.js +25025 -21739
  28. package/generator/review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
  29. package/generator/review/elm-stuff/tests-0.19.1/js/node_supervisor.js +4 -4
  30. package/generator/review/elm.json +10 -10
  31. package/generator/src/RouteBuilder.elm +115 -109
  32. package/generator/src/SharedTemplate.elm +3 -2
  33. package/generator/src/SiteConfig.elm +3 -2
  34. package/generator/src/basepath-middleware.js +3 -3
  35. package/generator/src/build.js +209 -92
  36. package/generator/src/cli.js +292 -88
  37. package/generator/src/codegen.js +29 -27
  38. package/generator/src/compatibility-key.js +3 -0
  39. package/generator/src/compile-elm.js +43 -26
  40. package/generator/src/config.js +39 -0
  41. package/generator/src/copy-dir.js +2 -2
  42. package/generator/src/dev-server.js +176 -138
  43. package/generator/src/dir-helpers.js +9 -26
  44. package/generator/src/elm-codegen.js +5 -4
  45. package/generator/src/elm-file-constants.js +2 -3
  46. package/generator/src/error-formatter.js +12 -11
  47. package/generator/src/file-helpers.js +3 -4
  48. package/generator/src/generate-template-module-connector.js +23 -23
  49. package/generator/src/init.js +9 -8
  50. package/generator/src/pre-render-html.js +39 -28
  51. package/generator/src/render-test.js +109 -0
  52. package/generator/src/render-worker.js +25 -28
  53. package/generator/src/render.js +321 -142
  54. package/generator/src/request-cache.js +265 -162
  55. package/generator/src/resolve-elm-module.js +64 -0
  56. package/generator/src/rewrite-client-elm-json.js +6 -5
  57. package/generator/src/rewrite-elm-json-help.js +56 -0
  58. package/generator/src/rewrite-elm-json.js +17 -7
  59. package/generator/src/route-codegen-helpers.js +16 -31
  60. package/generator/src/seo-renderer.js +12 -7
  61. package/generator/src/vite-utils.js +77 -0
  62. package/generator/static-code/elm-pages.js +10 -0
  63. package/generator/static-code/hmr.js +79 -13
  64. package/generator/template/app/Api.elm +6 -5
  65. package/generator/template/app/Effect.elm +123 -0
  66. package/generator/template/app/ErrorPage.elm +37 -6
  67. package/generator/template/app/Route/Index.elm +17 -10
  68. package/generator/template/app/Shared.elm +24 -47
  69. package/generator/template/app/Site.elm +19 -6
  70. package/generator/template/app/View.elm +1 -8
  71. package/generator/template/elm-tooling.json +0 -3
  72. package/generator/template/elm.json +32 -24
  73. package/generator/template/package.json +10 -4
  74. package/package.json +30 -27
  75. package/src/ApiRoute.elm +199 -61
  76. package/src/BackendTask/Custom.elm +325 -0
  77. package/src/BackendTask/Env.elm +90 -0
  78. package/src/{DataSource → BackendTask}/File.elm +171 -56
  79. package/src/{DataSource → BackendTask}/Glob.elm +136 -125
  80. package/src/BackendTask/Http.elm +679 -0
  81. package/src/{DataSource → BackendTask}/Internal/Glob.elm +1 -1
  82. package/src/BackendTask/Internal/Request.elm +69 -0
  83. package/src/BackendTask/Random.elm +79 -0
  84. package/src/BackendTask/Time.elm +47 -0
  85. package/src/BackendTask.elm +537 -0
  86. package/src/FatalError.elm +90 -0
  87. package/src/Head.elm +237 -7
  88. package/src/HtmlPrinter.elm +7 -3
  89. package/src/Internal/ApiRoute.elm +7 -5
  90. package/src/PageServerResponse.elm +6 -1
  91. package/src/Pages/Form.elm +229 -0
  92. package/src/Pages/GeneratorProgramConfig.elm +15 -0
  93. package/src/Pages/Internal/FatalError.elm +5 -0
  94. package/src/Pages/Internal/Msg.elm +93 -0
  95. package/src/Pages/Internal/Platform/Cli.elm +612 -763
  96. package/src/Pages/Internal/Platform/CompatibilityKey.elm +6 -0
  97. package/src/Pages/Internal/Platform/Effect.elm +1 -2
  98. package/src/Pages/Internal/Platform/GeneratorApplication.elm +379 -0
  99. package/src/Pages/Internal/Platform/StaticResponses.elm +65 -276
  100. package/src/Pages/Internal/Platform/ToJsPayload.elm +6 -9
  101. package/src/Pages/Internal/Platform.elm +327 -194
  102. package/src/Pages/Internal/Script.elm +17 -0
  103. package/src/Pages/Internal/StaticHttpBody.elm +35 -1
  104. package/src/Pages/Manifest.elm +29 -4
  105. package/src/Pages/PageUrl.elm +23 -9
  106. package/src/Pages/ProgramConfig.elm +26 -15
  107. package/src/Pages/Script.elm +109 -0
  108. package/src/Pages/SiteConfig.elm +3 -2
  109. package/src/Pages/StaticHttp/Request.elm +2 -2
  110. package/src/Pages/StaticHttpRequest.elm +23 -99
  111. package/src/Pages/Transition.elm +12 -3
  112. package/src/PagesMsg.elm +82 -0
  113. package/src/Path.elm +16 -19
  114. package/src/QueryParams.elm +21 -172
  115. package/src/RequestsAndPending.elm +37 -20
  116. package/src/Result/Extra.elm +26 -0
  117. package/src/Scaffold/Form.elm +546 -0
  118. package/src/Scaffold/Route.elm +1402 -0
  119. package/src/Server/Request.elm +73 -72
  120. package/src/Server/Session.elm +62 -42
  121. package/src/Server/SetCookie.elm +12 -4
  122. package/src/Stub.elm +53 -0
  123. package/src/Test/Html/Internal/ElmHtml/ToString.elm +8 -9
  124. package/src/DataSource/Env.elm +0 -38
  125. package/src/DataSource/Http.elm +0 -446
  126. package/src/DataSource/Internal/Request.elm +0 -20
  127. package/src/DataSource/Port.elm +0 -90
  128. package/src/DataSource.elm +0 -538
  129. package/src/Form/Field.elm +0 -717
  130. package/src/Form/FieldStatus.elm +0 -36
  131. package/src/Form/FieldView.elm +0 -417
  132. package/src/Form/FormData.elm +0 -22
  133. package/src/Form/Validation.elm +0 -391
  134. package/src/Form/Value.elm +0 -118
  135. package/src/Form.elm +0 -1683
  136. package/src/FormDecoder.elm +0 -102
  137. package/src/Pages/FormState.elm +0 -256
  138. package/src/Pages/Generate.elm +0 -800
  139. package/src/Pages/Internal/Form.elm +0 -17
  140. package/src/Pages/Msg.elm +0 -79
@@ -1,172 +1,225 @@
1
- const path = require("path");
2
- const fetch = require("node-fetch");
3
- const objectHash = require("object-hash");
4
- const kleur = require("kleur");
1
+ import * as path from "path";
2
+ import * as fsPromises from "fs/promises";
3
+ import * as kleur from "kleur/colors";
4
+ import { default as makeFetchHappenOriginal } from "make-fetch-happen";
5
5
 
6
- /**
7
- * To cache HTTP requests on disk with quick lookup and insertion, we store the hashed request.
8
- * This uses SHA1 hashes. They are uni-directional hashes, which works for this use case. Most importantly,
9
- * they're unique enough and can be expressed in a case-insensitive way so it works on Windows filesystems.
10
- * And they are 40 hex characters, so the length won't be too long no matter what the request payload.
11
- * @param {Object} request
12
- */
13
- function requestToString(request) {
14
- return objectHash(request);
15
- }
16
- /**
17
- * @param {Object} request
18
- */
19
- function fullPath(portsHash, request, hasFsAccess) {
20
- const requestWithPortHash =
21
- request.url === "elm-pages-internal://port"
22
- ? { portsHash, ...request }
23
- : request;
24
- if (hasFsAccess) {
25
- return path.join(
26
- process.cwd(),
27
- ".elm-pages",
28
- "http-response-cache",
29
- requestToString(requestWithPortHash)
30
- );
31
- } else {
32
- return path.join("/", requestToString(requestWithPortHash));
33
- }
34
- }
6
+ const defaultHttpCachePath = "./.elm-pages/http-cache";
7
+
8
+ /** @typedef {{kind: 'cache-response-path', value: string} | {kind: 'response-json', value: JSON}} Response */
35
9
 
36
10
  /**
37
11
  * @param {string} mode
38
12
  * @param {{url: string;headers: {[x: string]: string;};method: string;body: Body;}} rawRequest
39
- * @returns {Promise<string>}
40
- * @param {string} portsFile
13
+ * @param {Record<string, unknown>} portsFile
41
14
  * @param {boolean} hasFsAccess
15
+ * @returns {Promise<Response>}
42
16
  */
43
- function lookupOrPerform(portsFile, mode, rawRequest, hasFsAccess, useCache) {
44
- const { fs } = require("./request-cache-fs.js")(hasFsAccess);
17
+ export function lookupOrPerform(
18
+ portsFile,
19
+ mode,
20
+ rawRequest,
21
+ hasFsAccess,
22
+ useCache
23
+ ) {
24
+ const makeFetchHappen = makeFetchHappenOriginal.defaults({
25
+ cache: mode === "build" ? "no-cache" : "default",
26
+ });
45
27
  return new Promise(async (resolve, reject) => {
46
28
  const request = toRequest(rawRequest);
47
- const portsHash = (portsFile && portsFile.match(/-([^-]+)\.js$/)[1]) || "";
48
- const responsePath = fullPath(portsHash, request, hasFsAccess);
49
29
 
50
- // TODO check cache expiration time and delete and go to else if expired
51
- if (useCache && (await checkFileExists(fs, responsePath))) {
52
- // console.log("Skipping request, found file.");
53
- resolve(responsePath);
54
- } else {
55
- let portDataSource = {};
56
- let portDataSourceImportError = null;
57
- try {
58
- if (portsFile === undefined) {
59
- throw "missing";
60
- }
61
- const portDataSourcePath = path.resolve(portsFile);
62
- // On Windows, we need cannot use paths directly and instead must use a file:// URL.
63
- // portDataSource = await require(url.pathToFileURL(portDataSourcePath).href);
64
- portDataSource = require(portDataSourcePath);
65
- } catch (e) {
66
- portDataSourceImportError = e;
30
+ let portBackendTask = portsFile;
31
+ let portBackendTaskImportError = null;
32
+ try {
33
+ if (portsFile === undefined) {
34
+ throw "missing";
67
35
  }
36
+ } catch (e) {
37
+ portBackendTaskImportError = e;
38
+ }
68
39
 
69
- if (request.url === "elm-pages-internal://port") {
70
- try {
71
- const { input, portName } = rawRequest.body.args[0];
72
-
73
- if (!portDataSource[portName]) {
74
- if (portDataSourceImportError === null) {
75
- throw `DataSource.Port.send "${portName}" was called, but I couldn't find a function with that name in the port definitions file. Is it exported correctly?`;
76
- } else if (portDataSourceImportError === "missing") {
77
- throw `DataSource.Port.send "${portName}" was called, but I couldn't find the port definitions file. Be sure to create a 'port-data-source.ts' or 'port-data-source.js' file and maybe restart the dev server.`;
78
- } else {
79
- throw `DataSource.Port.send "${portName}" was called, but I couldn't import the port definitions file, because of this exception: \`${portDataSourceImportError}\` Are there syntax errors or expections thrown during import?`;
80
- }
81
- } else if (typeof portDataSource[portName] !== "function") {
82
- throw `DataSource.Port.send "${portName}" was called, but it is not a function. Be sure to export a function with that name from port-data-source.js`;
40
+ if (request.url === "elm-pages-internal://port") {
41
+ try {
42
+ const { input, portName } = rawRequest.body.args[0];
43
+
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]) {
60
+ if (portBackendTaskImportError === null) {
61
+ resolve({
62
+ kind: "response-json",
63
+ value: jsonResponse({
64
+ "elm-pages-internal-error": "CustomBackendTaskNotDefined",
65
+ }),
66
+ });
67
+ } else if (portBackendTaskImportError === "missing") {
68
+ resolve({
69
+ kind: "response-json",
70
+ value: jsonResponse({
71
+ "elm-pages-internal-error": "MissingCustomBackendTaskFile",
72
+ }),
73
+ });
74
+ } else {
75
+ resolve({
76
+ kind: "response-json",
77
+ value: jsonResponse({
78
+ "elm-pages-internal-error": "ErrorInCustomBackendTaskFile",
79
+ error:
80
+ (portBackendTaskImportError &&
81
+ portBackendTaskImportError.stack) ||
82
+ "",
83
+ }),
84
+ });
83
85
  }
84
- await fs.promises.writeFile(
85
- responsePath,
86
- JSON.stringify(jsonResponse(await portDataSource[portName](input)))
87
- );
88
- resolve(responsePath);
89
- } catch (error) {
90
- console.trace(error);
91
- reject({
92
- title: "DataSource.Port Error",
93
- message: error.toString(),
86
+ } else if (typeof portBackendTask[portName] !== "function") {
87
+ resolve({
88
+ kind: "response-json",
89
+ value: jsonResponse({
90
+ "elm-pages-internal-error": "ExportIsNotFunction",
91
+ error: typeof portBackendTask[portName],
92
+ }),
94
93
  });
94
+ } else {
95
+ console.time(`BackendTask.Custom.run "${portName}"`);
96
+ try {
97
+ resolve({
98
+ kind: "response-json",
99
+ value: jsonResponse(
100
+ toElmJson(await portBackendTask[portName](input))
101
+ ),
102
+ });
103
+ } catch (portCallError) {
104
+ if (portCallError instanceof Error) {
105
+ resolve({
106
+ kind: "response-json",
107
+ value: jsonResponse({
108
+ "elm-pages-internal-error": "NonJsonException",
109
+ error: portCallError.message,
110
+ stack: portCallError.stack || null,
111
+ }),
112
+ });
113
+ }
114
+ try {
115
+ resolve({
116
+ kind: "response-json",
117
+ value: jsonResponse({
118
+ "elm-pages-internal-error": "CustomBackendTaskException",
119
+ error: JSON.parse(JSON.stringify(portCallError, null, 0)),
120
+ }),
121
+ });
122
+ } catch (jsonDecodeError) {
123
+ resolve({
124
+ kind: "response-json",
125
+ value: jsonResponse({
126
+ "elm-pages-internal-error": "NonJsonException",
127
+ error: portCallError.toString(),
128
+ }),
129
+ });
130
+ }
131
+ }
132
+ console.timeEnd(`BackendTask.Custom.run "${portName}"`);
95
133
  }
96
- } else {
97
- try {
98
- console.time(`fetch ${request.url}`);
99
- const response = await fetch(request.url, {
100
- method: request.method,
101
- body: request.body,
102
- headers: {
103
- "User-Agent": "request",
104
- ...request.headers,
105
- },
106
- });
134
+ } catch (error) {
135
+ console.trace(error);
136
+ reject({
137
+ title: "BackendTask.Custom Error",
138
+ message: error.toString(),
139
+ });
140
+ }
141
+ } else {
142
+ try {
143
+ console.time(`fetch ${request.url}`);
144
+ const response = await safeFetch(makeFetchHappen, request.url, {
145
+ method: request.method,
146
+ body: request.body,
147
+ headers: {
148
+ "User-Agent": "request",
149
+ ...request.headers,
150
+ },
151
+ ...rawRequest.cacheOptions,
152
+ });
107
153
 
108
- console.timeEnd(`fetch ${request.url}`);
109
- const expectString = request.headers["elm-pages-internal"];
110
-
111
- if (response.ok || expectString === "ExpectResponse") {
112
- let body;
113
- let bodyKind;
114
- if (expectString === "ExpectJson") {
115
- body = await response.json();
116
- bodyKind = "json";
117
- } else if (
118
- expectString === "ExpectBytes" ||
119
- expectString === "ExpectBytesResponse"
120
- ) {
121
- bodyKind = "bytes";
122
- const arrayBuffer = await response.arrayBuffer();
123
- body = Buffer.from(arrayBuffer).toString("base64");
124
- } else if (expectString === "ExpectWhatever") {
125
- bodyKind = "whatever";
126
- body = null;
127
- } else if (
128
- expectString === "ExpectResponse" ||
129
- expectString === "ExpectString"
130
- ) {
131
- bodyKind = "string";
132
- body = await response.text();
133
- } else {
134
- throw `Unexpected expectString ${expectString}`;
135
- }
154
+ console.timeEnd(`fetch ${request.url}`);
155
+ const expectString = request.headers["elm-pages-internal"];
136
156
 
137
- await fs.promises.writeFile(
138
- responsePath,
139
- JSON.stringify({
140
- headers: Object.fromEntries(response.headers.entries()),
141
- statusCode: response.status,
142
- body: body,
143
- bodyKind,
144
- url: response.url,
145
- statusText: response.statusText,
146
- })
147
- );
148
-
149
- resolve(responsePath);
150
- } else {
151
- console.log("@@@ request-cache1 bad HTTP response");
152
- reject({
153
- title: "DataSource.Http Error",
154
- message: `${kleur
155
- .yellow()
156
- .underline(request.url)} Bad HTTP response ${response.status} ${
157
- response.statusText
158
- }
159
- `,
160
- });
157
+ let body;
158
+ let bodyKind;
159
+ if (expectString === "ExpectJson") {
160
+ try {
161
+ body = await response.buffer();
162
+ body = JSON.parse(body.toString("utf-8"));
163
+ bodyKind = "json";
164
+ } catch (error) {
165
+ body = body.toString("utf8");
166
+ bodyKind = "string";
167
+ }
168
+ } else if (
169
+ expectString === "ExpectBytes" ||
170
+ expectString === "ExpectBytesResponse"
171
+ ) {
172
+ body = await response.buffer();
173
+ try {
174
+ body = body.toString("base64");
175
+ bodyKind = "bytes";
176
+ } catch (e) {
177
+ body = body.toString("utf8");
178
+ bodyKind = "string";
161
179
  }
162
- } catch (error) {
163
- console.trace("@@@ request-cache2 HTTP error", error);
164
- reject({
165
- title: "DataSource.Http Error",
166
- message: `${kleur
167
- .yellow()
168
- .underline(request.url)} ${error.toString()}
169
- `,
180
+ } else if (expectString === "ExpectWhatever") {
181
+ bodyKind = "whatever";
182
+ body = null;
183
+ } else if (
184
+ expectString === "ExpectResponse" ||
185
+ expectString === "ExpectString"
186
+ ) {
187
+ bodyKind = "string";
188
+ body = await response.text();
189
+ } else {
190
+ throw `Unexpected expectString ${expectString}`;
191
+ }
192
+
193
+ resolve({
194
+ kind: "response-json",
195
+ value: {
196
+ headers: Object.fromEntries(response.headers.entries()),
197
+ statusCode: response.status,
198
+ body,
199
+ bodyKind,
200
+ url: response.url,
201
+ statusText: response.statusText,
202
+ },
203
+ });
204
+ } catch (error) {
205
+ if (error.code === "ECONNREFUSED") {
206
+ resolve({
207
+ kind: "response-json",
208
+ value: { "elm-pages-internal-error": "NetworkError" },
209
+ });
210
+ } else if (
211
+ error.code === "ETIMEDOUT" ||
212
+ error.code === "ERR_SOCKET_TIMEOUT"
213
+ ) {
214
+ resolve({
215
+ kind: "response-json",
216
+ value: { "elm-pages-internal-error": "Timeout" },
217
+ });
218
+ } else {
219
+ console.trace("elm-pages unhandled HTTP error", error);
220
+ resolve({
221
+ kind: "response-json",
222
+ value: { "elm-pages-internal-error": "NetworkError" },
170
223
  });
171
224
  }
172
225
  }
@@ -174,6 +227,33 @@ function lookupOrPerform(portsFile, mode, rawRequest, hasFsAccess, useCache) {
174
227
  });
175
228
  }
176
229
 
230
+ /**
231
+ * @param {unknown} obj
232
+ * @returns {JSON}
233
+ */
234
+ function toElmJson(obj) {
235
+ if (Array.isArray(obj)) {
236
+ return obj.map(toElmJson);
237
+ } else if (typeof obj === "object") {
238
+ for (let key in obj) {
239
+ const value = obj[key];
240
+ if (typeof value === "undefined") {
241
+ obj[key] = null;
242
+ } else if (value instanceof Date) {
243
+ obj[key] = {
244
+ "__elm-pages-normalized__": {
245
+ kind: "Date",
246
+ value: Math.floor(value.getTime()),
247
+ },
248
+ };
249
+ // } else if (value instanceof Object) {
250
+ // toElmJson(obj);
251
+ }
252
+ }
253
+ }
254
+ return obj;
255
+ }
256
+
177
257
  /**
178
258
  * @param {{url: string; headers: {[x: string]: string}; method: string; body: Body } } elmRequest
179
259
  */
@@ -188,15 +268,6 @@ function toRequest(elmRequest) {
188
268
  body: toBody(elmRequest.body),
189
269
  };
190
270
  }
191
- /**
192
- * @param {string} file
193
- */
194
- function checkFileExists(fs, file) {
195
- return fs.promises
196
- .access(file, fs.constants.F_OK)
197
- .then(() => true)
198
- .catch(() => false);
199
- }
200
271
  /**
201
272
  * @param {Body} body
202
273
  */
@@ -208,6 +279,9 @@ function toBody(body) {
208
279
  case "StringBody": {
209
280
  return body.args[1];
210
281
  }
282
+ case "BytesBody": {
283
+ return Buffer.from(body.args[1], "base64");
284
+ }
211
285
  case "JsonBody": {
212
286
  return JSON.stringify(body.args[0]);
213
287
  }
@@ -226,13 +300,16 @@ function toContentType(body) {
226
300
  case "StringBody": {
227
301
  return { "Content-Type": body.args[0] };
228
302
  }
303
+ case "BytesBody": {
304
+ return { "Content-Type": body.args[0] };
305
+ }
229
306
  case "JsonBody": {
230
307
  return { "Content-Type": "application/json" };
231
308
  }
232
309
  }
233
310
  }
234
311
 
235
- /** @typedef { { tag: 'EmptyBody'} | { tag: 'StringBody'; args: [string, string] } | {tag: 'JsonBody'; args: [ Object ] } } Body */
312
+ /** @typedef { { tag: 'EmptyBody'} |{ tag: 'BytesBody'; args: [string, string] } | { tag: 'StringBody'; args: [string, string] } | {tag: 'JsonBody'; args: [ Object ] } } Body */
236
313
  function requireUncached(mode, filePath) {
237
314
  if (mode === "dev-server") {
238
315
  // for the build command, we can skip clearing the cache because it won't change while the build is running
@@ -249,4 +326,30 @@ function jsonResponse(json) {
249
326
  return { bodyKind: "json", body: json };
250
327
  }
251
328
 
252
- module.exports = { lookupOrPerform };
329
+ async function safeFetch(makeFetchHappen, url, options) {
330
+ const { cachePath, ...optionsWithoutCachePath } = options;
331
+ const cachePathWithDefault = cachePath || defaultHttpCachePath;
332
+ if (await canAccess(cachePathWithDefault)) {
333
+ return await makeFetchHappen(url, {
334
+ cachePath: cachePathWithDefault,
335
+ ...options,
336
+ });
337
+ } else {
338
+ return await makeFetchHappen(url, {
339
+ cache: "no-store",
340
+ ...optionsWithoutCachePath,
341
+ });
342
+ }
343
+ }
344
+
345
+ async function canAccess(filePath) {
346
+ try {
347
+ await fsPromises.access(
348
+ filePath,
349
+ fsPromises.constants.R_OK | fsPromises.constants.W_OK
350
+ );
351
+ return true;
352
+ } catch {
353
+ return false;
354
+ }
355
+ }
@@ -0,0 +1,64 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+
4
+ function findNearestElmJson(filePath) {
5
+ function searchForElmJson(directory) {
6
+ if (directory === "/") {
7
+ return null;
8
+ }
9
+
10
+ const elmJsonPath = path.join(directory, "elm.json");
11
+ return fs.existsSync(elmJsonPath)
12
+ ? elmJsonPath
13
+ : searchForElmJson(path.dirname(directory));
14
+ }
15
+
16
+ return searchForElmJson(path.dirname(filePath));
17
+ }
18
+
19
+ function getElmModuleName(inputPath) {
20
+ const filePath = path.normalize(
21
+ path.isAbsolute(inputPath) ? inputPath : path.resolve(inputPath)
22
+ );
23
+ const elmJsonPath = findNearestElmJson(filePath);
24
+
25
+ if (!elmJsonPath) {
26
+ throw new Error("No elm.json found");
27
+ }
28
+
29
+ const elmJson = JSON.parse(fs.readFileSync(elmJsonPath, "utf8"));
30
+ const sourceDirectories = elmJson["source-directories"];
31
+ const projectDirectory = path.dirname(elmJsonPath);
32
+
33
+ const matchingSourceDir = sourceDirectories
34
+ .map((sourceDir) => path.join(projectDirectory, sourceDir))
35
+ .find((absoluteSourceDir) => filePath.startsWith(absoluteSourceDir));
36
+
37
+ if (!matchingSourceDir) {
38
+ throw new Error(
39
+ "File is not in any source-directories specified in elm.json"
40
+ );
41
+ }
42
+
43
+ const relativePath = path.relative(matchingSourceDir, filePath);
44
+ const moduleName = relativePath
45
+ .replace(path.extname(relativePath), "")
46
+ .replace("/", ".");
47
+
48
+ return { projectDirectory, moduleName, sourceDirectory: matchingSourceDir };
49
+ }
50
+
51
+ export function resolveInputPathOrModuleName(inputPathOrModuleName) {
52
+ if (
53
+ /^[A-Z][a-zA-Z0-9_]*(\.[A-Z][a-zA-Z0-9_]*)*$/.test(inputPathOrModuleName)
54
+ ) {
55
+ const absolutePathForScript = path.resolve("./script/src");
56
+ return {
57
+ moduleName: inputPathOrModuleName,
58
+ projectDirectory: path.resolve("./script"),
59
+ sourceDirectory: path.resolve("./script/src"),
60
+ };
61
+ } else {
62
+ return getElmModuleName(inputPathOrModuleName);
63
+ }
64
+ }
@@ -1,6 +1,6 @@
1
- const fs = require("fs");
1
+ import * as fs from "node:fs";
2
2
 
3
- module.exports = async function () {
3
+ export async function rewriteClientElmJson() {
4
4
  var elmJson = JSON.parse(
5
5
  (await fs.promises.readFile("./elm.json")).toString()
6
6
  );
@@ -9,11 +9,11 @@ module.exports = async function () {
9
9
 
10
10
  await writeFileIfChanged(
11
11
  "./elm-stuff/elm-pages/client/elm.json",
12
- JSON.stringify(rewriteElmJson(elmJson))
12
+ JSON.stringify(rewriteClientElmJsonHelp(elmJson))
13
13
  );
14
- };
14
+ }
15
15
 
16
- function rewriteElmJson(elmJson) {
16
+ function rewriteClientElmJsonHelp(elmJson) {
17
17
  // The internal generated file will be at:
18
18
  // ./elm-stuff/elm-pages/
19
19
  // So, we need to take the existing elmJson and
@@ -27,6 +27,7 @@ function rewriteElmJson(elmJson) {
27
27
  elmJson["source-directories"] = elmJson["source-directories"].map((item) => {
28
28
  return "../../../" + item;
29
29
  });
30
+ elmJson["dependencies"]["direct"]["lamdera/codecs"] = "1.0.0";
30
31
  // 3. add our own secret My.elm module 😈
31
32
  elmJson["source-directories"].push(".elm-pages");
32
33
  elmJson["source-directories"].push("app");
@@ -0,0 +1,56 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+
4
+ /**
5
+ * @param {string} sourceElmJsonPath
6
+ * @param {string} targetElmJsonPath
7
+ * @param {( (arg0: JSON) => JSON )?} modifyElmJson
8
+ */
9
+ export async function rewriteElmJson(
10
+ sourceElmJsonPath,
11
+ targetElmJsonPath,
12
+ modifyElmJson
13
+ ) {
14
+ if (!modifyElmJson) {
15
+ modifyElmJson = function (json) {
16
+ return json;
17
+ };
18
+ }
19
+ var elmJson = JSON.parse(
20
+ (
21
+ await fs.promises.readFile(path.join(sourceElmJsonPath, "elm.json"))
22
+ ).toString()
23
+ );
24
+
25
+ let modifiedElmJson = modifyElmJson(elmJson);
26
+ // always add `lamdera/codecs` dependency
27
+ modifiedElmJson["dependencies"]["direct"]["lamdera/codecs"] = "1.0.0";
28
+
29
+ // write new elm.json
30
+ await writeFileIfChanged(
31
+ path.join(targetElmJsonPath, "elm.json"),
32
+ JSON.stringify(modifiedElmJson)
33
+ );
34
+ }
35
+
36
+ /**
37
+ * @param {fs.PathLike | fs.promises.FileHandle} filePath
38
+ * @param {string | NodeJS.ArrayBufferView | Iterable<string | NodeJS.ArrayBufferView> | AsyncIterable<string | NodeJS.ArrayBufferView> | import("stream").Stream} content
39
+ */
40
+ async function writeFileIfChanged(filePath, content) {
41
+ if (
42
+ !(await fileExists(filePath)) ||
43
+ (await fs.promises.readFile(filePath, "utf8")) !== content
44
+ ) {
45
+ await fs.promises.writeFile(filePath, content);
46
+ }
47
+ }
48
+ /**
49
+ * @param {fs.PathLike} file
50
+ */
51
+ function fileExists(file) {
52
+ return fs.promises
53
+ .access(file, fs.constants.F_OK)
54
+ .then(() => true)
55
+ .catch(() => false);
56
+ }
@@ -1,19 +1,23 @@
1
- const fs = require("fs");
1
+ import * as fs from "node:fs";
2
2
 
3
- module.exports = async function () {
3
+ export async function rewriteElmJson(
4
+ sourceElmJsonPath,
5
+ targetElmJsonPath,
6
+ options
7
+ ) {
4
8
  var elmJson = JSON.parse(
5
- (await fs.promises.readFile("./elm.json")).toString()
9
+ (await fs.promises.readFile(sourceElmJsonPath)).toString()
6
10
  );
7
11
 
8
12
  // write new elm.json
9
13
 
10
14
  await writeFileIfChanged(
11
- "./elm-stuff/elm-pages/elm.json",
12
- JSON.stringify(rewriteElmJson(elmJson))
15
+ targetElmJsonPath,
16
+ JSON.stringify(rewriteElmJsonHelp(elmJson, options))
13
17
  );
14
- };
18
+ }
15
19
 
16
- function rewriteElmJson(elmJson) {
20
+ function rewriteElmJsonHelp(elmJson, options) {
17
21
  // The internal generated file will be at:
18
22
  // ./elm-stuff/elm-pages/
19
23
  // So, we need to take the existing elmJson and
@@ -27,6 +31,12 @@ function rewriteElmJson(elmJson) {
27
31
  elmJson["source-directories"] = elmJson["source-directories"].map((item) => {
28
32
  return "../../" + item;
29
33
  });
34
+ if (options && options.executableName === "elm") {
35
+ // elm, don't add lamdera/codecs
36
+ } else {
37
+ // lamdera, add codecs dependency
38
+ elmJson["dependencies"]["direct"]["lamdera/codecs"] = "1.0.0";
39
+ }
30
40
  // 3. add our own secret My.elm module 😈
31
41
  elmJson["source-directories"].push(".elm-pages");
32
42
  return elmJson;