From 170f46fbc3f711ed5d389b082e5ea23878ab6968 Mon Sep 17 00:00:00 2001
From: "Huu Le (Lee)" <39040748+leehuwuj@users.noreply.github.com>
Date: Thu, 1 Feb 2024 10:57:10 +0700
Subject: [PATCH] fix(create-llama): generate code option fail (#491)

---
 e2e/basic.spec.ts | 198 +++++++++++++++++++++++++---------------------
 e2e/utils.ts      |  21 +++++
 helpers/index.ts  |  19 +++--
 questions.ts      |   2 +-
 4 files changed, 140 insertions(+), 100 deletions(-)

diff --git a/e2e/basic.spec.ts b/e2e/basic.spec.ts
index 9d4a09ec..2090e0fe 100644
--- a/e2e/basic.spec.ts
+++ b/e2e/basic.spec.ts
@@ -6,6 +6,7 @@ import path from "path";
 import type {
   TemplateEngine,
   TemplateFramework,
+  TemplatePostInstallAction,
   TemplateType,
   TemplateUI,
 } from "../helpers";
@@ -19,110 +20,123 @@ const templateFrameworks: TemplateFramework[] = [
 ];
 const templateEngines: TemplateEngine[] = ["simple", "context"];
 const templateUIs: TemplateUI[] = ["shadcn", "html"];
+const templatePostInstallActions: TemplatePostInstallAction[] = [
+  "none",
+  "runApp",
+];
 
 for (const templateType of templateTypes) {
   for (const templateFramework of templateFrameworks) {
     for (const templateEngine of templateEngines) {
       for (const templateUI of templateUIs) {
-        if (templateFramework === "nextjs" && templateType === "simple") {
-          // nextjs doesn't support simple templates - skip tests
-          continue;
-        }
-        const appType: AppType =
-          templateFramework === "express" || templateFramework === "fastapi"
-            ? templateType === "simple"
-              ? "--no-frontend" // simple templates don't have frontends
-              : "--frontend"
-            : "";
-        if (appType === "--no-frontend" && templateUI !== "html") {
-          // if there's no frontend, don't iterate over UIs
-          continue;
-        }
-        test.describe(`try create-llama ${templateType} ${templateFramework} ${templateEngine} ${templateUI} ${appType}`, async () => {
-          let port: number;
-          let externalPort: number;
-          let cwd: string;
-          let name: string;
-          let appProcess: ChildProcess;
-          const postInstallAction = "runApp";
+        for (const templatePostInstallAction of templatePostInstallActions) {
+          if (templateFramework === "nextjs" && templateType === "simple") {
+            // nextjs doesn't support simple templates - skip tests
+            continue;
+          }
+          const appType: AppType =
+            templateFramework === "express" || templateFramework === "fastapi"
+              ? templateType === "simple"
+                ? "--no-frontend" // simple templates don't have frontends
+                : "--frontend"
+              : "";
+          if (appType === "--no-frontend" && templateUI !== "html") {
+            // if there's no frontend, don't iterate over UIs
+            continue;
+          }
+          test.describe(`try create-llama ${templateType} ${templateFramework} ${templateEngine} ${templateUI} ${appType} ${templatePostInstallAction}`, async () => {
+            let port: number;
+            let externalPort: number;
+            let cwd: string;
+            let name: string;
+            let appProcess: ChildProcess;
+            // Only test without using vector db for now
+            const vectorDb = "none";
 
-          test.beforeAll(async () => {
-            port = Math.floor(Math.random() * 10000) + 10000;
-            externalPort = port + 1;
-            cwd = await createTestDir();
-            const result = await runCreateLlama(
-              cwd,
-              templateType,
-              templateFramework,
-              templateEngine,
-              templateUI,
-              appType,
-              port,
-              externalPort,
-              postInstallAction,
-            );
-            name = result.projectName;
-            appProcess = result.appProcess;
-          });
+            test.beforeAll(async () => {
+              port = Math.floor(Math.random() * 10000) + 10000;
+              externalPort = port + 1;
+              cwd = await createTestDir();
+              const result = await runCreateLlama(
+                cwd,
+                templateType,
+                templateFramework,
+                templateEngine,
+                templateUI,
+                vectorDb,
+                appType,
+                port,
+                externalPort,
+                templatePostInstallAction,
+              );
+              name = result.projectName;
+              appProcess = result.appProcess;
+            });
 
-          test("App folder should exist", async () => {
-            const dirExists = fs.existsSync(path.join(cwd, name));
-            expect(dirExists).toBeTruthy();
-          });
-          test("Frontend should have a title", async ({ page }) => {
-            test.skip(appType === "--no-frontend");
-            await page.goto(`http://localhost:${port}`);
-            await expect(page.getByText("Built by LlamaIndex")).toBeVisible();
-          });
+            test("App folder should exist", async () => {
+              const dirExists = fs.existsSync(path.join(cwd, name));
+              expect(dirExists).toBeTruthy();
+            });
+            test("Frontend should have a title", async ({ page }) => {
+              test.skip(templatePostInstallAction !== "runApp");
+              test.skip(appType === "--no-frontend");
+              await page.goto(`http://localhost:${port}`);
+              await expect(page.getByText("Built by LlamaIndex")).toBeVisible();
+            });
 
-          test("Frontend should be able to submit a message and receive a response", async ({
-            page,
-          }) => {
-            test.skip(appType === "--no-frontend");
-            await page.goto(`http://localhost:${port}`);
-            await page.fill("form input", "hello");
-            await page.click("form button[type=submit]");
-            const response = await page.waitForResponse(
-              (res) => {
-                return res.url().includes("/api/chat") && res.status() === 200;
-              },
-              {
-                timeout: 1000 * 60,
-              },
-            );
-            const text = await response.text();
-            console.log("AI response when submitting message: ", text);
-            expect(response.ok()).toBeTruthy();
-          });
+            test("Frontend should be able to submit a message and receive a response", async ({
+              page,
+            }) => {
+              test.skip(templatePostInstallAction !== "runApp");
+              test.skip(appType === "--no-frontend");
+              await page.goto(`http://localhost:${port}`);
+              await page.fill("form input", "hello");
+              await page.click("form button[type=submit]");
+              const response = await page.waitForResponse(
+                (res) => {
+                  return (
+                    res.url().includes("/api/chat") && res.status() === 200
+                  );
+                },
+                {
+                  timeout: 1000 * 60,
+                },
+              );
+              const text = await response.text();
+              console.log("AI response when submitting message: ", text);
+              expect(response.ok()).toBeTruthy();
+            });
 
-          test("Backend should response when calling API", async ({
-            request,
-          }) => {
-            test.skip(appType !== "--no-frontend");
-            const backendPort = appType === "" ? port : externalPort;
-            const response = await request.post(
-              `http://localhost:${backendPort}/api/chat`,
-              {
-                data: {
-                  messages: [
-                    {
-                      role: "user",
-                      content: "Hello",
-                    },
-                  ],
+            test("Backend should response when calling API", async ({
+              request,
+            }) => {
+              test.skip(templatePostInstallAction !== "runApp");
+              test.skip(appType !== "--no-frontend");
+              const backendPort = appType === "" ? port : externalPort;
+              const response = await request.post(
+                `http://localhost:${backendPort}/api/chat`,
+                {
+                  data: {
+                    messages: [
+                      {
+                        role: "user",
+                        content: "Hello",
+                      },
+                    ],
+                  },
                 },
-              },
-            );
-            const text = await response.text();
-            console.log("AI response when calling API: ", text);
-            expect(response.ok()).toBeTruthy();
-          });
+              );
+              const text = await response.text();
+              console.log("AI response when calling API: ", text);
+              expect(response.ok()).toBeTruthy();
+            });
 
-          // clean processes
-          test.afterAll(async () => {
-            appProcess?.kill();
+            // clean processes
+            test.afterAll(async () => {
+              appProcess?.kill();
+            });
           });
-        });
+        }
       }
     }
   }
diff --git a/e2e/utils.ts b/e2e/utils.ts
index d8223278..b06776da 100644
--- a/e2e/utils.ts
+++ b/e2e/utils.ts
@@ -9,6 +9,7 @@ import {
   TemplatePostInstallAction,
   TemplateType,
   TemplateUI,
+  TemplateVectorDB,
 } from "../helpers";
 
 export type AppType = "--frontend" | "--no-frontend" | "";
@@ -67,6 +68,7 @@ export async function runCreateLlama(
   templateFramework: TemplateFramework,
   templateEngine: TemplateEngine,
   templateUI: TemplateUI,
+  vectorDb: TemplateVectorDB,
   appType: AppType,
   port: number,
   externalPort: number,
@@ -100,6 +102,8 @@ export async function runCreateLlama(
     templateEngine,
     "--ui",
     templateUI,
+    "--vector-db",
+    vectorDb,
     "--model",
     MODEL,
     "--open-ai-key",
@@ -139,6 +143,23 @@ export async function runCreateLlama(
       externalPort,
       1000 * 60 * 5,
     );
+  } else {
+    // wait create-llama to exit
+    // we don't test install dependencies for now, so just set timeout for 10 seconds
+    await new Promise((resolve, reject) => {
+      const timeout = setTimeout(() => {
+        reject(new Error("create-llama timeout error"));
+      }, 1000 * 10);
+      appProcess.on("exit", (code) => {
+        if (code !== 0 && code !== null) {
+          clearTimeout(timeout);
+          reject(new Error("create-llama command was failed!"));
+        } else {
+          clearTimeout(timeout);
+          resolve(undefined);
+        }
+      });
+    });
   }
 
   return {
diff --git a/helpers/index.ts b/helpers/index.ts
index f5467816..010bc6bd 100644
--- a/helpers/index.ts
+++ b/helpers/index.ts
@@ -207,13 +207,18 @@ export const installTemplate = async (
 
     if (props.engine === "context") {
       await copyContextData(props.root, props.dataSource);
-      await installDependencies(
-        props.framework,
-        props.packageManager,
-        props.openAiKey,
-        props.vectorDb,
-      );
-      console.log("installed Dependencies");
+      if (
+        props.postInstallAction === "runApp" ||
+        props.postInstallAction === "dependencies"
+      ) {
+        await installDependencies(
+          props.framework,
+          props.packageManager,
+          props.openAiKey,
+          props.vectorDb,
+        );
+        console.log("installed dependencies");
+      }
     }
   } else {
     // this is a frontend for a full-stack app, create .env file with model information
diff --git a/questions.ts b/questions.ts
index 59aa5151..e1b5824f 100644
--- a/questions.ts
+++ b/questions.ts
@@ -540,7 +540,7 @@ export const askQuestions = async (
     };
   }
 
-  if (!program.engine && program.engine !== "simple" && !program.vectorDb) {
+  if (program.engine !== "simple" && !program.vectorDb) {
     if (ciInfo.isCI) {
       program.vectorDb = getPrefOrDefault("vectorDb");
     } else {
-- 
GitLab