Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / drive-store / scripts / sync.mjs
blob4eb7ea86e9b5a44ecce8becb3d1aeb7a497b6564
1 import { execSync } from 'child_process';
2 import fs from 'fs/promises';
3 import path from 'path';
4 import { dirname } from 'path';
5 import { fileURLToPath } from 'url';
7 const __filename = fileURLToPath(import.meta.url);
8 const __dirname = dirname(__filename);
10 let logLevel = 'info';
12 // Function to copy files from source to target
13 async function copyFiles(source, target) {
14     try {
15         const entries = await fs.readdir(source, { withFileTypes: true });
17         try {
18             await fs.access(target);
19         } catch {
20             console.log(`Target directory does not exist and will be skipped: ${target}`);
21             return;
22         }
24         for (let entry of entries) {
25             const sourcePath = path.join(source, entry.name);
26             const targetPath = path.join(target, entry.name);
28             try {
29                 await fs.access(targetPath);
30                 if (entry.isDirectory()) {
31                     await copyFiles(sourcePath, targetPath);
32                 } else if (entry.isFile()) {
33                     await fs.copyFile(sourcePath, targetPath);
34                     if (logLevel === 'debug') {
35                         console.log(`Copied file from ${sourcePath} to ${targetPath}`);
36                     }
37                 }
38             } catch {
39                 console.log(`Skipping non-existent target: ${targetPath}`);
40             }
41         }
42     } catch (error) {
43         console.error(`Error accessing or copying files from ${source} to ${target}:`, error);
44     }
47 // Function to parse both import and export statements from a file
48 async function parseImportsAndExports(filePath) {
49     try {
50         const content = await fs.readFile(filePath, 'utf8');
51         const importExportRegex = /(import|export)\s+.*?['"](.*?\.\/.*?)['"]/g;
52         let matches;
53         const localPaths = [];
55         // Capture both import and export paths
56         while ((matches = importExportRegex.exec(content)) !== null) {
57             localPaths.push(matches[2]); // Path is in the second capture group
58         }
60         return localPaths;
61     } catch (error) {
62         console.error(`Error reading file: ${filePath}`, error);
63         return [];
64     }
67 // Function to resolve the actual path of an imported or exported file by checking multiple extensions
68 async function resolveImportExportPath(importPath, currentDir) {
69     const possibleExtensions = ['.ts', '.tsx', '/index.ts', '/index.tsx'];
71     for (let ext of possibleExtensions) {
72         const fullPath = path.resolve(currentDir, importPath + ext);
73         try {
74             await fs.access(fullPath); // Check if the file exists
75             return fullPath; // Return the first valid file path
76         } catch {
77             // File does not exist with this extension, try next one
78         }
79     }
81     return null; // Return null if no valid path is found
84 // Ensure target directory exists
85 async function ensureDirectoryExists(targetPath) {
86     try {
87         await fs.mkdir(path.dirname(targetPath), { recursive: true });
88     } catch (error) {
89         console.error(`Error creating directory for path: ${targetPath}`, error);
90     }
93 // Function to copy imported and exported files recursively
94 async function copyImportedExportedFiles(sourceFilePath, sourceDir, targetDir, visited = new Set(), importStack = []) {
95     if (visited.has(sourceFilePath)) return;
96     visited.add(sourceFilePath);
98     // Check for circular imports
99     if (importStack.includes(sourceFilePath)) {
100         console.error(`Circular import detected: ${importStack.join(' -> ')} -> ${sourceFilePath}`);
101         return;
102     }
104     importStack.push(sourceFilePath);
106     const localPaths = await parseImportsAndExports(sourceFilePath);
108     for (let relativeImportPath of localPaths) {
109         const sourceImportPath = await resolveImportExportPath(relativeImportPath, path.dirname(sourceFilePath));
111         if (!sourceImportPath) {
112             console.error(`Failed to resolve imported/exported file for ${relativeImportPath} from ${sourceFilePath}`);
113             continue;
114         }
116         const targetImportPath = path.resolve(targetDir, path.relative(sourceDir, sourceImportPath));
118         try {
119             await ensureDirectoryExists(targetImportPath); // Ensure the target directory exists
120             await fs.copyFile(sourceImportPath, targetImportPath);
121             if (logLevel === 'debug') {
122                 console.log(`Copied imported/exported file from ${sourceImportPath} to ${targetImportPath}`);
123             }
125             // Recursively handle the imported/exported file
126             await copyImportedExportedFiles(sourceImportPath, sourceDir, targetDir, visited, importStack);
127         } catch (error) {
128             console.error(`Failed to copy imported/exported file: ${sourceImportPath}`, error);
129         }
130     }
132     importStack.pop();
135 async function syncDirectories() {
136     try {
137         const configPath = path.join(__dirname, 'sync-config.json');
138         const config = JSON.parse(await fs.readFile(configPath, 'utf8'));
139         const baseDir = path.resolve(config.base);
140         const originalDir = path.resolve(config.original);
141         logLevel = config.logLevel || 'info';
143         for (let dir of config.directories) {
144             const targetPath = path.join(baseDir, dir);
145             const sourcePath = path.join(originalDir, dir);
146             console.log(`Checking sync for ${sourcePath} to ${targetPath}`);
147             await copyFiles(sourcePath, targetPath);
149             // Now process .ts and .tsx files to copy over imports and exports
150             const tsFiles = await fs.readdir(sourcePath);
151             for (let file of tsFiles.filter((f) => f.endsWith('.ts') || f.endsWith('.tsx'))) {
152                 const sourceFilePath = path.join(sourcePath, file);
153                 console.log(`Parsing and syncing imports/exports for ${sourceFilePath}`);
154                 await copyImportedExportedFiles(sourceFilePath, originalDir, baseDir);
155             }
156         }
158         // Apply patches
159         const patchDir = path.resolve(baseDir, 'patches');
160         const entries = await fs.readdir(patchDir, { withFileTypes: true });
162         for (let entry of entries) {
163             if (!entry.isFile()) {
164                 continue;
165             }
167             try {
168                 const patchPath = path.resolve(patchDir, entry.name);
170                 console.log(`Applying patch: ${entry.name}`);
171                 execSync(`git apply ${patchPath}`);
172             } catch (error) {
173                 console.error('Failed to apply patch:', error);
174             }
175         }
176     } catch (error) {
177         console.error('Failed to sync directories:', error);
178     }
181 syncDirectories();