mirror of
https://github.com/cyclotruc/gitingest.git
synced 2026-04-28 10:09:29 +00:00
Merge main into fix/windows-support
This commit is contained in:
parent
56ce0ed19e
commit
9dfb279ae3
28 changed files with 305 additions and 2319 deletions
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
|
|
@ -10,16 +10,6 @@ updates:
|
|||
dependency-type: "development"
|
||||
update-types: ["minor", "patch"]
|
||||
|
||||
# ─── Node (npm) ───────────────────────────────
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule: { interval: "weekly" }
|
||||
labels: [ "dependencies", "npm" ]
|
||||
cooldown: # wait before opening PRs
|
||||
semver-major-days: 30
|
||||
semver-minor-days: 7
|
||||
semver-patch-days: 3
|
||||
|
||||
# ─── GitHub Actions ───────────────────────────
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
|
|
|
|||
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
|
|
@ -48,27 +48,3 @@ jobs:
|
|||
- name: Run pre-commit hooks
|
||||
uses: pre-commit/action@v3.0.1
|
||||
if: ${{ matrix.python-version == '3.13' && matrix.os == 'ubuntu-latest' }}
|
||||
|
||||
frontend:
|
||||
needs: test # Builds Tailwind CSS only if tests pass
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
|
||||
- name: Install Node deps
|
||||
run: npm ci
|
||||
|
||||
- name: Build CSS
|
||||
run: npm run build:css # Creates src/static/css/site.css
|
||||
|
||||
- name: Upload artefact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: static-css
|
||||
path: src/static/css/site.css
|
||||
|
|
|
|||
88
.github/workflows/publish.yml
vendored
88
.github/workflows/publish.yml
vendored
|
|
@ -1,88 +0,0 @@
|
|||
name: Publish to PyPI
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created] # Run when you click “Publish release”
|
||||
workflow_dispatch: # ... or run it manually from the Actions tab
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# ── Build the Tailwind CSS bundle ───────────────────────────────
|
||||
jobs:
|
||||
frontend-build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
cache-dependency-path: package-lock.json
|
||||
|
||||
- name: Install deps + build Tailwind
|
||||
run: |
|
||||
npm ci
|
||||
npm run build:css
|
||||
|
||||
- name: Upload built CSS
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frontend-assets
|
||||
path: src/static/css/site.css
|
||||
if-no-files-found: error
|
||||
|
||||
# ── Build wheel/sdist (needs CSS) and upload “dist/” ────────────
|
||||
release-build:
|
||||
needs: frontend-build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Grab site.css produced above
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: frontend-assets
|
||||
path: src/static/css/
|
||||
|
||||
- name: Set up Python 3.13
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
cache: pip
|
||||
cache-dependency-path: pyproject.toml
|
||||
|
||||
- name: Install build backend
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install build twine
|
||||
python -m build
|
||||
twine check dist/*
|
||||
- name: Upload dist artefact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
|
||||
# ── Publish to PyPI (only if “dist/” succeeded) ─────────────────
|
||||
pypi-publish:
|
||||
needs: release-build
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi # Creates the “pypi” environment in repo-settings
|
||||
|
||||
permissions:
|
||||
id-token: write # OIDC token for trusted publishing
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
|
||||
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
verbose: true
|
||||
6
.github/workflows/scorecard.yml
vendored
6
.github/workflows/scorecard.yml
vendored
|
|
@ -6,9 +6,9 @@ on:
|
|||
push:
|
||||
branches: [ main ]
|
||||
|
||||
permissions: read-all # Default for the whole workflow
|
||||
permissions: read-all
|
||||
|
||||
concurrency: # (optional) avoid overlapping runs
|
||||
concurrency: # avoid overlapping runs
|
||||
group: scorecard-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
persist-credentials: false
|
||||
|
||||
- name: Run Scorecard
|
||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736
|
||||
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
|
|
|
|||
213
.gitignore
vendored
213
.gitignore
vendored
|
|
@ -1,185 +1,40 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
tmp/*
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
.venv*
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
.python-version
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
.vscode/settings.json
|
||||
# Operating-system
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# JavaScript tooling
|
||||
node_modules/
|
||||
# Editor / IDE settings
|
||||
.vscode/
|
||||
!.vscode/launch.json
|
||||
.idea/
|
||||
*.swp
|
||||
|
||||
# CSS
|
||||
src/static/css/site.css
|
||||
# Python virtual-envs & tooling
|
||||
.venv*/
|
||||
.python-version
|
||||
__pycache__/
|
||||
*.egg-info/
|
||||
*.egg
|
||||
.ruff_cache/
|
||||
|
||||
# Project specific
|
||||
# Test artifacts & coverage
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
coverage.xml
|
||||
htmlcov/
|
||||
|
||||
# Build, distribution & docs
|
||||
build/
|
||||
dist/
|
||||
*.wheel
|
||||
|
||||
|
||||
|
||||
# Logs & runtime output
|
||||
*.log
|
||||
logs/
|
||||
*.tmp
|
||||
tmp/
|
||||
|
||||
# Project-specific files
|
||||
history.txt
|
||||
cleanup.py
|
||||
Caddyfile
|
||||
|
||||
# ignore default output directory
|
||||
tmp/*
|
||||
|
||||
# Gitingest
|
||||
digest.txt
|
||||
|
|
|
|||
|
|
@ -151,4 +151,4 @@ repos:
|
|||
- repo: meta
|
||||
hooks:
|
||||
- id: check-hooks-apply
|
||||
- id: check-useless-excludes
|
||||
- id: check-useless-excludes
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ If you ever get stuck, reach out on [Discord](https://discord.com/invite/zerRaGK
|
|||
## How to submit a Pull Request
|
||||
|
||||
> **Prerequisites**: The project uses **Python 3.9+** and `pre-commit` for development.
|
||||
> If you plan to touch the frontend, you'll also need **Node ≥18** (for Tailwind).
|
||||
|
||||
1. **Fork** the repository.
|
||||
|
||||
|
|
@ -63,22 +62,7 @@ If you ever get stuck, reach out on [Discord](https://discord.com/invite/zerRaGK
|
|||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
9. **If you edited templates or CSS** rebuild Tailwind:
|
||||
|
||||
```bash
|
||||
# one-time install
|
||||
npm ci
|
||||
|
||||
# build once
|
||||
npm run build:css
|
||||
|
||||
# or watch & rebuild on every save
|
||||
npm run dev:css
|
||||
```
|
||||
|
||||
*Skip this step if your PR only touches Python code.*
|
||||
|
||||
10. **Run the local server** to sanity-check:
|
||||
9. **Run the local server** to sanity-check:
|
||||
|
||||
```bash
|
||||
cd src
|
||||
|
|
@ -87,30 +71,22 @@ If you ever get stuck, reach out on [Discord](https://discord.com/invite/zerRaGK
|
|||
|
||||
Open [http://localhost:8000](http://localhost:8000) to confirm everything works.
|
||||
|
||||
11. **Commit** (signed):
|
||||
10. **Commit** (signed):
|
||||
|
||||
```bash
|
||||
git commit -S -m "Your commit message"
|
||||
```
|
||||
|
||||
If *pre-commit* complains, fix the problems and repeat **6 – 10**.
|
||||
If *pre-commit* complains, fix the problems and repeat **5 – 9**.
|
||||
|
||||
12. **Push** your branch:
|
||||
11. **Push** your branch:
|
||||
|
||||
```bash
|
||||
git push origin your-branch
|
||||
```
|
||||
|
||||
13. **Open a pull request** on GitHub with a clear description.
|
||||
12. **Open a pull request** on GitHub with a clear description.
|
||||
|
||||
14. **Iterate** on any review feedback—update your branch and repeat **6 – 13** as needed.
|
||||
13. **Iterate** on any review feedback—update your branch and repeat **6 – 11** as needed.
|
||||
|
||||
*(Optional) Invite a maintainer to your branch for easier collaboration.*
|
||||
|
||||
---
|
||||
|
||||
## CSS & build artefacts
|
||||
|
||||
- **Do not commit `src/static/css/site.css`.** The CI pipeline runs `npm run build:css` during the container/image build, so the artefact is produced automatically.
|
||||
|
||||
- When developing locally you may run the build yourself (see step 9) so you can preview the styles.
|
||||
|
|
|
|||
51
Dockerfile
51
Dockerfile
|
|
@ -1,60 +1,41 @@
|
|||
# ---------- Stage 1: Build CSS with Node -------------------------
|
||||
FROM node:20-alpine AS css-builder
|
||||
WORKDIR /frontend
|
||||
|
||||
# Copy only files that affect the CSS build to leverage Docker cache
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Tailwind source --> final CSS
|
||||
# (adjust the paths if you store Tailwind input elsewhere)
|
||||
COPY tailwind.config.js ./ # Tailwind config
|
||||
COPY src/static/css/ ./src/static/css/ # Tailwind input file(s)
|
||||
RUN npm run build:css # writes ./src/static/css/site.css
|
||||
|
||||
|
||||
# ---------- Stage 2: Install Python dependencies -----------------
|
||||
FROM python:3.12-slim AS python-builder
|
||||
# Stage 1: Install Python dependencies
|
||||
FROM python:3.13-slim AS python-builder
|
||||
WORKDIR /build
|
||||
|
||||
# System build tools first (so later layers are cached if unchanged)
|
||||
# System build tools
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends gcc python3-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Python dependencies
|
||||
# Metadata and code that setuptools needs
|
||||
COPY pyproject.toml .
|
||||
COPY src/ ./src/
|
||||
|
||||
# Install runtime dependencies defined in pyproject.toml
|
||||
RUN pip install --no-cache-dir --upgrade pip \
|
||||
&& pip install --no-cache-dir --timeout 1000 "."
|
||||
&& pip install --no-cache-dir --timeout 1000 .
|
||||
|
||||
|
||||
# ---------- Stage 3: Final runtime image -------------------------
|
||||
FROM python:3.12-slim
|
||||
# Stage 2: Runtime image
|
||||
FROM python:3.13-slim
|
||||
LABEL org.opencontainers.image.source="https://github.com/cyclotruc/gitingest"
|
||||
|
||||
# Minimal runtime utilities
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends git curl \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
|
||||
WORKDIR /app
|
||||
|
||||
# Create non-root user (uid 1000 == common default on Linux host)
|
||||
RUN useradd -m -u 1000 appuser
|
||||
|
||||
# ── Copy Python site-packages & app code ───────────────────────────
|
||||
COPY --from=python-builder /usr/local/lib/python3.12/site-packages/ \
|
||||
/usr/local/lib/python3.12/site-packages/
|
||||
# Copy Python site-packages and code
|
||||
COPY --from=python-builder /usr/local/lib/python3.13/site-packages/ \
|
||||
/usr/local/lib/python3.13/site-packages/
|
||||
COPY src/ ./
|
||||
|
||||
# ── Copy the freshly-built CSS ────────────────────────────────────
|
||||
COPY --from=css-builder /frontend/src/static/css/site.css \
|
||||
src/static/css/site.css
|
||||
|
||||
# Fix permissions
|
||||
# Set permissions
|
||||
RUN chown -R appuser:appuser /app
|
||||
USER appuser
|
||||
|
||||
|
|
|
|||
1691
package-lock.json
generated
1691
package-lock.json
generated
File diff suppressed because it is too large
Load diff
15
package.json
15
package.json
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"private": true,
|
||||
"name": "gitingest-frontend",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"build:css": "tailwindcss -i ./src/static/css/tailwind.css -o ./src/static/css/site.css --minify",
|
||||
"dev:css": "tailwindcss -i ./src/static/css/tailwind.css -o ./src/static/css/site.css --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.21",
|
||||
"postcss": "^8.5.6",
|
||||
"simple-icons": "^15.4.0",
|
||||
"tailwindcss": "^3.4.17"
|
||||
}
|
||||
}
|
||||
|
|
@ -58,7 +58,6 @@ build-backend = "setuptools.build_meta"
|
|||
[tool.setuptools]
|
||||
packages = {find = {where = ["src"]}}
|
||||
include-package-data = true
|
||||
package-data = {"gitingest" = ["static/css/*.css"]}
|
||||
|
||||
# Linting configuration
|
||||
[tool.pylint.format]
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ async def ingest_async(
|
|||
branch: str | None = None,
|
||||
tag: str | None = None,
|
||||
include_gitignored: bool = False,
|
||||
include_submodules: bool = False,
|
||||
token: str | None = None,
|
||||
output: str | None = None,
|
||||
) -> tuple[str, str, str]:
|
||||
|
|
@ -52,6 +53,8 @@ async def ingest_async(
|
|||
The tag to clone and ingest. If ``None``, no tag is used.
|
||||
include_gitignored : bool
|
||||
If ``True``, include files ignored by ``.gitignore`` and ``.gitingestignore`` (default: ``False``).
|
||||
include_submodules : bool
|
||||
If ``True``, recursively include all Git submodules within the repository (default: ``False``).
|
||||
token : str | None
|
||||
GitHub personal access token (PAT) for accessing private repositories.
|
||||
Can also be set via the ``GITHUB_TOKEN`` environment variable.
|
||||
|
|
@ -86,6 +89,8 @@ async def ingest_async(
|
|||
if query.url:
|
||||
_override_branch_and_tag(query, branch=branch, tag=tag)
|
||||
|
||||
query.include_submodules = include_submodules
|
||||
|
||||
async with _clone_repo_if_remote(query, token=token):
|
||||
summary, tree, content = ingest_query(query)
|
||||
await _write_output(tree, content=content, target=output)
|
||||
|
|
@ -101,6 +106,7 @@ def ingest(
|
|||
branch: str | None = None,
|
||||
tag: str | None = None,
|
||||
include_gitignored: bool = False,
|
||||
include_submodules: bool = False,
|
||||
token: str | None = None,
|
||||
output: str | None = None,
|
||||
) -> tuple[str, str, str]:
|
||||
|
|
@ -126,6 +132,8 @@ def ingest(
|
|||
The tag to clone and ingest. If ``None``, no tag is used.
|
||||
include_gitignored : bool
|
||||
If ``True``, include files ignored by ``.gitignore`` and ``.gitingestignore`` (default: ``False``).
|
||||
include_submodules : bool
|
||||
If ``True``, recursively include all Git submodules within the repository (default: ``False``).
|
||||
token : str | None
|
||||
GitHub personal access token (PAT) for accessing private repositories.
|
||||
Can also be set via the ``GITHUB_TOKEN`` environment variable.
|
||||
|
|
@ -156,6 +164,7 @@ def ingest(
|
|||
branch=branch,
|
||||
tag=tag,
|
||||
include_gitignored=include_gitignored,
|
||||
include_submodules=include_submodules,
|
||||
token=token,
|
||||
output=output,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -4,20 +4,13 @@ from __future__ import annotations
|
|||
|
||||
import asyncio
|
||||
import base64
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from typing import Final
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from starlette.status import (
|
||||
HTTP_200_OK,
|
||||
HTTP_301_MOVED_PERMANENTLY,
|
||||
HTTP_302_FOUND,
|
||||
HTTP_401_UNAUTHORIZED,
|
||||
HTTP_403_FORBIDDEN,
|
||||
HTTP_404_NOT_FOUND,
|
||||
)
|
||||
import httpx
|
||||
from starlette.status import HTTP_200_OK, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND
|
||||
|
||||
from gitingest.utils.compat_func import removesuffix
|
||||
from gitingest.utils.exceptions import InvalidGitHubTokenError
|
||||
|
|
@ -133,45 +126,28 @@ async def check_repo_exists(url: str, token: str | None = None) -> bool:
|
|||
If the host returns an unrecognised status code.
|
||||
|
||||
"""
|
||||
# TODO: use `requests` instead of `curl`
|
||||
cmd: list[str] = [
|
||||
"curl",
|
||||
"--silent", # Suppress output
|
||||
"--location", # Follow redirects
|
||||
"--write-out",
|
||||
"%{http_code}", # Write the HTTP status code to stdout
|
||||
"-o",
|
||||
os.devnull,
|
||||
]
|
||||
headers = {}
|
||||
|
||||
if token and is_github_host(url):
|
||||
host, owner, repo = _parse_github_url(url)
|
||||
# Public GitHub vs. GitHub Enterprise
|
||||
base_api = "https://api.github.com" if host == "github.com" else f"https://{host}/api/v3"
|
||||
url = f"{base_api}/repos/{owner}/{repo}"
|
||||
cmd += ["--header", f"Authorization: Bearer {token}"]
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
|
||||
cmd.append(url)
|
||||
async with httpx.AsyncClient(follow_redirects=True) as client:
|
||||
try:
|
||||
response = await client.head(url, headers=headers)
|
||||
except httpx.RequestError:
|
||||
return False
|
||||
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, _ = await proc.communicate()
|
||||
status_code = response.status_code
|
||||
|
||||
if proc.returncode != 0:
|
||||
return False
|
||||
|
||||
status = int(stdout.decode().strip())
|
||||
if status in {HTTP_200_OK, HTTP_301_MOVED_PERMANENTLY}:
|
||||
if status_code == HTTP_200_OK:
|
||||
return True
|
||||
# TODO: handle 302 redirects
|
||||
if status in {HTTP_404_NOT_FOUND, HTTP_302_FOUND}:
|
||||
if status_code in {HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND}:
|
||||
return False
|
||||
if status in {HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN}:
|
||||
return False
|
||||
msg = f"Unexpected HTTP status {status} for {url}"
|
||||
msg = f"Unexpected HTTP status {status_code} for {url}"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -10,10 +10,7 @@ from gitingest.ingestion import ingest_query
|
|||
from gitingest.query_parser import IngestionQuery, parse_query
|
||||
from gitingest.utils.git_utils import validate_github_token
|
||||
from server.models import IngestErrorResponse, IngestResponse, IngestSuccessResponse
|
||||
from server.server_config import (
|
||||
DEFAULT_MAX_FILE_SIZE_KB,
|
||||
MAX_DISPLAY_SIZE,
|
||||
)
|
||||
from server.server_config import MAX_DISPLAY_SIZE
|
||||
from server.server_utils import Colors, log_slider_to_size
|
||||
|
||||
|
||||
|
|
@ -148,10 +145,11 @@ def _print_query(url: str, max_file_size: int, pattern_type: str, pattern: str)
|
|||
The actual pattern string to include or exclude in the query.
|
||||
|
||||
"""
|
||||
default_max_file_kb = 50
|
||||
print(f"{Colors.WHITE}{url:<20}{Colors.END}", end="")
|
||||
if int(max_file_size / 1024) != DEFAULT_MAX_FILE_SIZE_KB:
|
||||
if int(max_file_size / 1024) != default_max_file_kb:
|
||||
print(
|
||||
f" | {Colors.YELLOW}Size: {int(max_file_size / 1024)}kb{Colors.END}",
|
||||
f" | {Colors.YELLOW}Size: {int(max_file_size / 1024)}kB{Colors.END}",
|
||||
end="",
|
||||
)
|
||||
if pattern_type == "include" and pattern != "":
|
||||
|
|
|
|||
|
|
@ -50,9 +50,8 @@
|
|||
{% endif %}
|
||||
{% endblock %}
|
||||
</title>
|
||||
{# Style sheets #}
|
||||
<link rel="preload" href="/static/css/site.css" as="style">
|
||||
{% block css %}<link rel="stylesheet" href="/static/css/site.css">{% endblock %}
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
{% include 'components/tailwind_components.html' %}
|
||||
</head>
|
||||
<body class="bg-[#FFFDF8] min-h-screen flex flex-col">
|
||||
{% include 'components/navbar.jinja' %}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{# Icon link #}
|
||||
{% macro icon_link(href, icon, label) -%}
|
||||
{% macro footer_icon_link(href, icon, label) -%}
|
||||
<a href="{{ href }}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
{% from 'components/_macros.jinja' import icon_link %}
|
||||
{% from 'components/_macros.jinja' import footer_icon_link %}
|
||||
<footer class="w-full border-t-[3px] border-gray-900 mt-auto">
|
||||
<div class="max-w-4xl mx-auto px-4 py-4">
|
||||
<div class="grid grid-cols-2 items-center text-gray-900 text-sm">
|
||||
{# Left column — Chrome + PyPI #}
|
||||
<div class="flex items-center space-x-4">
|
||||
{{ icon_link('https://chromewebstore.google.com/detail/adfjahbijlkjfoicpjkhjicpjpjfaood',
|
||||
{{ footer_icon_link('https://chromewebstore.google.com/detail/adfjahbijlkjfoicpjkhjicpjpjfaood',
|
||||
'icons/chrome.svg',
|
||||
'Chrome Extension') }}
|
||||
{{ icon_link('https://pypi.org/project/gitingest',
|
||||
{{ footer_icon_link('https://pypi.org/project/gitingest',
|
||||
'icons/python.svg',
|
||||
'Python Package') }}
|
||||
</div>
|
||||
{# Right column - Discord #}
|
||||
<div class="flex justify-end">
|
||||
{{ icon_link('https://discord.gg/zerRaGK9EC',
|
||||
{{ footer_icon_link('https://discord.gg/zerRaGK9EC',
|
||||
'icons/discord.svg',
|
||||
'Discord') }}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
placeholder="https://github.com/..."
|
||||
value="{{ repo_url if repo_url else '' }}"
|
||||
required
|
||||
class="border-[3px] w-full relative z-20 border-gray-900 placeholder-gray-600 text-lg font-medium focus:outline-none py-3.5 px-6 rounded">
|
||||
class="border-[3px] w-full relative z-20 border-gray-900 placeholder-gray-600 text-lg font-medium focus:outline-none py-3.5 px-6 rounded bg-[#E8F0FE]">
|
||||
</div>
|
||||
<!-- Ingest button -->
|
||||
<div class="relative w-auto flex-shrink-0 h-full group">
|
||||
|
|
@ -40,9 +40,9 @@
|
|||
<!-- Pattern type selector -->
|
||||
<div class="relative flex items-center">
|
||||
<select id="pattern_type"
|
||||
onchange="changePattern()"
|
||||
name="pattern_type"
|
||||
class="w-21 py-2 pl-2 pr-6 appearance-none bg-[#e6e8eb] focus:outline-none border-r-[3px] border-gray-900 cursor-pointer">
|
||||
onchange="changePattern()"
|
||||
class="pattern-select">
|
||||
<option value="exclude"
|
||||
{% if pattern_type == 'exclude' or not pattern_type %}selected{% endif %}>
|
||||
Exclude
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<header class="sticky top-0 bg-[#FFFDF8] border-b-[3px] border-gray-900 z-50">
|
||||
<div class="max-w-4xl mx-auto px-4">
|
||||
<div class="flex justify-between items-center h-16">
|
||||
<!-- Logo -->
|
||||
{# Logo #}
|
||||
<div class="flex items-center gap-4">
|
||||
<h1 class="text-2xl font-bold tracking-tight">
|
||||
<a href="/" class="hover:opacity-80 transition-opacity">
|
||||
|
|
@ -9,33 +9,26 @@
|
|||
</a>
|
||||
</h1>
|
||||
</div>
|
||||
<!-- Navigation with updated styling -->
|
||||
{# Navigation with updated styling #}
|
||||
<nav class="flex items-center space-x-6">
|
||||
<a href="/llm.txt"
|
||||
class="text-gray-900 hover:-translate-y-0.5 transition-transform flex items-center">
|
||||
<a href="/llm.txt" class="link-bounce flex items-center text-gray-900">
|
||||
<span class="badge-new">NEW</span>
|
||||
/llm.txt
|
||||
</a>
|
||||
{# GitHub link #}
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="https://github.com/cyclotruc/gitingest"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-gray-900 hover:-translate-y-0.5 transition-transform flex items-center gap-1.5">
|
||||
<svg class="w-4 h-4"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd">
|
||||
</path>
|
||||
</svg>
|
||||
class="link-bounce flex items-center gap-1.5 text-gray-900">
|
||||
<img src="/static/icons/github.svg" class="w-4 h-4" alt="GitHub logo">
|
||||
GitHub
|
||||
</a>
|
||||
<div class="flex items-center text-sm text-gray-600">
|
||||
<svg class="w-4 h-4 text-[#ffc480] mr-1"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20">
|
||||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
||||
</svg>
|
||||
{# Star counter #}
|
||||
<div class="no-drag flex items-center text-sm text-gray-600">
|
||||
<img src="/static/svg/github-star.svg"
|
||||
class="w-4 h-4 mr-1"
|
||||
alt="GitHub star icon">
|
||||
<span id="github-stars">0</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.badge-new {
|
||||
@apply inline-block -rotate-6 -translate-y-1 mx-1 px-1
|
||||
bg-[#FE4A60] border border-gray-900 text-white
|
||||
text-[10px] font-bold shadow-[2px_2px_0_0_rgba(0,0,0,1)];
|
||||
}
|
||||
|
||||
.landing-page-title {
|
||||
@apply inline-block w-full relative
|
||||
text-center
|
||||
text-4xl sm:text-5xl md:text-6xl lg:text-7xl
|
||||
sm:pt-20 lg:pt-5
|
||||
font-bold tracking-tighter;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
@apply text-center
|
||||
text-gray-600 text-lg max-w-2xl mx-auto;
|
||||
}
|
||||
|
||||
.sparkle-red {
|
||||
@apply absolute flex-shrink-0 h-auto
|
||||
w-14 sm:w-20 md:w-24 p-2
|
||||
left-0 lg:ml-32
|
||||
-translate-x-2 md:translate-x-10 lg:-translate-x-full
|
||||
-translate-y-4 sm:-translate-y-8 md:-translate-y-0 lg:-translate-y-10;
|
||||
}
|
||||
|
||||
.sparkle-green {
|
||||
@apply absolute flex-shrink-0
|
||||
right-0 bottom-0
|
||||
w-10 sm:w-16 lg:w-20
|
||||
-translate-x-10 lg:-translate-x-12
|
||||
translate-y-4 sm:translate-y-10 md:translate-y-2 lg:translate-y-4;
|
||||
}
|
||||
|
||||
.no-drag {
|
||||
@apply pointer-events-none select-none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,27 +1,29 @@
|
|||
|
||||
function waitForStars() {
|
||||
return new Promise((resolve) => {
|
||||
const check = () => {
|
||||
const stars = document.getElementById("github-stars");
|
||||
if (stars && stars.textContent !== "0") resolve();
|
||||
else setTimeout(check, 10);
|
||||
const stars = document.getElementById('github-stars');
|
||||
|
||||
if (stars && stars.textContent !== '0') {resolve();}
|
||||
else {setTimeout(check, 10);}
|
||||
};
|
||||
|
||||
check();
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const urlInput = document.getElementById("input_text");
|
||||
const form = document.getElementById("ingestForm");
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const urlInput = document.getElementById('input_text');
|
||||
const form = document.getElementById('ingestForm');
|
||||
|
||||
if (urlInput && urlInput.value.trim() && form) {
|
||||
// Wait for stars to be loaded before submitting
|
||||
// Wait for stars to be loaded before submitting
|
||||
waitForStars().then(() => {
|
||||
const submitEvent = new SubmitEvent("submit", {
|
||||
const submitEvent = new SubmitEvent('submit', {
|
||||
cancelable: true,
|
||||
bubbles: true
|
||||
});
|
||||
Object.defineProperty(submitEvent, "target", {
|
||||
|
||||
Object.defineProperty(submitEvent, 'target', {
|
||||
value: form,
|
||||
enumerable: true
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,44 +1,45 @@
|
|||
// Strike-through / un-strike file lines when the pattern-type menu flips.
|
||||
function changePattern() {
|
||||
const files = document.getElementsByName("tree-line");
|
||||
const files = document.getElementsByName('tree-line');
|
||||
|
||||
files.forEach((el) => {
|
||||
if (el.textContent.includes("Directory structure:")) return;
|
||||
if (el.textContent.includes('Directory structure:')) {return;}
|
||||
[
|
||||
"line-through",
|
||||
"text-gray-500",
|
||||
"hover:text-inherit",
|
||||
"hover:no-underline",
|
||||
"hover:line-through",
|
||||
"hover:text-gray-500",
|
||||
'line-through',
|
||||
'text-gray-500',
|
||||
'hover:text-inherit',
|
||||
'hover:no-underline',
|
||||
'hover:line-through',
|
||||
'hover:text-gray-500',
|
||||
].forEach((cls) => el.classList.toggle(cls));
|
||||
});
|
||||
}
|
||||
|
||||
// Show/hide the Personal-Access-Token section when the “Private repository” checkbox is toggled.
|
||||
function toggleAccessSettings() {
|
||||
const container = document.getElementById("accessSettingsContainer");
|
||||
const examples = document.getElementById("exampleRepositories");
|
||||
const show = document.getElementById('showAccessSettings')?.checked;
|
||||
container?.classList.toggle("hidden", !show);
|
||||
examples?.classList.toggle("lg:mt-0", show);
|
||||
const container = document.getElementById('accessSettingsContainer');
|
||||
const examples = document.getElementById('exampleRepositories');
|
||||
const show = document.getElementById('showAccessSettings')?.checked;
|
||||
|
||||
container?.classList.toggle('hidden', !show);
|
||||
examples?.classList.toggle('lg:mt-0', show);
|
||||
}
|
||||
|
||||
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document
|
||||
.getElementById("pattern_type")
|
||||
?.addEventListener("change", () => changePattern());
|
||||
.getElementById('pattern_type')
|
||||
?.addEventListener('change', () => changePattern());
|
||||
|
||||
document
|
||||
.getElementById("showAccessSettings")
|
||||
?.addEventListener("change", toggleAccessSettings);
|
||||
.getElementById('showAccessSettings')
|
||||
?.addEventListener('change', toggleAccessSettings);
|
||||
|
||||
/* 3. Initial UI sync -------------------------------- */
|
||||
// Initial UI sync
|
||||
toggleAccessSettings();
|
||||
changePattern();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Make them available to existing inline attributes
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
function submitExample(repoName) {
|
||||
const input = document.getElementById("input_text");
|
||||
const input = document.getElementById('input_text');
|
||||
|
||||
if (input) {
|
||||
input.value = repoName;
|
||||
input.focus();
|
||||
|
|
|
|||
|
|
@ -1,22 +1,26 @@
|
|||
// Fetch GitHub stars
|
||||
function formatStarCount(count) {
|
||||
if (count >= 1000) return (count / 1000).toFixed(1) + 'k';
|
||||
return count.toString();
|
||||
}
|
||||
if (count >= 1000) {return `${ (count / 1000).toFixed(1) }k`;}
|
||||
|
||||
async function fetchGitHubStars() {
|
||||
return count.toString();
|
||||
}
|
||||
|
||||
async function fetchGitHubStars() {
|
||||
try {
|
||||
const res = await fetch('https://api.github.com/repos/cyclotruc/gitingest');
|
||||
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
||||
const data = await res.json();
|
||||
document.getElementById('github-stars').textContent =
|
||||
const res = await fetch('https://api.github.com/repos/cyclotruc/gitingest');
|
||||
|
||||
if (!res.ok) {throw new Error(`${res.status} ${res.statusText}`);}
|
||||
const data = await res.json();
|
||||
|
||||
document.getElementById('github-stars').textContent =
|
||||
formatStarCount(data.stargazers_count);
|
||||
} catch (err) {
|
||||
console.error('Error fetching GitHub stars:', err);
|
||||
const el = document.getElementById('github-stars').parentElement;
|
||||
if (el) el.style.display = 'none';
|
||||
}
|
||||
}
|
||||
console.error('Error fetching GitHub stars:', err);
|
||||
const el = document.getElementById('github-stars').parentElement;
|
||||
|
||||
// auto-run when script loads
|
||||
fetchGitHubStars();
|
||||
if (el) {el.style.display = 'none';}
|
||||
}
|
||||
}
|
||||
|
||||
// auto-run when script loads
|
||||
fetchGitHubStars();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
/* eslint-disable */
|
||||
!function (t, e) {
|
||||
var o, n, p, r;
|
||||
if (e.__SV) return; // already loaded
|
||||
let o, n, p, r;
|
||||
if (e.__SV) {return;} // already loaded
|
||||
|
||||
window.posthog = e;
|
||||
e._i = [];
|
||||
e.init = function (i, s, a) {
|
||||
function g(t, e) {
|
||||
var o = e.split(".");
|
||||
const o = e.split(".");
|
||||
if (o.length === 2) {
|
||||
t = t[o[0]];
|
||||
e = o[1];
|
||||
|
|
@ -20,12 +21,12 @@
|
|||
p.type = "text/javascript";
|
||||
p.crossOrigin = "anonymous";
|
||||
p.async = true;
|
||||
p.src = s.api_host.replace(".i.posthog.com", "-assets.i.posthog.com") + "/static/array.js";
|
||||
p.src = `${ s.api_host.replace(".i.posthog.com", "-assets.i.posthog.com") }/static/array.js`;
|
||||
|
||||
r = t.getElementsByTagName("script")[0];
|
||||
r.parentNode.insertBefore(p, r);
|
||||
|
||||
var u = e;
|
||||
let u = e;
|
||||
if (a !== undefined) {
|
||||
u = e[a] = [];
|
||||
} else {
|
||||
|
|
@ -34,13 +35,13 @@
|
|||
|
||||
u.people = u.people || [];
|
||||
u.toString = function (t) {
|
||||
var e = "posthog";
|
||||
if (a !== "posthog") e += "." + a;
|
||||
if (!t) e += " (stub)";
|
||||
let e = "posthog";
|
||||
if (a !== "posthog") {e += `.${ a }`;}
|
||||
if (!t) {e += " (stub)";}
|
||||
return e;
|
||||
};
|
||||
u.people.toString = function () {
|
||||
return u.toString(1) + ".people (stub)";
|
||||
return `${ u.toString(1) }.people (stub)`;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -58,9 +59,9 @@
|
|||
"createPersonProfile", "opt_in_capturing", "opt_out_capturing",
|
||||
"has_opted_in_capturing", "has_opted_out_capturing", "clear_opt_in_out_capturing",
|
||||
"debug", "getPageViewId"
|
||||
];
|
||||
];
|
||||
|
||||
for (n = 0; n < o.length; n++) g(u, o[n]);
|
||||
for (n = 0; n < o.length; n++) {g(u, o[n]);}
|
||||
e._i.push([i, s, a]);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,19 +3,22 @@ function copyText(className) {
|
|||
let textToCopy;
|
||||
|
||||
if (className === 'directory-structure') {
|
||||
// For directory structure, get the hidden input value
|
||||
// For directory structure, get the hidden input value
|
||||
const hiddenInput = document.getElementById('directory-structure-content');
|
||||
if (!hiddenInput) return;
|
||||
|
||||
if (!hiddenInput) {return;}
|
||||
textToCopy = hiddenInput.value;
|
||||
} else {
|
||||
// For other elements, get the textarea value
|
||||
const textarea = document.querySelector('.' + className);
|
||||
if (!textarea) return;
|
||||
// For other elements, get the textarea value
|
||||
const textarea = document.querySelector(`.${ className }`);
|
||||
|
||||
if (!textarea) {return;}
|
||||
textToCopy = textarea.value;
|
||||
}
|
||||
|
||||
const button = document.querySelector(`button[onclick="copyText('${className}')"]`);
|
||||
if (!button) return;
|
||||
|
||||
if (!button) {return;}
|
||||
|
||||
// Copy text
|
||||
navigator.clipboard.writeText(textToCopy)
|
||||
|
|
@ -31,9 +34,10 @@ function copyText(className) {
|
|||
button.innerHTML = originalContent;
|
||||
}, 1000);
|
||||
})
|
||||
.catch(err => {
|
||||
// Show error in button
|
||||
.catch((err) => {
|
||||
console.error('Failed to copy text:', err);
|
||||
const originalContent = button.innerHTML;
|
||||
|
||||
button.innerHTML = 'Failed to copy';
|
||||
setTimeout(() => {
|
||||
button.innerHTML = originalContent;
|
||||
|
|
@ -41,17 +45,74 @@ function copyText(className) {
|
|||
});
|
||||
}
|
||||
|
||||
function getFileName(element) {
|
||||
const indentSize = 4;
|
||||
let path = '';
|
||||
let prevIndentLevel = null;
|
||||
|
||||
while (element) {
|
||||
const line = element.textContent;
|
||||
const index = line.search(/[a-zA-Z0-9_.-]/);
|
||||
const indentLevel = index / indentSize;
|
||||
|
||||
// Stop when we reach or go above the top-level directory
|
||||
if (indentLevel <= 1) {
|
||||
break;
|
||||
}
|
||||
if (prevIndentLevel === null || indentLevel === prevIndentLevel - 1) {
|
||||
const fileName = line.substring(index).trim();
|
||||
|
||||
path = fileName + path;
|
||||
prevIndentLevel = indentLevel;
|
||||
}
|
||||
element = element.previousElementSibling;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
function toggleFile(element) {
|
||||
const patternInput = document.getElementById('pattern');
|
||||
const patternFiles = patternInput.value
|
||||
? patternInput.value.split(',').map((item) => item.trim())
|
||||
: [];
|
||||
|
||||
const directoryContainer = document.getElementById('directory-structure-container');
|
||||
const treeLineElements = Array.from(directoryContainer.children).filter(
|
||||
(child) => child.tagName === 'PRE',
|
||||
);
|
||||
|
||||
// Skip header and repository name
|
||||
if (treeLineElements.slice(0, 2).includes(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.classList.toggle('line-through');
|
||||
element.classList.toggle('text-gray-500');
|
||||
|
||||
const fileName = getFileName(element);
|
||||
const idx = patternFiles.indexOf(fileName);
|
||||
|
||||
if (idx !== -1) {
|
||||
patternFiles.splice(idx, 1);
|
||||
} else {
|
||||
patternFiles.push(fileName);
|
||||
}
|
||||
|
||||
patternInput.value = patternFiles.join(', ');
|
||||
}
|
||||
|
||||
function handleSubmit(event, showLoading = false) {
|
||||
event.preventDefault();
|
||||
const form = event.target || document.getElementById('ingestForm');
|
||||
if (!form) return;
|
||||
|
||||
if (!form) {return;}
|
||||
|
||||
// Declare resultsSection before use
|
||||
const resultsSection = document.querySelector('[data-results]');
|
||||
|
||||
if (resultsSection) {
|
||||
// Show in-content loading spinner
|
||||
// Show in-content loading spinner
|
||||
resultsSection.innerHTML = `
|
||||
<div class="relative mt-10">
|
||||
<div class="w-full h-full absolute inset-0 bg-black rounded-xl translate-y-2 translate-x-2"></div>
|
||||
|
|
@ -64,12 +125,14 @@ function handleSubmit(event, showLoading = false) {
|
|||
}
|
||||
|
||||
const submitButton = form.querySelector('button[type="submit"]');
|
||||
if (!submitButton) return;
|
||||
|
||||
if (!submitButton) {return;}
|
||||
|
||||
const formData = new FormData(form);
|
||||
|
||||
// Update file size
|
||||
const slider = document.getElementById('file_size');
|
||||
|
||||
if (slider) {
|
||||
formData.delete('max_file_size');
|
||||
formData.append('max_file_size', slider.value);
|
||||
|
|
@ -78,6 +141,7 @@ function handleSubmit(event, showLoading = false) {
|
|||
// Update pattern type and pattern
|
||||
const patternType = document.getElementById('pattern_type');
|
||||
const pattern = document.getElementById('pattern');
|
||||
|
||||
if (patternType && pattern) {
|
||||
formData.delete('pattern_type');
|
||||
formData.delete('pattern');
|
||||
|
|
@ -102,19 +166,20 @@ function handleSubmit(event, showLoading = false) {
|
|||
}
|
||||
|
||||
// Submit the form to /api/ingest
|
||||
fetch('/api/ingest', {method: 'POST', body: formData})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
fetch('/api/ingest', { method: 'POST', body: formData })
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
// Hide loading overlay
|
||||
if (resultsSection) resultsSection.innerHTML = '';
|
||||
if (resultsSection) {resultsSection.innerHTML = '';}
|
||||
submitButton.disabled = false;
|
||||
submitButton.innerHTML = originalContent;
|
||||
|
||||
if (!resultsSection) return;
|
||||
if (!resultsSection) {return;}
|
||||
|
||||
// Handle error
|
||||
if (data.error) {
|
||||
resultsSection.innerHTML = `<div class='mb-6 p-4 bg-red-50 border border-red-200 rounded-lg text-red-700'>${data.error}</div>`;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -190,20 +255,38 @@ function handleSubmit(event, showLoading = false) {
|
|||
// Set plain text content for summary, tree, and content
|
||||
document.getElementById('result-summary').value = data.summary || '';
|
||||
document.getElementById('directory-structure-content').value = data.tree || '';
|
||||
document.getElementById('directory-structure-pre').textContent = data.tree || '';
|
||||
document.getElementById('result-content').value = data.content || '';
|
||||
|
||||
// Populate directory structure lines as clickable <pre> elements
|
||||
const dirPre = document.getElementById('directory-structure-pre');
|
||||
|
||||
if (dirPre && data.tree) {
|
||||
dirPre.innerHTML = '';
|
||||
data.tree.split('\n').forEach((line) => {
|
||||
const pre = document.createElement('pre');
|
||||
|
||||
pre.setAttribute('name', 'tree-line');
|
||||
pre.className = 'cursor-pointer hover:line-through hover:text-gray-500';
|
||||
pre.textContent = line;
|
||||
pre.onclick = function () { toggleFile(this); };
|
||||
dirPre.appendChild(pre);
|
||||
});
|
||||
}
|
||||
|
||||
// Scroll to results
|
||||
resultsSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
// Hide loading overlay
|
||||
if (resultsSection) resultsSection.innerHTML = '';
|
||||
if (resultsSection) {
|
||||
resultsSection.innerHTML = '';
|
||||
}
|
||||
submitButton.disabled = false;
|
||||
submitButton.innerHTML = originalContent;
|
||||
const resultsSection = document.querySelector('[data-results]');
|
||||
if (resultsSection) {
|
||||
resultsSection.innerHTML = `<div class='mb-6 p-4 bg-red-50 border border-red-200 rounded-lg text-red-700'>${error}</div>`;
|
||||
const errorContainer = document.querySelector('[data-results]');
|
||||
|
||||
if (errorContainer) {
|
||||
errorContainer.innerHTML = `<div class='mb-6 p-4 bg-red-50 border border-red-200 rounded-lg text-red-700'>${error}</div>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -226,9 +309,10 @@ function copyFullDigest() {
|
|||
setTimeout(() => {
|
||||
button.innerHTML = originalText;
|
||||
}, 2000);
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy text: ', err);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to copy text: ', err);
|
||||
});
|
||||
}
|
||||
|
||||
function downloadFullDigest() {
|
||||
|
|
@ -245,8 +329,9 @@ function downloadFullDigest() {
|
|||
// Create a download link
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
|
||||
a.href = url;
|
||||
a.download = 'codebase-digest.txt';
|
||||
a.download = 'digest.txt';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
|
|
@ -275,7 +360,8 @@ function logSliderToSize(position) {
|
|||
const maxPosition = 500;
|
||||
const maxValue = Math.log(102400); // 100 MB
|
||||
|
||||
const value = Math.exp(maxValue * Math.pow(position / maxPosition, 1.5));
|
||||
const value = Math.exp(maxValue * (position / maxPosition)**1.5);
|
||||
|
||||
return Math.round(value);
|
||||
}
|
||||
|
||||
|
|
@ -284,10 +370,11 @@ function initializeSlider() {
|
|||
const slider = document.getElementById('file_size');
|
||||
const sizeValue = document.getElementById('size_value');
|
||||
|
||||
if (!slider || !sizeValue) return;
|
||||
if (!slider || !sizeValue) {return;}
|
||||
|
||||
function updateSlider() {
|
||||
const value = logSliderToSize(slider.value);
|
||||
|
||||
sizeValue.textContent = formatSize(value);
|
||||
slider.style.backgroundSize = `${(slider.value / slider.max) * 100}% 100%`;
|
||||
}
|
||||
|
|
@ -302,24 +389,18 @@ function initializeSlider() {
|
|||
// Add helper function for formatting size
|
||||
function formatSize(sizeInKB) {
|
||||
if (sizeInKB >= 1024) {
|
||||
return Math.round(sizeInKB / 1024) + 'MB';
|
||||
return `${ Math.round(sizeInKB / 1024) }MB`;
|
||||
}
|
||||
return Math.round(sizeInKB) + 'kB';
|
||||
|
||||
return `${ Math.round(sizeInKB) }kB`;
|
||||
}
|
||||
|
||||
// Make sure these are available globally
|
||||
window.copyText = copyText;
|
||||
|
||||
window.handleSubmit = handleSubmit;
|
||||
window.initializeSlider = initializeSlider;
|
||||
window.formatSize = formatSize;
|
||||
window.downloadFullDigest = downloadFullDigest;
|
||||
|
||||
// Add this new function
|
||||
function setupGlobalEnterHandler() {
|
||||
document.addEventListener('keydown', function (event) {
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Enter' && !event.target.matches('textarea')) {
|
||||
const form = document.getElementById('ingestForm');
|
||||
|
||||
if (form) {
|
||||
handleSubmit(new Event('submit'), true);
|
||||
}
|
||||
|
|
@ -332,3 +413,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
initializeSlider();
|
||||
setupGlobalEnterHandler();
|
||||
});
|
||||
|
||||
|
||||
// Make sure these are available globally
|
||||
window.handleSubmit = handleSubmit;
|
||||
window.toggleFile = toggleFile;
|
||||
window.copyText = copyText;
|
||||
window.copyFullDigest = copyFullDigest;
|
||||
window.downloadFullDigest = downloadFullDigest;
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.jinja",
|
||||
"./src/**/*.js",
|
||||
],
|
||||
};
|
||||
|
|
@ -91,9 +91,10 @@ async def test_clone_nonexistent_repository(repo_exists_true: AsyncMock) -> None
|
|||
@pytest.mark.parametrize(
|
||||
("status_code", "expected"),
|
||||
[
|
||||
(b"200\n", 0, True), # Existing repo
|
||||
(b"404\n", 0, False), # Non-existing repo
|
||||
(b"200\n", 1, False), # Failed request
|
||||
(HTTP_200_OK, True),
|
||||
(HTTP_401_UNAUTHORIZED, False),
|
||||
(HTTP_403_FORBIDDEN, False),
|
||||
(HTTP_404_NOT_FOUND, False),
|
||||
],
|
||||
)
|
||||
async def test_check_repo_exists(status_code: int, *, expected: bool, mocker: MockerFixture) -> None:
|
||||
|
|
@ -208,25 +209,6 @@ async def test_check_repo_exists_with_redirect(mocker: MockerFixture) -> None:
|
|||
assert repo_exists is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_repo_exists_with_permanent_redirect(mocker: MockerFixture) -> None:
|
||||
"""Test ``check_repo_exists`` when a permanent redirect (301) is returned.
|
||||
|
||||
Given a URL that responds with "301 Found":
|
||||
When ``check_repo_exists`` is called,
|
||||
Then it should return ``True``, indicating the repo may exist at the new location.
|
||||
"""
|
||||
mock_exec = mocker.patch("asyncio.create_subprocess_exec", new_callable=AsyncMock)
|
||||
mock_process = AsyncMock()
|
||||
mock_process.communicate.return_value = (b"301\n", b"")
|
||||
mock_process.returncode = 0 # Simulate successful request
|
||||
mock_exec.return_value = mock_process
|
||||
|
||||
repo_exists = await check_repo_exists(DEMO_URL)
|
||||
|
||||
assert repo_exists
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_clone_with_timeout(run_command_mock: AsyncMock) -> None:
|
||||
"""Test cloning a repository when a timeout occurs.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue