1/* 2 * Copyright (c) 2021 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16var path = require('path') 17var fs = require('fs') 18var shell = require('shelljs'); 19const crypto = require("crypto") 20 21const red = '\u001b[31m'; 22const reset = '\u001b[39m'; 23const multiResourceBuild = {}; 24const systemModules = []; 25 26;(function readSystemModules() { 27 const systemModulesPath = path.resolve(__dirname,'../../api'); 28 if (fs.existsSync(systemModulesPath)) { 29 systemModules.push(...fs.readdirSync(systemModulesPath)); 30 } 31})(); 32 33/** 34 * Delete files. 35 * @param {string} url The path of the file you want to delete. 36 */ 37function deleteFolderRecursive(url) { 38 let files = []; 39 if (fs.existsSync(url)) { 40 files = fs.readdirSync(url); 41 files.forEach(function(file) { 42 const curPath = path.join(url, file); 43 if (fs.statSync(curPath).isDirectory()) { 44 deleteFolderRecursive(curPath); 45 } else { 46 fs.unlinkSync(curPath); 47 } 48 }); 49 fs.rmdir(url, function(err) {}); 50 } 51} 52 53/** 54 * Read manifest file for configuration information. 55 * @param {string} manifestFilePath Path to configuration file. 56 * @returns 57 */ 58function readManifest(manifestFilePath) { 59 let manifest = {}; 60 try { 61 if (fs.existsSync(manifestFilePath)) { 62 const jsonString = fs.readFileSync(manifestFilePath).toString(); 63 manifest = JSON.parse(jsonString); 64 } 65 } catch (e) { 66 throw Error('\u001b[31m' + 'ERROR: the manifest.json file format is invalid.' + 67 '\u001b[39m').message; 68 } 69 return manifest; 70} 71 72/** 73 * Load pages from manifest infomation. 74 * @param {string} projectPath Current compiled file. 75 * @param {string} device_level Current device version. 76 * @param {string} abilityType Current Ability type. 77 * @param {string} manifestFilePath Path to configuration file. 78 * @returns {object} result of compiling the configuration infomation. 79 */ 80function loadEntryObj(projectPath, device_level, abilityType, manifestFilePath) { 81 let entryObj = {}; 82 switch (abilityType) { 83 case 'page': 84 const appJSPath = path.resolve(projectPath, 'app.js'); 85 if (device_level === 'card') { 86 entryObj = addPageEntryObj(readManifest(manifestFilePath), projectPath); 87 } else { 88 if (!fs.existsSync(appJSPath)) { 89 throw Error(red + 'ERROR: missing app.js' + reset).message; 90 } 91 entryObj['./app'] = path.resolve(projectPath, './app.js?entry'); 92 } 93 break; 94 case 'form': 95 entryObj = addPageEntryObj(readManifest(manifestFilePath), projectPath); 96 entryObj[`./${abilityType}`] = path.resolve(projectPath, `./${abilityType}.js?entry`); 97 break; 98 case 'testrunner': 99 break; 100 default: 101 entryObj[`./${abilityType}`] = path.resolve(projectPath, `./${abilityType}.js?entry`); 102 break; 103 } 104 return entryObj; 105} 106 107/** 108 * Read papes from manifest infomation. 109 * @param {object} manifest Configuration infomation. 110 * @param {string} projectPath Current compiled file. 111 * @returns {object} Pages in configuration infomation. 112 */ 113function addPageEntryObj(manifest, projectPath) { 114 let entryObj = {}; 115 const pages = manifest.pages; 116 if (pages === undefined) { 117 throw Error('ERROR: missing pages').message; 118 } 119 pages.forEach((element) => { 120 const sourcePath = element; 121 const hmlPath = path.join(projectPath, sourcePath + '.hml'); 122 const aceSuperVisualPath = process.env.aceSuperVisualPath || ''; 123 const visualPath = path.join(aceSuperVisualPath, sourcePath + '.visual'); 124 const isHml = fs.existsSync(hmlPath); 125 const isVisual = fs.existsSync(visualPath); 126 if (isHml && isVisual) { 127 throw Error(red + 'ERROR: ' + sourcePath + ' cannot both have hml && visual').message; 128 } else if (isHml) { 129 entryObj['./' + element] = path.resolve(projectPath, './' + sourcePath + '.hml?entry'); 130 } else if (isVisual) { 131 entryObj['./' + element] = path.resolve(aceSuperVisualPath, './' + sourcePath + 132 '.visual?entry'); 133 } 134 }) 135 return entryObj; 136} 137 138/** 139 * Match card mode. 140 * @param {object} env Collection of environmental variables. 141 */ 142 143function compileCardModule(env) { 144 if (process.env.aceModuleJsonPath && fs.existsSync(process.env.aceModuleJsonPath)) { 145 const moduleJsonConfig = JSON.parse(fs.readFileSync(process.env.aceModuleJsonPath).toString()); 146 if (moduleJsonConfig.module && 147 (moduleJsonConfig.module.uiSyntax === 'ets' || moduleJsonConfig.module.language === 'ets')) { 148 process.env.DEVICE_LEVEL = 'card'; 149 } else if (validateCardModule(moduleJsonConfig) && !process.env.compileCardModule) { 150 process.env.compileCardModule = true; 151 const cmd = `webpack --config webpack.rich.config.js --env compilerType=${env.compilerType} ` + 152 `DEVICE_LEVEL=card aceModuleRoot=${process.env.projectPath} ` + 153 `aceModuleJsonPath=${process.env.aceModuleJsonPath} aceProfilePath=${process.env.aceProfilePath} ` + 154 `watchMode=${process.env.watchMode} cachePath=${process.env.cachePath} ` + 155 `aceModuleBuild=${process.env.buildPath}`; 156 shell.exec(cmd, (err) => { 157 if (err) { 158 throw Error(err).message; 159 } 160 }) 161 } 162 } 163} 164 165/** 166 * Determine whether the current compilation is in card mode. 167 * @param {object} moduleJsonConfig Configration for module. 168 * @returns 169 */ 170function validateCardModule(moduleJsonConfig) { 171 if (moduleJsonConfig.module && moduleJsonConfig.module.extensionAbilities) { 172 for (let i = 0; i < moduleJsonConfig.module.extensionAbilities.length; i++) { 173 const extensionAbility = moduleJsonConfig.module.extensionAbilities[i]; 174 if (extensionAbility.type && extensionAbility.type === 'form') { 175 return true; 176 } 177 } 178 } 179 return false; 180} 181 182/** 183 * Hash for projectPath. 184 * @param {string} projectPath Current compiled file. 185 */ 186function hashProjectPath(projectPath) { 187 const hash = crypto.createHash('sha256') 188 hash.update(projectPath.toString()) 189 process.env.hashProjectPath = "_" + hash.digest('hex'); 190} 191 192/** 193 * Check if the current compilation requires multiple resource. 194 * @param {string} aceBuildJson Current compilation result path folder. 195 */ 196function checkMultiResourceBuild(aceBuildJson) { 197 if (aceBuildJson && fs.existsSync(aceBuildJson)) { 198 try { 199 const content = JSON.parse(fs.readFileSync(aceBuildJson)); 200 if (content["multiResourceBuild"]) { 201 multiResourceBuild.value = content["multiResourceBuild"] 202 } 203 if (content["projectRootPath"]) { 204 process.env.projectRootPath = content["projectRootPath"] 205 } 206 } catch (error) { 207 } 208 } 209} 210 211/** 212 * Read worker configuration information. 213 * @returns {object || null} Worker enttry pages. 214 */ 215function readWorkerFile() { 216 const workerEntryObj = {}; 217 if (process.env.aceBuildJson && fs.existsSync(process.env.aceBuildJson)) { 218 const workerConfig = JSON.parse(fs.readFileSync(process.env.aceBuildJson).toString()); 219 if(workerConfig.workers) { 220 workerConfig.workers.forEach(worker => { 221 if (!/\.(js)$/.test(worker)) { 222 worker += '.js'; 223 } 224 const relativePath = path.relative(process.env.projectPath, worker); 225 if (filterWorker(relativePath)) { 226 workerEntryObj[relativePath.replace(/\.(ts|js)$/,'').replace(/\\/g, '/')] = worker; 227 } 228 }) 229 return workerEntryObj; 230 } 231 } 232 return null; 233} 234 235/** 236 * Regular for worker. 237 * @param {string} workerPath Path of worker. 238 * @returns {boolean} is pages. 239 */ 240function filterWorker(workerPath) { 241 return /\.(ts|js)$/.test(workerPath); 242} 243 244/** 245 * Determine cache and decide whether to delete it. 246 * @param {string} cachePath Current compilation cache directory. 247 * @returns 248 */ 249function compareCache(cachePath) { 250 const entryFile = path.join(cachePath, 'entry.json'); 251 const cssFile = process.env.watchCSSFiles; 252 253 let files = []; 254 let cssObject = {}; 255 if (fs.existsSync(cssFile)) { 256 cssObject = JSON.parse(fs.readFileSync(cssFile)); 257 if (cssObject['clear'] === true) { 258 deleteFolderRecursive(cachePath); 259 return; 260 } 261 Object.keys(cssObject).forEach(key => { 262 if (key !== 'entry') { 263 files.push(key); 264 } 265 }) 266 } 267 268 if (fs.existsSync(entryFile)) { 269 files = files.concat(JSON.parse(fs.readFileSync(entryFile))); 270 } 271 272 for(let file of files) { 273 if (!fs.existsSync(file)) { 274 deleteFolderRecursive(cachePath); 275 break; 276 } else if (cssObject['atime'] && cssObject['atime'][file]) { 277 if (cssObject['atime'][file] !== fs.statSync(file).atime.toString()) { 278 deleteFolderRecursive(cachePath); 279 break; 280 } 281 } 282 } 283} 284 285/** 286 * Parse ability filePath. 287 * @param {string} abilityType Current Ability type. 288 * @param {string} projectPath Current compiled file. 289 * @returns {string} ability filePath. 290 */ 291function parseAbilityName(abilityType, projectPath) { 292 if (abilityType === 'page') { 293 return path.resolve(__dirname, projectPath, 'app.js'); 294 } else { 295 return path.resolve(__dirname, projectPath, abilityType + '.js'); 296 } 297} 298 299module.exports = { 300 deleteFolderRecursive: deleteFolderRecursive, 301 readManifest: readManifest, 302 loadEntryObj: loadEntryObj, 303 compileCardModule: compileCardModule, 304 hashProjectPath: hashProjectPath, 305 checkMultiResourceBuild: checkMultiResourceBuild, 306 multiResourceBuild: multiResourceBuild, 307 readWorkerFile: readWorkerFile, 308 compareCache: compareCache, 309 systemModules: systemModules, 310 parseAbilityName: parseAbilityName 311};