From 0269532fa89171e4b7fdce39f6fabcd982abb60c Mon Sep 17 00:00:00 2001
From: Chevdor <chevdor@users.noreply.github.com>
Date: Mon, 24 Jan 2022 09:44:55 +0100
Subject: [PATCH] New changelog scripts (#4491)

* Add templates

* Add folder for local storage of the digests

* Add first draft of the changelog scripts

* Enable Audits in the change template

* Fixes for Polkadot

* Fix templating issue in case there is no high prio change

* Fix Ruby setup

* Remove shell

* Fix chain names

* Fix ENV

* Fix how to get runtime

* Fix runtime_dir

* Fix context location

* Pin changelogerator to a specific version
---
 .../workflows/publish-draft-release.yml       | 75 ++++++++++++---
 polkadot/scripts/changelog/.gitignore         |  4 +
 polkadot/scripts/changelog/Gemfile            | 21 +++++
 polkadot/scripts/changelog/Gemfile.lock       | 79 ++++++++++++++++
 polkadot/scripts/changelog/README.md          | 71 ++++++++++++++
 polkadot/scripts/changelog/bin/changelog      | 93 +++++++++++++++++++
 polkadot/scripts/changelog/digests/.gitignore |  1 +
 polkadot/scripts/changelog/digests/.gitkeep   |  0
 polkadot/scripts/changelog/lib/changelog.rb   | 38 ++++++++
 .../changelog/templates/change.md.tera        | 42 +++++++++
 .../changelog/templates/changes.md.tera       | 13 +++
 .../templates/changes_client.md.tera          | 17 ++++
 .../changelog/templates/changes_misc.md.tera  | 39 ++++++++
 .../templates/changes_runtime.md.tera         | 19 ++++
 .../changelog/templates/compiler.md.tera      |  6 ++
 .../scripts/changelog/templates/debug.md.tera | 10 ++
 .../changelog/templates/docker_image.md.tera  | 11 +++
 .../templates/global_priority.md.tera         | 29 ++++++
 .../changelog/templates/high_priority.md.tera | 39 ++++++++
 .../templates/host_functions.md.tera          | 30 ++++++
 .../changelog/templates/migrations.md.tera    | 14 +++
 .../changelog/templates/pre_release.md.tera   | 11 +++
 .../changelog/templates/runtime.md.tera       | 28 ++++++
 .../changelog/templates/runtimes.md.tera      | 17 ++++
 .../changelog/templates/template.md.tera      | 30 ++++++
 polkadot/scripts/changelog/test/test_basic.rb | 23 +++++
 polkadot/scripts/github/lib.rb                |  8 +-
 27 files changed, 751 insertions(+), 17 deletions(-)
 create mode 100644 polkadot/scripts/changelog/.gitignore
 create mode 100644 polkadot/scripts/changelog/Gemfile
 create mode 100644 polkadot/scripts/changelog/Gemfile.lock
 create mode 100644 polkadot/scripts/changelog/README.md
 create mode 100755 polkadot/scripts/changelog/bin/changelog
 create mode 100644 polkadot/scripts/changelog/digests/.gitignore
 create mode 100644 polkadot/scripts/changelog/digests/.gitkeep
 create mode 100644 polkadot/scripts/changelog/lib/changelog.rb
 create mode 100644 polkadot/scripts/changelog/templates/change.md.tera
 create mode 100644 polkadot/scripts/changelog/templates/changes.md.tera
 create mode 100644 polkadot/scripts/changelog/templates/changes_client.md.tera
 create mode 100644 polkadot/scripts/changelog/templates/changes_misc.md.tera
 create mode 100644 polkadot/scripts/changelog/templates/changes_runtime.md.tera
 create mode 100644 polkadot/scripts/changelog/templates/compiler.md.tera
 create mode 100644 polkadot/scripts/changelog/templates/debug.md.tera
 create mode 100644 polkadot/scripts/changelog/templates/docker_image.md.tera
 create mode 100644 polkadot/scripts/changelog/templates/global_priority.md.tera
 create mode 100644 polkadot/scripts/changelog/templates/high_priority.md.tera
 create mode 100644 polkadot/scripts/changelog/templates/host_functions.md.tera
 create mode 100644 polkadot/scripts/changelog/templates/migrations.md.tera
 create mode 100644 polkadot/scripts/changelog/templates/pre_release.md.tera
 create mode 100644 polkadot/scripts/changelog/templates/runtime.md.tera
 create mode 100644 polkadot/scripts/changelog/templates/runtimes.md.tera
 create mode 100644 polkadot/scripts/changelog/templates/template.md.tera
 create mode 100644 polkadot/scripts/changelog/test/test_basic.rb

diff --git a/polkadot/.github/workflows/publish-draft-release.yml b/polkadot/.github/workflows/publish-draft-release.yml
index 39e2a069189..80da8329267 100644
--- a/polkadot/.github/workflows/publish-draft-release.yml
+++ b/polkadot/.github/workflows/publish-draft-release.yml
@@ -28,6 +28,7 @@ jobs:
     steps:
       - name: Checkout sources
         uses: actions/checkout@v2
+
       - name: Cache target dir
         uses: actions/cache@v2
         with:
@@ -36,6 +37,7 @@ jobs:
           restore-keys: |
             srtool-target-${{ matrix.runtime }}-
             srtool-target-
+
       - name: Build ${{ matrix.runtime }} runtime
         id: srtool_build
         uses: chevdor/srtool-actions@v0.3.0
@@ -43,14 +45,17 @@ jobs:
           # This is the default with chevdor/srtool-actions@v0.3.0 but we make it clear
           image: paritytech/srtool
           chain: ${{ matrix.runtime }}
+
       - name: Store srtool digest to disk
         run: |
           echo '${{ steps.srtool_build.outputs.json }}' | jq > ${{ matrix.runtime }}_srtool_output.json
+
       - name: Upload ${{ matrix.runtime }} srtool json
         uses: actions/upload-artifact@v2
         with:
           name: ${{ matrix.runtime }}-srtool-json
           path: ${{ matrix.runtime }}_srtool_output.json
+
       - name: Upload ${{ matrix.runtime }} runtime
         uses: actions/upload-artifact@v2
         with:
@@ -70,20 +75,59 @@ jobs:
         with:
           fetch-depth: 0
           path: polkadot
-      - name: Set up Ruby 2.7
-        uses: actions/setup-ruby@v1
+
+      - name: Set up Ruby
+        uses: ruby/setup-ruby@v1
         with:
-          ruby-version: 2.7
+          ruby-version: 3.0.0
+
       - name: Download srtool json output
         uses: actions/download-artifact@v2
-      - name: Generate release text
+
+      - name: Prepare tooling
+        run: |
+          cd polkadot/scripts/changelog
+          gem install bundler changelogerator:0.9.1
+          bundle install
+          changelogerator --help
+
+          URL=https://github.com/chevdor/tera-cli/releases/download/v0.2.1/tera-cli_linux_amd64.deb
+          wget $URL -O tera.deb
+          sudo dpkg -i tera.deb
+          tera --version
+
+      - name: Generate release notes
         env:
           RUSTC_STABLE: ${{ needs.get-rust-versions.outputs.rustc-stable }}
           RUSTC_NIGHTLY: ${{ needs.get-rust-versions.outputs.rustc-nightly }}
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          NO_CACHE: 1
+          DEBUG: 1
+          WESTEND_DIGEST: ${{ github.workspace}}/westend-srtool-json/westend_srtool_output.json
+          KUSAMA_DIGEST: ${{ github.workspace}}/kusama-srtool-json/kusama_srtool_output.json
+          POLKADOT_DIGEST: ${{ github.workspace}}/polkadot-srtool-json/polkadot_srtool_output.json
+          PRE_RELEASE: ${{ github.event.inputs.pre_release }}
+          HIDE_SRTOOL_ROCOCO: false
         run: |
-          gem install changelogerator:0.0.16 git toml
-          ruby $GITHUB_WORKSPACE/polkadot/scripts/github/generate_release_text.rb | tee release_text.md
+          find ${{env.GITHUB_WORKSPACE}} -type f -name "*_srtool_output.json"
+          ls -al $WESTEND_DIGEST
+          ls -al $KUSAMA_DIGEST
+          ls -al $POLKADOT_DIGEST
+
+          cd polkadot/scripts/changelog
+
+          ./bin/changelog
+          ls -al release-notes.md
+          ls -al context.json
+
+      - name: Archive artifact context.json
+        uses: actions/upload-artifact@v2
+        with:
+          name: release-notes-context
+          path: |
+            polkadot/scripts/changelog/context.json
+            **/*_srtool_output.json
+
       - name: Create draft release
         id: create-release
         uses: actions/create-release@v1
@@ -92,12 +136,14 @@ jobs:
         with:
           tag_name: ${{ github.ref }}
           release_name: Polkadot ${{ github.ref }}
-          body_path: ./release_text.md
+          body_path: ./polkadot/scripts/changelog/release-notes.md
           draft: true
 
   publish-runtimes:
     runs-on: ubuntu-latest
     needs: ["publish-draft-release"]
+    env:
+      RUNTIME_DIR: runtime
     strategy:
       matrix:
         runtime: ["polkadot", "kusama", "westend"]
@@ -107,16 +153,21 @@ jobs:
       - name: Download artifacts
         uses: actions/download-artifact@v2
       - name: Set up Ruby 2.7
-        uses: actions/setup-ruby@v1
+        uses: ruby/setup-ruby@v1
         with:
-          ruby-version: 2.7
+          ruby-version: 3.0.0
       - name: Get runtime version
         id: get-runtime-ver
         run: |
-          ls
-          ls "${{ matrix.runtime }}-runtime"
-          runtime_ver="$(ruby -e 'require "./scripts/github/lib.rb"; puts get_runtime("${{ matrix.runtime }}")')"
+          echo "require './scripts/github/lib.rb'" > script.rb
+          echo "puts get_runtime(runtime: \"${{ matrix.runtime }}\", runtime_dir: \"$RUNTIME_DIR\")" >> script.rb
+
+          echo "Current folder: $PWD"
+          ls "$RUNTIME_DIR/${{ matrix.runtime }}"
+          runtime_ver=$(ruby script.rb)
+          echo "Found version: >$runtime_ver<"
           echo "::set-output name=runtime_ver::$runtime_ver"
+
       - name: Upload compressed ${{ matrix.runtime }} wasm
         uses: actions/upload-release-asset@v1
         env:
diff --git a/polkadot/scripts/changelog/.gitignore b/polkadot/scripts/changelog/.gitignore
new file mode 100644
index 00000000000..4fbcc523b04
--- /dev/null
+++ b/polkadot/scripts/changelog/.gitignore
@@ -0,0 +1,4 @@
+changelog.md
+*.json
+release*.md
+.env
diff --git a/polkadot/scripts/changelog/Gemfile b/polkadot/scripts/changelog/Gemfile
new file mode 100644
index 00000000000..f2d7c3bd716
--- /dev/null
+++ b/polkadot/scripts/changelog/Gemfile
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+source 'https://rubygems.org'
+
+git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
+
+gem 'octokit', '~> 4'
+
+gem 'git_diff_parser', '~> 3'
+
+gem 'toml', '~> 0.3.0'
+
+gem 'rake', group: :dev
+
+gem 'optparse', '~> 0.1.1'
+
+gem 'logger', '~> 1.4'
+
+gem 'test-unit', group: :dev
+
+gem 'rubocop', group: :dev, require: false
diff --git a/polkadot/scripts/changelog/Gemfile.lock b/polkadot/scripts/changelog/Gemfile.lock
new file mode 100644
index 00000000000..855d7f91a54
--- /dev/null
+++ b/polkadot/scripts/changelog/Gemfile.lock
@@ -0,0 +1,79 @@
+GEM
+  remote: https://rubygems.org/
+  specs:
+    addressable (2.8.0)
+      public_suffix (>= 2.0.2, < 5.0)
+    ast (2.4.2)
+    faraday (1.8.0)
+      faraday-em_http (~> 1.0)
+      faraday-em_synchrony (~> 1.0)
+      faraday-excon (~> 1.1)
+      faraday-httpclient (~> 1.0.1)
+      faraday-net_http (~> 1.0)
+      faraday-net_http_persistent (~> 1.1)
+      faraday-patron (~> 1.0)
+      faraday-rack (~> 1.0)
+      multipart-post (>= 1.2, < 3)
+      ruby2_keywords (>= 0.0.4)
+    faraday-em_http (1.0.0)
+    faraday-em_synchrony (1.0.0)
+    faraday-excon (1.1.0)
+    faraday-httpclient (1.0.1)
+    faraday-net_http (1.0.1)
+    faraday-net_http_persistent (1.2.0)
+    faraday-patron (1.0.0)
+    faraday-rack (1.0.0)
+    git_diff_parser (3.2.0)
+    logger (1.4.4)
+    multipart-post (2.1.1)
+    octokit (4.21.0)
+      faraday (>= 0.9)
+      sawyer (~> 0.8.0, >= 0.5.3)
+    optparse (0.1.1)
+    parallel (1.21.0)
+    parser (3.0.2.0)
+      ast (~> 2.4.1)
+    parslet (2.0.0)
+    power_assert (2.0.1)
+    public_suffix (4.0.6)
+    rainbow (3.0.0)
+    rake (13.0.6)
+    regexp_parser (2.1.1)
+    rexml (3.2.5)
+    rubocop (1.23.0)
+      parallel (~> 1.10)
+      parser (>= 3.0.0.0)
+      rainbow (>= 2.2.2, < 4.0)
+      regexp_parser (>= 1.8, < 3.0)
+      rexml
+      rubocop-ast (>= 1.12.0, < 2.0)
+      ruby-progressbar (~> 1.7)
+      unicode-display_width (>= 1.4.0, < 3.0)
+    rubocop-ast (1.13.0)
+      parser (>= 3.0.1.1)
+    ruby-progressbar (1.11.0)
+    ruby2_keywords (0.0.5)
+    sawyer (0.8.2)
+      addressable (>= 2.3.5)
+      faraday (> 0.8, < 2.0)
+    test-unit (3.5.1)
+      power_assert
+    toml (0.3.0)
+      parslet (>= 1.8.0, < 3.0.0)
+    unicode-display_width (2.1.0)
+
+PLATFORMS
+  x86_64-darwin-20
+
+DEPENDENCIES
+  git_diff_parser (~> 3)
+  logger (~> 1.4)
+  octokit (~> 4)
+  optparse (~> 0.1.1)
+  rake
+  rubocop
+  test-unit
+  toml (~> 0.3.0)
+
+BUNDLED WITH
+   2.2.22
diff --git a/polkadot/scripts/changelog/README.md b/polkadot/scripts/changelog/README.md
new file mode 100644
index 00000000000..7226530c42a
--- /dev/null
+++ b/polkadot/scripts/changelog/README.md
@@ -0,0 +1,71 @@
+# Changelog
+
+Currently, the changelog is built locally. It will be moved to CI once labels stabilize.
+
+For now, a bit of preparation is required before you can run the script:
+- fetch the srtool digests
+- store them under the `digests` folder as `<chain>-srtool-digest.json`
+- ensure the `.env` file is up to date with correct information
+
+The content of the release notes is generated from the template files under the `scripts/changelog/templates` folder. For readability and maintenance, the template is split into several small snippets.
+
+Run:
+```
+./bin/changelog <ref_since> [<ref_until>=HEAD]
+```
+
+For instance:
+```
+./bin/changelog statemine-v5.0.0
+```
+
+A file called `release-notes.md` will be generated and can be used for the release.
+
+## ENV
+
+You may use the following ENV for testing:
+
+```
+RUSTC_STABLE="rustc 1.56.1 (59eed8a2a 2021-11-01)"
+RUSTC_NIGHTLY="rustc 1.57.0-nightly (51e514c0f 2021-09-12)"
+PRE_RELEASE=true
+HIDE_SRTOOL_ROCOCO=true
+HIDE_SRTOOL_SHELL=true
+REF1=statemine-v5.0.0
+REF2=HEAD
+DEBUG=1
+NO_CACHE=1
+```
+## Considered labels
+
+The following list will likely evolve over time and it will be hard to keep it in sync.
+In any case, if you want to find all the labels that are used, search for `meta` in the templates.
+Currently, the considered labels are:
+
+- Priority: C<N> labels
+- Audit: D<N> labels
+- E4 => new host function
+- B0 => silent, not showing up
+- B1-releasenotes (misc unless other labels)
+- B5-client (client changes)
+- B7-runtimenoteworthy (runtime changes)
+- T6-XCM
+
+Note that labels with the same letter are mutually exclusive.
+A PR should not have both `B0` and `B5`, or both `C1` and `C9`. In case of conflicts, the template will
+decide which label will be considered.
+
+## Dev and debuggin
+
+### Hot Reload
+
+The following command allows **Hot Reload**:
+```
+fswatch templates -e ".*\.md$" | xargs -n1 -I{} ./bin/changelog statemine-v5.0.0
+```
+### Caching
+
+By default, if the changelog data from Github is already present, the calls to the Github API will be skipped
+and the local version of the data will be used. This is much faster.
+If you know that some labels have changed in Github, you probably want to refresh the data.
+You can then either delete manually the `cumulus.json` file or `export NO_CACHE=1` to force refreshing the data.
diff --git a/polkadot/scripts/changelog/bin/changelog b/polkadot/scripts/changelog/bin/changelog
new file mode 100755
index 00000000000..9476fc50e6c
--- /dev/null
+++ b/polkadot/scripts/changelog/bin/changelog
@@ -0,0 +1,93 @@
+#!/usr/bin/env ruby
+
+# frozen_string_literal: true
+
+# call for instance as:
+# ./bin/changelog v0.9.13
+#
+# You may set the ENV NO_CACHE to force fetching from Github
+# You should also ensure you set the ENV: GITHUB_TOKEN
+
+require_relative '../lib/changelog'
+require 'logger'
+
+logger = Logger.new($stdout)
+logger.level = Logger::DEBUG
+logger.debug('Starting')
+
+owner = 'paritytech'
+repo = 'polkadot'
+
+gh_polkadot = SubRef.new(format('%<owner>s/%<repo>s', { owner: owner, repo: repo }))
+last_release_ref = gh_polkadot.get_last_ref()
+
+polkadot_ref1 = ARGV[0] || last_release_ref
+polkadot_ref2 = ARGV[1] || 'HEAD'
+output = ARGV[2] || 'release-notes.md'
+
+ENV['REF1'] = polkadot_ref1
+ENV['REF2'] = polkadot_ref2
+
+substrate_ref1 = gh_polkadot.get_dependency_reference(polkadot_ref1, 'sp-io')
+substrate_ref2 = gh_polkadot.get_dependency_reference(polkadot_ref2, 'sp-io')
+
+logger.debug("Polkadot from:   #{polkadot_ref1}")
+logger.debug("Polkadot to:     #{polkadot_ref2}")
+
+logger.debug("Substrate from:  #{substrate_ref1}")
+logger.debug("Substrate to:    #{substrate_ref2}")
+
+substrate_data = 'substrate.json'
+polkadot_data = 'polkadot.json'
+
+logger.debug("Using SUBSTRATE: #{substrate_data}")
+logger.debug("Using POLKADOT:  #{polkadot_data}")
+
+logger.warn('NO_CACHE set') if ENV['NO_CACHE']
+
+if ENV['NO_CACHE'] || !File.file?(polkadot_data)
+  logger.debug(format('Fetching data for Polkadot into %s', polkadot_data))
+  cmd = format('changelogerator %<owner>s/%<repo>s -f %<from>s -t %<to>s > %<output>s',
+               { owner: owner, repo: 'polkadot', from: polkadot_ref1, to: polkadot_ref2, output: polkadot_data })
+  system(cmd)
+else
+  logger.debug("Re-using:#{polkadot_data}")
+end
+
+if ENV['NO_CACHE'] || !File.file?(substrate_data)
+  logger.debug(format('Fetching data for Substrate into %s', substrate_data))
+  cmd = format('changelogerator %<owner>s/%<repo>s -f %<from>s -t %<to>s > %<output>s',
+               { owner: owner, repo: 'substrate', from: substrate_ref1, to: substrate_ref2, output: substrate_data })
+  system(cmd)
+else
+  logger.debug("Re-using:#{substrate_data}")
+end
+
+KUSAMA_DIGEST = ENV['KUSAMA_DIGEST'] || 'digests/kusama_srtool_output.json'
+WESTEND_DIGEST = ENV['WESTEND_DIGEST'] || 'digests/westend_srtool_output.json'
+POLKADOT_DIGEST = ENV['POLKADOT_DIGEST'] || 'digests/polkadot_srtool_output.json'
+
+# Here we compose all the pieces together into one
+# single big json file.
+cmd = format('jq \
+    --slurpfile substrate %s \
+    --slurpfile polkadot %s \
+    --slurpfile srtool_kusama %s \
+    --slurpfile srtool_westend %s \
+    --slurpfile srtool_polkadot %s \
+    -n \'{
+            substrate: $substrate[0],
+            polkadot: $polkadot[0],
+        srtool: [
+        { name: "kusama", data: $srtool_kusama[0] },
+        { name: "westend", data: $srtool_westend[0] },
+        { name: "polkadot", data: $srtool_polkadot[0] }
+    ] }\' > context.json', substrate_data, polkadot_data,
+             KUSAMA_DIGEST,
+             WESTEND_DIGEST,
+             POLKADOT_DIGEST)
+system(cmd)
+
+cmd = format('tera --env --env-key env --include-path templates \
+        --template templates/template.md.tera context.json > %s', output)
+system(cmd)
diff --git a/polkadot/scripts/changelog/digests/.gitignore b/polkadot/scripts/changelog/digests/.gitignore
new file mode 100644
index 00000000000..a6c57f5fb2f
--- /dev/null
+++ b/polkadot/scripts/changelog/digests/.gitignore
@@ -0,0 +1 @@
+*.json
diff --git a/polkadot/scripts/changelog/digests/.gitkeep b/polkadot/scripts/changelog/digests/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/polkadot/scripts/changelog/lib/changelog.rb b/polkadot/scripts/changelog/lib/changelog.rb
new file mode 100644
index 00000000000..e98baf82f01
--- /dev/null
+++ b/polkadot/scripts/changelog/lib/changelog.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+# A Class to find Substrate references
+class SubRef
+  require 'octokit'
+  require 'toml'
+
+  attr_reader :client, :repository
+
+  def initialize(github_repo)
+    @client = Octokit::Client.new(
+      access_token: ENV['GITHUB_TOKEN']
+    )
+    @repository = @client.repository(github_repo)
+  end
+
+  # This function checks the Cargo.lock of a given
+  # Rust project, for a given package, and fetches
+  # the dependency git ref.
+  def get_dependency_reference(ref, package)
+    cargo = TOML::Parser.new(
+      Base64.decode64(
+        @client.contents(
+          @repository.full_name,
+          path: 'Cargo.lock',
+          query: { ref: ref.to_s }
+        ).content
+      )
+    ).parsed
+    cargo['package'].find { |p| p['name'] == package }['source'].split('#').last
+  end
+
+  # Get the git ref of the last release for the repo.
+  # repo is given in the form paritytech/polkadot
+  def get_last_ref()
+    'refs/tags/' + @client.latest_release(@repository.full_name).tag_name
+  end
+end
diff --git a/polkadot/scripts/changelog/templates/change.md.tera b/polkadot/scripts/changelog/templates/change.md.tera
new file mode 100644
index 00000000000..2a22592dbe5
--- /dev/null
+++ b/polkadot/scripts/changelog/templates/change.md.tera
@@ -0,0 +1,42 @@
+{# This macro shows ONE change #}
+{%- macro change(c, cml="[C]", dot="[P]", sub="[S]") -%}
+
+{%- if c.meta.C and c.meta.C.value >= 7 -%}
+{%- set prio = " ‼️ HIGH" -%}
+{%- elif c.meta.C and c.meta.C.value >= 5 -%}
+{%- set prio = " ❗️ Medium" -%}
+{%- elif c.meta.C and c.meta.C.value >= 3 -%}
+{%- set prio = " Low" -%}
+{%- else -%}
+{%- set prio = "" -%}
+{%- endif -%}
+
+{%- set audit = "" -%}
+
+{%- if c.meta.D and c.meta.D.value == 1 -%}
+{%- set audit = "✅ audited " -%}
+{%- elif c.meta.D and c.meta.D.value == 2 -%}
+{%- set audit = "✅ trivial " -%}
+{%- elif c.meta.D and c.meta.D.value == 3 -%}
+{%- set audit = "✅ trivial " -%}
+{%- elif c.meta.D and c.meta.D.value == 5 -%}
+{%- set audit = "⏳ pending non-critical audit " -%}
+{%- else -%}
+{%- set audit = "" -%}
+{%- endif -%}
+
+{%- if c.html_url is containing("polkadot") -%}
+{%- set repo = dot -%}
+{%- elif c.html_url is containing("substrate") -%}
+{%- set repo = sub -%}
+{%- else -%}
+{%- set repo = "   " -%}
+{%- endif -%}
+
+{%- if c.meta.T and c.meta.T.value == 6 -%}
+{%- set xcm = " [✉️ XCM]" -%}
+{%- else -%}
+{%- set xcm = "" -%}
+{%- endif -%}
+{{- repo }} {{ audit }}[`#{{c.number}}`]({{c.html_url}}) {{- prio }} - {{ c.title | capitalize | truncate(length=60, end="…") }}{{xcm }}
+{%- endmacro change -%}
diff --git a/polkadot/scripts/changelog/templates/changes.md.tera b/polkadot/scripts/changelog/templates/changes.md.tera
new file mode 100644
index 00000000000..67124430ea6
--- /dev/null
+++ b/polkadot/scripts/changelog/templates/changes.md.tera
@@ -0,0 +1,13 @@
+{# This include generates the section showing the changes #}
+## Changes
+
+### Legend
+
+- {{ DOT }} Polkadot
+- {{ SUB }} Substrate
+
+{% include "changes_client.md.tera" %}
+
+{% include "changes_runtime.md.tera" %}
+
+{% include "changes_misc.md.tera" %}
diff --git a/polkadot/scripts/changelog/templates/changes_client.md.tera b/polkadot/scripts/changelog/templates/changes_client.md.tera
new file mode 100644
index 00000000000..36fb6b9de68
--- /dev/null
+++ b/polkadot/scripts/changelog/templates/changes_client.md.tera
@@ -0,0 +1,17 @@
+{% import "change.md.tera" as m_c -%}
+### Client
+
+{#- The changes are sorted by merge date #}
+{%- for pr in changes | sort(attribute="merged_at") %}
+
+{%- if pr.meta.B %}
+    {%- if pr.meta.B.value == 0 %}
+    {#- We skip silent ones -#}
+    {%- else -%}
+
+        {%- if pr.meta.B.value == 5 and not pr.title is containing("ompanion") %}
+- {{ m_c::change(c=pr) }}
+        {%- endif -%}
+    {% endif -%}
+    {% endif -%}
+{% endfor %}
diff --git a/polkadot/scripts/changelog/templates/changes_misc.md.tera b/polkadot/scripts/changelog/templates/changes_misc.md.tera
new file mode 100644
index 00000000000..7585eb8bc52
--- /dev/null
+++ b/polkadot/scripts/changelog/templates/changes_misc.md.tera
@@ -0,0 +1,39 @@
+{%- import "change.md.tera" as m_c -%}
+
+{%- set_global misc_count = 0 -%}
+{#- First pass to count #}
+{%- for pr in changes -%}
+    {%- if pr.meta.B %}
+        {%- if pr.meta.B.value == 0 -%}
+        {#- We skip silent ones -#}
+        {%- else -%}
+            {%- if pr.meta.B and pr.meta.B.value != 5 and pr.meta.B.value != 7 or pr.meta.C or not pr.meta.B %}
+{%- set_global misc_count = misc_count + 1 -%}
+            {%- endif -%}
+        {% endif -%}
+    {% endif -%}
+{% endfor %}
+
+### Misc
+
+{% if misc_count > 10 %}
+There are other misc. changes. You can expand the list below to view them all.
+<details><summary>Other misc. changes</summary>
+{% endif -%}
+
+{#- The changes are sorted by merge date #}
+{%- for pr in changes | sort(attribute="merged_at") %}
+    {%- if pr.meta.B and not pr.title is containing("ompanion") %}
+        {%- if pr.meta.B.value == 0 %}
+        {#- We skip silent ones -#}
+        {%- else -%}
+            {%- if pr.meta.B and pr.meta.B.value != 5 and pr.meta.B.value != 7 or pr.meta.C or not pr.meta.B %}
+- {{ m_c::change(c=pr) }}
+            {%- endif -%}
+        {% endif -%}
+    {% endif -%}
+{% endfor %}
+
+{% if misc_count > 10 %}
+</details>
+{% endif -%}
diff --git a/polkadot/scripts/changelog/templates/changes_runtime.md.tera b/polkadot/scripts/changelog/templates/changes_runtime.md.tera
new file mode 100644
index 00000000000..67da1ebbc39
--- /dev/null
+++ b/polkadot/scripts/changelog/templates/changes_runtime.md.tera
@@ -0,0 +1,19 @@
+{%- import "change.md.tera" as m_c -%}
+
+### Runtime
+
+{#- The changes are sorted by merge date -#}
+{% for pr in changes | sort(attribute="merged_at") -%}
+
+{%- if pr.meta.B -%}
+{%- if pr.meta.B.value == 0 -%}
+{#- We skip silent ones -#}
+{%- else -%}
+
+{%- if pr.meta.B.value == 7 and not pr.title is containing("ompanion") %}
+- {{ m_c::change(c=pr) }}
+{%- endif -%}
+{%- endif -%}
+
+{%- endif -%}
+{%- endfor %}
diff --git a/polkadot/scripts/changelog/templates/compiler.md.tera b/polkadot/scripts/changelog/templates/compiler.md.tera
new file mode 100644
index 00000000000..0420a88c396
--- /dev/null
+++ b/polkadot/scripts/changelog/templates/compiler.md.tera
@@ -0,0 +1,6 @@
+## Rust compiler versions
+
+This release was tested against the following versions of `rustc`. Other versions may work.
+
+- Rust Stable: `{{ env.RUSTC_STABLE }}`
+- Rust Nightly: `{{ env.RUSTC_NIGHTLY }}`
diff --git a/polkadot/scripts/changelog/templates/debug.md.tera b/polkadot/scripts/changelog/templates/debug.md.tera
new file mode 100644
index 00000000000..29ac673a394
--- /dev/null
+++ b/polkadot/scripts/changelog/templates/debug.md.tera
@@ -0,0 +1,10 @@
+{%- set to_ignore = changes | filter(attribute="meta.B.value", value=0) %}
+
+<!--
+## Debug
+
+changes:
+ - total: {{ changes | length }}
+ - silent: {{ to_ignore | length }}
+ - remaining: {{ changes | length - to_ignore | length }}
+-->
diff --git a/polkadot/scripts/changelog/templates/docker_image.md.tera b/polkadot/scripts/changelog/templates/docker_image.md.tera
new file mode 100644
index 00000000000..59f631106b5
--- /dev/null
+++ b/polkadot/scripts/changelog/templates/docker_image.md.tera
@@ -0,0 +1,11 @@
+<!--
+## Docker images
+
+The docker image for this release can be found in [Docker hub](https://hub.docker.com/r/parity/polkadot-collator/tags?page=1&ordering=last_updated).
+
+You may also pull it with:
+
+```
+docker pull parity/polkadot-collator:latest
+```
+-->
diff --git a/polkadot/scripts/changelog/templates/global_priority.md.tera b/polkadot/scripts/changelog/templates/global_priority.md.tera
new file mode 100644
index 00000000000..87fa0e3b9eb
--- /dev/null
+++ b/polkadot/scripts/changelog/templates/global_priority.md.tera
@@ -0,0 +1,29 @@
+{% import "high_priority.md.tera" as m_p -%}
+## Global Priority
+
+{%- set polkadot_prio = 0 -%}
+{%- set substrate_prio = 0 -%}
+
+{# We fetch the various priorities #}
+{%- if polkadot.meta.C -%}
+    {%- set polkadot_prio = polkadot.meta.C.max -%}
+{%- endif -%}
+{%- if substrate.meta.C -%}
+    {%- set substrate_prio = substrate.meta.C.max -%}
+{%- endif -%}
+
+{# We compute the global priority #}
+{%- set global_prio = polkadot_prio -%}
+{%- if substrate_prio > global_prio -%}
+    {%- set global_prio = substrate_prio -%}
+{%- endif -%}
+
+{# We show the result #}
+{{ m_p::high_priority(p=global_prio, changes=changes) }}
+
+<!--
+- Polkadot: {{ polkadot_prio }}
+- Substrate: {{ substrate_prio }}
+-->
+
+{# todo: show high prio list here #}
diff --git a/polkadot/scripts/changelog/templates/high_priority.md.tera b/polkadot/scripts/changelog/templates/high_priority.md.tera
new file mode 100644
index 00000000000..2bb2374b728
--- /dev/null
+++ b/polkadot/scripts/changelog/templates/high_priority.md.tera
@@ -0,0 +1,39 @@
+{%- import "change.md.tera" as m_c -%}
+
+{# This macro convert a priority level into readable output #}
+{%- macro high_priority(p, changes) -%}
+
+{%- if p >= 7 -%}
+    {%- set prio = "‼️ HIGH" -%}
+    {%- set text = "This is a **high priority** release and you must upgrade as as soon as possible." -%}
+{%- elif p >= 5 -%}
+    {%- set prio = "❗️ Medium" -%}
+    {%- set text = "This is a medium priority release and you should upgrade in a timely manner." -%}
+{%- else -%}
+    {%- set prio = "Low" -%}
+    {%- set text = "This is a low priority release and you may upgrade at your convenience." -%}
+{%- endif %}
+
+
+{%- if prio %}
+{{prio}}: {{text}}
+
+{% if p >= 3 %}
+The changes motivating this priority level are:
+
+{% for pr in changes | sort(attribute="merged_at") -%}
+    {%- if pr.meta.C -%}
+        {%- if pr.meta.C.value == p %}
+- {{ m_c::change(c=pr) }}
+{%- if pr.meta.B and pr.meta.B.value == 7 %}
+(RUNTIME)
+{% endif %}
+        {%- endif -%}
+    {%- endif -%}
+{%- endfor %}
+{%- else -%}
+<!-- No relevant Priority label as been detected for p={{ p }} -->
+{%- endif -%}
+{%- endif -%}
+
+{%- endmacro priority -%}
diff --git a/polkadot/scripts/changelog/templates/host_functions.md.tera b/polkadot/scripts/changelog/templates/host_functions.md.tera
new file mode 100644
index 00000000000..9f954078da2
--- /dev/null
+++ b/polkadot/scripts/changelog/templates/host_functions.md.tera
@@ -0,0 +1,30 @@
+{%- import "change.md.tera" as m_c -%}
+{%- set_global host_fn_count = 0 -%}
+
+{% for pr in changes | sort(attribute="merged_at") -%}
+
+{%- if pr.meta.B and pr.meta.B.value == 0 -%}
+{#- We skip silent ones -#}
+{%- else -%}
+    {%- if pr.meta.E and pr.meta.E.value == 4 -%}
+        {%- set_global host_fn_count = host_fn_count + 1 -%}
+            - {{ m_c::change(c=pr) }}
+        {% endif -%}
+    {% endif -%}
+{%- endfor -%}
+
+<!-- {{ host_fn_count }} host functions were detected -->
+
+{%- if host_fn_count == 0 -%}
+<!-- ℹ️ This release does not contain any new host functions. -->
+{% elif host_fn_count == 1 -%}
+## Host functions
+
+⚠️ The runtimes in this release contain one new **host function**.
+
+⚠️ It is critical that you update your client before the chain switches to the new runtimes.
+{%- else -%}
+⚠️ The runtimes in this release contain {{ host_fn_count }} new **host function{{ host_fn_count | pluralize }}**.
+
+⚠️ It is critical that you update your client before the chain switches to the new runtimes.
+{%- endif %}
diff --git a/polkadot/scripts/changelog/templates/migrations.md.tera b/polkadot/scripts/changelog/templates/migrations.md.tera
new file mode 100644
index 00000000000..af04821a2e1
--- /dev/null
+++ b/polkadot/scripts/changelog/templates/migrations.md.tera
@@ -0,0 +1,14 @@
+{%- import "change.md.tera" as m_c %}
+
+## Migrations
+
+{% for pr in changes | sort(attribute="merged_at") -%}
+
+{%- if pr.meta.B and pr.meta.B.value == 0 %}
+{#- We skip silent ones -#}
+{%- else -%}
+{%- if pr.meta.E and pr.meta.E.value == 1 -%}
+- {{ m_c::change(c=pr) }}
+{% endif -%}
+{% endif -%}
+{% endfor -%}
diff --git a/polkadot/scripts/changelog/templates/pre_release.md.tera b/polkadot/scripts/changelog/templates/pre_release.md.tera
new file mode 100644
index 00000000000..53a0e906541
--- /dev/null
+++ b/polkadot/scripts/changelog/templates/pre_release.md.tera
@@ -0,0 +1,11 @@
+{%- if env.PRE_RELEASE == "true" -%}
+<details><summary>⚠️ This is a pre-release</summary>
+
+**Release candidates** are **pre-releases** may not be final.
+Although they are reasonably tested, there may be additional changes or issues
+before an official release is tagged. Use at your own discretion, and consider
+only using published releases on critical production infrastructure.
+</details>
+{% else -%}
+<!-- NOT a pre-release-->
+{%- endif %}
diff --git a/polkadot/scripts/changelog/templates/runtime.md.tera b/polkadot/scripts/changelog/templates/runtime.md.tera
new file mode 100644
index 00000000000..85ea9cfb836
--- /dev/null
+++ b/polkadot/scripts/changelog/templates/runtime.md.tera
@@ -0,0 +1,28 @@
+{# This macro shows one runtime #}
+{%- macro runtime(runtime) -%}
+
+### {{ runtime.name | capitalize }}
+
+{%- if runtime.data.runtimes.compressed.subwasm.compression.compressed %}
+{%- set compressed = "Yes" %}
+{%- else %}
+{%- set compressed = "No" %}
+{%- endif %}
+
+{%- set comp_ratio = 100 - (runtime.data.runtimes.compressed.subwasm.compression.size_compressed / runtime.data.runtimes.compressed.subwasm.compression.size_decompressed *100) %}
+<!-- commit : {{ runtime.data.commit }} -->
+<!-- tag    : {{ runtime.data.tag }} -->
+<!-- branch : {{ runtime.data.branch }} -->
+<!-- pkg    : {{ runtime.data.pkg }} -->
+
+```
+🏋️ Runtime Size:		{{ runtime.data.runtimes.compressed.subwasm.size | filesizeformat }} ({{ runtime.data.runtimes.compressed.subwasm.size }} bytes)
+🔥 Core Version:		{{ runtime.data.runtimes.compressed.subwasm.core_version }}
+🗜 Compressed:			{{ compressed }}: {{ comp_ratio | round(method="ceil", precision=2) }}%
+🎁 Metadata version:		V{{ runtime.data.runtimes.compressed.subwasm.metadata_version }}
+🗳️ system.setCode hash:		{{ runtime.data.runtimes.compressed.subwasm.proposal_hash }}
+🗳️ authorizeUpgrade hash:	{{ runtime.data.runtimes.compressed.subwasm.parachain_authorize_upgrade_hash }}
+#️⃣ Blake2-256 hash:		{{ runtime.data.runtimes.compressed.subwasm.blake2_256 }}
+📦 IPFS:			{{ runtime.data.runtimes.compressed.subwasm.ipfs_hash }}
+```
+{%- endmacro runtime %}
\ No newline at end of file
diff --git a/polkadot/scripts/changelog/templates/runtimes.md.tera b/polkadot/scripts/changelog/templates/runtimes.md.tera
new file mode 100644
index 00000000000..600c5f17dc9
--- /dev/null
+++ b/polkadot/scripts/changelog/templates/runtimes.md.tera
@@ -0,0 +1,17 @@
+{# This include shows the list and details of the runtimes #}
+{%- import "runtime.md.tera" as m_r -%}
+
+## Runtimes
+
+{% set rtm = srtool[0] -%}
+
+The information about the runtimes included in this release can be found below.
+The runtimes have been built using [{{ rtm.data.gen }}](https://github.com/paritytech/srtool) and `{{ rtm.data.rustc }}`.
+
+{%- for runtime in srtool | sort(attribute="name") %}
+{%- set HIDE_VAR = "HIDE_SRTOOL_" ~ runtime.name | upper %}
+{%- if not env is containing(HIDE_VAR) %}
+
+{{ m_r::runtime(runtime=runtime) }}
+{%- endif %}
+{%- endfor %}
diff --git a/polkadot/scripts/changelog/templates/template.md.tera b/polkadot/scripts/changelog/templates/template.md.tera
new file mode 100644
index 00000000000..00040a43669
--- /dev/null
+++ b/polkadot/scripts/changelog/templates/template.md.tera
@@ -0,0 +1,30 @@
+{# This is the entry point of the template -#}
+<!-- repository: {{ polkadot.repository.name }} -->
+{% include "pre_release.md.tera" -%}
+
+{% if env.PRE_RELEASE == "true" -%}
+This pre-release contains the changes from `{{ env.REF1 }}` to `{{ env.REF2 }}`.
+{%- else -%}
+This release contains the changes from `{{ env.REF1 }}` to `{{ env.REF2 }}`.
+{% endif -%}
+
+{%- set changes = polkadot.changes | concat(with=substrate.changes) -%}
+{%- include "debug.md.tera" -%}
+
+{%- set CML = "[C]" -%}
+{%- set DOT = "[P]" -%}
+{%- set SUB = "[S]" -%}
+
+{% include "global_priority.md.tera" -%}
+
+{% include "host_functions.md.tera" -%}
+
+{% include "compiler.md.tera" -%}
+
+{% include "migrations.md.tera" -%}
+
+{% include "runtimes.md.tera" -%}
+
+{% include "changes.md.tera" -%}
+
+{% include "docker_image.md.tera" -%}
diff --git a/polkadot/scripts/changelog/test/test_basic.rb b/polkadot/scripts/changelog/test/test_basic.rb
new file mode 100644
index 00000000000..d099fadca43
--- /dev/null
+++ b/polkadot/scripts/changelog/test/test_basic.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require_relative '../lib/changelog'
+require 'test/unit'
+
+class TestChangelog < Test::Unit::TestCase
+  def test_get_dep_ref_polkadot
+    c = SubRef.new('paritytech/polkadot')
+    ref = '13c2695'
+    package = 'sc-cli'
+    result = c.get_dependency_reference(ref, package)
+    assert_equal('7db0768a85dc36a3f2a44d042b32f3715c00a90d', result)
+  end
+
+  def test_get_dep_ref_invalid_ref
+    c = SubRef.new('paritytech/polkadot')
+    ref = '9999999'
+    package = 'sc-cli'
+    assert_raise do
+      c.get_dependency_reference(ref, package)
+    end
+  end
+end
diff --git a/polkadot/scripts/github/lib.rb b/polkadot/scripts/github/lib.rb
index 35ebd3b6e7a..14663acaf31 100644
--- a/polkadot/scripts/github/lib.rb
+++ b/polkadot/scripts/github/lib.rb
@@ -1,12 +1,10 @@
 # frozen_string_literal: true
 
-# A collection of helper functions that might be useful for various scripts
-
-# Gets the runtime version for a given runtime.
+# Gets the runtime version for a given runtime from the filesystem.
 # Optionally accepts a path that is the root of the project which defaults to
 # the current working directory
-def get_runtime(runtime, path = '.')
-  File.open(path + "/runtime/#{runtime}/src/lib.rs") do |f|
+def get_runtime(runtime: nil, path: '.', runtime_dir: 'runtime')
+  File.open(path + "/#{runtime_dir}/#{runtime}/src/lib.rs") do |f|
     f.find { |l| l =~ /spec_version/ }.match(/[0-9]+/)[0]
   end
 end
-- 
GitLab