• 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 } 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