sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
417 lines (416 loc) • 19.8 kB
JavaScript
;
var node_worker_threads = require("node:worker_threads"), jsxRuntime = require("react/jsx-runtime"), DOMPurify = require("isomorphic-dompurify"), startCase = require("lodash/startCase"), server = require("react-dom/server"), sanity = require("sanity"), styledComponents = require("styled-components"), reactCompilerRuntime = require("react-compiler-runtime"), ui = require("@sanity/ui"), theme$1 = require("@sanity/ui/theme"), react = require("react"), reactIs = require("react-is"), getStudioWorkspaces = require("../../../_chunks-cjs/getStudioWorkspaces.js"), mockBrowserEnvironment = require("../../../_chunks-cjs/mockBrowserEnvironment.js");
function _interopDefaultCompat(e) {
return e && typeof e == "object" && "default" in e ? e : { default: e };
}
var DOMPurify__default = /* @__PURE__ */ _interopDefaultCompat(DOMPurify), startCase__default = /* @__PURE__ */ _interopDefaultCompat(startCase);
const theme = theme$1.buildTheme(), SchemaIcon = (t0) => {
const $ = reactCompilerRuntime.c(6), {
icon,
title,
subtitle
} = t0;
let t1;
$[0] !== icon || $[1] !== subtitle || $[2] !== title ? (t1 = normalizeIcon(icon, title, subtitle), $[0] = icon, $[1] = subtitle, $[2] = title, $[3] = t1) : t1 = $[3];
let t2;
return $[4] !== t1 ? (t2 = /* @__PURE__ */ jsxRuntime.jsx(ui.ThemeProvider, { theme, children: t1 }), $[4] = t1, $[5] = t2) : t2 = $[5], t2;
};
function normalizeIcon(Icon, title, subtitle = "") {
return reactIs.isValidElementType(Icon) ? /* @__PURE__ */ jsxRuntime.jsx(Icon, {}) : react.isValidElement(Icon) ? Icon : sanity.createDefaultIcon(title, subtitle);
}
const DEFAULT_IMAGE_FIELDS = ["asset", "hotspot", "crop", "media"], DEFAULT_FILE_FIELDS = ["asset", "media"], DEFAULT_GEOPOINT_FIELDS = ["lat", "lng", "alt"], DEFAULT_SLUG_FIELDS = ["current", "source"];
function getCustomFields(type) {
const fields = type.fieldsets ? type.fieldsets.flatMap((fs) => fs.single ? fs.field : fs.fields.map((field) => ({
...field,
fieldset: fs.name
}))) : type.fields;
return isType(type, "block") ? [] : isType(type, "slug") ? fields.filter((f) => !DEFAULT_SLUG_FIELDS.includes(f.name)) : isType(type, "geopoint") ? fields.filter((f) => !DEFAULT_GEOPOINT_FIELDS.includes(f.name)) : isType(type, "image") ? fields.filter((f) => !DEFAULT_IMAGE_FIELDS.includes(f.name)) : isType(type, "file") ? fields.filter((f) => !DEFAULT_FILE_FIELDS.includes(f.name)) : fields;
}
function isReference(type) {
return isType(type, "reference");
}
function isCrossDatasetReference(type) {
return isType(type, "crossDatasetReference");
}
function isGlobalDocumentReference(type) {
return isType(type, "globalDocumentReference");
}
function isObjectField(maybeOjectField) {
return typeof maybeOjectField == "object" && maybeOjectField !== null && "name" in maybeOjectField;
}
function isCustomized(maybeCustomized) {
const internalOwnProps = getSchemaTypeInternalOwnProps(maybeCustomized);
return isObjectField(maybeCustomized) && !isReference(maybeCustomized) && !isCrossDatasetReference(maybeCustomized) && !isGlobalDocumentReference(maybeCustomized) && "fields" in maybeCustomized && Array.isArray(maybeCustomized.fields) && // needed to differentiate inline, named array object types from globally defined types
// we only consider it customized if the _definition_ has fields declared
// this holds for all customizable object-like types: object, document, image and file
internalOwnProps?.fields ? !!getCustomFields(maybeCustomized).length : !1;
}
function isType(schemaType, typeName) {
return schemaType.name === typeName ? !0 : schemaType.type ? isType(schemaType.type, typeName) : !1;
}
function isDefined(value) {
return value != null;
}
function isRecord(value) {
return !!value && typeof value == "object";
}
function isPrimitive(value) {
return isString(value) || isBoolean(value) || isNumber(value);
}
function isString(value) {
return typeof value == "string";
}
function isNumber(value) {
return typeof value == "boolean";
}
function isBoolean(value) {
return typeof value == "number";
}
function getSchemaTypeInternalOwnProps(type) {
return type?._internal_ownProps;
}
function getDefinedTypeName(type) {
return getSchemaTypeInternalOwnProps(type)?.type;
}
const HTML_TAGS = ["img", "style"], SVG_TAGS = ["svg", "a", "altglyph", "altglyphdef", "altglyphitem", "animatecolor", "animatemotion", "animatetransform", "circle", "clippath", "defs", "desc", "ellipse", "filter", "font", "g", "glyph", "glyphref", "hkern", "image", "line", "lineargradient", "marker", "mask", "metadata", "mpath", "path", "pattern", "polygon", "polyline", "radialgradient", "rect", "stop", "style", "switch", "symbol", "text", "textpath", "title", "tref", "tspan", "view", "vkern"], SVG_FILTER_TAGS = ["feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence"], ALLOWED_TAGS = [...SVG_TAGS, ...HTML_TAGS, ...SVG_FILTER_TAGS], HTML_ATTRIBUTES = ["alt", "class", "crossorigin", "decoding", "elementtiming", "fetchpriority", "height", "loading", "src", "srcset", "style", "width"], SVG_ATTRIBUTES = ["accent-height", "accumulate", "additive", "alignment-baseline", "amplitude", "ascent", "attributename", "attributetype", "azimuth", "basefrequency", "baseline-shift", "begin", "bias", "by", "class", "clip", "clippathunits", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "cx", "cy", "d", "dx", "dy", "diffuseconstant", "direction", "display", "divisor", "dur", "edgemode", "elevation", "end", "exponent", "fill", "fill-opacity", "fill-rule", "filter", "filterunits", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "fx", "fy", "g1", "g2", "glyph-name", "glyphref", "gradientunits", "gradienttransform", "height", "href", "id", "image-rendering", "in", "in2", "intercept", "k", "k1", "k2", "k3", "k4", "kerning", "keypoints", "keysplines", "keytimes", "lang", "lengthadjust", "letter-spacing", "kernelmatrix", "kernelunitlength", "lighting-color", "local", "marker-end", "marker-mid", "marker-start", "markerheight", "markerunits", "markerwidth", "maskcontentunits", "maskunits", "max", "mask", "media", "method", "mode", "min", "name", "numoctaves", "offset", "operator", "opacity", "order", "orient", "orientation", "origin", "overflow", "paint-order", "path", "pathlength", "patterncontentunits", "patterntransform", "patternunits", "points", "preservealpha", "preserveaspectratio", "primitiveunits", "r", "rx", "ry", "radius", "refx", "refy", "repeatcount", "repeatdur", "restart", "result", "rotate", "scale", "seed", "shape-rendering", "slope", "specularconstant", "specularexponent", "spreadmethod", "startoffset", "stddeviation", "stitchtiles", "stop-color", "stop-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke", "stroke-width", "style", "surfacescale", "systemlanguage", "tabindex", "tablevalues", "targetx", "targety", "transform", "transform-origin", "text-anchor", "text-decoration", "text-rendering", "textlength", "type", "u1", "u2", "unicode", "values", "viewbox", "visibility", "version", "vert-adv-y", "vert-origin-x", "vert-origin-y", "width", "word-spacing", "wrap", "writing-mode", "xchannelselector", "ychannelselector", "x", "x1", "x2", "xmlns", "y", "y1", "y2", "z", "zoomandpan"], ALLOWED_ATTR = [...SVG_ATTRIBUTES, ...HTML_ATTRIBUTES], config = {
ALLOWED_ATTR,
ALLOWED_TAGS,
/**
* Required to allow for the use of `style` tags,
* namely rendering the style tags from `styled-components`
*/
FORCE_BODY: !0
}, MAX_CUSTOM_PROPERTY_DEPTH = 5;
function extractCreateWorkspaceManifest(workspace) {
const serializedSchema = extractManifestSchemaTypes(workspace.schema), serializedTools = extractManifestTools(workspace.tools);
return {
name: workspace.name,
title: workspace.title,
subtitle: workspace.subtitle,
basePath: workspace.basePath,
projectId: workspace.projectId,
dataset: workspace.dataset,
icon: resolveIcon({
icon: workspace.icon,
title: workspace.title,
subtitle: workspace.subtitle
}),
mediaLibrary: workspace.mediaLibrary,
schema: serializedSchema,
tools: serializedTools
};
}
function extractManifestSchemaTypes(schema) {
const typeNames = schema.getTypeNames(), studioDefaultTypeNames = sanity.createSchema({
name: "default",
types: []
}).getTypeNames();
return typeNames.filter((typeName) => !studioDefaultTypeNames.includes(typeName)).map((typeName) => schema.get(typeName)).filter((type) => typeof type < "u").map((type) => transformType(type));
}
function transformCommonTypeFields(type, typeName, context) {
const arrayProps = typeName === "array" && type.jsonType === "array" ? transformArrayMember(type) : {}, referenceProps = isReference(type) ? transformReference(type) : {}, crossDatasetRefProps = isCrossDatasetReference(type) ? transformCrossDatasetReference(type) : {}, globalRefProps = isGlobalDocumentReference(type) ? transformGlobalDocumentReference(type) : {}, objectFields = type.jsonType === "object" && type.type && isCustomized(type) ? {
fields: getCustomFields(type).map((objectField) => transformField(objectField))
} : {};
return {
...retainCustomTypeProps(type),
...transformValidation(type.validation),
...ensureString("description", type.description),
...objectFields,
...arrayProps,
...referenceProps,
...crossDatasetRefProps,
...globalRefProps,
...ensureConditional("readOnly", type.readOnly),
...ensureConditional("hidden", type.hidden),
...transformFieldsets(type),
// fieldset prop gets instrumented via getCustomFields
...ensureString("fieldset", type.fieldset),
...transformBlockType(type)
};
}
function transformFieldsets(type) {
if (type.jsonType !== "object")
return {};
const fieldsets = type.fieldsets?.filter((fs) => !fs.single).map((fs) => {
const options = isRecord(fs.options) ? {
options: retainSerializableProps(fs.options)
} : {};
return {
name: fs.name,
...ensureCustomTitle(fs.name, fs.title),
...ensureString("description", fs.description),
...ensureConditional("readOnly", fs.readOnly),
...ensureConditional("hidden", fs.hidden),
...options
};
});
return fieldsets?.length ? {
fieldsets
} : {};
}
function transformType(type, context) {
const typeName = type.type ? type.type.name : type.jsonType;
return {
...transformCommonTypeFields(type, typeName),
name: type.name,
type: typeName,
...ensureCustomTitle(type.name, type.title)
};
}
function retainCustomTypeProps(type) {
const manuallySerializedFields = [
//explicitly added
"name",
"title",
"description",
"readOnly",
"hidden",
"validation",
"fieldsets",
"fields",
"to",
"of",
// not serialized
"type",
"jsonType",
"__experimental_actions",
"__experimental_formPreviewTitle",
"__experimental_omnisearch_visibility",
"__experimental_search",
"components",
"icon",
"orderings",
"preview",
"groups",
//only exists on fields
"group"
// we know about these, but let them be generically handled
// deprecated
// rows (from text)
// initialValue
// options
// crossDatasetReference props
], typeWithoutManuallyHandledFields = Object.fromEntries(Object.entries(type).filter(([key]) => !manuallySerializedFields.includes(key)));
return retainSerializableProps(typeWithoutManuallyHandledFields);
}
function retainSerializableProps(maybeSerializable, depth = 0) {
if (!(depth > MAX_CUSTOM_PROPERTY_DEPTH) && isDefined(maybeSerializable)) {
if (isPrimitive(maybeSerializable))
return maybeSerializable === "" ? void 0 : maybeSerializable;
if (maybeSerializable instanceof RegExp)
return maybeSerializable.toString();
if (Array.isArray(maybeSerializable)) {
const arrayItems = maybeSerializable.map((item) => retainSerializableProps(item, depth + 1)).filter((item) => isDefined(item));
return arrayItems.length ? arrayItems : void 0;
}
if (isRecord(maybeSerializable)) {
const serializableEntries = Object.entries(maybeSerializable).map(([key, value]) => [key, retainSerializableProps(value, depth + 1)]).filter(([, value]) => isDefined(value));
return serializableEntries.length ? Object.fromEntries(serializableEntries) : void 0;
}
}
}
function transformField(field, context) {
const fieldType = field.type, typeName = getDefinedTypeName(fieldType) ?? fieldType.name;
return {
...transformCommonTypeFields(fieldType, typeName),
name: field.name,
type: typeName,
...ensureCustomTitle(field.name, fieldType.title),
// this prop gets added synthetically via getCustomFields
...ensureString("fieldset", field.fieldset)
};
}
function transformArrayMember(arrayMember, context) {
return {
of: arrayMember.of.map((type) => {
const typeName = getDefinedTypeName(type) ?? type.name;
return {
...transformCommonTypeFields(type, typeName),
type: typeName,
...typeName === type.name ? {} : {
name: type.name
},
...ensureCustomTitle(type.name, type.title)
};
})
};
}
function transformReference(reference) {
return {
to: (reference.to ?? []).map((type) => ({
...retainCustomTypeProps(type),
type: type.name
}))
};
}
function transformCrossDatasetReference(reference) {
return {
to: (reference.to ?? []).map((crossDataset) => {
const preview = crossDataset.preview?.select ? {
preview: {
select: crossDataset.preview.select
}
} : {};
return {
type: crossDataset.type,
...ensureCustomTitle(crossDataset.type, crossDataset.title),
...preview
};
})
};
}
function transformGlobalDocumentReference(reference) {
return {
to: (reference.to ?? []).map((crossDataset) => {
const preview = crossDataset.preview?.select ? {
preview: {
select: crossDataset.preview.select
}
} : {};
return {
type: crossDataset.type,
...ensureCustomTitle(crossDataset.type, crossDataset.title),
...preview
};
})
};
}
const transformTypeValidationRule = (rule) => ({
...rule,
constraint: "constraint" in rule && (typeof rule.constraint == "string" ? rule.constraint.toLowerCase() : retainSerializableProps(rule.constraint))
}), validationRuleTransformers = {
type: transformTypeValidationRule
};
function transformValidation(validation) {
const validationArray = (Array.isArray(validation) ? validation : [validation]).filter((value) => typeof value == "object" && "_type" in value), disallowedFlags = ["type"], disallowedConstraintTypes = [sanity.ConcreteRuleClass.FIELD_REF], serializedValidation = validationArray.map(({
_rules,
_message,
_level
}) => {
const message = typeof _message == "string" ? {
message: _message
} : {};
return {
rules: _rules.filter((rule) => {
if (!("constraint" in rule))
return !1;
const {
flag,
constraint
} = rule;
return disallowedFlags.includes(flag) ? !1 : !(typeof constraint == "object" && "type" in constraint && disallowedConstraintTypes.includes(constraint.type));
}).reduce((rules, rule) => {
const transformedRule = (validationRuleTransformers[rule.flag] ?? ((spec) => retainSerializableProps(spec)))(rule);
return transformedRule ? [...rules, transformedRule] : rules;
}, []),
level: _level,
...message
};
}).filter((group) => !!group.rules.length);
return serializedValidation.length ? {
validation: serializedValidation
} : {};
}
function ensureCustomTitle(typeName, value) {
const titleObject = ensureString("title", value), defaultTitle = startCase__default.default(typeName);
return titleObject.title === defaultTitle ? {} : titleObject;
}
function ensureString(key, value) {
return typeof value == "string" ? {
[key]: value
} : {};
}
function ensureConditional(key, value) {
return typeof value == "boolean" ? {
[key]: value
} : typeof value == "function" ? {
[key]: "conditional"
} : {};
}
function transformBlockType(blockType, context) {
if (blockType.jsonType !== "object" || !isType(blockType, "block"))
return {};
const childrenField = blockType.fields?.find((field) => field.name === "children");
if (!childrenField)
return {};
const ofType = childrenField.type.of;
if (!ofType)
return {};
const spanType = ofType.find((memberType) => memberType.name === "span");
if (!spanType)
return {};
const inlineObjectTypes = ofType.filter((memberType) => memberType.name !== "span") || [];
return {
marks: {
annotations: spanType.annotations.map((t) => transformType(t)),
decorators: resolveEnabledDecorators(spanType)
},
lists: resolveEnabledListItems(blockType),
styles: resolveEnabledStyles(blockType),
of: inlineObjectTypes.map((t) => transformType(t))
};
}
function resolveEnabledStyles(blockType) {
const styleField = blockType.fields?.find((btField) => btField.name === "style");
return resolveTitleValueArray(styleField?.type?.options?.list);
}
function resolveEnabledDecorators(spanType) {
return "decorators" in spanType ? resolveTitleValueArray(spanType.decorators) : void 0;
}
function resolveEnabledListItems(blockType) {
const listField = blockType.fields?.find((btField) => btField.name === "listItem");
return resolveTitleValueArray(listField?.type?.options?.list);
}
function resolveTitleValueArray(possibleArray) {
if (!possibleArray || !Array.isArray(possibleArray))
return;
const titledValues = possibleArray.filter((d) => isRecord(d) && !!d.value && isString(d.value)).map((item) => ({
value: item.value,
...ensureString("title", item.title)
}));
if (titledValues?.length)
return titledValues;
}
const extractManifestTools = (tools) => tools.map((tool) => {
const {
title,
name,
icon,
__internalApplicationType: type
} = tool;
return {
title,
name,
type: type || null,
icon: resolveIcon({
icon,
title
})
};
}), resolveIcon = (props) => {
const sheet = new styledComponents.ServerStyleSheet();
try {
const element = server.renderToString(sheet.collectStyles(/* @__PURE__ */ jsxRuntime.jsx(SchemaIcon, { ...props }))), html = `${sheet.getStyleTags()}${element}`.trim();
return DOMPurify__default.default.sanitize(html, config);
} catch {
return null;
} finally {
sheet.seal();
}
};
async function main() {
if (node_worker_threads.isMainThread || !node_worker_threads.parentPort)
throw new Error("This module must be run as a worker thread");
const opts = node_worker_threads.workerData, cleanup = mockBrowserEnvironment.mockBrowserEnvironment(opts.workDir);
try {
const workspaces = await getStudioWorkspaces.getStudioWorkspaces({
basePath: opts.workDir
});
for (const workspace of workspaces)
node_worker_threads.parentPort?.postMessage(extractCreateWorkspaceManifest(workspace));
} finally {
node_worker_threads.parentPort?.close(), cleanup();
}
}
main().then(() => process.exit());
//# sourceMappingURL=extractManifest.js.map