Add Docker Compose configuration and environment files for local and production setups
- Created docker-compose.docker-local.yml for local testing of frontend and backend services. - Added .env.development for development environment configuration. - Introduced .env.docker-local for local Docker environment settings. - Added .env.production for production environment configuration for Synology deployment.
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
/**
|
||||
* Build Verification Script
|
||||
*
|
||||
* Verifies that the production build was successful and contains
|
||||
* all necessary files for deployment.
|
||||
*
|
||||
* Extracts and validates environment variables embedded in the built bundle.
|
||||
* Ensures redirect URIs match the expected deployment target.
|
||||
*
|
||||
* Usage:
|
||||
* BUILD_TARGET=docker-local npm run build:verify
|
||||
* BUILD_TARGET=production npm run build:verify
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
@@ -12,154 +15,124 @@ import { fileURLToPath } from 'url';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const DIST_DIR = path.join(__dirname, '..', 'dist');
|
||||
const REQUIRED_FILES = [
|
||||
'index.html',
|
||||
'assets' // Directory
|
||||
];
|
||||
// Expected redirect URIs for each deployment target
|
||||
const EXPECTED_URIS = {
|
||||
'docker-local': 'http://localhost:8099/oauth/callback',
|
||||
production: 'https://app.pokedex.online/oauth/callback'
|
||||
};
|
||||
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
// ANSI colors
|
||||
const colors = {
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
reset: '\x1b[0m'
|
||||
};
|
||||
|
||||
console.log('🔍 Verifying build output...\n');
|
||||
|
||||
// Check if dist directory exists
|
||||
if (!fs.existsSync(DIST_DIR)) {
|
||||
errors.push('dist/ directory not found - build may have failed');
|
||||
console.error('❌ dist/ directory not found');
|
||||
process.exit(1);
|
||||
function log(color, symbol, message) {
|
||||
console.log(`${colors[color]}${symbol}${colors.reset} ${message}`);
|
||||
}
|
||||
|
||||
console.log(`📁 Build directory: ${DIST_DIR}`);
|
||||
|
||||
// Check required files
|
||||
console.log('\n📋 Checking required files:');
|
||||
for (const file of REQUIRED_FILES) {
|
||||
const filePath = path.join(DIST_DIR, file);
|
||||
const exists = fs.existsSync(filePath);
|
||||
|
||||
if (exists) {
|
||||
const stats = fs.statSync(filePath);
|
||||
const isDir = stats.isDirectory();
|
||||
console.log(
|
||||
` ✅ ${file} ${isDir ? '(directory)' : `(${(stats.size / 1024).toFixed(2)} KB)`}`
|
||||
);
|
||||
} else {
|
||||
errors.push(`Required file missing: ${file}`);
|
||||
console.log(` ❌ ${file} - MISSING`);
|
||||
function findBuiltAssets() {
|
||||
const distPath = path.resolve(__dirname, '../dist/assets');
|
||||
|
||||
if (!fs.existsSync(distPath)) {
|
||||
throw new Error('dist/assets directory not found. Run build first.');
|
||||
}
|
||||
}
|
||||
|
||||
// Check for JavaScript bundles
|
||||
console.log('\n📦 Checking JavaScript bundles:');
|
||||
const assetsDir = path.join(DIST_DIR, 'assets');
|
||||
if (fs.existsSync(assetsDir)) {
|
||||
const files = fs.readdirSync(assetsDir);
|
||||
const jsFiles = files.filter(f => f.endsWith('.js'));
|
||||
const cssFiles = files.filter(f => f.endsWith('.css'));
|
||||
|
||||
|
||||
const files = fs.readdirSync(distPath);
|
||||
const jsFiles = files.filter(f => f.endsWith('.js') && f.startsWith('index-'));
|
||||
|
||||
if (jsFiles.length === 0) {
|
||||
errors.push('No JavaScript bundles found in assets/');
|
||||
console.log(' ❌ No JavaScript bundles found');
|
||||
} else {
|
||||
console.log(` ✅ Found ${jsFiles.length} JavaScript bundles`);
|
||||
|
||||
// Show bundle sizes
|
||||
const totalJsSize = jsFiles.reduce((total, file) => {
|
||||
const stats = fs.statSync(path.join(assetsDir, file));
|
||||
return total + stats.size;
|
||||
}, 0);
|
||||
console.log(
|
||||
` Total JS size: ${(totalJsSize / 1024 / 1024).toFixed(2)} MB`
|
||||
);
|
||||
|
||||
// Warn if bundles are too large
|
||||
jsFiles.forEach(file => {
|
||||
const stats = fs.statSync(path.join(assetsDir, file));
|
||||
const sizeMB = stats.size / 1024 / 1024;
|
||||
if (sizeMB > 1) {
|
||||
warnings.push(
|
||||
`Large bundle detected: ${file} (${sizeMB.toFixed(2)} MB)`
|
||||
);
|
||||
}
|
||||
});
|
||||
throw new Error('No built JavaScript files found in dist/assets/');
|
||||
}
|
||||
|
||||
if (cssFiles.length === 0) {
|
||||
warnings.push('No CSS files found in assets/');
|
||||
console.log(' ⚠️ No CSS files found');
|
||||
} else {
|
||||
console.log(` ✅ Found ${cssFiles.length} CSS files`);
|
||||
}
|
||||
} else {
|
||||
errors.push('assets/ directory not found');
|
||||
console.log(' ❌ assets/ directory not found');
|
||||
|
||||
return jsFiles.map(f => path.join(distPath, f));
|
||||
}
|
||||
|
||||
// Check index.html
|
||||
console.log('\n📄 Checking index.html:');
|
||||
const indexPath = path.join(DIST_DIR, 'index.html');
|
||||
if (fs.existsSync(indexPath)) {
|
||||
const content = fs.readFileSync(indexPath, 'utf8');
|
||||
|
||||
// Check for script tags
|
||||
const scriptTags = content.match(/<script[^>]*src="[^"]*"[^>]*>/g);
|
||||
if (scriptTags && scriptTags.length > 0) {
|
||||
console.log(` ✅ Found ${scriptTags.length} script tag(s)`);
|
||||
} else {
|
||||
errors.push('No script tags found in index.html');
|
||||
console.log(' ❌ No script tags found');
|
||||
}
|
||||
|
||||
// Check for stylesheet links
|
||||
const linkTags = content.match(/<link[^>]*rel="stylesheet"[^>]*>/g);
|
||||
if (linkTags && linkTags.length > 0) {
|
||||
console.log(` ✅ Found ${linkTags.length} stylesheet link(s)`);
|
||||
} else {
|
||||
warnings.push('No stylesheet links found in index.html');
|
||||
console.log(' ⚠️ No stylesheet links found');
|
||||
}
|
||||
} else {
|
||||
errors.push('index.html not found');
|
||||
console.log(' ❌ index.html not found');
|
||||
}
|
||||
|
||||
// Calculate total build size
|
||||
console.log('\n📊 Build Statistics:');
|
||||
function getDirSize(dirPath) {
|
||||
let size = 0;
|
||||
const files = fs.readdirSync(dirPath);
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dirPath, file);
|
||||
const stats = fs.statSync(filePath);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
size += getDirSize(filePath);
|
||||
} else {
|
||||
size += stats.size;
|
||||
function extractRedirectUri(content) {
|
||||
// Look for the Discord redirect URI in the built bundle
|
||||
const patterns = [
|
||||
/VITE_DISCORD_REDIRECT_URI[\"']?\s*[:=]\s*[\"']([^\"']+)[\"']/,
|
||||
/discord_redirect_uri[\"']?\s*[:=]\s*[\"']([^\"']+)[\"']/i,
|
||||
/redirectUri[\"']?\s*[:=]\s*[\"']([^\"']+oauth\/callback)[\"']/,
|
||||
/(https?:\/\/[^\"'\s]+\/oauth\/callback)/
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = content.match(pattern);
|
||||
if (match && match[1]) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const totalSize = getDirSize(DIST_DIR);
|
||||
console.log(` Total build size: ${(totalSize / 1024 / 1024).toFixed(2)} MB`);
|
||||
|
||||
// Summary
|
||||
console.log('\n' + '='.repeat(50));
|
||||
if (errors.length > 0) {
|
||||
console.log('\n❌ Build verification FAILED\n');
|
||||
errors.forEach(error => console.error(` - ${error}`));
|
||||
process.exit(1);
|
||||
function verifyBuild() {
|
||||
console.log('\n🔍 Build Verification\n');
|
||||
|
||||
// Get target from environment variable
|
||||
const target = process.env.BUILD_TARGET || 'local';
|
||||
|
||||
if (!EXPECTED_URIS[target]) {
|
||||
log('red', '❌', `Invalid BUILD_TARGET: ${target}`);
|
||||
log('blue', 'ℹ', 'Valid targets: docker-local, production');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const expectedUri = EXPECTED_URIS[target];
|
||||
log('blue', 'ℹ', `Deployment target: ${target}`);
|
||||
log('blue', 'ℹ', `Expected redirect URI: ${expectedUri}`);
|
||||
|
||||
try {
|
||||
// Find built assets
|
||||
const assetFiles = findBuiltAssets();
|
||||
log('green', '✅', `Found ${assetFiles.length} built asset(s)`);
|
||||
|
||||
// Search for redirect URI in all assets
|
||||
let foundUri = null;
|
||||
for (const assetFile of assetFiles) {
|
||||
const content = fs.readFileSync(assetFile, 'utf8');
|
||||
const uri = extractRedirectUri(content);
|
||||
|
||||
if (uri) {
|
||||
foundUri = uri;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundUri) {
|
||||
log('yellow', '⚠', 'Could not find Discord redirect URI in bundle');
|
||||
log('blue', 'ℹ', 'This may be OK if OAuth is not used');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
log('blue', 'ℹ', `Found redirect URI: ${foundUri}`);
|
||||
|
||||
// Validate URI matches expected
|
||||
if (foundUri === expectedUri) {
|
||||
log('green', '✅', 'Redirect URI matches expected value!');
|
||||
console.log('\n✅ Build verification passed\n');
|
||||
process.exit(0);
|
||||
} else {
|
||||
log('red', '❌', 'Redirect URI MISMATCH!');
|
||||
log('blue', 'ℹ', `Expected: ${expectedUri}`);
|
||||
log('blue', 'ℹ', `Found: ${foundUri}`);
|
||||
console.log('\n❌ Build verification failed\n');
|
||||
console.log('This usually means the wrong .env.{mode} file was used during build.');
|
||||
console.log('Check that you\'re using the correct Vite mode flag:\n');
|
||||
console.log(' vite build --mode docker-local (for local Docker deployment)');
|
||||
console.log(' vite build --mode production (for Synology deployment)\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
log('red', '❌', `Verification error: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (warnings.length > 0) {
|
||||
console.log('\n⚠️ Build verification passed with warnings:\n');
|
||||
warnings.forEach(warning => console.warn(` - ${warning}`));
|
||||
}
|
||||
|
||||
console.log('\n✅ Build verification PASSED');
|
||||
console.log(' All required files present and valid');
|
||||
console.log(' Ready for deployment\n');
|
||||
// Run verification
|
||||
verifyBuild();
|
||||
|
||||
Reference in New Issue
Block a user