From 9492cc64b515b61bb5b097bd7666e2ee8dfaeeab Mon Sep 17 00:00:00 2001
From: "Huu Le (Lee)" <39040748+leehuwuj@users.noreply.github.com>
Date: Tue, 16 Jan 2024 16:12:37 +0700
Subject: [PATCH] Added option to automatically install dependencies (for
 Python and TS) (#381)

---
 .changeset/silly-bugs-fold.md               |  5 +++
 .github/workflows/e2e.yml                   | 12 +++++++
 packages/create-llama/create-app.ts         |  2 ++
 packages/create-llama/e2e/utils.ts          |  1 +
 packages/create-llama/helpers/index.ts      | 22 +++++++-----
 packages/create-llama/helpers/poetry.ts     | 34 ++++++++++++++++++
 packages/create-llama/helpers/python.ts     | 39 ++++++++++++++++++---
 packages/create-llama/helpers/types.ts      |  1 +
 packages/create-llama/helpers/typescript.ts | 19 +++++-----
 packages/create-llama/index.ts              |  8 +++++
 packages/create-llama/questions.ts          | 13 +++++++
 11 files changed, 135 insertions(+), 21 deletions(-)
 create mode 100644 .changeset/silly-bugs-fold.md
 create mode 100644 packages/create-llama/helpers/poetry.ts

diff --git a/.changeset/silly-bugs-fold.md b/.changeset/silly-bugs-fold.md
new file mode 100644
index 000000000..b34dfa940
--- /dev/null
+++ b/.changeset/silly-bugs-fold.md
@@ -0,0 +1,5 @@
+---
+"create-llama": patch
+---
+
+Added option to automatically install dependencies (for Python and TS)
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index f87865ebf..cc4dbadce 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -8,6 +8,9 @@ on:
       - ".github/workflows/e2e.yml"
     branches: [main]
 
+env:
+  POETRY_VERSION: "1.6.1"
+
 jobs:
   e2e:
     name: create-llama
@@ -16,10 +19,19 @@ jobs:
       fail-fast: true
       matrix:
         node-version: [18, 20]
+        python-version: ["3.11"]
         os: [macos-latest, windows-latest]
     runs-on: ${{ matrix.os }}
     steps:
       - uses: actions/checkout@v4
+      - name: Set up python ${{ matrix.python-version }}
+        uses: actions/setup-python@v4
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install Poetry
+        uses: snok/install-poetry@v1
+        with:
+          version: ${{ env.POETRY_VERSION }}
       - uses: pnpm/action-setup@v2
       - name: Setup Node.js ${{ matrix.node-version }}
         uses: actions/setup-node@v4
diff --git a/packages/create-llama/create-app.ts b/packages/create-llama/create-app.ts
index 65ff0a9f7..a0d437010 100644
--- a/packages/create-llama/create-app.ts
+++ b/packages/create-llama/create-app.ts
@@ -34,6 +34,7 @@ export async function createApp({
   communityProjectPath,
   vectorDb,
   externalPort,
+  installDependencies,
 }: InstallAppArgs): Promise<void> {
   const root = path.resolve(appPath);
 
@@ -75,6 +76,7 @@ export async function createApp({
     communityProjectPath,
     vectorDb,
     externalPort,
+    installDependencies,
   };
 
   if (frontend) {
diff --git a/packages/create-llama/e2e/utils.ts b/packages/create-llama/e2e/utils.ts
index 8bbc1330e..a792c13e4 100644
--- a/packages/create-llama/e2e/utils.ts
+++ b/packages/create-llama/e2e/utils.ts
@@ -104,6 +104,7 @@ export function runCreateLlama(
     "--use-npm",
     "--external-port",
     externalPort,
+    "--install-dependencies",
   ].join(" ");
   console.log(`running command '${command}' in ${cwd}`);
   execSync(command, {
diff --git a/packages/create-llama/helpers/index.ts b/packages/create-llama/helpers/index.ts
index 3f8dad54b..858d01d95 100644
--- a/packages/create-llama/helpers/index.ts
+++ b/packages/create-llama/helpers/index.ts
@@ -7,6 +7,7 @@ import { cyan } from "picocolors";
 
 import { COMMUNITY_OWNER, COMMUNITY_REPO } from "./constant";
 import { PackageManager } from "./get-pkg-manager";
+import { isHavingPoetryLockFile, tryPoetryRun } from "./poetry";
 import { installPythonTemplate } from "./python";
 import { downloadAndExtractRepo } from "./repo";
 import {
@@ -89,18 +90,23 @@ const copyTestData = async (
   if (packageManager && engine === "context") {
     const runGenerate = `${cyan(
       framework === "fastapi"
-        ? "python app/engine/generate.py"
+        ? "poetry run python app/engine/generate.py"
         : `${packageManager} run generate`,
     )}`;
     const hasOpenAiKey = openAiKey || process.env["OPENAI_API_KEY"];
     const hasVectorDb = vectorDb && vectorDb !== "none";
-    const shouldRunGenerateAfterInstall =
-      hasOpenAiKey && framework !== "fastapi" && vectorDb === "none";
-    if (shouldRunGenerateAfterInstall) {
-      console.log(`\nRunning ${runGenerate} to generate the context data.\n`);
-      await callPackageManager(packageManager, true, ["run", "generate"]);
-      console.log();
-      return;
+    if (framework === "fastapi") {
+      if (hasOpenAiKey && vectorDb === "none" && isHavingPoetryLockFile()) {
+        console.log(`Running ${runGenerate} to generate the context data.`);
+        tryPoetryRun("python app/engine/generate.py");
+        return;
+      }
+    } else {
+      if (hasOpenAiKey && vectorDb === "none") {
+        console.log(`Running ${runGenerate} to generate the context data.`);
+        await callPackageManager(packageManager, true, ["run", "generate"]);
+        return;
+      }
     }
 
     const settings = [];
diff --git a/packages/create-llama/helpers/poetry.ts b/packages/create-llama/helpers/poetry.ts
new file mode 100644
index 000000000..a42b2be02
--- /dev/null
+++ b/packages/create-llama/helpers/poetry.ts
@@ -0,0 +1,34 @@
+/* eslint-disable import/no-extraneous-dependencies */
+import { execSync } from "child_process";
+import fs from "fs";
+
+export function isPoetryAvailable(): boolean {
+  try {
+    execSync("poetry --version", { stdio: "ignore" });
+    return true;
+  } catch (_) {}
+  return false;
+}
+
+export function tryPoetryInstall(): boolean {
+  try {
+    execSync("poetry install", { stdio: "inherit" });
+    return true;
+  } catch (_) {}
+  return false;
+}
+
+export function tryPoetryRun(command: string): boolean {
+  try {
+    execSync(`poetry run ${command}`, { stdio: "inherit" });
+    return true;
+  } catch (_) {}
+  return false;
+}
+
+export function isHavingPoetryLockFile(): boolean {
+  try {
+    return fs.existsSync("poetry.lock");
+  } catch (_) {}
+  return false;
+}
diff --git a/packages/create-llama/helpers/python.ts b/packages/create-llama/helpers/python.ts
index 3c631b6ee..4b2823101 100644
--- a/packages/create-llama/helpers/python.ts
+++ b/packages/create-llama/helpers/python.ts
@@ -1,8 +1,10 @@
 import fs from "fs/promises";
 import path from "path";
-import { cyan } from "picocolors";
+import { cyan, yellow } from "picocolors";
 import { parse, stringify } from "smol-toml";
+import terminalLink from "terminal-link";
 import { copy } from "./copy";
+import { isPoetryAvailable, tryPoetryInstall } from "./poetry";
 import { InstallTemplateArgs, TemplateVectorDB } from "./types";
 
 interface Dependency {
@@ -96,9 +98,15 @@ export const installPythonTemplate = async ({
   framework,
   engine,
   vectorDb,
+  installDependencies,
 }: Pick<
   InstallTemplateArgs,
-  "root" | "framework" | "template" | "engine" | "vectorDb"
+  | "root"
+  | "framework"
+  | "template"
+  | "engine"
+  | "vectorDb"
+  | "installDependencies"
 >) => {
   console.log("\nInitializing Python project with template:", template, "\n");
   const templatePath = path.join(
@@ -146,7 +154,28 @@ export const installPythonTemplate = async ({
   const addOnDependencies = getAdditionalDependencies(vectorDb);
   await addDependencies(root, addOnDependencies);
 
-  console.log(
-    "\nPython project, dependencies won't be installed automatically.\n",
-  );
+  // install python dependencies
+  if (installDependencies) {
+    if (isPoetryAvailable()) {
+      console.log(
+        `Installing python dependencies using poetry. This may take a while...`,
+      );
+      const installSuccessful = tryPoetryInstall();
+      if (!installSuccessful) {
+        console.warn(
+          yellow("Install failed. Please install dependencies manually."),
+        );
+      }
+    } else {
+      console.warn(
+        yellow(
+          `Poetry is not available in the current environment. The Python dependencies will not be installed automatically.
+Please check ${terminalLink(
+            "Poetry Installation",
+            `https://python-poetry.org/docs/#installation`,
+          )} to install poetry first, then install the dependencies manually.`,
+        ),
+      );
+    }
+  }
 };
diff --git a/packages/create-llama/helpers/types.ts b/packages/create-llama/helpers/types.ts
index e245c8644..6c85c1c91 100644
--- a/packages/create-llama/helpers/types.ts
+++ b/packages/create-llama/helpers/types.ts
@@ -23,4 +23,5 @@ export interface InstallTemplateArgs {
   communityProjectPath?: string;
   vectorDb?: TemplateVectorDB;
   externalPort?: number;
+  installDependencies?: boolean;
 }
diff --git a/packages/create-llama/helpers/typescript.ts b/packages/create-llama/helpers/typescript.ts
index 9d498b406..81b895042 100644
--- a/packages/create-llama/helpers/typescript.ts
+++ b/packages/create-llama/helpers/typescript.ts
@@ -39,6 +39,7 @@ export const installTSTemplate = async ({
   customApiPath,
   forBackend,
   vectorDb,
+  installDependencies,
 }: InstallTemplateArgs) => {
   console.log(bold(`Using ${packageManager}.`));
 
@@ -210,15 +211,17 @@ export const installTSTemplate = async ({
     JSON.stringify(packageJson, null, 2) + os.EOL,
   );
 
-  console.log("\nInstalling dependencies:");
-  for (const dependency in packageJson.dependencies)
-    console.log(`- ${cyan(dependency)}`);
+  if (installDependencies) {
+    console.log("\nInstalling dependencies:");
+    for (const dependency in packageJson.dependencies)
+      console.log(`- ${cyan(dependency)}`);
 
-  console.log("\nInstalling devDependencies:");
-  for (const dependency in packageJson.devDependencies)
-    console.log(`- ${cyan(dependency)}`);
+    console.log("\nInstalling devDependencies:");
+    for (const dependency in packageJson.devDependencies)
+      console.log(`- ${cyan(dependency)}`);
 
-  console.log();
+    console.log();
 
-  await callPackageManager(packageManager, isOnline);
+    await callPackageManager(packageManager, isOnline);
+  }
 };
diff --git a/packages/create-llama/index.ts b/packages/create-llama/index.ts
index ebdcd1ee2..4be39f65f 100644
--- a/packages/create-llama/index.ts
+++ b/packages/create-llama/index.ts
@@ -117,6 +117,13 @@ const program = new Commander.Command(packageJson.name)
     `
 
 Select external port.
+`,
+  )
+  .option(
+    "--install-dependencies",
+    `
+
+Whether install dependencies (backend/frontend) automatically or not.
 `,
   )
   .allowUnknownOption()
@@ -224,6 +231,7 @@ async function run(): Promise<void> {
     communityProjectPath: program.communityProjectPath,
     vectorDb: program.vectorDb,
     externalPort: program.externalPort,
+    installDependencies: program.installDependencies,
   });
   conf.set("preferences", preferences);
 }
diff --git a/packages/create-llama/questions.ts b/packages/create-llama/questions.ts
index ea130e36f..c6fd984aa 100644
--- a/packages/create-llama/questions.ts
+++ b/packages/create-llama/questions.ts
@@ -211,6 +211,19 @@ export const askQuestions = async (
     }
   }
 
+  if (program.installDependencies === undefined) {
+    const { installDependencies } = await prompts({
+      onState: onPromptState,
+      type: "toggle",
+      name: "installDependencies",
+      message: `Would you like to install dependencies automatically? This may take a while`,
+      initial: getPrefOrDefault("installDependencies"),
+      active: "Yes",
+      inactive: "No",
+    });
+    program.installDependencies = Boolean(installDependencies);
+  }
+
   if (!program.model) {
     if (ciInfo.isCI) {
       program.model = getPrefOrDefault("model");
-- 
GitLab