161847f8eSopenharmony_ci/*
261847f8eSopenharmony_ci * Copyright (c) 2023 Huawei Device Co., Ltd.
361847f8eSopenharmony_ci * Licensed under the Apache License, Version 2.0 (the "License");
461847f8eSopenharmony_ci * you may not use this file except in compliance with the License.
561847f8eSopenharmony_ci * You may obtain a copy of the License at
661847f8eSopenharmony_ci *
761847f8eSopenharmony_ci *     http://www.apache.org/licenses/LICENSE-2.0
861847f8eSopenharmony_ci *
961847f8eSopenharmony_ci * Unless required by applicable law or agreed to in writing, software
1061847f8eSopenharmony_ci * distributed under the License is distributed on an "AS IS" BASIS,
1161847f8eSopenharmony_ci * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1261847f8eSopenharmony_ci * See the License for the specific language governing permissions and
1361847f8eSopenharmony_ci * limitations under the License.
1461847f8eSopenharmony_ci */
1561847f8eSopenharmony_ci
1661847f8eSopenharmony_ciconst JSON5 = require('json5');
1761847f8eSopenharmony_ciconst path = require('path');
1861847f8eSopenharmony_ciconst fs = require('fs');
1961847f8eSopenharmony_ci
2061847f8eSopenharmony_ciclass Project {
2161847f8eSopenharmony_ci  constructor(projectPath, nonProject) {
2261847f8eSopenharmony_ci    this.projectPath = projectPath;
2361847f8eSopenharmony_ci    this.nonProject = nonProject;
2461847f8eSopenharmony_ci    this.logTag = 'Project';
2561847f8eSopenharmony_ci  }
2661847f8eSopenharmony_ci
2761847f8eSopenharmony_ci  getPath() {
2861847f8eSopenharmony_ci    return this.projectPath;
2961847f8eSopenharmony_ci  }
3061847f8eSopenharmony_ci
3161847f8eSopenharmony_ci  getProfile() {
3261847f8eSopenharmony_ci    if (!this.profile) {
3361847f8eSopenharmony_ci      const buildProfilePath = path.resolve(this.projectPath, 'build-profile.json5');
3461847f8eSopenharmony_ci      if (!fs.existsSync(buildProfilePath)) {
3561847f8eSopenharmony_ci        Logger.error(this.logTag, 'build-profile.json5 can\'t be found, is it an openharmony project?');
3661847f8eSopenharmony_ci        return this.profile;
3761847f8eSopenharmony_ci      }
3861847f8eSopenharmony_ci      const profileContent = fs.readFileSync(buildProfilePath, 'utf-8');
3961847f8eSopenharmony_ci      try {
4061847f8eSopenharmony_ci        this.profile = JSON5.parse(profileContent);
4161847f8eSopenharmony_ci      } catch (ex) {
4261847f8eSopenharmony_ci        Logger.error(this.logTag, `parse build-profile.json error: ${JSON.stringify(ex)}`);
4361847f8eSopenharmony_ci      }
4461847f8eSopenharmony_ci    }
4561847f8eSopenharmony_ci    return this.profile;
4661847f8eSopenharmony_ci  }
4761847f8eSopenharmony_ci
4861847f8eSopenharmony_ci  getAppSdkVersion() {
4961847f8eSopenharmony_ci    const profile = this.getProfile();
5061847f8eSopenharmony_ci
5161847f8eSopenharmony_ci    if (!profile) {
5261847f8eSopenharmony_ci      return undefined;
5361847f8eSopenharmony_ci    }
5461847f8eSopenharmony_ci
5561847f8eSopenharmony_ci    if (!profile.app) {
5661847f8eSopenharmony_ci      return undefined;
5761847f8eSopenharmony_ci    }
5861847f8eSopenharmony_ci
5961847f8eSopenharmony_ci    if (profile.app.compileSdkVersion) {
6061847f8eSopenharmony_ci      return profile.app.compileSdkVersion;
6161847f8eSopenharmony_ci    }
6261847f8eSopenharmony_ci
6361847f8eSopenharmony_ci    if (profile.app.products) {
6461847f8eSopenharmony_ci      const compileSdkVersion = profile.app.products[0].compileSdkVersion;
6561847f8eSopenharmony_ci      if (typeof compileSdkVersion === 'number') {
6661847f8eSopenharmony_ci        return compileSdkVersion;
6761847f8eSopenharmony_ci      }
6861847f8eSopenharmony_ci      const version = compileSdkVersion.match(/\((.+)\)/g)[0].replace(/\(|\)/g, '');
6961847f8eSopenharmony_ci      return version;
7061847f8eSopenharmony_ci    }
7161847f8eSopenharmony_ci    return undefined;
7261847f8eSopenharmony_ci  }
7361847f8eSopenharmony_ci
7461847f8eSopenharmony_ci  getAppSdkPath() {
7561847f8eSopenharmony_ci    if (this.sdkPath) {
7661847f8eSopenharmony_ci      return this.sdkPath;
7761847f8eSopenharmony_ci    }
7861847f8eSopenharmony_ci    const localPropertiesPath = path.resolve(this.projectPath, 'local.properties');
7961847f8eSopenharmony_ci    if (!fs.existsSync(localPropertiesPath)) {
8061847f8eSopenharmony_ci      Logger.error(this.logTag, 'unable to get the sdk path of the project, specify it using the --sdk or --sdkRoot');
8161847f8eSopenharmony_ci      return this.sdkPath;
8261847f8eSopenharmony_ci    }
8361847f8eSopenharmony_ci    const properties = this.parseProperty(localPropertiesPath);
8461847f8eSopenharmony_ci    this.sdkPath = properties.get('sdk.dir');
8561847f8eSopenharmony_ci    return this.sdkPath;
8661847f8eSopenharmony_ci  }
8761847f8eSopenharmony_ci
8861847f8eSopenharmony_ci  parseProperty(propertyFilePath) {
8961847f8eSopenharmony_ci    const properties = fs.readFileSync(propertyFilePath, 'utf-8');
9061847f8eSopenharmony_ci    const lines = properties.split('\n');
9161847f8eSopenharmony_ci    const propertyRegExp = new RegExp(/(.*)=(.*)/);
9261847f8eSopenharmony_ci    const map = new Map();
9361847f8eSopenharmony_ci    lines.forEach((line) => {
9461847f8eSopenharmony_ci      if (line.startsWith('#')) {
9561847f8eSopenharmony_ci        return;
9661847f8eSopenharmony_ci      }
9761847f8eSopenharmony_ci      const expArray = line.match(propertyRegExp);
9861847f8eSopenharmony_ci      const MATCHED_RESULT_NUMBER = 3;
9961847f8eSopenharmony_ci      const KEY_INDEX = 1;
10061847f8eSopenharmony_ci      const VALUE_INDEX = 2;
10161847f8eSopenharmony_ci      if (expArray && expArray.length === MATCHED_RESULT_NUMBER) {
10261847f8eSopenharmony_ci        map.set(expArray[KEY_INDEX].trim(), expArray[VALUE_INDEX].trim());
10361847f8eSopenharmony_ci      }
10461847f8eSopenharmony_ci    });
10561847f8eSopenharmony_ci    return map;
10661847f8eSopenharmony_ci  }
10761847f8eSopenharmony_ci
10861847f8eSopenharmony_ci  /**
10961847f8eSopenharmony_ci   * 获取应用的源码列表
11061847f8eSopenharmony_ci   *
11161847f8eSopenharmony_ci   * @returns
11261847f8eSopenharmony_ci   */
11361847f8eSopenharmony_ci  getAppSources(isIncludeTest) {
11461847f8eSopenharmony_ci    if (this.nonProject) {
11561847f8eSopenharmony_ci      return this.getNonProjectAppSources();
11661847f8eSopenharmony_ci    }
11761847f8eSopenharmony_ci    const profile = this.getProfile();
11861847f8eSopenharmony_ci    if (!profile || !profile.modules || profile.modules.length === 0) {
11961847f8eSopenharmony_ci      return new Set();
12061847f8eSopenharmony_ci    }
12161847f8eSopenharmony_ci    const moduleSrcPaths = [];
12261847f8eSopenharmony_ci    profile.modules.forEach((module) => {
12361847f8eSopenharmony_ci      if (module.srcPath) {
12461847f8eSopenharmony_ci        moduleSrcPaths.push(path.resolve(this.projectPath, module.srcPath));
12561847f8eSopenharmony_ci      }
12661847f8eSopenharmony_ci    });
12761847f8eSopenharmony_ci    const appSources = [];
12861847f8eSopenharmony_ci    moduleSrcPaths.forEach((moduleSrc) => {
12961847f8eSopenharmony_ci      appSources.push(...this.getModuleSource(moduleSrc, isIncludeTest));
13061847f8eSopenharmony_ci    });
13161847f8eSopenharmony_ci    return new Set(appSources);
13261847f8eSopenharmony_ci  }
13361847f8eSopenharmony_ci
13461847f8eSopenharmony_ci  getNonProjectAppSources() {
13561847f8eSopenharmony_ci    Logger.info(this.logTag, 'find source files in non-project');
13661847f8eSopenharmony_ci    const appSources = [];
13761847f8eSopenharmony_ci    this.listSourceFiles(this.projectPath, appSources);
13861847f8eSopenharmony_ci    return new Set(appSources);
13961847f8eSopenharmony_ci  }
14061847f8eSopenharmony_ci
14161847f8eSopenharmony_ci  getModuleSource(modulePath, isIncludeTest) {
14261847f8eSopenharmony_ci    const sourceSets = ['src/main/ets'];
14361847f8eSopenharmony_ci    if (isIncludeTest) {
14461847f8eSopenharmony_ci      sourceSets.push(...['src/ohosTest/ets']);
14561847f8eSopenharmony_ci    }
14661847f8eSopenharmony_ci    const sources = [];
14761847f8eSopenharmony_ci    sourceSets.forEach((sourcePath) => {
14861847f8eSopenharmony_ci      const srcPath = path.resolve(modulePath, sourcePath);
14961847f8eSopenharmony_ci      this.listSourceFiles(srcPath, sources);
15061847f8eSopenharmony_ci    });
15161847f8eSopenharmony_ci    if (sources.length === 0) {
15261847f8eSopenharmony_ci      Logger.info(this.logTag, `can't find source file in ${this.projectPath}`);
15361847f8eSopenharmony_ci    }
15461847f8eSopenharmony_ci    return sources;
15561847f8eSopenharmony_ci  }
15661847f8eSopenharmony_ci
15761847f8eSopenharmony_ci  listSourceFiles(srcPath, dest) {
15861847f8eSopenharmony_ci    if (fs.existsSync(srcPath)) {
15961847f8eSopenharmony_ci      Logger.info(this.logTag, `find source code in ${srcPath}`);
16061847f8eSopenharmony_ci      FileSystem.listFiles(srcPath, (filePath) => {
16161847f8eSopenharmony_ci        const fileName = path.basename(filePath);
16261847f8eSopenharmony_ci        return fileName.endsWith('.ts') || fileName.endsWith('.ets');
16361847f8eSopenharmony_ci      }, dest);
16461847f8eSopenharmony_ci    }
16561847f8eSopenharmony_ci  }
16661847f8eSopenharmony_ci}
16761847f8eSopenharmony_ci
16861847f8eSopenharmony_ciclass Sdk {
16961847f8eSopenharmony_ci
17061847f8eSopenharmony_ci  /**
17161847f8eSopenharmony_ci   *
17261847f8eSopenharmony_ci   * @param {Project} project 应用工程对象
17361847f8eSopenharmony_ci   * @param {string} sdkEtsPath 指向sdk中ets目录的路径
17461847f8eSopenharmony_ci   * @param {string} sdkRoot sdk根目录
17561847f8eSopenharmony_ci   */
17661847f8eSopenharmony_ci  constructor(project, sdkEtsPath, sdkRoot) {
17761847f8eSopenharmony_ci    this.project = project;
17861847f8eSopenharmony_ci    this.sdkEtsPath = sdkEtsPath;
17961847f8eSopenharmony_ci    this.sdkRoot = sdkRoot;
18061847f8eSopenharmony_ci  }
18161847f8eSopenharmony_ci
18261847f8eSopenharmony_ci  getPath() {
18361847f8eSopenharmony_ci    if (this.sdkEtsPath) {
18461847f8eSopenharmony_ci      return this.sdkEtsPath;
18561847f8eSopenharmony_ci    }
18661847f8eSopenharmony_ci    if (this.sdkApiRoot) {
18761847f8eSopenharmony_ci      return this.sdkApiRoot;
18861847f8eSopenharmony_ci    }
18961847f8eSopenharmony_ci    const sdkVersion = this.project.getAppSdkVersion();
19061847f8eSopenharmony_ci    const sdkDir = this.sdkRoot || this.project.getAppSdkPath();
19161847f8eSopenharmony_ci    if (sdkVersion && sdkDir) {
19261847f8eSopenharmony_ci      this.sdkApiRoot = path.resolve(sdkDir, `${sdkVersion}`, 'ets');
19361847f8eSopenharmony_ci    }
19461847f8eSopenharmony_ci    return this.sdkApiRoot;
19561847f8eSopenharmony_ci  }
19661847f8eSopenharmony_ci
19761847f8eSopenharmony_ci  /**
19861847f8eSopenharmony_ci   * 获取SDK的d.ts文件列表
19961847f8eSopenharmony_ci   *
20061847f8eSopenharmony_ci   * @param {string} sdkRoot
20161847f8eSopenharmony_ci   * @returns
20261847f8eSopenharmony_ci   */
20361847f8eSopenharmony_ci  getApiLibs() {
20461847f8eSopenharmony_ci    if (this.apiLibs) {
20561847f8eSopenharmony_ci      return this.apiLibs;
20661847f8eSopenharmony_ci    }
20761847f8eSopenharmony_ci    this.apiLibs = [];
20861847f8eSopenharmony_ci    this.listDtsFiles('api', this.apiLibs);
20961847f8eSopenharmony_ci    return this.apiLibs;
21061847f8eSopenharmony_ci  }
21161847f8eSopenharmony_ci
21261847f8eSopenharmony_ci  getComponentLibs() {
21361847f8eSopenharmony_ci    if (this.componentLibs) {
21461847f8eSopenharmony_ci      return this.componentLibs;
21561847f8eSopenharmony_ci    }
21661847f8eSopenharmony_ci    this.componentLibs = [];
21761847f8eSopenharmony_ci    this.listDtsFiles('component', this.componentLibs);
21861847f8eSopenharmony_ci    return this.componentLibs;
21961847f8eSopenharmony_ci  }
22061847f8eSopenharmony_ci
22161847f8eSopenharmony_ci  getESLibs(libPath) {
22261847f8eSopenharmony_ci    if (!process.env.bundleMode) {
22361847f8eSopenharmony_ci      return [];
22461847f8eSopenharmony_ci    }
22561847f8eSopenharmony_ci    Logger.info('Sdk', `find ES libs in ${libPath}`);
22661847f8eSopenharmony_ci    if (this.esLibs) {
22761847f8eSopenharmony_ci      return this.esLibs;
22861847f8eSopenharmony_ci    }
22961847f8eSopenharmony_ci    this.esLibs = [];
23061847f8eSopenharmony_ci    FileSystem.listFiles(libPath, (filePath) => path.basename(filePath).endsWith('.d.ts'), this.esLibs);
23161847f8eSopenharmony_ci    FileSystem.listFiles(libPath, (filePath) => path.basename(filePath).endsWith('.d.ets'), this.esLibs);
23261847f8eSopenharmony_ci    return this.esLibs;
23361847f8eSopenharmony_ci  }
23461847f8eSopenharmony_ci
23561847f8eSopenharmony_ci  listDtsFiles(dir, dest) {
23661847f8eSopenharmony_ci    const sdkRoot = this.getPath();
23761847f8eSopenharmony_ci    if (!sdkRoot) {
23861847f8eSopenharmony_ci      return;
23961847f8eSopenharmony_ci    }
24061847f8eSopenharmony_ci    const subDir = path.resolve(sdkRoot, dir);
24161847f8eSopenharmony_ci    FileSystem.listFiles(subDir, (filePath) => path.basename(filePath).endsWith('.d.ts'), dest);
24261847f8eSopenharmony_ci    FileSystem.listFiles(subDir, (filePath) => path.basename(filePath).endsWith('.d.ets'), dest);
24361847f8eSopenharmony_ci  }
24461847f8eSopenharmony_ci}
24561847f8eSopenharmony_ci
24661847f8eSopenharmony_ciclass FileSystem {
24761847f8eSopenharmony_ci  static listFiles(dir, filter, dest) {
24861847f8eSopenharmony_ci    const files = fs.readdirSync(dir);
24961847f8eSopenharmony_ci    files.forEach((element) => {
25061847f8eSopenharmony_ci      const filePath = path.join(dir, element);
25161847f8eSopenharmony_ci      const status = fs.statSync(filePath);
25261847f8eSopenharmony_ci      if (status.isDirectory()) {
25361847f8eSopenharmony_ci        this.listFiles(filePath, filter, dest);
25461847f8eSopenharmony_ci      } else if (filter(filePath)) {
25561847f8eSopenharmony_ci        dest.push(this.convertToPosixPath(filePath));
25661847f8eSopenharmony_ci      }
25761847f8eSopenharmony_ci    });
25861847f8eSopenharmony_ci  }
25961847f8eSopenharmony_ci
26061847f8eSopenharmony_ci  static convertToPosixPath(filePath) {
26161847f8eSopenharmony_ci    return filePath.split(path.sep).join(path.posix.sep);
26261847f8eSopenharmony_ci  }
26361847f8eSopenharmony_ci
26461847f8eSopenharmony_ci  static isInDirectory(parentDir, subPath) {
26561847f8eSopenharmony_ci    const relative = path.relative(parentDir, subPath);
26661847f8eSopenharmony_ci    return (relative === '' || !relative.startsWith('..')) && !path.isAbsolute(relative);
26761847f8eSopenharmony_ci  }
26861847f8eSopenharmony_ci
26961847f8eSopenharmony_ci  static listAllAppDirs(parentDir) {
27061847f8eSopenharmony_ci    const dest = [];
27161847f8eSopenharmony_ci    this.listDirectory(parentDir, dest, (filePath) => {
27261847f8eSopenharmony_ci      const buildProfilePath = path.resolve(filePath, 'build-profile.json5');
27361847f8eSopenharmony_ci      if (!fs.existsSync(buildProfilePath)) {
27461847f8eSopenharmony_ci        return false;
27561847f8eSopenharmony_ci      }
27661847f8eSopenharmony_ci      const profileContent = fs.readFileSync(buildProfilePath, 'utf-8');
27761847f8eSopenharmony_ci      const profile = JSON5.parse(profileContent);
27861847f8eSopenharmony_ci      return profile.app && profile.modules;
27961847f8eSopenharmony_ci    }, (filePath) => {
28061847f8eSopenharmony_ci      return filePath;
28161847f8eSopenharmony_ci    });
28261847f8eSopenharmony_ci    return dest;
28361847f8eSopenharmony_ci  }
28461847f8eSopenharmony_ci
28561847f8eSopenharmony_ci  static listDirectory(dir, dest, filter, visitChildren) {
28661847f8eSopenharmony_ci    const files = fs.readdirSync(dir);
28761847f8eSopenharmony_ci    files.forEach((element) => {
28861847f8eSopenharmony_ci      const filePath = path.join(dir, element);
28961847f8eSopenharmony_ci      const status = fs.statSync(filePath);
29061847f8eSopenharmony_ci      if (status.isDirectory()) {
29161847f8eSopenharmony_ci        if (filter(filePath)) {
29261847f8eSopenharmony_ci          dest.push(filePath);
29361847f8eSopenharmony_ci        } else if (visitChildren(filePath)) {
29461847f8eSopenharmony_ci          this.listDirectory(filePath, dest, filter, visitChildren);
29561847f8eSopenharmony_ci        }
29661847f8eSopenharmony_ci      }
29761847f8eSopenharmony_ci    });
29861847f8eSopenharmony_ci  }
29961847f8eSopenharmony_ci}
30061847f8eSopenharmony_ci
30161847f8eSopenharmony_ciclass Logger {
30261847f8eSopenharmony_ci  static INFO = 0;
30361847f8eSopenharmony_ci  static WARN = 1;
30461847f8eSopenharmony_ci  static ERROR = 2;
30561847f8eSopenharmony_ci  static logs = '';
30661847f8eSopenharmony_ci  static LEVEL_NAME = new Map([
30761847f8eSopenharmony_ci    [this.INFO, 'I'],
30861847f8eSopenharmony_ci    [this.WARN, 'W'],
30961847f8eSopenharmony_ci    [this.ERROR, 'E']
31061847f8eSopenharmony_ci  ]);
31161847f8eSopenharmony_ci
31261847f8eSopenharmony_ci  static info(tag, message) {
31361847f8eSopenharmony_ci    this.wrap(this.INFO, tag, message);
31461847f8eSopenharmony_ci  }
31561847f8eSopenharmony_ci
31661847f8eSopenharmony_ci  static warn(tag, message) {
31761847f8eSopenharmony_ci    this.wrap(this.WARN, tag, message);
31861847f8eSopenharmony_ci  }
31961847f8eSopenharmony_ci
32061847f8eSopenharmony_ci  static error(tag, message) {
32161847f8eSopenharmony_ci    this.wrap(this.ERROR, tag, message);
32261847f8eSopenharmony_ci  }
32361847f8eSopenharmony_ci
32461847f8eSopenharmony_ci  static wrap(level, tag, message) {
32561847f8eSopenharmony_ci    const timeStamp = `${this.formatDate(Date.now(), 'Y-M-D H:m:s:x')}`;
32661847f8eSopenharmony_ci    const logMessage = `${timeStamp} ${this.getLevelName(level)} [${tag}] ${message}`;
32761847f8eSopenharmony_ci    console.log(logMessage);
32861847f8eSopenharmony_ci  }
32961847f8eSopenharmony_ci
33061847f8eSopenharmony_ci  static flush(output) {
33161847f8eSopenharmony_ci    const logName = path.resolve(output, `${this.formatDate(Date.now(), 'Y-M-D-Hmsx')}.log`);
33261847f8eSopenharmony_ci    fs.writeFileSync(logName, this.logs);
33361847f8eSopenharmony_ci    this.info('Logger', `log is in ${logName}`);
33461847f8eSopenharmony_ci  }
33561847f8eSopenharmony_ci
33661847f8eSopenharmony_ci  static getLevelName(level) {
33761847f8eSopenharmony_ci    if (this.LEVEL_NAME.has(level)) {
33861847f8eSopenharmony_ci      return this.LEVEL_NAME.get(level);
33961847f8eSopenharmony_ci    }
34061847f8eSopenharmony_ci    return this.LEVEL_NAME.get(this.INFO);
34161847f8eSopenharmony_ci  }
34261847f8eSopenharmony_ci
34361847f8eSopenharmony_ci  static formatDate(time, format) {
34461847f8eSopenharmony_ci    const date = new Date(time);
34561847f8eSopenharmony_ci    const year = date.getFullYear();
34661847f8eSopenharmony_ci    const month = date.getMonth() + 1;
34761847f8eSopenharmony_ci    const day = date.getDate();
34861847f8eSopenharmony_ci    const hour = date.getHours();
34961847f8eSopenharmony_ci    const min = date.getMinutes();
35061847f8eSopenharmony_ci    const sec = date.getSeconds();
35161847f8eSopenharmony_ci    const mis = date.getMilliseconds();
35261847f8eSopenharmony_ci    let dateStr = format.replace('Y', `${year}`);
35361847f8eSopenharmony_ci    dateStr = dateStr.replace('M', `${month}`);
35461847f8eSopenharmony_ci    dateStr = dateStr.replace('D', `${day}`);
35561847f8eSopenharmony_ci    dateStr = dateStr.replace('H', `${hour}`);
35661847f8eSopenharmony_ci    dateStr = dateStr.replace('m', `${min}`);
35761847f8eSopenharmony_ci    dateStr = dateStr.replace('s', `${sec}`);
35861847f8eSopenharmony_ci    dateStr = dateStr.replace('x', `${mis}`);
35961847f8eSopenharmony_ci    return dateStr;
36061847f8eSopenharmony_ci  }
36161847f8eSopenharmony_ci}
36261847f8eSopenharmony_ci
36361847f8eSopenharmony_ciexports.Project = Project;
36461847f8eSopenharmony_ciexports.Sdk = Sdk;
36561847f8eSopenharmony_ciexports.FileSystem = FileSystem;
36661847f8eSopenharmony_ciexports.Logger = Logger;