Skip to content
Snippets Groups Projects
index.ts 10.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • /* eslint-disable import/no-extraneous-dependencies */
    
    import { execSync } from "child_process";
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
    import Commander from "commander";
    import Conf from "conf";
    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 { QuestionArgs, askQuestions, onPromptState } from "./questions";
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
    
    
    // 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 Commander.Command(packageJson.name)
      .version(packageJson.version)
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
      .arguments("<project-directory>")
      .usage(`${green("<project-directory>")} [options]`)
    
      .action((name) => {
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
        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
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
    `,
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
        "--reset-preferences",
    
        `
    
      Explicitly tell the CLI to reset any stored preferences
    
    `,
      )
      .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",
        `
    
      Whether to 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.
    
    `,
      )
      .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.
    
    `,
      )
      .option(
        "--ask-examples",
        `
    
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
      Allow interactive selection of community templates and LlamaPacks.
    
      .allowUnknownOption()
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
      .parse(process.argv);
    
    if (process.argv.includes("--no-frontend")) {
      program.frontend = false;
    }
    
    if (process.argv.includes("--tools")) {
      if (program.tools === "none") {
        program.tools = [];
      } else {
    
        program.tools = getTools(program.tools.split(","));
    
    if (
      process.argv.includes("--no-llama-parse") ||
      program.template === "extractor"
    ) {
    
    program.askModels = process.argv.includes("--ask-models");
    
    program.askExamples = process.argv.includes("--ask-examples");
    
    if (process.argv.includes("--no-files")) {
      program.dataSources = [];
    
    } else if (process.argv.includes("--example-file")) {
    
      program.dataSources = getDataSources(program.files, program.exampleFile);
    
    } else if (process.argv.includes("--llamacloud")) {
      program.dataSources = [
        {
          type: "llamacloud",
          config: {},
        },
        EXAMPLE_FILE,
      ];
    
    } else if (process.argv.includes("--web-source")) {
      program.dataSources = [
        {
          type: "web",
          config: {
            baseUrl: program.webSource,
            prefix: program.webSource,
            depth: 1,
          },
        },
      ];
    } else if (process.argv.includes("--db-source")) {
      program.dataSources = [
        {
          type: "db",
          config: {
            uri: program.dbSource,
            queries: program.dbQuery || "SELECT * FROM mytable",
          },
        },
      ];
    
    
    const packageManager = !!program.useNpm
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
      ? "npm"
    
      : !!program.usePnpm
    
    yisding's avatar
    yisding committed
        ? "pnpm"
        : !!program.useYarn
          ? "yarn"
          : getPkgManager();
    
    
    async function run(): Promise<void> {
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
      const conf = new Conf({ projectName: "create-llama" });
    
    
      if (program.resetPreferences) {
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
        conf.clear();
        console.log(`Preferences reset successfully`);
        return;
    
    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 preferences = (conf.get("preferences") || {}) as QuestionArgs;
    
      await askQuestions(
        program as unknown as QuestionArgs,
        preferences,
        program.openAiKey,
      );
    
      await createApp({
    
        template: program.template,
    
        appPath: resolvedProjectPath,
        packageManager,
    
        frontend: program.frontend,
    
        modelConfig: program.modelConfig,
    
        llamaCloudKey: program.llamaCloudKey,
    
        communityProjectConfig: program.communityProjectConfig,
    
        llamapack: program.llamapack,
    
        externalPort: program.externalPort,
    
        postInstallAction: program.postInstallAction,
    
        dataSources: program.dataSources,
    
        tools: program.tools,
    
        useLlamaParse: program.useLlamaParse,
    
        observability: program.observability,
    
    Marcus Schiesser's avatar
    Marcus Schiesser committed
      conf.set("preferences", preferences);
    
      if (program.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 (program.postInstallAction === "runApp") {
    
        console.log(`Running app in ${root}...`);
    
        await runApp(
          root,
    
          program.frontend,
          program.framework,
          program.port,
          program.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);
      });