From 418400ed3028ad6d6faf598030fe1caee5a5eff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20H=C3=B6rberg?= Date: Wed, 5 Jun 2024 21:00:45 +0200 Subject: [PATCH] Use Stoplight Elements to render OpenAPI docs (#693) Instead of compiling static html files from openapi.yaml using redoc the docs are now rendered in the browser using https://github.com/stoplightio/elements --- .github/workflows/ci.yml | 21 ++++++++-------- .gitignore | 1 - Dockerfile | 10 -------- Dockerfile.deb | 4 +-- Dockerfile.rpm | 3 +-- Makefile | 25 +++++++++++-------- debian/control | 2 +- lavinmq.spec | 2 +- openapi/README.md | 23 +++-------------- openapi/openapi.rb | 9 ++++--- openapi/redoc-serve-and-watch | 13 ---------- src/lavinmq/http/controller/static.cr | 3 ++- static/docs/.gitkeep | 0 static/docs/index.html | 21 ++++++++++++++++ {openapi => static/docs}/openapi.yaml | 7 +++--- {openapi => static/docs}/paths/bindings.yaml | 0 {openapi => static/docs}/paths/channels.yaml | 0 .../docs}/paths/connections.yaml | 0 {openapi => static/docs}/paths/consumers.yaml | 0 .../docs}/paths/definitions.yaml | 0 {openapi => static/docs}/paths/exchanges.yaml | 0 {openapi => static/docs}/paths/main.yaml | 0 {openapi => static/docs}/paths/nodes.yaml | 0 .../docs}/paths/parameters.yaml | 0 .../docs}/paths/permissions.yaml | 0 {openapi => static/docs}/paths/policies.yaml | 0 {openapi => static/docs}/paths/queues.yaml | 0 {openapi => static/docs}/paths/users.yaml | 0 {openapi => static/docs}/paths/vhosts.yaml | 0 .../docs}/schemas/bindings.yaml | 0 .../docs}/schemas/channels.yaml | 0 .../docs}/schemas/connections.yaml | 0 .../docs}/schemas/consumers.yaml | 0 .../docs}/schemas/definitions.yaml | 0 .../docs}/schemas/exchanges.yaml | 0 {openapi => static/docs}/schemas/main.yaml | 0 {openapi => static/docs}/schemas/nodes.yaml | 0 .../docs}/schemas/parameters.yaml | 0 .../docs}/schemas/permissions.yaml | 0 .../docs}/schemas/policies.yaml | 0 {openapi => static/docs}/schemas/queues.yaml | 0 {openapi => static/docs}/schemas/users.yaml | 0 {openapi => static/docs}/schemas/vhosts.yaml | 0 43 files changed, 63 insertions(+), 81 deletions(-) delete mode 100755 openapi/redoc-serve-and-watch delete mode 100644 static/docs/.gitkeep create mode 100644 static/docs/index.html rename {openapi => static/docs}/openapi.yaml (98%) rename {openapi => static/docs}/paths/bindings.yaml (100%) rename {openapi => static/docs}/paths/channels.yaml (100%) rename {openapi => static/docs}/paths/connections.yaml (100%) rename {openapi => static/docs}/paths/consumers.yaml (100%) rename {openapi => static/docs}/paths/definitions.yaml (100%) rename {openapi => static/docs}/paths/exchanges.yaml (100%) rename {openapi => static/docs}/paths/main.yaml (100%) rename {openapi => static/docs}/paths/nodes.yaml (100%) rename {openapi => static/docs}/paths/parameters.yaml (100%) rename {openapi => static/docs}/paths/permissions.yaml (100%) rename {openapi => static/docs}/paths/policies.yaml (100%) rename {openapi => static/docs}/paths/queues.yaml (100%) rename {openapi => static/docs}/paths/users.yaml (100%) rename {openapi => static/docs}/paths/vhosts.yaml (100%) rename {openapi => static/docs}/schemas/bindings.yaml (100%) rename {openapi => static/docs}/schemas/channels.yaml (100%) rename {openapi => static/docs}/schemas/connections.yaml (100%) rename {openapi => static/docs}/schemas/consumers.yaml (100%) rename {openapi => static/docs}/schemas/definitions.yaml (100%) rename {openapi => static/docs}/schemas/exchanges.yaml (100%) rename {openapi => static/docs}/schemas/main.yaml (100%) rename {openapi => static/docs}/schemas/nodes.yaml (100%) rename {openapi => static/docs}/schemas/parameters.yaml (100%) rename {openapi => static/docs}/schemas/permissions.yaml (100%) rename {openapi => static/docs}/schemas/policies.yaml (100%) rename {openapi => static/docs}/schemas/queues.yaml (100%) rename {openapi => static/docs}/schemas/users.yaml (100%) rename {openapi => static/docs}/schemas/vhosts.yaml (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f5ad3ef95..e41def26b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: - name: Run ameba run: ameba/bin/ameba src spec - js-lint: + lint-js: name: Lint javascript runs-on: ubuntu-latest continue-on-error: true @@ -62,21 +62,20 @@ jobs: - uses: actions/setup-node@v4 with: node-version: latest - - name: Setup node - run: npm install -g standard - name: Run standard - run: standard static/js + run: make lint-js - api-lint: - name: Lint and build HTTP API documentation + lint-openapi: + name: Lint OpenAPI runs-on: ubuntu-latest continue-on-error: true steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Lint and build HTTP API documentation - run: make docs + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: latest + - name: Run spectral + run: make lint-openapi spec: name: Spec diff --git a/.gitignore b/.gitignore index 912e843a2e..3c4fa6f43f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ /lib/ /bin/ /node_modules/ -/static/docs/ /static/js/lib/ /tmp/ *.log diff --git a/Dockerfile b/Dockerfile index 946b7c405f..973d01d7ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,20 +21,10 @@ COPY .ameba.yml . RUN bin/ameba RUN crystal tool format --check -# Build docs in npm container -FROM node:lts AS docbuilder -WORKDIR /tmp -RUN npm install redoc-cli @stoplight/spectral-cli -COPY Makefile shard.yml . -COPY openapi openapi -RUN make docs - # Build FROM base AS builder COPY Makefile . RUN make js lib -COPY --from=docbuilder /tmp/openapi/openapi.yaml /tmp/openapi/.spectral.json openapi/ -COPY --from=docbuilder /tmp/static/docs/index.html static/docs/index.html ARG MAKEFLAGS=-j2 RUN make all bin/lavinmq-debug diff --git a/Dockerfile.deb b/Dockerfile.deb index 66593d100c..13cb4d464f 100644 --- a/Dockerfile.deb +++ b/Dockerfile.deb @@ -1,13 +1,11 @@ ARG build_image=84codes/crystal:latest-ubuntu-20.04 FROM $build_image AS builder -RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - -RUN apt-get install -y devscripts nodejs help2man lintian debhelper liblz4-dev +RUN apt-get update && apt-get install -y devscripts help2man lintian debhelper liblz4-dev ARG version WORKDIR /usr/src/lavinmq_${version} COPY Makefile README.md LICENSE NOTICE CHANGELOG.md shard.yml shard.lock ./ COPY extras/lavinmq.service extras/lavinmq.ini extras/ -COPY openapi/ openapi/ COPY static/ static/ COPY views/ views/ COPY src/ src/ diff --git a/Dockerfile.rpm b/Dockerfile.rpm index 49d62dd25c..cc64c7dfbd 100644 --- a/Dockerfile.rpm +++ b/Dockerfile.rpm @@ -2,7 +2,7 @@ ARG build_image=84codes/crystal:latest-fedora-39 FROM $build_image AS builder RUN dnf install -y --nodocs --setopt=install_weak_deps=False \ - rpmdevtools rpmlint systemd-rpm-macros /usr/bin/npm make help2man lz4-devel + rpmdevtools rpmlint systemd-rpm-macros make help2man lz4-devel RUN rpmdev-setuptree COPY lavinmq.spec /root/rpmbuild/SPECS/ ARG version @@ -11,7 +11,6 @@ RUN rpmlint /root/rpmbuild/SPECS/lavinmq.spec WORKDIR /usr/src/lavinmq COPY Makefile README.md LICENSE NOTICE CHANGELOG.md shard.yml shard.lock ./ COPY extras/lavinmq.service extras/lavinmq.ini extras/ -COPY openapi/ openapi/ COPY static/ static/ COPY views/ views/ COPY src/ src/ diff --git a/Makefile b/Makefile index ec2f80421a..22298016f0 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ BINS := bin/lavinmq bin/lavinmqctl bin/lavinmqperf SOURCES := $(shell find src/lavinmq src/stdlib -name '*.cr' 2> /dev/null) -JS := static/js/lib/chunks/helpers.segment.js static/js/lib/chart.js static/js/lib/amqp-websocket-client.mjs static/js/lib/amqp-websocket-client.mjs.map static/js/lib/luxon.js static/js/lib/chartjs-adapter-luxon.esm.js -DOCS := static/docs/index.html +JS := static/js/lib/chunks/helpers.segment.js static/js/lib/chart.js static/js/lib/amqp-websocket-client.mjs static/js/lib/amqp-websocket-client.mjs.map static/js/lib/luxon.js static/js/lib/chartjs-adapter-luxon.esm.js static/js/lib/elements-8.2.0.js static/js/lib/elements-8.2.0.css CRYSTAL_FLAGS := --release override CRYSTAL_FLAGS += --error-on-warnings --link-flags=-pie @@ -44,9 +43,11 @@ static/js/lib/chartjs-adapter-luxon.esm.js: | static/js/lib curl --retry 5 -sLo $@ https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@1.3.1/dist/chartjs-adapter-luxon.esm.js sed -i'' -e "s|\(import { _adapters } from\).*|\1 './chart.js'|; s|\(import { DateTime } from\).*|\1 './luxon.js'|" $@ -static/docs/index.html: openapi/openapi.yaml openapi/.spectral.json $(wildcard openapi/paths/*.yaml) $(wildcard openapi/schemas/*.yaml) - npx --package=@stoplight/spectral-cli spectral --ruleset openapi/.spectral.json lint $< - npx @redocly/cli build-docs --output $@ $< +static/js/lib/elements-8.2.0.js: | static/js/lib + curl --retry 5 -sLo $@ https://unpkg.com/@stoplight/elements@8.2.0/web-components.min.js + +static/js/lib/elements-8.2.0.css: | static/js/lib + curl --retry 5 -sLo $@ https://unpkg.com/@stoplight/elements@8.2.0/styles.min.css man1/lavinmq.1: bin/lavinmq | man1 help2man -Nn "fast and advanced message queue server" $< -o $@ @@ -62,19 +63,23 @@ MANPAGES := man1/lavinmq.1 man1/lavinmqctl.1 man1/lavinmqperf.1 .PHONY: man man: $(MANPAGES) -.PHONY: docs -docs: $(DOCS) - .PHONY: js js: $(JS) .PHONY: deps -deps: js lib docs +deps: js lib .PHONY: lint lint: lib lib/ameba/bin/ameba src/ - standard static/js + +.PHONY: lint-js +lint-js: + npx standard static/js + +.PHONY: lint-openapi +lint-openapi: + npx --package=@stoplight/spectral-cli spectral --ruleset openapi/.spectral.json lint static/docs/openapi.yaml .PHONY: test test: lib diff --git a/debian/control b/debian/control index 933fcdd022..fd386b7814 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Standards-Version: 4.6.0 Homepage: https://github.com/cloudamqp/lavinmq Section: net Priority: optional -Build-Depends: debhelper (>= 12), crystal (>= 1.8.0), nodejs (>= 12), curl +Build-Depends: debhelper (>= 12), crystal (>= 1.8.0), curl Maintainer: CloudAMQP Package: lavinmq diff --git a/lavinmq.spec b/lavinmq.spec index 3e635f4eb1..7f6a41d9e1 100644 --- a/lavinmq.spec +++ b/lavinmq.spec @@ -4,7 +4,7 @@ Version: 1.0.0 Release: 1%{?dist} License: ASL 2.0 -BuildRequires: systemd-rpm-macros crystal npm curl help2man +BuildRequires: systemd-rpm-macros crystal curl help2man Requires(pre): shadow-utils URL: https://github.com/cloudamqp/lavinmq Source: lavinmq.tar.gz diff --git a/openapi/README.md b/openapi/README.md index 68b6ae2e0a..bb57b5af66 100644 --- a/openapi/README.md +++ b/openapi/README.md @@ -1,32 +1,15 @@ -# LavinMQ Management HTTP API OpenAPI spec +# LavinMQ HTTP API OpenAPI spec -Run `make docs` to build docs (calls `npx redoc-cli`). - -It outputs to `static/docs/index.html` so you can view the docs at [http://localhost:15672/docs/](http://localhost:15672/docs/) if you have LavinMQ running. - -The dependencies: - -* [Spectral] is used to lint the documentation. To run it manually: `docker run --rm -it -v $(pwd):/tmp stoplight/spectral:6 --ruleset /tmp/openapi/.spectral.json lint openapi/openapi.yaml` -* [ReDoc] is to build the documentation. - To run it manually: `npx redoc-cli bundle openapi.yaml -o static/docs/index.html` - -You can preview docs using: - -``` -npx redoc-cli serve openapi.yaml -``` +OpenAPI docs rendered in browser using https://github.com/stoplightio/elements (Note the gotcha: the browser caches the YAML files even if they have changed, open dev console in the browser to mitigate.) ## OpenAPI notes -* `summary` is the short description (used in the redoc menu for instance) +* `summary` is the short description * `description` is a longer description (supports Markdown) The following script was used to generate the OpenAPI Spec YAML structure ruby openapi.rb -[Spectral]: https://github.com/stoplightio/spectral -[ReDoc]: https://github.com/Redocly/redoc -[ReDoc Docker image]: https://github.com/Redocly/redoc/tree/master/config/docker#official-redoc-docker-image diff --git a/openapi/openapi.rb b/openapi/openapi.rb index 8ac5c9b5be..33cdef5542 100644 --- a/openapi/openapi.rb +++ b/openapi/openapi.rb @@ -99,7 +99,8 @@ def ref end end -openapi_spec = YAML.load(File.read(File.join(__dir__, "openapi.yaml"))) +docs_path = File.join("..", "static", "docs") +openapi_spec = YAML.load(File.read(File.join(__dir__, docs_path, "openapi.yaml"))) src_code_dir = File.expand_path(File.join(__dir__, "..")) http_code_dir = File.join(src_code_dir, "src/lavinmq/http") files = Dir.glob(File.join(http_code_dir, "**/*.cr")) @@ -154,7 +155,7 @@ def ref } if WRITE - File.write(File.join(__dir__, Helper.schemas_path(tag_name)), YAML.dump(openapi_schemas)) + File.write(File.join(__dir__, docs_path, Helper.schemas_path(tag_name)), YAML.dump(openapi_schemas)) end schemas[tag_name] = { "$ref" => "./schemas/#{tag_name}.yaml#/#{tag_name}" } @@ -171,7 +172,7 @@ def ref end if WRITE - File.write(File.join(__dir__, Helper.paths_path(src_file)), YAML.dump(openapi_routes)) + File.write(File.join(__dir__, docs_path, Helper.paths_path(src_file)), YAML.dump(openapi_routes)) end end @@ -181,5 +182,5 @@ def ref puts YAML.dump(openapi_spec) if DEBUG if WRITE - File.write(File.join(__dir__, "openapi.yaml"), YAML.dump(openapi_spec)) + File.write(File.join(__dir__, docs_path, "openapi.yaml"), YAML.dump(openapi_spec)) end diff --git a/openapi/redoc-serve-and-watch b/openapi/redoc-serve-and-watch deleted file mode 100755 index 9cc6a17495..0000000000 --- a/openapi/redoc-serve-and-watch +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -set -eu - -ROOT=${PWD%/*} - -echo "View the docs at http://localhost:8080/" - -set -x - -docker run -it --rm -p 8080:80 \ - -v $ROOT:/usr/share/nginx/html/swagger/ \ - -e SPEC_URL=swagger/openapi/openapi.yaml redocly/redoc diff --git a/src/lavinmq/http/controller/static.cr b/src/lavinmq/http/controller/static.cr index 99a3d09dd5..a5ab20bf83 100644 --- a/src/lavinmq/http/controller/static.cr +++ b/src/lavinmq/http/controller/static.cr @@ -94,13 +94,14 @@ module LavinMQ when ".txt" then "text/plain;charset=utf-8" when ".html" then "text/html;charset=utf-8" when ".css" then "text/css;charset=utf-8" - when ".js", ".mjs" then "application/javascript;charset=utf-8" + when ".js", ".mjs" then "application/javascript" when ".png" then "image/png" when ".ico" then "image/x-icon" when ".jpg" then "image/jpeg" when ".gif" then "image/gif" when ".svg" then "image/svg+xml" when ".webp" then "image/webp" + when ".yaml" then "application/yaml" else "application/octet-stream" end end diff --git a/static/docs/.gitkeep b/static/docs/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/static/docs/index.html b/static/docs/index.html new file mode 100644 index 0000000000..7e165b619d --- /dev/null +++ b/static/docs/index.html @@ -0,0 +1,21 @@ + + + + + + LavinMQ HTTP API + + + + + + + + diff --git a/openapi/openapi.yaml b/static/docs/openapi.yaml similarity index 98% rename from openapi/openapi.yaml rename to static/docs/openapi.yaml index 2a34779b86..5cac8134ad 100644 --- a/openapi/openapi.yaml +++ b/static/docs/openapi.yaml @@ -1,16 +1,15 @@ --- openapi: 3.0.3 info: - version: - "$ref": "../shard.yml#/version" - title: LavinMQ Management HTTP API + version: v1.2.12 + title: LavinMQ HTTP API description: HTTP API to programmatically manage all aspects of LavinMQ. contact: name: LavinMQ Team url: https://www.lavinmq.com/ email: contact@lavinmq.com servers: -- url: http://localhost:15672/api +- url: /api components: schemas: ErrorResponse: diff --git a/openapi/paths/bindings.yaml b/static/docs/paths/bindings.yaml similarity index 100% rename from openapi/paths/bindings.yaml rename to static/docs/paths/bindings.yaml diff --git a/openapi/paths/channels.yaml b/static/docs/paths/channels.yaml similarity index 100% rename from openapi/paths/channels.yaml rename to static/docs/paths/channels.yaml diff --git a/openapi/paths/connections.yaml b/static/docs/paths/connections.yaml similarity index 100% rename from openapi/paths/connections.yaml rename to static/docs/paths/connections.yaml diff --git a/openapi/paths/consumers.yaml b/static/docs/paths/consumers.yaml similarity index 100% rename from openapi/paths/consumers.yaml rename to static/docs/paths/consumers.yaml diff --git a/openapi/paths/definitions.yaml b/static/docs/paths/definitions.yaml similarity index 100% rename from openapi/paths/definitions.yaml rename to static/docs/paths/definitions.yaml diff --git a/openapi/paths/exchanges.yaml b/static/docs/paths/exchanges.yaml similarity index 100% rename from openapi/paths/exchanges.yaml rename to static/docs/paths/exchanges.yaml diff --git a/openapi/paths/main.yaml b/static/docs/paths/main.yaml similarity index 100% rename from openapi/paths/main.yaml rename to static/docs/paths/main.yaml diff --git a/openapi/paths/nodes.yaml b/static/docs/paths/nodes.yaml similarity index 100% rename from openapi/paths/nodes.yaml rename to static/docs/paths/nodes.yaml diff --git a/openapi/paths/parameters.yaml b/static/docs/paths/parameters.yaml similarity index 100% rename from openapi/paths/parameters.yaml rename to static/docs/paths/parameters.yaml diff --git a/openapi/paths/permissions.yaml b/static/docs/paths/permissions.yaml similarity index 100% rename from openapi/paths/permissions.yaml rename to static/docs/paths/permissions.yaml diff --git a/openapi/paths/policies.yaml b/static/docs/paths/policies.yaml similarity index 100% rename from openapi/paths/policies.yaml rename to static/docs/paths/policies.yaml diff --git a/openapi/paths/queues.yaml b/static/docs/paths/queues.yaml similarity index 100% rename from openapi/paths/queues.yaml rename to static/docs/paths/queues.yaml diff --git a/openapi/paths/users.yaml b/static/docs/paths/users.yaml similarity index 100% rename from openapi/paths/users.yaml rename to static/docs/paths/users.yaml diff --git a/openapi/paths/vhosts.yaml b/static/docs/paths/vhosts.yaml similarity index 100% rename from openapi/paths/vhosts.yaml rename to static/docs/paths/vhosts.yaml diff --git a/openapi/schemas/bindings.yaml b/static/docs/schemas/bindings.yaml similarity index 100% rename from openapi/schemas/bindings.yaml rename to static/docs/schemas/bindings.yaml diff --git a/openapi/schemas/channels.yaml b/static/docs/schemas/channels.yaml similarity index 100% rename from openapi/schemas/channels.yaml rename to static/docs/schemas/channels.yaml diff --git a/openapi/schemas/connections.yaml b/static/docs/schemas/connections.yaml similarity index 100% rename from openapi/schemas/connections.yaml rename to static/docs/schemas/connections.yaml diff --git a/openapi/schemas/consumers.yaml b/static/docs/schemas/consumers.yaml similarity index 100% rename from openapi/schemas/consumers.yaml rename to static/docs/schemas/consumers.yaml diff --git a/openapi/schemas/definitions.yaml b/static/docs/schemas/definitions.yaml similarity index 100% rename from openapi/schemas/definitions.yaml rename to static/docs/schemas/definitions.yaml diff --git a/openapi/schemas/exchanges.yaml b/static/docs/schemas/exchanges.yaml similarity index 100% rename from openapi/schemas/exchanges.yaml rename to static/docs/schemas/exchanges.yaml diff --git a/openapi/schemas/main.yaml b/static/docs/schemas/main.yaml similarity index 100% rename from openapi/schemas/main.yaml rename to static/docs/schemas/main.yaml diff --git a/openapi/schemas/nodes.yaml b/static/docs/schemas/nodes.yaml similarity index 100% rename from openapi/schemas/nodes.yaml rename to static/docs/schemas/nodes.yaml diff --git a/openapi/schemas/parameters.yaml b/static/docs/schemas/parameters.yaml similarity index 100% rename from openapi/schemas/parameters.yaml rename to static/docs/schemas/parameters.yaml diff --git a/openapi/schemas/permissions.yaml b/static/docs/schemas/permissions.yaml similarity index 100% rename from openapi/schemas/permissions.yaml rename to static/docs/schemas/permissions.yaml diff --git a/openapi/schemas/policies.yaml b/static/docs/schemas/policies.yaml similarity index 100% rename from openapi/schemas/policies.yaml rename to static/docs/schemas/policies.yaml diff --git a/openapi/schemas/queues.yaml b/static/docs/schemas/queues.yaml similarity index 100% rename from openapi/schemas/queues.yaml rename to static/docs/schemas/queues.yaml diff --git a/openapi/schemas/users.yaml b/static/docs/schemas/users.yaml similarity index 100% rename from openapi/schemas/users.yaml rename to static/docs/schemas/users.yaml diff --git a/openapi/schemas/vhosts.yaml b/static/docs/schemas/vhosts.yaml similarity index 100% rename from openapi/schemas/vhosts.yaml rename to static/docs/schemas/vhosts.yaml