From 9be3947a921001e78ad2a45aed6f3a4679847fcd Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Fri, 2 May 2025 09:32:58 +0000 Subject: [PATCH] copier --- .copier-answers.yml | 2 +- .devcontainer/devcontainer.json | 9 +- .devcontainer/install-ci-tooling.sh | 6 +- .devcontainer/windows-host-helper.sh | 27 ++- .github/actions/install_deps_uv/action.yml | 2 +- .../install_deps_uv/install-ci-tooling.ps1 | 6 +- .github/pull_request_template.md | 2 +- .../build-docker-image.yaml | 120 +++++++++++-- .github/workflows/ci.yaml | 4 +- .github/workflows/hash_git_files.py | 170 ++++++++++++++++++ .pre-commit-config.yaml | 25 ++- biome.json | 8 + copier.yml | 4 +- extensions/context.py | 35 ++-- pyproject.toml | 6 +- .../.devcontainer/create-aws-profile.sh.jinja | 2 +- .../.devcontainer/devcontainer.json.jinja | 11 +- template/.devcontainer/windows-host-helper.sh | 27 ++- .../actions/install_deps_uv/action.yml | 2 +- .../install_deps_uv/install-ci-tooling.ps1 | 6 +- template/.github/pull_request_template.md | 2 +- template/.github/workflows/hash_git_files.py | 170 ++++++++++++++++++ template/.pre-commit-config.yaml | 25 ++- template/biome.json | 8 + uv.lock | 18 +- 25 files changed, 611 insertions(+), 86 deletions(-) create mode 100644 .github/workflows/hash_git_files.py create mode 100644 biome.json create mode 100644 template/.github/workflows/hash_git_files.py create mode 100644 template/biome.json diff --git a/.copier-answers.yml b/.copier-answers.yml index fab9fa40..0b53934b 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: v0.0.19 +_commit: v0.0.31 _src_path: gh:LabAutomationAndScreening/copier-base-template.git description: Copier template for creating Python libraries and executables python_ci_versions: diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e15a1e26..ce46b6c3 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -22,8 +22,8 @@ "eamodio.gitlens@15.5.1", "ms-vscode.live-server@0.5.2024091601", "MS-vsliveshare.vsliveshare@1.0.5905", - "github.copilot@1.234.0", - "github.copilot-chat@0.20.2", + "github.copilot@1.304.1523", + "github.copilot-chat@0.27.2025042301", // Python "ms-python.python@2024.14.1", @@ -31,10 +31,8 @@ "ms-vscode-remote.remote-containers@0.383.0", "charliermarsh.ruff@2024.54.0", - - // Misc file formats - "bierner.markdown-mermaid@1.25.0", + "bierner.markdown-mermaid@1.28.0", "samuelcolvin.jinjahtml@0.20.0", "tamasfe.even-better-toml@0.19.2", "emilast.LogFileHighlighter@3.3.3", @@ -61,4 +59,5 @@ "initializeCommand": "sh .devcontainer/initialize-command.sh", "onCreateCommand": "sh .devcontainer/on-create-command.sh", "postStartCommand": "sh .devcontainer/post-start-command.sh" + // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): bbebc7c9 } diff --git a/.devcontainer/install-ci-tooling.sh b/.devcontainer/install-ci-tooling.sh index 91284ea7..ccc8b050 100644 --- a/.devcontainer/install-ci-tooling.sh +++ b/.devcontainer/install-ci-tooling.sh @@ -6,7 +6,7 @@ set -ex -curl -LsSf https://astral.sh/uv/0.6.6/install.sh | sh +curl -LsSf https://astral.sh/uv/0.6.17/install.sh | sh uv --version # TODO: add uv autocompletion to the shell https://docs.astral.sh/uv/getting-started/installation/#shell-autocompletion @@ -19,8 +19,8 @@ input="${1:-$default_version}" export UV_PYTHON="$input" export UV_PYTHON_PREFERENCE=only-system -uv tool install 'copier==9.5.0' --with 'copier-templates-extensions==0.3.0' +uv tool install 'copier==9.6.0' --with 'copier-templates-extensions==0.3.0' -uv tool install 'pre-commit==4.1.0' +uv tool install 'pre-commit==4.2.0' uv tool list diff --git a/.devcontainer/windows-host-helper.sh b/.devcontainer/windows-host-helper.sh index 7c4c2f27..b3d1d876 100644 --- a/.devcontainer/windows-host-helper.sh +++ b/.devcontainer/windows-host-helper.sh @@ -7,7 +7,7 @@ # If you're still having issues, make sure in Windows Developer Settings that you enabled Developer Mode, and also that you set your git config to have `core.autocrlf=false` and `core.symlinks=true` globally -set -e # Exit immediately on error +set -euo pipefail # Exit immediately on error if [ -z "$BASH_VERSION" ]; then echo "Error: This script must be run with bash (e.g., 'bash windows-host-helper.sh')." >&2 @@ -39,13 +39,26 @@ tmpdir=$(mktemp -d) # This creates "$tmpdir/$repoName" with the repository's contents. git clone "$gitUrl" "$tmpdir/$repoName" -# Enable dotglob so that '*' includes hidden files -shopt -s dotglob -# Move all contents (including hidden files) from the cloned repo to the target folder -mv "$tmpdir/$repoName"/* "./$repoName/" +SRC="$(realpath "$tmpdir/$repoName")" +DST="$(realpath "./$repoName")" -# Clean up: remove the temporary directory +# 1) Recreate directory tree under $DST +while IFS= read -r -d '' dir; do + rel="${dir#$SRC/}" # strip leading $SRC/ → e.g. "sub/dir" + mkdir -p "$DST/$rel" +done < <(find "$SRC" -type d -print0) + +# 2) Move all files into that mirror +while IFS= read -r -d '' file; do + rel="${file#$SRC/}" # e.g. "sub/dir/file.txt" + # ensure parent exists (though step 1 already did) + mkdir -p "$(dirname "$DST/$rel")" + mv "$file" "$DST/$rel" +done < <(find "$SRC" -type f -print0) + +# 3) Clean up now‑empty dirs and the tmp clone +find "$SRC" -depth -type d -empty -delete rm -rf "$tmpdir" -echo "Repository '$repoName' has been updated." +echo "Repository '$repoName' has been synced into '$DST'." diff --git a/.github/actions/install_deps_uv/action.yml b/.github/actions/install_deps_uv/action.yml index dc7898e9..0a27ec5b 100644 --- a/.github/actions/install_deps_uv/action.yml +++ b/.github/actions/install_deps_uv/action.yml @@ -40,7 +40,7 @@ runs: shell: bash - name: Setup python - uses: actions/setup-python@v5.4.0 + uses: actions/setup-python@v5.5.0 with: python-version: ${{ env.PYTHON_VERSION }} diff --git a/.github/actions/install_deps_uv/install-ci-tooling.ps1 b/.github/actions/install_deps_uv/install-ci-tooling.ps1 index 75d6938d..363e5130 100644 --- a/.github/actions/install_deps_uv/install-ci-tooling.ps1 +++ b/.github/actions/install_deps_uv/install-ci-tooling.ps1 @@ -3,7 +3,7 @@ Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" -irm https://astral.sh/uv/0.6.6/install.ps1 | iex +irm https://astral.sh/uv/0.6.17/install.ps1 | iex # Add uv to path (in github runner) $env:Path = "C:\Users\runneradmin\.local\bin;$env:Path" @@ -24,8 +24,8 @@ if ($args.Count -eq 0) { $env:UV_PYTHON = "$input" $env:UV_PYTHON_PREFERENCE="only-system" -& uv tool install 'copier==9.5.0' --with 'copier-templates-extensions==0.3.0' +& uv tool install 'copier==9.6.0' --with 'copier-templates-extensions==0.3.0' -& uv tool install 'pre-commit==4.1.0' +& uv tool install 'pre-commit==4.2.0' & uv tool list diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b88d9360..0d27aced 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ - ## Link to Issue or Slack thread + ## Link to Issue or Message thread diff --git a/.github/reusable_workflows/build-docker-image.yaml b/.github/reusable_workflows/build-docker-image.yaml index 58e92db4..240c36f7 100644 --- a/.github/reusable_workflows/build-docker-image.yaml +++ b/.github/reusable_workflows/build-docker-image.yaml @@ -11,44 +11,144 @@ on: description: 'Docker image name' type: string required: true - tag: - description: 'Docker image tag' - type: string - required: true + tag-for-production: + description: 'Whether or not to add a tag indicating this is being used in production' + type: boolean + required: false + default: false context: description: 'Build context path' type: string required: false default: './' + push-role-name: + type: string + description: What's the IAM role name to use for Pushing to the ECR? + required: false + default: no-push + save-as-artifact: + type: boolean + description: 'Should the image be saved as an artifact?' + required: false + default: false +permissions: + id-token: write + contents: write # needed for mutex jobs: build-image: name: Build Docker Image runs-on: ubuntu-24.04 steps: + - name: Parse ECR URL + if: ${{ inputs.push-role-name != 'no-push' }} + id: parse_ecr_url + run: | + ECR_URL="${{ inputs.repository}}" + + # Extract the AWS Account ID, which is the first field + AWS_ACCOUNT_ID=$(echo "$ECR_URL" | cut -d. -f1) + + # Extract the AWS Region, which is the fourth field in the URL structure + AWS_REGION=$(echo "$ECR_URL" | cut -d. -f4) + + # Set the outputs for use in later steps + echo "aws_account_id=${AWS_ACCOUNT_ID}" >> "$GITHUB_OUTPUT" + echo "aws_region=${AWS_REGION}" >> "$GITHUB_OUTPUT" + shell: bash + - name: Checkout code uses: actions/checkout@v4.2.2 + - name: OIDC Auth for ECR + if: ${{ inputs.push-role-name != 'no-push' }} + uses: aws-actions/configure-aws-credentials@v4.1.0 + with: + role-to-assume: arn:aws:iam::${{ steps.parse_ecr_url.outputs.aws_account_id }}:role/${{ inputs.push-role-name }} + aws-region: ${{ steps.parse_ecr_url.outputs.aws_region }} + + - name: Calculate hash of files in build context + id: calculate-build-context-hash + run: | + python3 --version + BUILD_HASH=$(python3 .github/workflows/hash_git_files.py ${{ inputs.context }}) + echo "build_context_tag=context-${BUILD_HASH}" >> "$GITHUB_OUTPUT" + echo "Calculated build context tag as: ${BUILD_HASH}" + + IMAGE_NAME_WITH_NAMESPACE="${{ inputs.image_name }}" + IMAGE_NAME_NO_SLASHES="${IMAGE_NAME_WITH_NAMESPACE//\//-}" + echo "image_name_no_slashes=${IMAGE_NAME_NO_SLASHES}" >> "$GITHUB_OUTPUT" + echo "Image name without slashes: ${IMAGE_NAME_NO_SLASHES}" + + - name: Set up mutex # Github concurrency management is horrible, things get arbitrarily cancelled if queued up. So using mutex until github fixes itself. When multiple jobs are modifying cache at once, weird things can happen. possible issue is https://github.com/actions/toolkit/issues/658 + if: ${{ inputs.push-role-name != 'no-push' }} + uses: ben-z/gh-action-mutex@1ebad517141198e08d47cf72f3c0975316620a65 # v1.0.0-alpha.10 + with: + branch: mutex-${{ inputs.repository }}-${{ inputs.image_name }} + timeout-minutes: 30 # this is the amount of time this action will wait to attempt to acquire the mutex lock before failing, e.g. if other jobs are queued up in front of it + + - name: Test if docker image exists + if: ${{ inputs.push-role-name != 'no-push' }} + id: check-if-exists + run: | + BUILD_HASH=${{ steps.calculate-build-context-hash.outputs.build_context_tag }} + echo Checking for : $BUILD_HASH + if aws ecr describe-images --region ${{ steps.parse_ecr_url.outputs.aws_region }} --registry-id=${{ steps.parse_ecr_url.outputs.aws_account_id }} --repository-name=${{ inputs.image_name }} --image-ids=imageTag=$BUILD_HASH; then \ + echo "Image was found in ECR"; \ + echo "status=found" >> $GITHUB_OUTPUT + else \ + echo "Image was not found in ECR"; \ + echo "status=notfound" >> $GITHUB_OUTPUT + fi + + - name: Login to Amazon ECR + if: ${{ inputs.push-role-name != 'no-push' && (steps.check-if-exists.outputs.status == 'notfound' || inputs.save-as-artifact ) }} + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2.0.1 + + - name: Pull existing image to package as artifact + if: ${{ inputs.save-as-artifact && steps.check-if-exists.outputs.status == 'found' }} + run: | + docker pull ${{ inputs.repository }}/${{ inputs.image_name }}:${{ steps.calculate-build-context-hash.outputs.build_context_tag }} + - name: Set up Docker Buildx + if: ${{ (inputs.save-as-artifact && inputs.push-role-name == 'no-push') || steps.check-if-exists.outputs.status == 'notfound' }} uses: docker/setup-buildx-action@v3.10.0 with: version: v0.22.0 - name: Build Docker Image + if: ${{ (inputs.save-as-artifact && inputs.push-role-name == 'no-push') || steps.check-if-exists.outputs.status == 'notfound' }} uses: docker/build-push-action@v6.15.0 with: context: ${{ inputs.context }} - push: false - load: true # make the image available later for the `docker save` step - tags: ${{ inputs.repository }}/${{ inputs.image_name }}:${{ inputs.tag }} + push: ${{ inputs.push-role-name != 'no-push' && steps.check-if-exists.outputs.status == 'notfound' }} + load: ${{ inputs.save-as-artifact }} # make the image available later for the `docker save` step + tags: ${{ inputs.repository }}/${{ inputs.image_name }}:${{ steps.calculate-build-context-hash.outputs.build_context_tag }} + + - name: Add git sha tag + if: ${{ inputs.push-role-name != 'no-push' }} + run: | + aws ecr batch-get-image --registry-id=${{ steps.parse_ecr_url.outputs.aws_account_id }} --repository-name=${{ inputs.image_name }} --image-ids imageTag=${{ steps.calculate-build-context-hash.outputs.build_context_tag }} --query 'images[].imageManifest' --output text > manifest.json + aws ecr put-image --registry-id=${{ steps.parse_ecr_url.outputs.aws_account_id }} --repository-name=${{ inputs.image_name }} --image-tag git-sha-${{ github.sha }} --image-manifest file://manifest.json + + - name: Add tag for Production + if: ${{ inputs.push-role-name != 'no-push' && inputs.tag-for-production }} + run: | + aws ecr batch-get-image --registry-id=${{ steps.parse_ecr_url.outputs.aws_account_id }} --repository-name=${{ inputs.image_name }} --image-ids imageTag=${{ steps.calculate-build-context-hash.outputs.build_context_tag }} --query 'images[].imageManifest' --output text > manifest.json + # TODO: figure out some better conditional logic about adding a tag for the context in production, so we don't have to `|| true` at the end + aws ecr put-image --registry-id=${{ steps.parse_ecr_url.outputs.aws_account_id }} --repository-name=${{ inputs.image_name }} --image-tag production--${{ steps.calculate-build-context-hash.outputs.build_context_tag }} --image-manifest file://manifest.json || true + aws ecr put-image --registry-id=${{ steps.parse_ecr_url.outputs.aws_account_id }} --repository-name=${{ inputs.image_name }} --image-tag production--git-sha-${{ github.sha }} --image-manifest file://manifest.json - name: Save Docker Image as tar - run: docker save -o ${{ inputs.image_name }}.tar ${{ inputs.repository }}/${{ inputs.image_name }}:${{ inputs.tag }} + if: ${{ inputs.save-as-artifact }} + run: docker save -o ${{ steps.calculate-build-context-hash.outputs.image_name_no_slashes }}.tar ${{ inputs.repository }}/${{ inputs.image_name }}:${{ steps.calculate-build-context-hash.outputs.build_context_tag }} - name: Upload Docker Image Artifact + if: ${{ inputs.save-as-artifact }} uses: actions/upload-artifact@v4.6.2 with: - name: ${{ inputs.image_name }} - path: ${{ inputs.image_name }}.tar + name: ${{ steps.calculate-build-context-hash.outputs.image_name_no_slashes }} + path: ${{ steps.calculate-build-context-hash.outputs.image_name_no_slashes }}.tar if-no-files-found: error diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 37910f4f..9f9d09ce 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -143,8 +143,8 @@ jobs: - name: Run pre-commit run: | - # skip pip-compile because the command line args in the header are different - SKIP=git-dirty pre-commit run -a + # skip devcontainer context hash because the template instantiation may make it different every time + SKIP=git-dirty,compute-devcontainer-context-hash pre-commit run -a - name: Upload pre-commit log if failure if: ${{ failure() }} diff --git a/.github/workflows/hash_git_files.py b/.github/workflows/hash_git_files.py new file mode 100644 index 00000000..bead4e4f --- /dev/null +++ b/.github/workflows/hash_git_files.py @@ -0,0 +1,170 @@ +"""Used typically to calculate if all the files in the context of building a Docker image have changed or not.""" + +import argparse +import subprocess +import sys +import zlib +from pathlib import Path + +DEVCONTAINER_COMMENT_LINE_PREFIX = ( + " // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): " +) + + +def get_tracked_files(repo_path: Path) -> list[str]: + """Return a list of files tracked by Git in the given repository folder, using the 'git ls-files' command.""" + try: + result = subprocess.run( # noqa: S603 # there's no concern about executing untrusted input, only we will call this script + ["git", "-C", str(repo_path), "ls-files"], # noqa: S607 # yes, this is not using a complete executable path, but it's just git and git should always be present in PATH + capture_output=True, + text=True, + check=True, + ) + return result.stdout.splitlines() + + except subprocess.CalledProcessError: + print("Error: The directory does not appear to be a Git repository or Git is not installed.", file=sys.stderr) # noqa: T201 # this just runs as a simple script, so using print instead of log + sys.exit(1) + + +def filter_files_for_devcontainer_context(files: list[str]) -> tuple[list[str], Path]: + devcontainer_context: list[str] = [] + devcontainer_json_file_path: str | None = None + for file in files: + if file.startswith(".devcontainer/"): + if file.endswith("devcontainer.json"): + devcontainer_json_file_path = file + continue + devcontainer_context.append(file) + elif file.endswith((".lock", "pnpm-lock.yaml", "hash_git_files.py")) or file == ".pre-commit-config.yaml": + devcontainer_context.append(file) + if devcontainer_json_file_path is None: + raise ValueError("No devcontainer.json file found in the tracked files.") # noqa: TRY003 # not worth a custom exception for this + return devcontainer_context, Path(devcontainer_json_file_path) + + +def compute_adler32(repo_path: Path, files: list[str]) -> int: + """Compute an overall Adler-32 checksum of the provided files. + + The checksum incorporates both the file names and their contents. Files are processed in sorted order to ensure consistent ordering. + """ + checksum = 1 # Adler-32 default starting value + + for file in sorted(files): + file_path = repo_path / file # Use pathlib to combine paths + # Update the checksum with the file name (encoded as bytes) + checksum = zlib.adler32(file.encode("utf-8"), checksum) + try: + with file_path.open("rb") as f: + while True: + chunk = f.read(4096) + if not chunk: + break + checksum = zlib.adler32(chunk, checksum) + except Exception as e: + if "[Errno 21] Is a directory" in str(e): + # Ignore symlinks that on windows sometimes get confused as being directories + continue + print(f"Error reading file {file}: {e}", file=sys.stderr) # noqa: T201 # this just runs as a simple script, so using print instead of log + raise + + return checksum + + +def find_devcontainer_hash_line(lines: list[str]) -> tuple[int, str | None]: + """Find the line index and current hash in the devcontainer.json file.""" + for i in range(len(lines) - 1, -1, -1): + if lines[i].strip() == "}": + # Check the line above it + if i > 0 and lines[i - 1].startswith(DEVCONTAINER_COMMENT_LINE_PREFIX): + current_hash = lines[i - 1].split(": ", 1)[1].strip() + return i - 1, current_hash + return i, None + return -1, None + + +def extract_devcontainer_context_hash(devcontainer_json_file: Path) -> str | None: + """Extract the current devcontainer context hash from the given devcontainer.json file.""" + try: + with devcontainer_json_file.open("r", encoding="utf-8") as file: + lines = file.readlines() + except Exception as e: + print(f"Error reading file {devcontainer_json_file}: {e}", file=sys.stderr) # noqa: T201 + raise + _, current_hash = find_devcontainer_hash_line(lines) + return current_hash + + +def update_devcontainer_context_hash(devcontainer_json_file: Path, new_hash: str) -> None: + """Update the devcontainer.json file with the new context hash.""" + try: + with devcontainer_json_file.open("r", encoding="utf-8") as file: + lines = file.readlines() + + line_index, current_hash = find_devcontainer_hash_line(lines) + if current_hash is not None: + # Replace the old hash with the new hash + lines[line_index] = f"{DEVCONTAINER_COMMENT_LINE_PREFIX}{new_hash}\n" + else: + # Insert the new hash line above the closing `}` + lines.insert(line_index, f"{DEVCONTAINER_COMMENT_LINE_PREFIX}{new_hash}\n") + + # Write the updated lines back to the file + with devcontainer_json_file.open("w", encoding="utf-8") as file: + file.writelines(lines) + + except Exception as e: + print(f"Error updating file {devcontainer_json_file}: {e}", file=sys.stderr) # noqa: T201 + raise + + +def main(): + parser = argparse.ArgumentParser( + description="Compute an Adler-32 checksum of all Git-tracked files in the specified folder." + ) + _ = parser.add_argument("folder", type=Path, help="Path to the Git repository folder") + _ = parser.add_argument("--debug", action="store_true", help="Print all discovered Git-tracked files") + _ = parser.add_argument( + "--for-devcontainer-config-update", + action="store_true", + help="Update the hash in the devcontainer.json file based on all files relevant to devcontainer context", + ) + args = parser.parse_args() + + repo_path = args.folder + if not repo_path.is_dir(): + print(f"Error: {repo_path} is not a valid directory.", file=sys.stderr) # noqa: T201 # this just runs as a simple script, so using print instead of log + sys.exit(1) + + # Retrieve the list of Git-tracked files. + files = get_tracked_files(repo_path) + devcontainer_json_file: Path | None = None + if args.for_devcontainer_config_update: + files, devcontainer_json_file = filter_files_for_devcontainer_context(files) + + # If the debug flag is specified, print out all discovered files. + if args.debug: + print("Tracked files discovered:") # noqa: T201 # this just runs as a simple script, so using print instead of log + for file in files: + print(file) # noqa: T201 # this just runs as a simple script, so using print instead of log + + # Compute the overall Adler-32 checksum. + overall_checksum = compute_adler32(repo_path, files) + overall_checksum_str = f"{overall_checksum:08x}" # Format the checksum as an 8-digit hexadecimal value. + if args.for_devcontainer_config_update: + assert devcontainer_json_file is not None, ( + "this should have been set earlier in a similar conditional statement" + ) + current_hash = extract_devcontainer_context_hash(devcontainer_json_file) + if current_hash != overall_checksum_str: + update_devcontainer_context_hash(devcontainer_json_file, overall_checksum_str) + print( # noqa: T201 + f"Updated {devcontainer_json_file} with the new hash: {overall_checksum_str}" + ) + sys.exit(1) # Exit with non-zero code to indicate changes were made + else: + print(overall_checksum_str) # noqa: T201 # print this so that the value can be picked up via STDOUT when calling this in a CI pipeline or as a subprocess + + +if __name__ == "__main__": + main() diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ba9ddcd9..38ff0763 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: # Reformatting (should generally come before any file format or other checks, because reformatting can change things) - repo: https://github.com/crate-ci/typos - rev: 2300ad1b6b5c37da54bcafb1a06211196503eac9 # frozen: v1 + rev: 15ff058881549e16b0edb975a9b0b0d0cccd612c # frozen: v1 hooks: - id: typos - repo: https://github.com/pre-commit/pre-commit-hooks @@ -74,6 +74,15 @@ repos: )$ args: [--autofix, --no-sort-keys] + - repo: https://github.com/biomejs/pre-commit + rev: 748e40d32e076a6aaaf3353a2564c8fe43764f79 # frozen: v2.0.0-beta.1 + hooks: + - id: biome-check + exclude: | + (?x)^( + .*generated/graphql.ts| + )$ + - repo: https://github.com/pre-commit/mirrors-prettier # TODO: switch to a different approach...this was archived in 2024 rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8 hooks: @@ -90,6 +99,7 @@ repos: .*.yml| .*.md| )$ + files: (.*.json)|(.*.ts)|(.*.jsx)|(.*.tsx)|(.*.yaml)|(.*.yml)|(.*.md)|(.*.html)|(.*.css)|(.*.scss)|(.*.less)|(.*.vue)|(.*.graphql)|(.*.gql) - repo: https://github.com/myint/docformatter # black seems to be working on formatting docstrings, but use this for now @@ -179,7 +189,7 @@ repos: description: Runs hadolint to lint Dockerfiles - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 25a8c8da6c24a3b9a1a536e2674683dd0eead5d6 # frozen: v0.11.2 + rev: e84319e627902e1b348574ecf3238dc511933dc7 # frozen: v0.11.7 hooks: - id: ruff name: ruff-src @@ -199,6 +209,7 @@ repos: args: - --rcfile=./pylintrc.toml verbose: true + exclude: ^template/.* # exclude the template files---duplication within them will be discovered during CI of that template instantiation - repo: local hooks: @@ -214,3 +225,13 @@ repos: require_serial: true # print the number of files as a sanity-check verbose: true + + # Devcontainer context --- this makes Github's "prebuild codespaces" feature work more intelligently for the "Configuration Change" trigger + - repo: local + hooks: + - id: compute-devcontainer-context-hash + name: compute devcontainer context hash + entry: bash -c "python3 .github/workflows/hash_git_files.py . --for-devcontainer-config-update" + files: (.*.lock)|(.*pnpm-lock.yaml)|(.*hash_git_files.py)|(.devcontainer/.*)|(\.pre-commit-config.yaml) + pass_filenames: false + language: system diff --git a/biome.json b/biome.json new file mode 100644 index 00000000..7dbe4e1e --- /dev/null +++ b/biome.json @@ -0,0 +1,8 @@ +{ + "formatter": { + "enabled": false + }, + "linter": { + "enabled": false + } +} diff --git a/copier.yml b/copier.yml index 5e32568e..d87f3301 100644 --- a/copier.yml +++ b/copier.yml @@ -19,7 +19,8 @@ is_open_source: ssh_port_number: type: int help: What port should the devcontainer bind SSH to? - default: "{{ range(49152, 65536) | random }}" + # Pick a random port, but ensure it's not in the excluded port range on Windows (powershell: `netsh int ipv4 show excludedportrange protocol=tcp`) + default: "{{ ([p for p in range(49152, 65536) if not (49752 <= p <= 49851 or 50000 <= p <= 50059 or 50060 <= p <= 50159 or 50160 <= p <= 50259 or 50260 <= p <= 50359 or 50914 <= p <= 51013 or 51114 <= p <= 51213 or 51214 <= p <= 51313 or 51314 <= p <= 51413 or 51623 <= p <= 51722 or 51723 <= p <= 51822 or 65269 <= p <= 65368 or 65369 <= p <= 65468))] | random }}" use_windows_in_ci: type: bool @@ -64,6 +65,7 @@ aws_central_infrastructure_account_id: type: str help: What's the ID of your Organization's AWS Account containing Central Infrastructure (e.g. CodeArtifact)? when: "{{ python_package_registry == 'AWS CodeArtifact' }}" + default: "000000000000" core_infra_base_access_profile_name: type: str diff --git a/extensions/context.py b/extensions/context.py index b7833d30..734ac34e 100644 --- a/extensions/context.py +++ b/extensions/context.py @@ -10,36 +10,37 @@ class ContextUpdater(ContextHook): @override def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: - context["uv_version"] = "0.6.6" - context["pnpm_version"] = "10.6.3" - context["pre_commit_version"] = "4.1.0" - context["pyright_version"] = "1.1.397" - context["pytest_version"] = "8.3.4" + context["uv_version"] = "0.6.17" + context["pnpm_version"] = "10.10.0" + context["pre_commit_version"] = "4.2.0" + context["pyright_version"] = "1.1.400" + context["pytest_version"] = "8.3.5" context["pytest_randomly_version"] = "3.16.0" context["pytest_cov_version"] = "6.0.0" - context["copier_version"] = "9.5.0" + context["copier_version"] = "9.6.0" context["copier_templates_extension_version"] = "0.3.0" context["sphinx_version"] = "8.1.3" - context["pulumi_version"] = "3.156.0" - context["pulumi_aws_version"] = "6.72.0" - context["pulumi_aws_native_version"] = "1.26.0" + context["pulumi_version"] = "3.163.0" + context["pulumi_aws_version"] = "6.77.0" + context["pulumi_aws_native_version"] = "1.27.0" context["pulumi_command_version"] = "1.0.2" context["pulumi_github"] = "" context["boto3_version"] = "1.37.11" context["ephemeral_pulumi_deploy_version"] = "0.0.4" - context["pydantic_version"] = "2.10.6" + context["pydantic_version"] = "2.11.1" context["pyinstaller_version"] = "6.12.0" context["setuptools_version"] = "76.0.0" - context["strawberry_graphql_version"] = "0.262.5" - context["fastapi_version"] = "0.115.11" + context["strawberry_graphql_version"] = "0.264.0" + context["fastapi_version"] = "0.115.12" context["uvicorn_version"] = "0.34.0" + context["lab_auto_pulumi_version"] = "0.1.11" - context["nuxt_ui_version"] = "^3.0.0" - context["nuxt_version"] = "^3.16.0" + context["nuxt_ui_version"] = "^3.0.2" + context["nuxt_version"] = "^3.16.2" context["typescript_version"] = "^5.8.2" context["gha_checkout"] = "v4.2.2" - context["gha_setup_python"] = "v5.4.0" + context["gha_setup_python"] = "v5.5.0" context["gha_cache"] = "v4.2.2" context["gha_upload_artifact"] = "v4.6.2" context["gha_download_artifact"] = "v4.2.1" @@ -47,10 +48,12 @@ def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: context["buildx_version"] = "v0.22.0" context["gha_docker_build_push"] = "v6.15.0" context["gha_configure_aws_credentials"] = "v4.1.0" + context["gha_amazon_ecr_login"] = "v2.0.1" context["gha_setup_node"] = "v4.3.0" + context["gha_action_gh_release"] = "v2.2.1" context["gha_mutex"] = "1ebad517141198e08d47cf72f3c0975316620a65 # v1.0.0-alpha.10" context["gha_linux_runner"] = "ubuntu-24.04" - context["gha_windows_runner"] = "windows-2022" + context["gha_windows_runner"] = "windows-2025" context["py311_version"] = "" context["py312_version"] = "3.12.7" diff --git a/pyproject.toml b/pyproject.toml index c8bf920f..83bf4953 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,11 +6,11 @@ readme = "README.md" requires-python = ">=3.12.7" dependencies = [ # Managed by upstream template - "pytest>=8.3.4", + "pytest>=8.3.5", "pytest-cov>=6.0.0", "pytest-randomly>=3.16.0", - "pyright[nodejs]>=1.1.397", - "copier>=9.5.0", + "pyright[nodejs]>=1.1.400", + "copier>=9.6.0", "copier-templates-extensions>=0.3.0" # Specific to this template diff --git a/template/.devcontainer/create-aws-profile.sh.jinja b/template/.devcontainer/create-aws-profile.sh.jinja index 2801384c..b87756a3 100644 --- a/template/.devcontainer/create-aws-profile.sh.jinja +++ b/template/.devcontainer/create-aws-profile.sh.jinja @@ -26,7 +26,7 @@ region={% endraw %}{{ aws_org_home_region if (aws_org_home_region is defined and output=json endpoint_url = $LOCALSTACK_ENDPOINT_URL -{% endraw %}{% if aws_central_infrastructure_account_id is defined and aws_central_infrastructure_account_id != "" %}{% raw %} +{% endraw %}{% if aws_central_infrastructure_account_id is defined and aws_central_infrastructure_account_id != "" and aws_central_infrastructure_account_id != "000000000000" %}{% raw %} [profile {% endraw %}{{ core_infra_base_access_profile_name }}{% raw %}] sso_session = org sso_role_name = {% endraw %}{{ core_infra_base_access_profile_name }}{% raw %} diff --git a/template/.devcontainer/devcontainer.json.jinja b/template/.devcontainer/devcontainer.json.jinja index 97e9b4db..c34ef96f 100644 --- a/template/.devcontainer/devcontainer.json.jinja +++ b/template/.devcontainer/devcontainer.json.jinja @@ -18,8 +18,7 @@ // https://github.com/devcontainers/features/tree/main/src/node "version": "{% endraw %}{{ node_version }}{% raw %}", "pnpmVersion": "{% endraw %}{{ pnpm_version }}{% raw %}" - } -{% endraw %}{% endif %}{% raw %} + }{% endraw %}{% endif %}{% raw %} }, "customizations": { "vscode": { @@ -29,15 +28,14 @@ "eamodio.gitlens@15.5.1", "ms-vscode.live-server@0.5.2024091601", "MS-vsliveshare.vsliveshare@1.0.5905", - "github.copilot@1.234.0", - "github.copilot-chat@0.20.2", + "github.copilot@1.304.1523", + "github.copilot-chat@0.27.2025042301", // Python "ms-python.python@2024.14.1", "ms-python.vscode-pylance@2024.9.2", "ms-vscode-remote.remote-containers@0.383.0", "charliermarsh.ruff@2024.54.0", - {% endraw %}{% if is_child_of_copier_base_template is not defined and template_uses_vuejs is defined and template_uses_vuejs is sameas(true) %}{% raw %} // VueJS "vue.volar@2.2.8", @@ -46,9 +44,8 @@ // All javascript "dbaeumer.vscode-eslint@3.0.13", {% endraw %}{% endif %}{% raw %} - // Misc file formats - "bierner.markdown-mermaid@1.25.0", + "bierner.markdown-mermaid@1.28.0", "samuelcolvin.jinjahtml@0.20.0", "tamasfe.even-better-toml@0.19.2", "emilast.LogFileHighlighter@3.3.3", diff --git a/template/.devcontainer/windows-host-helper.sh b/template/.devcontainer/windows-host-helper.sh index 7c4c2f27..b3d1d876 100644 --- a/template/.devcontainer/windows-host-helper.sh +++ b/template/.devcontainer/windows-host-helper.sh @@ -7,7 +7,7 @@ # If you're still having issues, make sure in Windows Developer Settings that you enabled Developer Mode, and also that you set your git config to have `core.autocrlf=false` and `core.symlinks=true` globally -set -e # Exit immediately on error +set -euo pipefail # Exit immediately on error if [ -z "$BASH_VERSION" ]; then echo "Error: This script must be run with bash (e.g., 'bash windows-host-helper.sh')." >&2 @@ -39,13 +39,26 @@ tmpdir=$(mktemp -d) # This creates "$tmpdir/$repoName" with the repository's contents. git clone "$gitUrl" "$tmpdir/$repoName" -# Enable dotglob so that '*' includes hidden files -shopt -s dotglob -# Move all contents (including hidden files) from the cloned repo to the target folder -mv "$tmpdir/$repoName"/* "./$repoName/" +SRC="$(realpath "$tmpdir/$repoName")" +DST="$(realpath "./$repoName")" -# Clean up: remove the temporary directory +# 1) Recreate directory tree under $DST +while IFS= read -r -d '' dir; do + rel="${dir#$SRC/}" # strip leading $SRC/ → e.g. "sub/dir" + mkdir -p "$DST/$rel" +done < <(find "$SRC" -type d -print0) + +# 2) Move all files into that mirror +while IFS= read -r -d '' file; do + rel="${file#$SRC/}" # e.g. "sub/dir/file.txt" + # ensure parent exists (though step 1 already did) + mkdir -p "$(dirname "$DST/$rel")" + mv "$file" "$DST/$rel" +done < <(find "$SRC" -type f -print0) + +# 3) Clean up now‑empty dirs and the tmp clone +find "$SRC" -depth -type d -empty -delete rm -rf "$tmpdir" -echo "Repository '$repoName' has been updated." +echo "Repository '$repoName' has been synced into '$DST'." diff --git a/template/.github/actions/install_deps_uv/action.yml b/template/.github/actions/install_deps_uv/action.yml index dc7898e9..0a27ec5b 100644 --- a/template/.github/actions/install_deps_uv/action.yml +++ b/template/.github/actions/install_deps_uv/action.yml @@ -40,7 +40,7 @@ runs: shell: bash - name: Setup python - uses: actions/setup-python@v5.4.0 + uses: actions/setup-python@v5.5.0 with: python-version: ${{ env.PYTHON_VERSION }} diff --git a/template/.github/actions/install_deps_uv/install-ci-tooling.ps1 b/template/.github/actions/install_deps_uv/install-ci-tooling.ps1 index 75d6938d..363e5130 100644 --- a/template/.github/actions/install_deps_uv/install-ci-tooling.ps1 +++ b/template/.github/actions/install_deps_uv/install-ci-tooling.ps1 @@ -3,7 +3,7 @@ Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" -irm https://astral.sh/uv/0.6.6/install.ps1 | iex +irm https://astral.sh/uv/0.6.17/install.ps1 | iex # Add uv to path (in github runner) $env:Path = "C:\Users\runneradmin\.local\bin;$env:Path" @@ -24,8 +24,8 @@ if ($args.Count -eq 0) { $env:UV_PYTHON = "$input" $env:UV_PYTHON_PREFERENCE="only-system" -& uv tool install 'copier==9.5.0' --with 'copier-templates-extensions==0.3.0' +& uv tool install 'copier==9.6.0' --with 'copier-templates-extensions==0.3.0' -& uv tool install 'pre-commit==4.1.0' +& uv tool install 'pre-commit==4.2.0' & uv tool list diff --git a/template/.github/pull_request_template.md b/template/.github/pull_request_template.md index b88d9360..0d27aced 100644 --- a/template/.github/pull_request_template.md +++ b/template/.github/pull_request_template.md @@ -1,4 +1,4 @@ - ## Link to Issue or Slack thread + ## Link to Issue or Message thread diff --git a/template/.github/workflows/hash_git_files.py b/template/.github/workflows/hash_git_files.py new file mode 100644 index 00000000..bead4e4f --- /dev/null +++ b/template/.github/workflows/hash_git_files.py @@ -0,0 +1,170 @@ +"""Used typically to calculate if all the files in the context of building a Docker image have changed or not.""" + +import argparse +import subprocess +import sys +import zlib +from pathlib import Path + +DEVCONTAINER_COMMENT_LINE_PREFIX = ( + " // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): " +) + + +def get_tracked_files(repo_path: Path) -> list[str]: + """Return a list of files tracked by Git in the given repository folder, using the 'git ls-files' command.""" + try: + result = subprocess.run( # noqa: S603 # there's no concern about executing untrusted input, only we will call this script + ["git", "-C", str(repo_path), "ls-files"], # noqa: S607 # yes, this is not using a complete executable path, but it's just git and git should always be present in PATH + capture_output=True, + text=True, + check=True, + ) + return result.stdout.splitlines() + + except subprocess.CalledProcessError: + print("Error: The directory does not appear to be a Git repository or Git is not installed.", file=sys.stderr) # noqa: T201 # this just runs as a simple script, so using print instead of log + sys.exit(1) + + +def filter_files_for_devcontainer_context(files: list[str]) -> tuple[list[str], Path]: + devcontainer_context: list[str] = [] + devcontainer_json_file_path: str | None = None + for file in files: + if file.startswith(".devcontainer/"): + if file.endswith("devcontainer.json"): + devcontainer_json_file_path = file + continue + devcontainer_context.append(file) + elif file.endswith((".lock", "pnpm-lock.yaml", "hash_git_files.py")) or file == ".pre-commit-config.yaml": + devcontainer_context.append(file) + if devcontainer_json_file_path is None: + raise ValueError("No devcontainer.json file found in the tracked files.") # noqa: TRY003 # not worth a custom exception for this + return devcontainer_context, Path(devcontainer_json_file_path) + + +def compute_adler32(repo_path: Path, files: list[str]) -> int: + """Compute an overall Adler-32 checksum of the provided files. + + The checksum incorporates both the file names and their contents. Files are processed in sorted order to ensure consistent ordering. + """ + checksum = 1 # Adler-32 default starting value + + for file in sorted(files): + file_path = repo_path / file # Use pathlib to combine paths + # Update the checksum with the file name (encoded as bytes) + checksum = zlib.adler32(file.encode("utf-8"), checksum) + try: + with file_path.open("rb") as f: + while True: + chunk = f.read(4096) + if not chunk: + break + checksum = zlib.adler32(chunk, checksum) + except Exception as e: + if "[Errno 21] Is a directory" in str(e): + # Ignore symlinks that on windows sometimes get confused as being directories + continue + print(f"Error reading file {file}: {e}", file=sys.stderr) # noqa: T201 # this just runs as a simple script, so using print instead of log + raise + + return checksum + + +def find_devcontainer_hash_line(lines: list[str]) -> tuple[int, str | None]: + """Find the line index and current hash in the devcontainer.json file.""" + for i in range(len(lines) - 1, -1, -1): + if lines[i].strip() == "}": + # Check the line above it + if i > 0 and lines[i - 1].startswith(DEVCONTAINER_COMMENT_LINE_PREFIX): + current_hash = lines[i - 1].split(": ", 1)[1].strip() + return i - 1, current_hash + return i, None + return -1, None + + +def extract_devcontainer_context_hash(devcontainer_json_file: Path) -> str | None: + """Extract the current devcontainer context hash from the given devcontainer.json file.""" + try: + with devcontainer_json_file.open("r", encoding="utf-8") as file: + lines = file.readlines() + except Exception as e: + print(f"Error reading file {devcontainer_json_file}: {e}", file=sys.stderr) # noqa: T201 + raise + _, current_hash = find_devcontainer_hash_line(lines) + return current_hash + + +def update_devcontainer_context_hash(devcontainer_json_file: Path, new_hash: str) -> None: + """Update the devcontainer.json file with the new context hash.""" + try: + with devcontainer_json_file.open("r", encoding="utf-8") as file: + lines = file.readlines() + + line_index, current_hash = find_devcontainer_hash_line(lines) + if current_hash is not None: + # Replace the old hash with the new hash + lines[line_index] = f"{DEVCONTAINER_COMMENT_LINE_PREFIX}{new_hash}\n" + else: + # Insert the new hash line above the closing `}` + lines.insert(line_index, f"{DEVCONTAINER_COMMENT_LINE_PREFIX}{new_hash}\n") + + # Write the updated lines back to the file + with devcontainer_json_file.open("w", encoding="utf-8") as file: + file.writelines(lines) + + except Exception as e: + print(f"Error updating file {devcontainer_json_file}: {e}", file=sys.stderr) # noqa: T201 + raise + + +def main(): + parser = argparse.ArgumentParser( + description="Compute an Adler-32 checksum of all Git-tracked files in the specified folder." + ) + _ = parser.add_argument("folder", type=Path, help="Path to the Git repository folder") + _ = parser.add_argument("--debug", action="store_true", help="Print all discovered Git-tracked files") + _ = parser.add_argument( + "--for-devcontainer-config-update", + action="store_true", + help="Update the hash in the devcontainer.json file based on all files relevant to devcontainer context", + ) + args = parser.parse_args() + + repo_path = args.folder + if not repo_path.is_dir(): + print(f"Error: {repo_path} is not a valid directory.", file=sys.stderr) # noqa: T201 # this just runs as a simple script, so using print instead of log + sys.exit(1) + + # Retrieve the list of Git-tracked files. + files = get_tracked_files(repo_path) + devcontainer_json_file: Path | None = None + if args.for_devcontainer_config_update: + files, devcontainer_json_file = filter_files_for_devcontainer_context(files) + + # If the debug flag is specified, print out all discovered files. + if args.debug: + print("Tracked files discovered:") # noqa: T201 # this just runs as a simple script, so using print instead of log + for file in files: + print(file) # noqa: T201 # this just runs as a simple script, so using print instead of log + + # Compute the overall Adler-32 checksum. + overall_checksum = compute_adler32(repo_path, files) + overall_checksum_str = f"{overall_checksum:08x}" # Format the checksum as an 8-digit hexadecimal value. + if args.for_devcontainer_config_update: + assert devcontainer_json_file is not None, ( + "this should have been set earlier in a similar conditional statement" + ) + current_hash = extract_devcontainer_context_hash(devcontainer_json_file) + if current_hash != overall_checksum_str: + update_devcontainer_context_hash(devcontainer_json_file, overall_checksum_str) + print( # noqa: T201 + f"Updated {devcontainer_json_file} with the new hash: {overall_checksum_str}" + ) + sys.exit(1) # Exit with non-zero code to indicate changes were made + else: + print(overall_checksum_str) # noqa: T201 # print this so that the value can be picked up via STDOUT when calling this in a CI pipeline or as a subprocess + + +if __name__ == "__main__": + main() diff --git a/template/.pre-commit-config.yaml b/template/.pre-commit-config.yaml index ba9ddcd9..38ff0763 100644 --- a/template/.pre-commit-config.yaml +++ b/template/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: # Reformatting (should generally come before any file format or other checks, because reformatting can change things) - repo: https://github.com/crate-ci/typos - rev: 2300ad1b6b5c37da54bcafb1a06211196503eac9 # frozen: v1 + rev: 15ff058881549e16b0edb975a9b0b0d0cccd612c # frozen: v1 hooks: - id: typos - repo: https://github.com/pre-commit/pre-commit-hooks @@ -74,6 +74,15 @@ repos: )$ args: [--autofix, --no-sort-keys] + - repo: https://github.com/biomejs/pre-commit + rev: 748e40d32e076a6aaaf3353a2564c8fe43764f79 # frozen: v2.0.0-beta.1 + hooks: + - id: biome-check + exclude: | + (?x)^( + .*generated/graphql.ts| + )$ + - repo: https://github.com/pre-commit/mirrors-prettier # TODO: switch to a different approach...this was archived in 2024 rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8 hooks: @@ -90,6 +99,7 @@ repos: .*.yml| .*.md| )$ + files: (.*.json)|(.*.ts)|(.*.jsx)|(.*.tsx)|(.*.yaml)|(.*.yml)|(.*.md)|(.*.html)|(.*.css)|(.*.scss)|(.*.less)|(.*.vue)|(.*.graphql)|(.*.gql) - repo: https://github.com/myint/docformatter # black seems to be working on formatting docstrings, but use this for now @@ -179,7 +189,7 @@ repos: description: Runs hadolint to lint Dockerfiles - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 25a8c8da6c24a3b9a1a536e2674683dd0eead5d6 # frozen: v0.11.2 + rev: e84319e627902e1b348574ecf3238dc511933dc7 # frozen: v0.11.7 hooks: - id: ruff name: ruff-src @@ -199,6 +209,7 @@ repos: args: - --rcfile=./pylintrc.toml verbose: true + exclude: ^template/.* # exclude the template files---duplication within them will be discovered during CI of that template instantiation - repo: local hooks: @@ -214,3 +225,13 @@ repos: require_serial: true # print the number of files as a sanity-check verbose: true + + # Devcontainer context --- this makes Github's "prebuild codespaces" feature work more intelligently for the "Configuration Change" trigger + - repo: local + hooks: + - id: compute-devcontainer-context-hash + name: compute devcontainer context hash + entry: bash -c "python3 .github/workflows/hash_git_files.py . --for-devcontainer-config-update" + files: (.*.lock)|(.*pnpm-lock.yaml)|(.*hash_git_files.py)|(.devcontainer/.*)|(\.pre-commit-config.yaml) + pass_filenames: false + language: system diff --git a/template/biome.json b/template/biome.json new file mode 100644 index 00000000..7dbe4e1e --- /dev/null +++ b/template/biome.json @@ -0,0 +1,8 @@ +{ + "formatter": { + "enabled": false + }, + "linter": { + "enabled": false + } +} diff --git a/uv.lock b/uv.lock index c6c514c1..cc9a3111 100644 --- a/uv.lock +++ b/uv.lock @@ -59,10 +59,10 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "copier", specifier = ">=9.5.0" }, + { name = "copier", specifier = ">=9.6.0" }, { name = "copier-templates-extensions", specifier = ">=0.3.0" }, - { name = "pyright", extras = ["nodejs"], specifier = ">=1.1.397" }, - { name = "pytest", specifier = ">=8.3.4" }, + { name = "pyright", extras = ["nodejs"], specifier = ">=1.1.400" }, + { name = "pytest", specifier = ">=8.3.5" }, { name = "pytest-cov", specifier = ">=6.0.0" }, { name = "pytest-randomly", specifier = ">=3.16.0" }, ] @@ -358,15 +358,15 @@ wheels = [ [[package]] name = "pyright" -version = "1.1.397" +version = "1.1.400" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/92/23/cefa10c9cb198e0858ed0b9233371d62bca880337f628e58f50dfdfb12f0/pyright-1.1.397.tar.gz", hash = "sha256:07530fd65a449e4b0b28dceef14be0d8e0995a7a5b1bb2f3f897c3e548451ce3", size = 3818998 } +sdist = { url = "https://files.pythonhosted.org/packages/6c/cb/c306618a02d0ee8aed5fb8d0fe0ecfed0dbf075f71468f03a30b5f4e1fe0/pyright-1.1.400.tar.gz", hash = "sha256:b8a3ba40481aa47ba08ffb3228e821d22f7d391f83609211335858bf05686bdb", size = 3846546 } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/b5/98ec41e1e0ad5576ecd42c90ec363560f7b389a441722ea3c7207682dec7/pyright-1.1.397-py3-none-any.whl", hash = "sha256:2e93fba776e714a82b085d68f8345b01f91ba43e1ab9d513e79b70fc85906257", size = 5693631 }, + { url = "https://files.pythonhosted.org/packages/c8/a5/5d285e4932cf149c90e3c425610c5efaea005475d5f96f1bfdb452956c62/pyright-1.1.400-py3-none-any.whl", hash = "sha256:c80d04f98b5a4358ad3a35e241dbf2a408eee33a40779df365644f8054d2517e", size = 5563460 }, ] [package.optional-dependencies] @@ -376,7 +376,7 @@ nodejs = [ [[package]] name = "pytest" -version = "8.3.4" +version = "8.3.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -384,9 +384,9 @@ dependencies = [ { name = "packaging" }, { name = "pluggy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, ] [[package]]