From 84cf0878d70c1c67d5e1c63c29ed4e3e067a85f5 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 5 May 2026 19:59:19 +0200 Subject: [PATCH 1/2] Fix Node.js 24 workflow compatibility and refresh template/repo docs (#20) * Bump actions for Node.js 24 * Drop Node.js 24 compatibility override * Bump actions for Node.js 24 * Refactor dashboard publish to custom script * Update pixi.lock * Escape GitHub expression in dashboard template * Re-apply latest templates to itself * Update README project setup instructions --- .copier-answers.yml | 2 +- .github/actions/download-artifact/action.yml | 4 +- .github/actions/github-script/action.yml | 2 +- .../actions/setup-easyscience-bot/action.yml | 4 +- .github/actions/setup-pixi/action.yml | 2 +- .github/actions/upload-artifact/action.yml | 4 +- .github/actions/upload-codecov/action.yml | 4 +- .github/workflows/backmerge.yml | 9 +- .github/workflows/cleanup.yml | 9 +- .github/workflows/issues-labels.yml | 7 +- .github/workflows/lint-format.yml | 5 +- .github/workflows/release-notes.yml | 9 +- .github/workflows/release-pr.yml | 5 +- .github/workflows/security.yml | 9 +- README.md | 151 ++++++++++-------- pixi.lock | 4 +- .../actions/download-artifact/action.yml | 4 +- .../.github/actions/github-script/action.yml | 2 +- .../actions/setup-easyscience-bot/action.yml | 4 +- .../.github/actions/setup-pixi/action.yml | 2 +- .../actions/upload-artifact/action.yml | 4 +- .../.github/actions/upload-codecov/action.yml | 4 +- template/.github/scripts/publish-dashboard.sh | 77 +++++++++ template/.github/workflows/backmerge.yml | 9 +- template/.github/workflows/cleanup.yml | 9 +- template/.github/workflows/coverage.yml | 9 +- .../.github/workflows/dashboard.yml.jinja | 35 ++-- template/.github/workflows/docs.yml.jinja | 5 +- template/.github/workflows/issues-labels.yml | 7 +- .../.github/workflows/lint-format.yml.jinja | 5 +- .../.github/workflows/release-notes.yml.jinja | 9 +- template/.github/workflows/release-pr.yml | 5 +- template/.github/workflows/security.yml | 9 +- template/.github/workflows/test-trigger.yml | 7 +- ... == 'app' else '_exclude_'}}test.yml.jinja | 5 +- ...' else '_exclude_'}}pypi-publish.yml.jinja | 7 +- ...lib' else '_exclude_'}}pypi-test.yml.jinja | 5 +- ... == 'lib' else '_exclude_'}}test.yml.jinja | 7 +- ...xclude_'}}tutorial-tests-trigger.yml.jinja | 7 +- ...else '_exclude_'}}tutorial-tests.yml.jinja | 5 +- tools/license_headers.py | 8 +- 41 files changed, 245 insertions(+), 235 deletions(-) create mode 100644 template/.github/scripts/publish-dashboard.sh diff --git a/.copier-answers.yml b/.copier-answers.yml index 7315d51..6c651cf 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,6 +1,6 @@ # WARNING: Do not edit this file manually. # Any changes will be overwritten by Copier. -_commit: v0.10.1-41-g508666e +_commit: v0.11.0-7-gb5113cf _src_path: gh:easyscience/templates lib_docs_url: https://easyscience.github.io/templates lib_doi: 10.5281/zenodo.18163581 diff --git a/.github/actions/download-artifact/action.yml b/.github/actions/download-artifact/action.yml index e4fd62f..d1fff1a 100644 --- a/.github/actions/download-artifact/action.yml +++ b/.github/actions/download-artifact/action.yml @@ -1,5 +1,5 @@ name: 'Download artifact' -description: 'Generic wrapper for actions/download-artifact' +description: 'Wrapper for actions/download-artifact' inputs: name: description: 'Name of the artifact to download' @@ -39,7 +39,7 @@ runs: using: 'composite' steps: - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: ${{ inputs.name }} path: ${{ inputs.path }} diff --git a/.github/actions/github-script/action.yml b/.github/actions/github-script/action.yml index ab32da5..50de89b 100644 --- a/.github/actions/github-script/action.yml +++ b/.github/actions/github-script/action.yml @@ -13,7 +13,7 @@ inputs: runs: using: 'composite' steps: - - uses: actions/github-script@v8 + - uses: actions/github-script@v9 with: script: ${{ inputs.script }} github-token: ${{ inputs.github-token }} diff --git a/.github/actions/setup-easyscience-bot/action.yml b/.github/actions/setup-easyscience-bot/action.yml index 4b28eaf..e51eb01 100644 --- a/.github/actions/setup-easyscience-bot/action.yml +++ b/.github/actions/setup-easyscience-bot/action.yml @@ -22,9 +22,9 @@ runs: steps: - name: Create GitHub App installation token id: app-token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3 with: - app-id: ${{ inputs.app-id }} + client-id: ${{ inputs.app-id }} private-key: ${{ inputs.private-key }} repositories: ${{ inputs.repositories }} diff --git a/.github/actions/setup-pixi/action.yml b/.github/actions/setup-pixi/action.yml index 167ee62..ec7d7ba 100644 --- a/.github/actions/setup-pixi/action.yml +++ b/.github/actions/setup-pixi/action.yml @@ -1,5 +1,5 @@ name: 'Setup Pixi Environment' -description: 'Sets up pixi with common configuration' +description: 'Wrapper for prefix-dev/setup-pixi' inputs: environments: description: 'Pixi environments to setup' diff --git a/.github/actions/upload-artifact/action.yml b/.github/actions/upload-artifact/action.yml index 825ac39..fe8e468 100644 --- a/.github/actions/upload-artifact/action.yml +++ b/.github/actions/upload-artifact/action.yml @@ -1,5 +1,5 @@ name: 'Upload artifact' -description: 'Generic wrapper for actions/upload-artifact' +description: 'Wrapper for actions/upload-artifact' inputs: name: description: 'Artifact name' @@ -38,7 +38,7 @@ runs: using: 'composite' steps: - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ inputs.name }} path: ${{ inputs.path }} diff --git a/.github/actions/upload-codecov/action.yml b/.github/actions/upload-codecov/action.yml index 37d6298..0cb15d1 100644 --- a/.github/actions/upload-codecov/action.yml +++ b/.github/actions/upload-codecov/action.yml @@ -1,5 +1,5 @@ name: 'Upload coverage to Codecov' -description: 'Generic wrapper for codecov/codecov-action@v5' +description: 'Wrapper for codecov/codecov-action' inputs: name: @@ -32,7 +32,7 @@ inputs: runs: using: composite steps: - - uses: codecov/codecov-action@v5 + - uses: codecov/codecov-action@v6 with: name: ${{ inputs.name }} flags: ${{ inputs.flags }} diff --git a/.github/workflows/backmerge.yml b/.github/workflows/backmerge.yml index d8569f0..36ce6f5 100644 --- a/.github/workflows/backmerge.yml +++ b/.github/workflows/backmerge.yml @@ -20,11 +20,6 @@ concurrency: group: backmerge-master-into-develop cancel-in-progress: false -# Opt into Node.js 24 for all JavaScript actions. -# Remove once all referenced actions natively target Node 24. -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - jobs: backmerge: runs-on: ubuntu-latest @@ -32,7 +27,7 @@ jobs: steps: - name: Checkout repository (for local actions) - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup easyscience[bot] id: bot @@ -43,7 +38,7 @@ jobs: repositories: ${{ github.event.repository.name }} - name: Checkout repository (with bot token) - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ steps.bot.outputs.token }} diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index d3ebf3a..21c72b3 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -15,11 +15,11 @@ on: days: description: 'Number of days.' required: true - default: 30 + default: '30' minimum_runs: description: 'The minimum runs to keep for each workflow.' required: true - default: 6 + default: '6' delete_workflow_pattern: description: 'The name or filename of the workflow. if not set then it will target all @@ -61,11 +61,6 @@ on: - 'false' - 'true' -# Opt into Node.js 24 for all JavaScript actions. -# Remove once all referenced actions natively target Node 24. -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - jobs: del-runs: runs-on: ubuntu-latest diff --git a/.github/workflows/issues-labels.yml b/.github/workflows/issues-labels.yml index ed9d1c8..31a69ad 100644 --- a/.github/workflows/issues-labels.yml +++ b/.github/workflows/issues-labels.yml @@ -11,11 +11,6 @@ on: permissions: issues: write -# Opt into Node.js 24 for all JavaScript actions. -# Remove once all referenced actions natively target Node 24. -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - jobs: check-labels: if: github.actor != 'easyscience[bot]' @@ -28,7 +23,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup easyscience[bot] id: bot diff --git a/.github/workflows/lint-format.yml b/.github/workflows/lint-format.yml index 720c9a4..16dd95c 100644 --- a/.github/workflows/lint-format.yml +++ b/.github/workflows/lint-format.yml @@ -34,9 +34,6 @@ permissions: # Set the environment variables to be used in all jobs defined in this workflow env: CI_BRANCH: ${{ github.head_ref || github.ref_name }} - # Opt into Node.js 24 for all JavaScript actions. - # Remove once all referenced actions natively target Node 24. - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: lint-format: @@ -44,7 +41,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up pixi uses: ./.github/actions/setup-pixi diff --git a/.github/workflows/release-notes.yml b/.github/workflows/release-notes.yml index c353958..48ed0e2 100644 --- a/.github/workflows/release-notes.yml +++ b/.github/workflows/release-notes.yml @@ -10,11 +10,6 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: -# Opt into Node.js 24 for all JavaScript actions. -# Remove once all referenced actions natively target Node 24. -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - jobs: draft-release-notes: permissions: @@ -25,7 +20,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 # full history with tags to get the version number @@ -66,7 +61,7 @@ jobs: GITHUB_TOKEN: ${{ steps.bot.outputs.token }} - name: Create GitHub draft release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: draft: true tag_name: ${{ steps.draft.outputs.tag_name }} diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 5694a2a..0e716c2 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -24,16 +24,13 @@ permissions: env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} SOURCE_BRANCH: ${{ inputs.source_branch || 'develop' }} - # Opt into Node.js 24 for all JavaScript actions. - # Remove once all referenced actions natively target Node 24. - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: create-pull-request: runs-on: ubuntu-latest steps: - name: Checkout ${{ env.SOURCE_BRANCH }} branch - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ env.SOURCE_BRANCH }} diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index a53e9af..0f43db3 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -44,11 +44,6 @@ permissions: contents: read security-events: write -# Opt into Node.js 24 for all JavaScript actions. -# Remove once all referenced actions natively target Node 24. -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - jobs: codeql: name: Code scanning @@ -66,13 +61,13 @@ jobs: # focused on the active dev branch. - name: Checkout repository (scheduled → develop) if: ${{ github.event_name == 'schedule' }} - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: develop - name: Checkout repository if: ${{ github.event_name != 'schedule' }} - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Initialize CodeQL uses: github/codeql-action/init@v4 diff --git a/README.md b/README.md index 4fa68be..0512515 100644 --- a/README.md +++ b/README.md @@ -29,43 +29,44 @@ updates over time. - [2.3. Install Copier](#23-install-copier) - [2.4. Generate Project Description (Home Repository)](#24-generate-project-description-home-repository) - [2.5. Generate Library / Application Repositories](#25-generate-library--application-repositories) - - [2.6. Where Are Answers Stored?](#26-where-answers-are-stored) + - [2.6. Where Are Answers Stored?](#26-where-are-answers-stored) - [2.7. Push Changes to the Repository](#27-push-changes-to-the-repository) - [2.8. Finalize Project Setup](#28-finalize-project-setup) - - [2.8. Code Quality Checks](#28-code-quality-checks) - - [2.9. Push Changes to the Repository](#29-push-changes-to-the-repository) + - [2.9. Code Quality Checks](#29-code-quality-checks) + - [2.10. Push Changes to the Repository](#210-push-changes-to-the-repository) - [Step 3: Post-Initialization Repository Setup](#-step-3-post-initialization-repository-setup) - [3.1. Create develop Branch](#31-create-develop-branch) - [3.2. About gh-pages Branch and Pages Activation](#32-about-gh-pages-branch-and-pages-activation) - - [3.3. Add Repository Secrets](#34-add-repository-secrets) - - [3.4. Set Branch Protection Rules](#35-set-branch-protection-rules) + - [3.3. Add Repository Secrets](#33-add-repository-secrets) + - [3.4. Set Branch Protection Rules](#34-set-branch-protection-rules) + - [3.5. Set Repository Configuration](#35-set-repository-configuration) - [Step 4: Updating Existing Repositories](#-step-4-updating-existing-repositories) - [To update the repository with template changes](#to-update-the-repository-with-template-changes) - [Using a Specific Version/Tag](#using-a-specific-versiontag) - [GitHub Actions Workflows](#github-actions-workflows) - [Release Workflow](#-release-workflow) -> **⚠️ Important:** The following guide describes the recommended -> procedure on example of the `EasyPeasy` project. Replace `peasy` with -> the relevant name throughout the steps. +> **⚠️ Important:** The following guide uses the `EasyPeasy` project as +> an example. Replace `peasy` with the relevant name throughout the +> steps, and follow the repository track that matches your chosen +> `project_type`. ## 🧱 Overall Project Structure EasyScience projects typically consist of multiple repositories: -1. Home (umbrella) repository. Example: `easyscience/peasy` - - Acts as the entry point for the project - - Stores the project description file (Copier answers) -2. Library repository (if applicable). Example: `easyscience/peasy-lib` - - Contains the Python library - - Uses shared metadata from the home repository -3. Application repository (if applicable). Example: - `easyscience/peasy-app` - - Contains the GUI application - - Uses the same shared project metadata +- Home (umbrella) repository. Example: `easyscience/peasy`. Acts as the + entry point for the project and stores the project description file + (Copier answers) and other shared project metadata. +- Library repository (if applicable). Example: `easyscience/peasy-lib`. + Contains the Python library and uses shared metadata from the home + repository. +- Application repository (if applicable). Example: + `easyscience/peasy-app`. Contains the GUI application and uses the + same shared project metadata. -The home repository is created first, because it defines metadata reused -by the others. +The home repository is created first because it defines metadata reused +by the other repositories. ## 🚀 Step 1: Create GitHub Repositories @@ -88,29 +89,40 @@ To create a new repository: will handle these). 7. Click **Create repository**. -Depending on your project, repeat the same steps for additional -repositories as needed: +Depending on your selected `project_type`, repeat the same steps for +additional repositories as needed: -- For libraries: `peasy-lib` -- For applications: `peasy-app` +- `lib`: `peasy` and `peasy-lib` +- `app`: `peasy` and `peasy-app` +- `both`: `peasy`, `peasy-lib`, and `peasy-app` --- -## 🛠️ Step 2: Initialize Projects Using Copier +## 🛠 Step 2: Initialize Projects Using Copier ### 2.1 Clone Repositories -Clone all related repositories locally: +Clone the repositories required by your selected `project_type`. + +For `project_type=lib`: ```bash git clone https://github.com/easyscience/peasy.git +git clone https://github.com/easyscience/peasy-lib.git ``` +For `project_type=app`: + ```bash -git clone https://github.com/easyscience/peasy-lib.git +git clone https://github.com/easyscience/peasy.git +git clone https://github.com/easyscience/peasy-app.git ``` +For `project_type=both`: + ```bash +git clone https://github.com/easyscience/peasy.git +git clone https://github.com/easyscience/peasy-lib.git git clone https://github.com/easyscience/peasy-app.git ``` @@ -120,8 +132,8 @@ We use [**Pixi**](https://prefix.dev) for dependency management and project configuration: [`ADR` Use Pixi for Project and Environment Management](https://github.com/orgs/easyscience/discussions/43) -To install Pixi, follow the instructions at -https://pixi.prefix.dev/latest/installation/. +To install Pixi, follow the +[Pixi installation guide](https://pixi.prefix.dev/latest/installation/). Navigate into the home repository (e.g., `peasy`) and initialize a new Pixi project: @@ -156,10 +168,10 @@ pixi add copier ### 2.4 Generate Project Description (Home Repository) > **Note:** This step generates only the project metadata, not code or -> structure. Therefore, we exclude all files except the `.gitignore`, -> `README.md` and `.copier-answers.yml`. The latter one is defined in -> the `easyscience/templates` repository, in `copier.yml` as -> `_answers_file: .copier-answers.yml` +> structure. Therefore, we exclude all files except `.gitignore`, +> `README.md`, and `.copier-answers.yml`. The latter is defined in the +> `easyscience/templates` repository, in `copier.yml`, as +> `_answers_file: .copier-answers.yml`. ```bash pixi run copier copy gh:easyscience/templates . --data template_type=home --exclude '**/*' --exclude '!.gitignore' --exclude '!README.md' --exclude '!.copier-answers.yml' @@ -173,10 +185,11 @@ repository. The important fields to fill in accurately are: - Project name -- Project repository name +- Home repository name - Library repository name (if applicable) - Library package name (if applicable) - Application repository name (if applicable) +- Application package name (if applicable) For project name, alias, and short description, refer to the organization profile for consistency. @@ -205,9 +218,9 @@ cd .. ### 2.5 Generate Library / Application Repositories -Now, set up Pixi and Copier for the library or application repository. -In the example below, we use the library repository (e.g., `peasy-lib`). -In case of an application, replace with the application repository name +Now set up Pixi and Copier for the library or application repository. In +the example below, we use the library repository (e.g., `peasy-lib`). +For an application, replace it with the application repository name (e.g., `peasy-app`): Navigate to the target repository: @@ -241,7 +254,7 @@ pixi add copier Apply the Copier templates to generate the project structure. > **Important:** Use the `--data-file` option to provide the path to the -> `.copier-answers.yml` file with answers created in the main repository +> `.copier-answers.yml` file with answers created in the home repository > (`peasy`). For library: @@ -256,15 +269,20 @@ For application: pixi run copier copy gh:easyscience/templates . --data-file ../peasy/.copier-answers.yml --data template_type=app ``` +For `project_type=lib`, run only the library command. For +`project_type=app`, run only the application command. For +`project_type=both`, run both commands in their respective repositories. + > **Note:** When prompted with `conflict. overwrite pixi.toml?` or > `conflict. overwrite .gitignore?` confirm with `Yes` to overwrite the > configuration files created during `pixi init`. ### 2.6. Where Are Answers Stored? -The answers needed to fill in the library templates are automatically -taken from the home repository and stored locally in -`peasy-lib/.copier-answers.yml` or `peasy-app/.copier-answers.yml`. +The source of truth is `peasy/.copier-answers.yml` in the home +repository. Copier also writes autogenerated local answers files in +`peasy-lib/.copier-answers.yml` and `peasy-app/.copier-answers.yml` when +those repositories exist. They are autogenerated by Copier and should not be modified manually. @@ -292,8 +310,8 @@ finalize the setup: ``` - **Install extra development dependencies and set up tools:** This step - installs additional development dependencies, and configures - non-Python file formatting. See, `pixi.toml` for details regarding the + installs additional development dependencies and configures non-Python + file formatting. See `pixi.toml` for details regarding the `post-install` task. ```bash @@ -323,7 +341,7 @@ finalize the setup: new files are added: ```bash - pixi run spdx-add + pixi run license-add ``` - **Format all project files:** Ensures all files adhere to the @@ -337,16 +355,16 @@ finalize the setup: ``` > **Tip:** Normally, after running `pixi run fix`, you should see the -> message `✅ All code auto-formatting steps have been applied.` +> message `✅ All auto-formatting steps completed successfully!` > indicating that all steps in the auto-formatting pipeline were > successfully executed. If you do not see this message, try running the > command again. > -> Note, that even if you see this message, there might still be some +> Note that even if you see this message, there might still be some > issues left, which need to be fixed manually. In such cases, refer to > the output to identify and address the remaining issues. -### 2.8. Code Quality Checks +### 2.9. Code Quality Checks In order to ensure code quality, run the following command to check for issues: @@ -361,23 +379,27 @@ making pull requests to ensure code quality. After fixing any issues using `pixi run fix` or manually, it is recommended to run the check command again to verify that all issues -have been resolved. When all checks pass, you should see this: +have been resolved. `pixi run check` runs the manual pre-commit hooks, +so the output should list hook names such as the following: ```bash pixi run pyproject-check...................................Passed -pixi run spdx-check........................................Passed +pixi run license-check.....................................Passed pixi run py-lint-check.....................................Passed pixi run py-format-check...................................Passed +pixi run docstring-lint-check..............................Passed pixi run nonpy-format-check................................Passed -pixi run docs-format-check.................................Passed -pixi run notebook-format-check.............................Passed +pixi run notebook-lint-check...............................Passed pixi run unit-tests........................................Passed ``` +For application repositories, `pixi run notebook-lint-check` is not +included. + If any of the checks fail, address the reported issues accordingly. They can be executed individually as well, e.g., `pixi run py-lint-check`. -### 2.9. Push Changes to the Repository +### 2.10. Push Changes to the Repository After generating the project structure, **push the changes** to GitHub: @@ -389,7 +411,7 @@ git push origin master --- -## 🛡️ Step 3: Post-Initialization Repository Setup +## 🛡 Step 3: Post-Initialization Repository Setup After you have made your initial commit and pushed to GitHub, complete the following steps: @@ -438,14 +460,15 @@ Add repository secrets (e.g., API keys, deployment keys): the org level). Add `easyscience App` to the `develop` bypass protection rules for automatic backmerge after new releases. - The Codecov token secret CODECOV_TOKEN is already set for all - repositories within the EasyScience organisation. This token from - https://app.codecov.io/account/gh/EasyScience/org-upload-token is used - for code coverage reporting. + repositories within the EasyScience organisation. The token from the + [Codecov org upload token page](https://app.codecov.io/account/gh/EasyScience/org-upload-token) + is used for code coverage reporting. - For libraries, to enable PyPI publishing, we use GitHub Actions OIDC to get a short-lived token from PyPI, so no personal access token is needed. However, a new publisher must be previously configured in PyPI - at https://pypi.org/manage/project/easypeasy/settings/publishing/ Use - the following data: + in the + [PyPI trusted publishing settings](https://pypi.org/manage/project/easypeasy/settings/publishing/). + Use the following data: - Owner: easyscience - Repository name: peasy-lib - Workflow name: pypi-publish.yml @@ -461,8 +484,8 @@ line: pixi run branch-protection ``` -You can find the current branch protection rules at -https://github.com/easyscience/peasy-lib/settings/rules +You can find the current branch protection rules at the +[GitHub rulesets page](https://github.com/easyscience/peasy-lib/settings/rules). ### 3.5. Set Repository Configuration @@ -482,7 +505,7 @@ pixi run repo-config When templates evolve, existing repositories must be updated. -### 📌 To update the repository with template changes: +### To update the repository with template changes 1. Go to the project directory, e.g., `peasy-lib`: @@ -490,7 +513,7 @@ When templates evolve, existing repositories must be updated. cd peasy-lib ``` -2. Apply updated templates: +1. Apply updated templates: ```bash pixi run copier-update @@ -498,8 +521,8 @@ pixi run copier-update If conflicts arise, Copier will prompt you to review them. -Sometimes, one need to run Copier recopy instead of update, or even redo -a standard copy again (see +Sometimes, you need to run Copier recopy instead of update, or even redo +a standard copy operation (see [Copier docs](https://copier.readthedocs.io/en/stable/generating/#regenerating-a-project) for details). diff --git a/pixi.lock b/pixi.lock index 7b72e6c..afd270a 100644 --- a/pixi.lock +++ b/pixi.lock @@ -854,8 +854,8 @@ packages: requires_python: '>=3.5' - pypi: ./ name: easytemplates - version: 0.10.1+devdirty42 - sha256: 59c7e8a060a02900b459e88a13a116e96926f0ade08cfe418b9bd216c3f70f4c + version: 0.11.0+devdirty1 + sha256: 5d7b97060c861c561261a105f4d0699cb0af552a01ea59be816a3a98431510cb requires_dist: - build ; extra == 'dev' - copier ; extra == 'dev' diff --git a/template/.github/actions/download-artifact/action.yml b/template/.github/actions/download-artifact/action.yml index e4fd62f..d1fff1a 100644 --- a/template/.github/actions/download-artifact/action.yml +++ b/template/.github/actions/download-artifact/action.yml @@ -1,5 +1,5 @@ name: 'Download artifact' -description: 'Generic wrapper for actions/download-artifact' +description: 'Wrapper for actions/download-artifact' inputs: name: description: 'Name of the artifact to download' @@ -39,7 +39,7 @@ runs: using: 'composite' steps: - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: ${{ inputs.name }} path: ${{ inputs.path }} diff --git a/template/.github/actions/github-script/action.yml b/template/.github/actions/github-script/action.yml index ab32da5..50de89b 100644 --- a/template/.github/actions/github-script/action.yml +++ b/template/.github/actions/github-script/action.yml @@ -13,7 +13,7 @@ inputs: runs: using: 'composite' steps: - - uses: actions/github-script@v8 + - uses: actions/github-script@v9 with: script: ${{ inputs.script }} github-token: ${{ inputs.github-token }} diff --git a/template/.github/actions/setup-easyscience-bot/action.yml b/template/.github/actions/setup-easyscience-bot/action.yml index 4b28eaf..e51eb01 100644 --- a/template/.github/actions/setup-easyscience-bot/action.yml +++ b/template/.github/actions/setup-easyscience-bot/action.yml @@ -22,9 +22,9 @@ runs: steps: - name: Create GitHub App installation token id: app-token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3 with: - app-id: ${{ inputs.app-id }} + client-id: ${{ inputs.app-id }} private-key: ${{ inputs.private-key }} repositories: ${{ inputs.repositories }} diff --git a/template/.github/actions/setup-pixi/action.yml b/template/.github/actions/setup-pixi/action.yml index 167ee62..ec7d7ba 100644 --- a/template/.github/actions/setup-pixi/action.yml +++ b/template/.github/actions/setup-pixi/action.yml @@ -1,5 +1,5 @@ name: 'Setup Pixi Environment' -description: 'Sets up pixi with common configuration' +description: 'Wrapper for prefix-dev/setup-pixi' inputs: environments: description: 'Pixi environments to setup' diff --git a/template/.github/actions/upload-artifact/action.yml b/template/.github/actions/upload-artifact/action.yml index 825ac39..fe8e468 100644 --- a/template/.github/actions/upload-artifact/action.yml +++ b/template/.github/actions/upload-artifact/action.yml @@ -1,5 +1,5 @@ name: 'Upload artifact' -description: 'Generic wrapper for actions/upload-artifact' +description: 'Wrapper for actions/upload-artifact' inputs: name: description: 'Artifact name' @@ -38,7 +38,7 @@ runs: using: 'composite' steps: - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ inputs.name }} path: ${{ inputs.path }} diff --git a/template/.github/actions/upload-codecov/action.yml b/template/.github/actions/upload-codecov/action.yml index 37d6298..0cb15d1 100644 --- a/template/.github/actions/upload-codecov/action.yml +++ b/template/.github/actions/upload-codecov/action.yml @@ -1,5 +1,5 @@ name: 'Upload coverage to Codecov' -description: 'Generic wrapper for codecov/codecov-action@v5' +description: 'Wrapper for codecov/codecov-action' inputs: name: @@ -32,7 +32,7 @@ inputs: runs: using: composite steps: - - uses: codecov/codecov-action@v5 + - uses: codecov/codecov-action@v6 with: name: ${{ inputs.name }} flags: ${{ inputs.flags }} diff --git a/template/.github/scripts/publish-dashboard.sh b/template/.github/scripts/publish-dashboard.sh new file mode 100644 index 0000000..dcbd0b3 --- /dev/null +++ b/template/.github/scripts/publish-dashboard.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +set -euo pipefail + +remote_repository="${DASHBOARD_REMOTE_REPOSITORY:?}" +publish_branch="${DASHBOARD_PUBLISH_BRANCH:-master}" +source_dir="${DASHBOARD_SOURCE_DIR:?}" +token="${DASHBOARD_TOKEN:?}" +git_user_name="${DASHBOARD_GIT_USER_NAME:-easyscience[bot]}" +git_user_email="${DASHBOARD_GIT_USER_EMAIL:?}" +commit_message="${DASHBOARD_COMMIT_MESSAGE:?}" +max_attempts="${DASHBOARD_PUSH_ATTEMPTS:-3}" +delay_seconds="${DASHBOARD_PUSH_DELAY_SECONDS:-15}" + +workspace_dir="$(mktemp -d)" +repo_dir="${workspace_dir}/dashboard" +remote_url="https://x-access-token:${token}@github.com/${remote_repository}.git" + +cleanup() { + rm -rf "${workspace_dir}" +} + +prepare_worktree() { + if [[ ! -d "${repo_dir}/.git" ]]; then + git clone --branch "${publish_branch}" --depth 1 "${remote_url}" "${repo_dir}" + else + git -C "${repo_dir}" fetch origin "${publish_branch}" + git -C "${repo_dir}" checkout "${publish_branch}" + git -C "${repo_dir}" reset --hard "origin/${publish_branch}" + git -C "${repo_dir}" clean -fd + fi + + git -C "${repo_dir}" config user.name "${git_user_name}" + git -C "${repo_dir}" config user.email "${git_user_email}" +} + +sync_publish_dir() { + cp -R "${source_dir}/." "${repo_dir}/" + git -C "${repo_dir}" add . + + if git -C "${repo_dir}" diff --cached --quiet; then + return 1 + fi + + git -C "${repo_dir}" commit -m "${commit_message}" +} + +trap cleanup EXIT + +prepare_worktree + +if ! sync_publish_dir; then + echo "No dashboard changes to publish." + exit 0 +fi + +for ((attempt = 1; attempt <= max_attempts; attempt += 1)); do + if git -C "${repo_dir}" push origin "HEAD:${publish_branch}"; then + echo "Dashboard published on attempt ${attempt}." + exit 0 + fi + + if ((attempt == max_attempts)); then + echo "Dashboard publish failed after ${max_attempts} attempts." >&2 + exit 1 + fi + + echo "Dashboard push attempt ${attempt} failed. Retrying in ${delay_seconds}s." >&2 + sleep "${delay_seconds}" + + prepare_worktree + + if ! sync_publish_dir; then + echo "Dashboard changes already exist in the target repository." + exit 0 + fi +done \ No newline at end of file diff --git a/template/.github/workflows/backmerge.yml b/template/.github/workflows/backmerge.yml index d8569f0..36ce6f5 100644 --- a/template/.github/workflows/backmerge.yml +++ b/template/.github/workflows/backmerge.yml @@ -20,11 +20,6 @@ concurrency: group: backmerge-master-into-develop cancel-in-progress: false -# Opt into Node.js 24 for all JavaScript actions. -# Remove once all referenced actions natively target Node 24. -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - jobs: backmerge: runs-on: ubuntu-latest @@ -32,7 +27,7 @@ jobs: steps: - name: Checkout repository (for local actions) - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup easyscience[bot] id: bot @@ -43,7 +38,7 @@ jobs: repositories: ${{ github.event.repository.name }} - name: Checkout repository (with bot token) - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ steps.bot.outputs.token }} diff --git a/template/.github/workflows/cleanup.yml b/template/.github/workflows/cleanup.yml index d3ebf3a..21c72b3 100644 --- a/template/.github/workflows/cleanup.yml +++ b/template/.github/workflows/cleanup.yml @@ -15,11 +15,11 @@ on: days: description: 'Number of days.' required: true - default: 30 + default: '30' minimum_runs: description: 'The minimum runs to keep for each workflow.' required: true - default: 6 + default: '6' delete_workflow_pattern: description: 'The name or filename of the workflow. if not set then it will target all @@ -61,11 +61,6 @@ on: - 'false' - 'true' -# Opt into Node.js 24 for all JavaScript actions. -# Remove once all referenced actions natively target Node 24. -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - jobs: del-runs: runs-on: ubuntu-latest diff --git a/template/.github/workflows/coverage.yml b/template/.github/workflows/coverage.yml index 1ad9313..95c190c 100644 --- a/template/.github/workflows/coverage.yml +++ b/template/.github/workflows/coverage.yml @@ -26,9 +26,6 @@ concurrency: # Set the environment variables to be used in all jobs defined in this workflow env: CI_BRANCH: ${{ github.head_ref || github.ref_name }} - # Opt into Node.js 24 for all JavaScript actions. - # Remove once all referenced actions natively target Node 24. - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: # Job 1: Run docstring coverage @@ -37,7 +34,7 @@ jobs: steps: - name: Check-out repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up pixi uses: ./.github/actions/setup-pixi @@ -51,7 +48,7 @@ jobs: steps: - name: Check-out repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up pixi uses: ./.github/actions/setup-pixi @@ -74,7 +71,7 @@ jobs: steps: - name: Check-out repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up pixi uses: ./.github/actions/setup-pixi diff --git a/template/.github/workflows/dashboard.yml.jinja b/template/.github/workflows/dashboard.yml.jinja index 244aebd..e93ede7 100644 --- a/template/.github/workflows/dashboard.yml.jinja +++ b/template/.github/workflows/dashboard.yml.jinja @@ -7,6 +7,10 @@ on: permissions: contents: read +concurrency: + group: {% raw %}dashboard-publish-${{ github.repository }}{% endraw %} + cancel-in-progress: false + # Set the environment variables to be used in all jobs defined in this workflow env: CI_BRANCH: {% raw %}${{ github.head_ref || github.ref_name }}{% endraw %} @@ -14,9 +18,6 @@ env: DEVELOP_BRANCH: develop REPO_OWNER: {% raw %}${{ github.repository_owner }}{% endraw %} REPO_NAME: {% raw %}${{ github.event.repository.name }}{% endraw %} - # Opt into Node.js 24 for all JavaScript actions. - # Remove once all referenced actions natively target Node 24. - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: dashboard: @@ -24,7 +25,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -84,22 +85,22 @@ jobs: {% raw %}${{ github.event.repository.name }}{% endraw %} dashboard - # Publish to external dashboard repository with retry logic. + # Push to external dashboard repository with retry logic. # Retry is needed to handle transient GitHub API/authentication issues # that occasionally cause 403 errors when multiple workflows push concurrently. # Uses personal_token (not github_token) as GITHUB_TOKEN cannot access external repos. - - name: Publish to main branch of {% raw %}${{ github.repository }}{% endraw %} - uses: Wandalen/wretry.action@v3.8.0 - with: - attempt_limit: 3 - attempt_delay: 15000 # 15 seconds between retries - action: peaceiris/actions-gh-pages@v4 - with: | - publish_dir: ./_dashboard_publish - keep_files: true - external_repository: {% raw %}${{ env.REPO_OWNER }}{% endraw %}/dashboard - publish_branch: master - personal_token: {% raw %}${{ steps.bot.outputs.token }}{% endraw %} + - name: Push to {% raw %}${{ env.REPO_OWNER }}/dashboard/${{ env.REPO_NAME }}/${{ env.CI_BRANCH }}{% endraw %} + shell: bash + env: + DASHBOARD_COMMIT_MESSAGE: {% raw %}${{ env.CI_BRANCH }}{% endraw %} + DASHBOARD_GIT_USER_EMAIL: {% raw %}${{ vars.EASYSCIENCE_APP_ID }}+easyscience[bot]@users.noreply.github.com{% endraw %} + DASHBOARD_PUSH_ATTEMPTS: "3" + DASHBOARD_PUSH_DELAY_SECONDS: "15" + DASHBOARD_PUBLISH_BRANCH: master + DASHBOARD_REMOTE_REPOSITORY: {% raw %}${{ env.REPO_OWNER }}/dashboard{% endraw %} + DASHBOARD_SOURCE_DIR: ./_dashboard_publish + DASHBOARD_TOKEN: {% raw %}${{ steps.bot.outputs.token }}{% endraw %} + run: bash ./.github/scripts/publish-dashboard.sh - name: Add dashboard link to summary run: | diff --git a/template/.github/workflows/docs.yml.jinja b/template/.github/workflows/docs.yml.jinja index cb96673..1aed653 100644 --- a/template/.github/workflows/docs.yml.jinja +++ b/template/.github/workflows/docs.yml.jinja @@ -48,9 +48,6 @@ env: IS_RELEASE_TAG: {% raw %}${{ startsWith(github.ref, 'refs/tags/v') }}{% endraw %} GITHUB_REPOSITORY: {% raw %}${{ github.repository }}{% endraw %} NOTEBOOKS_DIR: tutorials - # Opt into Node.js 24 for all JavaScript actions. - # Remove once all referenced actions natively target Node 24. - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: # Single job that builds and deploys documentation. @@ -86,7 +83,7 @@ jobs: # Check out the repository source code. # Note: The gh-pages branch is fetched separately later for mike deployment. - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 # Activate dark mode to create documentation with Plotly charts in dark mode # Need a better solution to automatically switch the chart colour theme based on the mkdocs material switcher diff --git a/template/.github/workflows/issues-labels.yml b/template/.github/workflows/issues-labels.yml index ed9d1c8..31a69ad 100644 --- a/template/.github/workflows/issues-labels.yml +++ b/template/.github/workflows/issues-labels.yml @@ -11,11 +11,6 @@ on: permissions: issues: write -# Opt into Node.js 24 for all JavaScript actions. -# Remove once all referenced actions natively target Node 24. -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - jobs: check-labels: if: github.actor != 'easyscience[bot]' @@ -28,7 +23,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup easyscience[bot] id: bot diff --git a/template/.github/workflows/lint-format.yml.jinja b/template/.github/workflows/lint-format.yml.jinja index ea33f0e..9cd09ec 100644 --- a/template/.github/workflows/lint-format.yml.jinja +++ b/template/.github/workflows/lint-format.yml.jinja @@ -34,9 +34,6 @@ permissions: # Set the environment variables to be used in all jobs defined in this workflow env: CI_BRANCH: {% raw %}${{ github.head_ref || github.ref_name }}{% endraw %} - # Opt into Node.js 24 for all JavaScript actions. - # Remove once all referenced actions natively target Node 24. - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: lint-format: @@ -44,7 +41,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up pixi uses: ./.github/actions/setup-pixi diff --git a/template/.github/workflows/release-notes.yml.jinja b/template/.github/workflows/release-notes.yml.jinja index e61e87e..db5d89e 100644 --- a/template/.github/workflows/release-notes.yml.jinja +++ b/template/.github/workflows/release-notes.yml.jinja @@ -10,11 +10,6 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: -# Opt into Node.js 24 for all JavaScript actions. -# Remove once all referenced actions natively target Node 24. -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - jobs: draft-release-notes: permissions: @@ -25,7 +20,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 # full history with tags to get the version number @@ -66,7 +61,7 @@ jobs: GITHUB_TOKEN: {% raw %}${{ steps.bot.outputs.token }}{% endraw %} - name: Create GitHub draft release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: draft: true tag_name: {% raw %}${{ steps.draft.outputs.tag_name }}{% endraw %} diff --git a/template/.github/workflows/release-pr.yml b/template/.github/workflows/release-pr.yml index 5694a2a..0e716c2 100644 --- a/template/.github/workflows/release-pr.yml +++ b/template/.github/workflows/release-pr.yml @@ -24,16 +24,13 @@ permissions: env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} SOURCE_BRANCH: ${{ inputs.source_branch || 'develop' }} - # Opt into Node.js 24 for all JavaScript actions. - # Remove once all referenced actions natively target Node 24. - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: create-pull-request: runs-on: ubuntu-latest steps: - name: Checkout ${{ env.SOURCE_BRANCH }} branch - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ env.SOURCE_BRANCH }} diff --git a/template/.github/workflows/security.yml b/template/.github/workflows/security.yml index a53e9af..0f43db3 100644 --- a/template/.github/workflows/security.yml +++ b/template/.github/workflows/security.yml @@ -44,11 +44,6 @@ permissions: contents: read security-events: write -# Opt into Node.js 24 for all JavaScript actions. -# Remove once all referenced actions natively target Node 24. -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - jobs: codeql: name: Code scanning @@ -66,13 +61,13 @@ jobs: # focused on the active dev branch. - name: Checkout repository (scheduled → develop) if: ${{ github.event_name == 'schedule' }} - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: develop - name: Checkout repository if: ${{ github.event_name != 'schedule' }} - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Initialize CodeQL uses: github/codeql-action/init@v4 diff --git a/template/.github/workflows/test-trigger.yml b/template/.github/workflows/test-trigger.yml index f4a39e9..440bae3 100644 --- a/template/.github/workflows/test-trigger.yml +++ b/template/.github/workflows/test-trigger.yml @@ -10,18 +10,13 @@ on: permissions: contents: read -# Opt into Node.js 24 for all JavaScript actions. -# Remove once all referenced actions natively target Node 24. -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - jobs: code-tests-trigger: runs-on: ubuntu-latest steps: - name: Checkout develop branch - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: develop diff --git a/template/.github/workflows/{{'' if template_type == 'app' else '_exclude_'}}test.yml.jinja b/template/.github/workflows/{{'' if template_type == 'app' else '_exclude_'}}test.yml.jinja index 3c8d5c8..5c761ea 100644 --- a/template/.github/workflows/{{'' if template_type == 'app' else '_exclude_'}}test.yml.jinja +++ b/template/.github/workflows/{{'' if template_type == 'app' else '_exclude_'}}test.yml.jinja @@ -45,9 +45,6 @@ concurrency: # Set the environment variables to be used in all jobs defined in this workflow env: CI_BRANCH: {% raw %}${{ github.head_ref || github.ref_name }}{% endraw %} - # Opt into Node.js 24 for all JavaScript actions. - # Remove once all referenced actions natively target Node 24. - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: # Job 1: Set up environment variables @@ -82,7 +79,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up pixi uses: ./.github/actions/setup-pixi diff --git a/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}pypi-publish.yml.jinja b/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}pypi-publish.yml.jinja index 787251a..dbf0908 100644 --- a/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}pypi-publish.yml.jinja +++ b/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}pypi-publish.yml.jinja @@ -10,11 +10,6 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: -# Opt into Node.js 24 for all JavaScript actions. -# Remove once all referenced actions natively target Node 24. -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - jobs: pypi-publish: runs-on: ubuntu-latest @@ -25,7 +20,7 @@ jobs: steps: - name: Check-out repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 # full history with tags to get the version number by versioningit diff --git a/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}pypi-test.yml.jinja b/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}pypi-test.yml.jinja index 1497397..e7a1fe9 100644 --- a/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}pypi-test.yml.jinja +++ b/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}pypi-test.yml.jinja @@ -20,9 +20,6 @@ permissions: env: CI_BRANCH: {% raw %}${{ github.head_ref || github.ref_name }}{% endraw %} DEFAULT_BRANCH: {% raw %}${{ github.event.repository.default_branch }}{% endraw %} - # Opt into Node.js 24 for all JavaScript actions. - # Remove once all referenced actions natively target Node 24. - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: # Job 1: Test installation from PyPI on multiple OS @@ -35,7 +32,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up pixi uses: ./.github/actions/setup-pixi diff --git a/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}test.yml.jinja b/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}test.yml.jinja index ef990e5..1d2eeea 100644 --- a/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}test.yml.jinja +++ b/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}test.yml.jinja @@ -46,9 +46,6 @@ env: CI_BRANCH: {% raw %}${{ github.head_ref || github.ref_name }}{% endraw %} PY_VERSIONS: '{{ lib_python_min }} {{ lib_python_max }}' PIXI_ENVS: 'py-{{ lib_python_min | replace(".", "") }}-env py-{{ lib_python_max | replace(".", "") }}-env' - # Opt into Node.js 24 for all JavaScript actions. - # Remove once all referenced actions natively target Node 24. - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: # Job 1: Set up environment variables @@ -83,7 +80,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up pixi uses: ./.github/actions/setup-pixi @@ -187,7 +184,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Download package (incl. extras) from previous job uses: ./.github/actions/download-artifact diff --git a/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}tutorial-tests-trigger.yml.jinja b/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}tutorial-tests-trigger.yml.jinja index 2cd0546..dfc8e37 100644 --- a/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}tutorial-tests-trigger.yml.jinja +++ b/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}tutorial-tests-trigger.yml.jinja @@ -10,18 +10,13 @@ on: permissions: contents: read -# Opt into Node.js 24 for all JavaScript actions. -# Remove once all referenced actions natively target Node 24. -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - jobs: tutorial-tests-trigger: runs-on: ubuntu-latest steps: - name: Checkout develop branch - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: develop diff --git a/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}tutorial-tests.yml.jinja b/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}tutorial-tests.yml.jinja index 00d8a84..0494a8c 100644 --- a/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}tutorial-tests.yml.jinja +++ b/template/.github/workflows/{{'' if template_type == 'lib' else '_exclude_'}}tutorial-tests.yml.jinja @@ -27,9 +27,6 @@ concurrency: # Set the environment variables to be used in all jobs defined in this workflow env: CI_BRANCH: {% raw %}${{ github.head_ref || github.ref_name }}{% endraw %} - # Opt into Node.js 24 for all JavaScript actions. - # Remove once all referenced actions natively target Node 24. - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: # Job 1: Test tutorials as scripts and notebooks on multiple OS @@ -43,7 +40,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up pixi uses: ./.github/actions/setup-pixi diff --git a/tools/license_headers.py b/tools/license_headers.py index d2687e2..f276ca1 100644 --- a/tools/license_headers.py +++ b/tools/license_headers.py @@ -25,7 +25,9 @@ def load_pyproject(repo_path: Union[str, Path]) -> dict[str, Any]: - """Load and return parsed ``pyproject.toml`` data for the repository.""" + """ + Load and return parsed ``pyproject.toml`` data for the repository. + """ repo_root = find_repository_root(repo_path) pyproject_path = repo_root / 'pyproject.toml' @@ -121,7 +123,9 @@ def get_file_creation_year(file_path: Union[str, Path]) -> str: def get_org_url(repo_path: Union[str, Path]) -> str: - """Return the organization URL derived from the repository source URL.""" + """ + Return the organization URL derived from the repository source URL. + """ pyproject_data = load_pyproject(repo_path) repo_url = pyproject_data['project']['urls']['Source Code'] return repo_url.rsplit('/', 1)[0] From 9f8b18f33abbf73c39334a8d60c2a2c9a5152ae2 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 5 May 2026 22:20:59 +0200 Subject: [PATCH 2/2] Improve issue labels workflow and add diagnostics (#21) * Add docstring transform docs * Improve issue labels workflow and add diagnostics --- .github/workflows/issues-labels.yml | 152 +++++++++++++++---- README.md | 11 ++ template/.github/workflows/issues-labels.yml | 152 +++++++++++++++---- 3 files changed, 261 insertions(+), 54 deletions(-) diff --git a/.github/workflows/issues-labels.yml b/.github/workflows/issues-labels.yml index 31a69ad..56ab5b1 100644 --- a/.github/workflows/issues-labels.yml +++ b/.github/workflows/issues-labels.yml @@ -1,6 +1,6 @@ -# Verifies if an issue has at least one of the `[scope]` and one of the -# `[priority]` labels. If not, the bot adds labels with a warning emoji -# to indicate that those labels need to be added. +# Verifies if the current issue has at least one real `[scope]` label and one +# real `[priority]` label. If either is missing, the workflow adds a reminder +# label with a warning emoji. name: Issue labels check @@ -13,8 +13,6 @@ permissions: jobs: check-labels: - if: github.actor != 'easyscience[bot]' - runs-on: ubuntu-latest concurrency: @@ -25,27 +23,127 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 - - name: Setup easyscience[bot] - id: bot - uses: ./.github/actions/setup-easyscience-bot + - name: Sync missing-label reminders + uses: ./.github/actions/github-script with: - app-id: ${{ vars.EASYSCIENCE_APP_ID }} - private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} + script: | + const fs = require('fs'); - - name: Check for required [scope] label - uses: trstringer/require-label-prefix@v1 - with: - secret: ${{ steps.bot.outputs.token }} - prefix: '[scope]' - labelSeparator: ' ' - addLabel: true - defaultLabel: '[scope] ⚠️ label needed' - - - name: Check for required [priority] label - uses: trstringer/require-label-prefix@v1 - with: - secret: ${{ steps.bot.outputs.token }} - prefix: '[priority]' - labelSeparator: ' ' - addLabel: true - defaultLabel: '[priority] ⚠️ label needed' + const issueNumber = context.issue.number; + const action = context.payload.action; + const changedLabel = context.payload.label?.name ?? null; + const labels = context.payload.issue.labels.map(({ name }) => name); + const requirements = [ + { + prefix: '[scope] ', + reminder: '[scope] ⚠️ label needed', + }, + { + prefix: '[priority] ', + reminder: '[priority] ⚠️ label needed', + }, + ]; + + const labelsToAdd = []; + const labelsToRemove = []; + const evaluations = []; + + console.log(`::group::Issue label check for #${issueNumber}`); + console.log(`Event action: ${action}`); + if (changedLabel) { + console.log(`Event label: ${changedLabel}`); + } + console.log( + `Current labels: ${labels.length > 0 ? labels.join(', ') : '(none)'}`, + ); + + for (const { prefix, reminder } of requirements) { + const matchingRealLabels = labels.filter( + (name) => name.startsWith(prefix) && name !== reminder, + ); + const hasRealLabel = matchingRealLabels.length > 0; + const hasReminderLabel = labels.includes(reminder); + + evaluations.push({ + prefix, + reminder, + matchingRealLabels, + hasReminderLabel, + }); + + if (hasRealLabel && hasReminderLabel) { + labelsToRemove.push(reminder); + } else if (!hasRealLabel && !hasReminderLabel) { + labelsToAdd.push(reminder); + } + } + + for (const evaluation of evaluations) { + if (evaluation.matchingRealLabels.length > 0) { + console.log( + `Found required ${evaluation.prefix}label(s): ${evaluation.matchingRealLabels.join(', ')}`, + ); + } else { + console.log(`Missing required ${evaluation.prefix}label.`); + } + + if (evaluation.hasReminderLabel) { + console.log(`Reminder label already present: ${evaluation.reminder}`); + } + } + + if (labelsToAdd.length > 0) { + console.log(`Adding reminder labels: ${labelsToAdd.join(', ')}`); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: labelsToAdd, + }); + } + + for (const name of labelsToRemove) { + console.log(`Removing reminder label: ${name}`); + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name, + }); + } + + if (labelsToAdd.length === 0 && labelsToRemove.length === 0) { + console.log('No label changes required.'); + } + + console.log('::endgroup::'); + + if (process.env.GITHUB_STEP_SUMMARY) { + const summaryLines = [ + '### Issue Label Check', + `- Issue: #${issueNumber}`, + `- Event: ${action}`, + `- Trigger label: ${changedLabel ?? '(none)'}`, + `- Current labels: ${labels.length > 0 ? labels.join(', ') : '(none)'}`, + '', + '#### Requirement status', + ...evaluations.map((evaluation) => { + const status = + evaluation.matchingRealLabels.length > 0 + ? `found ${evaluation.matchingRealLabels.join(', ')}` + : 'missing'; + const reminder = evaluation.hasReminderLabel + ? `reminder present: ${evaluation.reminder}` + : `reminder absent: ${evaluation.reminder}`; + return `- ${evaluation.prefix}: ${status}; ${reminder}`; + }), + '', + `- Labels to add: ${labelsToAdd.length > 0 ? labelsToAdd.join(', ') : '(none)'}`, + `- Labels to remove: ${labelsToRemove.length > 0 ? labelsToRemove.join(', ') : '(none)'}`, + ]; + + fs.appendFileSync( + process.env.GITHUB_STEP_SUMMARY, + `${summaryLines.join('\n')}\n`, + ); + } diff --git a/README.md b/README.md index 0512515..0aca1d7 100644 --- a/README.md +++ b/README.md @@ -344,6 +344,17 @@ finalize the setup: pixi run license-add ``` +- **Transform docstrings to the standard format:** We use `NumPy` style + for docstrings across all projects. See `ADR` + [Docstring Style Standardization](https://github.com/orgs/easyscience/discussions/67) + for details. If you use a different style, or if you have inconsistent + formatting, you can run the following command to transform all + docstrings to the standard format: + + ```bash + pixi run docstring-transform + ``` + - **Format all project files:** Ensures all files adhere to the project's coding standards as defined in `pyproject.toml`. As this step may be time-consuming, it is recommended to run it only after diff --git a/template/.github/workflows/issues-labels.yml b/template/.github/workflows/issues-labels.yml index 31a69ad..56ab5b1 100644 --- a/template/.github/workflows/issues-labels.yml +++ b/template/.github/workflows/issues-labels.yml @@ -1,6 +1,6 @@ -# Verifies if an issue has at least one of the `[scope]` and one of the -# `[priority]` labels. If not, the bot adds labels with a warning emoji -# to indicate that those labels need to be added. +# Verifies if the current issue has at least one real `[scope]` label and one +# real `[priority]` label. If either is missing, the workflow adds a reminder +# label with a warning emoji. name: Issue labels check @@ -13,8 +13,6 @@ permissions: jobs: check-labels: - if: github.actor != 'easyscience[bot]' - runs-on: ubuntu-latest concurrency: @@ -25,27 +23,127 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 - - name: Setup easyscience[bot] - id: bot - uses: ./.github/actions/setup-easyscience-bot + - name: Sync missing-label reminders + uses: ./.github/actions/github-script with: - app-id: ${{ vars.EASYSCIENCE_APP_ID }} - private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} + script: | + const fs = require('fs'); - - name: Check for required [scope] label - uses: trstringer/require-label-prefix@v1 - with: - secret: ${{ steps.bot.outputs.token }} - prefix: '[scope]' - labelSeparator: ' ' - addLabel: true - defaultLabel: '[scope] ⚠️ label needed' - - - name: Check for required [priority] label - uses: trstringer/require-label-prefix@v1 - with: - secret: ${{ steps.bot.outputs.token }} - prefix: '[priority]' - labelSeparator: ' ' - addLabel: true - defaultLabel: '[priority] ⚠️ label needed' + const issueNumber = context.issue.number; + const action = context.payload.action; + const changedLabel = context.payload.label?.name ?? null; + const labels = context.payload.issue.labels.map(({ name }) => name); + const requirements = [ + { + prefix: '[scope] ', + reminder: '[scope] ⚠️ label needed', + }, + { + prefix: '[priority] ', + reminder: '[priority] ⚠️ label needed', + }, + ]; + + const labelsToAdd = []; + const labelsToRemove = []; + const evaluations = []; + + console.log(`::group::Issue label check for #${issueNumber}`); + console.log(`Event action: ${action}`); + if (changedLabel) { + console.log(`Event label: ${changedLabel}`); + } + console.log( + `Current labels: ${labels.length > 0 ? labels.join(', ') : '(none)'}`, + ); + + for (const { prefix, reminder } of requirements) { + const matchingRealLabels = labels.filter( + (name) => name.startsWith(prefix) && name !== reminder, + ); + const hasRealLabel = matchingRealLabels.length > 0; + const hasReminderLabel = labels.includes(reminder); + + evaluations.push({ + prefix, + reminder, + matchingRealLabels, + hasReminderLabel, + }); + + if (hasRealLabel && hasReminderLabel) { + labelsToRemove.push(reminder); + } else if (!hasRealLabel && !hasReminderLabel) { + labelsToAdd.push(reminder); + } + } + + for (const evaluation of evaluations) { + if (evaluation.matchingRealLabels.length > 0) { + console.log( + `Found required ${evaluation.prefix}label(s): ${evaluation.matchingRealLabels.join(', ')}`, + ); + } else { + console.log(`Missing required ${evaluation.prefix}label.`); + } + + if (evaluation.hasReminderLabel) { + console.log(`Reminder label already present: ${evaluation.reminder}`); + } + } + + if (labelsToAdd.length > 0) { + console.log(`Adding reminder labels: ${labelsToAdd.join(', ')}`); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: labelsToAdd, + }); + } + + for (const name of labelsToRemove) { + console.log(`Removing reminder label: ${name}`); + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name, + }); + } + + if (labelsToAdd.length === 0 && labelsToRemove.length === 0) { + console.log('No label changes required.'); + } + + console.log('::endgroup::'); + + if (process.env.GITHUB_STEP_SUMMARY) { + const summaryLines = [ + '### Issue Label Check', + `- Issue: #${issueNumber}`, + `- Event: ${action}`, + `- Trigger label: ${changedLabel ?? '(none)'}`, + `- Current labels: ${labels.length > 0 ? labels.join(', ') : '(none)'}`, + '', + '#### Requirement status', + ...evaluations.map((evaluation) => { + const status = + evaluation.matchingRealLabels.length > 0 + ? `found ${evaluation.matchingRealLabels.join(', ')}` + : 'missing'; + const reminder = evaluation.hasReminderLabel + ? `reminder present: ${evaluation.reminder}` + : `reminder absent: ${evaluation.reminder}`; + return `- ${evaluation.prefix}: ${status}; ${reminder}`; + }), + '', + `- Labels to add: ${labelsToAdd.length > 0 ? labelsToAdd.join(', ') : '(none)'}`, + `- Labels to remove: ${labelsToRemove.length > 0 ? labelsToRemove.join(', ') : '(none)'}`, + ]; + + fs.appendFileSync( + process.env.GITHUB_STEP_SUMMARY, + `${summaryLines.join('\n')}\n`, + ); + }