@schalkneethling/miyagi-core 4.6.2 → 4.7.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.
@@ -24,8 +24,8 @@
24
24
  {% if assets.js %}
25
25
  <script src="{{ assets.js }}"></script>
26
26
  {% endif %}
27
- {% if mockDataResolved is defined %}
28
- <script type="application/json" id="miyagi-mock-data">{{ mockDataResolved|replace({'</script>': '<\\/script>'})|raw }}</script>
27
+ {% if dataJson is defined and dataJson %}
28
+ <script type="application/json" id="{{ dataJsonId }}">{{ dataJson|replace({'</script>': '<\\/script>'})|raw }}</script>
29
29
  {% endif %}
30
30
  <script>
31
31
  {{ theme.js }}
@@ -34,6 +34,12 @@
34
34
  <div id="json-tree-resolved-mocks" data-jsontree-js="jsontree.mocks"></div>
35
35
  </details>
36
36
  {% endif %}
37
+ {% if dataJson %}
38
+ <details class="Tabs-tab">
39
+ <summary>Data</summary>
40
+ <div id="json-tree-resolved-data" data-jsontree-js="jsontree.data"></div>
41
+ </details>
42
+ {% endif %}
37
43
  {% if template %}
38
44
  <details class="Tabs-tab">
39
45
  <summary>Template</summary>
@@ -135,6 +141,9 @@
135
141
  },
136
142
  "mocks": {
137
143
  "data": {{ mocks|default({})|replace({'</script>': '<\\/script>'})|raw }}
144
+ },
145
+ "data": {
146
+ "data": {{ dataJson|default({})|replace({'</script>': '<\\/script>'})|raw }}
138
147
  }
139
148
  };
140
149
  </script>
@@ -665,6 +665,7 @@ export default () => {
665
665
  component,
666
666
  componentData: data.resolved,
667
667
  componentDeclaredAssets: data.$assets || null,
668
+ variation,
668
669
  cb: async (err, response) => {
669
670
  if (err) {
670
671
  if (typeof err === "string") {
@@ -0,0 +1,69 @@
1
+ import { getVariationData, getComponentData } from "../mocks/index.js";
2
+ import log from "../logger.js";
3
+
4
+ /**
5
+ * Resolves the data.json content for a component.
6
+ *
7
+ * If no data.json exists, returns null values (no JSON exposed in DOM).
8
+ * If data.json has useMocks: true, uses the mocks pipeline as the data source.
9
+ * Otherwise, uses the data.json content directly (stripping the useMocks key).
10
+ * @param {object} component - the component route object
11
+ * @param {object} options
12
+ * @param {string} [options.variation] - the variation name (used when useMocks is true)
13
+ * @returns {Promise<{ json: string|null, id: string }>}
14
+ */
15
+ export async function resolveDataJson(component, { variation } = {}) {
16
+ const dataJsonPath = component.paths.data.full;
17
+ const dataJsonContent = global.state.fileContents[dataJsonPath];
18
+
19
+ if (!dataJsonContent) {
20
+ return { json: null, id: "miyagi-mock-data" };
21
+ }
22
+
23
+ if (dataJsonContent.useMocks === true) {
24
+ return resolveFromMocks(component, variation);
25
+ }
26
+
27
+ return resolveFromDataJson(dataJsonContent);
28
+ }
29
+
30
+ /**
31
+ * Uses the mocks pipeline to produce the JSON content.
32
+ * @param {object} component
33
+ * @param {string} [variation]
34
+ * @returns {Promise<{ json: string|null, id: string }>}
35
+ */
36
+ async function resolveFromMocks(component, variation) {
37
+ try {
38
+ let data;
39
+
40
+ if (variation) {
41
+ data = await getVariationData(component, variation);
42
+ } else {
43
+ const allData = await getComponentData(component);
44
+ data = allData?.[0] ?? null;
45
+ }
46
+
47
+ const resolved = data?.resolved ?? {};
48
+ return { json: JSON.stringify(resolved), id: "miyagi-mock-data" };
49
+ } catch (err) {
50
+ log(
51
+ "error",
52
+ `Error resolving mock data for data.json in ${component.paths.dir.short}`,
53
+ err,
54
+ );
55
+ return { json: null, id: "miyagi-mock-data" };
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Uses the data.json content directly, stripping internal properties.
61
+ * @param {object} dataJsonContent - parsed data.json object
62
+ * @returns {{ json: string, id: string }}
63
+ */
64
+ function resolveFromDataJson(dataJsonContent) {
65
+ const clone = structuredClone(dataJsonContent);
66
+ delete clone.useMocks;
67
+
68
+ return { json: JSON.stringify(clone), id: "miyagi-mock-data" };
69
+ }
package/lib/helpers.js CHANGED
@@ -1,4 +1,3 @@
1
- import v8 from "v8";
2
1
  import path from "path";
3
2
 
4
3
  /**
@@ -69,7 +68,7 @@ export const getResolvedFileName = function (nameInConfig, fileName) {
69
68
  * @returns {object} clone of rhe given object
70
69
  */
71
70
  export const cloneDeep = function (obj) {
72
- return v8.deserialize(v8.serialize(obj));
71
+ return structuredClone(obj);
73
72
  };
74
73
 
75
74
  /**
@@ -133,6 +132,15 @@ export const fileIsSchemaFile = function (filePath) {
133
132
  );
134
133
  };
135
134
 
135
+ /**
136
+ * Accepts a file path and checks if it is a data.json file
137
+ * @param {string} filePath - path to any type of file
138
+ * @returns {boolean} is true if the given file is a data.json file
139
+ */
140
+ export const fileIsDataJsonFile = function (filePath) {
141
+ return path.basename(filePath) === "data.json";
142
+ };
143
+
136
144
  /**
137
145
  * Accepts a file path and checks if it is component js or css file
138
146
  * @param {string} filePath - path to any type of file
@@ -279,6 +279,7 @@ export default function Router() {
279
279
  componentDeclaredAssets: data?.$assets || null,
280
280
  cookies: req.cookies,
281
281
  overrides: overrides ?? null,
282
+ variation,
282
283
  });
283
284
  }
284
285
 
@@ -298,6 +298,7 @@ async function updateFileContents(events) {
298
298
  fs.lstatSync(fullPath).isFile() &&
299
299
  (helpers.fileIsTemplateFile(relativePath) ||
300
300
  helpers.fileIsDataFile(relativePath) ||
301
+ helpers.fileIsDataJsonFile(relativePath) ||
301
302
  helpers.fileIsDocumentationFile(relativePath) ||
302
303
  helpers.fileIsSchemaFile(relativePath))
303
304
  ) {
@@ -357,6 +358,9 @@ async function handleFileChange(events) {
357
358
  const schemaEvents = events.filter(({ relativePath }) =>
358
359
  helpers.fileIsSchemaFile(relativePath),
359
360
  );
361
+ const dataJsonEvents = events.filter(({ relativePath }) =>
362
+ helpers.fileIsDataJsonFile(relativePath),
363
+ );
360
364
  const componentAssetEvents = events.filter(({ relativePath }) =>
361
365
  helpers.fileIsAssetFile(relativePath),
362
366
  );
@@ -532,6 +536,19 @@ async function handleFileChange(events) {
532
536
  return;
533
537
  }
534
538
 
539
+ if (dataJsonEvents.length > 0) {
540
+ await setState({
541
+ fileContents: await updateFileContents(dataJsonEvents),
542
+ });
543
+
544
+ sendReload(watchRules.data, {
545
+ reason: "data-json",
546
+ paths: dataJsonEvents.map(({ relativePath }) => relativePath),
547
+ });
548
+ log("success", `${t("updatingDone")}\n`);
549
+ return;
550
+ }
551
+
535
552
  if (componentAssetEvents.length > 0) {
536
553
  sendReload(watchRules.componentAsset, {
537
554
  reason: "component-asset",
@@ -32,6 +32,8 @@ export default async function renderIframeComponent({
32
32
  global.state.fileContents[path.join(component.paths.dir.full, "README.md")];
33
33
  const componentSchema =
34
34
  global.state.fileContents[component.paths.schema.full];
35
+ const componentDataJson =
36
+ global.state.fileContents[component.paths.data.full] ?? null;
35
37
  const componentTemplate = hasTemplate
36
38
  ? global.state.fileContents[component.paths.tpl.full]
37
39
  : null;
@@ -103,6 +105,16 @@ export default async function renderIframeComponent({
103
105
  ),
104
106
  }
105
107
  : null,
108
+ data: componentDataJson
109
+ ? {
110
+ string: JSON.stringify(componentDataJson, null, 2),
111
+ type: "json",
112
+ file: path.join(
113
+ global.config.components.folder,
114
+ component.paths.data.short,
115
+ ),
116
+ }
117
+ : null,
106
118
  };
107
119
 
108
120
  await renderVariations({
@@ -205,13 +217,24 @@ async function renderVariations({
205
217
  }
206
218
  }
207
219
 
220
+ let dataJson;
221
+
222
+ if (fileContents?.data?.string) {
223
+ try {
224
+ dataJson = JSON.stringify(JSON.parse(fileContents.data.string));
225
+ } catch (err) {
226
+ log("error", null, err);
227
+ }
228
+ }
229
+
208
230
  return Promise.all(promises)
209
231
  .then(async () => {
210
232
  const themeMode = getThemeMode(cookies);
211
233
  const renderFileTabs = !!(
212
234
  fileContents.schema ||
213
235
  fileContents.mocks ||
214
- fileContents.template
236
+ fileContents.template ||
237
+ fileContents.data
215
238
  );
216
239
  const componentsEntry = global.state.components.find(
217
240
  ({ shortPath }) => shortPath === component.paths.dir.short,
@@ -259,6 +282,7 @@ async function renderVariations({
259
282
  ? validatedMocks[0].data.map((error) => error.message).join("\n")
260
283
  : null,
261
284
  mocks: mocksJson,
285
+ dataJson,
262
286
  template: fileContents.template,
263
287
  renderInformation: renderFileTabs || variations.length > 0,
264
288
  renderFileTabs,
@@ -1,5 +1,6 @@
1
1
  import path from "path";
2
2
  import config from "../../../default-config.js";
3
+ import { resolveDataJson } from "../../../data-json/index.js";
3
4
  import { getUserUiConfig } from "../../helpers.js";
4
5
  import resolveAssets from "../../helpers/resolve-assets.js";
5
6
  import applyOverrides from "../../helpers/apply-overrides.js";
@@ -13,6 +14,7 @@ import applyOverrides from "../../helpers/apply-overrides.js";
13
14
  * @param {Function} [object.cb] - callback function
14
15
  * @param {object} [object.cookies]
15
16
  * @param {object} [object.overrides] - query-param override values
17
+ * @param {string} [object.variation] - the variation name
16
18
  * @returns {Promise} gets resolved when the variation has been rendered
17
19
  */
18
20
  export default async function renderIframeVariationStandalone({
@@ -23,6 +25,7 @@ export default async function renderIframeVariationStandalone({
23
25
  cb,
24
26
  cookies,
25
27
  overrides,
28
+ variation,
26
29
  }) {
27
30
  if (overrides) {
28
31
  const schema =
@@ -31,6 +34,9 @@ export default async function renderIframeVariationStandalone({
31
34
  }
32
35
 
33
36
  const directoryPath = component.paths.dir.short;
37
+ const { json: dataJson, id: dataJsonId } = await resolveDataJson(component, {
38
+ variation,
39
+ });
34
40
 
35
41
  return new Promise((resolve, reject) => {
36
42
  global.app.render(
@@ -58,7 +64,8 @@ export default async function renderIframeVariationStandalone({
58
64
  "component_variation.twig.miyagi",
59
65
  {
60
66
  html: result,
61
- mockDataResolved: JSON.stringify(componentData ?? {}),
67
+ dataJson,
68
+ dataJsonId,
62
69
  cssFiles,
63
70
  jsFilesHead,
64
71
  jsFilesBody,
@@ -116,6 +116,10 @@ function addToRoutes({ name, shortPath, fullPath }, partials = []) {
116
116
  full: path.join(fullPath, "README.md"),
117
117
  short: path.join(shortPath, "README.md"),
118
118
  },
119
+ data: {
120
+ full: path.join(fullPath, "data.json"),
121
+ short: path.join(shortPath, "data.json"),
122
+ },
119
123
  },
120
124
  type: "components",
121
125
  };
@@ -69,6 +69,7 @@ async function getFilePaths(sourceTree) {
69
69
  `${files.mocks.name}.${files.mocks.extension[0]}`,
70
70
  `${files.mocks.name}.${files.mocks.extension[1]}`,
71
71
  `${files.schema.name}.${files.schema.extension}`,
72
+ "data.json",
72
73
  ]) ||
73
74
  helpers.fileIsDocumentationFile(entry.path)
74
75
  ) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schalkneethling/miyagi-core",
3
- "version": "4.6.2",
3
+ "version": "4.7.0",
4
4
  "description": "miyagi is a component development tool for JavaScript template engines.",
5
5
  "main": "index.js",
6
6
  "author": "Schalk Neethling <schalkneethling@duck.com>, Michael Großklaus <mail@mgrossklaus.de> (https://www.mgrossklaus.de)",