Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions .github/workflows/backend-ci.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
name: Python CI

on:
pull_request:
branches:
- "**"
# This is so we can call CI locally from other workflows that might want to
# run CI before doing whatever task they're doing. Like the release workflow.
# Invoked by ci.yml, which runs on pull requests and aggregates every check
# into a single `gate` job, and by release.yml before it publishes anything.
workflow_call:

concurrency:
Expand Down
52 changes: 52 additions & 0 deletions .github/workflows/brand-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Brand CI

on:
# Invoked by ci.yml, which aggregates every check into a single `gate` job.
workflow_call:

concurrency:
# See backend-ci.yml for why non-pull_request events key on the run id.
group: ${{ github.workflow }}-brand-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

defaults:
run:
working-directory: "./brand-sample"

jobs:
build:
name: build
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: './brand-sample/.nvmrc'
cache: 'npm'
cache-dependency-path: './brand-sample/package-lock.json'

# `npm ci` rather than `npm install`, for the same reason as
# frontend-ci.yml: install the lockfile exactly and fail on drift.
- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

# dist/ is committed and served to real MFEs straight from GitHub via
# https://cdn.jsdelivr.net/gh/openedx/sample-plugin@main/brand-sample/dist/
# so it is a published artifact, not a build leftover. Bumping
# package-lock.json does not regenerate it, which means a dependency
# update can silently leave the CSS people download out of step with the
# sources here. Rebuilding and diffing is what catches that.
#
# If this step fails, the fix is to run `npm run build` in brand-sample
# and commit the result.
- name: Check that the committed dist/ matches the build
run: git diff --exit-code -- dist/
80 changes: 80 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: CI

# Every check that runs on a pull request, aggregated behind a single `gate`
# job.
#
# The point of the aggregation is branch protection. GitHub's required status
# checks are matched by name, so requiring the individual jobs means the
# required list has to spell out every matrix leg -- `tests (ubuntu-latest,
# 3.12, django52)` and friends -- and adding or retiring a Python or Django
# version silently breaks protection until somebody remembers to update the
# repository settings. Worse, two of the per-package jobs were both called
# `build`, so they could not be told apart in that list at all.
#
# `gate` succeeds only when every job it depends on succeeded, so `gate` is
# the only name that has to be required. It never changes, whatever happens to
# the matrix.
#
# This matters for automerge specifically: GitHub's auto-merge waits for
# *required* checks and nothing else, so a check that runs but is not required
# cannot hold a merge back.

on:
pull_request:
branches:
- "**"
# So the release workflow can run every check before it publishes anything.
workflow_call:

concurrency:
# See backend-ci.yml for why non-pull_request events key on the run id.
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

jobs:
backend:
uses: ./.github/workflows/backend-ci.yml

frontend:
uses: ./.github/workflows/frontend-ci.yml

tutor:
uses: ./.github/workflows/tutor-ci.yml

brand:
uses: ./.github/workflows/brand-ci.yml

gate:
name: gate
# `always()` so the gate still reports when something upstream fails.
# Without it the gate would be skipped, and a skipped required check never
# reports at all, which blocks the pull request forever instead of failing
# it.
if: always()
needs: [backend, frontend, tutor, brand]
runs-on: ubuntu-latest

steps:
# Deliberately not using a third-party aggregation action. This job is
# the one thing standing between a pull request and `main`, so it is the
# last place worth adding a dependency -- and the logic is five lines.
#
# Every result must be `success`. Anything else fails the gate,
# `skipped` included: no job here is conditional, so a skip means
# something went wrong rather than something was legitimately not
# needed. If a genuinely optional job is added later, this is the place
# that has to learn about it.
- name: Check that every job succeeded
run: |
results='${{ join(needs.*.result, ' ') }}'
echo "Job results: $results"
for result in $results; do
if [ "$result" != "success" ]; then
echo "::error::A required job reported '$result'."
exit 1
fi
done
echo "All jobs succeeded."
5 changes: 1 addition & 4 deletions .github/workflows/frontend-ci.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
name: Frontend CI

on:
pull_request:
branches:
- "**"
# So the release workflow can run these checks before it publishes anything.
# Invoked by ci.yml, which aggregates every check into a single `gate` job.
workflow_call:

concurrency:
Expand Down
15 changes: 6 additions & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,18 @@ on:
branches: [main]

jobs:
run_backend_tests:
uses: ./.github/workflows/backend-ci.yml

run_frontend_tests:
uses: ./.github/workflows/frontend-ci.yml

run_tutor_tests:
uses: ./.github/workflows/tutor-ci.yml
# The same aggregate a pull request has to pass, so the release runs exactly
# the checks that were required to merge -- and picks up any package added to
# ci.yml later without needing a change here.
run_ci:
uses: ./.github/workflows/ci.yml

release:
# Every package this workflow publishes has to build before we tag
# anything. The GitHub release and the PyPI uploads cannot be taken back,
# so a package that only fails to build in its publish job would leave the
# release half-finished.
needs: [run_backend_tests, run_frontend_tests, run_tutor_tests]
needs: [run_ci]
runs-on: ubuntu-latest
if: github.ref_name == 'main'
concurrency:
Expand Down
5 changes: 1 addition & 4 deletions .github/workflows/tutor-ci.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
name: Tutor Plugin CI

on:
pull_request:
branches:
- "**"
# So the release workflow can run these checks before it publishes anything.
# Invoked by ci.yml, which aggregates every check into a single `gate` job.
workflow_call:

concurrency:
Expand Down
16 changes: 14 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,20 @@ This is a **sample plugin repository** that demonstrates all major Open edX plug
- Backend testing: `cd backend-plugin-sample && pytest` or `cd backend-plugin-sample && make test`
- Run a single test: `cd backend-plugin-sample && pytest tests/test_models.py::test_placeholder`
- Quality checks: `cd backend-plugin-sample && make quality`
- Install requirements: `cd backend-plugin-sample && make requirements`
- Compile requirements: `cd backend-plugin-sample && make compile-requirements`

Python dependencies are managed with `uv` and locked in
`backend-plugin-sample/uv.lock`. There is no `requirements/` directory.

- Install the dev environment: `cd backend-plugin-sample && make requirements` (`uv sync --group dev`)
- Relock without upgrading: `cd backend-plugin-sample && make compile-requirements` (`uv lock`)
- Upgrade everything and resync the edx-lint constraints: `cd backend-plugin-sample && make upgrade`

Routine dependency bumps are handled by Renovate, one pull request per
package, so `make upgrade` is only needed for a deliberate full re-resolve.
All three package managers enforce a 7-day minimum release age: see
`exclude-newer` in `backend-plugin-sample/pyproject.toml`, `min-release-age`
in the two `.npmrc` files, and `minimumReleaseAge` in `renovate.json5`. Those
values have to stay in agreement.

## Code Style Guidelines
- Python: Follow PEP 8 with max line length of 120
Expand Down
4 changes: 0 additions & 4 deletions backend-plugin-sample/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@ docs/modules.rst
docs/sample_plugin.rst
docs/sample_plugin.*.rst

# Private requirements
requirements/private.in
requirements/private.txt

# Test cruft
default.db
pii_report
25 changes: 18 additions & 7 deletions backend-plugin-sample/.readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ sphinx:
configuration: docs/conf.py
fail_on_warning: true

# Note: this configuration is currently dormant. Read the Docs looks for
# .readthedocs.yaml in the repository root, and this file is a directory down,
# so it is not read unless a project is created and pointed at this path
# explicitly. No such project exists today. The docs are built and
# warning-checked on every pull request by `tox -e docs` instead.
#
# It is kept, and kept correct, so that enabling hosted docs later is a matter
# of creating the project rather than rewriting this file. Every path here is
# relative to backend-plugin-sample/, not the repository root.

# Set the version of python needed to build these docs.
build:
os: "ubuntu-22.04"
Expand All @@ -18,10 +28,11 @@ build:

python:
install:
- requirements: requirements/doc.txt

# This will pip install this repo into the python environment
# if you are using this in a repo that is not pip installable
# then you should remove the following two lines.
- method: pip
path: .
# `uv sync` against the committed uv.lock, so the docs build against the
# same pinned versions as CI -- and inherits the minimum release age
# recorded in the lock. This used to be `requirements: requirements/doc.txt`,
# which stopped existing when the repo moved to uv.lock.
- method: uv
command: sync
groups:
- doc
25 changes: 23 additions & 2 deletions backend-plugin-sample/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.PHONY: clean clean_tox compile_translations coverage diff_cover docs dummy_translations \
extract_translations fake_translations help pii_check pull_translations \
quality requirements selfcheck test test-all upgrade compile-requirements validate install_transifex_client
quality requirements selfcheck test test-all upgrade compile-requirements validate install_transifex_client \
check-constraints

.DEFAULT_GOAL := help

Expand Down Expand Up @@ -35,10 +36,30 @@ docs: ## generate Sphinx HTML documentation, including API docs
compile-requirements: ## generate the uv.lock file without upgrading packages
uv lock

# Renovate handles routine dependency bumps now, one pull request per package.
# This target remains the way to re-resolve everything at once, and the only
# thing that regenerates [tool.uv].constraint-dependencies from edx-lint.
upgrade: ## upgrade all packages in uv.lock and sync constraints from edx-lint
uv run --with edx-lint edx_lint write_uv_constraints pyproject.toml
uv run --with 'edx-lint>=6.2' edx_lint write_uv_constraints pyproject.toml
uv lock --upgrade

# Regenerates the constraints into a scratch copy and diffs, rather than
# checking `git diff`, so that it reports only this drift and not whatever else
# happens to be uncommitted in the working tree.
check-constraints: ## fail if [tool.uv].constraint-dependencies is out of date
@cp pyproject.toml pyproject.toml.orig
@uv run --with 'edx-lint>=6.2' edx_lint write_uv_constraints pyproject.toml >/dev/null
@if cmp -s pyproject.toml.orig pyproject.toml; then \
rm -f pyproject.toml.orig; \
else \
diff -u pyproject.toml.orig pyproject.toml || true; \
mv pyproject.toml.orig pyproject.toml; \
echo ""; \
echo "[tool.uv].constraint-dependencies is out of date with edx-lint."; \
echo "Run 'make upgrade' and commit the result."; \
exit 1; \
fi

quality: ## check coding style with pycodestyle and pylint
tox -e quality

Expand Down
11 changes: 9 additions & 2 deletions backend-plugin-sample/pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
# SERIOUSLY.
#
# ------------------------------
# Generated by edx-lint version: 5.6.0
# Generated by edx-lint version: 6.2.0
# ------------------------------
[MASTER]
ignore = migrations
Expand Down Expand Up @@ -257,6 +257,8 @@ enable =
deprecated-pragma,
unrecognized-inline-option,
useless-suppression,

pii-invalid-no-pii-annotation,
disable =
bad-indentation,
broad-exception-raised,
Expand Down Expand Up @@ -387,4 +389,9 @@ int-import-graph =
[EXCEPTIONS]
overgeneral-exceptions = builtins.Exception

# 79853056a08a5009db4a6e9a8ca00c194036ac5a
[PII]
pii-terms =
email,
username

# 80d06cad4da142244dcae7bb096c0394ef420379
Loading