summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQMK Bot <hello@qmk.fm>2021-08-10 14:48:55 +0000
committerQMK Bot <hello@qmk.fm>2021-08-10 14:48:55 +0000
commitd888ac17ea35b6093d0a6717462ae2c18519ec41 (patch)
tree96de083a0c2d9e174357522943293c928fd4c070
parent5b8f2eccbfceea3cdc234de48d6a821ecd7bee24 (diff)
parented84a4e7e3e65d1ef090117cdb9c6d70ed554a28 (diff)
Merge remote-tracking branch 'origin/master' into develop
-rw-r--r--docs/_summary.md1
-rw-r--r--docs/configurator_architecture.md61
-rw-r--r--docs/configurator_diagram.drawio1
-rw-r--r--docs/configurator_diagram.svg3
-rw-r--r--lib/python/qmk/cli/lint.py9
5 files changed, 71 insertions, 4 deletions
diff --git a/docs/_summary.md b/docs/_summary.md
index 7e6c062136..1d95400822 100644
--- a/docs/_summary.md
+++ b/docs/_summary.md
@@ -19,6 +19,7 @@
* [Overview](newbs_building_firmware_configurator.md)
* [Step by Step](configurator_step_by_step.md)
* [Troubleshooting](configurator_troubleshooting.md)
+ * [Architecture](configurator_architecture.md)
* QMK API
* [Overview](api_overview.md)
* [API Documentation](api_docs.md)
diff --git a/docs/configurator_architecture.md b/docs/configurator_architecture.md
new file mode 100644
index 0000000000..0d7fc8a73b
--- /dev/null
+++ b/docs/configurator_architecture.md
@@ -0,0 +1,61 @@
+# QMK Configurator Architecture
+
+This page describes the web architecture behind QMK Configurator at a high level. If you are interested in the architecture of the QMK Configurator code itself you should start at the [qmk_configurator](https://github.com/qmk/qmk_configurator) repository.
+
+# Overview
+
+![QMK Configurator Architecture Diagram](configurator_diagram.svg)
+
+# Detailed Description
+
+QMK Configurator is a [Single Page Application](https://en.wikipedia.org/wiki/Single-page_application) that allows users to create custom keymaps for their QMK-compatible keyboard. They can export JSON representation of their keymaps and compile firmware binaries that can be flashed to their keyboard using a tool like [QMK Toolbox](https://github.com/qmk/qmk_toolbox).
+
+Configurator gets metadata about keyboards from the Keyboard Metadata store and submits compile requests to the QMK API. The results of those compile requests will be made available on [Digital Ocean Spaces](https://www.digitalocean.com/products/spaces/), an S3-compatible data store.
+
+## Configurator Frontend
+
+Address: <https://config.qmk.fm>
+
+The [Configurator Frontend](https://config.qmk.fm) is compiled into a set of static files that are served by Github Pages. This action happens every time a commit is pushed to the [qmk_configurator `master`](https://github.com/qmk/qmk_configurator) branch. You can view the status of these jobs on the [qmk_configurator actions tab](https://github.com/qmk/qmk_configurator/actions/workflows/build.yml).
+
+## Keyboard Metadata
+
+Address: <https://keyboards.qmk.fm>
+
+The Keyboard Metadata is generated every time a keyboard in [qmk_firmware](https://github.com/qmk/qmk_firmware) changes. The resulting JSON files are uploaded to Spaces and used by Configurator to generate UI for each keyboard. You can view the status of this job on the [qmk_firmware actions tab](https://github.com/qmk/qmk_firmware/actions/workflows/api.yml). If you are a QMK Collaborator you can manually run this job using the `workflow_dispatch` event trigger.
+
+## QMK API
+
+Address: <http://api.qmk.fm>
+
+The QMK API accepts `keymap.json` files for compilation. These are the same files you can use directly with `qmk compile` and `qmk flash`. When a `keymap.json` is submitted the browser will poll the status of the job periodically (every 2 seconds or longer, preferably) until the job has completed. The final status JSON will contain pointers to source and binary downloads for the keymap.
+
+QMK API always presents the source and binary downloads side-by-side to comply with the GPL.
+
+There are 3 non-error status responses from the API-
+
+1. Compile Job Queued
+2. Compile Job Running
+3. Compile Job Finished
+
+### Compile Job Queued
+
+This status indicates that the job has not yet been picked up by a [QMK Compiler](#qmk-compiler) node. Configurator shows this status as "Waiting for an oven".
+
+### Compile Job Running
+
+This status indicates that the job has started compiling. Configurator shows this status as "Baking".
+
+### Compile Job Finished
+
+This status indicates that the job has completed. There will be keys in the status JSON for source and binary downloads.
+
+## Redis/RQ
+
+QMK API uses RQ to distribute jobs to the available [QMK Compiler](#qmk-compiler) nodes. When a `keymap.json` is received it's put into the RQ queue, where a `qmk_compiler` node will pick it up from.
+
+## QMK Compiler
+
+[QMK Compiler](https://github.com/qmk/qmk_compiler) is what actually performs the compilation of the `keymap.json`. It does so by checking out the requested `qmk_firmware` branch, running `qmk compile keymap.json`, and then uploading the resulting source and binary to Digital Ocean Spaces.
+
+When users download their source/binary, API will redirect them to the authenticated Spaces download URL.
diff --git a/docs/configurator_diagram.drawio b/docs/configurator_diagram.drawio
new file mode 100644
index 0000000000..091a3a76b8
--- /dev/null
+++ b/docs/configurator_diagram.drawio
@@ -0,0 +1 @@
+<mxfile host="Electron" modified="2021-08-09T19:46:29.036Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.6.13 Chrome/89.0.4389.128 Electron/12.0.7 Safari/537.36" etag="PQ2r34UrZa0TfW4Fw0EV" version="14.6.13" type="device"><diagram id="NEtccoSKIy4HskWlhJpu" name="Page-1">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</diagram></mxfile> \ No newline at end of file
diff --git a/docs/configurator_diagram.svg b/docs/configurator_diagram.svg
new file mode 100644
index 0000000000..bcf0bf76d1
--- /dev/null
+++ b/docs/configurator_diagram.svg
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1261px" height="991px" viewBox="-0.5 -0.5 1261 991" style="background-color: rgb(255, 255, 255);"><defs/><g><path d="M 885.2 40 L 1060 40" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><image x="804.7" y="-0.5" width="80" height="80" xlink:href="data:image/png;base64,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"/><path d="M 845.33 302.4 C 815.12 302.4 794 277.79 794 251.59 C 794 220.27 819.22 200 845.71 200 C 871.22 200 896.4 220.93 896.4 251.18 C 896.4 278.28 874.84 302.4 845.33 302.4 Z" fill="#0d2636" stroke="none" pointer-events="all"/><path d="M 834.25 291.19 C 834.25 293.4 832.57 294.32 830.06 293.46 C 814.91 288.15 800.34 271.75 800.34 251.27 C 800.34 223.57 824.03 206.1 844.57 206.1 C 871.22 206.1 890.18 227.94 890.18 251 C 890.18 270.03 877.94 287.54 859.59 293.69 C 857.52 294.24 856.23 293.13 856.23 291.31 L 856.23 277.98 C 856.23 275.4 855.18 272.65 853.24 270.74 C 860.69 269.89 865.21 268.02 868.67 264.46 C 872.07 261.08 873.6 256.13 873.88 250.01 C 874.07 245.15 872.77 240.41 869.25 236.8 C 870.48 233.82 870.69 229.86 868.83 224.97 C 865.14 224.69 860.94 226.9 856.64 229.56 C 849.08 227.59 841.52 227.33 833.96 229.67 C 830.58 227.47 827.51 225 821.65 224.97 C 820.1 229.29 819.86 233.23 821.19 236.72 C 817 241.37 816.52 245.93 816.6 250.47 C 817.06 259.25 820.27 263.68 823.83 266.33 C 826.79 268.52 830.94 269.9 837.27 270.83 C 835.57 272.51 834.62 274.51 834.46 276.86 C 830.74 278.56 825.42 279.41 821.78 274.25 C 820.15 271.66 817.93 268.73 813.72 268.74 C 813.03 268.72 812.35 268.98 812.25 269.28 C 812.15 269.61 812.54 270.3 813.01 270.57 C 816.7 272.92 817.29 274.23 818.8 277.44 C 820.2 281.19 822.62 282.63 825.24 283.64 C 827.92 284.59 832.25 284.32 834.25 283.64 Z" fill="#ffffff" stroke="none" pointer-events="all"/><path d="M 845.2 193.63 L 845.2 86.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 845.2 198.88 L 841.7 191.88 L 845.2 193.63 L 848.7 191.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 845.2 81.12 L 848.7 88.12 L 845.2 86.37 L 841.7 88.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><rect x="1060" y="10" width="200" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 40px; margin-left: 1061px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: white; white-space: normal; word-wrap: normal; ">Clients Supported:<br />Chrome, Firefox<br />Desktop Only</div></div></div></foreignObject><text x="1160" y="44" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Clients Supported:...</text></switch></g><rect x="1060" y="206.2" width="200" height="90" fill="#ffffff" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 251px; margin-left: 1061px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><span>https://config.qmk.fm</span><br /><span>Single Page Site<br /></span>JavaScript/VUE<br />Source: qmk/qmk_configurator<br /><span>Host: Github Pages</span></div></div></div></foreignObject><text x="1160" y="255" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">https://config.qmk.fm...</text></switch></g><path d="M 896.4 251.2 L 1060 251.2" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><path d="M 880 457.9 L 1060 457.9" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><path d="M 833.22 495.8 C 828.16 495.8 821.72 491.64 821.72 483.75 L 821.72 475.34 L 813.15 475.34 C 811.65 475.34 810 474.49 810.03 471.92 L 810.03 455.36 C 810.03 453.81 810.93 452.17 812.91 452.12 L 821.72 452.12 L 821.72 431.37 C 821.72 425.69 826.62 420 833.69 420 L 856.49 420 C 858.53 420 861.18 421.45 863.72 423.93 L 876.39 436.67 C 879.49 440.04 880 441.77 880 443.93 L 880 484.09 C 880 490.1 875.34 495.8 867.72 495.8 Z" fill="#000000" stroke="none" pointer-events="all"/><path d="M 850.47 469.58 L 850.47 457.06 L 854.43 457.06 L 858.82 463.99 L 858.82 457.06 L 861.89 457.06 L 861.89 469.58 L 858.67 469.58 L 853.58 461.29 L 853.58 469.58 Z M 844.7 463.4 C 844.69 461.89 844.28 459.4 841.65 459.33 C 839.7 459.3 838.71 461.33 838.71 463.26 C 838.77 466.2 840.13 467.37 841.74 467.37 C 843.53 467.33 844.68 465.87 844.7 463.4 Z M 835.26 462.55 C 835.34 459.28 837.91 457.1 840.4 456.87 C 843.13 456.63 844.5 457.13 845.74 457.91 C 847.41 459.11 848.53 461.32 848.06 464.79 C 847.61 467.23 846.07 469.6 842.36 469.85 C 841 469.92 838.36 469.99 836.56 467.58 C 835.73 466.38 835.22 465.51 835.26 462.55 Z M 823.19 465.77 L 823.65 465.72 C 825.75 467.29 827.29 467.4 828.78 467.35 C 829.66 467.23 830.41 467.01 830.47 466.24 C 830.51 465.69 829.93 465.31 829.36 465.17 C 827.6 464.71 826.16 464.86 824.34 463.46 C 822.89 462.1 822.96 460.46 823.66 459.16 C 824.9 457.24 827.05 456.94 828.36 456.83 C 830.57 456.81 832.33 457.19 833.44 457.73 L 833.43 460.67 L 832.94 460.71 C 831.93 459.92 830.82 459.41 829.27 459.32 C 828.41 459.31 827.38 459.3 826.76 460 C 826.43 461.17 827.29 461.25 828.04 461.42 C 830.02 461.87 831.13 461.99 832.65 462.95 C 834.58 464.43 833.97 466.96 832.96 468.1 C 831.63 469.39 830.35 469.86 827.92 469.85 C 826.52 469.83 824.81 469.63 823.17 468.83 Z M 813.29 466.83 C 814.07 467.09 814.63 467.45 816.44 467.23 C 817.09 467.05 817.76 466.56 817.76 465.57 L 817.76 459.51 L 814.91 459.51 L 814.91 457.06 L 821.13 457.06 L 821.13 466.31 C 820.87 468.24 819.78 469.26 818.09 469.59 C 816.91 469.84 815.53 469.98 813.29 469.57 Z M 827.52 475.34 L 862.48 475.34 C 864.3 475.34 865.44 473.93 865.44 472.17 L 865.44 455.09 C 865.44 453.39 864.22 452.12 862.32 452.12 L 827.52 452.12 L 827.52 431.76 C 827.52 427.61 830.96 425.79 833.33 425.79 L 854.21 425.79 C 856.33 425.79 856.7 428.23 856.7 429.52 L 856.71 440.31 C 856.71 442.3 858.38 443.31 859.64 443.31 L 870.53 443.31 C 872.02 443.31 874.24 443.77 874.24 446.39 L 874.24 483.83 C 874.24 487.16 872.07 489.95 868.16 489.95 L 833.53 489.95 C 830.7 489.95 827.52 487.95 827.52 483.92 Z" fill="#ffffff" stroke="none" pointer-events="all"/><rect x="1060" y="412.9" width="200" height="90" fill="#ffffff" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 458px; margin-left: 1061px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">https://keyboards.qmk.fm<br />Keyboard Metadata<br />Source: qmk/qmk_firmware<br />GH Action: Update API Data<br />Host: DigitalOcean Spaces</div></div></div></foreignObject><text x="1160" y="462" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">https://keyboards.qmk.fm...</text></switch></g><path d="M 845.04 420 L 845.14 308.77" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 845.15 303.52 L 848.64 310.52 L 845.14 308.77 L 841.64 310.51 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 905 630 L 1060 630" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><path d="M 815 610 C 791 610 785 630 804.2 634 C 785 642.8 806.6 662 822.2 654 C 833 670 869 670 881 654 C 905 654 905 638 890 630 C 905 614 881 598 860 606 C 845 594 821 594 815 610 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 630px; margin-left: 786px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">QMK API</div></div></div></foreignObject><text x="845" y="634" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">QMK API</text></switch></g><rect x="1060" y="595" width="200" height="70" fill="#ffffff" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 630px; margin-left: 1061px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">https://api.qmk.fm<br />RESTful API<br />Source: qmk/qmk_api<br />Host: Rancher on DO VM's</div></div></div></foreignObject><text x="1160" y="634" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">https://api.qmk.fm...</text></switch></g><path d="M 794 275.46 L 709.04 315.72 Q 700 320 700 330 L 700 570 Q 700 580 709.45 583.26 L 785.69 609.55" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 790.65 611.26 L 782.89 612.29 L 785.69 609.55 L 785.17 605.67 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 460 251.8 L 290 251.8" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><path d="M 490 229 C 466 229 460 251.8 479.2 256.36 C 460 266.39 481.6 288.28 497.2 279.16 C 508 297.4 544 297.4 556 279.16 C 580 279.16 580 260.92 565 251.8 C 580 233.56 556 215.32 535 224.44 C 520 210.76 496 210.76 490 229 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 252px; margin-left: 461px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Digital Ocean<br />Spaces<br />(S3)</div></div></div></foreignObject><text x="520" y="255" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Digital Ocean...</text></switch></g><rect x="0" y="221.8" width="290" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 288px; height: 1px; padding-top: 252px; margin-left: 1px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">https://qmk-api.nyc3.cdn.digitaloceanspaces.com<br />Space: qmk-api<br />Host: Digital Ocean</div></div></div></foreignObject><text x="145" y="255" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">https://qmk-api.nyc3.cdn.digitaloceanspaces.com...</text></switch></g><path d="M 580 251.8 L 787.63 251.31" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 792.88 251.3 L 785.89 254.81 L 787.63 251.31 L 785.87 247.81 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 875 790 L 1060 790" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><path d="M 815 768 C 815 757.33 875 757.33 875 768 L 875 812 C 875 822.67 815 822.67 815 812 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 815 768 C 815 776 875 776 875 768 M 815 772 C 815 780 875 780 875 772 M 815 776 C 815 784 875 784 875 776" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 800px; margin-left: 816px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">RQ</div></div></div></foreignObject><text x="845" y="804" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">RQ</text></switch></g><rect x="1060" y="755" width="200" height="70" fill="#ffffff" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 790px; margin-left: 1061px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Redis / RQ<br />Job Queue<br />Source: qmk/qmk_redis<br />Host: Rancher on DO VM's</div></div></div></foreignObject><text x="1160" y="794" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Redis / RQ...</text></switch></g><path d="M 845 670 L 845 753.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 845 758.88 L 841.5 751.88 L 845 753.63 L 848.5 751.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><rect x="785" y="910" width="120" height="80" rx="12" ry="12" fill="#ffffff" stroke="#000000" pointer-events="all"/><rect x="787" y="912" width="116" height="76" rx="11.4" ry="11.4" fill="#ffffff" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 114px; height: 1px; padding-top: 950px; margin-left: 788px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">qmk_complier</div></div></div></foreignObject><text x="845" y="954" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">qmk_complier</text></switch></g><rect x="1060" y="915" width="200" height="70" fill="#ffffff" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 950px; margin-left: 1061px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">QMK Compiler<br />Job Runners<br />Source: qmk/qmk_compiler<br />Host: Rancher on DO VM's</div></div></div></foreignObject><text x="1160" y="954" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">QMK Compiler...</text></switch></g><path d="M 845 820 L 845 903.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 845 908.88 L 841.5 901.88 L 845 903.63 L 848.5 901.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 779.03 925.64 L 529.38 833.46 Q 520 830 520 820 L 520 303.77" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 783.95 927.46 L 776.17 928.32 L 779.03 925.64 L 778.6 921.75 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 520 298.52 L 523.5 305.52 L 520 303.77 L 516.5 305.52 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 905 950 L 1060 950" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Viewer does not support full SVG 1.1</text></a></switch></svg> \ No newline at end of file
diff --git a/lib/python/qmk/cli/lint.py b/lib/python/qmk/cli/lint.py
index a164dba632..02b31fbc41 100644
--- a/lib/python/qmk/cli/lint.py
+++ b/lib/python/qmk/cli/lint.py
@@ -4,7 +4,7 @@ from milc import cli
from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.info import info_json
-from qmk.keyboard import keyboard_completer
+from qmk.keyboard import find_readme, keyboard_completer
from qmk.keymap import locate_keymap
from qmk.path import is_keyboard, keyboard
@@ -31,7 +31,8 @@ def lint(cli):
ok = True
keyboard_path = keyboard(cli.config.lint.keyboard)
keyboard_info = info_json(cli.config.lint.keyboard)
- readme_path = keyboard_path / 'readme.md'
+ readme_path = find_readme(cli.config.lint.keyboard)
+ missing_readme_path = keyboard_path / 'readme.md'
# Check for errors in the info.json
if keyboard_info['parse_errors']:
@@ -43,9 +44,9 @@ def lint(cli):
cli.log.error('Warnings found when generating info.json (Strict mode enabled.)')
# Check for a readme.md and warn if it doesn't exist
- if not readme_path.exists():
+ if not readme_path:
ok = False
- cli.log.error('Missing %s', readme_path)
+ cli.log.error('Missing %s', missing_readme_path)
# Keymap specific checks
if cli.config.lint.keymap: