From 31d07534d0646af82e03d1c910efbc58c0d37ee6 Mon Sep 17 00:00:00 2001 From: mahee96 <47920326+mahee96@users.noreply.github.com> Date: Tue, 24 Feb 2026 05:27:15 +0530 Subject: [PATCH] CI: improve more ci worflow --- .github/workflows/nightly.yml | 21 ++- AltStore.xcodeproj/project.pbxproj | 252 +++++++++++++++++++++++++ Dependencies/apps-v2.json | 2 +- release-notes.md | 37 ++++ scripts/ci/generate_release_notes.py | 71 ++++--- scripts/ci/generate_source_metadata.py | 80 ++++---- scripts/ci/update_source_metadata.py | 44 ++--- scripts/ci/workflow.py | 38 +++- source-metadata.json | 11 ++ 9 files changed, 463 insertions(+), 93 deletions(-) create mode 100644 release-notes.md create mode 100644 source-metadata.json diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index fcd9bac7..891cec12 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -16,6 +16,8 @@ jobs: runs-on: macos-26 env: REF_NAME: nightly + CHANNEL: nightly + BUNDLE_ID: com.SideStore.SideStore steps: - uses: actions/checkout@v4 @@ -166,13 +168,22 @@ jobs: # -------------------------------------------------- - name: Deploy run: | + PRODUCT_NAME=$(python3 scripts/ci/workflow.py get-product-name) + BUNDLE_ID=$(python3 scripts/ci/workflow.py get-bundle-id) + SOURCE_JSON="_includes/source.json" + IPA_NAME="$PRODUCT_NAME.ipa" + LAST_SUCCESSFUL_COMMIT=$(python3 scripts/ci/workflow.py last-successful-commit \ + "${{ github.workflow }}" "$CHANNEL") + + python3 scripts/ci/workflow.py deploy \ Dependencies/apps-v2.json \ - "_includes/source.json" \ - "${{ env.REF_NAME }}" \ + "$SOURCE_JSON" \ + "$REF_NAME" \ "$SHORT_COMMIT" \ "$MARKETING_VERSION" \ "$VERSION" \ - "${{ env.REF_NAME }}" \ - "com.SideStore.SideStore" \ - "SideStore.ipa" \ No newline at end of file + "$CHANNEL" \ + "$BUNDLE_ID" \ + "$IPA_NAME" \ + "$LAST_SUCCESSFUL_COMMIT" \ No newline at end of file diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index c527d332..211cc94b 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -881,6 +881,90 @@ remoteGlobalIDString = 191E5FAA290A5D92001A3B7C; remoteInfo = minimuxer; }; + A86372AE2F4D1E4400E66784 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A8636E922F4CF74D00E66784 /* libfragmentzip.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 87B8C3401E0E9C37002F817D; + remoteInfo = "fragmentzip-cli-macOS"; + }; + A86372B02F4D1E4400E66784 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A8636E922F4CF74D00E66784 /* libfragmentzip.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B315FDB02866CCF8002E243C; + remoteInfo = "fragmentzip-cli-iOS"; + }; + A86372B22F4D1E4400E66784 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A8636E922F4CF74D00E66784 /* libfragmentzip.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B315FDB52866CD91002E243C; + remoteInfo = "fragmentzip-macOS"; + }; + A86372B42F4D1E4400E66784 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A8636E922F4CF74D00E66784 /* libfragmentzip.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B315FDCE2866CDD3002E243C; + remoteInfo = "fragmentzip-iOS"; + }; + A86372B72F4D1E4400E66784 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A8636E6E2F4CF74D00E66784 /* libgeneral.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 87977F6F227C4B71004F31DA; + remoteInfo = libgeneral; + }; + A86372BC2F4D1E4400E66784 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A86372112F4CF74D00E66784 /* Roxas.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = BFADAFF819AE7BB70050CF31; + remoteInfo = Roxas; + }; + A86372BE2F4D1E4400E66784 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A86372112F4CF74D00E66784 /* Roxas.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = BF8624801BB742E700C12EEE; + remoteInfo = RoxasTV; + }; + A86372C02F4D1E4400E66784 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A86372112F4CF74D00E66784 /* Roxas.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = BFADB00319AE7BB80050CF31; + remoteInfo = RoxasTests; + }; + A86372C32F4D1E4400E66784 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A86371872F4CF74D00E66784 /* SampleApp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 44E8FA8923D90632009E1D13; + remoteInfo = SampleApp; + }; + A86372C72F4D1E4400E66784 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A863716E2F4CF74D00E66784 /* SampleApp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 44B1EE7C23DB90D5004E2E29; + remoteInfo = SampleApp; + }; + A86372C92F4D1E4400E66784 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A863716E2F4CF74D00E66784 /* SampleApp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 445A906A2400612800B487B4; + remoteInfo = "NSAttributedString+MarkdownTests"; + }; + A86372CC2F4D1E4400E66784 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A86371932F4CF74D00E66784 /* SwiftSampleApp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 44962FDA23E7A54A00E2A598; + remoteInfo = SwiftSampleApp; + }; A8A5AC022F4C2CFC00572B4A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = A8A5AB6D2F4C2CFC00572B4A /* minimuxer.xcodeproj */; @@ -2168,6 +2252,12 @@ A85AEC662F4B22F6002E2E11 /* em_proxy.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = em_proxy.xcodeproj; sourceTree = ""; }; A85AEC682F4B22F6002E2E11 /* minimuxer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = minimuxer.xcodeproj; sourceTree = ""; }; A8635D052F4CF16D00E66784 /* OpenSSL.xcframework */ = {isa = PBXFileReference; expectedSignature = "AppleDeveloperProgram:67RAULRX93:Marcin Krzyzanowski"; lastKnownFileType = wrapper.xcframework; name = OpenSSL.xcframework; path = Dependencies/AltSign/Dependencies/OpenSSL.xcframework; sourceTree = ""; }; + A8636E6E2F4CF74D00E66784 /* libgeneral.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = libgeneral.xcodeproj; sourceTree = ""; }; + A8636E922F4CF74D00E66784 /* libfragmentzip.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = libfragmentzip.xcodeproj; sourceTree = ""; }; + A863716E2F4CF74D00E66784 /* SampleApp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = SampleApp.xcodeproj; sourceTree = ""; }; + A86371872F4CF74D00E66784 /* SampleApp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = SampleApp.xcodeproj; sourceTree = ""; }; + A86371932F4CF74D00E66784 /* SwiftSampleApp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = SwiftSampleApp.xcodeproj; sourceTree = ""; }; + A86372112F4CF74D00E66784 /* Roxas.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = Roxas.xcodeproj; sourceTree = ""; }; A8945AA52D059B6100D86CBE /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A8A5A7E82F4C2CFC00572B4A /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; A8A5A7E92F4C2CFC00572B4A /* build.rs */ = {isa = PBXFileReference; lastKnownFileType = text; path = build.rs; sourceTree = ""; }; @@ -3231,6 +3321,60 @@ name = Products; sourceTree = ""; }; + A8636E992F4CF74D00E66784 /* Products */ = { + isa = PBXGroup; + children = ( + A86372B82F4D1E4400E66784 /* libgeneral */, + ); + name = Products; + sourceTree = ""; + }; + A8636E9B2F4CF74D00E66784 /* Products */ = { + isa = PBXGroup; + children = ( + A86372AF2F4D1E4400E66784 /* libfragmentzip */, + A86372B12F4D1E4400E66784 /* libfragmentzip */, + A86372B32F4D1E4400E66784 /* libfragmentzip.a */, + A86372B52F4D1E4400E66784 /* libfragmentzip.a */, + ); + name = Products; + sourceTree = ""; + }; + A863719B2F4CF74D00E66784 /* Products */ = { + isa = PBXGroup; + children = ( + A86372C82F4D1E4400E66784 /* SampleApp.app */, + A86372CA2F4D1E4400E66784 /* NSAttributedString+MarkdownTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + A863719D2F4CF74D00E66784 /* Products */ = { + isa = PBXGroup; + children = ( + A86372CD2F4D1E4400E66784 /* SwiftSampleApp.app */, + ); + name = Products; + sourceTree = ""; + }; + A863719F2F4CF74D00E66784 /* Products */ = { + isa = PBXGroup; + children = ( + A86372C42F4D1E4400E66784 /* SampleApp.app */, + ); + name = Products; + sourceTree = ""; + }; + A86372122F4CF74D00E66784 /* Products */ = { + isa = PBXGroup; + children = ( + A86372BD2F4D1E4400E66784 /* Roxas.framework */, + A86372BF2F4D1E4400E66784 /* Roxas.framework */, + A86372C12F4D1E4400E66784 /* RoxasTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; A8A5A7F42F4C2CFC00572B4A /* em_proxy */ = { isa = PBXGroup; children = ( @@ -4497,6 +4641,10 @@ ProductGroup = A8A5B06A2F4C347700572B4A /* Products */; ProjectRef = A8A5B0622F4C347700572B4A /* libfragmentzip.xcodeproj */; }, + { + ProductGroup = A8636E9B2F4CF74D00E66784 /* Products */; + ProjectRef = A8636E922F4CF74D00E66784 /* libfragmentzip.xcodeproj */; + }, { ProductGroup = A8FAC0612F4B50D10061A851 /* Products */; ProjectRef = A8FABCE72F4B50D10061A851 /* libfragmentzip.xcodeproj */; @@ -4609,6 +4757,10 @@ ProductGroup = A8EEDA212F4B19B000F2436D /* Products */; ProjectRef = A8EED1D72F4B19B000F2436D /* libgeneral.xcodeproj */; }, + { + ProductGroup = A8636E992F4CF74D00E66784 /* Products */; + ProjectRef = A8636E6E2F4CF74D00E66784 /* libgeneral.xcodeproj */; + }, { ProductGroup = A81197412F4C1C710013ABD0 /* Products */; ProjectRef = A81193E22F4C1C710013ABD0 /* libgeneral.xcodeproj */; @@ -4773,6 +4925,10 @@ ProductGroup = A8A5B7332F4C4C8600572B4A /* Products */; ProjectRef = A8A5B7322F4C4C8600572B4A /* Roxas.xcodeproj */; }, + { + ProductGroup = A86372122F4CF74D00E66784 /* Products */; + ProjectRef = A86372112F4CF74D00E66784 /* Roxas.xcodeproj */; + }, { ProductGroup = A8EEDA272F4B19B000F2436D /* Products */; ProjectRef = A8EEDA1E2F4B19B000F2436D /* Roxas.xcodeproj */; @@ -4817,6 +4973,10 @@ ProductGroup = A81180DC2F4C1B230013ABD0 /* Products */; ProjectRef = A81180302F4C1B230013ABD0 /* SampleApp.xcodeproj */; }, + { + ProductGroup = A863719F2F4CF74D00E66784 /* Products */; + ProjectRef = A86371872F4CF74D00E66784 /* SampleApp.xcodeproj */; + }, { ProductGroup = A81197372F4C1C710013ABD0 /* Products */; ProjectRef = A81196912F4C1C710013ABD0 /* SampleApp.xcodeproj */; @@ -4865,6 +5025,10 @@ ProductGroup = A8A5ACE92F4C339400572B4A /* Products */; ProjectRef = A8A5ACD02F4C339400572B4A /* SampleApp.xcodeproj */; }, + { + ProductGroup = A863719B2F4CF74D00E66784 /* Products */; + ProjectRef = A863716E2F4CF74D00E66784 /* SampleApp.xcodeproj */; + }, { ProductGroup = A8A5ACE72F4C339400572B4A /* Products */; ProjectRef = A8A5ACB72F4C339400572B4A /* SampleApp.xcodeproj */; @@ -4973,6 +5137,10 @@ ProductGroup = A81197392F4C1C710013ABD0 /* Products */; ProjectRef = A811969D2F4C1C710013ABD0 /* SwiftSampleApp.xcodeproj */; }, + { + ProductGroup = A863719D2F4CF74D00E66784 /* Products */; + ProjectRef = A86371932F4CF74D00E66784 /* SwiftSampleApp.xcodeproj */; + }, { ProductGroup = A8FAC0592F4B50D10061A851 /* Products */; ProjectRef = A8FABF7E2F4B50D10061A851 /* SwiftSampleApp.xcodeproj */; @@ -5860,6 +6028,90 @@ remoteRef = A85AED342F4B2315002E2E11 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + A86372AF2F4D1E4400E66784 /* libfragmentzip */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = libfragmentzip; + remoteRef = A86372AE2F4D1E4400E66784 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A86372B12F4D1E4400E66784 /* libfragmentzip */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = libfragmentzip; + remoteRef = A86372B02F4D1E4400E66784 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A86372B32F4D1E4400E66784 /* libfragmentzip.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libfragmentzip.a; + remoteRef = A86372B22F4D1E4400E66784 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A86372B52F4D1E4400E66784 /* libfragmentzip.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libfragmentzip.a; + remoteRef = A86372B42F4D1E4400E66784 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A86372B82F4D1E4400E66784 /* libgeneral */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = libgeneral; + remoteRef = A86372B72F4D1E4400E66784 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A86372BD2F4D1E4400E66784 /* Roxas.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Roxas.framework; + remoteRef = A86372BC2F4D1E4400E66784 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A86372BF2F4D1E4400E66784 /* Roxas.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Roxas.framework; + remoteRef = A86372BE2F4D1E4400E66784 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A86372C12F4D1E4400E66784 /* RoxasTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RoxasTests.xctest; + remoteRef = A86372C02F4D1E4400E66784 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A86372C42F4D1E4400E66784 /* SampleApp.app */ = { + isa = PBXReferenceProxy; + fileType = wrapper.application; + path = SampleApp.app; + remoteRef = A86372C32F4D1E4400E66784 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A86372C82F4D1E4400E66784 /* SampleApp.app */ = { + isa = PBXReferenceProxy; + fileType = wrapper.application; + path = SampleApp.app; + remoteRef = A86372C72F4D1E4400E66784 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A86372CA2F4D1E4400E66784 /* NSAttributedString+MarkdownTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = "NSAttributedString+MarkdownTests.xctest"; + remoteRef = A86372C92F4D1E4400E66784 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A86372CD2F4D1E4400E66784 /* SwiftSampleApp.app */ = { + isa = PBXReferenceProxy; + fileType = wrapper.application; + path = SwiftSampleApp.app; + remoteRef = A86372CC2F4D1E4400E66784 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; A8A5AC032F4C2CFC00572B4A /* libminimuxer_static.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; diff --git a/Dependencies/apps-v2.json b/Dependencies/apps-v2.json index e0914d34..9724b1c5 160000 --- a/Dependencies/apps-v2.json +++ b/Dependencies/apps-v2.json @@ -1 +1 @@ -Subproject commit e0914d346315338d1600acda48d18982bb902fef +Subproject commit 9724b1c56d9c339ceefe2197abfbc026cd4cc1ff diff --git a/release-notes.md b/release-notes.md new file mode 100644 index 00000000..a268ad38 --- /dev/null +++ b/release-notes.md @@ -0,0 +1,37 @@ + + +### nightly +#### What's Changed +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- altsign updated to latest +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- re added openSSL from new path +- updated altsign to use xcframework for openSSL which was causing huge download of 1.2 GB each time +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: improve more ci worflow +- CI: full rewrite - moved logic into ci.py and kept workflow scripts mostly dummy + +#### Full Changelog: [38715283...99712f00](https://github.com/SideStore/SideStore/compare/38715283073ea37949a462b889ce3cad403ea499...99712f0020a4f2ae57d8d781514fa735f893c23a) diff --git a/scripts/ci/generate_release_notes.py b/scripts/ci/generate_release_notes.py index 1621a89b..7629ebc4 100644 --- a/scripts/ci/generate_release_notes.py +++ b/scripts/ci/generate_release_notes.py @@ -19,6 +19,20 @@ def run(cmd: str) -> str: return subprocess.check_output(cmd, shell=True, text=True).strip() +def commit_exists(rev: str) -> bool: + if not rev: + return False + try: + subprocess.check_output( + f"git rev-parse --verify {rev}^{{commit}}", + shell=True, + stderr=subprocess.DEVNULL, + ) + return True + except subprocess.CalledProcessError: + return False + + def head_commit(): return run("git rev-parse HEAD") @@ -35,12 +49,8 @@ def repo_url(): def commit_messages(start, end="HEAD"): - try: - out = run(f"git log {start}..{end} --pretty=format:%s") - return out.splitlines() if out else [] - except subprocess.CalledProcessError: - fallback = run("git rev-parse HEAD~5") - return run(f"git log {fallback}..{end} --pretty=format:%s").splitlines() + out = run(f"git log {start}..{end} --pretty=format:%s") + return out.splitlines() if out else [] def authors(range_expr, fmt="%an"): @@ -76,10 +86,35 @@ def fmt_author(author): # release note generation # ---------------------------------------------------------- +def resolve_start_commit(last_successful: str): + if commit_exists(last_successful): + return last_successful + + try: + return run("git rev-parse HEAD~10") + except Exception: + return first_commit() + def generate_release_notes(last_successful, tag, branch): current = head_commit() + + # fallback if missing/invalid + if not last_successful or not commit_exists(last_successful): + try: + last_successful = run("git rev-parse HEAD~10") + except Exception: + last_successful = first_commit() + messages = commit_messages(last_successful, current) + # fallback if empty range + if not messages: + try: + last_successful = run("git rev-parse HEAD~10") + except Exception: + last_successful = first_commit() + messages = commit_messages(last_successful, current) + section = f"{TAG_MARKER} {tag}\n" section += f"{HEADER_MARKER} What's Changed\n" @@ -90,7 +125,8 @@ def generate_release_notes(last_successful, tag, branch): section += f"{fmt_msg(m)}\n" prev_authors = authors(branch) - new_authors = authors(f"{last_successful}..{current}") - prev_authors + recent_authors = authors(f"{last_successful}..{current}") + new_authors = recent_authors - prev_authors if new_authors: section += f"\n{HEADER_MARKER} New Contributors\n" @@ -170,7 +206,7 @@ def update_release_md(existing, new_section, tag): # retrieval # ---------------------------------------------------------- -def retrieve_tag(tag, file_path): +def retrieve_tag(tag, file_path: Path): if not file_path.exists(): return "" @@ -209,30 +245,20 @@ def main(): " generate_release_notes.py --retrieve [--output-dir DIR]" ) - # parse optional output dir output_dir = Path.cwd() if "--output-dir" in args: idx = args.index("--output-dir") - try: - output_dir = Path(args[idx + 1]).resolve() - except IndexError: - sys.exit("Missing value for --output-dir") - + output_dir = Path(args[idx + 1]).resolve() del args[idx:idx + 2] output_dir.mkdir(parents=True, exist_ok=True) release_file = output_dir / "release-notes.md" - # retrieval mode if args[0] == "--retrieve": - if len(args) < 2: - sys.exit("Missing tag after --retrieve") - print(retrieve_tag(args[1], release_file)) return - # generation mode last_successful = args[0] tag = args[1] if len(args) > 1 else head_commit() branch = args[2] if len(args) > 2 else ( @@ -241,12 +267,7 @@ def main(): new_section = generate_release_notes(last_successful, tag, branch) - existing = ( - release_file.read_text() - if release_file.exists() - else "" - ) - + existing = release_file.read_text() if release_file.exists() else "" updated = update_release_md(existing, new_section, tag) release_file.write_text(updated) diff --git a/scripts/ci/generate_source_metadata.py b/scripts/ci/generate_source_metadata.py index d59a01ba..51c4d4d8 100644 --- a/scripts/ci/generate_source_metadata.py +++ b/scripts/ci/generate_source_metadata.py @@ -5,16 +5,33 @@ import json import subprocess from pathlib import Path import argparse +import sys + +SCRIPT_DIR = Path(__file__).resolve().parent # ---------------------------------------------------------- # helpers # ---------------------------------------------------------- +def resolve_script(name: str) -> Path: + p = Path.cwd() / name + if p.exists(): + return p + return SCRIPT_DIR / name + + def sh(cmd: str, cwd: Path) -> str: - return subprocess.check_output( - cmd, shell=True, cwd=cwd - ).decode().strip() + try: + return subprocess.check_output( + cmd, + shell=True, + cwd=cwd, + stderr=subprocess.STDOUT, + ).decode().strip() + except subprocess.CalledProcessError as e: + print(e.output.decode(), file=sys.stderr) + raise SystemExit(f"Command failed: {cmd}") def file_size(path: Path) -> int: @@ -38,35 +55,16 @@ def sha256(path: Path) -> str: def main(): p = argparse.ArgumentParser() - p.add_argument( - "--repo-root", - required=True, - help="Repo used for git history + release notes", - ) + p.add_argument("--repo-root", required=True) + p.add_argument("--ipa", required=True) + p.add_argument("--output-dir", required=True) - p.add_argument( - "--ipa", - required=True, - help="Path to IPA file", - ) - - p.add_argument( - "--output-dir", - required=True, - help="Output Directory where source_metadata.json is written", - ) - p.add_argument( "--output-name", default="source_metadata.json", - help="Output metadata filename", ) - p.add_argument( - "--release-notes-dir", - required=True, - help="Output Directory where release-notes.md is generated/read", - ) + p.add_argument("--release-notes-dir", required=True) p.add_argument("--release-tag", required=True) p.add_argument("--version", required=True) @@ -74,6 +72,10 @@ def main(): p.add_argument("--short-commit", required=True) p.add_argument("--release-channel", required=True) p.add_argument("--bundle-id", required=True) + + # optional + p.add_argument("--last-successful-commit") + p.add_argument("--is-beta", action="store_true") args = p.parse_args() @@ -95,19 +97,27 @@ def main(): out_file = out_dir / args.output_name # ------------------------------------------------------ - # ensure release notes exist + # generate release notes # ------------------------------------------------------ print("Generating release notes…") - sh( - ( - "python3 generate_release_notes.py " + script = resolve_script("generate_release_notes.py") + + if args.last_successful_commit: + gen_cmd = ( + f"python3 {script} " + f"{args.last_successful_commit} {args.release_tag} " + f"--output-dir \"{notes_dir}\"" + ) + else: + gen_cmd = ( + f"python3 {script} " f"{args.short_commit} {args.release_tag} " f"--output-dir \"{notes_dir}\"" - ), - cwd=repo_root, - ) + ) + + sh(gen_cmd, cwd=repo_root) # ------------------------------------------------------ # retrieve release notes @@ -115,7 +125,7 @@ def main(): notes = sh( ( - "python3 generate_release_notes.py " + f"python3 {script} " f"--retrieve {args.release_tag} " f"--output-dir \"{notes_dir}\"" ), @@ -126,7 +136,7 @@ def main(): # compute metadata # ------------------------------------------------------ - now = datetime.datetime.now(datetime.UTC) + now = datetime.datetime.now(datetime.timezone.utc) formatted = now.strftime("%Y-%m-%dT%H:%M:%SZ") human = now.strftime("%c") diff --git a/scripts/ci/update_source_metadata.py b/scripts/ci/update_source_metadata.py index a81ccf13..355faed6 100755 --- a/scripts/ci/update_source_metadata.py +++ b/scripts/ci/update_source_metadata.py @@ -5,23 +5,6 @@ import sys from pathlib import Path -''' -metadata.json template - -{ - "version_ipa": "0.0.0", - "version_date": "2000-12-18T00:00:00Z", - "is_beta": true, - "release_channel": "alpha", - "size": 0, - "sha256": "", - "localized_description": "Invalid Update", - "download_url": "https://github.com/SideStore/SideStore/releases/download/0.0.0/SideStore.ipa", - "bundle_identifier": "com.SideStore.SideStore" -} -''' - - # ---------------------------------------------------------- # args # ---------------------------------------------------------- @@ -89,7 +72,7 @@ RELEASE_CHANNEL = RELEASE_CHANNEL.lower() # ---------------------------------------------------------- -# load or create source.json +# load source.json # ---------------------------------------------------------- if source_file.exists(): @@ -108,7 +91,7 @@ if int(data.get("version", 1)) < 2: # ---------------------------------------------------------- -# ensure app entry exists +# locate app # ---------------------------------------------------------- apps = data.setdefault("apps", []) @@ -128,7 +111,7 @@ if app is None: # ---------------------------------------------------------- -# update logic +# update storefront metadata (stable only) # ---------------------------------------------------------- if RELEASE_CHANNEL == "stable": @@ -141,6 +124,11 @@ if RELEASE_CHANNEL == "stable": "downloadURL": DOWNLOAD_URL, }) + +# ---------------------------------------------------------- +# releaseChannels update (ORIGINAL FORMAT) +# ---------------------------------------------------------- + channels = app.setdefault("releaseChannels", []) new_version = { @@ -152,19 +140,31 @@ new_version = { "sha256": SHA256, } -tracks = [t for t in channels if t.get("track") == RELEASE_CHANNEL] +# find track +tracks = [ + t for t in channels + if isinstance(t, dict) and t.get("track") == RELEASE_CHANNEL +] if len(tracks) > 1: print(f"Multiple tracks named {RELEASE_CHANNEL}") sys.exit(1) if not tracks: + # create new track at top (original behaviour) channels.insert(0, { "track": RELEASE_CHANNEL, "releases": [new_version], }) else: - tracks[0]["releases"][0] = new_version + track = tracks[0] + releases = track.setdefault("releases", []) + + if not releases: + releases.append(new_version) + else: + # replace top entry only (original logic) + releases[0] = new_version # ---------------------------------------------------------- diff --git a/scripts/ci/workflow.py b/scripts/ci/workflow.py index 6ff0677d..465ce09a 100644 --- a/scripts/ci/workflow.py +++ b/scripts/ci/workflow.py @@ -253,7 +253,7 @@ def release_notes(tag): # DEPLOY SOURCE.JSON # ---------------------------------------------------------- -def deploy(repo, source_json, release_tag, short_commit, marketing_version, version, channel, bundle_id, ipa_name): +def deploy(repo, source_json, release_tag, short_commit, marketing_version, version, channel, bundle_id, ipa_name, last_successful_commit=None): repo = (ROOT / repo).resolve() ipa_path = ROOT / ipa_name source_path = repo / source_json @@ -268,8 +268,7 @@ def deploy(repo, source_json, release_tag, short_commit, marketing_version, vers if not source_path.exists(): raise SystemExit(f"{source_json} missing inside repo") - - run( + cmd = ( f"python3 {SCRIPTS}/generate_source_metadata.py " f"--repo-root {ROOT} " f"--ipa {ipa_path} " @@ -284,6 +283,12 @@ def deploy(repo, source_json, release_tag, short_commit, marketing_version, vers f"--bundle-id {bundle_id}" ) + # pass only if provided + if last_successful_commit: + cmd += f" --last-successful-commit {last_successful_commit}" + + run(cmd) + run("git config user.name 'GitHub Actions'", check=False) run("git config user.email 'github-actions@github.com'", check=False) @@ -309,6 +314,27 @@ def deploy(repo, source_json, release_tag, short_commit, marketing_version, vers else: raise SystemExit("Deploy push failed after retries") +def last_successful_commit(workflow, branch): + import json + + try: + out = runAndGet( + f'gh run list ' + f'--workflow "{workflow}" ' + f'--json headSha,conclusion,headBranch' + ) + + runs = json.loads(out) + + for r in runs: + if r.get("conclusion") == "success" and r.get("headBranch") == branch: + return r["headSha"] + + except Exception: + pass + + return None + # ---------------------------------------------------------- # ENTRYPOINT # ---------------------------------------------------------- @@ -357,8 +383,10 @@ COMMANDS = { # ---------------------------------------------------------- # RELEASE / DEPLOY # ---------------------------------------------------------- - "release-notes" : (release_notes, 1, ""), - "deploy" : (deploy, 9, " "), + "last-successful-commit" : (last_successful_commit, 2, " "), + "release-notes" : (release_notes, 1, ""), + "deploy" : (deploy, 10, + " [last_successful_commit]"), } def main(): diff --git a/source-metadata.json b/source-metadata.json new file mode 100644 index 00000000..e3b80492 --- /dev/null +++ b/source-metadata.json @@ -0,0 +1,11 @@ +{ + "is_beta": false, + "bundle_identifier": "com.SideStore.SideStore", + "version_ipa": "0.6.3", + "version_date": "2026-02-23T23:38:22Z", + "release_channel": "nightly", + "size": 29313346, + "sha256": "51ec327bca0b0056ccd4c2eb1a130cb7c5bb21de2f303251eea3e0a7336699c4", + "download_url": "https://github.com/SideStore/SideStore/releases/download/nightly/SideStore.ipa", + "localized_description": "This is release for:\n - version: \"0.6.3-nightly.2026.02.24.42+abc123de\"\n - revision: \"99712f00\"\n - timestamp: \"Mon Feb 23 23:38:22 2026\"\n\nRelease Notes:\n#### What's Changed\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- altsign updated to latest\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- re added openSSL from new path\n- updated altsign to use xcframework for openSSL which was causing huge download of 1.2 GB each time\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: full rewrite - moved logic into ci.py and kept workflow scripts mostly dummy\n\n#### Full Changelog: [38715283...99712f00](https://github.com/SideStore/SideStore/compare/38715283073ea37949a462b889ce3cad403ea499...99712f0020a4f2ae57d8d781514fa735f893c23a)" +} \ No newline at end of file