Skip to content

Commit 34b9c70

Browse files
BackBack
authored andcommitted
Merge branch 'main' into merge/main-into-dl-engine
# Conflicts: # .github/workflows/release-installers.yml # Mac/scripts/app-shell.test.cjs # Mac/src/main/main.js # Mac/src/main/updateChecker.cjs # Mac/src/renderer/App.jsx # Windows/scripts/app-shell.test.cjs # Windows/src/main/main.js # Windows/src/main/updateChecker.cjs # Windows/src/renderer/App.jsx
2 parents 56a3a50 + 6265186 commit 34b9c70

19 files changed

Lines changed: 7198 additions & 2187 deletions

.github/workflows/release-installers.yml

Lines changed: 239 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ on:
99
tag:
1010
description: "Release tag to upload installers to"
1111
required: true
12-
default: "v1.0.3"
12+
default: "v1.0.8"
1313

1414
permissions:
1515
contents: write
@@ -35,18 +35,20 @@ jobs:
3535
3636
mac:
3737
name: macOS ${{ matrix.arch }} installers
38-
runs-on: macos-latest
38+
runs-on: ${{ matrix.runner }}
3939
needs: release
4040
strategy:
4141
fail-fast: false
4242
matrix:
4343
include:
4444
- arch: arm64
45+
runner: macos-14
4546
ffprobe: ffprobe-arm64
46-
file_match: "Mach-O 64-bit executable arm64"
47+
ffprobe_match: "Mach-O 64-bit executable arm64"
4748
- arch: x64
49+
runner: macos-15-intel
4850
ffprobe: ffprobe-x64
49-
file_match: "Mach-O 64-bit executable x86_64"
51+
ffprobe_match: "Mach-O 64-bit executable x86_64"
5052
env:
5153
RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }}
5254
DL_STUDIO_MAC_ARCH: ${{ matrix.arch }}
@@ -66,12 +68,17 @@ jobs:
6668
- run: npx electron-builder --config electron-builder.config.cjs --mac dmg pkg --${{ matrix.arch }} --config.directories.output=release-upload
6769
- name: Verify macOS bundled binaries
6870
run: |
69-
file "release-upload/mac-${{ matrix.arch }}/DL Studio.app/Contents/MacOS/DL Studio" | tee /tmp/app-file.txt
70-
file "release-upload/mac-${{ matrix.arch }}/DL Studio.app/Contents/Resources/bin/ffmpeg" | tee /tmp/ffmpeg-file.txt
71-
file "release-upload/mac-${{ matrix.arch }}/DL Studio.app/Contents/Resources/bin/${{ matrix.ffprobe }}" | tee /tmp/ffprobe-file.txt
72-
grep -q "${{ matrix.file_match }}" /tmp/app-file.txt
73-
grep -q "${{ matrix.file_match }}" /tmp/ffmpeg-file.txt
74-
grep -q "${{ matrix.file_match }}" /tmp/ffprobe-file.txt
71+
app_dir="$(find release-upload -type d -name "DL Studio.app" -print -quit)"
72+
if [ -z "$app_dir" ]; then
73+
find release-upload -maxdepth 3 -print
74+
echo "DL Studio.app was not found in release-upload"
75+
exit 1
76+
fi
77+
bin_dir="$app_dir/Contents/Resources/bin"
78+
file "$bin_dir/ffmpeg" | tee /tmp/ffmpeg-file.txt
79+
grep -q "${{ matrix.ffprobe_match }}" /tmp/ffmpeg-file.txt
80+
file "$bin_dir/${{ matrix.ffprobe }}" | tee /tmp/ffprobe-file.txt
81+
grep -q "${{ matrix.ffprobe_match }}" /tmp/ffprobe-file.txt
7582
- name: Upload macOS assets
7683
env:
7784
GH_TOKEN: ${{ github.token }}
@@ -83,6 +90,14 @@ jobs:
8390
"release-upload/DL-Studio-Mac-${version}-${{ matrix.arch }}.dmg.blockmap" \
8491
--clobber \
8592
--repo "${{ github.repository }}"
93+
- name: Upload macOS installer artifact
94+
uses: actions/upload-artifact@v4
95+
with:
96+
name: mac-${{ matrix.arch }}-installers
97+
path: |
98+
Mac/release-upload/DL-Studio-Mac-*-${{ matrix.arch }}.dmg
99+
Mac/release-upload/DL-Studio-Mac-*-${{ matrix.arch }}.pkg
100+
if-no-files-found: error
86101

87102
windows:
88103
name: Windows x64 installer
@@ -112,16 +127,230 @@ jobs:
112127
"release/DL-Studio-Windows-Setup-$version.exe.blockmap" `
113128
--clobber `
114129
--repo "${{ github.repository }}"
130+
- name: Upload Windows installer artifact
131+
uses: actions/upload-artifact@v4
132+
with:
133+
name: windows-installers
134+
path: Windows/release/DL-Studio-Windows-Setup-*.exe
135+
if-no-files-found: error
115136

116137
publish:
117138
name: Publish release
118139
runs-on: ubuntu-latest
140+
environment: production
119141
needs:
120142
- mac
121143
- windows
122144
env:
123145
RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }}
124146
steps:
147+
- uses: actions/download-artifact@v4
148+
with:
149+
path: release-assets
150+
- uses: actions/setup-python@v5
151+
with:
152+
python-version: "3.11"
153+
- name: Upload installers and manifests to OSS
154+
env:
155+
OSS_ACCESS_KEY_ID: ${{ secrets.OSS_ACCESS_KEY_ID }}
156+
OSS_ACCESS_KEY_SECRET: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
157+
ALIYUN_OSS_ACCESS_KEY_ID: ${{ secrets.ALIYUN_OSS_ACCESS_KEY_ID }}
158+
ALIYUN_OSS_ACCESS_KEY_SECRET: ${{ secrets.ALIYUN_OSS_ACCESS_KEY_SECRET }}
159+
OSS_ENDPOINT: ${{ vars.CLIENT_RELEASE_OSS_ENDPOINT || 'https://infmemo.oss-cn-beijing.aliyuncs.com' }}
160+
OSS_BUCKET: ${{ vars.CLIENT_RELEASE_OSS_BUCKET || 'infmemo' }}
161+
OSS_IS_CNAME: ${{ vars.CLIENT_RELEASE_OSS_IS_CNAME || 'true' }}
162+
OSS_PREFIX: ${{ vars.CLIENT_RELEASE_OSS_PREFIX || 'infmemo' }}
163+
CLIENT_UPDATE_OSS_PREFIX: ${{ vars.CLIENT_UPDATE_OSS_PREFIX || 'client-releases' }}
164+
run: |
165+
python -m pip install --upgrade pip
166+
python -m pip install oss2
167+
python - <<'PY'
168+
import hashlib
169+
import json
170+
import mimetypes
171+
import os
172+
from datetime import datetime, timezone
173+
from pathlib import Path
174+
175+
import oss2
176+
177+
release_tag = os.environ["RELEASE_TAG"].strip()
178+
if not release_tag.lower().startswith("v"):
179+
release_tag = f"v{release_tag}"
180+
version = release_tag[1:]
181+
oss_prefix = os.environ.get("OSS_PREFIX", "infmemo").strip().strip("/")
182+
release_prefix = os.environ.get("CLIENT_UPDATE_OSS_PREFIX", "client-releases").strip().strip("/")
183+
bucket_name = os.environ["OSS_BUCKET"].strip()
184+
endpoint = os.environ["OSS_ENDPOINT"].strip()
185+
is_cname = os.environ.get("OSS_IS_CNAME", "true").strip().lower() == "true"
186+
access_key_id = (os.environ.get("OSS_ACCESS_KEY_ID") or os.environ.get("ALIYUN_OSS_ACCESS_KEY_ID") or "").strip()
187+
access_key_secret = (os.environ.get("OSS_ACCESS_KEY_SECRET") or os.environ.get("ALIYUN_OSS_ACCESS_KEY_SECRET") or "").strip()
188+
if not access_key_id or not access_key_secret:
189+
raise RuntimeError("OSS_ACCESS_KEY_ID/OSS_ACCESS_KEY_SECRET or ALIYUN_OSS_ACCESS_KEY_ID/ALIYUN_OSS_ACCESS_KEY_SECRET are required")
190+
191+
root = Path("release-assets")
192+
files = sorted(path for path in root.rglob("*") if path.is_file())
193+
allowed_suffixes = {".exe", ".dmg", ".pkg"}
194+
release_root = "/".join(part for part in (oss_prefix, release_prefix, release_tag) if part)
195+
auth = oss2.Auth(access_key_id, access_key_secret)
196+
bucket = oss2.Bucket(auth, endpoint, bucket_name, is_cname=is_cname)
197+
198+
def normalize_version(value):
199+
text = str(value or "").strip()
200+
if text.lower().startswith("v"):
201+
text = text[1:]
202+
return text.split("-", 1)[0]
203+
204+
def version_parts(value):
205+
parts = []
206+
for raw in normalize_version(value).split("."):
207+
digits = ""
208+
for char in raw:
209+
if not char.isdigit():
210+
break
211+
digits += char
212+
parts.append(int(digits) if digits else 0)
213+
return parts
214+
215+
def compare_versions(left, right):
216+
left_parts = version_parts(left)
217+
right_parts = version_parts(right)
218+
length = max(len(left_parts), len(right_parts), 3)
219+
for index in range(length):
220+
left_value = left_parts[index] if index < len(left_parts) else 0
221+
right_value = right_parts[index] if index < len(right_parts) else 0
222+
if left_value > right_value:
223+
return 1
224+
if left_value < right_value:
225+
return -1
226+
return 0
227+
228+
def hash_file(path):
229+
digest = hashlib.sha256()
230+
size = 0
231+
with path.open("rb") as source:
232+
while True:
233+
chunk = source.read(1024 * 1024)
234+
if not chunk:
235+
break
236+
digest.update(chunk)
237+
size += len(chunk)
238+
return digest.hexdigest(), size
239+
240+
def content_type_for(name):
241+
lower = name.lower()
242+
if lower.endswith(".exe"):
243+
return "application/vnd.microsoft.portable-executable"
244+
if lower.endswith(".dmg"):
245+
return "application/x-apple-diskimage"
246+
if lower.endswith(".pkg"):
247+
return "application/octet-stream"
248+
return mimetypes.guess_type(name)[0] or "application/octet-stream"
249+
250+
def platform_for(name):
251+
lower = name.lower()
252+
if lower.endswith(".exe") or "windows" in lower:
253+
return "win32", "x64", "windows"
254+
if lower.endswith((".dmg", ".pkg")) or "mac" in lower:
255+
arch = "x64" if "x64" in lower or "x86_64" in lower else "arm64"
256+
return "darwin", arch, "mac"
257+
return "", "", "unknown"
258+
259+
def make_progress_callback(name, size_bytes):
260+
last_reported = {"percent": -1}
261+
262+
def progress(consumed_bytes, total_bytes):
263+
total = total_bytes or size_bytes
264+
if total <= 0:
265+
print(f"Uploading {name}: {consumed_bytes} bytes", flush=True)
266+
return
267+
percent = min(100, int(consumed_bytes * 100 / total))
268+
if percent == 100 or percent >= last_reported["percent"] + 10:
269+
last_reported["percent"] = percent
270+
print(f"Uploading {name}: {consumed_bytes}/{total} bytes ({percent}%)", flush=True)
271+
272+
return progress
273+
274+
assets = []
275+
for path in files:
276+
name = path.name
277+
suffix = path.suffix.lower()
278+
lower = name.lower()
279+
if suffix not in allowed_suffixes or lower.endswith((".blockmap", ".yml", ".yaml")):
280+
continue
281+
platform, arch, platform_dir = platform_for(name)
282+
if not platform:
283+
raise RuntimeError(f"Unsupported release asset name: {name}")
284+
sha256, size_bytes = hash_file(path)
285+
object_key = f"{release_root}/{platform_dir}/{name}"
286+
content_type = content_type_for(name)
287+
print(f"Starting OSS upload {name} -> oss://{bucket_name}/{object_key} ({size_bytes} bytes)", flush=True)
288+
bucket.put_object_from_file(
289+
object_key,
290+
str(path),
291+
headers={"Content-Type": content_type},
292+
progress_callback=make_progress_callback(name, size_bytes),
293+
)
294+
assets.append(
295+
{
296+
"platform": platform,
297+
"arch": arch,
298+
"assetName": name,
299+
"objectKey": object_key,
300+
"sha256": sha256,
301+
"sizeBytes": size_bytes,
302+
"contentType": content_type,
303+
}
304+
)
305+
print(f"Uploaded {name} -> oss://{bucket_name}/{object_key} ({platform}/{arch}, {size_bytes} bytes)", flush=True)
306+
307+
if not assets:
308+
raise RuntimeError("No installer assets were found for OSS publishing")
309+
assets.sort(key=lambda item: (item["platform"], item["arch"], item["assetName"]))
310+
311+
published_at = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
312+
manifest = {
313+
"version": release_tag,
314+
"tagName": release_tag,
315+
"releaseName": f"DL Studio {version}",
316+
"releaseUrl": f"https://github.com/{os.environ['GITHUB_REPOSITORY']}/releases/tag/{release_tag}",
317+
"publishedAt": published_at,
318+
"assets": assets,
319+
}
320+
manifest_bytes = json.dumps(manifest, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
321+
version_manifest_key = f"{release_root}/manifest.json"
322+
latest_manifest_key = "/".join(part for part in (oss_prefix, release_prefix, "latest.json") if part)
323+
bucket.put_object(version_manifest_key, manifest_bytes, headers={"Content-Type": "application/json; charset=utf-8"})
324+
325+
update_latest = True
326+
try:
327+
current_latest = json.loads(bucket.get_object(latest_manifest_key).read().decode("utf-8"))
328+
current_tag = current_latest.get("tagName") or current_latest.get("version")
329+
update_latest = compare_versions(release_tag, current_tag) >= 0
330+
except Exception as exc:
331+
if getattr(exc, "status", None) not in (404, None):
332+
print(f"Unable to read existing latest manifest, will overwrite: {exc}")
333+
334+
if update_latest:
335+
bucket.put_object(latest_manifest_key, manifest_bytes, headers={"Content-Type": "application/json; charset=utf-8"})
336+
print(f"Updated latest manifest: oss://{bucket_name}/{latest_manifest_key}")
337+
else:
338+
print(f"Kept existing latest manifest because {release_tag} is older")
339+
340+
Path("client-release-manifest.json").write_bytes(manifest_bytes)
341+
with open(os.environ["GITHUB_STEP_SUMMARY"], "a", encoding="utf-8") as summary:
342+
summary.write(f"### Client release OSS publish\n\n")
343+
summary.write(f"- Tag: `{release_tag}`\n")
344+
summary.write(f"- Version manifest: `oss://{bucket_name}/{version_manifest_key}`\n")
345+
summary.write(f"- Latest manifest updated: `{str(update_latest).lower()}`\n")
346+
for asset in assets:
347+
summary.write(f"- `{asset['platform']}/{asset['arch']}` `{asset['assetName']}` `{asset['sizeBytes']}` bytes\n")
348+
PY
349+
- name: Upload generated manifest artifact
350+
uses: actions/upload-artifact@v4
351+
with:
352+
name: client-release-manifest
353+
path: client-release-manifest.json
125354
- name: Publish GitHub release
126355
env:
127356
GH_TOKEN: ${{ github.token }}

Mac/package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Mac/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dl-studio-mac",
3-
"version": "1.0.3",
3+
"version": "1.0.8",
44
"private": true,
55
"description": "Desktop batch video downsampler for macOS.",
66
"main": "src/main/main.js",
@@ -21,7 +21,7 @@
2121
"@vitejs/plugin-react": "^4.3.4",
2222
"concurrently": "^9.1.2",
2323
"cross-env": "^7.0.3",
24-
"electron": "^32.3.3",
24+
"electron": "^34.1.1",
2525
"electron-builder": "^25.1.8",
2626
"ffmpeg-static": "^5.2.0",
2727
"ffprobe-static": "^3.1.0",

0 commit comments

Comments
 (0)