From ccf8a0b9171f5a68362e48a86d025fdbea30c174 Mon Sep 17 00:00:00 2001
From: Marcus Schiesser <mail@marcusschiesser.de>
Date: Thu, 26 Oct 2023 11:13:20 +0700
Subject: [PATCH] removed URL download

---
 README.md           |  30 ++-----
 create-app.ts       | 211 ++++----------------------------------------
 helpers/examples.ts | 133 ----------------------------
 index.ts            | 206 +++++++++++++++---------------------------
 4 files changed, 93 insertions(+), 487 deletions(-)
 delete mode 100644 helpers/examples.ts

diff --git a/README.md b/README.md
index 74134373..b52ddc03 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
-# Create Next App
+# Create LlamaIndex App
 
-The easiest way to get started with LlamaIndex is by using `create-llama`. This CLI tool enables you to quickly start building a new LlamaIndex application, with everything set up for you. You can create a new app using the default LlamaIndex template, or by using one of the [official LlamaIndex examples](https://github.com/vercel/next.js/tree/canary/examples). To get started, use the following command:
+The easiest way to get started with LlamaIndex is by using `create-llama`. This CLI tool enables you to quickly start building a new LlamaIndex application, with everything set up for you. 
+To get started, use the following command:
 
 ### Interactive
 
@@ -9,9 +10,9 @@ You can create a new project interactively by running:
 ```bash
 npx create-llama@latest
 # or
-yarn create next-app
+yarn create llama-app
 # or
-pnpm create next-app
+pnpm create llama-app
 # or
 bunx create-llama
 ```
@@ -53,26 +54,5 @@ Options:
 
     Explicitly tell the CLI to bootstrap the app using Bun
 
-  -e, --example [name]|[github-url]
-
-    An example to bootstrap the app with. You can use an example name
-    from the official LlamaIndex repo or a GitHub URL. The URL can use
-    any branch and/or subdirectory
-
-  --example-path <path-to-example>
-
-    In a rare case, your GitHub URL might contain a branch name with
-    a slash (e.g. bug/fix-1) and the path to the example (e.g. foo/bar).
-    In this case, you must specify the path to the example separately:
-    --example-path foo/bar
 ```
 
-### Why use Create Next App?
-
-`create-llama` allows you to create a new LlamaIndex app within seconds. It is officially maintained by the creators of LlamaIndex, and includes a number of benefits:
-
-- **Interactive Experience**: Running `npx create-llama@latest` (with no arguments) launches an interactive experience that guides you through setting up a project.
-- **Zero Dependencies**: Initializing a project is as quick as one second. Create Next App has zero dependencies.
-- **Offline Support**: Create Next App will automatically detect if you're offline and bootstrap your project using your local package cache.
-- **Support for Examples**: Create Next App can bootstrap your application using an example from the LlamaIndex examples collection (e.g. `npx create-llama --example api-routes`).
-- **Tested**: The package is part of the LlamaIndex monorepo and tested using the same integration test suite as LlamaIndex itself, ensuring it works as expected with every release.
diff --git a/create-app.ts b/create-app.ts
index fb76bc8a..18e18117 100644
--- a/create-app.ts
+++ b/create-app.ts
@@ -1,34 +1,19 @@
 /* eslint-disable import/no-extraneous-dependencies */
-import retry from "async-retry";
-import fs from "fs";
 import path from "path";
-import { cyan, green, red } from "picocolors";
-import type { RepoInfo } from "./helpers/examples";
-import {
-  downloadAndExtractExample,
-  downloadAndExtractRepo,
-  existsInRepo,
-  getRepoInfo,
-  hasRepo,
-} from "./helpers/examples";
+import { green } from "picocolors";
 import type { PackageManager } from "./helpers/get-pkg-manager";
 import { tryGitInit } from "./helpers/git";
-import { install } from "./helpers/install";
 import { isFolderEmpty } from "./helpers/is-folder-empty";
 import { getOnline } from "./helpers/is-online";
 import { isWriteable } from "./helpers/is-writeable";
 import { makeDir } from "./helpers/make-dir";
 
 import type { TemplateMode, TemplateType } from "./templates";
-import { getTemplateFile, installTemplate } from "./templates";
-
-export class DownloadError extends Error {}
+import { installTemplate } from "./templates";
 
 export async function createApp({
   appPath,
   packageManager,
-  example,
-  examplePath,
   tailwind,
   eslint,
   srcDir,
@@ -36,78 +21,14 @@ export async function createApp({
 }: {
   appPath: string;
   packageManager: PackageManager;
-  example?: string;
-  examplePath?: string;
   tailwind: boolean;
   eslint: boolean;
   srcDir: boolean;
   importAlias: string;
 }): Promise<void> {
-  let repoInfo: RepoInfo | undefined;
   const mode: TemplateMode = "nextjs";
   const template: TemplateType = "simple";
 
-  if (example) {
-    let repoUrl: URL | undefined;
-
-    try {
-      repoUrl = new URL(example);
-    } catch (error: any) {
-      if (error.code !== "ERR_INVALID_URL") {
-        console.error(error);
-        process.exit(1);
-      }
-    }
-
-    if (repoUrl) {
-      if (repoUrl.origin !== "https://github.com") {
-        console.error(
-          `Invalid URL: ${red(
-            `"${example}"`,
-          )}. Only GitHub repositories are supported. Please use a GitHub URL and try again.`,
-        );
-        process.exit(1);
-      }
-
-      repoInfo = await getRepoInfo(repoUrl, examplePath);
-
-      if (!repoInfo) {
-        console.error(
-          `Found invalid GitHub URL: ${red(
-            `"${example}"`,
-          )}. Please fix the URL and try again.`,
-        );
-        process.exit(1);
-      }
-
-      const found = await hasRepo(repoInfo);
-
-      if (!found) {
-        console.error(
-          `Could not locate the repository for ${red(
-            `"${example}"`,
-          )}. Please check that the repository exists and try again.`,
-        );
-        process.exit(1);
-      }
-    } else if (example !== "__internal-testing-retry") {
-      const found = await existsInRepo(example);
-
-      if (!found) {
-        console.error(
-          `Could not locate an example named ${red(
-            `"${example}"`,
-          )}. It could be due to the following:\n`,
-          `1. Your spelling of example ${red(
-            `"${example}"`,
-          )} might be incorrect.\n`,
-          `2. You might not be connected to the internet or you are behind a proxy.`,
-        );
-        process.exit(1);
-      }
-    }
-  }
-
   const root = path.resolve(appPath);
 
   if (!(await isWriteable(path.dirname(root)))) {
@@ -129,130 +50,34 @@ export async function createApp({
 
   const useYarn = packageManager === "yarn";
   const isOnline = !useYarn || (await getOnline());
-  const originalDirectory = process.cwd();
 
   console.log(`Creating a new LlamaIndex app in ${green(root)}.`);
   console.log();
 
   process.chdir(root);
 
-  const packageJsonPath = path.join(root, "package.json");
-  let hasPackageJson = false;
-
-  if (example) {
-    /**
-     * If an example repository is provided, clone it.
-     */
-    try {
-      if (repoInfo) {
-        const repoInfo2 = repoInfo;
-        console.log(
-          `Downloading files from repo ${cyan(
-            example,
-          )}. This might take a moment.`,
-        );
-        console.log();
-        await retry(() => downloadAndExtractRepo(root, repoInfo2), {
-          retries: 3,
-        });
-      } else {
-        console.log(
-          `Downloading files for example ${cyan(
-            example,
-          )}. This might take a moment.`,
-        );
-        console.log();
-        await retry(() => downloadAndExtractExample(root, example), {
-          retries: 3,
-        });
-      }
-    } catch (reason) {
-      function isErrorLike(err: unknown): err is { message: string } {
-        return (
-          typeof err === "object" &&
-          err !== null &&
-          typeof (err as { message?: unknown }).message === "string"
-        );
-      }
-      throw new DownloadError(
-        isErrorLike(reason) ? reason.message : reason + "",
-      );
-    }
-    // Copy `.gitignore` if the application did not provide one
-    const ignorePath = path.join(root, ".gitignore");
-    if (!fs.existsSync(ignorePath)) {
-      fs.copyFileSync(
-        getTemplateFile({ template, mode, file: "gitignore" }),
-        ignorePath,
-      );
-    }
-
-    // Copy `next-env.d.ts` to any example that is typescript
-    const tsconfigPath = path.join(root, "tsconfig.json");
-    if (fs.existsSync(tsconfigPath)) {
-      fs.copyFileSync(
-        getTemplateFile({ template, mode: "nextjs", file: "next-env.d.ts" }),
-        path.join(root, "next-env.d.ts"),
-      );
-    }
-
-    hasPackageJson = fs.existsSync(packageJsonPath);
-    if (hasPackageJson) {
-      console.log("Installing packages. This might take a couple of minutes.");
-      console.log();
-
-      await install(packageManager, isOnline);
-      console.log();
-    }
-  } else {
-    /**
-     * If an example repository is not provided for cloning, proceed
-     * by installing from a template.
-     */
-    await installTemplate({
-      appName,
-      root,
-      template,
-      mode,
-      packageManager,
-      isOnline,
-      tailwind,
-      eslint,
-      srcDir,
-      importAlias,
-    });
-  }
+  /**
+   * If an example repository is not provided for cloning, proceed
+   * by installing from a template.
+   */
+  await installTemplate({
+    appName,
+    root,
+    template,
+    mode,
+    packageManager,
+    isOnline,
+    tailwind,
+    eslint,
+    srcDir,
+    importAlias,
+  });
 
   if (tryGitInit(root)) {
     console.log("Initialized a git repository.");
     console.log();
   }
 
-  let cdpath: string;
-  if (path.join(originalDirectory, appName) === appPath) {
-    cdpath = appName;
-  } else {
-    cdpath = appPath;
-  }
-
   console.log(`${green("Success!")} Created ${appName} at ${appPath}`);
-
-  if (hasPackageJson) {
-    console.log("Inside that directory, you can run several commands:");
-    console.log();
-    console.log(cyan(`  ${packageManager} ${useYarn ? "" : "run "}dev`));
-    console.log("    Starts the development server.");
-    console.log();
-    console.log(cyan(`  ${packageManager} ${useYarn ? "" : "run "}build`));
-    console.log("    Builds the app for production.");
-    console.log();
-    console.log(cyan(`  ${packageManager} start`));
-    console.log("    Runs the built app in production mode.");
-    console.log();
-    console.log("We suggest that you begin by typing:");
-    console.log();
-    console.log(cyan("  cd"), cdpath);
-    console.log(`  ${cyan(`${packageManager} ${useYarn ? "" : "run "}dev`)}`);
-  }
   console.log();
 }
diff --git a/helpers/examples.ts b/helpers/examples.ts
deleted file mode 100644
index b8c74be6..00000000
--- a/helpers/examples.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-/* eslint-disable import/no-extraneous-dependencies */
-import { createWriteStream, promises as fs } from "fs";
-import got from "got";
-import { tmpdir } from "os";
-import { join } from "path";
-import { Stream } from "stream";
-import tar from "tar";
-import { promisify } from "util";
-
-const pipeline = promisify(Stream.pipeline);
-
-export type RepoInfo = {
-  username: string;
-  name: string;
-  branch: string;
-  filePath: string;
-};
-
-export async function isUrlOk(url: string): Promise<boolean> {
-  const res = await got.head(url).catch((e) => e);
-  return res.statusCode === 200;
-}
-
-export async function getRepoInfo(
-  url: URL,
-  examplePath?: string,
-): Promise<RepoInfo | undefined> {
-  const [, username, name, t, _branch, ...file] = url.pathname.split("/");
-  const filePath = examplePath
-    ? examplePath.replace(/^\//, "")
-    : file.join("/");
-
-  if (
-    // Support repos whose entire purpose is to be a LlamaIndex example, e.g.
-    // https://github.com/:username/:my-cool-nextjs-example-repo-name.
-    t === undefined ||
-    // Support GitHub URL that ends with a trailing slash, e.g.
-    // https://github.com/:username/:my-cool-nextjs-example-repo-name/
-    // In this case "t" will be an empty string while the next part "_branch" will be undefined
-    (t === "" && _branch === undefined)
-  ) {
-    const infoResponse = await got(
-      `https://api.github.com/repos/${username}/${name}`,
-    ).catch((e) => e);
-    if (infoResponse.statusCode !== 200) {
-      return;
-    }
-    const info = JSON.parse(infoResponse.body);
-    return { username, name, branch: info["default_branch"], filePath };
-  }
-
-  // If examplePath is available, the branch name takes the entire path
-  const branch = examplePath
-    ? `${_branch}/${file.join("/")}`.replace(new RegExp(`/${filePath}|/$`), "")
-    : _branch;
-
-  if (username && name && branch && t === "tree") {
-    return { username, name, branch, filePath };
-  }
-}
-
-export function hasRepo({
-  username,
-  name,
-  branch,
-  filePath,
-}: RepoInfo): Promise<boolean> {
-  const contentsUrl = `https://api.github.com/repos/${username}/${name}/contents`;
-  const packagePath = `${filePath ? `/${filePath}` : ""}/package.json`;
-
-  return isUrlOk(contentsUrl + packagePath + `?ref=${branch}`);
-}
-
-export function existsInRepo(nameOrUrl: string): Promise<boolean> {
-  try {
-    const url = new URL(nameOrUrl);
-    return isUrlOk(url.href);
-  } catch {
-    return isUrlOk(
-      `https://api.github.com/repos/vercel/next.js/contents/examples/${encodeURIComponent(
-        nameOrUrl,
-      )}`,
-    );
-  }
-}
-
-async function downloadTar(url: string) {
-  const tempFile = join(tmpdir(), `next.js-cna-example.temp-${Date.now()}`);
-  await pipeline(got.stream(url), createWriteStream(tempFile));
-  return tempFile;
-}
-
-export async function downloadAndExtractRepo(
-  root: string,
-  { username, name, branch, filePath }: RepoInfo,
-) {
-  const tempFile = await downloadTar(
-    `https://codeload.github.com/${username}/${name}/tar.gz/${branch}`,
-  );
-
-  await tar.x({
-    file: tempFile,
-    cwd: root,
-    strip: filePath ? filePath.split("/").length + 1 : 1,
-    filter: (p) =>
-      p.startsWith(
-        `${name}-${branch.replace(/\//g, "-")}${
-          filePath ? `/${filePath}/` : "/"
-        }`,
-      ),
-  });
-
-  await fs.unlink(tempFile);
-}
-
-export async function downloadAndExtractExample(root: string, name: string) {
-  if (name === "__internal-testing-retry") {
-    throw new Error("This is an internal example for testing the CLI.");
-  }
-
-  const tempFile = await downloadTar(
-    "https://codeload.github.com/vercel/next.js/tar.gz/canary",
-  );
-
-  await tar.x({
-    file: tempFile,
-    cwd: root,
-    strip: 2 + name.split("/").length,
-    filter: (p) => p.includes(`next.js-canary/examples/${name}/`),
-  });
-
-  await fs.unlink(tempFile);
-}
diff --git a/index.ts b/index.ts
index b74215f9..01fac2d5 100644
--- a/index.ts
+++ b/index.ts
@@ -8,7 +8,7 @@ import path from "path";
 import { blue, bold, cyan, green, red, yellow } from "picocolors";
 import prompts from "prompts";
 import checkForUpdate from "update-check";
-import { createApp, DownloadError } from "./create-app";
+import { createApp } from "./create-app";
 import { getPkgManager } from "./helpers/get-pkg-manager";
 import { isFolderEmpty } from "./helpers/is-folder-empty";
 import { validateNpmName } from "./helpers/validate-pkg";
@@ -78,25 +78,6 @@ const program = new Commander.Command(packageJson.name)
     `
 
   Explicitly tell the CLI to bootstrap the application using Bun
-`,
-  )
-  .option(
-    "-e, --example [name]|[github-url]",
-    `
-
-  An example to bootstrap the app with. You can use an example name
-  from the official LlamaIndex repo or a GitHub URL. The URL can use
-  any branch and/or subdirectory
-`,
-  )
-  .option(
-    "--example-path <path-to-example>",
-    `
-
-  In a rare case, your GitHub URL might contain a branch name with
-  a slash (e.g. bug/fix-1) and the path to the example (e.g. foo/bar).
-  In this case, you must specify the path to the example separately:
-  --example-path foo/bar
 `,
   )
   .option(
@@ -179,13 +160,6 @@ async function run(): Promise<void> {
     process.exit(1);
   }
 
-  if (program.example === true) {
-    console.error(
-      "Please provide an example name or url, otherwise remove the example option.",
-    );
-    process.exit(1);
-  }
-
   /**
    * Verify the project dir is empty or doesn't exist
    */
@@ -197,130 +171,90 @@ async function run(): Promise<void> {
     process.exit(1);
   }
 
-  const example = typeof program.example === "string" && program.example.trim();
   const preferences = (conf.get("preferences") || {}) as Record<
     string,
     boolean | string
   >;
-  /**
-   * If the user does not provide the necessary flags, prompt them for whether
-   * to use TS or JS.
-   */
-  if (!example) {
-    const defaults: typeof preferences = {
-      typescript: true,
-      eslint: true,
-      tailwind: true,
-      app: true,
-      srcDir: false,
-      importAlias: "@/*",
-      customizeImportAlias: false,
-    };
-    const getPrefOrDefault = (field: string) =>
-      preferences[field] ?? defaults[field];
-
-    if (
-      !process.argv.includes("--eslint") &&
-      !process.argv.includes("--no-eslint")
-    ) {
-      if (ciInfo.isCI) {
-        program.eslint = getPrefOrDefault("eslint");
-      } else {
-        const styledEslint = blue("ESLint");
-        const { eslint } = await prompts({
-          onState: onPromptState,
-          type: "toggle",
-          name: "eslint",
-          message: `Would you like to use ${styledEslint}?`,
-          initial: getPrefOrDefault("eslint"),
-          active: "Yes",
-          inactive: "No",
-        });
-        program.eslint = Boolean(eslint);
-        preferences.eslint = Boolean(eslint);
-      }
+
+  const defaults: typeof preferences = {
+    eslint: true,
+    tailwind: true,
+    app: true,
+    srcDir: false,
+    importAlias: "@/*",
+    customizeImportAlias: false,
+  };
+  const getPrefOrDefault = (field: string) =>
+    preferences[field] ?? defaults[field];
+
+  if (
+    !process.argv.includes("--eslint") &&
+    !process.argv.includes("--no-eslint")
+  ) {
+    if (ciInfo.isCI) {
+      program.eslint = getPrefOrDefault("eslint");
+    } else {
+      const styledEslint = blue("ESLint");
+      const { eslint } = await prompts({
+        onState: onPromptState,
+        type: "toggle",
+        name: "eslint",
+        message: `Would you like to use ${styledEslint}?`,
+        initial: getPrefOrDefault("eslint"),
+        active: "Yes",
+        inactive: "No",
+      });
+      program.eslint = Boolean(eslint);
+      preferences.eslint = Boolean(eslint);
     }
+  }
 
-    if (
-      typeof program.importAlias !== "string" ||
-      !program.importAlias.length
-    ) {
-      if (ciInfo.isCI) {
+  if (typeof program.importAlias !== "string" || !program.importAlias.length) {
+    if (ciInfo.isCI) {
+      // We don't use preferences here because the default value is @/* regardless of existing preferences
+      program.importAlias = defaults.importAlias;
+    } else {
+      const styledImportAlias = blue("import alias");
+
+      const { customizeImportAlias } = await prompts({
+        onState: onPromptState,
+        type: "toggle",
+        name: "customizeImportAlias",
+        message: `Would you like to customize the default ${styledImportAlias} (${defaults.importAlias})?`,
+        initial: getPrefOrDefault("customizeImportAlias"),
+        active: "Yes",
+        inactive: "No",
+      });
+
+      if (!customizeImportAlias) {
         // We don't use preferences here because the default value is @/* regardless of existing preferences
         program.importAlias = defaults.importAlias;
       } else {
-        const styledImportAlias = blue("import alias");
-
-        const { customizeImportAlias } = await prompts({
+        const { importAlias } = await prompts({
           onState: onPromptState,
-          type: "toggle",
-          name: "customizeImportAlias",
-          message: `Would you like to customize the default ${styledImportAlias} (${defaults.importAlias})?`,
-          initial: getPrefOrDefault("customizeImportAlias"),
-          active: "Yes",
-          inactive: "No",
+          type: "text",
+          name: "importAlias",
+          message: `What ${styledImportAlias} would you like configured?`,
+          initial: getPrefOrDefault("importAlias"),
+          validate: (value) =>
+            /.+\/\*/.test(value)
+              ? true
+              : "Import alias must follow the pattern <prefix>/*",
         });
-
-        if (!customizeImportAlias) {
-          // We don't use preferences here because the default value is @/* regardless of existing preferences
-          program.importAlias = defaults.importAlias;
-        } else {
-          const { importAlias } = await prompts({
-            onState: onPromptState,
-            type: "text",
-            name: "importAlias",
-            message: `What ${styledImportAlias} would you like configured?`,
-            initial: getPrefOrDefault("importAlias"),
-            validate: (value) =>
-              /.+\/\*/.test(value)
-                ? true
-                : "Import alias must follow the pattern <prefix>/*",
-          });
-          program.importAlias = importAlias;
-          preferences.importAlias = importAlias;
-        }
+        program.importAlias = importAlias;
+        preferences.importAlias = importAlias;
       }
     }
   }
 
-  try {
-    await createApp({
-      appPath: resolvedProjectPath,
-      packageManager,
-      example: example && example !== "default" ? example : undefined,
-      examplePath: program.examplePath,
-      tailwind: true,
-      eslint: program.eslint,
-      srcDir: program.srcDir,
-      importAlias: program.importAlias,
-    });
-  } catch (reason) {
-    if (!(reason instanceof DownloadError)) {
-      throw reason;
-    }
-
-    const res = await prompts({
-      onState: onPromptState,
-      type: "confirm",
-      name: "builtin",
-      message:
-        `Could not download "${example}" because of a connectivity issue between your machine and GitHub.\n` +
-        `Do you want to use the default template instead?`,
-      initial: true,
-    });
-    if (!res.builtin) {
-      throw reason;
-    }
-
-    await createApp({
-      appPath: resolvedProjectPath,
-      packageManager,
-      eslint: program.eslint,
-      tailwind: true,
-      srcDir: program.srcDir,
-      importAlias: program.importAlias,
-    });
-  }
+  await createApp({
+    appPath: resolvedProjectPath,
+    packageManager,
+    tailwind: true,
+    eslint: program.eslint,
+    srcDir: program.srcDir,
+    importAlias: program.importAlias,
+  });
   conf.set("preferences", preferences);
 }
 
-- 
GitLab