create-baml-app
Version:
<p align="center"> <img src="https://github.com/ashayas/create-baml-app/raw/master/.github/assets/lamb.png" width="250" alt="Create BAML App"/> </p>
346 lines (316 loc) ⢠14.1 kB
JavaScript
import inquirer from 'inquirer';
import chalk from 'chalk';
import { execa } from 'execa';
import { existsSync, mkdirSync } from 'fs';
import { join, resolve } from 'path';
import fs from 'fs';
function showHelp() {
console.log(chalk.bold.magenta('\nCreate BAML App š¦'));
console.log('\nUsage:');
console.log(' npx create-baml-app [project-name] [options]');
console.log('\nOptions:');
console.log(' -h, --help Show this help message');
console.log(' --use-current-dir, -. Use current directory instead of creating a new one');
console.log('\nExamples:');
console.log(' npx create-baml-app my-baml-app');
console.log(' npx create-baml-app my-baml-app --use-current-dir');
console.log(' npx create-baml-app my-baml-app -.');
console.log('\nSupported Package Managers:');
console.log(' - Python: pip, poetry, uv');
console.log(' - TypeScript: npm, pnpm, yarn, deno, bun');
console.log(' - Ruby: bundle');
process.exit(0);
}
async function runCommand(command, args, description, options = {}) {
console.log(chalk.cyan(`\nRunning: ${description}...`));
try {
const { stdout } = await execa(command, args, { stdio: 'inherit', ...options });
console.log(chalk.green('ā Success!'));
return stdout;
} catch (error) {
if (error.code === 'ENOENT') {
console.error(chalk.red(`Error: Command '${command}' not found. Please make sure it is installed and available in your PATH.`));
} else {
console.error(chalk.red(`Error: ${error.message}`));
}
process.exit(1);
}
}
// Check if a command is available without exiting if not
async function checkCommandAvailable(command) {
try {
await execa(command, ['--version'], { stdio: 'ignore' });
return true;
} catch (error) {
return false;
}
}
async function main() {
console.log(chalk.bold.magenta('\nWelcome to create-baml-app! š¦'));
// Handle command line arguments
const args = process.argv.slice(2);
// Show help if requested
if (args.includes('-h') || args.includes('--help')) {
showHelp();
}
// Get project name from arguments or prompt
let projectName = args[0];
if (!projectName) {
const response = await inquirer.prompt([
{
type: 'input',
name: 'projectName',
message: 'What is your project named? (Use "." to create in current directory)',
default: 'my-app',
validate: (input) => {
if (input === '.') return true;
if (!/^[a-zA-Z0-9-_]+$/.test(input)) {
return 'Project name can only contain letters, numbers, dashes and underscores';
}
return true;
}
}
]);
projectName = response.projectName;
}
// Determine target directory
const useCurrentDir = projectName === '.' || args.includes('--use-current-dir') || args.includes('-.');
const targetDir = useCurrentDir ? process.cwd() : resolve(projectName);
console.log(chalk.yellow("\nLet's set up your BAML project...\n"));
// Step 1: Select Language
const { language } = await inquirer.prompt([
{
type: 'list',
name: 'language',
message: chalk.blue('Which language would you like to use?'),
choices: ['Python', 'TypeScript', 'Ruby'],
},
]);
// Step 2: Select Package Manager based on Language
let packageManagerChoices;
switch (language) {
case 'Python':
packageManagerChoices = ['pip', 'poetry', 'uv'];
break;
case 'TypeScript':
packageManagerChoices = ['npm', 'pnpm', 'yarn', 'deno', 'bun'];
break;
case 'Ruby':
packageManagerChoices = ['bundle'];
break;
}
const { packageManager } = await inquirer.prompt([
{
type: 'list',
name: 'packageManager',
message: chalk.blue(`Which package manager would you like to use for ${language}?`),
choices: packageManagerChoices,
},
]);
// Check if the package manager is available before creating directories
let isPackageManagerAvailable = await checkCommandAvailable(packageManager);
// For npm/pnpm/yarn, we might need to check node instead
if (!isPackageManagerAvailable && ['npm', 'pnpm', 'yarn'].includes(packageManager)) {
isPackageManagerAvailable = await checkCommandAvailable('node');
}
if (!isPackageManagerAvailable) {
console.log(chalk.red(`\nError: ${packageManager} is not installed or not available in PATH`));
console.log(chalk.yellow(`Please install ${packageManager} first.`));
let installLink = '';
switch (packageManager) {
case 'pip':
installLink = 'https://pip.pypa.io/en/stable/installation/';
break;
case 'poetry':
installLink = 'https://python-poetry.org/docs/#installation';
break;
case 'uv':
installLink = 'https://github.com/astral-sh/uv#installation';
break;
case 'npm':
installLink = 'https://docs.npmjs.com/downloading-and-installing-node-js-and-npm';
break;
case 'pnpm':
installLink = 'https://pnpm.io/installation';
break;
case 'yarn':
installLink = 'https://classic.yarnpkg.com/en/docs/install';
break;
case 'deno':
installLink = 'https://deno.land/#installation';
break;
case 'bundle':
installLink = 'https://bundler.io/#getting-started';
break;
case 'bun':
installLink = 'https://bun.sh/docs/installation';
break;
}
console.log(chalk.gray(`${installLink}\n`));
process.exit(1);
}
// Only check if directory is empty if using current directory
if (useCurrentDir && existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
console.error(chalk.red('\nError: Current directory is not empty. Please use a new directory or empty the current one.'));
process.exit(1);
}
// Create directory if not using current directory
if (!useCurrentDir) {
if (existsSync(targetDir)) {
console.error(chalk.red(`\nError: Directory ${projectName} already exists. Please choose a different name or use --use-current-dir to use an existing directory.`));
process.exit(1);
}
console.log(chalk.cyan(`\nCreating a new BAML app in ${chalk.green(targetDir)}`));
try {
mkdirSync(targetDir, { recursive: true });
} catch (error) {
console.error(chalk.red(`Error creating directory: ${error.message}`));
process.exit(1);
}
}
// Change to target directory
try {
process.chdir(targetDir);
} catch (error) {
console.error(chalk.red(`Error changing to directory: ${error.message}`));
process.exit(1);
}
// Step 3: Execute Commands
console.log(chalk.magenta(`\nSetting up your ${language} BAML project with ${packageManager}...\n`));
if (language === 'Python') {
if (packageManager === 'pip') {
// Initialize requirements.txt if it doesn't exist
if (!existsSync('requirements.txt')) {
console.log(chalk.yellow('\nInitializing new Python project with pip...'));
await runCommand('touch', ['requirements.txt'], 'Creating requirements.txt');
}
// Continue with the rest of the installation
await runCommand('pip', ['install', 'baml-py'], 'Installing baml-py');
await runCommand('baml-cli', ['init'], 'Initializing BAML');
await runCommand('baml-cli', ['generate'], 'Generating BAML files');
} else if (packageManager === 'poetry') {
// Initialize poetry project if pyproject.toml doesn't exist
if (!existsSync('pyproject.toml')) {
console.log(chalk.yellow('\nInitializing new Python project with Poetry...'));
await runCommand('poetry', ['init', '--name=baml-project', '--description=A BAML project', '--author=', '--python=^3.8', '--no-interaction'], 'Creating Poetry project');
}
await runCommand('poetry', ['add', 'baml-py'], 'Adding baml-py');
await runCommand('poetry', ['run', 'baml-cli', 'init'], 'Initializing BAML');
await runCommand('poetry', ['run', 'baml-cli', 'generate'], 'Generating BAML files');
} else if (packageManager === 'uv') {
// Initialize pyproject.toml properly with uv init if it doesn't exist
if (!existsSync('pyproject.toml')) {
console.log(chalk.yellow('\nInitializing new Python project with uv...'));
await runCommand('uv', ['init'], 'Initializing uv project');
}
await runCommand('uv', ['add', 'baml-py'], 'Adding baml-py');
await runCommand('uv', ['run', 'baml-cli', 'init'], 'Initializing BAML');
await runCommand('uv', ['run', 'baml-cli', 'generate'], 'Generating BAML files');
}
} else if (language === 'TypeScript') {
// Install TypeScript first based on package manager (except for Deno)
if (packageManager !== 'deno' && packageManager !== 'bun') {
switch (packageManager) {
case 'npm':
await runCommand('npm', ['install', 'typescript', '--save-dev'], 'Installing TypeScript');
break;
case 'pnpm':
await runCommand('pnpm', ['add', 'typescript', '-D'], 'Installing TypeScript');
break;
case 'yarn':
await runCommand('yarn', ['add', 'typescript', '--dev'], 'Installing TypeScript');
break;
}
} else if (packageManager === 'bun') {
await runCommand('bun', ['add', 'typescript', '--dev'], 'Installing TypeScript');
}
// Create tsconfig.json if it doesn't exist
if (!existsSync('tsconfig.json')) {
try {
switch (packageManager) {
case 'npm':
await runCommand('npx', ['tsc', '--init'], 'Creating tsconfig.json');
break;
case 'pnpm':
await runCommand('pnpm', ['exec', 'tsc', '--init'], 'Creating tsconfig.json');
break;
case 'yarn':
await runCommand('yarn', ['tsc', '--init'], 'Creating tsconfig.json');
break;
case 'bun':
await runCommand('bun', ['x', 'tsc', '--init'], 'Creating tsconfig.json');
break;
case 'deno':
// For Deno, we'll create a basic tsconfig.json directly
console.log(chalk.cyan('\nCreating tsconfig.json for Deno...'));
const denoConfig = {
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"module": "ES2022",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
}
};
fs.writeFileSync('tsconfig.json', JSON.stringify(denoConfig, null, 2));
console.log(chalk.green('ā Success!'));
break;
}
} catch (error) {
console.error(chalk.red(`Error creating tsconfig.json: ${error.message}`));
process.exit(1);
}
}
if (packageManager === 'npm') {
await runCommand('npm', ['install', '@boundaryml/baml'], 'Installing @boundaryml/baml');
await runCommand('npx', ['baml-cli', 'init'], 'Initializing BAML');
await runCommand('npx', ['baml-cli', 'generate'], 'Generating BAML files');
} else if (packageManager === 'pnpm') {
await runCommand('pnpm', ['add', '@boundaryml/baml'], 'Adding @boundaryml/baml');
await runCommand('pnpm', ['exec', 'baml-cli', 'init'], 'Initializing BAML');
await runCommand('pnpm', ['exec', 'baml-cli', 'generate'], 'Generating BAML files');
} else if (packageManager === 'yarn') {
await runCommand('yarn', ['add', '@boundaryml/baml'], 'Adding @boundaryml/baml');
await runCommand('yarn', ['baml-cli', 'init'], 'Initializing BAML');
await runCommand('yarn', ['baml-cli', 'generate'], 'Generating BAML files');
} else if (packageManager === 'deno') {
// For Deno, create deno.json if it doesn't exist
if (!existsSync('deno.json')) {
console.log(chalk.yellow('\nInitializing new Deno project...'));
await runCommand('deno', ['init'], 'Creating deno.json');
}
await runCommand('deno', ['install', 'npm:@boundaryml/baml'], 'Installing @boundaryml/baml');
await runCommand('deno', ['run', '-A', 'npm:@boundaryml/baml/baml-cli', 'init'], 'Initializing BAML');
await runCommand('deno', ['run', '--unstable-sloppy-imports', '-A', 'npm:@boundaryml/baml/baml-cli', 'generate'], 'Generating BAML files');
console.log(chalk.yellow('\nNote: As per BAML docs, for Deno in VSCode, add this to your config:'));
console.log(chalk.gray('{\n "deno.unstable": ["sloppy-imports"]\n}'));
} else if (packageManager === 'bun') {
// For Bun, create package.json if it doesn't exist
if (!existsSync('package.json')) {
console.log(chalk.yellow('\nInitializing new Bun project...'));
await runCommand('bun', ['init', '-y'], 'Creating package.json', { cwd: targetDir });
}
await runCommand('bun', ['install', '@boundaryml/baml'], 'Installing @boundaryml/baml', { cwd: targetDir });
await runCommand('bun', ['x', 'baml-cli', 'init'], 'Initializing BAML', { cwd: targetDir });
await runCommand('bun', ['x', 'baml-cli', 'generate'], 'Generating BAML files', { cwd: targetDir });
}
} else if (language === 'Ruby') {
// Initialize Gemfile if it doesn't exist
if (!existsSync('Gemfile')) {
console.log(chalk.yellow('\nInitializing new Ruby project...'));
await runCommand('bundle', ['init'], 'Creating Gemfile');
}
await runCommand('bundle', ['add', 'baml', 'sorbet-runtime'], 'Adding baml and sorbet-runtime');
await runCommand('bundle', ['exec', 'baml-cli', 'init'], 'Initializing BAML');
await runCommand('bundle', ['exec', 'baml-cli', 'generate'], 'Generating BAML files');
}
console.log(chalk.bold.green('\nš All done! Your BAML project is ready.'));
console.log(chalk.cyan('Next steps: Check your project directory and start coding!'));
}
main().catch((error) => {
console.error(chalk.red('An unexpected error occurred:', error.message));
process.exit(1);
});