"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UpdateHermitError = void 0;
exports.updateArtifacts = updateArtifacts;
const tslib_1 = require("tslib");
const shlex_1 = require("shlex");
const upath_1 = tslib_1.__importDefault(require("upath"));
const logger_1 = require("../../../logger");
const exec_1 = require("../../../util/exec");
const fs_1 = require("../../../util/fs");
const git_1 = require("../../../util/git");
const p = tslib_1.__importStar(require("../../../util/promises"));
/**
 * updateArtifacts runs hermit install for each updated dependencies
 */
async function updateArtifacts(update) {
    const { packageFileName } = update;
    try {
        await updateHermitPackage(update);
    }
    catch (err) {
        const execErr = err;
        logger_1.logger.debug({ err }, `error updating hermit packages.`);
        return [
            {
                artifactError: {
                    lockFile: `from: ${execErr.from}, to: ${execErr.to}`,
                    stderr: execErr.stderr,
                },
            },
        ];
    }
    logger_1.logger.debug(`scanning the changes after update`);
    let updateResult = null;
    try {
        updateResult = await getUpdateResult(packageFileName);
        logger_1.logger.debug({ updateResult }, `update result for hermit`);
    }
    catch (err) {
        logger_1.logger.debug({ err }, 'Error getting hermet update results');
        return [
            {
                artifactError: {
                    stderr: err.message,
                },
            },
        ];
    }
    return updateResult;
}
/**
 * getContent returns the content of either link or a normal file
 */
async function getContent(file) {
    let contents = '';
    const isSymlink = await (0, fs_1.localPathIsSymbolicLink)(file);
    if (isSymlink) {
        contents = await (0, fs_1.readLocalSymlink)(file);
    }
    if (contents === null) {
        throw new Error(`error getting content for ${file}`);
    }
    return {
        isSymlink,
        contents,
    };
}
/**
 * getAddResult returns the UpdateArtifactsResult for the added files
 */
function getAddResult(path, contentRes) {
    return {
        file: {
            type: 'addition',
            path,
            contents: contentRes.contents,
            isSymlink: contentRes.isSymlink,
            isExecutable: contentRes.isExecutable,
        },
    };
}
/**
 * getDeleteResult returns the UpdateArtifactsResult for deleted files
 */
function getDeleteResult(path) {
    return {
        file: {
            type: 'deletion',
            path,
        },
    };
}
/**
 * getUpdateResult will return the update result after `hermit install`
 * has been performed for all packages
 */
async function getUpdateResult(packageFileName) {
    const hermitFolder = `${upath_1.default.dirname(packageFileName)}/`;
    const hermitChanges = await (0, git_1.getRepoStatus)(hermitFolder);
    logger_1.logger.debug({ hermitChanges, hermitFolder }, `hermit changes after package update`);
    // handle added files
    const added = await p.map([...hermitChanges.created, ...hermitChanges.not_added], async (path) => {
        const contents = await getContent(path);
        return getAddResult(path, contents);
    });
    const deleted = hermitChanges.deleted.map(getDeleteResult);
    const modified = await p.map(hermitChanges.modified, async (path) => {
        const contents = await getContent(path);
        return [
            getDeleteResult(path), // delete existing link
            getAddResult(path, contents), // add a new link
        ];
    });
    const renamed = await p.map(hermitChanges.renamed, async (renamed) => {
        const from = renamed.from;
        const to = renamed.to;
        const toContents = await getContent(to);
        return [getDeleteResult(from), getAddResult(to, toContents)];
    });
    return [
        // rename will need to go first, because
        // it needs to create the new link for the new version
        // for the modified links to use
        ...renamed.flat(),
        ...modified.flat(),
        ...added,
        ...deleted,
    ];
}
/**
 * getHermitPackage returns the hermit package for running the hermit install
 */
function getHermitPackage(name, version) {
    return `${name}-${version}`;
}
/**
 * updateHermitPackage runs hermit install for the given package
 */
async function updateHermitPackage(update) {
    logger_1.logger.trace({ update }, `hermit.updateHermitPackage()`);
    const toInstall = [];
    const from = [];
    // storing the old package for replacement
    const toUninstall = [];
    for (const pkg of update.updatedDeps) {
        if (!pkg.depName || !pkg.currentVersion || !pkg.newValue) {
            logger_1.logger.debug({
                depName: pkg.depName,
                currentVersion: pkg.currentVersion,
                newValue: pkg.newValue,
            }, 'missing package update information');
            throw new UpdateHermitError(getHermitPackage(pkg.depName ?? '', pkg.currentVersion ?? ''), getHermitPackage(pkg.depName ?? '', pkg.newValue ?? ''), 'invalid package to update');
        }
        const depName = pkg.depName;
        const newName = pkg.newName;
        const currentVersion = pkg.currentVersion;
        const newValue = pkg.newValue;
        const fromPackage = getHermitPackage(depName, currentVersion);
        // newName will be available for replacement
        const toPackage = getHermitPackage(newName ?? depName, newValue);
        toInstall.push(toPackage);
        from.push(fromPackage);
        // skips uninstall for version only replacement
        if (pkg.updateType === 'replacement' && newName !== depName) {
            toUninstall.push(depName);
        }
    }
    const execOptions = {
        docker: {},
        userConfiguredEnv: update?.config?.env,
        cwdFile: update.packageFileName,
    };
    const fromPackages = from.map(shlex_1.quote).join(' ');
    // when a name replacement happens, need to uninstall the old package
    if (toUninstall.length > 0) {
        const packagesToUninstall = toUninstall.join(' ');
        const uninstallCommands = `./hermit uninstall ${packagesToUninstall}`;
        try {
            const result = await (0, exec_1.exec)(uninstallCommands, execOptions);
            logger_1.logger.trace({ stdout: result.stdout }, `hermit uninstall command stdout`);
        }
        catch (e) {
            logger_1.logger.warn({ err: e }, `error uninstall hermit package for replacement`);
            throw new UpdateHermitError(fromPackages, packagesToUninstall, e.stderr, e.stdout);
        }
    }
    const packagesToInstall = toInstall.join(' ');
    const execCommands = `./hermit install ${packagesToInstall}`;
    logger_1.logger.debug({
        packageFile: update.packageFileName,
        packagesToInstall,
    }, `performing updates`);
    try {
        const result = await (0, exec_1.exec)(execCommands, execOptions);
        logger_1.logger.trace({ stdout: result.stdout }, `hermit command stdout`);
    }
    catch (e) {
        logger_1.logger.warn({ err: e }, `error updating hermit package`);
        throw new UpdateHermitError(fromPackages, packagesToInstall, e.stderr, e.stdout);
    }
}
class UpdateHermitError extends Error {
    stdout;
    stderr;
    from;
    to;
    constructor(from, to, stderr, stdout = '') {
        super();
        this.stdout = stdout;
        this.stderr = stderr;
        this.from = from;
        this.to = to;
    }
}
exports.UpdateHermitError = UpdateHermitError;
//# sourceMappingURL=artifacts.js.map