code: def cutarelease(project_name, version_files, dry_run=False):
      """Cut a release.
  
      @param project_name {str}
      @param version_files {list} List of paths to files holding the version
          info for this project.
  
          If none are given it attempts to guess the version file:
          package.json or VERSION.txt or VERSION or $project_name.py
          or lib/$project_name.py or $project_name.js or lib/$project_name.js.
  
          The version file can be in one of the following forms:
  
          - A .py file, in which case the file is expect to have a top-level
            global called "__version_info__" as follows. [1]
  
              __version_info__ = (0, 7, 6)
  
            Note that I typically follow that with the following to get a
            string version attribute on my modules:
  
              __version__ = '.'.join(map(str, __version_info__))
  
          - A .js file, in which case the file is expected to have a top-level
            global called "VERSION" as follows:
  
              ver VERSION = "1.2.3";
  
          - A "package.json" file, typical of a node.js npm-using project.
            The package.json file must have a "version" field.
  
          - TODO: A simple version file whose only content is a "1.2.3"-style version
            string.
  
      [1]: This is a convention I tend to follow in my projects.
          Granted it might not be your cup of tea. I should add support for
          just `__version__ = "1.2.3"`. I'm open to other suggestions too.
      """
      dry_run_str = dry_run and " (dry-run)" or ""
  
      if not version_files:
          log.info("guessing version file")
          candidates = [
              "package.json",
              "VERSION.txt",
              "VERSION",
              "%s.py" % project_name,
              "lib/%s.py" % project_name,
              "%s.js" % project_name,
              "lib/%s.js" % project_name,
          ]
          for candidate in candidates:
              if exists(candidate):
                  version_files = [candidate]
                  break
          else:
              raise Error(
                  "could not find a version file: specify its path or "
                  "add one of the following to your project: '%s'"
                  % "', '".join(candidates)
              )
          log.info("using '%s' as version file", version_files[0])
  
      parsed_version_files = [
          _parse_version_file(f) for f in version_files
      ]
      version_file_type, version_info = parsed_version_files[0]
      version = _version_from_version_info(version_info)
  
      # Confirm
      if not dry_run:
          answer = query_yes_no(
              "* * *\n"
              "Are you sure you want cut a %s release?\n"
              "This will involved commits and a push." % version,
              default="no",
          )
          print("* * *")
          if answer != "yes":
              log.info("user abort")
              return
      log.info("cutting a %s release%s", version, dry_run_str)
  
      # Checks: Ensure there is a section in changes for this version.
  
      changes_path = "CHANGES.md"
      changes_txt, changes, nyr = parse_changelog(changes_path)
      # pprint(changes)
      top_ver = changes[0]["version"]
      if top_ver != version:
          raise Error(
              "changelog '%s' top section says "
              "version %r, expected version %r: aborting"
              % (changes_path, top_ver, version)
          )
      top_verline = changes[0]["verline"]
      if not top_verline.endswith(nyr):
          answer = query_yes_no(
              "\n* * *\n"
              "The changelog '%s' top section doesn't have the expected\n"
              "'%s' marker. Has this been released already?"
              % (changes_path, nyr),
              default="yes",
          )
          print("* * *")
          if answer != "no":
              log.info("abort")
              return
      top_body = changes[0]["body"]
      if top_body.strip() == "(nothing yet)":
          raise Error(
              "top section body is `(nothing yet)': it looks like "
              "nothing has been added to this release"
          )
  
      # Commits to prepare release.
      changes_txt_before = changes_txt
      changes_txt = changes_txt.replace(" (not yet released)", "", 1)
      if not dry_run and changes_txt != changes_txt_before:
          log.info("prepare `%s' for release", changes_path)
          f = codecs.open(changes_path, "w", "utf-8")
          f.write(changes_txt)
          f.close()
          run(
              'git commit %s -m "prepare for %s release"'
              % (changes_path, version)
          )
  
      # Tag version and push.
      curr_tags = set(
          t
          for t in _capture_stdout(["git", "tag", "-l"]).split(b"\n")
          if t
      )
      if not dry_run and version not in curr_tags:
          log.info("tag the release")
          run('git tag -a "%s" -m "version %s"' % (version, version))
          run("git push --tags")
  
      # Optionally release.
      if exists("package.json"):
          answer = query_yes_no(
              "\n* * *\nPublish to npm?", default="yes"
          )
          print("* * *")
          if answer == "yes":
              if dry_run:
                  log.info("skipping npm publish (dry-run)")
              else:
                  run("npm publish")
      elif exists("setup.py"):
          answer = query_yes_no(
              "\n* * *\nPublish to pypi?", default="yes"
          )
          print("* * *")
          if answer == "yes":
              if dry_run:
                  log.info("skipping pypi publish (dry-run)")
              else:
                  run("rm -rf dist")
                  run(
                      "%spython setup.py sdist bdist_wheel"
                      % _setup_command_prefix()
                  )
                  run("twine upload dist/*")
                  run("rm -rf dist")
  
      # Commits to prepare for future dev and push.
      # - update changelog file
      next_version_info = _get_next_version_info(version_info)
      next_version = _version_from_version_info(next_version_info)
      log.info("prepare for future dev (version %s)", next_version)
      marker = "## " + changes[0]["verline"]
      if marker.endswith(nyr):
          marker = marker[0 : -len(nyr)]
      if marker not in changes_txt:
          raise Error(
              "couldn't find `%s' marker in `%s' "
              "content: can't prep for subsequent dev"
              % (marker, changes_path)
          )
      next_verline = "%s %s%s" % (
          marker.rsplit(None, 1)[0],
          next_version,
          nyr,
      )
      changes_txt = changes_txt.replace(
          marker + "\n",
          "%s\n\n(nothing yet)\n\n\n%s\n" % (next_verline, marker),
      )
      if not dry_run:
          f = codecs.open(changes_path, "w", "utf-8")
          f.write(changes_txt)
          f.close()
  
      # - update version file
      next_version_tuple = _tuple_from_version(next_version)
      for i, ver_file in enumerate(version_files):
          ver_content = codecs.open(ver_file, "r", "utf-8").read()
          ver_file_type, ver_info = parsed_version_files[i]
          if ver_file_type == "json":
              marker = '"version": "%s"' % version
              if marker not in ver_content:
                  raise Error(
                      "couldn't find `%s' version marker in `%s' "
                      "content: can't prep for subsequent dev"
                      % (marker, ver_file)
                  )
              ver_content = ver_content.replace(
                  marker, '"version": "%s"' % next_version
              )
          elif ver_file_type == "javascript":
              marker = 'var VERSION = "%s";' % version
              if marker not in ver_content:
                  raise Error(
                      "couldn't find `%s' version marker in `%s' "
                      "content: can't prep for subsequent dev"
                      % (marker, ver_file)
                  )
              ver_content = ver_content.replace(
                  marker, 'var VERSION = "%s";' % next_version
              )
          elif ver_file_type == "python":
              marker = "__version_info__ = %r" % (version_info,)
              if marker not in ver_content:
                  raise Error(
                      "couldn't find `%s' version marker in `%s' "
                      "content: can't prep for subsequent dev"
                      % (marker, ver_file)
                  )
              ver_content = ver_content.replace(
                  marker,
                  "__version_info__ = %r" % (next_version_tuple,),
              )
          elif ver_file_type == "version":
              ver_content = next_version
          else:
              raise Error("unknown ver_file_type: %r" % ver_file_type)
          if not dry_run:
              log.info(
                  "update version to '%s' in '%s'",
                  next_version,
                  ver_file,
              )
              f = codecs.open(ver_file, "w", "utf-8")
              f.write(ver_content)
              f.close()
  
      if not dry_run:
          run(
              'git commit %s %s -m "prep for future dev"'
              % (changes_path, " ".join(version_files))
          )
          run("git push")
  
imports: import sys
  import os
  from os.path import exists, basename, splitext
  import re
  import codecs
  import logging
  import optparse
  import json
label_desc: Use image node_id 1 for calling a global function with example usage: cutarelease(project_name, version_files, dry_run) without return types