1/* 2 * Copyright (c) 2023 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 16import path from 'path'; 17import fs from 'fs'; 18import type sourceMap from 'source-map'; 19 20import { minify, MinifyOutput } from 'terser'; 21import { getMapFromJson } from "arkguard" 22 23import { OH_MODULES } from './fast_build/ark_compiler/common/ark_define'; 24import { 25 PACKAGES, 26 TEMPORARY, 27 ZERO, 28 ONE, 29 EXTNAME_JS, 30 EXTNAME_TS, 31 EXTNAME_MJS, 32 EXTNAME_CJS, 33 EXTNAME_ABC, 34 EXTNAME_ETS, 35 EXTNAME_TS_MAP, 36 EXTNAME_JS_MAP, 37 ESMODULE, 38 FAIL, 39 TS2ABC, 40 ES2ABC, 41 EXTNAME_PROTO_BIN, 42 NATIVE_MODULE 43} from './pre_define'; 44import { 45 isMac, 46 isWindows, 47 isPackageModulesFile, 48 genTemporaryPath, 49 getExtensionIfUnfullySpecifiedFilepath, 50 mkdirsSync, 51 toUnixPath, 52 validateFilePathLength, 53 harFilesRecord, 54} from './utils'; 55import type { GeneratedFileInHar } from './utils'; 56import { 57 extendSdkConfigs, 58 projectConfig, 59 sdkConfigPrefix 60} from '../main'; 61import { mangleFilePath } from './fast_build/ark_compiler/common/ob_config_resolver'; 62import { getRealModulePath, type ResolveModuleInfo } from './ets_checker'; 63 64const red: string = '\u001b[31m'; 65const reset: string = '\u001b[39m'; 66 67export const SRC_MAIN: string = 'src/main'; 68 69export var newSourceMaps: Object = {}; 70export var identifierCaches: Object = {}; 71export const packageCollection: Map<string, Array<string>> = new Map(); 72 73export function getOhmUrlByFilepath(filePath: string, projectConfig: Object, logger: Object, namespace?: string): string { 74 // remove '\x00' from the rollup virtual commonjs file's filePath 75 if (filePath.startsWith('\x00')) { 76 filePath = filePath.replace('\x00', ''); 77 } 78 let unixFilePath: string = toUnixPath(filePath); 79 unixFilePath = unixFilePath.substring(0, filePath.lastIndexOf('.')); // remove extension 80 const REG_PROJECT_SRC: RegExp = /(\S+)\/src\/(?:main|ohosTest)\/(ets|js|mock)\/(\S+)/; 81 82 const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath); 83 const bundleName: string = packageInfo[0]; 84 const moduleName: string = packageInfo[1]; 85 const moduleRootPath: string = toUnixPath(projectConfig.modulePathMap[moduleName]); 86 const projectRootPath: string = toUnixPath(projectConfig.projectRootPath); 87 // case1: /entry/src/main/ets/xxx/yyy ---> @bundle:<bundleName>/entry/ets/xxx/yyy 88 // case2: /entry/src/ohosTest/ets/xxx/yyy ---> @bundle:<bundleName>/entry_test@entry/ets/xxx/yyy 89 // case3: /node_modules/xxx/yyy ---> @package:pkg_modules/xxx/yyy 90 // case4: /entry/node_modules/xxx/yyy ---> @package:pkg_modules@entry/xxx/yyy 91 // case5: /library/node_modules/xxx/yyy ---> @package:pkg_modules@library/xxx/yyy 92 // case6: /library/index.ts ---> @bundle:<bundleName>/library/index 93 const projectFilePath: string = unixFilePath.replace(projectRootPath, ''); 94 const packageDir: string = projectConfig.packageDir; 95 const result: RegExpMatchArray | null = projectFilePath.match(REG_PROJECT_SRC); 96 if (result && result[1].indexOf(packageDir) === -1) { 97 const relativePath = processSrcMain(result, projectFilePath); 98 if (namespace && moduleName !== namespace) { 99 return `${bundleName}/${moduleName}@${namespace}/${relativePath}`; 100 } 101 return `${bundleName}/${moduleName}/${relativePath}`; 102 } 103 104 const processParams: Object = { 105 projectFilePath, 106 unixFilePath, 107 packageDir, 108 projectRootPath, 109 moduleRootPath, 110 projectConfig, 111 namespace, 112 logger, 113 originalFilePath: filePath 114 }; 115 return processPackageDir(processParams); 116} 117 118function processSrcMain(result: RegExpMatchArray | null, projectFilePath: string): string { 119 let langType: string = result[2]; 120 let relativePath: string = result[3]; 121 // case7: /entry/src/main/ets/xxx/src/main/js/yyy ---> @bundle:<bundleName>/entry/ets/xxx/src/main/js/yyy 122 const REG_SRC_MAIN: RegExp = /src\/(?:main|ohosTest)\/(ets|js)\//; 123 const srcMainIndex: number = result[1].search(REG_SRC_MAIN); 124 if (srcMainIndex !== -1) { 125 relativePath = projectFilePath.substring(srcMainIndex).replace(REG_SRC_MAIN, ''); 126 langType = projectFilePath.replace(relativePath, '').match(REG_SRC_MAIN)[1]; 127 } 128 return `${langType}/${relativePath}`; 129} 130 131function processPackageDir(params: Object): string { 132 const { 133 projectFilePath, unixFilePath, packageDir, projectRootPath, moduleRootPath, 134 projectConfig, namespace, logger, originalFilePath 135 } = params; 136 if (projectFilePath.indexOf(packageDir) !== -1) { 137 if (process.env.compileTool === 'rollup') { 138 const tryProjectPkg: string = toUnixPath(path.join(projectRootPath, packageDir)); 139 if (unixFilePath.indexOf(tryProjectPkg) !== -1) { 140 return unixFilePath.replace(tryProjectPkg, `${packageDir}`).replace(new RegExp(packageDir, 'g'), PACKAGES); 141 } 142 143 // iterate the modulePathMap to find the module which contains the pkg_module's file 144 for (const moduleName in projectConfig.modulePathMap) { 145 const modulePath: string = projectConfig.modulePathMap[moduleName]; 146 const tryModulePkg: string = toUnixPath(path.resolve(modulePath, packageDir)); 147 if (unixFilePath.indexOf(tryModulePkg) !== -1) { 148 return unixFilePath.replace(tryModulePkg, `${packageDir}@${moduleName}`).replace(new RegExp(packageDir, 'g'), PACKAGES); 149 } 150 } 151 152 logger.error(red, `ArkTS:ERROR Failed to get an resolved OhmUrl by filepath "${originalFilePath}"`, reset); 153 return originalFilePath; 154 } 155 156 // webpack with old implematation 157 const tryProjectPkg: string = toUnixPath(path.join(projectRootPath, packageDir)); 158 if (unixFilePath.indexOf(tryProjectPkg) !== -1) { 159 return unixFilePath.replace(tryProjectPkg, `${packageDir}/${ONE}`).replace(new RegExp(packageDir, 'g'), PACKAGES); 160 } 161 162 const tryModulePkg: string = toUnixPath(path.join(moduleRootPath, packageDir)); 163 if (unixFilePath.indexOf(tryModulePkg) !== -1) { 164 return unixFilePath.replace(tryModulePkg, `${packageDir}/${ZERO}`).replace(new RegExp(packageDir, 'g'), PACKAGES); 165 } 166 } 167 168 const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath); 169 const bundleName: string = packageInfo[0]; 170 const moduleName: string = packageInfo[1]; 171 for (const key in projectConfig.modulePathMap) { 172 const moduleRootPath: string = toUnixPath(projectConfig.modulePathMap[key]); 173 if (unixFilePath.indexOf(moduleRootPath + '/') !== -1) { 174 const relativeModulePath: string = unixFilePath.replace(moduleRootPath + '/', ''); 175 if (namespace && moduleName !== namespace) { 176 return `${bundleName}/${moduleName}@${namespace}/${relativeModulePath}`; 177 } 178 return `${bundleName}/${moduleName}/${relativeModulePath}`; 179 } 180 } 181 182 logger.error(red, `ArkTS:ERROR Failed to get an resolved OhmUrl by filepath "${originalFilePath}"`, reset); 183 return originalFilePath; 184} 185 186 187export function getOhmUrlBySystemApiOrLibRequest(moduleRequest: string) : string 188{ 189 // 'arkui-x' represents cross platform related APIs, processed as 'ohos' 190 const REG_SYSTEM_MODULE: RegExp = new RegExp(`@(${sdkConfigPrefix})\\.(\\S+)`); 191 const REG_LIB_SO: RegExp = /lib(\S+)\.so/; 192 193 if (REG_SYSTEM_MODULE.test(moduleRequest.trim())) { 194 return moduleRequest.replace(REG_SYSTEM_MODULE, (_, moduleType, systemKey) => { 195 let moduleRequestStr = ''; 196 if (extendSdkConfigs) { 197 moduleRequestStr = moduleRequestCallback(moduleRequest, _, moduleType, systemKey); 198 } 199 if (moduleRequestStr !== '') { 200 return moduleRequestStr; 201 } 202 const systemModule: string = `${moduleType}.${systemKey}`; 203 if (NATIVE_MODULE.has(systemModule)) { 204 return `@native:${systemModule}`; 205 } else { 206 return `@ohos:${systemKey}`; 207 }; 208 }); 209 } 210 if (REG_LIB_SO.test(moduleRequest.trim())) { 211 return moduleRequest.replace(REG_LIB_SO, (_, libsoKey) => { 212 return `@app:${projectConfig.bundleName}/${projectConfig.moduleName}/${libsoKey}`; 213 }); 214 } 215 return undefined; 216} 217 218function moduleRequestCallback(moduleRequest, _, moduleType, systemKey): string { 219 for (let config of extendSdkConfigs.values()) { 220 if (config.prefix === '@arkui-x') { 221 continue; 222 } 223 if (moduleRequest.startsWith(config.prefix + '.')) { 224 let compileRequest: string = `${config.prefix}:${systemKey}`; 225 const resolveModuleInfo: ResolveModuleInfo = getRealModulePath(config.apiPath, moduleRequest, ['.d.ts', '.d.ets']); 226 const modulePath: string = resolveModuleInfo.modulePath; 227 if (!fs.existsSync(modulePath)) { 228 return compileRequest; 229 } 230 const bundleInfo: BundleInfo = parseOhmBundle(modulePath); 231 if (checkBundleVersion(bundleInfo.bundleVersion)) { 232 compileRequest = `@bundle:${bundleInfo.bundlePath}`; 233 } 234 return compileRequest; 235 } 236 } 237 return ''; 238} 239 240export function genSourceMapFileName(temporaryFile: string): string { 241 let abcFile: string = temporaryFile; 242 if (temporaryFile.endsWith(EXTNAME_TS)) { 243 abcFile = temporaryFile.replace(/\.ts$/, EXTNAME_TS_MAP); 244 } else { 245 abcFile = temporaryFile.replace(/\.js$/, EXTNAME_JS_MAP); 246 } 247 return abcFile; 248} 249 250export function getBuildModeInLowerCase(projectConfig: Object): string { 251 return (process.env.compileTool === 'rollup' ? projectConfig.buildMode : projectConfig.buildArkMode).toLowerCase(); 252} 253 254export function writeFileSyncByString(sourcePath: string, sourceCode: string, projectConfig: Object, logger: Object): void { 255 const filePath: string = genTemporaryPath(sourcePath, projectConfig.projectPath, process.env.cachePath, projectConfig); 256 if (filePath.length === 0) { 257 return; 258 } 259 mkdirsSync(path.dirname(filePath)); 260 if (/\.js$/.test(sourcePath)) { 261 sourceCode = transformModuleSpecifier(sourcePath, sourceCode, projectConfig); 262 if (projectConfig.buildArkMode === 'debug') { 263 fs.writeFileSync(filePath, sourceCode); 264 return; 265 } 266 writeObfuscatedSourceCode(sourceCode, filePath, logger, projectConfig); 267 } 268 if (/\.json$/.test(sourcePath)) { 269 fs.writeFileSync(filePath, sourceCode); 270 } 271} 272 273export function transformModuleSpecifier(sourcePath: string, sourceCode: string, projectConfig: Object): string { 274 // replace relative moduleSpecifier with ohmURl 275 const REG_RELATIVE_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]((?:\.\/|\.\.\/)[^'"]+|(?:\.\/?|\.\.\/?))['"]/g; 276 const REG_HAR_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]([^\.\/][^'"]+)['"]/g; 277 // replace requireNapi and requireNativeModule with import 278 const REG_REQUIRE_NATIVE_MODULE: RegExp = /var (\S+) = globalThis.requireNativeModule\(['"](\S+)['"]\);/g; 279 const REG_REQUIRE_NAPI_APP: RegExp = /var (\S+) = globalThis.requireNapi\(['"](\S+)['"], true, ['"](\S+)['"]\);/g; 280 const REG_REQUIRE_NAPI_OHOS: RegExp = /var (\S+) = globalThis.requireNapi\(['"](\S+)['"]\);/g; 281 282 return sourceCode.replace(REG_HAR_DEPENDENCY, (item, moduleRequest) => { 283 return replaceHarDependency(item, moduleRequest, projectConfig); 284 }).replace(REG_RELATIVE_DEPENDENCY, (item, moduleRequest) => { 285 return replaceRelativeDependency(item, moduleRequest, toUnixPath(sourcePath), projectConfig); 286 }).replace(REG_REQUIRE_NATIVE_MODULE, (_, moduleRequest, moduleName) => { 287 return `import ${moduleRequest} from '@native:${moduleName}';`; 288 }).replace(REG_REQUIRE_NAPI_APP, (_, moduleRequest, soName, moudlePath) => { 289 return `import ${moduleRequest} from '@app:${moudlePath}/${soName}';`; 290 }).replace(REG_REQUIRE_NAPI_OHOS, (_, moduleRequest, moduleName) => { 291 return `import ${moduleRequest} from '@ohos:${moduleName}';`; 292 }); 293} 294 295export function getOhmUrlByHarName(moduleRequest: string, projectConfig: Object): string | undefined { 296 if (projectConfig.harNameOhmMap) { 297 // case1: "@ohos/lib" ---> "@bundle:bundleName/lib/ets/index" 298 if (projectConfig.harNameOhmMap.hasOwnProperty(moduleRequest)) { 299 return projectConfig.harNameOhmMap[moduleRequest]; 300 } 301 // case2: "@ohos/lib/src/main/ets/pages/page1" ---> "@bundle:bundleName/lib/ets/pages/page1" 302 for (const harName in projectConfig.harNameOhmMap) { 303 if (moduleRequest.startsWith(harName + '/')) { 304 const idx: number = projectConfig.harNameOhmMap[harName].split('/', 2).join('/').length; 305 const harOhmName: string = projectConfig.harNameOhmMap[harName].substring(0, idx); 306 if (moduleRequest.indexOf(harName + '/' + SRC_MAIN) === 0) { 307 return moduleRequest.replace(harName + '/' + SRC_MAIN, harOhmName); 308 } else { 309 return moduleRequest.replace(harName, harOhmName); 310 } 311 } 312 } 313 } 314 return undefined; 315} 316 317function replaceHarDependency(item:string, moduleRequest: string, projectConfig: Object): string { 318 const harOhmUrl: string | undefined = getOhmUrlByHarName(moduleRequest, projectConfig); 319 if (harOhmUrl !== undefined) { 320 return item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => { 321 return quotation + harOhmUrl + quotation; 322 }); 323 } 324 return item; 325} 326 327function locateActualFilePathWithModuleRequest(absolutePath: string): string { 328 if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isDirectory()) { 329 return absolutePath 330 } 331 332 const filePath: string = absolutePath + getExtensionIfUnfullySpecifiedFilepath(absolutePath); 333 if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { 334 return absolutePath; 335 } 336 337 return path.join(absolutePath, 'index'); 338} 339 340function replaceRelativeDependency(item:string, moduleRequest: string, sourcePath: string, projectConfig: Object): string { 341 if (sourcePath && projectConfig.compileMode === ESMODULE) { 342 // remove file extension from moduleRequest 343 const SUFFIX_REG: RegExp = /\.(?:[cm]?js|[e]?ts|json)$/; 344 moduleRequest = moduleRequest.replace(SUFFIX_REG, ''); 345 346 // normalize the moduleRequest 347 item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => { 348 let normalizedModuleRequest: string = toUnixPath(path.normalize(moduleRequest)); 349 if (moduleRequest.startsWith("./")) { 350 normalizedModuleRequest = "./" + normalizedModuleRequest; 351 } 352 return quotation + normalizedModuleRequest + quotation; 353 }); 354 355 const filePath: string = 356 locateActualFilePathWithModuleRequest(path.resolve(path.dirname(sourcePath), moduleRequest)); 357 const result: RegExpMatchArray | null = 358 filePath.match(/(\S+)(\/|\\)src(\/|\\)(?:main|ohosTest)(\/|\\)(ets|js)(\/|\\)(\S+)/); 359 if (result && projectConfig.aceModuleJsonPath) { 360 const npmModuleIdx: number = result[1].search(/(\/|\\)node_modules(\/|\\)/); 361 const projectRootPath: string = projectConfig.projectRootPath; 362 if (npmModuleIdx === -1 || npmModuleIdx === projectRootPath.search(/(\/|\\)node_modules(\/|\\)/)) { 363 const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath); 364 const bundleName: string = packageInfo[0]; 365 const moduleName: string = packageInfo[1]; 366 moduleRequest = `@bundle:${bundleName}/${moduleName}/${result[5]}/${toUnixPath(result[7])}`; 367 item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => { 368 return quotation + moduleRequest + quotation; 369 }); 370 } 371 } 372 } 373 return item; 374} 375 376export async function writeObfuscatedSourceCode(content: string, filePath: string, logger: Object, projectConfig: Object, 377 relativeSourceFilePath: string = '', rollupNewSourceMaps: Object = {}, sourcePath?: string): Promise<void> { 378 if (projectConfig.arkObfuscator) { 379 await writeArkguardObfuscatedSourceCode(content, filePath, logger, projectConfig, relativeSourceFilePath, rollupNewSourceMaps, sourcePath); 380 return; 381 } 382 mkdirsSync(path.dirname(filePath)); 383 if (projectConfig.terserConfig) { 384 await writeTerserObfuscatedSourceCode(content, filePath, logger, projectConfig.terserConfig, relativeSourceFilePath, rollupNewSourceMaps); 385 return; 386 } 387 if (process.env.compileTool !== 'rollup') { 388 await writeMinimizedSourceCode(content, filePath, logger, projectConfig.compileHar); 389 return; 390 } 391 392 sourcePath = toUnixPath(sourcePath); 393 let genFileInHar: GeneratedFileInHar = harFilesRecord.get(sourcePath); 394 395 if (!genFileInHar) { 396 genFileInHar = {sourcePath: sourcePath}; 397 } 398 if (!genFileInHar.sourceCachePath) { 399 genFileInHar.sourceCachePath = toUnixPath(filePath); 400 } 401 harFilesRecord.set(sourcePath, genFileInHar); 402 403 fs.writeFileSync(filePath, content); 404} 405 406 407export async function writeArkguardObfuscatedSourceCode(content: string, filePath: string, logger: Object, projectConfig: Object, 408 relativeSourceFilePath: string = '', rollupNewSourceMaps: Object = {}, originalFilePath: string): Promise<void> { 409 const arkObfuscator = projectConfig.arkObfuscator; 410 const isDeclaration = (/\.d\.e?ts$/).test(filePath); 411 let previousStageSourceMap: sourceMap.RawSourceMap | undefined = undefined; 412 if (relativeSourceFilePath.length > 0) { 413 previousStageSourceMap = rollupNewSourceMaps[relativeSourceFilePath]; 414 } 415 416 let historyNameCache: Map<string, string> = undefined; 417 418 if (identifierCaches) { 419 let namecachePath = relativeSourceFilePath; 420 if (isDeclaration) { 421 namecachePath = harFilesRecord.get(originalFilePath).sourceCachePath; 422 } 423 if (identifierCaches[namecachePath]) { 424 historyNameCache = getMapFromJson(identifierCaches[namecachePath]); 425 } 426 } 427 428 let mixedInfo: {content: string, sourceMap?: Object, nameCache?: Object}; 429 try { 430 mixedInfo = await arkObfuscator.obfuscate(content, filePath, previousStageSourceMap, historyNameCache, originalFilePath); 431 } catch { 432 logger.error(red, `ArkTS:ERROR Failed to obfuscate file: ${relativeSourceFilePath}`); 433 } 434 435 if (mixedInfo.sourceMap && !isDeclaration) { 436 mixedInfo.sourceMap.sources = [relativeSourceFilePath]; 437 rollupNewSourceMaps[relativeSourceFilePath] = mixedInfo.sourceMap; 438 } 439 440 if (mixedInfo.nameCache && !isDeclaration) { 441 identifierCaches[relativeSourceFilePath] = mixedInfo.nameCache; 442 } 443 444 tryMangleFileNameAndWriteFile(filePath, mixedInfo.content, projectConfig, originalFilePath); 445} 446 447export function tryMangleFileNameAndWriteFile(filePath: string, content: string, projectConfig: Object, originalFilePath: string): void { 448 originalFilePath = toUnixPath(originalFilePath); 449 let genFileInHar: GeneratedFileInHar = harFilesRecord.get(originalFilePath); 450 if (!genFileInHar) { 451 genFileInHar = {sourcePath: originalFilePath}; 452 harFilesRecord.set(originalFilePath, genFileInHar); 453 } 454 455 if (projectConfig.obfuscationMergedObConfig?.options?.enableFileNameObfuscation) { 456 const mangledFilePath: string = mangleFilePath(filePath); 457 if ((/\.d\.e?ts$/).test(filePath)) { 458 genFileInHar.obfuscatedDeclarationCachePath = mangledFilePath; 459 } else { 460 genFileInHar.obfuscatedSourceCachePath = mangledFilePath; 461 } 462 filePath = mangledFilePath; 463 } else if (!(/\.d\.e?ts$/).test(filePath)) { 464 genFileInHar.sourceCachePath = filePath; 465 } 466 467 mkdirsSync(path.dirname(filePath)); 468 fs.writeFileSync(filePath, content ?? ''); 469} 470 471export async function mangleDeclarationFileName(logger: Object, projectConfig: Object): Promise<void> { 472 for (const [sourcePath, genFilesInHar] of harFilesRecord) { 473 if (genFilesInHar.originalDeclarationCachePath && genFilesInHar.originalDeclarationContent) { 474 let relativeSourceFilePath = toUnixPath(genFilesInHar.originalDeclarationCachePath).replace(toUnixPath(projectConfig.projectRootPath) + '/', ''); 475 await writeObfuscatedSourceCode(genFilesInHar.originalDeclarationContent, genFilesInHar.originalDeclarationCachePath, logger, projectConfig, 476 relativeSourceFilePath, {}, sourcePath); 477 } 478 } 479} 480 481export async function writeTerserObfuscatedSourceCode(content: string, filePath: string, logger: Object, 482 minifyOptions: Object, relativeSourceFilePath: string = '', rollupNewSourceMaps: Object = {}): Promise<void> { 483 let result: MinifyOutput; 484 485 if (relativeSourceFilePath.length > 0) { 486 minifyOptions['sourceMap'] = { 487 content: rollupNewSourceMaps[relativeSourceFilePath], 488 asObject: true 489 }; 490 } 491 492 try { 493 result = await minify(content, minifyOptions); 494 } catch { 495 logger.error(red, `ArkTS:ERROR Failed to obfuscate file: ${relativeSourceFilePath}`); 496 } 497 498 if (result.map) { 499 result.map.sourcesContent && delete result.map.sourcesContent; 500 result.map.sources = [relativeSourceFilePath]; 501 rollupNewSourceMaps[relativeSourceFilePath] = result.map; 502 } 503 504 fs.writeFileSync(filePath, result.code ?? ''); 505} 506 507export async function writeMinimizedSourceCode(content: string, filePath: string, logger: Object, 508 isHar: boolean = false): Promise<void> { 509 let result: MinifyOutput; 510 try { 511 const minifyOptions = { 512 compress: { 513 join_vars: false, 514 sequences: 0, 515 directives: false 516 } 517 }; 518 if (!isHar) { 519 minifyOptions['format'] = { 520 semicolons: false, 521 beautify: true, 522 indent_level: 2 523 }; 524 } 525 result = await minify(content, minifyOptions); 526 } catch { 527 logger.error(red, `ArkTS:ERROR Failed to source code obfuscation.`, reset); 528 } 529 530 fs.writeFileSync(filePath, result.code); 531} 532 533export function genBuildPath(filePath: string, projectPath: string, buildPath: string, projectConfig: Object): string { 534 filePath = toUnixPath(filePath); 535 if (filePath.endsWith(EXTNAME_MJS)) { 536 filePath = filePath.replace(/\.mjs$/, EXTNAME_JS); 537 } 538 if (filePath.endsWith(EXTNAME_CJS)) { 539 filePath = filePath.replace(/\.cjs$/, EXTNAME_JS); 540 } 541 projectPath = toUnixPath(projectPath); 542 543 if (isPackageModulesFile(filePath, projectConfig)) { 544 const packageDir: string = projectConfig.packageDir; 545 const fakePkgModulesPath: string = toUnixPath(path.join(projectConfig.projectRootPath, packageDir)); 546 let output: string = ''; 547 if (filePath.indexOf(fakePkgModulesPath) === -1) { 548 const hapPath: string = toUnixPath(projectConfig.projectRootPath); 549 const tempFilePath: string = filePath.replace(hapPath, ''); 550 const sufStr: string = tempFilePath.substring(tempFilePath.indexOf(packageDir) + packageDir.length + 1); 551 output = path.join(projectConfig.nodeModulesPath, ZERO, sufStr); 552 } else { 553 output = filePath.replace(fakePkgModulesPath, path.join(projectConfig.nodeModulesPath, ONE)); 554 } 555 return output; 556 } 557 558 if (filePath.indexOf(projectPath) !== -1) { 559 const sufStr: string = filePath.replace(projectPath, ''); 560 const output: string = path.join(buildPath, sufStr); 561 return output; 562 } 563 564 return ''; 565} 566 567export function getPackageInfo(configFile: string): Array<string> { 568 if (packageCollection.has(configFile)) { 569 return packageCollection.get(configFile); 570 } 571 const data: Object = JSON.parse(fs.readFileSync(configFile).toString()); 572 const bundleName: string = data.app.bundleName; 573 const moduleName: string = data.module.name; 574 packageCollection.set(configFile, [bundleName, moduleName]); 575 return [bundleName, moduleName]; 576} 577 578export function generateSourceFilesToTemporary(sourcePath: string, sourceContent: string, sourceMap: Object, 579 projectConfig: Object, logger: Object): void { 580 let jsFilePath: string = genTemporaryPath(sourcePath, projectConfig.projectPath, process.env.cachePath, projectConfig); 581 if (jsFilePath.length === 0) { 582 return; 583 } 584 if (jsFilePath.endsWith(EXTNAME_ETS)) { 585 jsFilePath = jsFilePath.replace(/\.ets$/, EXTNAME_JS); 586 } else { 587 jsFilePath = jsFilePath.replace(/\.ts$/, EXTNAME_JS); 588 } 589 let sourceMapFile: string = genSourceMapFileName(jsFilePath); 590 if (sourceMapFile.length > 0 && projectConfig.buildArkMode === 'debug') { 591 let source = toUnixPath(sourcePath).replace(toUnixPath(projectConfig.projectRootPath) + '/', ''); 592 // adjust sourceMap info 593 sourceMap.sources = [source]; 594 sourceMap.file = path.basename(sourceMap.file); 595 delete sourceMap.sourcesContent; 596 newSourceMaps[source] = sourceMap; 597 } 598 sourceContent = transformModuleSpecifier(sourcePath, sourceContent, projectConfig); 599 600 mkdirsSync(path.dirname(jsFilePath)); 601 if (projectConfig.buildArkMode === 'debug') { 602 fs.writeFileSync(jsFilePath, sourceContent); 603 return; 604 } 605 606 writeObfuscatedSourceCode(sourceContent, jsFilePath, logger, projectConfig); 607} 608 609export function genAbcFileName(temporaryFile: string): string { 610 let abcFile: string = temporaryFile; 611 if (temporaryFile.endsWith(EXTNAME_TS)) { 612 abcFile = temporaryFile.replace(/\.ts$/, EXTNAME_ABC); 613 } else { 614 abcFile = temporaryFile.replace(/\.js$/, EXTNAME_ABC); 615 } 616 return abcFile; 617} 618 619export function isOhModules(projectConfig: Object): boolean { 620 return projectConfig.packageDir === OH_MODULES; 621} 622 623export function isEs2Abc(projectConfig: Object): boolean { 624 return projectConfig.pandaMode === ES2ABC || projectConfig.pandaMode === "undefined" || 625 projectConfig.pandaMode === undefined; 626} 627 628export function isTs2Abc(projectConfig: Object): boolean { 629 return projectConfig.pandaMode === TS2ABC; 630} 631 632export function genProtoFileName(temporaryFile: string): string { 633 return temporaryFile.replace(/\.(?:[tj]s|json)$/, EXTNAME_PROTO_BIN); 634} 635 636export function genMergeProtoFileName(temporaryFile: string): string { 637 let protoTempPathArr: string[] = temporaryFile.split(TEMPORARY); 638 const sufStr: string = protoTempPathArr[protoTempPathArr.length - 1]; 639 let protoBuildPath: string = path.join(process.env.cachePath, "protos", sufStr); 640 641 return protoBuildPath; 642} 643 644export function removeDuplicateInfo(moduleInfos: Array<any>): Array<any> { 645 const tempModuleInfos: any[] = Array<any>(); 646 moduleInfos.forEach((item) => { 647 let check: boolean = tempModuleInfos.every((newItem) => { 648 return item.tempFilePath !== newItem.tempFilePath; 649 }); 650 if (check) { 651 tempModuleInfos.push(item); 652 } 653 }); 654 moduleInfos = tempModuleInfos; 655 656 return moduleInfos; 657} 658 659export function buildCachePath(tailName: string, projectConfig: Object, logger: Object): string { 660 let pathName: string = process.env.cachePath !== undefined ? 661 path.join(projectConfig.cachePath, tailName) : path.join(projectConfig.aceModuleBuild, tailName); 662 validateFilePathLength(pathName, logger); 663 return pathName; 664} 665 666export function getArkBuildDir(arkDir: string): string { 667 if (isWindows()) { 668 return path.join(arkDir, 'build-win'); 669 } else if (isMac()) { 670 return path.join(arkDir, 'build-mac'); 671 } else { 672 return path.join(arkDir, 'build'); 673 } 674} 675 676export function getBuildBinDir(arkDir: string): string { 677 return path.join(getArkBuildDir(arkDir), 'bin'); 678} 679 680interface BundleInfo { 681 bundlePath: string; 682 bundleVersion: string; 683} 684 685function parseOhmBundle(modulePath: string): BundleInfo { 686 const apiCode: string = fs.readFileSync(modulePath, {encoding: 'utf-8'}); 687 const bundleTags: string[] = apiCode.match(/@bundle.+/g); 688 const bundleInfo: BundleInfo = { 689 bundlePath: '', 690 bundleVersion: '' 691 }; 692 if (bundleTags && bundleTags.length > CONSTANT_STEP_0) { 693 const bundleTag: string = bundleTags[CONSTANT_STEP_0]; 694 const bundleInfos: string[] = bundleTag.split(' '); 695 if (bundleInfos.length === CONSTANT_STEP_3) { 696 bundleInfo.bundlePath = bundleInfos[CONSTANT_STEP_1]; 697 bundleInfo.bundleVersion = bundleInfos[CONSTANT_STEP_2]; 698 } 699 } 700 return bundleInfo; 701} 702 703function checkBundleVersion(bundleVersion: string): boolean { 704 const versionSteps: string[] = bundleVersion.split(/[\.\(\)]/g); 705 // eg: since xx 706 if (versionSteps.length === CONSTANT_STEP_1 && !isNaN(Number(versionSteps[CONSTANT_STEP_0])) && 707 Number(versionSteps[CONSTANT_STEP_0]) > CONSTANT_VERSION_10) { 708 return true; 709 // eg: since x.x.x(xx) 710 } else if (versionSteps.length >= CONSTANT_STEP_3 && !isNaN(Number(versionSteps[CONSTANT_STEP_0])) && 711 !isNaN(Number(versionSteps[CONSTANT_STEP_1]))) { 712 const firstStep: number = Number(versionSteps[CONSTANT_STEP_0]); 713 const secondStep: number = Number(versionSteps[CONSTANT_STEP_1]); 714 if (firstStep > CONSTANT_STEP_4 || firstStep === CONSTANT_STEP_4 && secondStep >= CONSTANT_STEP_1) { 715 return true; 716 } 717 } 718 return false; 719} 720 721export function cleanUpUtilsObjects(): void { 722 newSourceMaps = {}; 723 identifierCaches = {}; 724 packageCollection.clear(); 725} 726 727const CONSTANT_STEP_0: number = 0; 728const CONSTANT_STEP_1: number = 1; 729const CONSTANT_STEP_2: number = 2; 730const CONSTANT_STEP_3: number = 3; 731const CONSTANT_STEP_4: number = 4; 732const CONSTANT_VERSION_10: number = 10; 733 734export function getHookEventFactory(share: Object, pluginName: string, hookName: string): Object { 735 if (typeof share.getHookEventFactory === 'function') { 736 return share.getHookEventFactory(pluginName, hookName); 737 } else { 738 return undefined; 739 } 740} 741 742export function createAndStartEvent(eventOrEventFactory: Object, eventName: string, syncFlag = false): Object { 743 if (eventOrEventFactory === undefined) { 744 return undefined; 745 } 746 let event: Object; 747 if (typeof eventOrEventFactory.createSubEvent === 'function') { 748 event = eventOrEventFactory.createSubEvent(eventName); 749 } else { 750 event = eventOrEventFactory.createEvent(eventName); 751 } 752 if (typeof event.startAsyncEvent === 'function' && syncFlag) { 753 event.startAsyncEvent(); 754 } else { 755 event.start(); 756 } 757 return event; 758} 759 760export function stopEvent(event: Object, syncFlag = false): void { 761 if (event !== undefined) { 762 if (typeof event.stopAsyncEvent === 'function' && syncFlag) { 763 event.stopAsyncEvent(); 764 } else { 765 event.stop(); 766 } 767 } 768} 769