safeuploads 1.0.0__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- safeuploads-1.1.0/.forgejo/ISSUE_TEMPLATE/bug_report.md +35 -0
- safeuploads-1.1.0/.forgejo/ISSUE_TEMPLATE/config.yml +5 -0
- safeuploads-1.1.0/.forgejo/ISSUE_TEMPLATE/feature_request.md +27 -0
- safeuploads-1.1.0/.forgejo/PULL_REQUEST_TEMPLATE.md +33 -0
- safeuploads-1.1.0/.forgejo/scripts/check_conventional_commits.py +165 -0
- safeuploads-1.1.0/.forgejo/workflows/conventional-commits.yml +40 -0
- safeuploads-1.1.0/.forgejo/workflows/docs.yml +59 -0
- {safeuploads-1.0.0/.github → safeuploads-1.1.0/.forgejo}/workflows/fuzz.yml +6 -16
- safeuploads-1.0.0/.github/workflows/lint&test.yml → safeuploads-1.1.0/.forgejo/workflows/lint-and-test.yml +19 -30
- {safeuploads-1.0.0/.github → safeuploads-1.1.0/.forgejo}/workflows/publish.yml +11 -18
- safeuploads-1.1.0/.forgejo/workflows/renovate.yml +32 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/copilot-instructions.md +21 -0
- safeuploads-1.1.0/CHANGELOG.md +147 -0
- safeuploads-1.1.0/CONTRIBUTING.md +82 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/PKG-INFO +24 -14
- {safeuploads-1.0.0 → safeuploads-1.1.0}/README.md +23 -13
- {safeuploads-1.0.0 → safeuploads-1.1.0}/docs/index.md +23 -14
- {safeuploads-1.0.0 → safeuploads-1.1.0}/docs/rate-limiting.md +1 -1
- {safeuploads-1.0.0 → safeuploads-1.1.0}/docs/security/architecture.md +11 -11
- {safeuploads-1.0.0 → safeuploads-1.1.0}/docs/security/threat-model.md +21 -2
- {safeuploads-1.0.0 → safeuploads-1.1.0}/examples/README.md +13 -5
- {safeuploads-1.0.0 → safeuploads-1.1.0}/examples/fastapi_example.py +125 -3
- {safeuploads-1.0.0 → safeuploads-1.1.0}/mkdocs.yml +1 -1
- {safeuploads-1.0.0 → safeuploads-1.1.0}/pyproject.toml +51 -1
- safeuploads-1.1.0/renovate.json +35 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/__init__.py +6 -3
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/audit.py +4 -3
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/config.py +467 -310
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/enums.py +28 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/exceptions.py +29 -13
- safeuploads-1.1.0/safeuploads/file_validator.py +1212 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/inspectors/__init__.py +4 -2
- safeuploads-1.1.0/safeuploads/inspectors/base.py +34 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/inspectors/content_inspector.py +40 -63
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/inspectors/gzip_inspector.py +16 -20
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/inspectors/zip_inspector.py +44 -69
- safeuploads-1.1.0/safeuploads/py.typed +0 -0
- safeuploads-1.1.0/safeuploads/utils.py +311 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/validators/base.py +6 -1
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/validators/compression_validator.py +67 -28
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/validators/extension_validator.py +3 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/validators/unicode_validator.py +5 -1
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/validators/windows_validator.py +3 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/validators/xml_validator.py +10 -3
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/inspectors/test_zip_inspector.py +36 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_config.py +45 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_config_validation.py +63 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_enums.py +20 -0
- safeuploads-1.1.0/tests/test_fastapi_integration.py +164 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_file_validator.py +241 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_utils.py +37 -4
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/validators/test_compression_validator.py +89 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/validators/test_unicode_validator.py +5 -1
- safeuploads-1.1.0/uv.lock +1110 -0
- safeuploads-1.0.0/.github/CODEOWNERS +0 -15
- safeuploads-1.0.0/.github/FUNDING.yml +0 -13
- safeuploads-1.0.0/.github/dependabot.yml +0 -20
- safeuploads-1.0.0/.github/workflows/docs.yml +0 -49
- safeuploads-1.0.0/CONTRIBUTING.md +0 -47
- safeuploads-1.0.0/safeuploads/file_validator.py +0 -1026
- safeuploads-1.0.0/safeuploads/utils.py +0 -176
- safeuploads-1.0.0/uv.lock +0 -419
- {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/agents/se-gitops-ci-specialist.agent.md +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/agents/se-security-reviewer.agent.md +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/agents/test-implementor.agent.md +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/agents/test-reviewer.agent.md +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/instructions/agents.instructions.md +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/instructions/github-actions-ci-cd-best-practices.instructions.md +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/instructions/instructions.instructions.md +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/instructions/prompt.instructions.md +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/instructions/python.instructions.md +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/instructions/security-and-owasp.instructions.md +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/.gitignore +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/CODE_OF_CONDUCT.md +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/LICENSE.md +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/SECURITY.md +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/docs/api.md +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/docs/security/integration-checklist.md +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/protocols.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/validators/__init__.py +2 -2
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/__init__.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/conftest.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/fuzz/__init__.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/fuzz/test_fuzz_config.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/fuzz/test_fuzz_filenames.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/fuzz/test_fuzz_images.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/fuzz/test_fuzz_zip.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/inspectors/__init__.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/inspectors/test_content_inspector.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/inspectors/test_gzip_inspector.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_audit.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_exceptions.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_performance.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_validate_activity_file.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_validate_gzip_file.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/validators/__init__.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/validators/test_extension_validator.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/validators/test_windows_validator.py +0 -0
- {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/validators/test_xml_validator.py +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 🐞 Bug report
|
|
3
|
+
about: Report a bug
|
|
4
|
+
title: "[BUG]: "
|
|
5
|
+
labels: [bug]
|
|
6
|
+
assignees: ''
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Checklist
|
|
10
|
+
|
|
11
|
+
- [ ] I have searched **existing issues** to make sure this bug hasn't already been reported.
|
|
12
|
+
- [ ] I have checked the **documentation** at [https://safeuploads.endurain.com](https://safeuploads.endurain.com) and didn't find a solution.
|
|
13
|
+
|
|
14
|
+
## Description
|
|
15
|
+
|
|
16
|
+
Describe the bug you encountered.
|
|
17
|
+
|
|
18
|
+
## Steps to Reproduce
|
|
19
|
+
|
|
20
|
+
1. Version
|
|
21
|
+
2. Go to '...'
|
|
22
|
+
3. Click on '...'
|
|
23
|
+
4. See error
|
|
24
|
+
|
|
25
|
+
## Expected Behavior
|
|
26
|
+
|
|
27
|
+
What did you expect to happen?
|
|
28
|
+
|
|
29
|
+
## Screenshots, logs or files
|
|
30
|
+
|
|
31
|
+
If applicable, add screenshots, logs or files to help explain your problem. If you do not wish to upload sensitive information to the issue you can send that info to [joao@endurain.com](joao@endurain.com).
|
|
32
|
+
|
|
33
|
+
## Additional Context
|
|
34
|
+
|
|
35
|
+
Add any other context about the problem here.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 💡 Feature request
|
|
3
|
+
about: Suggest an idea or enhancement for this project
|
|
4
|
+
title: "[FEATURE]: "
|
|
5
|
+
labels: [enhancement]
|
|
6
|
+
assignees: ''
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
Briefly describe the feature you'd like to see.
|
|
12
|
+
|
|
13
|
+
## Motivation
|
|
14
|
+
|
|
15
|
+
Why is this feature important? What problem does it solve?
|
|
16
|
+
|
|
17
|
+
## Proposed Solution
|
|
18
|
+
|
|
19
|
+
How would this feature work? Describe any ideas you have.
|
|
20
|
+
|
|
21
|
+
## Alternatives
|
|
22
|
+
|
|
23
|
+
Have you considered any alternatives?
|
|
24
|
+
|
|
25
|
+
## Additional Context
|
|
26
|
+
|
|
27
|
+
Add screenshots, mockups, or examples if relevant.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
## What does this PR do?
|
|
2
|
+
|
|
3
|
+
A clear description of the change.
|
|
4
|
+
|
|
5
|
+
## Related issue
|
|
6
|
+
|
|
7
|
+
Required for anything beyond a small bug fix or typo.
|
|
8
|
+
Don't forget to reference the issue so it auto-closes works, for example use `Closes #XYZ`
|
|
9
|
+
|
|
10
|
+
## Type of change
|
|
11
|
+
|
|
12
|
+
Check all that apply
|
|
13
|
+
|
|
14
|
+
- [ ] Bug fix
|
|
15
|
+
- [ ] New feature (exception during freeze; link prior discussion/acknowledgment)
|
|
16
|
+
- [ ] Documentation
|
|
17
|
+
- [ ] Refactor / code quality
|
|
18
|
+
- [ ] Translation
|
|
19
|
+
- [ ] Other (describe below)
|
|
20
|
+
|
|
21
|
+
## Checklist
|
|
22
|
+
|
|
23
|
+
- [ ] This PR is focused on a single concern
|
|
24
|
+
- [ ] An issue was opened and acknowledged before writing this code (if not a small fix)
|
|
25
|
+
- [ ] If this PR introduces a new feature during freeze, it was discussed and explicitly acknowledged first
|
|
26
|
+
- [ ] The PR is under ~300 lines changed, excluding lock files, migrations, and generated code
|
|
27
|
+
- [ ] Tests have been added or updated where applicable
|
|
28
|
+
- [ ] Documentation has been updated where applicable
|
|
29
|
+
- [ ] Commit messages follow Conventional Commits format
|
|
30
|
+
|
|
31
|
+
## Notes for the reviewer
|
|
32
|
+
|
|
33
|
+
Anything that would help review this. Tricky areas, alternative approaches considered, screenshots, etc.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Validate that messages follow the Conventional Commits 1.0.0 specification.
|
|
3
|
+
|
|
4
|
+
Reads one or more messages (either from CLI arguments or, when ``--stdin`` is
|
|
5
|
+
passed, one message per line from standard input) and exits non-zero if any of
|
|
6
|
+
them is invalid.
|
|
7
|
+
|
|
8
|
+
Spec reference: https://www.conventionalcommits.org/en/v1.0.0/
|
|
9
|
+
|
|
10
|
+
Usage::
|
|
11
|
+
|
|
12
|
+
check_conventional_commits.py "feat(api): add endpoint"
|
|
13
|
+
git log --format=%s base..head | check_conventional_commits.py --stdin
|
|
14
|
+
|
|
15
|
+
Designed to run with the Python interpreter already available on the Forgejo
|
|
16
|
+
runner image. Uses only the standard library so there is no third-party
|
|
17
|
+
supply-chain surface.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import re
|
|
24
|
+
import sys
|
|
25
|
+
from collections.abc import Iterable
|
|
26
|
+
|
|
27
|
+
# Allowed types. This whitelist is a *project policy* layered on top of the
|
|
28
|
+
# Conventional Commits 1.0.0 spec (the spec recommends but does not mandate a
|
|
29
|
+
# fixed set of types). Keep this list in sync with the project's commit
|
|
30
|
+
# conventions documented in ``.github/copilot-instructions.md``.
|
|
31
|
+
#
|
|
32
|
+
# Per the spec, types are matched case-insensitively.
|
|
33
|
+
ALLOWED_TYPES: tuple[str, ...] = (
|
|
34
|
+
"build",
|
|
35
|
+
"chore",
|
|
36
|
+
"ci",
|
|
37
|
+
"docs",
|
|
38
|
+
"feat",
|
|
39
|
+
"fix",
|
|
40
|
+
"perf",
|
|
41
|
+
"refactor",
|
|
42
|
+
"revert",
|
|
43
|
+
"style",
|
|
44
|
+
"test",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Subject pattern: ``type(scope)!: description``. Scope and ``!`` are optional.
|
|
48
|
+
# Per CC 1.0.0:
|
|
49
|
+
# - the type is a noun (matched case-insensitively against ALLOWED_TYPES);
|
|
50
|
+
# - the scope is any noun describing a section of the codebase, enclosed in
|
|
51
|
+
# parentheses, so we accept any non-empty string that is not a closing
|
|
52
|
+
# parenthesis;
|
|
53
|
+
# - the description is free-form text.
|
|
54
|
+
_HEADER_RE = re.compile(
|
|
55
|
+
r"""
|
|
56
|
+
^
|
|
57
|
+
(?P<type>[A-Za-z]+)
|
|
58
|
+
(?:\((?P<scope>[^)]+)\))?
|
|
59
|
+
(?P<breaking>!)?
|
|
60
|
+
:\x20
|
|
61
|
+
(?P<description>.+?)
|
|
62
|
+
\s*$
|
|
63
|
+
""",
|
|
64
|
+
re.VERBOSE,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _validate_message(message: str) -> list[str]:
|
|
69
|
+
"""Return a list of validation errors for ``message`` (empty if valid)."""
|
|
70
|
+
errors: list[str] = []
|
|
71
|
+
|
|
72
|
+
# Conventional Commits only constrains the first line (the header).
|
|
73
|
+
header = message.splitlines()[0] if message else ""
|
|
74
|
+
|
|
75
|
+
if not header.strip():
|
|
76
|
+
return ["message is empty"]
|
|
77
|
+
|
|
78
|
+
match = _HEADER_RE.match(header)
|
|
79
|
+
if match is None:
|
|
80
|
+
errors.append(
|
|
81
|
+
"header does not match '<type>(<scope>)!: <description>' "
|
|
82
|
+
"(see https://www.conventionalcommits.org/en/v1.0.0/)"
|
|
83
|
+
)
|
|
84
|
+
return errors
|
|
85
|
+
|
|
86
|
+
commit_type = match.group("type")
|
|
87
|
+
|
|
88
|
+
# Project policy: restrict to a known set of types. Types are matched
|
|
89
|
+
# case-insensitively per the Conventional Commits spec.
|
|
90
|
+
if commit_type.lower() not in ALLOWED_TYPES:
|
|
91
|
+
errors.append(
|
|
92
|
+
f"type '{commit_type}' is not allowed; "
|
|
93
|
+
f"use one of: {', '.join(ALLOWED_TYPES)}"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return errors
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _safe_log_value(value: str) -> str:
|
|
100
|
+
"""Return ``value`` escaped for safe CI log output."""
|
|
101
|
+
return value.encode("unicode_escape").decode("ascii")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _iter_messages(args: argparse.Namespace) -> Iterable[str]:
|
|
105
|
+
"""Yield messages from stdin or CLI arguments."""
|
|
106
|
+
if args.stdin:
|
|
107
|
+
for line in sys.stdin:
|
|
108
|
+
stripped = line.rstrip("\n")
|
|
109
|
+
if stripped:
|
|
110
|
+
yield stripped
|
|
111
|
+
else:
|
|
112
|
+
yield from args.messages
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def main(argv: list[str] | None = None) -> int:
|
|
116
|
+
"""Validate all messages and return a process exit code."""
|
|
117
|
+
parser = argparse.ArgumentParser(
|
|
118
|
+
description="Validate Conventional Commits headers.",
|
|
119
|
+
)
|
|
120
|
+
parser.add_argument(
|
|
121
|
+
"messages",
|
|
122
|
+
nargs="*",
|
|
123
|
+
help="One or more commit messages (or PR titles) to validate.",
|
|
124
|
+
)
|
|
125
|
+
parser.add_argument(
|
|
126
|
+
"--stdin",
|
|
127
|
+
action="store_true",
|
|
128
|
+
help="Read messages from standard input, one per line.",
|
|
129
|
+
)
|
|
130
|
+
args = parser.parse_args(argv)
|
|
131
|
+
|
|
132
|
+
if not args.stdin and not args.messages:
|
|
133
|
+
parser.error("provide at least one message or use --stdin")
|
|
134
|
+
|
|
135
|
+
failures = 0
|
|
136
|
+
checked = 0
|
|
137
|
+
for message in _iter_messages(args):
|
|
138
|
+
checked += 1
|
|
139
|
+
errors = _validate_message(message)
|
|
140
|
+
safe_message = _safe_log_value(message)
|
|
141
|
+
if errors:
|
|
142
|
+
failures += 1
|
|
143
|
+
print(f"INVALID: {safe_message}", file=sys.stderr)
|
|
144
|
+
for err in errors:
|
|
145
|
+
print(f" - {err}", file=sys.stderr)
|
|
146
|
+
else:
|
|
147
|
+
print(f"OK: {safe_message}")
|
|
148
|
+
|
|
149
|
+
if checked == 0:
|
|
150
|
+
print("No messages to validate.", file=sys.stderr)
|
|
151
|
+
return 1
|
|
152
|
+
|
|
153
|
+
if failures:
|
|
154
|
+
print(
|
|
155
|
+
f"\n{failures} of {checked} message(s) failed "
|
|
156
|
+
"Conventional Commits validation.",
|
|
157
|
+
file=sys.stderr,
|
|
158
|
+
)
|
|
159
|
+
return 1
|
|
160
|
+
|
|
161
|
+
return 0
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
sys.exit(main())
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Conventional Commits
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, edited, synchronize, reopened]
|
|
6
|
+
|
|
7
|
+
concurrency:
|
|
8
|
+
group: ${{ forgejo.workflow }}-${{ forgejo.event.pull_request.number }}
|
|
9
|
+
cancel-in-progress: true
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
validate:
|
|
13
|
+
runs-on: local-runner
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout code
|
|
17
|
+
uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0
|
|
20
|
+
|
|
21
|
+
- name: Validate PR title
|
|
22
|
+
# The PR title is passed through an environment variable to prevent
|
|
23
|
+
# shell injection via crafted titles (see security-and-owasp guidelines).
|
|
24
|
+
env:
|
|
25
|
+
PR_TITLE: ${{ forgejo.event.pull_request.title }}
|
|
26
|
+
run: |
|
|
27
|
+
python3 .forgejo/scripts/check_conventional_commits.py "$PR_TITLE"
|
|
28
|
+
|
|
29
|
+
- name: Validate commits in PR
|
|
30
|
+
# Validates every commit subject between the PR base and head. Use the
|
|
31
|
+
# base/head SHAs from the event payload so we don't depend on the local
|
|
32
|
+
# checkout ref layout.
|
|
33
|
+
env:
|
|
34
|
+
BASE_SHA: ${{ forgejo.event.pull_request.base.sha }}
|
|
35
|
+
HEAD_SHA: ${{ forgejo.event.pull_request.head.sha }}
|
|
36
|
+
run: |
|
|
37
|
+
set -euo pipefail
|
|
38
|
+
git fetch --no-tags origin "$BASE_SHA" "$HEAD_SHA"
|
|
39
|
+
git log --no-merges --format=%s "$BASE_SHA".."$HEAD_SHA" \
|
|
40
|
+
| python3 .forgejo/scripts/check_conventional_commits.py --stdin
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: Deploy documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
dry-run:
|
|
7
|
+
description: Build docs but do not deploy
|
|
8
|
+
required: false
|
|
9
|
+
default: false
|
|
10
|
+
type: boolean
|
|
11
|
+
push:
|
|
12
|
+
branches:
|
|
13
|
+
- main
|
|
14
|
+
- master
|
|
15
|
+
paths:
|
|
16
|
+
- 'docs/**'
|
|
17
|
+
- 'mkdocs.yml'
|
|
18
|
+
- 'safeuploads/**/*.py'
|
|
19
|
+
- '.forgejo/workflows/docs.yml'
|
|
20
|
+
|
|
21
|
+
concurrency:
|
|
22
|
+
group: ${{ forgejo.workflow }}-${{ forgejo.ref }}
|
|
23
|
+
cancel-in-progress: true
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
|
|
27
|
+
deploy:
|
|
28
|
+
|
|
29
|
+
runs-on: local-runner
|
|
30
|
+
|
|
31
|
+
steps:
|
|
32
|
+
- name: Checkout code
|
|
33
|
+
uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
34
|
+
with:
|
|
35
|
+
fetch-depth: 0
|
|
36
|
+
|
|
37
|
+
- name: Install uv
|
|
38
|
+
uses: https://data.forgejo.org/astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
39
|
+
with:
|
|
40
|
+
working-directory: .
|
|
41
|
+
|
|
42
|
+
- name: Sync documentation dependencies
|
|
43
|
+
working-directory: .
|
|
44
|
+
run: uv sync --frozen --only-group docs
|
|
45
|
+
- name: Build documentation
|
|
46
|
+
run: uv run --no-sync --project safeuploads mkdocs build --site-dir site
|
|
47
|
+
|
|
48
|
+
- name: Set dry-run mode
|
|
49
|
+
if: ${{ inputs.dry-run == true }}
|
|
50
|
+
run: echo "DOCS_DRY_RUN=true" >> "$FORGEJO_ENV"
|
|
51
|
+
|
|
52
|
+
- name: Deploy documentation to Codeberg Pages
|
|
53
|
+
if: env.DOCS_DRY_RUN != 'true'
|
|
54
|
+
uses: https://data.forgejo.org/actions/git-pages@5978530ffb9bbe196ad9fdc0c48fdaf1483de4cf # v2.1.0
|
|
55
|
+
with:
|
|
56
|
+
site: https://safeuploads.endurain.com/
|
|
57
|
+
server: codeberg.page
|
|
58
|
+
token: ${{ forge.token }}
|
|
59
|
+
source: site/
|
|
@@ -9,35 +9,25 @@ on:
|
|
|
9
9
|
# Cancel in-progress runs for the same branch to avoid wasted
|
|
10
10
|
# runner minutes.
|
|
11
11
|
concurrency:
|
|
12
|
-
group: ${{
|
|
12
|
+
group: ${{ forge.workflow }}-${{ forge.ref }}
|
|
13
13
|
cancel-in-progress: true
|
|
14
14
|
|
|
15
|
-
# Principle of least privilege: only read access by default.
|
|
16
|
-
permissions:
|
|
17
|
-
contents: read
|
|
18
|
-
|
|
19
15
|
jobs:
|
|
20
16
|
fuzz:
|
|
21
17
|
name: Fuzz
|
|
22
|
-
runs-on:
|
|
18
|
+
runs-on: local-runner
|
|
23
19
|
|
|
24
20
|
steps:
|
|
25
21
|
- name: Checkout repository
|
|
26
|
-
uses: actions/checkout@v6
|
|
22
|
+
uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
27
23
|
|
|
28
24
|
- name: Install system dependencies
|
|
29
|
-
run:
|
|
30
|
-
|
|
31
|
-
- name: Set up Python 3.13
|
|
32
|
-
uses: actions/setup-python@v6
|
|
33
|
-
with:
|
|
34
|
-
python-version: "3.13"
|
|
25
|
+
run: apt-get update && apt-get install -y libmagic1
|
|
35
26
|
|
|
36
27
|
- name: Install uv
|
|
37
|
-
uses: astral-sh/setup-uv@
|
|
28
|
+
uses: https://data.forgejo.org/astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
38
29
|
with:
|
|
39
|
-
|
|
40
|
-
cache-dependency-glob: "uv.lock"
|
|
30
|
+
working-directory: .
|
|
41
31
|
|
|
42
32
|
- name: Install dependencies
|
|
43
33
|
run: uv sync --frozen --dev
|
|
@@ -9,13 +9,9 @@ on:
|
|
|
9
9
|
# Cancel in-progress runs for the same branch/PR to avoid wasted
|
|
10
10
|
# runner minutes and race conditions on shared resources.
|
|
11
11
|
concurrency:
|
|
12
|
-
group: ${{
|
|
12
|
+
group: ${{ forge.workflow }}-${{ forge.ref }}
|
|
13
13
|
cancel-in-progress: true
|
|
14
14
|
|
|
15
|
-
# Principle of least privilege: only read access by default.
|
|
16
|
-
permissions:
|
|
17
|
-
contents: read
|
|
18
|
-
|
|
19
15
|
jobs:
|
|
20
16
|
# ----------------------------------------------------------------
|
|
21
17
|
# Lint
|
|
@@ -24,22 +20,18 @@ jobs:
|
|
|
24
20
|
# ----------------------------------------------------------------
|
|
25
21
|
lint:
|
|
26
22
|
name: Lint
|
|
27
|
-
runs-on:
|
|
23
|
+
runs-on: codeberg-tiny
|
|
28
24
|
|
|
29
25
|
steps:
|
|
30
|
-
- name: Checkout
|
|
31
|
-
uses: actions/checkout@v6
|
|
32
|
-
|
|
33
|
-
- name: Set up Python 3.13
|
|
34
|
-
uses: actions/setup-python@v6
|
|
26
|
+
- name: Checkout code
|
|
27
|
+
uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
35
28
|
with:
|
|
36
|
-
|
|
29
|
+
fetch-depth: 1
|
|
37
30
|
|
|
38
31
|
- name: Install uv
|
|
39
|
-
uses: astral-sh/setup-uv@
|
|
32
|
+
uses: https://data.forgejo.org/astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
40
33
|
with:
|
|
41
|
-
|
|
42
|
-
cache-dependency-glob: "uv.lock"
|
|
34
|
+
working-directory: .
|
|
43
35
|
|
|
44
36
|
- name: Install dependencies
|
|
45
37
|
run: uv sync --frozen --dev
|
|
@@ -50,6 +42,9 @@ jobs:
|
|
|
50
42
|
- name: Check formatting with ruff
|
|
51
43
|
run: uv run ruff format --check safeuploads tests examples
|
|
52
44
|
|
|
45
|
+
- name: Type check with mypy
|
|
46
|
+
run: uv run mypy
|
|
47
|
+
|
|
53
48
|
# ----------------------------------------------------------------
|
|
54
49
|
# Test
|
|
55
50
|
# Runs the full pytest suite with coverage. Only runs after lint
|
|
@@ -57,40 +52,34 @@ jobs:
|
|
|
57
52
|
# ----------------------------------------------------------------
|
|
58
53
|
test:
|
|
59
54
|
name: Test
|
|
60
|
-
runs-on:
|
|
55
|
+
runs-on: local-runner
|
|
61
56
|
needs: lint
|
|
62
57
|
|
|
63
58
|
steps:
|
|
64
59
|
- name: Checkout repository
|
|
65
|
-
uses: actions/checkout@v6
|
|
60
|
+
uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
66
61
|
|
|
67
62
|
- name: Install system dependencies
|
|
68
|
-
run:
|
|
69
|
-
|
|
70
|
-
- name: Set up Python 3.13
|
|
71
|
-
uses: actions/setup-python@v6
|
|
72
|
-
with:
|
|
73
|
-
python-version: "3.13"
|
|
63
|
+
run: apt-get update && apt-get install -y libmagic1
|
|
74
64
|
|
|
75
65
|
- name: Install uv
|
|
76
|
-
uses: astral-sh/setup-uv@
|
|
66
|
+
uses: https://data.forgejo.org/astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
77
67
|
with:
|
|
78
|
-
|
|
79
|
-
cache-dependency-glob: "uv.lock"
|
|
68
|
+
working-directory: .
|
|
80
69
|
|
|
81
70
|
- name: Install dependencies
|
|
82
|
-
run: uv sync --frozen --dev
|
|
71
|
+
run: uv sync --frozen --dev --extra fastapi
|
|
83
72
|
|
|
84
73
|
- name: Audit Python dependencies
|
|
85
|
-
run: uvx pip-audit --require-hashes --disable-pip -r <(uv export --frozen --no-header --no-emit-project)
|
|
74
|
+
run: uvx pip-audit --require-hashes --disable-pip -r <(uv export --frozen --no-header --no-emit-project --no-dev)
|
|
86
75
|
|
|
87
76
|
- name: Run tests with coverage
|
|
88
77
|
run: uv run pytest --cov-fail-under=90
|
|
89
78
|
|
|
90
79
|
- name: Upload coverage report
|
|
91
|
-
uses:
|
|
80
|
+
uses: https://data.forgejo.org/forgejo/upload-artifact@v4
|
|
92
81
|
if: always()
|
|
93
82
|
with:
|
|
94
83
|
name: safeuploads-coverage
|
|
95
84
|
path: .coverage
|
|
96
|
-
retention-days: 7
|
|
85
|
+
retention-days: 7
|
|
@@ -13,25 +13,22 @@ concurrency:
|
|
|
13
13
|
group: publish
|
|
14
14
|
cancel-in-progress: false
|
|
15
15
|
|
|
16
|
-
permissions:
|
|
17
|
-
contents: read
|
|
18
|
-
id-token: write # Required for trusted publishing (OIDC)
|
|
19
|
-
|
|
20
16
|
jobs:
|
|
21
17
|
publish:
|
|
22
18
|
name: Build & Publish
|
|
23
|
-
runs-on:
|
|
24
|
-
environment: pypi
|
|
19
|
+
runs-on: local-runner
|
|
25
20
|
|
|
26
21
|
steps:
|
|
27
22
|
- name: Checkout repository
|
|
28
|
-
uses: actions/checkout@v6
|
|
23
|
+
uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
29
24
|
|
|
30
25
|
- name: Verify version matches input
|
|
31
26
|
run: |
|
|
32
27
|
PKG_VERSION=$(python3 -c "
|
|
33
28
|
import tomllib, pathlib
|
|
34
|
-
data = tomllib.loads(
|
|
29
|
+
data = tomllib.loads(
|
|
30
|
+
pathlib.Path('pyproject.toml').read_text()
|
|
31
|
+
)
|
|
35
32
|
print(data['project']['version'])
|
|
36
33
|
")
|
|
37
34
|
if [ "$PKG_VERSION" != "${{ inputs.confirm }}" ]; then
|
|
@@ -41,18 +38,12 @@ jobs:
|
|
|
41
38
|
echo "Publishing version $PKG_VERSION"
|
|
42
39
|
|
|
43
40
|
- name: Install system dependencies
|
|
44
|
-
run:
|
|
45
|
-
|
|
46
|
-
- name: Set up Python 3.13
|
|
47
|
-
uses: actions/setup-python@v6
|
|
48
|
-
with:
|
|
49
|
-
python-version: "3.13"
|
|
41
|
+
run: apt-get update && apt-get install -y libmagic1
|
|
50
42
|
|
|
51
43
|
- name: Install uv
|
|
52
|
-
uses: astral-sh/setup-uv@
|
|
44
|
+
uses: https://data.forgejo.org/astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
53
45
|
with:
|
|
54
|
-
|
|
55
|
-
cache-dependency-glob: "uv.lock"
|
|
46
|
+
working-directory: .
|
|
56
47
|
|
|
57
48
|
- name: Install dependencies
|
|
58
49
|
run: uv sync --frozen --dev
|
|
@@ -64,4 +55,6 @@ jobs:
|
|
|
64
55
|
run: uv build
|
|
65
56
|
|
|
66
57
|
- name: Publish to PyPI
|
|
67
|
-
|
|
58
|
+
env:
|
|
59
|
+
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
60
|
+
run: uv publish
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Dependency updates
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
schedule:
|
|
6
|
+
- cron: '0 4 * * 0'
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: ${{ forgejo.workflow }}-${{ forgejo.ref }}
|
|
10
|
+
cancel-in-progress: false
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
|
|
14
|
+
renovate:
|
|
15
|
+
|
|
16
|
+
runs-on: local-runner
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout code
|
|
20
|
+
uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
21
|
+
with:
|
|
22
|
+
fetch-depth: 1
|
|
23
|
+
|
|
24
|
+
- name: Run Renovate
|
|
25
|
+
uses: docker://ghcr.io/renovatebot/renovate@sha256:38aac6e29e09288bc3fb3b95ba9445962508a1081013144d5b6ec5a6735707b9 # 43.168.5
|
|
26
|
+
env:
|
|
27
|
+
LOG_LEVEL: info
|
|
28
|
+
RENOVATE_CONFIG_FILE: renovate.json
|
|
29
|
+
RENOVATE_ENDPOINT: https://codeberg.org/api/v1/
|
|
30
|
+
RENOVATE_PLATFORM: gitea
|
|
31
|
+
RENOVATE_REPOSITORIES: ${{ forgejo.repository }}
|
|
32
|
+
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
|
@@ -70,3 +70,24 @@ class MyClass:
|
|
|
70
70
|
## Documentation & Examples
|
|
71
71
|
- Update docstrings and README examples when public APIs change (e.g., `FileValidator`, utility helpers).
|
|
72
72
|
- Demonstrate usage snippets with async FastAPI context where applicable.
|
|
73
|
+
|
|
74
|
+
## Commits logic
|
|
75
|
+
|
|
76
|
+
Committing should use clear messages following [Conventional Commits](https://www.conventionalcommits.org/) format:
|
|
77
|
+
|
|
78
|
+
**Format:** `<type>(<scope>)!: <description>` — `(<scope>)` and the breaking-change `!` are optional.
|
|
79
|
+
|
|
80
|
+
The following rules are enforced automatically on every PR (against the PR title and every commit subject) by `.forgejo/workflows/conventional-commits.yml`. Validation follows the [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/) spec, with a single project-policy addition (the allowed-type whitelist). Generated commit messages must comply:
|
|
81
|
+
|
|
82
|
+
- **Header format:** `<type>(<scope>)!: <description>` — the `(<scope>)` and breaking-change `!` are optional, and a space is required after the colon.
|
|
83
|
+
- **Allowed types (project policy):** `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test`. Types are matched case-insensitively per the spec.
|
|
84
|
+
- **Scope** (optional) is any non-empty string (no parentheses), per the spec.
|
|
85
|
+
- **Description** is free-form text, per the spec (no lowercase requirement, no trailing-period restriction, no length limit).
|
|
86
|
+
- **Breaking changes** are marked with `!` after the type/scope (e.g. `feat(api)!: ...`) or a `BREAKING CHANGE:` footer in the commit body.
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
- `feat: add GPX max speed parsing`
|
|
90
|
+
- `fix(garmin): handle multi-segment GPX distance correctly`
|
|
91
|
+
- `docs: update development instructions`
|
|
92
|
+
- `test(activities): add regression test for GPX segment handling`
|
|
93
|
+
- `refactor(api)!: rename Activity.distance to total_distance`
|