#!/usr/bin/env python3 # This file is part of Cockpit. # # Copyright (C) 2017 Red Hat, Inc. # # Cockpit is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # # Cockpit is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with Cockpit; If not, see . # To use this example add a line to an issue with the "bot" label # # * [ ] npm-update patternfly # # Dependencies that are either fragile (minor updates break) # or are part of our public Javascript API and need caution FRAGILE = [ "jquery", "patternfly", "paternfly-bootstrap-combobox", # version 0.32.2 switches to babel 7, which causes build failures with our babel 6 build system "react-bootstrap", ] import collections import json import os import random import sys import subprocess sys.dont_write_bytecode = True import task BOTS = os.path.abspath(os.path.dirname(__file__)) BASE = os.path.normpath(os.path.join(BOTS, "..")) def package_json(data=None, package=None, version=None): package_path = os.path.join(BASE, "package.json") if data is None: with open(package_path, "r") as f: return json.load(f, object_pairs_hook=collections.OrderedDict) else: if package: data = dict(data, dependencies=dict(data["dependencies"], **{ package: version })) with open(package_path, "w") as f: json.dump(data, f, indent=2, separators=(',', ': ')) f.write("\n") def map_dict(dependencies, function): items = [ ] for (name, value) in dependencies.items(): items.append((name, function(name, value))) return collections.OrderedDict(items) def execute(*args): if task.verbose: sys.stderr.write("+ " + " ".join(args) + "\n") subprocess.check_call(args, cwd=BASE) def output(*args): if task.verbose: sys.stderr.write("+ " + " ".join(args) + "\n") return subprocess.check_output(args, cwd=BASE, universal_newlines=True) def run(specified_package, verbose=False, **kwargs): # Force all current dependencies in place execute("npm", "install") orig_package_json = package_json() if specified_package: packages = [ specified_package ] else: packages = list(orig_package_json["dependencies"].keys()) random.shuffle(packages) def prefix(name, version): if not version[0].isdigit(): return version if name == specified_package or name not in FRAGILE: return "^" + version return "~" + version for package in packages: orig_version = orig_package_json["dependencies"][package] upgradeable_version = prefix(package, orig_version) if orig_version == upgradeable_version: continue # Run npm upgrade for our package # package_json(orig_package_json, package, upgradeable_version) execute("npm", "upgrade", "--save", package) # Check if that did an upgrade new_version = package_json()["dependencies"][package].lstrip("^~") if new_version != orig_version: # Write out the original package.json with the updated version package_json(orig_package_json, package, new_version) # Create a pull request from these changes title = "package.json: Update {0} package dependency".format(package) branch = task.branch(package, title, pathspec="package.json", **kwargs) # List of files that probably touch this package lines = output("git", "grep", "-w", package) comment = "Please manually check features related to these files:\n\n```\n{0}```".format(lines) if branch: kwargs["title"] = title pull = task.pull(branch, **kwargs) task.comment(pull, comment) break # Undo our changes package_json(orig_package_json) if __name__ == '__main__': task.main(function=run, title="Upgrade a node dependency")