diff --git a/README.md b/README.md index e966781..4d1f454 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ This monorepo contains: - `--dir, -d` - Specify demo directory (default: ~/.cache/atxp/demo) ### Create Options -- `--framework, -f` - Specify framework template (default: express) +- `--framework, -f` - Specify framework template (options: express, cloudflare; default: express) - `--git` - Force git initialization - `--no-git` - Skip git initialization @@ -66,12 +66,15 @@ npx atxp demo --refresh ### Create a New Project ```bash -# Create a new project (auto-detects git) +# Create a new Express project (auto-detects git) npx atxp create my-app -# Create with specific framework +# Create with Express framework (default) npx atxp create my-app --framework express +# Create with Cloudflare Workers framework +npx atxp create my-app --framework cloudflare + # Skip git initialization npx atxp create my-app --no-git @@ -84,7 +87,9 @@ npm create atxp my-app # Set up the project cd my-app npm install -npm start +npm start # For Express projects +# or +npm start # For Cloudflare projects (runs vite dev) ``` ## Development diff --git a/packages/atxp/src/create-project.test.ts b/packages/atxp/src/create-project.test.ts index d583a02..9387d60 100644 --- a/packages/atxp/src/create-project.test.ts +++ b/packages/atxp/src/create-project.test.ts @@ -32,13 +32,17 @@ describe('createProject', () => { describe('Framework type', () => { it('should have correct framework types', () => { - const validFramework: Framework = 'express'; - expect(validFramework).toBe('express'); - + const expressFramework: Framework = 'express'; + expect(expressFramework).toBe('express'); + + const cloudflareFramework: Framework = 'cloudflare'; + expect(cloudflareFramework).toBe('cloudflare'); + // Test that the type system prevents invalid frameworks // This is compile-time validation, but we can test the concept - const frameworks = ['express'] as const; + const frameworks = ['express', 'cloudflare'] as const; expect(frameworks).toContain('express'); + expect(frameworks).toContain('cloudflare'); }); }); diff --git a/packages/atxp/src/create-project.ts b/packages/atxp/src/create-project.ts index 4cc4955..a5e6824 100644 --- a/packages/atxp/src/create-project.ts +++ b/packages/atxp/src/create-project.ts @@ -4,7 +4,7 @@ import chalk from 'chalk'; import { spawn } from 'child_process'; import inquirer from 'inquirer'; -export type Framework = 'express'; +export type Framework = 'express' | 'cloudflare'; // Utility function to check if git is available async function isGitAvailable(): Promise { @@ -141,6 +141,10 @@ const TEMPLATES: Record = { express: { url: 'https://github.com/atxp-dev/atxp-express-starter.git', humanText: 'Express (Express.js starter template)' + }, + cloudflare: { + url: 'https://github.com/atxp-dev/atxp-cloudflare-chat-template', + humanText: 'Cloudflare (Cloudflare Chat agent with ATXP integration)' } // Future frameworks can be added here // vercel: { @@ -177,6 +181,34 @@ async function findEnvExample(projectPath: string): Promise { return null; } +// Find .dev.vars.example file in project directory or one level of subdirectories +async function findDevVarsExample(projectPath: string): Promise { + // Check root directory first + const rootDevVars = path.join(projectPath, '.dev.vars.example'); + if (await fs.pathExists(rootDevVars)) { + return rootDevVars; + } + + // Check subdirectories (max depth 1) + try { + const entries = await fs.readdir(projectPath, { withFileTypes: true }); + + for (const entry of entries) { + if (entry.isDirectory()) { + const subDirDevVars = path.join(projectPath, entry.name, '.dev.vars.example'); + if (await fs.pathExists(subDirDevVars)) { + return subDirDevVars; + } + } + } + } catch { + // If we can't read the directory, just return null + console.warn(chalk.yellow('Could not search for .dev.vars.example files')); + } + + return null; +} + export async function createProject(appName: string, framework: Framework, gitOption?: 'git' | 'no-git'): Promise { try { // Validate app name @@ -220,22 +252,27 @@ export async function createProject(appName: string, framework: Framework, gitOp // Clone template from GitHub await cloneTemplate(framework, projectPath); - // Copy .env file from env.example if it exists and configure it interactively - // Search for env.example in project root and one level of subdirectories + // Copy configuration file from template if it exists and configure it interactively + // Search for .dev.vars.example or env.example in project root and one level of subdirectories + // Prioritize .dev.vars.example (used by Cloudflare) over env.example + const devVarsExamplePath = await findDevVarsExample(projectPath); const envExamplePath = await findEnvExample(projectPath); - let createdEnvPath: string | null = null; + let createdConfigPath: string | null = null; - if (envExamplePath) { - const envDir = path.dirname(envExamplePath); - const envPath = path.join(envDir, '.env'); + const configSource = devVarsExamplePath || envExamplePath; + if (configSource) { + const configDir = path.dirname(configSource); + const isDevVars = configSource === devVarsExamplePath; + const configPath = path.join(configDir, isDevVars ? '.dev.vars' : '.env'); + const configFileName = isDevVars ? '.dev.vars' : '.env'; - await fs.copy(envExamplePath, envPath); - console.log(chalk.green('Environment file created from template')); + await fs.copy(configSource, configPath); + console.log(chalk.green(`${configFileName} file created from template`)); // Configure environment variables interactively - await configureEnvironmentVariables(envPath); + await configureEnvironmentVariables(configPath); - createdEnvPath = envPath; + createdConfigPath = configPath; } // Update package.json with project name @@ -266,12 +303,20 @@ export async function createProject(appName: string, framework: Framework, gitOp console.log(chalk.green('\nProject created successfully!')); console.log(chalk.blue('\nNext steps:')); console.log(chalk.white(` cd ${appName}`)); - console.log(chalk.white(' npm run install-all')); + + // Different installation instructions based on framework + if (framework === 'express') { + console.log(chalk.white(' npm run install-all')); + } else { + console.log(chalk.white(' npm install')); + } + console.log(chalk.white(' npm start')); - - // Only show env reminder if there is an .env file that exists - if (createdEnvPath && await fs.pathExists(createdEnvPath)) { - console.log(chalk.yellow('\nRemember to configure your environment variables in the .env file!')); + + // Only show config file reminder if a config file was created and exists + if (createdConfigPath && await fs.pathExists(createdConfigPath)) { + const configFileName = path.basename(createdConfigPath); + console.log(chalk.yellow(`\nRemember to configure your environment variables in the ${configFileName} file!`)); } } catch (error) { diff --git a/packages/atxp/src/index.ts b/packages/atxp/src/index.ts index f446d2d..876815c 100644 --- a/packages/atxp/src/index.ts +++ b/packages/atxp/src/index.ts @@ -114,7 +114,7 @@ async function main() { const framework: Framework = createOptions.framework || 'express'; // Validate framework - const validFrameworks: Framework[] = ['express']; + const validFrameworks: Framework[] = ['express', 'cloudflare']; if (createOptions.framework && !validFrameworks.includes(createOptions.framework)) { console.error(`Error: Unknown framework "${createOptions.framework}". Available frameworks: ${validFrameworks.join(', ')}`); process.exit(1);