"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExtensionScanner = void 0;
const tslib_1 = require("tslib");
const os_1 = tslib_1.__importDefault(require("os"));
const path_1 = tslib_1.__importDefault(require("path"));
const fs = tslib_1.__importStar(require("fs-extra"));
const semver_1 = tslib_1.__importDefault(require("semver"));
const ide_core_node_1 = require("@opensumi/ide-core-node");
const utils_1 = require("../common/utils");
const merge_contributes_1 = require("./merge-contributes");
function resolvePath(path) {
    if (path[0] === '~') {
        return path.join(os_1.default.homedir(), path.slice(1));
    }
    return path;
}
class ExtensionScanner {
    static async getExtension(extensionPath, localization, extraMetaData) {
        // Electron 中，extensionPath 可能为一个 `.asar` 结尾的路径，这种情况下，fs-extra的pathExists 会判断为不存在
        try {
            await fs.stat(extensionPath);
        }
        catch (e) {
            (0, ide_core_node_1.getDebugLogger)().error(`extension path ${extensionPath} does not exist`);
            return;
        }
        const pkgPath = path_1.default.join(extensionPath, 'package.json');
        const defaultPkgNlsPath = path_1.default.join(extensionPath, 'package.nls.json');
        const pkgNlsPath = await ExtensionScanner.getLocalizedExtraMetadataPath('package.nls', extensionPath, localization, '.json');
        const extendPath = path_1.default.join(extensionPath, 'kaitian.js');
        const pkgExist = await fs.pathExists(pkgPath);
        const defaultPkgNlsPathExist = await fs.pathExists(defaultPkgNlsPath);
        const extendExist = await fs.pathExists(extendPath);
        let pkgCheckResult = pkgExist;
        const extendCheckResult = extendExist;
        if (pkgExist) {
            try {
                const packageJSON = await fs.readJSON(pkgPath);
                if (!packageJSON.engines) {
                    pkgCheckResult = false;
                }
                else if (!(packageJSON.engines.vscode || packageJSON.engines.kaitian)) {
                    pkgCheckResult = false;
                }
            }
            catch (e) {
                (0, ide_core_node_1.getDebugLogger)().error(e);
                pkgCheckResult = false;
            }
        }
        let pkgNlsJSON;
        if (pkgNlsPath) {
            pkgNlsJSON = await fs.readJSON(pkgNlsPath);
        }
        let defaultPkgNlsJSON;
        if (defaultPkgNlsPathExist) {
            defaultPkgNlsJSON = await fs.readJSON(defaultPkgNlsPath);
        }
        if (!(pkgCheckResult || extendCheckResult)) {
            return;
        }
        const extensionExtraMetaData = {};
        let packageJSON = {};
        try {
            packageJSON = await fs.readJSON(pkgPath);
            if (extraMetaData) {
                for (const extraField of Object.keys(extraMetaData)) {
                    try {
                        const basename = path_1.default.basename(extraMetaData[extraField]);
                        const suffix = path_1.default.extname(extraMetaData[extraField]);
                        const prefix = basename.substr(0, basename.length - suffix.length);
                        const extraFieldFilePath = await ExtensionScanner.getLocalizedExtraMetadataPath(prefix, extensionPath, localization, suffix);
                        extensionExtraMetaData[extraField] = await fs.readFile(extraFieldFilePath || path_1.default.join(extensionPath, extraMetaData[extraField]), 'utf-8');
                    }
                    catch (e) {
                        extensionExtraMetaData[extraField] = null;
                    }
                }
            }
        }
        catch (e) {
            (0, ide_core_node_1.getDebugLogger)().error(e);
            return;
        }
        let extendConfig = {};
        if (await fs.pathExists(extendPath)) {
            try {
                // 这里必须clear cache, 不然每次都一样
                delete (0, utils_1.getNodeRequire)().cache[extendPath];
                extendConfig = (0, utils_1.getNodeRequire)()(extendPath);
            }
            catch (e) {
                (0, ide_core_node_1.getDebugLogger)().error(e);
            }
        }
        // compatible with `kaitianContributes` declare
        if (!packageJSON.sumiContributes && packageJSON.kaitianContributes) {
            packageJSON.sumiContributes = packageJSON.kaitianContributes;
        }
        packageJSON.contributes = packageJSON.contributes || {};
        // merge for `sumiContributes` and `contributes`
        // `sumiContributesOverride` is used for override the `contributes`
        if (packageJSON.sumiContributesOverride) {
            packageJSON.contributes = Object.assign(packageJSON.contributes, packageJSON.sumiContributesOverride);
        }
        // `sumiContributes` is used for merge the `contributes`
        packageJSON.contributes = (0, merge_contributes_1.mergeContributes)(packageJSON.sumiContributes, packageJSON.contributes);
        const extension = {
            // vscode 规范
            id: `${packageJSON.publisher}.${packageJSON.name}`,
            // 使用插件市场的 id
            // 从插件市场下载的插件命名规范为 ${publisher}.${name}-${version}
            extensionId: this.getExtensionIdByExtensionPath(extensionPath, packageJSON.version),
            extendConfig,
            path: extensionPath,
            packageJSON,
            defaultPkgNlsJSON,
            packageNlsJSON: pkgNlsJSON,
            extraMetadata: extensionExtraMetaData,
            realPath: await fs.realpath(extensionPath),
            uri: ide_core_node_1.Uri.file(extensionPath),
        };
        return extension;
    }
    /**
     * 通过文件夹名获取插件 id
     * 文件夹名目前有两种：
     *  1. ${publisher}.${name}-${version} (推荐)
     *  2. ${extensionId}-${name}-${version}
     *  以上两种
     * @param extensionPath
     * @param version 可能有用户使用非 semver 的规范，所以传进来
     */
    static getExtensionIdByExtensionPath(extensionPath, version) {
        const regExp = version ? new RegExp(`^(.+?)\\.(.+?)-(${version})$`) : /^(.+?)\.(.+?)-(\d+\.\d+\.\d+)$/;
        const baseName = path_1.default.basename(extensionPath);
        const nameStr = baseName.endsWith('.asar') ? baseName.split('.asar').shift() : baseName;
        const match = regExp.exec(nameStr);
        if (match == null) {
            // 按照第二种方式返回
            return nameStr.split('-')[0];
        }
        const [, publisher, name] = match;
        return `${publisher}.${name}`;
    }
    constructor(scan, localization, extensionCandidate, extraMetaData) {
        this.scan = scan;
        this.localization = localization;
        this.extensionCandidate = extensionCandidate;
        this.extraMetaData = extraMetaData;
        this.results = new Map();
        this.availableExtensions = new Map();
    }
    async run() {
        const scan = this.scan.map((dir) => resolvePath(dir));
        await Promise.all(scan
            .map((dir) => this.scanDir(dir))
            .concat(this.extensionCandidate.map(async (extension) => {
            await this.getExtension(extension, this.localization);
        })));
        return Array.from(this.availableExtensions.values());
    }
    async scanDir(dir) {
        (0, ide_core_node_1.getDebugLogger)().info('scan directory: ', dir);
        try {
            const extensionDirArr = await fs.readdir(dir);
            await Promise.all(extensionDirArr.map((extensionDir) => {
                const extensionPath = path_1.default.join(dir, extensionDir);
                return this.getExtension(extensionPath, this.localization);
            }));
        }
        catch (e) {
            (0, ide_core_node_1.getDebugLogger)().error(e);
        }
    }
    static async getLocalizedExtraMetadataPath(prefix, extensionPath, localization, suffix) {
        const lowerCasePrefix = prefix.toLowerCase();
        const lowerCaseLocalization = localization.toLowerCase();
        const maybeExist = [
            `${prefix}.${localization}${suffix}`, // {prefix}.zh-CN{suffix}
            `${lowerCasePrefix}.${localization}${suffix}`,
            `${prefix}.${lowerCaseLocalization}${suffix}`, // {prefix}.zh-cn{suffix}
            `${lowerCasePrefix}.${lowerCaseLocalization}${suffix}`,
            `${prefix}.${localization.split('-')[0]}${suffix}`, // {prefix}.zh{suffix}
            `${lowerCasePrefix}.${localization.split('-')[0]}${suffix}`,
        ];
        for (const maybe of maybeExist) {
            const filepath = path_1.default.join(extensionPath, maybe);
            if (await fs.pathExists(filepath)) {
                return filepath;
            }
        }
        return undefined;
    }
    isLatestVersion(extension) {
        if (this.availableExtensions.has(extension.id)) {
            const existedExtension = this.availableExtensions.get(extension.id);
            if (!existedExtension.packageJSON) {
                return true;
            }
            const existedPkgJson = existedExtension.packageJSON;
            const incomingPkgJson = extension.packageJSON;
            const compared = semver_1.default.compare(existedPkgJson.version, incomingPkgJson.version);
            if (compared === 0) {
                return false;
                // v1 greater
            }
            else if (compared === 1) {
                return false;
            }
            else {
                // v2 greater
                return true;
            }
        }
        return true;
    }
    async getExtension(extensionPath, localization, extraMetaData) {
        if (this.results.has(extensionPath)) {
            return;
        }
        const extension = await ExtensionScanner.getExtension(extensionPath, localization, {
            ...this.extraMetaData,
            ...extraMetaData,
        });
        if (extension) {
            const latest = this.isLatestVersion(extension);
            if (latest) {
                this.availableExtensions.set(extension.id, extension);
            }
            this.results.set(extensionPath, extension);
            return extension;
        }
    }
}
exports.ExtensionScanner = ExtensionScanner;
//# sourceMappingURL=extension.scanner.js.map