diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 47879a23824..43fa5de44bd 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -8,6 +8,9 @@ FROM mcr.microsoft.com/playwright:v1.61.1-noble ARG LUA_VERSION=5.1.5 ARG LUAROCKS_VERSION=3.11.1 ARG LUALS_VERSION=3.18.2 +# Not in requirements.txt on purpose: only needed to run scripts/proxy_lp.py, +# which is a local-development tool rather than a dependency of the repo +ARG MITMPROXY_VERSION=12.2.3 ENV DEBIAN_FRONTEND=noninteractive @@ -62,6 +65,7 @@ ENV VIRTUAL_ENV=/opt/venv ENV PATH=/opt/venv/bin:${PATH} RUN python3 -m venv "${VIRTUAL_ENV}" \ && pip install --no-cache-dir --upgrade pip \ + && pip install --no-cache-dir "mitmproxy==${MITMPROXY_VERSION}" \ && chmod -R a+rwX "${VIRTUAL_ENV}" # Runs before the features do, which is where the user we run as gets created. diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7a3fe0400de..f3c5d6e8558 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -27,8 +27,32 @@ "source": "lua-modules-node-modules", "target": "${containerWorkspaceFolder}/node_modules", "type": "volume" + }, + // Keeps the mitmproxy CA across rebuilds, so the certificate only has + // to be trusted in the browser once + { + "source": "lua-modules-mitmproxy", + "target": "/home/vscode/.mitmproxy", + "type": "volume" } ], + // mitmproxy, for serving locally built CSS/JS into the live wiki. + // forwardPorts is the editor side tunnel, appPort is what publishes the port + // for anyone driving the container from the CLI instead. Bound to loopback + // on purpose: publishing on every interface would leave an open forward + // proxy reachable from the local network. + "forwardPorts": [ + 8080 + ], + "appPort": [ + "127.0.0.1:8080:8080" + ], + "portsAttributes": { + "8080": { + "label": "mitmproxy", + "onAutoForward": "silent" + } + }, "containerEnv": { "PLAYWRIGHT_BROWSERS_PATH": "/ms-playwright" }, diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh index ec7e6e7a398..215635a7c8c 100755 --- a/.devcontainer/post-create.sh +++ b/.devcontainer/post-create.sh @@ -4,13 +4,17 @@ set -euo pipefail cd "$(dirname "$0")/.." echo "==> Installing npm dependencies" -# node_modules is a named volume, which docker creates owned by root -sudo chown "$(id -u):$(id -g)" node_modules +# these are named volumes, which docker creates owned by root +sudo chown "$(id -u):$(id -g)" node_modules ~/.mitmproxy npm install echo "==> Installing python dependencies" pip install --no-cache-dir -r requirements.txt +echo "==> Building css and js" +# The proxy serves these, and without them it silently passes requests through +npm run build + if [ ! -f .env ]; then echo "==> Creating .env from .env.example (fill in your bot credentials before deploying)" # Nobody chose these settings, we generated them, so start in dry-run and diff --git a/README.md b/README.md index 4eda132e0f8..e2356b7c7d4 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,18 @@ Open the repository in VS Code and pick *Dev Containers: Reopen in Container* (r If you prefer a native setup, follow the platform instructions below instead. +###### Previewing CSS and JS changes on the live wiki + +The container also ships [mitmproxy](https://mitmproxy.org/) and the dependencies for `scripts/proxy_lp.py`, which serves your locally built `lua/output/css/main.css` and `lua/output/js/main.js` in place of the ones liquipedia.net would load. See the [wiki page](https://github.com/Liquipedia/Lua-Modules/wiki/Local-Development-Setup-for-CSS-and-JS) for the full background; inside the container the setup is: + +1. Run `npm run build`, so there is something to serve. The container does this on first start, but the proxy has nothing to substitute until it has run, and it will tell you so rather than quietly serving you the real files. +2. Start the proxy with `python scripts/proxy_lp.py` from the repository root, or via the *Launch proxy* task in `.vscode/tasks.json`. It listens on port 8080, published to `127.0.0.1:8080` on your host. +3. Point your browser at that proxy. A switcher extension such as Proxy SwitchyOmega (HTTP, `127.0.0.1`, port `8080`) makes it easy to toggle on and off; a system-wide proxy setting works too. +4. With the proxy enabled, open and install the certificate for your browser, so it will trust the intercepted HTTPS responses. The CA is kept in a volume, so this is only needed once, not after every rebuild. +5. Edit `.scss` or `.js`, run `npm run build` again, then hard refresh the wiki page (Ctrl+Shift+R / Cmd+Shift+R). + +Turn the proxy off in your browser when you are done — while it is enabled, every request goes through the container. + ##### Windows Recommended to use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install). Then follow the Unix instructions. diff --git a/scripts/proxy_lp.py b/scripts/proxy_lp.py index 316d67a0ec7..a26da397085 100644 --- a/scripts/proxy_lp.py +++ b/scripts/proxy_lp.py @@ -23,26 +23,31 @@ def request(self, flow: http.HTTPFlow) -> None: self.__serve_local_js_resource(flow) def __serve_local_css_resource(self, flow: http.HTTPFlow): - with open("lua/output/css/main.css", "rb") as f: - flow.response = http.Response.make( - HTTPStatus.OK, - f.read(), - {"Content-Type": "text/css; charset=utf-8"}, - ) - flow.response.headers["Via"] = ( - f"{flow.response.http_version} LiquipediaMapper" - ) + self.__serve_local_file( + flow, "lua/output/css/main.css", "text/css; charset=utf-8" + ) def __serve_local_js_resource(self, flow: http.HTTPFlow): - with open("lua/output/js/main.js", "rb") as f: - flow.response = http.Response.make( - HTTPStatus.OK, - f.read(), - {"Content-Type": "text/javascript; charset=utf-8"}, - ) - flow.response.headers["Via"] = ( - f"{flow.response.http_version} LiquipediaMapper" - ) + self.__serve_local_file( + flow, "lua/output/js/main.js", "text/javascript; charset=utf-8" + ) + + def __serve_local_file(self, flow: http.HTTPFlow, path: str, content_type: str): + try: + with open(path, "rb") as f: + body = f.read() + except OSError as error: + # Leaving this unsaid means the request goes upstream, the page looks + # untouched and nothing explains why + print(f"LiquipediaMapper: {error}. Run npm run build first.") + return + + flow.response = http.Response.make( + HTTPStatus.OK, + body, + {"Content-Type": content_type}, + ) + flow.response.headers["Via"] = f"{flow.response.http_version} LiquipediaMapper" async def main():