feat: add Tailwind CSS pipeline, tag-aware cloning & overhaul CI/CD (#352)

Frontend

* introduce Tailwind CSS (package.json, tailwind.config.js, input CSS)
* build site.css on-the-fly (removed tracked artefact; added .gitignore)
* new favicon/icon assets & template cleanup
* split JS into modular files

Docker

* replace single-stage image with 3-stage build
  • css-builder (Node 20 alpine) → compiles Tailwind
  • python-builder installs project with PEP 621 metadata
  • runtime image copies site-packages + compiled CSS, runs as uid 1000

CI/CD

* ci.yml: cache by pyproject.toml, install with `pip -e .[dev]`
* new frontend job builds/archives CSS after tests
* publish.yml: build CSS first, then wheel/sdist; trusted OIDC upload
* tidy scorecard workflow

Core library

* clone.py, parser & utils now resolve tags in addition to branches/commits
* fallback branch/tag discovery when `git ls-remote` fails
* compat\_func.py back-ports Path.readlink / str.removesuffix for Py 3.8

Tooling & docs

* add `[dev]` extra, drop requirements-dev.txt & its pre-commit fixer
* refreshed CONTRIBUTING.md with Node/Tailwind instructions
* updated tests for new tag logic
This commit is contained in:
Filip Christiansen 2025-07-02 21:31:14 +02:00 committed by GitHub
parent 2b1f228ae1
commit 016817d559
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 2631 additions and 458 deletions

View file

@ -8,7 +8,6 @@ from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING, Callable
from unittest.mock import AsyncMock
import pytest
@ -17,8 +16,6 @@ from gitingest.utils.ignore_patterns import DEFAULT_IGNORE_PATTERNS
from tests.conftest import DEMO_URL
if TYPE_CHECKING:
from pytest_mock import MockerFixture
from gitingest.schemas.ingestion import IngestionQuery
@ -379,43 +376,6 @@ async def test_parse_query_with_branch() -> None:
assert query.type == "blob"
@pytest.mark.asyncio
@pytest.mark.parametrize(
("path", "expected_branch", "expected_subpath"),
[
("/tree/main/src", "main", "/src"),
("/tree/fix1", "fix1", "/"),
("/tree/nonexistent-branch/src", "nonexistent-branch", "/src"),
],
)
async def test_parse_repo_source_with_failed_git_command(
path: str,
expected_branch: str,
expected_subpath: str,
mocker: MockerFixture,
) -> None:
"""Test ``_parse_remote_repo`` when git fetch fails.
Given a URL referencing a branch, but Git fetching fails:
When ``_parse_remote_repo`` is called,
Then it should fall back to path components for branch identification.
"""
url = DEMO_URL + path
mock_fetch_branches = mocker.patch("gitingest.utils.git_utils.fetch_remote_branch_list", new_callable=AsyncMock)
mock_fetch_branches.side_effect = Exception("Failed to fetch branch list")
with pytest.warns(
RuntimeWarning,
match="Warning: Failed to fetch branch list: Command failed: "
"git ls-remote --heads https://github.com/user/repo",
):
query = await _parse_remote_repo(url)
assert query.branch == expected_branch
assert query.subpath == expected_subpath
@pytest.mark.asyncio
@pytest.mark.parametrize(
("path", "expected_branch", "expected_subpath"),