@embeddable.com/sdk-react
Version:
Embeddable SDK React plugin responsible for React components bundling.
1,332 lines (1,303 loc) • 68 kB
JavaScript
import * as path$1 from 'node:path';
import { resolve, join } from 'node:path';
import * as fs$2 from 'fs/promises';
import * as path from 'path';
import * as vite from 'vite';
import ora from 'ora';
import viteReactPlugin from '@vitejs/plugin-react';
import * as fs from 'node:fs/promises';
import { readdir, lstat, readFile, rm } from 'node:fs/promises';
import * as url from 'node:url';
import { GlobalRegistrator } from '@happy-dom/global-registrator';
import 'node:child_process';
import * as crypto from 'node:crypto';
import { z } from 'zod';
import { parse } from '@babel/parser';
import traverse from '@babel/traverse';
import * as fs$1 from 'fs';
import { existsSync } from 'node:fs';
import dts from 'vite-plugin-dts';
var createContext = (pluginRoot, coreCtx) => {
coreCtx["sdk-react"] = {
rootDir: pluginRoot,
templatesDir: resolve(pluginRoot, "templates"),
outputOptions: {
fileName: "embeddable-prepared",
buildName: "embeddable-prepared-build",
componentsEntryPointFilename: "embeddable-entry-point.jsx",
},
};
};
GlobalRegistrator.register({
url: "http://localhost:3000",
width: 1920,
height: 1080,
});
const loadComponentMeta = async (moduleId) => {
const module = await import(url.pathToFileURL(moduleId).href);
return module.meta;
};
/**
* TODO: for some reason code from @embeddable.com/extract-components-config
* is being cached. Thus we use this code duplication in place. Please investigate and fix.
*/
var extractComponentsConfigPlugin = ({ globalKey, outputDir, fileName, componentFileRegex, isDev = false, searchEntry = "", useBundleHash = true, ctx, externalComponents = [], }) => {
let configs = [];
return {
name: "extract-components-config",
moduleParsed: async (moduleInfo) => {
var _a;
if (componentFileRegex.test(moduleInfo.id) &&
((_a = moduleInfo.code) === null || _a === void 0 ? void 0 : _a.includes(searchEntry))) {
try {
const meta = await loadComponentMeta(moduleInfo.id);
const configJSON = buildConfigJson(meta);
configs.push(configJSON);
}
catch (error) {
console.log("Error parsing component meta: ", moduleInfo.id);
console.error(error);
}
}
},
buildEnd: async () => {
for (const [_, externalComponent] of externalComponents) {
const meta = await loadComponentMeta(externalComponent);
configs.push(buildConfigJson(meta));
}
configs.sort((a, b) => a.localeCompare(b));
const contentString = configs.filter(Boolean).join(",\n");
let newFileName = fileName;
const bundleHash = ctx.client.bundleHash;
if (!isDev && bundleHash) {
const fileHashExtension = `-${bundleHash}.js`;
newFileName = `${fileName.replace(".js", fileHashExtension)}`;
}
let template;
if (useBundleHash && bundleHash) {
template = `
globalThis.__EMBEDDABLE__ = globalThis.__EMBEDDABLE__ || {};
globalThis.__EMBEDDABLE__["${bundleHash}"] = globalThis.__EMBEDDABLE__["${bundleHash}"] || {};
globalThis.__EMBEDDABLE__["${bundleHash}"].${globalKey} = globalThis.__EMBEDDABLE__["${bundleHash}"].${globalKey} || [
__PLACEHOLDER__
];
globalThis.__EMBEDDABLE_BUNDLE_HASH__ = "${bundleHash}";
`;
}
else {
template = `
globalThis.__EMBEDDABLE__ = globalThis.__EMBEDDABLE__ || {};
globalThis.__EMBEDDABLE__.${globalKey} = globalThis.__EMBEDDABLE__.${globalKey} || [
__PLACEHOLDER__
];
`;
}
await fs.writeFile(`${outputDir}/${newFileName}`, template.replace("__PLACEHOLDER__", contentString));
configs = [];
},
};
};
const buildConfigJson = (meta) => {
return JSON.stringify(meta, (_key, value) => typeof value === "object" && value !== null && "__embeddableType" in value
? value.toString()
: value);
};
var findFiles = async (initialSrcDir, regex) => {
const filesList = [];
async function findFilesRec(srcDir) {
const allFiles = await readdir(srcDir);
for (const file of allFiles) {
const filePath = join(srcDir, file);
const status = await lstat(filePath);
if (status.isDirectory()) {
await findFilesRec(filePath);
}
const fileName = file.match(regex);
if (fileName) {
filesList.push([fileName[1], filePath]);
}
}
}
await findFilesRec(initialSrcDir);
return filesList;
};
var util;
(function (util) {
util.assertEqual = (val) => val;
function assertIs(_arg) { }
util.assertIs = assertIs;
function assertNever(_x) {
throw new Error();
}
util.assertNever = assertNever;
util.arrayToEnum = (items) => {
const obj = {};
for (const item of items) {
obj[item] = item;
}
return obj;
};
util.getValidEnumValues = (obj) => {
const validKeys = util.objectKeys(obj).filter((k) => typeof obj[obj[k]] !== "number");
const filtered = {};
for (const k of validKeys) {
filtered[k] = obj[k];
}
return util.objectValues(filtered);
};
util.objectValues = (obj) => {
return util.objectKeys(obj).map(function (e) {
return obj[e];
});
};
util.objectKeys = typeof Object.keys === "function" // eslint-disable-line ban/ban
? (obj) => Object.keys(obj) // eslint-disable-line ban/ban
: (object) => {
const keys = [];
for (const key in object) {
if (Object.prototype.hasOwnProperty.call(object, key)) {
keys.push(key);
}
}
return keys;
};
util.find = (arr, checker) => {
for (const item of arr) {
if (checker(item))
return item;
}
return undefined;
};
util.isInteger = typeof Number.isInteger === "function"
? (val) => Number.isInteger(val) // eslint-disable-line ban/ban
: (val) => typeof val === "number" && isFinite(val) && Math.floor(val) === val;
function joinValues(array, separator = " | ") {
return array
.map((val) => (typeof val === "string" ? `'${val}'` : val))
.join(separator);
}
util.joinValues = joinValues;
util.jsonStringifyReplacer = (_, value) => {
if (typeof value === "bigint") {
return value.toString();
}
return value;
};
})(util || (util = {}));
var objectUtil;
(function (objectUtil) {
objectUtil.mergeShapes = (first, second) => {
return {
...first,
...second, // second overwrites first
};
};
})(objectUtil || (objectUtil = {}));
util.arrayToEnum([
"string",
"nan",
"number",
"integer",
"float",
"boolean",
"date",
"bigint",
"symbol",
"function",
"undefined",
"null",
"array",
"object",
"unknown",
"promise",
"void",
"never",
"map",
"set",
]);
const ZodIssueCode = util.arrayToEnum([
"invalid_type",
"invalid_literal",
"custom",
"invalid_union",
"invalid_union_discriminator",
"invalid_enum_value",
"unrecognized_keys",
"invalid_arguments",
"invalid_return_type",
"invalid_date",
"invalid_string",
"too_small",
"too_big",
"invalid_intersection_types",
"not_multiple_of",
"not_finite",
]);
class ZodError extends Error {
get errors() {
return this.issues;
}
constructor(issues) {
super();
this.issues = [];
this.addIssue = (sub) => {
this.issues = [...this.issues, sub];
};
this.addIssues = (subs = []) => {
this.issues = [...this.issues, ...subs];
};
const actualProto = new.target.prototype;
if (Object.setPrototypeOf) {
// eslint-disable-next-line ban/ban
Object.setPrototypeOf(this, actualProto);
}
else {
this.__proto__ = actualProto;
}
this.name = "ZodError";
this.issues = issues;
}
format(_mapper) {
const mapper = _mapper ||
function (issue) {
return issue.message;
};
const fieldErrors = { _errors: [] };
const processError = (error) => {
for (const issue of error.issues) {
if (issue.code === "invalid_union") {
issue.unionErrors.map(processError);
}
else if (issue.code === "invalid_return_type") {
processError(issue.returnTypeError);
}
else if (issue.code === "invalid_arguments") {
processError(issue.argumentsError);
}
else if (issue.path.length === 0) {
fieldErrors._errors.push(mapper(issue));
}
else {
let curr = fieldErrors;
let i = 0;
while (i < issue.path.length) {
const el = issue.path[i];
const terminal = i === issue.path.length - 1;
if (!terminal) {
curr[el] = curr[el] || { _errors: [] };
// if (typeof el === "string") {
// curr[el] = curr[el] || { _errors: [] };
// } else if (typeof el === "number") {
// const errorArray: any = [];
// errorArray._errors = [];
// curr[el] = curr[el] || errorArray;
// }
}
else {
curr[el] = curr[el] || { _errors: [] };
curr[el]._errors.push(mapper(issue));
}
curr = curr[el];
i++;
}
}
}
};
processError(this);
return fieldErrors;
}
static assert(value) {
if (!(value instanceof ZodError)) {
throw new Error(`Not a ZodError: ${value}`);
}
}
toString() {
return this.message;
}
get message() {
return JSON.stringify(this.issues, util.jsonStringifyReplacer, 2);
}
get isEmpty() {
return this.issues.length === 0;
}
flatten(mapper = (issue) => issue.message) {
const fieldErrors = {};
const formErrors = [];
for (const sub of this.issues) {
if (sub.path.length > 0) {
fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || [];
fieldErrors[sub.path[0]].push(mapper(sub));
}
else {
formErrors.push(mapper(sub));
}
}
return { formErrors, fieldErrors };
}
get formErrors() {
return this.flatten();
}
}
ZodError.create = (issues) => {
const error = new ZodError(issues);
return error;
};
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
var errorUtil;
(function (errorUtil) {
errorUtil.errToObj = (message) => typeof message === "string" ? { message } : message || {};
errorUtil.toString = (message) => typeof message === "string" ? message : message === null || message === void 0 ? void 0 : message.message;
})(errorUtil || (errorUtil = {}));
var ZodFirstPartyTypeKind;
(function (ZodFirstPartyTypeKind) {
ZodFirstPartyTypeKind["ZodString"] = "ZodString";
ZodFirstPartyTypeKind["ZodNumber"] = "ZodNumber";
ZodFirstPartyTypeKind["ZodNaN"] = "ZodNaN";
ZodFirstPartyTypeKind["ZodBigInt"] = "ZodBigInt";
ZodFirstPartyTypeKind["ZodBoolean"] = "ZodBoolean";
ZodFirstPartyTypeKind["ZodDate"] = "ZodDate";
ZodFirstPartyTypeKind["ZodSymbol"] = "ZodSymbol";
ZodFirstPartyTypeKind["ZodUndefined"] = "ZodUndefined";
ZodFirstPartyTypeKind["ZodNull"] = "ZodNull";
ZodFirstPartyTypeKind["ZodAny"] = "ZodAny";
ZodFirstPartyTypeKind["ZodUnknown"] = "ZodUnknown";
ZodFirstPartyTypeKind["ZodNever"] = "ZodNever";
ZodFirstPartyTypeKind["ZodVoid"] = "ZodVoid";
ZodFirstPartyTypeKind["ZodArray"] = "ZodArray";
ZodFirstPartyTypeKind["ZodObject"] = "ZodObject";
ZodFirstPartyTypeKind["ZodUnion"] = "ZodUnion";
ZodFirstPartyTypeKind["ZodDiscriminatedUnion"] = "ZodDiscriminatedUnion";
ZodFirstPartyTypeKind["ZodIntersection"] = "ZodIntersection";
ZodFirstPartyTypeKind["ZodTuple"] = "ZodTuple";
ZodFirstPartyTypeKind["ZodRecord"] = "ZodRecord";
ZodFirstPartyTypeKind["ZodMap"] = "ZodMap";
ZodFirstPartyTypeKind["ZodSet"] = "ZodSet";
ZodFirstPartyTypeKind["ZodFunction"] = "ZodFunction";
ZodFirstPartyTypeKind["ZodLazy"] = "ZodLazy";
ZodFirstPartyTypeKind["ZodLiteral"] = "ZodLiteral";
ZodFirstPartyTypeKind["ZodEnum"] = "ZodEnum";
ZodFirstPartyTypeKind["ZodEffects"] = "ZodEffects";
ZodFirstPartyTypeKind["ZodNativeEnum"] = "ZodNativeEnum";
ZodFirstPartyTypeKind["ZodOptional"] = "ZodOptional";
ZodFirstPartyTypeKind["ZodNullable"] = "ZodNullable";
ZodFirstPartyTypeKind["ZodDefault"] = "ZodDefault";
ZodFirstPartyTypeKind["ZodCatch"] = "ZodCatch";
ZodFirstPartyTypeKind["ZodPromise"] = "ZodPromise";
ZodFirstPartyTypeKind["ZodBranded"] = "ZodBranded";
ZodFirstPartyTypeKind["ZodPipeline"] = "ZodPipeline";
ZodFirstPartyTypeKind["ZodReadonly"] = "ZodReadonly";
})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
const errorFormatter = (issues) => {
const errors = [];
for (let issue of issues) {
if (issue.code === ZodIssueCode.invalid_union && issue.unionErrors.length) {
const error = issue.unionErrors[issue.unionErrors.length - 1];
issue = error.issues[error.issues.length - 1];
}
const path = formatErrorPath(issue.path);
const message = issue.message;
errors.push(`"${path}": ${message}`);
}
return errors;
};
const formatErrorPath = (path) => {
let formatted = "";
for (const pathElement of path) {
if (formatted.length === 0) {
formatted = `${pathElement}`;
}
else {
formatted +=
typeof pathElement === "number"
? `[${pathElement}]`
: `.${pathElement}`;
}
}
return formatted;
};
/**
* Get the hash of the content string. It returns the first 5 characters of the hash
* Example: getContentHash("Hello World")
* @param contentString The content string to hash
* @returns
*/
const getContentHash = (contentString) => {
return crypto
.createHash("md5")
.update(contentString)
.digest("hex")
.substring(0, 5);
};
const loadJson = async (filePath) => {
const data = await readFile(filePath, "utf-8");
return JSON.parse(data);
};
const getComponentLibraryConfig = (componentLibrary) => {
let libraryName = componentLibrary;
let include;
const exclude = [];
if (typeof componentLibrary === "object" && componentLibrary !== null) {
libraryName = componentLibrary.name;
if (componentLibrary.include) {
include = [...componentLibrary.include];
}
if (componentLibrary.exclude) {
exclude.push(...componentLibrary.exclude);
}
}
return { libraryName, include, exclude };
};
const EXTERNAL_LIBRARY_META_FILE_NAME = "embeddable-components.json";
const getComponentLibraryMeta = async (ctx, componentLibrary) => {
const { libraryName, include, exclude } = getComponentLibraryConfig(componentLibrary);
const libraryMeta = (await loadJson(getLibraryPath(ctx, libraryName, EXTERNAL_LIBRARY_META_FILE_NAME)));
const filterItems = (items) => {
let result = items;
if (include) {
result = result.filter((item) => include.includes(item));
}
return result.filter((item) => !exclude.includes(item));
};
return {
...libraryMeta,
components: filterItems(libraryMeta.components),
editors: filterItems(libraryMeta.editors),
};
};
const getLibraryPath = (ctx, libraryName, fileName) => path.resolve(ctx.client.rootDir, "node_modules", libraryName, "dist", fileName);
const DEFAULT_LOCALE = "en-US";
const createBuiltInType = (name, typeConfig = {}) => ({
__embeddableType: "built-in",
toString: () => name,
typeConfig: {
label: name,
optionLabel: () => name,
...typeConfig,
},
});
const STRING = "string";
const NUMBER = "number";
const BOOLEAN = "boolean";
const TIME = "time";
const TIME_RANGE = "timeRange";
const GRANULARITY = "granularity";
const DATASET = "dataset";
const MEASURE = "measure";
const DIMENSION = "dimension";
const DIMENSION_OR_MEASURE = "dimensionOrMeasure";
const DEFAULT_NATIVE_TYPES = [
STRING,
NUMBER,
BOOLEAN,
TIME,
TIME_RANGE,
GRANULARITY,
];
const ALL_NATIVE_TYPES = [
...DEFAULT_NATIVE_TYPES,
DATASET,
MEASURE,
DIMENSION,
DIMENSION_OR_MEASURE,
];
const MEASURE_TYPE_STRING = "string";
const MEASURE_TYPE_TIME = "time";
const MEASURE_TYPE_BOOLEAN = "boolean";
const MEASURE_TYPE_NUMBER = "number";
const MEASURE_TYPE_COUNT = "count";
const MEASURE_TYPE_COUNT_DISTINCT = "count_distinct";
const MEASURE_TYPE_COUNT_DISTINCT_APPROX = "count_distinct_approx";
const MEASURE_TYPE_SUM = "sum";
const MEASURE_TYPE_AVG = "avg";
const MEASURE_TYPE_MIN = "min";
const MEASURE_TYPE_MAX = "max";
const MEASURE_TYPES = [
MEASURE_TYPE_STRING,
MEASURE_TYPE_TIME,
MEASURE_TYPE_BOOLEAN,
MEASURE_TYPE_NUMBER,
MEASURE_TYPE_COUNT,
MEASURE_TYPE_COUNT_DISTINCT,
MEASURE_TYPE_COUNT_DISTINCT_APPROX,
MEASURE_TYPE_SUM,
MEASURE_TYPE_AVG,
MEASURE_TYPE_MIN,
MEASURE_TYPE_MAX,
];
const DIMENSION_TYPE_STRING = "string";
const DIMENSION_TYPE_NUMBER = "number";
const DIMENSION_TYPE_BOOLEAN = "boolean";
const DIMENSION_TYPE_GEO = "geo";
const DIMENSION_TYPE_TIME = "time";
const DIMENSION_TYPES = [
DIMENSION_TYPE_STRING,
DIMENSION_TYPE_NUMBER,
DIMENSION_TYPE_BOOLEAN,
DIMENSION_TYPE_GEO,
DIMENSION_TYPE_TIME,
];
[
...DIMENSION_TYPES,
...MEASURE_TYPES,
];
createBuiltInType("string", {
transform: (value) => value,
optionLabel: (value) => Array.isArray(value)
? `[${value.map((v) => `"${v}"`).join(",")}]`
: `"${value}"`,
});
createBuiltInType("number", {
transform: (value) => Array.isArray(value) ? value : value ? Number(value) : value,
optionLabel: (value) => Array.isArray(value)
? `[${value.join(",")}]`
: (value?.toLocaleString(DEFAULT_LOCALE) ?? ""),
});
createBuiltInType("boolean", {
transform: (value) => value === "true" || value === true,
optionLabel: (value) => (value ? "true" : "false"),
});
createBuiltInType("time", {
transform: (value) => {
const date = value?.date ? new Date(value.date) : undefined;
const isValid = date && date.toString() !== "Invalid Date";
return {
date: isValid ? date : undefined,
relativeTimeString: value?.relativeTimeString,
};
},
optionLabel: (value) => {
if (!value)
return "";
if (value?.date) {
return (value.date?.toLocaleDateString(DEFAULT_LOCALE) ??
value.date.toLocaleString());
}
return value.relativeTimeString;
},
});
createBuiltInType("timeRange", {
transform: (value) => {
// Return undefined instead of a null populated object
if (!value)
return undefined;
const [from, to] = [value?.from, value?.to];
const fromDate = new Date(from);
const toDate = new Date(to);
return {
from: fromDate.toString() !== "Invalid Date" ? fromDate : undefined,
to: toDate.toString() !== "Invalid Date" ? toDate : undefined,
relativeTimeString: value?.relativeTimeString,
};
},
optionLabel: (value) => {
if (!value)
return "";
if (value?.from && value?.to) {
return `${value.from?.toLocaleDateString(DEFAULT_LOCALE) ??
value.from?.toLocaleString()},${value.to?.toLocaleDateString(DEFAULT_LOCALE) ??
value.to?.toLocaleString()}`;
}
return value?.relativeTimeString;
},
});
createBuiltInType("granularity", {
transform: (value) => value,
optionLabel: (value) => value,
});
createBuiltInType("dataset");
createBuiltInType("measure");
createBuiltInType("dimension");
createBuiltInType("dimensionOrMeasure");
const embeddableTypeSchema = z
.object({
typeConfig: z.object({
label: z.string(),
toString: z.function(),
}),
})
.or(z.enum(ALL_NATIVE_TYPES));
const editorMetaSchema = z
.object({
name: z.string(),
label: z.string(),
type: embeddableTypeSchema,
config: z.object({}).optional(),
})
.strict();
const editorMetaValidator = (metaInfo) => {
const result = editorMetaSchema.safeParse(metaInfo.meta);
if (result.success)
return [];
return errorFormatter(result.error.issues);
};
const embeddableInputTypeSchema = z
.object({
typeConfig: z.object({
label: z.string(),
toString: z.function(),
}),
})
.or(z.enum(DEFAULT_NATIVE_TYPES));
const embeddableInputSupportedTypesSchema = z
.union([z.enum(DIMENSION_TYPES), z.enum(MEASURE_TYPES)])
.array()
.optional();
const uniqueNameRefinement = (items, refinementContext, message) => {
const names = items.map((item) => item.name);
if (new Set(names).size !== names.length) {
const duplicateNames = names.filter((name, index) => names.indexOf(name) !== index);
refinementContext.addIssue({
code: z.ZodIssueCode.custom,
message: `${message} Duplicate names: ${duplicateNames.join(", ")}`,
});
}
};
const componentMetaSchema = z
.object({
name: z.string(),
label: z.string(),
category: z.string().optional(),
classNames: z.string().array().min(1).optional(),
defaultWidth: z.number().optional(),
defaultHeight: z.number().optional(),
inputs: z
.object({
name: z.string(),
label: z.string(),
description: z.string().optional(),
defaultValue: z.any().optional(),
config: z.record(z.string(), z.any()).optional(),
type: embeddableTypeSchema,
array: z.boolean().optional(),
category: z.string().optional(),
required: z.boolean().optional(),
inputs: z
.object({
name: z.string(),
label: z.string(),
description: z.string().optional(),
defaultValue: z.any().optional(),
config: z.record(z.string(), z.any()).optional(),
type: embeddableInputTypeSchema,
required: z.boolean().optional(),
supportedTypes: embeddableInputSupportedTypesSchema,
})
.superRefine((subInput, ctx) => {
if (subInput.name === "granularity") {
if (!subInput.supportedTypes ||
JSON.stringify(subInput.supportedTypes) !==
JSON.stringify(["time"])) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `A 'granularity' sub-input must have 'supportedTypes' of ['time'].`,
path: ["supportedTypes"],
});
}
if (subInput.type !== "granularity") {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `A 'granularity' sub-input must have a 'type' of 'granularity'.`,
path: ["type"],
});
}
const allowedKeys = [
"name",
"type",
"label",
"defaultValue",
"supportedTypes",
];
const extraKeys = Object.keys(subInput).filter((key) => !allowedKeys.includes(key));
if (extraKeys.length > 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `The 'granularity' sub-input has forbidden properties: ${extraKeys.join(", ")}. Only 'label' and 'defaultValue' are allowed.`,
path: [extraKeys[0]],
});
}
}
})
.array()
.superRefine((inputs, ctx) => uniqueNameRefinement(inputs, ctx, "SubInput names must be unique."))
.optional(),
})
.strict()
.array()
.superRefine((inputs, ctx) => uniqueNameRefinement(inputs, ctx, "Input names must be unique."))
.superRefine((inputs, ctx) => {
for (const input of inputs) {
if (input.type === "dimension" && input.inputs) {
const granularityInput = input.inputs.find((sub) => sub.name === "granularity");
if (granularityInput) {
if (!granularityInput.supportedTypes ||
JSON.stringify(granularityInput.supportedTypes) !==
JSON.stringify(["time"])) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Input '${input.name}': a 'granularity' sub-input must have 'supportedTypes' of ['time'].`,
path: [input.name, "inputs", "granularity", "supportedTypes"],
});
}
if (granularityInput.type !== "granularity") {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Input '${input.name}': the 'granularity' sub-input must have a 'type' of 'granularity'.`,
path: [input.name, "inputs", "granularity", "type"],
});
}
const allowedKeys = [
"name",
"type",
"label",
"defaultValue",
"supportedTypes",
];
const extraKeys = Object.keys(granularityInput).filter((key) => !allowedKeys.includes(key));
if (extraKeys.length > 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Input '${input.name}': the 'granularity' sub-input has forbidden overrides: ${extraKeys.join(", ")}. Only 'label' and 'defaultValue' are allowed.`,
path: [input.name, "inputs", "granularity"],
});
}
}
}
}
})
.optional(),
events: z
.object({
name: z.string(),
label: z.string(),
properties: z
.object({
name: z.string(),
type: embeddableTypeSchema,
array: z.boolean().optional(),
label: z.string().optional(),
})
.array()
.optional(),
})
.strict()
.array()
.optional(),
variables: z
.object({
name: z.string(),
type: embeddableTypeSchema,
array: z.boolean().optional(),
defaultValue: z.any().optional(),
inputs: z.array(z.string()).optional(),
events: z
.array(z.object({
name: z.string(),
property: z.string(),
}))
.optional(),
})
.strict()
.array()
.optional(),
})
.strict()
.superRefine(({ defaultWidth, defaultHeight }, refinementContext) => {
const widthAndHeight = [defaultHeight, defaultWidth].filter(Boolean);
if (widthAndHeight.length === 1) {
refinementContext.addIssue({
code: z.ZodIssueCode.custom,
message: "both defaultWidth and defaultHeight must be set",
path: ["defaultWidth | defaultHeight"],
});
}
});
// @ts-ignore
const babelTraverse = traverse.default;
const parseAndTraverse = (code, visitor) => {
const ast = parse(code || "", {
sourceType: "module",
});
babelTraverse(ast, visitor);
};
const componentMetaValidator = (metaInfo) => {
const result = componentMetaSchema.safeParse(metaInfo.meta);
if (!result.success)
return errorFormatter(result.error.issues);
let moduleNameErrors = validateModuleName(metaInfo);
const variableErrors = validateVariables(metaInfo.meta);
const eventErrors = validateComponentEvents(metaInfo);
const loadDataErrors = validateComponentProps(metaInfo);
return [
...moduleNameErrors,
...variableErrors,
...eventErrors,
...loadDataErrors,
];
};
const validateComponentProps = (metaInfo) => {
const errors = [];
parseAndTraverse(metaInfo.moduleInfo.code, {
ExportDefaultDeclaration: (path) => {
var _a;
const componentConfig = path.node.declaration
.arguments[2];
const propsNode = (_a = componentConfig.properties) === null || _a === void 0 ? void 0 : _a.find((x) => { var _a; return ((_a = x.key) === null || _a === void 0 ? void 0 : _a.name) === "props"; });
// There is no props defined
if (!propsNode)
return;
// if propsNode is a function defined elsewhere
if (propsNode.value.type === "Identifier") {
const functionName = propsNode.value.name;
path
.findParent((path) => path.isProgram())
.traverse({
VariableDeclarator(path) {
if (path.node.id.name === functionName &&
(path.node.init.type === "FunctionExpression" ||
path.node.init.type === "ArrowFunctionExpression")) {
path.node.init.body.body;
}
},
});
}
else {
// assume that propsNode is anonymous function
propsNode.value.body.body;
}
},
});
return errors;
};
const validateComponentEvents = (metaInfo) => {
var _a, _b;
const definedEvents = (_b = (_a = metaInfo.meta.events) === null || _a === void 0 ? void 0 : _a.map((e) => e.name)) !== null && _b !== void 0 ? _b : [];
let implementedEvents = [];
const errors = [];
parseAndTraverse(metaInfo.moduleInfo.code, {
ExportDefaultDeclaration: (path) => {
var _a, _b, _c, _d;
const componentConfig = path.node.declaration
.arguments[2];
const eventsNode = (_a = componentConfig.properties) === null || _a === void 0 ? void 0 : _a.find((p) => { var _a; return ((_a = p.key) === null || _a === void 0 ? void 0 : _a.name) === "events"; });
implementedEvents =
(_d = (_c = (_b = eventsNode === null || eventsNode === void 0 ? void 0 : eventsNode.value) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c.map((p) => p.key.name)) !== null && _d !== void 0 ? _d : [];
},
});
const definedEventsSet = new Set(definedEvents);
const implementedEventsSet = new Set(implementedEvents);
const notImplementedEvents = definedEvents.filter((e) => !implementedEventsSet.has(e));
const notDefinedEvents = implementedEvents.filter((e) => !definedEventsSet.has(e));
for (const notDefinedEvent of notDefinedEvents) {
errors.push(`event '${notDefinedEvent}' is not defined in the component's meta`);
}
for (const notImplementedEvent of notImplementedEvents) {
errors.push(`event '${notImplementedEvent}' is defined but not implemented`);
}
return errors;
};
const validateModuleName = (metaInfo) => {
const basename = path.basename(metaInfo.moduleId);
const fileName = basename.split(".")[0];
if (fileName !== metaInfo.meta.name) {
return [
`meta.name: The name of the .emb.* file and [meta.name]'s value must match. In this case, [meta.name] must be ${fileName}`,
];
}
return [];
};
const validateVariables = (meta) => {
const variables = meta.variables;
if (!variables)
return [];
const errors = [];
// all inputs should be defined
errors.push(...validateVariableInputs(meta));
// all events and properties should be valid
errors.push(...validateVariableEvents(meta));
return errors;
};
const validateVariableInputs = (meta) => {
const variables = meta.variables;
const definedInputs = meta.inputs;
if (!variables)
return [];
const errors = [];
// all inputs should be defined
variables.forEach((variableConfig, idx) => {
const inputs = variableConfig.inputs;
if (inputs) {
inputs.forEach((input, inputIdx) => {
const definedInput = definedInputs === null || definedInputs === void 0 ? void 0 : definedInputs.find((d) => d.name === input);
if (!definedInput) {
const path = formatErrorPath(["variables", idx, "inputs", inputIdx]);
errors.push(`${path}: input "${input}" is not defined`);
}
});
}
});
return errors;
};
const validateVariableEvents = (meta) => {
const variables = meta.variables;
const definedEvents = meta.events;
if (!variables)
return [];
const errors = [];
variables.forEach((variableConfig, idx) => {
const events = variableConfig.events;
if (events) {
events.forEach((event, eventIdx) => {
const definedEvent = definedEvents === null || definedEvents === void 0 ? void 0 : definedEvents.find((d) => d.name === event.name);
if (!definedEvent) {
const path = formatErrorPath([
"variables",
idx,
"events",
eventIdx,
"name",
]);
errors.push(`${path}: event "${event.name}" is not defined`);
return;
}
const definedProperties = definedEvent.properties;
const definedProperty = definedProperties === null || definedProperties === void 0 ? void 0 : definedProperties.find((p) => p.name === event.property);
if (!definedProperty) {
const path = formatErrorPath([
"variables",
idx,
"events",
eventIdx,
"property",
]);
errors.push(`${path}: property "${event.property}" is not defined`);
return;
}
const path = formatErrorPath(["variables", idx]);
if (definedProperty.type !== variableConfig.type) {
errors.push(`${path}: the type of the variable "${variableConfig.name}" and the type of the property "${event.property}" do not match`);
}
if (Boolean(definedProperty.array) !== Boolean(variableConfig.array)) {
errors.push(`${path}: the array of the variable "${variableConfig.name}" and the array of the property "${event.property}" do not match`);
}
});
}
});
return errors;
};
const EDITOR = "editor";
const COMPONENT = "component";
const validators = {
[EDITOR]: editorMetaValidator,
[COMPONENT]: componentMetaValidator,
};
const getModuleType = (moduleInfo) => {
var _a, _b;
if ((_a = moduleInfo.code) === null || _a === void 0 ? void 0 : _a.includes("defineComponent")) {
return COMPONENT;
}
else if ((_b = moduleInfo.code) === null || _b === void 0 ? void 0 : _b.includes("defineEditor")) {
return EDITOR;
}
else {
return null;
}
};
var validateComponentMetaPlugin = (componentFileRegex) => {
let metaConfigs = [];
return {
name: "validate-component-meta",
moduleParsed: async (moduleInfo) => {
const moduleType = componentFileRegex.test(moduleInfo.id)
? getModuleType(moduleInfo)
: null;
if (moduleType) {
const meta = await loadComponentMeta(moduleInfo.id);
metaConfigs.push({
moduleId: moduleInfo.id,
meta,
moduleType,
moduleInfo,
});
}
},
buildEnd: function () {
const errors = [];
for (const metaConfig of metaConfigs) {
const validator = validators[metaConfig.moduleType];
const validationResult = validator(metaConfig);
if (validationResult && validationResult.length) {
errors.push(`\nComponent meta is not valid ${metaConfig.moduleId}:\n${validationResult.join("\n")}`);
}
}
metaConfigs = [];
if (errors.length) {
// @ts-ignore
this.error(errors.join("\n"));
}
},
};
};
/**
* Checks if a given package exists in dependencies or devDependencies of a package.json object.
* @param packageJson The parsed package.json object.
* @param packageName The name of the package to check.
* @returns True if the package exists, false otherwise.
*/
function hasPackage(packageJson, packageName) {
var _a, _b, _c;
return !!((_b = (_a = packageJson.dependencies) === null || _a === void 0 ? void 0 : _a[packageName]) !== null && _b !== void 0 ? _b : (_c = packageJson.devDependencies) === null || _c === void 0 ? void 0 : _c[packageName]);
}
/**
* Modifies entry point only if Emotion is installed in the client's package.json.
* Wraps the app with CacheProvider and creates a custom cache to inject Emotion styles
* into the Shadow DOM or desired container.
*/
const emotionReactEntrypointModifier = {
getContentBegin() {
return `<EmbeddableEmotionCacheProvider value={createCache({ key: 'css', container: rootEl.getRootNode() })}>`;
},
getContentEnd() {
return `</EmbeddableEmotionCacheProvider>`;
},
getImports() {
return `
import { CacheProvider as EmbeddableEmotionCacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';
`;
},
async needToModify(ctx) {
const packageJsonFilePath = path.resolve(ctx.client.rootDir, "package.json");
try {
const packageJson = JSON.parse(await fs.readFile(packageJsonFilePath, "utf-8"));
return !!(hasPackage(packageJson, "@emotion/react") ||
hasPackage(packageJson, "react-select"));
}
catch (error) {
console.error("emotionReactEntrypointModifier: Error reading package.json", error);
return false;
}
},
};
/**
* Modifies entry point only if there is 'styled-components' in the client's package.json.
* Imports StyleSheetManager from styled-components and adds shadow root as "target".
* That will force styled-components to add classes to the customers web-component
* see https://styled-components.com/docs/api#stylesheetmanager
*/
const styledComponentsEntrypointModifier = {
getContentBegin() {
return "<StyleSheetManager target={rootEl.getRootNode()}>";
},
getContentEnd() {
return "</StyleSheetManager>";
},
getImports() {
return "import {StyleSheetManager} from 'styled-components'";
},
async needToModify(ctx) {
const packageJsonFilePath = path.resolve(ctx.client.rootDir, "package.json");
try {
const packageJson = JSON.parse(await fs.readFile(packageJsonFilePath, "utf-8"));
return !!hasPackage(packageJson, "styled-components");
}
catch (error) {
console.error("styledComponentsEntrypointModifier: Error reading package.json", error);
return false;
}
},
};
const entrypointModifiers = [
styledComponentsEntrypointModifier,
emotionReactEntrypointModifier,
];
function findFilesWithWrongUsage(directory, pattern, mustEndWith = []) {
const files = fs$1.readdirSync(directory);
const result = [];
files.forEach((file) => {
const filePath = path.join(directory, file);
if (fs$1.statSync(filePath).isDirectory()) {
// Recursively check files in subdirectories
result.push(...findFilesWithWrongUsage(filePath, pattern, mustEndWith));
}
else {
const fileContent = fs$1.readFileSync(filePath, "utf8");
if (fileContent.match(pattern)) {
result.push(filePath);
}
}
});
return result.filter((file) => !mustEndWith.some((ending) => file.endsWith(ending)));
}
function findTypeNames(directory) {
const files = fs$1.readdirSync(directory);
const typeNames = [];
files.forEach((file) => {
const filePath = path.join(directory, file);
if (fs$1.statSync(filePath).isDirectory()) {
// Recursively check files in subdirectories
typeNames.push(...findTypeNames(filePath));
}
else {
const fileContent = fs$1.readFileSync(filePath, "utf8");
const regexDefineTypeFunctionTypeName = /defineType\(["'](.*?)["']\,/g;
const matches = fileContent.match(regexDefineTypeFunctionTypeName) || [];
const extractedContent = matches.map((match) => match.slice(12, -2))[0];
if (extractedContent) {
typeNames.push({ file: filePath, typeName: extractedContent });
}
}
});
return typeNames;
}
const regexDefineComponentFunction = /defineComponent(<\w+>)?\(/;
const regexDefineTypeFunction = /defineType\(/;
const regexDefineOptionFunction = /defineOption\(/;
const regexCubeFunction = /cube\(/;
const regexCubes = /cubes:/;
const usageValidator = async (directory, validateCube = true) => {
let isValid = true;
const progress = ora("React: function usage validating...").start();
// defineComponent function
const filesWithWrongDefineComponentFunctionUsage = findFilesWithWrongUsage(directory, regexDefineComponentFunction, [".emb.js", ".emb.ts"]);
if (filesWithWrongDefineComponentFunctionUsage.length) {
isValid = false;
progress.fail("React: defineComponent() usage is only allowed inside files with the extension .emb.(js|ts) files.\nFix the following files:\n" +
filesWithWrongDefineComponentFunctionUsage.join("\n"));
}
// defineType function
const filesWithWrongDefineTypeFunctionUsage = findFilesWithWrongUsage(directory, regexDefineTypeFunction, [".type.emb.js", ".type.emb.ts"]);
if (filesWithWrongDefineTypeFunctionUsage.length) {
isValid = false;
progress.fail("React: defineType() usage is only allowed inside files with the extension .type.emb.(js|ts) files.\nFix the following files:\n" +
filesWithWrongDefineTypeFunctionUsage.join("\n"));
}
// defineOption function
const filesWithWrongDefineOptionFunctionUsage = findFilesWithWrongUsage(directory, regexDefineOptionFunction, [".type.emb.js", ".type.emb.ts"]);
if (filesWithWrongDefineOptionFunctionUsage.length) {
isValid = false;
progress.fail("React: defineOption() usage is only allowed inside files with the extension .type.emb.(js|ts) files.\nFix the following files:\n" +
filesWithWrongDefineOptionFunctionUsage.join("\n"));
}
if (validateCube) {
// cube function
const filesWithWrongCubeFunctionUsage = findFilesWithWrongUsage(directory, regexCubeFunction, [".cube.js", ".cube.ts"]);
if (filesWithWrongCubeFunctionUsage.length) {
isValid = false;
progress.fail("React: cube() usage is only allowed inside files with the extension .cube.(ts|js) files.\nFix the following files:\n" +
filesWithWrongCubeFunctionUsage.join("\n"));
}
// cube in yml or yaml files
const filesWithWrongCubeUsage = findFilesWithWrongUsage(directory, regexCubes, [".cube.yml", ".cube.yaml"]);
if (filesWithWrongCubeUsage.length) {
isValid = false;
progress.fail("React: cubes: usage is only allowed inside files with the extension .cube.(yml|yaml) files.\nFix the following files:\n" +
filesWithWrongCubeUsage.join("\n"));
}
}
const typeNames = findTypeNames(directory);
// check for definedTypes using a native typeNames
const duplicatedNativeTypeNames = typeNames.filter((item) => ALL_NATIVE_TYPES.includes(item.typeName));
if (duplicatedNativeTypeNames.length) {
isValid = false;
progress.fail("React: defineType() can't be used with the same typeName as the nativeTypes. Fix the following files:\n" +
duplicatedNativeTypeNames
.map((dtn) => `${dtn.typeName} (file: ${dtn.file})`)
.join("\n"));
}
// check for duplicated typeNames
const duplicatedTypeNames = typeNames.filter((item, index) => {
return typeNames.some((otherItem, otherIndex) => {
if (index !== otherIndex) {
return item.typeName === otherItem.typeName;
}
return false;
});
});
if (duplicatedTypeNames.length) {
isValid = false;
progress.fail("React: defineType() must be used with a unique typeName. Fix the following files:\n" +
duplicatedTypeNames
.map((dtn) => `${dtn.typeName} (file: ${dtn.file})`)
.join("\n"));
}
isValid
? progress.succeed("React: function usage validated")
: process.exit(1);
};
const validateComponentDuplicates = (filesList, additionalComponents, ora) => {
const progress = ora("React: validating component names...").start();
const errors = [];
// Ensure filesList components are unique
const filesSet = new Set();
for (const [componentName] of filesList) {
if (filesSet.has(componentName)) {
errors.push(`Error: Component '${componentName}' must be unique.`);
}
else {
filesSet.add(componentName);
}
}
// Ensure no filesList component exists in additionalComponents values
for (const [componentName] of filesList) {
for (const [library, components] of Object.entries(additionalComponents)) {
if (components.includes(componentName)) {
errors.push(`Error: Component '${componentName}' must be unique but is already present in library '${library}'.`);
}
}
}
// Ensure additionalComponents values are unique across different keys
const componentToLibraries = {};
for (const [library, components] of Object.entries(additionalComponents)) {
for (const component of components) {
if (!componentToLibraries[component]) {
componentToLibraries[component] = [];
}
componentToLibraries[component].push(library);
}
}
for (const [component, libraries] of Object.entries(componentToLibraries)) {
if (libraries.length > 1) {
errors.push(`Error: Component '${component}' is declared in multiple libraries: ${libraries.join(", ")}.`);
}
}
if (errors.length > 0) {
progress.fail("Validation errors found:\n");
console.error(errors.join("\n"));
}
else {
progress.succeed("Validation completed successfully. No issues found.");
}
return errors;
};
const EMB_FILE_REGEX = /^(.*)(?<!\.type|\.options)\.emb\.[jt]s$/;
var generate = async (ctx) => {
var _a;
await usageValidator(ctx.client.srcDir);
const filesList = await findFiles(ctx.client.srcDir, EMB_FILE_REGEX);
const additionalComponents = await getAdditionalComponents$1(ctx);
const errors = validateComponentDuplicates(filesList, additionalComponents, ora);
if (errors.length) {
process.exit(1);
}
await prepareEntrypoint(ctx, filesList, additionalComponents);
let result;
if ((_a = ctx.dev) === null || _a === void 0 ? void 0 : _a.watch) {
result = await runViteWatch(ctx);
}
else {
const progress = ora("React: building components...").start();
result = await runViteBuild(ctx);
progress.succeed("Building React components completed");
}
return result;
};
function getViteConfig(ctx, watch, plugins) {
var _a;
return {
logLevel: (!!watch ? "info" : "error"),
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],
...(_a = ctx.client.vi