From 69ac0acb3ca766473a9ac0d0162b5c30ad63cc0d Mon Sep 17 00:00:00 2001
From: praveen-mohan-cs <praveen.mohan@contentstack.com>
Date: Wed, 4 Sep 2024 11:15:44 +0530
Subject: [PATCH] Enhanced modular blocks to generate separate interface

---
 README.md                               | 54 ++++++++++---------------
 package-lock.json                       | 38 ++++++++---------
 package.json                            |  4 +-
 src/generateTS/factory.ts               | 51 +++++++++++++----------
 tests/unit/tsgen/modular.blocks.test.ts | 24 ++++++-----
 5 files changed, 88 insertions(+), 83 deletions(-)

diff --git a/README.md b/README.md
index 1a80c3f..8ac63c6 100644
--- a/README.md
+++ b/README.md
@@ -123,38 +123,7 @@ interface BuiltinExample {
   /** Single Choice */
   single_choice: "Choice 1" | "Choice 2" | "Choice 3";
   /** Modular Blocks */
-  modular_blocks?: (
-    | {
-        block_1: {
-          /** Number */
-          number?: number;
-          /** Single line textbox */
-          single_line?: string;
-        };
-        block_2: undefined;
-        seo_gf: undefined;
-      }
-    | {
-        block_2: {
-          /** Boolean */
-          boolean?: boolean;
-          /** Date */
-          date?: string;
-        };
-        block_1: undefined;
-        seo_gf: undefined;
-      }
-    | {
-        seo_gf: {
-          /** Keywords */
-          keywords?: string;
-          /** Description */
-          description?: string;
-        };
-        block_1: undefined;
-        block_2: undefined;
-      }
-  )[];
+  modular_blocks?: ModularBlocks[];
   /** Number */
   number?: number;
   /** Link */
@@ -166,6 +135,27 @@ interface BuiltinExample {
   /** Date */
   date?: string;
 }
+
+interface ModularBlocks {
+  block_1: {
+    /** Number */
+    number?: number;
+    /** Single line textbox */
+    single_line?: string;
+  };
+  block_2: {
+    /** Boolean */
+    boolean?: boolean;
+    /** Date */
+    date?: string;
+  };
+  seo_gf: {
+    /** Keywords */
+    keywords?: string;
+    /** Description */
+    description?: string;
+  };
+}
 ```
 
 #### 2. `graphqlTS()` (Available only for NodeJS)
diff --git a/package-lock.json b/package-lock.json
index 66468c5..22d30e4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,15 +1,15 @@
 {
   "name": "@contentstack/types-generator",
-  "version": "1.0.0",
+  "version": "2.0.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "@contentstack/types-generator",
-      "version": "1.0.0",
+      "version": "2.0.0",
       "license": "MIT",
       "dependencies": {
-        "@contentstack/delivery-sdk": "^4.0.5",
+        "@contentstack/delivery-sdk": "^4.1.0",
         "@gql2ts/from-schema": "^2.0.0-4",
         "axios": "^1.7.4",
         "lodash": "^4.17.21",
@@ -599,23 +599,23 @@
       "dev": true
     },
     "node_modules/@contentstack/core": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/@contentstack/core/-/core-1.0.3.tgz",
-      "integrity": "sha512-yZaZM/I5NWY1LskyXvvkMG6CIqQN92EFb5cB7jeA8ahZ8RSF/5pDBKRM6P3BcHRfQ8JQOWkFs4M5ZiDTadIHrA==",
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@contentstack/core/-/core-1.1.0.tgz",
+      "integrity": "sha512-yZZswNe8yw6hH+uAIsFTHL5/rP6jJ5oyjy6S1BPHiCaTc24osQrsMHzG3PxLjC1Z6F0976gEbBkquln+deRaXA==",
       "dependencies": {
-        "axios": "^1.6.8",
+        "axios": "^1.7.2",
         "axios-mock-adapter": "^1.22.0",
         "lodash": "^4.17.21",
-        "qs": "^6.12.1",
-        "tslib": "^2.6.2"
+        "qs": "^6.13.0",
+        "tslib": "^2.6.3"
       }
     },
     "node_modules/@contentstack/delivery-sdk": {
-      "version": "4.0.5",
-      "resolved": "https://registry.npmjs.org/@contentstack/delivery-sdk/-/delivery-sdk-4.0.5.tgz",
-      "integrity": "sha512-sqNxOu8wXUS0caPa7PML+0c2HOoq0owX3XIWlvigFkTKLKTDJWAH+N7TdDnJAI4q0/JWLyGGvOKVIPuFh2SQMg==",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/@contentstack/delivery-sdk/-/delivery-sdk-4.1.0.tgz",
+      "integrity": "sha512-AlYRoRpkEI+AtSf5EKxoAOnfqycF1ji5WnW/3CdF6XOk3VtZmWD6dat3R8BNObo4vuVNq+hWYPMz29EgBiogsQ==",
       "dependencies": {
-        "@contentstack/core": "^1.0.3",
+        "@contentstack/core": "^1.1.0",
         "@contentstack/utils": "^1.3.8",
         "@types/humps": "^2.0.6",
         "axios": "^1.7.2",
@@ -4772,9 +4772,9 @@
       ]
     },
     "node_modules/qs": {
-      "version": "6.12.3",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.3.tgz",
-      "integrity": "sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==",
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+      "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
       "dependencies": {
         "side-channel": "^1.0.6"
       },
@@ -5448,9 +5448,9 @@
       }
     },
     "node_modules/tslib": {
-      "version": "2.6.3",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
-      "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
+      "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
     },
     "node_modules/tsup": {
       "version": "8.1.0",
diff --git a/package.json b/package.json
index 6ffc54b..b713c5a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@contentstack/types-generator",
-  "version": "1.0.4",
+  "version": "2.0.0",
   "description": "Contentstack type definition generation library",
   "private": false,
   "author": "Contentstack",
@@ -42,7 +42,7 @@
     "typescript": "^5.4.5"
   },
   "dependencies": {
-    "@contentstack/delivery-sdk": "^4.0.5",
+    "@contentstack/delivery-sdk": "^4.1.0",
     "@gql2ts/from-schema": "^2.0.0-4",
     "axios": "^1.7.4",
     "lodash": "^4.17.21",
diff --git a/src/generateTS/factory.ts b/src/generateTS/factory.ts
index e6ef688..5f135d6 100644
--- a/src/generateTS/factory.ts
+++ b/src/generateTS/factory.ts
@@ -65,6 +65,7 @@ export default function (userOptions: TSGenOptions) {
   const visitedGlobalFields = new Set<string>();
   const visitedContentTypes = new Set<string>();
   const cachedGlobalFields: GlobalFieldCache = {};
+  const modularBlockInterfaces = new Set<string>();
 
   const typeMap: TypeMap = {
     text: { func: type_text, track: true, flag: TypeFlags.BuiltinJS },
@@ -233,6 +234,8 @@ export default function (userOptions: TSGenOptions) {
       if (field.multiple) {
         fieldType += "[]";
       }
+    } else if (field.data_type === "blocks") {
+      fieldType = type_modular_blocks(field);
     }
     return [
       field.uid + op_required(field.mandatory) + ":",
@@ -260,7 +263,8 @@ export default function (userOptions: TSGenOptions) {
   function visit_content_type(
     contentType: ContentstackTypes.ContentType | ContentstackTypes.GlobalField
   ) {
-    return [
+    modularBlockInterfaces.clear();
+    const contentTypeInterface = [
       options.docgen.interface(contentType.description),
       define_interface(contentType, options.systemFields),
       "{",
@@ -271,29 +275,34 @@ export default function (userOptions: TSGenOptions) {
     ]
       .filter((v) => v)
       .join("\n");
-  }
 
-  function visit_modular_block(
-    field: ContentstackTypes.Field,
-    block: ContentstackTypes.Block
-  ) {
-    return (
-      "{" +
-      [
-        block.uid + ":",
-        block.reference_to
-          ? name_type(block.reference_to as string) + ";"
-          : "{" + visit_fields(block.schema || []) + "};",
-      ].join(" ") +
-      visit_block_names(field, block) +
-      "}"
-    );
+    return [...modularBlockInterfaces, contentTypeInterface].join("\n\n");
   }
 
-  function type_modular_blocks(field: ContentstackTypes.Field) {
-    return op_paren(
-      field.blocks.map((block) => visit_modular_block(field, block)).join(" | ")
-    );
+  function type_modular_blocks(field: ContentstackTypes.Field): string {
+    const blockInterfaceName = name_type(field.uid);
+    const blockInterfaces = field.blocks.map((block) => {
+      const fieldType =
+        block.reference_to && cachedGlobalFields[name_type(block.reference_to)]
+          ? name_type(block.reference_to)
+          : visit_fields(block.schema || []);
+
+      const schema = block.reference_to
+        ? `${fieldType};`
+        : `{\n ${fieldType} }`;
+      return `${block.uid}: ${schema}`;
+    });
+
+    const modularInterface = [
+      `export interface ${blockInterfaceName} {`,
+      blockInterfaces.join("\n"),
+      "}",
+    ].join("\n");
+
+    // Store or track the generated block interface for later use
+    modularBlockInterfaces.add(modularInterface);
+
+    return field.multiple ? `${blockInterfaceName}[]` : blockInterfaceName;
   }
 
   function type_group(field: ContentstackTypes.Field) {
diff --git a/tests/unit/tsgen/modular.blocks.test.ts b/tests/unit/tsgen/modular.blocks.test.ts
index 6414b55..5e85c7e 100644
--- a/tests/unit/tsgen/modular.blocks.test.ts
+++ b/tests/unit/tsgen/modular.blocks.test.ts
@@ -18,20 +18,26 @@ describe("modular blocks", () => {
 
   test("definition", () => {
     expect(result.definition).toMatchInlineSnapshot(`
-      "export interface ModularBlocks
+      "export interface ModularBlocks {
+      string_block: {
+       single_line?: string  ;
+      multi_line?: string  ;
+      markdown?: string  ;
+      rich_text_editor?: string  ; }
+      string_block_with_options: {
+       single_line_textbox_required: string  ;
+      single_line_textbox_multiple?: string[]  ; }
+      boolean_block: {
+       boolean?: boolean  ; }
+      }
+
+      export interface ModularBlocks
       {
       /** Version */
       _version:  2 ;
       title: string  ;
       url: string  ;
-      modular_blocks?: ({string_block: {single_line?: string  ;
-      multi_line?: string  ;
-      markdown?: string  ;
-      rich_text_editor?: string  ;};string_block_with_options: undefined;
-      boolean_block: undefined;} | {string_block_with_options: {single_line_textbox_required: string  ;
-      single_line_textbox_multiple?: string[]  ;};string_block: undefined;
-      boolean_block: undefined;} | {boolean_block: {boolean?: boolean  ;};string_block: undefined;
-      string_block_with_options: undefined;})[]  ;
+      modular_blocks?: ModularBlocks[]  ;
       }"
     `);
   });