UNPKG

@miyagi/core

Version:

miyagi is a component development tool for JavaScript template engines.

485 lines (417 loc) 10.5 kB
import path from "path"; import deepMerge from "deepmerge"; import * as helpers from "../../helpers.js"; import { t } from "../../i18n/index.js"; import { mergeRootDataWithVariationData } from "../resolve.js"; /** * @param {object|Array|string|boolean} entry - a value from the mock data object * @param {object} component * @returns {Promise<object|Array|string|boolean>} the resolved value from the mock data object */ export const resolveRefs = async function (entry, component) { const messages = []; if (entry) { if ( typeof entry === "string" || typeof entry === "number" || typeof entry === "boolean" || entry instanceof Map || entry === null ) { return { messages, data: entry, }; } if (entry instanceof Array) { const o = []; for (const ent of entry) { let newComponent = component; if (ent.$ref) { if (!ent.$ref.startsWith("#/$defs/")) { if (ent.$ref.startsWith("/")) { ent.$ref = ent.$ref.replace("/", ""); } const path = ent.$ref.split("#")[0]; const { resolved, message } = resolveNamespace(path); if (resolved) { newComponent = global.state.routes.find( (route) => route.paths.dir.short === resolved, ); } else { messages.push(message); } } } const json = await resolve(ent, newComponent); const result = await resolveRefs(json.data, newComponent); o.push(result.data); for (const msg of [...json.messages, ...result.messages]) { if (msg) { messages.push(msg); } } } return { messages, data: o, }; } let o = entry; for (const [key, value] of Object.entries({ ...entry })) { if (key === "$ref") { let newComponent; if (value.startsWith("#/$defs/")) { newComponent = component; } else { let path = value.split("#")[0]; if (path.startsWith("/")) { path = path.replace("/", ""); } const { resolved, message } = resolveNamespace(path); if (resolved) { newComponent = global.state.routes.find( (route) => route.paths.dir.short === resolved, ); } else { messages.push(message); } } let { message, data: resolvedValue } = await getRootOrVariantDataOfReference(value, newComponent); if (message) { messages.push(message); } const json = await resolve(resolvedValue, newComponent); const result = await resolveRefs(json.data, newComponent); for (const msg of [...json.messages, ...result.messages]) { if (msg) { messages.push(msg); } } o = { ...o, ...result.data }; delete o.$ref; } else { const json = await resolve(value, component); const result = await resolveRefs(json.data, component); o[key] = result.data; for (const msg of [...json.messages, ...result.messages]) { if (msg) { messages.push(msg); } } } } return { messages, data: o, }; } return { messages, data: entry, }; }; /** * @param {object|Array|string|boolean} entry - a value from the mock data object * @param {object} component * @returns {Promise<object|Array|string|boolean>} the resolved value from the mock data object */ async function resolve(entry, component) { const messages = []; if (entry !== null) { if (Array.isArray(entry)) { return { messages, data: entry, }; } if ( typeof entry === "string" || typeof entry === "number" || typeof entry === "boolean" || entry instanceof Map || entry === null ) { return { messages, data: entry, }; } if (entry === undefined) { return { messages: [ ...messages, { type: "warn", text: t("referencedMockFileNotFound"), }, ], data: entry, }; } if (entry.$ref) { const customData = helpers.cloneDeep(entry); delete customData.$ref; let newComponent; if (entry.$ref.startsWith("#/$defs/")) { newComponent = component; } else { let path = entry.$ref.split("#")[0]; if (path.startsWith("/")) { path = path.replace("/", ""); } const { resolved, message } = resolveNamespace(path); if (resolved) { newComponent = global.state.routes.find( (route) => route.paths.dir.short === resolved, ); if (!newComponent) { messages.push({ type: "warn", text: `${resolved} not found`, }); } } else { messages.push(message); } } if (newComponent) { const { message, data } = await getRootOrVariantDataOfReference( entry.$ref, newComponent, ); return { messages: [...messages, message], data: deepMerge(data, customData), }; } else { return { messages, data: customData, }; } } } return { messages, data: entry, }; } /** * @param {string} ref - the reference to another mock data * @param {object} component * @returns {Promise<object>} the resolved data object */ async function getRootOrVariantDataOfReference(ref, component) { const { mocks } = global.config.files; let [shortVal, variation] = ref.split("#"); let jsonFromData; let mocksBaseName; let fullPath; if (ref.startsWith("#/$defs/")) { const componentJson = helpers.cloneDeep( global.state.fileContents[ component.paths.mocks.full(global.config.files.mocks.extension[0]) ] || global.state.fileContents[ component.paths.mocks.full(global.config.files.mocks.extension[1]) ], ); mocksBaseName = ref; const defRef = ref.replace("#/$defs/", ""); if ("$defs" in componentJson && defRef in componentJson.$defs) { return { data: componentJson.$defs[defRef], }; } return { message: { type: "warn", text: t("fileNotFoundLinkIncorrect") .replace("{{filePath}}", mocksBaseName) .replace( "{{component}}", component.paths.mocks.short( global.config.files.mocks.extension[0], ) || component.paths.mocks.short( global.config.files.mocks.extension[1], ), ), }, data: {}, }; } if (ref.startsWith(`${global.config.files.mocks.name}/#/$defs/`)) { const defaultFile = helpers.getFullPathFromShortPath( `${global.config.files.mocks.name}.${global.config.files.mocks.extension[0]}`, ); const jsFile = helpers.getFullPathFromShortPath( `${global.config.files.mocks.name}.${global.config.files.mocks.extension[1]}`, ); const globalData = { ...(global.state.fileContents[defaultFile] || global.state.fileContents[jsFile]), }; const defRef = ref.replace( `${global.config.files.mocks.name}/#/$defs/`, "", ); if (defRef in globalData.$defs) { return { data: globalData.$defs[defRef], }; } return { message: { type: "warn", text: t("fileNotFoundLinkIncorrect") .replace("{{filePath}}", ref) .replace( "{{component}}", component.paths.mocks.short( global.config.files.mocks.extension[0], ) || component.paths.mocks.short( global.config.files.mocks.extension[1], ), ), }, data: {}, }; } if (shortVal.startsWith("@")) { const namespace = shortVal.split("/")[0]; const resolvedNamespace = global.config.namespaces[namespace]; if (!resolvedNamespace) { return { message: { type: "warn", text: `Could not resolve namespace ${namespace}`, }, data: null, }; } shortVal = path.relative(namespace, shortVal); mocksBaseName = path.join(shortVal, mocks.name); fullPath = path.join( process.cwd(), resolvedNamespace, shortVal, mocks.name, ); } else { mocksBaseName = path.join(shortVal, mocks.name); fullPath = helpers.getFullPathFromShortPath(mocksBaseName); } jsonFromData = global.state.fileContents[`${fullPath}.${mocks.extension[0]}`] || global.state.fileContents[`${fullPath}.${mocks.extension[1]}`]; if (jsonFromData) { if (variation?.startsWith("/$defs/") && "$defs" in jsonFromData) { const defRef = variation.replace("/$defs/", ""); if (defRef in jsonFromData.$defs) { return { data: jsonFromData.$defs[defRef], }; } return { message: { type: "warn", text: t("fileNotFoundLinkIncorrect") .replace("{{filePath}}", ref) .replace( "{{component}}", component.paths.mocks.short( global.config.files.mocks.extension[0], ) || component.paths.mocks.short( global.config.files.mocks.extension[1], ), ), }, data: {}, }; } const embeddedJson = jsonFromData; const rootJson = helpers.removeInternalKeys(embeddedJson); let variantJson = {}; let message; if (variation && embeddedJson.$variants && embeddedJson.$variants.length) { const variant = embeddedJson.$variants.find((vari) => { if (vari.$name) { return ( helpers.normalizeString(vari.$name) === helpers.normalizeString(variation) ); } return false; }); if (variant) { variantJson = helpers.removeInternalKeys(variant); } else { message = { type: "warn", text: t("variationNotFound") .replace("{{variation}}", variation) .replace("{{fileName}}", mocksBaseName), }; } } return { message, data: mergeRootDataWithVariationData( helpers.cloneDeep(rootJson), variantJson, ), }; } return { message: { type: "warn", text: t("fileNotFoundLinkIncorrect") .replace("{{filePath}}", mocksBaseName) .replace( "{{component}}", component.paths.mocks.short(global.config.files.mocks.extension[0]) || component.paths.mocks.short(global.config.files.mocks.extension[1]), ), }, data: {}, }; } /** * @param {string} namespacedPath * @returns {object} */ function resolveNamespace(namespacedPath) { if ( !Object.keys(global.config.namespaces).some((namespace) => namespacedPath.startsWith(namespace), ) ) { return { resolved: namespacedPath, }; } const namespace = namespacedPath.split("/")[0]; const resolvedNamespace = global.config.namespaces[namespace]; if (!resolvedNamespace) { return { message: { type: "warn", text: `Could not resolve namespace ${namespace}`, }, resolved: null, }; } return { resolved: namespacedPath.replace( namespace, helpers.getShortPathFromFullPath( path.join(process.cwd(), resolvedNamespace), ), ), }; }