import { execSync } from 'child_process'; import os from 'os'; import fs from 'fs'; import * as path from 'path'; import Progress from '../util/Progress'; import Analyzer from './Analyzer'; type Z8ClassProperties = { static: boolean, type: string, key: string, value: string, inherited: boolean, dynamic: boolean, overridden: boolean, nearestParent?: string, nearestParentRoot?: string }; type ClassMapEntity = { name: string, root: string, filePath: string, shortName?: string | null, extends: string | null, children: string[], parentsBranch: string[], mixins: string[], mixedIn: string[], statics: Z8ClassProperties[], properties: Z8ClassProperties[], dynamicProperties: Z8ClassProperties[], isPackage: false }; type ClassMap = { [key: string]: ClassMapEntity }; type PackageEntity = { name: string, root: string, isPackage: true }; class Sources { private static repoNames: string[] = []; private static repoUrls: string[] = []; private static repoSourcePaths: string[] = []; private static repoSourceCopyPaths: string[] = []; private static repoSparseCheckoutPaths: string[] = []; private static repoCollectFromPaths: string[] = []; public static sourcesPath: string = `${os.homedir()}/.doczilla_js_docs`; public static sourcesCopyPath = `${Sources.sourcesPath}/JsSources`; public static dzAppPath = `${Sources.sourcesCopyPath}/DZApp.js`; public static classMapPath = `${Sources.sourcesCopyPath}/ClassMap.json`; public static ClassMap: ClassMap = {}; public static Z8Locales: { [key: string]: { [key: string]: string } } = {}; private static init(): void { // refresh Sources.sourcesPath = ''; Sources.repoNames = []; Sources.repoUrls = []; Sources.repoSourcePaths = []; Sources.repoSourceCopyPaths = []; Sources.repoSparseCheckoutPaths = []; Sources.repoCollectFromPaths = []; const SourcesConfig = JSON.parse(fs.readFileSync(path.resolve(__dirname,'../config.json'), { encoding: 'utf-8' }).toString()).sources; Sources.sourcesPath = `${os.homedir()}/${SourcesConfig.assetsDir}`; for(const repoInfo of SourcesConfig.repos) { Sources.repoNames.push(repoInfo.name); Sources.repoUrls.push(repoInfo.url); Sources.repoSourcePaths.push(`${Sources.sourcesPath}/${repoInfo.name}`); Sources.repoSourceCopyPaths.push(`${Sources.sourcesCopyPath}/${repoInfo.name}`); Sources.repoSparseCheckoutPaths.push(repoInfo.sparseCheckout); Sources.repoCollectFromPaths.push(repoInfo.collectFrom); } } public static get(callback?: () => any): ClassMap { if(fs.existsSync(Sources.classMapPath)) { Sources.ClassMap = JSON.parse(fs.readFileSync(Sources.classMapPath).toString()); Sources.repoNames = JSON.parse(fs.readFileSync(path.resolve(__dirname,'../config.json'), { encoding: 'utf-8' }).toString()).sources.repos.map((repo: { name: string; }) => repo.name); Analyzer.analyze(null, callback, true); } else { Sources.update(callback); } return Sources.ClassMap; } public static getShortenedClassMap(): { [key: string]: { shortName: string | null, filePath: string, name: string } } { const classList: { [key: string]: { shortName: string | null, filePath: string, name: string } } = {}; Object.keys(Sources.ClassMap).map((cls) => { const scls = Sources.ClassMap[cls]; classList[cls] = { name: cls, shortName: scls.shortName || null, filePath: scls.filePath }; }); return classList; } public static getRepoNames(): string[] { return Sources.repoNames; } public static time(): Date { return fs.statSync(Sources.classMapPath).birthtime; } public static findClass(className: string): ClassMapEntity | null { if(Sources.ClassMap[className]) return Sources.ClassMap[className]; const keys = Object.keys(Sources.ClassMap).filter((key) => { return Sources.ClassMap[key].shortName === className; }); if(keys.length === 1) return Sources.ClassMap[keys[0]]; return null; } public static findPackage(root: string, packageName: string): PackageEntity | null { const regex = /^(?!.*\.\.)(?!\.)[a-zA-Zа-яА-Я0-9.]+(? { return key.startsWith(packageName) && key !== packageName && Sources.ClassMap[key].root === root; }); if(keys.length > 0) return { name: packageName, root: root, isPackage: true }; return null; } public static update(callback?: () => any, silent?: boolean): void { Sources.init(); Sources.clone(silent); Sources.copy(silent); Sources.collect(silent); Analyzer.analyze(Sources.ClassMap, callback, silent); } private static clone(silent?: boolean): void { if(!fs.existsSync(Sources.sourcesPath)) fs.mkdirSync(Sources.sourcesPath); const progress = silent ? null : Progress.start('Sources update, Step 1 [Clone/Pull]:', Sources.repoNames.length); for(let i = 0; i < Sources.repoNames.length; i++) { if(fs.existsSync(Sources.repoSourcePaths[i])) { execSync('git pull', { encoding: 'utf8', cwd: Sources.repoSourcePaths[i], stdio: [] }); progress && progress.next(); } else { execSync(Sources.getCloneCommand(Sources.repoUrls[i]), { encoding: 'utf8', cwd: Sources.sourcesPath, stdio: [] }); execSync(`git sparse-checkout set ${Sources.repoSparseCheckoutPaths[i]}`, { encoding: 'utf8', cwd: Sources.repoSourcePaths[i], stdio: [] }); progress && progress.next(); } } progress && progress.finish(); } private static copy(silent?: boolean): void { if(fs.existsSync(Sources.sourcesCopyPath)) fs.rmSync(Sources.sourcesCopyPath, { recursive: true, force: true }); fs.mkdirSync(Sources.sourcesCopyPath); const progress = silent ? null : Progress.start('Sources update, Step 2 [Copy]:', Sources.repoNames.length); for(let i = 0; i < Sources.repoNames.length; i++) { fs.mkdirSync(Sources.repoSourceCopyPaths[i]); fs.cpSync(`${Sources.repoSourcePaths[i]}/${Sources.repoCollectFromPaths[i]}`, Sources.repoSourceCopyPaths[i], { recursive: true }); progress && progress.next(); } progress && progress.finish(); } private static collect(silent?: boolean): void { if(fs.existsSync(Sources.dzAppPath)) fs.rmSync(Sources.dzAppPath, { force: true }); Sources.ClassMap = {}; const buildorders = []; let totalBuildordersLength = 0; for(let i = 0; i < Sources.repoNames.length; i++) { const bo = fs.readFileSync(`${Sources.repoSourceCopyPaths[i]}/.buildorder`, 'utf8').toString().replaceAll('\r', '').split('\n').filter((str) => { return str.length > 0; }); buildorders.push(bo); totalBuildordersLength += bo.length; } const progress = silent ? null : Progress.start('Sources update, Step 3 [Collect]:', totalBuildordersLength); for(let i = 0; i < Sources.repoNames.length; i++) { Sources.processBuildorder(buildorders[i], Sources.repoSourceCopyPaths[i], Sources.repoNames[i], progress); } progress && progress.finish(); } private static processBuildorder(buildorder: string[], sourcesRoot: string, pathPrefix: string, progress: Progress | null): void { const z8DefinePattern: RegExp = /Z8\.define\('([^']+)'/g; const dynamicPropertiesPattern: RegExp = /this\.([\wа-яА-Я_$]+)\s*=;/g; const dynamicConfigPropertiesPattern: RegExp = /this\.([\wа-яА-Я_$]+)/g; for(const filePath of buildorder) { const content = fs.readFileSync(`${sourcesRoot}/${filePath}`, 'utf8').toString(); const defines: string[] = Array.from(content.matchAll(z8DefinePattern), match => match[1]); for(const define of defines) { const classRx = new RegExp(`Z8\\.define\\(\'${define}\',\\s*\\{(?:.|[\r\n])+?^\\}\\);?`, 'gm'); const classContentRxArray = content.match(classRx); // change to `classContent = content.match(classRx)![0]` after `}\n);` issue fixed const classContent = classContentRxArray ? classContentRxArray[0] : ''; const dynamicProperties = Sources.cleanDuplicates(Array.from(classContent.matchAll(dynamicPropertiesPattern), (match) => { return { static: false, type: 'undefined', key: match[1], value: '', inherited: false, dynamic: true, overridden: false }; })); const dynamicConfigProperties = Sources.cleanDuplicates(Array.from(classContent.matchAll(dynamicConfigPropertiesPattern), (match) => { return { static: false, type: 'undefined', key: match[1], value: '', inherited: false, dynamic: true, overridden: false }; }).filter(item1 => !dynamicProperties.some(item2 => item2.key === item1.key))); Sources.ClassMap[define] = { name: define, root: pathPrefix, filePath: `${pathPrefix}/${filePath}`, children: [], extends: null, parentsBranch: [], mixins: [], mixedIn: [], properties: dynamicProperties, dynamicProperties: dynamicConfigProperties, statics: [], isPackage: false }; } fs.writeFileSync(Sources.dzAppPath, content + '\n', { flag: 'a+', encoding: 'utf8' }); progress && progress.next(); } } private static getCloneCommand(repoUrl: string): string { return `git clone --depth=1 --single-branch --branch dev --filter=blob:none --sparse ${repoUrl}`; } private static cleanDuplicates(array: Z8ClassProperties[]): Z8ClassProperties[] { for(let i = array.length - 1; i >= 0; i--) { const currentItem = array[i]; const duplicateIndex = array.findIndex((item, index) => index !== i && item.key === currentItem.key); if (duplicateIndex !== -1) { array.splice(i, 1); } } return array; } } export { Sources, ClassMap, ClassMapEntity, PackageEntity, Z8ClassProperties };