@communities-webruntime/services
Version:
If you would like to run Lightning Web Runtime without the CLI, we expose some of our programmatic APIs available in Node.js. If you're looking for the CLI documentation [you can find that here](https://www.npmjs.com/package/@communities-webruntime/cli).
226 lines • 7.66 kB
JavaScript
/**
* Copyright (c) 2019, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
import crypto from 'crypto';
import { existsSync } from 'fs';
import { dirname } from 'path';
import { unparse as unparseUUID } from 'uuid-parse';
import { ContextService } from '../context/context-service.js';
import { readFolder, readJsonFile } from '../utils/files.js';
import { findInTree } from '../utils/tree.js';
import { assert } from '../utils/assert.js';
import { validate as validateMetadata } from './metadata-validation.js';
import { getBrandingTokenMap as getBrandingTokenMapFromProperties } from './process-branding.js';
const { getContext } = ContextService;
/**
* Get all the context template routes
*/
function getRoutes() {
const { routes } = getContext();
if (typeof routes === 'object') {
return routes;
}
return readJsonFile(routes);
}
/**
* Get the context template theme
*/
function getTheme() {
const { theme } = getContext();
if (typeof theme === 'object') {
return theme;
}
return readJsonFile(theme);
}
/**
* Get all the context template labels
*/
function getLabels() {
const { labels } = getContext();
if (typeof labels === 'object') {
return labels;
}
return (labels && readJsonFile(labels)) || {};
}
/**
* Get all the context template locales
*/
function getLocales() {
const { locales } = getContext();
if (typeof locales === 'object') {
return locales;
}
return locales && existsSync(locales) ? readJsonFile(locales) : [];
}
/**
* Get all the context template branding properties
*/
function getBrandingProperties() {
const { theme: themeConfig } = getContext();
let brandingProperties;
const theme = getTheme();
if (typeof themeConfig === 'object') {
brandingProperties = (theme && theme.branding) || [];
}
else {
const dir = dirname(themeConfig);
brandingProperties =
(theme && theme.branding && readJsonFile(`${dir}/${theme.branding}`)) || [];
}
return brandingProperties;
}
function getBrandingTokenMap() {
return getBrandingTokenMapFromProperties(getBrandingProperties());
}
/**
* Compute components and regions uuids for design mode.
*
* Ids are a hash of the concatenation of the view name
* and the cmp/region (deep) string representation.
*
* The resulting 128 bits hash is unparsed as a UUID.
*
* The view is mutated in place.
*
* @param {object} view the view to add uuids to
* @returns {object} the mutated view
*/
function updateViewWithUuids(view) {
// compute a uuid for a given region or component
function uuid(node) {
return unparseUUID(crypto
.createHash('md5')
.update(`${view.devName}:${JSON.stringify(node, (key, value) => {
// filter out uuid and region properties cause they would impact the hash
return ['uuid', 'region'].includes(key) ? undefined : value;
})}`)
.digest());
}
// update a region recursively
function updateRegionWithUuids(region) {
// First, generate region key
region.uuid = region.key ? region.key : uuid(region);
if (region.components) {
region.components.forEach((cmp) => {
cmp.region = region.slotName;
if (cmp.regions) {
// recursively generate keys for each inner component region
cmp.regions.forEach(updateRegionWithUuids);
}
// generate a key for the component itself
cmp.uuid = uuid(cmp);
});
}
}
// if the view has a corresponding component and this component has inner regions, generate
// keys for each region
if (view.component && view.component.regions) {
view.component.regions.forEach(updateRegionWithUuids);
}
return view;
}
function getView(devName) {
const { views, isDesignMode } = getContext();
const view = typeof views === 'object' ? views[devName] : readJsonFile(`${views}/${devName}.json`);
assert(view, `Cannot find view '${devName}'`);
// Compute components and regions ids for design mode.
// Ids are a hash of the concatenation of the view name
// and the cmp/region (deep) string representation
if (isDesignMode) {
return updateViewWithUuids(view);
}
return view;
}
function getAllViews(routes = getRoutes(), theme = getTheme()) {
// using `[].concat(routes)` allows the caller to pass either a single route (the current route for example)
// or an array of routes
const routesArr = [].concat(routes || []);
// get all route views
const routeViewDevNames = routesArr.filter((route) => route.view).map((route) => route.view);
const routeViews = routeViewDevNames.map(getView);
// get all the theme layout views
const themeLayoutViewDevNames = routeViews
.filter((view) => view.themeLayoutType)
.map((view) => view.themeLayoutType)
.filter((themeLayoutType) => {
return theme.themeLayouts[themeLayoutType] && theme.themeLayouts[themeLayoutType].view;
})
.map((themeLayoutType) => theme.themeLayouts[themeLayoutType].view);
// filter out unique views
const allUniqueViewIds = [...new Set(routeViewDevNames.concat(themeLayoutViewDevNames))];
return allUniqueViewIds.map(getView);
}
/**
* Get all the HTML partials from the template's partials folder.
* Result is in format of `{[fileNameWithoutExtension] : stringContentOfFile }`
*/
function getPartials() {
const extension = '.html';
const { partials } = getContext();
if (typeof partials === 'object') {
return partials;
}
const fileMap = readFolder(partials, extension);
return Object.keys(fileMap).reduce((result, key) => {
result[key.slice(0, -extension.length)] = fileMap[key];
return result;
}, {});
}
/**
* Get all views which are routed
*/
function getViewToThemeLayoutMap() {
const viewToThemeLayoutMap = {};
const theme = getTheme();
getRoutes().forEach((route) => {
const view = getView(route.view);
const themeLayout = theme.themeLayouts[view.themeLayoutType];
viewToThemeLayoutMap[route.view] = themeLayout.view;
});
return viewToThemeLayoutMap;
}
function validate(schema = 'communities') {
return validateMetadata({
routes: getRoutes(),
labels: getLabels(),
locales: getLocales(),
brandingProperties: getBrandingProperties(),
theme: getTheme(),
views: getAllViews(),
partials: getPartials(),
}, schema);
}
/**
* Returns the component with the given UUID among all the views.
*
* This is only available in design mode.
*
* @param uuid the UUID of the component to find
* @returns the component, or `undefined` if the component cannot be found
* @throws an error is not in design mode
*/
function getComponent(uuid) {
const { isDesignMode } = getContext();
assert(isDesignMode, 'Can only get components by UUIDs in design mode');
return getAllViews().reduce((c, view) => {
return c || findInTree(view, (cmp) => cmp && cmp.uuid === uuid);
}, undefined);
}
export const MetadataService = {
getRoutes,
getTheme,
getBrandingProperties,
getBrandingTokenMap,
getComponent,
getLabels,
getLocales,
getView,
getViewToThemeLayoutMap,
getAllViews,
getPartials,
validate,
};
//# sourceMappingURL=metadata-service.js.map