mirror of
https://github.com/rememberber/MooTool.git
synced 2026-08-30 18:02:12 +08:00
GitHub Actions CI 自动打包
This commit is contained in:
@@ -298,4 +298,67 @@ jobs:
|
||||
release-assets/**/*.exe
|
||||
release-assets/**/*.jar
|
||||
|
||||
publish-download-links:
|
||||
name: Update download_links.json on master
|
||||
if: >-
|
||||
${{
|
||||
always() && (
|
||||
(startsWith(github.ref, 'refs/tags/v') && needs.release.result == 'success') ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.release_tag != '' && needs.release-manual-assets.result == 'success')
|
||||
)
|
||||
}}
|
||||
needs:
|
||||
- package-mac-apple-silicon
|
||||
- package-windows-x64
|
||||
- package-linux-x64
|
||||
- package-mac-intel-self-hosted
|
||||
- release
|
||||
- release-manual-assets
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Resolve release tag
|
||||
id: release
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "tag=${{ inputs.release_tag }}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
echo "merge=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: master
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Download packaged artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: release-assets
|
||||
merge-multiple: true
|
||||
|
||||
- name: Generate download_links.json
|
||||
run: |
|
||||
ARGS=(
|
||||
--assets-dir release-assets
|
||||
--tag "${{ steps.release.outputs.tag }}"
|
||||
--output download_links.json
|
||||
)
|
||||
ARGS+=(--merge-existing)
|
||||
python -u scripts/generate_download_links.py "${ARGS[@]}"
|
||||
|
||||
- name: Commit and push download_links.json
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add download_links.json
|
||||
if git diff --staged --quiet; then
|
||||
echo "download_links.json is already up to date"
|
||||
exit 0
|
||||
fi
|
||||
git commit -m "chore: update download_links.json for ${{ steps.release.outputs.tag }}"
|
||||
git push
|
||||
|
||||
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"windows": "https://mootool-1258719368.cos.ap-beijing.myqcloud.com/MooTool-1.7.0-windows.exe",
|
||||
"mac": "https://mootool-1258719368.cos.ap-beijing.myqcloud.com/MooTool_1.7.0.dmg",
|
||||
"macSilicon": "https://mootool-1258719368.cos.ap-beijing.myqcloud.com/MooTool_1.7.0-AppleSilicon.dmg",
|
||||
"linux": "https://mootool-1258719368.cos.ap-beijing.myqcloud.com/MooTool_1.7.0.deb"
|
||||
}
|
||||
"windows": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool-1.7.0-windows.exe",
|
||||
"mac": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool_1.7.0.dmg",
|
||||
"macSilicon": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool_1.7.0-AppleSilicon.dmg",
|
||||
"linux": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool_1.7.0.deb"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.luoboduner.moo.tool</groupId>
|
||||
<artifactId>MooTool</artifactId>
|
||||
<version>1.7.0</version>
|
||||
<version>1.7.1</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>MooTool</name>
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate download_links.json pointing at GitHub Release assets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
DEFAULT_REPO = "rememberber/MooTool"
|
||||
|
||||
PLATFORM_RULES: dict[str, tuple[str, ...]] = {
|
||||
"windows": ("windows-x64",),
|
||||
"macSilicon": ("mac-apple-silicon",),
|
||||
"mac": ("mac-intel",),
|
||||
"linux": ("linux-x64",),
|
||||
}
|
||||
|
||||
INSTALLER_SUFFIXES: dict[str, tuple[str, ...]] = {
|
||||
"windows": (".exe", ".msi", ".zip"),
|
||||
"macSilicon": (".dmg", ".pkg", ".zip"),
|
||||
"mac": (".dmg", ".pkg", ".zip"),
|
||||
"linux": (".deb", ".rpm", ".tar.gz"),
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AssetMatch:
|
||||
platform_key: str
|
||||
file_name: str
|
||||
|
||||
|
||||
def normalize_tag(tag: str) -> str:
|
||||
tag = tag.strip()
|
||||
if not tag:
|
||||
raise ValueError("Release tag must not be empty")
|
||||
return tag if tag.startswith("v") else f"v{tag}"
|
||||
|
||||
|
||||
def version_from_tag(tag: str) -> str:
|
||||
normalized = normalize_tag(tag)
|
||||
return normalized[1:] if normalized.startswith("v") else normalized
|
||||
|
||||
|
||||
def release_asset_url(repo: str, tag: str, file_name: str) -> str:
|
||||
return f"https://github.com/{repo}/releases/download/{normalize_tag(tag)}/{file_name}"
|
||||
|
||||
|
||||
def list_assets(assets_dir: Path) -> list[Path]:
|
||||
if not assets_dir.exists():
|
||||
return []
|
||||
return sorted(path for path in assets_dir.rglob("*") if path.is_file())
|
||||
|
||||
|
||||
def find_installer(assets: Iterable[Path], version: str, target_label: str, suffixes: tuple[str, ...]) -> Path | None:
|
||||
prefix = f"MooTool-{version}-{target_label}"
|
||||
for suffix in suffixes:
|
||||
exact = [asset for asset in assets if asset.name == f"{prefix}{suffix}"]
|
||||
if exact:
|
||||
return exact[0]
|
||||
|
||||
prefixed = [asset for asset in assets if asset.name.startswith(f"{prefix}-") and asset.name.endswith(suffix)]
|
||||
if prefixed:
|
||||
return prefixed[0]
|
||||
return None
|
||||
|
||||
|
||||
def match_assets(assets_dir: Path, version: str) -> list[AssetMatch]:
|
||||
assets = list_assets(assets_dir)
|
||||
matches: list[AssetMatch] = []
|
||||
|
||||
for platform_key, target_labels in PLATFORM_RULES.items():
|
||||
suffixes = INSTALLER_SUFFIXES[platform_key]
|
||||
for target_label in target_labels:
|
||||
installer = find_installer(assets, version, target_label, suffixes)
|
||||
if installer is not None:
|
||||
matches.append(AssetMatch(platform_key=platform_key, file_name=installer.name))
|
||||
break
|
||||
|
||||
return matches
|
||||
|
||||
|
||||
def build_links(
|
||||
assets_dir: Path,
|
||||
tag: str,
|
||||
repo: str = DEFAULT_REPO,
|
||||
existing_links: dict[str, str] | None = None,
|
||||
) -> dict[str, str]:
|
||||
version = version_from_tag(tag)
|
||||
links = dict(existing_links or {})
|
||||
for match in match_assets(assets_dir, version):
|
||||
links[match.platform_key] = release_asset_url(repo, tag, match.file_name)
|
||||
return links
|
||||
|
||||
|
||||
def write_links(output_path: Path, links: dict[str, str]) -> None:
|
||||
ordered_keys = [key for key in PLATFORM_RULES if key in links]
|
||||
extra_keys = sorted(key for key in links if key not in PLATFORM_RULES)
|
||||
ordered_links = {key: links[key] for key in ordered_keys + extra_keys}
|
||||
output_path.write_text(json.dumps(ordered_links, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def load_existing_links(path: Path | None) -> dict[str, str]:
|
||||
if path is None or not path.exists():
|
||||
return {}
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError(f"Expected object in {path}")
|
||||
return {str(key): str(value) for key, value in data.items()}
|
||||
|
||||
|
||||
def main(argv: Iterable[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--assets-dir", required=True, type=Path)
|
||||
parser.add_argument("--tag", required=True)
|
||||
parser.add_argument("--repo", default=DEFAULT_REPO)
|
||||
parser.add_argument("--output", required=True, type=Path)
|
||||
parser.add_argument("--merge-existing", action="store_true")
|
||||
args = parser.parse_args(list(argv) if argv is not None else None)
|
||||
|
||||
existing_links = load_existing_links(args.output) if args.merge_existing else {}
|
||||
links = build_links(args.assets_dir.resolve(), args.tag, args.repo, existing_links)
|
||||
if not links:
|
||||
raise SystemExit("No release assets matched the expected CI naming convention")
|
||||
|
||||
write_links(args.output.resolve(), links)
|
||||
for key, url in links.items():
|
||||
print(f"{key}: {url}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,67 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.generate_download_links import build_links, find_installer, release_asset_url, version_from_tag
|
||||
|
||||
|
||||
class GenerateDownloadLinksTests(unittest.TestCase):
|
||||
def test_version_from_tag(self) -> None:
|
||||
self.assertEqual(version_from_tag("v1.7.0"), "1.7.0")
|
||||
self.assertEqual(version_from_tag("1.7.0"), "1.7.0")
|
||||
|
||||
def test_release_asset_url(self) -> None:
|
||||
actual = release_asset_url("rememberber/MooTool", "v1.7.0", "MooTool-1.7.0-windows-x64.exe")
|
||||
self.assertEqual(
|
||||
actual,
|
||||
"https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool-1.7.0-windows-x64.exe",
|
||||
)
|
||||
|
||||
def test_find_installer_prefers_exact_name(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
root = Path(tmp_dir)
|
||||
(root / "MooTool-1.7.0-windows-x64.exe").write_text("exe", encoding="utf-8")
|
||||
(root / "MooTool-1.7.0-windows-x64.zip").write_text("zip", encoding="utf-8")
|
||||
actual = find_installer([root / "MooTool-1.7.0-windows-x64.exe", root / "MooTool-1.7.0-windows-x64.zip"], "1.7.0", "windows-x64", (".exe", ".zip"))
|
||||
self.assertEqual(actual.name, "MooTool-1.7.0-windows-x64.exe")
|
||||
|
||||
def test_build_links_for_tag_release_assets(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
root = Path(tmp_dir)
|
||||
(root / "MooTool-1.7.0-mac-apple-silicon.dmg").write_text("dmg", encoding="utf-8")
|
||||
(root / "MooTool-1.7.0-windows-x64.exe").write_text("exe", encoding="utf-8")
|
||||
(root / "MooTool-1.7.0-linux-x64.deb").write_text("deb", encoding="utf-8")
|
||||
|
||||
links = build_links(root, "v1.7.0")
|
||||
self.assertEqual(
|
||||
links,
|
||||
{
|
||||
"windows": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool-1.7.0-windows-x64.exe",
|
||||
"macSilicon": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool-1.7.0-mac-apple-silicon.dmg",
|
||||
"linux": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool-1.7.0-linux-x64.deb",
|
||||
},
|
||||
)
|
||||
|
||||
def test_build_links_merge_keeps_existing_mac_when_only_intel_rebuilt(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
root = Path(tmp_dir)
|
||||
(root / "MooTool-1.7.0-mac-intel.dmg").write_text("dmg", encoding="utf-8")
|
||||
existing = {
|
||||
"windows": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool-1.7.0-windows-x64.exe",
|
||||
"macSilicon": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool-1.7.0-mac-apple-silicon.dmg",
|
||||
}
|
||||
|
||||
links = build_links(root, "v1.7.0", existing_links=existing)
|
||||
self.assertEqual(
|
||||
links["mac"],
|
||||
"https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool-1.7.0-mac-intel.dmg",
|
||||
)
|
||||
self.assertEqual(links["windows"], existing["windows"])
|
||||
self.assertEqual(links["macSilicon"], existing["macSilicon"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -19,7 +19,7 @@ public class UiConsts {
|
||||
* 软件名称,版本
|
||||
*/
|
||||
public static final String APP_NAME = "MooTool";
|
||||
public static final String APP_VERSION = "v1.7.0";
|
||||
public static final String APP_VERSION = "v1.7.1";
|
||||
|
||||
public static final int TABLE_ROW_HEIGHT = 30;
|
||||
|
||||
@@ -87,28 +87,33 @@ public class UiConsts {
|
||||
*/
|
||||
public final static Icon HELP_FOCUSED_ICON = new FlatSVGIcon("icon/help-filling.svg");
|
||||
|
||||
/**
|
||||
* GitHub master 分支 raw 内容根路径
|
||||
*/
|
||||
public static final String GITHUB_RAW_MASTER_URL = "https://raw.githubusercontent.com/rememberber/MooTool/master/";
|
||||
|
||||
/**
|
||||
* 软件版本检查url
|
||||
*/
|
||||
public static final String CHECK_VERSION_URL = "https://gitee.com/zhoubochina/MooTool/raw/master/src/main/resources/version_summary.json";
|
||||
public static final String CHECK_VERSION_URL = GITHUB_RAW_MASTER_URL + "src/main/resources/version_summary.json";
|
||||
|
||||
/**
|
||||
* 软件下载链接信息url
|
||||
*/
|
||||
public static final String DOWNLOAD_LINK_INFO_URL = "https://gitee.com/zhoubochina/MooTool/raw/develop/download_links.json";
|
||||
public static final String DOWNLOAD_LINK_INFO_URL = GITHUB_RAW_MASTER_URL + "download_links.json";
|
||||
|
||||
/**
|
||||
* Grace信息url
|
||||
*/
|
||||
public static final String GRACE_INFO_URL = "https://gitee.com/zhoubochina/MooTool/raw/develop/grace.json";
|
||||
public static final String GRACE_INFO_URL = GITHUB_RAW_MASTER_URL + "grace.json";
|
||||
|
||||
/**
|
||||
* 贡献者信息url
|
||||
*/
|
||||
public static final String CONTRIBUTOR_URL = "https://gitee.com/zhoubochina/MooTool/raw/develop/contributor.json";
|
||||
public static final String CONTRIBUTOR_URL = GITHUB_RAW_MASTER_URL + "contributor.json";
|
||||
|
||||
/**
|
||||
* DAU信息url
|
||||
*/
|
||||
public static final String DAU_URL = "https://gitee.com/zhoubochina/MooTool/raw/develop/dau.json";
|
||||
public static final String DAU_URL = GITHUB_RAW_MASTER_URL + "dau.json";
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ public class UpdateDialog extends JDialog {
|
||||
buttonDownloadFromWeb.addActionListener(e -> {
|
||||
Desktop desktop = Desktop.getDesktop();
|
||||
try {
|
||||
desktop.browse(new URI("https://gitee.com/zhoubochina/MooTool/releases"));
|
||||
desktop.browse(new URI("https://github.com/rememberber/MooTool/releases"));
|
||||
} catch (IOException | URISyntaxException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
@@ -98,7 +98,7 @@ public class UpdateDialog extends JDialog {
|
||||
String downloadLinkInfo = HttpUtil.get(UiConsts.DOWNLOAD_LINK_INFO_URL);
|
||||
if (StringUtils.isEmpty(downloadLinkInfo) || downloadLinkInfo.contains("404: Not Found")) {
|
||||
JOptionPane.showMessageDialog(App.mainFrame,
|
||||
"获取下载链接失败,请关注Gitee Release!", "网络错误",
|
||||
"获取下载链接失败,请关注 GitHub Release!", "网络错误",
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
return;
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"currentVersion": "v1.7.0",
|
||||
"currentVersion": "v1.7.1",
|
||||
"versionIndex": {
|
||||
"v0.0.0": "0",
|
||||
"v1.0.0": "1",
|
||||
@@ -41,7 +41,8 @@
|
||||
"v1.6.7": "37",
|
||||
"v1.6.8": "38",
|
||||
"v1.6.9": "39",
|
||||
"v1.7.0": "40"
|
||||
"v1.7.0": "40",
|
||||
"v1.7.1": "41"
|
||||
},
|
||||
"versionDetailList": [
|
||||
{
|
||||
@@ -248,6 +249,11 @@
|
||||
"version": "v1.7.0",
|
||||
"title": "HTTP工具支持cURL导入",
|
||||
"log": "● 新功能:HTTP工具支持cURL导入 by Cassian Florin\n\n"
|
||||
},
|
||||
{
|
||||
"version": "v1.7.1",
|
||||
"title": "近期累积的一些更新和优化",
|
||||
"log": "● feat(cron): 支持Linux Cron表达式 by Cassian Florin\n● feat(cron): fix(undo): 修复 Ctrl+Z 撤销异常 by Cassian Florin\n● fix(tray): 修复多屏托盘菜单定位 by Cassian Florin\n● fix: support Apple Silicon macOS package selection by Cassian Florin\n● feat: 时间转换支持时区\n\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user