configlet icon indicating copy to clipboard operation
configlet copied to clipboard

lint: implement remaining linting rules

Open ee7 opened this issue 3 years ago • 0 comments

This issue will try to track which linting rules are implemented in the latest configlet release, and to what extent. This issue was last updated for configlet 4.0.0-alpha.34.

The list below is from the spec (up to date with https://github.com/exercism/docs/commit/322d63ecdbf643351b080c7397caceb3ccd5bc94). When we change the spec, we should remember to make the same changes in the below.

For the near future, I will handle the implementation of the JSON rules.

A ticked checkbox means that the item is fully implemented. An empty checkbox means the item is unimplemented or only partially implemented.

Backlog: https://github.com/exercism/configlet/compare/4.0.0-alpha.34...4.0.0-beta.2

git log 4.0.0-alpha.34..4.0.0-beta.2 --oneline -- src/lint*

152a9536 lint, sync, fmt: support the files.invalidator field 0832f812 lint(exercises): check files.editor field 4ab7a531 remove trailing whitespace d0cd08cc lint: error on missing concept/exercise files a71637f7 lint: add annotation for key features in config.json 09d5108c lint: add annotation for tags in config.json cf70d5fa validators: pass error annotation to hasValidRuneLength 856ad411 lint: link to docs on exercism.org, not GitHub aa22e359 lint: allow overlap of solution and test files for d and plsql tracks fea7bccd lint: allow overlap of files.example and files.exemplar values 3a55f57a lint: support error annotations fe3e7e49 lint: clarify the need to update configlet 3171b116 lint: refactor final output message 816a1186 lint: advise that warnings may not become errors fd78c1b8 lint(track_config): optimize concept cycle checking slightly 1fd2a46f sync: handle docs, metadata, and filepaths


Rule: required files are present

The linter should check if all the required files are present. The non-exercise specific files that must be present are:

  • [x] config.json
  • [x] docs/ABOUT.md
  • [x] docs/INSTALLATION.md
  • [x] docs/LEARNING.md
  • [x] docs/RESOURCES.md
  • [x] docs/SNIPPET.txt
  • [x] docs/TESTS.md
  • [x] exercises/shared/.docs/help.md
  • [x] exercises/shared/.docs/tests.md

The Concept Exercise specific files that must be present are:

  • [x] exercises/concept/<slug>/.docs/hints.md
  • [x] exercises/concept/<slug>/.docs/instructions.md
  • [x] exercises/concept/<slug>/.docs/introduction.md
  • [x] exercises/concept/<slug>/.meta/config.json

The Practice Exercise specific files that must be present are at least (not yet in the spec):

  • [x] exercises/practice/<slug>/.docs/instructions.md
  • [x] exercises/practice/<slug>/.meta/config.json

Each concept listed in the config.json should have the following files:

  • [x] concepts/<slug>/about.md
  • [x] concepts/<slug>/introduction.md
  • [x] concepts/<slug>/links.json

Rule: config.json file is valid

The config.json file should have the following checks:

  • [x] The file must be valid JSON
  • [x] The "language" key is required
  • [x] The "language" value must be a non-blank string¹ with length <= 255
  • [x] The "slug" key is required
  • [x] The "slug" value must be a kebab-case string² with length <= 255
  • [x] The "active" key is required
  • [x] The "active" value must be a boolean
  • [x] The "blurb" key is required
  • [x] The "blurb" value must be a non-blank string¹ with length <= 400
  • [x] The "version" key is required
  • [x] The "version" value must be the integer 3
  • [x] The "status.concept_exercises" key is required
  • [x] The "status.concept_exercises" value must be a boolean
  • [x] The "status.test_runner" key is required
  • [x] The "status.test_runner" value must be a boolean
  • [x] The "status.representer" key is required
  • [x] The "status.representer" value must be a boolean
  • [x] The "status.analyzer" key is required
  • [x] The "status.analyzer" value must be a boolean
  • [x] The "online_editor.indent_style" key is required
  • [x] The "online_editor.indent_style" value must be the string space or tab
  • [x] The "online_editor.indent_size" key is required
  • [x] The "online_editor.indent_size" value must be an integer >= 0 and <= 8
  • [x] The "online_editor.highlightjs_language" key is required
  • [x] The "online_editor.highlightjs_language" value must be a non-blank string¹
  • [x] The "files" key is optional
  • [x] The "files" value must be an object
  • [x] The "files.solution" key is optional
  • [x] The "files.solution" value must be an array
  • [x] The "files.solution" values must be valid patterns⁵
  • [x] The "files.solution" values must not have duplicates
  • [x] The "files.test" key is optional
  • [x] The "files.test" value must be an array
  • [x] The "files.test" values must be valid patterns⁵
  • [x] The "files.test" values must not have duplicates
  • [x] The "files.example" key is optional
  • [x] The "files.example" value must be an array
  • [x] The "files.example" values must be valid patterns⁵
  • [x] The "files.example" values must not have duplicates
  • [x] The "files.exemplar" key is optional
  • [x] The "files.exemplar" value must be an array
  • [x] The "files.exemplar" values must be valid patterns⁵
  • [x] The "files.exemplar" values must not have duplicates
  • [x] The "files.editor" key is optional
  • [x] The "files.editor" value must be an array
  • [x] The "files.editor" values must be valid patterns⁵
  • [x] The "files.editor" values must not have duplicates
  • [ ] The "files.invalidator" key is optional
  • [ ] The "files.invalidator" value must be an array
  • [ ] The "files.invalidator" values must be valid patterns⁵
  • [ ] The "files.invalidator" values must not have duplicates
  • [ ] Patterns can only be listed in either the "files.solution", "files.test", "files.example", "files.exemplar", "files.editor" or "files.invalidator" array (no overlap)
  • [ ] If the track is d or plsql, the "files.solution" and "files.test" files can overlap
  • [ ] The "files.example" and "files.exemplar" files can overlap
  • [x] The "test_runner.average_run_time" key is required if status.test_runner is equal to true
  • [x] The "test_runner.average_run_time" value must be a floating-point number > 0 with one decimal point of precision
  • [x] The "exercises" key is required
  • [x] The "exercises.concept" key is required
  • [x] The "exercises.concept" value must be an array
  • [x] The "exercises.concept[].slug" key is required
  • [x] The "exercises.concept[].slug" value must be a kebab-case string² with length <= 255
  • [x] The "exercises.concept[].slug" value must be unique in "exercises.concept[].slug" and may not exist in "exercises.practice[].slug"
  • [x] The "exercises.concept[].name" key is required
  • [ ] The "exercises.concept[].name" value must be a Title Case string³ with length <= 255
  • [x] The "exercises.concept[].uuid" key is required
  • [ ] The "exercises.concept[].uuid" value must be a unique version 4 UUID string⁶
  • [ ] The "exercises.concept[].uuid" value for each exercise must never change
  • [x] The "exercises.concept[].concepts" key is required
  • [x] The "exercises.concept[].concepts" value must be a non-empty array of strings if "exercises.concept[].status" is not equal to deprecated
  • [x] The "exercises.concept[].concepts" value must be an empty array if "exercises.concept[].status" is equal to deprecated
  • [x] The "exercises.concept[].concepts" values must be kebab-case strings²
  • [x] The "exercises.concept[].concepts" values must not have duplicates
  • [x] The "exercises.concept[].concepts" values must not be in any other concept exercise's "concepts" property
  • [x] The "exercises.concept[].concepts" values must match the "concepts.slug" property of one of the concepts
  • [x] The "exercises.concept[].prerequisites" key is required
  • [x] The "exercises.concept[].prerequisites" value must be a non-empty array of strings if "exercises.concept[].status" is not equal to deprecated, except for exactly one exercise which is allowed to have an empty array as its value
  • [x] The "exercises.concept[].prerequisites" value must be an empty array if "exercises.concept[].status" is equal to deprecated
  • [x] The "exercises.concept[].prerequisites" values must be kebab-case strings²
  • [x] The "exercises.concept[].prerequisites" values must not have duplicates
  • [x] The "exercises.concept[].prerequisites" values must match any other concept exercise's "concepts" property values
  • [x] The "exercises.concept[].prerequisites" values must not match any of the values in the exercise's "exercises.concept[].concepts" property
  • [x] The "exercises.concept[].prerequisites" values must match the "concepts.slug" property of one of the concepts
  • [x] There must not be any cycles between "exercises.concept[].concepts" and "exercises.concept[].prerequisites"
  • [x] The "exercises.concept[].status" key is optional
  • [x] The "exercises.concept[].status" value must be the string wip, beta, active or deprecated
  • [x] The "exercises.practice" key is required
  • [x] The "exercises.practice" value must be an array
  • [x] The "exercises.practice[].slug" key is required
  • [x] The "exercises.practice[].slug" value must be a kebab-case string² with length <= 255
  • [x] The "exercises.practice[].slug" value must be unique in "exercises.practice[].slug" and may not exist in "exercises.concept[].slug"
  • [x] There must be exactly one "exercises.practice[].slug" value that is the string hello-world
  • [x] The "exercises.practice[].name" key is required
  • [ ] The "exercises.practice[].name" value must be a Title Case string³ with length <= 255
  • [x] The "exercises.practice[].uuid" key is required
  • [ ] The "exercises.practice[].uuid" value must be a unique version 4 UUID string⁶
  • [ ] The "exercises.practice[].uuid" value for each exercise must never change
  • [x] The "exercises.practice[].difficulty" key is required
  • [x] The "exercises.practice[].difficulty" value must be an integer >= 1 and <= 10
  • [x] The "exercises.practice[].practices" key is required
  • [ ] The "exercises.practice[].practices" value must be a non-empty array of strings if "exercises.practice[].status" is not equal to deprecated (TODO: enable this supported check)
  • [x] The "exercises.practice[].practices" value must be an empty array if "exercises.practice[].status" is equal to deprecated
  • [x] The "exercises.practice[].practices" values must be kebab-case strings²
  • [x] The "exercises.practice[].practices" values must not have duplicates
  • [x] The "exercises.practice[].practices" values must match the "concepts[].slug" property of one of the concepts (TODO: this is currently implemented as a warning, and will become an error)
  • [x] The "exercises.practice[].practices" values must refer to an individual concept's slug at most 10 times (across all exercises) (TODO: this is currently implemented as a warning, and will become an error)
  • [x] The "exercises.practice[].prerequisites" key is required
  • [ ] The "exercises.practice[].prerequisites" value must be a non-empty array of strings if "exercises.practice[].status" is not equal to deprecated (TODO: enable this supported check)
  • [x] The "exercises.practice[].prerequisites" value must be an empty array if "exercises.practice[].status" is equal to deprecated
  • [x] The "exercises.practice[].prerequisites" value must be an empty array if "exercises.practice[].slug" is equal to hello-world
  • [x] The "exercises.practice[].prerequisites" values must be kebab-case strings²
  • [x] The "exercises.practice[].prerequisites" values must not have duplicates
  • [x] The "exercises.practice[].prerequisites" values must match any concept exercise's "exercises.concept[].concepts" values (TODO: this is currently implemented as a warning, and will become an error)
  • [x] The "exercises.practice[].prerequisites" values must match the "concepts[].slug" property of one of the concepts (TODO: this is currently implemented as a warning, and will become an error)
  • [x] The "exercises.practice[].status" key is optional
  • [x] The "exercises.practice[].status" value must be the string wip, beta, active or deprecated
  • [x] The "exercises.practice[].status" value must, if "exercises.practice[].slug" is equal to hello-world, be either omitted or the string active
  • [x] The "exercises.foregone" key is optional
  • [x] The "exercises.foregone" value must be an array
  • [x] The "exercises.foregone" values must be kebab-case strings²
  • [x] The "exercises.foregone" values must not have duplicates
  • [x] The "exercises.foregone" values must not match any of the concept or practice exercise slugs
  • [x] The "concepts" key is required
  • [x] The "concepts" value must be an array
  • [x] The "concepts[].uuid" key is required
  • [ ] The "concepts[].uuid" value must be a unique version 4 UUID string⁶
  • [ ] The "concepts[].uuid" value for each concept must never change
  • [x] The "concepts[].slug" key is required
  • [x] The "concepts[].slug" value must be a kebab-case string² with length <= 255
  • [x] The "concepts[].name" key is required
  • [ ] The "concepts[].name" value must be a Title Case string³ with length <= 255
  • [ ] Each "concepts" value must have a concept/<concepts.slug>/about.md file. Linting rules for this file are specified below.
  • [ ] Each "concepts" value must have a concept/<concepts.slug>/introduction.md file. Linting rules for this file are specified below.
  • [ ] Each "concepts" value must have a concept/<concepts.slug>/links.json file. Linting rules for this file are specified below.
  • [x] The "key_features" key is optional
  • [x] The "key_features" value must be an array with length = 6
  • [x] The "key_features[].icon" key is required
  • [x] The "key_features[].icon" value must use one of the pre-defined icon values
  • [x] The "key_features[].title" key is required
  • [ ] The "key_features[].title" value must be a Sentence Case string⁴ with length <= 25
  • [x] The "key_features[].content" key is required
  • [x] The "key_features[].content" value must be a non-blank string¹ with length <= 100
  • [x] The "tags" key is required
  • [x] The "tags" value must be an array of strings
  • [x] The "tags" values must not have duplicates
  • [x] The "tags" values must use one of the pre-defined tag values

Rule: exercises/concept/<slug>/.meta/config.json is valid

  • [x] The file must be valid JSON
  • [x] The JSON root must be an object
  • [x] The "blurb" key is required
  • [x] The "blurb" value must be a non-blank string¹ with length <= 350
  • [x] The "source" key is optional
  • [x] The "source" value must be a non-blank string¹
  • [x] The "source_url" key is optional
  • [ ] The "source_url" value must be a URL
  • [x] The "authors" key is required
  • [x] The "authors" value must be a non-empty array
  • [x] The "authors" values must be non-blank strings¹
  • [x] The "authors" values must not have duplicates
  • [ ] The "authors" values are treated case-insensitively
  • [x] The "contributors" key is optional
  • [x] The "contributors" value must be an array
  • [x] The "contributors" values must be non-blank strings¹
  • [x] The "contributors" values must not have duplicates
  • [ ] The "contributors" values are treated case-insensitively
  • [ ] Users can only be listed in either the "authors" or "contributors" array (no overlap)
  • [x] The "files.solution" key is required
  • [x] The "files.solution" value must be a non-empty array
  • [x] The "files.solution" values must not have duplicates
  • [x] The "files.test" key is required
  • [x] The "files.test" value must be a non-empty array
  • [x] The "files.test" values must not have duplicates
  • [x] The "files.exemplar" key is required
  • [x] The "files.exemplar" value must be a non-empty array
  • [x] The "files.exemplar" values must not have duplicates
  • [ ] The "files.editor" key is optional
  • [ ] The "files.editor" value must be an array
  • [ ] The "files.editor" values must not have duplicates
  • [ ] The "files.invalidator" key is optional
  • [ ] The "files.invalidator" value must be an array
  • [ ] The "files.invalidator" values must not have duplicates
  • [x] The files listed in the "files.solution" must exist
  • [x] The files listed in the "files.test" must exist
  • [x] The files listed in the "files.exemplar" must exist
  • [ ] The files listed in the "files.editor" must exist
  • [ ] The files listed in the "files.invalidator" must exist
  • [ ] Files can only be listed in either the "files.solution", "files.test", "files.exemplar" or "files.invalidator" array (no overlap)
  • [ ] If the track is d or plsql, the "files.solution" and "files.test" files can overlap
  • [x] The "forked_from" key is optional
  • [x] The "forked_from" value must be an array
  • [ ] The "forked_from" values must be strings formatted as <track-slug>/<exercise-slug> (e.g. fsharp/bird-watcher)
  • [ ] The "forked_from" values must refer to actually implemented exercises
  • [x] The "forked_from" values must not have duplicates
  • [x] The "language_versions" key is optional
  • [x] The "language_versions" value must be a string
  • [x] The "icon" key is optional
  • [x] The "icon" value must be a kebab-case string²

Rule: exercises/concept/<slug>/.docs/hints.md is valid

  • [ ] The Markdown must conform to the Markdown standards
  • [ ] All headings must be either ## General or ## X. <task> where X matches the task number heading in the instructions.md
  • [ ] All hints must be specified as Markdown list items
  • [ ] Links must be absolute (relative links are not allowed)

Rule: exercises/concept/<slug>/.docs/instructions.md is valid

  • [ ] The Markdown must conform to the Markdown standards
  • [ ] All tasks must start with a level two heading that starts with a number followed by a dot: ## 1. Do X
  • [ ] Links must be absolute (relative links are not allowed)

Rule: exercises/concept/<slug>/.docs/instructions.md.tpl is valid (if present)

  • [ ] Each concept placeholders's concept must match the "concepts.slug" property of one of the concepts in the track's config.json.

Rule: exercises/concept/<slug>/.docs/introduction.md is valid

  • [ ] The Markdown must conform to the Markdown standards
  • [ ] Links must be absolute (relative links are not allowed)

Rule: exercises/practice/<slug>/.meta/config.json is valid

  • [x] The file must be valid JSON
  • [x] The JSON root must be an object
  • [x] The "blurb" key is required
  • [x] The "blurb" value must be a non-blank string¹ with length <= 350
  • [x] The "source" key is optional
  • [x] The "source" value must be a non-blank string¹
  • [x] The "source_url" key is optional
  • [ ] The "source_url" value must be a URL
  • [x] The "authors" key is optional
  • [x] The "authors" value must be an array
  • [x] The "authors" values must be non-blank strings¹
  • [x] The "authors" values must not have duplicates
  • [ ] The "authors" values are treated case-insensitively
  • [x] The "contributors" key is optional
  • [x] The "contributors" value must be an array
  • [x] The "contributors" values must be non-blank strings¹
  • [x] The "contributors" values must not have duplicates
  • [ ] The "contributors" values are treated case-insensitively
  • [ ] Users can only be listed in either the "authors" or "contributors" array (no overlap)
  • [x] The "files.solution" key is required
  • [x] The "files.solution" value must be a non-empty array
  • [x] The "files.solution" values must not have duplicates
  • [x] The "files.test" key is required
  • [x] The "files.test" value must be a non-empty array
  • [x] The "files.test" values must not have duplicates
  • [x] The "files.example" key is required
  • [x] The "files.example" value must be a non-empty array
  • [x] The "files.example" values must not have duplicates
  • [ ] The "files.editor" key is optional
  • [ ] The "files.editor" value must be an array
  • [ ] The "files.editor" values must not have duplicates
  • [ ] The "files.invalidator" key is optional
  • [ ] The "files.invalidator" value must be an array
  • [ ] The "files.invalidator" values must not have duplicates
  • [x] The files listed in the "files.solution" must exist
  • [x] The files listed in the "files.test" must exist
  • [x] The files listed in the "files.example" must exist
  • [ ] The files listed in the "files.editor" must exist
  • [ ] The files listed in the "files.invalidator" must exist
  • [ ] Files can only be listed in either the "files.solution", "files.test", "files.example or "files.invalidator" array (no overlap)
  • [ ] If the track is d or plsql, the "files.solution" and "files.test" files can overlap
  • [x] The "language_versions" key is optional
  • [x] The "language_versions" value must be a string
  • [x] The "test_runner" key is optional
  • [x] The "test_runner" value must be a boolean

Rule: exercises/shared/.docs/debug.md is valid

  • [x] The file's presence is optional
  • [ ] The Markdown must conform to the Markdown standards
  • [ ] Links must be absolute (relative links are not allowed)

Rule: exercises/shared/.docs/help.md is valid

  • [x] The file's presence is required
  • [ ] The Markdown must conform to the Markdown standards
  • [ ] Links must be absolute (relative links are not allowed)

Rule: exercises/shared/.docs/tests.md is valid

  • [x] The file's presence is required
  • [ ] The Markdown must conform to the Markdown standards
  • [ ] Links must be absolute (relative links are not allowed)

Rule: concepts/<slug>/about.md is valid

  • [ ] The Markdown must conform to the Markdown standards
  • [ ] Links must be absolute (relative links are not allowed)

Rule: concepts/<slug>/introduction.md is valid

  • [ ] The Markdown must conform to the Markdown standards
  • [ ] Links must be absolute (relative links are not allowed)

Rule: concept/<slug>/links.json is valid

  • [x] The file must be valid JSON
  • [x] The JSON root must be an array
  • [x] The "[].url" property is required
  • [ ] The "[].url" value must be an URL
  • [x] The "[].description" property is required
  • [x] The "[].description" value must be a non-blank string¹
  • [x] The "[].icon_url" property is optional
  • [ ] The "[].icon_url" value must be an URL

Rule: concept/<slug>/.meta/config.json is valid

  • [x] The file must be valid JSON
  • [x] The JSON root must be an object
  • [x] The "blurb" key is required
  • [x] The "blurb" value must be a non-blank string¹ with length <= 350
  • [x] The "authors" key is required
  • [x] The "authors" value must be an array
  • [x] The "authors" values must be non-blank strings¹
  • [x] The "authors" values must not have duplicates
  • [ ] The "authors" values are treated case-insensitively
  • [x] The "contributors" key is optional
  • [x] The "contributors" value must be an array
  • [x] The "contributors" values must be non-blank strings¹
  • [x] The "contributors" values must not have duplicates
  • [ ] The "contributors" values are treated case-insensitively
  • [ ] Users can only be listed in either the "authors" or "contributors" array (no overlap)

Rule: docs/ABOUT.md is valid

  • [x] The file's presence is required
  • [ ] The file's contents must be non-blank
  • [ ] The Markdown must conform to the Markdown standards
  • [ ] Links must be absolute (relative links are not allowed)

Rule: docs/INSTALLATION.md is valid

  • [x] The file's presence is required
  • [ ] The file's contents must be non-blank
  • [ ] The Markdown must conform to the Markdown standards
  • [ ] Links must be absolute (relative links are not allowed)

Rule: docs/LEARNING.md is valid

  • [x] The file's presence is required
  • [ ] The file's contents must be non-blank
  • [ ] The Markdown must conform to the Markdown standards
  • [ ] Links must be absolute (relative links are not allowed)

Rule: docs/RESOURCES.md is valid

  • [x] The file's presence is required
  • [ ] The file's contents must be non-blank
  • [ ] The Markdown must conform to the Markdown standards
  • [ ] Links must be absolute (relative links are not allowed)

Rule: docs/SNIPPET.txt is valid

  • [x] The file's presence is required
  • [ ] The file's contents must be non-blank

Rule: docs/TESTS.md is valid

  • [x] The file's presence is required
  • [ ] The file's contents must be non-blank
  • [ ] The Markdown must conform to the Markdown standards
  • [ ] Links must be absolute (relative links are not allowed)

Glossary

  1. Non-blank string: a string that contains at least one non-whitespace character.
  2. kebab-case string: a string that contains only characters in the range [a-z0-9], optionally separated by dashes (e.g. "two-fer"). It must match the regular expression: ^[a-z0-9]+(-[a-z0-9]+)*$
  3. Title Case string: a non-blank string that follows the below guidelines (from the Chicago Manual of Style, see https://en.wikipedia.org/wiki/Title_case):
    • Capitalize the first and last words of titles and subtitles.
    • Capitalize "major" words (nouns, pronouns, verbs, adjectives, adverbs, and some conjunctions).
    • Lowercase the conjunctions and, but, for, or, and nor.
    • Lowercase the articles the, a, and an.
    • Lowercase prepositions, regardless of length, except when they are stressed, are used adverbially or adjectivally, or are used as conjunctions.
    • Lowercase the words to and as.
    • Lowercase the second part of Latin species names.
  4. Sentence Case string: a non-blank string that follows the below guidelines (see https://en.wikipedia.org/wiki/Letter_case#Sentence_case):
    • Capitalize the first word of the sentence, as well as proper nouns and other words as required by a more specific rule.
  5. Valid files pattern: A non-blank string¹ that specifies a location of a file used in an exercise, relative to the exercise's directory. A pattern may use one of the following placeholders:
    • %{kebab_slug}: the kebab-case exercise slug (e.g. bit-manipulation)
    • %{snake_slug}: the snake_case exercise slug (e.g. bit_manipulation)
    • %{camel_slug}: the camelCase exercise slug (e.g. bitManipulation)
    • %{pascal_slug}: the PascalCase exercise slug (e.g. BitManipulation)
  6. Unique version 4 UUID string: A string that satisfies all of these conditions:
    • It only exists once in the track-level config.json file of a given Exercism track
    • It does not exist in the track-level config.json file of any other Exercism track
    • It does not exist in any canonical-data.json file in https://github.com/exercism/problem-specifications
    • It does not exist anywhere else on Exercism
    • It matches the regular expression:
    ^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
    
    For example, the below UUID has the correct form:
    d334ffe3-657e-4725-a950-290b284b6d9f
    
    You can run configlet uuid to generate a suitable UUID.

ee7 avatar Mar 28 '21 12:03 ee7