diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000000000000000000000000000000000000..1cda54be937849072650e8a9ad7ae3ffcdf5a8d0
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1 @@
+*.yml
diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index 8de31b04363a66be184bbf2b38e4443112c8f56b..0000000000000000000000000000000000000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,10 +0,0 @@
-// This file does not strictly need to exist in contrib, because eslint config
-// files placed futher up in the directory structure will be merged and
-// inherited. The top-level .eslintrc.json extends ./core/.eslintrc.json.
-// However there is no 'sniff all files' argument for eslint in MR testing, and
-// a bug in the testbot script means that any changed .yml files are not checked
-// in patch or MR tests. However, having this file and making a change to it
-// will cause all js and yml files to be checked.
-// See https://www.drupal.org/project/drupalci_testbot/issues/3333051
-
-{}
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..32ae6b67494ee0aa04dd2613486a16dd27bee593
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,50 @@
+################
+# DrupalCI GitLabCI template
+#
+# Gitlab-ci.yml to replicate DrupalCI testing for Contrib
+#
+# With thanks to:
+#   * The GitLab Acceleration Initiative participants
+#   * DrupalSpoons
+################
+
+################
+# Guidelines
+#
+# This template is designed to give any Contrib maintainer everything they need to test, without requiring modification.
+# It is also designed to keep up to date with Core Development automatically through the use of include files that can
+# be centrally maintained.
+#
+# However, you can modify this template if you have additional needs for your project.
+################
+
+################
+# Includes
+#
+# Additional configuration can be provided through includes.
+# One advantage of include files is that if they are updated upstream, the changes affect all pipelines using that include.
+#
+# Includes can be overridden by re-declaring anything provided in an include, here in gitlab-ci.yml
+# https://docs.gitlab.com/ee/ci/yaml/includes.html#override-included-configuration-values
+################
+
+include:
+  ################
+  # DrupalCI includes:
+  # As long as you include this, any future includes added by the Drupal Association will be accessible to your pipelines automatically.
+  # View these include files at https://git.drupalcode.org/project/gitlab_templates/
+  ################
+  - project: $_GITLAB_TEMPLATES_REPO
+    ref: $_GITLAB_TEMPLATES_REF
+    file:
+      - '/includes/include.drupalci.main.yml'
+      - '/includes/include.drupalci.variables.yml'
+      - '/includes/include.drupalci.workflows.yml'
+
+################
+# Pipeline configuration variables
+#
+# These are the variables provided to the Run Pipeline form that a user may want to override.
+#
+# Docs at https://git.drupalcode.org/project/gitlab_templates/-/blob/1.0.x/includes/include.drupalci.variables.yml
+################
diff --git a/css/module_filter.css b/css/module_filter.css
index d7f93079f2f1532bc8000e430f299070dd22123f..c59fa3f31839e91f2e1255375c7414cb64f75611 100644
--- a/css/module_filter.css
+++ b/css/module_filter.css
@@ -8,26 +8,26 @@
 .winnow-clear {
   position: absolute;
   top: 0;
-  bottom: 0;
   right: 2px;
+  bottom: 0;
   left: auto;
   text-align: left;
   text-indent: -9999px;
 }
-.winnow-clear:after {
+.winnow-clear::after {
   position: absolute;
   right: 0;
-  padding: 0 0.5em;
   height: 100%;
+  padding: 0 0.5em;
   content: '✕';
-  color: #aaa;
-  font: 1em/2.2em arial, sans-serif;
   text-decoration: none;
-  text-shadow: 0 1px 0 #fff;
   text-indent: 0;
   opacity: 0.7;
+  color: #aaa;
+  text-shadow: 0 1px 0 #fff;
+  font: 1em/2.2em arial, sans-serif;
 }
-.winnow-clear:hover:after {
+.winnow-clear:hover::after {
   opacity: 1;
 }
 .admin-missing {
diff --git a/css/module_filter.modules_tabs.css b/css/module_filter.modules_tabs.css
index 39d04fbe90607ce6291e173410a7752d57da9776..62b88c6b880b84a58eaa8ac668e56447a4328334 100644
--- a/css/module_filter.modules_tabs.css
+++ b/css/module_filter.modules_tabs.css
@@ -5,9 +5,9 @@
   position: relative;
   overflow: hidden;
   margin: 10px 0;
-  background: #e6e5e1;
   border: 1px solid #bdbdbd;
   border-radius: 4px;
+  background: #e6e5e1;
 }
 .modules-tabs__menu {
   float: left; /* LTR */
@@ -22,8 +22,8 @@
   margin: 0 0 -1px -100%;
 }
 .modules-tabs__menu-item {
-  background: #eee;
   border-top: 1px solid #ccc;
+  background: #eee;
 }
 .modules-tabs__menu-item:first-child {
   border-top: 0 none;
@@ -36,35 +36,35 @@
   background: #f9f9f9;
 }
 .modules-tabs__menu-item.disabled {
-  background: #ccc;
   border-top-color: #bbb;
   border-bottom-color: #bbb;
+  background: #ccc;
 }
 .modules-tabs__menu-item a {
   display: block;
-  text-decoration: none;
   padding: 10px;
+  text-decoration: none;
 }
 .modules-tabs__menu-item a:hover,
 .modules-tabs__menu-item a:focus {
-  background: #d5d5d5;
   text-decoration: none;
   outline: 0;
+  background: #d5d5d5;
 }
 .modules-tabs__menu-item.is-selected a,
 .modules-tabs__menu-item.is-selected a:hover,
 .modules-tabs__menu-item.is-selected a:focus,
 .modules-tabs__menu-item.is-selected a:active {
-  background-color: #fcfcfa;
   margin-right: -1px;
+  background-color: #fcfcfa;
 }
 .modules-tabs__menu-item.disabled a,
 .modules-tabs__menu-item.disabled span {
   color: #999;
 }
 .modules-tabs__menu-item.disabled a {
-  pointer-events: none;
   cursor: default;
+  pointer-events: none;
 }
 .modules-tabs__menu-item .summary {
   padding-top: 0.4em;
@@ -78,7 +78,7 @@
   font-size: 0.75em;
   font-weight: bold;
 }
-.modules-tabs__menu-item ul.enabling:before {
+.modules-tabs__menu-item ul.enabling::before {
   content: '+';
 }
 .modules-tabs__menu-item span.result {
@@ -93,18 +93,18 @@
 .modules-tabs__pane {
   margin: 0 0 0 240px; /* LTR */
   padding: 10px 15px;
-  background: #fcfcfa;
   border-left: 1px solid #a6a5a1; /* LTR */
+  background: #fcfcfa;
 }
 [dir="rtl"] .modules-tabs__pane {
   margin: 0 240px 0 0;
-  border-left: none;
   border-right: 1px solid #a6a5a1;
+  border-left: none;
 }
-.modules-tabs__pane:after {
-  content: '';
+.modules-tabs__pane::after {
   display: table;
   clear: both;
+  content: '';
 }
 .modules-tabs__pane input.table-filter-text {
   width: 100%;
diff --git a/module_filter.module b/module_filter.module
index afdd1bd29cae26abeac0f246dd72122067ea5a44..6d79a00a932807cfb4331c79bd31b3b585e7fa8d 100644
--- a/module_filter.module
+++ b/module_filter.module
@@ -5,9 +5,9 @@
  * Provides a filtering mechanism to various admin pages.
  */
 
-use Drupal\Core\Render\Element;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Link;
+use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;