Skip to content
Merged
4 changes: 3 additions & 1 deletion .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ fileignoreconfig:
ignore_detectors:
- filecontent
- filename: package-lock.json
checksum: 2e59256a4223df4fa670d9bedb571586daa21e59194400b8f9aa4725d378cc72
checksum: c447ed3d22eef9d2b26b9ae85370de31be04cc94da0af506ada0025bc7a9bbb6
- filename: .husky/pre-commit
checksum: 5baabd7d2c391648163f9371f0e5e9484f8fb90fa2284cfc378732ec3192c193
- filename: src/graphqlTS/index.ts
Expand All @@ -18,4 +18,6 @@ fileignoreconfig:
checksum: 7ffa82084fd0fc2f5ec9c8e8124cdf1bce08c7f75c5be2ede2eb5cd506812db1
- filename: tests/integration/generateTS/generateTS.test.ts
checksum: 7c1bc7d659ee2f9f52bf644b9e512984f89e0ff6aa4288b6e30b2c899bf80123
- filename: src/generateTS/shared/utils.ts
checksum: da69dab1717422e12f3b3865604667151d46c96bde5faba13ae862c41d856fba
version: "1.0"
325 changes: 164 additions & 161 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentstack/types-generator",
"version": "3.6.0",
"version": "3.7.0",
"description": "Contentstack type definition generation library",
"private": false,
"author": "Contentstack",
Expand Down Expand Up @@ -40,13 +40,13 @@
"husky": "^9.1.7",
"jest": "^29.7.0",
"nock": "^13.5.6",
"rollup": "^4.46.2",
"rollup": "^4.48.0",
"ts-jest": "^29.4.0",
"tsup": "^8.5.0",
"typescript": "^5.7.3"
},
"dependencies": {
"@contentstack/cli-utilities": "^1.13.0",
"@contentstack/cli-utilities": "^1.13.1",
"@contentstack/delivery-sdk": "^4.8.0",
"@gql2ts/from-schema": "^2.0.0-4",
"async": "^3.2.6",
Expand Down
62 changes: 54 additions & 8 deletions src/generateTS/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
isNumericIdentifier,
NUMERIC_IDENTIFIER_EXCLUSION_REASON,
checkNumericIdentifierExclusion,
throwNumericIdentifierValidationError,
} from "./shared/utils";

export type TSGenOptions = {
Expand Down Expand Up @@ -94,6 +95,13 @@ export default function (userOptions: TSGenOptions) {
const skippedBlocks: Array<{ uid: string; path: string; reason: string }> =
[];

// Collect numeric identifier errors instead of throwing immediately
const numericIdentifierErrors: Array<{
uid: string;
referenceTo?: string;
type: "content_type" | "global_field";
}> = [];

const typeMap: TypeMap = {
text: { func: type_text, track: true, flag: TypeFlags.BuiltinJS },
number: { func: type_number, track: true, flag: TypeFlags.BuiltinJS },
Expand Down Expand Up @@ -152,6 +160,12 @@ export default function (userOptions: TSGenOptions) {
}

function name_type(uid: string) {
// Check if the UID starts with a number, which would create invalid TypeScript
if (isNumericIdentifier(uid)) {
// Return a fallback name to continue processing
return `InvalidInterface_${uid}`;
}

return [options?.naming?.prefix, _.upperFirst(_.camelCase(uid))]
.filter((v) => v)
.join("");
Expand All @@ -161,14 +175,41 @@ export default function (userOptions: TSGenOptions) {
contentType: ContentstackTypes.ContentType | ContentstackTypes.GlobalField,
systemFields = false
) {
const interface_declaration = [
"export interface",
name_type(
contentType.data_type === "global_field"
? (contentType.reference_to as string)
: contentType.uid
),
];
// Validate the interface name before creating it
let interfaceName: string;

const isGlobalField = contentType.data_type === "global_field";

// Check if the content type's own UID starts with a number
if (isNumericIdentifier(contentType.uid)) {
numericIdentifierErrors.push({
uid: contentType.uid,
type: "content_type",
});
// Return a fallback interface declaration to continue processing
interfaceName = `InvalidInterface_${contentType.uid}`;
} else if (
isGlobalField &&
contentType.reference_to &&
isNumericIdentifier(contentType.reference_to as string)
) {
// For global fields, check if the referenced content type has a numeric identifier
// This is a global field error because it references an invalid content type
numericIdentifierErrors.push({
uid: contentType.uid, // The global field's UID
type: "global_field",
referenceTo: contentType.reference_to as string, // The referenced content type's UID
});
// Return a fallback interface declaration to continue processing
interfaceName = `InvalidInterface_${contentType.reference_to}`;
} else {
// No numeric identifier issues, generate normal interface name
interfaceName = name_type(
isGlobalField ? (contentType.reference_to as string) : contentType.uid
);
}

const interface_declaration = ["export interface", interfaceName];
if (systemFields && contentType.schema_type !== "global_field") {
interface_declaration.push("extends", name_type("SystemFields"));
}
Expand Down Expand Up @@ -570,6 +611,11 @@ export default function (userOptions: TSGenOptions) {

const definition = visit_content_type(contentType);

// Check for numeric identifier errors and throw them immediately
if (numericIdentifierErrors.length > 0) {
throwNumericIdentifierValidationError(numericIdentifierErrors);
}

// Log summary table of skipped fields and blocks
if (skippedFields.length > 0 || skippedBlocks.length > 0) {
cliux.print("");
Expand Down
74 changes: 33 additions & 41 deletions src/generateTS/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { defaultInterfaces } from "./stack/builtins";
import { format } from "../format/index";
import { ContentType } from "../types/schema";
import { cliux } from "@contentstack/cli-utilities";
import { createValidationError, createErrorDetails } from "./shared/utils";

export const generateTS = async ({
token,
Expand All @@ -28,11 +29,9 @@ export const generateTS = async ({
}: GenerateTS) => {
try {
if (!token || !tokenType || !apiKey || !environment || !region) {
throw {
type: "validation",
error_message:
"Please provide all the required params (token, tokenType, apiKey, environment, region)",
};
throw createValidationError(
"Please provide all the required params (token, tokenType, apiKey, environment, region)"
);
}

if (tokenType === TOKEN_TYPE.DELIVERY) {
Expand Down Expand Up @@ -62,11 +61,9 @@ export const generateTS = async ({
"Please create Content Models to generate type definitions",
{ color: "yellow" }
);
throw {
type: "validation",
error_message:
"There are no Content Types in the Stack, please create Content Models to generate type definitions",
};
throw createValidationError(
"There are no Content Types in the Stack, please create Content Models to generate type definitions"
);
}

let schemas: ContentType[] = [];
Expand Down Expand Up @@ -95,43 +92,40 @@ export const generateTS = async ({
}
} catch (error: any) {
if (error.type === "validation") {
throw { error_message: error.error_message };
// Handle validation errors with proper error codes
throw {
error_message: error.error_message,
error_code: error.error_code || "VALIDATION_ERROR",
};
} else {
const errorObj = JSON.parse(error.message.replace("Error: ", ""));
let errorMessage = "Something went wrong";
let errorCode = "API_ERROR";

if (errorObj.status) {
switch (errorObj.status) {
case 401:
cliux.print("Authentication failed", {
color: "red",
bold: true,
});
cliux.print("Please check your apiKey, token, and region", {
color: "yellow",
});
errorMessage =
"Unauthorized: The apiKey, token or region is not valid.";
errorCode = "AUTHENTICATION_FAILED";
break;
case 412:
cliux.print("Invalid credentials", { color: "red", bold: true });
cliux.print("Please verify your apiKey, token, and region", {
color: "yellow",
});
errorMessage =
"Invalid Credentials: Please check the provided apiKey, token and region.";
errorCode = "INVALID_CREDENTIALS";
break;
default:
cliux.print(`API Error (${errorObj.status})`, {
color: "red",
bold: true,
});
errorMessage = `${errorMessage}, ${errorObj.error_message}`;
errorCode = `API_ERROR_${errorObj.status}`;
}
}
if (errorObj.error_message && !errorObj.status) {
errorMessage = `${errorMessage}, ${errorObj.error_message}`;
}
throw { error_message: errorMessage };
throw {
error_message: errorMessage,
error_code: errorCode,
};
}
}
};
Expand Down Expand Up @@ -193,21 +187,19 @@ export const generateTSFromContentTypes = async ({

return output;
} catch (err: any) {
// Enhanced error logging with more context
const errorMessage = err.message || "Unknown error occurred";
const errorDetails = {
error_message: `Type generation failed: ${errorMessage}`,
context: "generateTSFromContentTypes",
timestamp: new Date().toISOString(),
error_type: err.constructor.name,
};

// Log detailed error information for debugging
cliux.print(`Type generation failed: ${errorMessage}`, {
color: "red",
bold: true,
});
// Handle numeric identifier errors specially to preserve their detailed format
if (
err.type === "validation" &&
err.error_code === "VALIDATION_ERROR" &&
err.error_message &&
err.error_message.includes("numeric identifiers")
) {
// Pass through the detailed error as-is
throw err;
}

// Use common function to create detailed error information for other errors
const errorDetails = createErrorDetails(err, "generateTSFromContentTypes");
throw errorDetails;
}
};
Expand Down
Loading