Skip to content

Commit 45c97a7

Browse files
Merge pull request #119 from contentstack/development
DX | 18-08-25 | Release
2 parents 7b62775 + a9c922f commit 45c97a7

10 files changed

Lines changed: 406 additions & 246 deletions

File tree

.talismanrc

Lines changed: 5 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: 935e3c4e56b12c01608ff169a0d025acee6a01e7464fa500f50c1c202c8af08b
6+
checksum: 2e59256a4223df4fa670d9bedb571586daa21e59194400b8f9aa4725d378cc72
77
- filename: .husky/pre-commit
88
checksum: 5baabd7d2c391648163f9371f0e5e9484f8fb90fa2284cfc378732ec3192c193
99
- filename: src/graphqlTS/index.ts
@@ -14,4 +14,8 @@ fileignoreconfig:
1414
checksum: a7febf9673f6bb759da48c1984cffbf813861a9e2695f1416741ca17aeeb71d7
1515
- filename: src/generateTS/factory.ts
1616
checksum: d6dd1ebc15493f9ed5e748f93d2f7c11b8dead5e0985482677270d450b94d270
17+
- filename: tests/integration/graphqlTS/graphqlTS.test.ts
18+
checksum: 7ffa82084fd0fc2f5ec9c8e8124cdf1bce08c7f75c5be2ede2eb5cd506812db1
19+
- filename: tests/integration/generateTS/generateTS.test.ts
20+
checksum: 7c1bc7d659ee2f9f52bf644b9e512984f89e0ff6aa4288b6e30b2c899bf80123
1721
version: "1.0"

package-lock.json

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@contentstack/types-generator",
3-
"version": "3.5.0",
3+
"version": "3.6.0",
44
"description": "Contentstack type definition generation library",
55
"private": false,
66
"author": "Contentstack",

src/generateTS/factory.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type TSGenOptions = {
1717
};
1818
systemFields?: boolean;
1919
isEditableTags?: boolean;
20+
includeReferencedEntry?: boolean;
2021
};
2122

2223
export type TSGenResult = {
@@ -73,6 +74,7 @@ const defaultOptions: TSGenOptions = {
7374
},
7475
systemFields: false,
7576
isEditableTags: false,
77+
includeReferencedEntry: false,
7678
};
7779

7880
export default function (userOptions: TSGenOptions) {
@@ -493,6 +495,33 @@ export default function (userOptions: TSGenOptions) {
493495
return name_type(field.reference_to);
494496
}
495497

498+
function buildReferenceArrayType(references: string[], options: any): string {
499+
// If no valid references remain, return a more specific fallback type
500+
if (references.length === 0) {
501+
return "Record<string, unknown>[]";
502+
}
503+
504+
// Handle reference types with or without ReferencedEntry interface
505+
if (options.includeReferencedEntry) {
506+
const referencedEntryType = `${options.naming?.prefix || ""}ReferencedEntry`;
507+
508+
const wrapWithReferencedEntry = (refType: string) =>
509+
`(${refType} | ${referencedEntryType})`;
510+
511+
const types =
512+
references.length === 1
513+
? wrapWithReferencedEntry(references[0])
514+
: references.map(wrapWithReferencedEntry).join(" | ");
515+
516+
return `${types}[]`;
517+
}
518+
519+
const baseType =
520+
references.length === 1 ? references[0] : references.join(" | ");
521+
522+
return `${baseType}[]`;
523+
}
524+
496525
function type_reference(field: ContentstackTypes.Field) {
497526
const references: string[] = [];
498527

@@ -520,25 +549,7 @@ export default function (userOptions: TSGenOptions) {
520549
}
521550
}
522551

523-
// If no valid references remain, return a more specific fallback type
524-
if (references.length === 0) {
525-
return "Record<string, unknown>[]";
526-
}
527-
528-
// Use the ReferencedEntry interface from builtins
529-
const referencedEntryType = `${options.naming?.prefix || ""}ReferencedEntry`;
530-
531-
// If there's only one reference type, create a simple union
532-
if (references.length === 1) {
533-
return `(${references[0]} | ${referencedEntryType})[]`;
534-
}
535-
536-
// If there are multiple reference types, create separate unions for each
537-
const unionTypes = references.map((refType) => {
538-
return `(${refType} | ${referencedEntryType})`;
539-
});
540-
541-
return `${unionTypes.join(" | ")}[]`;
552+
return buildReferenceArrayType(references, options);
542553
}
543554

544555
return function (

src/generateTS/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { flatMap, flatten } from "lodash";
33
import { TOKEN_TYPE } from "../constants";
44
import { initializeContentstackSdk } from "../sdk/utils";
55
import { GenerateTS, GenerateTSFromContentTypes } from "../types";
6-
import * as fs from "fs";
76
import { DocumentationGenerator } from "./docgen/doc";
87
import JSDocumentationGenerator from "./docgen/jsdoc";
98
import NullDocumentationGenerator from "./docgen/nulldoc";
@@ -24,6 +23,7 @@ export const generateTS = async ({
2423
includeDocumentation,
2524
systemFields,
2625
isEditableTags,
26+
includeReferencedEntry,
2727
host,
2828
}: GenerateTS) => {
2929
try {
@@ -88,6 +88,7 @@ export const generateTS = async ({
8888
includeDocumentation,
8989
systemFields,
9090
isEditableTags,
91+
includeReferencedEntry,
9192
});
9293
return generatedTS;
9394
}
@@ -141,6 +142,7 @@ export const generateTSFromContentTypes = async ({
141142
includeDocumentation = true,
142143
systemFields = false,
143144
isEditableTags = false,
145+
includeReferencedEntry = false,
144146
}: GenerateTSFromContentTypes) => {
145147
try {
146148
const docgen: DocumentationGenerator = includeDocumentation
@@ -154,6 +156,7 @@ export const generateTSFromContentTypes = async ({
154156
naming: { prefix },
155157
systemFields,
156158
isEditableTags,
159+
includeReferencedEntry,
157160
});
158161
for (const contentType of contentTypes) {
159162
const tsgenResult = tsgen(contentType);
@@ -180,7 +183,8 @@ export const generateTSFromContentTypes = async ({
180183
prefix,
181184
systemFields,
182185
isEditableTags,
183-
hasJsonField
186+
hasJsonField,
187+
includeReferencedEntry
184188
).join("\n\n"),
185189
[...globalFields].join("\n\n"),
186190
definitions.join("\n\n"),

src/generateTS/stack/builtins.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,36 @@ export const defaultInterfaces = (
44
prefix = "",
55
systemFields = false,
66
isEditableTags = false,
7-
hasJsonRte?: boolean
7+
hasJsonRte?: boolean,
8+
includeReferencedEntry = false
89
) => {
910
const defaultInterfaces = [
1011
`type BuildTuple<T, N extends number, R extends T[] = []> =
1112
R['length'] extends N ? R : BuildTuple<T, N, [...R, T]>`,
1213
`type TuplePrefixes<T extends any[]> =
1314
T extends [any, ...infer Rest] ? T | TuplePrefixes<Rest extends any[] ? Rest : []> : []`,
1415
`type MaxTuple<T, N extends number> = TuplePrefixes<BuildTuple<T, N>>`,
15-
`export interface ${prefix}ReferencedEntry {
16+
];
17+
18+
// Conditionally include ReferencedEntry interface
19+
if (includeReferencedEntry) {
20+
defaultInterfaces.push(
21+
`export interface ${prefix}ReferencedEntry {
1622
uid: string;
1723
_content_type_uid: string;
18-
}`,
24+
}`
25+
);
26+
}
27+
28+
defaultInterfaces.push(
1929
`export interface ${prefix}PublishDetails {
2030
environment: string;
2131
locale: string;
2232
time: string;
2333
user: string;
24-
}`,
34+
}`
35+
);
36+
defaultInterfaces.push(
2537
`export interface ${prefix}File {
2638
uid: string;
2739
created_at: string;
@@ -45,19 +57,25 @@ export const defaultInterfaces = (
4557
width: number;
4658
}
4759
publish_details: ${prefix}PublishDetails;
48-
}`,
60+
}`
61+
);
62+
defaultInterfaces.push(
4963
`export interface ${prefix}Link {
5064
title: string;
5165
href: string;
52-
}`,
66+
}`
67+
);
68+
defaultInterfaces.push(
5369
`export interface ${prefix}Taxonomy {
5470
taxonomy_uid: string;
5571
max_terms?: number;
5672
mandatory: boolean;
5773
non_localizable: boolean;
58-
}`,
59-
`export type ${prefix}TaxonomyEntry = ${prefix}Taxonomy & { term_uid: string }`,
60-
];
74+
}`
75+
);
76+
defaultInterfaces.push(
77+
`export type ${prefix}TaxonomyEntry = ${prefix}Taxonomy & { term_uid: string }`
78+
);
6179
if (hasJsonRte) {
6280
defaultInterfaces.push(
6381
`export interface JSONRTENode {

src/types/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface GenerateTSBase extends StackConnectionConfig {
2424
includeDocumentation?: boolean;
2525
systemFields?: boolean;
2626
isEditableTags?: boolean;
27+
includeReferencedEntry?: boolean;
2728
}
2829

2930
export type GenerateTS = GenerateTSBase;
@@ -38,4 +39,5 @@ export interface GenerateTSFromContentTypes {
3839
includeDocumentation?: boolean;
3940
systemFields?: boolean;
4041
isEditableTags?: boolean;
42+
includeReferencedEntry?: boolean;
4143
}

tests/integration/generateTS/generateTS.test.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,58 @@ describe("generateTS function", () => {
111111
});
112112

113113
expect(generatedTS).toEqual(expect.stringContaining("interface")); // Check for Output is not undefined
114-
expect(generatedTS).toMatch(/export interface CSLPAttribute/); // Check for CSLP attribute interface is created
115-
expect(generatedTS).toMatch(/export type CSLPFieldMapping/); // Check for CSLP field mapping type is created
114+
expect(generatedTS).toMatch(/Dishes/); // Check for whether typeDef of Content type is included
115+
expect(generatedTS).toMatch(/export interface CSLPAttribute/); // Check for whether CSLP attribute interface is created
116+
expect(generatedTS).toMatch(/export type CSLPFieldMapping/); // Check for whether CSLP field mapping type is created
116117
expect(generatedTS).toMatch(/\$\?\:/); // Check for editable field mappings with $ property
117118
expect(generatedTS).toMatch(/\?\: CSLPFieldMapping/); // Check for individual field CSLP mappings
118119
expect(generatedTS).toMatch(/\/\*\*.*\*\/\n\s*(export)/); // Check for Documentation is generated
119120
});
121+
122+
it("generates type definitions with ReferencedEntry enabled", async () => {
123+
const token = process.env.TOKEN as unknown as any;
124+
const apiKey = process.env.APIKEY as unknown as any;
125+
const environment = process.env.ENVIRONMENT as unknown as any;
126+
const region = process.env.REGION as unknown as any;
127+
const tokenType = process.env.TOKENTYPE as unknown as any;
128+
const includeReferencedEntry = true;
129+
130+
const generatedTS = await generateTS({
131+
token,
132+
apiKey,
133+
environment,
134+
region,
135+
tokenType,
136+
includeReferencedEntry,
137+
});
138+
139+
expect(generatedTS).toEqual(expect.stringContaining("interface")); // Check for Output is not undefined
140+
expect(generatedTS).toEqual(expect.stringContaining("Dishes")); // Check for whether typeDef of Content type is included
141+
expect(generatedTS).toMatch(/ReferencedEntry/); // Check that ReferencedEntry interface is included
142+
expect(generatedTS).toMatch(/\/\*\*.*\*\/\n\s*(export)/); // Check for Documentation is generated
143+
});
144+
145+
it("generates type definitions without ReferencedEntry (default)", async () => {
146+
const token = process.env.TOKEN as unknown as any;
147+
const apiKey = process.env.APIKEY as unknown as any;
148+
const environment = process.env.ENVIRONMENT as unknown as any;
149+
const region = process.env.REGION as unknown as any;
150+
const tokenType = process.env.TOKENTYPE as unknown as any;
151+
// Don't pass includeReferencedEntry, should default to false
152+
153+
const generatedTS = await generateTS({
154+
token,
155+
apiKey,
156+
environment,
157+
region,
158+
tokenType,
159+
});
160+
161+
expect(generatedTS).toEqual(expect.stringContaining("interface")); // Check for Output is not undefined
162+
expect(generatedTS).toEqual(expect.stringContaining("Dishes")); // Check for whether typeDef of Content type is included
163+
expect(generatedTS).not.toMatch(/ReferencedEntry/); // Check that ReferencedEntry interface is not included
164+
expect(generatedTS).toMatch(/\/\*\*.*\*\/\n\s*(export)/); // Check for Documentation is generated
165+
});
120166
});
121167

122168
describe("generateTS function with errors", () => {

tests/integration/graphqlTS/graphqlTS.test.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ describe("graphqlTS function with errors", () => {
6666
});
6767

6868
it("check for if wrong apiKey, token and environment is provided", async () => {
69-
const token = process.env.TOKEN as unknown as any;
70-
const apiKey = "process.env.APIKEY" as unknown as any;
71-
const environment = process.env.ENVIRONMENT as unknown as any;
72-
const region = process.env.REGION as unknown as any;
73-
const branch = process.env.BRANCH as unknown as any;
69+
const token = "";
70+
const apiKey = "";
71+
const environment = "";
72+
const region = "US";
73+
const branch = "main";
7474

7575
try {
7676
await graphqlTS({
@@ -81,8 +81,9 @@ describe("graphqlTS function with errors", () => {
8181
branch,
8282
});
8383
} catch (err: any) {
84+
expect(err.error_message).toBeDefined();
8485
expect(err.error_message).toEqual(
85-
"Unauthorized: The apiKey, token or environment is not valid."
86+
"Please provide all the required params (token, apiKey, environment, region)"
8687
);
8788
}
8889
});

0 commit comments

Comments
 (0)