@ordojs/cli
Version:
Command-line interface for OrdoJS framework
348 lines (331 loc) • 12.2 kB
JavaScript
/**
* @fileoverview OrdoJS CLI - Netlify deployment adapter
* Adapter for deploying to Netlify
*/
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';
/**
* Netlify deployment adapter
*/
export class NetlifyAdapter {
/**
* Adapter name
*/
name = 'netlify';
/**
* Adapter description
*/
description = 'Deploy to Netlify';
/**
* Validate Netlify deployment configuration
* @param config Deployment configuration
* @returns Validation result
*/
validateConfig(config) {
const errors = [];
// Check required fields
if (!config.outputDir) {
errors.push('outputDir is required');
}
// Validate Netlify-specific settings
const netlifyConfig = config;
if (netlifyConfig.settings) {
// Validate site name if provided
if (netlifyConfig.settings.siteName && !/^[a-z0-9-]+$/.test(netlifyConfig.settings.siteName)) {
errors.push('Site name must contain only lowercase letters, numbers, and hyphens');
}
}
return {
valid: errors.length === 0,
errors: errors.length > 0 ? errors : undefined
};
}
/**
* Prepare Netlify deployment
* @param config Deployment configuration
* @returns Deployment result
*/
async prepareDeployment(config) {
const netlifyConfig = config;
const generatedFiles = [];
try {
// Create netlify.toml configuration
const netlifyTomlContent = this.generateNetlifyConfig(netlifyConfig);
const netlifyTomlPath = path.join(config.outputDir, 'netlify.toml');
await writeFile(netlifyTomlPath, netlifyTomlContent);
generatedFiles.push({
path: netlifyTomlPath,
content: netlifyTomlContent
});
// Create build output directory if it doesn't exist
await mkdir(config.outputDir, { recursive: true });
// Generate _redirects file
if (config.redirects && config.redirects.length > 0) {
const redirectsContent = this.generateRedirectsFile(config.redirects);
const redirectsPath = path.join(config.outputDir, '_redirects');
await writeFile(redirectsPath, redirectsContent);
generatedFiles.push({
path: redirectsPath,
content: redirectsContent
});
}
// Generate _headers file
if (config.headers && config.headers.length > 0) {
const headersContent = this.generateHeadersFile(config.headers);
const headersPath = path.join(config.outputDir, '_headers');
await writeFile(headersPath, headersContent);
generatedFiles.push({
path: headersPath,
content: headersContent
});
}
// Generate server functions if needed
if (config.includeServerFunctions) {
await this.generateServerFunctions(config);
}
// Optimize assets for Netlify
const optimizationResults = await this.optimizeForDeployment(config, config.outputDir);
return {
success: true,
generatedFiles,
instructions: this.getDeploymentInstructions(netlifyConfig),
optimizationResults
};
}
catch (error) {
logger.error(`Netlify deployment preparation failed: ${error instanceof Error ? error.message : String(error)}`);
return {
success: false,
error: `Netlify deployment preparation failed: ${error instanceof Error ? error.message : String(error)}`,
generatedFiles,
instructions: 'An error occurred during Netlify deployment preparation. Please check the logs for details.'
};
}
}
/**
* Generate Netlify configuration
* @param config Netlify configuration
* @returns Netlify configuration TOML
*/
generateNetlifyConfig(config) {
const siteName = config.settings?.siteName || 'ordojs-app';
const publishDir = config.outputDir || 'dist';
const functionsDir = config.includeServerFunctions ? 'functions' : undefined;
let toml = `# Netlify configuration for OrdoJS
[build]
publish = "${publishDir}"
command = "ordojs build --production"
${functionsDir ? ` functions = "${functionsDir}"` : ''}
[dev]
framework = "ordojs"
command = "ordojs dev"
port = 8888
targetPort = 3000
publish = "${publishDir}"
${functionsDir ? ` functions = "${functionsDir}"` : ''}
`;
// Add environment variables
if (config.env && Object.keys(config.env).length > 0) {
toml += '[build.environment]\n';
for (const [key, value] of Object.entries(config.env)) {
toml += ` ${key} = "${value}"\n`;
}
toml += '\n';
}
// Add headers
if (config.headers && config.headers.length > 0) {
for (const header of config.headers) {
toml += `[[headers]]\n`;
toml += ` for = "${header.source}"\n`;
toml += ` [headers.values]\n`;
for (const h of header.headers) {
toml += ` ${h.key} = "${h.value}"\n`;
}
toml += '\n';
}
}
// Add redirects
if (config.redirects && config.redirects.length > 0) {
for (const redirect of config.redirects) {
toml += `[[redirects]]\n`;
toml += ` from = "${redirect.source}"\n`;
toml += ` to = "${redirect.destination}"\n`;
toml += ` status = ${redirect.permanent ? 301 : 302}\n`;
toml += ` force = true\n\n`;
}
}
// Add plugins
toml += `[plugins]\n`;
if (config.settings?.useAnalytics) {
toml += ` package = "@netlify/plugin-analytics"\n\n`;
}
// Add site settings
toml += `[build.processing]\n`;
toml += ` skip_processing = false\n`;
toml += `[build.processing.css]\n`;
toml += ` bundle = true\n`;
toml += ` minify = true\n`;
toml += `[build.processing.js]\n`;
toml += ` bundle = true\n`;
toml += ` minify = true\n`;
toml += `[build.processing.html]\n`;
toml += ` pretty_urls = true\n`;
toml += `[build.processing.images]\n`;
toml += ` compress = true\n`;
return toml;
}
/**
* Generate Netlify _redirects file
* @param redirects Redirects configuration
* @returns Redirects file content
*/
generateRedirectsFile(redirects) {
return redirects.map(redirect => {
return `${redirect.source} ${redirect.destination} ${redirect.permanent ? 301 : 302}`;
}).join('\n');
}
/**
* Generate Netlify _headers file
* @param headers Headers configuration
* @returns Headers file content
*/
generateHeadersFile(headers) {
let content = '';
for (const header of headers) {
content += `${header.source}\n`;
for (const h of header.headers) {
content += ` ${h.key}: ${h.value}\n`;
}
content += '\n';
}
return content;
}
/**
* Generate server functions for Netlify
* @param config Deployment configuration
*/
async generateServerFunctions(config) {
// Create functions directory
const functionsDir = path.join(config.outputDir, 'functions');
await mkdir(functionsDir, { recursive: true });
// Generate example function
const exampleFunctionContent = `
exports.handler = async function(event, context) {
return {
statusCode: 200,
body: JSON.stringify({
message: 'Hello from OrdoJS on Netlify!',
timestamp: new Date().toISOString()
})
};
};
`;
await writeFile(path.join(functionsDir, 'hello.js'), exampleFunctionContent);
}
/**
* Get deployment instructions
* @param config Netlify configuration
* @returns Deployment instructions
*/
getDeploymentInstructions(config) {
return `
# Netlify Deployment Instructions
Your project has been prepared for deployment to Netlify.
## Prerequisites
1. Install the Netlify CLI:
\`\`\`
npm install -g netlify-cli
\`\`\`
2. Login to Netlify:
\`\`\`
netlify login
\`\`\`
## Deployment
To deploy your application to Netlify, run:
\`\`\`
cd ${config.outputDir}
netlify deploy --prod
\`\`\`
## Configuration
The following files have been generated:
- \`netlify.toml\`: Netlify configuration file
${config.redirects && config.redirects.length > 0 ? '- `_redirects`: URL redirects configuration\n' : ''}
${config.headers && config.headers.length > 0 ? '- `_headers`: HTTP headers configuration\n' : ''}
${config.includeServerFunctions ? '- `functions/`: Netlify Functions directory\n' : ''}
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 Netlify dashboard or in the netlify.toml file.'}
## Custom Domain
${config.domain
? `Your application will be available at: ${config.domain.name}`
: 'You can configure a custom domain in the Netlify dashboard after deployment.'}
`;
}
/**
* Optimize assets for Netlify deployment
* @param config Deployment configuration
* @param outputDir Output directory
* @returns Optimization results
*/
async optimizeForDeployment(config, outputDir) {
logger.info('Optimizing assets for Netlify deployment...');
// Initialize asset optimizer with Netlify-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 Netlify-specific environment variables
* @param config Deployment configuration
* @returns Environment variables
*/
getEnvironmentVariables(config) {
const env = {
NETLIFY: 'true',
CONTEXT: 'production',
NODE_ENV: 'production',
...config.env
};
// Add Netlify-specific environment variables
if (config.domain) {
env.URL = `https://${config.domain.name}`;
}
return env;
}
/**
* Get Netlify deployment command
* @param config Deployment configuration
* @returns Deployment command
*/
getDeployCommand(config) {
return `cd ${config.outputDir} && netlify deploy --prod`;
}
}
//# sourceMappingURL=netlify-adapter.js.map