UNPKG

@contentstack/types-generator

Version:

Contentstack type definition generation library

730 lines (719 loc) 23.3 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __getProtoOf = Object.getPrototypeOf; 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)); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/web.ts var web_exports = {}; __export(web_exports, { generateTS: () => generateTS, generateTSFromContentTypes: () => generateTSFromContentTypes }); module.exports = __toCommonJS(web_exports); // src/generateTS/index.ts var import_async = __toESM(require("async")); var import_lodash = require("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 var import_delivery_sdk = __toESM(require("@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 import_delivery_sdk.Region.US; case REGIONS.EU: return import_delivery_sdk.Region.EU; case REGIONS.AWS_NA: return import_delivery_sdk.Region.US; case REGIONS.AWS_EU: return import_delivery_sdk.Region.EU; case REGIONS.AZURE_NA: return import_delivery_sdk.Region.AZURE_NA; case REGIONS.AZURE_EU: return import_delivery_sdk.Region.AZURE_EU; case REGIONS.GCP_NA: return import_delivery_sdk.Region.GCP_NA; case REGIONS.GCP_EU: return import_delivery_sdk.Region.GCP_EU; default: isCustomRegion = true; break; } }(region); let Stack; if (isCustomRegion && host) { Stack = import_delivery_sdk.default.stack({ apiKey, deliveryToken: token, environment, host, branch }); } else if (regionVal) { Stack = import_delivery_sdk.default.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 var _ = __toESM(require("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 var prettier = __toESM(require("prettier")); var tsParser = __toESM(require("prettier/plugins/typescript")); var estreeParser = __toESM(require("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 import_async.default.reflect(async () => { contentTypeQuery._queryParams["skip"] = (i + 1) * limit; contentTypeQuery._queryParams["limit"] = limit; return contentTypeQuery.find(); }); } ); const additionalResults = await import_async.default.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 = (0, import_lodash.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; }); }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { generateTS, generateTSFromContentTypes });