Lock Python dependencies and ignore frontend env files

This commit is contained in:
2569718930@qq.com
2026-06-14 23:35:22 +08:00
parent ddca698406
commit d1124ae17e
13 changed files with 366 additions and 80 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt -r requirements-dev.txt
pip install -r requirements.lock -r requirements-dev.lock
- name: Ruff
run: python -m ruff check .
+5
View File
@@ -44,6 +44,11 @@ Thumbs.db
frontend/node_modules/
frontend/.next/
frontend/.vercel/
frontend/.env
frontend/.env.local
frontend/.env.production
frontend/.env.*
!frontend/.env.example
frontend/*.tsbuildinfo
frontend/.codex-next-dev*.log
frontend/.codex-next-start*.log
+2 -2
View File
@@ -15,10 +15,10 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
gcc libhdf5-dev libnetcdf-dev && \
rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
COPY requirements.lock .
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --prefer-binary -r requirements.txt
pip install --prefer-binary -r requirements.lock
COPY . .
+12
View File
@@ -0,0 +1,12 @@
.git
.next
node_modules
.env
.env.local
.env.production
.env.*
!.env.example
*.log
*.tsbuildinfo
-52
View File
@@ -1,52 +0,0 @@
# PolyWeather 前端最小配置(本地 / Vercel)
# 只部署天气看板时,先填下面 4 项即可。
# 必填:后端 FastAPI 基础地址
# 默认供 Next.js API Route 在服务端代理后端使用。
POLYWEATHER_API_BASE_URL=http://127.0.0.1:8000
# 可选:浏览器直连后端 FastAPI 基础地址。
# 在 Vercel 免费额度下建议配置为 VPS HTTPS 域名,让 AI / METAR / scan 等
# 长耗时请求绕过 Vercel Functions / Fluid Compute。
# 例如:https://api.example.com
# 本地开发时注释掉,让 Next.js API Route 代理请求,避免 CORS 问题
# NEXT_PUBLIC_POLYWEATHER_API_BASE_URL=http://38.54.27.70:8080
# 必填:Supabase 前端公钥(鉴权开启时必须)
NEXT_PUBLIC_SUPABASE_URL=https://bttgfgupldyowkdhriqb.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=sb_publishable_1z0DR7nZ1Juf_HGTASA8WA_uxcHJnby
# 必填:生产环境站点 URL(OAuth 回调强制使用此域名)
# 设置后,所有登录回调将始终跳转到此域名,而非当前浏览器地址。
# 生产环境必须设为 https://polyweather.top
NEXT_PUBLIC_SITE_URL=https://polyweather.top
# 常用:前端鉴权开关
# true: 启用 Supabase 登录
# false: 关闭登录能力,访客模式
POLYWEATHER_AUTH_ENABLED=true
# 常用:是否强制登录
# true: middleware 强制登录后才能访问主页面
# false: 登录可选,访客可浏览
POLYWEATHER_AUTH_REQUIRED=true
# 关闭本地开发鉴权绕过
NEXT_PUBLIC_POLYWEATHER_LOCAL_FULL_ACCESS=false
# 可选:分享式看板访问令牌
# 设置后,可通过 /?access_token=<token> 打开受保护看板
POLYWEATHER_DASHBOARD_ACCESS_TOKEN=
# 可选:前端 API Route 转发到后端时附带的共享令牌
# 仅当后端启用了 entitlement / 订阅校验时需要
POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN=
# 可选:钱包支付 / Telegram 入口
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=8ce6332b57a42e606cf28224e82d1e02
NEXT_PUBLIC_WALLETCONNECT_POLYGON_RPC_URL=https://polygon-bor-rpc.publicnode.com
NEXT_PUBLIC_PAYMENT_ALLOWED_HOSTS=polyweather.top,www.polyweather.top
POLYWEATHER_OPS_ADMIN_EMAILS=yhrsc30@gmail.com
NEXT_PUBLIC_TELEGRAM_GROUP_URL=https://t.me/your_group
NEXT_PUBLIC_TELEGRAM_BOT_URL=https://t.me/polyyuanbot
NEXT_PUBLIC_TELEGRAM_LOGIN_BOT_USERNAME=polyyuanbot
-8
View File
@@ -1,8 +0,0 @@
NEXT_PUBLIC_SUPABASE_URL=https://bttgfgupldyowkdhriqb.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=sb_publishable_1z0DR7nZ1Juf_HGTASA8WA_uxcHJnby
NEXT_PUBLIC_SITE_URL=https://polyweather.top
NEXT_PUBLIC_POLYWEATHER_API_BASE_URL=https://api.polyweather.top
NEXT_PUBLIC_POLYWEATHER_LOCAL_FULL_ACCESS=false
NEXT_PUBLIC_PAYMENT_ALLOWED_HOSTS=polyweather.top,www.polyweather.top
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=8ce6332b57a42e606cf28224e82d1e02
NEXT_PUBLIC_WALLETCONNECT_POLYGON_RPC_URL=https://polygon-bor-rpc.publicnode.com
+5
View File
@@ -1 +1,6 @@
.vercel
.env
.env.local
.env.production
.env.*
!.env.example
+48
View File
@@ -0,0 +1,48 @@
# This file was autogenerated by uv via the following command:
# uv pip compile --python-version 3.11 --universal --constraint requirements.lock --output-file requirements-dev.lock requirements-dev.txt
anyio==4.13.0
# via
# -c requirements.lock
# httpx
certifi==2026.5.20
# via
# -c requirements.lock
# httpcore
# httpx
colorama==0.4.6 ; sys_platform == 'win32'
# via
# -c requirements.lock
# pytest
h11==0.16.0
# via
# -c requirements.lock
# httpcore
httpcore==1.0.9
# via
# -c requirements.lock
# httpx
httpx==0.28.1
# via
# -c requirements.lock
# -r requirements-dev.txt
idna==3.18
# via
# -c requirements.lock
# anyio
# httpx
iniconfig==2.3.0
# via pytest
packaging==26.2
# via pytest
pluggy==1.6.0
# via pytest
pygments==2.20.0
# via pytest
pytest==8.4.2
# via -r requirements-dev.txt
ruff==0.15.17
# via -r requirements-dev.txt
typing-extensions==4.15.0 ; python_full_version < '3.13'
# via
# -c requirements.lock
# anyio
+3 -3
View File
@@ -1,3 +1,3 @@
pytest
ruff
httpx
pytest>=8.4,<9
ruff>=0.12,<1
httpx>=0.28,<1
+204
View File
@@ -0,0 +1,204 @@
# This file was autogenerated by uv via the following command:
# uv pip compile --python-version 3.11 --universal --output-file requirements.lock requirements.txt
aiohappyeyeballs==2.6.2
# via aiohttp
aiohttp==3.14.1
# via
# pytelegrambotapi
# web3
aiosignal==1.4.0
# via aiohttp
annotated-doc==0.0.4
# via fastapi
annotated-types==0.7.0
# via pydantic
anyio==4.13.0
# via
# httpx
# starlette
async-timeout==5.0.1 ; python_full_version < '3.11.3'
# via redis
attrs==26.1.0
# via aiohttp
bitarray==3.8.1
# via eth-account
certifi==2026.5.20
# via
# httpcore
# httpx
# netcdf4
# requests
cftime==1.6.5
# via netcdf4
charset-normalizer==3.4.7
# via requests
ckzg==2.1.7
# via eth-account
click==8.4.1
# via uvicorn
colorama==0.4.6 ; sys_platform == 'win32'
# via
# click
# loguru
cytoolz==1.1.0 ; implementation_name == 'cpython'
# via eth-utils
eth-abi==5.2.0
# via
# eth-account
# web3
eth-account==0.13.7
# via web3
eth-hash==0.8.0
# via
# eth-utils
# web3
eth-keyfile==0.8.1
# via eth-account
eth-keys==0.7.0
# via
# eth-account
# eth-keyfile
eth-rlp==2.2.0
# via eth-account
eth-typing==6.0.0
# via
# eth-abi
# eth-keys
# eth-utils
# web3
eth-utils==6.0.0
# via
# eth-abi
# eth-account
# eth-keyfile
# eth-keys
# eth-rlp
# rlp
# web3
fastapi==0.137.0
# via -r requirements.txt
frozenlist==1.8.0
# via
# aiohttp
# aiosignal
h11==0.16.0
# via
# httpcore
# uvicorn
hexbytes==1.3.1
# via
# eth-account
# eth-rlp
# web3
httpcore==1.0.9
# via httpx
httpx==0.28.1
# via -r requirements.txt
idna==3.18
# via
# anyio
# httpx
# requests
# yarl
loguru==0.7.3
# via -r requirements.txt
multidict==6.7.1
# via
# aiohttp
# yarl
netcdf4==1.7.4
# via -r requirements.txt
numpy==2.4.6
# via
# -r requirements.txt
# cftime
# netcdf4
parsimonious==0.10.0
# via eth-abi
propcache==0.5.2
# via
# aiohttp
# yarl
pyaes==1.6.1
# via telethon
pyasn1==0.6.3
# via rsa
pycryptodome==3.23.0
# via
# eth-hash
# eth-keyfile
pydantic==2.13.4
# via
# eth-account
# eth-utils
# fastapi
# web3
pydantic-core==2.46.4
# via pydantic
pytelegrambotapi==4.34.0
# via -r requirements.txt
python-dotenv==1.2.2
# via -r requirements.txt
pyunormalize==17.0.0
# via web3
pywin32==312 ; sys_platform == 'win32'
# via web3
pyyaml==6.0.3
# via -r requirements.txt
redis==6.4.0
# via -r requirements.txt
regex==2026.5.9
# via parsimonious
requests==2.34.2
# via
# -r requirements.txt
# pytelegrambotapi
# web3
rlp==4.1.0
# via
# eth-account
# eth-rlp
rsa==4.9.1
# via telethon
starlette==1.3.1
# via fastapi
telethon==1.43.2
# via -r requirements.txt
toolz==1.1.0 ; implementation_name == 'cpython' or implementation_name == 'pypy'
# via
# cytoolz
# eth-utils
types-requests==2.33.0.20260518
# via web3
typing-extensions==4.15.0
# via
# aiohttp
# aiosignal
# anyio
# eth-typing
# fastapi
# pydantic
# pydantic-core
# starlette
# typing-inspection
# web3
typing-inspection==0.4.2
# via
# fastapi
# pydantic
urllib3==2.7.0
# via
# requests
# types-requests
uvicorn==0.49.0
# via -r requirements.txt
web3==7.16.0
# via -r requirements.txt
websockets==15.0.1
# via
# -r requirements.txt
# web3
win32-setctime==1.2.0 ; sys_platform == 'win32'
# via loguru
yarl==1.24.2
# via aiohttp
+14 -14
View File
@@ -1,14 +1,14 @@
requests
httpx
loguru
pyTelegramBotAPI
telethon
python-dotenv
PyYAML
netCDF4
numpy
web3
fastapi
uvicorn
websockets
redis>=5.0.0
requests>=2.32,<3
httpx>=0.28,<1
loguru>=0.7,<1
pyTelegramBotAPI>=4.23,<5
telethon>=1.40,<2
python-dotenv>=1.1,<2
PyYAML>=6.0,<7
netCDF4>=1.7,<2
numpy>=2.2,<3
web3>=7.13,<8
fastapi>=0.115,<1
uvicorn>=0.34,<1
websockets>=15,<16
redis>=5.0,<7
+1
View File
@@ -51,6 +51,7 @@ REPLACE_WITH_EXAMPLE: dict[str, str] = {
".env": ".env.example",
".env.local": ".env.example",
"frontend/.env.local": "frontend/.env.example",
"frontend/.env.production": "frontend/.env.example",
}
+71
View File
@@ -0,0 +1,71 @@
from __future__ import annotations
import shutil
import subprocess
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
def _meaningful_requirement_lines(path: Path) -> list[str]:
return [
line.strip()
for line in path.read_text(encoding="utf-8").splitlines()
if line.strip() and not line.lstrip().startswith("#")
]
def test_python_dependency_builds_use_lock_files() -> None:
assert (ROOT / "requirements.lock").is_file()
assert (ROOT / "requirements-dev.lock").is_file()
dockerfile = (ROOT / "Dockerfile").read_text(encoding="utf-8")
assert "COPY requirements.lock ." in dockerfile
assert "pip install --prefer-binary -r requirements.lock" in dockerfile
assert "pip install --prefer-binary -r requirements.txt" not in dockerfile
ci = (ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8")
assert "pip install -r requirements.lock -r requirements-dev.lock" in ci
assert "pip install -r requirements.txt -r requirements-dev.txt" not in ci
def test_requirement_input_files_keep_upper_bounds() -> None:
for path in (ROOT / "requirements.txt", ROOT / "requirements-dev.txt"):
for line in _meaningful_requirement_lines(path):
assert "<" in line, f"{path.name} entry lacks an upper bound: {line}"
def test_frontend_runtime_env_files_are_not_tracked_or_in_docker_context() -> None:
if shutil.which("git") is None or not (ROOT / ".git").exists():
pytest.skip("git metadata is required for tracking policy checks")
tracked = subprocess.run(
[
"git",
"ls-files",
"--",
"frontend/.env.local",
"frontend/.env.production",
],
cwd=ROOT,
check=True,
capture_output=True,
text=True,
)
assert tracked.stdout.strip() == ""
frontend_gitignore = (ROOT / "frontend" / ".gitignore").read_text(
encoding="utf-8"
)
assert ".env.local" in frontend_gitignore
assert ".env.production" in frontend_gitignore
frontend_dockerignore = (ROOT / "frontend" / ".dockerignore").read_text(
encoding="utf-8"
)
assert ".env" in frontend_dockerignore
assert ".env.*" in frontend_dockerignore
assert "!.env.example" in frontend_dockerignore