From a42930a699e56f1b01a6c2881bd95c2a1ab4cbe9 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Fri, 21 Aug 2026 23:51:40 +0200 Subject: [PATCH] build(make): run e2e without act, lint yaml, js and shell test-e2e drove Cypress through act, which fails to start where it cannot resolve a host address for its artifact server. It now starts Flask and Cypress in one shell via scripts/run-e2e.sh, so both share a network namespace; the act path stays available as test-e2e-act. The script guards the cases that made the old target lie: it aborts when something already serves the port instead of testing that server, checks that its own Flask is alive before trusting a response, pins Cypress to the same origin Flask binds, and drops ELECTRON_RUN_AS_NODE, which VS Code exports and which makes Cypress' bundled Electron reject its own flags. 30 YAML and 18 JavaScript files had no linter. yamllint runs correctness rules only, because the repository predates it and its cosmetic findings would be noise; key-duplicates is the one that earns its keep, since PyYAML keeps the last of two identical keys without complaining. eslint runs the recommended set and already found a dead getBoundingClientRect() call in navigation.js. Both get a CI job so make lint and the workflows stop diverging. flask>=3.1 because app.config["TRUSTED_HOSTS"] arrived in 3.1 and an older Flask accepts the key and ignores it. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/lint.yml | 55 ++++++++++++++++++++++++++ .yamllint | 23 +++++++++++ Makefile | 44 +++++++++++++++++++-- app/eslint.config.js | 78 +++++++++++++++++++++++++++++++++++++ app/package.json | 5 ++- app/static/js/navigation.js | 1 - pyproject.toml | 7 +++- scripts/run-e2e.sh | 73 ++++++++++++++++++++++++++++++++++ 8 files changed, 280 insertions(+), 6 deletions(-) create mode 100644 .yamllint create mode 100644 app/eslint.config.js create mode 100755 scripts/run-e2e.sh diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b479192..6dfc9ad 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,6 +19,61 @@ jobs: - name: Run actionlint run: docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:latest + lint-yaml: + name: Lint YAML + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install lint dependencies + run: | + python -m pip install --upgrade pip + pip install ".[dev]" + + - name: Run yamllint + run: yamllint --strict . + + lint-js: + name: Lint JavaScript + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: npm + cache-dependency-path: app/package.json + + - name: Install Node dependencies + working-directory: app + run: npm install + + - name: Run eslint + working-directory: app + run: npx eslint . + + lint-shell: + name: Lint shell scripts + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Run shellcheck + run: docker run --rm -v "$PWD:/mnt" -w /mnt koalaman/shellcheck:stable scripts/*.sh + lint-python: name: Lint Python runs-on: ubuntu-latest diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..8fce015 --- /dev/null +++ b/.yamllint @@ -0,0 +1,23 @@ +--- +# Correctness only. No `extends: default`, so nothing but the rules below runs: +# the repository predates this linter and its cosmetic findings (indentation, +# line length, trailing spaces) would be noise nobody acts on. +# +# key-duplicates is the rule that earns its keep: PyYAML keeps the last of two +# identical keys without complaining, so a duplicated entry in a translation +# catalogue silently drops a translation. +ignore: | + .git/ + .venv/ + venv/ + node_modules/ + app/node_modules/ + app/static/vendor/ + build/ + .ruff_cache/ + +rules: + key-duplicates: enable + octal-values: + forbid-implicit-octal: true + forbid-explicit-octal: true diff --git a/Makefile b/Makefile index 9dc9a2b..666efed 100644 --- a/Makefile +++ b/Makefile @@ -78,6 +78,8 @@ run-dev: env config config -p "$$PORT:$$PORT" \ --name portfolio \ -v "$(PWD)/app/:/app" \ + -e PORT="$$PORT" \ + -e TRUSTED_HOSTS="$$TRUSTED_HOSTS" \ -e FLASK_APP=app.py \ -e FLASK_ENV=development \ "$$IMAGE_NAME" @@ -89,6 +91,8 @@ run-prod: env config config docker run -d \ -p "$$PORT:$$PORT" \ --name portfolio \ + -e PORT="$$PORT" \ + -e TRUSTED_HOSTS="$$TRUSTED_HOSTS" \ "$$IMAGE_NAME" .PHONY: logs @@ -132,6 +136,14 @@ install-dev: # Install runtime and developer dependencies from pyproject.toml. $(PYTHON) -m pip install -e ".[dev]" +.PHONY: i18n +i18n: env config + # Fill missing content translations in app/i18n/content/ via LibreTranslate. + @$(call _require_env,LIBRETRANSLATE_URL); \ + $(PYTHON) utils/i18n_sync.py \ + --url "$$LIBRETRANSLATE_URL" \ + --api-key "$$LIBRETRANSLATE_API_KEY" + .PHONY: lint-actions lint-actions: # Lint GitHub Actions workflows. @@ -148,6 +160,21 @@ lint-docker: # Lint the Dockerfile. docker run --rm -i hadolint/hadolint < Dockerfile +.PHONY: lint-yaml +lint-yaml: install-dev + # Lint YAML for duplicate keys and ambiguous scalars (see .yamllint). + $(PYTHON) -m yamllint --strict . + +.PHONY: lint-js +lint-js: node-deps + # Lint the browser and Cypress JavaScript. + cd app && env -u ELECTRON_RUN_AS_NODE npx eslint . + +.PHONY: lint-shell +lint-shell: + # Lint the shell scripts. + docker run --rm -v "$$PWD:/mnt" -w /mnt koalaman/shellcheck:stable scripts/*.sh + .PHONY: test-lint test-lint: # Run lint guardrail tests. @@ -169,7 +196,7 @@ test-security: install $(PYTHON) -m unittest discover -s tests/security -t . .PHONY: lint -lint: lint-actions lint-python lint-docker test-lint +lint: lint-actions lint-python lint-yaml lint-js lint-docker lint-shell test-lint # Run the full lint suite. .PHONY: security @@ -179,9 +206,20 @@ security: install-dev test-security $(PYTHON) utils/export_runtime_requirements.py > /tmp/portfolio-runtime-requirements.txt $(PYTHON) -m pip_audit -r /tmp/portfolio-runtime-requirements.txt +.PHONY: node-deps +node-deps: + # Install the Cypress binary and the browser vendor assets into app/. + cd app && npm install + .PHONY: test-e2e -test-e2e: - # Run Cypress end-to-end tests via act (stop portfolio container to free port first). +test-e2e: env config node-deps + # Run Cypress against a locally started Flask app — no act, no runner image. + @$(call _require_env,PORT); \ + PORT="$$PORT" PYTHON="$(PYTHON)" scripts/run-e2e.sh + +.PHONY: test-e2e-act +test-e2e-act: + # Run the CI end-to-end job through act (stop portfolio container to free port first). -docker stop portfolio 2>/dev/null || true $(ACT) workflow_dispatch -W .github/workflows/tests.yml -j e2e -docker start portfolio 2>/dev/null || true diff --git a/app/eslint.config.js b/app/eslint.config.js new file mode 100644 index 0000000..50ea303 --- /dev/null +++ b/app/eslint.config.js @@ -0,0 +1,78 @@ +'use strict'; +/** + * Correctness only — eslint's recommended set, no stylistic rules. + * + * The browser scripts are plain