@ordojs/cli
Version:
Command-line interface for OrdoJS framework
369 lines (308 loc) • 9.99 kB
text/typescript
/**
* @fileoverview OrdoJS CLI - Vercel deployment adapter
* Adapter for deploying to Vercel
*/
import path from 'path';
import { AssetOptimizer, OptimizationResults } from '../../asset-optimizer.js';
import { mkdir, writeFile } from '../../fs.js';
import { logger } from '../../logger.js';
import { DeploymentAdapter, DeploymentConfig, DeploymentResult } from '../adapter-interface.js';
/**
* Vercel-specific configuration
*/
export interface VercelConfig extends DeploymentConfig {
/**
* Vercel project settings
*/
settings?: {
/**
* Vercel project name
*/
projectName?: string;
/**
* Vercel framework preset
*/
framework?: string;
/**
* Vercel region
*/
region?: string;
/**
* Node.js version
*/
nodeVersion?: string;
/**
* Whether to use Edge Functions
*/
useEdgeFunctions?: boolean;
/**
* Whether to use Vercel Analytics
*/
analytics?: boolean;
/**
* Whether to use Vercel Image Optimization
*/
imageOptimization?: boolean;
};
}
/**
* Vercel deployment adapter
*/
export class VercelAdapter implements DeploymentAdapter {
/**
* Adapter name
*/
name = 'vercel';
/**
* Adapter description
*/
description = 'Deploy to Vercel';
/**
* Validate Vercel deployment configuration
* @param config Deployment configuration
* @returns Validation result
*/
validateConfig(config: DeploymentConfig): { valid: boolean; errors?: string[] } {
const errors: string[] = [];
// Check required fields
if (!config.outputDir) {
errors.push('outputDir is required');
}
// Validate Vercel-specific settings
const vercelConfig = config as VercelConfig;
if (vercelConfig.settings) {
// Validate project name if provided
if (vercelConfig.settings.projectName && !/^[a-z0-9-]+$/.test(vercelConfig.settings.projectName)) {
errors.push('Project name must contain only lowercase letters, numbers, and hyphens');
}
// Validate Node.js version if provided
if (vercelConfig.settings.nodeVersion && !/^[0-9]+\.[0-9]+\.[0-9]+$|^[0-9]+$/.test(vercelConfig.settings.nodeVersion)) {
errors.push('Invalid Node.js version format');
}
}
return {
valid: errors.length === 0,
errors: errors.length > 0 ? errors : undefined
};
}
/**
* Prepare Vercel deployment
* @param config Deployment configuration
* @returns Deployment result
*/
async prepareDeployment(config: DeploymentConfig): Promise<DeploymentResult> {
const vercelConfig = config as VercelConfig;
const generatedFiles: Array<{ path: string; content: string }> = [];
try {
// Create vercel.json configuration
const vercelJsonContent = this.generateVercelConfig(vercelConfig);
const vercelJsonPath = path.join(config.outputDir, 'vercel.json');
await writeFile(vercelJsonPath, vercelJsonContent);
generatedFiles.push({
path: vercelJsonPath,
content: vercelJsonContent
});
// Create build output directory if it doesn't exist
await mkdir(config.outputDir, { recursive: true });
// Generate .vercel directory with project.json
const projectJsonContent = this.generateProjectConfig(vercelConfig);
const projectJsonDir = path.join(config.outputDir, '.vercel');
const projectJsonPath = path.join(projectJsonDir, 'project.json');
await mkdir(projectJsonDir, { recursive: true });
await writeFile(projectJsonPath, projectJsonContent);
generatedFiles.push({
path: projectJsonPath,
content: projectJsonContent
});
// Generate server functions if needed
if (config.includeServerFunctions) {
await this.generateServerFunctions(config);
}
// Optimize assets for Vercel
const optimizationResults = await this.optimizeForDeployment(config, config.outputDir);
return {
success: true,
generatedFiles,
instructions: this.getDeploymentInstructions(vercelConfig),
optimizationResults
};
} catch (error) {
logger.error(`Vercel deployment preparation failed: ${error instanceof Error ? error.message : String(error)}`);
return {
success: false,
error: `Vercel deployment preparation failed: ${error instanceof Error ? error.message : String(error)}`,
generatedFiles,
instructions: 'An error occurred during Vercel deployment preparation. Please check the logs for details.'
};
}
}
/**
* Generate Vercel configuration
* @param config Vercel configuration
* @returns Vercel configuration JSON
*/
private generateVercelConfig(config: VercelConfig): string {
const vercelConfig: any = {
version: 2,
buildCommand: 'ordojs build --production',
outputDirectory: config.outputDir,
framework: config.settings?.framework || 'ordojs',
regions: config.settings?.region ? [config.settings.region] : ['iad1'],
env: config.env || {}
};
// Add headers if provided
if (config.headers && config.headers.length > 0) {
vercelConfig.headers = config.headers;
}
// Add redirects if provided
if (config.redirects && config.redirects.length > 0) {
vercelConfig.redirects = config.redirects;
}
// Add routes for API functions if needed
if (config.includeServerFunctions) {
vercelConfig.routes = [
{
src: '/api/(.*)',
dest: '/api/$1'
}
];
}
return JSON.stringify(vercelConfig, null, 2);
}
/**
* Generate Vercel project configuration
* @param config Vercel configuration
* @returns Project configuration JSON
*/
private generateProjectConfig(config: VercelConfig): string {
const projectConfig: any = {
projectId: config.settings?.projectName || 'ordojs-app',
orgId: 'your-org-id',
settings: {
framework: config.settings?.framework || 'ordojs',
nodeVersion: config.settings?.nodeVersion || '18.x'
}
};
return JSON.stringify(projectConfig, null, 2);
}
/**
* Generate server functions for Vercel
* @param config Deployment configuration
*/
private async generateServerFunctions(config: DeploymentConfig): Promise<void> {
// Create api directory
const apiDir = path.join(config.outputDir, 'api');
await mkdir(apiDir, { recursive: true });
// Generate example API function
const exampleFunctionContent = `
export default function handler(req, res) {
res.status(200).json({
message: 'Hello from OrdoJS on Vercel!',
timestamp: new Date().toISOString()
});
}
`;
await writeFile(path.join(apiDir, 'hello.js'), exampleFunctionContent);
}
/**
* Get deployment instructions
* @param config Vercel configuration
* @returns Deployment instructions
*/
private getDeploymentInstructions(config: VercelConfig): string {
return `
# Vercel Deployment Instructions
Your project has been prepared for deployment to Vercel.
## Prerequisites
1. Install the Vercel CLI:
\`\`\`
npm install -g vercel
\`\`\`
2. Login to Vercel:
\`\`\`
vercel login
\`\`\`
## Deployment
To deploy your application to Vercel, run:
\`\`\`
cd ${config.outputDir}
vercel --prod
\`\`\`
## Configuration
The following files have been generated:
- \`vercel.json\`: Vercel configuration file
- \`.vercel/project.json\`: Project configuration
You can customize these files to adjust your deployment settings.
## Environment Variables
${config.env && Object.keys(config.env).length > 0
? 'The following environment variables have been configured:\n\n' +
Object.entries(config.env).map(([key, value]) => `- ${key}: ${value}`).join('\n')
: 'No environment variables have been configured. You can add them in the Vercel dashboard or in the vercel.json file.'
}
## Custom Domain
${config.domain
? `Your application will be available at: ${config.domain.name}`
: 'You can configure a custom domain in the Vercel dashboard after deployment.'
}
`;
}
/**
* Optimize assets for Vercel deployment
* @param config Deployment configuration
* @param outputDir Output directory
* @returns Optimization results
*/
async optimizeForDeployment(config: DeploymentConfig, outputDir: string): Promise<OptimizationResults> {
logger.info('Optimizing assets for Vercel deployment...');
// Initialize asset optimizer with Vercel-specific options
const optimizer = new AssetOptimizer({
minifyJs: true,
minifyCss: true,
brotli: true,
gzip: true,
sizeReport: true,
terserOptions: {
compress: {
passes: 2,
drop_console: true,
drop_debugger: true
},
mangle: true,
format: {
comments: false
}
}
});
// Optimize all assets in the output directory
const optimizationResults = await optimizer.optimizeDirectory(outputDir);
// Generate and save size report
const sizeReport = optimizer.generateSizeReport(optimizationResults);
await writeFile(path.join(outputDir, 'size-report.txt'), sizeReport);
return optimizationResults;
}
/**
* Get Vercel-specific environment variables
* @param config Deployment configuration
* @returns Environment variables
*/
getEnvironmentVariables(config: DeploymentConfig): Record<string, string> {
const env: Record<string, string> = {
VERCEL: '1',
NODE_ENV: 'production',
...config.env
};
// Add Vercel-specific environment variables
if (config.domain) {
env.VERCEL_URL = config.domain.name;
}
return env;
}
/**
* Get Vercel deployment command
* @param config Deployment configuration
* @returns Deployment command
*/
getDeployCommand(config: DeploymentConfig): string {
return `cd ${config.outputDir} && vercel --prod`;
}
}