elm-pages 2.1.11 → 3.0.0-beta.0

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 (136) hide show
  1. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Internal-RoutePattern.elmi +0 -0
  2. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Internal-RoutePattern.elmo +0 -0
  3. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolations.elmi +0 -0
  4. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolations.elmo +0 -0
  5. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolationsTest.elmi +0 -0
  6. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolationsTest.elmo +0 -0
  7. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Reporter.elmi +0 -0
  8. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Reporter.elmo +0 -0
  9. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Runner.elmi +0 -0
  10. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Runner.elmo +0 -0
  11. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
  12. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
  13. package/generator/{template/public/style.css → review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/lock} +0 -0
  14. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
  15. package/generator/review/elm-stuff/tests-0.19.1/elm.json +1 -0
  16. package/generator/review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +6795 -0
  17. package/generator/review/elm-stuff/tests-0.19.1/js/Runner.elm.js +27617 -0
  18. package/generator/review/elm-stuff/tests-0.19.1/js/node_runner.js +110 -0
  19. package/generator/review/elm-stuff/tests-0.19.1/js/node_supervisor.js +187 -0
  20. package/generator/review/elm-stuff/tests-0.19.1/js/package.json +1 -0
  21. package/generator/review/elm-stuff/tests-0.19.1/src/Reporter.elm +26 -0
  22. package/generator/review/elm-stuff/tests-0.19.1/src/Runner.elm +62 -0
  23. package/generator/review/elm.json +13 -4
  24. package/{src → generator/review/src}/Pages/Review/NoContractViolations.elm +148 -148
  25. package/generator/review/tests/Pages/Review/NoContractViolationsTest.elm +331 -0
  26. package/generator/src/RouteBuilder.elm +420 -0
  27. package/generator/src/SharedTemplate.elm +4 -5
  28. package/generator/src/SiteConfig.elm +3 -9
  29. package/generator/src/build.js +308 -95
  30. package/generator/src/cli.js +103 -8
  31. package/generator/src/codegen.js +192 -35
  32. package/generator/src/compile-elm.js +183 -31
  33. package/generator/src/dev-server.js +353 -96
  34. package/generator/src/elm-application.json +3 -1
  35. package/generator/src/elm-codegen.js +34 -0
  36. package/generator/src/elm-file-constants.js +2 -0
  37. package/generator/src/error-formatter.js +20 -1
  38. package/generator/src/generate-template-module-connector.js +120 -924
  39. package/generator/src/hello.ts +5 -0
  40. package/generator/src/pre-render-html.js +58 -104
  41. package/generator/src/render-worker.js +27 -13
  42. package/generator/src/render.js +252 -197
  43. package/generator/src/request-cache-fs.js +18 -0
  44. package/generator/src/request-cache.js +128 -56
  45. package/generator/src/rewrite-client-elm-json.js +49 -0
  46. package/generator/src/route-codegen-helpers.js +62 -1
  47. package/generator/static-code/dev-style.css +22 -0
  48. package/generator/static-code/elm-pages.js +43 -39
  49. package/generator/static-code/hmr.js +98 -88
  50. package/generator/template/app/Api.elm +25 -0
  51. package/generator/template/app/ErrorPage.elm +38 -0
  52. package/generator/template/app/Route/Index.elm +87 -0
  53. package/generator/template/{src → app}/Shared.elm +34 -13
  54. package/generator/template/app/Site.elm +19 -0
  55. package/generator/template/{src → app}/View.elm +0 -0
  56. package/generator/template/elm-pages.config.mjs +5 -0
  57. package/generator/template/elm.json +1 -0
  58. package/generator/template/{public/index.js → index.ts} +7 -3
  59. package/generator/template/package.json +4 -4
  60. package/generator/template/public/favicon.ico +0 -0
  61. package/generator/template/public/images/icon-png.png +0 -0
  62. package/generator/template/src/.gitkeep +0 -0
  63. package/generator/template/style.css +4 -0
  64. package/package.json +30 -23
  65. package/src/ApiRoute.elm +176 -43
  66. package/src/BuildError.elm +10 -1
  67. package/src/CookieParser.elm +84 -0
  68. package/src/DataSource/Env.elm +38 -0
  69. package/src/DataSource/File.elm +27 -16
  70. package/src/DataSource/Glob.elm +126 -80
  71. package/src/DataSource/Http.elm +283 -304
  72. package/src/DataSource/Internal/Glob.elm +5 -21
  73. package/src/DataSource/Internal/Request.elm +25 -0
  74. package/src/DataSource/Port.elm +17 -14
  75. package/src/DataSource.elm +55 -318
  76. package/src/Form/Field.elm +717 -0
  77. package/src/Form/FieldStatus.elm +36 -0
  78. package/src/Form/FieldView.elm +417 -0
  79. package/src/Form/FormData.elm +22 -0
  80. package/src/Form/Validation.elm +391 -0
  81. package/src/Form/Value.elm +118 -0
  82. package/src/Form.elm +1683 -0
  83. package/src/FormData.elm +58 -0
  84. package/src/FormDecoder.elm +102 -0
  85. package/src/Head/Seo.elm +12 -4
  86. package/src/Head.elm +12 -2
  87. package/src/HtmlPrinter.elm +1 -1
  88. package/src/Internal/ApiRoute.elm +17 -4
  89. package/src/Internal/Request.elm +7 -0
  90. package/src/PageServerResponse.elm +68 -0
  91. package/src/Pages/ContentCache.elm +1 -229
  92. package/src/Pages/Fetcher.elm +58 -0
  93. package/src/Pages/FormState.elm +256 -0
  94. package/src/Pages/Generate.elm +800 -0
  95. package/src/Pages/Internal/Form.elm +17 -0
  96. package/src/Pages/Internal/NotFoundReason.elm +3 -55
  97. package/src/Pages/Internal/Platform/Cli.elm +777 -579
  98. package/src/Pages/Internal/Platform/Effect.elm +5 -5
  99. package/src/Pages/Internal/Platform/StaticResponses.elm +178 -394
  100. package/src/Pages/Internal/Platform/ToJsPayload.elm +24 -23
  101. package/src/Pages/Internal/Platform.elm +1244 -504
  102. package/src/Pages/Internal/ResponseSketch.elm +19 -0
  103. package/src/Pages/Internal/RoutePattern.elm +596 -45
  104. package/src/Pages/Manifest.elm +26 -0
  105. package/src/Pages/Msg.elm +79 -0
  106. package/src/Pages/ProgramConfig.elm +67 -14
  107. package/src/Pages/SiteConfig.elm +3 -6
  108. package/src/Pages/StaticHttp/Request.elm +4 -2
  109. package/src/Pages/StaticHttpRequest.elm +50 -215
  110. package/src/Pages/Transition.elm +70 -0
  111. package/src/Path.elm +1 -0
  112. package/src/Pattern.elm +98 -0
  113. package/src/RenderRequest.elm +2 -2
  114. package/src/RequestsAndPending.elm +111 -9
  115. package/src/Server/Request.elm +1253 -0
  116. package/src/Server/Response.elm +292 -0
  117. package/src/Server/Session.elm +316 -0
  118. package/src/Server/SetCookie.elm +169 -0
  119. package/src/TerminalText.elm +1 -1
  120. package/src/Test/Html/Internal/ElmHtml/Markdown.elm +0 -1
  121. package/src/Test/Html/Internal/ElmHtml/ToString.elm +1 -1
  122. package/generator/src/Page.elm +0 -359
  123. package/generator/src/codegen-template-module.js +0 -183
  124. package/generator/src/elm-pages-js-minified.js +0 -1
  125. package/generator/template/src/Api.elm +0 -14
  126. package/generator/template/src/Page/Index.elm +0 -69
  127. package/generator/template/src/Site.elm +0 -41
  128. package/src/DataSource/ServerRequest.elm +0 -60
  129. package/src/Internal/OptimizedDecoder.elm +0 -18
  130. package/src/KeepOrDiscard.elm +0 -6
  131. package/src/OptimizedDecoder/Pipeline.elm +0 -335
  132. package/src/OptimizedDecoder.elm +0 -818
  133. package/src/Pages/Internal/ApplicationType.elm +0 -6
  134. package/src/Pages/Secrets.elm +0 -83
  135. package/src/Secrets.elm +0 -111
  136. package/src/SecretsDict.elm +0 -45
@@ -1,14 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const build = require("./build.js");
4
+ const dirHelpers = require("./dir-helpers.js");
4
5
  const dev = require("./dev-server.js");
5
- const generate = require("./codegen-template-module.js");
6
6
  const init = require("./init.js");
7
7
  const codegen = require("./codegen.js");
8
8
  const fs = require("fs");
9
9
  const path = require("path");
10
+ const { restoreColorSafe } = require("./error-formatter");
10
11
 
11
12
  const commander = require("commander");
13
+ const { compileCliApp } = require("./compile-elm.js");
14
+ const { runElmCodegenInstall } = require("./elm-codegen.js");
15
+ const Argument = commander.Argument;
16
+ const Option = commander.Option;
12
17
 
13
18
  const packageVersion = require("../../package.json").version;
14
19
 
@@ -38,10 +43,25 @@ async function main() {
38
43
  await build.run(options);
39
44
  });
40
45
 
46
+ program
47
+ .command("gen")
48
+ .option(
49
+ "--base <basePath>",
50
+ "build site to be served under a base path",
51
+ "/"
52
+ )
53
+ .description(
54
+ "generate code, useful for CI where you don't want to run a full build"
55
+ )
56
+ .action(async (options) => {
57
+ await codegen.generate(options.base);
58
+ });
59
+
41
60
  program
42
61
  .command("dev")
43
62
  .description("start a dev server")
44
63
  .option("--port <number>", "serve site at localhost:<port>", "1234")
64
+ .option("--debug", "Run elm make with --debug")
45
65
  .option(
46
66
  "--keep-cache",
47
67
  "Preserve the HTTP and JS Port cache instead of deleting it on server start"
@@ -56,13 +76,6 @@ async function main() {
56
76
  await dev.start(options);
57
77
  });
58
78
 
59
- program
60
- .command("add <moduleName>")
61
- .description("create a new Page module")
62
- .action(async (moduleName) => {
63
- await generate.run({ moduleName });
64
- });
65
-
66
79
  program
67
80
  .command("init <projectName>")
68
81
  .description("scaffold a new elm-pages project boilerplate")
@@ -70,6 +83,69 @@ async function main() {
70
83
  await init.run(projectName);
71
84
  });
72
85
 
86
+ program
87
+ .command("codegen <moduleName>")
88
+ .description("run a generator")
89
+ .allowUnknownOption()
90
+ .allowExcessArguments()
91
+ .action(async (moduleName, options, options2) => {
92
+ if (!/^[A-Z][a-zA-Z0-9_]*(\.[A-Z][a-zA-Z0-9_]*)*$/.test(moduleName)) {
93
+ throw `Invalid module name "${moduleName}", must be in the format of an Elm module`;
94
+ }
95
+ const splitModuleName = moduleName.split(".");
96
+ const expectedFilePath = path.join(
97
+ process.cwd(),
98
+ "codegen",
99
+ `${splitModuleName.join("/")}.elm`
100
+ );
101
+ if (!fs.existsSync(expectedFilePath)) {
102
+ throw `I couldn't find a module named ${expectedFilePath}`;
103
+ }
104
+ try {
105
+ await codegen.generate("");
106
+ await runElmCodegenInstall();
107
+ await compileCliApp(
108
+ // { debug: true },
109
+ {},
110
+ `${splitModuleName.join("/")}.elm`,
111
+ path.join(process.cwd(), "codegen/elm-stuff/scaffold.js"),
112
+ "codegen",
113
+
114
+ path.join(process.cwd(), "codegen/elm-stuff/scaffold.js")
115
+ );
116
+ } catch (error) {
117
+ console.log(restoreColorSafe(error));
118
+ process.exit(1);
119
+ }
120
+
121
+ const elmScaffoldProgram = getAt(
122
+ splitModuleName,
123
+ require(path.join(process.cwd(), "./codegen/elm-stuff/scaffold.js")).Elm
124
+ );
125
+ const program = elmScaffoldProgram.init({
126
+ flags: { argv: ["", ...options2.args], versionMessage: "1.2.3" },
127
+ });
128
+
129
+ safeSubscribe(program, "print", (message) => {
130
+ console.log(message);
131
+ });
132
+ safeSubscribe(program, "printAndExitFailure", (message) => {
133
+ console.log(message);
134
+ process.exit(1);
135
+ });
136
+ safeSubscribe(program, "printAndExitSuccess", (message) => {
137
+ console.log(message);
138
+ process.exit(0);
139
+ });
140
+ safeSubscribe(program, "writeFile", async (info) => {
141
+ const filePath = path.join(process.cwd(), "app", info.path);
142
+ await dirHelpers.tryMkdir(path.dirname(filePath));
143
+ fs.writeFileSync(filePath, info.body);
144
+ console.log("Success! Created file", filePath);
145
+ process.exit(0);
146
+ });
147
+ });
148
+
73
149
  program
74
150
  .command("docs")
75
151
  .description("open the docs for locally generated modules")
@@ -89,6 +165,25 @@ async function main() {
89
165
  program.parse(process.argv);
90
166
  }
91
167
 
168
+ /**
169
+ * @param {string[]} properties
170
+ * @param {Object} object
171
+ * @returns unknown
172
+ */
173
+ function getAt(properties, object) {
174
+ if (properties.length === 0) {
175
+ return object;
176
+ } else {
177
+ const [next, ...rest] = properties;
178
+ return getAt(rest, object[next]);
179
+ }
180
+ }
181
+
182
+ function safeSubscribe(program, portName, subscribeFunction) {
183
+ program.ports[portName] &&
184
+ program.ports[portName].subscribe(subscribeFunction);
185
+ }
186
+
92
187
  function clearHttpAndPortCache() {
93
188
  const directory = ".elm-pages/http-response-cache";
94
189
  if (fs.existsSync(directory)) {
@@ -1,6 +1,10 @@
1
1
  const fs = require("fs");
2
+ const fsExtra = require("fs-extra");
2
3
  const copyModifiedElmJson = require("./rewrite-elm-json.js");
4
+ const copyModifiedElmJsonClient = require("./rewrite-client-elm-json.js");
3
5
  const { elmPagesCliFile, elmPagesUiFile } = require("./elm-file-constants.js");
6
+ const spawnCallback = require("cross-spawn").spawn;
7
+ const which = require("which");
4
8
  const {
5
9
  generateTemplateModuleConnector,
6
10
  } = require("./generate-template-module-connector.js");
@@ -12,65 +16,218 @@ global.builtAt = new Date();
12
16
  * @param {string} basePath
13
17
  */
14
18
  async function generate(basePath) {
15
- const cliCode = generateTemplateModuleConnector(basePath, "cli");
16
- const browserCode = generateTemplateModuleConnector(basePath, "browser");
19
+ const cliCode = await generateTemplateModuleConnector(basePath, "cli");
20
+ const browserCode = await generateTemplateModuleConnector(
21
+ basePath,
22
+ "browser"
23
+ );
17
24
  ensureDirSync("./elm-stuff");
18
25
  ensureDirSync("./.elm-pages");
26
+ ensureDirSync("./gen");
19
27
  ensureDirSync("./elm-stuff/elm-pages/.elm-pages");
20
28
 
21
29
  const uiFileContent = elmPagesUiFile();
30
+
22
31
  await Promise.all([
23
- fs.promises.copyFile(
24
- path.join(__dirname, `./Page.elm`),
25
- `./.elm-pages/Page.elm`
26
- ),
32
+ copyToBoth("RouteBuilder.elm"),
33
+ copyToBoth("SharedTemplate.elm"),
34
+ copyToBoth("SiteConfig.elm"),
35
+
36
+ fs.promises.writeFile("./.elm-pages/Pages.elm", uiFileContent),
27
37
  fs.promises.copyFile(
28
38
  path.join(__dirname, `./elm-application.json`),
29
39
  `./elm-stuff/elm-pages/elm-application.json`
30
40
  ),
31
- fs.promises.copyFile(
32
- path.join(__dirname, `./Page.elm`),
33
- `./elm-stuff/elm-pages/.elm-pages/Page.elm`
34
- ),
35
- fs.promises.copyFile(
36
- path.join(__dirname, `./SharedTemplate.elm`),
37
- `./.elm-pages/SharedTemplate.elm`
38
- ),
39
- fs.promises.copyFile(
40
- path.join(__dirname, `./SharedTemplate.elm`),
41
- `./elm-stuff/elm-pages/.elm-pages/SharedTemplate.elm`
42
- ),
43
- fs.promises.copyFile(
44
- path.join(__dirname, `./SiteConfig.elm`),
45
- `./.elm-pages/SiteConfig.elm`
46
- ),
47
- fs.promises.copyFile(
48
- path.join(__dirname, `./SiteConfig.elm`),
49
- `./elm-stuff/elm-pages/.elm-pages/SiteConfig.elm`
50
- ),
51
- fs.promises.writeFile("./.elm-pages/Pages.elm", uiFileContent),
52
41
  // write `Pages.elm` with cli interface
53
42
  fs.promises.writeFile(
54
43
  "./elm-stuff/elm-pages/.elm-pages/Pages.elm",
55
44
  elmPagesCliFile()
56
45
  ),
57
46
  fs.promises.writeFile(
58
- "./elm-stuff/elm-pages/.elm-pages/TemplateModulesBeta.elm",
47
+ "./elm-stuff/elm-pages/.elm-pages/Main.elm",
59
48
  cliCode.mainModule
60
49
  ),
61
50
  fs.promises.writeFile(
62
51
  "./elm-stuff/elm-pages/.elm-pages/Route.elm",
63
52
  cliCode.routesModule
64
53
  ),
65
- fs.promises.writeFile(
66
- "./.elm-pages/TemplateModulesBeta.elm",
67
- browserCode.mainModule
68
- ),
54
+ fs.promises.writeFile("./.elm-pages/Main.elm", browserCode.mainModule),
69
55
  fs.promises.writeFile("./.elm-pages/Route.elm", browserCode.routesModule),
56
+ writeFetcherModules("./.elm-pages", browserCode.fetcherModules),
57
+ writeFetcherModules(
58
+ "./elm-stuff/elm-pages/client/.elm-pages",
59
+ browserCode.fetcherModules
60
+ ),
61
+ writeFetcherModules(
62
+ "./elm-stuff/elm-pages/.elm-pages",
63
+ browserCode.fetcherModules
64
+ ),
65
+ // write modified elm.json to elm-stuff/elm-pages/
66
+ copyModifiedElmJson(),
67
+ // ...(await listFiles("./Pages/Internal")).map(copyToBoth),
70
68
  ]);
69
+ }
70
+
71
+ function writeFetcherModules(basePath, fetcherData) {
72
+ Promise.all(
73
+ fetcherData.map(([name, fileContent]) => {
74
+ let filePath = path.join(basePath, `/Fetcher/${name.join("/")}.elm`);
75
+ ensureDirSync(path.dirname(filePath));
76
+ return fs.promises.writeFile(filePath, fileContent);
77
+ })
78
+ );
79
+ }
80
+
81
+ async function newCopyBoth(modulePath) {
82
+ await fs.promises.copyFile(
83
+ path.join(__dirname, modulePath),
84
+ path.join(`./elm-stuff/elm-pages/client/.elm-pages/`, modulePath)
85
+ );
86
+ }
87
+
88
+ async function generateClientFolder(basePath) {
89
+ const browserCode = await generateTemplateModuleConnector(
90
+ basePath,
91
+ "browser"
92
+ );
93
+ const uiFileContent = elmPagesUiFile();
94
+ ensureDirSync("./elm-stuff/elm-pages/client/app");
95
+ ensureDirSync("./elm-stuff/elm-pages/client/.elm-pages");
96
+ await newCopyBoth("RouteBuilder.elm");
97
+ await newCopyBoth("SharedTemplate.elm");
98
+ await newCopyBoth("SiteConfig.elm");
71
99
 
72
- // write modified elm.json to elm-stuff/elm-pages/
73
- copyModifiedElmJson();
100
+ await copyModifiedElmJsonClient();
101
+ await fsExtra.copy("./app", "./elm-stuff/elm-pages/client/app", {
102
+ recursive: true,
103
+ });
104
+
105
+ await fs.promises.writeFile(
106
+ "./elm-stuff/elm-pages/client/.elm-pages/Main.elm",
107
+ browserCode.mainModule
108
+ );
109
+ await fs.promises.writeFile(
110
+ "./elm-stuff/elm-pages/client/.elm-pages/Route.elm",
111
+ browserCode.routesModule
112
+ );
113
+ await fs.promises.writeFile(
114
+ "./elm-stuff/elm-pages/client/.elm-pages/Pages.elm",
115
+ uiFileContent
116
+ );
117
+ await runElmReviewCodemod("./elm-stuff/elm-pages/client/");
118
+ }
119
+
120
+ /**
121
+ * @param {string} [ cwd ]
122
+ */
123
+ async function runElmReviewCodemod(cwd) {
124
+ return new Promise(async (resolve, reject) => {
125
+ const child = spawnCallback(
126
+ `elm-review`,
127
+ [
128
+ "--fix-all-without-prompt",
129
+ "--report",
130
+ "json",
131
+ "--namespace",
132
+ "elm-pages",
133
+ "--config",
134
+ path.join(__dirname, "../../generator/dead-code-review"),
135
+ "--elmjson",
136
+ "elm.json",
137
+ "--compiler",
138
+ await which("lamdera"),
139
+ ],
140
+ { cwd: path.join(process.cwd(), cwd || ".") }
141
+ );
142
+
143
+ let scriptOutput = "";
144
+
145
+ child.stdout.setEncoding("utf8");
146
+ child.stdout.on("data", function (/** @type {string} */ data) {
147
+ scriptOutput += data.toString();
148
+ });
149
+
150
+ child.stderr.setEncoding("utf8");
151
+ child.stderr.on("data", function (/** @type {string} */ data) {
152
+ scriptOutput += data.toString();
153
+ });
154
+ child.on("error", function () {
155
+ reject(scriptOutput);
156
+ });
157
+
158
+ child.on("close", function (code) {
159
+ if (code === 0) {
160
+ resolve(scriptOutput);
161
+ } else {
162
+ reject(scriptOutput);
163
+ }
164
+ });
165
+ });
166
+ }
167
+
168
+ /**
169
+ * @param {string} moduleToCopy
170
+ * @returns { Promise<void> }
171
+ */
172
+ async function copyToBoth(moduleToCopy) {
173
+ await Promise.all([
174
+ copyFileEnsureDir(
175
+ path.join(__dirname, moduleToCopy),
176
+ path.join(`./.elm-pages/`, moduleToCopy)
177
+ ),
178
+ copyFileEnsureDir(
179
+ path.join(__dirname, moduleToCopy),
180
+ path.join(`./elm-stuff/elm-pages/client/.elm-pages`, moduleToCopy)
181
+ ),
182
+ copyFileEnsureDir(
183
+ path.join(__dirname, moduleToCopy),
184
+ path.join(`./elm-stuff/elm-pages/.elm-pages/`, moduleToCopy)
185
+ )]);
186
+ }
187
+
188
+ /**
189
+ * @param {string} from
190
+ * @param {string} to
191
+ */
192
+ async function copyFileEnsureDir(from, to) {
193
+ await fs.promises.mkdir(path.dirname(to), {
194
+ recursive: true,
195
+ })
196
+ await fs.promises.copyFile(from, to)
197
+ }
198
+
199
+ /**
200
+ * @param {string} dir
201
+ * @returns {Promise<string[]>}
202
+ */
203
+ async function listFiles(dir) {
204
+ try {
205
+ const fullDir = path.join(__dirname, dir);
206
+ const files = await fs.promises.readdir(fullDir);
207
+ return merge(
208
+ await Promise.all(
209
+ files.flatMap(async (file_) => {
210
+ const file = path.join(dir, path.basename(file_));
211
+ if (
212
+ (await fs.promises.stat(path.join(__dirname, file))).isDirectory()
213
+ ) {
214
+ return await listFiles(file);
215
+ } else {
216
+ return [file];
217
+ }
218
+ })
219
+ )
220
+ );
221
+ } catch (e) {
222
+ return [];
223
+ }
224
+ }
225
+
226
+ /**
227
+ * @param {any[]} arrays
228
+ */
229
+ function merge(arrays) {
230
+ return [].concat.apply([], arrays);
74
231
  }
75
232
 
76
- module.exports = { generate };
233
+ module.exports = { generate, generateClientFolder };
@@ -1,8 +1,9 @@
1
1
  const spawnCallback = require("cross-spawn").spawn;
2
2
  const fs = require("fs");
3
+ const fsHelpers = require("./dir-helpers.js");
4
+ const fsPromises = require("fs").promises;
3
5
  const path = require("path");
4
6
  const kleur = require("kleur");
5
- const debug = true;
6
7
  const { inject } = require("elm-hot");
7
8
  const pathToClientElm = path.join(
8
9
  process.cwd(),
@@ -10,55 +11,172 @@ const pathToClientElm = path.join(
10
11
  "browser-elm.js"
11
12
  );
12
13
 
13
- async function spawnElmMake(elmEntrypointPath, outputPath, cwd) {
14
- const fullOutputPath = cwd ? path.join(cwd, outputPath) : outputPath;
15
- await runElm(elmEntrypointPath, outputPath, cwd);
14
+ async function compileElmForBrowser(options) {
15
+ await runElm(options, "./.elm-pages/Main.elm", pathToClientElm);
16
+ return fs.promises.writeFile(
17
+ "./.elm-pages/cache/elm.js",
18
+ inject(await fs.promises.readFile(pathToClientElm, "utf-8")).replace(
19
+ /return \$elm\$json\$Json\$Encode\$string\(.REPLACE_ME_WITH_FORM_TO_STRING.\)/g,
20
+ "let appendSubmitter = (myFormData, event) => { event.submitter && event.submitter.name && event.submitter.name.length > 0 ? myFormData.append(event.submitter.name, event.submitter.value) : myFormData; return myFormData }; return " +
21
+ (true
22
+ ? // TODO remove hardcoding
23
+ "_Json_wrap([...(appendSubmitter(new FormData(_Json_unwrap(event).target), _Json_unwrap(event)))])"
24
+ : "[...(new FormData(event.target))")
25
+ )
26
+ );
27
+ }
28
+
29
+ async function compileCliApp(
30
+ options,
31
+ elmEntrypointPath,
32
+ outputPath,
33
+ cwd,
34
+ readFrom
35
+ ) {
36
+ await compileElm(options, elmEntrypointPath, outputPath, cwd);
37
+
38
+ const elmFileContent = await fsPromises.readFile(readFrom, "utf-8");
39
+ // Source: https://github.com/elm-explorations/test/blob/d5eb84809de0f8bbf50303efd26889092c800609/src/Elm/Kernel/HtmlAsJson.js
40
+ const forceThunksSource = ` _HtmlAsJson_toJson(x)
41
+ }
42
+
43
+ var virtualDomKernelConstants =
44
+ {
45
+ nodeTypeTagger: 4,
46
+ nodeTypeThunk: 5,
47
+ kids: "e",
48
+ refs: "l",
49
+ thunk: "m",
50
+ node: "k",
51
+ value: "a"
52
+ }
16
53
 
17
- await fs.promises.writeFile(
18
- fullOutputPath,
19
- (await fs.promises.readFile(fullOutputPath, "utf-8"))
54
+ function forceThunks(vNode) {
55
+ if (typeof vNode !== "undefined" && vNode.$ === "#2") {
56
+ // This is a tuple (the kids : List (String, Html) field of a Keyed node); recurse into the right side of the tuple
57
+ vNode.b = forceThunks(vNode.b);
58
+ }
59
+ if (typeof vNode !== 'undefined' && vNode.$ === virtualDomKernelConstants.nodeTypeThunk && !vNode[virtualDomKernelConstants.node]) {
60
+ // This is a lazy node; evaluate it
61
+ var args = vNode[virtualDomKernelConstants.thunk];
62
+ vNode[virtualDomKernelConstants.node] = vNode[virtualDomKernelConstants.thunk].apply(args);
63
+ // And then recurse into the evaluated node
64
+ vNode[virtualDomKernelConstants.node] = forceThunks(vNode[virtualDomKernelConstants.node]);
65
+ }
66
+ if (typeof vNode !== 'undefined' && vNode.$ === virtualDomKernelConstants.nodeTypeTagger) {
67
+ // This is an Html.map; recurse into the node it is wrapping
68
+ vNode[virtualDomKernelConstants.node] = forceThunks(vNode[virtualDomKernelConstants.node]);
69
+ }
70
+ if (typeof vNode !== 'undefined' && typeof vNode[virtualDomKernelConstants.kids] !== 'undefined') {
71
+ // This is something with children (either a node with kids : List Html, or keyed with kids : List (String, Html));
72
+ // recurse into the children
73
+ vNode[virtualDomKernelConstants.kids] = vNode[virtualDomKernelConstants.kids].map(forceThunks);
74
+ }
75
+ return vNode;
76
+ }
77
+
78
+ function _HtmlAsJson_toJson(html) {
79
+ `;
80
+
81
+ await fsPromises.writeFile(
82
+ readFrom,
83
+ elmFileContent
20
84
  .replace(
21
85
  /return \$elm\$json\$Json\$Encode\$string\(.REPLACE_ME_WITH_JSON_STRINGIFY.\)/g,
22
- "return " + (debug ? "_Json_wrap(x)" : "x")
23
- )
24
- .replace(
25
- "return ports ? { ports: ports } : {};",
26
- `const die = function() {
27
- managers = null
28
- model = null
29
- stepper = null
30
- ports = null
31
- }
32
-
33
- return ports ? { ports: ports, die: die } : { die: die };`
86
+ "return " +
87
+ // TODO should the logic for this be `if options.optimize`? Or does the first case not make sense at all?
88
+ (true
89
+ ? `${forceThunksSource}
90
+ return _Json_wrap(forceThunks(html));
91
+ `
92
+ : `${forceThunksSource}
93
+ return forceThunks(html);
94
+ `)
34
95
  )
96
+ .replace(/console\.log..App dying../, "")
35
97
  );
36
98
  }
37
99
 
38
- async function compileElmForBrowser() {
39
- await runElm("./.elm-pages/TemplateModulesBeta.elm", pathToClientElm);
40
- return fs.promises.writeFile(
41
- "./.elm-pages/cache/elm.js",
42
- inject(await fs.promises.readFile(pathToClientElm, "utf-8"))
43
- );
100
+ /**
101
+ * @param {string} elmEntrypointPath
102
+ * @param {string} outputPath
103
+ * @param {string | undefined} cwd
104
+ */
105
+ async function compileElm(options, elmEntrypointPath, outputPath, cwd) {
106
+ await spawnElmMake(options, elmEntrypointPath, outputPath, cwd);
107
+ if (!options.debug) {
108
+ // TODO maybe pass in a boolean argument for whether it's build or dev server, and only do eol2 for build
109
+ // await elmOptimizeLevel2(outputPath, cwd);
110
+ }
111
+ }
112
+
113
+ function spawnElmMake(options, elmEntrypointPath, outputPath, cwd) {
114
+ return new Promise(async (resolve, reject) => {
115
+ const subprocess = spawnCallback(
116
+ `lamdera`,
117
+ [
118
+ "make",
119
+ elmEntrypointPath,
120
+ "--output",
121
+ outputPath,
122
+ // TODO use --optimize for prod build
123
+ ...(options.debug ? ["--debug"] : []),
124
+ "--report",
125
+ "json",
126
+ ],
127
+ {
128
+ // ignore stdout
129
+ // stdio: ["inherit", "ignore", "inherit"],
130
+
131
+ cwd: cwd,
132
+ }
133
+ );
134
+ if (await fsHelpers.fileExists(outputPath)) {
135
+ await fsPromises.unlink(outputPath, {
136
+ force: true /* ignore errors if file doesn't exist */,
137
+ });
138
+ }
139
+ let commandOutput = "";
140
+
141
+ subprocess.stderr.on("data", function (data) {
142
+ commandOutput += data;
143
+ });
144
+ subprocess.on("error", function () {
145
+ reject(commandOutput);
146
+ });
147
+
148
+ subprocess.on("close", async (code) => {
149
+ if (
150
+ code == 0 &&
151
+ (await fsHelpers.fileExists(outputPath)) &&
152
+ commandOutput === ""
153
+ ) {
154
+ resolve();
155
+ } else {
156
+ reject(commandOutput);
157
+ }
158
+ });
159
+ });
44
160
  }
45
161
 
46
162
  /**
47
163
  * @param {string} elmEntrypointPath
48
164
  * @param {string} outputPath
49
- * @param {string} [ cwd ]
165
+ * @param {string} [cwd]
166
+ * @param {{ debug: boolean; }} options
50
167
  */
51
- async function runElm(elmEntrypointPath, outputPath, cwd) {
168
+ async function runElm(options, elmEntrypointPath, outputPath, cwd) {
52
169
  const startTime = Date.now();
53
170
  return new Promise((resolve, reject) => {
54
171
  const child = spawnCallback(
55
- `elm`,
172
+ `lamdera`,
56
173
  [
57
174
  "make",
58
175
  elmEntrypointPath,
59
176
  "--output",
60
177
  outputPath,
61
- "--debug",
178
+ ...(options.debug ? ["--debug"] : []),
179
+ ...(options.optimize ? ["--optimize"] : []),
62
180
  "--report",
63
181
  "json",
64
182
  ],
@@ -124,7 +242,7 @@ async function runElmReview(cwd) {
124
242
  child.on("close", function (code) {
125
243
  console.log(`Ran elm-review in ${timeFrom(startTime)}`);
126
244
  if (code === 0) {
127
- reject();
245
+ resolve(scriptOutput);
128
246
  } else {
129
247
  resolve(scriptOutput);
130
248
  }
@@ -132,10 +250,44 @@ async function runElmReview(cwd) {
132
250
  });
133
251
  }
134
252
 
253
+ function elmOptimizeLevel2(outputPath, cwd) {
254
+ return new Promise((resolve, reject) => {
255
+ const optimizedOutputPath = outputPath + ".opt";
256
+ const subprocess = spawnCallback(
257
+ `elm-optimize-level-2`,
258
+ [outputPath, "--output", optimizedOutputPath],
259
+ {
260
+ // ignore stdout
261
+ // stdio: ["inherit", "ignore", "inherit"],
262
+
263
+ cwd: cwd,
264
+ }
265
+ );
266
+ let commandOutput = "";
267
+
268
+ subprocess.stderr.on("data", function (data) {
269
+ commandOutput += data;
270
+ });
271
+
272
+ subprocess.on("close", async (code) => {
273
+ if (
274
+ code === 0 &&
275
+ commandOutput === "" &&
276
+ (await fsHelpers.fileExists(optimizedOutputPath))
277
+ ) {
278
+ await fs.promises.copyFile(optimizedOutputPath, outputPath);
279
+ resolve();
280
+ } else {
281
+ reject(commandOutput);
282
+ }
283
+ });
284
+ });
285
+ }
286
+
135
287
  module.exports = {
136
- spawnElmMake,
137
288
  compileElmForBrowser,
138
289
  runElmReview,
290
+ compileCliApp,
139
291
  };
140
292
 
141
293
  /**