CH-J Tools Mobile透過 SSH 管理伺服器
選單
已發布的原始碼

CH-J Tools Mobile

瀏覽特定應用程式版本的目錄與檔案。

下載原始碼 ZIP
CH-J / CH-J Tools Mobile 175 個檔案
sm-mobile-20261016T185235Z-8e93ecc1b59a 新增時間: · 建置時間:
prepare_release.py 新增時間: 未記錄日期 7.3 KB · 122 行
程式碼 下載檔案
1#!/usr/bin/env python3
2"""Package a built mobile release and allowlisted source without signing secrets.
4Run tool/build.py distribution first. This script does not build or deploy.
5"""
6import datetime
7import hashlib
8import json
9from pathlib import Path
10import re
11import shutil
12import subprocess
13import tempfile
14import zipfile
16ROOT = Path(__file__).resolve().parents[1]
17WORKSPACE = ROOT.parent
18INFO = (ROOT / 'lib/core/build_info.dart').read_text()
19BUILD_ID = re.search(r"const buildId = '([^']+)'", INFO).group(1)
20VERSION = re.search(r"const appVersion = '([^']+)'", INFO).group(1)
21if not re.fullmatch(r'sm-mobile-\d{8}T\d{6}Z-[a-f0-9]{12}', BUILD_ID):
22 raise SystemExit('Invalid build ID')
23RELEASE = WORKSPACE / 'POUZE-VYVOJOVA-DATA-BEZ-ZAVISLOSTI-NA-APP/builds' / BUILD_ID
24RELEASE.mkdir(exist_ok=False)
25FILES = RELEASE / 'source/files'
26FILES.mkdir(parents=True)
28# Explicit roots; local SDK configuration and generated Xcode configuration are excluded.
29selected = [ROOT / name for name in [
30 'README.md', 'LICENSE', 'NOTICE', 'pubspec.yaml', 'pubspec.lock',
31 'analysis_options.yaml', 'l10n.yaml', '.gitignore', '.metadata',
32 'android/build.gradle.kts', 'android/settings.gradle.kts', 'android/gradle.properties',
33 'android/gradlew', 'android/gradlew.bat', 'android/app/build.gradle.kts',
34 'ios/Flutter/AppFrameworkInfo.plist', 'ios/Flutter/Debug.xcconfig', 'ios/Flutter/Release.xcconfig',
35 'ios/Runner.xcodeproj/project.pbxproj', 'ios/Runner.xcworkspace/contents.xcworkspacedata',
36]]
37for folder in ['lib', 'assets', 'docs', 'test', 'integration_test', 'tool',
38 'android/gradle/wrapper', 'android/app/src', 'ios/Runner', 'ios/RunnerTests',
39 'ios/Runner.xcodeproj/xcshareddata', 'ios/Runner.xcworkspace/xcshareddata']:
40 selected.extend((ROOT / folder).rglob('*'))
41entries = []
42for source in sorted(set(selected)):
43 if source.is_symlink():
44 raise SystemExit('Symlink in source allowlist: ' + str(source))
45 if not source.is_file():
46 continue
47 relative = source.relative_to(ROOT)
48 if source.name.startswith('.') and source.name not in ['.gitignore', '.metadata']:
49 continue
50 if any(part in ['__pycache__', '.dart_tool', 'build', 'ephemeral', '.swiftpm', 'xcuserdata'] for part in relative.parts):
51 continue
52 if source.name in ['GeneratedPluginRegistrant.java', 'GeneratedPluginRegistrant.h', 'GeneratedPluginRegistrant.m']:
53 continue
54 if source.suffix.lower() in ['.jks', '.keystore', '.mobileprovision', '.p12', '.p8', '.pem', '.key', '.pyc'] or source.name in ['key.properties', 'local.properties', 'Generated.xcconfig', 'flutter_export_environment.sh']:
55 raise SystemExit('Private or machine-specific file in source allowlist: ' + str(relative))
56 if not re.fullmatch(r'[A-Za-z0-9_@.\/-]+', relative.as_posix()):
57 raise SystemExit('Unsupported source browser path: ' + str(relative))
58 data = source.read_bytes()
59 destination = FILES / relative
60 destination.parent.mkdir(parents=True, exist_ok=True)
61 destination.write_bytes(data)
62 destination.chmod(0o755 if source.name == 'gradlew' else 0o644)
63 try:
64 text = data.decode('utf-8')
65 is_text = '\0' not in text
66 except UnicodeDecodeError:
67 text, is_text = '', False
68 entries.append(dict(path=relative.as_posix(), size=len(data), sha256=hashlib.sha256(data).hexdigest(),
69 text=is_text, lines=len(text.splitlines()) if is_text else None))
70archive = RELEASE / f'CH-J-SM-Mobile-{VERSION}-{BUILD_ID}-source.zip'
71with zipfile.ZipFile(archive, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as handle:
72 for entry in entries:
73 handle.write(FILES / entry['path'], 'app/' + entry['path'])
74built_at = datetime.datetime.strptime(BUILD_ID.split('-')[2], '%Y%m%dT%H%M%SZ').replace(tzinfo=datetime.timezone.utc).isoformat()
75manifest = dict(buildId=BUILD_ID, version=VERSION, edition='mobile', builtAt=built_at,
76 addedAt=datetime.datetime.now(datetime.timezone.utc).isoformat(),
77 license='CH-J Proprietary Software License 1.14', archive=archive.name,
78 archiveSize=archive.stat().st_size, archiveSha256=hashlib.sha256(archive.read_bytes()).hexdigest(), files=entries)
79(RELEASE / 'source/manifest.json').write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + '\n')
81artifacts = []
82def add(path, kind, platform, arch, signing, certificate=None):
83 data = path.read_bytes()
84 entry = dict(id=hashlib.sha256((BUILD_ID + ':' + kind).encode()).hexdigest()[:24],
85 type='mobile', kind=kind, active=True, version=VERSION, build_id=BUILD_ID,
86 platform=platform, arch=arch, signing=signing, channel='alpha', artifact_role='installer',
87 filename=path.name, file=f'files/mobile/{BUILD_ID}/{path.name}', size=len(data),
88 sha512=hashlib.sha512(data).hexdigest(), sha256=hashlib.sha256(data).hexdigest(),
89 published_at=manifest['addedAt'])
90 if certificate:
91 entry['certificateSha256'] = certificate
92 artifacts.append(entry)
94# Verify APK signature using the same Java runtime as the Flutter Android toolchain.
95import os
96java_home = '/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home'
97android_sdk = Path(dict(line.split('=', 1) for line in (ROOT / 'android/local.properties').read_text().splitlines() if '=' in line)['sdk.dir'])
98checker = sorted((android_sdk / 'build-tools').glob('*/apksigner'))[-1]
99signature = subprocess.check_output([str(checker), 'verify', '--verbose', '--print-certs', str(ROOT / 'build/app/outputs/flutter-apk/app-release.apk')], env={**os.environ, 'JAVA_HOME': java_home}, text=True)
100(RELEASE / 'android-signature.txt').write_text(signature)
101certificate = re.search(r'Signer #1 certificate SHA-256 digest: ([a-f0-9]+)', signature).group(1)
102for kind, original in [('apk', 'build/app/outputs/flutter-apk/app-release.apk'), ('aab', 'build/app/outputs/bundle/release/app-release.aab')]:
103 destination = RELEASE / f'CH-J-SM-Mobile-{VERSION}-{BUILD_ID}.{kind}'
104 shutil.copyfile(ROOT / original, destination)
105 add(destination, kind, 'android', 'arm64-v8a / armeabi-v7a / x86_64', 'android-release', certificate)
107# ZIP the actual device .app under Payload without inventing an Apple signature.
108ipa = RELEASE / f'CH-J-SM-Mobile-{VERSION}-{BUILD_ID}-unsigned.ipa'
109with tempfile.TemporaryDirectory() as temporary:
110 payload = Path(temporary) / 'Payload'
111 payload.mkdir()
112 subprocess.run(['ditto', str(ROOT / 'build/ios/iphoneos/Runner.app'), str(payload / 'Runner.app')], check=True)
113 subprocess.run(['ditto', '-c', '-k', '--keepParent', str(payload), str(ipa)], check=True)
114add(ipa, 'ipa', 'ios', 'arm64', 'unsigned')
115simulator = ROOT / 'build/ios/iphonesimulator/Runner.app'
116if simulator.is_dir():
117 destination = RELEASE / f'CH-J-SM-Mobile-{VERSION}-{BUILD_ID}-simulator.app.zip'
118 subprocess.run(['ditto', '-c', '-k', '--keepParent', str(simulator), str(destination)], check=True)
119 add(destination, 'simulator', 'ios-simulator', 'arm64', 'simulator-only')
120(RELEASE / 'mobile-release.json').write_text(json.dumps(dict(buildId=BUILD_ID, version=VERSION, artifacts=artifacts), indent=2) + '\n')
121(RELEASE / 'SHA512SUMS.txt').write_text(''.join(f"{entry['sha512']} {entry['filename']}\n" for entry in artifacts) + hashlib.sha512(archive.read_bytes()).hexdigest() + ' ' + archive.name + '\n')
122print(json.dumps(dict(release=str(RELEASE), buildId=BUILD_ID, files=len(entries), artifacts=len(artifacts))))
SHA-256f811f21c261a3eab38be61b3e30694a7729803bcc95b7a29d4ba35708addac1e

CH-J Proprietary Software License 1.14 · 原始碼依 CH-J Proprietary Software License 1.14 提供。公開原始碼不會改變授權條款,也不會授予額外的權利。

壓縮檔 SHA-25646e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655
檔案