1/* 2 * Copyright (c) 2020 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 16const fs = require('fs'); 17const path = require('path'); 18const md5 = require('md5'); 19 20const { readFile } = require('./lib/utils'); 21const { WORKERS_DIR } = require('./lib/pre_define'); 22 23const { 24 configure, 25 getLogger 26} = require('log4js'); 27 28configure({ 29 appenders: { 'ETS': {type: 'stderr', layout: {type: 'messagePassThrough'}}}, 30 categories: {'default': {appenders: ['ETS'], level: 'info'}} 31}); 32const logger = getLogger('ETS'); 33 34const staticPreviewPage = process.env.aceStaticPreview; 35const aceCompileMode = process.env.aceCompileMode || 'page'; 36const abilityConfig = { 37 abilityType: process.env.abilityType || 'page', 38 abilityEntryFile: null, 39 projectAbilityPath: [], 40 testRunnerFile: [] 41}; 42const projectConfig = {}; 43const resources = { 44 app: {}, 45 sys: {} 46}; 47 48const systemModules = []; 49 50function initProjectConfig(projectConfig) { 51 projectConfig.entryObj = {}; 52 projectConfig.projectPath = projectConfig.projectPath || process.env.aceModuleRoot || 53 path.join(process.cwd(), 'sample'); 54 projectConfig.buildPath = projectConfig.buildPath || process.env.aceModuleBuild || 55 path.resolve(projectConfig.projectPath, 'build'); 56 projectConfig.manifestFilePath = projectConfig.manifestFilePath || process.env.aceManifestPath || 57 path.join(projectConfig.projectPath, 'manifest.json'); 58 projectConfig.aceProfilePath = projectConfig.aceProfilePath || process.env.aceProfilePath; 59 projectConfig.aceModuleJsonPath = projectConfig.aceModuleJsonPath || process.env.aceModuleJsonPath; 60 projectConfig.aceSuperVisualPath = projectConfig.aceSuperVisualPath || 61 process.env.aceSuperVisualPath; 62 projectConfig.hashProjectPath = projectConfig.hashProjectPath || 63 hashProjectPath(projectConfig.projectPath); 64 projectConfig.aceBuildJson = projectConfig.aceBuildJson || process.env.aceBuildJson; 65 projectConfig.cachePath = projectConfig.cachePath || process.env.cachePath || 66 path.resolve(__dirname, 'node_modules/.cache'); 67 projectConfig.aceSoPath = projectConfig.aceSoPath || process.env.aceSoPath; 68 projectConfig.xtsMode = /ets_loader_ark$/.test(__dirname); 69 projectConfig.localPropertiesPath = projectConfig.localPropertiesPath || process.env.localPropertiesPath; 70 projectConfig.projectProfilePath = projectConfig.projectProfilePath || process.env.projectProfilePath; 71 projectConfig.compileMode = projectConfig.compileMode || 'jsbundle'; 72} 73 74function loadEntryObj(projectConfig) { 75 let manifest = {}; 76 initProjectConfig(projectConfig); 77 if (process.env.aceManifestPath && aceCompileMode === 'page') { 78 setEntryFile(projectConfig); 79 setFaTestRunnerFile(projectConfig); 80 } 81 if (process.env.aceModuleJsonPath) { 82 setAbilityPages(projectConfig); 83 setStageTestRunnerFile(projectConfig); 84 } 85 86 if(staticPreviewPage) { 87 projectConfig.entryObj['./' + staticPreviewPage] = projectConfig.projectPath + path.sep + 88 staticPreviewPage + '.ets?entry'; 89 } else if (abilityConfig.abilityType === 'page') { 90 if (fs.existsSync(projectConfig.manifestFilePath)) { 91 const jsonString = fs.readFileSync(projectConfig.manifestFilePath).toString(); 92 manifest = JSON.parse(jsonString); 93 projectConfig.pagesJsonFileName = 'config.json'; 94 } else if (projectConfig.aceModuleJsonPath && fs.existsSync(projectConfig.aceModuleJsonPath)) { 95 process.env.compileMode = 'moduleJson'; 96 buildManifest(manifest, projectConfig.aceModuleJsonPath); 97 } else { 98 throw Error('\u001b[31m ERROR: the manifest file ' + projectConfig.manifestFilePath.replace(/\\/g, '/') + 99 ' or module.json is lost or format is invalid. \u001b[39m').message; 100 } 101 if (manifest.pages) { 102 const pages = manifest.pages; 103 pages.forEach((element) => { 104 const sourcePath = element.replace(/^\.\/ets\//, ''); 105 const fileName = path.resolve(projectConfig.projectPath, sourcePath + '.ets'); 106 if (fs.existsSync(fileName)) { 107 projectConfig.entryObj['./' + sourcePath] = fileName + '?entry'; 108 } else { 109 throw Error(`\u001b[31m ERROR: page '${fileName.replace(/\\/g, '/')}' does not exist. \u001b[39m`) 110 .message; 111 } 112 }); 113 } else { 114 throw Error('\u001b[31m ERROR: missing pages attribute in ' + 115 projectConfig.manifestFilePath.replace(/\\/g, '/') + 116 '. \u001b[39m').message; 117 } 118 } 119} 120 121function buildManifest(manifest, aceConfigPath) { 122 try { 123 const moduleConfigJson = JSON.parse(fs.readFileSync(aceConfigPath).toString()); 124 manifest.type = process.env.abilityType; 125 if (moduleConfigJson.module && moduleConfigJson.module.uiSyntax === 'ets') { 126 manifest.pages = getPages(moduleConfigJson); 127 } else { 128 throw Error('\u001b[31m'+ 129 'ERROR: the config.json file miss key word module || module[abilities].' + 130 '\u001b[39m').message; 131 } 132 } catch (e) { 133 throw Error("\x1B[31m" + 'ERROR: the module.json file is lost or format is invalid.' + 134 "\x1B[39m").message; 135 } 136} 137 138function getPages(configJson) { 139 const pages = [] 140 const pagesJsonFileName = `${configJson.module.pages.replace(/\$profile\:/, '')}.json`; 141 const modulePagePath = path.resolve(projectConfig.aceProfilePath, pagesJsonFileName); 142 if (fs.existsSync(modulePagePath)) { 143 const pagesConfig = JSON.parse(fs.readFileSync(modulePagePath, 'utf-8')); 144 if (pagesConfig && pagesConfig.src) { 145 projectConfig.pagesJsonFileName = pagesJsonFileName; 146 return pagesConfig.src; 147 } 148 } 149 return pages; 150} 151 152function setEntryFile(projectConfig) { 153 const entryFileName = abilityConfig.abilityType === 'page' ? 'app' : abilityConfig.abilityType; 154 const extendFile = entryFileName === 'app' ? '.ets' : '.ts'; 155 const entryFileRealPath = entryFileName + extendFile; 156 const entryFilePath = path.resolve(projectConfig.projectPath, entryFileRealPath); 157 abilityConfig.abilityEntryFile = entryFilePath; 158 if (!fs.existsSync(entryFilePath) && aceCompileMode === 'page') { 159 throw Error(`\u001b[31m ERROR: missing ${entryFilePath.replace(/\\/g, '/')}. \u001b[39m`).message; 160 } 161 projectConfig.entryObj[`./${entryFileName}`] = entryFilePath + '?entry'; 162} 163 164function setAbilityPages(projectConfig) { 165 let abilityPages = []; 166 if (projectConfig.aceModuleJsonPath && fs.existsSync(projectConfig.aceModuleJsonPath)) { 167 const moduleJson = JSON.parse(fs.readFileSync(projectConfig.aceModuleJsonPath).toString()); 168 abilityPages = readAbilityEntrance(moduleJson); 169 setAbilityFile(projectConfig, abilityPages); 170 } 171} 172 173function setFaTestRunnerFile(projectConfig) { 174 const index =projectConfig.projectPath.split(path.sep).join('/').lastIndexOf('\/'); 175 const testRunnerPath = path.resolve(projectConfig.projectPath.substring(0,index + 1), "TestRunner"); 176 if (fs.existsSync(testRunnerPath)) { 177 const testRunnerFiles = []; 178 readFile(testRunnerPath, testRunnerFiles); 179 testRunnerFiles.forEach((item) => { 180 if (/\.(ts|js)$/.test(item)) { 181 const relativePath = path.relative(testRunnerPath, item).replace(/\.(ts|js)$/, ''); 182 projectConfig.entryObj["../TestRunner/" + relativePath] = item; 183 abilityConfig.testRunnerFile.push(item); 184 } 185 }); 186 } 187} 188 189function setStageTestRunnerFile(projectConfig) { 190 const index = projectConfig.projectPath.split(path.sep).join('/').lastIndexOf('\/'); 191 const testRunnerPath = path.resolve(projectConfig.projectPath, "TestRunner"); 192 if (fs.existsSync(testRunnerPath)) { 193 const testRunnerFiles = []; 194 readFile(testRunnerPath, testRunnerFiles); 195 testRunnerFiles.forEach((item) => { 196 if (/\.(ts|js)$/.test(item)) { 197 const relativePath = path.relative(testRunnerPath, item).replace(/\.(ts|js)$/, ''); 198 projectConfig.entryObj["./TestRunner/" + relativePath] = item; 199 abilityConfig.testRunnerFile.push(item); 200 } 201 }); 202 } 203} 204 205function setAbilityFile(projectConfig, abilityPages) { 206 abilityPages.forEach(abilityPath => { 207 const projectAbilityPath = path.resolve(projectConfig.projectPath, '../', abilityPath); 208 const entryPageKey = abilityPath.replace(/^\.\/ets\//, './').replace(/\.ts$/, ''); 209 if (fs.existsSync(projectAbilityPath)) { 210 abilityConfig.projectAbilityPath.push(projectAbilityPath); 211 projectConfig.entryObj[entryPageKey] = projectAbilityPath + '?entry'; 212 } else { 213 throw Error( 214 `\u001b[31m ERROR: srcEntrance file '${projectAbilityPath.replace(/\\/g, '/')}' does not exist. \u001b[39m` 215 ).message; 216 } 217 }); 218} 219 220function readAbilityEntrance(moduleJson) { 221 let abilityPages = []; 222 if (moduleJson.module) { 223 if (moduleJson.module.srcEntrance) { 224 abilityPages.push(moduleJson.module.srcEntrance); 225 } 226 if (moduleJson.module.abilities && moduleJson.module.abilities.length > 0) { 227 setEntrance(moduleJson.module.abilities, abilityPages); 228 } 229 if (moduleJson.module.extensionAbilities && moduleJson.module.extensionAbilities.length > 0) { 230 setEntrance(moduleJson.module.extensionAbilities, abilityPages); 231 } 232 } 233 return abilityPages; 234} 235 236function setEntrance(abilityConfig, abilityPages) { 237 if (abilityConfig && abilityConfig.length > 0) { 238 abilityConfig.forEach(ability => { 239 if (ability.srcEntrance) { 240 abilityPages.push(ability.srcEntrance); 241 } 242 }); 243 } 244} 245 246function loadWorker(projectConfig, workerFileEntry) { 247 if (workerFileEntry) { 248 projectConfig.entryObj = Object.assign(projectConfig.entryObj, workerFileEntry); 249 } else { 250 const workerPath = path.resolve(projectConfig.projectPath, WORKERS_DIR); 251 if (fs.existsSync(workerPath)) { 252 const workerFiles = []; 253 readFile(workerPath, workerFiles); 254 workerFiles.forEach((item) => { 255 if (/\.(ts|js)$/.test(item)) { 256 const relativePath = path.relative(workerPath, item) 257 .replace(/\.(ts|js)$/, '').replace(/\\/g, '/'); 258 projectConfig.entryObj[`./${WORKERS_DIR}/` + relativePath] = item; 259 } 260 }) 261 } 262 } 263} 264 265function readWorkerFile() { 266 const workerFileEntry = {}; 267 if (projectConfig.aceBuildJson && fs.existsSync(projectConfig.aceBuildJson)) { 268 const workerConfig = JSON.parse(fs.readFileSync(projectConfig.aceBuildJson).toString()); 269 if (workerConfig.workers) { 270 workerConfig.workers.forEach(worker => { 271 if (!/\.(ts|js)$/.test(worker)) { 272 worker += '.ts'; 273 } 274 const relativePath = path.relative(projectConfig.projectPath, worker); 275 if (filterWorker(relativePath)) { 276 const workerKey = relativePath.replace(/\.(ts|js)$/, '').replace(/\\/g, '/'); 277 if (workerFileEntry[workerKey]) { 278 throw Error( 279 '\u001b[31m ERROR: The worker file cannot use the same file name: \n' + 280 workerFileEntry[workerKey] + '\n' + worker + '\u001b[39m' 281 ).message; 282 } else { 283 workerFileEntry[workerKey] = worker; 284 } 285 } 286 }); 287 return workerFileEntry; 288 } 289 } 290 return null; 291} 292 293function filterWorker(workerPath) { 294 return /\.(ts|js)$/.test(workerPath); 295} 296 297;(function initSystemResource() { 298 const sysResourcePath = path.resolve('./sysResource.js'); 299 if (fs.existsSync(sysResourcePath)) { 300 resources.sys = require(sysResourcePath).sys; 301 } 302})() 303 304;(function readSystemModules() { 305 const systemModulesPath = path.resolve(__dirname,'../../api'); 306 if (fs.existsSync(systemModulesPath)) { 307 systemModules.push(...fs.readdirSync(systemModulesPath)); 308 } 309})() 310 311function readAppResource(resources, filePath) { 312 if (fs.existsSync(filePath)) { 313 const appResource = fs.readFileSync(filePath, "utf-8"); 314 const resourceArr = appResource.split(/\n/); 315 let resourceMap = new Map(); 316 processResourceArr(resourceArr, resourceMap, filePath); 317 for (let [key, value] of resourceMap) { 318 resources.app[key] = value; 319 } 320 } 321} 322 323function processResourceArr(resourceArr, resourceMap, filePath) { 324 for (let i = 0; i < resourceArr.length; i++) { 325 if (!resourceArr[i].length) { 326 continue; 327 } 328 const resourceData = resourceArr[i].split(/\s/); 329 if (resourceData.length === 3 && !isNaN(Number(resourceData[2])) ) { 330 if (resourceMap.get(resourceData[0])) { 331 const resourceKeys = resourceMap.get(resourceData[0]); 332 if (!resourceKeys[resourceData[1]] || resourceKeys[resourceData[1]] !== Number(resourceData[2])) { 333 resourceKeys[resourceData[1]] = Number(resourceData[2]); 334 } 335 } else { 336 let obj = {}; 337 obj[resourceData[1]] = Number(resourceData[2]); 338 resourceMap.set(resourceData[0], obj); 339 } 340 } else { 341 logger.warn(`\u001b[31m ArkTS:WARN The format of file '${filePath}' is incorrect. \u001b[39m`); 342 break; 343 } 344 } 345} 346 347function hashProjectPath(projectPath) { 348 process.env.hashProjectPath = "_" + md5(projectPath); 349 return process.env.hashProjectPath; 350} 351 352function checkAppResourcePath(appResourcePath, config) { 353 if (appResourcePath) { 354 readAppResource(resources, appResourcePath); 355 if (fs.existsSync(appResourcePath) && config.cache) { 356 config.cache.buildDependencies.config.push(appResourcePath); 357 } 358 if (!projectConfig.xtsMode) { 359 const appResourcePathSavePath = path.resolve(projectConfig.cachePath, 'resource_path.txt'); 360 saveAppResourcePath(appResourcePath, appResourcePathSavePath); 361 if (fs.existsSync(appResourcePathSavePath) && config.cache) { 362 config.cache.buildDependencies.config.push(appResourcePathSavePath); 363 } 364 } 365 } 366} 367 368function saveAppResourcePath(appResourcePath, appResourcePathSavePath) { 369 let isSave = false; 370 if (fs.existsSync(appResourcePathSavePath)) { 371 const saveContent = fs.readFileSync(appResourcePathSavePath); 372 if (appResourcePath !== saveContent) { 373 isSave = true; 374 } 375 } else { 376 isSave = true; 377 } 378 if (isSave) { 379 fs.writeFileSync(appResourcePathSavePath, appResourcePath); 380 } 381} 382 383function addSDKBuildDependencies(config) { 384 if (projectConfig.localPropertiesPath && 385 fs.existsSync(projectConfig.localPropertiesPath) && config.cache) { 386 config.cache.buildDependencies.config.push(projectConfig.localPropertiesPath) 387 } 388 if (projectConfig.projectProfilePath && 389 fs.existsSync(projectConfig.projectProfilePath) && config.cache) { 390 config.cache.buildDependencies.config.push(projectConfig.projectProfilePath) 391 } 392} 393 394const globalProgram = { 395 program: null, 396 watchProgram: null 397}; 398 399exports.globalProgram = globalProgram; 400exports.projectConfig = projectConfig; 401exports.loadEntryObj = loadEntryObj; 402exports.readAppResource = readAppResource; 403exports.resources = resources; 404exports.loadWorker = loadWorker; 405exports.abilityConfig = abilityConfig; 406exports.readWorkerFile = readWorkerFile; 407exports.systemModules = systemModules; 408exports.checkAppResourcePath = checkAppResourcePath; 409exports.addSDKBuildDependencies = addSDKBuildDependencies; 410