@schalkneethling/miyagi-core 4.0.2

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 (138) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +43 -0
  3. package/api/app.js +39 -0
  4. package/api/index.js +236 -0
  5. package/bin/miyagi.js +2 -0
  6. package/dist/css/iframe.css +31 -0
  7. package/dist/css/main.css +1 -0
  8. package/dist/js/_iframe-links-DdifIr4P.js +1 -0
  9. package/dist/js/_mock-data-Dypo4Bl_.js +1 -0
  10. package/dist/js/_prism-By3NMwUd.js +1 -0
  11. package/dist/js/iframe.build.js +1 -0
  12. package/dist/js/iframe.js +1 -0
  13. package/dist/js/index-BKDKaBC6.js +1 -0
  14. package/dist/js/jsontree.js +1 -0
  15. package/dist/js/main.build.js +1 -0
  16. package/dist/js/main.js +1 -0
  17. package/frontend/assets/css/iframe/accordion-tabs.css +77 -0
  18. package/frontend/assets/css/iframe/jsontree.js.css +325 -0
  19. package/frontend/assets/css/iframe/prism.css +132 -0
  20. package/frontend/assets/css/iframe/styleguide/colors.css +61 -0
  21. package/frontend/assets/css/iframe/styleguide/fonts.css +37 -0
  22. package/frontend/assets/css/iframe/styleguide/index.css +109 -0
  23. package/frontend/assets/css/iframe/styleguide/spacings.css +21 -0
  24. package/frontend/assets/css/iframe.css +410 -0
  25. package/frontend/assets/css/main/menu/config-switcher.css +49 -0
  26. package/frontend/assets/css/main/menu/config-switchers.css +67 -0
  27. package/frontend/assets/css/main/menu/goto.css +24 -0
  28. package/frontend/assets/css/main/menu/nav.css +113 -0
  29. package/frontend/assets/css/main/menu/search.css +64 -0
  30. package/frontend/assets/css/main/menu/title.css +40 -0
  31. package/frontend/assets/css/main/menu.css +114 -0
  32. package/frontend/assets/css/main/reset.css +217 -0
  33. package/frontend/assets/css/main.css +71 -0
  34. package/frontend/assets/css/shared.css +34 -0
  35. package/frontend/assets/css/tokens.css +112 -0
  36. package/frontend/assets/favicon.ico +0 -0
  37. package/frontend/assets/js/_accordion-tabs.js +403 -0
  38. package/frontend/assets/js/_goto.js +63 -0
  39. package/frontend/assets/js/_iframe-links.js +19 -0
  40. package/frontend/assets/js/_is-triggered.js +15 -0
  41. package/frontend/assets/js/_main.js +379 -0
  42. package/frontend/assets/js/_mock-data.js +13 -0
  43. package/frontend/assets/js/_prism.js +1098 -0
  44. package/frontend/assets/js/_search.js +190 -0
  45. package/frontend/assets/js/_socket.js +9 -0
  46. package/frontend/assets/js/config-switcher/development-mode.js +49 -0
  47. package/frontend/assets/js/config-switcher/index.js +63 -0
  48. package/frontend/assets/js/config-switcher/text-direction.js +30 -0
  49. package/frontend/assets/js/config-switcher/theme.js +87 -0
  50. package/frontend/assets/js/iframe.build.js +43 -0
  51. package/frontend/assets/js/iframe.js +52 -0
  52. package/frontend/assets/js/jsontree.js +979 -0
  53. package/frontend/assets/js/main.build.js +40 -0
  54. package/frontend/assets/js/main.js +42 -0
  55. package/frontend/assets/js/styleguide/color-converter.js +741 -0
  56. package/frontend/assets/js/styleguide/index.js +119 -0
  57. package/frontend/views/component_variation.twig.miyagi +57 -0
  58. package/frontend/views/design-tokens/colors.twig.miyagi +43 -0
  59. package/frontend/views/design-tokens/sizes.twig.miyagi +35 -0
  60. package/frontend/views/design-tokens/typography.twig.miyagi +38 -0
  61. package/frontend/views/iframe_component.twig.miyagi +141 -0
  62. package/frontend/views/iframe_component_variation.twig.miyagi +55 -0
  63. package/frontend/views/iframe_index.twig.miyagi +14 -0
  64. package/frontend/views/layouts/iframe_default.twig.miyagi +22 -0
  65. package/frontend/views/main.twig.miyagi +24 -0
  66. package/frontend/views/menu/config-switchers.twig.miyagi +83 -0
  67. package/frontend/views/menu/goto.twig.miyagi +9 -0
  68. package/frontend/views/menu/menu.twig.miyagi +21 -0
  69. package/frontend/views/menu/nav.twig.miyagi +95 -0
  70. package/frontend/views/menu/search.twig.miyagi +13 -0
  71. package/frontend/views/menu/title.twig.miyagi +24 -0
  72. package/index.js +3 -0
  73. package/lib/build/index.js +1020 -0
  74. package/lib/cli/app.js +38 -0
  75. package/lib/cli/component.js +56 -0
  76. package/lib/cli/index.js +5 -0
  77. package/lib/cli/lint.js +180 -0
  78. package/lib/config.js +74 -0
  79. package/lib/default-config.js +105 -0
  80. package/lib/generator/component.js +199 -0
  81. package/lib/generator/mocks.js +201 -0
  82. package/lib/helpers.js +184 -0
  83. package/lib/i18n/en.js +91 -0
  84. package/lib/i18n/index.js +17 -0
  85. package/lib/index.js +166 -0
  86. package/lib/init/args.js +55 -0
  87. package/lib/init/config.js +330 -0
  88. package/lib/init/engines.js +65 -0
  89. package/lib/init/index.js +102 -0
  90. package/lib/init/rendering.js +12 -0
  91. package/lib/init/router.js +249 -0
  92. package/lib/init/static.js +133 -0
  93. package/lib/init/twing/cache.js +34 -0
  94. package/lib/init/twing/functions.js +51 -0
  95. package/lib/init/views.js +19 -0
  96. package/lib/init/watcher.js +402 -0
  97. package/lib/logger.js +94 -0
  98. package/lib/mocks/get.js +111 -0
  99. package/lib/mocks/index.js +9 -0
  100. package/lib/mocks/resolve/ref.js +484 -0
  101. package/lib/mocks/resolve/tpl.js +246 -0
  102. package/lib/mocks/resolve.js +205 -0
  103. package/lib/render/helpers.js +51 -0
  104. package/lib/render/index.js +38 -0
  105. package/lib/render/views/iframe/component.docs.js +77 -0
  106. package/lib/render/views/iframe/component.js +338 -0
  107. package/lib/render/views/iframe/design-tokens/colors.js +52 -0
  108. package/lib/render/views/iframe/design-tokens/index.js +9 -0
  109. package/lib/render/views/iframe/design-tokens/sizes.js +49 -0
  110. package/lib/render/views/iframe/design-tokens/typography.js +52 -0
  111. package/lib/render/views/iframe/docs.js +68 -0
  112. package/lib/render/views/iframe/index.js +44 -0
  113. package/lib/render/views/iframe/variation.js +116 -0
  114. package/lib/render/views/iframe/variation.standalone.js +89 -0
  115. package/lib/render/views/main/component.docs.js +53 -0
  116. package/lib/render/views/main/component.js +74 -0
  117. package/lib/render/views/main/design-tokens.js +53 -0
  118. package/lib/render/views/main/docs.js +47 -0
  119. package/lib/render/views/main/index.js +46 -0
  120. package/lib/state/components.js +132 -0
  121. package/lib/state/css.js +50 -0
  122. package/lib/state/docs.js +111 -0
  123. package/lib/state/file-contents.js +207 -0
  124. package/lib/state/helpers.js +86 -0
  125. package/lib/state/index.js +56 -0
  126. package/lib/state/menu/index.js +275 -0
  127. package/lib/state/menu/structure.js +146 -0
  128. package/lib/state/partials.js +23 -0
  129. package/lib/state/source-tree.js +75 -0
  130. package/lib/styleguide/color-names.js +150 -0
  131. package/lib/styleguide/colors.js +135 -0
  132. package/lib/styleguide/helpers.js +37 -0
  133. package/lib/styleguide/index.js +17 -0
  134. package/lib/styleguide/media-queries.js +26 -0
  135. package/lib/styleguide/spacings.js +35 -0
  136. package/lib/styleguide/typography.js +61 -0
  137. package/lib/validator/mocks.js +105 -0
  138. package/package.json +117 -0
@@ -0,0 +1,205 @@
1
+ import deepMerge from "deepmerge";
2
+ import * as helpers from "../helpers.js";
3
+ import { resolveRefs } from "./resolve/ref.js";
4
+ import { resolveTpls } from "./resolve/tpl.js";
5
+ import { extendTemplateData } from "../render/helpers.js";
6
+
7
+ /**
8
+ * @param {string} method
9
+ * @returns {Function}
10
+ */
11
+ function getMergeMethod(method) {
12
+ const methods = {
13
+ combine: (target, source, options) => {
14
+ const destination = target.slice();
15
+
16
+ source.forEach((item, index) => {
17
+ if (options.isMergeableObject(item)) {
18
+ if (typeof destination[index] === "undefined") {
19
+ destination[index] = options.cloneUnlessOtherwiseSpecified(
20
+ item,
21
+ options,
22
+ );
23
+ } else {
24
+ destination[index] = deepMerge(target[index], item, options);
25
+ }
26
+ } else {
27
+ destination[index] = options.cloneUnlessOtherwiseSpecified(
28
+ item,
29
+ options,
30
+ );
31
+ }
32
+ });
33
+
34
+ return destination;
35
+ },
36
+
37
+ overwrite: (destinationArray, sourceArray) => sourceArray,
38
+ };
39
+
40
+ return methods[method];
41
+ }
42
+
43
+ /**
44
+ * @param {object} data - the mock data object that will be passed into the component
45
+ * @param {object} component
46
+ * @param {object} [rootData] - the root mock data object
47
+ * @returns {Promise<{ merged, resolved, messages }>} the resolved data object
48
+ */
49
+ export const resolveData = async function (data, component, rootData) {
50
+ const mergedWithGlobalData = mergeWithGlobalData(
51
+ rootData ? mergeRootDataWithVariationData(rootData, data) : data,
52
+ );
53
+ const { data: refsResolved, messages: refMessages } = await resolveRefs(
54
+ { ...mergedWithGlobalData },
55
+ component,
56
+ );
57
+ const extended = await extendTemplateData(
58
+ global.config,
59
+ refsResolved,
60
+ component,
61
+ );
62
+ const { data: tplsResolved, messages: tplMessages } =
63
+ await resolveTpls(extended);
64
+ const resolved = overwriteRenderKey(tplsResolved);
65
+
66
+ return {
67
+ merged: mergedWithGlobalData,
68
+ resolved,
69
+ messages: [...refMessages, ...tplMessages],
70
+ };
71
+ };
72
+
73
+ /**
74
+ * @param {object} data - the mock data object that will be passed into the component
75
+ * @returns {object} the resolved data object
76
+ */
77
+ export const overwriteRenderKey = function (data) {
78
+ let o;
79
+
80
+ if (
81
+ ["string", "number", "boolean"].includes(typeof data) ||
82
+ data instanceof Map
83
+ ) {
84
+ return data;
85
+ }
86
+
87
+ if (Array.isArray(data)) {
88
+ for (let item of data) {
89
+ item = overwriteRenderKey(item);
90
+ }
91
+
92
+ return data;
93
+ }
94
+
95
+ if (data) {
96
+ o = { ...data };
97
+ const entries = Object.entries(o);
98
+
99
+ for (const [key, val] of entries) {
100
+ if (key === "$render") {
101
+ let str = "";
102
+
103
+ if (val) {
104
+ for (const html of val) {
105
+ str += html;
106
+ }
107
+ }
108
+
109
+ o = str;
110
+ } else {
111
+ if (
112
+ typeof val == "string" ||
113
+ typeof val === "number" ||
114
+ typeof val === "boolean" ||
115
+ val instanceof Map ||
116
+ val === null
117
+ ) {
118
+ o[key] = val;
119
+ } else if (Array.isArray(val)) {
120
+ val.forEach((v, i) => {
121
+ if (
122
+ typeof v == "string" ||
123
+ typeof v === "number" ||
124
+ typeof v === "boolean" ||
125
+ v instanceof Map ||
126
+ v === null
127
+ ) {
128
+ o[key][i] = v;
129
+ } else {
130
+ o[key][i] = overwriteRenderKey(v);
131
+ }
132
+ });
133
+ } else {
134
+ o[key] = overwriteRenderKey(val);
135
+ }
136
+ }
137
+ }
138
+ }
139
+
140
+ return o;
141
+ };
142
+
143
+ /**
144
+ * @param {object} rootData - the root mock data of a component
145
+ * @param {object} variationData - a variation mock data of a component
146
+ * @returns {object} the merged data
147
+ */
148
+ export const mergeRootDataWithVariationData = function (
149
+ rootData,
150
+ variationData,
151
+ ) {
152
+ if (!rootData) {
153
+ return variationData;
154
+ }
155
+
156
+ if (!variationData) {
157
+ return rootData;
158
+ }
159
+
160
+ const merged = deepMerge(rootData, variationData, {
161
+ customMerge: (key) => {
162
+ const options = variationData.$opts || rootData.$opts;
163
+
164
+ if (options) {
165
+ const option = options[key];
166
+
167
+ if (option) {
168
+ return getMergeMethod(option);
169
+ }
170
+ }
171
+
172
+ return undefined;
173
+ },
174
+ });
175
+
176
+ if (merged.$opts) {
177
+ delete merged.$opts;
178
+ }
179
+
180
+ return merged;
181
+ };
182
+
183
+ /**
184
+ * @param {object} data - the mock data object that will be passed into the component
185
+ * @returns {object} the merged data object
186
+ */
187
+ function mergeWithGlobalData(data) {
188
+ const defaultFile = helpers.getFullPathFromShortPath(
189
+ `${global.config.files.mocks.name}.${global.config.files.mocks.extension[0]}`,
190
+ );
191
+ const jsFile = helpers.getFullPathFromShortPath(
192
+ `${global.config.files.mocks.name}.${global.config.files.mocks.extension[1]}`,
193
+ );
194
+ const globalData = {
195
+ ...(global.state.fileContents[defaultFile] ||
196
+ global.state.fileContents[jsFile]),
197
+ };
198
+
199
+ delete globalData.$defs;
200
+
201
+ return {
202
+ ...globalData,
203
+ ...data,
204
+ };
205
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Helper functions for the render module
3
+ * @module renderHelpers
4
+ */
5
+
6
+ import path from "path";
7
+
8
+ /**
9
+ * @param {object} config - the user configuration object
10
+ * @param {object} data - the mock data object that will be passed into the component
11
+ * @param {object} component
12
+ * @returns {Promise<object>} the extended data object
13
+ */
14
+ export const extendTemplateData = async (config, data, component) => {
15
+ for (const extension of config.extensions) {
16
+ if (extension) {
17
+ const ext = Array.isArray(extension) ? extension[0] : extension;
18
+
19
+ if (ext.extendTemplateData) {
20
+ data = await ext.extendTemplateData(
21
+ path.join(config.components.folder, component.paths.tpl.short),
22
+ {},
23
+ data,
24
+ );
25
+ }
26
+ }
27
+ }
28
+
29
+ return data;
30
+ };
31
+
32
+ export const getUserUiConfig = (cookies = {}) => {
33
+ const projectName = global.config.projectName.replaceAll(" ", "-");
34
+ const mode = cookies[`miyagi_${projectName}_mode`];
35
+ const theme = cookies[`miyagi_${projectName}_theme`];
36
+ const componentTextDirection =
37
+ cookies[`miyagi_${projectName}_text_direction`];
38
+
39
+ return {
40
+ mode: global.config.isBuild ? "presentation" : mode || "dev",
41
+ theme: theme || global.config.ui.mode,
42
+ componentTextDirection:
43
+ componentTextDirection || global.config.components.textDirection,
44
+ };
45
+ };
46
+
47
+ export const getThemeMode = (cookies = {}) => {
48
+ return cookies[
49
+ `miyagi_${global.config.projectName.replaceAll(" ", "-")}_theme`
50
+ ];
51
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Rendering module
3
+ * @module render
4
+ */
5
+
6
+ import renderIframeComponent from "./views/iframe/component.js";
7
+ import renderIframeComponentDocs from "./views/iframe/component.docs.js";
8
+ import renderIframeDocs from "./views/iframe/docs.js";
9
+ import renderIframeIndex from "./views/iframe/index.js";
10
+ import renderIframeVariation from "./views/iframe/variation.js";
11
+ import renderIframeVariationStandalone from "./views/iframe/variation.standalone.js";
12
+ import renderMainComponent from "./views/main/component.js";
13
+ import renderMainComponentDocs from "./views/main/component.docs.js";
14
+ import renderMainDocs from "./views/main/docs.js";
15
+ import renderMainIndex from "./views/main/index.js";
16
+ import iframeDesignTokens from "./views/iframe/design-tokens/index.js";
17
+ import renderMainDesignTokens from "./views/main/design-tokens.js";
18
+
19
+ export default {
20
+ renderMainIndex,
21
+ renderMainComponent,
22
+ renderMainComponentDocs,
23
+ renderMainDocs,
24
+ renderIframeVariation,
25
+ renderIframeVariationStandalone,
26
+ renderIframeComponent,
27
+ renderIframeComponentDocs,
28
+ renderIframeDocs,
29
+ renderIframeIndex,
30
+ renderMainDesignTokens,
31
+ iframe: {
32
+ designTokens: {
33
+ colors: iframeDesignTokens.colors,
34
+ sizes: iframeDesignTokens.sizes,
35
+ typography: iframeDesignTokens.typography,
36
+ },
37
+ },
38
+ };
@@ -0,0 +1,77 @@
1
+ import path from "path";
2
+ import config from "../../../default-config.js";
3
+ import { getUserUiConfig, getThemeMode } from "../../helpers.js";
4
+
5
+ /**
6
+ * @param {object} object - parameter object
7
+ * @param {object} object.res - the express response object
8
+ * @param {object} object.component
9
+ * @param {Function} [object.cb] - callback function
10
+ * @param {object} [object.cookies]
11
+ */
12
+ export default async function renderIframeComponentDocs({
13
+ res,
14
+ component,
15
+ cb,
16
+ cookies,
17
+ }) {
18
+ const componentDocumentation =
19
+ global.state.fileContents[component.paths.docs.full];
20
+ const componentName = getHeadlineFromFileName(
21
+ path.basename(
22
+ component.paths.docs.full,
23
+ path.extname(component.paths.docs.full),
24
+ ),
25
+ );
26
+ const themeMode = getThemeMode(cookies);
27
+
28
+ await res.render(
29
+ "iframe_component.twig.miyagi",
30
+ {
31
+ lang: global.config.ui.lang,
32
+ miyagiDev: !!process.env.MIYAGI_DEVELOPMENT,
33
+ prod: process.env.NODE_ENV === "production",
34
+ projectName: config.projectName,
35
+ userProjectName: global.config.projectName,
36
+ isBuild: global.config.isBuild,
37
+ userUiConfig: getUserUiConfig(cookies),
38
+ theme: themeMode
39
+ ? Object.assign(global.config.ui.theme, { mode: themeMode })
40
+ : global.config.ui.theme,
41
+ documentation: componentDocumentation,
42
+ name: componentDocumentation?.includes("<h1") ? null : componentName,
43
+ uiTextDirection: global.config.ui.textDirection,
44
+ },
45
+ (html) => {
46
+ if (res.send) {
47
+ res.send(html);
48
+ }
49
+
50
+ if (cb) {
51
+ cb(null, html);
52
+ }
53
+ },
54
+ );
55
+ }
56
+
57
+ /**
58
+ * @param {string} file
59
+ * @returns {string}
60
+ */
61
+ function getHeadlineFromFileName(file) {
62
+ if (typeof file !== "string") return "";
63
+
64
+ let fileName = file;
65
+
66
+ if (fileName.startsWith("/")) {
67
+ fileName = fileName.slice(1);
68
+ }
69
+
70
+ if (file.endsWith("README.md") || file.endsWith("index.md")) {
71
+ fileName = path.dirname(fileName);
72
+ } else {
73
+ fileName = path.basename(fileName, ".md");
74
+ }
75
+
76
+ return fileName.replaceAll("-", " ");
77
+ }
@@ -0,0 +1,338 @@
1
+ import path from "path";
2
+ import jsonToYaml from "js-yaml";
3
+ import config from "../../../default-config.js";
4
+ import { t } from "../../../i18n/index.js";
5
+ import * as helpers from "../../../helpers.js";
6
+ import validateMocks from "../../../validator/mocks.js";
7
+ import { getComponentData } from "../../../mocks/index.js";
8
+ import { getUserUiConfig, getThemeMode } from "../../helpers.js";
9
+ import log from "../../../logger.js";
10
+
11
+ /**
12
+ * @param {object} object - parameter object
13
+ * @param {object} object.res - the express response object
14
+ * @param {object} object.component
15
+ * @param {Function} [object.cb] - callback function
16
+ * @param {object} [object.cookies]
17
+ * @param {boolean} [object.noCli]
18
+ */
19
+ export default async function renderIframeComponent({
20
+ res,
21
+ component,
22
+ cb,
23
+ cookies,
24
+ noCli,
25
+ }) {
26
+ const hasTemplate =
27
+ component.paths.tpl &&
28
+ Object.values(global.state.partials).includes(component.paths.tpl.full);
29
+ const componentJson = (await getComponentData(component)) || [];
30
+ const componentDocumentation =
31
+ global.state.fileContents[path.join(component.paths.dir.full, "README.md")];
32
+ const componentSchema =
33
+ global.state.fileContents[component.paths.schema.full];
34
+ const componentTemplate = hasTemplate
35
+ ? global.state.fileContents[component.paths.tpl.full]
36
+ : null;
37
+
38
+ let mockFilePath;
39
+
40
+ const defaultMockDataPath = component.paths.mocks.full(
41
+ global.config.files.mocks.extension[0],
42
+ );
43
+ const jsMockDataPath = component.paths.mocks.full(
44
+ global.config.files.mocks.extension[1],
45
+ );
46
+ const jsMockData = global.state.fileContents[jsMockDataPath];
47
+
48
+ if (jsMockData) {
49
+ mockFilePath = jsMockDataPath;
50
+ } else {
51
+ mockFilePath = defaultMockDataPath;
52
+ }
53
+
54
+ const componentMocks = global.state.fileContents[mockFilePath];
55
+
56
+ let componentSchemaString;
57
+ if (componentSchema) {
58
+ if (["yaml", "yml"].includes(global.config.files.schema.extension)) {
59
+ componentSchemaString = jsonToYaml.dump(componentSchema);
60
+ } else {
61
+ componentSchemaString = JSON.stringify(componentSchema, null, 2);
62
+ }
63
+ }
64
+
65
+ let componentMocksString;
66
+ if (componentMocks) {
67
+ if (["yaml", "yml"].includes(global.config.files.mocks.extension[0])) {
68
+ componentMocksString = jsonToYaml.dump(componentMocks);
69
+ } else {
70
+ componentMocksString = JSON.stringify(componentMocks, null, 2);
71
+ }
72
+ }
73
+
74
+ const fileContents = {
75
+ schema: componentSchema
76
+ ? {
77
+ string: componentSchemaString,
78
+ type: global.config.files.schema.extension,
79
+ file: path.join(
80
+ global.config.components.folder,
81
+ component.paths.schema.short,
82
+ ),
83
+ }
84
+ : null,
85
+ mocks: componentMocks
86
+ ? {
87
+ string: componentMocksString,
88
+ type: global.config.files.mocks.extension[0],
89
+ file: path.join(
90
+ global.config.components.folder,
91
+ helpers.getShortPathFromFullPath(mockFilePath),
92
+ ),
93
+ }
94
+ : null,
95
+ template: componentTemplate
96
+ ? {
97
+ string: componentTemplate,
98
+ type: global.config.files.templates.extension,
99
+ file: path.join(
100
+ global.config.components.folder,
101
+ helpers.getShortPathFromFullPath(component.paths.tpl.full),
102
+ ),
103
+ }
104
+ : null,
105
+ };
106
+
107
+ await renderVariations({
108
+ res,
109
+ component,
110
+ context: componentJson.filter((entry) => entry !== null),
111
+ componentDocumentation,
112
+ fileContents,
113
+ name: component.name,
114
+ cb,
115
+ templateFilePath: hasTemplate ? component.paths.tpl.full : null,
116
+ cookies,
117
+ noCli,
118
+ });
119
+ }
120
+
121
+ /**
122
+ * @typedef {object} FileContents
123
+ * @property {object} schema - schema object
124
+ * @property {string} schema.string - string with schema
125
+ * @property {("yaml"|"yml"|"json")} schema.type - the file type of the schema file
126
+ * @property {boolean} [schema.selected] - true if the schema tab should initially be visible
127
+ * @property {string} schema.file - the schema file path
128
+ * @property {object} mocks - mocks object
129
+ * @property {string} mocks.string - string with mocks
130
+ * @property {("yaml"|"yml"|"js"|"json")} mocks.type - the file type of the mocks file
131
+ * @property {boolean} [mocks.selected] - true if the mocks tab should initially be visible
132
+ * @property {string} mocks.file - the mock file path
133
+ * @property {object} template - template object
134
+ * @property {string} template.string - string with template
135
+ * @property {string} template.type - the file type of the template file
136
+ * @property {boolean} [template.selected] - true if the template tab should initially be visible
137
+ * @property {string} template.file - the template file path
138
+ */
139
+
140
+ /**
141
+ * @param {object} object - parameter object
142
+ * @param {object} object.res - the express response object
143
+ * @param {object} object.component
144
+ * @param {Array} object.context - mock data for each variation
145
+ * @param {string} object.componentDocumentation - html string with documentation
146
+ * @param {FileContents} object.fileContents - file contents object
147
+ * @param {string} object.name - component name
148
+ * @param {Function} object.cb - callback function
149
+ * @param {string} object.templateFilePath - the absolute component file path
150
+ * @param {object} [object.cookies]
151
+ * @param {boolean} object.noCli
152
+ * @returns {Promise}
153
+ */
154
+ async function renderVariations({
155
+ res,
156
+ component,
157
+ context,
158
+ componentDocumentation,
159
+ fileContents,
160
+ name,
161
+ cb,
162
+ templateFilePath,
163
+ cookies,
164
+ noCli,
165
+ }) {
166
+ const variations = [];
167
+ const promises = [];
168
+ const validatedMocks = validateMocks(component, context, noCli);
169
+
170
+ if (templateFilePath) {
171
+ if (context.length > 0) {
172
+ for (let i = 0, len = context.length; i < len; i += 1) {
173
+ const entry = context[i];
174
+
175
+ variations[i] = getData(
176
+ context[i].name,
177
+ component.paths.dir.short,
178
+ entry,
179
+ validatedMocks,
180
+ );
181
+ }
182
+ }
183
+ } else {
184
+ promises.push(Promise.resolve());
185
+ }
186
+
187
+ let schemaJson;
188
+
189
+ if (fileContents?.schema?.string) {
190
+ try {
191
+ schemaJson = JSON.stringify(jsonToYaml.load(fileContents.schema.string));
192
+ } catch (err) {
193
+ log("error", null, err);
194
+ }
195
+ }
196
+
197
+ let mocksJson;
198
+
199
+ if (fileContents?.mocks?.string) {
200
+ try {
201
+ mocksJson = JSON.stringify(jsonToYaml.load(fileContents.mocks.string));
202
+ } catch (err) {
203
+ log("error", null, err);
204
+ }
205
+ }
206
+
207
+ return Promise.all(promises)
208
+ .then(async () => {
209
+ const themeMode = getThemeMode(cookies);
210
+ const renderFileTabs = !!(
211
+ fileContents.schema ||
212
+ fileContents.mocks ||
213
+ fileContents.template
214
+ );
215
+ const componentsEntry = global.state.components.find(
216
+ ({ shortPath }) => shortPath === component.paths.dir.short,
217
+ );
218
+
219
+ await res.render(
220
+ "iframe_component.twig.miyagi",
221
+ {
222
+ lang: global.config.ui.lang,
223
+ variations,
224
+ cssFiles: global.config.assets.css,
225
+ jsFilesHead: global.config.assets.js.filter(
226
+ (entry) => entry.position === "head" || !entry.position,
227
+ ),
228
+ jsFilesBody: global.config.assets.js.filter(
229
+ (entry) => entry.position === "body",
230
+ ),
231
+ assets: {
232
+ css: componentsEntry
233
+ ? componentsEntry.assets.css
234
+ ? path.join("/", componentsEntry.assets.css)
235
+ : false
236
+ : false,
237
+ js: componentsEntry
238
+ ? componentsEntry.assets.js
239
+ ? path.join("/", componentsEntry.assets.js)
240
+ : false
241
+ : false,
242
+ },
243
+ miyagiDev: !!process.env.MIYAGI_DEVELOPMENT,
244
+ prod: process.env.NODE_ENV === "production",
245
+ projectName: config.projectName,
246
+ userProjectName: global.config.projectName,
247
+ isBuild: global.config.isBuild,
248
+ userUiConfig: getUserUiConfig(cookies),
249
+ theme: themeMode
250
+ ? Object.assign(global.config.ui.theme, { mode: themeMode })
251
+ : global.config.ui.theme,
252
+ documentation: componentDocumentation,
253
+ schema: schemaJson,
254
+ schemaError:
255
+ validatedMocks?.length > 0 && validatedMocks[0].type === "schema"
256
+ ? validatedMocks[0].data.map((error) => error.message).join("\n")
257
+ : null,
258
+ mocks: mocksJson,
259
+ template: fileContents.template,
260
+ renderInformation: renderFileTabs || variations.length > 0,
261
+ renderFileTabs,
262
+ folder: path.join(
263
+ global.config.components.folder,
264
+ component.paths.dir.short,
265
+ ),
266
+ name: componentDocumentation?.includes("<h1>") ? null : name,
267
+ uiTextDirection: global.config.ui.textDirection,
268
+ componentLanguage: global.config.components.lang,
269
+ },
270
+ (html) => {
271
+ if (res.send) {
272
+ res.send(html);
273
+ }
274
+
275
+ if (cb) {
276
+ cb(null, html);
277
+ }
278
+ },
279
+ );
280
+ })
281
+ .catch(() => {
282
+ if (cb) {
283
+ cb(true);
284
+ }
285
+ });
286
+ }
287
+
288
+ /**
289
+ * @param {string} variation
290
+ * @param {string} shortPath
291
+ * @param {object} entry
292
+ * @param {Array} validatedMocks
293
+ * @returns {object}
294
+ */
295
+ function getData(variation, shortPath, entry, validatedMocks) {
296
+ let standaloneUrl;
297
+
298
+ if (global.config.isBuild) {
299
+ standaloneUrl = `component-${helpers.normalizeString(
300
+ shortPath,
301
+ )}-variation-${helpers.normalizeString(variation)}.html`;
302
+ } else {
303
+ standaloneUrl = `/component?file=${shortPath}&variation=${encodeURIComponent(
304
+ variation,
305
+ )}`;
306
+ }
307
+
308
+ const data = {
309
+ url: global.config.isBuild
310
+ ? `component-${helpers.normalizeString(
311
+ shortPath,
312
+ )}-variation-${helpers.normalizeString(variation)}-embedded.html`
313
+ : `/component?file=${shortPath}&variation=${variation}&embedded=true`,
314
+ file: shortPath,
315
+ variation,
316
+ normalizedVariation: helpers.normalizeString(variation),
317
+ standaloneUrl,
318
+ mockData: JSON.stringify(entry.raw),
319
+ mockDataResolved: JSON.stringify(entry.resolved),
320
+ };
321
+
322
+ const validationEntry = validatedMocks?.find(
323
+ (item) => item.variant === entry.name,
324
+ );
325
+
326
+ if (validationEntry) {
327
+ data.mockValidation = {
328
+ valid: false,
329
+ copy: t("validator.mocks.invalid"),
330
+ };
331
+ } else {
332
+ data.mockValidation = {
333
+ valid: true,
334
+ };
335
+ }
336
+
337
+ return data;
338
+ }