• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, deleteLineInfoForNameString, MemoryUtils, nameCacheMap } from 'arkguard';
22import {
23  OH_MODULES,
24  SEPARATOR_AT,
25  SEPARATOR_BITWISE_AND,
26  SEPARATOR_SLASH
27} from './fast_build/ark_compiler/common/ark_define';
28import {
29  ARKTS_MODULE_NAME,
30  PACKAGES,
31  TEMPORARY,
32  ZERO,
33  ONE,
34  EXTNAME_JS,
35  EXTNAME_TS,
36  EXTNAME_MJS,
37  EXTNAME_CJS,
38  EXTNAME_ABC,
39  EXTNAME_ETS,
40  EXTNAME_TS_MAP,
41  EXTNAME_JS_MAP,
42  ESMODULE,
43  FAIL,
44  TS2ABC,
45  ES2ABC,
46  EXTNAME_PROTO_BIN,
47  NATIVE_MODULE
48} from './pre_define';
49import {
50  isMac,
51  isWindows,
52  isPackageModulesFile,
53  genTemporaryPath,
54  getExtensionIfUnfullySpecifiedFilepath,
55  mkdirsSync,
56  toUnixPath,
57  validateFilePathLength,
58  harFilesRecord,
59  getProjectRootPath
60} from './utils';
61import type { GeneratedFileInHar } from './utils';
62import {
63  extendSdkConfigs,
64  projectConfig,
65  sdkConfigPrefix
66} from '../main';
67import { getRelativeSourcePath, mangleFilePath } from './fast_build/ark_compiler/common/ob_config_resolver';
68import { moduleRequestCallback } from './fast_build/system_api/api_check_utils';
69import { performancePrinter } from 'arkguard/lib/ArkObfuscator';
70import { SourceMapGenerator } from './fast_build/ark_compiler/generate_sourcemap';
71import { sourceFileBelongProject } from './fast_build/ark_compiler/module/module_source_file';
72
73const red: string = '\u001b[31m';
74const reset: string = '\u001b[39m';
75const IDENTIFIER_CACHE: string = 'IdentifierCache';
76
77export const SRC_MAIN: string = 'src/main';
78
79export let newSourceMaps: Object = {};
80
81export const packageCollection: Map<string, Array<string>> = new Map();
82// Splicing ohmurl or record name based on filePath and context information table.
83export function getNormalizedOhmUrlByFilepath(filePath: string, projectConfig: Object, logger: Object,
84  pkgParams: Object, importerFile: string): string {
85  const { pkgName, pkgPath, isRecordName } = pkgParams;
86  // rollup uses commonjs plugin to handle commonjs files,
87  // the commonjs files are prefixed with '\x00' and need to be removed.
88  if (filePath.startsWith('\x00')) {
89    filePath = filePath.replace('\x00', '');
90  }
91  let unixFilePath: string = toUnixPath(filePath);
92  unixFilePath = unixFilePath.substring(0, filePath.lastIndexOf('.')); // remove extension
93  let projectFilePath: string = unixFilePath.replace(toUnixPath(pkgPath), '');
94  // case1: /entry/src/main/ets/xxx/yyy
95  // case2: /entry/src/ohosTest/ets/xxx/yyy
96  // case3: /node_modules/xxx/yyy
97  // case4: /entry/node_modules/xxx/yyy
98  // case5: /library/node_modules/xxx/yyy
99  // case6: /library/index.ts
100  // ---> @normalized:N&<moduleName>&<bunldName>&<packageName>/entry/ets/xxx/yyy&<version>
101  let pkgInfo = projectConfig.pkgContextInfo[pkgName];
102  if (pkgInfo === undefined) {
103    logger.error(red, 'ArkTS:ERROR Failed to resolve OhmUrl.\n' +
104      `Error Message: Failed to get a resolved OhmUrl for "${filePath}" imported by "${importerFile}".\n` +
105      `Solutions: > Check whether the module which ${filePath} belongs to is correctly configured.` +
106      '> Check the corresponding file name is correct(including case-sensitivity).', reset);
107  }
108  let recordName = `${pkgInfo.bundleName}&${pkgName}${projectFilePath}&${pkgInfo.version}`;
109  if (isRecordName) {
110    // record name style: <bunldName>&<packageName>/entry/ets/xxx/yyy&<version>
111    return recordName;
112  }
113  return `${pkgInfo.isSO ? 'Y' : 'N'}&${pkgInfo.moduleName}&${recordName}`;
114}
115
116export function getOhmUrlByFilepath(filePath: string, projectConfig: Object, logger: Object, namespace?: string,
117  importerFile?: string): string {
118  // remove '\x00' from the rollup virtual commonjs file's filePath
119  if (filePath.startsWith('\x00')) {
120    filePath = filePath.replace('\x00', '');
121  }
122  let unixFilePath: string = toUnixPath(filePath);
123  unixFilePath = unixFilePath.substring(0, filePath.lastIndexOf('.')); // remove extension
124  const REG_PROJECT_SRC: RegExp = /(\S+)\/src\/(?:main|ohosTest)\/(ets|js|mock)\/(\S+)/;
125
126  const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath);
127  const bundleName: string = packageInfo[0];
128  const moduleName: string = packageInfo[1];
129  const moduleRootPath: string = toUnixPath(projectConfig.modulePathMap[moduleName]);
130  const projectRootPath: string = toUnixPath(getProjectRootPath(filePath, projectConfig, projectConfig?.rootPathSet));
131  // case1: /entry/src/main/ets/xxx/yyy     ---> @bundle:<bundleName>/entry/ets/xxx/yyy
132  // case2: /entry/src/ohosTest/ets/xxx/yyy ---> @bundle:<bundleName>/entry_test@entry/ets/xxx/yyy
133  // case3: /node_modules/xxx/yyy           ---> @package:pkg_modules/xxx/yyy
134  // case4: /entry/node_modules/xxx/yyy     ---> @package:pkg_modules@entry/xxx/yyy
135  // case5: /library/node_modules/xxx/yyy   ---> @package:pkg_modules@library/xxx/yyy
136  // case6: /library/index.ts               ---> @bundle:<bundleName>/library/index
137  const projectFilePath: string = unixFilePath.replace(projectRootPath, '');
138  const packageDir: string = projectConfig.packageDir;
139  const result: RegExpMatchArray | null = projectFilePath.match(REG_PROJECT_SRC);
140  if (result && result[1].indexOf(packageDir) === -1) {
141    const relativePath = processSrcMain(result, projectFilePath);
142    if (namespace && moduleName !== namespace) {
143      return `${bundleName}/${moduleName}@${namespace}/${relativePath}`;
144    }
145    return `${bundleName}/${moduleName}/${relativePath}`;
146  }
147
148  const processParams: Object = {
149    projectFilePath,
150    unixFilePath,
151    packageDir,
152    projectRootPath,
153    moduleRootPath,
154    projectConfig,
155    namespace,
156    logger,
157    importerFile,
158    originalFilePath: filePath
159  };
160  return processPackageDir(processParams);
161}
162
163function processSrcMain(result: RegExpMatchArray | null, projectFilePath: string): string {
164  let langType: string = result[2];
165  let relativePath: string = result[3];
166  // case7: /entry/src/main/ets/xxx/src/main/js/yyy ---> @bundle:<bundleName>/entry/ets/xxx/src/main/js/yyy
167  const REG_SRC_MAIN: RegExp = /src\/(?:main|ohosTest)\/(ets|js)\//;
168  const srcMainIndex: number = result[1].search(REG_SRC_MAIN);
169  if (srcMainIndex !== -1) {
170    relativePath = projectFilePath.substring(srcMainIndex).replace(REG_SRC_MAIN, '');
171    langType = projectFilePath.replace(relativePath, '').match(REG_SRC_MAIN)[1];
172  }
173  return `${langType}/${relativePath}`;
174}
175
176function processPackageDir(params: Object): string {
177  const { projectFilePath, unixFilePath, packageDir, projectRootPath, moduleRootPath,
178    projectConfig, namespace, logger, importerFile, originalFilePath } = params;
179  if (projectFilePath.indexOf(packageDir) !== -1) {
180    if (compileToolIsRollUp()) {
181      const tryProjectPkg: string = toUnixPath(path.join(projectRootPath, packageDir));
182      if (unixFilePath.indexOf(tryProjectPkg) !== -1) {
183        return unixFilePath.replace(tryProjectPkg, `${packageDir}`).replace(new RegExp(packageDir, 'g'), PACKAGES);
184      }
185
186      // iterate the modulePathMap to find the module which contains the pkg_module's file
187      for (const moduleName in projectConfig.modulePathMap) {
188        const modulePath: string = projectConfig.modulePathMap[moduleName];
189        const tryModulePkg: string = toUnixPath(path.resolve(modulePath, packageDir));
190        if (unixFilePath.indexOf(tryModulePkg) !== -1) {
191          return unixFilePath.replace(tryModulePkg, `${packageDir}@${moduleName}`).replace(new RegExp(packageDir, 'g'), PACKAGES);
192        }
193      }
194
195      logger.error(red, 'ArkTS:ERROR Failed to resolve OhmUrl.\n' +
196        `Error Message: Failed to get a resolved OhmUrl for "${originalFilePath}" imported by "${importerFile}".\n` +
197        `Solutions: > Check whether the module which ${originalFilePath} belongs to is correctly configured.` +
198        '> Check the corresponding file name is correct(including case-sensitivity).', reset);
199      return originalFilePath;
200    }
201
202    // webpack with old implematation
203    const tryProjectPkg: string = toUnixPath(path.join(projectRootPath, packageDir));
204    if (unixFilePath.indexOf(tryProjectPkg) !== -1) {
205      return unixFilePath.replace(tryProjectPkg, `${packageDir}/${ONE}`).replace(new RegExp(packageDir, 'g'), PACKAGES);
206    }
207
208    const tryModulePkg: string = toUnixPath(path.join(moduleRootPath, packageDir));
209    if (unixFilePath.indexOf(tryModulePkg) !== -1) {
210      return unixFilePath.replace(tryModulePkg, `${packageDir}/${ZERO}`).replace(new RegExp(packageDir, 'g'), PACKAGES);
211    }
212  }
213
214  const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath);
215  const bundleName: string = packageInfo[0];
216  const moduleName: string = packageInfo[1];
217  for (const key in projectConfig.modulePathMap) {
218    const moduleRootPath: string = toUnixPath(projectConfig.modulePathMap[key]);
219    if (unixFilePath.indexOf(moduleRootPath + '/') !== -1) {
220      const relativeModulePath: string = unixFilePath.replace(moduleRootPath + '/', '');
221      if (namespace && moduleName !== namespace) {
222        return `${bundleName}/${moduleName}@${namespace}/${relativeModulePath}`;
223      }
224      return `${bundleName}/${moduleName}/${relativeModulePath}`;
225    }
226  }
227
228  logger.error(red, 'ArkTS:ERROR Failed to resolve OhmUrl.\n' +
229    `Error Message: Failed to get a resolved OhmUrl for "${originalFilePath}" imported by "${importerFile}".\n` +
230    `Solutions: > Check whether the module which ${originalFilePath} belongs to is correctly configured.` +
231    '> Check the corresponding file name is correct(including case-sensitivity).', reset);
232  return originalFilePath;
233}
234
235
236export function getOhmUrlBySystemApiOrLibRequest(moduleRequest: string, config?: Object, logger?: Object,
237  importerFile?: string, useNormalizedOHMUrl: boolean = false): string {
238  // 'arkui-x' represents cross platform related APIs, processed as 'ohos'
239  const REG_SYSTEM_MODULE: RegExp = new RegExp(`@(${sdkConfigPrefix})\\.(\\S+)`);
240  const REG_LIB_SO: RegExp = /lib(\S+)\.so/;
241
242  if (REG_SYSTEM_MODULE.test(moduleRequest.trim())) {
243    return moduleRequest.replace(REG_SYSTEM_MODULE, (_, moduleType, systemKey) => {
244      let moduleRequestStr = '';
245      if (extendSdkConfigs) {
246        moduleRequestStr = moduleRequestCallback(moduleRequest, _, moduleType, systemKey);
247      }
248      if (moduleRequestStr !== '') {
249        return moduleRequestStr;
250      }
251      const systemModule: string = `${moduleType}.${systemKey}`;
252      if (NATIVE_MODULE.has(systemModule)) {
253        return `@native:${systemModule}`;
254      } else if (moduleType === ARKTS_MODULE_NAME) {
255        // @arkts.xxx -> @ohos:arkts.xxx
256        return `@ohos:${systemModule}`;
257      } else {
258        return `@ohos:${systemKey}`;
259      };
260    });
261  }
262  if (REG_LIB_SO.test(moduleRequest.trim())) {
263    if (useNormalizedOHMUrl) {
264      const pkgInfo = config.pkgContextInfo[moduleRequest];
265      if (pkgInfo === undefined) {
266        logger?.error(red, `ArkTS:INTERNAL ERROR: Can not get pkgContextInfo of package '${moduleRequest}' ` +
267          `which being imported by '${importerFile}'`, reset);
268      }
269      const isSo = pkgInfo.isSO ? 'Y' : 'N';
270      return `@normalized:${isSo}&${pkgInfo.moduleName}&${pkgInfo.bundleName}&${moduleRequest}&${pkgInfo.version}`;
271    }
272    return moduleRequest.replace(REG_LIB_SO, (_, libsoKey) => {
273      return `@app:${projectConfig.bundleName}/${projectConfig.moduleName}/${libsoKey}`;
274    });
275  }
276  return undefined;
277}
278
279export function genSourceMapFileName(temporaryFile: string): string {
280  let abcFile: string = temporaryFile;
281  if (temporaryFile.endsWith(EXTNAME_TS)) {
282    abcFile = temporaryFile.replace(/\.ts$/, EXTNAME_TS_MAP);
283  } else {
284    abcFile = temporaryFile.replace(/\.js$/, EXTNAME_JS_MAP);
285  }
286  return abcFile;
287}
288
289export function getBuildModeInLowerCase(projectConfig: Object): string {
290  return (compileToolIsRollUp() ? projectConfig.buildMode : projectConfig.buildArkMode).toLowerCase();
291}
292
293/**
294 * This Api only used by webpack compiling process - js-loader
295 * @param sourcePath The path in build cache dir
296 * @param sourceCode The intermediate js source code
297 */
298export function writeFileSyncByString(sourcePath: string, sourceCode: string, projectConfig: Object, logger: Object): void {
299  const filePath: string = genTemporaryPath(sourcePath, projectConfig.projectPath, process.env.cachePath,
300    projectConfig, undefined);
301  if (filePath.length === 0) {
302    return;
303  }
304  mkdirsSync(path.dirname(filePath));
305  if (/\.js$/.test(sourcePath)) {
306    sourceCode = transformModuleSpecifier(sourcePath, sourceCode, projectConfig);
307    if (projectConfig.buildArkMode === 'debug') {
308      fs.writeFileSync(filePath, sourceCode);
309      return;
310    }
311    writeObfuscatedSourceCode({content: sourceCode, buildFilePath: filePath, relativeSourceFilePath: ''},
312      logger, projectConfig);
313  }
314  if (/\.json$/.test(sourcePath)) {
315    fs.writeFileSync(filePath, sourceCode);
316  }
317}
318
319export function transformModuleSpecifier(sourcePath: string, sourceCode: string, projectConfig: Object): string {
320  // replace relative moduleSpecifier with ohmURl
321  const REG_RELATIVE_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]((?:\.\/|\.\.\/)[^'"]+|(?:\.\/?|\.\.\/?))['"]/g;
322  const REG_HAR_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]([^\.\/][^'"]+)['"]/g;
323  // replace requireNapi and requireNativeModule with import
324  const REG_REQUIRE_NATIVE_MODULE: RegExp = /var (\S+) = globalThis.requireNativeModule\(['"](\S+)['"]\);/g;
325  const REG_REQUIRE_NAPI_APP: RegExp = /var (\S+) = globalThis.requireNapi\(['"](\S+)['"], true, ['"](\S+)['"]\);/g;
326  const REG_REQUIRE_NAPI_OHOS: RegExp = /var (\S+) = globalThis.requireNapi\(['"](\S+)['"]\);/g;
327
328  return sourceCode.replace(REG_HAR_DEPENDENCY, (item, moduleRequest) => {
329    return replaceHarDependency(item, moduleRequest, projectConfig);
330  }).replace(REG_RELATIVE_DEPENDENCY, (item, moduleRequest) => {
331    return replaceRelativeDependency(item, moduleRequest, toUnixPath(sourcePath), projectConfig);
332  }).replace(REG_REQUIRE_NATIVE_MODULE, (_, moduleRequest, moduleName) => {
333    return `import ${moduleRequest} from '@native:${moduleName}';`;
334  }).replace(REG_REQUIRE_NAPI_APP, (_, moduleRequest, soName, moudlePath) => {
335    return `import ${moduleRequest} from '@app:${moudlePath}/${soName}';`;
336  }).replace(REG_REQUIRE_NAPI_OHOS, (_, moduleRequest, moduleName) => {
337    return `import ${moduleRequest} from '@ohos:${moduleName}';`;
338  });
339}
340
341function removeSuffix(filePath: string): string {
342  const SUFFIX_REG = /\.(?:d\.)?(ets|ts|mjs|cjs|js)$/;
343  return filePath.split(path.sep).join('/').replace(SUFFIX_REG, '');
344}
345
346export function getNormalizedOhmUrlByAliasName(aliasName: string, projectConfig: Object,
347  logger?: Object, filePath?: string): string {
348  let pkgName: string = aliasName;
349  const aliasPkgNameMap: Map<string, string> = projectConfig.dependencyAliasMap;
350  if (aliasPkgNameMap.has(aliasName)) {
351    pkgName = aliasPkgNameMap.get(aliasName);
352  }
353  const pkgInfo: Object = projectConfig.pkgContextInfo[pkgName];
354  if (!pkgInfo) {
355    logger.error(red, `ArkTS:INTERNAL ERROR: Failed to find package '${pkgName}'.\n` +
356      `Error Message: Failed to obtain package '${pkgName}' from the package context information.`, reset);
357  }
358  let normalizedPath: string = '';
359  if (!filePath) {
360    if (!pkgInfo.entryPath) {
361      logger.error(red, `ArkTS:INTERNAL ERROR: Failed to find entry file of '${pkgName}'.\n` +
362        `Error Message: Failed to obtain the entry file information of '${pkgName}' from the package context information.`, reset);
363    }
364    normalizedPath = `${pkgName}/${toUnixPath(pkgInfo.entryPath)}`;
365    normalizedPath = removeSuffix(normalizedPath);
366  } else {
367    const relativePath = toUnixPath(filePath).replace(aliasName, '');
368    normalizedPath = `${pkgName}${relativePath}`;
369  }
370  const isSo = pkgInfo.isSO ? 'Y' : 'N';
371  return `@normalized:${isSo}&${pkgInfo.moduleName}&${pkgInfo.bundleName}&${normalizedPath}&${pkgInfo.version}`;
372}
373
374export function getOhmUrlByByteCodeHar(moduleRequest: string, projectConfig: Object, logger?: Object):
375  string | undefined {
376  if (projectConfig.byteCodeHarInfo) {
377    if (Object.prototype.hasOwnProperty.call(projectConfig.byteCodeHarInfo, moduleRequest)) {
378      return getNormalizedOhmUrlByAliasName(moduleRequest, projectConfig, logger);
379    }
380    for (const byteCodeHarName in projectConfig.byteCodeHarInfo) {
381      if (moduleRequest.startsWith(byteCodeHarName + '/')) {
382        return getNormalizedOhmUrlByAliasName(byteCodeHarName, projectConfig, logger, moduleRequest);
383      }
384    }
385  }
386  return undefined;
387}
388
389export function getOhmUrlByExternalPackage(moduleRequest: string, projectConfig: Object, logger?: Object,
390  useNormalizedOHMUrl: boolean = false): string | undefined {
391  // The externalPkgMap store the ohmurl with the alias of hsp package and the hars depended on bytecode har.
392  let externalPkgMap: Object = Object.assign({}, projectConfig.hspNameOhmMap, projectConfig.harNameOhmMap);
393  if (Object.keys(externalPkgMap).length !== 0) {
394    if (Object.prototype.hasOwnProperty.call(externalPkgMap, moduleRequest)) {
395      if (useNormalizedOHMUrl) {
396        return getNormalizedOhmUrlByAliasName(moduleRequest, projectConfig, logger);
397      }
398      // case1: "@ohos/lib" ---> "@bundle:bundleName/lib/ets/index"
399      return externalPkgMap[moduleRequest];
400    }
401
402    for (const externalPkgName in externalPkgMap) {
403      if (moduleRequest.startsWith(externalPkgName + '/')) {
404        if (useNormalizedOHMUrl) {
405          return getNormalizedOhmUrlByAliasName(externalPkgName, projectConfig, logger, moduleRequest);
406        }
407        // case2: "@ohos/lib/src/main/ets/pages/page1" ---> "@bundle:bundleName/lib/ets/pages/page1"
408        const idx: number = externalPkgMap[externalPkgName].split('/', 2).join('/').length;
409        const ohmName: string = externalPkgMap[externalPkgName].substring(0, idx);
410        if (moduleRequest.indexOf(externalPkgName + '/' + SRC_MAIN) === 0) {
411          return moduleRequest.replace(externalPkgName + '/' + SRC_MAIN, ohmName);
412        } else {
413          return moduleRequest.replace(externalPkgName, ohmName);
414        }
415      }
416    }
417  }
418  return undefined;
419}
420
421function replaceHarDependency(item: string, moduleRequest: string, projectConfig: Object): string {
422  const hspOhmUrl: string | undefined = getOhmUrlByExternalPackage(moduleRequest, projectConfig);
423  if (hspOhmUrl !== undefined) {
424    return item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => {
425      return quotation + hspOhmUrl + quotation;
426    });
427  }
428  return item;
429}
430
431function locateActualFilePathWithModuleRequest(absolutePath: string): string {
432  if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isDirectory()) {
433    return absolutePath;
434  }
435
436  const filePath: string = absolutePath + getExtensionIfUnfullySpecifiedFilepath(absolutePath);
437  if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
438    return absolutePath;
439  }
440
441  return path.join(absolutePath, 'index');
442}
443
444function replaceRelativeDependency(item: string, moduleRequest: string, sourcePath: string, projectConfig: Object): string {
445  if (sourcePath && projectConfig.compileMode === ESMODULE) {
446    // remove file extension from moduleRequest
447    const SUFFIX_REG: RegExp = /\.(?:[cm]?js|[e]?ts|json)$/;
448    moduleRequest = moduleRequest.replace(SUFFIX_REG, '');
449
450    // normalize the moduleRequest
451    item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => {
452      let normalizedModuleRequest: string = toUnixPath(path.normalize(moduleRequest));
453      if (moduleRequest.startsWith('./')) {
454        normalizedModuleRequest = './' + normalizedModuleRequest;
455      }
456      return quotation + normalizedModuleRequest + quotation;
457    });
458
459    const filePath: string =
460      locateActualFilePathWithModuleRequest(path.resolve(path.dirname(sourcePath), moduleRequest));
461    const result: RegExpMatchArray | null =
462      filePath.match(/(\S+)(\/|\\)src(\/|\\)(?:main|ohosTest)(\/|\\)(ets|js)(\/|\\)(\S+)/);
463    if (result && projectConfig.aceModuleJsonPath) {
464      const npmModuleIdx: number = result[1].search(/(\/|\\)node_modules(\/|\\)/);
465      const projectRootPath: string = projectConfig.projectRootPath;
466      if (npmModuleIdx === -1 || npmModuleIdx === projectRootPath.search(/(\/|\\)node_modules(\/|\\)/)) {
467        const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath);
468        const bundleName: string = packageInfo[0];
469        const moduleName: string = packageInfo[1];
470        moduleRequest = `@bundle:${bundleName}/${moduleName}/${result[5]}/${toUnixPath(result[7])}`;
471        item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => {
472          return quotation + moduleRequest + quotation;
473        });
474      }
475    }
476  }
477  return item;
478}
479
480interface ModuleInfo {
481  content: string,
482  /**
483   * the path in build cache dir
484   */
485  buildFilePath: string,
486  /**
487   * the `originSourceFilePath` relative to project root dir.
488   */
489  relativeSourceFilePath: string,
490  /**
491   * the origin source file path will be set with rollup moduleId when obfuscate intermediate js source code,
492   * whereas be set with tsc node.fileName when obfuscate intermediate ts source code.
493   */
494  originSourceFilePath?: string,
495  rollupModuleId?: string
496}
497
498export async function writeObfuscatedSourceCode(moduleInfo: ModuleInfo, logger: Object,
499  projectConfig: Object, rollupNewSourceMaps: Object = {}): Promise<void> {
500  if (compileToolIsRollUp() && projectConfig.arkObfuscator) {
501    MemoryUtils.tryGC();
502    performancePrinter?.filesPrinter?.startEvent(moduleInfo.buildFilePath);
503    await writeArkguardObfuscatedSourceCode(moduleInfo, logger, projectConfig, rollupNewSourceMaps);
504    performancePrinter?.filesPrinter?.endEvent(moduleInfo.buildFilePath, undefined, true);
505    MemoryUtils.tryGC();
506    return;
507  }
508  mkdirsSync(path.dirname(moduleInfo.buildFilePath));
509  if (!compileToolIsRollUp()) {
510    await writeMinimizedSourceCode(moduleInfo.content, moduleInfo.buildFilePath, logger, projectConfig.compileHar);
511    return;
512  }
513
514  if (moduleInfo.originSourceFilePath) {
515    const originSourceFilePath = toUnixPath(moduleInfo.originSourceFilePath);
516    let genFileInHar: GeneratedFileInHar = harFilesRecord.get(originSourceFilePath);
517
518    if (!genFileInHar) {
519      genFileInHar = { sourcePath: originSourceFilePath };
520    }
521    if (!genFileInHar.sourceCachePath) {
522      genFileInHar.sourceCachePath = toUnixPath(moduleInfo.buildFilePath);
523    }
524    harFilesRecord.set(originSourceFilePath, genFileInHar);
525  }
526
527  fs.writeFileSync(moduleInfo.buildFilePath, moduleInfo.content);
528}
529
530/**
531 * This Api only be used by rollup compiling process & only be
532 * exported for unit test.
533 */
534export async function writeArkguardObfuscatedSourceCode(moduleInfo: ModuleInfo, logger: Object,
535  projectConfig: Object, rollupNewSourceMaps: Object = {}): Promise<void> {
536  const arkObfuscator = projectConfig.arkObfuscator;
537  const isDeclaration = (/\.d\.e?ts$/).test(moduleInfo.buildFilePath);
538  const packageDir = projectConfig.packageDir;
539  const projectRootPath = projectConfig.projectRootPath;
540  const useNormalized = projectConfig.useNormalizedOHMUrl;
541  const localPackageSet = projectConfig.localPackageSet;
542  const useTsHar = projectConfig.useTsHar;
543  const sourceMapGeneratorInstance = SourceMapGenerator.getInstance();
544
545  let previousStageSourceMap: sourceMap.RawSourceMap | undefined = undefined;
546  if (moduleInfo.relativeSourceFilePath.length > 0 && !isDeclaration) {
547    const selectedFilePath = sourceMapGeneratorInstance.isNewSourceMaps() ? moduleInfo.rollupModuleId! : moduleInfo.relativeSourceFilePath;
548    previousStageSourceMap = sourceMapGeneratorInstance.getSpecifySourceMap(rollupNewSourceMaps, selectedFilePath) as sourceMap.RawSourceMap;
549  }
550
551  let historyNameCache = new Map<string, string>();
552  if (nameCacheMap) {
553    let namecachePath = moduleInfo.relativeSourceFilePath;
554    if (isDeclaration) {
555      namecachePath = getRelativeSourcePath(moduleInfo.originSourceFilePath, projectRootPath,
556        sourceFileBelongProject.get(toUnixPath(moduleInfo.originSourceFilePath)));
557    }
558    let identifierCache = nameCacheMap.get(namecachePath)?.[IDENTIFIER_CACHE];
559    deleteLineInfoForNameString(historyNameCache, identifierCache);
560  }
561
562  let mixedInfo: { content: string, sourceMap?: Object, nameCache?: Object };
563  let projectInfo: {
564    packageDir: string,
565    projectRootPath: string,
566    localPackageSet: Set<string>,
567    useNormalized: boolean,
568    useTsHar: boolean
569  } = { packageDir, projectRootPath, localPackageSet, useNormalized, useTsHar };
570  try {
571    mixedInfo = await arkObfuscator.obfuscate(moduleInfo.content, moduleInfo.buildFilePath, previousStageSourceMap,
572      historyNameCache, moduleInfo.originSourceFilePath, projectInfo);
573  } catch (err) {
574    logger.error(red, `ArkTS:INTERNAL ERROR: Failed to obfuscate file '${moduleInfo.relativeSourceFilePath}' with arkguard. ${err}`);
575  }
576
577  if (mixedInfo.sourceMap && !isDeclaration) {
578    const selectedFilePath = sourceMapGeneratorInstance.isNewSourceMaps() ? moduleInfo.rollupModuleId! : moduleInfo.relativeSourceFilePath;
579    mixedInfo.sourceMap.sources = [moduleInfo.relativeSourceFilePath];
580    sourceMapGeneratorInstance.fillSourceMapPackageInfo(moduleInfo.rollupModuleId!, mixedInfo.sourceMap);
581    sourceMapGeneratorInstance.updateSpecifySourceMap(rollupNewSourceMaps, selectedFilePath, mixedInfo.sourceMap);
582  }
583
584  if (mixedInfo.nameCache && !isDeclaration) {
585    let obfName: string = moduleInfo.relativeSourceFilePath;
586    let isOhModule = isPackageModulesFile(moduleInfo.originSourceFilePath, projectConfig);
587    if (projectConfig.obfuscationMergedObConfig?.options.enableFileNameObfuscation && !isOhModule) {
588      obfName = mangleFilePath(moduleInfo.relativeSourceFilePath);
589    }
590    mixedInfo.nameCache.obfName = obfName;
591    nameCacheMap.set(moduleInfo.relativeSourceFilePath, mixedInfo.nameCache);
592  }
593
594  const newFilePath: string = tryMangleFileName(moduleInfo.buildFilePath, projectConfig, moduleInfo.originSourceFilePath);
595  if (newFilePath !== moduleInfo.buildFilePath && !isDeclaration) {
596    sourceMapGeneratorInstance.saveKeyMappingForObfFileName(moduleInfo.rollupModuleId!);
597  }
598  mkdirsSync(path.dirname(newFilePath));
599  fs.writeFileSync(newFilePath, mixedInfo.content ?? '');
600}
601
602export function tryMangleFileName(filePath: string, projectConfig: Object, originalFilePath: string): string {
603  originalFilePath = toUnixPath(originalFilePath);
604  let isOhModule = isPackageModulesFile(originalFilePath, projectConfig);
605  let genFileInHar: GeneratedFileInHar = harFilesRecord.get(originalFilePath);
606  if (!genFileInHar) {
607    genFileInHar = { sourcePath: originalFilePath };
608    harFilesRecord.set(originalFilePath, genFileInHar);
609  }
610
611  if (projectConfig.obfuscationMergedObConfig?.options?.enableFileNameObfuscation && !isOhModule) {
612    const mangledFilePath: string = mangleFilePath(filePath);
613    if ((/\.d\.e?ts$/).test(filePath)) {
614      genFileInHar.obfuscatedDeclarationCachePath = mangledFilePath;
615    } else {
616      genFileInHar.obfuscatedSourceCachePath = mangledFilePath;
617    }
618    filePath = mangledFilePath;
619  } else if (!(/\.d\.e?ts$/).test(filePath)) {
620    genFileInHar.sourceCachePath = filePath;
621  }
622  return filePath;
623}
624
625export async function mangleDeclarationFileName(logger: Object, projectConfig: Object,
626  sourceFileBelongProject: Map<string, string>): Promise<void> {
627  for (const [sourcePath, genFilesInHar] of harFilesRecord) {
628    if (genFilesInHar.originalDeclarationCachePath && genFilesInHar.originalDeclarationContent) {
629      let filePath = genFilesInHar.originalDeclarationCachePath;
630      let relativeSourceFilePath = getRelativeSourcePath(filePath,
631         projectConfig.projectRootPath, sourceFileBelongProject.get(toUnixPath(filePath)));
632      await writeObfuscatedSourceCode({
633          content: genFilesInHar.originalDeclarationContent,
634          buildFilePath: genFilesInHar.originalDeclarationCachePath,
635          relativeSourceFilePath: relativeSourceFilePath,
636          originSourceFilePath: sourcePath
637        }, logger, projectConfig, {});
638    }
639  }
640}
641
642export async function writeMinimizedSourceCode(content: string, filePath: string, logger: Object,
643  isHar: boolean = false): Promise<void> {
644  let result: MinifyOutput;
645  try {
646    const minifyOptions = {
647      compress: {
648        join_vars: false,
649        sequences: 0,
650        directives: false
651      }
652    };
653    if (!isHar) {
654      minifyOptions['format'] = {
655        semicolons: false,
656        beautify: true,
657        indent_level: 2
658      };
659    }
660    result = await minify(content, minifyOptions);
661  } catch {
662    logger.error(red, `ArkTS:INTERNAL ERROR: Failed to obfuscate source code for ${filePath}`, reset);
663  }
664
665  fs.writeFileSync(filePath, result.code);
666}
667
668export function genBuildPath(filePath: string, projectPath: string, buildPath: string, projectConfig: Object): string {
669  filePath = toUnixPath(filePath);
670  if (filePath.endsWith(EXTNAME_MJS)) {
671    filePath = filePath.replace(/\.mjs$/, EXTNAME_JS);
672  }
673  if (filePath.endsWith(EXTNAME_CJS)) {
674    filePath = filePath.replace(/\.cjs$/, EXTNAME_JS);
675  }
676  projectPath = toUnixPath(projectPath);
677
678  if (isPackageModulesFile(filePath, projectConfig)) {
679    const packageDir: string = projectConfig.packageDir;
680    const fakePkgModulesPath: string = toUnixPath(path.join(projectConfig.projectRootPath, packageDir));
681    let output: string = '';
682    if (filePath.indexOf(fakePkgModulesPath) === -1) {
683      const hapPath: string = toUnixPath(projectConfig.projectRootPath);
684      const tempFilePath: string = filePath.replace(hapPath, '');
685      const sufStr: string = tempFilePath.substring(tempFilePath.indexOf(packageDir) + packageDir.length + 1);
686      output = path.join(projectConfig.nodeModulesPath, ZERO, sufStr);
687    } else {
688      output = filePath.replace(fakePkgModulesPath, path.join(projectConfig.nodeModulesPath, ONE));
689    }
690    return output;
691  }
692
693  if (filePath.indexOf(projectPath) !== -1) {
694    const sufStr: string = filePath.replace(projectPath, '');
695    const output: string = path.join(buildPath, sufStr);
696    return output;
697  }
698
699  return '';
700}
701
702export function getPackageInfo(configFile: string): Array<string> {
703  if (packageCollection.has(configFile)) {
704    return packageCollection.get(configFile);
705  }
706  const data: Object = JSON.parse(fs.readFileSync(configFile).toString());
707  const bundleName: string = data.app.bundleName;
708  const moduleName: string = data.module.name;
709  packageCollection.set(configFile, [bundleName, moduleName]);
710  return [bundleName, moduleName];
711}
712
713/**
714 * This Api only used by webpack compiling process - result_process
715 * @param sourcePath The path in build cache dir
716 * @param sourceContent The intermediate js source code
717 */
718export function generateSourceFilesToTemporary(sourcePath: string, sourceContent: string, sourceMap: Object,
719  projectConfig: Object, logger: Object): void {
720    let jsFilePath: string = genTemporaryPath(sourcePath, projectConfig.projectPath, process.env.cachePath,
721      projectConfig, undefined);
722  if (jsFilePath.length === 0) {
723    return;
724  }
725  if (jsFilePath.endsWith(EXTNAME_ETS)) {
726    jsFilePath = jsFilePath.replace(/\.ets$/, EXTNAME_JS);
727  } else {
728    jsFilePath = jsFilePath.replace(/\.ts$/, EXTNAME_JS);
729  }
730  let sourceMapFile: string = genSourceMapFileName(jsFilePath);
731  if (sourceMapFile.length > 0 && projectConfig.buildArkMode === 'debug') {
732    let source = toUnixPath(sourcePath).replace(toUnixPath(projectConfig.projectRootPath) + '/', '');
733    // adjust sourceMap info
734    sourceMap.sources = [source];
735    sourceMap.file = path.basename(sourceMap.file);
736    delete sourceMap.sourcesContent;
737    newSourceMaps[source] = sourceMap;
738  }
739  sourceContent = transformModuleSpecifier(sourcePath, sourceContent, projectConfig);
740
741  mkdirsSync(path.dirname(jsFilePath));
742  if (projectConfig.buildArkMode === 'debug') {
743    fs.writeFileSync(jsFilePath, sourceContent);
744    return;
745  }
746
747  writeObfuscatedSourceCode({content: sourceContent, buildFilePath: jsFilePath, relativeSourceFilePath: ''},
748    logger, projectConfig);
749}
750
751export function genAbcFileName(temporaryFile: string): string {
752  let abcFile: string = temporaryFile;
753  if (temporaryFile.endsWith(EXTNAME_TS)) {
754    abcFile = temporaryFile.replace(/\.ts$/, EXTNAME_ABC);
755  } else {
756    abcFile = temporaryFile.replace(/\.js$/, EXTNAME_ABC);
757  }
758  return abcFile;
759}
760
761export function isOhModules(projectConfig: Object): boolean {
762  return projectConfig.packageDir === OH_MODULES;
763}
764
765export function isEs2Abc(projectConfig: Object): boolean {
766  return projectConfig.pandaMode === ES2ABC || projectConfig.pandaMode === 'undefined' ||
767    projectConfig.pandaMode === undefined;
768}
769
770export function isTs2Abc(projectConfig: Object): boolean {
771  return projectConfig.pandaMode === TS2ABC;
772}
773
774export function genProtoFileName(temporaryFile: string): string {
775  return temporaryFile.replace(/\.(?:[tj]s|json)$/, EXTNAME_PROTO_BIN);
776}
777
778export function genMergeProtoFileName(temporaryFile: string): string {
779  let protoTempPathArr: string[] = temporaryFile.split(TEMPORARY);
780  const sufStr: string = protoTempPathArr[protoTempPathArr.length - 1];
781  let protoBuildPath: string = path.join(process.env.cachePath, 'protos', sufStr);
782
783  return protoBuildPath;
784}
785
786export function removeDuplicateInfo(moduleInfos: Array<any>): Array<any> {
787  const tempModuleInfos: any[] = Array<any>();
788  moduleInfos.forEach((item) => {
789    let check: boolean = tempModuleInfos.every((newItem) => {
790      return item.tempFilePath !== newItem.tempFilePath;
791    });
792    if (check) {
793      tempModuleInfos.push(item);
794    }
795  });
796  moduleInfos = tempModuleInfos;
797
798  return moduleInfos;
799}
800
801export function buildCachePath(tailName: string, projectConfig: Object, logger: Object): string {
802  let pathName: string = process.env.cachePath !== undefined ?
803    path.join(projectConfig.cachePath, tailName) : path.join(projectConfig.aceModuleBuild, tailName);
804  validateFilePathLength(pathName, logger);
805  return pathName;
806}
807
808export function getArkBuildDir(arkDir: string): string {
809  if (isWindows()) {
810    return path.join(arkDir, 'build-win');
811  } else if (isMac()) {
812    return path.join(arkDir, 'build-mac');
813  } else {
814    return path.join(arkDir, 'build');
815  }
816}
817
818export function getBuildBinDir(arkDir: string): string {
819  return path.join(getArkBuildDir(arkDir), 'bin');
820}
821
822export function cleanUpUtilsObjects(): void {
823  newSourceMaps = {};
824  nameCacheMap.clear();
825  packageCollection.clear();
826}
827
828export function getHookEventFactory(share: Object, pluginName: string, hookName: string): Object {
829  if (typeof share.getHookEventFactory === 'function') {
830    return share.getHookEventFactory(pluginName, hookName);
831  } else {
832    return undefined;
833  }
834}
835
836export function createAndStartEvent(eventOrEventFactory: Object, eventName: string, syncFlag = false): Object {
837  if (eventOrEventFactory === undefined) {
838    return undefined;
839  }
840  let event: Object;
841  if (typeof eventOrEventFactory.createSubEvent === 'function') {
842    event = eventOrEventFactory.createSubEvent(eventName);
843  } else {
844    event = eventOrEventFactory.createEvent(eventName);
845  }
846  if (typeof event.startAsyncEvent === 'function' && syncFlag) {
847    event.startAsyncEvent();
848  } else {
849    event.start();
850  }
851  return event;
852}
853
854export function stopEvent(event: Object, syncFlag = false): void {
855  if (event !== undefined) {
856    if (typeof event.stopAsyncEvent === 'function' && syncFlag) {
857      event.stopAsyncEvent();
858    } else {
859      event.stop();
860    }
861  }
862}
863
864export function compileToolIsRollUp(): boolean {
865  return process.env.compileTool === 'rollup';
866}
867
868export function transformOhmurlToRecordName(ohmurl: string): string {
869  // @normalized:N&<moduleName>&<bunldName>&<packageName>/entry/ets/xxx/yyy&<version>
870  // ----> <bunldName>&<packageName>/entry/ets/xxx/yyy&<version>
871  return ohmurl.split(SEPARATOR_BITWISE_AND).slice(2).join(SEPARATOR_BITWISE_AND);
872}
873
874export function transformOhmurlToPkgName(ohmurl: string): string {
875  let normalizedPath: string = ohmurl.split(SEPARATOR_BITWISE_AND)[3];
876  let paths: Array<string> = normalizedPath.split(SEPARATOR_SLASH);
877  if (normalizedPath.startsWith(SEPARATOR_AT)) {
878    // Spec: If the normalized import path starts with '@', the package name is before the second '/' in the normalized
879    // import path, like:  @aaa/bbb/ccc/ddd ---> package name is @aaa/bbb
880    return paths.slice(0, 2).join(SEPARATOR_SLASH);
881  }
882  return paths[0];
883}