From b1391a3da69ccba69e34e0b1b86d15b35a141ce2 Mon Sep 17 00:00:00 2001 From: 2pk03 Date: Mon, 12 Oct 2026 15:57:18 +0200 Subject: [PATCH] Add make lint to commit-check and CI Adds markdownlint, a lychee link check, clang-format on the sketches, actionlint, yamllint and gitleaks next to shellcheck, and fixes what they found: code block languages, a broken list in the Pico W README and sketch formatting. --- .clang-format | 3 +++ .github/workflows/ci.yml | 11 +++++++++-- .markdownlint-cli2.yaml | 9 +++++++++ .yamllint.yml | 8 ++++++++ Makefile | 26 +++++++++++++++++++++++--- README.md | 2 +- examples/curl/README.md | 2 +- examples/esp32-http/README.md | 2 +- examples/esp32-http/esp32-http.ino | 6 ++++-- examples/esp32-mqtt/README.md | 2 +- examples/esp32-mqtt/esp32-mqtt.ino | 8 ++++---- examples/picow-mqtt/README.md | 24 ++++++++++++------------ lychee.toml | 14 ++++++++++++++ scripts/install-lint-tools.sh | 14 ++++++++++++++ scripts/lychee.sh | 9 +++++++++ 15 files changed, 113 insertions(+), 27 deletions(-) create mode 100644 .clang-format create mode 100644 .markdownlint-cli2.yaml create mode 100644 .yamllint.yml create mode 100644 lychee.toml create mode 100755 scripts/install-lint-tools.sh create mode 100755 scripts/lychee.sh diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..fcefb62 --- /dev/null +++ b/.clang-format @@ -0,0 +1,3 @@ +BasedOnStyle: Google +ColumnLimit: 100 +SortIncludes: Never diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dece29d..fbbc8a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,12 +9,19 @@ permissions: contents: read jobs: - shellcheck: + lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - uses: actions/setup-node@v4 + with: + node-version: "22" - run: sudo apt-get update && sudo apt-get install -y shellcheck - - run: make shellcheck + - run: sh scripts/install-lint-tools.sh + - run: make lint micropython: runs-on: ubuntu-latest diff --git a/.markdownlint-cli2.yaml b/.markdownlint-cli2.yaml new file mode 100644 index 0000000..6b6a034 --- /dev/null +++ b/.markdownlint-cli2.yaml @@ -0,0 +1,9 @@ +config: + default: true + MD013: false # long lines are fine in prose + MD033: false # allow inline HTML + MD041: false # README may start with a badge or image + MD024: + siblings_only: true +ignores: + - "**/node_modules/**" diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..08d0655 --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,8 @@ +extends: default +rules: + line-length: disable + document-start: disable + truthy: + allowed-values: ["true", "false", "on"] + comments: + min-spaces-from-content: 1 diff --git a/Makefile b/Makefile index eb84e22..b4cb2ab 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,36 @@ FQBN ?= esp32:esp32:esp32 SKETCHES := examples/esp32-mqtt examples/esp32-http +CSOURCES := $(wildcard examples/*/*.ino examples/*/*.c examples/*/*.h) -.PHONY: commit-check lint compile shellcheck micropython arduino +.PHONY: commit-check lint compile shellcheck markdownlint links clang-format actionlint yamllint gitleaks micropython arduino commit-check: lint compile -lint: shellcheck +lint: shellcheck markdownlint links clang-format actionlint yamllint gitleaks + @echo "lint passed." compile: micropython arduino shellcheck: - shellcheck examples/curl/*.sh + shellcheck examples/curl/*.sh scripts/*.sh + +markdownlint: + markdownlint-cli2 "**/*.md" + +links: + sh scripts/lychee.sh '**/*.md' examples + +clang-format: + clang-format --dry-run --Werror $(CSOURCES) + +actionlint: + actionlint + +yamllint: + yamllint --strict . + +gitleaks: + gitleaks dir . --no-banner --redact micropython: mpy-cross -o /tmp/picow-main.mpy examples/picow-mqtt/main.py diff --git a/README.md b/README.md index 9e9b9d1..6c28139 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Each example is one sketch or script with a short README. make commit-check ``` -This runs shellcheck, a MicroPython compile check, and `arduino-cli compile` for both ESP32 sketches. +This runs `make lint` and then compiles the examples. `make lint` runs shellcheck, markdownlint, a lychee link check, clang-format on the sketches, actionlint, yamllint and gitleaks. The compile step runs a MicroPython compile check and `arduino-cli compile` for both ESP32 sketches. On Linux, `sh scripts/install-lint-tools.sh` installs the lint tools; on macOS use `brew install shellcheck markdownlint-cli2 lychee clang-format actionlint yamllint gitleaks`. ## License diff --git a/examples/curl/README.md b/examples/curl/README.md index e36f6bd..bb8de20 100644 --- a/examples/curl/README.md +++ b/examples/curl/README.md @@ -48,7 +48,7 @@ INF_API=https://api.example.com DEVICE_ID= DEVICE_TOKEN= ./infinime ## Expected output -``` +```text Posting reported state: temp=21.5 Reading state back { diff --git a/examples/esp32-http/README.md b/examples/esp32-http/README.md index ea1d183..b3a80a9 100644 --- a/examples/esp32-http/README.md +++ b/examples/esp32-http/README.md @@ -52,7 +52,7 @@ arduino-cli compile --fqbn esp32:esp32:esp32 examples/esp32-http ## Expected output (serial, 115200) -``` +```text WiFi..... connected POST /devices/states -> 200, sent {"device":"","reported":{"data":{"temp":24.1,"uptime":5}}} GET /devices/states -> 200, desired: "desired":{"timestamp":"...","data":{"led":true} diff --git a/examples/esp32-http/esp32-http.ino b/examples/esp32-http/esp32-http.ino index c319b46..acd21b5 100644 --- a/examples/esp32-http/esp32-http.ino +++ b/examples/esp32-http/esp32-http.ino @@ -11,7 +11,7 @@ // ---- Config: change these ---- const char* WIFI_SSID = "your-wifi"; const char* WIFI_PASS = "your-password"; -const char* API_URL = "https://api.example.com"; // infinimesh REST gateway +const char* API_URL = "https://api.example.com"; // infinimesh REST gateway const char* DEVICE_ID = "00000000-0000-0000-0000-000000000000"; // device uuid const char* DEVICE_TOKEN = "paste the device token here"; @@ -52,7 +52,9 @@ void readDesired() { String res = http.getString(); int i = res.indexOf("\"desired\""); Serial.printf("GET /devices/states -> 200, desired: %s\n", - i >= 0 ? res.substring(i, res.indexOf('}', res.indexOf("\"data\"", i)) + 1).c_str() : "none"); + i >= 0 + ? res.substring(i, res.indexOf('}', res.indexOf("\"data\"", i)) + 1).c_str() + : "none"); } else { Serial.printf("GET /devices/states -> %d\n", code); } diff --git a/examples/esp32-mqtt/README.md b/examples/esp32-mqtt/README.md index b9db809..3d95a5b 100644 --- a/examples/esp32-mqtt/README.md +++ b/examples/esp32-mqtt/README.md @@ -55,7 +55,7 @@ arduino-cli compile --fqbn esp32:esp32:esp32 examples/esp32-mqtt ## Expected output (serial, 115200) -``` +```text WiFi..... connected, IP 192.168.1.42 MQTT connected Published {"temp":23.4,"uptime":12} (id 12345) diff --git a/examples/esp32-mqtt/esp32-mqtt.ino b/examples/esp32-mqtt/esp32-mqtt.ino index fc712af..2293d0a 100644 --- a/examples/esp32-mqtt/esp32-mqtt.ino +++ b/examples/esp32-mqtt/esp32-mqtt.ino @@ -12,9 +12,9 @@ // ---- Config: change these ---- const char* WIFI_SSID = "your-wifi"; const char* WIFI_PASS = "your-password"; -const char* MQTT_URI = "mqtts://mqtt.example.com:8883"; // your infinimesh MQTT host +const char* MQTT_URI = "mqtts://mqtt.example.com:8883"; // your infinimesh MQTT host const char* DEVICE_ID = "00000000-0000-0000-0000-000000000000"; // device uuid -const int PUBLISH_QOS = 1; // 0 or 1 +const int PUBLISH_QOS = 1; // 0 or 1 // CA that signed the server certificate. For Let's Encrypt use ISRG Root X1. // For a local test, paste core's hack/server.crt. @@ -60,8 +60,8 @@ static void onEvent(void* args, esp_event_base_t base, int32_t id, void* data) { Serial.printf("PUBACK for message %d\n", event->msg_id); break; case MQTT_EVENT_DATA: - Serial.printf("Desired state on %.*s: %.*s\n", - event->topic_len, event->topic, event->data_len, event->data); + Serial.printf("Desired state on %.*s: %.*s\n", event->topic_len, event->topic, + event->data_len, event->data); break; case MQTT_EVENT_ERROR: Serial.println("MQTT error (check certificates, host and device id)"); diff --git a/examples/picow-mqtt/README.md b/examples/picow-mqtt/README.md index a4fa7d0..ec0cf1a 100644 --- a/examples/picow-mqtt/README.md +++ b/examples/picow-mqtt/README.md @@ -19,25 +19,25 @@ None. 2. Install the MQTT library: `mpremote mip install umqtt.simple` 3. Make the device key and certificate (EC P-256 is fastest) and convert them to DER: -```bash -openssl ecparam -name prime256v1 -genkey -noout -out device.key -openssl req -new -x509 -sha256 -key device.key -out device.crt -days 365 -subj "/CN=my-pico" -openssl x509 -in device.crt -outform DER -out device.der -openssl ec -in device.key -outform DER -out key.der -openssl x509 -in isrgrootx1.pem -outform DER -out ca.der # or your server CA -``` + ```bash + openssl ecparam -name prime256v1 -genkey -noout -out device.key + openssl req -new -x509 -sha256 -key device.key -out device.crt -days 365 -subj "/CN=my-pico" + openssl x509 -in device.crt -outform DER -out device.der + openssl ec -in device.key -outform DER -out key.der + openssl x509 -in isrgrootx1.pem -outform DER -out ca.der # or your server CA + ``` 4. Register `device.crt` when you create the device (see [../esp32-mqtt/README.md](../esp32-mqtt/README.md)). 5. Edit the config at the top of `main.py`, then copy the files: -```bash -mpremote cp device.der key.der ca.der main.py : -mpremote reset -``` + ```bash + mpremote cp device.der key.der ca.der main.py : + mpremote reset + ``` ## Expected output -``` +```text WiFi connected 192.168.1.43 MQTT connected Published {"temp": 21.5, "uptime": 14} diff --git a/lychee.toml b/lychee.toml new file mode 100644 index 0000000..c64e43a --- /dev/null +++ b/lychee.toml @@ -0,0 +1,14 @@ +# Link check. Local hosts and example domains are skipped. +exclude = [ + "^https?://localhost", + "^https?://127\\.0\\.0\\.1", + "nip\\.io", + "infinimesh\\.local", + "example\\.(com|org)", + "^mqtts?://", +] +exclude_path = [".git"] +max_retries = 2 +timeout = 20 +accept = ["200..=299", "403", "429"] +include_fragments = false diff --git a/scripts/install-lint-tools.sh b/scripts/install-lint-tools.sh new file mode 100755 index 0000000..873c162 --- /dev/null +++ b/scripts/install-lint-tools.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# Installs the lint tools on a Linux x86_64 CI runner. +set -eu +BIN="${BIN:-/usr/local/bin}" +ACTIONLINT=1.7.7 +GITLEAKS=8.28.0 +LYCHEE=0.20.1 + +pip install --quiet yamllint==1.37.1 clang-format==23.1.3 +npm install --global --silent markdownlint-cli2@0.23.3 + +curl -fsSL "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT}/actionlint_${ACTIONLINT}_linux_amd64.tar.gz" | sudo tar xz -C "$BIN" actionlint +curl -fsSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS}/gitleaks_${GITLEAKS}_linux_x64.tar.gz" | sudo tar xz -C "$BIN" gitleaks +curl -fsSL "https://github.com/lycheeverse/lychee/releases/download/lychee-v${LYCHEE}/lychee-x86_64-unknown-linux-gnu.tar.gz" | sudo tar xz -C "$BIN" lychee diff --git a/scripts/lychee.sh b/scripts/lychee.sh new file mode 100755 index 0000000..fc5bf8f --- /dev/null +++ b/scripts/lychee.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# Link check. Falls back to local links only when there is no network. +set -eu +if curl -fsS -m 5 -o /dev/null https://github.com; then + lychee --config lychee.toml --no-progress "$@" +else + echo "no network, checking local links only" + lychee --config lychee.toml --no-progress --offline "$@" +fi