@bookshop/hugo-engine 3.5.0-alpha.0 → 3.5.1

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 (19) hide show
  1. package/.nyc_output/processinfo/{0021c648-0cec-4195-a0dc-985b510e7b75.json → 0576b405-4d6f-416a-a012-1cd6714d2de3.json} +1 -1
  2. package/.nyc_output/processinfo/{437debd8-2dd5-4b95-b772-f5bba688f936.json → 1186c40f-ab1b-4fc3-8c8f-905478755bdd.json} +1 -1
  3. package/.nyc_output/processinfo/2fdbc94b-8ef1-4abe-8419-3b1f5ae13538.json +1 -0
  4. package/.nyc_output/processinfo/{4bd04565-b332-432b-8129-e9fed05110a9.json → 9afa86b4-7892-4e9a-b022-45b2ba35af2a.json} +1 -1
  5. package/.nyc_output/processinfo/{bf6f8f9b-7706-4a31-ba31-965369f4c4ce.json → e261f702-c88f-4b27-aebb-f0ddcd3d512e.json} +1 -1
  6. package/.nyc_output/processinfo/index.json +1 -1
  7. package/full-hugo-renderer/hugo_renderer.wasm +0 -0
  8. package/full-hugo-renderer/hugo_renderer.wasm.gz +0 -0
  9. package/lib/engine.js +52 -18
  10. package/lib/engine.test.js +79 -2
  11. package/lib/engine__test__.js +445 -0
  12. package/package.json +4 -3
  13. package/prep__test__.js +5 -0
  14. package/.nyc_output/processinfo/a08fb26c-673c-4f78-bf68-a2615bce7bad.json +0 -1
  15. /package/.nyc_output/{0021c648-0cec-4195-a0dc-985b510e7b75.json → 0576b405-4d6f-416a-a012-1cd6714d2de3.json} +0 -0
  16. /package/.nyc_output/{437debd8-2dd5-4b95-b772-f5bba688f936.json → 1186c40f-ab1b-4fc3-8c8f-905478755bdd.json} +0 -0
  17. /package/.nyc_output/{4bd04565-b332-432b-8129-e9fed05110a9.json → 2fdbc94b-8ef1-4abe-8419-3b1f5ae13538.json} +0 -0
  18. /package/.nyc_output/{a08fb26c-673c-4f78-bf68-a2615bce7bad.json → 9afa86b4-7892-4e9a-b022-45b2ba35af2a.json} +0 -0
  19. /package/.nyc_output/{bf6f8f9b-7706-4a31-ba31-965369f4c4ce.json → e261f702-c88f-4b27-aebb-f0ddcd3d512e.json} +0 -0
@@ -1 +1 @@
1
- {"parent":"a08fb26c-673c-4f78-bf68-a2615bce7bad","pid":5573,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1683711571709,"ppid":5562,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/0021c648-0cec-4195-a0dc-985b510e7b75.json","externalId":"","uuid":"0021c648-0cec-4195-a0dc-985b510e7b75","files":[]}
1
+ {"parent":"2fdbc94b-8ef1-4abe-8419-3b1f5ae13538","pid":5527,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1683882586943,"ppid":5494,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/0576b405-4d6f-416a-a012-1cd6714d2de3.json","externalId":"","uuid":"0576b405-4d6f-416a-a012-1cd6714d2de3","files":[]}
@@ -1 +1 @@
1
- {"parent":"a08fb26c-673c-4f78-bf68-a2615bce7bad","pid":5579,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1683711571719,"ppid":5562,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/437debd8-2dd5-4b95-b772-f5bba688f936.json","externalId":"","uuid":"437debd8-2dd5-4b95-b772-f5bba688f936","files":[]}
1
+ {"parent":"2fdbc94b-8ef1-4abe-8419-3b1f5ae13538","pid":5511,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1683882586504,"ppid":5494,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/1186c40f-ab1b-4fc3-8c8f-905478755bdd.json","externalId":"","uuid":"1186c40f-ab1b-4fc3-8c8f-905478755bdd","files":[]}
@@ -0,0 +1 @@
1
+ {"parent":null,"pid":5494,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/cli.js","-v"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1683882585987,"ppid":5483,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/2fdbc94b-8ef1-4abe-8419-3b1f5ae13538.json","externalId":"","uuid":"2fdbc94b-8ef1-4abe-8419-3b1f5ae13538","files":[]}
@@ -1 +1 @@
1
- {"parent":"a08fb26c-673c-4f78-bf68-a2615bce7bad","pid":5596,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1683711572082,"ppid":5562,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/4bd04565-b332-432b-8129-e9fed05110a9.json","externalId":"","uuid":"4bd04565-b332-432b-8129-e9fed05110a9","files":[]}
1
+ {"parent":"2fdbc94b-8ef1-4abe-8419-3b1f5ae13538","pid":5505,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1683882586497,"ppid":5494,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/9afa86b4-7892-4e9a-b022-45b2ba35af2a.json","externalId":"","uuid":"9afa86b4-7892-4e9a-b022-45b2ba35af2a","files":[]}
@@ -1 +1 @@
1
- {"parent":"a08fb26c-673c-4f78-bf68-a2615bce7bad","pid":5595,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1683711572091,"ppid":5562,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/bf6f8f9b-7706-4a31-ba31-965369f4c4ce.json","externalId":"","uuid":"bf6f8f9b-7706-4a31-ba31-965369f4c4ce","files":[]}
1
+ {"parent":"2fdbc94b-8ef1-4abe-8419-3b1f5ae13538","pid":5534,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1683882586987,"ppid":5494,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/e261f702-c88f-4b27-aebb-f0ddcd3d512e.json","externalId":"","uuid":"e261f702-c88f-4b27-aebb-f0ddcd3d512e","files":[]}
@@ -1 +1 @@
1
- {"processes":{"0021c648-0cec-4195-a0dc-985b510e7b75":{"parent":"a08fb26c-673c-4f78-bf68-a2615bce7bad","children":[]},"437debd8-2dd5-4b95-b772-f5bba688f936":{"parent":"a08fb26c-673c-4f78-bf68-a2615bce7bad","children":[]},"4bd04565-b332-432b-8129-e9fed05110a9":{"parent":"a08fb26c-673c-4f78-bf68-a2615bce7bad","children":[]},"a08fb26c-673c-4f78-bf68-a2615bce7bad":{"parent":null,"children":["0021c648-0cec-4195-a0dc-985b510e7b75","437debd8-2dd5-4b95-b772-f5bba688f936","4bd04565-b332-432b-8129-e9fed05110a9","bf6f8f9b-7706-4a31-ba31-965369f4c4ce"]},"bf6f8f9b-7706-4a31-ba31-965369f4c4ce":{"parent":"a08fb26c-673c-4f78-bf68-a2615bce7bad","children":[]}},"files":{},"externalIds":{}}
1
+ {"processes":{"0576b405-4d6f-416a-a012-1cd6714d2de3":{"parent":"2fdbc94b-8ef1-4abe-8419-3b1f5ae13538","children":[]},"1186c40f-ab1b-4fc3-8c8f-905478755bdd":{"parent":"2fdbc94b-8ef1-4abe-8419-3b1f5ae13538","children":[]},"2fdbc94b-8ef1-4abe-8419-3b1f5ae13538":{"parent":null,"children":["0576b405-4d6f-416a-a012-1cd6714d2de3","1186c40f-ab1b-4fc3-8c8f-905478755bdd","9afa86b4-7892-4e9a-b022-45b2ba35af2a","e261f702-c88f-4b27-aebb-f0ddcd3d512e"]},"9afa86b4-7892-4e9a-b022-45b2ba35af2a":{"parent":"2fdbc94b-8ef1-4abe-8419-3b1f5ae13538","children":[]},"e261f702-c88f-4b27-aebb-f0ddcd3d512e":{"parent":"2fdbc94b-8ef1-4abe-8419-3b1f5ae13538","children":[]}},"files":{},"externalIds":{}}
package/lib/engine.js CHANGED
@@ -2,7 +2,6 @@ import compressedHugoWasm from "../full-hugo-renderer/hugo_renderer.wasm.gz";
2
2
  import { gunzipSync } from 'fflate';
3
3
  import translateTextTemplate from './translateTextTemplate.js';
4
4
  import { IdentifierParser } from './hugoIdentifierParser.js';
5
- import { version } from '../package.json';
6
5
 
7
6
  const sleep = (ms = 0) => {
8
7
  return new Promise(r => setTimeout(r, ms));
@@ -42,9 +41,12 @@ export class Engine {
42
41
  this.key = 'hugo';
43
42
  this.name = options.name;
44
43
  this.files = options.files;
45
- this.origin = document.currentScript?.src || `/bookshop.js`;
44
+ this.origin = (typeof document === 'undefined' ? '' : document.currentScript?.src) || `/bookshop.js`;
45
+ this.synthetic = options.synthetic ?? false;
46
46
 
47
- this.initializeHugo();
47
+ if (!this.synthetic) {
48
+ this.initializeHugo();
49
+ }
48
50
  }
49
51
 
50
52
  async initializeHugo() {
@@ -79,6 +81,20 @@ export class Engine {
79
81
 
80
82
  const componentSuccess = window["writeHugoFiles"](JSON.stringify(mappedFiles));
81
83
  const templateSuccess = window["writeHugoFiles"](JSON.stringify(templates));
84
+
85
+ // BIG OL' TODO: Writing these files ahead of render() seems to be load-bearing,
86
+ // which doesn't yet make sense to me.
87
+ // If these files are created for the first time in render(), the site doesn't
88
+ // appear to build (need to extract some logs from hugo to determine why).
89
+ // Perplexingly, writing then building in eval() appears to have been working
90
+ // fine in the same case.
91
+ // My suspicion would be something around our changeEvents() handling
92
+ // in the Hugo builder, but I don't know why that wouldn't affect eval()... 🤷‍♂️
93
+ // For now, the load-bearing stubs will live in this Hugo initialization.
94
+ window.writeHugoFiles(JSON.stringify({
95
+ "layouts/index.html": "Uninitialized Layout",
96
+ "content/_index.md": "{ \”initialized\": false }\n"
97
+ }));
82
98
  }
83
99
 
84
100
  async initializeLocalCompressedHugo() {
@@ -210,14 +226,14 @@ export class Engine {
210
226
  const component_regex = /execute of template failed: template: ([^:]+):\d/ig;
211
227
  let file_stack = [...error_string.matchAll(component_regex)].map(([, file]) => `layouts/${file}`);
212
228
  if (file_stack.length) {
213
- const deepest_errored_component = file_stack[file_stack.length-1];
229
+ const deepest_errored_component = file_stack[file_stack.length - 1];
214
230
  const error_chunks = error_string.split("execute of template failed:");
215
- const error_msg = error_chunks[error_chunks.length-1] ?? "template error";
231
+ const error_msg = error_chunks[error_chunks.length - 1] ?? "template error";
216
232
  window.writeHugoFiles(JSON.stringify({
217
233
  [deepest_errored_component]: [
218
234
  `<div style="padding: 10px; background-color: lightcoral; color: black; font-weight: bold;">`,
219
235
  `Failed to render ${deepest_errored_component}. <br/>`,
220
- `<pre style="margin-top: 10px; background-color: lightcoral; border: solid 1px black;">`,
236
+ `<pre style="margin-top: 10px; background-color: lightcoral; border: solid 1px black; white-space: pre-line;">`,
221
237
  `<code style="font-family: monospace; color: black;">${error_msg.replace(/</, '&lt;')}</code></pre>`,
222
238
  `</div>`
223
239
  ].join('\n')
@@ -227,9 +243,12 @@ export class Engine {
227
243
 
228
244
  const error_logs = log_messages.filter(log => log.startsWith("ERROR")).join("\n");
229
245
  const missing_regex = /Component "([^"]+)" does not exist/ig;
230
- file_stack = [...error_logs.matchAll(missing_regex)].map(([, file]) => `layouts/partials/bookshop/components/${file}/${file}.hugo.html`);
246
+ file_stack = [...error_logs.matchAll(missing_regex)].map(([, file]) => {
247
+ let filename = file.split('/').pop();
248
+ return `layouts/partials/bookshop/components/${file}/${filename}.hugo.html`;
249
+ });
231
250
  if (file_stack.length) {
232
- const deepest_errored_component = file_stack[file_stack.length-1];
251
+ const deepest_errored_component = file_stack[file_stack.length - 1];
233
252
  window.writeHugoFiles(JSON.stringify({
234
253
  [deepest_errored_component]: [
235
254
  `<div class="bookshop_error" style="padding: 10px; background-color: lightcoral; color: black; font-weight: bold;">`,
@@ -305,9 +324,25 @@ export class Engine {
305
324
  }
306
325
 
307
326
  async eval(str, props = [{}], logger) {
308
- while (!window.buildHugo) await sleep(10);
327
+ if (!this.synthetic) {
328
+ while (!window.buildHugo) await sleep(10);
329
+ }
309
330
  let props_obj = props.reduce((a, b) => { return { ...a, ...b } });
310
331
  let full_props = props_obj;
332
+ str = str.trim();
333
+
334
+ // We can return early if we're evaluating a literal string or digit
335
+ if (/^".*"$|^`.*`$|^\d+(\.\d+)?$|^true$|^false$/.test(str)) {
336
+ logger?.log?.(`Unwrapping the string ${str}`);
337
+ try {
338
+ if (/^`.*`$/.test(str)) {
339
+ return JSON.parse(str.replace(/^`|`$/g, '"'));
340
+ }
341
+ return JSON.parse(str);
342
+ } catch (e) {
343
+ logger?.log?.(`Was not valid JSON for some reason. Moving on...`);
344
+ }
345
+ }
311
346
 
312
347
  // We're capable of looking up a simple variable
313
348
  // (and it's hard to pass to wasm since we store variables on the context)
@@ -333,17 +368,11 @@ export class Engine {
333
368
  const process = async (obj) => {
334
369
  for (const [k, v] of Object.entries(obj)) {
335
370
  if (typeof v === "string") {
336
- if (/^".*"$/.test(v)) {
337
- // Normalized strings look like "\"string\""
338
- logger?.log?.(`Unwrapping the string ${k}: ${v}`);
339
- obj[k] = JSON.parse(v);
340
- } else {
341
- // No inner quotes means it needs to be evaled again
342
- logger?.log?.(`Evaluating the inner ${k}: ${v}`);
343
- obj[k] = await this.eval(v, [props_obj], logger.nested());
344
- }
371
+ logger?.log?.(`Evaluating the inner ${k}: ${v}`);
372
+ obj[k] = await this.eval(v, [props_obj], logger?.nested?.());
345
373
  } else if (typeof v === "object") {
346
374
  logger?.log?.(`Processing the inner object ${k}`);
375
+ process(v);
347
376
  }
348
377
  }
349
378
  }
@@ -377,6 +406,11 @@ export class Engine {
377
406
  }
378
407
  }).join('');
379
408
  const eval_str = `${variable_decl}{{ with .Params.props }}${assignments}{{ jsonify (${str}) }}{{ end }}`;
409
+
410
+ if (this.synthetic) {
411
+ return null;
412
+ }
413
+
380
414
  window.writeHugoFiles(JSON.stringify({
381
415
  "layouts/index.html": eval_str,
382
416
  "content/_index.md": JSON.stringify({ props: props_obj, full_props: full_props }, null, 2)
@@ -1,5 +1,82 @@
1
1
  import test from 'ava';
2
+ import { Engine } from './engine__test__.js';
2
3
 
3
- test("stub", async t => {
4
- t.pass()
4
+ let engine = null, logger = null;
5
+ test.before(async t => {
6
+ engine = new Engine({ synthetic: true });
7
+ logger = {
8
+ log: (str) => console.log(str),
9
+ nested: () => logger,
10
+ }
11
+ });
12
+
13
+ test("eval value", async t => {
14
+ const val = await engine.eval(`.test`, [{ test: 'bookshop' }]);
15
+ t.is(val, 'bookshop');
16
+ });
17
+
18
+ test("eval variable", async t => {
19
+ const val = await engine.eval(`$var`, [{ "$var": 'string' }, { test: 'bookshop' }]);
20
+ t.is(val, 'string');
21
+ });
22
+
23
+ test("eval literals", async t => {
24
+ let val = await engine.eval(`"test"`, [{}]);
25
+ t.is(val, 'test');
26
+
27
+ val = await engine.eval("`test`", [{}]);
28
+ t.is(val, 'test');
29
+
30
+ val = await engine.eval("4", [{}]);
31
+ t.is(val, 4);
32
+
33
+ val = await engine.eval("99.9", [{}]);
34
+ t.is(val, 99.9);
35
+
36
+ val = await engine.eval("true", [{}]);
37
+ t.is(val, true);
38
+
39
+ val = await engine.eval("false", [{}]);
40
+ t.is(val, false);
41
+ });
42
+
43
+ test("eval through dot scope", async t => {
44
+ const val_later = await engine.eval(`.test`, [{ test: 'bookshop' }, { ".": { test: 'inner_bookshop' } }]);
45
+ t.is(val_later, 'inner_bookshop');
46
+
47
+ const val_earlier = await engine.eval(`.test`, [{ ".": { test: 'inner_bookshop' } }, { test: 'bookshop' }]);
48
+ t.is(val_earlier, 'inner_bookshop');
49
+ });
50
+
51
+ test("access dot scope", async t => {
52
+ const dot = await engine.eval(`.`, [{ test: 'bookshop' }]);
53
+ t.deepEqual(dot, { test: 'bookshop' });
54
+
55
+ const dot_dot = await engine.eval(`.`, [{ test: 'bookshop' }, { ".": { test: 'inner_bookshop' } }]);
56
+ t.deepEqual(dot_dot, { test: 'inner_bookshop' });
57
+ });
58
+
59
+ test("eval literal dict", async t => {
60
+ const dot = await engine.eval(`(dict "zanzibar" "gin")`, [{}]);
61
+ t.deepEqual(dot, { zanzibar: 'gin' });
62
+ });
63
+
64
+ test("eval dict with scope access", async t => {
65
+ const dot = await engine.eval(`(dict "zanzibar" .live)`, [{ live: "editing" }]);
66
+ t.deepEqual(dot, { zanzibar: 'editing' });
67
+ });
68
+
69
+ test("eval dict with variable access", async t => {
70
+ const dot = await engine.eval(`(dict "zanzibar" $live)`, [{ "$live": "editing" }]);
71
+ t.deepEqual(dot, { zanzibar: 'editing' });
72
+ });
73
+
74
+ test("eval complex dict", async t => {
75
+ const dot = await engine.eval(`(dict "zanzibar" (slice .edit .live) "and" "a_literal")`, [{ live: "editing" }, { edit: "live" }]);
76
+ t.deepEqual(dot, { zanzibar: ['live', 'editing'], and: 'a_literal' });
77
+ });
78
+
79
+ test("eval array index", async t => {
80
+ const dot = await engine.eval(`(index .live 1)`, [{ live: ["editing", "lively"] }]);
81
+ t.deepEqual(dot, "lively");
5
82
  });
@@ -0,0 +1,445 @@
1
+ // import compressedHugoWasm from "../full-hugo-renderer/hugo_renderer.wasm.gz";
2
+ import { gunzipSync } from 'fflate';
3
+ import translateTextTemplate from './translateTextTemplate.js';
4
+ import { IdentifierParser } from './hugoIdentifierParser.js';
5
+
6
+ const sleep = (ms = 0) => {
7
+ return new Promise(r => setTimeout(r, ms));
8
+ }
9
+
10
+ const dig = (obj, path) => {
11
+ if (typeof path === 'string') path = path.replace(/\[(\d+)]/g, '.$1').split('.');
12
+ obj = obj[path.shift()];
13
+ if (obj && path.length) return dig(obj, path);
14
+ return obj;
15
+ }
16
+
17
+ let mapHugoVariablesToParams = (obj, path) => {
18
+ let resolved = {};
19
+ for (let [k, v] of Object.entries(obj)) {
20
+ if (k.startsWith('$')) {
21
+ const remapped = `__bksh_${k.substring(1)}`;
22
+ obj[remapped] = v;
23
+ resolved[k] = `${path}.${remapped}`;
24
+ } else {
25
+ if (v && typeof v === 'object' && !Array.isArray(v)) {
26
+ resolved = { ...resolved, ...mapHugoVariablesToParams(v, `${path}.${k}`) }
27
+ }
28
+ }
29
+ }
30
+ return resolved
31
+ }
32
+
33
+ export class Engine {
34
+ constructor(options) {
35
+ options = {
36
+ name: "Hugo",
37
+ files: {},
38
+ ...options,
39
+ };
40
+
41
+ this.key = 'hugo';
42
+ this.name = options.name;
43
+ this.files = options.files;
44
+ this.origin = (typeof document === 'undefined' ? '' : document.currentScript?.src) || `/bookshop.js`;
45
+ this.synthetic = options.synthetic ?? false;
46
+
47
+ if (!this.synthetic) {
48
+ this.initializeHugo();
49
+ }
50
+ }
51
+
52
+ async initializeHugo() {
53
+ const templates = {
54
+ "layouts/partials/bookshop.html": (await import("../bookshop-hugo-templates/bookshop.html")).default,
55
+ "layouts/partials/bookshop_scss.html": (await import("../bookshop-hugo-templates/bookshop_scss.html")).default,
56
+ "layouts/partials/bookshop_partial.html": (await import("../bookshop-hugo-templates/bookshop_partial.html")).default,
57
+ "layouts/partials/bookshop_component_browser.html": (await import("../bookshop-hugo-templates/bookshop_component_browser.html")).default,
58
+ "layouts/partials/bookshop_bindings.html": (await import("../bookshop-hugo-templates/bookshop_bindings.html")).default,
59
+ "layouts/partials/_bookshop/helpers/component.html": (await import("../bookshop-hugo-templates/helpers/component.html")).default,
60
+ "layouts/partials/_bookshop/helpers/component_key.html": (await import("../bookshop-hugo-templates/helpers/component_key.html")).default,
61
+ "layouts/partials/_bookshop/helpers/partial.html": (await import("../bookshop-hugo-templates/helpers/partial.html")).default,
62
+ "layouts/partials/_bookshop/helpers/partial_key.html": (await import("../bookshop-hugo-templates/helpers/partial_key.html")).default,
63
+ "layouts/partials/_bookshop/errors/bad_bookshop_tag.html": (await import("../bookshop-hugo-templates/errors/bad_bookshop_tag.html")).default,
64
+ "layouts/partials/_bookshop/errors/err.html": (await import("../bookshop-hugo-templates/errors/err.html")).default,
65
+ };
66
+
67
+ templates["config.toml"] = "params.env_bookshop_live = true";
68
+
69
+ // When this script is run locally, the hugo wasm is loaded as binary rather than output as a file.
70
+ if (compressedHugoWasm?.constructor === Uint8Array) {
71
+ await this.initializeInlineHugo();
72
+ } else {
73
+ await this.initializeLocalCompressedHugo();
74
+ }
75
+
76
+ // TODO: Tidy
77
+ const mappedFiles = {};
78
+ for (const file of Object.entries(this.files)) {
79
+ mappedFiles[`layouts/partials/bookshop/${file[0]}`] = translateTextTemplate(file[1], {});
80
+ }
81
+
82
+ const componentSuccess = window["writeHugoFiles"](JSON.stringify(mappedFiles));
83
+ const templateSuccess = window["writeHugoFiles"](JSON.stringify(templates));
84
+
85
+ // BIG OL' TODO: Writing these files ahead of render() seems to be load-bearing,
86
+ // which doesn't yet make sense to me.
87
+ // If these files are created for the first time in render(), the site doesn't
88
+ // appear to build (need to extract some logs from hugo to determine why).
89
+ // Perplexingly, writing then building in eval() appears to have been working
90
+ // fine in the same case.
91
+ // My suspicion would be something around our changeEvents() handling
92
+ // in the Hugo builder, but I don't know why that wouldn't affect eval()... 🤷‍♂️
93
+ // For now, the load-bearing stubs will live in this Hugo initialization.
94
+ window.writeHugoFiles(JSON.stringify({
95
+ "layouts/index.html": "Uninitialized Layout",
96
+ "content/_index.md": "{ \”initialized\": false }\n"
97
+ }));
98
+ }
99
+
100
+ async initializeLocalCompressedHugo() {
101
+ try {
102
+
103
+ let prefetched = {};
104
+ if (typeof window !== "undefined" && window?.CloudCannon && window?.CloudCannon?.prefetchedFiles) {
105
+ prefetched = await window.CloudCannon.prefetchedFiles?.();
106
+ this.availablePrefetchKeys = Object.keys(prefetched);
107
+ }
108
+
109
+ const go = new Go();
110
+ const compressedWasmOrigin = this.origin.replace(/\/[^\.\/]+\.(min\.)?js/, compressedHugoWasm.replace(/^\./, ''));
111
+ const compressedWasmPath = (new URL(compressedWasmOrigin)).pathname;
112
+
113
+ let compressedBuffer;
114
+ if (prefetched[compressedWasmPath]) {
115
+ compressedBuffer = await prefetched[compressedWasmPath]?.arrayBuffer();
116
+ this.loadedFrom = "prefetch";
117
+ } else {
118
+ const compressedResponse = await fetch(compressedWasmOrigin);
119
+ compressedBuffer = await compressedResponse.arrayBuffer();
120
+ this.loadedFrom = "network";
121
+ }
122
+
123
+ const renderer = gunzipSync(new Uint8Array(compressedBuffer));
124
+
125
+ // Check the magic word at the start of the buffer as a way to verify the CDN download worked.
126
+ const isWasm = ([...renderer.slice(0, 4)]).map(g => g.toString(16).padStart(2, '0')).join('') === "0061736d";
127
+ if (!isWasm) throw "Not WASM";
128
+
129
+ const compressedResult = await WebAssembly.instantiate(renderer, go.importObject);
130
+ go.run(compressedResult.instance);
131
+ } catch (e) {
132
+ console.error("Couldn't load the local compressed Hugo WASM");
133
+ console.error(e);
134
+ }
135
+ }
136
+
137
+ async initializeInlineHugo() {
138
+ const go = new Go();
139
+ const buffer = compressedHugoWasm.buffer;
140
+ const renderer = gunzipSync(new Uint8Array(buffer));
141
+ const result = await WebAssembly.instantiate(renderer, go.importObject);
142
+ go.run(result.instance);
143
+ }
144
+
145
+ getSharedKey(name) {
146
+ return `shared/hugo/${name}.hugo.html`;
147
+ }
148
+
149
+ getShared(name) {
150
+ const key = this.getSharedKey(name);
151
+ return this.files?.[key];
152
+ }
153
+
154
+ getComponentKey(name) {
155
+ const base = name.split("/").reverse()[0];
156
+ return `components/${name}/${base}.hugo.html`;
157
+ }
158
+
159
+ getComponent(name) {
160
+ const key = this.getComponentKey(name);
161
+ return this.files?.[key];
162
+ }
163
+
164
+ hasComponent(name) {
165
+ const key = this.getComponentKey(name);
166
+ return !!this.files?.[key];
167
+ }
168
+
169
+ hasShared(name) {
170
+ const key = this.getSharedKey(name);
171
+ return !!this.files?.[key];
172
+ }
173
+
174
+ resolveComponentType(name) {
175
+ if (this.hasComponent(name)) return 'component';
176
+ if (this.hasShared(name)) return 'shared';
177
+ return false;
178
+ }
179
+
180
+ transformData(data) {
181
+ return {
182
+ Params: data
183
+ };
184
+ }
185
+
186
+ async storeMeta(meta = {}) {
187
+ while (!window.writeHugoFiles) {
188
+ await sleep(100);
189
+ };
190
+ window.writeHugoFiles(JSON.stringify({
191
+ "config.toml": [
192
+ meta.baseurl ? `baseURL = ${meta.baseurl}` : "",
193
+ meta.copyright ? `copyright = ${meta.copyright}` : "",
194
+ meta.title ? `title = ${meta.title}` : "",
195
+ "params.env_bookshop_live = true"
196
+ ].join('\n')
197
+ }));
198
+ const err = window.initHugoConfig();
199
+ if (err) {
200
+ console.error(err);
201
+ }
202
+
203
+ // window.loadHugoBookshopMeta(JSON.stringify(meta));
204
+ }
205
+
206
+ async storeInfo(info = {}) {
207
+ while (!window.writeHugoFiles) {
208
+ await sleep(100);
209
+ };
210
+
211
+ const data = info?.data;
212
+ if (!data || typeof data !== "object") return;
213
+ const files = {};
214
+ for (const [file, contents] of Object.entries(data)) {
215
+ files[`data/${file}.json`] = JSON.stringify(contents);
216
+ }
217
+ window.writeHugoFiles(JSON.stringify(files));
218
+ }
219
+
220
+ /**
221
+ * Tries to parse an error message and patch up the components
222
+ * in a rudimentary way before a rebuild.
223
+ */
224
+ async componentQuack(error_string = "", log_messages = []) {
225
+ try {
226
+ const component_regex = /execute of template failed: template: ([^:]+):\d/ig;
227
+ let file_stack = [...error_string.matchAll(component_regex)].map(([, file]) => `layouts/${file}`);
228
+ if (file_stack.length) {
229
+ const deepest_errored_component = file_stack[file_stack.length - 1];
230
+ const error_chunks = error_string.split("execute of template failed:");
231
+ const error_msg = error_chunks[error_chunks.length - 1] ?? "template error";
232
+ window.writeHugoFiles(JSON.stringify({
233
+ [deepest_errored_component]: [
234
+ `<div style="padding: 10px; background-color: lightcoral; color: black; font-weight: bold;">`,
235
+ `Failed to render ${deepest_errored_component}. <br/>`,
236
+ `<pre style="margin-top: 10px; background-color: lightcoral; border: solid 1px black; white-space: pre-line;">`,
237
+ `<code style="font-family: monospace; color: black;">${error_msg.replace(/</, '&lt;')}</code></pre>`,
238
+ `</div>`
239
+ ].join('\n')
240
+ }));
241
+ return deepest_errored_component;
242
+ }
243
+
244
+ const error_logs = log_messages.filter(log => log.startsWith("ERROR")).join("\n");
245
+ const missing_regex = /Component "([^"]+)" does not exist/ig;
246
+ file_stack = [...error_logs.matchAll(missing_regex)].map(([, file]) => {
247
+ let filename = file.split('/').pop();
248
+ return `layouts/partials/bookshop/components/${file}/${filename}.hugo.html`;
249
+ });
250
+ if (file_stack.length) {
251
+ const deepest_errored_component = file_stack[file_stack.length - 1];
252
+ window.writeHugoFiles(JSON.stringify({
253
+ [deepest_errored_component]: [
254
+ `<div class="bookshop_error" style="padding: 10px; background-color: lightcoral; color: black; font-weight: bold;">`,
255
+ `Failed to find component ${deepest_errored_component}`,
256
+ `</div>`
257
+ ].join('\n')
258
+ }));
259
+ return deepest_errored_component;
260
+ }
261
+ } catch (e) {
262
+ console.error(`ComponentQuack failed to patch things up: ${e}`);
263
+ return null;
264
+ }
265
+ }
266
+
267
+ async render(target, name, props, globals, logger) {
268
+ while (!window.buildHugo) {
269
+ logger?.log?.(`Waiting for the Hugo WASM to be available...`);
270
+ await sleep(100);
271
+ };
272
+
273
+ let writeFiles = {};
274
+
275
+ if (this.hasComponent(name)) {
276
+ writeFiles["layouts/index.html"] = `{{ partial "bookshop" (slice "${name}" .Params.component) }}`
277
+ } else if (this.hasShared(name)) {
278
+ writeFiles["layouts/index.html"] = `{{ partial "bookshop_partial" (slice "${name}" .Params.component) }}`
279
+ } else {
280
+ console.warn(`[hugo-engine] No component found for ${name}`);
281
+ return "";
282
+ }
283
+ logger?.log?.(`Going to render ${name}, with layout:`);
284
+ logger?.log?.(writeFiles["layouts/index.html"]);
285
+
286
+ if (!globals || typeof globals !== "object") globals = {};
287
+ props = {
288
+ ...globals, ...props,
289
+ env_bookshop_live: true
290
+ };
291
+
292
+ // If we have assigned a root scope we need to pass that in as the context
293
+ if (props["."]) props = props["."];
294
+ writeFiles["content/_index.md"] = JSON.stringify({
295
+ component: props
296
+ }, null, 2) + "\n";
297
+ window.writeHugoFiles(JSON.stringify(writeFiles));
298
+
299
+ window.hugo_wasm_logging = [];
300
+ let render_attempts = 1;
301
+ let buildError = window.buildHugo();
302
+ while (buildError && render_attempts < 5) {
303
+ if (this.componentQuack(buildError, window.hugo_wasm_logging) === null) {
304
+ // Can't find a template to overwrite and re-render
305
+ break;
306
+ }
307
+ // Try render again with the problem template stubbed out
308
+ window.hugo_wasm_logging = [];
309
+ buildError = window.buildHugo();
310
+ render_attempts += 1;
311
+ }
312
+
313
+ if (buildError) {
314
+ console.error(buildError);
315
+ return;
316
+ }
317
+
318
+ const output = window.readHugoFiles(JSON.stringify([
319
+ "public/index.html"
320
+ ]));
321
+
322
+ target.innerHTML = output["public/index.html"];
323
+ return;
324
+ }
325
+
326
+ async eval(str, props = [{}], logger) {
327
+ if (!this.synthetic) {
328
+ while (!window.buildHugo) await sleep(10);
329
+ }
330
+ let props_obj = props.reduce((a, b) => { return { ...a, ...b } });
331
+ let full_props = props_obj;
332
+ str = str.trim();
333
+
334
+ // We can return early if we're evaluating a literal string or digit
335
+ if (/^".*"$|^`.*`$|^\d+(\.\d+)?$|^true$|^false$/.test(str)) {
336
+ logger?.log?.(`Unwrapping the string ${str}`);
337
+ try {
338
+ if (/^`.*`$/.test(str)) {
339
+ return JSON.parse(str.replace(/^`|`$/g, '"'));
340
+ }
341
+ return JSON.parse(str);
342
+ } catch (e) {
343
+ logger?.log?.(`Was not valid JSON for some reason. Moving on...`);
344
+ }
345
+ }
346
+
347
+ // We're capable of looking up a simple variable
348
+ // (and it's hard to pass to wasm since we store variables on the context)
349
+ if (/^\$/.test(str)) {
350
+ logger?.log?.(`Trying to short circuit to return a variable`);
351
+ if (props_obj[str]) return props_obj[str];
352
+ }
353
+
354
+ // If we have assigned a root scope we need to pass that in as the context
355
+ if (props_obj["."]) {
356
+ logger?.log?.(`Nesting the props object into its dot scope`);
357
+ props_obj = props_obj["."];
358
+ }
359
+
360
+ if (str === ".") {
361
+ logger?.log?.(`Short circuiting dot notation to return the scope`);
362
+ return props_obj;
363
+ }
364
+
365
+ let normalized = this.normalize(str);
366
+ if (typeof normalized === 'object') {
367
+ logger?.log?.(`Digging into the object ${JSON.stringify(normalized)}`);
368
+ const process = async (obj) => {
369
+ for (const [k, v] of Object.entries(obj)) {
370
+ if (typeof v === "string") {
371
+ logger?.log?.(`Evaluating the inner ${k}: ${v}`);
372
+ obj[k] = await this.eval(v, [props_obj], logger?.nested?.());
373
+ } else if (typeof v === "object") {
374
+ logger?.log?.(`Processing the inner object ${k}`);
375
+ process(v);
376
+ }
377
+ }
378
+ }
379
+ await process(normalized);
380
+ return normalized;
381
+ }
382
+ if (/^\w+(\.\w+)*$/.test(normalized)) {
383
+ logger?.log?.(`Trying to short circuit to return the dot notation ${normalized}`);
384
+ // We're capable of looking up a simple dot notation access
385
+ const result = dig(props_obj, normalized);
386
+ if (result !== undefined) return result
387
+ }
388
+
389
+ // Rewrite array.0 into index array 0
390
+ str = str.replace(/(.*)\.(\d+)$/, (_, obj, index) => {
391
+ return `index (${obj}) ${index}`;
392
+ })
393
+
394
+ const variable_pairs = mapHugoVariablesToParams(full_props, ".Params.full_props");
395
+ const variable_decl = Object.entries(variable_pairs).map(([k, v]) => {
396
+ return `{{ ${k} := ${v} }}`
397
+ }).join('');
398
+
399
+ const assignments = Object.entries(props_obj).filter(([key]) => key.startsWith('$')).map(([key, value]) => {
400
+ if (Array.isArray(value)) {
401
+ return `{{ ${key} := index ( \`{"a": ${JSON.stringify(value)}}\` | transform.Unmarshal ) "a" }}`
402
+ } else if (typeof value === 'object') {
403
+ return `{{ ${key} := \`${JSON.stringify(value)}\` | transform.Unmarshal }}`
404
+ } else {
405
+ return `{{ ${key} := ${JSON.stringify(value)} }}`
406
+ }
407
+ }).join('');
408
+ const eval_str = `${variable_decl}{{ with .Params.props }}${assignments}{{ jsonify (${str}) }}{{ end }}`;
409
+
410
+ if (this.synthetic) {
411
+ return null;
412
+ }
413
+
414
+ window.writeHugoFiles(JSON.stringify({
415
+ "layouts/index.html": eval_str,
416
+ "content/_index.md": JSON.stringify({ props: props_obj, full_props: full_props }, null, 2)
417
+ }));
418
+
419
+ const buildError = window.buildHugo();
420
+ if (buildError) {
421
+ console.warn(buildError);
422
+ return;
423
+ }
424
+
425
+ const output = window.readHugoFiles(JSON.stringify([
426
+ "public/index.html"
427
+ ]))["public/index.html"];
428
+
429
+ try {
430
+ return JSON.parse(output);
431
+ } catch (e) {
432
+ logger?.log?.(`Error evaluating \`${str}\` in the Hugo engine`);
433
+ logger?.log?.(output);
434
+ return null;
435
+ }
436
+ }
437
+
438
+ normalize(str) {
439
+ return (new IdentifierParser(str)).build();
440
+ }
441
+
442
+ loader() {
443
+ // esbuild loader if required
444
+ }
445
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bookshop/hugo-engine",
3
3
  "packageManager": "yarn@3.0.0",
4
- "version": "3.5.0-alpha.0",
4
+ "version": "3.5.1",
5
5
  "description": "Bookshop frontend Hugo renderer",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -14,11 +14,12 @@
14
14
  "*.js",
15
15
  "lib/*.js",
16
16
  "!**/*.test.js",
17
+ "!**/*__test__.js",
17
18
  "full-hugo-renderer/hugo_renderer.wasm.gz",
18
19
  "bookshop-hugo-templates/**/*"
19
20
  ],
20
21
  "scripts": {
21
- "test": "nyc ava -v"
22
+ "test": "node prep__test__.js && nyc ava -v"
22
23
  },
23
24
  "author": "@bglw",
24
25
  "license": "MIT",
@@ -30,7 +31,7 @@
30
31
  "nyc": "^15.1.0"
31
32
  },
32
33
  "dependencies": {
33
- "@bookshop/helpers": "3.5.0-alpha.0",
34
+ "@bookshop/helpers": "3.5.1",
34
35
  "esbuild": "^0.13.10",
35
36
  "fflate": "^0.7.3",
36
37
  "liquidjs": "9.28.0"
@@ -0,0 +1,5 @@
1
+ import fs from "fs";
2
+
3
+ let contents = fs.readFileSync("lib/engine.js", { encoding: "utf-8" });
4
+ contents = contents.replace("import compressedHugoWasm", "// import compressedHugoWasm");
5
+ fs.writeFileSync("lib/engine__test__.js", contents);
@@ -1 +0,0 @@
1
- {"parent":null,"pid":5562,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/cli.js","-v"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1683711571277,"ppid":5551,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/a08fb26c-673c-4f78-bf68-a2615bce7bad.json","externalId":"","uuid":"a08fb26c-673c-4f78-bf68-a2615bce7bad","files":[]}