Newer
Older
Marcus Schiesser
committed
import { callPackageManager } from "../helpers/install";
import fs from "fs/promises";
import os from "os";
import path from "path";
import { bold, cyan } from "picocolors";
import { version } from "../../core/package.json";
Marcus Schiesser
committed
import { PackageManager } from "../helpers/get-pkg-manager";
import {
InstallTemplateArgs,
TemplateEngine,
TemplateFramework,
} from "./types";
const envFileNameMap: Record<TemplateFramework, string> = {
nextjs: ".env.local",
express: ".env",
fastapi: ".env",
};
const createEnvLocalFile = async (
root: string,
framework: TemplateFramework,
openAIKey?: string,
) => {
if (openAIKey) {
const envFileName = envFileNameMap[framework];
if (!envFileName) return;
await fs.writeFile(
path.join(root, envFileName),
`OPENAI_API_KEY=${openAIKey}\n`,
);
console.log(`Created '${envFileName}' file containing OPENAI_API_KEY`);
}
};
Marcus Schiesser
committed
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
const copyTestData = async (
root: string,
framework: TemplateFramework,
packageManager?: PackageManager,
engine?: TemplateEngine,
) => {
if (engine === "context" || framework === "fastapi") {
const srcPath = path.join(__dirname, "components", "data");
const destPath = path.join(root, "data");
console.log(`\nCopying test data to ${cyan(destPath)}\n`);
await copy("**", destPath, {
parents: true,
cwd: srcPath,
});
}
if (packageManager && engine === "context") {
console.log(
`\nRunning ${cyan("npm run generate")} to generate the context data.\n`,
);
await callPackageManager(packageManager, true, ["run", "generate"]);
console.log();
}
};
const rename = (name: string) => {
switch (name) {
case "gitignore":
case "eslintrc.json": {
return `.${name}`;
}
// README.md is ignored by webpack-asset-relocator-loader used by ncc:
// https://github.com/vercel/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227
case "README-template.md": {
return "README.md";
}
default: {
return name;
}
}
};
/**
* Install a LlamaIndex internal template to a given `root` directory.
*/
const installTSTemplate = async ({
appName,
root,
packageManager,
isOnline,
template,
Marcus Schiesser
committed
framework,
engine,
/**
* Copy the template files to the target directory.
*/
console.log("\nInitializing project with template:", template, "\n");
const templatePath = path.join(__dirname, "types", template, framework);
const copySource = ["**"];
if (!eslint) copySource.push("!eslintrc.json");
await copy(copySource, root, {
parents: true,
cwd: templatePath,
/**
* Copy the selected chat engine files to the target directory and reference it.
*/
const compPath = path.join(__dirname, "components");
if (engine && (framework === "express" || framework === "nextjs")) {
console.log("\nUsing chat engine:", engine, "\n");
const enginePath = path.join(compPath, "engines", engine);
relativeEngineDestPath =
framework === "nextjs"
? path.join("app", "api", "chat")
: path.join("src", "controllers");
await copy("**", path.join(root, relativeEngineDestPath, "engine"), {
parents: true,
cwd: enginePath,
});
}
/**
* Copy the selected UI files to the target directory and reference it.
*/
Marcus Schiesser
committed
if (framework === "nextjs" && ui !== "html") {
console.log("\nUsing UI:", ui, "\n");
const uiPath = path.join(compPath, "ui", ui);
Marcus Schiesser
committed
const destUiPath = path.join(root, "app", "components", "ui");
// remove the default ui folder
await fs.rm(destUiPath, { recursive: true });
// copy the selected ui folder
await copy("**", destUiPath, {
parents: true,
cwd: uiPath,
Marcus Schiesser
committed
* Update the package.json scripts.
Marcus Schiesser
committed
const packageJsonFile = path.join(root, "package.json");
const packageJson: any = JSON.parse(
await fs.readFile(packageJsonFile, "utf8"),
);
packageJson.name = appName;
packageJson.version = "0.1.0";
Marcus Schiesser
committed
packageJson.dependencies = {
...packageJson.dependencies,
llamaindex: version,
};
if (framework === "nextjs" && customApiPath) {
console.log(
"\nUsing external API with custom API path:",
customApiPath,
"\n",
);
// remove the default api folder
const apiPath = path.join(root, "app", "api");
await fs.rm(apiPath, { recursive: true });
// modify the dev script to use the custom api path
packageJson.scripts = {
...packageJson.scripts,
dev: `NEXT_PUBLIC_CHAT_API=${customApiPath} next dev`,
};
}
if (engine === "context" && relativeEngineDestPath) {
// add generate script if using context engine
packageJson.scripts = {
...packageJson.scripts,
generate: `node ${path.join(
relativeEngineDestPath,
"engine",
"generate.mjs",
)}`,
};
}
if (framework === "nextjs" && ui === "shadcn") {
// add shadcn dependencies to package.json
packageJson.dependencies = {
...packageJson.dependencies,
"tailwind-merge": "^2",
"@radix-ui/react-slot": "^1",
"class-variance-authority": "^0.7",
"lucide-react": "^0.291",
remark: "^14.0.3",
"remark-code-import": "^1.2.0",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"react-markdown": "^8.0.7",
"react-syntax-highlighter": "^15.5.0",
};
packageJson.devDependencies = {
...packageJson.devDependencies,
"@types/react-syntax-highlighter": "^15.5.6",
Marcus Schiesser
committed
if (!eslint) {
// Remove packages starting with "eslint" from devDependencies
packageJson.devDependencies = Object.fromEntries(
Object.entries(packageJson.devDependencies).filter(
([key]) => !key.startsWith("eslint"),
),
);
Marcus Schiesser
committed
packageJsonFile,
for (const dependency in packageJson.dependencies)
Marcus Schiesser
committed
console.log("\nInstalling devDependencies:");
for (const dependency in packageJson.devDependencies)
console.log(`- ${cyan(dependency)}`);
Marcus Schiesser
committed
await callPackageManager(packageManager, isOnline);
const installPythonTemplate = async ({
Marcus Schiesser
committed
}: Pick<InstallTemplateArgs, "root" | "framework" | "template">) => {
console.log("\nInitializing Python project with template:", template, "\n");
const templatePath = path.join(__dirname, "types", template, framework);
await copy("**", root, {
parents: true,
cwd: templatePath,
rename(name) {
switch (name) {
case "gitignore": {
return `.${name}`;
}
// README.md is ignored by webpack-asset-relocator-loader used by ncc:
// https://github.com/vercel/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227
case "README-template.md": {
return "README.md";
}
default: {
return name;
}
}
},
});
console.log(
"\nPython project, dependencies won't be installed automatically.\n",
);
};
export const installTemplate = async (
props: InstallTemplateArgs & { backend: boolean },
) => {
process.chdir(props.root);
if (props.framework === "fastapi") {
await installPythonTemplate(props);
} else {
await installTSTemplate(props);
}
Marcus Schiesser
committed
if (props.backend) {
// This is a backend, so we need to copy the test data and create the env file.
Marcus Schiesser
committed
// Copy the environment file to the target directory.
await createEnvLocalFile(props.root, props.framework, props.openAIKey);
// Copy test pdf file
await copyTestData(
props.root,
props.framework,
props.packageManager,
props.engine,
);
}