Skip to content
Open
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
14 changes: 14 additions & 0 deletions .github/workflow-templates/lint_workshop_caller.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Checks that workshop material can be opened: notebooks are valid, scripts parse.
# The checks live in QLS-MiCM/MiCM_PR_Bot. Change them there, not here.
name: Lint workshop material

on:
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
lint:
uses: QLS-MiCM/MiCM_PR_Bot/.github/workflows/reusable_lint_workshop.yml@main
65 changes: 65 additions & 0 deletions .github/workflows/reusable_lint_workshop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Lints the files a pull request changes in a workshop repo.
# Called via pull_request (NOT pull_request_target): the token is read-only and there are
# no secrets, so checking out the PR is safe here. Do not add secrets to this workflow.
# The linter is taken from this repo, not from the PR, so a PR cannot change its own checks.
name: Lint workshop material (Reusable)

on:
workflow_call:
inputs:
linter_ref:
description: MiCM_PR_Bot branch, tag or commit to take the linter from
type: string
default: main

permissions:
contents: read

jobs:
lint:
runs-on: ubuntu-latest

steps:
- name: Checkout workshop repo
uses: actions/checkout@v5
with:
# The pull request merge commit and its first parent (the base branch)
fetch-depth: 2

- name: Checkout linter
uses: actions/checkout@v5
with:
repository: QLS-MiCM/MiCM_PR_Bot
ref: ${{ inputs.linter_ref }}
path: .micm_pr_bot
sparse-checkout: scripts

- uses: actions/setup-python@v6
with:
python-version: "3.12"

- name: Install linter dependencies
run: pip install --quiet nbformat pyyaml

- name: Select files
id: select
env:
EVENT: ${{ github.event_name }}
run: |
if [ "$EVENT" = "pull_request" ]; then
scope="--changed-since HEAD^1"
else
scope="--all"
fi
echo "scope=$scope" >> "$GITHUB_OUTPUT"
python .micm_pr_bot/scripts/lint_workshop.py $scope --print-files | tee "$RUNNER_TEMP/files.txt"
if grep -qiE '\.(r|rmd|qmd)$' "$RUNNER_TEMP/files.txt"; then
echo "needs_r=true" >> "$GITHUB_OUTPUT"
fi

- name: Set up R
if: steps.select.outputs.needs_r == 'true'
uses: r-lib/actions/setup-r@v2

- name: Lint
run: python .micm_pr_bot/scripts/lint_workshop.py ${{ steps.select.outputs.scope }} --github
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,45 @@ Central GitHub Actions automation for the [QLS-MiCM](https://github.com/QLS-MiCM
| `reusable_pr_check_issue_deploy.yml` | Reusable workflow: posts a PR checklist comment and creates a tracking issue in `Workshop_Template` |
| `distribute_pr_check_workflow.yml` | Commits the thin caller workflow directly to each org repo's default branch when the canonical template changes |
| `workflow-templates/pr_check_issue_deploy_caller.yml` | Canonical caller text (not executed; read by the distributor) |
| `reusable_lint_workshop.yml` | Reusable workflow: lints the files a pull request changes in a workshop repo |
| `workflow-templates/lint_workshop_caller.yml` | Caller text for the lint workflow (not executed, not distributed automatically) |
| `scripts/lint_workshop.py` | The linter run by `reusable_lint_workshop.yml` |


## Updating workflows

- **Checklist / issue logic:** Edit `reusable_pr_check_issue_deploy.yml` only. All org repos pick up changes on the next PR open (no redistribution).
- **Caller trigger or guard:** Edit `workflow-templates/pr_check_issue_deploy_caller.yml` and merge to `main`. The distributor commits directly to each org repo's default branch.
- **Lint checks:** Edit `scripts/lint_workshop.py` or `reusable_lint_workshop.yml`. Repos that have the lint caller pick up changes on their next pull request.

## Workshop lint

The lint workflow checks the files a pull request changes.

It fails the check when a file cannot be opened or run at all:

- `.ipynb` is not valid JSON
- `.py`, `.R` or `.sh` does not parse
- `.Rmd` / `.qmd` has a broken YAML header or a code chunk that is never closed
- `.mlx` is not a valid archive

It only warns (annotations and a job summary) when:

- a notebook code cell or an Rmd/qmd chunk does not parse (student versions contain intentional blanks)
- a notebook was committed with an error output
- a notebook uses a relative image path, which does not render in Colab

To enable it in a repo, copy `workflow-templates/lint_workshop_caller.yml` to `.github/workflows/lint_workshop.yml` in that repo. The distributor does not push this caller.

Unlike the checklist workflow, the lint workflow runs on `pull_request`, with a read-only token and no secrets, so it can check out the pull request. The linter itself is always taken from this repo.

To run the linter locally:

```
pip install nbformat pyyaml
python scripts/lint_workshop.py path/to/file.ipynb # given files
python scripts/lint_workshop.py --all # every tracked file in the current repo
```

## Reusable workflow access

Expand Down
Loading