Skip to content

Commit cf3b3e0

Browse files
Merge pull request #123 from contentstack/development
DX | 25-08-2025 | Release
2 parents 45c97a7 + 900475a commit cf3b3e0

9 files changed

Lines changed: 488 additions & 225 deletions

File tree

.talismanrc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ fileignoreconfig:
33
ignore_detectors:
44
- filecontent
55
- filename: package-lock.json
6-
checksum: 2e59256a4223df4fa670d9bedb571586daa21e59194400b8f9aa4725d378cc72
6+
checksum: c447ed3d22eef9d2b26b9ae85370de31be04cc94da0af506ada0025bc7a9bbb6
77
- filename: .husky/pre-commit
88
checksum: 5baabd7d2c391648163f9371f0e5e9484f8fb90fa2284cfc378732ec3192c193
99
- filename: src/graphqlTS/index.ts
@@ -18,4 +18,6 @@ fileignoreconfig:
1818
checksum: 7ffa82084fd0fc2f5ec9c8e8124cdf1bce08c7f75c5be2ede2eb5cd506812db1
1919
- filename: tests/integration/generateTS/generateTS.test.ts
2020
checksum: 7c1bc7d659ee2f9f52bf644b9e512984f89e0ff6aa4288b6e30b2c899bf80123
21+
- filename: src/generateTS/shared/utils.ts
22+
checksum: da69dab1717422e12f3b3865604667151d46c96bde5faba13ae862c41d856fba
2123
version: "1.0"

package-lock.json

Lines changed: 164 additions & 161 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@contentstack/types-generator",
3-
"version": "3.6.0",
3+
"version": "3.7.0",
44
"description": "Contentstack type definition generation library",
55
"private": false,
66
"author": "Contentstack",
@@ -40,13 +40,13 @@
4040
"husky": "^9.1.7",
4141
"jest": "^29.7.0",
4242
"nock": "^13.5.6",
43-
"rollup": "^4.46.2",
43+
"rollup": "^4.48.0",
4444
"ts-jest": "^29.4.0",
4545
"tsup": "^8.5.0",
4646
"typescript": "^5.7.3"
4747
},
4848
"dependencies": {
49-
"@contentstack/cli-utilities": "^1.13.0",
49+
"@contentstack/cli-utilities": "^1.13.1",
5050
"@contentstack/delivery-sdk": "^4.8.0",
5151
"@gql2ts/from-schema": "^2.0.0-4",
5252
"async": "^3.2.6",

src/generateTS/factory.ts

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
isNumericIdentifier,
99
NUMERIC_IDENTIFIER_EXCLUSION_REASON,
1010
checkNumericIdentifierExclusion,
11+
throwNumericIdentifierValidationError,
1112
} from "./shared/utils";
1213

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

98+
// Collect numeric identifier errors instead of throwing immediately
99+
const numericIdentifierErrors: Array<{
100+
uid: string;
101+
referenceTo?: string;
102+
type: "content_type" | "global_field";
103+
}> = [];
104+
97105
const typeMap: TypeMap = {
98106
text: { func: type_text, track: true, flag: TypeFlags.BuiltinJS },
99107
number: { func: type_number, track: true, flag: TypeFlags.BuiltinJS },
@@ -152,6 +160,12 @@ export default function (userOptions: TSGenOptions) {
152160
}
153161

154162
function name_type(uid: string) {
163+
// Check if the UID starts with a number, which would create invalid TypeScript
164+
if (isNumericIdentifier(uid)) {
165+
// Return a fallback name to continue processing
166+
return `InvalidInterface_${uid}`;
167+
}
168+
155169
return [options?.naming?.prefix, _.upperFirst(_.camelCase(uid))]
156170
.filter((v) => v)
157171
.join("");
@@ -161,14 +175,41 @@ export default function (userOptions: TSGenOptions) {
161175
contentType: ContentstackTypes.ContentType | ContentstackTypes.GlobalField,
162176
systemFields = false
163177
) {
164-
const interface_declaration = [
165-
"export interface",
166-
name_type(
167-
contentType.data_type === "global_field"
168-
? (contentType.reference_to as string)
169-
: contentType.uid
170-
),
171-
];
178+
// Validate the interface name before creating it
179+
let interfaceName: string;
180+
181+
const isGlobalField = contentType.data_type === "global_field";
182+
183+
// Check if the content type's own UID starts with a number
184+
if (isNumericIdentifier(contentType.uid)) {
185+
numericIdentifierErrors.push({
186+
uid: contentType.uid,
187+
type: "content_type",
188+
});
189+
// Return a fallback interface declaration to continue processing
190+
interfaceName = `InvalidInterface_${contentType.uid}`;
191+
} else if (
192+
isGlobalField &&
193+
contentType.reference_to &&
194+
isNumericIdentifier(contentType.reference_to as string)
195+
) {
196+
// For global fields, check if the referenced content type has a numeric identifier
197+
// This is a global field error because it references an invalid content type
198+
numericIdentifierErrors.push({
199+
uid: contentType.uid, // The global field's UID
200+
type: "global_field",
201+
referenceTo: contentType.reference_to as string, // The referenced content type's UID
202+
});
203+
// Return a fallback interface declaration to continue processing
204+
interfaceName = `InvalidInterface_${contentType.reference_to}`;
205+
} else {
206+
// No numeric identifier issues, generate normal interface name
207+
interfaceName = name_type(
208+
isGlobalField ? (contentType.reference_to as string) : contentType.uid
209+
);
210+
}
211+
212+
const interface_declaration = ["export interface", interfaceName];
172213
if (systemFields && contentType.schema_type !== "global_field") {
173214
interface_declaration.push("extends", name_type("SystemFields"));
174215
}
@@ -570,6 +611,11 @@ export default function (userOptions: TSGenOptions) {
570611

571612
const definition = visit_content_type(contentType);
572613

614+
// Check for numeric identifier errors and throw them immediately
615+
if (numericIdentifierErrors.length > 0) {
616+
throwNumericIdentifierValidationError(numericIdentifierErrors);
617+
}
618+
573619
// Log summary table of skipped fields and blocks
574620
if (skippedFields.length > 0 || skippedBlocks.length > 0) {
575621
cliux.print("");

src/generateTS/index.ts

Lines changed: 33 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { defaultInterfaces } from "./stack/builtins";
1111
import { format } from "../format/index";
1212
import { ContentType } from "../types/schema";
1313
import { cliux } from "@contentstack/cli-utilities";
14+
import { createValidationError, createErrorDetails } from "./shared/utils";
1415

1516
export const generateTS = async ({
1617
token,
@@ -28,11 +29,9 @@ export const generateTS = async ({
2829
}: GenerateTS) => {
2930
try {
3031
if (!token || !tokenType || !apiKey || !environment || !region) {
31-
throw {
32-
type: "validation",
33-
error_message:
34-
"Please provide all the required params (token, tokenType, apiKey, environment, region)",
35-
};
32+
throw createValidationError(
33+
"Please provide all the required params (token, tokenType, apiKey, environment, region)"
34+
);
3635
}
3736

3837
if (tokenType === TOKEN_TYPE.DELIVERY) {
@@ -62,11 +61,9 @@ export const generateTS = async ({
6261
"Please create Content Models to generate type definitions",
6362
{ color: "yellow" }
6463
);
65-
throw {
66-
type: "validation",
67-
error_message:
68-
"There are no Content Types in the Stack, please create Content Models to generate type definitions",
69-
};
64+
throw createValidationError(
65+
"There are no Content Types in the Stack, please create Content Models to generate type definitions"
66+
);
7067
}
7168

7269
let schemas: ContentType[] = [];
@@ -95,43 +92,40 @@ export const generateTS = async ({
9592
}
9693
} catch (error: any) {
9794
if (error.type === "validation") {
98-
throw { error_message: error.error_message };
95+
// Handle validation errors with proper error codes
96+
throw {
97+
error_message: error.error_message,
98+
error_code: error.error_code || "VALIDATION_ERROR",
99+
};
99100
} else {
100101
const errorObj = JSON.parse(error.message.replace("Error: ", ""));
101102
let errorMessage = "Something went wrong";
103+
let errorCode = "API_ERROR";
104+
102105
if (errorObj.status) {
103106
switch (errorObj.status) {
104107
case 401:
105-
cliux.print("Authentication failed", {
106-
color: "red",
107-
bold: true,
108-
});
109-
cliux.print("Please check your apiKey, token, and region", {
110-
color: "yellow",
111-
});
112108
errorMessage =
113109
"Unauthorized: The apiKey, token or region is not valid.";
110+
errorCode = "AUTHENTICATION_FAILED";
114111
break;
115112
case 412:
116-
cliux.print("Invalid credentials", { color: "red", bold: true });
117-
cliux.print("Please verify your apiKey, token, and region", {
118-
color: "yellow",
119-
});
120113
errorMessage =
121114
"Invalid Credentials: Please check the provided apiKey, token and region.";
115+
errorCode = "INVALID_CREDENTIALS";
122116
break;
123117
default:
124-
cliux.print(`API Error (${errorObj.status})`, {
125-
color: "red",
126-
bold: true,
127-
});
128118
errorMessage = `${errorMessage}, ${errorObj.error_message}`;
119+
errorCode = `API_ERROR_${errorObj.status}`;
129120
}
130121
}
131122
if (errorObj.error_message && !errorObj.status) {
132123
errorMessage = `${errorMessage}, ${errorObj.error_message}`;
133124
}
134-
throw { error_message: errorMessage };
125+
throw {
126+
error_message: errorMessage,
127+
error_code: errorCode,
128+
};
135129
}
136130
}
137131
};
@@ -193,21 +187,19 @@ export const generateTSFromContentTypes = async ({
193187

194188
return output;
195189
} catch (err: any) {
196-
// Enhanced error logging with more context
197-
const errorMessage = err.message || "Unknown error occurred";
198-
const errorDetails = {
199-
error_message: `Type generation failed: ${errorMessage}`,
200-
context: "generateTSFromContentTypes",
201-
timestamp: new Date().toISOString(),
202-
error_type: err.constructor.name,
203-
};
204-
205-
// Log detailed error information for debugging
206-
cliux.print(`Type generation failed: ${errorMessage}`, {
207-
color: "red",
208-
bold: true,
209-
});
190+
// Handle numeric identifier errors specially to preserve their detailed format
191+
if (
192+
err.type === "validation" &&
193+
err.error_code === "VALIDATION_ERROR" &&
194+
err.error_message &&
195+
err.error_message.includes("numeric identifiers")
196+
) {
197+
// Pass through the detailed error as-is
198+
throw err;
199+
}
210200

201+
// Use common function to create detailed error information for other errors
202+
const errorDetails = createErrorDetails(err, "generateTSFromContentTypes");
211203
throw errorDetails;
212204
}
213205
};

0 commit comments

Comments
 (0)