@contentstack/types-generator
Version:
Contentstack type definition generation library
696 lines (685 loc) • 21.4 kB
JavaScript
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
// src/generateTS/index.ts
import async from "async";
import { flatten } from "lodash";
// src/constants/index.ts
var TOKEN_TYPE = {
DELIVERY: "delivery"
};
var REGIONS = {
US: "US",
EU: "EU",
AWS_NA: "AWS-NA",
AWS_EU: "AWS-EU",
AZURE_NA: "AZURE_NA",
AZURE_EU: "AZURE_EU",
GCP_NA: "GCP_NA",
GCP_EU: "GCP_EU",
CUSTOM: "CUSTOM"
};
// src/sdk/utils.ts
import Contentstack, { Region } from "@contentstack/delivery-sdk";
var initializeContentstackSdk = ({
apiKey,
token,
environment,
region,
branch,
host
}) => {
try {
let isCustomRegion = false;
const regionVal = function(regionValue) {
switch (regionValue) {
case REGIONS.US:
return Region.US;
case REGIONS.EU:
return Region.EU;
case REGIONS.AWS_NA:
return Region.US;
case REGIONS.AWS_EU:
return Region.EU;
case REGIONS.AZURE_NA:
return Region.AZURE_NA;
case REGIONS.AZURE_EU:
return Region.AZURE_EU;
case REGIONS.GCP_NA:
return Region.GCP_NA;
case REGIONS.GCP_EU:
return Region.GCP_EU;
default:
isCustomRegion = true;
break;
}
}(region);
let Stack;
if (isCustomRegion && host) {
Stack = Contentstack.stack({
apiKey,
deliveryToken: token,
environment,
host,
branch
});
} else if (regionVal) {
Stack = Contentstack.stack({
apiKey,
deliveryToken: token,
environment,
region: regionVal,
branch
});
} else {
throw {
type: "validation",
error_message: `The region "${region}" is not supported and no host is provided for a custom region.`
};
}
return Stack;
} catch (err) {
throw {
type: "validation",
error_message: "Something went wrong while initializing Contentstack SDK."
};
}
};
// src/generateTS/docgen/jsdoc.ts
var JSDocumentationGenerator = class {
interface(description) {
return description ? this.block(description) : null;
}
field(description) {
return description ? this.block(description) : null;
}
block(description) {
return ["/**", description, "*/"].join(" ");
}
};
// src/generateTS/docgen/nulldoc.ts
var NullDocumentationGenerator = class {
interface(__description) {
return null;
}
field(__description) {
return null;
}
};
// src/generateTS/factory.ts
import * as _ from "lodash";
var defaultOptions = {
docgen: new NullDocumentationGenerator(),
naming: {
prefix: ""
},
systemFields: false
};
function factory_default(userOptions) {
const options = Object.assign({}, defaultOptions, userOptions);
const visitedJSTypes = /* @__PURE__ */ new Set();
const visitedCSTypes = /* @__PURE__ */ new Set();
const visitedGlobalFields = /* @__PURE__ */ new Set();
const visitedContentTypes = /* @__PURE__ */ new Set();
const cachedGlobalFields = {};
const cachedModularBlocks = {};
const modularBlockInterfaces = /* @__PURE__ */ new Set();
const uniqueBlockInterfaces = /* @__PURE__ */ new Set();
let counter = 1;
const typeMap = {
text: { func: type_text, track: true, flag: 1 /* BuiltinJS */ },
number: { func: type_number, track: true, flag: 1 /* BuiltinJS */ },
isodate: { func: type_text, track: true, flag: 1 /* BuiltinJS */ },
boolean: { func: type_boolean, track: true, flag: 1 /* BuiltinJS */ },
blocks: {
func: type_modular_blocks,
track: false,
flag: 8 /* UserBlock */
},
global_field: {
func: type_global_field,
track: true,
flag: 4 /* UserGlobalField */
},
group: { func: type_group, track: false, flag: 16 /* UserGroup */ },
link: { func: type_link, track: true, flag: 2 /* BuiltinCS */ },
file: { func: type_file, track: true, flag: 2 /* BuiltinCS */ },
reference: {
func: type_reference,
track: true,
flag: 32 /* UserReference */
},
taxonomy: {
func: type_taxonomy,
track: true,
flag: 2 /* BuiltinCS */
}
};
function track_dependency(field, type, flag) {
if (flag === 1 /* BuiltinJS */) {
visitedJSTypes.add(type);
} else if (flag === 4 /* UserGlobalField */) {
const _type = name_type(field.reference_to);
visitedGlobalFields.add(_type);
if (!cachedGlobalFields[_type]) {
cachedGlobalFields[_type] = {
definition: visit_content_type(field)
};
}
} else if (flag === 2 /* BuiltinCS */) {
visitedCSTypes.add(type);
} else if (flag === 32 /* UserReference */) {
if (Array.isArray(field.reference_to)) {
field.reference_to.forEach((v) => {
visitedContentTypes.add(name_type(v));
});
}
}
}
function name_type(uid) {
var _a;
return [(_a = options == null ? void 0 : options.naming) == null ? void 0 : _a.prefix, _.upperFirst(_.camelCase(uid))].filter((v) => v).join("");
}
function define_interface(contentType, systemFields = false) {
const interface_declaration = [
"export interface",
name_type(
contentType.data_type === "global_field" ? contentType.reference_to : contentType.uid
)
];
if (systemFields && contentType.schema_type !== "global_field") {
interface_declaration.push("extends", name_type("SystemFields"));
}
return interface_declaration.join(" ");
}
function op_array(type, field) {
let op = "";
if (field.multiple) {
op = "[]";
if (field.max_instance) {
return ["MaxTuple<", type, ", ", field.max_instance, ">"].join("");
}
}
return type + op;
}
function op_required(required) {
return required ? "" : "?";
}
function op_paren(block) {
return `(${block})`;
}
function visit_field_choices(field) {
const choices = field.enum.choices;
const length = choices.length;
if (!choices && !length) return "";
function get_value(choice) {
if (field.data_type === "number") {
return choice.value;
}
return `${JSON.stringify(choice.value)}`;
}
return op_paren(choices.map((v) => get_value(v)).join(" | "));
}
function visit_field_type(field) {
let type = "any";
if (field.enum) {
type = visit_field_choices(field);
} else {
const match = typeMap[field.data_type];
if (match) {
type = match.func(field);
if (match.track) {
track_dependency(field, type, match.flag);
}
}
}
return op_array(type, field);
}
const handleGlobalField = (field) => {
const referenceName = name_type(field.reference_to);
return `${referenceName}${field.multiple ? "[]" : ""}`;
};
function visit_field(field) {
let fieldType = "";
if (field.data_type === "global_field") {
fieldType = handleGlobalField(field);
} else if (field.data_type === "blocks") {
fieldType = type_modular_blocks(field);
} else if (field.data_type === "json") {
fieldType = type_json_rte(field);
} else {
fieldType = visit_field_type(field);
}
const requiredFlag = field.data_type === "boolean" ? "" : op_required(field.mandatory);
const typeModifier = ["isodate", "file", "number"].includes(field.data_type) || ["radio", "dropdown"].includes(field.display_type) ? field.mandatory ? "" : " | null" : "";
return `${field.uid}${requiredFlag}: ${fieldType}${typeModifier};`;
}
function visit_fields(schema) {
return schema.map((v) => {
return [options.docgen.field(v.display_name), visit_field(v)].filter((v2) => v2).join("\n");
}).join("\n");
}
function visit_content_type(contentType) {
modularBlockInterfaces.clear();
const contentTypeInterface = [
options.docgen.interface(contentType.description),
define_interface(contentType, options.systemFields),
"{",
["/**", "Version", "*/"].join(" "),
`_version?: number;`,
visit_fields(contentType.schema),
"}"
].filter((v) => v).join("\n");
return [...modularBlockInterfaces, contentTypeInterface].join("\n\n");
}
function type_modular_blocks(field) {
let blockInterfaceName = name_type(field.uid);
const blockInterfaces = field.blocks.map((block) => {
const fieldType = block.reference_to ? name_type(block.reference_to) : visit_fields(block.schema || []);
const schema = block.reference_to ? `${fieldType};` : `{
${fieldType} }`;
return `${block.uid}: ${schema}`;
});
const blockInterfacesKey = blockInterfaces.join(";");
if (!uniqueBlockInterfaces.has(blockInterfacesKey)) {
uniqueBlockInterfaces.add(blockInterfacesKey);
while (cachedModularBlocks[blockInterfaceName]) {
blockInterfaceName = `${blockInterfaceName}${counter}`;
counter++;
}
}
const modularInterface = [
`export interface ${blockInterfaceName} {`,
blockInterfaces.join("\n"),
"}"
].join("\n");
modularBlockInterfaces.add(modularInterface);
cachedModularBlocks[blockInterfaceName] = blockInterfaceName;
return field.multiple ? `${blockInterfaceName}[]` : blockInterfaceName;
}
function type_group(field) {
return ["{", visit_fields(field.schema), "}"].filter((v) => v).join("\n");
}
function type_text() {
return "string";
}
function type_number() {
return "number";
}
function type_boolean() {
return "boolean";
}
function type_link() {
var _a;
return `${(_a = options.naming) == null ? void 0 : _a.prefix}Link`;
}
function type_file(field) {
var _a;
if (field.uid === "parent_uid") {
return "string | null";
}
return `${(_a = options.naming) == null ? void 0 : _a.prefix}File`;
}
function type_global_field(field) {
if (!field.schema) {
throw new Error(
`Schema not found for global field '${field.uid}'. Did you forget to include it?`
);
}
return name_type(field.reference_to);
}
function type_reference(field) {
const references = [];
if (Array.isArray(field.reference_to)) {
field.reference_to.forEach((v) => {
references.push(name_type(v));
});
} else {
references.push(name_type(field.reference_to));
}
return ["(", references.join(" | "), ")", "[]"].join("");
}
return function(contentType) {
if (contentType.schema_type === "global_field") {
const name = name_type(contentType.uid);
if (!cachedGlobalFields[name]) {
cachedGlobalFields[name] = {
definition: visit_content_type(contentType)
};
}
return {
definition: cachedGlobalFields[name].definition,
isGlobalField: true
};
}
return {
definition: visit_content_type(contentType),
metadata: {
name: name_type(contentType.uid),
types: {
javascript: visitedJSTypes,
contentstack: visitedCSTypes,
globalFields: visitedGlobalFields
},
dependencies: {
globalFields: cachedGlobalFields,
contentTypes: visitedContentTypes
}
}
};
};
function type_taxonomy() {
var _a;
return `${(_a = options == null ? void 0 : options.naming) == null ? void 0 : _a.prefix}Taxonomy`;
}
function type_json_rte(field) {
var _a;
let json_rte;
if (field.config && ((_a = field.field_metadata) == null ? void 0 : _a.extension)) {
json_rte = `{ value: { key: string; value: string }[] }`;
} else {
json_rte = `{
type: string;
uid: string;
_version: number;
attrs: Record<string, any>;
children: JSONRTENode[];
}`;
}
return json_rte;
}
}
// src/generateTS/stack/builtins.ts
var defaultInterfaces = (prefix = "", systemFields = false, hasJsonRte) => {
const defaultInterfaces2 = [
`type BuildTuple<T, N extends number, R extends T[] = []> =
R['length'] extends N ? R : BuildTuple<T, N, [...R, T]>`,
`type TuplePrefixes<T extends any[]> =
T extends [any, ...infer Rest] ? T | TuplePrefixes<Rest extends any[] ? Rest : []> : []`,
`type MaxTuple<T, N extends number> = TuplePrefixes<BuildTuple<T, N>>`,
`export interface ${prefix}PublishDetails {
environment: string;
locale: string;
time: string;
user: string;
}`,
`export interface ${prefix}File {
uid: string;
created_at: string;
updated_at: string;
created_by: string;
updated_by: string;
content_type: string;
file_size: string;
tags: string[];
filename: string;
url: string;
ACL: any[] | object;
is_dir: boolean;
parent_uid: string;
_version: number;
title: string;
_metadata?:object;
description?:string;
publish_details: ${prefix}PublishDetails;
}`,
`export interface ${prefix}Link {
title: string;
href: string;
}`,
`export interface ${prefix}Taxonomy {
taxonomy_uid: string;
max_terms?: number;
mandatory: boolean;
non_localizable: boolean;
}`
];
if (hasJsonRte) {
defaultInterfaces2.push(
`export interface JSONRTENode {
type: string;
uid: string;
_version: number;
attrs: Record<string, any>;
children?: JSONRTENode[];
text?: string;
bold?: boolean;
italic?: boolean;
underline?: boolean;
src?: string;
alt?: string;
href?: string;
target?: string;
embed?: {
type: string;
uid: string;
_version: number;
attrs: Record<string, any>;
};
};`
);
}
if (systemFields) {
defaultInterfaces2.push(
`export interface ${prefix}SystemFields {
uid?: string;
created_at?: string;
updated_at?: string;
created_by?: string;
updated_by?: string;
_content_type_uid?: string;
tags?: string[];
ACL?: any[];
_version?: number;
_in_progress?: boolean;
locale?: string;
publish_details?: ${prefix}PublishDetails;
title?: string;
}`
);
return defaultInterfaces2;
} else {
return defaultInterfaces2;
}
};
// src/format/index.ts
import * as prettier from "prettier";
import * as tsParser from "prettier/plugins/typescript";
import * as estreeParser from "prettier/plugins/estree";
async function format2(definition) {
const formatted = await prettier.format(definition, {
parser: "typescript",
plugins: [tsParser, estreeParser]
});
return formatted;
}
// src/generateTS/index.ts
var generateTS = async ({
token,
tokenType,
apiKey,
environment,
region,
branch,
prefix,
includeDocumentation,
systemFields,
host
}) => {
var _a;
try {
if (!token || !tokenType || !apiKey || !environment || !region) {
throw {
type: "validation",
error_message: "Please provide all the required params (token, tokenType, apiKey, environment, region)"
};
}
if (tokenType === TOKEN_TYPE.DELIVERY) {
const Stack = initializeContentstackSdk({
apiKey,
token,
environment,
region,
branch,
host
});
const contentTypeQuery = Stack.contentType();
contentTypeQuery._queryParams["include_count"] = "true";
const globalFieldsQuery = Stack.globalField();
const contentTypes = await getContentTypes(contentTypeQuery);
const globalFields = await globalFieldsQuery.find();
const { content_types } = contentTypes;
if (!content_types.length) {
throw {
type: "validation",
error_message: "There are no Content Types in the Stack, please create Content Models to generate type definitions"
};
}
let schemas = [];
if (content_types == null ? void 0 : content_types.length) {
if ((_a = globalFields == null ? void 0 : globalFields.global_fields) == null ? void 0 : _a.length) {
schemas = schemas.concat(
globalFields.global_fields
);
schemas = schemas.map((schema) => __spreadProps(__spreadValues({}, schema), {
schema_type: "global_field"
}));
}
schemas = schemas.concat(content_types);
const generatedTS = generateTSFromContentTypes({
contentTypes: schemas,
prefix,
includeDocumentation,
systemFields
});
return generatedTS;
}
}
} catch (error) {
if (error.type === "validation") {
throw { error_message: error.error_message };
} else {
const errorObj = JSON.parse(error.message.replace("Error: ", ""));
let errorMessage = "Something went wrong";
if (errorObj.status) {
switch (errorObj.status) {
case 401:
errorMessage = "Unauthorized: The apiKey, token or region is not valid.";
break;
case 412:
errorMessage = "Invalid Credentials: Please check the provided apiKey, token and region.";
break;
default:
errorMessage = `${errorMessage}, ${errorObj.error_message}`;
}
}
if (errorObj.error_message && !errorObj.status) {
errorMessage = `${errorMessage}, ${errorObj.error_message}`;
}
throw { error_message: errorMessage };
}
}
};
var generateTSFromContentTypes = async ({
contentTypes,
prefix = "",
includeDocumentation = true,
systemFields = false
}) => {
try {
const docgen = includeDocumentation ? new JSDocumentationGenerator() : new NullDocumentationGenerator();
const globalFields = /* @__PURE__ */ new Set();
const definitions = [];
const tsgen = factory_default({ docgen, naming: { prefix }, systemFields });
for (const contentType of contentTypes) {
const tsgenResult = tsgen(contentType);
if (tsgenResult.isGlobalField) {
globalFields.add(tsgenResult.definition);
} else {
definitions.push(tsgenResult.definition);
tsgenResult.metadata.types.globalFields.forEach((field) => {
globalFields.add(
tsgenResult.metadata.dependencies.globalFields[field].definition
);
});
}
}
const hasJsonField = contentTypes.some(
(contentType) => checkJsonField(contentType.schema)
);
const output = await format2(
[
defaultInterfaces(prefix, systemFields, hasJsonField).join("\n\n"),
[...globalFields].join("\n\n"),
definitions.join("\n\n")
].join("\n\n")
);
return output;
} catch (err) {
throw { error_message: "Something went wrong, " + err.message };
}
};
var getContentTypes = async (contentTypeQuery) => {
try {
const limit = 100;
const results = await contentTypeQuery.find();
if ((results == null ? void 0 : results.count) > limit) {
const additionalQueries = Array.from(
{ length: Math.ceil(results.count / limit) - 1 },
(_2, i) => {
return async.reflect(async () => {
contentTypeQuery._queryParams["skip"] = (i + 1) * limit;
contentTypeQuery._queryParams["limit"] = limit;
return contentTypeQuery.find();
});
}
);
const additionalResults = await async.parallel(additionalQueries);
const flattenedResult = additionalResults.flatMap(
(res) => {
var _a;
return (_a = res == null ? void 0 : res.value) == null ? void 0 : _a.content_types;
}
);
results.content_types = flatten([flattenedResult, results.content_types]);
}
return results;
} catch (error) {
throw error;
}
};
var checkJsonField = (schema) => {
return schema.some((field) => {
var _a;
if (field.data_type === "json" && ((_a = field.field_metadata) == null ? void 0 : _a.allow_json_rte)) {
return true;
}
if (field.data_type === "group" && Array.isArray(field.schema)) {
return checkJsonField(field.schema);
}
if (field.data_type === "blocks" && Array.isArray(field.blocks)) {
return field.blocks.some((block) => checkJsonField(block.schema || []));
}
return false;
});
};
export {
generateTS,
generateTSFromContentTypes
};