77import typer
88
99from ..core .context import Context
10+ from ..core .products import ProductDescriptor , get_product_descriptor
1011from ..lib .notify import slack_subscriber
1112from ..lib .paths import get_package_root
1213from ..core .runner import StepExecutionError , run as run_steps
1314from ..lib .utils import log_info , log_error
15+ from ..products import PRODUCTS
1416
1517from ..release import (
1618 AVAILABLE_MODULES ,
2022 PublishModule ,
2123 DownloadModule ,
2224)
25+ from ..release .list import DEFAULT_LIST_LIMIT
2326
2427app = typer .Typer (
2528 help = "Release automation commands" ,
3538)
3639app .add_typer (github_app , name = "github" )
3740
41+ _PRODUCT_HELP = f"Product to operate on ({ ', ' .join (PRODUCTS )} )"
42+
43+
44+ def _resolve_product (product_id : Optional [str ]) -> ProductDescriptor :
45+ """Resolve --product to a descriptor with a CLI-friendly error."""
46+ try :
47+ return get_product_descriptor (product_id )
48+ except ValueError :
49+ log_error (
50+ f"Unknown product '{ product_id } '. Valid: { ', ' .join (sorted (PRODUCTS ))} "
51+ )
52+ raise typer .Exit (1 )
53+
3854
3955def create_release_context (
4056 version : str ,
4157 repo : Optional [str ] = None ,
58+ product : Optional [str ] = None ,
4259) -> Context :
4360 """Create Context for release operations.
4461
@@ -51,6 +68,7 @@ def create_release_context(
5168 chromium_src = root ,
5269 architecture = "" ,
5370 build_type = "release" ,
71+ product = _resolve_product (product ),
5472 )
5573 ctx .release_version = version
5674 ctx .github_repo = repo or ""
@@ -71,51 +89,22 @@ def execute_module(ctx: Context, module) -> None:
7189@app .callback (invoke_without_command = True )
7290def main (
7391 ctx : typer .Context ,
74- version : Optional [str ] = typer .Option (
75- None , "--version" , "-v" , help = "Version to operate on (e.g., 0.31.0)"
76- ),
77- list_artifacts : bool = typer .Option (
78- False , "--list" , "-l" , help = "List artifacts for version from R2"
79- ),
80- appcast : bool = typer .Option (
81- False , "--appcast" , "-a" , help = "Generate appcast XML snippets"
82- ),
83- publish : bool = typer .Option (
84- False , "--publish" , "-p" , help = "Publish to download/ paths (make live)"
85- ),
86- download : bool = typer .Option (
87- False , "--download" , "-d" , help = "Download artifacts to temp directory"
88- ),
89- os_filter : Optional [str ] = typer .Option (
90- None , "--os" , help = "Filter by OS: macos, windows, linux"
91- ),
92- output : Optional [Path ] = typer .Option (
93- None , "--output" , "-o" , help = "Output directory for downloads (default: temp dir)"
94- ),
9592 show_modules : bool = typer .Option (
9693 False , "--show-modules" , help = "Show available modules and exit"
9794 ),
9895):
9996 """Release automation for BrowserOS
10097
10198 \b
102- Quick Operations (Flags):
103- browseros release --list # List all available versions
104- browseros release --list --version 0.31.0 # List artifacts for version
105- browseros release --version 0.31.0 --appcast # Generate appcast XML
106- browseros release --version 0.31.0 --publish # Publish to download/ paths
107- browseros release --version 0.31.0 --download # Download all artifacts
108- browseros release --version 0.31.0 --download --os macos # Download macOS only
109- browseros release --version 0.31.0 --download --output ./downloads # Custom dir
110-
111- \b
112- GitHub Release (Sub-command):
99+ Commands:
100+ browseros release list # Newest releases per product
101+ browseros release list 0.31.0 # Artifacts for a version
102+ browseros release appcast --version 0.31.0 # Generate appcast XML
103+ browseros release publish --version 0.31.0 # Publish to download/ paths
104+ browseros release download --version 0.31.0 # Download all artifacts
113105 browseros release github create --version 0.31.0
114- browseros release github create --version 0.31.0 --publish
115106
116- \b
117- Show Available Modules:
118- browseros release --show-modules
107+ Use --product to target a specific product (default: browseros).
119108 """
120109 if show_modules :
121110 log_info ("\n 📦 Available Release Modules:" )
@@ -125,49 +114,107 @@ def main(
125114 log_info ("-" * 50 )
126115 return
127116
128- # If subcommand invoked, let it handle things
129- if ctx . invoked_subcommand is not None :
130- return
117+ if ctx . invoked_subcommand is None :
118+ typer . echo ( ctx . get_help ())
119+ raise typer . Exit ( 0 )
131120
132- # Check if any flags specified
133- has_flags = any ([list_artifacts , appcast , publish , download ])
134121
135- if not has_flags :
136- typer .echo (
137- "Error: Specify a flag (--list, --appcast, --publish, --download) or use a sub-command\n "
138- )
139- typer .echo ("Use --help for usage information" )
140- typer .echo ("Use --show-modules to see available modules" )
141- raise typer .Exit (1 )
122+ @app .command ("list" )
123+ def list_releases (
124+ version_arg : Optional [str ] = typer .Argument (
125+ None , metavar = "[VERSION]" , help = "Show artifact details for this version"
126+ ),
127+ version : Optional [str ] = typer .Option (
128+ None , "--version" , "-v" , help = "Show artifact details for this version"
129+ ),
130+ product : Optional [str ] = typer .Option (
131+ None , "--product" , help = f"{ _PRODUCT_HELP } ; default: all products"
132+ ),
133+ limit : int = typer .Option (
134+ DEFAULT_LIST_LIMIT , "--limit" , "-n" , min = 1 , help = "Versions shown per product"
135+ ),
136+ show_all : bool = typer .Option (False , "--all" , help = "Show every version" ),
137+ ):
138+ """List releases from R2 (newest first), or artifacts for one version.
142139
143- # Version is required for all flags except --list
144- requires_version = any ([appcast , publish , download ])
145- if requires_version and not version :
146- log_error ("--version is required for this operation" )
140+ \b
141+ Examples:
142+ browseros release list # Newest 5 per product
143+ browseros release list --all # Every version
144+ browseros release list -n 10 # Newest 10 per product
145+ browseros release list --product browserclaw # One product only
146+ browseros release list 0.31.0 # Artifact details
147+ """
148+ if version_arg and version and version_arg != version :
149+ log_error (f"Conflicting versions: '{ version_arg } ' vs --version '{ version } '" )
147150 raise typer .Exit (1 )
151+ resolved_version = version_arg or version
148152
149- # Create context
150- release_ctx = create_release_context (version or "" )
151-
152- # Execute requested modules
153- if list_artifacts :
154- if version :
155- log_info (f"📋 Listing artifacts for v{ version } " )
156- else :
157- log_info ("📋 Listing all available releases" )
153+ if resolved_version :
154+ release_ctx = create_release_context (resolved_version , product = product )
155+ log_info (f"📋 Listing artifacts for v{ resolved_version } " )
158156 execute_module (release_ctx , ListModule ())
157+ return
158+
159+ products = [_resolve_product (product )] if product else list (PRODUCTS .values ())
160+ release_ctx = create_release_context ("" , product = product )
161+ log_info ("📋 Listing available releases" )
162+ execute_module (
163+ release_ctx ,
164+ ListModule (products = products , limit = None if show_all else limit ),
165+ )
159166
160- if appcast :
161- log_info (f"📝 Generating appcast for v{ version } " )
162- execute_module (release_ctx , AppcastModule ())
163167
164- if publish :
165- log_info (f"🚀 Publishing v{ version } to download/ paths" )
166- execute_module (release_ctx , PublishModule ())
168+ @app .command ("appcast" )
169+ def appcast (
170+ version : str = typer .Option (
171+ ..., "--version" , "-v" , help = "Version to operate on (e.g., 0.31.0)"
172+ ),
173+ product : Optional [str ] = typer .Option (None , "--product" , help = _PRODUCT_HELP ),
174+ ):
175+ """Generate Sparkle appcast XML snippets."""
176+ release_ctx = create_release_context (version , product = product )
177+ log_info (f"📝 Generating appcast for v{ version } " )
178+ execute_module (release_ctx , AppcastModule ())
179+
180+
181+ @app .command ("publish" )
182+ def publish (
183+ version : str = typer .Option (
184+ ..., "--version" , "-v" , help = "Version to operate on (e.g., 0.31.0)"
185+ ),
186+ product : Optional [str ] = typer .Option (None , "--product" , help = _PRODUCT_HELP ),
187+ ):
188+ """Publish versioned artifacts to download/ paths (make live)."""
189+ release_ctx = create_release_context (version , product = product )
190+ log_info (f"🚀 Publishing v{ version } to download/ paths" )
191+ execute_module (release_ctx , PublishModule ())
167192
168- if download :
169- log_info (f"📥 Downloading artifacts for v{ version } " )
170- execute_module (release_ctx , DownloadModule (os_filter = os_filter , output_dir = output ))
193+
194+ @app .command ("download" )
195+ def download (
196+ version : str = typer .Option (
197+ ..., "--version" , "-v" , help = "Version to operate on (e.g., 0.31.0)"
198+ ),
199+ os_filter : Optional [str ] = typer .Option (
200+ None , "--os" , help = "Filter by OS: macos, windows, linux"
201+ ),
202+ output : Optional [Path ] = typer .Option (
203+ None , "--output" , "-o" , help = "Output directory for downloads (default: temp dir)"
204+ ),
205+ product : Optional [str ] = typer .Option (None , "--product" , help = _PRODUCT_HELP ),
206+ ):
207+ """Download release artifacts to a local directory.
208+
209+ \b
210+ Examples:
211+ browseros release download --version 0.31.0
212+ browseros release download --version 0.31.0 --os macos
213+ browseros release download --version 0.31.0 --output ./downloads
214+ """
215+ release_ctx = create_release_context (version , product = product )
216+ log_info (f"📥 Downloading artifacts for v{ version } " )
217+ execute_module (release_ctx , DownloadModule (os_filter = os_filter , output_dir = output ))
171218
172219
173220@github_app .command ("create" )
@@ -190,6 +237,7 @@ def github_create(
190237 publish_to_download : bool = typer .Option (
191238 False , "--publish" , "-p" , help = "Also publish to download/ paths after creating release"
192239 ),
240+ product : Optional [str ] = typer .Option (None , "--product" , help = _PRODUCT_HELP ),
193241):
194242 """Create GitHub release from R2 artifacts
195243
@@ -199,7 +247,7 @@ def github_create(
199247 browseros release github create --version 0.31.0 --publish # Also publish to download/
200248 browseros release github create --version 0.31.0 --no-draft # Create published release
201249 """
202- ctx = create_release_context (version , repo )
250+ ctx = create_release_context (version , repo , product )
203251
204252 log_info (f"🚀 Creating GitHub release for v{ version } " )
205253 module = GithubModule (
0 commit comments