"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommunityPackagesLifecycleService = void 0;
const backend_common_1 = require("@n8n/backend-common");
const di_1 = require("@n8n/di");
const constants_1 = require("../../constants");
const event_service_1 = require("../../events/event.service");
const bad_request_error_1 = require("../../errors/response-errors/bad-request.error");
const internal_server_error_1 = require("../../errors/response-errors/internal-server.error");
const not_found_error_1 = require("../../errors/response-errors/not-found.error");
const push_1 = require("../../push");
const n8n_core_1 = require("n8n-core");
const semver_1 = require("semver");
const n8n_workflow_1 = require("n8n-workflow");
const community_node_types_service_1 = require("./community-node-types.service");
const community_packages_service_1 = require("./community-packages.service");
const npm_utils_1 = require("./npm-utils");
const { PACKAGE_NOT_INSTALLED, PACKAGE_NAME_NOT_PROVIDED, PACKAGE_VERSION_NOT_FOUND, PACKAGE_DOES_NOT_CONTAIN_NODES, PACKAGE_NOT_FOUND, } = constants_1.RESPONSE_ERROR_MESSAGES;
const isCommunityPackageInstallClientError = (error) => [PACKAGE_VERSION_NOT_FOUND, PACKAGE_DOES_NOT_CONTAIN_NODES, PACKAGE_NOT_FOUND].some((msg) => typeof error.message === 'string' && error.message.includes(msg));
let CommunityPackagesLifecycleService = class CommunityPackagesLifecycleService {
    constructor(logger, push, communityPackagesService, eventService, communityNodeTypesService, instanceSettings) {
        this.logger = logger;
        this.push = push;
        this.communityPackagesService = communityPackagesService;
        this.eventService = eventService;
        this.communityNodeTypesService = communityNodeTypesService;
        this.instanceSettings = instanceSettings;
    }
    async listInstalledPackages() {
        const installedPackages = await this.communityPackagesService.getAllInstalledPackages();
        if (installedPackages.length === 0)
            return [];
        let pendingUpdates;
        try {
            await (0, npm_utils_1.executeNpmCommand)(['outdated', '--json'], {
                doNotHandleError: true,
                cwd: this.instanceSettings.nodesDownloadDir,
            });
        }
        catch (error) {
            if ((0, npm_utils_1.isNpmExecErrorWithStdout)(error) && error.code === 1) {
                try {
                    pendingUpdates = (0, n8n_workflow_1.jsonParse)(error.stdout.trim());
                }
                catch (parseError) {
                    this.logger.warn('Failed to parse npm outdated output', {
                        error: (0, n8n_workflow_1.ensureError)(parseError),
                    });
                }
            }
        }
        let packages = this.communityPackagesService.matchPackagesWithUpdates(installedPackages, pendingUpdates);
        try {
            if (this.communityPackagesService.hasMissingPackages) {
                packages = this.communityPackagesService.matchMissingPackages(packages);
            }
        }
        catch {
        }
        return packages;
    }
    async install(args, user, presentation) {
        const { name, verify, version } = args;
        if (!name) {
            throw new bad_request_error_1.BadRequestError(PACKAGE_NAME_NOT_PROVIDED);
        }
        if (version && !(0, semver_1.valid)(version)) {
            throw new bad_request_error_1.BadRequestError(`Invalid version: ${version}`);
        }
        let checksum;
        if (verify) {
            checksum = this.communityNodeTypesService.findVetted(name)?.checksum;
            if (!checksum) {
                throw new bad_request_error_1.BadRequestError(`Package ${name} is not vetted for installation`);
            }
        }
        let parsed;
        try {
            parsed = this.communityPackagesService.parseNpmPackageName(name);
        }
        catch (error) {
            throw new bad_request_error_1.BadRequestError(error instanceof Error ? error.message : 'Failed to parse package name');
        }
        if (parsed.packageName === constants_1.STARTER_TEMPLATE_NAME) {
            const templateMessage = presentation === 'ui'
                ? [
                    `Package "${parsed.packageName}" is only a template`,
                    'Please enter an actual package to install',
                ].join('.')
                : `Package "${parsed.packageName}" is only a template. Please enter an actual package to install`;
            throw new bad_request_error_1.BadRequestError(templateMessage);
        }
        const isInstalled = await this.communityPackagesService.isPackageInstalled(parsed.packageName);
        const hasLoaded = this.communityPackagesService.hasPackageLoaded(name);
        if (isInstalled && hasLoaded) {
            const alreadyMessage = presentation === 'ui'
                ? [
                    `Package "${parsed.packageName}" is already installed`,
                    'To update it, click the corresponding button in the UI',
                ].join('.')
                : `Package "${parsed.packageName}" is already installed`;
            throw new bad_request_error_1.BadRequestError(alreadyMessage);
        }
        const packageStatus = await this.communityPackagesService.checkNpmPackageStatus(name);
        if (packageStatus.status !== 'OK') {
            throw new bad_request_error_1.BadRequestError(`Package "${name}" is banned so it cannot be installed`);
        }
        const packageVersion = version ?? parsed.version;
        let installedPackage;
        try {
            installedPackage = await this.communityPackagesService.installPackage(parsed.packageName, packageVersion, checksum);
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : constants_1.UNKNOWN_FAILURE_REASON;
            this.eventService.emit('community-package-installed', {
                user,
                inputString: name,
                packageName: parsed.packageName,
                success: false,
                packageVersion,
                failureReason: errorMessage,
            });
            let message = [`Error loading package "${name}" `, errorMessage].join(':');
            if (error instanceof Error && error.cause instanceof Error) {
                message += `\nCause: ${error.cause.message}`;
            }
            const clientError = error instanceof Error ? isCommunityPackageInstallClientError(error) : false;
            throw new (clientError ? bad_request_error_1.BadRequestError : internal_server_error_1.InternalServerError)(message);
        }
        if (!hasLoaded)
            this.communityPackagesService.removePackageFromMissingList(name);
        installedPackage.installedNodes.forEach((node) => {
            this.push.broadcast({
                type: 'reloadNodeType',
                data: {
                    name: node.type,
                    version: node.latestVersion,
                },
            });
        });
        this.eventService.emit('community-package-installed', {
            user,
            inputString: name,
            packageName: parsed.packageName,
            success: true,
            packageVersion,
            packageNodeNames: installedPackage.installedNodes.map((node) => node.name),
            packageAuthor: installedPackage.authorName,
            packageAuthorEmail: installedPackage.authorEmail,
        });
        return installedPackage;
    }
    async update(args, user, whenMissing) {
        const { name, version, checksum } = args;
        if (!name) {
            throw new bad_request_error_1.BadRequestError(PACKAGE_NAME_NOT_PROVIDED);
        }
        if (version && !(0, semver_1.valid)(version)) {
            throw new bad_request_error_1.BadRequestError(`Invalid version: ${version}`);
        }
        const previouslyInstalledPackage = await this.communityPackagesService.findInstalledPackage(name);
        if (!previouslyInstalledPackage) {
            if (whenMissing === 'notFound') {
                throw new not_found_error_1.NotFoundError(PACKAGE_NOT_INSTALLED);
            }
            throw new bad_request_error_1.BadRequestError(PACKAGE_NOT_INSTALLED);
        }
        try {
            const newInstalledPackage = await this.communityPackagesService.updatePackage(this.communityPackagesService.parseNpmPackageName(name).packageName, previouslyInstalledPackage, version, checksum);
            previouslyInstalledPackage.installedNodes.forEach((node) => {
                this.push.broadcast({
                    type: 'removeNodeType',
                    data: {
                        name: node.type,
                        version: node.latestVersion,
                    },
                });
            });
            newInstalledPackage.installedNodes.forEach((node) => {
                this.push.broadcast({
                    type: 'reloadNodeType',
                    data: {
                        name: node.type,
                        version: node.latestVersion,
                    },
                });
            });
            this.eventService.emit('community-package-updated', {
                user,
                packageName: name,
                packageVersionCurrent: previouslyInstalledPackage.installedVersion,
                packageVersionNew: newInstalledPackage.installedVersion,
                packageNodeNames: newInstalledPackage.installedNodes.map((n) => n.name),
                packageAuthor: newInstalledPackage.authorName,
                packageAuthorEmail: newInstalledPackage.authorEmail,
            });
            return newInstalledPackage;
        }
        catch (error) {
            previouslyInstalledPackage.installedNodes.forEach((node) => {
                this.push.broadcast({
                    type: 'removeNodeType',
                    data: {
                        name: node.type,
                        version: node.latestVersion,
                    },
                });
            });
            const message = [
                `Error updating package "${name}"`,
                error instanceof Error ? error.message : constants_1.UNKNOWN_FAILURE_REASON,
            ].join(':');
            throw new internal_server_error_1.InternalServerError(message, error);
        }
    }
    async uninstall(packageName, user, whenMissing) {
        if (!packageName) {
            throw new bad_request_error_1.BadRequestError(PACKAGE_NAME_NOT_PROVIDED);
        }
        try {
            this.communityPackagesService.parseNpmPackageName(packageName);
        }
        catch (error) {
            const message = error instanceof Error ? error.message : constants_1.UNKNOWN_FAILURE_REASON;
            throw new bad_request_error_1.BadRequestError(message);
        }
        const installedPackage = await this.communityPackagesService.findInstalledPackage(packageName);
        if (!installedPackage) {
            if (whenMissing === 'notFound') {
                throw new not_found_error_1.NotFoundError(PACKAGE_NOT_INSTALLED);
            }
            throw new bad_request_error_1.BadRequestError(PACKAGE_NOT_INSTALLED);
        }
        try {
            await this.communityPackagesService.removePackage(packageName, installedPackage);
        }
        catch (error) {
            const message = [
                `Error removing package "${packageName}"`,
                error instanceof Error ? error.message : constants_1.UNKNOWN_FAILURE_REASON,
            ].join(':');
            throw new internal_server_error_1.InternalServerError(message, error);
        }
        installedPackage.installedNodes.forEach((node) => {
            this.push.broadcast({
                type: 'removeNodeType',
                data: {
                    name: node.type,
                    version: node.latestVersion,
                },
            });
        });
        this.eventService.emit('community-package-deleted', {
            user,
            packageName,
            packageVersion: installedPackage.installedVersion,
            packageNodeNames: installedPackage.installedNodes.map((node) => node.name),
            packageAuthor: installedPackage.authorName,
            packageAuthorEmail: installedPackage.authorEmail,
        });
    }
};
exports.CommunityPackagesLifecycleService = CommunityPackagesLifecycleService;
exports.CommunityPackagesLifecycleService = CommunityPackagesLifecycleService = __decorate([
    (0, di_1.Service)(),
    __metadata("design:paramtypes", [backend_common_1.Logger,
        push_1.Push,
        community_packages_service_1.CommunityPackagesService,
        event_service_1.EventService,
        community_node_types_service_1.CommunityNodeTypesService,
        n8n_core_1.InstanceSettings])
], CommunityPackagesLifecycleService);
//# sourceMappingURL=community-packages.lifecycle.service.js.map