Skip to content

Commit cecf963

Browse files
authored
Merge pull request #12 from whtis/ci/pr-checks
ci: check every pull request
2 parents c4d3f9a + 5a59e0e commit cecf963

5 files changed

Lines changed: 281 additions & 6 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
name: Setup Android build environment
2+
description: JDK, Gradle with caching, and the google-services.json the build needs to configure.
3+
4+
inputs:
5+
google-services-json-base64:
6+
description: >-
7+
Base64 of the real google-services.json. Optional: pull requests from forks
8+
cannot read secrets, and this file is gitignored, so a placeholder stands in
9+
when it is absent.
10+
required: false
11+
default: ''
12+
13+
runs:
14+
using: composite
15+
steps:
16+
- name: Set up JDK 17
17+
uses: actions/setup-java@v4
18+
with:
19+
java-version: '17'
20+
distribution: 'temurin'
21+
22+
- name: Setup Gradle
23+
uses: gradle/actions/setup-gradle@v4
24+
with:
25+
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
26+
27+
# The google-services plugin fails configuration outright without this file,
28+
# so every job needs one. Fork PRs get a structurally valid placeholder: it is
29+
# enough to configure and build, and nothing in CI talks to Firebase.
30+
- name: Provide google-services.json
31+
shell: bash
32+
env:
33+
GOOGLE_SERVICES_JSON_BASE64: ${{ inputs.google-services-json-base64 }}
34+
run: |
35+
if [ -n "$GOOGLE_SERVICES_JSON_BASE64" ]; then
36+
echo "$GOOGLE_SERVICES_JSON_BASE64" | base64 -d > app/google-services.json
37+
echo "Using google-services.json from secret."
38+
else
39+
cat > app/google-services.json <<'JSON'
40+
{
41+
"project_info": {
42+
"project_number": "000000000000",
43+
"project_id": "memosly-ci-placeholder",
44+
"storage_bucket": "memosly-ci-placeholder.appspot.com"
45+
},
46+
"client": [
47+
{
48+
"client_info": {
49+
"mobilesdk_app_id": "1:000000000000:android:0000000000000000000000",
50+
"android_client_info": {
51+
"package_name": "com.whtis.memosly"
52+
}
53+
},
54+
"oauth_client": [],
55+
"api_key": [
56+
{
57+
"current_key": "AIzaSyA0000000000000000000000000000000000"
58+
}
59+
],
60+
"services": {
61+
"appinvite_service": {
62+
"other_platform_oauth_client": []
63+
}
64+
}
65+
}
66+
],
67+
"configuration_version": "1"
68+
}
69+
JSON
70+
echo "No secret available (likely a fork PR) — using a placeholder google-services.json."
71+
fi

.github/workflows/ci.yml

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
push:
7+
branches: [main]
8+
workflow_dispatch:
9+
10+
# A new push to the same PR makes the previous run irrelevant.
11+
concurrency:
12+
group: ci-${{ github.workflow }}-${{ github.ref }}
13+
cancel-in-progress: true
14+
15+
permissions:
16+
contents: read
17+
18+
jobs:
19+
# Guards against a tampered gradle-wrapper.jar, which would otherwise execute
20+
# unreviewed code on every runner and developer machine.
21+
wrapper:
22+
name: Gradle wrapper
23+
runs-on: ubuntu-latest
24+
steps:
25+
- uses: actions/checkout@v5
26+
- uses: gradle/actions/wrapper-validation@v4
27+
28+
build:
29+
name: Build
30+
runs-on: ubuntu-latest
31+
steps:
32+
- uses: actions/checkout@v5
33+
- uses: ./.github/actions/setup-android
34+
with:
35+
google-services-json-base64: ${{ secrets.GOOGLE_SERVICES_JSON_BASE64 }}
36+
37+
- name: Assemble debug
38+
run: ./gradlew assembleDebug --stacktrace
39+
40+
- name: Upload APK
41+
uses: actions/upload-artifact@v4
42+
with:
43+
name: app-debug-apk
44+
path: app/build/outputs/apk/debug/app-debug.apk
45+
if-no-files-found: error
46+
retention-days: 14
47+
48+
test:
49+
name: Unit tests
50+
runs-on: ubuntu-latest
51+
steps:
52+
- uses: actions/checkout@v5
53+
- uses: ./.github/actions/setup-android
54+
with:
55+
google-services-json-base64: ${{ secrets.GOOGLE_SERVICES_JSON_BASE64 }}
56+
57+
- name: Run unit tests
58+
run: ./gradlew testDebugUnitTest --stacktrace
59+
60+
# Reports are what make a red run diagnosable, so keep them even on failure.
61+
- name: Upload test reports
62+
if: always()
63+
uses: actions/upload-artifact@v4
64+
with:
65+
name: test-reports
66+
path: '**/build/reports/tests/'
67+
if-no-files-found: ignore
68+
retention-days: 14
69+
70+
lint:
71+
name: Android lint
72+
runs-on: ubuntu-latest
73+
steps:
74+
- uses: actions/checkout@v5
75+
- uses: ./.github/actions/setup-android
76+
with:
77+
google-services-json-base64: ${{ secrets.GOOGLE_SERVICES_JSON_BASE64 }}
78+
79+
- name: Run lint
80+
run: ./gradlew lintDebug --stacktrace
81+
82+
- name: Upload lint reports
83+
if: always()
84+
uses: actions/upload-artifact@v4
85+
with:
86+
name: lint-reports
87+
path: '**/build/reports/lint-results-*.html'
88+
if-no-files-found: ignore
89+
retention-days: 14
90+
91+
release-metadata:
92+
name: Release metadata
93+
runs-on: ubuntu-latest
94+
steps:
95+
- uses: actions/checkout@v5
96+
- uses: actions/setup-python@v5
97+
with:
98+
python-version: '3.12'
99+
100+
- name: Check version metadata is in sync
101+
run: python3 scripts/check_release_metadata.py

core/ui/src/main/res/values-zh/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
<string name="sign_out">退出登录</string>
2929
<string name="all_fields_required">所有字段为必填项</string>
3030
<string name="sign_in_failed">登录失败</string>
31+
<string name="access_token_label">访问令牌</string>
32+
<string name="use_access_token">使用访问令牌</string>
33+
<string name="use_password">使用密码</string>
3134
<string name="not_authenticated">未登录</string>
3235
<string name="server_version">服务器版本</string>
3336

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
v1.2.0 — Share photos and videos, plus an important image fix.
1+
v1.2.0 — Share photos and videos, plus an image fix.
22

33
New:
4-
- Share images and videos to Memosly from any app — Memosly now shows up in the share sheet for pictures and video, not just text. Share several at once (up to 9); they upload straight into a new memo.
5-
- Files over 32 MB are refused up front with a clear message instead of failing mid-upload. This matches the Memos server's own default upload limit.
4+
- Share images and videos to Memosly from any app, up to 9 at once. They upload straight into a new memo.
5+
- Files over 32 MB are refused up front instead of failing mid-upload.
66

77
Fixed:
8-
- Images added in the editor never showed up on Memos server v0.25/v0.26. They uploaded fine but were linked to no memo, leaving the file on the server and invisible everywhere. This affected every image added through the editor and has been broken since v1.1.1. If you have images that went missing, re-add them and they will stick.
9-
- Sharing several files at once could silently drop some of them.
10-
- A share that arrived while the editor was already open could resurface later and attach itself to an unrelated memo.
8+
- Images added in the editor never appeared on Memos server v0.25/v0.26 — they uploaded but linked to no memo, leaving the file invisible. Broken since v1.1.1; re-add any that went missing.
9+
- Sharing several files at once could silently drop some.

scripts/check_release_metadata.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/env python3
2+
"""Check that the release metadata scattered across the repo agrees with itself.
3+
4+
A release touches four places, and nothing but care keeps them in sync:
5+
6+
app/build.gradle.kts versionCode / versionName
7+
fastlane/metadata/android/{locale}/changelogs/N.txt store changelog, named by versionCode
8+
version.json what the in-app update check reads
9+
CHANGELOG.md the human history
10+
11+
Getting one wrong is quiet: a missing changelogs/N.txt just means the store shows
12+
nothing, and a stale version.json prompts every user to "update" to what they have.
13+
14+
Run with no arguments from the repo root. Exits non-zero on the first failure.
15+
"""
16+
17+
from __future__ import annotations
18+
19+
import json
20+
import re
21+
import sys
22+
from pathlib import Path
23+
24+
# Both F-Droid and Play cap a changelog at 500 characters and truncate past it.
25+
CHANGELOG_LIMIT = 500
26+
27+
LOCALES = ("en-US", "zh-CN")
28+
29+
REPO = Path(__file__).resolve().parent.parent
30+
31+
32+
class Failures(list):
33+
def check(self, ok: bool, message: str) -> bool:
34+
if not ok:
35+
self.append(message)
36+
return ok
37+
38+
39+
def read_version() -> tuple[int, str]:
40+
text = (REPO / "app/build.gradle.kts").read_text()
41+
code = re.search(r"versionCode\s*=\s*(\d+)", text)
42+
name = re.search(r'versionName\s*=\s*"([^"]+)"', text)
43+
if not code or not name:
44+
sys.exit("could not parse versionCode/versionName out of app/build.gradle.kts")
45+
return int(code.group(1)), name.group(1)
46+
47+
48+
def main() -> int:
49+
version_code, version_name = read_version()
50+
print(f"versionCode={version_code} versionName={version_name}")
51+
f = Failures()
52+
53+
# Store changelogs are named by versionCode, so a bump needs a new file per locale.
54+
for locale in LOCALES:
55+
path = REPO / f"fastlane/metadata/android/{locale}/changelogs/{version_code}.txt"
56+
if not f.check(path.exists(), f"missing {path.relative_to(REPO)} for versionCode {version_code}"):
57+
continue
58+
body = path.read_text()
59+
f.check(body.strip() != "", f"{path.relative_to(REPO)} is empty")
60+
f.check(
61+
len(body) <= CHANGELOG_LIMIT,
62+
f"{path.relative_to(REPO)} is {len(body)} chars, over the {CHANGELOG_LIMIT} store limit",
63+
)
64+
65+
# version.json is fetched from main by the in-app update check — if it disagrees
66+
# with versionName, users are told to update to a version that does not exist.
67+
version_json = REPO / "version.json"
68+
try:
69+
data = json.loads(version_json.read_text())
70+
except (OSError, json.JSONDecodeError) as e:
71+
f.append(f"version.json is unreadable: {e}")
72+
else:
73+
f.check(
74+
data.get("version") == version_name,
75+
f"version.json says {data.get('version')!r}, app/build.gradle.kts says {version_name!r}",
76+
)
77+
for field in ("release_notes", "release_notes_zh"):
78+
f.check(bool(data.get(field, "").strip()), f"version.json has no {field}")
79+
80+
changelog = (REPO / "CHANGELOG.md").read_text()
81+
f.check(
82+
f"## [{version_name}]" in changelog,
83+
f"CHANGELOG.md has no '## [{version_name}]' section",
84+
)
85+
f.check(
86+
re.search(rf"^\[{re.escape(version_name)}\]:\s*http", changelog, re.M) is not None,
87+
f"CHANGELOG.md has no '[{version_name}]: <url>' link reference at the bottom",
88+
)
89+
90+
if f:
91+
print("\nrelease metadata is out of sync:", file=sys.stderr)
92+
for problem in f:
93+
print(f" - {problem}", file=sys.stderr)
94+
return 1
95+
96+
print("release metadata is consistent")
97+
return 0
98+
99+
100+
if __name__ == "__main__":
101+
sys.exit(main())

0 commit comments

Comments
 (0)