You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							299 lines
						
					
					
						
							6.8 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							299 lines
						
					
					
						
							6.8 KiB
						
					
					
				
								#!/usr/bin/env node
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * TimeSafari Build Script
							 | 
						|
								 * 
							 | 
						|
								 * Integrates the Daily Notification Plugin with TimeSafari PWA's build system.
							 | 
						|
								 * Handles platform-specific builds, tree-shaking, and SSR safety validation.
							 | 
						|
								 * 
							 | 
						|
								 * @author Matthew Raymer
							 | 
						|
								 * @version 1.0.0
							 | 
						|
								 */
							 | 
						|
								
							 | 
						|
								const { execSync } = require('child_process');
							 | 
						|
								const fs = require('fs');
							 | 
						|
								const path = require('path');
							 | 
						|
								
							 | 
						|
								// Configuration
							 | 
						|
								const CONFIG = {
							 | 
						|
								  platforms: ['android', 'ios', 'electron'],
							 | 
						|
								  bundleSizeBudget: 35, // KB
							 | 
						|
								  ssrSafe: true,
							 | 
						|
								  treeShaking: true
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Log with timestamp
							 | 
						|
								 */
							 | 
						|
								function log(message, level = 'INFO') {
							 | 
						|
								  const timestamp = new Date().toISOString();
							 | 
						|
								  console.log(`[${timestamp}] [${level}] ${message}`);
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Check if file exists
							 | 
						|
								 */
							 | 
						|
								function fileExists(filePath) {
							 | 
						|
								  return fs.existsSync(filePath);
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Get file size in KB (gzipped)
							 | 
						|
								 */
							 | 
						|
								function getFileSizeKB(filePath) {
							 | 
						|
								  if (!fileExists(filePath)) return 0;
							 | 
						|
								  
							 | 
						|
								  try {
							 | 
						|
								    // Use gzip to get compressed size
							 | 
						|
								    const content = fs.readFileSync(filePath);
							 | 
						|
								    const zlib = require('zlib');
							 | 
						|
								    const gzipped = zlib.gzipSync(content);
							 | 
						|
								    return Math.round(gzipped.length / 1024 * 100) / 100;
							 | 
						|
								  } catch (error) {
							 | 
						|
								    // Fallback to uncompressed size
							 | 
						|
								    const stats = fs.statSync(filePath);
							 | 
						|
								    return Math.round(stats.size / 1024 * 100) / 100;
							 | 
						|
								  }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Validate SSR safety
							 | 
						|
								 */
							 | 
						|
								function validateSSRSafety() {
							 | 
						|
								  log('Validating SSR safety...');
							 | 
						|
								  
							 | 
						|
								  // Only validate core files, not platform-specific implementations
							 | 
						|
								  const coreFiles = [
							 | 
						|
								    'src/index.ts',
							 | 
						|
								    'src/timesafari-integration.ts',
							 | 
						|
								    'src/timesafari-community-integration.ts'
							 | 
						|
								  ];
							 | 
						|
								  
							 | 
						|
								  const ssrUnsafePatterns = [
							 | 
						|
								    /window\./g,
							 | 
						|
								    /document\./g,
							 | 
						|
								    /navigator\./g
							 | 
						|
								  ];
							 | 
						|
								  
							 | 
						|
								  let hasUnsafeCode = false;
							 | 
						|
								  
							 | 
						|
								  coreFiles.forEach(filePath => {
							 | 
						|
								    if (!fileExists(filePath)) return;
							 | 
						|
								    
							 | 
						|
								    const content = fs.readFileSync(filePath, 'utf8');
							 | 
						|
								    
							 | 
						|
								    ssrUnsafePatterns.forEach(pattern => {
							 | 
						|
								      if (pattern.test(content)) {
							 | 
						|
								        log(`SSR-unsafe code found in ${filePath}`, 'WARN');
							 | 
						|
								        hasUnsafeCode = true;
							 | 
						|
								      }
							 | 
						|
								    });
							 | 
						|
								  });
							 | 
						|
								  
							 | 
						|
								  if (hasUnsafeCode) {
							 | 
						|
								    log('SSR safety validation failed for core files', 'ERROR');
							 | 
						|
								    return false;
							 | 
						|
								  }
							 | 
						|
								  
							 | 
						|
								  // Check platform-specific files for proper guards
							 | 
						|
								  const platformFiles = [
							 | 
						|
								    'src/web/index.ts',
							 | 
						|
								    'src/timesafari-storage-adapter.ts'
							 | 
						|
								  ];
							 | 
						|
								  
							 | 
						|
								  platformFiles.forEach(filePath => {
							 | 
						|
								    if (!fileExists(filePath)) return;
							 | 
						|
								    
							 | 
						|
								    const content = fs.readFileSync(filePath, 'utf8');
							 | 
						|
								    
							 | 
						|
								    // Check if file has proper platform guards
							 | 
						|
								    const hasPlatformGuards = content.includes('typeof window') || 
							 | 
						|
								                             content.includes('typeof document') ||
							 | 
						|
								                             content.includes('platform check');
							 | 
						|
								    
							 | 
						|
								    if (!hasPlatformGuards && (content.includes('window.') || content.includes('document.'))) {
							 | 
						|
								      log(`Platform-specific file ${filePath} should have platform guards`, 'WARN');
							 | 
						|
								    }
							 | 
						|
								  });
							 | 
						|
								  
							 | 
						|
								  log('SSR safety validation passed');
							 | 
						|
								  return true;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Validate tree-shaking
							 | 
						|
								 */
							 | 
						|
								function validateTreeShaking() {
							 | 
						|
								  log('Validating tree-shaking...');
							 | 
						|
								  
							 | 
						|
								  // Check if sideEffects is set to false in package.json
							 | 
						|
								  const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
							 | 
						|
								  
							 | 
						|
								  if (packageJson.sideEffects !== false) {
							 | 
						|
								    log('sideEffects not set to false in package.json', 'WARN');
							 | 
						|
								    return false;
							 | 
						|
								  }
							 | 
						|
								  
							 | 
						|
								  // Check exports map
							 | 
						|
								  if (!packageJson.exports) {
							 | 
						|
								    log('exports map not defined in package.json', 'WARN');
							 | 
						|
								    return false;
							 | 
						|
								  }
							 | 
						|
								  
							 | 
						|
								  log('Tree-shaking validation passed');
							 | 
						|
								  return true;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Build for specific platform
							 | 
						|
								 */
							 | 
						|
								function buildForPlatform(platform) {
							 | 
						|
								  log(`Building for platform: ${platform}`);
							 | 
						|
								  
							 | 
						|
								  try {
							 | 
						|
								    // Set platform-specific environment variables
							 | 
						|
								    process.env.TIMESAFARI_PLATFORM = platform;
							 | 
						|
								    
							 | 
						|
								    // Run build command
							 | 
						|
								    execSync('npm run build', { stdio: 'inherit' });
							 | 
						|
								    
							 | 
						|
								    // Validate build output
							 | 
						|
								    const expectedFiles = [
							 | 
						|
								      'dist/plugin.js',
							 | 
						|
								      'dist/esm/index.js',
							 | 
						|
								      'dist/esm/index.d.ts'
							 | 
						|
								    ];
							 | 
						|
								    
							 | 
						|
								    expectedFiles.forEach(file => {
							 | 
						|
								      if (!fileExists(file)) {
							 | 
						|
								        throw new Error(`Expected file not found: ${file}`);
							 | 
						|
								      }
							 | 
						|
								    });
							 | 
						|
								    
							 | 
						|
								    log(`Build completed for platform: ${platform}`);
							 | 
						|
								    return true;
							 | 
						|
								    
							 | 
						|
								  } catch (error) {
							 | 
						|
								    log(`Build failed for platform: ${platform} - ${error.message}`, 'ERROR');
							 | 
						|
								    return false;
							 | 
						|
								  }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Validate bundle size
							 | 
						|
								 */
							 | 
						|
								function validateBundleSize() {
							 | 
						|
								  log('Validating bundle size...');
							 | 
						|
								  
							 | 
						|
								  const bundleFiles = [
							 | 
						|
								    'dist/plugin.js',
							 | 
						|
								    'dist/esm/index.js'
							 | 
						|
								  ];
							 | 
						|
								  
							 | 
						|
								  let totalSize = 0;
							 | 
						|
								  
							 | 
						|
								  bundleFiles.forEach(file => {
							 | 
						|
								    const size = getFileSizeKB(file);
							 | 
						|
								    totalSize += size;
							 | 
						|
								    log(`  ${file}: ${size}KB`);
							 | 
						|
								  });
							 | 
						|
								  
							 | 
						|
								  log(`Total bundle size: ${totalSize}KB`);
							 | 
						|
								  
							 | 
						|
								  if (totalSize > CONFIG.bundleSizeBudget) {
							 | 
						|
								    log(`Bundle size (${totalSize}KB) exceeds budget (${CONFIG.bundleSizeBudget}KB)`, 'ERROR');
							 | 
						|
								    return false;
							 | 
						|
								  }
							 | 
						|
								  
							 | 
						|
								  log('Bundle size validation passed');
							 | 
						|
								  return true;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Generate build report
							 | 
						|
								 */
							 | 
						|
								function generateBuildReport() {
							 | 
						|
								  log('Generating build report...');
							 | 
						|
								  
							 | 
						|
								  const report = {
							 | 
						|
								    timestamp: new Date().toISOString(),
							 | 
						|
								    platform: process.env.TIMESAFARI_PLATFORM || 'all',
							 | 
						|
								    bundleSize: {
							 | 
						|
								      plugin: getFileSizeKB('dist/plugin.js'),
							 | 
						|
								      esm: getFileSizeKB('dist/esm/index.js'),
							 | 
						|
								      total: getFileSizeKB('dist/plugin.js') + getFileSizeKB('dist/esm/index.js')
							 | 
						|
								    },
							 | 
						|
								    validation: {
							 | 
						|
								      ssrSafe: CONFIG.ssrSafe,
							 | 
						|
								      treeShaking: CONFIG.treeShaking,
							 | 
						|
								      bundleSizeBudget: CONFIG.bundleSizeBudget
							 | 
						|
								    },
							 | 
						|
								    files: {
							 | 
						|
								      plugin: fileExists('dist/plugin.js'),
							 | 
						|
								      esm: fileExists('dist/esm/index.js'),
							 | 
						|
								      types: fileExists('dist/esm/index.d.ts')
							 | 
						|
								    }
							 | 
						|
								  };
							 | 
						|
								  
							 | 
						|
								  // Write report to file
							 | 
						|
								  fs.writeFileSync('dist/build-report.json', JSON.stringify(report, null, 2));
							 | 
						|
								  
							 | 
						|
								  log('Build report generated: dist/build-report.json');
							 | 
						|
								  return report;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Main build function
							 | 
						|
								 */
							 | 
						|
								function main() {
							 | 
						|
								  log('Starting TimeSafari build process...');
							 | 
						|
								  
							 | 
						|
								  // Validate configuration
							 | 
						|
								  if (!validateSSRSafety()) {
							 | 
						|
								    process.exit(1);
							 | 
						|
								  }
							 | 
						|
								  
							 | 
						|
								  if (!validateTreeShaking()) {
							 | 
						|
								    process.exit(1);
							 | 
						|
								  }
							 | 
						|
								  
							 | 
						|
								  // Build for all platforms
							 | 
						|
								  const platform = process.env.TIMESAFARI_PLATFORM || 'all';
							 | 
						|
								  
							 | 
						|
								  if (platform === 'all') {
							 | 
						|
								    CONFIG.platforms.forEach(p => {
							 | 
						|
								      if (!buildForPlatform(p)) {
							 | 
						|
								        process.exit(1);
							 | 
						|
								      }
							 | 
						|
								    });
							 | 
						|
								  } else {
							 | 
						|
								    if (!buildForPlatform(platform)) {
							 | 
						|
								      process.exit(1);
							 | 
						|
								    }
							 | 
						|
								  }
							 | 
						|
								  
							 | 
						|
								  // Validate bundle size
							 | 
						|
								  if (!validateBundleSize()) {
							 | 
						|
								    process.exit(1);
							 | 
						|
								  }
							 | 
						|
								  
							 | 
						|
								  // Generate build report
							 | 
						|
								  const report = generateBuildReport();
							 | 
						|
								  
							 | 
						|
								  log('TimeSafari build process completed successfully');
							 | 
						|
								  log(`Bundle size: ${report.bundleSize.total}KB`);
							 | 
						|
								  log(`Platform: ${report.platform}`);
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Run if called directly
							 | 
						|
								if (require.main === module) {
							 | 
						|
								  main();
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								module.exports = {
							 | 
						|
								  buildForPlatform,
							 | 
						|
								  validateSSRSafety,
							 | 
						|
								  validateTreeShaking,
							 | 
						|
								  validateBundleSize,
							 | 
						|
								  generateBuildReport
							 | 
						|
								};
							 | 
						|
								
							 |