elm-pages 3.0.20 → 3.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  - [elm-pages Docs Site](https://elm-pages.com/docs)
13
13
  - [elm-pages site showcase](https://elm-pages.com/showcase/)
14
- - [elm-pages Elm API Docs](https://package.elm-lang.org/packages/dillonkearns/elm-pages/10.2.0/)
14
+ - [elm-pages Elm API Docs](https://package.elm-lang.org/packages/dillonkearns/elm-pages/10.2.1/)
15
15
  - [Quick start repo](https://github.com/dillonkearns/elm-pages-starter) [(live site hosted here)](https://elm-pages-starter.netlify.com)
16
16
  - [Introducing `elm-pages` blog post](https://elm-pages.com/blog/introducing-elm-pages)
17
17
  - [`examples` folder](https://github.com/dillonkearns/elm-pages/blob/master/examples/) (includes https://elm-pages.com site source) Use `git clone --recurse-submodules https://github.com/dillonkearns/elm-pages.git` so that there aren't missing files when you try to build the examples.
@@ -10,8 +10,8 @@
10
10
  "elm/core": "1.0.5",
11
11
  "elm/json": "1.1.3",
12
12
  "elm-community/result-extra": "2.4.0",
13
- "jfmengels/elm-review": "2.14.0",
14
- "stil4m/elm-syntax": "7.3.2"
13
+ "jfmengels/elm-review": "2.15.1",
14
+ "stil4m/elm-syntax": "7.3.8"
15
15
  },
16
16
  "indirect": {
17
17
  "elm/bytes": "1.0.8",
@@ -23,7 +23,6 @@
23
23
  "elm/time": "1.0.0",
24
24
  "elm/virtual-dom": "1.0.3",
25
25
  "elm-explorations/test": "2.2.0",
26
- "miniBill/elm-unicode": "1.1.1",
27
26
  "rtfeldman/elm-hex": "1.0.0",
28
27
  "stil4m/structured-writer": "1.0.3"
29
28
  }
@@ -143,7 +143,7 @@ placeholder moduleName =
143
143
  , details =
144
144
  [ "" ]
145
145
  , under =
146
- """import Html.Styled as Html"""
146
+ """import Html.Styled as Html exposing (Html)"""
147
147
  }
148
148
  |> Review.Test.whenFixed
149
149
  """module View exposing (View, map, placeholder)
@@ -11,9 +11,9 @@
11
11
  "elm/html": "1.0.0",
12
12
  "elm/json": "1.1.3",
13
13
  "elm/regex": "1.0.0",
14
- "jfmengels/elm-review": "2.14.0",
15
- "mdgriffith/elm-codegen": "5.0.0",
16
- "stil4m/elm-syntax": "7.3.2",
14
+ "jfmengels/elm-review": "2.15.1",
15
+ "mdgriffith/elm-codegen": "5.2.0",
16
+ "stil4m/elm-syntax": "7.3.8",
17
17
  "the-sett/elm-syntax-dsl": "6.0.3"
18
18
  },
19
19
  "indirect": {
@@ -30,7 +30,7 @@
30
30
  "miniBill/elm-unicode": "1.1.1",
31
31
  "rtfeldman/elm-hex": "1.0.0",
32
32
  "stil4m/structured-writer": "1.0.3",
33
- "the-sett/elm-pretty-printer": "3.1.0"
33
+ "the-sett/elm-pretty-printer": "3.1.1"
34
34
  }
35
35
  },
36
36
  "test-dependencies": {
@@ -73,7 +73,11 @@ export async function run(options) {
73
73
  // This is a temporary hack to avoid this warning. elm-pages manages compiling the Elm code without Vite's involvement, so it is external to Vite.
74
74
  // There is a pending issue to allow having external scripts in Vite, once this issue is fixed we can remove this hack:
75
75
  // https://github.com/vitejs/vite/issues/3533
76
- if (messages && messages[0] && !messages[0].startsWith(`<script src="/elm.js">`)) {
76
+ if (
77
+ messages &&
78
+ messages[0] &&
79
+ !messages[0].startsWith(`<script src="/elm.js">`)
80
+ ) {
77
81
  console.info(...messages);
78
82
  }
79
83
  };
@@ -97,13 +101,11 @@ export async function run(options) {
97
101
  configFile: false,
98
102
  root: process.cwd(),
99
103
  base: options.base,
100
- assetsInclude: [
101
- '/elm-pages.js'
102
- ],
104
+ assetsInclude: ["/elm-pages.js"],
103
105
  ssr: false,
104
106
 
105
107
  build: {
106
- manifest: '___vite-manifest___.json',
108
+ manifest: "___vite-manifest___.json",
107
109
  outDir: "dist",
108
110
  rollupOptions: {
109
111
  input: "elm-stuff/elm-pages/index.html",
@@ -126,7 +128,10 @@ export async function run(options) {
126
128
  fullOutputPath,
127
129
  withoutExtension
128
130
  );
129
- const assetManifestPath = path.join(process.cwd(), "dist/___vite-manifest___.json");
131
+ const assetManifestPath = path.join(
132
+ process.cwd(),
133
+ "dist/___vite-manifest___.json"
134
+ );
130
135
  const manifest = JSON.parse(
131
136
  await fsPromises.readFile(assetManifestPath, { encoding: "utf-8" })
132
137
  );
@@ -292,7 +297,7 @@ export async function render(request) {
292
297
  processedIndexTemplate
293
298
  );
294
299
  } catch (error) {
295
- if(error) {
300
+ if (error) {
296
301
  console.error(restoreColorSafe(error));
297
302
  }
298
303
  buildError = true;
@@ -486,9 +491,7 @@ async function spawnElmMake(mode, options, elmEntrypointPath, outputPath, cwd) {
486
491
  }
487
492
  await fsPromises.writeFile(
488
493
  outputPath,
489
- (
490
- await fsPromises.readFile(outputPath, "utf-8")
491
- ).replace(
494
+ (await fsPromises.readFile(outputPath, "utf-8")).replace(
492
495
  /return \$elm\$json\$Json\$Encode\$string\(.REPLACE_ME_WITH_FORM_TO_STRING.\)/g,
493
496
  "function 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 " +
494
497
  (options.debug
@@ -685,19 +688,26 @@ function _HtmlAsJson_toJson(html) {
685
688
 
686
689
  await fsPromises.writeFile(
687
690
  ELM_FILE_PATH().replace(/\.js$/, ".cjs"),
688
- applyScriptPatches(options, elmFileContent
689
- .replace(
690
- /return \$elm\$json\$Json\$Encode\$string\(.REPLACE_ME_WITH_JSON_STRINGIFY.\)/g,
691
- `return ${forceThunksSource}
691
+ applyScriptPatches(
692
+ options,
693
+ elmFileContent
694
+ .replace(
695
+ /return \$elm\$json\$Json\$Encode\$string\(.REPLACE_ME_WITH_JSON_STRINGIFY.\)/g,
696
+ `return ${forceThunksSource}
692
697
  return _Json_wrap(forceThunks(html));
693
698
  `
694
- )
695
- .replace(`console.log('App dying')`, "")));
699
+ )
700
+ .replace(`console.log('App dying')`, "")
701
+ )
702
+ );
696
703
  }
697
704
 
698
705
  function applyScriptPatches(options, input) {
699
706
  if (options.isScript) {
700
- return input.replace(`_Debug_crash(8, moduleName, region, message)`, "console.error('TODO in module `' + moduleName + '` ' + _Debug_regionToString(region) + '\\n\\n' + message); process.exitCode = 1; debugger; throw 'CRASH!';");
707
+ return input.replace(
708
+ `_Debug_crash(8, moduleName, region, message)`,
709
+ "console.error('TODO in module `' + moduleName + '` ' + _Debug_regionToString(region) + '\\n\\n' + message); process.exitCode = 1; debugger; throw 'CRASH!';"
710
+ );
701
711
  } else {
702
712
  return input;
703
713
  }
@@ -344,7 +344,9 @@ async function requireElm(compiledElmPath) {
344
344
  const warnOriginal = console.warn;
345
345
  console.warn = function () {};
346
346
 
347
- let Elm = (await import(url.pathToFileURL(path.resolve(compiledElmPath)).href)).default;
347
+ let Elm = (
348
+ await import(url.pathToFileURL(path.resolve(compiledElmPath)).href)
349
+ ).default;
348
350
  console.warn = warnOriginal;
349
351
  return Elm;
350
352
  }
@@ -1,3 +1,3 @@
1
1
  export const compatibilityKey = 22;
2
2
 
3
- export const packageVersion = "3.0.20";
3
+ export const packageVersion = "3.0.22";
@@ -37,7 +37,11 @@ const __dirname = path.dirname(__filename);
37
37
  */
38
38
  export async function start(options) {
39
39
  console.error = function (...messages) {
40
- if (messages && messages[0] && !messages[0].startsWith("Failed to load url")) {
40
+ if (
41
+ messages &&
42
+ messages[0] &&
43
+ !messages[0].startsWith("Failed to load url")
44
+ ) {
41
45
  console.info(...messages);
42
46
  }
43
47
  };
@@ -175,9 +179,7 @@ export async function start(options) {
175
179
  base: options.base,
176
180
  port: options.port,
177
181
  },
178
- assetsInclude: [
179
- '/elm-pages.js'
180
- ],
182
+ assetsInclude: ["/elm-pages.js"],
181
183
  appType: "custom",
182
184
  configFile: false,
183
185
  root: process.cwd(),
@@ -66,7 +66,9 @@ export async function generateTemplateModuleConnector(basePath, phase) {
66
66
  async function runElmCodegenCli(templates, basePath, phase) {
67
67
  const __filename = fileURLToPath(import.meta.url);
68
68
  const __dirname = path.dirname(__filename);
69
- const filePath = pathToFileURL(path.join(__dirname, `../../codegen/elm-pages-codegen.cjs`)).href;
69
+ const filePath = pathToFileURL(
70
+ path.join(__dirname, `../../codegen/elm-pages-codegen.cjs`)
71
+ ).href;
70
72
 
71
73
  const promise = new Promise(async (resolve, reject) => {
72
74
  const elmPagesCodegen = (await import(filePath)).default.Elm.Generate;
@@ -48,11 +48,12 @@ export function templateHtml(devMode, userHeadTagsTemplate) {
48
48
  ${indent(userHeadTagsTemplate({ cliVersion: packageVersion }))}
49
49
  <!-- PLACEHOLDER_HEAD_AND_DATA -->
50
50
  </head>
51
- <body>
52
- <div data-url="" display="none"></div>
53
- <!-- PLACEHOLDER_HTML -->
54
- </body>
55
- </html>`;
51
+ <body><div data-elm data-url="" style="display: none;"></div><div data-elm id="elm-pages-announcer" aria-live="assertive" aria-atomic="true" style="position: absolute; top: 0; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); whiteSpace: nowrap; border: 0;"></div><!-- PLACEHOLDER_HTML --></body></html>`;
52
+ // NOTE: The above line needs to:
53
+ // - Be in sync with `view` in Platform.elm (render the same elements).
54
+ // - Have `data-elm` on each child of `<body>`.
55
+ // - Not include any extra whitespace. Even whitespace between </body> and </html> is parsed by browsers as a text node _inside_ <body>.
56
+ // This is to avoid unnecessary rerenders on init (when the first `view` call is diffed with the virtualized form of the above HTML).
56
57
  }
57
58
 
58
59
  /**
@@ -21,6 +21,14 @@ export function lookupOrPerform(
21
21
  hasFsAccess,
22
22
  useCache
23
23
  ) {
24
+ const uniqueTimeId =
25
+ Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
26
+ const timeStart = (message) => {
27
+ !rawRequest.quiet && console.time(`${message} ${uniqueTimeId}`);
28
+ };
29
+ const timeEnd = (message) => {
30
+ !rawRequest.quiet && console.timeEnd(`${message} ${uniqueTimeId}`);
31
+ };
24
32
  const makeFetchHappen = makeFetchHappenOriginal.defaults({
25
33
  cache: mode === "build" ? "no-cache" : "default",
26
34
  });
@@ -92,8 +100,7 @@ export function lookupOrPerform(
92
100
  }),
93
101
  });
94
102
  } else {
95
- !rawRequest.quiet &&
96
- console.time(`BackendTask.Custom.run "${portName}"`);
103
+ timeStart(`BackendTask.Custom.run "${portName}"`);
97
104
  let context = {
98
105
  cwd: path.resolve(...rawRequest.dir),
99
106
  quiet: rawRequest.quiet,
@@ -135,8 +142,7 @@ export function lookupOrPerform(
135
142
  });
136
143
  }
137
144
  }
138
- !rawRequest.quiet &&
139
- console.timeEnd(`BackendTask.Custom.run "${portName}"`);
145
+ timeEnd(`BackendTask.Custom.run "${portName}"`);
140
146
  }
141
147
  } catch (error) {
142
148
  console.trace(error);
@@ -147,7 +153,7 @@ export function lookupOrPerform(
147
153
  }
148
154
  } else {
149
155
  try {
150
- !rawRequest.quiet && console.time(`fetch ${request.url}`);
156
+ timeStart(`fetch ${request.url}`);
151
157
  const response = await safeFetch(makeFetchHappen, request.url, {
152
158
  method: request.method,
153
159
  body: request.body,
@@ -158,7 +164,7 @@ export function lookupOrPerform(
158
164
  ...rawRequest.cacheOptions,
159
165
  });
160
166
 
161
- !rawRequest.quiet && console.timeEnd(`fetch ${request.url}`);
167
+ timeEnd(`fetch ${request.url}`);
162
168
  const expectString = request.headers["elm-pages-internal"];
163
169
 
164
170
  let body;
@@ -1,25 +1,31 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
- import * as readline from 'readline';
3
+ import * as readline from "readline";
4
4
  import * as chalk from "kleur/colors";
5
- import cliCursor from 'cli-cursor';
6
-
7
-
8
- import { purgeSpinnerOptions, purgeSpinnersOptions, colorOptions, breakText, getLinesLength, terminalSupportsUnicode } from './utils.js';
9
- import { dots, dashes, writeStream, cleanStream } from './utils.js';
5
+ import cliCursor from "cli-cursor";
6
+
7
+ import {
8
+ purgeSpinnerOptions,
9
+ purgeSpinnersOptions,
10
+ colorOptions,
11
+ breakText,
12
+ getLinesLength,
13
+ terminalSupportsUnicode,
14
+ } from "./utils.js";
15
+ import { dots, dashes, writeStream, cleanStream } from "./utils.js";
10
16
 
11
17
  export class Spinnies {
12
18
  constructor(options = {}) {
13
19
  options = purgeSpinnersOptions(options);
14
20
  this.options = {
15
- // TODO kleur doesn't support brightGreen, only nested function or chained syntax
16
- // spinnerColor: 'brightGreen',
17
- spinnerColor: 'green',
18
- succeedColor: 'green',
19
- failColor: 'red',
21
+ // TODO kleur doesn't support brightGreen, only nested function or chained syntax
22
+ // spinnerColor: 'brightGreen',
23
+ spinnerColor: "green",
24
+ succeedColor: "green",
25
+ failColor: "red",
20
26
  spinner: terminalSupportsUnicode() ? dots : dashes,
21
27
  disableSpins: false,
22
- ...options
28
+ ...options,
23
29
  };
24
30
  this.spinners = {};
25
31
  this.isCursorHidden = false;
@@ -27,7 +33,11 @@ export class Spinnies {
27
33
  this.stream = process.stderr;
28
34
  this.lineCount = 0;
29
35
  this.currentFrameIndex = 0;
30
- this.spin = !this.options.disableSpins && !process.env.CI && process.stderr && process.stderr.isTTY;
36
+ this.spin =
37
+ !this.options.disableSpins &&
38
+ !process.env.CI &&
39
+ process.stderr &&
40
+ process.stderr.isTTY;
31
41
  this.bindSigint();
32
42
  }
33
43
 
@@ -36,13 +46,14 @@ export class Spinnies {
36
46
  }
37
47
 
38
48
  add(name, options = {}) {
39
- if (typeof name !== 'string') throw Error('A spinner reference name must be specified');
49
+ if (typeof name !== "string")
50
+ throw Error("A spinner reference name must be specified");
40
51
  if (!options.text) options.text = name;
41
52
  const spinnerProperties = {
42
53
  ...colorOptions(this.options),
43
54
  succeedPrefix: this.options.succeedPrefix,
44
55
  failPrefix: this.options.failPrefix,
45
- status: 'spinning',
56
+ status: "spinning",
46
57
  ...purgeSpinnerOptions(options),
47
58
  };
48
59
 
@@ -61,37 +72,42 @@ export class Spinnies {
61
72
  }
62
73
 
63
74
  succeed(name, options = {}) {
64
- this.setSpinnerProperties(name, options, 'succeed');
75
+ this.setSpinnerProperties(name, options, "succeed");
65
76
  this.updateSpinnerState();
66
77
 
67
78
  return this.spinners[name];
68
79
  }
69
80
 
70
81
  fail(name, options = {}) {
71
- this.setSpinnerProperties(name, options, 'fail');
82
+ this.setSpinnerProperties(name, options, "fail");
72
83
  this.updateSpinnerState();
73
84
 
74
85
  return this.spinners[name];
75
86
  }
76
87
 
77
88
  remove(name) {
78
- if (typeof name !== 'string') throw Error('A spinner reference name must be specified');
89
+ if (typeof name !== "string")
90
+ throw Error("A spinner reference name must be specified");
79
91
  const spinner = this.spinners[name];
80
92
  delete this.spinners[name];
81
93
 
82
94
  return spinner;
83
95
  }
84
96
 
85
- stopAll(newStatus = 'stopped') {
86
- Object.keys(this.spinners).forEach(name => {
97
+ stopAll(newStatus = "stopped") {
98
+ Object.keys(this.spinners).forEach((name) => {
87
99
  const { status: currentStatus } = this.spinners[name];
88
- if (currentStatus !== 'fail' && currentStatus !== 'succeed' && currentStatus !== 'non-spinnable') {
89
- if (newStatus === 'succeed' || newStatus === 'fail') {
100
+ if (
101
+ currentStatus !== "fail" &&
102
+ currentStatus !== "succeed" &&
103
+ currentStatus !== "non-spinnable"
104
+ ) {
105
+ if (newStatus === "succeed" || newStatus === "fail") {
90
106
  this.spinners[name].status = newStatus;
91
107
  this.spinners[name].color = this.options[`${newStatus}Color`];
92
108
  } else {
93
- this.spinners[name].status = 'stopped';
94
- this.spinners[name].color = 'grey';
109
+ this.spinners[name].status = "stopped";
110
+ this.spinners[name].color = "grey";
95
111
  }
96
112
  }
97
113
  });
@@ -101,14 +117,18 @@ export class Spinnies {
101
117
  }
102
118
 
103
119
  hasActiveSpinners() {
104
- return !!Object.values(this.spinners).find(({ status }) => status === 'spinning');
120
+ return !!Object.values(this.spinners).find(
121
+ ({ status }) => status === "spinning"
122
+ );
105
123
  }
106
124
 
107
125
  setSpinnerProperties(name, options, status) {
108
- if (typeof name !== 'string') throw Error('A spinner reference name must be specified');
109
- if (!this.spinners[name]) throw Error(`No spinner initialized with name ${name}`);
126
+ if (typeof name !== "string")
127
+ throw Error("A spinner reference name must be specified");
128
+ if (!this.spinners[name])
129
+ throw Error(`No spinner initialized with name ${name}`);
110
130
  options = purgeSpinnerOptions(options);
111
- status = status || 'spinning';
131
+ status = status || "spinning";
112
132
 
113
133
  this.spinners[name] = { ...this.spinners[name], ...options, status };
114
134
  }
@@ -129,29 +149,41 @@ export class Spinnies {
129
149
  const { frames, interval } = this.options.spinner;
130
150
  return setInterval(() => {
131
151
  this.setStreamOutput(frames[this.currentFrameIndex]);
132
- this.currentFrameIndex = this.currentFrameIndex === frames.length - 1 ? 0 : ++this.currentFrameIndex
152
+ this.currentFrameIndex =
153
+ this.currentFrameIndex === frames.length - 1
154
+ ? 0
155
+ : ++this.currentFrameIndex;
133
156
  }, interval);
134
157
  }
135
158
 
136
- setStreamOutput(frame = '') {
137
- let output = '';
159
+ setStreamOutput(frame = "") {
160
+ let output = "";
138
161
  const linesLength = [];
139
162
  const hasActiveSpinners = this.hasActiveSpinners();
140
- Object
141
- .values(this.spinners)
142
- .map(({ text, status, color, spinnerColor, succeedColor, failColor, succeedPrefix, failPrefix, indent }) => {
163
+ Object.values(this.spinners).map(
164
+ ({
165
+ text,
166
+ status,
167
+ color,
168
+ spinnerColor,
169
+ succeedColor,
170
+ failColor,
171
+ succeedPrefix,
172
+ failPrefix,
173
+ indent,
174
+ }) => {
143
175
  let line;
144
176
  let prefixLength = indent || 0;
145
- if (status === 'spinning') {
177
+ if (status === "spinning") {
146
178
  prefixLength += frame.length + 1;
147
179
  text = breakText(text, prefixLength);
148
180
  line = `${chalk[spinnerColor](frame)} ${color ? chalk[color](text) : text}`;
149
181
  } else {
150
- if (status === 'succeed') {
182
+ if (status === "succeed") {
151
183
  prefixLength += succeedPrefix.length + 1;
152
184
  if (hasActiveSpinners) text = breakText(text, prefixLength);
153
185
  line = `${chalk.green(succeedPrefix)} ${chalk[succeedColor](text)}`;
154
- } else if (status === 'fail') {
186
+ } else if (status === "fail") {
155
187
  prefixLength += failPrefix.length + 1;
156
188
  if (hasActiveSpinners) text = breakText(text, prefixLength);
157
189
  line = `${chalk.red(failPrefix)} ${chalk[failColor](text)}`;
@@ -162,16 +194,17 @@ export class Spinnies {
162
194
  }
163
195
  linesLength.push(...getLinesLength(text, prefixLength));
164
196
  output += indent ? `${" ".repeat(indent)}${line}\n` : `${line}\n`;
165
- });
197
+ }
198
+ );
166
199
 
167
- if(!hasActiveSpinners) readline.clearScreenDown(this.stream);
200
+ if (!hasActiveSpinners) readline.clearScreenDown(this.stream);
168
201
  writeStream(this.stream, output, linesLength);
169
202
  if (hasActiveSpinners) cleanStream(this.stream, linesLength);
170
203
  this.lineCount = linesLength.length;
171
204
  }
172
205
 
173
206
  setRawStreamOutput() {
174
- Object.values(this.spinners).forEach(i => {
207
+ Object.values(this.spinners).forEach((i) => {
175
208
  process.stderr.write(`- ${i.text}\n`);
176
209
  });
177
210
  }
@@ -190,8 +223,8 @@ export class Spinnies {
190
223
  }
191
224
 
192
225
  bindSigint(lines) {
193
- process.removeAllListeners('SIGINT');
194
- process.on('SIGINT', () => {
226
+ process.removeAllListeners("SIGINT");
227
+ process.on("SIGINT", () => {
195
228
  cliCursor.show();
196
229
  readline.moveCursor(process.stderr, 0, this.lineCount);
197
230
  process.exit(0);
@@ -1,4 +1,4 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
3
  // source: https://github.com/jbcarpanelli/spinnies/blob/master/utils.js
4
4
  // Code vemodified to ESM syntax from original repo
@@ -6,20 +6,42 @@
6
6
  import * as readline from "readline";
7
7
  // original code dependend on this for `getLinesLength`. Is this necessary?
8
8
  // const stripAnsi = require('strip-ansi');
9
- export const { dashes,dots } =
10
- {
11
- "dots": {
12
- "interval": 50,
13
- "frames": ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
9
+ export const { dashes, dots } = {
10
+ dots: {
11
+ interval: 50,
12
+ frames: ["", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
14
13
  },
15
- "dashes": {
16
- "interval": 80,
17
- "frames": ["-", "_"]
18
- }
19
- }
20
-
21
- const VALID_STATUSES = ['succeed', 'fail', 'spinning', 'non-spinnable', 'stopped'];
22
- const VALID_COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'gray', 'redBright', 'greenBright', 'yellowBright', 'blueBright', 'magentaBright', 'cyanBright', 'whiteBright'];
14
+ dashes: {
15
+ interval: 80,
16
+ frames: ["-", "_"],
17
+ },
18
+ };
19
+
20
+ const VALID_STATUSES = [
21
+ "succeed",
22
+ "fail",
23
+ "spinning",
24
+ "non-spinnable",
25
+ "stopped",
26
+ ];
27
+ const VALID_COLORS = [
28
+ "black",
29
+ "red",
30
+ "green",
31
+ "yellow",
32
+ "blue",
33
+ "magenta",
34
+ "cyan",
35
+ "white",
36
+ "gray",
37
+ "redBright",
38
+ "greenBright",
39
+ "yellowBright",
40
+ "blueBright",
41
+ "magentaBright",
42
+ "cyanBright",
43
+ "whiteBright",
44
+ ];
23
45
 
24
46
  export function purgeSpinnerOptions(options) {
25
47
  const { text, status, indent } = options;
@@ -27,8 +49,8 @@ export function purgeSpinnerOptions(options) {
27
49
  const colors = colorOptions(options);
28
50
 
29
51
  if (!VALID_STATUSES.includes(status)) delete opts.status;
30
- if (typeof text !== 'string') delete opts.text;
31
- if (typeof indent !== 'number') delete opts.indent;
52
+ if (typeof text !== "string") delete opts.text;
53
+ if (typeof indent !== "number") delete opts.indent;
32
54
 
33
55
  return { ...colors, ...opts };
34
56
  }
@@ -36,25 +58,27 @@ export function purgeSpinnerOptions(options) {
36
58
  export function purgeSpinnersOptions({ spinner, disableSpins, ...others }) {
37
59
  const colors = colorOptions(others);
38
60
  const prefixes = prefixOptions(others);
39
- const disableSpinsOption = typeof disableSpins === 'boolean' ? { disableSpins } : {};
61
+ const disableSpinsOption =
62
+ typeof disableSpins === "boolean" ? { disableSpins } : {};
40
63
  spinner = turnToValidSpinner(spinner);
41
64
 
42
- return { ...colors, ...prefixes, ...disableSpinsOption, spinner }
65
+ return { ...colors, ...prefixes, ...disableSpinsOption, spinner };
43
66
  }
44
67
 
45
68
  function turnToValidSpinner(spinner = {}) {
46
69
  const platformSpinner = terminalSupportsUnicode() ? dots : dashes;
47
- if (!typeof spinner === 'object') return platformSpinner;
70
+ if (!typeof spinner === "object") return platformSpinner;
48
71
  let { interval, frames } = spinner;
49
- if (!Array.isArray(frames) || frames.length < 1) frames = platformSpinner.frames;
50
- if (typeof interval !== 'number') interval = platformSpinner.interval;
72
+ if (!Array.isArray(frames) || frames.length < 1)
73
+ frames = platformSpinner.frames;
74
+ if (typeof interval !== "number") interval = platformSpinner.interval;
51
75
 
52
76
  return { interval, frames };
53
77
  }
54
78
 
55
79
  export function colorOptions({ color, succeedColor, failColor, spinnerColor }) {
56
80
  const colors = { color, succeedColor, failColor, spinnerColor };
57
- Object.keys(colors).forEach(key => {
81
+ Object.keys(colors).forEach((key) => {
58
82
  if (!VALID_COLORS.includes(colors[key])) delete colors[key];
59
83
  });
60
84
 
@@ -62,29 +86,33 @@ export function colorOptions({ color, succeedColor, failColor, spinnerColor }) {
62
86
  }
63
87
 
64
88
  function prefixOptions({ succeedPrefix, failPrefix }) {
65
- if(terminalSupportsUnicode()) {
66
- succeedPrefix = succeedPrefix || '';
67
- failPrefix = failPrefix || '';
89
+ if (terminalSupportsUnicode()) {
90
+ succeedPrefix = succeedPrefix || "";
91
+ failPrefix = failPrefix || "";
68
92
  } else {
69
- succeedPrefix = succeedPrefix || '';
70
- failPrefix = failPrefix || '×';
93
+ succeedPrefix = succeedPrefix || "";
94
+ failPrefix = failPrefix || "×";
71
95
  }
72
96
 
73
97
  return { succeedPrefix, failPrefix };
74
98
  }
75
99
 
76
100
  export function breakText(text, prefixLength) {
77
- return text.split('\n')
78
- .map((line, index) => index === 0 ? breakLine(line, prefixLength) : breakLine(line, 0))
79
- .join('\n');
101
+ return text
102
+ .split("\n")
103
+ .map((line, index) =>
104
+ index === 0 ? breakLine(line, prefixLength) : breakLine(line, 0)
105
+ )
106
+ .join("\n");
80
107
  }
81
108
 
82
109
  function breakLine(line, prefixLength) {
83
110
  const columns = process.stderr.columns || 95;
84
- return line.length >= columns - prefixLength
85
- ? `${line.substring(0, columns - prefixLength - 1)}\n${
86
- breakLine(line.substring(columns - prefixLength - 1, line.length), 0)
87
- }`
111
+ return line.length >= columns - prefixLength
112
+ ? `${line.substring(0, columns - prefixLength - 1)}\n${breakLine(
113
+ line.substring(columns - prefixLength - 1, line.length),
114
+ 0
115
+ )}`
88
116
  : line;
89
117
  }
90
118
 
@@ -94,8 +122,11 @@ function breakLine(line, prefixLength) {
94
122
  // .map((line, index) => index === 0 ? line.length + prefixLength : line.length);
95
123
  // }
96
124
  export function getLinesLength(text, prefixLength) {
97
- return text.split('\n')
98
- .map((line, index) => index === 0 ? line.length + prefixLength : line.length);
125
+ return text
126
+ .split("\n")
127
+ .map((line, index) =>
128
+ index === 0 ? line.length + prefixLength : line.length
129
+ );
99
130
  }
100
131
 
101
132
  export function writeStream(stream, output, rawLines) {
@@ -115,9 +146,11 @@ export function cleanStream(stream, rawLines) {
115
146
  }
116
147
 
117
148
  export function terminalSupportsUnicode() {
118
- // The default command prompt and powershell in Windows do not support Unicode characters.
119
- // However, the VSCode integrated terminal and the Windows Terminal both do.
120
- return process.platform !== 'win32'
121
- || process.env.TERM_PROGRAM === 'vscode'
122
- || !!process.env.WT_SESSION
149
+ // The default command prompt and powershell in Windows do not support Unicode characters.
150
+ // However, the VSCode integrated terminal and the Windows Terminal both do.
151
+ return (
152
+ process.platform !== "win32" ||
153
+ process.env.TERM_PROGRAM === "vscode" ||
154
+ !!process.env.WT_SESSION
155
+ );
123
156
  }
@@ -510,9 +510,8 @@ function hideError(velocity) {
510
510
  } catch (error) {}
511
511
  } else {
512
512
  try {
513
- document.getElementById(
514
- "elm-live:elmErrorBackground"
515
- ).style.opacity = 0;
513
+ document.getElementById("elm-live:elmErrorBackground").style.opacity =
514
+ 0;
516
515
  document.getElementById("elm-live:elmError").style.transform =
517
516
  "rotateX(90deg)";
518
517
  } catch (error) {}
@@ -14,7 +14,7 @@
14
14
  "dillonkearns/elm-bcp47-language-tag": "2.0.0",
15
15
  "dillonkearns/elm-form": "3.0.1",
16
16
  "dillonkearns/elm-markdown": "7.0.1",
17
- "dillonkearns/elm-pages": "10.2.0",
17
+ "dillonkearns/elm-pages": "10.2.1",
18
18
  "elm/browser": "1.0.2",
19
19
  "elm/bytes": "1.0.8",
20
20
  "elm/core": "1.0.5",
@@ -55,7 +55,7 @@
55
55
  "rtfeldman/elm-iso8601-date-strings": "1.1.4",
56
56
  "stil4m/elm-syntax": "7.3.8",
57
57
  "stil4m/structured-writer": "1.0.3",
58
- "the-sett/elm-pretty-printer": "3.1.0",
58
+ "the-sett/elm-pretty-printer": "3.1.1",
59
59
  "wolfadex/elm-ansi": "3.0.0"
60
60
  }
61
61
  },
@@ -9,7 +9,7 @@
9
9
  "devDependencies": {
10
10
  "elm-codegen": "^0.6.1",
11
11
  "elm-optimize-level-2": "^0.3.5",
12
- "elm-pages": "3.0.19",
12
+ "elm-pages": "3.0.22",
13
13
  "elm-review": "^2.12.0",
14
14
  "elm-tooling": "^1.15.1",
15
15
  "lamdera": "^0.19.1-1.3.2",
@@ -2,6 +2,4 @@
2
2
  async function hello(name) {
3
3
  return `Hello ${name}!`;
4
4
  }
5
- export {
6
- hello
7
- };
5
+ export { hello };
@@ -6,8 +6,9 @@ import Cli.OptionsParser as OptionsParser
6
6
  import Cli.Program as Program
7
7
  import Elm
8
8
  import Elm.Annotation as Type
9
+ import Elm.Arg
9
10
  import Elm.Case
10
- import Elm.Declare
11
+ import Elm.Declare exposing (Function, fn2)
11
12
  import Elm.Let
12
13
  import Elm.Op
13
14
  import Gen.BackendTask
@@ -97,8 +98,8 @@ createFile { moduleName, fields } =
97
98
  )
98
99
  )
99
100
  |> Elm.Let.fn2 "fieldView"
100
- ( "label", Type.string |> Just )
101
- ( "field", Nothing )
101
+ (Elm.Arg.var "label")
102
+ (Elm.Arg.var "field")
102
103
  (\label field ->
103
104
  Html.div []
104
105
  [ Html.label []
@@ -131,57 +132,71 @@ createFile { moduleName, fields } =
131
132
  (\justFormHelp ->
132
133
  Request.formData justFormHelp.formHandlers request
133
134
  |> Gen.Maybe.call_.map
134
- (Elm.fn ( "formData", Nothing )
135
+ (Elm.fn (Elm.Arg.var "formData")
135
136
  (\formData ->
136
- Elm.Case.tuple formData
137
- "response"
138
- "parsedForm"
139
- (\response parsedForm ->
140
- Elm.Case.custom parsedForm
141
- Type.int
142
- [ Elm.Case.branch1 "Form.Valid"
143
- ( "validatedForm", Type.int )
144
- (\validatedForm ->
145
- Elm.Case.custom validatedForm
146
- Type.int
147
- [ Elm.Case.branch1 "Action"
148
- ( "parsed", Type.int )
149
- (\parsed ->
150
- Scaffold.Form.recordEncoder parsed fields
151
- |> Gen.Json.Encode.encode 2
152
- |> Gen.Pages.Script.call_.log
153
- |> Gen.BackendTask.call_.map
154
- (Elm.fn ( "_", Nothing )
155
- (\_ ->
156
- Response.render
157
- (Elm.record
158
- [ ( "errors", response )
159
- ]
160
- )
137
+ Elm.Case.custom
138
+ formData
139
+ Type.unit
140
+ [ Elm.Case.branch
141
+ (Elm.Arg.tuple
142
+ (Elm.Arg.var "response")
143
+ (Elm.Arg.var "parsedForm")
144
+ )
145
+ (\( response, parsedForm ) ->
146
+ Elm.Case.custom parsedForm
147
+ Type.int
148
+ [ Elm.Case.branch
149
+ (Elm.Arg.customType "Form.Valid" identity
150
+ |> Elm.Arg.item (Elm.Arg.var "validatedForm")
151
+ )
152
+ (\validatedForm ->
153
+ Elm.Case.custom validatedForm
154
+ Type.int
155
+ [ Elm.Case.branch
156
+ (Elm.Arg.customType "Action" identity
157
+ |> Elm.Arg.item (Elm.Arg.var "parsed")
158
+ )
159
+ (\parsed ->
160
+ Scaffold.Form.recordEncoder parsed fields
161
+ |> Gen.Json.Encode.encode 2
162
+ |> Gen.Pages.Script.call_.log
163
+ |> Gen.BackendTask.call_.map
164
+ (Elm.fn Elm.Arg.ignore
165
+ (\_ ->
166
+ Response.render
167
+ (Elm.record
168
+ [ ( "errors", response )
169
+ ]
170
+ )
171
+ )
161
172
  )
162
- )
163
- )
164
- ]
165
- )
166
- , Elm.Case.branch2 "Form.Invalid"
167
- ( "parsed", Type.int )
168
- ( "error", Type.int )
169
- (\_ _ ->
170
- "Form validations did not succeed!"
171
- |> Gen.Pages.Script.log
172
- |> Gen.BackendTask.call_.map
173
- (Elm.fn ( "_", Nothing )
174
- (\_ ->
175
- Response.render
176
- (Elm.record
177
- [ ( "errors", response )
178
- ]
179
- )
180
173
  )
181
- )
182
- )
183
- ]
184
- )
174
+ ]
175
+ )
176
+ , Elm.Case.branch
177
+ (Elm.Arg.customType
178
+ "Form.Invalid"
179
+ Tuple.pair
180
+ |> Elm.Arg.item (Elm.Arg.var "parsed")
181
+ |> Elm.Arg.item (Elm.Arg.var "error")
182
+ )
183
+ (\( _, _ ) ->
184
+ "Form validations did not succeed!"
185
+ |> Gen.Pages.Script.log
186
+ |> Gen.BackendTask.call_.map
187
+ (Elm.fn Elm.Arg.ignore
188
+ (\_ ->
189
+ Response.render
190
+ (Elm.record
191
+ [ ( "errors", response )
192
+ ]
193
+ )
194
+ )
195
+ )
196
+ )
197
+ ]
198
+ )
199
+ ]
185
200
  )
186
201
  )
187
202
  |> Gen.Maybe.withDefault
@@ -244,9 +259,10 @@ createFile { moduleName, fields } =
244
259
  \{ shared, app, msg, model } ->
245
260
  Elm.Case.custom msg
246
261
  (Type.named [] "Msg")
247
- [ Elm.Case.branch0 "NoOp"
248
- (Elm.tuple model
249
- Effect.none
262
+ [ Elm.Case.branch (Elm.Arg.customType "NoOp" ())
263
+ (\() ->
264
+ Elm.tuple model
265
+ Effect.none
250
266
  )
251
267
  ]
252
268
  , init =
@@ -262,24 +278,11 @@ createFile { moduleName, fields } =
262
278
  }
263
279
 
264
280
 
265
- errorsView :
266
- { declaration : Elm.Declaration
267
- , call : Elm.Expression -> Elm.Expression -> Elm.Expression
268
- , callFrom : List String -> Elm.Expression -> Elm.Expression -> Elm.Expression
269
- , value : List String -> Elm.Expression
270
- }
281
+ errorsView : Function (Elm.Expression -> Elm.Expression -> Elm.Expression)
271
282
  errorsView =
272
- Elm.Declare.fn2 "errorsView"
273
- ( "errors", Type.namedWith [ "Form" ] "Errors" [ Type.string ] |> Just )
274
- ( "field"
275
- , Type.namedWith [ "Form", "Validation" ]
276
- "Field"
277
- [ Type.string
278
- , Type.var "parsed"
279
- , Type.var "kind"
280
- ]
281
- |> Just
282
- )
283
+ fn2 "errorsView"
284
+ (Elm.Arg.var "errors")
285
+ (Elm.Arg.var "field")
283
286
  (\errors field ->
284
287
  Elm.ifThen
285
288
  (Gen.List.call_.isEmpty (Form.errorsForField field errors))
@@ -288,7 +291,7 @@ errorsView =
288
291
  []
289
292
  [ Html.call_.ul (Elm.list [])
290
293
  (Gen.List.call_.map
291
- (Elm.fn ( "error", Nothing )
294
+ (Elm.fn (Elm.Arg.var "error")
292
295
  (\error ->
293
296
  Html.li
294
297
  [ Attr.style "color" "red"
@@ -6,6 +6,7 @@ import Cli.OptionsParser as OptionsParser
6
6
  import Cli.Program as Program
7
7
  import Elm
8
8
  import Elm.Annotation as Type
9
+ import Elm.Arg
9
10
  import Elm.Case
10
11
  import Gen.BackendTask
11
12
  import Gen.Effect as Effect
@@ -69,9 +70,10 @@ createFile { moduleName } =
69
70
  \{ shared, app, msg, model } ->
70
71
  Elm.Case.custom msg
71
72
  (Type.named [] "Msg")
72
- [ Elm.Case.branch0 "NoOp"
73
- (Elm.tuple model
74
- Effect.none
73
+ [ Elm.Case.branch (Elm.Arg.customType "NoOp" ())
74
+ (\() ->
75
+ Elm.tuple model
76
+ Effect.none
75
77
  )
76
78
  ]
77
79
  , init =
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "elm-pages",
3
3
  "type": "module",
4
- "version": "3.0.20",
4
+ "version": "3.0.22",
5
5
  "homepage": "https://elm-pages.com",
6
6
  "moduleResolution": "node",
7
- "description": "Type-safe static sites, written in pure elm with your own custom elm-markup syntax.",
7
+ "description": "Hybrid Elm framework with full-stack and static routes.",
8
8
  "main": "index.js",
9
9
  "scripts": {
10
10
  "start": "cd examples/end-to-end && npm i && npx elm-pages dev",
@@ -12,7 +12,8 @@
12
12
  "test:snapshot": "(cd examples/escaping && npm install && npm test) && (cd examples/base-path && npm install && npm test)",
13
13
  "cypress": "npm start & cypress run",
14
14
  "build:generator": "elm-codegen install && (cd codegen && lamdera make Generate.elm --output elm-pages-codegen.js && mv elm-pages-codegen.js elm-pages-codegen.cjs)",
15
- "review": "elm-review"
15
+ "review": "elm-review",
16
+ "format": "prettier . --write --cache --cache-location='node_modules/.cache/.prettiercache' --log-level=warn"
16
17
  },
17
18
  "repository": "https://github.com/dillonkearns/elm-pages",
18
19
  "keywords": [
@@ -27,27 +28,27 @@
27
28
  "dependencies": {
28
29
  "@sindresorhus/merge-streams": "^4.0.0",
29
30
  "busboy": "^1.6.0",
30
- "chokidar": "^4.0.1",
31
+ "chokidar": "^4.0.3",
31
32
  "cli-cursor": "^5.0.0",
32
- "commander": "^12.1.0",
33
+ "commander": "^13.1.0",
33
34
  "connect": "^3.7.0",
34
35
  "cookie-signature": "^1.2.2",
35
36
  "cross-spawn": "7.0.6",
36
37
  "devcert": "^1.2.2",
37
- "elm-doc-preview": "^6.0.0",
38
+ "elm-doc-preview": "^6.0.1",
38
39
  "elm-hot": "^1.1.6",
39
- "esbuild": "^0.24.0",
40
- "fs-extra": "^11.2.0",
41
- "globby": "14.0.2",
40
+ "esbuild": "^0.25.0",
41
+ "fs-extra": "^11.3.0",
42
+ "globby": "14.1.0",
42
43
  "gray-matter": "^4.0.3",
43
- "jsesc": "^3.0.2",
44
+ "jsesc": "^3.1.0",
44
45
  "kleur": "^4.1.5",
45
46
  "make-fetch-happen": "^14.0.3",
46
- "memfs": "^4.14.0",
47
+ "memfs": "^4.17.0",
47
48
  "micromatch": "^4.0.8",
48
49
  "serve-static": "^1.16.2",
49
- "terser": "^5.36.0",
50
- "vite": "^6.0.0",
50
+ "terser": "^5.39.0",
51
+ "vite": "^6.2.0",
51
52
  "which": "^5.0.0"
52
53
  },
53
54
  "devDependencies": {
@@ -55,18 +56,19 @@
55
56
  "@types/fs-extra": "^11.0.4",
56
57
  "@types/make-fetch-happen": "^10.0.4",
57
58
  "@types/micromatch": "^4.0.9",
58
- "@types/node": "^22.10.0",
59
+ "@types/node": "^22.13.9",
59
60
  "@types/serve-static": "^1.15.7",
60
- "cypress": "^13.16.0",
61
+ "cypress": "^14.1.0",
61
62
  "elm-codegen": "^0.6.1",
62
63
  "elm-optimize-level-2": "^0.3.5",
63
- "elm-review": "^2.12.0",
64
- "elm-test": "^0.19.1-revision12",
64
+ "elm-review": "^2.13.2",
65
+ "elm-test": "^0.19.1-revision15",
65
66
  "elm-tooling": "^1.15.1",
66
67
  "elm-verify-examples": "^6.0.3",
67
68
  "lamdera": "^0.19.1-1.3.2",
68
- "typescript": "^5.7.2",
69
- "vitest": "^2.1.6"
69
+ "prettier": "^3.5.3",
70
+ "typescript": "^5.8.2",
71
+ "vitest": "^3.0.7"
70
72
  },
71
73
  "files": [
72
74
  "adapter/",
@@ -17,6 +17,7 @@ view title =
17
17
 
18
18
  mainView : String -> Html msg
19
19
  mainView title =
20
+ -- NOTE: If you make changes here, also update pre-render-html.js!
20
21
  Html.div
21
22
  [ Attr.id "elm-pages-announcer"
22
23
  , Attr.attribute "aria-live" "assertive"
@@ -1025,7 +1025,8 @@ render404Page config sharedData isDevServer path notFoundReason =
1025
1025
 
1026
1026
  bodyToString : List (Html msg) -> String
1027
1027
  bodyToString body =
1028
- body |> List.map (HtmlPrinter.htmlToString Nothing) |> String.join "\n"
1028
+ -- NOTE: Don’t join the strings with a newline here – see pre-render-html.js.
1029
+ body |> List.map (HtmlPrinter.htmlToString Nothing) |> String.concat
1029
1030
 
1030
1031
 
1031
1032
  urlToRoute : ProgramConfig userMsg userModel route pageData actionData sharedData effect mappedMsg errorPage -> Url -> route
@@ -123,6 +123,7 @@ view config model =
123
123
  in
124
124
  { title = title
125
125
  , body =
126
+ -- NOTE: If you make changes here, also update pre-render-html.js!
126
127
  [ onViewChangeElement model.url
127
128
  , AriaLiveAnnouncer.view model.ariaNavigationAnnouncement
128
129
  ]
@@ -136,9 +137,10 @@ onViewChangeElement currentUrl =
136
137
  -- it is used from the JS-side to reliably
137
138
  -- check when Elm has changed pages
138
139
  -- (and completed rendering the view)
140
+ -- NOTE: If you make changes here, also update pre-render-html.js!
139
141
  Html.div
140
142
  [ Attr.attribute "data-url" (Url.toString currentUrl)
141
- , Attr.attribute "display" "none"
143
+ , Attr.style "display" "none"
142
144
  ]
143
145
  []
144
146
 
@@ -2,7 +2,7 @@ module Test.Html.Internal.ElmHtml.InternalTypes exposing
2
2
  ( ElmHtml(..), TextTagRecord, NodeRecord, CustomNodeRecord, MarkdownNodeRecord
3
3
  , Facts, Tagger, EventHandler, ElementKind(..)
4
4
  , Attribute(..), AttributeRecord, NamespacedAttributeRecord, PropertyRecord, EventRecord
5
- , decodeElmHtml, emptyFacts, toElementKind, decodeAttribute
5
+ , decodeElmHtml, emptyFacts, toElementKind, decodeAttribute, isUnsafeName
6
6
  )
7
7
 
8
8
  {-| Internal types used to represent Elm Html in pure Elm
@@ -13,7 +13,7 @@ module Test.Html.Internal.ElmHtml.InternalTypes exposing
13
13
 
14
14
  @docs Attribute, AttributeRecord, NamespacedAttributeRecord, PropertyRecord, EventRecord
15
15
 
16
- @docs decodeElmHtml, emptyFacts, toElementKind, decodeAttribute
16
+ @docs decodeElmHtml, emptyFacts, toElementKind, decodeAttribute, isUnsafeName
17
17
 
18
18
  -}
19
19
 
@@ -21,6 +21,7 @@ import Dict exposing (Dict)
21
21
  import Html.Events
22
22
  import Json.Decode exposing (field)
23
23
  import Json.Encode
24
+ import Regex exposing (Regex)
24
25
  import Test.Html.Internal.ElmHtml.Constants as Constants exposing (..)
25
26
  import Test.Html.Internal.ElmHtml.Helpers exposing (..)
26
27
  import Test.Html.Internal.ElmHtml.Markdown exposing (..)
@@ -124,6 +125,7 @@ type ElementKind
124
125
  | EscapableRawTextElements
125
126
  | ForeignElements
126
127
  | NormalElements
128
+ | InvalidElements
127
129
 
128
130
 
129
131
  type HtmlContext msg
@@ -510,12 +512,27 @@ escapableRawTextElements =
510
512
  -}
511
513
 
512
514
 
515
+ unsafeName : Regex
516
+ unsafeName =
517
+ {- https://github.com/preactjs/preact-render-to-string/blob/27f340b6e7d77ec7775a49a78d105cad26fa0857/src/lib/util.js#L2 -}
518
+ Regex.fromString "[\\s\\n\\\\/='\"\\0<>]"
519
+ |> Maybe.withDefault Regex.never
520
+
521
+
522
+ isUnsafeName : String -> Bool
523
+ isUnsafeName name =
524
+ Regex.contains unsafeName name
525
+
526
+
513
527
  {-| Identify the kind of element. Helper to convert an tag name into a type for
514
528
  pattern matching.
515
529
  -}
516
530
  toElementKind : String -> ElementKind
517
531
  toElementKind element =
518
- if List.member element voidElements then
532
+ if isUnsafeName element then
533
+ InvalidElements
534
+
535
+ else if List.member element voidElements then
519
536
  VoidElements
520
537
 
521
538
  else if List.member element rawTextElements then
@@ -115,23 +115,25 @@ nodeRecordToString options { tag, children, facts } =
115
115
  styleValues
116
116
  |> List.map (\( key, value ) -> key ++ ":" ++ value ++ ";")
117
117
  |> String.join ""
118
- |> (\styleString -> "style=\"" ++ styleString ++ "\"")
118
+ |> (\styleString -> "style=\"" ++ escapeHtml styleString ++ "\"")
119
119
  |> Just
120
120
 
121
121
  classes =
122
122
  Dict.get "className" facts.stringAttributes
123
- |> Maybe.map (\name -> "class=\"" ++ name ++ "\"")
123
+ |> Maybe.map (\name -> "class=\"" ++ escapeHtml name ++ "\"")
124
124
 
125
125
  stringAttributes =
126
126
  Dict.filter (\k v -> k /= "className") facts.stringAttributes
127
127
  |> Dict.toList
128
+ |> List.filter (\( k, _ ) -> not (isUnsafeName k))
128
129
  |> List.map (Tuple.mapFirst propertyToAttributeName)
129
- |> List.map (\( k, v ) -> k ++ "=\"" ++ v ++ "\"")
130
+ |> List.map (\( k, v ) -> k ++ "=\"" ++ escapeHtml v ++ "\"")
130
131
  |> String.join " "
131
132
  |> Just
132
133
 
133
134
  boolAttributes =
134
135
  Dict.toList facts.boolAttributes
136
+ |> List.filter (\( k, _ ) -> not (isUnsafeName k))
135
137
  |> List.filterMap
136
138
  (\( k, v ) ->
137
139
  if v then
@@ -144,6 +146,9 @@ nodeRecordToString options { tag, children, facts } =
144
146
  |> Just
145
147
  in
146
148
  case toElementKind tag of
149
+ InvalidElements ->
150
+ [ "<!-- invalid element -->" ]
151
+
147
152
  {- Void elements only have a start tag; end tags must not be
148
153
  specified for void elements.
149
154
  -}
@@ -187,10 +192,15 @@ escapeRawText kind rawText =
187
192
  rawText
188
193
 
189
194
  _ ->
190
- {- https://github.com/elm/virtual-dom/blob/5a5bcf48720bc7d53461b3cd42a9f19f119c5503/src/Elm/Kernel/VirtualDom.server.js#L8-L26 -}
191
- rawText
192
- |> String.replace "&" "&amp;"
193
- |> String.replace "<" "&lt;"
194
- |> String.replace ">" "&gt;"
195
- |> String.replace "\"" "&quot;"
196
- |> String.replace "'" "&#039;"
195
+ escapeHtml rawText
196
+
197
+
198
+ escapeHtml : String -> String
199
+ escapeHtml rawText =
200
+ {- https://github.com/elm/virtual-dom/blob/5a5bcf48720bc7d53461b3cd42a9f19f119c5503/src/Elm/Kernel/VirtualDom.server.js#L8-L26 -}
201
+ rawText
202
+ |> String.replace "&" "&amp;"
203
+ |> String.replace "<" "&lt;"
204
+ |> String.replace ">" "&gt;"
205
+ |> String.replace "\"" "&quot;"
206
+ |> String.replace "'" "&#039;"
File without changes
@@ -1,5 +0,0 @@
1
- export function hello(): string {
2
- console.log("HELLLO!!!!");
3
-
4
- return "Hello World!";
5
- }