Skip to content

Commit

Permalink
Merge pull request #153 from run-llama/add/create-llama
Browse files Browse the repository at this point in the history
Add create-llama CLI tool
  • Loading branch information
yisding authored Nov 14, 2023
2 parents ced3555 + db58cf2 commit 683c4ad
Show file tree
Hide file tree
Showing 128 changed files with 4,467 additions and 125 deletions.
54 changes: 54 additions & 0 deletions packages/create-llama/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# 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.
To get started, use the following command:

### Interactive

You can create a new project interactively by running:

```bash
npx create-llama@latest
# or
npm create llama
# or
yarn create llama
# or
pnpm create llama
```

You will be asked for the name of your project, and then which framework you want to use
create a TypeScript project:

```bash
✔ Which framework would you like to use? › NextJS
```

You can choose between NextJS and Express.

### Non-interactive

You can also pass command line arguments to set up a new project
non-interactively. See `create-llama --help`:

```bash
create-llama <project-directory> [options]

Options:
-V, --version output the version number


--use-npm

Explicitly tell the CLI to bootstrap the app using npm

--use-pnpm

Explicitly tell the CLI to bootstrap the app using pnpm

--use-yarn

Explicitly tell the CLI to bootstrap the app using Yarn

```

109 changes: 109 additions & 0 deletions packages/create-llama/create-app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/* eslint-disable import/no-extraneous-dependencies */
import path from "path";
import { green } from "picocolors";
import { tryGitInit } from "./helpers/git";
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 fs from "fs";
import terminalLink from "terminal-link";
import type { InstallTemplateArgs } from "./templates";
import { installTemplate } from "./templates";

export async function createApp({
template,
framework,
engine,
ui,
appPath,
packageManager,
eslint,
frontend,
openAIKey,
}: Omit<
InstallTemplateArgs,
"appName" | "root" | "isOnline" | "customApiPath"
> & {
appPath: string;
frontend: boolean;
}): Promise<void> {
const root = path.resolve(appPath);

if (!(await isWriteable(path.dirname(root)))) {
console.error(
"The application path is not writable, please check folder permissions and try again.",
);
console.error(
"It is likely you do not have write permissions for this folder.",
);
process.exit(1);
}

const appName = path.basename(root);

await makeDir(root);
if (!isFolderEmpty(root, appName)) {
process.exit(1);
}

const useYarn = packageManager === "yarn";
const isOnline = !useYarn || (await getOnline());

console.log(`Creating a new LlamaIndex app in ${green(root)}.`);
console.log();

const args = {
appName,
root,
template,
framework,
engine,
ui,
packageManager,
isOnline,
eslint,
openAIKey,
};

if (frontend) {
// install backend
const backendRoot = path.join(root, "backend");
await makeDir(backendRoot);
await installTemplate({ ...args, root: backendRoot, backend: true });
// install frontend
const frontendRoot = path.join(root, "frontend");
await makeDir(frontendRoot);
await installTemplate({
...args,
root: frontendRoot,
framework: "nextjs",
customApiPath: "http://localhost:8000/api/chat",
backend: false,
});
// copy readme for fullstack
await fs.promises.copyFile(
path.join(__dirname, "templates", "README-fullstack.md"),
path.join(root, "README.md"),
);
} else {
await installTemplate({ ...args, backend: true });
}

process.chdir(root);
if (tryGitInit(root)) {
console.log("Initialized a git repository.");
console.log();
}

console.log(`${green("Success!")} Created ${appName} at ${appPath}`);

console.log(
`Now have a look at the ${terminalLink(
"README.md",
`file://${appName}/README.md`,
)} and learn how to get started.`,
);
console.log();
}
50 changes: 50 additions & 0 deletions packages/create-llama/helpers/copy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* eslint-disable import/no-extraneous-dependencies */
import { async as glob } from "fast-glob";
import fs from "fs";
import path from "path";

interface CopyOption {
cwd?: string;
rename?: (basename: string) => string;
parents?: boolean;
}

const identity = (x: string) => x;

export const copy = async (
src: string | string[],
dest: string,
{ cwd, rename = identity, parents = true }: CopyOption = {},
) => {
const source = typeof src === "string" ? [src] : src;

if (source.length === 0 || !dest) {
throw new TypeError("`src` and `dest` are required");
}

const sourceFiles = await glob(source, {
cwd,
dot: true,
absolute: false,
stats: false,
});

const destRelativeToCwd = cwd ? path.resolve(cwd, dest) : dest;

return Promise.all(
sourceFiles.map(async (p) => {
const dirname = path.dirname(p);
const basename = rename(path.basename(p));

const from = cwd ? path.resolve(cwd, p) : p;
const to = parents
? path.join(destRelativeToCwd, dirname, basename)
: path.join(destRelativeToCwd, basename);

// Ensure the destination directory exists
await fs.promises.mkdir(path.dirname(to), { recursive: true });

return fs.promises.copyFile(from, to);
}),
);
};
15 changes: 15 additions & 0 deletions packages/create-llama/helpers/get-pkg-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export type PackageManager = "npm" | "pnpm" | "yarn";

export function getPkgManager(): PackageManager {
const userAgent = process.env.npm_config_user_agent || "";

if (userAgent.startsWith("yarn")) {
return "yarn";
}

if (userAgent.startsWith("pnpm")) {
return "pnpm";
}

return "npm";
}
58 changes: 58 additions & 0 deletions packages/create-llama/helpers/git.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* eslint-disable import/no-extraneous-dependencies */
import { execSync } from "child_process";
import fs from "fs";
import path from "path";

function isInGitRepository(): boolean {
try {
execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore" });
return true;
} catch (_) {}
return false;
}

function isInMercurialRepository(): boolean {
try {
execSync("hg --cwd . root", { stdio: "ignore" });
return true;
} catch (_) {}
return false;
}

function isDefaultBranchSet(): boolean {
try {
execSync("git config init.defaultBranch", { stdio: "ignore" });
return true;
} catch (_) {}
return false;
}

export function tryGitInit(root: string): boolean {
let didInit = false;
try {
execSync("git --version", { stdio: "ignore" });
if (isInGitRepository() || isInMercurialRepository()) {
return false;
}

execSync("git init", { stdio: "ignore" });
didInit = true;

if (!isDefaultBranchSet()) {
execSync("git checkout -b main", { stdio: "ignore" });
}

execSync("git add -A", { stdio: "ignore" });
execSync('git commit -m "Initial commit from Create Next App"', {
stdio: "ignore",
});
return true;
} catch (e) {
if (didInit) {
try {
fs.rmSync(path.join(root, ".git"), { recursive: true, force: true });
} catch (_) {}
}
return false;
}
}
50 changes: 50 additions & 0 deletions packages/create-llama/helpers/install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* eslint-disable import/no-extraneous-dependencies */
import spawn from "cross-spawn";
import { yellow } from "picocolors";
import type { PackageManager } from "./get-pkg-manager";

/**
* Spawn a package manager installation based on user preference.
*
* @returns A Promise that resolves once the installation is finished.
*/
export async function callPackageManager(
/** Indicate which package manager to use. */
packageManager: PackageManager,
/** Indicate whether there is an active Internet connection.*/
isOnline: boolean,
args: string[] = ["install"],
): Promise<void> {
if (!isOnline) {
console.log(
yellow("You appear to be offline.\nFalling back to the local cache."),
);
args.push("--offline");
}
/**
* Return a Promise that resolves once the installation is finished.
*/
return new Promise((resolve, reject) => {
/**
* Spawn the installation process.
*/
const child = spawn(packageManager, args, {
stdio: "inherit",
env: {
...process.env,
ADBLOCK: "1",
// we set NODE_ENV to development as pnpm skips dev
// dependencies when production
NODE_ENV: "development",
DISABLE_OPENCOLLECTIVE: "1",
},
});
child.on("close", (code) => {
if (code !== 0) {
reject({ command: `${packageManager} ${args.join(" ")}` });
return;
}
resolve();
});
});
}
Loading

0 comments on commit 683c4ad

Please sign in to comment.