All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.jenkins.updatebot.kind.npm.PackageJsonUpdater Maven / Gradle / Ivy

There is a newer version: 1.1.7
Show newest version
/*
 * Copyright 2016 Red Hat, Inc.
 *
 * Red Hat licenses this file to you under the Apache License, version
 * 2.0 (the "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 */
package io.jenkins.updatebot.kind.npm;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.jenkins.updatebot.Configuration;
import io.jenkins.updatebot.commands.CommandContext;
import io.jenkins.updatebot.commands.PushVersionChangesContext;
import io.jenkins.updatebot.kind.Kind;
import io.jenkins.updatebot.kind.KindDependenciesCheck;
import io.jenkins.updatebot.kind.Updater;
import io.jenkins.updatebot.kind.npm.dependency.DependencyCheck;
import io.jenkins.updatebot.kind.npm.dependency.DependencyTree;
import io.jenkins.updatebot.model.Dependencies;
import io.jenkins.updatebot.model.DependencySet;
import io.jenkins.updatebot.model.DependencyVersionChange;
import io.jenkins.updatebot.model.NpmDependencies;
import io.jenkins.updatebot.support.FileDeleter;
import io.jenkins.updatebot.support.FileHelper;
import io.jenkins.updatebot.support.JsonNodes;
import io.jenkins.updatebot.support.MarkupHelper;
import io.jenkins.updatebot.support.ProcessHelper;
import io.jenkins.updatebot.support.Strings;
import io.fabric8.utils.Files;
import io.fabric8.utils.Filter;
import io.fabric8.utils.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 */
public class PackageJsonUpdater implements Updater {

    private static final transient Logger LOG = LoggerFactory.getLogger(PackageJsonUpdater.class);

    @Override
    public boolean isApplicable(CommandContext context) {
        boolean answer = FileHelper.isFile(context.file("package.json"));
        if (answer) {
            // lets verify we have a npm install
            Configuration configuration = context.getConfiguration();
            String npmCommand = configuration.getNpmCommand();
            int returnCode = ProcessHelper.runCommandIgnoreOutput(context.getDir(), configuration.getNpmEnvironmentVariables(), npmCommand, "-v");
            if (returnCode != 0) {
                context.warn(LOG, "Could not invoke NodeJS!. Command failed: " + npmCommand + " -v => " + returnCode);
                context.warn(LOG, "Please verify you have `npm` on your PATH or you have configured NodeJS property");
                return false;
            }
        }
        return answer;
    }

    protected boolean pushVersions(PushVersionChangesContext context) throws IOException {
        File file = context.file("package.json");
        JsonNode tree = MarkupHelper.loadJson(file);
        boolean answer = false;
        for (String dependencyKey : NpmDependencyKinds.DEPENDENCY_KEYS) {
            JsonNode dependencies = tree.get(dependencyKey);
            if (dependencies instanceof ObjectNode) {
                ObjectNode objectNode = (ObjectNode) dependencies;
                if (doPushVersionChange(dependencyKey, objectNode, context)) {
                    answer = true;
                }
            }
        }
        if (answer) {
            MarkupHelper.savePrettyJson(file, tree);
            context.updatedFile(file);
        }
        return answer;
    }

    @Override
    public boolean pushVersions(CommandContext parentContext, List changes) throws IOException {
        boolean answer = false;
        if (isApplicable(parentContext)) {
            for (DependencyVersionChange step : changes) {
                PushVersionChangesContext context = new PushVersionChangesContext(parentContext, step);
                boolean updated = pushVersions(context);
                if (updated) {
                    answer = true;
                } else {
                    parentContext.removeChild(context);
                }
            }
        }
        return answer;
    }

    /**
     * Adds the list of possible dependency update steps from the given source context that we can then apply to
     * other repositories
     */
    @Override
    public void addVersionChangesFromSource(CommandContext context, Dependencies dependencyConfig, List list) {
        NpmDependencies dependencies = dependencyConfig.getNpm();
        JsonNode tree = getPackageJsonTree(context);
        if (tree != null) {
            String name = JsonNodes.textValue(tree, "name");
            String version = JsonNodes.textValue(tree, "version");
            if (Strings.notEmpty(name) && Strings.notEmpty(version)) {
                if (isDevelopmentVersion(name, version)) {
                    context.info(LOG, "Not updating NPM dependency " + name + " version " + version + " as this is a development version and not a release");
                } else {
                    list.add(new DependencyVersionChange(Kind.NPM, name, version, NpmDependencyKinds.DEPENDENCIES));
                }
            }
            if (dependencies != null) {
                addUpdateDependencySteps(list, tree, dependencies.getDependencies(), NpmDependencyKinds.DEPENDENCIES);
                addUpdateDependencySteps(list, tree, dependencies.getDevDependencies(), NpmDependencyKinds.DEV_DEPENDENCIES);
                addUpdateDependencySteps(list, tree, dependencies.getPeerDependencies(), NpmDependencyKinds.PEER_DEPENDENCIES);
            }
        }
    }

    protected JsonNode getPackageJsonTree(CommandContext context) {
        return getJsonFile(context, "package.json");
    }

    protected JsonNode getJsonFile(CommandContext context, String fileName) {
        JsonNode tree = null;
        File file = context.file(fileName);
        if (Files.isFile(file)) {
            try {
                tree = MarkupHelper.loadJson(file);
            } catch (IOException e) {
                LOG.warn("Failed to parse JSON " + file + ". " + e, e);
            }
        }
        return tree;
    }

    @Override
    public KindDependenciesCheck checkDependencies(CommandContext context, List changes) throws IOException {
        List validChanges = new ArrayList<>();
        List invalidChanges = new ArrayList<>();
        Map failedChecks = new TreeMap<>();

        String dependencyFileName = ".dependency-tree.json";
        JsonNode json = null;
        File file = new File(context.getDir(), dependencyFileName);
        try (FileDeleter ignore = new FileDeleter(file)) {
            generateDependencyTree(context, dependencyFileName);
            json = getJsonFile(context, dependencyFileName);
        } catch (IOException e) {
            LOG.warn("Caught " + e, e);
        }
        if (json != null) {
            DependencyTree dependencyTree = DependencyTree.parseTree(json);
            for (DependencyVersionChange change : changes) {
                DependencyCheck dependencyCheck = dependencyTree.dependencyCheck(change.getDependency());
                if (dependencyCheck.isValid()) {
                    validChanges.add(change);
                } else {
                    invalidChanges.add(change);
                    failedChecks.put(change.getDependency(), dependencyCheck);
                }
            }
        }
        return new KindDependenciesCheck(validChanges, invalidChanges, failedChecks);
    }

    protected void generateDependencyTree(CommandContext context, String dependencyFileName) throws IOException {
        context.getConfiguration().getNpmDependencyTreeGenerator().generateDependencyTree(context, dependencyFileName);

    }


    /**
     * Returns true if the version string is
     *
     * @param name
     * @param version
     * @return
     */
    protected boolean isDevelopmentVersion(String name, String version) {
        if (version.endsWith("-development")) {
            return true;
        }
        return false;
    }

    protected void addUpdateDependencySteps(List list, JsonNode tree, DependencySet dependencySet, String dependencyKey) {
        if (dependencySet != null) {
            Filter filter = dependencySet.createFilter();
            JsonNode dependencies = tree.get(dependencyKey);
            if (dependencies instanceof ObjectNode) {
                ObjectNode objectNode = (ObjectNode) dependencies;
                Iterator iter = objectNode.fieldNames();
                while (iter.hasNext()) {
                    String field = iter.next();
                    if (filter.matches(field)) {
                        String value = JsonNodes.textValue(objectNode, field);
                        if (value != null) {
                            list.add(new DependencyVersionChange(Kind.NPM, field, value, dependencyKey));
                        }
                    }
                }
            }
        }
    }

    @Override
    public boolean pullVersions(CommandContext context) throws IOException {
        File dir = context.getRepository().getDir();
        return ProcessHelper.runCommandAndLogOutput(context.getConfiguration(), LOG, dir, "ncu", "--upgrade");
    }


    protected boolean doPushVersionChange(String dependencyKey, ObjectNode dependencies, PushVersionChangesContext context) {
        String name = context.getName();
        String value = context.getValue();
        JsonNode dependency = dependencies.get(name);
        if (dependency != null && dependency.isTextual()) {
            String old = dependency.textValue();
            if (!Objects.equal(old, value)) {
                dependencies.put(name, value);
                context.updatedVersion(dependencyKey, name, value, old);
                return true;
            }
        }
        return false;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy