summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-07-20 15:40:28 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-07-20 15:40:28 +0000
commitb595cb0c1dec83de5bdee18284abe86614bed33b (patch)
tree8c3d4540f193c5ff98019352f554e921b3a41a72 /scripts
parent2f9104a328fc8a4bddeaa4627b595166d24671d0 (diff)
downloadgitlab-ce-b595cb0c1dec83de5bdee18284abe86614bed33b.tar.gz
Add latest changes from gitlab-org/gitlab@15-2-stable-eev15.2.0-rc42
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/checkout-mr-source-sha7
-rwxr-xr-xscripts/decomposition/generate-loose-foreign-key138
-rwxr-xr-xscripts/determine-qa-tests106
-rw-r--r--scripts/frontend/stylelint/stylelint_duplicate_selectors.js25
-rw-r--r--scripts/frontend/stylelint/stylelint_utility_classes.js25
-rw-r--r--scripts/frontend/stylelint/stylelint_utility_map.js64
-rw-r--r--scripts/frontend/stylelint/stylelint_utils.js78
-rw-r--r--scripts/frontend/stylelint/utility_classes_map.js259
-rwxr-xr-xscripts/glfm/run-snapshot-tests.sh7
-rw-r--r--scripts/lib/glfm/constants.rb2
-rw-r--r--scripts/lib/glfm/parse_examples.rb59
-rw-r--r--scripts/lib/glfm/render_wysiwyg_html_and_json.js112
-rw-r--r--scripts/lib/glfm/update_example_snapshots.rb21
-rwxr-xr-xscripts/pipeline_test_report_builder.rb7
-rwxr-xr-xscripts/review_apps/gcp_cleanup.sh2
-rwxr-xr-xscripts/review_apps/review-apps.sh1
-rw-r--r--scripts/rspec_helpers.sh5
-rwxr-xr-xscripts/undercoverage26
-rw-r--r--scripts/utils.sh5
19 files changed, 231 insertions, 718 deletions
diff --git a/scripts/checkout-mr-source-sha b/scripts/checkout-mr-source-sha
new file mode 100755
index 00000000000..962e3f1348d
--- /dev/null
+++ b/scripts/checkout-mr-source-sha
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+if [ -n "$CI_MERGE_REQUEST_SOURCE_BRANCH_SHA" ]; then
+ echo "Checking out \$CI_MERGE_REQUEST_SOURCE_BRANCH_SHA ($CI_MERGE_REQUEST_SOURCE_BRANCH_SHA) instead of \$CI_COMMIT_SHA (merge result commit $CI_COMMIT_SHA) so that code is in sync with gitlab images built upstream."
+ echo "See https://docs.gitlab.com/ee/development/testing_guide/end_to_end/index.html#with-pipeline-for-merged-results for more details."
+ git checkout -f ${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}
+fi
diff --git a/scripts/decomposition/generate-loose-foreign-key b/scripts/decomposition/generate-loose-foreign-key
index 35f84c64ce1..3f4c510020a 100755
--- a/scripts/decomposition/generate-loose-foreign-key
+++ b/scripts/decomposition/generate-loose-foreign-key
@@ -6,10 +6,8 @@
require 'optparse'
$options = {
- milestone: "#{Gitlab.version_info.major}.#{Gitlab.version_info.minor}",
cross_schema: false,
dry_run: false,
- branch: true,
rspec: true
}
@@ -24,18 +22,10 @@ OptionParser.new do |opts|
$options[:dry_run] = v
end
- opts.on("-b", "--[no-]branch", "Create or not a new branch") do |v|
- $options[:branch] = v
- end
-
opts.on("-r", "--[no-]rspec", "Create or not a rspecs automatically") do |v|
$options[:rspec] = v
end
- opts.on("-m", "--milestone MILESTONE", "Specify custom milestone (current: #{$options[:milestone]})") do |v|
- $options[:milestone] = v
- end
-
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
@@ -50,9 +40,7 @@ unless system("git diff --quiet")
raise "There are uncommitted changes. Commit to continue."
end
-if Gitlab::Database.database_base_models.many?
- raise 'Cannot run in multiple-databases mode. Use only `main:` in `config/database.yml`.'
-end
+$files_affected = []
puts "Re-creating current test database"
ActiveRecord::Tasks::DatabaseTasks.drop_current
@@ -86,6 +74,18 @@ def exec_cmd(*args, fail: nil)
false
end
+def write_file(file_path, content)
+ $files_affected << file_path
+ File.write(file_path, content)
+end
+
+def print_files_affected
+ puts "The following files have been generated/modified:"
+ $files_affected.each do |filepath|
+ puts filepath
+ end
+end
+
def has_lfk?(definition)
Gitlab::Database::LooseForeignKeys.definitions.any? do |lfk_definition|
lfk_definition.from_table == definition.from_table &&
@@ -147,12 +147,10 @@ def add_definition_to_yaml(definition)
end
# emulate existing formatting
- File.write(
+ write_file(
Rails.root.join('config/gitlab_loose_foreign_keys.yml'),
content.to_yaml.gsub(/^([- ] )/, ' \1')
)
-
- exec_cmd("git", "add", "config/gitlab_loose_foreign_keys.yml")
end
def generate_migration(definition)
@@ -185,12 +183,9 @@ def generate_migration(definition)
end
EOF
- File.write(migration_name, content)
+ write_file(migration_name, content)
- exec_cmd("git", "add", migration_name, fail: "Failed to add migration file.")
exec_cmd("bin/rails", "db:migrate", fail: "Failed to run db:migrate.")
- exec_cmd("git", "add", "db/schema_migrations/#{timestamp}", "db/structure.sql", fail: "There are uncommitted changes. We should not have any.")
- exec_cmd("git diff --exit-code --name-only", fail: "There are uncommitted changes. We should not have any.")
end
def class_by_table_name
@@ -232,33 +227,7 @@ def add_test_to_specs(definition)
lines = File.readlines(spec_path)
insert_line = lines.count - 1
lines.insert(insert_line, "\n", *spec_test.lines)
- File.write(spec_path, lines.join(""))
-
- # find a matching line
- test_lines = (1..lines.count).select do |line|
- lines[line-1].include?("it_behaves_like 'cleanup by a loose foreign key' do")
- end.join(":")
-
- loop do
- if system("bin/rspec", "#{spec_path}:#{test_lines}")
- puts "Test seems fine?"
- break
- end
-
- puts "--------------------------------------------------"
- puts "Test failed:"
- puts "Edit: vim #{spec_path} (lines #{test_lines})"
- puts "Re-run: bin/rspec #{spec_path}:#{test_lines}"
- puts "--------------------------------------------------"
- puts "Running bash. To exit do 'Ctrl-D' to re-run, or do 'Ctrl-C' to break (and ignore failure)."
- puts
-
- unless exec_cmd("bash")
- break
- end
- end
-
- exec_cmd("git", "add", spec_path, fail: "There are uncommitted changes. We should not have any.")
+ write_file(spec_path, lines.join(""))
end
def update_no_cross_db_foreign_keys_spec(definition)
@@ -274,76 +243,7 @@ def update_no_cross_db_foreign_keys_spec(definition)
return
end
- File.write(spec_path, updated.join(""))
- exec_cmd("git", "add", spec_path, fail: "Failed to add changes from #{spec_path}")
-end
-
-def commit_changes(definition)
- branch_name = "remove-#{definition.to_table}_#{definition.from_table}_#{definition.column}-fk"
- commit_title = "Swap FK #{definition.from_table} to #{definition.to_table} for LFK"
- mr_title = "Swap FK #{definition.from_table}.#{definition.column} to #{definition.to_table} for LFK"
- description = <<-EOF.strip_heredoc
- Swaps FK for #{definition.from_table}.#{definition.column} to #{definition.to_table}
-
- Changelog: changed
- EOF
-
- commit_message = "#{commit_title}\n\n#{description}"
-
- existing_branch = %x[git rev-parse --abbrev-ref HEAD].strip
-
- if $options[:branch]
- unless exec_cmd("git", "checkout", "-b", branch_name)
- raise "Failed to create branch: #{branch_name}"
- end
- end
-
- unless exec_cmd("git", "commit", "-m", commit_message)
- raise "Failed to commit changes."
- end
-
- if $options[:branch]
- exec_cmd("git", "push", "origin", "-u", "HEAD",
- "-o", "merge_request.create",
- "-o", "merge_request.target=#{existing_branch}",
- "-o", "merge_request.milestone=#{$options[:milestone]}",
- "-o", "merge_request.title=#{mr_title}"
- )
-
- puts
- puts "--------------------------------------------------"
- puts "Put this as MR description:"
- puts "--------------------------------------------------"
- puts <<-EOF.strip_heredoc
- ## What does this MR do and why?
-
- Per https://gitlab.com/groups/gitlab-org/-/epics/7249
-
- As part of our CI "decomposition" efforts we need to remove all foreign keys that are cross-database (ie. between the planned \`main\` and \`ci\` databases). We are going to replace them all with ["loose foreign keys"](https://docs.gitlab.com/ee/development/database/loose_foreign_keys.html).
-
- Related: <DETAIL>
-
- ## Validations
-
- - **Best team to review (check off when reviewed):** TBD
- - [ ] No way for user to access once parent is deleted. Please explain: <DETAIL>
- - [ ] Possible to access once parent deleted but low user impact. Please explain: <DETAIL>
- - [ ] Possible Sidekiq workers that may load directly and possibly lead to exceptions. Please explain: <DETAIL>
- - [ ] Possible user impact to be evaluated or mitigated. Please explain: <DETAIL>
- - [ ] Is this FK safe to be removed to avoid LOCKing problems? (Explanation: https://gitlab.com/groups/gitlab-org/-/epics/7249#note_819662046). Please explain: <DETAIL>
-
- ## MR acceptance checklist
-
- This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
-
- * [ ] I have evaluated the [MR acceptance checklist](https://docs.gitlab.com/ee/development/code_review.html#acceptance-checklist) for this MR.
-
- /label ~"ci-decomposition::phase4" ~"database::review pending" ~"devops::enablement" ~"group::sharding" ~"section::enablement" ~"sharding::active" ~"type::feature" ~"workflow::in dev" ~backend ~"ci-decomposition" ~database ~"Category:Sharding"
- /milestone %"#{$options[:milestone]}"
- /assign_reviewer @ahegyi
- EOF
- puts "--------------------------------------------------"
- end
+ write_file(spec_path, updated.join(""))
end
all_foreign_keys = ActiveRecord::Base.connection.tables.flat_map do |table|
@@ -400,6 +300,8 @@ all_foreign_keys.each_with_index do |definition, idx|
generate_migration(definition)
add_test_to_specs(definition)
update_no_cross_db_foreign_keys_spec(definition)
- commit_changes(definition)
end
+
+print_files_affected
+
puts
diff --git a/scripts/determine-qa-tests b/scripts/determine-qa-tests
new file mode 100755
index 00000000000..b1e9d8e9312
--- /dev/null
+++ b/scripts/determine-qa-tests
@@ -0,0 +1,106 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+require 'optparse'
+
+# This script returns end-to-end tests or test directories. The returned value is stored in QA_TESTS
+# variable for executing only those tests.
+
+class DetermineQATests # rubocop:disable Gitlab/NamespacedClass
+ def initialize(options)
+ @changed_files = options.delete(:changed_files)
+ @mr_labels = options.delete(:mr_labels) || []
+ end
+
+ def execute
+ # If only e2e test files have changed, run only those tests
+ qa_tests = if has_qa_spec_only_changes?
+ changed_files
+
+ # If only non qa files have changed, use the devops MR label to run the files in the related directory
+ # However, if a feature flag file has changed, do not return any specific test/test directory
+ elsif has_non_qa_only_changes? && mr_labels.any? && !has_dev_ops_feature_flag_changes?
+ devops_stage = devops_stage_from_mr_labels
+
+ qa_spec_directories_for_devops_stage(devops_stage) if devops_stage
+ end
+
+ trim_path(qa_tests).join(' ') if qa_tests
+ end
+
+ private
+
+ attr_reader :changed_files, :mr_labels
+
+ # Are the changed files only qa specs?
+ #
+ # @return [Boolean] whether the changes files are only qa specs
+ def has_qa_spec_only_changes?
+ changed_files.all? { |file_path| file_path =~ %r{^qa/qa/specs/features/} }
+ end
+
+ # Are the changed files only outside the qa directory?
+ #
+ # @return [Boolean] whether the changes files are outside of qa directory
+ def has_non_qa_only_changes?
+ changed_files.none? { |file_path| file_path =~ %r{^qa/} }
+ end
+
+ # Are the changed files for development and ops feature flags?
+ #
+ # @return [Boolean] whether the changes files are for development and ops feature flags
+ def has_dev_ops_feature_flag_changes?
+ changed_files.any? { |file_path| file_path =~ %r{/feature_flags/(development|ops)/.*\.yml} }
+ end
+
+ # Remove the leading `qa/` from the file or directory paths
+ #
+ # @param [Array] paths Array of file or directory paths
+ # @return [Array] Array of files or directories with the first occurance of `qa/` removed
+ def trim_path(paths)
+ paths.map { |path| path.delete_prefix("qa/") }
+ end
+
+ # Extract devops stage from MR labels
+ #
+ # @return [String] a devops stage
+ def devops_stage_from_mr_labels
+ mr_labels.find { |label| label =~ /^devops::/ }&.delete_prefix('devops::')
+ end
+
+ # Get qa spec directories for devops stage
+ #
+ # @param [String] devops_stage a devops stage
+ # @return [Array] qa spec directories
+ def qa_spec_directories_for_devops_stage(devops_stage)
+ Dir.glob("qa/qa/specs/**/*/").select { |dir| dir =~ %r{\d+_#{devops_stage}/$} }
+ end
+end
+
+if $0 == __FILE__
+ options = {}
+
+ OptionParser.new do |opts|
+ opts.on("-f", "--files CHANGED_FILES_PATH", String,
+ "A path to a file containing a list of changed files") do |value|
+ changed_files_path = value
+ abort("ERROR: The specified changed files path does not exist") unless File.exist?(changed_files_path)
+
+ changed_files = File.read(changed_files_path).split(' ')
+ abort("ERROR: There are no changed files") if changed_files.empty?
+
+ options[:changed_files] = changed_files
+ end
+
+ opts.on("-l", "--labels MR_LABELS", String, "A comma separated list of MR labels") do |value|
+ options[:mr_labels] = Array(value&.split(',')).compact
+ end
+
+ opts.on("-h", "--help", "Prints this help") do
+ puts opts
+ exit
+ end
+ end.parse!
+
+ puts DetermineQATests.new(options).execute
+end
diff --git a/scripts/frontend/stylelint/stylelint_duplicate_selectors.js b/scripts/frontend/stylelint/stylelint_duplicate_selectors.js
deleted file mode 100644
index 77c1f1292b7..00000000000
--- a/scripts/frontend/stylelint/stylelint_duplicate_selectors.js
+++ /dev/null
@@ -1,25 +0,0 @@
-const stylelint = require('stylelint');
-const utils = require('./stylelint_utils');
-
-const ruleName = 'stylelint-gitlab/duplicate-selectors';
-
-const messages = stylelint.utils.ruleMessages(ruleName, {
- expected: (selector1, selector2) => {
- return `"${selector1}" and "${selector2}" have the same properties.`;
- },
-});
-
-module.exports = stylelint.createPlugin(ruleName, (enabled) => {
- if (!enabled) {
- return;
- }
-
- // eslint-disable-next-line consistent-return
- return (root, result) => {
- const selectorGroups = {};
- utils.createPropertiesHashmap(root, result, ruleName, messages, selectorGroups, true);
- };
-});
-
-module.exports.ruleName = ruleName;
-module.exports.messages = messages;
diff --git a/scripts/frontend/stylelint/stylelint_utility_classes.js b/scripts/frontend/stylelint/stylelint_utility_classes.js
deleted file mode 100644
index ad2b2ddbb20..00000000000
--- a/scripts/frontend/stylelint/stylelint_utility_classes.js
+++ /dev/null
@@ -1,25 +0,0 @@
-const stylelint = require('stylelint');
-const utils = require('./stylelint_utils');
-const utilityClasses = require('./utility_classes_map');
-
-const ruleName = 'stylelint-gitlab/utility-classes';
-
-const messages = stylelint.utils.ruleMessages(ruleName, {
- expected: (selector1, selector2) => {
- return `"${selector1}" has the same properties as our BS4 utility class "${selector2}" so please use that instead.`;
- },
-});
-
-module.exports = stylelint.createPlugin(ruleName, (enabled) => {
- if (!enabled) {
- return;
- }
-
- // eslint-disable-next-line consistent-return
- return (root, result) => {
- utils.createPropertiesHashmap(root, result, ruleName, messages, utilityClasses, false);
- };
-});
-
-module.exports.ruleName = ruleName;
-module.exports.messages = messages;
diff --git a/scripts/frontend/stylelint/stylelint_utility_map.js b/scripts/frontend/stylelint/stylelint_utility_map.js
deleted file mode 100644
index 187b2065823..00000000000
--- a/scripts/frontend/stylelint/stylelint_utility_map.js
+++ /dev/null
@@ -1,64 +0,0 @@
-const fs = require('fs');
-const path = require('path');
-const postcss = require('postcss');
-const prettier = require('prettier');
-const sass = require('sass');
-
-const utils = require('./stylelint_utils');
-
-const ROOT_PATH = path.resolve(__dirname, '../../..');
-const hashMapPath = path.resolve(__dirname, './utility_classes_map.js');
-
-//
-// This creates a JS based hash map (saved in utility_classes_map.js) of the different values in the utility classes
-//
-sass.render(
- {
- data: `
- @import './functions';
- @import './variables';
- @import './mixins';
- @import './utilities';
- `,
- includePaths: [path.resolve(ROOT_PATH, 'node_modules/bootstrap/scss')],
- },
- (err, result) => {
- if (err) {
- return console.error('Error ', err);
- }
-
- const cssResult = result.css.toString();
-
- // We just use postcss to create a CSS tree
- return postcss([])
- .process(cssResult, {
- // This suppresses a postcss warning
- from: undefined,
- })
- .then((processedResult) => {
- const selectorGroups = {};
- utils.createPropertiesHashmap(
- processedResult.root,
- processedResult,
- null,
- null,
- selectorGroups,
- true,
- );
-
- const prettierOptions = prettier.resolveConfig.sync(hashMapPath);
- const prettyHashmap = prettier.format(
- `module.exports = ${JSON.stringify(selectorGroups)};`,
- prettierOptions,
- );
-
- fs.writeFile(hashMapPath, prettyHashmap, (e) => {
- if (e) {
- return console.log(e);
- }
-
- return console.log('The file was saved!');
- });
- });
- },
-);
diff --git a/scripts/frontend/stylelint/stylelint_utils.js b/scripts/frontend/stylelint/stylelint_utils.js
deleted file mode 100644
index c9d9c7d9aad..00000000000
--- a/scripts/frontend/stylelint/stylelint_utils.js
+++ /dev/null
@@ -1,78 +0,0 @@
-const md5 = require('md5');
-const stylelint = require('stylelint');
-
-module.exports.createPropertiesHashmap = (
- ruleRoot,
- result,
- ruleName,
- messages,
- selectorGroups,
- addSelectors,
-) => {
- ruleRoot.walkRules((rule) => {
- const selector = rule.selector.replace(/(?:\r\n|\r|\n)/g, ' ');
-
- if (
- rule &&
- rule.parent &&
- rule.parent.type !== 'atrule' &&
- !(
- selector.includes('-webkit-') ||
- selector.includes('-moz-') ||
- selector.includes('-o-') ||
- selector.includes('-ms-') ||
- selector.includes(':')
- )
- ) {
- let cssArray = [];
- rule.nodes.forEach((property) => {
- const { prop, value } = property;
- if (property && value) {
- const propval = `${prop}${value}${property.important ? '!important' : ''}`;
- cssArray.push(propval);
- }
- });
-
- cssArray = cssArray.sort();
- const cssContent = cssArray.toString();
-
- if (cssContent) {
- const hashValue = md5(cssContent);
- const selObj = selectorGroups[hashValue];
-
- const selectorLine = `${selector} (${
- rule.source.input.file ? `${rule.source.input.file} -` : ''
- }${rule.source.start.line}:${rule.source.start.column})`;
-
- if (selObj) {
- if (selectorGroups[hashValue].selectors.indexOf(selector) === -1) {
- let lastSelector =
- selectorGroups[hashValue].selectors[selectorGroups[hashValue].selectors.length - 1];
-
- // So we have nicer formatting if it is the same file, we remove the filename
- lastSelector = lastSelector.replace(`${rule.source.input.file} - `, '');
-
- if (messages) {
- stylelint.utils.report({
- result,
- ruleName,
- message: messages.expected(selector, lastSelector),
- node: rule,
- word: rule.node,
- });
- }
-
- if (addSelectors) {
- selectorGroups[hashValue].selectors.push(selectorLine);
- }
- }
- } else if (addSelectors) {
- // eslint-disable-next-line no-param-reassign
- selectorGroups[hashValue] = {
- selectors: [selectorLine],
- };
- }
- }
- }
- });
-};
diff --git a/scripts/frontend/stylelint/utility_classes_map.js b/scripts/frontend/stylelint/utility_classes_map.js
deleted file mode 100644
index a174812ff93..00000000000
--- a/scripts/frontend/stylelint/utility_classes_map.js
+++ /dev/null
@@ -1,259 +0,0 @@
-module.exports = {
- '99097f29a9473b56eacdb9ff0681c366': { selectors: ['.align-baseline (1:1)'] },
- d969b318bb994e104e8c965006d71cb7: { selectors: ['.align-top (5:1)'] },
- '8cd54ab97b9cc43cb9d13d2ea7c601c7': { selectors: ['.align-middle (9:1)'] },
- dd06eb6c49e979b7a9fdaa7119aa0a0b: { selectors: ['.align-bottom (13:1)'] },
- '0af1e90cbc468615e299ec9f49e97c4a': { selectors: ['.align-text-bottom (17:1)'] },
- '50af706df238cf59bdc634fc684ba0c9': { selectors: ['.align-text-top (21:1)'] },
- c968922e6e47445362129a684b5913c0: { selectors: ['.bg-primary (25:1)'] },
- '3c397f9786c24cff4779a11cf5b3d7e7': { selectors: ['.bg-secondary (35:1)'] },
- '659677469a4477267fabc1788f7cad4e': { selectors: ['.bg-success (45:1)'] },
- '56d246d5b6a708a4c6f78dbd2444106c': { selectors: ['.bg-info (55:1)'] },
- '6bec0a33df3a6380c30103db5c273455': { selectors: ['.bg-warning (65:1)'] },
- '0ce5d074c8667ce6c32360658f428d5d': { selectors: ['.bg-danger (75:1)'] },
- '0d0269c62a01e97caa9039d227a25d12': { selectors: ['.bg-light (85:1)'] },
- '3a56309ad8c5b46ebcc3b13fe1987ac1': { selectors: ['.bg-dark (95:1)'] },
- '0e252f8dd392a33343d3d5efc1e3194a': { selectors: ['.bg-white (105:1)'] },
- '3af6f52f0ed4f98e797d5f10a35ca6bc': { selectors: ['.bg-transparent (109:1)'] },
- '16da7fdce74577ceab356509db565612': { selectors: ['.border (113:1)'] },
- '929622517ca05efde3b51e5f1a57064e': { selectors: ['.border-top (117:1)'] },
- '7283090353df54f1d515a6ceddfb9693': { selectors: ['.border-right (121:1)'] },
- bd5670d71332c652b46db82949042e31: { selectors: ['.border-bottom (125:1)'] },
- fa71e003d04734a898a85cc5285e3cbb: { selectors: ['.border-left (129:1)'] },
- ed482cea071e316f29d78fd93c3f3644: { selectors: ['.border-0 (133:1)'] },
- '90cb661baf21e10be6e479cb0544b1a7': { selectors: ['.border-top-0 (137:1)'] },
- '8a32707eaa09fc998bf8cc915710b60c': { selectors: ['.border-right-0 (141:1)'] },
- a6f01957e142a000e7742b31ac6c2331: { selectors: ['.border-bottom-0 (145:1)'] },
- c740fe952cc1985ee14f7d1c7a359a29: { selectors: ['.border-left-0 (149:1)'] },
- af9dd93e9780306ffa4bb25a6384902f: { selectors: ['.border-primary (153:1)'] },
- afa290dfe58cca06be5924ceae1b019b: { selectors: ['.border-secondary (157:1)'] },
- '9b1ac460bdddf1e0164d7bf988cc2da8': { selectors: ['.border-success (161:1)'] },
- '091cbf41d6be12061382fa571ee1ce82': { selectors: ['.border-info (165:1)'] },
- '3ada321d4a387901dad6d80e1b6be3fd': { selectors: ['.border-warning (169:1)'] },
- '13b4713dd52c1e359d1b43dd658cb249': { selectors: ['.border-danger (173:1)'] },
- '0048e110875ea22b04104d55e764a367': { selectors: ['.border-light (177:1)'] },
- a900b6b567c9a911326cdd0e19f40f8e: { selectors: ['.border-dark (181:1)'] },
- '78bcd867ac9677c743c2bc33b872f27b': { selectors: ['.border-white (185:1)'] },
- '35ef133b860874a5879e9451c491bc1c': { selectors: ['.rounded-sm (189:1)'] },
- e0fc10c49c7b7f4d1924336d21a4f64e: { selectors: ['.rounded (193:1)'] },
- '1b74b9d0a7d6a59281b5b5cae43c859a': { selectors: ['.rounded-top (197:1)'] },
- '20b75f55f39e662e038d51a6442c03df': { selectors: ['.rounded-right (202:1)'] },
- '83ea6db794873239c21f44af25618677': { selectors: ['.rounded-bottom (207:1)'] },
- '8464e9e8001e65dfc06397436a5eebd7': { selectors: ['.rounded-left (212:1)'] },
- a1148f40e8c509b2bcc829e2181c7582: { selectors: ['.rounded-lg (217:1)'] },
- '59c2f788287fa43caf5891adfc5c796e': { selectors: ['.rounded-circle (221:1)'] },
- '7f35b0a4b74ee7174b13cb841df0bdb3': { selectors: ['.rounded-pill (225:1)'] },
- '31a632ba94f8c41558bd6044458f1459': { selectors: ['.rounded-0 (229:1)'] },
- '16aaf53ab29d6b248b0257f2fa413914': { selectors: ['.d-none (239:1)'] },
- '4f42736ac9217039ed791b4306e60aeb': { selectors: ['.d-inline (243:1)'] },
- '067efa04b76649e8afcdceb9f5f7e870': { selectors: ['.d-inline-block (247:1)'] },
- de54f49149fb9b512aa79ad9ada838f2: { selectors: ['.d-block (251:1)'] },
- '80fc32acbc0c28ee890a160c23529d26': { selectors: ['.d-table (255:1)'] },
- '6a87b1db48298ca94cbe5dee79a6eed1': { selectors: ['.d-table-row (259:1)'] },
- b9896f0d94760bf5920f47904e9f7512: { selectors: ['.d-table-cell (263:1)'] },
- d25c51f38c4d057209b96c664de68c44: { selectors: ['.d-flex (267:1)'] },
- e72d46b636d5b8e17e771daa95793f33: { selectors: ['.d-inline-flex (271:1)'] },
- '2c433b7c14a5ae32cfa8ec7867ee8526': { selectors: ['.embed-responsive (460:1)'] },
- '56b318b8d8eb845b769d60cefcd131bb': {
- selectors: [
- '.embed-responsive .embed-responsive-item, .embed-responsive iframe, .embed-responsive embed, .embed-responsive object, .embed-responsive video (471:1)',
- ],
- },
- c5009af89633c4d2f71a0a9fa333630d: { selectors: ['.flex-row (501:1)'] },
- '7b06a3d956579cd64b6f5b1a57255369': { selectors: ['.flex-column (505:1)'] },
- '21744a8c4dc6ed1519903b4236b00af4': { selectors: ['.flex-row-reverse (509:1)'] },
- '18d903735f9c71070b6d8166aa1112f1': { selectors: ['.flex-column-reverse (513:1)'] },
- e2a57aa8196347d4da84f33a4f551325: { selectors: ['.flex-wrap (517:1)'] },
- b6b29f75b174b5609f9e7d5eef457b70: { selectors: ['.flex-nowrap (521:1)'] },
- '839230fc7c0abacb6418b49d8f10b27f': { selectors: ['.flex-wrap-reverse (525:1)'] },
- '924d9b261944a8e8ff684d5b519062bb': { selectors: ['.flex-fill (529:1)'] },
- '5ed396aeb08464b7df8fc37d29455319': { selectors: ['.flex-grow-0 (533:1)'] },
- '94251dc4d012339a3e37df6196fc79bb': { selectors: ['.flex-grow-1 (537:1)'] },
- '0eeed7dabca0452a46574776a4485e6e': { selectors: ['.flex-shrink-0 (541:1)'] },
- c69e60d5e51a1b74d22b172ab98ef9d5: { selectors: ['.flex-shrink-1 (545:1)'] },
- '03401c1a81eb6d4639f020f76dd60176': { selectors: ['.justify-content-start (549:1)'] },
- '39e3d2c344e78869c98ef099249e717d': { selectors: ['.justify-content-end (553:1)'] },
- '3b2d00c0bea857ab78a71b0872842980': { selectors: ['.justify-content-center (557:1)'] },
- b1c3c9edd20ed7c08b43863d38ebee40: { selectors: ['.justify-content-between (561:1)'] },
- a11b4b1d983c5fa75777f273e998f73e: { selectors: ['.justify-content-around (565:1)'] },
- '50e33f29f65bfffa6a3591fcb6045ca9': { selectors: ['.align-items-start (569:1)'] },
- e44b276e47415ec19b74cc16740ada1d: { selectors: ['.align-items-end (573:1)'] },
- '4a9d2716bca651758564059dceed1271': { selectors: ['.align-items-center (577:1)'] },
- fd970b017f7f558f30cb273bf71ede7d: { selectors: ['.align-items-baseline (581:1)'] },
- '7204b6b00b69f8af1e4a24c9b6e7f7f9': { selectors: ['.align-items-stretch (585:1)'] },
- '350fbb74eb70bd05f9438067c3990e9f': { selectors: ['.align-content-start (589:1)'] },
- '74d61397b4fcbf608f3dba39ab3b2a1b': { selectors: ['.align-content-end (593:1)'] },
- eed1ab4ee9e5b327a434512176741548: { selectors: ['.align-content-center (597:1)'] },
- '19878ab832978ef7e1746ac2fe4084b2': { selectors: ['.align-content-between (601:1)'] },
- dded333d0522692809517039f5a727c1: { selectors: ['.align-content-around (605:1)'] },
- '5cbb83ea2af7e9db8ef13f4c7d6db875': { selectors: ['.align-content-stretch (609:1)'] },
- dc3df46d3c023184d375a63a71916646: { selectors: ['.align-self-auto (613:1)'] },
- '0c6d2d8c9732c571f9cf61a4b1d2877f': { selectors: ['.align-self-start (617:1)'] },
- fe7c6071e3e17214df1bdd38850d9ff0: { selectors: ['.align-self-end (621:1)'] },
- '9611bbad74d72d50cf238088576a5089': { selectors: ['.align-self-center (625:1)'] },
- '4bc5edddf5981866946175bfedb7247f': { selectors: ['.align-self-baseline (629:1)'] },
- '4fffdd27ec60120ec9ed16fd7feef801': { selectors: ['.align-self-stretch (633:1)'] },
- '39e658501f5502b35919f02fa9591360': { selectors: ['.float-left (1185:1)'] },
- b51436c537ffc4269b1533e44d7c3467: { selectors: ['.float-right (1189:1)'] },
- c4597a87d2c793a6d696cfe06f6c95ce: { selectors: ['.float-none (1193:1)'] },
- '16b2e7e5b666b69b9581be6a44947d6f': { selectors: ['.user-select-all (1249:1)'] },
- f5c4d8f25b8338031e719edc77659558: { selectors: ['.user-select-auto (1253:1)'] },
- '71ba65d43dcc2b1c3f47ac2578983e83': { selectors: ['.user-select-none (1257:1)'] },
- '5902adc842d83ad70b89ece99b82a7a9': { selectors: ['.overflow-auto (1261:1)'] },
- bfb19f18b91fa46f0502e32153287cc4: { selectors: ['.overflow-hidden (1265:1)'] },
- aaf8dc6e0768e59f3098a98a5c144d66: { selectors: ['.position-static (1269:1)'] },
- '79592de2383045d15ab57d35aa1dab95': { selectors: ['.position-relative (1273:1)'] },
- a7c272f53d0368730bde4c2740ffb5c3: { selectors: ['.position-absolute (1277:1)'] },
- dad0bb92d53f17cf8affc10f77824b7f: { selectors: ['.position-fixed (1281:1)'] },
- '6da0f6a7354a75fe6c95b08a4cabc06f': { selectors: ['.position-sticky (1285:1)'] },
- '66602e21eea7b673883155c8f42b9590': { selectors: ['.fixed-top (1289:1)'] },
- '33ea70eb6db7f6ab3469680f182abb19': { selectors: ['.fixed-bottom (1297:1)'] },
- '947009e63b4547795ef7cc873f4e4ae9': { selectors: ['.sr-only (1313:1)'] },
- '9220ad156a70c2586b15fe2b9b6108b2': { selectors: ['.shadow-sm (1334:1)'] },
- b1b8c0ff70767ca2160811a026766982: { selectors: ['.shadow (1338:1)'] },
- da0792abe99964acb6692a03c61d6dd8: { selectors: ['.shadow-lg (1342:1)'] },
- bb2173057af1cf20e687469b2d9cbb3c: { selectors: ['.shadow-none (1346:1)'] },
- '6d8abb6519a186483b25429ab8b9765e': { selectors: ['.w-25 (1350:1)'] },
- a087c1ffdf8ead76cdd37445b414d63e: { selectors: ['.w-50 (1354:1)'] },
- '28180742013a90275be5633e6ec0dd51': { selectors: ['.w-75 (1358:1)'] },
- '195a03bc95a0af0ba6c8824db97a0b2f': { selectors: ['.w-100 (1362:1)'] },
- e67c74b650d6236b03be9dfc10c78e32: { selectors: ['.w-auto (1366:1)'] },
- c1b6262b3ee069addc1fbe46f64aac4e: { selectors: ['.h-25 (1370:1)'] },
- a520396ae349bef86145e0761aa0699e: { selectors: ['.h-50 (1374:1)'] },
- '7c53b57d54beb087fd7ab8b669c5fe60': { selectors: ['.h-75 (1378:1)'] },
- ad74f1972cb745b7a78b03e16a387f21: { selectors: ['.h-100 (1382:1)'] },
- '2cd49c3d63d260ba4f0b23c559ad05e0': { selectors: ['.h-auto (1386:1)'] },
- '0b43071a67efc45ee1735fdc2491313c': { selectors: ['.mw-100 (1390:1)'] },
- eac31a6f08e5c935e24b97df0fdad579: { selectors: ['.mh-100 (1394:1)'] },
- bd5f5d687d100d127abeb044fc78a3fd: { selectors: ['.min-vw-100 (1398:1)'] },
- '8dec7662784b943d3ca0615df59d7970': { selectors: ['.min-vh-100 (1402:1)'] },
- eacc17f16197f6c0291ced3e7cfc067e: { selectors: ['.vw-100 (1406:1)'] },
- '1a2ec1c9ad7db8d4156ca76a81416c49': { selectors: ['.vh-100 (1410:1)'] },
- cfdb4f497b16074959bfd3deb7ea9c42: { selectors: ['.m-0 (1414:1)'] },
- '4d666c270ba50524312d97c4b937d153': { selectors: ['.mt-0, .my-0 (1418:1)'] },
- eccf47ccd76ceffb4b139cb6c080b5ac: { selectors: ['.mr-0, .mx-0 (1423:1)'] },
- '9bc513e73c0bdc6efdf170cb31de16d1': { selectors: ['.mb-0, .my-0 (1428:1)'] },
- e99cfc55b03f0e67f11628b19889ad7b: { selectors: ['.ml-0, .mx-0 (1433:1)'] },
- e1c39484d90d2acaa00973531f47f738: { selectors: ['.m-1 (1438:1)'] },
- '63791bc02eccfdfa2c01621a801e565f': { selectors: ['.mt-1, .my-1 (1442:1)'] },
- bcb27ab9d7dcfdd0d7cacad02709966c: { selectors: ['.mr-1, .mx-1 (1447:1)'] },
- cb5d1c4328e25b5bc93be9a252973690: { selectors: ['.mb-1, .my-1 (1452:1)'] },
- b80b1010c7dcfbb30bed9015c4f2e969: { selectors: ['.ml-1, .mx-1 (1457:1)'] },
- ecab1c9cdf8a562e3c0f70307aeafa89: { selectors: ['.m-2 (1462:1)'] },
- '6505fe17fbbd88b1884113a754aa82ab': { selectors: ['.mt-2, .my-2 (1466:1)'] },
- '6f0c7d09d1e729f332c4671ccc2b48c0': { selectors: ['.mr-2, .mx-2 (1471:1)'] },
- '70ef7b668b382b3c747b2d73e08cdbed': { selectors: ['.mb-2, .my-2 (1476:1)'] },
- '2d7f277cc78ed324a8fc1f71ab281e1f': { selectors: ['.ml-2, .mx-2 (1481:1)'] },
- '8ebcfe52fd4024861082ffb1735747a7': { selectors: ['.m-3 (1486:1)'] },
- '9965fb516bdb72b87023a533123a8035': { selectors: ['.mt-3, .my-3 (1490:1)'] },
- b1fcbbb1dc6226f6da6000830088e051: { selectors: ['.mr-3, .mx-3 (1495:1)'] },
- '02204826cfbe3da98535c0d802870940': { selectors: ['.mb-3, .my-3 (1500:1)'] },
- '0259859060250ae6b730218733e7a437': { selectors: ['.ml-3, .mx-3 (1505:1)'] },
- '8cf300dab2a4994a105eeddda826f2e6': { selectors: ['.m-4 (1510:1)'] },
- '1ba62fdddd3349f52a452050688905c7': { selectors: ['.mt-4, .my-4 (1514:1)'] },
- '66a104129fa13db5a0829567fba6ee41': { selectors: ['.mr-4, .mx-4 (1519:1)'] },
- eefcc4c10b79e70e8e8a5a66fb2b7aa1: { selectors: ['.mb-4, .my-4 (1524:1)'] },
- eb1503656dc920d15a31116956fdffa4: { selectors: ['.ml-4, .mx-4 (1529:1)'] },
- '79cbb6e5c9b73fd0be29d4fc5733a099': { selectors: ['.m-5 (1534:1)'] },
- '67d8671699df706a428e7da42a7141cb': { selectors: ['.mt-5, .my-5 (1538:1)'] },
- e9cb4a0a8a60ff018c87a0b7efa9de29: { selectors: ['.mr-5, .mx-5 (1543:1)'] },
- '93f579214354dbd8cb60209c068f0086': { selectors: ['.mb-5, .my-5 (1548:1)'] },
- '2a789d4af97d2b87fd0bf2b4626120cd': { selectors: ['.ml-5, .mx-5 (1553:1)'] },
- '64a89d28e8287c1a0ac153001082644c': { selectors: ['.p-0 (1558:1)'] },
- b03aa6db5ddf110bbdbefbbec43fda30: { selectors: ['.pt-0, .py-0 (1562:1)'] },
- e38192ca32a98888d4c4876880f4fece: { selectors: ['.pr-0, .px-0 (1567:1)'] },
- '70fe8ef50e999ddd29506f672c107069': { selectors: ['.pb-0, .py-0 (1572:1)'] },
- '9355e8cd9109049726475ba356661bcf': { selectors: ['.pl-0, .px-0 (1577:1)'] },
- '0d4c53468c2658c5324b9ec7a8ca6de2': { selectors: ['.p-1 (1582:1)'] },
- d74e430b2a56b3a4e20065c972b7fa3f: { selectors: ['.pt-1, .py-1 (1586:1)'] },
- '21e4644967aedd19888b6f4a700b629b': { selectors: ['.pr-1, .px-1 (1591:1)'] },
- e315a7b9b7a1d0df3ea7d95af5203a0b: { selectors: ['.pb-1, .py-1 (1596:1)'] },
- '14630ca122e1d9830a9ef5591c4097d0': { selectors: ['.pl-1, .px-1 (1601:1)'] },
- '5b1c65e5139e86e5f4755824f8b77d13': { selectors: ['.p-2 (1606:1)'] },
- '244af70950a1e200d3849f75ce51d707': { selectors: ['.pt-2, .py-2 (1610:1)'] },
- b583832738cad724c7c23e5c14ac9bfb: { selectors: ['.pr-2, .px-2 (1615:1)'] },
- e1e633c4f1375e8276154192d8899e39: { selectors: ['.pb-2, .py-2 (1620:1)'] },
- '676b01e25f0dbb3f7d2f2529231cda08': { selectors: ['.pl-2, .px-2 (1625:1)'] },
- '9b5165e3333b22801f2287f7983d7516': { selectors: ['.p-3 (1630:1)'] },
- '5bcaa9df87a507f6cd14659ea176bdc5': { selectors: ['.pt-3, .py-3 (1634:1)'] },
- f706637180776c5589385599705a2409: { selectors: ['.pr-3, .px-3 (1639:1)'] },
- '41157cfbcf47990b383b5b0379386ab2': { selectors: ['.pb-3, .py-3 (1644:1)'] },
- cac1e7a204bb6a1f42707b684ad46238: { selectors: ['.pl-3, .px-3 (1649:1)'] },
- '43e0671cd41a4b7590284888b607a134': { selectors: ['.p-4 (1654:1)'] },
- '116b0f95ebde1ff8907e488413a88854': { selectors: ['.pt-4, .py-4 (1658:1)'] },
- ecb06765fe691d892df000eebbb23dcc: { selectors: ['.pr-4, .px-4 (1663:1)'] },
- '1331503a48d36025c861e660bc615048': { selectors: ['.pb-4, .py-4 (1668:1)'] },
- f8665f7e547e499abd7ac63813b274f5: { selectors: ['.pl-4, .px-4 (1673:1)'] },
- '4160a315459f1b5a98255863f42136fe': { selectors: ['.p-5 (1678:1)'] },
- f55a6b2de6a434ec7b4375f06f4fad75: { selectors: ['.pt-5, .py-5 (1682:1)'] },
- '19391dc45c8d7730a86d521c28f52c3f': { selectors: ['.pr-5, .px-5 (1687:1)'] },
- '15898bcb7ff74a60006f9931422b4ad3': { selectors: ['.pb-5, .py-5 (1692:1)'] },
- '6290bdc6355aed1e9b27379003aa4828': { selectors: ['.pl-5, .px-5 (1697:1)'] },
- '04a144abd49b1402e3588fb4ec237ec0': { selectors: ['.m-n1 (1702:1)'] },
- b81793fb475f7cc3a0698aa92ae41777: { selectors: ['.mt-n1, .my-n1 (1706:1)'] },
- '3c39187fca578a65e7aa2d704208cde8': { selectors: ['.mr-n1, .mx-n1 (1711:1)'] },
- c6c24440dd9edef71c7bc7da951dccd5: { selectors: ['.mb-n1, .my-n1 (1716:1)'] },
- '8b528f327ea3e562fc61d13c9f3901d8': { selectors: ['.ml-n1, .mx-n1 (1721:1)'] },
- '4a1926d211f81e5c96d4c9c4f2097774': { selectors: ['.m-n2 (1726:1)'] },
- f78f1986440a943aeb5553cb11b2f23c: { selectors: ['.mt-n2, .my-n2 (1730:1)'] },
- '941a9ea73cc97224a3f55ef65ab727c9': { selectors: ['.mr-n2, .mx-n2 (1735:1)'] },
- '074441007089a0c89cda9bfcb31c763a': { selectors: ['.mb-n2, .my-n2 (1740:1)'] },
- '7f9e45f6286b38eec32d471b7c1b28b6': { selectors: ['.ml-n2, .mx-n2 (1745:1)'] },
- c322ab87c4e181d835e6c17a1ca179ad: { selectors: ['.m-n3 (1750:1)'] },
- dee7caca54972db68f956a7cfd5d372b: { selectors: ['.mt-n3, .my-n3 (1754:1)'] },
- '60c8b6aa0702bbc3eed911f7483876ce': { selectors: ['.mr-n3, .mx-n3 (1759:1)'] },
- '595290cc6428614c03bac4631fe3fc12': { selectors: ['.mb-n3, .my-n3 (1764:1)'] },
- '7b968bc3150d9768117e099261cf23df': { selectors: ['.ml-n3, .mx-n3 (1769:1)'] },
- '90ca4942944fbc4dd543a821244daa31': { selectors: ['.m-n4 (1774:1)'] },
- '1716842c880f907fc62889f928273e0c': { selectors: ['.mt-n4, .my-n4 (1778:1)'] },
- '087ab1596162227935a974af5e856174': { selectors: ['.mr-n4, .mx-n4 (1783:1)'] },
- f53133714696eaf034f3efe057ac82fe: { selectors: ['.mb-n4, .my-n4 (1788:1)'] },
- ce56811b70ca493d65e3f63acfa2d9df: { selectors: ['.ml-n4, .mx-n4 (1793:1)'] },
- '7f52135983abe2eccfbb72e5d16a6a20': { selectors: ['.m-n5 (1798:1)'] },
- de8fc351c8c23bd867cf10a14642899a: { selectors: ['.mt-n5, .my-n5 (1802:1)'] },
- b134cecaa2883916d516ae6a2b1a3891: { selectors: ['.mr-n5, .mx-n5 (1807:1)'] },
- '124ccc6a210fadb889b2742eb0f9b540': { selectors: ['.mb-n5, .my-n5 (1812:1)'] },
- c3948b63b7b0c864f4f78faf640fd9a0: { selectors: ['.ml-n5, .mx-n5 (1817:1)'] },
- e57ec4fe9e8ed36e38f1c50041fc9f47: { selectors: ['.m-auto (1822:1)'] },
- f10380665932186d1effe0674a74ba12: { selectors: ['.mt-auto, .my-auto (1826:1)'] },
- '2ce71a27023eb50a47c24a99399faa28': { selectors: ['.mr-auto, .mx-auto (1831:1)'] },
- '196c77d357d314678cd3a99cfacbea96': { selectors: ['.mb-auto, .my-auto (1836:1)'] },
- ca007ce268b463a6bf42145cf5ce3685: { selectors: ['.ml-auto, .mx-auto (1841:1)'] },
- '695821b1faf108a559486730b246876a': { selectors: ['.text-monospace (3590:1)'] },
- a8fc5ca823f51d72673577064387a029: { selectors: ['.text-justify (3594:1)'] },
- '04e83ef005e9c653fb368008f8ae8a33': { selectors: ['.text-wrap (3598:1)'] },
- '0bb94dfab7ca2c9892ebbd993b2baf0f': { selectors: ['.text-nowrap (3602:1)'] },
- aea4958ce85ddc0cbffca1015c3a7eba: { selectors: ['.text-truncate (3606:1)'] },
- '52b9443947b6b94a5c7e1b837da115e2': { selectors: ['.text-left (3612:1)'] },
- baaf5136fc6e1c54ba29b6040f166d5f: { selectors: ['.text-right (3616:1)'] },
- '282aa4319bee75af06cc2632b7124e26': { selectors: ['.text-center (3620:1)'] },
- '1cb1c8ad9b560eca25ebcefe95c1b7fa': { selectors: ['.text-lowercase (3676:1)'] },
- '45234533eac658ba2857e9c4d3bc78a5': { selectors: ['.text-uppercase (3680:1)'] },
- f9e3f64237f2e81b6aed84223a0ceb1d: { selectors: ['.text-capitalize (3684:1)'] },
- '09caca3d36aa9f3ef815e0da7e1a16b4': { selectors: ['.font-weight-light (3688:1)'] },
- '23544682bb214a76e383e032d4056be4': { selectors: ['.font-weight-lighter (3692:1)'] },
- '25189f4fad18eaeef19e349c6680834c': { selectors: ['.font-weight-normal (3696:1)'] },
- b2a9507678ec557603eb8ec077f0eb1f: { selectors: ['.font-weight-bold (3700:1)'] },
- '9d05a94a577558235edce4c8fd9ab639': { selectors: ['.font-weight-bolder (3704:1)'] },
- '7d2da06b621a98a8599e5ec82e39eac8': { selectors: ['.font-italic (3708:1)'] },
- '0020d10e4fce033b418aace7c3143b82': { selectors: ['.text-white (3712:1)'] },
- '34ad81e372a038e6f78ae4f22bd4813d': { selectors: ['.text-primary (3716:1)'] },
- '9fde9a179d24755438ace2a874dda817': {
- selectors: ['.text-secondary (3724:1)', '.text-muted (3784:1)'],
- },
- '9ffcb1532b3fb397c0e818850683da29': { selectors: ['.text-success (3732:1)'] },
- f28fd089809bcd15d5684b158a0af98d: { selectors: ['.text-info (3740:1)'] },
- '6cac1cb5ee5149e91e45d15d0bdae310': { selectors: ['.text-warning (3748:1)'] },
- '2faab1e0abf22b20fdf05b9b01fff29b': { selectors: ['.text-danger (3756:1)'] },
- '46b52fea531aaaf29b63c40be2356849': { selectors: ['.text-light (3764:1)'] },
- '78f31d1ab6529decf28e0366a8ee81aa': { selectors: ['.text-dark (3772:1)'] },
- '45330b41b77e8880ad7680c51e0f61c4': { selectors: ['.text-body (3780:1)'] },
- '60d93588f62b5e85eb4f11dfd3461897': { selectors: ['.text-black-50 (3788:1)'] },
- '7dea35658553032ff7b7cc0287613b7c': { selectors: ['.text-white-50 (3792:1)'] },
- '61bf92980cac3d51d0cf1ba24c948fa1': { selectors: ['.text-hide (3796:1)'] },
- '187bfde85cb4d8bdaa41735f54f5c4f8': { selectors: ['.text-decoration-none (3804:1)'] },
- fd57878967b8dce947091cdc114efd83: { selectors: ['.text-break (3808:1)'] },
- '43b8d67240c3b09505d94522a62a977c': { selectors: ['.text-reset (3813:1)'] },
- '7dcad258820769677bc60871fafe9b93': { selectors: ['.visible (3817:1)'] },
- '0f8833af4e2f4a6fc785bd7edc1e75b3': { selectors: ['.invisible (3821:1)'] },
-};
diff --git a/scripts/glfm/run-snapshot-tests.sh b/scripts/glfm/run-snapshot-tests.sh
index 70fdae60edc..59a7c8f06b0 100755
--- a/scripts/glfm/run-snapshot-tests.sh
+++ b/scripts/glfm/run-snapshot-tests.sh
@@ -24,10 +24,9 @@ printf "\nStarting GLFM snapshot example tests. See https://docs.gitlab.com/ee/d
printf "Set 'FOCUSED_MARKDOWN_EXAMPLES=example_name_1[,...]' for focused examples, with example name(s) from https://docs.gitlab.com/ee/development/gitlab_flavored_markdown/specification_guide/#glfm_specificationexample_snapshotsexamples_indexyml.\n"
printf "${Color_Off}"
-# This section can be uncommented as soon as this is merged: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89953
-#printf "\n${BBlue}Running frontend 'yarn jest spec/frontend/content_editor/markdown_snapshot_spec.js'...${Color_Off}\n\n"
-#yarn jest spec/frontend/content_editor/markdown_snapshot_spec.js
-#printf "\n${BBlue}'yarn jest spec/frontend/content_editor/markdown_snapshot_spec.js' passed!${Color_Off}\n\n"
+printf "\n${BBlue}Running frontend 'yarn jest spec/frontend/content_editor/markdown_snapshot_spec.js'...${Color_Off}\n\n"
+yarn jest spec/frontend/content_editor/markdown_snapshot_spec.js
+printf "\n${BBlue}'yarn jest spec/frontend/content_editor/markdown_snapshot_spec.js' passed!${Color_Off}\n\n"
printf "\n${BBlue}Running backend 'bundle exec rspec spec/requests/api/markdown_snapshot_spec.rb'...${Color_Off}\n\n"
bundle exec rspec spec/requests/api/markdown_snapshot_spec.rb
diff --git a/scripts/lib/glfm/constants.rb b/scripts/lib/glfm/constants.rb
index e5917fc5cdb..42977248c0d 100644
--- a/scripts/lib/glfm/constants.rb
+++ b/scripts/lib/glfm/constants.rb
@@ -21,7 +21,7 @@ module Glfm
GLFM_SPEC_TXT_PATH = specification_path.join('output/spec.txt')
# Example Snapshot (ES) files
- es_fixtures_path = File.expand_path("../../../spec/fixtures/glfm/example_snapshots", __dir__)
+ es_fixtures_path = File.expand_path("../../../glfm_specification/example_snapshots", __dir__)
ES_EXAMPLES_INDEX_YML_PATH = File.join(es_fixtures_path, 'examples_index.yml')
ES_MARKDOWN_YML_PATH = File.join(es_fixtures_path, 'markdown.yml')
ES_HTML_YML_PATH = File.join(es_fixtures_path, 'html.yml')
diff --git a/scripts/lib/glfm/parse_examples.rb b/scripts/lib/glfm/parse_examples.rb
index 1c6afb800c3..14634bcfb3e 100644
--- a/scripts/lib/glfm/parse_examples.rb
+++ b/scripts/lib/glfm/parse_examples.rb
@@ -13,7 +13,11 @@
# 1. Capture all nested headers, not just the most recent.
# 2. Raise an exception if an unexpected state is encountered.
#
-# Comments indicate where changes or additions were made.
+# Comments indicate where changes, deletions, or additions were made.
+#
+# See more detailed documentation of rules regarding the handling of headers
+# in the comments at the top of `Glfm::UpdateExampleSnapshots#add_example_names`,
+# in `scripts/lib/glfm/update_example_snapshots.rb`
module Glfm
module ParseExamples
REGULAR_TEXT = 0
@@ -51,22 +55,29 @@ module Glfm
state = REGULAR_TEXT
example_number += 1
end_line = line_number
- unless extensions.include?('disabled')
- tests <<
- {
- markdown: markdown_lines.join.tr('→', "\t"),
- html: html_lines.join.tr('→', "\t"),
- example: example_number,
- start_line: start_line,
- end_line: end_line,
- section: headertext,
- extensions: extensions,
- headers: headers.dup # new logic compared to original Python code
- }
- start_line = 0
- markdown_lines = []
- html_lines = []
- end
+
+ # NOTE: The original implementation completely excludes disabled examples, but we need
+ # to include them in order to correctly count the header numbering, so we set a flag
+ # instead. This will need to be accounted for when we run conformance testing.
+
+ # unless extensions.include?('disabled') # commented logic compared to original Python code
+ tests <<
+ {
+ markdown: markdown_lines.join.tr('→', "\t"),
+ html: html_lines.join.tr('→', "\t"),
+ example: example_number,
+ start_line: start_line,
+ end_line: end_line,
+ section: headertext,
+ extensions: extensions,
+ headers: headers.dup, # new logic compared to original Python code
+ disabled: extensions.include?('disabled') # new logic compared to original Python code
+ }
+ # end # commented logic compared to original Python code
+
+ start_line = 0
+ markdown_lines = []
+ html_lines = []
elsif stripped_line == "."
# Else if the example divider line...
state = HTML_OUTPUT
@@ -80,19 +91,23 @@ module Glfm
html_lines.append(line)
elsif state == REGULAR_TEXT && line =~ header_regex
# Else if we are in regular text and it is a header line
- # NOTE: This assumes examples are only nested up to 2 levels deep (H2)
+ # NOTE: This assumes examples are within the section under
+ # Heading level 2 with Heading levels above 2 ignored
# Extract the header text from the line
headertext = line.gsub(header_regex, '').strip
+ # The 'headers' array is new logic compared to the original Python code
+
# reset the headers array if we found a new H1
- headers = [] if line =~ h1_regex # new logic compared to original Python code
+ headers = [] if line =~ h1_regex
- # pop the last entry from the headers array if we found a new H2
- headers.pop if headers.length == 2 && line =~ h2_regex # new logic compared to original Python code
+ # headers should be size 2 or less [<H1_headertext>, <H2_headertext>]
+ # pop the last entry from the headers array if we are in an H2 and found a new H2
+ headers.pop if headers.length == 2 && line =~ h2_regex
# push the new header text to the headers array
- headers << headertext # New logic compared to original Python code
+ headers << headertext if line =~ h1_regex || line =~ h2_regex
else
# Else if we are in regular text...
diff --git a/scripts/lib/glfm/render_wysiwyg_html_and_json.js b/scripts/lib/glfm/render_wysiwyg_html_and_json.js
index 58b440d7ab2..ed8bfdb4638 100644
--- a/scripts/lib/glfm/render_wysiwyg_html_and_json.js
+++ b/scripts/lib/glfm/render_wysiwyg_html_and_json.js
@@ -1,117 +1,7 @@
import fs from 'fs';
-import { DOMSerializer } from 'prosemirror-model';
import jsYaml from 'js-yaml';
-// TODO: DRY up duplication with spec/frontend/content_editor/services/markdown_serializer_spec.js
-// See https://gitlab.com/groups/gitlab-org/-/epics/7719#plan
-import Blockquote from '~/content_editor/extensions/blockquote';
-import Bold from '~/content_editor/extensions/bold';
-import BulletList from '~/content_editor/extensions/bullet_list';
-import Code from '~/content_editor/extensions/code';
-import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight';
-import DescriptionItem from '~/content_editor/extensions/description_item';
-import DescriptionList from '~/content_editor/extensions/description_list';
-import Details from '~/content_editor/extensions/details';
-import DetailsContent from '~/content_editor/extensions/details_content';
-import Division from '~/content_editor/extensions/division';
-import Emoji from '~/content_editor/extensions/emoji';
-import Figure from '~/content_editor/extensions/figure';
-import FigureCaption from '~/content_editor/extensions/figure_caption';
-import FootnoteDefinition from '~/content_editor/extensions/footnote_definition';
-import FootnoteReference from '~/content_editor/extensions/footnote_reference';
-import FootnotesSection from '~/content_editor/extensions/footnotes_section';
-import HardBreak from '~/content_editor/extensions/hard_break';
-import Heading from '~/content_editor/extensions/heading';
-import HorizontalRule from '~/content_editor/extensions/horizontal_rule';
-import Image from '~/content_editor/extensions/image';
-import InlineDiff from '~/content_editor/extensions/inline_diff';
-import Italic from '~/content_editor/extensions/italic';
-import Link from '~/content_editor/extensions/link';
-import ListItem from '~/content_editor/extensions/list_item';
-import OrderedList from '~/content_editor/extensions/ordered_list';
-import Strike from '~/content_editor/extensions/strike';
-import Table from '~/content_editor/extensions/table';
-import TableCell from '~/content_editor/extensions/table_cell';
-import TableHeader from '~/content_editor/extensions/table_header';
-import TableRow from '~/content_editor/extensions/table_row';
-import TaskItem from '~/content_editor/extensions/task_item';
-import TaskList from '~/content_editor/extensions/task_list';
-import createMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer';
-import { createTestEditor } from 'jest/content_editor/test_utils';
import { setTestTimeout } from 'jest/__helpers__/timeout';
-
-const tiptapEditor = createTestEditor({
- extensions: [
- Blockquote,
- Bold,
- BulletList,
- Code,
- CodeBlockHighlight,
- DescriptionItem,
- DescriptionList,
- Details,
- DetailsContent,
- Division,
- Emoji,
- FootnoteDefinition,
- FootnoteReference,
- FootnotesSection,
- Figure,
- FigureCaption,
- HardBreak,
- Heading,
- HorizontalRule,
- Image,
- InlineDiff,
- Italic,
- Link,
- ListItem,
- OrderedList,
- Strike,
- Table,
- TableCell,
- TableHeader,
- TableRow,
- TaskItem,
- TaskList,
- ],
-});
-
-async function renderMarkdownToHTMLAndJSON(markdown, schema, deserializer) {
- let prosemirrorDocument;
- try {
- const { document } = await deserializer.deserialize({ schema, content: markdown });
- prosemirrorDocument = document;
- } catch (e) {
- const errorMsg = `Error - check implementation:\n${e.message}`;
- return {
- html: errorMsg,
- json: errorMsg,
- };
- }
-
- const documentFragment = DOMSerializer.fromSchema(schema).serializeFragment(
- prosemirrorDocument.content,
- );
- const htmlString = documentFragment.firstChild.outerHTML;
-
- const json = prosemirrorDocument.toJSON();
- const jsonString = JSON.stringify(json, null, 2);
- return { html: htmlString, json: jsonString };
-}
-
-function renderHtmlAndJsonForAllExamples(markdownExamples) {
- const { schema } = tiptapEditor;
- const deserializer = createMarkdownDeserializer();
- const exampleNames = Object.keys(markdownExamples);
-
- return exampleNames.reduce(async (promisedExamples, exampleName) => {
- const markdown = markdownExamples[exampleName];
- const htmlAndJson = await renderMarkdownToHTMLAndJSON(markdown, schema, deserializer);
- const examples = await promisedExamples;
- examples[exampleName] = htmlAndJson;
- return examples;
- }, Promise.resolve({}));
-}
+import { renderHtmlAndJsonForAllExamples } from 'jest/content_editor/render_html_and_json_for_all_examples';
/* eslint-disable no-undef */
jest.mock('~/emoji');
diff --git a/scripts/lib/glfm/update_example_snapshots.rb b/scripts/lib/glfm/update_example_snapshots.rb
index 9ffa54cd5d4..893d8d9c014 100644
--- a/scripts/lib/glfm/update_example_snapshots.rb
+++ b/scripts/lib/glfm/update_example_snapshots.rb
@@ -40,18 +40,27 @@ module Glfm
add_example_names(all_examples)
+ reject_disabled_examples(all_examples)
+
write_snapshot_example_files(all_examples, skip_static_and_wysiwyg: skip_static_and_wysiwyg)
end
private
def add_example_names(all_examples)
- # NOTE: This method assumes:
+ # NOTE: This method and the parse_examples method assume:
# 1. Section 2 is the first section which contains examples
- # 2. Examples are always nested exactly than 2 levels deep in an H2
- # 3. We assume that the Appendix doesn't ever contain any examples, so it doesn't show up
+ # 2. Examples are always nested exactly 2 levels deep in an H2
+ # 3. There may exist H3 headings with no examples (e.g. "Motivation" in the GLFM spec.txt)
+ # 4. The Appendix doesn't ever contain any examples, so it doesn't show up
# in the H1 header count. So, even though due to the concatenation it appears before the
# GitLab examples sections, it doesn't result in their header counts being off by +1.
+ # 5. If an example contains the 'disabled' string extension, it is skipped (and will thus
+ # result in a skip in the `spec_txt_example_position`). This behavior is taken from the
+ # GFM `spec_test.py` script (but it's NOT in the original CommonMark `spec_test.py`).
+ # 6. If a section contains ONLY disabled examples, the section numbering will still be
+ # incremented to match the rendered HTML specification section numbering.
+ # 7. Every H2 must contain at least one example, but it is allowed that they are all disabled.
h1_count = 1 # examples start in H1 section 2; section 1 is the overview with no examples.
h2_count = 0
@@ -86,6 +95,10 @@ module Glfm
end
end
+ def reject_disabled_examples(all_examples)
+ all_examples.reject! { |example| example[:disabled] }
+ end
+
def write_snapshot_example_files(all_examples, skip_static_and_wysiwyg:)
output("Reading #{GLFM_EXAMPLE_STATUS_YML_PATH}...")
glfm_examples_statuses = YAML.safe_load(File.open(GLFM_EXAMPLE_STATUS_YML_PATH))
@@ -231,7 +244,7 @@ module Glfm
name = example.fetch(:name)
json = if glfm_examples_statuses.dig(name, 'skip_update_example_snapshot_prosemirror_json')
- existing_hash.dig(name)
+ existing_hash[name]
else
wysiwyg_html_and_json_hash.dig(name, 'json')
end
diff --git a/scripts/pipeline_test_report_builder.rb b/scripts/pipeline_test_report_builder.rb
index 2101decf59a..649b68427ea 100755
--- a/scripts/pipeline_test_report_builder.rb
+++ b/scripts/pipeline_test_report_builder.rb
@@ -72,6 +72,10 @@ class PipelineTestReportBuilder
# Please see for more info: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69053#note_709939709
def test_report_for_build(pipeline, build_id)
fetch("#{pipeline['web_url']}/tests/suite.json?build_ids[]=#{build_id}")
+ rescue Net::HTTPServerException => e
+ raise e unless e.response.code.to_i == 404
+
+ puts "Artifacts not found. They may have expired. Skipping this build."
end
def build_test_report_json_for_pipeline(pipeline)
@@ -92,7 +96,8 @@ class PipelineTestReportBuilder
test_report['suites'] ||= []
failed_builds_for_test_stage.each do |failed_build|
- test_report['suites'] << test_report_for_build(pipeline, failed_build['id'])
+ suite = test_report_for_build(pipeline, failed_build['id'])
+ test_report['suites'] << suite if suite
end
end
diff --git a/scripts/review_apps/gcp_cleanup.sh b/scripts/review_apps/gcp_cleanup.sh
index 3225631e8c7..114ac6f7ec0 100755
--- a/scripts/review_apps/gcp_cleanup.sh
+++ b/scripts/review_apps/gcp_cleanup.sh
@@ -3,7 +3,7 @@
source scripts/utils.sh
function setup_gcp_dependencies() {
- apk add jq
+ apt-get update && apt-get install -y jq
gcloud auth activate-service-account --key-file="${REVIEW_APPS_GCP_CREDENTIALS}"
gcloud config set project "${REVIEW_APPS_GCP_PROJECT}"
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index 8a868edce3a..b6ac7b4281b 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -248,6 +248,7 @@ function download_chart() {
helm repo add gitlab https://charts.gitlab.io
echoinfo "Building the gitlab chart's dependencies..."
+ helm dependency build "gitlab-${GITLAB_HELM_CHART_REF}"
}
function base_config_changed() {
diff --git a/scripts/rspec_helpers.sh b/scripts/rspec_helpers.sh
index af09d6d0edd..b31e3663eaa 100644
--- a/scripts/rspec_helpers.sh
+++ b/scripts/rspec_helpers.sh
@@ -304,12 +304,15 @@ function retry_failed_rspec_examples() {
# Disable Crystalball on retry to not overwrite the existing report
export CRYSTALBALL="false"
+ # Disable simplecov so retried tests don't override test coverage report
+ export SIMPLECOV=0
+
# Retry only the tests that failed on first try
rspec_simple_job "--only-failures --pattern \"${KNAPSACK_TEST_FILE_PATTERN}\"" "${JUNIT_RETRY_FILE}"
rspec_run_status=$?
# Merge the JUnit report from retry into the first-try report
- junit_merge "${JUNIT_RETRY_FILE}" "${JUNIT_RESULT_FILE}"
+ junit_merge "${JUNIT_RETRY_FILE}" "${JUNIT_RESULT_FILE}" --update-only
exit $rspec_run_status
}
diff --git a/scripts/undercoverage b/scripts/undercoverage
index d6e6197911e..b9266b015a7 100755
--- a/scripts/undercoverage
+++ b/scripts/undercoverage
@@ -1,3 +1,25 @@
-#!/usr/bin/env bash
+#!/usr/bin/env ruby
-bundle exec undercover -c "${1:-$(git merge-base origin/master HEAD)}"
+# frozen_string_literal: true
+
+require 'undercover'
+
+module Undercover
+ class Changeset
+ # Rugged merge_base complains when graft/shallow
+ # (https://github.com/libgit2/rugged/issues/846)
+ #
+ # So we assume we provide the merge-base ourself. Modified from
+ # https://github.com/grodowski/undercover/blob/32e62f66682ee566032b5970437ed2934ef29701/lib/undercover/changeset.rb#L74-L78
+ def compare_base_obj
+ return unless compare_base
+
+ repo.lookup(compare_base.to_s)
+ end
+ end
+end
+
+compare_base = ARGV[0]
+compare_base ||= IO.popen(%w(git merge-base origin/master HEAD)) { |p| p.read.chomp }
+
+Undercover::CLI.run(%W(-c #{compare_base}))
diff --git a/scripts/utils.sh b/scripts/utils.sh
index 4f339bbc850..d9205921963 100644
--- a/scripts/utils.sh
+++ b/scripts/utils.sh
@@ -38,11 +38,12 @@ function bundle_install_script() {
exit 1;
fi;
- gem install bundler --no-document --conservative --version 2.3.15
+ gem --version
bundle --version
+ gem install bundler --no-document --conservative --version 2.3.15
+ test -d jh && bundle config set --local gemfile 'jh/Gemfile'
bundle config set path "$(pwd)/vendor"
bundle config set clean 'true'
- test -d jh && bundle config set --local gemfile 'jh/Gemfile'
echo "${BUNDLE_WITHOUT}"
bundle config