@schalkneethling/miyagi-core 4.1.1 → 4.3.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.
package/api/index.js CHANGED
@@ -7,6 +7,11 @@ import build from "../lib/build/index.js";
7
7
  import generateMockData from "../lib/generator/mocks.js";
8
8
  import generateComponent from "../lib/generator/component.js";
9
9
  import validateMockData from "../lib/validator/mocks.js";
10
+ import {
11
+ getSchemaValidationMode,
12
+ toSchemaValidationResult,
13
+ validateSchemas,
14
+ } from "../lib/validator/schemas.js";
10
15
 
11
16
  /**
12
17
  * @param {object} obj
@@ -172,14 +177,44 @@ export const createComponent = async ({ component, only = [], skip = [] }) => {
172
177
 
173
178
  export const lintComponents = async () => {
174
179
  global.app = await init("api");
175
- const promises = [];
180
+ const mode = getSchemaValidationMode();
181
+ const components = global.state.routes.filter((route) => route.paths.tpl);
182
+ const schemaValidation = validateSchemas({
183
+ components,
184
+ });
185
+ const schemaErrorsByComponent = new Map();
186
+
187
+ schemaValidation.errors.forEach((entry) => {
188
+ if (!schemaErrorsByComponent.has(entry.component)) {
189
+ schemaErrorsByComponent.set(entry.component, []);
190
+ }
191
+ schemaErrorsByComponent
192
+ .get(entry.component)
193
+ .push(toSchemaValidationResult(entry));
194
+ });
176
195
 
177
- global.state.routes.forEach((route) => {
178
- if (route.paths.tpl) {
179
- promises.push(
196
+ if (mode === "fail-fast" && schemaValidation.errors.length > 0) {
197
+ return {
198
+ success: false,
199
+ data: getLintComponentErrorsInRouteOrder({
200
+ components,
201
+ errorMap: schemaErrorsByComponent,
202
+ }),
203
+ };
204
+ }
205
+
206
+ const promises = components
207
+ .filter((route) => !schemaErrorsByComponent.has(route.paths.dir.short))
208
+ .map(
209
+ (route) =>
180
210
  new Promise((resolve) => {
181
211
  getComponentData(route).then((data) => {
182
- const validation = validateMockData(route, data || [], true);
212
+ const validation = validateMockData(
213
+ route,
214
+ data || [],
215
+ true,
216
+ schemaValidation.validSchemas,
217
+ );
183
218
 
184
219
  resolve({
185
220
  component: route.alias,
@@ -187,13 +222,25 @@ export const lintComponents = async () => {
187
222
  });
188
223
  });
189
224
  }),
190
- );
191
- }
192
- });
225
+ );
193
226
 
194
227
  return await Promise.all(promises)
195
228
  .then((res) => {
196
- const errors = res.filter((result) => result?.errors?.length > 0);
229
+ res.forEach((result) => {
230
+ if (!result?.errors?.length) {
231
+ return;
232
+ }
233
+ const componentErrors = schemaErrorsByComponent.get(result.component) || [];
234
+ schemaErrorsByComponent.set(result.component, [
235
+ ...componentErrors,
236
+ ...result.errors,
237
+ ]);
238
+ });
239
+
240
+ const errors = getLintComponentErrorsInRouteOrder({
241
+ components,
242
+ errorMap: schemaErrorsByComponent,
243
+ });
197
244
 
198
245
  return {
199
246
  success: errors.length === 0,
@@ -216,8 +263,26 @@ export const lintComponent = async ({ component }) => {
216
263
  message: `The component ${component} does not seem to exist.`,
217
264
  };
218
265
 
266
+ const allSchemaValidation = validateSchemas({
267
+ components: [componentObject],
268
+ });
269
+
270
+ if (allSchemaValidation.errors.length > 0) {
271
+ return {
272
+ success: false,
273
+ data: allSchemaValidation.errors.map((entry) =>
274
+ toSchemaValidationResult(entry),
275
+ ),
276
+ };
277
+ }
278
+
219
279
  const data = await getComponentData(componentObject);
220
- const errors = validateMockData(componentObject, data, true);
280
+ const errors = validateMockData(
281
+ componentObject,
282
+ data || [],
283
+ true,
284
+ allSchemaValidation.validSchemas,
285
+ );
221
286
 
222
287
  return {
223
288
  success: errors === null || errors?.length === 0,
@@ -234,3 +299,26 @@ function getComponentsObject(component) {
234
299
  (route) => route.paths.dir.short === component,
235
300
  );
236
301
  }
302
+
303
+ /**
304
+ * @param {object} params
305
+ * @param {Array<object>} params.components
306
+ * @param {Map<string, Array<object>>} params.errorMap
307
+ * @returns {Array<object>}
308
+ */
309
+ function getLintComponentErrorsInRouteOrder({ components, errorMap }) {
310
+ return components
311
+ .map((route) => {
312
+ const componentErrors = errorMap.get(route.alias) || [];
313
+
314
+ if (componentErrors.length === 0) {
315
+ return null;
316
+ }
317
+
318
+ return {
319
+ component: route.alias,
320
+ errors: componentErrors,
321
+ };
322
+ })
323
+ .filter(Boolean);
324
+ }
package/dist/js/iframe.js CHANGED
@@ -1 +1 @@
1
- customElements.define("accordion-tabs",class extends HTMLElement{#e;#t;#s;#i;#n;static get observedAttributes(){return["breakpoint","current"]}constructor(){super()}connectedCallback(){if(!this.closest("code")){if(this.content=[],this.hasAttribute("breakpoint")){const e=this.getAttribute("breakpoint"),t=parseInt(e,10);let s;s=e.endsWith("rem")?t*parseInt(window.getComputedStyle(document.documentElement).fontSize,10):e.endsWith("em")?t*parseInt(window.getComputedStyle(this).fontSize,10):t,this.#t=s}window.requestAnimationFrame(()=>{this.details=Array.from(this.children),this.details.forEach((e,t)=>{const s=e.querySelector("summary");e.open&&(this.index=t),this.content.push({title:s.textContent,content:[...s.parentElement.children].filter(e=>1===e.nodeType&&e!==s)})});let e=!1;this.#i=new ResizeObserver(t=>{if(e)for(const e of t)this.#r(e.borderBoxSize[0].inlineSize);e=!0}),this.#r(this.clientWidth,()=>{this.#i.observe(this)})})}}attributeChangedCallback(e,t,s){"current"===e&&(this.index=parseInt(s,10),this.#r(this.clientWidth))}disconnectedCallback(){this.#i&&this.#i.disconnect()}async#r(e,t){!this.#t||e<this.#t?(await this.#o(),this.#s="accordion"):(await this.#a(),this.#s="tabs"),t&&t()}#o(){return this.#e||(this.#e=new e(this)),"tabs"===this.#s?(this.#c(),this.#e.setElements(),this.#e.elements.forEach(e=>this.appendChild(e))):this.#e.render(),!0}async#a(){this.#c(),this.#n?(this.#n.index="number"==typeof this.index?this.index:0,this.#n.setElements()):this.#n=new t(this);const[e,s]=this.#n.elements;return this.appendChild(e),s.forEach(e=>{this.appendChild(e)}),await this.#n.render(!1)}#c(){Array.from(this.children).forEach(e=>this.removeChild(e))}});class e{constructor(e){this.AccordionTabs=e,this.elements=this.AccordionTabs.details,this.elements.forEach((e,t)=>{e.querySelector("summary").addEventListener("click",({target:e})=>{requestAnimationFrame(()=>{this.#d(e.closest("details"),t)})})}),this.render()}setElements(){this.elements.forEach((e,t)=>{this.AccordionTabs.content[t].content.forEach(t=>e.appendChild(t)),e.open=t===this.AccordionTabs.index})}render(){this.elements.forEach((e,t)=>{e.open=t===this.AccordionTabs.index})}#d(e,t){e.open?(this.elements.forEach(t=>{e!==t&&(t.open=!1)}),this.AccordionTabs.index=t):this.AccordionTabs.index=null}}class t{#h;#l=[];#b=[];#u;constructor(e){this.#h=e,this.elements=this.getElements(),this.index="number"==typeof this.#h.index?this.#h.index:0,this.#u=new s(this)}getElements(){const e=document.createElement("ol"),t=[];return this.#h.content.forEach(({title:s,content:i},n)=>{const r=document.createElement("button"),o=document.createElement("li"),a=document.createElement("div"),c=crypto.randomUUID(),d=`tab-${c}`,h=`panel-${c}`;e.setAttribute("role","tablist"),o.setAttribute("role","presentation"),r.textContent=s,r.type="button",r.id=d,r.setAttribute("aria-selected",n===this.index?"true":"false"),r.setAttribute("tabindex",n===this.index?0:-1),r.setAttribute("aria-controls",h),r.setAttribute("role","tab"),this.#l.push(r),o.appendChild(r),e.appendChild(o),i.forEach(e=>{a.appendChild(e)}),a.id=h,a.hidden=this.index!==n,a.setAttribute("role","tabpanel"),a.setAttribute("tabindex","0"),a.setAttribute("aria-labelledby",d),t.push(a),this.#b.push(a)}),[e,t]}setElements(){this.elements[1].forEach((e,t)=>{this.#h.content[t].content.forEach(t=>e.appendChild(t))})}setActiveTab(e){this.#h.index=this.index=e,this.render()}async render(e=!0){return this.elements[1].forEach((e,t)=>{e.hidden=t!==this.index}),await this.#u.render(e)}}class s{#m;#p;constructor(e){this.#m=e,this.#p=Array.from(this.#m.elements[0].querySelectorAll("button")),this.#p.forEach(e=>{e.addEventListener("click",this.#T.bind(this)),e.addEventListener("keydown",this.#A.bind(this))})}#T({currentTarget:e}){this.#m.setActiveTab(this.#p.indexOf(e))}#A(e){const{dir:t}=e.target.closest("[dir]")||document.documentElement;let s=!1;switch(e.key){case"ArrowLeft":"rtl"===t?this.#f():this.#w(),s=!0;break;case"ArrowRight":"rtl"===t?this.#w():this.#f(),s=!0;break;case"Home":this.#m.setActiveTab(0),s=!0;break;case"End":this.#m.setActiveTab(this.#p.length-1),s=!0}s&&(e.stopPropagation(),e.preventDefault())}#f(){this.#m.setActiveTab(this.#m.index===this.#p.length-1?0:this.#m.index+1)}#w(){this.#m.setActiveTab(0===this.#m.index?this.#p.length-1:this.#m.index-1)}render(e=!0){return this.#p.forEach((t,s)=>{s===this.#m.index?(t.setAttribute("aria-selected","true"),t.removeAttribute("tabindex"),e&&t.focus()):(t.setAttribute("aria-selected","false"),t.setAttribute("tabindex",-1))}),new Promise(e=>setTimeout(e,1e3))}}new WebSocket(`ws://${document.location.host}`).onmessage=async e=>{"reloadParent"===e.data?parent.window.location.reload():window.location.reload()};function i(e){return["INPUT","SELECT","TEXTAREA"].includes(e)}window.location.pathname.startsWith("/component?")&&window.location.href.indexOf("&embedded=true")>=0&&window.self===window.top&&(window.location=new URL(window.location).replace("&embedded=true","")),document.addEventListener("DOMContentLoaded",function(){const e=Array.from(document.querySelectorAll(".Component-file")),t=document.querySelector(".Styleguide");e.length>0&&import("./_iframe-links-DdifIr4P.js").then(t=>{t.default(e)}).catch(e=>console.error(e)),document.querySelector(".Code")&&import("./_prism-By3NMwUd.js"),t&&import("./index-BKDKaBC6.js").then(e=>new e.default(t)).catch(e=>console.error(e)),document.querySelector(".js-openMockData")&&import("./_mock-data-Dypo4Bl_.js")}),document.addEventListener("keyup",e=>{const{path:t,originalTarget:s,target:n,key:r}=e,o=t?t[0]:s||n;((e,t)=>!i(e.tagName)&&"f"===t)(o,r)?parent.window.dispatchEvent(new CustomEvent("searchTriggered")):((e,t)=>!i(e.tagName)&&"g"===t)(o,r)&&parent.window.dispatchEvent(new CustomEvent("gotoTriggered"))});
1
+ customElements.define("accordion-tabs",class extends HTMLElement{#e;#t;#n;#i;#s;static get observedAttributes(){return["breakpoint","current"]}constructor(){super()}connectedCallback(){if(!this.closest("code")){if(this.content=[],this.hasAttribute("breakpoint")){const e=this.getAttribute("breakpoint"),t=parseInt(e,10);let n;n=e.endsWith("rem")?t*parseInt(window.getComputedStyle(document.documentElement).fontSize,10):e.endsWith("em")?t*parseInt(window.getComputedStyle(this).fontSize,10):t,this.#t=n}window.requestAnimationFrame(()=>{this.details=Array.from(this.children),this.details.forEach((e,t)=>{const n=e.querySelector("summary");e.open&&(this.index=t),this.content.push({title:n.textContent,content:[...n.parentElement.children].filter(e=>1===e.nodeType&&e!==n)})});let e=!1;this.#i=new ResizeObserver(t=>{if(e)for(const e of t)this.#r(e.borderBoxSize[0].inlineSize);e=!0}),this.#r(this.clientWidth,()=>{this.#i.observe(this)})})}}attributeChangedCallback(e,t,n){"current"===e&&(this.index=parseInt(n,10),this.#r(this.clientWidth))}disconnectedCallback(){this.#i&&this.#i.disconnect()}async#r(e,t){!this.#t||e<this.#t?(await this.#o(),this.#n="accordion"):(await this.#a(),this.#n="tabs"),t&&t()}#o(){return this.#e||(this.#e=new e(this)),"tabs"===this.#n?(this.#c(),this.#e.setElements(),this.#e.elements.forEach(e=>this.appendChild(e))):this.#e.render(),!0}async#a(){this.#c(),this.#s?(this.#s.index="number"==typeof this.index?this.index:0,this.#s.setElements()):this.#s=new t(this);const[e,n]=this.#s.elements;return this.appendChild(e),n.forEach(e=>{this.appendChild(e)}),await this.#s.render(!1)}#c(){Array.from(this.children).forEach(e=>this.removeChild(e))}});class e{constructor(e){this.AccordionTabs=e,this.elements=this.AccordionTabs.details,this.elements.forEach((e,t)=>{e.querySelector("summary").addEventListener("click",({target:e})=>{requestAnimationFrame(()=>{this.#d(e.closest("details"),t)})})}),this.render()}setElements(){this.elements.forEach((e,t)=>{this.AccordionTabs.content[t].content.forEach(t=>e.appendChild(t)),e.open=t===this.AccordionTabs.index})}render(){this.elements.forEach((e,t)=>{e.open=t===this.AccordionTabs.index})}#d(e,t){e.open?(this.elements.forEach(t=>{e!==t&&(t.open=!1)}),this.AccordionTabs.index=t):this.AccordionTabs.index=null}}class t{#h;#l=[];#b=[];#u;constructor(e){this.#h=e,this.elements=this.getElements(),this.index="number"==typeof this.#h.index?this.#h.index:0,this.#u=new n(this)}getElements(){const e=document.createElement("ol"),t=[];return this.#h.content.forEach(({title:n,content:i},s)=>{const r=document.createElement("button"),o=document.createElement("li"),a=document.createElement("div"),c=crypto.randomUUID(),d=`tab-${c}`,h=`panel-${c}`;e.setAttribute("role","tablist"),o.setAttribute("role","presentation"),r.textContent=n,r.type="button",r.id=d,r.setAttribute("aria-selected",s===this.index?"true":"false"),r.setAttribute("tabindex",s===this.index?0:-1),r.setAttribute("aria-controls",h),r.setAttribute("role","tab"),this.#l.push(r),o.appendChild(r),e.appendChild(o),i.forEach(e=>{a.appendChild(e)}),a.id=h,a.hidden=this.index!==s,a.setAttribute("role","tabpanel"),a.setAttribute("tabindex","0"),a.setAttribute("aria-labelledby",d),t.push(a),this.#b.push(a)}),[e,t]}setElements(){this.elements[1].forEach((e,t)=>{this.#h.content[t].content.forEach(t=>e.appendChild(t))})}setActiveTab(e){this.#h.index=this.index=e,this.render()}async render(e=!0){return this.elements[1].forEach((e,t)=>{e.hidden=t!==this.index}),await this.#u.render(e)}}class n{#m;#p;constructor(e){this.#m=e,this.#p=Array.from(this.#m.elements[0].querySelectorAll("button")),this.#p.forEach(e=>{e.addEventListener("click",this.#T.bind(this)),e.addEventListener("keydown",this.#f.bind(this))})}#T({currentTarget:e}){this.#m.setActiveTab(this.#p.indexOf(e))}#f(e){const{dir:t}=e.target.closest("[dir]")||document.documentElement;let n=!1;switch(e.key){case"ArrowLeft":"rtl"===t?this.#w():this.#A(),n=!0;break;case"ArrowRight":"rtl"===t?this.#A():this.#w(),n=!0;break;case"Home":this.#m.setActiveTab(0),n=!0;break;case"End":this.#m.setActiveTab(this.#p.length-1),n=!0}n&&(e.stopPropagation(),e.preventDefault())}#w(){this.#m.setActiveTab(this.#m.index===this.#p.length-1?0:this.#m.index+1)}#A(){this.#m.setActiveTab(0===this.#m.index?this.#p.length-1:this.#m.index-1)}render(e=!0){return this.#p.forEach((t,n)=>{n===this.#m.index?(t.setAttribute("aria-selected","true"),t.removeAttribute("tabindex"),e&&t.focus()):(t.setAttribute("aria-selected","false"),t.setAttribute("tabindex",-1))}),new Promise(e=>setTimeout(e,1e3))}}let i,s=250;function r(e){const t=function(e){return"reloadParent"===e?"parent":"string"==typeof e&&0===e.length?"iframe":null}(e);return t||function(e){try{return JSON.parse(e).scope||"iframe"}catch{return"iframe"}}(e)}function o(){i=new WebSocket(`${"https:"===window.location.protocol?"wss":"ws"}://${window.location.host}/__miyagi_ws`),i.onopen=()=>{s=250},i.onmessage=e=>{"parent"!==r(e.data)?window.location.reload():parent.window.location.reload()},i.onerror=()=>{i.close()},i.onclose=()=>{!function(){const e=Math.floor(100*Math.random()),t=Math.min(s+e,5e3);s=Math.min(2*s,5e3),window.setTimeout(()=>{o()},t)}()}}o();function a(e){return["INPUT","SELECT","TEXTAREA"].includes(e)}window.location.pathname.startsWith("/component?")&&window.location.href.indexOf("&embedded=true")>=0&&window.self===window.top&&(window.location=new URL(window.location).replace("&embedded=true","")),document.addEventListener("DOMContentLoaded",function(){const e=Array.from(document.querySelectorAll(".Component-file")),t=document.querySelector(".Styleguide");e.length>0&&import("./_iframe-links-DdifIr4P.js").then(t=>{t.default(e)}).catch(e=>console.error(e)),document.querySelector(".Code")&&import("./_prism-By3NMwUd.js"),t&&import("./index-BKDKaBC6.js").then(e=>new e.default(t)).catch(e=>console.error(e)),document.querySelector(".js-openMockData")&&import("./_mock-data-Dypo4Bl_.js")}),document.addEventListener("keyup",e=>{const{path:t,originalTarget:n,target:i,key:s}=e,r=t?t[0]:n||i;((e,t)=>!a(e.tagName)&&"f"===t)(r,s)?parent.window.dispatchEvent(new CustomEvent("searchTriggered")):((e,t)=>!a(e.tagName)&&"g"===t)(r,s)&&parent.window.dispatchEvent(new CustomEvent("gotoTriggered"))});
@@ -1,9 +1,82 @@
1
- const ws = new WebSocket(`ws://${document.location.host}`);
1
+ const SOCKET_PATH = "/__miyagi_ws";
2
+ const INITIAL_RETRY_DELAY_MS = 250;
3
+ const MAX_RETRY_DELAY_MS = 5000;
2
4
 
3
- ws.onmessage = async (message) => {
4
- if (message.data === "reloadParent") {
5
+ let retryDelay = INITIAL_RETRY_DELAY_MS;
6
+ let websocket;
7
+
8
+ function getWebSocketUrl() {
9
+ const protocol = window.location.protocol === "https:" ? "wss" : "ws";
10
+ return `${protocol}://${window.location.host}${SOCKET_PATH}`;
11
+ }
12
+
13
+ function triggerReload(scope) {
14
+ if (scope === "parent") {
5
15
  parent.window.location.reload();
6
- } else {
7
- window.location.reload();
16
+ return;
17
+ }
18
+
19
+ window.location.reload();
20
+ }
21
+
22
+ function parseLegacyScope(messageData) {
23
+ if (messageData === "reloadParent") {
24
+ return "parent";
8
25
  }
9
- };
26
+
27
+ if (typeof messageData === "string" && messageData.length === 0) {
28
+ return "iframe";
29
+ }
30
+
31
+ return null;
32
+ }
33
+
34
+ function parseJsonScope(messageData) {
35
+ try {
36
+ const parsed = JSON.parse(messageData);
37
+ return parsed.scope || "iframe";
38
+ } catch {
39
+ return "iframe";
40
+ }
41
+ }
42
+
43
+ function parseScope(messageData) {
44
+ const legacyScope = parseLegacyScope(messageData);
45
+ if (legacyScope) {
46
+ return legacyScope;
47
+ }
48
+
49
+ return parseJsonScope(messageData);
50
+ }
51
+
52
+ function scheduleReconnect() {
53
+ const jitter = Math.floor(Math.random() * 100);
54
+ const delay = Math.min(retryDelay + jitter, MAX_RETRY_DELAY_MS);
55
+ retryDelay = Math.min(retryDelay * 2, MAX_RETRY_DELAY_MS);
56
+
57
+ window.setTimeout(() => {
58
+ connect();
59
+ }, delay);
60
+ }
61
+
62
+ function connect() {
63
+ websocket = new WebSocket(getWebSocketUrl());
64
+
65
+ websocket.onopen = () => {
66
+ retryDelay = INITIAL_RETRY_DELAY_MS;
67
+ };
68
+
69
+ websocket.onmessage = (message) => {
70
+ triggerReload(parseScope(message.data));
71
+ };
72
+
73
+ websocket.onerror = () => {
74
+ websocket.close();
75
+ };
76
+
77
+ websocket.onclose = () => {
78
+ scheduleReconnect();
79
+ };
80
+ }
81
+
82
+ connect();
package/lib/cli/lint.js CHANGED
@@ -4,6 +4,11 @@ import getConfig from "../config.js";
4
4
  import log from "../logger.js";
5
5
  import { getComponentData } from "../mocks/index.js";
6
6
  import validateMockData from "../validator/mocks.js";
7
+ import {
8
+ getSchemaValidationMode,
9
+ toSchemaValidationResult,
10
+ validateSchemas,
11
+ } from "../validator/schemas.js";
7
12
  import { t } from "../i18n/index.js";
8
13
 
9
14
  /**
@@ -14,6 +19,8 @@ export default async function lint(args) {
14
19
 
15
20
  const componentArg = args._.slice(1)[0];
16
21
  const config = await getConfig(args);
22
+ process.env.MIYAGI_LOG_CONTEXT = "lint";
23
+ process.env.MIYAGI_LOG_LEVEL = config.lint?.logLevel || "error";
17
24
  global.app = await init(config);
18
25
 
19
26
  if (componentArg) {
@@ -23,8 +30,20 @@ export default async function lint(args) {
23
30
  );
24
31
 
25
32
  if (component) {
33
+ const schemaValidation = validateSchemas({
34
+ components: [component],
35
+ });
36
+
37
+ if (schemaValidation.errors.length > 0) {
38
+ reportSchemaErrors(schemaValidation.errors);
39
+ process.exit(1);
40
+ }
41
+
42
+ log("success", "All schemas valid.");
43
+
26
44
  await validateComponentMockData({
27
45
  component,
46
+ validSchemas: schemaValidation.validSchemas,
28
47
  });
29
48
  } else {
30
49
  log("error", `The component ${componentArg} does not seem to exist.`);
@@ -40,81 +59,90 @@ export default async function lint(args) {
40
59
  */
41
60
  async function validateAllMockData(exitProcess = true) {
42
61
  log("info", t("linter.all.start"));
62
+ const mode = getSchemaValidationMode();
63
+ const components = global.state.routes.filter(
64
+ (route) => route.type === "components" && route.paths.tpl,
65
+ );
66
+ const schemaValidation = validateSchemas({
67
+ components,
68
+ });
69
+ const invalidSchemaComponents = new Set(
70
+ schemaValidation.errors.map((entry) => entry.component),
71
+ );
43
72
 
44
- const promises = [];
45
-
46
- global.state.routes.forEach((route) => {
47
- if (route.type === "components") {
48
- promises.push(
49
- new Promise((resolve, reject) => {
50
- validateComponentMockData({
51
- component: route,
52
- silent: true,
53
- exitProcess: false,
54
- })
55
- .then((result) => resolve(result))
56
- .catch((err) => {
57
- console.error(err);
58
- reject();
59
- });
60
- }),
61
- );
73
+ if (schemaValidation.errors.length === 0) {
74
+ log("success", "All schemas valid.");
75
+ }
76
+
77
+ if (schemaValidation.errors.length > 0 && mode === "fail-fast") {
78
+ reportSchemaErrors(schemaValidation.errors);
79
+ log(
80
+ "error",
81
+ schemaValidation.errors.length === 1
82
+ ? t("linter.all.schema.invalid.one")
83
+ : t("linter.all.schema.invalid.other").replace(
84
+ "{{amount}}",
85
+ schemaValidation.errors.length,
86
+ ),
87
+ );
88
+ if (exitProcess) {
89
+ process.exit(1);
62
90
  }
63
- });
91
+ return;
92
+ }
64
93
 
65
- Promise.all(promises)
66
- .then((results) => {
67
- const mockInvalidResults = results.filter(
68
- (result) => result?.valid === false && result.type === "mocks",
69
- );
70
- const schemaInvalidResults = results.filter(
71
- (result) => result?.valid === false && result.type === "schema",
72
- );
94
+ const results = await Promise.all(
95
+ components
96
+ .filter((route) => !invalidSchemaComponents.has(route.paths.dir.short))
97
+ .map((component) =>
98
+ validateComponentMockData({
99
+ component,
100
+ silent: true,
101
+ exitProcess: false,
102
+ validSchemas: schemaValidation.validSchemas,
103
+ }),
104
+ ),
105
+ );
106
+ const mockInvalidResults = results.filter(
107
+ (result) => result?.valid === false && result.type === "mocks",
108
+ );
73
109
 
74
- if (
75
- mockInvalidResults.length === 0 &&
76
- schemaInvalidResults.length === 0
77
- ) {
78
- log("success", t("linter.all.valid"));
79
- if (exitProcess) {
80
- process.exit(0);
81
- }
82
- }
110
+ if (mode === "collect-all" && schemaValidation.errors.length > 0) {
111
+ reportSchemaErrors(schemaValidation.errors);
112
+ log(
113
+ "error",
114
+ schemaValidation.errors.length === 1
115
+ ? t("linter.all.schema.invalid.one")
116
+ : t("linter.all.schema.invalid.other").replace(
117
+ "{{amount}}",
118
+ schemaValidation.errors.length,
119
+ ),
120
+ );
121
+ }
83
122
 
84
- if (schemaInvalidResults.length > 0) {
85
- log(
86
- "error",
87
- schemaInvalidResults.length === 1
88
- ? t("linter.all.schema.invalid.one")
89
- : t("linter.all.schema.invalid.other").replace(
90
- "{{amount}}",
91
- schemaInvalidResults.length,
92
- ),
93
- );
94
- }
123
+ if (mockInvalidResults.length > 0) {
124
+ log(
125
+ "error",
126
+ mockInvalidResults.length === 1
127
+ ? t("linter.all.mocks.invalid.one")
128
+ : t("linter.all.mocks.invalid.other").replace(
129
+ "{{amount}}",
130
+ mockInvalidResults.length,
131
+ ),
132
+ );
133
+ }
95
134
 
96
- if (mockInvalidResults.length > 0) {
97
- log(
98
- "error",
99
- mockInvalidResults.length === 1
100
- ? t("linter.all.mocks.invalid.one")
101
- : t("linter.all.mocks.invalid.other").replace(
102
- "{{amount}}",
103
- mockInvalidResults.length,
104
- ),
105
- );
106
- }
135
+ if (mockInvalidResults.length === 0 && schemaValidation.errors.length === 0) {
136
+ log("success", t("linter.all.valid"));
137
+ if (exitProcess) {
138
+ process.exit(0);
139
+ }
140
+ return;
141
+ }
107
142
 
108
- if (exitProcess) {
109
- process.exit(1);
110
- }
111
- })
112
- .catch((err) => {
113
- console.error(err);
114
- if (exitProcess) {
115
- process.exit(1);
116
- }
117
- });
143
+ if (exitProcess) {
144
+ process.exit(1);
145
+ }
118
146
  }
119
147
 
120
148
  /**
@@ -122,12 +150,14 @@ async function validateAllMockData(exitProcess = true) {
122
150
  * @param {object} obj.component
123
151
  * @param {boolean} [obj.silent]
124
152
  * @param {boolean} [obj.exitProcess]
153
+ * @param {Array<object>} [obj.validSchemas]
125
154
  * @returns {Promise<object|null>}
126
155
  */
127
156
  async function validateComponentMockData({
128
157
  component,
129
158
  silent,
130
159
  exitProcess = true,
160
+ validSchemas = [],
131
161
  }) {
132
162
  if (!silent) {
133
163
  log(
@@ -139,42 +169,60 @@ async function validateComponentMockData({
139
169
  );
140
170
  }
141
171
 
142
- const data = await getComponentData(component);
172
+ const data = (await getComponentData(component)) || [];
143
173
 
144
- if (data) {
145
- for (const { messages } of data) {
174
+ if (data.length > 0) {
175
+ for (const { messages = [] } of data) {
146
176
  for (const { type, text, verbose } of messages) {
147
177
  log(type, text, verbose);
148
178
  }
149
179
  }
180
+ }
150
181
 
151
- const results = validateMockData(component, data);
182
+ const results = validateMockData(component, data, false, validSchemas);
152
183
 
153
- if (!results) return null;
184
+ if (!results) return null;
154
185
 
155
- if (results.length === 0) {
156
- if (!silent) {
157
- log("success", t("linter.component.valid"));
158
- }
186
+ if (results.length === 0) {
187
+ if (!silent) {
188
+ log("success", t("linter.component.valid"));
189
+ }
159
190
 
160
- if (exitProcess) {
161
- process.exit(0);
162
- } else {
163
- return {
164
- valid: true,
165
- };
166
- }
167
- } else {
168
- if (exitProcess) {
169
- process.exit(0);
170
- } else {
171
- return {
172
- valid: false,
173
- type: results[0].type,
174
- };
175
- }
191
+ if (exitProcess) {
192
+ process.exit(0);
176
193
  }
194
+
195
+ return {
196
+ valid: true,
197
+ };
177
198
  }
178
199
 
179
- return null;
200
+ if (exitProcess) {
201
+ process.exit(1);
202
+ }
203
+
204
+ return {
205
+ valid: false,
206
+ type: results[0].type,
207
+ };
208
+ }
209
+
210
+ /**
211
+ * @param {Array<object>} schemaErrors
212
+ */
213
+ function reportSchemaErrors(schemaErrors) {
214
+ schemaErrors.forEach((entry) => {
215
+ const result = toSchemaValidationResult(entry);
216
+ log("error", `${entry.component}:\n${result.data[0].message}`);
217
+ log("error", `schema: ${entry.schemaFile}`);
218
+ if (entry.schemaPath || entry.instancePath) {
219
+ log(
220
+ "error",
221
+ `schemaPath: ${entry.schemaPath || "-"} | instancePath: ${entry.instancePath || "-"}`,
222
+ );
223
+ }
224
+ if (entry.hint) {
225
+ log("warn", entry.hint);
226
+ }
227
+ });
180
228
  }
package/lib/config.js CHANGED
@@ -70,5 +70,29 @@ function getCliArgs(args) {
70
70
 
71
71
  cliArgs.build = buildArgs;
72
72
 
73
+ if (
74
+ cliArgs.watchReport !== undefined ||
75
+ cliArgs.watchReportFormat !== undefined ||
76
+ cliArgs.watchReportNoColor !== undefined
77
+ ) {
78
+ cliArgs.watch = cliArgs.watch || {};
79
+ cliArgs.watch.report = cliArgs.watch.report || {};
80
+
81
+ if (cliArgs.watchReport !== undefined) {
82
+ cliArgs.watch.report.enabled = cliArgs.watchReport;
83
+ delete cliArgs.watchReport;
84
+ }
85
+
86
+ if (cliArgs.watchReportFormat !== undefined) {
87
+ cliArgs.watch.report.format = cliArgs.watchReportFormat;
88
+ delete cliArgs.watchReportFormat;
89
+ }
90
+
91
+ if (cliArgs.watchReportNoColor !== undefined) {
92
+ cliArgs.watch.report.useColors = !cliArgs.watchReportNoColor;
93
+ delete cliArgs.watchReportNoColor;
94
+ }
95
+ }
96
+
73
97
  return cliArgs;
74
98
  }
@@ -0,0 +1,11 @@
1
+ export const LINT_LOG_LEVELS = Object.freeze({
2
+ ERROR: "error",
3
+ WARN: "warn",
4
+ INFO: "info",
5
+ });
6
+
7
+ export const LINT_LOG_LEVEL_ORDER = Object.freeze({
8
+ [LINT_LOG_LEVELS.ERROR]: 0,
9
+ [LINT_LOG_LEVELS.WARN]: 1,
10
+ [LINT_LOG_LEVELS.INFO]: 2,
11
+ });
@@ -46,6 +46,9 @@ export default {
46
46
  render: null,
47
47
  options: {},
48
48
  },
49
+ lint: {
50
+ logLevel: "error",
51
+ },
49
52
  extensions: [],
50
53
  files: {
51
54
  css: {
@@ -94,9 +97,69 @@ export default {
94
97
  },
95
98
  watchConfigFile: true,
96
99
  },
100
+ watch: {
101
+ enabled: true,
102
+ backend: "chokidar",
103
+ sources: [],
104
+ ignore: {
105
+ defaults: true,
106
+ patterns: [],
107
+ },
108
+ behavior: {
109
+ debounceMs: 60,
110
+ coalesceWindowMs: 120,
111
+ awaitWriteFinish: {
112
+ enabled: true,
113
+ stabilityThresholdMs: 200,
114
+ pollIntervalMs: 50,
115
+ },
116
+ },
117
+ reload: {
118
+ enabled: true,
119
+ rules: {
120
+ template: "iframe",
121
+ data: "parent",
122
+ docs: "parent",
123
+ schema: "iframe",
124
+ componentAsset: "none",
125
+ globalCss: "none",
126
+ globalJs: "none",
127
+ unknown: "parent",
128
+ },
129
+ },
130
+ socket: {
131
+ reconnect: {
132
+ enabled: true,
133
+ initialDelayMs: 250,
134
+ maxDelayMs: 5000,
135
+ jitter: true,
136
+ },
137
+ heartbeat: {
138
+ enabled: true,
139
+ intervalMs: 30000,
140
+ },
141
+ },
142
+ report: {
143
+ enabled: true,
144
+ onStart: true,
145
+ format: "pretty",
146
+ destination: "stdout",
147
+ useColors: true,
148
+ },
149
+ configFile: {
150
+ enabled: true,
151
+ },
152
+ debug: {
153
+ logEvents: false,
154
+ logDecisions: false,
155
+ logResolvedSources: false,
156
+ },
157
+ },
97
158
  schema: {
98
159
  ajv: AJV,
160
+ verbose: false,
99
161
  },
162
+ schemaValidationMode: "collect-all",
100
163
  },
101
164
  projectName: "miyagi",
102
165
  defaultPort: 5000,