mirror of
https://github.com/nimbold/Firelink.git
synced 2026-07-26 20:18:37 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b71a58ae06 | |||
| 6dcb0e33e8 | |||
| 34847b6234 | |||
| ac7963e353 | |||
| a4936fa141 |
@@ -31,11 +31,14 @@ jobs:
|
||||
run: |
|
||||
if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
|
||||
VERSION="${GITHUB_REF_NAME#v}"
|
||||
TAG_NAME="${GITHUB_REF_NAME}"
|
||||
else
|
||||
VERSION="${{ inputs.version }}"
|
||||
TAG_NAME="v${VERSION}"
|
||||
fi
|
||||
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT"
|
||||
echo "Version: $VERSION"
|
||||
|
||||
- name: Show build environment
|
||||
@@ -102,17 +105,88 @@ jobs:
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Publish GitHub release
|
||||
if: github.ref_type == 'tag'
|
||||
if: github.ref_type == 'tag' || github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
VERSION="${GITHUB_REF_NAME#v}"
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
TAG_NAME="${{ steps.version.outputs.tag_name }}"
|
||||
awk '/^## \['"$VERSION"'\]/{flag=1; next} /^## \[/{if(flag) exit} flag' CHANGELOG.md > release_notes.md
|
||||
test -s release_notes.md
|
||||
|
||||
if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
|
||||
gh release upload "$GITHUB_REF_NAME" dist/*.dmg --clobber
|
||||
gh release edit "$GITHUB_REF_NAME" --notes-file release_notes.md
|
||||
if gh release view "$TAG_NAME" >/dev/null 2>&1; then
|
||||
gh release upload "$TAG_NAME" dist/*.dmg --clobber
|
||||
gh release edit "$TAG_NAME" --notes-file release_notes.md
|
||||
else
|
||||
gh release create "$GITHUB_REF_NAME" dist/*.dmg --title "$GITHUB_REF_NAME" --notes-file release_notes.md --verify-tag
|
||||
gh release create "$TAG_NAME" dist/*.dmg --title "$TAG_NAME" --notes-file release_notes.md --verify-tag
|
||||
fi
|
||||
|
||||
- name: Update Sparkle appcast
|
||||
if: github.ref_type == 'tag' || github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
SPARKLE_ED_PRIVATE_KEY: ${{ secrets.SPARKLE_ED_PRIVATE_KEY }}
|
||||
run: |
|
||||
if [[ -z "${SPARKLE_ED_PRIVATE_KEY}" ]]; then
|
||||
echo "Missing SPARKLE_ED_PRIVATE_KEY repository secret; cannot update appcast.xml" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
TAG_NAME="${{ steps.version.outputs.tag_name }}"
|
||||
BUILD_NUMBER="${{ github.run_number }}"
|
||||
DMG_PATH="dist/Firelink-${VERSION}-mac-arm64.dmg"
|
||||
DMG_URL="https://github.com/nimbold/Firelink/releases/download/${TAG_NAME}/Firelink-${VERSION}-mac-arm64.dmg"
|
||||
RELEASE_NOTES_URL="https://github.com/nimbold/Firelink/releases/tag/${TAG_NAME}"
|
||||
PUB_DATE="$(LC_ALL=C date -u '+%a, %d %b %Y %H:%M:%S +0000')"
|
||||
|
||||
SIGN_OUTPUT="$(printf '%s' "$SPARKLE_ED_PRIVATE_KEY" | .build/artifacts/sparkle/Sparkle/bin/sign_update --ed-key-file - "$DMG_PATH")"
|
||||
ED_SIGNATURE="$(printf '%s\n' "$SIGN_OUTPUT" | sed -n 's/.*sparkle:edSignature="\([^"]*\)".*/\1/p' | head -1)"
|
||||
LENGTH="$(stat -f%z "$DMG_PATH" 2>/dev/null || stat -c%s "$DMG_PATH")"
|
||||
|
||||
if [[ -z "$ED_SIGNATURE" || -z "$LENGTH" ]]; then
|
||||
echo "Failed to extract Sparkle signature or DMG length" >&2
|
||||
printf '%s\n' "$SIGN_OUTPUT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git fetch origin main
|
||||
git checkout main
|
||||
git pull --ff-only origin main
|
||||
|
||||
cat > appcast.xml <<XML
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<channel>
|
||||
<title>Firelink Updates</title>
|
||||
<link>https://github.com/nimbold/Firelink</link>
|
||||
<description>Most recent updates for Firelink</description>
|
||||
<language>en</language>
|
||||
<item>
|
||||
<title>Version ${VERSION}</title>
|
||||
<sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>
|
||||
<sparkle:hardwareRequirements>arm64</sparkle:hardwareRequirements>
|
||||
<sparkle:releaseNotesLink>${RELEASE_NOTES_URL}</sparkle:releaseNotesLink>
|
||||
<pubDate>${PUB_DATE}</pubDate>
|
||||
<enclosure url="${DMG_URL}"
|
||||
sparkle:version="${BUILD_NUMBER}"
|
||||
sparkle:shortVersionString="${VERSION}"
|
||||
length="${LENGTH}"
|
||||
type="application/octet-stream"
|
||||
sparkle:edSignature="${ED_SIGNATURE}" />
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
XML
|
||||
|
||||
xmllint --noout appcast.xml
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add appcast.xml
|
||||
if git diff --cached --quiet; then
|
||||
echo "appcast.xml already up to date"
|
||||
else
|
||||
git commit -m "chore(release): update appcast for ${VERSION}"
|
||||
git push origin main
|
||||
fi
|
||||
|
||||
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.6.2] - 2026-06-08
|
||||
|
||||
### Fixes
|
||||
- Fix a bug where confirming a duplicate resolution failed to close the Add Downloads window, misleading users into thinking the download didn't start.
|
||||
- Fix keyboard shortcut collision that caused the main window to intercept Enter/Escape keys when the duplicate resolution sheet was open.
|
||||
- Fix UI freeze when checking release notes for an update by parsing HTML asynchronously on a background thread.
|
||||
- Improve Sparkle changelog formatting by converting HTML tags to clean Markdown instead of stripping them into an unreadable block of text.
|
||||
- Change the internal `Process xxxxx` status message to a cleaner `Starting...` message when queueing a new download.
|
||||
- Fix `EXC_BREAKPOINT` crash on app launch in production builds by prioritizing `Bundle.main` over `Bundle.module` when accessing resources.
|
||||
|
||||
## [0.6.1] - 2026-06-08
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -56,7 +56,9 @@ struct AddDownloadsView: View {
|
||||
conflicts: $conflictingDownloads,
|
||||
onConfirm: {
|
||||
showingDuplicates = false
|
||||
executeAddDownloads(start: pendingStartFlag, conflicts: conflictingDownloads)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
executeAddDownloads(start: pendingStartFlag, conflicts: conflictingDownloads)
|
||||
}
|
||||
},
|
||||
onCancel: {
|
||||
showingDuplicates = false
|
||||
@@ -312,7 +314,7 @@ struct AddDownloadsView: View {
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
}
|
||||
.keyboardShortcut(.cancelAction)
|
||||
.keyboardShortcut(showingDuplicates ? nil : .cancelAction)
|
||||
|
||||
Button("Add to Queue") {
|
||||
addDownloads(start: false)
|
||||
@@ -324,7 +326,7 @@ struct AddDownloadsView: View {
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.disabled(!canAddDownloads)
|
||||
.keyboardShortcut(.defaultAction)
|
||||
.keyboardShortcut(showingDuplicates ? nil : .defaultAction)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -602,7 +602,7 @@ final class DownloadController: ObservableObject {
|
||||
update(item.id) {
|
||||
$0.rpcPort = handle.rpcPort
|
||||
$0.rpcSecret = handle.rpcSecret
|
||||
$0.message = "Process \(handle.processIdentifier)"
|
||||
$0.message = "Starting..."
|
||||
}
|
||||
saveDownloads()
|
||||
applySpeedLimitsToActiveDownloads()
|
||||
|
||||
@@ -31,11 +31,19 @@ final class MediaEngineManager: ObservableObject {
|
||||
}
|
||||
|
||||
func binaryPath(for addon: AddonType) -> URL? {
|
||||
for bundle in [Bundle.main, Bundle.module] {
|
||||
if let bundled = bundle.url(forResource: addon.binaryName, withExtension: nil),
|
||||
if let bundled = Bundle.main.url(forResource: addon.binaryName, withExtension: nil),
|
||||
FileManager.default.isExecutableFile(atPath: bundled.path) {
|
||||
return bundled
|
||||
}
|
||||
|
||||
// Prevent fatalError crash: avoid accessing Bundle.module if running in a packaged app.
|
||||
if Bundle.main.bundleURL.pathExtension.lowercased() != "app" {
|
||||
#if SWIFT_PACKAGE
|
||||
if let bundled = Bundle.module.url(forResource: addon.binaryName, withExtension: nil),
|
||||
FileManager.default.isExecutableFile(atPath: bundled.path) {
|
||||
return bundled
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -32,19 +32,53 @@ class InlineUpdateUserDriver: NSObject, SPUUserDriver {
|
||||
}
|
||||
|
||||
func showUpdateReleaseNotes(with downloadData: SPUDownloadData) {
|
||||
DispatchQueue.main.async {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
if let htmlString = String(data: downloadData.data, encoding: .utf8) {
|
||||
self.updater?.releaseNotes = self.stripHTML(htmlString)
|
||||
let parsedText = self.fastHTMLToMarkdown(htmlString)
|
||||
DispatchQueue.main.async {
|
||||
self.updater?.releaseNotes = parsedText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func stripHTML(_ string: String) -> String {
|
||||
guard let data = string.data(using: .utf8) else { return string }
|
||||
if let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil) {
|
||||
return attributedString.string.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
nonisolated private func fastHTMLToMarkdown(_ html: String) -> String {
|
||||
var text = html
|
||||
text = text.replacingOccurrences(of: "<br>", with: "\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<br/>", with: "\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<br />", with: "\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</p>", with: "\n\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<li>", with: "- ", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</li>", with: "\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<h1>", with: "# ", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</h1>", with: "\n\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<h2>", with: "## ", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</h2>", with: "\n\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<h3>", with: "### ", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</h3>", with: "\n\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<b>", with: "**", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</b>", with: "**", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<strong>", with: "**", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</strong>", with: "**", options: .caseInsensitive)
|
||||
|
||||
if let regex = try? NSRegularExpression(pattern: "<[^>]+>", options: .caseInsensitive) {
|
||||
let range = NSRange(location: 0, length: text.utf16.count)
|
||||
text = regex.stringByReplacingMatches(in: text, options: [], range: range, withTemplate: "")
|
||||
}
|
||||
return string
|
||||
|
||||
text = text.replacingOccurrences(of: " ", with: " ")
|
||||
text = text.replacingOccurrences(of: "&", with: "&")
|
||||
text = text.replacingOccurrences(of: "<", with: "<")
|
||||
text = text.replacingOccurrences(of: ">", with: ">")
|
||||
text = text.replacingOccurrences(of: """, with: "\"")
|
||||
text = text.replacingOccurrences(of: "'", with: "'")
|
||||
|
||||
if let regex = try? NSRegularExpression(pattern: "\\n{3,}", options: []) {
|
||||
let range = NSRange(location: 0, length: text.utf16.count)
|
||||
text = regex.stringByReplacingMatches(in: text, options: [], range: range, withTemplate: "\n\n")
|
||||
}
|
||||
|
||||
return text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
func showUpdateReleaseNotesFailedToDownloadWithError(_ error: Error) {
|
||||
|
||||
+8
-8
@@ -6,17 +6,17 @@
|
||||
<description>Most recent updates for Firelink</description>
|
||||
<language>en</language>
|
||||
<item>
|
||||
<title>Version 0.5.7</title>
|
||||
<title>Version 0.6.2</title>
|
||||
<sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>
|
||||
<sparkle:hardwareRequirements>arm64</sparkle:hardwareRequirements>
|
||||
<sparkle:releaseNotesLink>https://github.com/nimbold/Firelink/releases/tag/v0.5.7</sparkle:releaseNotesLink>
|
||||
<pubDate>Sat, 06 Jun 2026 11:36:06 +0000</pubDate>
|
||||
<enclosure url="https://github.com/nimbold/Firelink/releases/download/v0.5.7/Firelink-0.5.7-mac-arm64.dmg"
|
||||
sparkle:version="22"
|
||||
sparkle:shortVersionString="0.5.7"
|
||||
length="8871979"
|
||||
<sparkle:releaseNotesLink>https://github.com/nimbold/Firelink/releases/tag/v0.6.2</sparkle:releaseNotesLink>
|
||||
<pubDate>Mon, 08 Jun 2026 12:54:48 +0000</pubDate>
|
||||
<enclosure url="https://github.com/nimbold/Firelink/releases/download/v0.6.2/Firelink-0.6.2-mac-arm64.dmg"
|
||||
sparkle:version="26"
|
||||
sparkle:shortVersionString="0.6.2"
|
||||
length="75882342"
|
||||
type="application/octet-stream"
|
||||
sparkle:edSignature="Y4QEJqGw6IeXL5JmaRqt5U22Bmc8JD02+oLRIFoKgOJO4v6p6+AmBP9VoM3yp/MAzj+gJMWiI/Gf4aPx4x3dAg==" />
|
||||
sparkle:edSignature="vvylIjvPhUZMvs/XCvhPDISBdRxolstgLLDDmU2i2Nb6Hf5mnSf+orMRYV0VewBSREB49DN64l0tkHaZrkXVBQ==" />
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
|
||||
Reference in New Issue
Block a user