Skip to content
Snippets Groups Projects
index.ts 9.19 KiB
Newer Older
  • Learn to ignore specific revisions
  • /* eslint-disable import/no-extraneous-dependencies */
    
    import { execSync } from "child_process";
    
    import { Command } from "commander";
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
    import fs from "fs";
    import path from "path";
    
    import { bold, cyan, green, red, yellow } from "picocolors";
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
    import prompts from "prompts";
    
    import terminalLink from "terminal-link";
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
    import checkForUpdate from "update-check";
    
    import { createApp } from "./create-app";
    
    import { EXAMPLE_FILE, getDataSources } from "./helpers/datasources";
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
    import { getPkgManager } from "./helpers/get-pkg-manager";
    import { isFolderEmpty } from "./helpers/is-folder-empty";
    
    import { initializeGlobalAgent } from "./helpers/proxy";
    
    import { runApp } from "./helpers/run-app";
    
    import { getTools } from "./helpers/tools";
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
    import { validateNpmName } from "./helpers/validate-pkg";
    import packageJson from "./package.json";
    
    import { askQuestions } from "./questions/index";
    import { QuestionArgs } from "./questions/types";
    import { onPromptState } from "./questions/utils";
    
    // Run the initialization function
    initializeGlobalAgent();
    
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
    let projectPath: string = "";
    
    const handleSigTerm = () => process.exit(0);
    
    process.on("SIGINT", handleSigTerm);
    process.on("SIGTERM", handleSigTerm);
    
    const program = new Command(packageJson.name)
    
      .version(packageJson.version)
    
      .arguments("[project-directory]")
      .usage(`${green("[project-directory]")} [options]`)
    
      .action((name) => {
    
        if (name) {
          projectPath = name;
        }
    
      })
      .option(
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
        "--use-npm",
    
        `
    
      Explicitly tell the CLI to bootstrap the application using npm
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
    `,
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
        "--use-pnpm",
    
        `
    
      Explicitly tell the CLI to bootstrap the application using pnpm
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
    `,
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
        "--use-yarn",
    
        `
    
      Explicitly tell the CLI to bootstrap the application using Yarn
    
    `,
      )
      .option(
        "--template <template>",
        `
    
      Select a template to bootstrap the application with.
    `,
      )
      .option(
        "--framework <framework>",
        `
    
      Select a framework to bootstrap the application with.
    
      Specify the path to a local file or folder for chatting.
    
    `,
      )
      .option(
        "--example-file",
        `
    
      Select to use an example PDF as data source.
    
    `,
      )
      .option(
        "--web-source <url>",
        `
      
      Specify a website URL to use as a data source.
    `,
      )
      .option(
        "--db-source <connection-string>",
        `
      
      Specify a database connection string to use as a data source.
    
    `,
      )
      .option(
        "--open-ai-key <key>",
        `
    
      Provide an OpenAI API key.
    `,
      )
      .option(
        "--ui <ui>",
        `
    
      Select a UI to bootstrap the application with.
    `,
      )
      .option(
        "--frontend",
        `
    
      Generate a frontend for your backend.
    `,
      )
      .option(
        "--no-frontend",
        `
    
      Do not generate a frontend for your backend.
    
    `,
      )
      .option(
        "--port <port>",
        `
    
      Select UI port.
    
      Select external port.
    
        "--post-install-action <action>",
    
      Choose an action after installation. For example, 'runApp' or 'dependencies'. The default option is just to generate the app.
    
    `,
      )
      .option(
        "--vector-db <vectorDb>",
        `
    
      Select which vector database you would like to use, such as 'none', 'pg' or 'mongo'. The default option is not to use a vector database and use the local filesystem instead ('none').
    
      Specify the tools you want to use by providing a comma-separated list. For example, 'wikipedia.WikipediaToolSpec,google.GoogleSearchToolSpec'. Use 'none' to not using any tools.
    
        (tools, _) => {
          if (tools === "none") {
            return [];
          } else {
            return getTools(tools.split(","));
          }
        },
    
    `,
      )
      .option(
        "--llama-cloud-key <key>",
        `
    
      Provide a LlamaCloud API key.
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
    `,
    
        "--observability <observability>",
        `
        
      Specify observability tools to use. Eg: none, opentelemetry
    `,
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
      Allow interactive selection of LLM and embedding models of different model providers.
    
      Allow interactive selection of all features.
    
      .allowUnknownOption()
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
      .parse(process.argv);
    
    
    const options = program.opts();
    
    
    if (
      process.argv.includes("--no-llama-parse") ||
    
      options.template === "extractor"
    
      options.useLlamaParse = false;
    
    }
    if (process.argv.includes("--no-files")) {
    
      options.dataSources = [];
    
    } else if (process.argv.includes("--example-file")) {
    
      options.dataSources = getDataSources(options.files, options.exampleFile);
    
    } else if (process.argv.includes("--llamacloud")) {
    
      options.dataSources = [EXAMPLE_FILE];
      options.vectorDb = "llamacloud";
    
    } else if (process.argv.includes("--web-source")) {
    
      options.dataSources = [
    
        {
          type: "web",
          config: {
    
            baseUrl: options.webSource,
            prefix: options.webSource,
    
            depth: 1,
          },
        },
      ];
    } else if (process.argv.includes("--db-source")) {
    
      options.dataSources = [
    
        {
          type: "db",
          config: {
    
            uri: options.dbSource,
            queries: options.dbQuery || "SELECT * FROM mytable",
    
    const packageManager = !!options.useNpm
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
      ? "npm"
    
      : !!options.usePnpm
    
    yisding's avatar
    yisding committed
        ? "pnpm"
    
        : !!options.useYarn
    
    yisding's avatar
    yisding committed
          ? "yarn"
          : getPkgManager();
    
    
    async function run(): Promise<void> {
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
      if (typeof projectPath === "string") {
        projectPath = projectPath.trim();
    
      }
    
      if (!projectPath) {
        const res = await prompts({
          onState: onPromptState,
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
          type: "text",
          name: "path",
          message: "What is your project named?",
          initial: "my-app",
    
          validate: (name) => {
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
            const validation = validateNpmName(path.basename(path.resolve(name)));
    
            if (validation.valid) {
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
              return true;
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
            return "Invalid project name: " + validation.problems![0];
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
        });
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
        if (typeof res.path === "string") {
          projectPath = res.path.trim();
    
        }
      }
    
      if (!projectPath) {
        console.log(
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
          "\nPlease specify the project directory:\n" +
            `  ${cyan(program.name())} ${green("<project-directory>")}\n` +
            "For example:\n" +
    
            `  ${cyan(program.name())} ${green("my-app")}\n\n` +
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
            `Run ${cyan(`${program.name()} --help`)} to see all options.`,
        );
        process.exit(1);
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
      const resolvedProjectPath = path.resolve(projectPath);
      const projectName = path.basename(resolvedProjectPath);
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
      const { valid, problems } = validateNpmName(projectName);
    
      if (!valid) {
        console.error(
          `Could not create a project called ${red(
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
            `"${projectName}"`,
          )} because of npm naming restrictions:`,
        );
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
        problems!.forEach((p) => console.error(`    ${red(bold("*"))} ${p}`));
        process.exit(1);
    
      }
    
      /**
       * Verify the project dir is empty or doesn't exist
       */
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
      const root = path.resolve(resolvedProjectPath);
      const appName = path.basename(root);
      const folderExists = fs.existsSync(root);
    
    
      if (folderExists && !isFolderEmpty(root, appName)) {
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
        process.exit(1);
    
      const answers = await askQuestions(options as unknown as QuestionArgs);
    
      await createApp({
    
        appPath: resolvedProjectPath,
        packageManager,
    
        externalPort: options.externalPort,
    
      if (answers.postInstallAction === "VSCode") {
    
        console.log(`Starting VSCode in ${root}...`);
        try {
          execSync(`code . --new-window --goto README.md`, {
            stdio: "inherit",
            cwd: root,
          });
        } catch (error) {
          console.log(
            red(
              `Failed to start VSCode in ${root}. 
    Got error: ${(error as Error).message}.\n`,
            ),
          );
          console.log(
    
            `Make sure you have VSCode installed and added to your PATH (shell alias will not work). 
    
    Please check ${cyan(
              terminalLink(
                "This documentation",
                `https://code.visualstudio.com/docs/setup/setup-overview`,
              ),
            )} for more information.`,
          );
        }
    
      } else if (answers.postInstallAction === "runApp") {
    
        console.log(`Running app in ${root}...`);
    
        await runApp(
          root,
    
          answers.template,
          answers.frontend,
          answers.framework,
          options.port,
          options.externalPort,
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
    const update = checkForUpdate(packageJson).catch(() => null);
    
    
    async function notifyUpdate(): Promise<void> {
      try {
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
        const res = await update;
    
        if (res?.latest) {
          const updateMessage =
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
            packageManager === "yarn"
    
              ? "yarn global add create-llama@latest"
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
              : packageManager === "pnpm"
    
    yisding's avatar
    yisding committed
                ? "pnpm add -g create-llama@latest"
                : "npm i -g create-llama@latest";
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
            yellow(bold("A new version of `create-llama` is available!")) +
              "\n" +
              "You can update by running: " +
    
              cyan(updateMessage) +
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
              "\n",
          );
    
        }
      } catch {
        // ignore error
      }
    }
    
    run()
      .then(notifyUpdate)
      .catch(async (reason) => {
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
        console.log();
        console.log("Aborting installation.");
    
        if (reason.command) {
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
          console.log(`  ${cyan(reason.command)} has failed.`);
    
        } else {
          console.log(
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
            red("Unexpected error. Please report it as a bug:") + "\n",
            reason,
          );
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
        console.log();
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
        await notifyUpdate();
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
        process.exit(1);
      });