@contentstack/types-generator
Version:
Contentstack type definition generation library
1,235 lines (1,216 loc) • 40.8 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/messages.ts
var ERROR_MESSAGES = {
// Validation Errors
MISSING_REQUIRED_PARAMS: "Missing required parameters",
REQUIRED_PARAMS_LIST: "Required: token, apiKey, environment, region",
UNSUPPORTED_REGION: (region) => `Unsupported region: ${region}`,
SUPPORTED_REGIONS: "Supported regions: US, EU, AU, AZURE_NA, AZURE_EU, GCP_NA, GCP_EU",
CUSTOM_HOST_OPTION: "Or provide a custom host",
// Content Type Errors
NO_CONTENT_TYPES: "No Content Types found in the Stack",
CREATE_CONTENT_MODELS: "Please create Content Models to generate type definitions",
NO_CONTENT_TYPES_DETAILED: "There are no Content Types in the Stack, please create Content Models to generate type definitions",
// Authentication Errors
UNAUTHORIZED: "Unauthorized: The apiKey, token or region is not valid.",
INVALID_CREDENTIALS: "Invalid credentials. Please verify your apiKey, token, and region.",
INVALID_CREDENTIALS_GRAPHQL: "Unauthorized: The apiKey, token or environment is not valid.",
// API Errors
API_ERROR_DEFAULT: "Something went wrong",
API_ERROR_WITH_STATUS: (status, message) => `API error occurred. Status: ${status}${message ? `. ${message}` : ""}`,
GRAPHQL_SCHEMA_ERROR: "An error occurred while processing GraphQL schema",
// Field/Block Skip Messages
SKIPPED_FIELD_UNKNOWN_TYPE: (uid, dataType, reason) => `Skipped field "${uid}" with unknown type "${dataType}": ${reason}`,
SKIPPED_GLOBAL_FIELD_REFERENCE: (uid, referenceTo, reason) => `Skipped global field reference "${uid}" to "${referenceTo}": ${reason}`,
SKIPPED_FIELD_AT_PATH: (uid, path, reason) => `Skipped field "${uid}" at path "${path}": ${reason}`,
SKIPPED_BLOCK_AT_PATH: (uid, path, reason) => `Skipped block "${uid}" at path "${path}": ${reason}`,
SKIPPED_GLOBAL_FIELD: (uid, reason) => `Skipped global field "${uid}": ${reason}`,
SKIPPED_GLOBAL_FIELD_NO_SCHEMA: (uid, reason) => `Skipped global field "${uid}": ${reason}. Did you forget to include it?`,
SKIPPED_REFERENCE: (reference, reason) => `Skipped reference to content type "${reference}": ${reason}`,
// Summary Messages
SUMMARY_HEADER: "Summary of Skipped Items:",
TOTAL_SKIPPED_ITEMS: (count) => `Total skipped items: ${count}`,
GENERATION_COMPLETED_PARTIAL: "Generation completed successfully with partial schema.",
// GraphQL Errors
GRAPHQL_API_UNAVAILABLE: (region) => `GraphQL content delivery api unavailable for '${region}' region and no custom host provided`
};
// src/constants/index.ts
var TOKEN_TYPE = {
DELIVERY: "delivery"
};
var REGIONS = {
US: "US",
EU: "EU",
AU: "AU",
AWS_NA: "AWS-NA",
AWS_EU: "AWS-EU",
AWS_AU: "AWS-AU",
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.AU:
return Region.AU;
case REGIONS.AWS_NA:
return Region.US;
case REGIONS.AWS_EU:
return Region.EU;
case REGIONS.AWS_AU:
return Region.AU;
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;
}
versionComment() {
return "/** Version */";
}
block(description) {
return ["/**", description, "*/"].join(" ");
}
};
// src/generateTS/docgen/nulldoc.ts
var NullDocumentationGenerator = class {
interface(__description) {
return null;
}
field(__description) {
return null;
}
versionComment() {
return null;
}
};
// src/generateTS/factory.ts
import * as _ from "lodash";
// src/generateTS/shared/cslp-helpers.ts
var CSLP_HELPERS = {
INTERFACE_DEFINITION: `export interface CSLPAttribute {
"data-cslp"?: string;
"data-cslp-parent-field"?: string;
}
export type CSLPFieldMapping = CSLPAttribute | string;`,
createFieldMapping: (fieldUid) => `${JSON.stringify(fieldUid)}?: CSLPFieldMapping`,
createMappingBlock: (dollarKeys) => `$?: {
${dollarKeys.join(";\n ")};
};`
};
// src/generateTS/shared/utils.ts
function isNumericIdentifier(identifier) {
return /^\d/.test(identifier);
}
function createSkippedItemRecord(uid, path, reason) {
return { uid, path, reason };
}
var NUMERIC_IDENTIFIER_EXCLUSION_REASON = "TypeScript constraint: object keys cannot start with numbers";
function checkNumericIdentifierExclusion(uid, path) {
if (isNumericIdentifier(uid)) {
return {
shouldExclude: true,
record: createSkippedItemRecord(
uid,
path,
NUMERIC_IDENTIFIER_EXCLUSION_REASON
)
};
}
return { shouldExclude: false };
}
function createValidationError(errorMessage) {
return {
type: "validation",
error_message: errorMessage
};
}
function createErrorDetails(err, context = "generateTSFromContentTypes") {
if (err.type === "validation") {
return {
error_message: err.error_message || "Validation error occurred",
// Keep for backwards compatibility
error_code: err.error_code || "VALIDATION_ERROR",
// New property
details: err.details || {}
};
} else {
const errorMessage = err.message || "Unknown error occurred";
return {
error_message: `Type generation failed: ${errorMessage}`,
// Keep for backwards compatibility
error_code: "TYPE_GENERATION_FAILED",
// New property
details: {}
};
}
}
function formatErrorDetails(error, index, skipHeader = false) {
if (skipHeader) {
return `TypeScript constraint: Object keys cannot start with a number.
Suggestion: Since UIDs cannot be changed, use the --prefix flag to add a valid prefix to all interface names (e.g., --prefix "ContentType").
`;
}
return `${index}. UID: "${error.uid}"
TypeScript constraint: Object keys cannot start with a number.
Suggestion: Since UIDs cannot be changed, use the --prefix flag to add a valid prefix to all interface names (e.g., --prefix "ContentType").
`;
}
function buildErrorHeader(totalErrors) {
return `Type generation failed: ${totalErrors} items use numeric identifiers, which result in invalid TypeScript interface names. Use the --prefix flag to resolve this issue.
`;
}
function buildContentTypeErrorsSection(contentTypeErrors) {
if (contentTypeErrors.length === 0) return "";
let section = "Content Types and Global Fields with Numeric UIDs\n";
section += "Note: Global Fields are also Content Types. If their UID begins with a number, they are listed here.\n\n";
contentTypeErrors.forEach((error, index) => {
section += formatErrorDetails(error, index + 1);
});
return section;
}
function buildGlobalFieldErrorsSection(globalFieldErrors) {
if (globalFieldErrors.length === 0) return "";
let section = "Global Fields Referencing Invalid Content Types:\n\n";
globalFieldErrors.forEach((error, index) => {
section += `${index + 1}. Global Field: "${error.uid}"
`;
section += ` References: "${error.referenceTo || "Unknown"}"
`;
section += formatErrorDetails(error, index + 1, true);
});
return section;
}
function buildResolutionInstructionsSection() {
return 'To resolve these issues:\n\u2022 Use the --prefix flag to add a valid prefix to all interface names.\n\u2022 Example: --prefix "ContentType"\n';
}
function buildNumericIdentifierErrorDetails(errors) {
const contentTypeErrors = errors.filter((err) => err.type === "content_type");
const globalFieldErrors = errors.filter((err) => err.type === "global_field");
let errorDetails = buildErrorHeader(errors.length);
errorDetails += buildContentTypeErrorsSection(contentTypeErrors);
errorDetails += buildGlobalFieldErrorsSection(globalFieldErrors);
errorDetails += buildResolutionInstructionsSection();
return errorDetails;
}
function throwNumericIdentifierValidationError(errors) {
const errorDetails = buildNumericIdentifierErrorDetails(errors);
throw {
type: "validation",
error_code: "VALIDATION_ERROR",
error_message: errorDetails
};
}
// src/generateTS/factory.ts
function composePrefixedInterfaceName(uid, prefix) {
const trimmed = prefix.trim();
return trimmed + _.upperFirst(_.camelCase(uid));
}
var defaultOptions = {
docgen: new NullDocumentationGenerator(),
naming: {
prefix: ""
},
systemFields: false,
isEditableTags: false,
includeReferencedEntry: false
};
function factory_default(userOptions) {
var _a;
const options = Object.assign({}, defaultOptions, userOptions);
const logger = options.logger;
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();
const blockInterfacesKeyToName = {};
let counter = 1;
const skippedFields = [];
const skippedBlocks = [];
const trimmedNamingPrefix = typeof ((_a = options.naming) == null ? void 0 : _a.prefix) === "string" ? options.naming.prefix.trim() : "";
const numericIdentifierErrors = [];
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) {
if (trimmedNamingPrefix) {
return composePrefixedInterfaceName(uid, trimmedNamingPrefix);
}
if (isNumericIdentifier(uid)) {
return `InvalidInterface_${uid}`;
}
return composePrefixedInterfaceName(uid, "");
}
function define_interface(contentType, systemFields = false) {
const isGlobalField = contentType.data_type === "global_field";
const nameSourceUid = isGlobalField && contentType.reference_to ? contentType.reference_to : contentType.uid;
let interfaceName;
if (!trimmedNamingPrefix && isNumericIdentifier(nameSourceUid)) {
if (isGlobalField && contentType.reference_to) {
numericIdentifierErrors.push({
uid: contentType.uid,
type: "global_field",
referenceTo: contentType.reference_to
});
} else {
numericIdentifierErrors.push({
uid: contentType.uid,
type: "content_type"
});
}
interfaceName = `InvalidInterface_${nameSourceUid}`;
} else {
interfaceName = name_type(nameSourceUid);
}
const interface_declaration = ["export interface", interfaceName];
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);
}
} else {
const reason = `Unknown field type: ${field.data_type}`;
skippedFields.push({ uid: field.uid, path: field.uid, reason });
logger == null ? void 0 : logger.warn(
ERROR_MESSAGES.SKIPPED_FIELD_UNKNOWN_TYPE(
field.uid,
field.data_type,
reason
)
);
type = "Record<string, unknown>";
}
}
return op_array(type, field);
}
const handleGlobalField = (field) => {
if (!trimmedNamingPrefix && isNumericIdentifier(field.reference_to)) {
const exclusionCheck = checkNumericIdentifierExclusion(
field.reference_to,
field.uid
);
skippedFields.push(exclusionCheck.record);
logger == null ? void 0 : logger.warn(
ERROR_MESSAGES.SKIPPED_GLOBAL_FIELD_REFERENCE(
field.uid,
field.reference_to,
NUMERIC_IDENTIFIER_EXCLUSION_REASON
)
);
return "string";
}
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, path = "") {
const fieldLines = [];
const dollarKeys = [];
for (const field of schema) {
const fieldPath = path ? `${path}.${field.uid}` : field.uid;
const exclusionCheck = checkNumericIdentifierExclusion(
field.uid,
fieldPath
);
if (exclusionCheck.shouldExclude) {
skippedFields.push(exclusionCheck.record);
logger == null ? void 0 : logger.warn(
ERROR_MESSAGES.SKIPPED_FIELD_AT_PATH(
field.uid,
fieldPath,
NUMERIC_IDENTIFIER_EXCLUSION_REASON
)
);
continue;
}
const line = [
options.docgen.field(field.display_name),
visit_field(field)
].filter((v) => v).join("\n");
fieldLines.push(line);
dollarKeys.push(CSLP_HELPERS.createFieldMapping(field.uid));
}
if (options.isEditableTags) {
const fieldComment = options.docgen.field(
"CSLP mapping for editable fields"
);
const lines = fieldComment ? [fieldComment, CSLP_HELPERS.createMappingBlock(dollarKeys)] : [CSLP_HELPERS.createMappingBlock(dollarKeys)];
fieldLines.push(...lines);
}
return fieldLines.join("\n");
}
function visit_content_type(contentType) {
modularBlockInterfaces.clear();
const contentTypeInterface = [
options.docgen.interface(contentType.description),
define_interface(contentType, options.systemFields),
"{",
options.docgen.versionComment(),
`_version?: number;`,
visit_fields(contentType.schema),
"}"
].filter((v) => v).join("\n");
return [...modularBlockInterfaces, contentTypeInterface].join("\n\n");
}
function type_modular_blocks(field) {
let modularBlockInterfaceName = name_type(field.uid);
const modularBlockDefinitions = field.blocks.map((block) => {
const blockPath = `${field.uid}.blocks.${block.uid}`;
const exclusionCheck = checkNumericIdentifierExclusion(
block.uid,
blockPath
);
if (exclusionCheck.shouldExclude) {
skippedBlocks.push(exclusionCheck.record);
logger == null ? void 0 : logger.warn(
ERROR_MESSAGES.SKIPPED_BLOCK_AT_PATH(
block.uid,
blockPath,
NUMERIC_IDENTIFIER_EXCLUSION_REASON
)
);
return null;
}
const blockFieldType = block.reference_to ? name_type(block.reference_to) : visit_fields(
block.schema || [],
`${field.uid}.blocks.${block.uid}`
);
const blockSchemaDefinition = block.reference_to ? `${blockFieldType};` : `{
${blockFieldType} }`;
return `${block.uid}: ${blockSchemaDefinition}`;
}).filter(Boolean);
if (modularBlockDefinitions.length === 0) {
if (options.systemFields) {
const modularBlocksType = `${trimmedNamingPrefix}ModularBlocksExtension`;
return field.multiple ? `${modularBlocksType}<Record<string, unknown>>[]` : `${modularBlocksType}<Record<string, unknown>>`;
}
return field.multiple ? "Record<string, unknown>[]" : "Record<string, unknown>";
}
const modularBlockSignature = JSON.stringify(modularBlockDefinitions);
if (uniqueBlockInterfaces.has(modularBlockSignature)) {
const existingInterfaceName = blockInterfacesKeyToName[modularBlockSignature];
if (existingInterfaceName) {
if (options.systemFields) {
const modularBlocksType = `${trimmedNamingPrefix}ModularBlocksExtension`;
return field.multiple ? `${modularBlocksType}<${existingInterfaceName}>[]` : `${modularBlocksType}<${existingInterfaceName}>`;
}
return field.multiple ? `${existingInterfaceName}[]` : existingInterfaceName;
}
}
uniqueBlockInterfaces.add(modularBlockSignature);
while (cachedModularBlocks[modularBlockInterfaceName]) {
modularBlockInterfaceName = `${modularBlockInterfaceName}${counter}`;
counter++;
}
const modularBlockInterfaceDefinition = [
`export interface ${modularBlockInterfaceName}${options.systemFields ? ` extends ${trimmedNamingPrefix}SystemFields` : ""} {`,
modularBlockDefinitions.join("\n"),
"}"
].join("\n");
modularBlockInterfaces.add(modularBlockInterfaceDefinition);
cachedModularBlocks[modularBlockInterfaceName] = modularBlockSignature;
blockInterfacesKeyToName[modularBlockSignature] = modularBlockInterfaceName;
if (options.systemFields) {
const modularBlocksType = `${trimmedNamingPrefix}ModularBlocksExtension`;
return field.multiple ? `${modularBlocksType}<${modularBlockInterfaceName}>[]` : `${modularBlocksType}<${modularBlockInterfaceName}>`;
}
return field.multiple ? `${modularBlockInterfaceName}[]` : modularBlockInterfaceName;
}
function type_group(field) {
return ["{", visit_fields(field.schema, field.uid), "}"].filter((v) => v).join("\n");
}
function type_text() {
return "string";
}
function type_number() {
return "number";
}
function type_boolean() {
return "boolean";
}
function type_link() {
return `${trimmedNamingPrefix}Link`;
}
function type_file(field) {
if (field.uid === "parent_uid") {
return "string | null";
}
return `${trimmedNamingPrefix}File`;
}
function type_global_field(field) {
const exclusionCheck = checkNumericIdentifierExclusion(
field.uid,
field.uid
);
if (exclusionCheck.shouldExclude) {
skippedFields.push(exclusionCheck.record);
logger == null ? void 0 : logger.warn(
ERROR_MESSAGES.SKIPPED_GLOBAL_FIELD(
field.uid,
NUMERIC_IDENTIFIER_EXCLUSION_REASON
)
);
return "string";
}
if (!field.schema) {
const reason = "Schema not found for global field";
skippedFields.push({ uid: field.uid, path: field.uid, reason });
logger == null ? void 0 : logger.warn(
ERROR_MESSAGES.SKIPPED_GLOBAL_FIELD_NO_SCHEMA(field.uid, reason)
);
return "string";
}
return name_type(field.reference_to);
}
function buildReferenceArrayType(references, options2) {
if (references.length === 0) {
return "Record<string, unknown>[]";
}
if (options2.includeReferencedEntry) {
const referencedEntryType = `${trimmedNamingPrefix}ReferencedEntry`;
const baseUnion2 = references.join(" | ");
const types = `(${baseUnion2} | ${referencedEntryType})`;
return `${types}[]`;
}
const baseUnion = references.join(" | ");
return `(${baseUnion})[]`;
}
function type_reference(field) {
const references = [];
if (Array.isArray(field.reference_to)) {
field.reference_to.forEach((v) => {
if (trimmedNamingPrefix || !isNumericIdentifier(v)) {
references.push(name_type(v));
} else {
logger == null ? void 0 : logger.warn(
ERROR_MESSAGES.SKIPPED_REFERENCE(
v,
NUMERIC_IDENTIFIER_EXCLUSION_REASON
)
);
}
});
} else {
const v = field.reference_to;
if (trimmedNamingPrefix || !isNumericIdentifier(v)) {
references.push(name_type(v));
} else {
logger == null ? void 0 : logger.warn(
ERROR_MESSAGES.SKIPPED_REFERENCE(
v,
NUMERIC_IDENTIFIER_EXCLUSION_REASON
)
);
}
}
return buildReferenceArrayType(references, options);
}
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
};
}
const definition = visit_content_type(contentType);
if (numericIdentifierErrors.length > 0) {
throwNumericIdentifierValidationError(numericIdentifierErrors);
}
if (logger && (skippedFields.length > 0 || skippedBlocks.length > 0)) {
logger.info("");
logger.info(ERROR_MESSAGES.SUMMARY_HEADER);
const allSkippedItems = [
...skippedFields.map((field) => ({
Type: "Field",
"Key Name": field.uid,
"Schema Path": field.path,
Reason: field.reason
})),
...skippedBlocks.map((block) => ({
Type: "Block",
"Key Name": block.uid,
"Schema Path": block.path,
Reason: block.reason
}))
];
if (logger.table) {
logger.table(
[
{ value: "Type" },
{ value: "Key Name" },
{ value: "Schema Path" },
{ value: "Reason" }
],
allSkippedItems
);
}
const totalSkipped = skippedFields.length + skippedBlocks.length;
logger.info("");
logger.warn(ERROR_MESSAGES.TOTAL_SKIPPED_ITEMS(totalSkipped));
logger.success(ERROR_MESSAGES.GENERATION_COMPLETED_PARTIAL);
}
return {
definition,
metadata: {
name: name_type(contentType.uid),
types: {
javascript: visitedJSTypes,
contentstack: visitedCSTypes,
globalFields: visitedGlobalFields
},
dependencies: {
globalFields: cachedGlobalFields,
contentTypes: visitedContentTypes
},
skippedFields: {
fields: [...skippedFields],
// Create a copy to avoid reference issues
blocks: [...skippedBlocks]
// Create a copy to avoid reference issues
}
}
};
};
function type_taxonomy() {
return `${trimmedNamingPrefix}Taxonomy | ${trimmedNamingPrefix}TaxonomyEntry`;
}
function type_json_rte(field) {
var _a2;
let json_rte;
if (field.config && ((_a2 = field.field_metadata) == null ? void 0 : _a2.extension)) {
json_rte = `unknown`;
} 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, isEditableTags = false, hasJsonRte, includeReferencedEntry = false) => {
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>>`
];
if (includeReferencedEntry) {
const extendsClause = systemFields ? ` extends ${prefix}SystemFields` : "";
defaultInterfaces2.push(
`export interface ${prefix}ReferencedEntry${extendsClause} {
uid: string;
_content_type_uid: string;
}`
);
}
defaultInterfaces2.push(
`export interface ${prefix}PublishDetails {
environment: string;
locale: string;
time: string;
user: string;
}`
);
defaultInterfaces2.push(
`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;
dimension?: {
height: number;
width: number;
}
publish_details: ${prefix}PublishDetails;
}`
);
defaultInterfaces2.push(
`export interface ${prefix}Link {
title: string;
href: string;
}`
);
defaultInterfaces2.push(
`export interface ${prefix}Taxonomy {
taxonomy_uid: string;
max_terms?: number;
mandatory: boolean;
non_localizable: boolean;
}`
);
defaultInterfaces2.push(
`export type ${prefix}TaxonomyEntry = ${prefix}Taxonomy & { term_uid: string }`
);
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 (isEditableTags) {
defaultInterfaces2.push(CSLP_HELPERS.INTERFACE_DEFINITION);
}
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;
}`
);
defaultInterfaces2.push(
`export type ${prefix}ModularBlocksExtension<T> = {
[P in keyof T]?: T[P] & { _metadata?: { uid?: 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/logger/index.ts
var BasicLogger = class {
constructor() {
this.colorMap = {
red: "\x1B[31m",
green: "\x1B[32m",
yellow: "\x1B[33m",
blue: "\x1B[34m",
magenta: "\x1B[35m",
cyan: "\x1B[36m",
white: "\x1B[37m",
gray: "\x1B[90m",
reset: "\x1B[0m",
bold: "\x1B[1m"
};
}
print(message, options = {}) {
let formattedMessage = message;
if (options.color && this.colorMap[options.color]) {
formattedMessage = `${this.colorMap[options.color]}${formattedMessage}${this.colorMap.reset}`;
}
if (options.bold) {
formattedMessage = `${this.colorMap.bold}${formattedMessage}${this.colorMap.reset}`;
}
console.log(formattedMessage);
}
success(message) {
this.print(message, { color: "green" });
}
info(message) {
if (message === "") {
console.log();
} else {
console.log(message);
}
}
warn(message) {
this.print(message, { color: "yellow" });
}
error(message) {
this.print(message, { color: "red" });
}
table(headers, data) {
if (data.length === 0) return;
if (console.table) {
console.table(data);
} else {
const columnWidths = {};
headers.forEach((header) => {
columnWidths[header.value] = Math.max(
header.value.length,
...data.map((row) => String(row[header.value] || "").length)
);
});
const headerRow = headers.map((header) => header.value.padEnd(columnWidths[header.value])).join(" | ");
this.print(headerRow, { bold: true });
this.print(
headers.map((header) => "-".repeat(columnWidths[header.value])).join("-|-")
);
data.forEach((row) => {
const dataRow = headers.map(
(header) => String(row[header.value] || "").padEnd(columnWidths[header.value])
).join(" | ");
this.print(dataRow);
});
}
}
};
function createLogger(externalLogger) {
if (externalLogger) {
if (!externalLogger.table) {
externalLogger.table = (headers, data) => {
console.table(data);
};
}
return externalLogger;
}
return new BasicLogger();
}
// src/generateTS/index.ts
var generateTS = async ({
token,
tokenType,
apiKey,
environment,
region,
branch,
prefix,
includeDocumentation,
systemFields,
isEditableTags,
includeReferencedEntry,
host,
logger: loggerInstance
}) => {
var _a, _b;
const logger = createLogger(loggerInstance);
try {
if (!token || !tokenType || !apiKey || !environment || !region) {
throw createValidationError(
"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) {
logger.error(ERROR_MESSAGES.NO_CONTENT_TYPES);
logger.warn(ERROR_MESSAGES.CREATE_CONTENT_MODELS);
throw createValidationError(ERROR_MESSAGES.NO_CONTENT_TYPES_DETAILED);
}
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,
isEditableTags,
includeReferencedEntry
});
return generatedTS;
}
}
} catch (error) {
if (error.type === "validation") {
throw {
error_message: error.error_message,
error_code: error.error_code || "VALIDATION_ERROR"
};
} else {
const errorObj = JSON.parse((_b = error == null ? void 0 : error.message) == null ? void 0 : _b.replace("Error: ", ""));
let errorMessage = ERROR_MESSAGES.API_ERROR_DEFAULT;
let errorCode = "API_ERROR";
if (errorObj.status) {
switch (errorObj.status) {
case 401:
errorMessage = ERROR_MESSAGES.UNAUTHORIZED;
errorCode = "AUTHENTICATION_FAILED";
break;
case 412:
errorMessage = ERROR_MESSAGES.INVALID_CREDENTIALS;
errorCode = "INVALID_CREDENTIALS";
break;
default:
errorMessage = ERROR_MESSAGES.API_ERROR_WITH_STATUS(errorObj.status, errorObj.error_message);
errorCode = `API_ERROR_${errorObj.status}`;
}
}
if (errorObj.error_message && !errorObj.status) {
errorMessage = `${errorMessage}, ${errorObj.error_message}`;
}
throw {
error_message: errorMessage,
error_code: errorCode
};
}
}
};
var generateTSFromContentTypes = async ({
contentTypes,
prefix = "",
includeDocumentation = true,
systemFields = false,
isEditableTags = false,
includeReferencedEntry = false,
logger: loggerInstance
}) => {
const logger = createLogger(loggerInstance);
try {
const docgen = includeDocumentation ? new JSDocumentationGenerator() : new NullDocumentationGenerator();
const globalFields = /* @__PURE__ */ new Set();
const definitions = [];
const tsgen = factory_default({
docgen,
naming: { prefix },
systemFields,
isEditableTags,
includeReferencedEntry,
logger
});
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,
isEditableTags,
hasJsonField,
includeReferencedEntry
).join("\n\n"),
[...globalFields].join("\n\n"),
definitions.join("\n\n")
].join("\n\n")
);
return output;
} catch (err) {
if (err.type === "validation" && err.error_code === "VALIDATION_ERROR" && err.error_message && err.error_message.includes("numeric identifiers")) {
throw err;
}
const errorDetails = createErrorDetails(err, "generateTSFromContentTypes");
throw errorDetails;
}
};
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 {
ERROR_MESSAGES,
createLogger,
generateTS,
generateTSFromContentTypes
};