-
Notifications
You must be signed in to change notification settings - Fork 373
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #153 from run-llama/add/create-llama
Add create-llama CLI tool
- Loading branch information
Showing
128 changed files
with
4,467 additions
and
125 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}), | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); | ||
}); | ||
} |
Oops, something went wrong.